Merge branch 'main' into update-orca-single-sided-pools-and-send_transaction

This commit is contained in:
calintje
2024-12-25 14:20:59 +01:00
59 changed files with 2761 additions and 444 deletions

40
.eslintrc Normal file
View File

@@ -0,0 +1,40 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "prettier"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"prettier/prettier": "error",
"no-constant-condition": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
"no-console": ["warn", { "allow": ["warn", "error"] }],
"curly": ["error", "all"],
"eqeqeq": ["error", "always"],
"no-floating-decimal": "error",
"no-var": "error",
"prefer-const": "error"
},
"env": {
"browser": true,
"node": true,
"es6": true
},
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"overrides": [
{
"files": ["test/**/*", "src/utils/keypair.ts"],
"rules": {
"no-console": "off"
}
}
]
}

4
.prettierignore Normal file
View File

@@ -0,0 +1,4 @@
dist
node_modules
docs
*.md

12
.prettierrc Normal file
View File

@@ -0,0 +1,12 @@
{
"semi": true,
"trailingComma": "all",
"singleQuote": false,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf",
"bracketSameLine": false
}

View File

@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright [2024] [SendAI]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -183,60 +183,14 @@ import { PublicKey } from "@solana/web3.js";
### Fetch Price Data from Pyth
```typescript
import { pythFetchPrice } from "solana-agent-kit";
const price = await pythFetchPrice(
agent,
const price = await agent.pythFetchPrice(
"0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"
);
console.log("Price in BTC/USD:", price);
```
## API Reference
### Core Functions
#### `deploy_token(agent, decimals?, name, uri, symbol, initialSupply?)`
Deploy a new SPL token with optional initial supply. If not specified, decimals default to 9.
#### `deploy_collection(agent, options)`
Create a new NFT collection with customizable metadata and royalties.
#### `mintCollectionNFT(agent, collectionMint, metadata, recipient?)`
Mint a new NFT as part of an existing collection.
#### `transfer(agent, to, amount, mint?)`
Transfer SOL or SPL tokens to a recipient.
#### `trade(agent, outputMint, inputAmount, inputMint?, slippageBps?)`
Swap tokens using Jupiter Exchange integration.
#### `get_balance(agent, token_address)`
Check SOL or token balance for the agent's wallet.
#### `lendAsset(agent, assetMint, amount, apiKey)`
Lend idle assets to earn interest with Lulo.
#### `stakeWithJup(agent, amount)`
Stake SOL with Jupiter to earn rewards.
#### `sendCompressedAirdrop(agent, mintAddress, amount, recipients, priorityFeeInLamports?, shouldLog?)`
Send an SPL token airdrop to many recipients at low cost via ZK Compression.
#### `pythFetchPrice(agent, priceFeedID)`
Fetch price data from Pyth's Hermes service.
## Dependencies
The toolkit relies on several key Solana and Metaplex libraries:

View File

@@ -1 +1 @@
window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAE4XTQUvDMBTA8e+Sc7E4dEhvw9KDzm1ob+IhxNc1LH0vJC+gyL67bBNdXfZ26SX//l4T0tcvxfDBqlIv5DTq2RqQHy2rQnnNvaqUcTpGiOV4/arnwalCbSy+q+p6crctfqV7cg4MW8IavKPPAfDIs8gQOm0glrlwDE9up1l46XfPeEH9qUQygGYKeeiwJr3eAJt+FayBZ4ieMEJWOs0k9CF5yxBa2gDWmnWW/B9J4Dw5mhlDCbkG1tZF8WvP59KQJ4v8d/SLphVnnK2lEas0+C7hXCc0vchny0t0k3B/nNLlynQSa3ZXCA6/TkvkjtAu4X7vsTyJxuL0Zvv2DYU9ByOnAwAA"
window.navigationData = "data:application/octet-stream;base64,H4sIAAAAAAAAE42TwU7DMAyG3yXniooJJtTbRNUDDJigN8QhCi6JltpR40hMaO+OtiFYWebtkos/f7asP69fiuGTVaVeyGvUsw9AvnesChU0W1Up43WMEMtx/cJy71Whlg7fVXU5uVkXv6Zb8h4MO8IagqdVD7jnc8gwdNpALHPgWDy5nmbFT2HzxhPWH0pUDqCZhrxoV5PaG2BjF4Mz8AwxEEbImg4xSXqXgmMYWloC1pp1VvkfkoTz5GlmDCXkGlg7H8Vtj+PSkAeH/Hf6x6YVZxylpRGL1Icu4VwnNFbUZ8lT6ibh9pxSuDKcqF2xPTMjeVSSm00+YfcvWyK/t3GXcHvYWB5AY+P0av32DUTvPWMEBAAA"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

154
docs/media/CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,154 @@
# Contributing to Solana Agent Kit
First off, thank you for considering contributing to Solana Agent Kit! 🎉 Your contributions are **greatly appreciated**.
## Table of Contents
- [Contributing to Solana Agent Kit](#contributing-to-solana-agent-kit)
- [Table of Contents](#table-of-contents)
- [Code of Conduct](#code-of-conduct)
- [How Can I Contribute?](#how-can-i-contribute)
- [Reporting Bugs](#reporting-bugs)
- [Suggesting Enhancements](#suggesting-enhancements)
- [Your First Code Contribution](#your-first-code-contribution)
- [Pull Requests](#pull-requests)
- [Style Guides](#style-guides)
- [Code Style](#code-style)
- [Commit Messages](#commit-messages)
- [Naming Conventions](#naming-conventions)
- [Development Setup](#development-setup)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Building the Project](#building-the-project)
- [Running Tests](#running-tests)
- [Generating Documentation](#generating-documentation)
- [Security](#security)
- [License](#license)
## Code of Conduct
This project adheres to the [Contributor Covenant Code of Conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct/). By participating, you are expected to uphold this code. Please report unacceptable behavior to [aryan@sendai.fun](mailto:aryan@sendai.fun).
## How Can I Contribute?
### Reporting Bugs
**Great**! Opening an issue is the best way to help us improve. Here's how you can report a bug:
1. **Search** the [existing issues](https://github.com/sendaifun/solana-agent-kit/issues) to make sure it hasn't been reported.
2. **Open a new issue** and fill out the template with as much information as possible.
3. **Provide reproduction steps** if applicable.
### Suggesting Enhancements
We welcome your ideas for improving Solana Agent Kit! To suggest an enhancement:
1. **Search** the [existing issues](https://github.com/sendaifun/solana-agent-kit/issues) to see if it's already been suggested.
2. **Open a new issue** and describe your idea in detail.
### Your First Code Contribution
Unsure where to start? You can help out by:
- Fixing simple bugs.
- Improving documentation.
- Adding tests.
Check out the [Good First Issues](https://github.com/sendaifun/solana-agent-kit/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) to get started!
### Pull Requests
1. **Fork** the repository.
2. **Create** a new branch for your feature or bugfix.
```bash
git checkout -b feature/your-feature-name
```
3. **Commit** your changes with clear and descriptive messages.
4. **Push** to your fork.
```bash
git push origin feature/your-feature-name
```
5. **Open a Pull Request** against the `main` branch of this repository.
## Style Guides
### Code Style
- **Language**: TypeScript
- **Formatting**: Follow the existing codebase formatting. Consider using [Prettier](https://prettier.io/) for consistent code formatting.
- **Code Quality**: Adhere to the code quality rules defined in `.eslintrc`. Ensure all checks pass before submitting a PR.
### Commit Messages
Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for your commit messages. Examples:
- `feat: add ability to deploy new SPL token`
- `fix: handle edge case when deploying collection`
- `docs: update README with new usage examples`
### Naming Conventions
- **Variables and Functions**: `camelCase`
- **Classes and Types**: `PascalCase`
- **Constants**: `UPPER_SNAKE_CASE`
## Development Setup
### Prerequisites
- **Node.js**: v23.x or higher
- **npm**: v10.x or higher
- **Git**: Installed and configured
### Installation
1. **Clone** the repository:
```bash
git clone https://github.com/yourusername/solana-agent-kit.git
```
2. **Navigate** to the project directory:
```bash
cd solana-agent-kit
```
3. **Install** dependencies:
```bash
pnpm install
```
### Building the Project
To compile the TypeScript code:
```bash
pnpm run build
```
### Running Tests
To execute the test suite:
```bash
pnpm run test
```
### Generating Documentation
To generate the project documentation using TypeDoc:
```bash
npm run docs
```
The documentation will be available in the `docs/` directory.
## Security
This toolkit handles sensitive information such as private keys and API keys. **Ensure you never commit `.env` files or any sensitive data**. Review the `.gitignore` to confirm that sensitive files are excluded.
For security vulnerabilities, please follow the [responsible disclosure](mailto:aryan@sendai.fun) process.
## License
This project is licensed under the [ISC License](LICENSE).
---

View File

@@ -8,5 +8,6 @@
<a href="interfaces/MintCollectionNFTResponse.html" class="tsd-index-link"><svg class="tsd-kind-icon" viewBox="0 0 24 24"><use href="assets/icons.svg#icon-256"></use></svg><span>Mint<wbr/>CollectionNFTResponse</span></a>
<a href="interfaces/PumpfunLaunchResponse.html" class="tsd-index-link"><svg class="tsd-kind-icon" viewBox="0 0 24 24"><use href="assets/icons.svg#icon-256"></use></svg><span>Pumpfun<wbr/>Launch<wbr/>Response</span></a>
<a href="interfaces/PumpFunTokenOptions.html" class="tsd-index-link"><svg class="tsd-kind-icon" viewBox="0 0 24 24"><use href="assets/icons.svg#icon-256"></use></svg><span>Pump<wbr/>Fun<wbr/>Token<wbr/>Options</span></a>
<a href="interfaces/PythFetchPriceResponse.html" class="tsd-index-link"><svg class="tsd-kind-icon" viewBox="0 0 24 24"><use href="assets/icons.svg#icon-256"></use></svg><span>Pyth<wbr/>Fetch<wbr/>Price<wbr/>Response</span></a>
</div></section><section class="tsd-index-section"><h3 class="tsd-index-heading">Functions</h3><div class="tsd-index-list"><a href="functions/createSolanaTools.html" class="tsd-index-link"><svg class="tsd-kind-icon" viewBox="0 0 24 24"><use href="assets/icons.svg#icon-64"></use></svg><span>create<wbr/>Solana<wbr/>Tools</span></a>
</div></section></section></section></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><h3><svg width="20" height="20" viewBox="0 0 24 24" fill="none"><use href="assets/icons.svg#icon-chevronDown"></use></svg>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-external" name="external"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>External</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="modules.html" class="current"><svg class="tsd-kind-icon" viewBox="0 0 24 24"><use href="assets/icons.svg#icon-1"></use></svg><span>solana-agent-kit</span></a><ul class="tsd-small-nested-navigation" id="tsd-nav-container" data-base="."><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>

View File

@@ -178,4 +178,4 @@ If you encounter any issues while implementing your custom tool:
- Contact the maintainer
- Check existing tools for implementation examples
---
---

View File

@@ -8,15 +8,18 @@
"build": "tsc",
"docs": "typedoc src --out docs",
"test": "ts-node test/index.ts",
"generate": "ts-node src/utils/keypair.ts"
"generate": "ts-node src/utils/keypair.ts",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\""
},
"engines": {
"node": ">=23.1.0",
"pnpm": ">=8.0.0"
},
"keywords": [],
"author": "",
"license": "ISC",
"author": "sendaifun",
"license": "Apache-2.0",
"dependencies": {
"@bonfida/spl-name-service": "^3.0.7",
"@coral-xyz/anchor": "0.29",
@@ -32,6 +35,7 @@
"@metaplex-foundation/umi": "^0.9.2",
"@metaplex-foundation/umi-bundle-defaults": "^0.9.2",
"@metaplex-foundation/umi-web3js-adapters": "^0.9.2",
"@onsol/tldparser": "^0.6.7",
"@orca-so/common-sdk": "0.6.4",
"@orca-so/whirlpools-sdk": "^0.13.12",
"@pythnetwork/price-service-client": "^1.9.0",
@@ -40,6 +44,7 @@
"@solana/web3.js": "^1.95.4",
"bn.js": "^5.2.1",
"bs58": "^6.0.0",
"chai": "^5.1.2",
"decimal.js": "^10.4.3",
"dotenv": "^16.4.5",
"form-data": "^4.0.1",
@@ -49,8 +54,15 @@
},
"devDependencies": {
"@types/bn.js": "^5.1.5",
"@types/chai": "^5.0.1",
"@types/node": "^22.9.0",
"ts-node": "^10.9.2",
"typescript": "^5.7.2"
"typescript": "^5.7.2",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"prettier": "^3.2.5"
}
}

1487
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -25,12 +25,21 @@ import {
stakeWithJup,
sendCompressedAirdrop,
createOrcaSingleSidedWhirlpool,
FEE_TIERS,
fetchPrice,
pythFetchPrice,
FEE_TIERS,
getAllDomainsTLDs,
getAllRegisteredAllDomains,
getOwnedDomainsForTLD,
getMainAllDomainsDomain,
getOwnedAllDomains,
resolveAllDomains,
create_gibwork_task,
} from "../tools";
import {
CollectionDeployment,
CollectionOptions,
GibworkCreateTaskReponse,
JupiterTokenData,
MintCollectionNFTResponse,
PumpfunLaunchResponse,
@@ -40,7 +49,7 @@ import { BN } from "@coral-xyz/anchor";
/**
* Main class for interacting with Solana blockchain
* Provides a unified interface for token operations, NFT management, and trading
* Provides a unified interface for token operations, NFT management, trading and more
*
* @class SolanaAgentKit
* @property {Connection} connection - Solana RPC connection
@@ -146,6 +155,10 @@ export class SolanaAgentKit {
return getTokenDataByTicker(ticker);
}
async fetchTokenPrice(mint: string) {
return fetchPrice(new PublicKey(mint));
}
async launchPumpFunToken(
tokenName: string,
tokenTicker: string,
@@ -193,7 +206,7 @@ export class SolanaAgentKit {
initialPrice: Decimal,
maxPrice: Decimal,
feeTier: keyof typeof FEE_TIERS,
): Promise<string> {
) {
return createOrcaSingleSidedWhirlpool(
this,
depositTokenAmount,
@@ -205,6 +218,31 @@ export class SolanaAgentKit {
);
}
async resolveAllDomains(domain: string): Promise<PublicKey | undefined> {
return resolveAllDomains(this, domain);
}
async getOwnedAllDomains(owner: PublicKey): Promise<string[]> {
return getOwnedAllDomains(this, owner);
}
async getOwnedDomainsForTLD(tld: string): Promise<string[]> {
return getOwnedDomainsForTLD(this, tld);
}
// eslint-disable-next-line @typescript-eslint/ban-types
async getAllDomainsTLDs(): Promise<String[]> {
return getAllDomainsTLDs(this);
}
async getAllRegisteredAllDomains(): Promise<string[]> {
return getAllRegisteredAllDomains(this);
}
async getMainAllDomainsDomain(owner: PublicKey): Promise<string | null> {
return getMainAllDomainsDomain(this, owner);
}
async raydiumCreateAmmV4(
marketId: PublicKey,
baseAmount: BN,
@@ -254,6 +292,7 @@ export class SolanaAgentKit {
configId,
mintAAmount,
mintBAmount,
startTime,
);
}
@@ -271,10 +310,31 @@ export class SolanaAgentKit {
lotSize,
tickSize,
)
);
}
async pythFetchPrice(priceFeedID: string) {
return pythFetchPrice(this, priceFeedID);
async pythFetchPrice(priceFeedID: string): Promise<string> {
return pythFetchPrice(priceFeedID);
}
async createGibworkTask(
title: string,
content: string,
requirements: string,
tags: string[],
tokenMintAddress: string,
tokenAmount: number,
payer?: string,
): Promise<GibworkCreateTaskReponse> {
return create_gibwork_task(
this,
title,
content,
requirements,
tags,
new PublicKey(tokenMintAddress),
tokenAmount,
payer ? new PublicKey(payer) : undefined,
);
}
}

View File

@@ -1,7 +1,7 @@
import { SolanaAgentKit } from './agent'; // Move the SolanaAgentKit class to src/agent.ts
import { createSolanaTools } from './langchain';
import { SolanaAgentKit } from "./agent";
import { createSolanaTools } from "./langchain";
export { SolanaAgentKit, createSolanaTools };
// Optional: Export types that users might need
export * from './types';
export * from "./types";

View File

@@ -1,9 +1,12 @@
import { PublicKey } from "@solana/web3.js";
import Decimal from "decimal.js";
import { Tool } from "langchain/tools";
import { PythFetchPriceResponse, SolanaAgentKit } from "../index";
import {
GibworkCreateTaskReponse,
PythFetchPriceResponse,
SolanaAgentKit,
} from "../index";
import { create_image } from "../tools/create_image";
import { fetchPrice } from "../tools/fetch_price";
import { BN } from "@coral-xyz/anchor";
import { FEE_TIERS } from "../tools";
import { toJSON } from "../utils/toJSON";
@@ -67,7 +70,7 @@ export class SolanaTransferTool extends Tool {
const tx = await this.solanaKit.transfer(
recipient,
parsedInput.amount,
mintAddress
mintAddress,
);
return JSON.stringify({
@@ -94,7 +97,7 @@ export class SolanaDeployTokenTool extends Tool {
Inputs (input is a JSON string):
name: string, eg "My Token" (required)
uri: string, eg "https://example.com/token.json" (required)
uri: string, eg "https://example.com/token.json" (required)
symbol: string, eg "MTK" (required)
decimals?: number, eg 9 (optional, defaults to 9)
initialSupply?: number, eg 1000000 (optional)`;
@@ -112,7 +115,7 @@ export class SolanaDeployTokenTool extends Tool {
parsedInput.uri,
parsedInput.symbol,
parsedInput.decimals,
parsedInput.initialSupply
parsedInput.initialSupply,
);
return JSON.stringify({
@@ -192,7 +195,7 @@ export class SolanaMintNFTTool extends Tool {
},
parsedInput.recipient
? new PublicKey(parsedInput.recipient)
: this.solanaKit.wallet_address
: this.solanaKit.wallet_address,
);
return JSON.stringify({
@@ -240,7 +243,7 @@ export class SolanaTradeTool extends Tool {
parsedInput.inputMint
? new PublicKey(parsedInput.inputMint)
: new PublicKey("So11111111111111111111111111111111111111112"),
parsedInput.slippageBps
parsedInput.slippageBps,
);
return JSON.stringify({
@@ -320,7 +323,7 @@ export class SolanaRegisterDomainTool extends Tool {
const tx = await this.solanaKit.registerDomain(
parsedInput.name,
parsedInput.spaceKB || 1
parsedInput.spaceKB || 1,
);
return JSON.stringify({
@@ -342,10 +345,12 @@ export class SolanaRegisterDomainTool extends Tool {
export class SolanaResolveDomainTool extends Tool {
name = "solana_resolve_domain";
description = `Resolve a .sol domain to a Solana PublicKey.
description = `Resolve ONLY .sol domain names to a Solana PublicKey.
This tool is exclusively for .sol domains.
DO NOT use this for other domain types like .blink, .bonk, etc.
Inputs:
domain: string, eg "pumpfun.sol" or "pumpfun"(required)
domain: string, eg "pumpfun.sol" (required)
`;
constructor(private solanaKit: SolanaAgentKit) {
@@ -460,7 +465,7 @@ export class SolanaPumpfunTokenLaunchTool extends Tool {
try {
// Parse and normalize input
input = input.trim();
let parsedInput = JSON.parse(input);
const parsedInput = JSON.parse(input);
this.validateInput(parsedInput);
@@ -475,7 +480,7 @@ export class SolanaPumpfunTokenLaunchTool extends Tool {
telegram: parsedInput.telegram,
website: parsedInput.website,
initialLiquiditySOL: parsedInput.initialLiquiditySOL,
}
},
);
return JSON.stringify({
@@ -542,7 +547,7 @@ export class SolanaLendAssetTool extends Tool {
async _call(input: string): Promise<string> {
try {
let amount = JSON.parse(input).amount || input;
const amount = JSON.parse(input).amount || input;
const tx = await this.solanaKit.lendAssets(amount);
@@ -619,7 +624,7 @@ export class SolanaStakeTool extends Tool {
export class SolanaFetchPriceTool extends Tool {
name = "solana_fetch_price";
description = `Fetch the price of a given token in USDC.
Inputs:
- tokenId: string, the mint address of the token, e.g., "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN"`;
@@ -629,7 +634,7 @@ export class SolanaFetchPriceTool extends Tool {
async _call(input: string): Promise<string> {
try {
const price = await fetchPrice(this.solanaKit, input.trim());
const price = await this.solanaKit.fetchTokenPrice(input.trim());
return JSON.stringify({
status: "success",
tokenId: input.trim(),
@@ -708,7 +713,7 @@ export class SolanaTokenDataByTickerTool extends Tool {
export class SolanaCompressedAirdropTool extends Tool {
name = "solana_compressed_airdrop";
description = `Airdrop SPL tokens with ZK Compression (also called as airdropping tokens)
Inputs (input is a JSON string):
mintAddress: string, the mint address of the token, e.g., "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN" (required)
amount: number, the amount of tokens to airdrop per recipient, e.g., 42 (required)
@@ -731,7 +736,7 @@ export class SolanaCompressedAirdropTool extends Tool {
parsedInput.decimals,
parsedInput.recipients,
parsedInput.priorityFeeInLamports || 30_000,
parsedInput.shouldLog || false
parsedInput.shouldLog || false,
);
return JSON.stringify({
@@ -776,7 +781,11 @@ export class SolanaCreateSingleSidedWhirlpoolTool extends Tool {
const feeTier = inputFormat.feeTier;
if (!feeTier || !(feeTier in FEE_TIERS)) {
throw new Error(`Invalid feeTier. Available options: ${Object.keys(FEE_TIERS).join(", ")}`);
throw new Error(
`Invalid feeTier. Available options: ${Object.keys(FEE_TIERS).join(
", ",
)}`,
);
}
const txId = await this.solanaKit.createOrcaSingleSidedWhirlpool(
@@ -820,7 +829,7 @@ export class SolanaRaydiumCreateAmmV4 extends Tool {
async _call(input: string): Promise<string> {
try {
let inputFormat = JSON.parse(input)
const inputFormat = JSON.parse(input);
const tx = await this.solanaKit.raydiumCreateAmmV4(
new PublicKey(inputFormat.marketId),
@@ -862,7 +871,7 @@ export class SolanaRaydiumCreateClmm extends Tool {
async _call(input: string): Promise<string> {
try {
let inputFormat = JSON.parse(input)
const inputFormat = JSON.parse(input);
const tx = await this.solanaKit.raydiumCreateClmm(
new PublicKey(inputFormat.mint1),
@@ -891,7 +900,7 @@ export class SolanaRaydiumCreateClmm extends Tool {
export class SolanaRaydiumCreateCpmm extends Tool {
name = "raydium_create_cpmm";
description = `Raydium's newest CPMM, does not require marketID, supports Token 2022 standard
description = `Raydium's newest CPMM, does not require marketID, supports Token 2022 standard
Inputs (input is a json string):
mint1: string (required)
@@ -908,7 +917,7 @@ export class SolanaRaydiumCreateCpmm extends Tool {
async _call(input: string): Promise<string> {
try {
let inputFormat = JSON.parse(input)
const inputFormat = JSON.parse(input);
const tx = await this.solanaKit.raydiumCreateCpmm(
new PublicKey(inputFormat.mint1),
@@ -939,7 +948,7 @@ export class SolanaRaydiumCreateCpmm extends Tool {
export class SolanaOpenbookCreateMarket extends Tool {
name = "solana_openbook_create_market";
description = `Openbook marketId, required for ammv4
description = `Openbook marketId, required for ammv4
Inputs (input is a json string):
baseMint: string (required)
@@ -954,7 +963,7 @@ export class SolanaOpenbookCreateMarket extends Tool {
async _call(input: string): Promise<string> {
try {
let inputFormat = JSON.parse(input)
const inputFormat = JSON.parse(input);
const tx = await this.solanaKit.openbookCreateMarket(
new PublicKey(inputFormat.baseMint),
@@ -993,14 +1002,14 @@ export class SolanaPythFetchPrice extends Tool {
async _call(input: string): Promise<string> {
try {
const price = await this.solanaKit.pythFetchPrice(input);
let response: PythFetchPriceResponse = {
const response: PythFetchPriceResponse = {
status: "success",
priceFeedID: input,
price: price,
};
return JSON.stringify(response);
} catch (error: any) {
let response: PythFetchPriceResponse = {
const response: PythFetchPriceResponse = {
status: "error",
priceFeedID: input,
message: error.message,
@@ -1011,6 +1020,215 @@ export class SolanaPythFetchPrice extends Tool {
}
}
export class SolanaResolveAllDomainsTool extends Tool {
name = "solana_resolve_all_domains";
description = `Resolve domain names to a public key for ALL domain types EXCEPT .sol domains.
Use this for domains like .blink, .bonk, etc.
DO NOT use this for .sol domains (use solana_resolve_domain instead).
Input:
domain: string, eg "mydomain.blink" or "mydomain.bonk" (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
async _call(input: string): Promise<string> {
try {
const owner = await this.solanaKit.resolveAllDomains(input);
if (!owner) {
return JSON.stringify({
status: "error",
message: "Domain not found",
code: "DOMAIN_NOT_FOUND",
});
}
return JSON.stringify({
status: "success",
message: "Domain resolved successfully",
owner: owner?.toString(),
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "DOMAIN_RESOLUTION_ERROR",
});
}
}
}
export class SolanaGetOwnedDomains extends Tool {
name = "solana_get_owned_domains";
description = `Get all domains owned by a specific wallet address.
Inputs:
owner: string, eg "4Be9CvxqHW6BYiRAxW9Q3xu1ycTMWaL5z8NX4HR3ha7t" (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
async _call(input: string): Promise<string> {
try {
const ownerPubkey = new PublicKey(input.trim());
const domains = await this.solanaKit.getOwnedAllDomains(ownerPubkey);
return JSON.stringify({
status: "success",
message: "Owned domains fetched successfully",
domains: domains,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "FETCH_OWNED_DOMAINS_ERROR",
});
}
}
}
export class SolanaGetOwnedTldDomains extends Tool {
name = "solana_get_owned_tld_domains";
description = `Get all domains owned by the agent's wallet for a specific TLD.
Inputs:
tld: string, eg "bonk" (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
async _call(input: string): Promise<string> {
try {
const domains = await this.solanaKit.getOwnedDomainsForTLD(input);
return JSON.stringify({
status: "success",
message: "TLD domains fetched successfully",
domains: domains,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "FETCH_TLD_DOMAINS_ERROR",
});
}
}
}
export class SolanaGetAllTlds extends Tool {
name = "solana_get_all_tlds";
description = `Get all active top-level domains (TLDs) in the AllDomains Name Service`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
async _call(): Promise<string> {
try {
const tlds = await this.solanaKit.getAllDomainsTLDs();
return JSON.stringify({
status: "success",
message: "TLDs fetched successfully",
tlds: tlds,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "FETCH_TLDS_ERROR",
});
}
}
}
export class SolanaGetMainDomain extends Tool {
name = "solana_get_main_domain";
description = `Get the main/favorite domain for a given wallet address.
Inputs:
owner: string, eg "4Be9CvxqHW6BYiRAxW9Q3xu1ycTMWaL5z8NX4HR3ha7t" (required)`;
constructor(private solanaKit: SolanaAgentKit) {
super();
}
async _call(input: string): Promise<string> {
try {
const ownerPubkey = new PublicKey(input.trim());
const mainDomain =
await this.solanaKit.getMainAllDomainsDomain(ownerPubkey);
return JSON.stringify({
status: "success",
message: "Main domain fetched successfully",
domain: mainDomain,
});
} catch (error: any) {
return JSON.stringify({
status: "error",
message: error.message,
code: error.code || "FETCH_MAIN_DOMAIN_ERROR",
});
}
}
}
export class SolanaCreateGibworkTask extends Tool {
name = "create_gibwork_task";
description = `Create a task on Gibwork.
Inputs (input is a JSON string):
title: string, title of the task (required)
content: string, description of the task (required)
requirements: string, requirements to complete the task (required)
tags: string[], list of tags associated with the task (required)
payer: string, payer address (optional, defaults to agent wallet)
tokenMintAddress: string, the mint address of the token, e.g., "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN" (required)
amount: number, payment amount (required)
`;
constructor(private solanaSdk: SolanaAgentKit) {
super();
}
protected async _call(input: string): Promise<string> {
try {
const parsedInput = JSON.parse(input);
const taskData = await this.solanaSdk.createGibworkTask(
parsedInput.title,
parsedInput.content,
parsedInput.requirements,
parsedInput.tags,
parsedInput.tokenMintAddress,
parsedInput.amount,
parsedInput.payer,
);
const response: GibworkCreateTaskReponse = {
status: "success",
taskId: taskData.taskId,
signature: taskData.signature,
};
return JSON.stringify(response);
} catch (err: any) {
return JSON.stringify({
status: "error",
message: err.message,
code: err.code || "CREATE_TASK_ERROR",
});
}
}
}
export function createSolanaTools(solanaKit: SolanaAgentKit) {
return [
new SolanaBalanceTool(solanaKit),
@@ -1028,7 +1246,6 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
new SolanaTPSCalculatorTool(solanaKit),
new SolanaStakeTool(solanaKit),
new SolanaFetchPriceTool(solanaKit),
new SolanaResolveDomainTool(solanaKit),
new SolanaGetDomainTool(solanaKit),
new SolanaTokenDataTool(solanaKit),
new SolanaTokenDataByTickerTool(solanaKit),
@@ -1039,6 +1256,12 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
new SolanaOpenbookCreateMarket(solanaKit),
new SolanaCreateSingleSidedWhirlpoolTool(solanaKit),
new SolanaPythFetchPrice(solanaKit),
new SolanaResolveDomainTool(solanaKit),
new SolanaGetOwnedDomains(solanaKit),
new SolanaGetOwnedTldDomains(solanaKit),
new SolanaGetAllTlds(solanaKit),
new SolanaGetMainDomain(solanaKit),
new SolanaResolveAllDomainsTool(solanaKit),
new SolanaCreateGibworkTask(solanaKit),
];
}

View File

@@ -0,0 +1,81 @@
import { VersionedTransaction } from "@solana/web3.js";
import { PublicKey } from "@solana/web3.js";
import { GibworkCreateTaskReponse, SolanaAgentKit } from "../index";
/**
* Create an new task on Gibwork
* @param agent SolanaAgentKit instance
* @param title Title of the task
* @param content Description of the task
* @param requirements Requirements to complete the task
* @param tags List of tags associated with the task
* @param payer Payer address for the task (default: agent wallet address)
* @param tokenMintAddress Token mint address for payment
* @param tokenAmount Payment amount for the task
* @returns Object containing task creation transaction and generated taskId
*/
export async function create_gibwork_task(
agent: SolanaAgentKit,
title: string,
content: string,
requirements: string,
tags: string[],
tokenMintAddress: PublicKey,
tokenAmount: number,
payer?: PublicKey,
): Promise<GibworkCreateTaskReponse> {
try {
const apiResponse = await fetch(
"https://api2.gib.work/tasks/public/transaction",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: title,
content: content,
requirements: requirements,
tags: tags,
payer: payer?.toBase58() || agent.wallet.publicKey.toBase58(),
token: {
mintAddress: tokenMintAddress.toBase58(),
amount: tokenAmount,
},
}),
},
);
const responseData = await apiResponse.json();
if (!responseData.taskId && !responseData.serializedTransaction) {
throw new Error(`${responseData.message}`);
}
const serializedTransaction = Buffer.from(
responseData.serializedTransaction,
"base64",
);
const tx = VersionedTransaction.deserialize(serializedTransaction);
tx.sign([agent.wallet]);
const signature = await agent.connection.sendTransaction(tx, {
preflightCommitment: "confirmed",
maxRetries: 3,
});
const latestBlockhash = await agent.connection.getLatestBlockhash();
await agent.connection.confirmTransaction({
signature,
blockhash: latestBlockhash.blockhash,
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
});
return {
status: "success",
taskId: responseData.taskId,
signature: signature,
};
} catch (err: any) {
throw new Error(`${err.message}`);
}
}

View File

@@ -39,7 +39,7 @@ import { sendTx } from "../utils/send_tx";
* @remarks
* Fee tiers determine the percentage of fees collected on swaps, while tick spacing affects
* the granularity of price ranges for liquidity positions.
*
*
* For more details, refer to:
* - [Whirlpool Fees](https://orca-so.github.io/whirlpools/Architecture%20Overview/Whirlpool%20Fees)
* - [Whirlpool Parameters](https://orca-so.github.io/whirlpools/Architecture%20Overview/Whirlpool%20Parameters)
@@ -53,17 +53,17 @@ export const FEE_TIERS = {
0.04: 4,
0.05: 8,
0.16: 16,
0.30: 64,
0.3: 64,
0.65: 96,
1.00: 128,
2.00: 256,
1.0: 128,
2.0: 256,
} as const;
/**
* # Creates a single-sided Whirlpool.
*
* This function initializes a new Whirlpool (liquidity pool) on Orca and seeds it with liquidity from a single token.
*
*
* ## Example Usage:
* You created a new token called SHARK, and you want to set the initial price to 0.001 USDC.
* You set `depositTokenMint` to SHARK's mint address and `otherTokenMint` to USDC's mint address.
@@ -71,7 +71,7 @@ export const FEE_TIERS = {
* 1. Increase the amount of tokens you deposit
* 2. Set the initial price very low
* 3. Set the maximum price closer to the initial price
*
*
* ### Note for experts:
* The Wrhirlpool program initializes the Whirlpool with the in a specific order. This might not be
* the order you expect, so the function checks the order and adjusts the inverts the prices. This means that
@@ -85,13 +85,13 @@ export const FEE_TIERS = {
* @param initialPrice - The initial price of the deposit token in terms of the other token.
* @param maxPrice - The maximum price at which liquidity is added.
* @param feeTier - The fee tier percentage for the pool, determining tick spacing and fee collection rates.
*
*
* @returns A promise that resolves to a transaction ID (`string`) of the transaction creating the pool.
*
*
* @throws Will throw an error if:
* - Mint accounts for the tokens cannot be fetched.
* - Prices are out of bounds.
*
*
* @remarks
* This function is designed for single-sided deposits where users only contribute one type of token,
* and the function manages mint order and necessary calculations.
@@ -102,7 +102,7 @@ export const FEE_TIERS = {
* import { PublicKey } from "@solana/web3.js";
* import { BN } from "@coral-xyz/anchor";
* import Decimal from "decimal.js";
*
*
* const agent = new SolanaAgentKit(wallet, connection);
* const depositAmount = new BN(1_000_000_000_000); // 1 million SHARK if SHARK has 6 decimals
* const depositTokenMint = new PublicKey("DEPOSTI_TOKEN_ADDRESS");
@@ -141,13 +141,19 @@ export async function createOrcaSingleSidedWhirlpool(
throw new Error('Unsupported network');
}
const wallet = new Wallet(agent.wallet);
const ctx = WhirlpoolContext.from(agent.connection, wallet, ORCA_WHIRLPOOL_PROGRAM_ID);
const ctx = WhirlpoolContext.from(
agent.connection,
wallet,
ORCA_WHIRLPOOL_PROGRAM_ID,
);
const fetcher = ctx.fetcher;
const correctTokenOrder = PoolUtil.orderMints(otherTokenMint, depositTokenMint).map(
(addr) => addr.toString(),
);
const isCorrectMintOrder = correctTokenOrder[0] === depositTokenMint.toString();
const correctTokenOrder = PoolUtil.orderMints(
otherTokenMint,
depositTokenMint,
).map((addr) => addr.toString());
const isCorrectMintOrder =
correctTokenOrder[0] === depositTokenMint.toString();
let mintA, mintB;
if (isCorrectMintOrder) {
[mintA, mintB] = [depositTokenMint, otherTokenMint];
@@ -158,10 +164,19 @@ export async function createOrcaSingleSidedWhirlpool(
}
const mintAAccount = await fetcher.getMintInfo(mintA);
const mintBAccount = await fetcher.getMintInfo(mintB);
if (mintAAccount === null || mintBAccount === null) throw Error('Mint account not found');
if (mintAAccount === null || mintBAccount === null) {
throw Error("Mint account not found");
}
const tickSpacing = FEE_TIERS[feeTier];
const tickIndex = PriceMath.priceToTickIndex(initialPrice, mintAAccount.decimals, mintBAccount.decimals);
const initialTick = TickUtil.getInitializableTickIndex(tickIndex, tickSpacing);
const tickIndex = PriceMath.priceToTickIndex(
initialPrice,
mintAAccount.decimals,
mintBAccount.decimals,
);
const initialTick = TickUtil.getInitializableTickIndex(
tickIndex,
tickSpacing,
);
const tokenExtensionCtx: TokenExtensionContextForPool = {
...NO_TOKEN_EXTENSION_CONTEXT,
@@ -203,17 +218,17 @@ export async function createOrcaSingleSidedWhirlpool(
tokenVaultBKeypair,
feeTierKey,
tickSpacing: tickSpacing,
funder: wallet.publicKey
funder: wallet.publicKey,
};
const initPoolIx = !TokenExtensionUtil.isV2IxRequiredPool(tokenExtensionCtx)
? WhirlpoolIx.initializePoolIx(ctx.program, baseParamsPool)
: WhirlpoolIx.initializePoolV2Ix(ctx.program, {
...baseParamsPool,
tokenProgramA: tokenExtensionCtx.tokenMintWithProgramA.tokenProgram,
tokenProgramB: tokenExtensionCtx.tokenMintWithProgramB.tokenProgram,
tokenBadgeA,
tokenBadgeB,
});
...baseParamsPool,
tokenProgramA: tokenExtensionCtx.tokenMintWithProgramA.tokenProgram,
tokenProgramB: tokenExtensionCtx.tokenMintWithProgramB.tokenProgram,
tokenBadgeA,
tokenBadgeB,
});
const initialTickArrayStartTick = TickUtil.getStartTickIndex(
initialTick,
tickSpacing,
@@ -242,14 +257,33 @@ export async function createOrcaSingleSidedWhirlpool(
let tickLowerIndex, tickUpperIndex;
if (isCorrectMintOrder) {
tickLowerIndex = initialTick;
tickUpperIndex = PriceMath.priceToTickIndex(maxPrice, mintAAccount.decimals, mintBAccount.decimals);
tickUpperIndex = PriceMath.priceToTickIndex(
maxPrice,
mintAAccount.decimals,
mintBAccount.decimals,
);
} else {
tickLowerIndex = PriceMath.priceToTickIndex(maxPrice, mintAAccount.decimals, mintBAccount.decimals);
tickLowerIndex = PriceMath.priceToTickIndex(
maxPrice,
mintAAccount.decimals,
mintBAccount.decimals,
);
tickUpperIndex = initialTick;
}
const tickLowerInitializableIndex = TickUtil.getInitializableTickIndex(tickLowerIndex, tickSpacing);
const tickUpperInitializableIndex = TickUtil.getInitializableTickIndex(tickUpperIndex, tickSpacing);
if (!TickUtil.checkTickInBounds(tickLowerInitializableIndex) || !TickUtil.checkTickInBounds(tickUpperInitializableIndex)) throw Error('Prices out of bounds');
const tickLowerInitializableIndex = TickUtil.getInitializableTickIndex(
tickLowerIndex,
tickSpacing,
);
const tickUpperInitializableIndex = TickUtil.getInitializableTickIndex(
tickUpperIndex,
tickSpacing,
);
if (
!TickUtil.checkTickInBounds(tickLowerInitializableIndex) ||
!TickUtil.checkTickInBounds(tickUpperInitializableIndex)
) {
throw Error("Prices out of bounds");
}
const increasLiquidityQuoteParam: IncreaseLiquidityQuoteParam = {
inputTokenAmount: new BN(depositTokenAmount),
inputTokenMint: depositTokenMint,
@@ -260,11 +294,11 @@ export async function createOrcaSingleSidedWhirlpool(
tickLowerIndex: tickLowerInitializableIndex,
tickUpperIndex: tickUpperInitializableIndex,
tokenExtensionCtx: tokenExtensionCtx,
slippageTolerance: Percentage.fromFraction(0, 100)
}
slippageTolerance: Percentage.fromFraction(0, 100),
};
const liquidityInput = increaseLiquidityQuoteByInputTokenWithParams(
increasLiquidityQuoteParam
)
increasLiquidityQuoteParam,
);
const { liquidityAmount: liquidity, tokenMaxA, tokenMaxB } = liquidityInput;
const positionMintKeypair = Keypair.generate();
@@ -292,7 +326,7 @@ export async function createOrcaSingleSidedWhirlpool(
...params,
positionMint: positionMintPubkey,
withTokenMetadataExtension: true,
})
});
txBuilder.addInstruction(positionIx);
txBuilder.addSigner(positionMintKeypair);
@@ -372,17 +406,15 @@ export async function createOrcaSingleSidedWhirlpool(
tickArrayUpper: tickArrayUpperPda.publicKey,
};
const liquidityIx = !TokenExtensionUtil.isV2IxRequiredPool(
tokenExtensionCtx,
)
const liquidityIx = !TokenExtensionUtil.isV2IxRequiredPool(tokenExtensionCtx)
? increaseLiquidityIx(ctx.program, baseParamsLiquidity)
: increaseLiquidityV2Ix(ctx.program, {
...baseParamsLiquidity,
tokenMintA: mintA,
tokenMintB: mintB,
tokenProgramA: tokenExtensionCtx.tokenMintWithProgramA.tokenProgram,
tokenProgramB: tokenExtensionCtx.tokenMintWithProgramB.tokenProgram,
});
...baseParamsLiquidity,
tokenMintA: mintA,
tokenMintB: mintB,
tokenProgramA: tokenExtensionCtx.tokenMintWithProgramA.tokenProgram,
tokenProgramB: tokenExtensionCtx.tokenMintWithProgramB.tokenProgram,
});
txBuilder.addInstruction(liquidityIx);
const txPayload = await txBuilder.build();

View File

@@ -1,8 +1,19 @@
import { SolanaAgentKit } from "../index";
import { generateSigner, keypairIdentity, publicKey } from "@metaplex-foundation/umi";
import { createCollection, mplCore, ruleSet } from "@metaplex-foundation/mpl-core";
import {
generateSigner,
keypairIdentity,
publicKey,
} from "@metaplex-foundation/umi";
import {
createCollection,
mplCore,
ruleSet,
} from "@metaplex-foundation/mpl-core";
import { CollectionOptions, CollectionDeployment } from "../types";
import { fromWeb3JsKeypair, toWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters";
import {
fromWeb3JsKeypair,
toWeb3JsPublicKey,
} from "@metaplex-foundation/umi-web3js-adapters";
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
/**
@@ -28,11 +39,11 @@ export async function deploy_collection(
address: publicKey(creator.address),
percentage: creator.percentage,
})) || [
{
address: publicKey(agent.wallet_address.toString()),
percentage: 100,
},
];
{
address: publicKey(agent.wallet_address.toString()),
percentage: 100,
},
];
// Create collection
const tx = await createCollection(umi, {

View File

@@ -2,9 +2,17 @@ import { SolanaAgentKit } from "../index";
import { PublicKey } from "@solana/web3.js";
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import { generateSigner, keypairIdentity } from "@metaplex-foundation/umi";
import { createFungible, mintV1, TokenStandard } from "@metaplex-foundation/mpl-token-metadata";
import { fromWeb3JsKeypair, fromWeb3JsPublicKey, toWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters";
import {mplToolbox} from "@metaplex-foundation/mpl-toolbox"
import {
createFungible,
mintV1,
TokenStandard,
} from "@metaplex-foundation/mpl-token-metadata";
import {
fromWeb3JsKeypair,
fromWeb3JsPublicKey,
toWeb3JsPublicKey,
} from "@metaplex-foundation/umi-web3js-adapters";
import { mplToolbox } from "@metaplex-foundation/mpl-toolbox";
/**
* Deploy a new SPL token
@@ -22,11 +30,11 @@ export async function deploy_token(
uri: string,
symbol: string,
decimals: number = 9,
initialSupply?: number
initialSupply?: number,
): Promise<{ mint: PublicKey }> {
try {
// Create UMI instance from agent
const umi = createUmi(agent.connection.rpcEndpoint).use(mplToolbox())
const umi = createUmi(agent.connection.rpcEndpoint).use(mplToolbox());
umi.use(keypairIdentity(fromWeb3JsKeypair(agent.wallet)));
// Create new token mint
@@ -52,11 +60,11 @@ export async function deploy_token(
tokenStandard: TokenStandard.Fungible,
tokenOwner: fromWeb3JsPublicKey(agent.wallet_address),
amount: initialSupply,
})
}),
);
}
builder.sendAndConfirm(umi, { confirm: { commitment: 'finalized' } });
builder.sendAndConfirm(umi, { confirm: { commitment: "finalized" } });
return {
mint: toWeb3JsPublicKey(mint.publicKey),

View File

@@ -1,20 +1,13 @@
import { SolanaAgentKit } from "../index";
import { Tool } from "langchain/tools";
import { PublicKey } from "@solana/web3.js";
/**
* Fetch the price of a given token in USDC using Jupiter API
* @param agent SolanaAgentKit instance
* Fetch the price of a given token quoted in USDC using Jupiter API
* @param tokenId The token mint address
* @returns The price of the token in USDC
* @returns The price of the token quoted in USDC
*/
export async function fetchPrice(
agent: SolanaAgentKit,
tokenId: string
): Promise<string> {
export async function fetchPrice(tokenId: PublicKey): Promise<string> {
try {
const response = await fetch(
`https://api.jup.ag/price/v2?ids=${tokenId}`
);
const response = await fetch(`https://api.jup.ag/price/v2?ids=${tokenId}`);
if (!response.ok) {
throw new Error(`Failed to fetch price: ${response.statusText}`);
@@ -22,7 +15,7 @@ export async function fetchPrice(
const data = await response.json();
const price = data.data[tokenId]?.price;
const price = data.data[tokenId.toBase58()]?.price;
if (!price) {
throw new Error("Price data not available for the given token.");
@@ -32,4 +25,4 @@ export async function fetchPrice(
} catch (error: any) {
throw new Error(`Price fetch failed: ${error.message}`);
}
}
}

View File

@@ -0,0 +1,19 @@
import { SolanaAgentKit } from "../index";
import { getAllTld } from "@onsol/tldparser";
/**
* Get all active top-level domains (TLDs) in the AllDomains Name Service
* @param agent SolanaAgentKit instance
* @returns Array of active TLD strings
*/
export async function getAllDomainsTLDs(
agent: SolanaAgentKit,
// eslint-disable-next-line @typescript-eslint/ban-types
): Promise<String[]> {
try {
const tlds = await getAllTld(agent.connection);
return tlds.map((tld) => tld.tld);
} catch (error: any) {
throw new Error(`Failed to fetch TLDs: ${error.message}`);
}
}

View File

@@ -0,0 +1,36 @@
import { getAllDomains } from "@bonfida/spl-name-service";
import { SolanaAgentKit } from "../agent";
import { PublicKey } from "@solana/web3.js";
import { getAllDomainsTLDs } from "./get_all_domains_tlds";
/**
* Get all registered domains across all TLDs
* @param agent SolanaAgentKit instance
* @returns Array of all registered domain names with their TLDs
*/
export async function getAllRegisteredAllDomains(
agent: SolanaAgentKit,
): Promise<string[]> {
try {
// First get all TLDs
const tlds = await getAllDomainsTLDs(agent);
const allDomains: string[] = [];
// For each TLD, fetch all registered domains
for (const tld of tlds) {
const domains = await getAllDomains(
agent.connection,
new PublicKey("namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX"),
);
// Add domains with TLD suffix
domains.forEach((domain) => {
allDomains.push(`${domain}.${tld}`);
});
}
return allDomains;
} catch (error: any) {
throw new Error(`Failed to fetch all registered domains: ${error.message}`);
}
}

View File

@@ -11,11 +11,12 @@ export async function get_balance(
agent: SolanaAgentKit,
token_address?: PublicKey,
): Promise<number> {
if (!token_address)
if (!token_address) {
return (
(await agent.connection.getBalance(agent.wallet_address)) /
LAMPORTS_PER_SOL
);
}
const token_account =
await agent.connection.getTokenAccountBalance(token_address);

View File

@@ -0,0 +1,21 @@
import { getFavoriteDomain as _getFavoriteDomain } from "@bonfida/spl-name-service";
import { PublicKey } from "@solana/web3.js";
/**
* Get the user's main/favorite domain for a SolanaAgentKit instance
* @param agent SolanaAgentKit instance
* @param owner Owner's public key
* @returns Promise resolving to the main domain name or null if not found
*/
export async function getMainAllDomainsDomain(
agent: any,
owner: PublicKey,
): Promise<string | null> {
let mainDomain = null;
try {
mainDomain = await _getFavoriteDomain(agent.connection, owner);
return mainDomain.stale ? null : mainDomain.reverse;
} catch (error: any) {
return null;
}
}

View File

@@ -0,0 +1,23 @@
import { SolanaAgentKit } from "../agent";
import { PublicKey } from "@solana/web3.js";
import { TldParser } from "@onsol/tldparser";
/**
* Get all domains owned domains for a specific TLD for the agent's wallet
* @param agent SolanaAgentKit instance
* @param owner - PublicKey of the owner
* @returns Promise resolving to an array of owned domains or an empty array if none are found
*/
export async function getOwnedAllDomains(
agent: SolanaAgentKit,
owner: PublicKey,
): Promise<string[]> {
try {
const domains = await new TldParser(
agent.connection,
).getParsedAllUserDomains(owner);
return domains.map((domain) => domain.domain);
} catch (error: any) {
throw new Error(`Failed to fetch owned domains: ${error.message}`);
}
}

View File

@@ -0,0 +1,21 @@
import { TldParser } from "@onsol/tldparser";
import { SolanaAgentKit } from "../agent";
/**
* Get all domains owned by an address for a specific TLD
* @param agent SolanaAgentKit instance
* @param tld Top-level domain (e.g., "sol")
* @returns Promise resolving to an array of owned domain names for the specified TLD or an empty array if none are found
*/
export async function getOwnedDomainsForTLD(
agent: SolanaAgentKit,
tld: string,
): Promise<string[]> {
try {
const domains = await new TldParser(
agent.connection,
).getParsedAllUserDomainsFromTld(agent.wallet_address, tld);
return domains.map((domain) => domain.domain);
} catch (error: any) {
throw new Error(`Failed to fetch domains for TLD: ${error.message}`);
}
}

View File

@@ -16,22 +16,22 @@ import { SolanaAgentKit } from "../index";
*/
export async function getPrimaryDomain(
agent: SolanaAgentKit,
account: PublicKey
account: PublicKey,
): Promise<string> {
try {
const { reverse, stale } = await _getPrimaryDomain(
agent.connection,
account
account,
);
if (stale) {
throw new Error(
`Primary domain is stale for account: ${account.toBase58()}`
`Primary domain is stale for account: ${account.toBase58()}`,
);
}
return reverse;
} catch (error) {
throw new Error(
`Failed to get primary domain for account: ${account.toBase58()}`
`Failed to get primary domain for account: ${account.toBase58()}`,
);
}
}

View File

@@ -27,11 +27,11 @@ export async function getTokenDataByAddress(
}
export async function getTokenAddressFromTicker(
ticker: string
ticker: string,
): Promise<string | null> {
try {
const response = await fetch(
`https://api.dexscreener.com/latest/dex/search?q=${ticker}`
`https://api.dexscreener.com/latest/dex/search?q=${ticker}`,
);
const data = await response.json();
@@ -46,7 +46,7 @@ export async function getTokenAddressFromTicker(
solanaPairs = solanaPairs.filter(
(pair: any) =>
pair.baseToken.symbol.toLowerCase() === ticker.toLowerCase()
pair.baseToken.symbol.toLowerCase() === ticker.toLowerCase(),
);
// Return the address of the highest FDV Solana pair
@@ -58,11 +58,11 @@ export async function getTokenAddressFromTicker(
}
export async function getTokenDataByTicker(
ticker: string
ticker: string,
): Promise<JupiterTokenData | undefined> {
const address = await getTokenAddressFromTicker(ticker);
if (!address) {
throw new Error(`Token address not found for ticker: ${ticker}`);
}
return getTokenDataByAddress(new PublicKey(address));
}
}

View File

@@ -16,8 +16,24 @@ export * from "./stake_with_jup";
export * from "./fetch_price";
export * from "./send_compressed_airdrop";
export * from "./create_orca_single_sided_whirlpool";
export * from "./get_all_domains_tlds";
export * from "./get_all_registered_all_domains";
export * from "./get_owned_domains_for_tld";
export * from "./get_main_all_domains_domain";
export * from "./get_owned_all_domains";
export * from "./resolve_domain";
export * from "./get_all_domains_tlds";
export * from "./get_all_registered_all_domains";
export * from "./get_owned_domains_for_tld";
export * from "./get_main_all_domains_domain";
export * from "./get_owned_all_domains";
export * from "./resolve_domain";
export * from "./raydium_create_ammV4";
export * from "./raydium_create_clmm";
export * from "./raydium_create_cpmm";
export * from "./openbook_create_market";
export * from "./pyth_fetch_price";
export * from "./pyth_fetch_price";
export * from "./create_gibwork_task";

View File

@@ -21,9 +21,15 @@ async function uploadMetadata(
formData.append("showName", "true");
if (options?.twitter) formData.append("twitter", options.twitter);
if (options?.telegram) formData.append("telegram", options.telegram);
if (options?.website) formData.append("website", options.website);
if (options?.twitter) {
formData.append("twitter", options.twitter);
}
if (options?.telegram) {
formData.append("telegram", options.telegram);
}
if (options?.website) {
formData.append("website", options.website);
}
const imageResponse = await fetch(imageUrl);
const imageBlob = await imageResponse.blob();

View File

@@ -1,6 +1,5 @@
import { VersionedTransaction } from "@solana/web3.js";
import { LuloAccountDetailsResponse } from "../types";
import { SolanaAgentKit } from "../agent";
import { SolanaAgentKit } from "../index";
/**
* Lend tokens for yields using Lulo
@@ -10,7 +9,7 @@ import { SolanaAgentKit } from "../agent";
*/
export async function lendAsset(
agent: SolanaAgentKit,
amount: number
amount: number,
): Promise<string> {
try {
const response = await fetch(
@@ -23,14 +22,14 @@ export async function lendAsset(
body: JSON.stringify({
account: agent.wallet.publicKey.toBase58(),
}),
}
},
);
const data = await response.json();
// Deserialize the transaction
const luloTxn = VersionedTransaction.deserialize(
Buffer.from(data.transaction, "base64")
Buffer.from(data.transaction, "base64"),
);
// Get a recent blockhash and set it

View File

@@ -1,11 +1,15 @@
import { SolanaAgentKit } from "../index";
import { generateSigner, keypairIdentity } from '@metaplex-foundation/umi';
import { create, mplCore } from '@metaplex-foundation/mpl-core';
import { fetchCollection } from '@metaplex-foundation/mpl-core';
import { generateSigner, keypairIdentity } from "@metaplex-foundation/umi";
import { create, mplCore } from "@metaplex-foundation/mpl-core";
import { fetchCollection } from "@metaplex-foundation/mpl-core";
import { PublicKey } from "@solana/web3.js";
import { fromWeb3JsKeypair, fromWeb3JsPublicKey, toWeb3JsPublicKey } from "@metaplex-foundation/umi-web3js-adapters";
import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
import { MintCollectionNFTResponse } from '../types';
import {
fromWeb3JsKeypair,
fromWeb3JsPublicKey,
toWeb3JsPublicKey,
} from "@metaplex-foundation/umi-web3js-adapters";
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import { MintCollectionNFTResponse } from "../types";
/**
* Mint a new NFT as part of an existing collection
@@ -27,7 +31,7 @@ export async function mintCollectionNFT(
share: number;
}>;
},
recipient?: PublicKey
recipient?: PublicKey,
): Promise<MintCollectionNFTResponse> {
try {
// Create UMI instance from agent
@@ -49,15 +53,15 @@ export async function mintCollectionNFT(
collection: collection,
name: metadata.name,
uri: metadata.uri,
owner: fromWeb3JsPublicKey(recipient ?? agent.wallet.publicKey)
owner: fromWeb3JsPublicKey(recipient ?? agent.wallet.publicKey),
}).sendAndConfirm(umi);
return {
mint: toWeb3JsPublicKey(assetSigner.publicKey),
// Note: Token account is now handled automatically by the create instruction
metadata: toWeb3JsPublicKey(assetSigner.publicKey)
metadata: toWeb3JsPublicKey(assetSigner.publicKey),
};
} catch (error: any) {
throw new Error(`Collection NFT minting failed: ${error.message}`);
}
}
}

View File

@@ -1,33 +1,34 @@
import { OPEN_BOOK_PROGRAM, Raydium, TxVersion } from "@raydium-io/raydium-sdk-v2";
import {
OPEN_BOOK_PROGRAM,
Raydium,
TxVersion,
} from "@raydium-io/raydium-sdk-v2";
import { MintLayout, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { PublicKey } from "@solana/web3.js";
import { SolanaAgentKit } from "../agent";
import { SolanaAgentKit } from "../index";
export async function openbookCreateMarket(
agent: SolanaAgentKit,
baseMint: PublicKey,
quoteMint: PublicKey,
lotSize: number = 1,
tickSize: number = 0.01,
): Promise<string[]> {
const raydium = await Raydium.load({
owner: agent.wallet,
connection: agent.connection,
})
});
const baseMintInfo = await agent.connection.getAccountInfo(baseMint)
const quoteMintInfo = await agent.connection.getAccountInfo(quoteMint)
const baseMintInfo = await agent.connection.getAccountInfo(baseMint);
const quoteMintInfo = await agent.connection.getAccountInfo(quoteMint);
if (
baseMintInfo?.owner.toString() !== TOKEN_PROGRAM_ID.toBase58() ||
quoteMintInfo?.owner.toString() !== TOKEN_PROGRAM_ID.toBase58()
) {
throw new Error(
'openbook market only support TOKEN_PROGRAM_ID mints, if you want to create pool with token-2022, please create raydium cpmm pool instead'
)
"openbook market only support TOKEN_PROGRAM_ID mints, if you want to create pool with token-2022, please create raydium cpmm pool instead",
);
}
const { execute } = await raydium.marketV2.create({
@@ -44,9 +45,9 @@ export async function openbookCreateMarket(
dexProgramId: OPEN_BOOK_PROGRAM,
txVersion: TxVersion.V0,
})
});
const { txIds } = await execute({ sequentially: true, })
const { txIds } = await execute({ sequentially: true });
return txIds
return txIds;
}

View File

@@ -1,6 +1,3 @@
import { PublicKey } from "@solana/web3.js";
import { SolanaAgentKit } from "../index";
import { Tool } from "langchain/tools";
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
import BN from "bn.js";
@@ -9,40 +6,35 @@ import BN from "bn.js";
* @param agent SolanaAgentKit instance
* @param priceFeedID Price feed ID
* @returns Latest price value from feed
*
*
* You can find priceFeedIDs here: https://www.pyth.network/developers/price-feed-ids#stable
*/
export async function pythFetchPrice(
agent: SolanaAgentKit,
priceFeedID: string
) {
// get Hermes service URL from https://docs.pyth.network/price-feeds/api-instances-and-providers/hermes
export async function pythFetchPrice(priceFeedID: string): Promise<string> {
// get Hermes service URL from https://docs.pyth.network/price-feeds/api-instances-and-providers/hermes
const stableHermesServiceUrl: string = "https://hermes.pyth.network";
const connection = new PriceServiceConnection(stableHermesServiceUrl);
const feeds = [priceFeedID];
try {
const currentPrice = await connection.getLatestPriceFeeds(feeds);
if (currentPrice === undefined) {
throw new Error("Price data not available for the given token.");
}
if (currentPrice.length === 0) {
throw new Error("Price data not available for the given token.");
}
// get price and exponent from price feed
let price = new BN(currentPrice[0].getPriceUnchecked().price);
let exponent = new BN(currentPrice[0].getPriceUnchecked().expo);
const price = new BN(currentPrice[0].getPriceUnchecked().price);
const exponent = new BN(currentPrice[0].getPriceUnchecked().expo);
// convert to scaled price
let scaledPrice = price.div(new BN(10).pow(exponent));
const scaledPrice = price.div(new BN(10).pow(exponent));
return scaledPrice.toString();
} catch (error: any) {
throw new Error(`Fetching price from Pyth failed: ${error.message}`);
}
}

View File

@@ -1,42 +1,59 @@
import { AMM_V4, FEE_DESTINATION_ID, MARKET_STATE_LAYOUT_V3, OPEN_BOOK_PROGRAM, Raydium, TxVersion } from "@raydium-io/raydium-sdk-v2";
import {
AMM_V4,
FEE_DESTINATION_ID,
MARKET_STATE_LAYOUT_V3,
OPEN_BOOK_PROGRAM,
Raydium,
TxVersion,
} from "@raydium-io/raydium-sdk-v2";
import { MintLayout, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { PublicKey } from "@solana/web3.js";
import BN from 'bn.js';
import { SolanaAgentKit } from "../agent";
import BN from "bn.js";
import { SolanaAgentKit } from "../index";
export async function raydiumCreateAmmV4(
agent: SolanaAgentKit,
marketId: PublicKey,
baseAmount: BN,
quoteAmount: BN,
startTime: BN,
): Promise<string> {
const raydium = await Raydium.load({
owner: agent.wallet,
connection: agent.connection,
})
});
const marketBufferInfo = await agent.connection.getAccountInfo(new PublicKey(marketId))
const { baseMint, quoteMint } = MARKET_STATE_LAYOUT_V3.decode(marketBufferInfo!.data)
const marketBufferInfo = await agent.connection.getAccountInfo(
new PublicKey(marketId),
);
const { baseMint, quoteMint } = MARKET_STATE_LAYOUT_V3.decode(
marketBufferInfo!.data,
);
const baseMintInfo = await agent.connection.getAccountInfo(baseMint)
const quoteMintInfo = await agent.connection.getAccountInfo(quoteMint)
const baseMintInfo = await agent.connection.getAccountInfo(baseMint);
const quoteMintInfo = await agent.connection.getAccountInfo(quoteMint);
if (
baseMintInfo?.owner.toString() !== TOKEN_PROGRAM_ID.toBase58() ||
quoteMintInfo?.owner.toString() !== TOKEN_PROGRAM_ID.toBase58()
) {
throw new Error(
'amm pools with openbook market only support TOKEN_PROGRAM_ID mints, if you want to create pool with token-2022, please create cpmm pool instead'
)
"amm pools with openbook market only support TOKEN_PROGRAM_ID mints, if you want to create pool with token-2022, please create cpmm pool instead",
);
}
if (baseAmount.mul(quoteAmount).lte(new BN(1).mul(new BN(10 ** MintLayout.decode(baseMintInfo.data).decimals)).pow(new BN(2)))) {
throw new Error('initial liquidity too low, try adding more baseAmount/quoteAmount')
if (
baseAmount
.mul(quoteAmount)
.lte(
new BN(1)
.mul(new BN(10 ** MintLayout.decode(baseMintInfo.data).decimals))
.pow(new BN(2)),
)
) {
throw new Error(
"initial liquidity too low, try adding more baseAmount/quoteAmount",
);
}
const { execute } = await raydium.liquidity.createPoolV4({
@@ -63,9 +80,9 @@ export async function raydiumCreateAmmV4(
associatedOnly: false,
txVersion: TxVersion.V0,
feeDestinationId: FEE_DESTINATION_ID,
})
});
const { txId } = await execute({ sendAndConfirm: true })
const { txId } = await execute({ sendAndConfirm: true });
return txId
}
return txId;
}

View File

@@ -1,62 +1,66 @@
import { CLMM_PROGRAM_ID, Raydium, TxVersion } from '@raydium-io/raydium-sdk-v2'
import { MintLayout } from '@solana/spl-token'
import { PublicKey } from '@solana/web3.js'
import BN from 'bn.js'
import Decimal from 'decimal.js'
import { SolanaAgentKit } from '../agent'
import {
CLMM_PROGRAM_ID,
Raydium,
TxVersion,
} from "@raydium-io/raydium-sdk-v2";
import { MintLayout } from "@solana/spl-token";
import { PublicKey } from "@solana/web3.js";
import BN from "bn.js";
import Decimal from "decimal.js";
import { SolanaAgentKit } from "../index";
export async function raydiumCreateClmm(
agent: SolanaAgentKit,
mint1: PublicKey,
mint2: PublicKey,
configId: PublicKey,
initialPrice: Decimal,
startTime: BN,
): Promise<string> {
const raydium = await Raydium.load({
owner: agent.wallet,
connection: agent.connection,
})
});
const [mintInfo1, mintInfo2] = await agent.connection.getMultipleAccountsInfo([mint1, mint2])
if (mintInfo1 === null || mintInfo2 === null) throw Error('fetch mint info error')
const [mintInfo1, mintInfo2] = await agent.connection.getMultipleAccountsInfo(
[mint1, mint2],
);
if (mintInfo1 === null || mintInfo2 === null) {
throw Error("fetch mint info error");
}
const mintDecodeInfo1 = MintLayout.decode(mintInfo1.data)
const mintDecodeInfo2 = MintLayout.decode(mintInfo2.data)
const mintDecodeInfo1 = MintLayout.decode(mintInfo1.data);
const mintDecodeInfo2 = MintLayout.decode(mintInfo2.data);
const mintFormatInfo1 = {
chainId: 101,
address: mint1.toString(),
programId: mintInfo1.owner.toString(),
logoURI: '',
symbol: '',
name: '',
logoURI: "",
symbol: "",
name: "",
decimals: mintDecodeInfo1.decimals,
tags: [],
extensions: {}
}
extensions: {},
};
const mintFormatInfo2 = {
chainId: 101,
address: mint2.toString(),
programId: mintInfo2.owner.toString(),
logoURI: '',
symbol: '',
name: '',
logoURI: "",
symbol: "",
name: "",
decimals: mintDecodeInfo2.decimals,
tags: [],
extensions: {}
}
extensions: {},
};
const { execute } = await raydium.clmm.createPool({
programId: CLMM_PROGRAM_ID,
// programId: DEVNET_PROGRAM_ID.CLMM,
mint1: mintFormatInfo1,
mint2: mintFormatInfo2,
// @ts-ignore
// @ts-expect-error sdk bug
ammConfig: { id: configId },
initialPrice,
startTime,
@@ -65,9 +69,9 @@ export async function raydiumCreateClmm(
// units: 600000,
// microLamports: 46591500,
// },
})
});
const { txId } = await execute({ sendAndConfirm: true })
const { txId } = await execute({ sendAndConfirm: true });
return txId
return txId;
}

View File

@@ -2,62 +2,61 @@ import {
CREATE_CPMM_POOL_FEE_ACC,
CREATE_CPMM_POOL_PROGRAM,
Raydium,
TxVersion
} from '@raydium-io/raydium-sdk-v2'
import { MintLayout } from '@solana/spl-token'
import { PublicKey } from '@solana/web3.js'
import BN from 'bn.js'
import { SolanaAgentKit } from '../agent'
TxVersion,
} from "@raydium-io/raydium-sdk-v2";
import { MintLayout } from "@solana/spl-token";
import { PublicKey } from "@solana/web3.js";
import BN from "bn.js";
import { SolanaAgentKit } from "../index";
export async function raydiumCreateCpmm(
agent: SolanaAgentKit,
mintA: PublicKey,
mintB: PublicKey,
configId: PublicKey,
mintAAmount: BN,
mintBAmount: BN,
startTime: BN,
): Promise<string> {
const raydium = await Raydium.load({
owner: agent.wallet,
connection: agent.connection,
})
});
const [mintInfoA, mintInfoB] = await agent.connection.getMultipleAccountsInfo([mintA, mintB])
if (mintInfoA === null || mintInfoB === null) throw Error('fetch mint info error')
const [mintInfoA, mintInfoB] = await agent.connection.getMultipleAccountsInfo(
[mintA, mintB],
);
if (mintInfoA === null || mintInfoB === null) {
throw Error("fetch mint info error");
}
const mintDecodeInfoA = MintLayout.decode(mintInfoA.data)
const mintDecodeInfoB = MintLayout.decode(mintInfoB.data)
const mintDecodeInfoA = MintLayout.decode(mintInfoA.data);
const mintDecodeInfoB = MintLayout.decode(mintInfoB.data);
const mintFormatInfoA = {
chainId: 101,
address: mintA.toString(),
programId: mintInfoA.owner.toString(),
logoURI: '',
symbol: '',
name: '',
logoURI: "",
symbol: "",
name: "",
decimals: mintDecodeInfoA.decimals,
tags: [],
extensions: {}
}
extensions: {},
};
const mintFormatInfoB = {
chainId: 101,
address: mintB.toString(),
programId: mintInfoB.owner.toString(),
logoURI: '',
symbol: '',
name: '',
logoURI: "",
symbol: "",
name: "",
decimals: mintDecodeInfoB.decimals,
tags: [],
extensions: {}
}
extensions: {},
};
const { execute, extInfo } = await raydium.cpmm.createPool({
const { execute } = await raydium.cpmm.createPool({
programId: CREATE_CPMM_POOL_PROGRAM,
poolFeeAccount: CREATE_CPMM_POOL_FEE_ACC,
mintA: mintFormatInfoA,
@@ -65,7 +64,7 @@ export async function raydiumCreateCpmm(
mintAAmount,
mintBAmount,
startTime,
//@ts-ignore
//@ts-expect-error sdk bug
feeConfig: { id: configId.toString() },
associatedOnly: false,
ownerInfo: {
@@ -76,9 +75,9 @@ export async function raydiumCreateCpmm(
// units: 600000,
// microLamports: 46591500,
// },
})
});
const { txId } = await execute({ sendAndConfirm: true })
const { txId } = await execute({ sendAndConfirm: true });
return txId
return txId;
}

View File

@@ -0,0 +1,30 @@
import { TldParser } from "@onsol/tldparser";
import { SolanaAgentKit } from "../index";
import { PublicKey } from "@solana/web3.js";
/**
* Resolve all domains for a given agent and domain
* @param agent SolanaAgentKit instance
* @param domain Domain name to resolve
* @returns Promise resolving to the domain or undefined if not found
*/
export async function resolveAllDomains(
agent: SolanaAgentKit,
domain: string,
): Promise<PublicKey | undefined> {
try {
const tld = await new TldParser(agent.connection).getOwnerFromDomainTld(
domain,
);
return tld;
} catch (error: any) {
if (
error.message.includes(
"Cannot read properties of undefined (reading 'owner')",
)
) {
return undefined;
}
throw new Error(`Domain resolution failed: ${error.message}`);
}
}

View File

@@ -16,7 +16,7 @@ import { SolanaAgentKit } from "../index";
*/
export async function resolveSolDomain(
agent: SolanaAgentKit,
domain: string
domain: string,
): Promise<PublicKey> {
if (!domain || typeof domain !== "string") {
throw new Error("Invalid domain. Expected a non-empty string.");

View File

@@ -1,12 +1,11 @@
import {
AddressLookupTableAccount,
ComputeBudgetProgram,
Connection,
Keypair,
PublicKey,
TransactionInstruction,
} from "@solana/web3.js";
import { SolanaAgentKit } from "../agent/index.js";
import { SolanaAgentKit } from "../index";
import {
buildAndSignTx,
calculateComputeUnitPrice,
@@ -19,7 +18,7 @@ import {
CompressedTokenProgram,
createTokenPool,
} from "@lightprotocol/compressed-token";
import { Account, getOrCreateAssociatedTokenAccount } from "@solana/spl-token";
import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token";
// arbitrary
const MAX_AIRDROP_RECIPIENTS = 1000;
@@ -33,7 +32,7 @@ const MAX_CONCURRENT_TXS = 30;
*/
export const getAirdropCostEstimate = (
numberOfRecipients: number,
priorityFeeInLamports: number
priorityFeeInLamports: number,
) => {
const baseFee = 5000;
const perRecipientCompressedStateFee = 300;
@@ -63,36 +62,34 @@ export async function sendCompressedAirdrop(
decimals: number,
recipients: PublicKey[],
priorityFeeInLamports: number,
shouldLog: boolean = false
shouldLog: boolean = false,
): Promise<string[]> {
if (recipients.length > MAX_AIRDROP_RECIPIENTS) {
throw new Error(
`Max airdrop can be ${MAX_AIRDROP_RECIPIENTS} recipients at a time. For more scale, use open source ZK Compression airdrop tools such as https://github.com/helius-labs/airship.`
`Max airdrop can be ${MAX_AIRDROP_RECIPIENTS} recipients at a time. For more scale, use open source ZK Compression airdrop tools such as https://github.com/helius-labs/airship.`,
);
}
const url = agent.connection.rpcEndpoint;
if (url.includes("devnet")) {
throw new Error("Devnet is not supported for airdrop. Please use mainnet.");
}
if (!url.includes("helius")) {
console.warn(
"Warning: Must use RPC with ZK Compression support. Double check with your RPC provider if in doubt."
"Warning: Must use RPC with ZK Compression support. Double check with your RPC provider if in doubt.",
);
}
let sourceTokenAccount: Account;
try {
sourceTokenAccount = await getOrCreateAssociatedTokenAccount(
await getOrCreateAssociatedTokenAccount(
agent.connection,
agent.wallet,
mintAddress,
agent.wallet.publicKey
agent.wallet.publicKey,
);
} catch (error) {
throw new Error(
"Source token account not found and failed to create it. Please add funds to your wallet and try again."
"Source token account not found and failed to create it. Please add funds to your wallet and try again.",
);
}
@@ -100,7 +97,7 @@ export async function sendCompressedAirdrop(
await createTokenPool(
agent.connection as unknown as Rpc,
agent.wallet,
mintAddress
mintAddress,
);
} catch (error: any) {
if (error.message.includes("already in use")) {
@@ -116,7 +113,7 @@ export async function sendCompressedAirdrop(
mintAddress,
recipients,
priorityFeeInLamports,
shouldLog
shouldLog,
);
}
@@ -126,7 +123,7 @@ async function processAll(
mint: PublicKey,
recipients: PublicKey[],
priorityFeeInLamports: number,
shouldLog: boolean
shouldLog: boolean,
): Promise<string[]> {
const mintAddress = mint;
const payer = agent.wallet;
@@ -135,13 +132,13 @@ async function processAll(
agent.connection,
agent.wallet,
mintAddress,
agent.wallet.publicKey
agent.wallet.publicKey,
);
const maxRecipientsPerInstruction = 5;
const maxIxs = 3; // empirically determined (as of 12/15/2024)
const lookupTableAddress = new PublicKey(
"9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ"
"9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ",
);
const lookupTableAccount = (
@@ -164,7 +161,7 @@ async function processAll(
ComputeBudgetProgram.setComputeUnitPrice({
microLamports: calculateComputeUnitPrice(
priorityFeeInLamports,
500_000
500_000,
),
}),
];
@@ -184,13 +181,13 @@ async function processAll(
toAddress: batch,
amount: batch.map(() => amount),
mint: mintAddress,
})
}),
);
}
const compressIxs = await Promise.all(compressIxPromises);
return [...instructions, ...compressIxs];
})
}),
);
const url = agent.connection.rpcEndpoint;
@@ -225,12 +222,12 @@ async function processAll(
instructions,
payer,
lookupTableAccount,
i + idx
i + idx,
).then((signature) => {
confirmedCount++;
log("\r" + renderProgressBar(confirmedCount, totalBatches));
return signature;
})
}),
);
const batchResults = await Promise.allSettled(batchPromises);
@@ -250,7 +247,7 @@ async function processAll(
throw new Error(
`Failed to process ${failures.length} batches: ${failures
.map((f) => f.error)
.join(", ")}`
.join(", ")}`,
);
}
@@ -262,7 +259,7 @@ async function sendTransactionWithRetry(
instructions: TransactionInstruction[],
payer: Keypair,
lookupTableAccount: AddressLookupTableAccount,
batchIndex: number
batchIndex: number,
): Promise<string> {
const MAX_RETRIES = 3;
const INITIAL_BACKOFF = 500; // ms
@@ -275,7 +272,7 @@ async function sendTransactionWithRetry(
payer,
blockhash,
[],
[lookupTableAccount]
[lookupTableAccount],
);
const signature = await sendAndConfirmTx(connection, tx);
@@ -292,7 +289,7 @@ async function sendTransactionWithRetry(
throw new Error(
`Batch ${batchIndex} failed after ${attempt + 1} attempts: ${
error.message
}`
}`,
);
}

View File

@@ -1,5 +1,5 @@
import { VersionedTransaction } from "@solana/web3.js";
import { SolanaAgentKit } from "../agent";
import { SolanaAgentKit } from "../index";
/**
* Stake SOL with Jup validator

View File

@@ -1,4 +1,8 @@
import { VersionedTransaction, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
import {
VersionedTransaction,
PublicKey,
LAMPORTS_PER_SOL,
} from "@solana/web3.js";
import { SolanaAgentKit } from "../index";
import { TOKENS, DEFAULT_OPTIONS, JUP_API } from "../constants";

View File

@@ -1,14 +1,10 @@
import { SolanaAgentKit } from "../index";
import {
PublicKey,
SystemProgram,
Transaction
} from "@solana/web3.js";
import { PublicKey, SystemProgram, Transaction } from "@solana/web3.js";
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
import {
getAssociatedTokenAddress,
import {
getAssociatedTokenAddress,
createTransferInstruction,
getMint
getMint,
} from "@solana/spl-token";
/**
@@ -23,7 +19,7 @@ export async function transfer(
agent: SolanaAgentKit,
to: PublicKey,
amount: number,
mint?: PublicKey
mint?: PublicKey,
): Promise<string> {
try {
let tx: string;
@@ -34,19 +30,19 @@ export async function transfer(
SystemProgram.transfer({
fromPubkey: agent.wallet_address,
toPubkey: to,
lamports: amount * LAMPORTS_PER_SOL
})
lamports: amount * LAMPORTS_PER_SOL,
}),
);
tx = await agent.connection.sendTransaction(
transaction,
[agent.wallet]
);
tx = await agent.connection.sendTransaction(transaction, [agent.wallet]);
} else {
// Transfer SPL token
const fromAta = await getAssociatedTokenAddress(mint, agent.wallet_address);
const fromAta = await getAssociatedTokenAddress(
mint,
agent.wallet_address,
);
const toAta = await getAssociatedTokenAddress(mint, to);
// Get mint info to determine decimals
const mintInfo = await getMint(agent.connection, mint);
const adjustedAmount = amount * Math.pow(10, mintInfo.decimals);
@@ -56,18 +52,15 @@ export async function transfer(
fromAta,
toAta,
agent.wallet_address,
adjustedAmount
)
adjustedAmount,
),
);
tx = await agent.connection.sendTransaction(
transaction,
[agent.wallet]
);
tx = await agent.connection.sendTransaction(transaction, [agent.wallet]);
}
return tx;
} catch (error: any) {
throw new Error(`Transfer failed: ${error.message}`);
}
}
}

View File

@@ -85,3 +85,9 @@ export interface PythFetchPriceResponse {
message?: string;
code?: string;
}
export interface GibworkCreateTaskReponse {
status: "success" | "error";
taskId?: string | undefined;
signature?: string | undefined;
}

View File

@@ -4,4 +4,4 @@ import bs58 from "bs58";
export const keypair = Keypair.generate();
console.log(keypair.publicKey.toString());
console.log(bs58.encode(keypair.secretKey));
console.log(bs58.encode(keypair.secretKey));

View File

@@ -14,7 +14,7 @@ function validateEnvironment(): void {
const missingVars: string[] = [];
const requiredVars = ["OPENAI_API_KEY", "RPC_URL", "SOLANA_PRIVATE_KEY"];
requiredVars.forEach(varName => {
requiredVars.forEach((varName) => {
if (!process.env[varName]) {
missingVars.push(varName);
}
@@ -22,7 +22,7 @@ function validateEnvironment(): void {
if (missingVars.length > 0) {
console.error("Error: Required environment variables are not set");
missingVars.forEach(varName => {
missingVars.forEach((varName) => {
console.error(`${varName}=your_${varName.toLowerCase()}_here`);
});
process.exit(1);
@@ -50,13 +50,13 @@ async function initializeAgent() {
}
}
const solanaKit = new SolanaAgentKit(
const solanaAgent = new SolanaAgentKit(
process.env.SOLANA_PRIVATE_KEY!,
process.env.RPC_URL,
process.env.OPENAI_API_KEY!
process.env.OPENAI_API_KEY!,
);
const tools = createSolanaTools(solanaKit);
const tools = createSolanaTools(solanaAgent);
const memory = new MemorySaver();
const config = { configurable: { thread_id: "Solana Agent Kit!" } };
@@ -65,14 +65,13 @@ async function initializeAgent() {
tools,
checkpointSaver: memory,
messageModifier: `
You are a helpful agent that can interact onchain using the Solana Agent Kit. You are
empowered to interact onchain using your tools. If you ever need funds, you can request them from the
faucet. If not, you can provide your wallet details and request funds from the user. If there is a 5XX
(internal) HTTP error code, ask the user to try again later. If someone asks you to do something you
can't do with your currently available tools, you must say so, and encourage them to implement it
themselves using the Solana Agent Kit, recommend they go to https://www.solanaagentkit.xyz for more information. Be
concise and helpful with your responses. Refrain from restating your tools' descriptions unless it is
explicitly requested.
You are a helpful agent that can interact onchain using the Solana Agent Kit. You are
empowered to interact onchain using your tools. If you ever need funds, you can request them from the
faucet. If not, you can provide your wallet details and request funds from the user. If there is a 5XX
(internal) HTTP error code, ask the user to try again later. If someone asks you to do something you
can't do with your currently available tools, you must say so, and encourage them to implement it
themselves using the Solana Agent Kit, recommend they go to https://www.solanaagentkit.xyz for more information. Be
concise and helpful with your responses. Refrain from restating your tools' descriptions unless it is explicitly requested.
`,
});
@@ -96,7 +95,10 @@ async function runAutonomousMode(agent: any, config: any, interval = 10) {
"Be creative and do something interesting on the blockchain. " +
"Choose an action or set of actions and execute it that highlights your abilities.";
const stream = await agent.stream({ messages: [new HumanMessage(thought)] }, config);
const stream = await agent.stream(
{ messages: [new HumanMessage(thought)] },
config,
);
for await (const chunk of stream) {
if ("agent" in chunk) {
@@ -107,7 +109,7 @@ async function runAutonomousMode(agent: any, config: any, interval = 10) {
console.log("-------------------");
}
await new Promise(resolve => setTimeout(resolve, interval * 1000));
await new Promise((resolve) => setTimeout(resolve, interval * 1000));
} catch (error) {
if (error instanceof Error) {
console.error("Error:", error.message);
@@ -126,7 +128,7 @@ async function runChatMode(agent: any, config: any) {
});
const question = (prompt: string): Promise<string> =>
new Promise(resolve => rl.question(prompt, resolve));
new Promise((resolve) => rl.question(prompt, resolve));
try {
while (true) {
@@ -136,7 +138,10 @@ async function runChatMode(agent: any, config: any) {
break;
}
const stream = await agent.stream({ messages: [new HumanMessage(userInput)] }, config);
const stream = await agent.stream(
{ messages: [new HumanMessage(userInput)] },
config,
);
for await (const chunk of stream) {
if ("agent" in chunk) {
@@ -164,7 +169,7 @@ async function chooseMode(): Promise<"chat" | "auto"> {
});
const question = (prompt: string): Promise<string> =>
new Promise(resolve => rl.question(prompt, resolve));
new Promise((resolve) => rl.question(prompt, resolve));
while (true) {
console.log("\nAvailable modes:");
@@ -206,7 +211,7 @@ async function main() {
}
if (require.main === module) {
main().catch(error => {
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});