diff --git a/package.json b/package.json index bcb2d27..a84f7b5 100644 --- a/package.json +++ b/package.json @@ -1,76 +1,75 @@ { - "name": "solana-agent-kit", - "version": "1.3.4", - "description": "connect any ai agents to solana protocols", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "tsc", - "docs": "typedoc src --out docs", - "test": "ts-node test/index.ts", - "test:vercel-ai": "ts-node test/agent_sdks/vercel_ai.ts", - "generate": "ts-node src/utils/keypair.ts", - "lint": "eslint . --ext .ts,.json", - "lint:fix": "eslint . --ext .ts,.json --fix", - "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"" - }, - "engines": { - "node": ">=22.0.0", - "pnpm": ">=8.0.0" - }, - "keywords": [], - "author": "sendaifun", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/openai": "^1.0.11", - "@bonfida/spl-name-service": "^3.0.7", - "@cks-systems/manifest-sdk": "^0.1.73", - "@coral-xyz/anchor": "0.29", - "@langchain/core": "^0.3.26", - "@langchain/groq": "^0.1.2", - "@langchain/langgraph": "^0.2.36", - "@langchain/openai": "^0.3.16", - "@lightprotocol/compressed-token": "^0.17.1", - "@lightprotocol/stateless.js": "^0.17.1", - "@metaplex-foundation/mpl-core": "^1.1.1", - "@metaplex-foundation/mpl-token-metadata": "^3.3.0", - "@metaplex-foundation/mpl-toolbox": "^0.9.4", - "@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", - "@raydium-io/raydium-sdk-v2": "0.1.95-alpha", - "@solana/spl-token": "^0.4.9", - "ai": "^4.0.22", - "@tensor-oss/tensorswap-sdk": "^4.5.0", - "@solana/web3.js": "^1.98.0", - "@tiplink/api": "^0.3.1", - "bn.js": "^5.2.1", - "bs58": "^6.0.0", - "chai": "^5.1.2", - "decimal.js": "^10.4.3", - "dotenv": "^16.4.7", - "form-data": "^4.0.1", - "zod": "^3.24.1" - "langchain": "^0.3.8", - "openai": "^4.77.0", - "typedoc": "^0.27.6", - "zod": "^3.24.1" - }, - "devDependencies": { - "@types/bn.js": "^5.1.6", - "@types/chai": "^5.0.1", - "@types/node": "^22.10.2", - "@typescript-eslint/eslint-plugin": "^8.18.2", - "@typescript-eslint/parser": "^8.18.2", - "eslint": "^8.56.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.2.1", - "prettier": "^3.4.2", - "ts-node": "^10.9.2", - "typescript": "^5.7.2" - } + "name": "solana-agent-kit", + "version": "1.3.4", + "description": "connect any ai agents to solana protocols", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "docs": "typedoc src --out docs", + "test": "ts-node test/index.ts", + "test:vercel-ai": "ts-node test/agent_sdks/vercel_ai.ts", + "generate": "ts-node src/utils/keypair.ts", + "lint": "eslint . --ext .ts,.json", + "lint:fix": "eslint . --ext .ts,.json --fix", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"" + }, + "engines": { + "node": ">=22.0.0", + "pnpm": ">=8.0.0" + }, + "keywords": [], + "author": "sendaifun", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/openai": "^1.0.11", + "@bonfida/spl-name-service": "^3.0.7", + "@cks-systems/manifest-sdk": "^0.1.73", + "@coral-xyz/anchor": "0.29", + "@langchain/core": "^0.3.26", + "@langchain/groq": "^0.1.2", + "@langchain/langgraph": "^0.2.36", + "@langchain/openai": "^0.3.16", + "@lightprotocol/compressed-token": "^0.17.1", + "@lightprotocol/stateless.js": "^0.17.1", + "@metaplex-foundation/mpl-core": "^1.1.1", + "@metaplex-foundation/mpl-token-metadata": "^3.3.0", + "@metaplex-foundation/mpl-toolbox": "^0.9.4", + "@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", + "@raydium-io/raydium-sdk-v2": "0.1.95-alpha", + "@solana/spl-token": "^0.4.9", + "ai": "^4.0.22", + "@tensor-oss/tensorswap-sdk": "^4.5.0", + "@solana/web3.js": "^1.98.0", + "@tiplink/api": "^0.3.1", + "bn.js": "^5.2.1", + "bs58": "^6.0.0", + "chai": "^5.1.2", + "decimal.js": "^10.4.3", + "dotenv": "^16.4.7", + "form-data": "^4.0.1", + "zod": "^3.24.1", + "langchain": "^0.3.8", + "openai": "^4.77.0", + "typedoc": "^0.27.6" + }, + "devDependencies": { + "@types/bn.js": "^5.1.6", + "@types/chai": "^5.0.1", + "@types/node": "^22.10.2", + "@typescript-eslint/eslint-plugin": "^8.18.2", + "@typescript-eslint/parser": "^8.18.2", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", + "prettier": "^3.4.2", + "ts-node": "^10.9.2", + "typescript": "^5.7.2" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index edb9a3e..40aa400 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,15 +77,15 @@ importers: '@solana/web3.js': specifier: ^1.98.0 version: 1.98.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) - ai: - specifier: ^4.0.22 - version: 4.0.22(react@19.0.0)(zod@3.24.1) '@tensor-oss/tensorswap-sdk': specifier: ^4.5.0 version: 4.5.0(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(typescript@5.7.2)(utf-8-validate@5.0.10) '@tiplink/api': specifier: ^0.3.1 version: 0.3.1(bufferutil@4.0.8)(fastestsmallesttextencoderdecoder@1.0.22)(sodium-native@3.4.1)(utf-8-validate@5.0.10) + ai: + specifier: ^4.0.22 + version: 4.0.22(react@19.0.0)(zod@3.24.1) bn.js: specifier: ^5.2.1 version: 5.2.1 @@ -2165,12 +2165,12 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + react@19.0.0: resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} - - randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} @@ -4923,7 +4923,7 @@ snapshots: jsondiffpatch@0.6.0: dependencies: '@types/diff-match-patch': 1.0.36 - chalk: 5.4.0 + chalk: 5.4.1 diff-match-patch: 1.0.5 jsonfile@6.1.0: @@ -5286,12 +5286,12 @@ snapshots: queue-microtask@1.2.3: {} - react@19.0.0: {} - randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 + react@19.0.0: {} + readable-stream@3.6.2: dependencies: inherits: 2.0.4 diff --git a/src/actions/getWalletAddress.ts b/src/actions/getWalletAddress.ts new file mode 100644 index 0000000..114d642 --- /dev/null +++ b/src/actions/getWalletAddress.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import { SolanaAgentKit } from ".."; +import { get_wallet_address } from "../tools"; +import { Action } from "../types/action"; + +const getWalletAddressAction: Action = { + name: "GET_WALLET_ADDRESS", + similes: ["wallet address", "address", "wallet"], + description: "Get wallet address of the agent", + examples: [ + [ + { + input: {}, + output: { + status: "success", + address: "0x1234567890abcdef", + }, + explanation: "The agent's wallet address is 0x1234567890abcdef", + }, + ], + ], + schema: z.object({}), + handler: async (agent: SolanaAgentKit) => ({ + status: "success", + address: get_wallet_address(agent), + }), +}; + +export default getWalletAddressAction; diff --git a/src/actions/index.ts b/src/actions/index.ts index b66c89e..b99698a 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -26,36 +26,39 @@ import raydiumCreateCpmmAction from "./raydiumCreateCpmm"; import raydiumCreateAmmV4Action from "./raydiumCreateAmmV4"; import createOrcaSingleSidedWhirlpoolAction from "./createOrcaSingleSidedWhirlpool"; import launchPumpfunTokenAction from "./launchPumpfunToken"; +import getWalletAddressAction from "./getWalletAddress"; export const ACTIONS = { - "DEPLOY_TOKEN_ACTION" : deployTokenAction, - "BALANCE_ACTION" : balanceAction, - "TRANSFER_ACTION" : transferAction, - "DEPLOY_COLLECTION_ACTION" : deployCollectionAction, - "MINT_NFT_ACTION" : mintNFTAction, - "TRADE_ACTION" : tradeAction, - "REQUEST_FUNDS_ACTION" : requestFundsAction, - "RESOLVE_DOMAIN_ACTION" : resolveDomainAction, - "GET_TOKEN_DATA_ACTION" : getTokenDataAction, - "GET_TPS_ACTION" : getTPSAction, - "FETCH_PRICE_ACTION" : fetchPriceAction, - "STAKE_WITH_JUP_ACTION" : stakeWithJupAction, - "REGISTER_DOMAIN_ACTION" : registerDomainAction, - "LEND_ASSET_ACTION" : lendAssetAction, - "CREATE_GIBWORK_TASK_ACTION" : createGibworkTaskAction, - "RESOLVE_SOL_DOMAIN_ACTION" : resolveSolDomainAction, - "PYTH_FETCH_PRICE_ACTION" : pythFetchPriceAction, - "GET_OWNED_DOMAINS_FOR_TLD_ACTION" : getOwnedDomainsForTLDAction, - "GET_PRIMARY_DOMAIN_ACTION" : getPrimaryDomainAction, - "GET_ALL_DOMAINS_TLDS_ACTION" : getAllDomainsTLDsAction, - "GET_OWNED_ALL_DOMAINS_ACTION" : getOwnedAllDomainsAction, - "CREATE_IMAGE_ACTION" : createImageAction, - "GET_MAIN_ALL_DOMAINS_DOMAIN_ACTION" : getMainAllDomainsDomainAction, - "GET_ALL_REGISTERED_ALL_DOMAINS_ACTION" : getAllRegisteredAllDomainsAction, - "RAYDIUM_CREATE_CPMM_ACTION" : raydiumCreateCpmmAction, - "RAYDIUM_CREATE_AMM_V4_ACTION" : raydiumCreateAmmV4Action, - "CREATE_ORCA_SINGLE_SIDED_WHIRLPOOL_ACTION" : createOrcaSingleSidedWhirlpoolAction, - "LAUNCH_PUMPFUN_TOKEN_ACTION" : launchPumpfunTokenAction, + WALLET_ADDRESS_ACTION: getWalletAddressAction, + DEPLOY_TOKEN_ACTION: deployTokenAction, + BALANCE_ACTION: balanceAction, + TRANSFER_ACTION: transferAction, + DEPLOY_COLLECTION_ACTION: deployCollectionAction, + MINT_NFT_ACTION: mintNFTAction, + TRADE_ACTION: tradeAction, + REQUEST_FUNDS_ACTION: requestFundsAction, + RESOLVE_DOMAIN_ACTION: resolveDomainAction, + GET_TOKEN_DATA_ACTION: getTokenDataAction, + GET_TPS_ACTION: getTPSAction, + FETCH_PRICE_ACTION: fetchPriceAction, + STAKE_WITH_JUP_ACTION: stakeWithJupAction, + REGISTER_DOMAIN_ACTION: registerDomainAction, + LEND_ASSET_ACTION: lendAssetAction, + CREATE_GIBWORK_TASK_ACTION: createGibworkTaskAction, + RESOLVE_SOL_DOMAIN_ACTION: resolveSolDomainAction, + PYTH_FETCH_PRICE_ACTION: pythFetchPriceAction, + GET_OWNED_DOMAINS_FOR_TLD_ACTION: getOwnedDomainsForTLDAction, + GET_PRIMARY_DOMAIN_ACTION: getPrimaryDomainAction, + GET_ALL_DOMAINS_TLDS_ACTION: getAllDomainsTLDsAction, + GET_OWNED_ALL_DOMAINS_ACTION: getOwnedAllDomainsAction, + CREATE_IMAGE_ACTION: createImageAction, + GET_MAIN_ALL_DOMAINS_DOMAIN_ACTION: getMainAllDomainsDomainAction, + GET_ALL_REGISTERED_ALL_DOMAINS_ACTION: getAllRegisteredAllDomainsAction, + RAYDIUM_CREATE_CPMM_ACTION: raydiumCreateCpmmAction, + RAYDIUM_CREATE_AMM_V4_ACTION: raydiumCreateAmmV4Action, + CREATE_ORCA_SINGLE_SIDED_WHIRLPOOL_ACTION: + createOrcaSingleSidedWhirlpoolAction, + LAUNCH_PUMPFUN_TOKEN_ACTION: launchPumpfunTokenAction, }; export type { Action, ActionExample, Handler } from "../types/action"; diff --git a/src/langchain/index.ts b/src/langchain/index.ts index 4605fd3..567cf8e 100644 --- a/src/langchain/index.ts +++ b/src/langchain/index.ts @@ -1,1877 +1,36 @@ -import { PublicKey } from "@solana/web3.js"; -import Decimal from "decimal.js"; import { Tool } from "langchain/tools"; -import { - GibworkCreateTaskReponse, - PythFetchPriceResponse, - SolanaAgentKit, -} from "../index"; -import { create_image } from "../tools/create_image"; -import { BN } from "@coral-xyz/anchor"; -import { FEE_TIERS } from "../tools"; +import { ACTIONS, executeAction, SolanaAgentKit } from "../index"; +import { Action } from "../actions"; -export class SolanaBalanceTool extends Tool { - name = "solana_balance"; - description = `Get the balance of a Solana wallet or token account. +class ToolWrapper extends Tool { + constructor( + private solanaAgentKit: SolanaAgentKit, + private action: Action, + ) { + super(); + this.name = action.name; + this.description = action.description; + } - If you want to get the balance of your wallet, you don't need to provide the tokenAddress. - If no tokenAddress is provided, the balance will be in SOL. + name: string; + description: string; - Inputs ( input is a JSON string ): - tokenAddress: string, eg "So11111111111111111111111111111111111111112" (optional)`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const tokenAddress = input ? new PublicKey(input) : undefined; - const balance = await this.solanaKit.getBalance(tokenAddress); - - return JSON.stringify({ - status: "success", - balance, - token: input || "SOL", - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } + protected async _call( + input: typeof this.action.schema, + ): ReturnType { + return await executeAction(this.action, this.solanaAgentKit, input); + } } -export class SolanaBalanceOtherTool extends Tool { - name = "solana_balance_other"; - description = `Get the balance of a Solana wallet or token account which is different from the agent's wallet. +export function createSolanaTools(solanaAgentKit: SolanaAgentKit): Tool[] { + const tools: Tool[] = []; + const actionKeys = Object.keys(ACTIONS); - If no tokenAddress is provided, the SOL balance of the wallet will be returned. + for (const actionKey of actionKeys) { + const action = ACTIONS[actionKey as keyof typeof ACTIONS]; + const tool = new ToolWrapper(solanaAgentKit, action); + tools.push(tool); + } - Inputs ( input is a JSON string ): - walletAddress: string, eg "GDEkQF7UMr7RLv1KQKMtm8E2w3iafxJLtyXu3HVQZnME" (required) - tokenAddress: string, eg "SENDdRQtYMWaQrBroBrJ2Q53fgVuq95CV9UPGEvpCxa" (optional)`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const { walletAddress, tokenAddress } = JSON.parse(input); - - const tokenPubKey = tokenAddress - ? new PublicKey(tokenAddress) - : undefined; - - const balance = await this.solanaKit.getBalanceOther( - new PublicKey(walletAddress), - tokenPubKey, - ); - - return JSON.stringify({ - status: "success", - balance, - wallet: walletAddress, - token: tokenAddress || "SOL", - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaTransferTool extends Tool { - name = "solana_transfer"; - description = `Transfer tokens or SOL to another address ( also called as wallet address ). - - Inputs ( input is a JSON string ): - to: string, eg "8x2dR8Mpzuz2YqyZyZjUbYWKSWesBo5jMx2Q9Y86udVk" (required) - amount: number, eg 1 (required) - mint?: string, eg "So11111111111111111111111111111111111111112" or "SENDdRQtYMWaQrBroBrJ2Q53fgVuq95CV9UPGEvpCxa" (optional)`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const parsedInput = JSON.parse(input); - - const recipient = new PublicKey(parsedInput.to); - const mintAddress = parsedInput.mint - ? new PublicKey(parsedInput.mint) - : undefined; - - const tx = await this.solanaKit.transfer( - recipient, - parsedInput.amount, - mintAddress, - ); - - return JSON.stringify({ - status: "success", - message: "Transfer completed successfully", - amount: parsedInput.amount, - recipient: parsedInput.to, - token: parsedInput.mint || "SOL", - transaction: tx, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaDeployTokenTool extends Tool { - name = "solana_deploy_token"; - description = `Deploy a new token on Solana blockchain. - - Inputs (input is a JSON string): - name: string, eg "My Token" (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)`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const parsedInput = JSON.parse(input); - - const result = await this.solanaKit.deployToken( - parsedInput.name, - parsedInput.uri, - parsedInput.symbol, - parsedInput.decimals, - parsedInput.initialSupply, - ); - - return JSON.stringify({ - status: "success", - message: "Token deployed successfully", - mintAddress: result.mint.toString(), - decimals: parsedInput.decimals || 9, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaDeployCollectionTool extends Tool { - name = "solana_deploy_collection"; - description = `Deploy a new NFT collection on Solana blockchain. - - Inputs (input is a JSON string): - name: string, eg "My Collection" (required) - uri: string, eg "https://example.com/collection.json" (required) - royaltyBasisPoints?: number, eg 500 for 5% (optional)`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const parsedInput = JSON.parse(input); - - const result = await this.solanaKit.deployCollection(parsedInput); - - return JSON.stringify({ - status: "success", - message: "Collection deployed successfully", - collectionAddress: result.collectionAddress.toString(), - name: parsedInput.name, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaMintNFTTool extends Tool { - name = "solana_mint_nft"; - description = `Mint a new NFT in a collection on Solana blockchain. - - Inputs (input is a JSON string): - collectionMint: string, eg "J1S9H3QjnRtBbbuD4HjPV6RpRhwuk4zKbxsnCHuTgh9w" (required) - The address of the collection to mint into - name: string, eg "My NFT" (required) - uri: string, eg "https://example.com/nft.json" (required) - recipient?: string, eg "9aUn5swQzUTRanaaTwmszxiv89cvFwUCjEBv1vZCoT1u" (optional) - The wallet to receive the NFT, defaults to agent's wallet which is ${this.solanaKit.wallet_address.toString()}`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const parsedInput = JSON.parse(input); - - const result = await this.solanaKit.mintNFT( - new PublicKey(parsedInput.collectionMint), - { - name: parsedInput.name, - uri: parsedInput.uri, - }, - parsedInput.recipient - ? new PublicKey(parsedInput.recipient) - : this.solanaKit.wallet_address, - ); - - return JSON.stringify({ - status: "success", - message: "NFT minted successfully", - mintAddress: result.mint.toString(), - metadata: { - name: parsedInput.name, - symbol: parsedInput.symbol, - uri: parsedInput.uri, - }, - recipient: parsedInput.recipient || result.mint.toString(), - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaTradeTool extends Tool { - name = "solana_trade"; - description = `This tool can be used to swap tokens to another token ( It uses Jupiter Exchange ). - - Inputs ( input is a JSON string ): - outputMint: string, eg "So11111111111111111111111111111111111111112" or "SENDdRQtYMWaQrBroBrJ2Q53fgVuq95CV9UPGEvpCxa" (required) - inputAmount: number, eg 1 or 0.01 (required) - inputMint?: string, eg "So11111111111111111111111111111111111111112" (optional) - slippageBps?: number, eg 100 (optional)`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const parsedInput = JSON.parse(input); - - const tx = await this.solanaKit.trade( - new PublicKey(parsedInput.outputMint), - parsedInput.inputAmount, - parsedInput.inputMint - ? new PublicKey(parsedInput.inputMint) - : new PublicKey("So11111111111111111111111111111111111111112"), - parsedInput.slippageBps, - ); - - return JSON.stringify({ - status: "success", - message: "Trade executed successfully", - transaction: tx, - inputAmount: parsedInput.inputAmount, - inputToken: parsedInput.inputMint || "SOL", - outputToken: parsedInput.outputMint, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaLimitOrderTool extends Tool { - name = "solana_limit_order"; - description = `This tool can be used to place limit orders using Manifest. - - Inputs ( input is a JSON string ): - marketId: PublicKey, eg "ENhU8LsaR7vDD2G1CsWcsuSGNrih9Cv5WZEk7q9kPapQ" for SOL/USDC (required) - quantity: number, eg 1 or 0.01 (required) - side: string, eg "Buy" or "Sell" (required) - price: number, in tokens eg 200 for SOL/USDC (required)`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const parsedInput = JSON.parse(input); - - const tx = await this.solanaKit.limitOrder( - new PublicKey(parsedInput.marketId), - parsedInput.quantity, - parsedInput.side, - parsedInput.price, - ); - - return JSON.stringify({ - status: "success", - message: "Trade executed successfully", - transaction: tx, - marketId: parsedInput.marketId, - quantity: parsedInput.quantity, - side: parsedInput.side, - price: parsedInput.price, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaCancelAllOrdersTool extends Tool { - name = "solana_cancel_all_orders"; - description = `This tool can be used to cancel all orders from a Manifest market. - - Input ( input is a JSON string ): - marketId: string, eg "ENhU8LsaR7vDD2G1CsWcsuSGNrih9Cv5WZEk7q9kPapQ" for SOL/USDC (required)`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const marketId = new PublicKey(input.trim()); - const tx = await this.solanaKit.cancelAllOrders(marketId); - - return JSON.stringify({ - status: "success", - message: "Cancel orders successfully", - transaction: tx, - marketId, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaWithdrawAllTool extends Tool { - name = "solana_withdraw_all"; - description = `This tool can be used to withdraw all funds from a Manifest market. - - Input ( input is a JSON string ): - marketId: string, eg "ENhU8LsaR7vDD2G1CsWcsuSGNrih9Cv5WZEk7q9kPapQ" for SOL/USDC (required)`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const marketId = new PublicKey(input.trim()); - const tx = await this.solanaKit.withdrawAll(marketId); - - return JSON.stringify({ - status: "success", - message: "Withdrew successfully", - transaction: tx, - marketId, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaRequestFundsTool extends Tool { - name = "solana_request_funds"; - description = "Request SOL from Solana faucet (devnet/testnet only)"; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(_input: string): Promise { - try { - await this.solanaKit.requestFaucetFunds(); - - return JSON.stringify({ - status: "success", - message: "Successfully requested faucet funds", - network: this.solanaKit.connection.rpcEndpoint.split("/")[2], - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaRegisterDomainTool extends Tool { - name = "solana_register_domain"; - description = `Register a .sol domain name for your wallet. - - Inputs: - name: string, eg "pumpfun.sol" (required) - spaceKB: number, eg 1 (optional, default is 1) - `; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - private validateInput(input: any): void { - if (!input.name || typeof input.name !== "string") { - throw new Error("name is required and must be a string"); - } - if ( - input.spaceKB !== undefined && - (typeof input.spaceKB !== "number" || input.spaceKB <= 0) - ) { - throw new Error("spaceKB must be a positive number when provided"); - } - } - - protected async _call(input: string): Promise { - try { - const parsedInput = JSON.parse(input); - this.validateInput(parsedInput); - - const tx = await this.solanaKit.registerDomain( - parsedInput.name, - parsedInput.spaceKB || 1, - ); - - return JSON.stringify({ - status: "success", - message: "Domain registered successfully", - transaction: tx, - domain: `${parsedInput.name}.sol`, - spaceKB: parsedInput.spaceKB || 1, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaResolveDomainTool extends Tool { - name = "solana_resolve_domain"; - 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" (required) - `; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const domain = input.trim(); - const publicKey = await this.solanaKit.resolveSolDomain(domain); - - return JSON.stringify({ - status: "success", - message: "Domain resolved successfully", - publicKey: publicKey.toBase58(), - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaGetDomainTool extends Tool { - name = "solana_get_domain"; - description = `Retrieve the .sol domain associated for a given account address. - - Inputs: - account: string, eg "4Be9CvxqHW6BYiRAxW9Q3xu1ycTMWaL5z8NX4HR3ha7t" (required) - `; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const account = new PublicKey(input.trim()); - const domain = await this.solanaKit.getPrimaryDomain(account); - - return JSON.stringify({ - status: "success", - message: "Primary domain retrieved successfully", - domain, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaGetWalletAddressTool extends Tool { - name = "solana_get_wallet_address"; - description = `Get the wallet address of the agent`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - async _call(_input: string): Promise { - return this.solanaKit.wallet_address.toString(); - } -} - -export class SolanaPumpfunTokenLaunchTool extends Tool { - name = "solana_launch_pumpfun_token"; - - description = `This tool can be used to launch a token on Pump.fun, - do not use this tool for any other purpose, or for creating SPL tokens. - If the user asks you to chose the parameters, you should generate valid values. - For generating the image, you can use the solana_create_image tool. - - Inputs: - tokenName: string, eg "PumpFun Token", - tokenTicker: string, eg "PUMP", - description: string, eg "PumpFun Token is a token on the Solana blockchain", - imageUrl: string, eg "https://i.imgur.com/UFm07Np_d.png`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - private validateInput(input: any): void { - if (!input.tokenName || typeof input.tokenName !== "string") { - throw new Error("tokenName is required and must be a string"); - } - if (!input.tokenTicker || typeof input.tokenTicker !== "string") { - throw new Error("tokenTicker is required and must be a string"); - } - if (!input.description || typeof input.description !== "string") { - throw new Error("description is required and must be a string"); - } - if (!input.imageUrl || typeof input.imageUrl !== "string") { - throw new Error("imageUrl is required and must be a string"); - } - if ( - input.initialLiquiditySOL !== undefined && - typeof input.initialLiquiditySOL !== "number" - ) { - throw new Error("initialLiquiditySOL must be a number when provided"); - } - } - - protected async _call(input: string): Promise { - try { - // Parse and normalize input - input = input.trim(); - const parsedInput = JSON.parse(input); - - this.validateInput(parsedInput); - - // Launch token with validated input - await this.solanaKit.launchPumpFunToken( - parsedInput.tokenName, - parsedInput.tokenTicker, - parsedInput.description, - parsedInput.imageUrl, - { - twitter: parsedInput.twitter, - telegram: parsedInput.telegram, - website: parsedInput.website, - initialLiquiditySOL: parsedInput.initialLiquiditySOL, - }, - ); - - return JSON.stringify({ - status: "success", - message: "Token launched successfully on Pump.fun", - tokenName: parsedInput.tokenName, - tokenTicker: parsedInput.tokenTicker, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaCreateImageTool extends Tool { - name = "solana_create_image"; - description = - "Create an image using OpenAI's DALL-E. Input should be a string prompt for the image."; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - private validateInput(input: string): void { - if (typeof input !== "string" || input.trim().length === 0) { - throw new Error("Input must be a non-empty string prompt"); - } - } - - protected async _call(input: string): Promise { - try { - this.validateInput(input); - const result = await create_image(this.solanaKit, input.trim()); - - return JSON.stringify({ - status: "success", - message: "Image created successfully", - ...result, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaLendAssetTool extends Tool { - name = "solana_lend_asset"; - description = `Lend idle USDC for yield using Lulo. ( only USDC is supported ) - - Inputs (input is a json string): - amount: number, eg 1, 0.01 (required)`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - async _call(input: string): Promise { - try { - const amount = JSON.parse(input).amount || input; - - const tx = await this.solanaKit.lendAssets(amount); - - return JSON.stringify({ - status: "success", - message: "Asset lent successfully", - transaction: tx, - amount, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaTPSCalculatorTool extends Tool { - name = "solana_get_tps"; - description = "Get the current TPS of the Solana network"; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - async _call(_input: string): Promise { - try { - const tps = await this.solanaKit.getTPS(); - return `Solana (mainnet-beta) current transactions per second: ${tps}`; - } catch (error: any) { - return `Error fetching TPS: ${error.message}`; - } - } -} - -export class SolanaStakeTool extends Tool { - name = "solana_stake"; - description = `This tool can be used to stake your SOL (Solana), also called as SOL staking or liquid staking. - - Inputs ( input is a JSON string ): - amount: number, eg 1 or 0.01 (required)`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const parsedInput = JSON.parse(input) || Number(input); - - const tx = await this.solanaKit.stake(parsedInput.amount); - - return JSON.stringify({ - status: "success", - message: "Staked successfully", - transaction: tx, - amount: parsedInput.amount, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -/** - * Tool to fetch the price of a token in USDC - */ -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"`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - async _call(input: string): Promise { - try { - const price = await this.solanaKit.fetchTokenPrice(input.trim()); - return JSON.stringify({ - status: "success", - tokenId: input.trim(), - priceInUSDC: price, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaTokenDataTool extends Tool { - name = "solana_token_data"; - description = `Get the token data for a given token mint address - - Inputs: mintAddress is required. - mintAddress: string, eg "So11111111111111111111111111111111111111112" (required)`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const parsedInput = input.trim(); - - const tokenData = await this.solanaKit.getTokenDataByAddress(parsedInput); - - return JSON.stringify({ - status: "success", - tokenData, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaTokenDataByTickerTool extends Tool { - name = "solana_token_data_by_ticker"; - description = `Get the token data for a given token ticker - - Inputs: ticker is required. - ticker: string, eg "USDC" (required)`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const ticker = input.trim(); - const tokenData = await this.solanaKit.getTokenDataByTicker(ticker); - return JSON.stringify({ - status: "success", - tokenData, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -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) - decimals: number, the decimals of the token, e.g., 6 (required) - recipients: string[], the recipient addresses, e.g., ["1nc1nerator11111111111111111111111111111111"] (required) - priorityFeeInLamports: number, the priority fee in lamports. Default is 30_000. (optional) - shouldLog: boolean, whether to log progress to stdout. Default is false. (optional)`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const parsedInput = JSON.parse(input); - - const txs = await this.solanaKit.sendCompressedAirdrop( - parsedInput.mintAddress, - parsedInput.amount, - parsedInput.decimals, - parsedInput.recipients, - parsedInput.priorityFeeInLamports || 30_000, - parsedInput.shouldLog || false, - ); - - return JSON.stringify({ - status: "success", - message: `Airdropped ${parsedInput.amount} tokens to ${parsedInput.recipients.length} recipients.`, - transactionHashes: txs, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaClosePosition extends Tool { - name = "orca_close_position"; - description = `Closes an existing liquidity position in an Orca Whirlpool. This function fetches the position - details using the provided mint address and closes the position with a 1% slippage. - - Inputs (JSON string): - - positionMintAddress: string, the address of the position mint that represents the liquidity position.`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - async _call(input: string): Promise { - try { - const inputFormat = JSON.parse(input); - const positionMintAddress = new PublicKey( - inputFormat.positionMintAddress, - ); - - const txId = await this.solanaKit.orcaClosePosition(positionMintAddress); - - return JSON.stringify({ - status: "success", - message: "Liquidity position closed successfully.", - transaction: txId, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaOrcaCreateCLMM extends Tool { - name = "orca_create_clmm"; - description = `Create a Concentrated Liquidity Market Maker (CLMM) pool on Orca, the most efficient and capital-optimized CLMM on Solana. This function initializes a CLMM pool but does not add liquidity. You can add liquidity later using a centered position or a single-sided position. - - Inputs (JSON string): - - mintDeploy: string, the mint of the token you want to deploy (required). - - mintPair: string, The mint of the token you want to pair the deployed mint with (required). - - initialPrice: number, initial price of mintA in terms of mintB, e.g., 0.001 (required). - - feeTier: number, fee tier in bps. Options: 1, 2, 4, 5, 16, 30, 65, 100, 200 (required).`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - async _call(input: string): Promise { - try { - const inputFormat = JSON.parse(input); - const mintA = new PublicKey(inputFormat.mintDeploy); - const mintB = new PublicKey(inputFormat.mintPair); - const initialPrice = new Decimal(inputFormat.initialPrice); - const feeTier = inputFormat.feeTier; - - if (!feeTier || !(feeTier in FEE_TIERS)) { - throw new Error( - `Invalid feeTier. Available options: ${Object.keys(FEE_TIERS).join( - ", ", - )}`, - ); - } - - const txId = await this.solanaKit.orcaCreateCLMM( - mintA, - mintB, - initialPrice, - feeTier, - ); - - return JSON.stringify({ - status: "success", - message: - "CLMM pool created successfully. Note: No liquidity was added.", - transaction: txId, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaOrcaCreateSingleSideLiquidityPool extends Tool { - name = "orca_create_single_sided_liquidity_pool"; - description = `Create a single-sided liquidity pool on Orca, the most efficient and capital-optimized CLMM platform on Solana. - - This function initializes a single-sided liquidity pool, ideal for community driven project, fair launches, and fundraising. Minimize price impact by setting a narrow price range. - - Inputs (JSON string): - - depositTokenAmount: number, in units of the deposit token including decimals, e.g., 1000000000 (required). - - depositTokenMint: string, mint address of the deposit token, e.g., "DepositTokenMintAddress" (required). - - otherTokenMint: string, mint address of the other token, e.g., "OtherTokenMintAddress" (required). - - initialPrice: number, initial price of the deposit token in terms of the other token, e.g., 0.001 (required). - - maxPrice: number, maximum price at which liquidity is added, e.g., 5.0 (required). - - feeTier: number, fee tier for the pool in bps. Options: 1, 2, 4, 5, 16, 30, 65, 100, 200 (required).`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - async _call(input: string): Promise { - try { - const inputFormat = JSON.parse(input); - const depositTokenAmount = inputFormat.depositTokenAmount; - const depositTokenMint = new PublicKey(inputFormat.depositTokenMint); - const otherTokenMint = new PublicKey(inputFormat.otherTokenMint); - const initialPrice = new Decimal(inputFormat.initialPrice); - const maxPrice = new Decimal(inputFormat.maxPrice); - const feeTier = inputFormat.feeTier; - - if (!feeTier || !(feeTier in FEE_TIERS)) { - throw new Error( - `Invalid feeTier. Available options: ${Object.keys(FEE_TIERS).join( - ", ", - )}`, - ); - } - - const txId = await this.solanaKit.orcaCreateSingleSidedLiquidityPool( - depositTokenAmount, - depositTokenMint, - otherTokenMint, - initialPrice, - maxPrice, - feeTier, - ); - - return JSON.stringify({ - status: "success", - message: "Single-sided Whirlpool created successfully", - transaction: txId, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaOrcaFetchPositions extends Tool { - name = "orca_fetch_positions"; - description = `Fetch all the liquidity positions in an Orca Whirlpool by owner. Returns an object with positiont mint addresses as keys and position status details as values.`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - async _call(): Promise { - try { - const txId = await this.solanaKit.orcaFetchPositions(); - - return JSON.stringify({ - status: "success", - message: "Liquidity positions fetched.", - transaction: txId, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaOrcaOpenCenteredPosition extends Tool { - name = "orca_open_centered_position_with_liquidity"; - description = `Add liquidity to a CLMM by opening a centered position in an Orca Whirlpool, the most efficient liquidity pool on Solana. - - Inputs (JSON string): - - whirlpoolAddress: string, address of the Orca Whirlpool (required). - - priceOffsetBps: number, bps offset (one side) from the current pool price, e.g., 500 for 5% (required). - - inputTokenMint: string, mint address of the deposit token (required). - - inputAmount: number, amount of the deposit token, e.g., 100.0 (required).`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - async _call(input: string): Promise { - try { - const inputFormat = JSON.parse(input); - const whirlpoolAddress = new PublicKey(inputFormat.whirlpoolAddress); - const priceOffsetBps = parseInt(inputFormat.priceOffsetBps, 10); - const inputTokenMint = new PublicKey(inputFormat.inputTokenMint); - const inputAmount = new Decimal(inputFormat.inputAmount); - - if (priceOffsetBps < 0) { - throw new Error( - "Invalid distanceFromCurrentPriceBps. It must be equal or greater than 0.", - ); - } - - const txId = await this.solanaKit.orcaOpenCenteredPositionWithLiquidity( - whirlpoolAddress, - priceOffsetBps, - inputTokenMint, - inputAmount, - ); - - return JSON.stringify({ - status: "success", - message: "Centered liquidity position opened successfully.", - transaction: txId, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaOrcaOpenSingleSidedPosition extends Tool { - name = "orca_open_single_sided_position"; - description = `Add liquidity to a CLMM by opening a single-sided position in an Orca Whirlpool, the most efficient liquidity pool on Solana. - - Inputs (JSON string): - - whirlpoolAddress: string, address of the Orca Whirlpool (required). - - distanceFromCurrentPriceBps: number, distance in basis points from the current price for the position (required). - - widthBps: number, width of the position in basis points (required). - - inputTokenMint: string, mint address of the deposit token (required). - - inputAmount: number, amount of the deposit token, e.g., 100.0 (required).`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - async _call(input: string): Promise { - try { - const inputFormat = JSON.parse(input); - const whirlpoolAddress = new PublicKey(inputFormat.whirlpoolAddress); - const distanceFromCurrentPriceBps = - inputFormat.distanceFromCurrentPriceBps; - const widthBps = inputFormat.widthBps; - const inputTokenMint = new PublicKey(inputFormat.inputTokenMint); - const inputAmount = new Decimal(inputFormat.inputAmount); - - if (distanceFromCurrentPriceBps < 0 || widthBps < 0) { - throw new Error( - "Invalid distanceFromCurrentPriceBps or width. It must be equal or greater than 0.", - ); - } - - const txId = await this.solanaKit.orcaOpenSingleSidedPosition( - whirlpoolAddress, - distanceFromCurrentPriceBps, - widthBps, - inputTokenMint, - inputAmount, - ); - - return JSON.stringify({ - status: "success", - message: "Single-sided liquidity position opened successfully.", - transaction: txId, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaRaydiumCreateAmmV4 extends Tool { - name = "raydium_create_ammV4"; - description = `Raydium's Legacy AMM that requires an OpenBook marketID - - Inputs (input is a json string): - marketId: string (required) - baseAmount: number(int), eg: 111111 (required) - quoteAmount: number(int), eg: 111111 (required) - startTime: number(seconds), eg: now number or zero (required) - `; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - async _call(input: string): Promise { - try { - const inputFormat = JSON.parse(input); - - const tx = await this.solanaKit.raydiumCreateAmmV4( - new PublicKey(inputFormat.marketId), - new BN(inputFormat.baseAmount), - new BN(inputFormat.quoteAmount), - new BN(inputFormat.startTime), - ); - - return JSON.stringify({ - status: "success", - message: "Raydium amm v4 pool created successfully", - transaction: tx, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaRaydiumCreateClmm extends Tool { - name = "raydium_create_clmm"; - description = `Concentrated liquidity market maker, custom liquidity ranges, increased capital efficiency - - Inputs (input is a json string): - mint1: string (required) - mint2: string (required) - configId: string (required) stores pool info, id, index, protocolFeeRate, tradeFeeRate, tickSpacing, fundFeeRate - initialPrice: number, eg: 123.12 (required) - startTime: number(seconds), eg: now number or zero (required) - `; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - async _call(input: string): Promise { - try { - const inputFormat = JSON.parse(input); - - const tx = await this.solanaKit.raydiumCreateClmm( - new PublicKey(inputFormat.mint1), - new PublicKey(inputFormat.mint2), - - new PublicKey(inputFormat.configId), - - new Decimal(inputFormat.initialPrice), - new BN(inputFormat.startTime), - ); - - return JSON.stringify({ - status: "success", - message: "Raydium clmm pool created successfully", - transaction: tx, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaRaydiumCreateCpmm extends Tool { - name = "raydium_create_cpmm"; - description = `Raydium's newest CPMM, does not require marketID, supports Token 2022 standard - - Inputs (input is a json string): - mint1: string (required) - mint2: string (required) - configId: string (required), stores pool info, index, protocolFeeRate, tradeFeeRate, fundFeeRate, createPoolFee - mintAAmount: number(int), eg: 1111 (required) - mintBAmount: number(int), eg: 2222 (required) - startTime: number(seconds), eg: now number or zero (required) - `; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - async _call(input: string): Promise { - try { - const inputFormat = JSON.parse(input); - - const tx = await this.solanaKit.raydiumCreateCpmm( - new PublicKey(inputFormat.mint1), - new PublicKey(inputFormat.mint2), - - new PublicKey(inputFormat.configId), - - new BN(inputFormat.mintAAmount), - new BN(inputFormat.mintBAmount), - - new BN(inputFormat.startTime), - ); - - return JSON.stringify({ - status: "success", - message: "Raydium cpmm pool created successfully", - transaction: tx, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaOpenbookCreateMarket extends Tool { - name = "solana_openbook_create_market"; - description = `Openbook marketId, required for ammv4 - - Inputs (input is a json string): - baseMint: string (required) - quoteMint: string (required) - lotSize: number (required) - tickSize: number (required) - `; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - async _call(input: string): Promise { - try { - const inputFormat = JSON.parse(input); - - const tx = await this.solanaKit.openbookCreateMarket( - new PublicKey(inputFormat.baseMint), - new PublicKey(inputFormat.quoteMint), - - inputFormat.lotSize, - inputFormat.tickSize, - ); - - return JSON.stringify({ - status: "success", - message: "Openbook market created successfully", - transaction: tx, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaManifestCreateMarket extends Tool { - name = "solana_manifest_create_market"; - description = `Manifest market - - Inputs (input is a json string): - baseMint: string (required) - quoteMint: string (required) - `; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - async _call(input: string): Promise { - try { - const inputFormat = JSON.parse(input); - - const tx = await this.solanaKit.manifestCreateMarket( - new PublicKey(inputFormat.baseMint), - new PublicKey(inputFormat.quoteMint), - ); - - return JSON.stringify({ - status: "success", - message: "Create manifest market successfully", - transaction: tx[0], - marketId: tx[1], - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaPythFetchPrice extends Tool { - name = "solana_pyth_fetch_price"; - description = `Fetch the price of a given price feed from Pyth's Hermes service - - Inputs: - priceFeedID: string, the price feed ID, e.g., "0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43" for BTC/USD`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - async _call(input: string): Promise { - try { - const price = await this.solanaKit.pythFetchPrice(input); - const response: PythFetchPriceResponse = { - status: "success", - priceFeedID: input, - price, - }; - return JSON.stringify(response); - } catch (error: any) { - const response: PythFetchPriceResponse = { - status: "error", - priceFeedID: input, - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }; - return JSON.stringify(response); - } - } -} - -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 { - 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 { - 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, - }); - } 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 { - try { - const domains = await this.solanaKit.getOwnedDomainsForTLD(input); - - return JSON.stringify({ - status: "success", - message: "TLD domains fetched successfully", - 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 { - try { - const tlds = await this.solanaKit.getAllDomainsTLDs(); - - return JSON.stringify({ - status: "success", - message: "TLDs fetched successfully", - 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 { - 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 { - 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 class SolanaRockPaperScissorsTool extends Tool { - name = "rock_paper_scissors"; - description = `Play rock paper scissors to win SEND coins. - - Inputs (input is a JSON string): - choice: string, either "rock", "paper", or "scissors" (required) - amount: number, amount of SOL to play with - must be 0.1, 0.01, or 0.005 SOL (required)`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - private validateInput(input: any): void { - if (input.choice !== undefined) { - throw new Error("choice is required."); - } - if ( - input.amount !== undefined && - (typeof input.spaceKB !== "number" || input.spaceKB <= 0) - ) { - throw new Error("amount must be a positive number when provided"); - } - } - - protected async _call(input: string): Promise { - try { - const parsedInput = JSON.parse(input); - this.validateInput(parsedInput); - const result = await this.solanaKit.rockPaperScissors( - Number(parsedInput['"amount"']), - parsedInput['"choice"'].replace(/^"|"$/g, "") as - | "rock" - | "paper" - | "scissors", - ); - - return JSON.stringify({ - status: "success", - message: result, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaTipLinkTool extends Tool { - name = "solana_tiplink"; - description = `Create a TipLink for transferring SOL or SPL tokens. - Input is a JSON string with: - - amount: number (required) - Amount to transfer - - splmintAddress: string (optional) - SPL token mint address`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const parsedInput = JSON.parse(input); - - if (!parsedInput.amount) { - throw new Error("Amount is required"); - } - - const amount = parseFloat(parsedInput.amount); - const splmintAddress = parsedInput.splmintAddress - ? new PublicKey(parsedInput.splmintAddress) - : undefined; - - const { url, signature } = await this.solanaKit.createTiplink( - amount, - splmintAddress, - ); - - return JSON.stringify({ - status: "success", - url, - signature, - amount, - tokenType: splmintAddress ? "SPL" : "SOL", - message: `TipLink created successfully`, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaListNFTForSaleTool extends Tool { - name = "solana_list_nft_for_sale"; - description = `List an NFT for sale on Tensor Trade. - - Inputs (input is a JSON string): - nftMint: string, the mint address of the NFT (required) - price: number, price in SOL (required)`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const parsedInput = JSON.parse(input); - - // Validate NFT ownership first - const nftAccount = - await this.solanaKit.connection.getTokenAccountsByOwner( - this.solanaKit.wallet_address, - { mint: new PublicKey(parsedInput.nftMint) }, - ); - - if (nftAccount.value.length === 0) { - return JSON.stringify({ - status: "error", - message: - "NFT not found in wallet. Please make sure you own this NFT.", - code: "NFT_NOT_FOUND", - }); - } - - const tx = await this.solanaKit.tensorListNFT( - new PublicKey(parsedInput.nftMint), - parsedInput.price, - ); - - return JSON.stringify({ - status: "success", - message: "NFT listed for sale successfully", - transaction: tx, - price: parsedInput.price, - nftMint: parsedInput.nftMint, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export class SolanaCancelNFTListingTool extends Tool { - name = "solana_cancel_nft_listing"; - description = `Cancel an NFT listing on Tensor Trade. - - Inputs (input is a JSON string): - nftMint: string, the mint address of the NFT (required)`; - - constructor(private solanaKit: SolanaAgentKit) { - super(); - } - - protected async _call(input: string): Promise { - try { - const parsedInput = JSON.parse(input); - - const tx = await this.solanaKit.tensorCancelListing( - new PublicKey(parsedInput.nftMint), - ); - - return JSON.stringify({ - status: "success", - message: "NFT listing cancelled successfully", - transaction: tx, - nftMint: parsedInput.nftMint, - }); - } catch (error: any) { - return JSON.stringify({ - status: "error", - message: error.message, - code: error.code || "UNKNOWN_ERROR", - }); - } - } -} - -export function createSolanaTools(solanaKit: SolanaAgentKit) { - return [ - new SolanaBalanceTool(solanaKit), - new SolanaBalanceOtherTool(solanaKit), - new SolanaTransferTool(solanaKit), - new SolanaDeployTokenTool(solanaKit), - new SolanaDeployCollectionTool(solanaKit), - new SolanaMintNFTTool(solanaKit), - new SolanaTradeTool(solanaKit), - new SolanaRequestFundsTool(solanaKit), - new SolanaRegisterDomainTool(solanaKit), - new SolanaGetWalletAddressTool(solanaKit), - new SolanaPumpfunTokenLaunchTool(solanaKit), - new SolanaCreateImageTool(solanaKit), - new SolanaLendAssetTool(solanaKit), - new SolanaTPSCalculatorTool(solanaKit), - new SolanaStakeTool(solanaKit), - new SolanaFetchPriceTool(solanaKit), - new SolanaGetDomainTool(solanaKit), - new SolanaTokenDataTool(solanaKit), - new SolanaTokenDataByTickerTool(solanaKit), - new SolanaCompressedAirdropTool(solanaKit), - new SolanaRaydiumCreateAmmV4(solanaKit), - new SolanaRaydiumCreateClmm(solanaKit), - new SolanaRaydiumCreateCpmm(solanaKit), - new SolanaOpenbookCreateMarket(solanaKit), - new SolanaManifestCreateMarket(solanaKit), - new SolanaLimitOrderTool(solanaKit), - new SolanaCancelAllOrdersTool(solanaKit), - new SolanaWithdrawAllTool(solanaKit), - new SolanaClosePosition(solanaKit), - new SolanaOrcaCreateCLMM(solanaKit), - new SolanaOrcaCreateSingleSideLiquidityPool(solanaKit), - new SolanaOrcaFetchPositions(solanaKit), - new SolanaOrcaOpenCenteredPosition(solanaKit), - new SolanaOrcaOpenSingleSidedPosition(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), - new SolanaRockPaperScissorsTool(solanaKit), - new SolanaTipLinkTool(solanaKit), - new SolanaListNFTForSaleTool(solanaKit), - new SolanaCancelNFTListingTool(solanaKit), - ]; + return tools; } diff --git a/src/tools/get_wallet_address.ts b/src/tools/get_wallet_address.ts new file mode 100644 index 0000000..d0b0ad4 --- /dev/null +++ b/src/tools/get_wallet_address.ts @@ -0,0 +1,10 @@ +import { SolanaAgentKit } from ".."; + +/** + * Get the agents wallet address + * @param agent - SolanaAgentKit instance + * @returns string + */ +export function get_wallet_address(agent: SolanaAgentKit) { + return agent.wallet_address.toBase58(); +} diff --git a/src/tools/index.ts b/src/tools/index.ts index b9f7542..9d66118 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,3 +1,4 @@ +export * from "./get_wallet_address"; export * from "./request_faucet_funds"; export * from "./deploy_token"; export * from "./deploy_collection"; diff --git a/src/vercel-ai/index.ts b/src/vercel-ai/index.ts index 800241e..a9a47d4 100644 --- a/src/vercel-ai/index.ts +++ b/src/vercel-ai/index.ts @@ -1,1489 +1,25 @@ import { tool, type CoreTool } from "ai"; import { SolanaAgentKit } from "../agent"; -import z from "zod"; -import { PublicKey } from "@solana/web3.js"; -import { create_image } from "../tools/create_image"; -import BN from "bn.js"; -import Decimal from "decimal.js"; -import { FEE_TIERS } from "../tools"; -import { GibworkCreateTaskReponse, PythFetchPriceResponse } from "../types"; - -export class VercelAITools { - constructor(private solanaKit: SolanaAgentKit) {} - - solanaBalanceTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.balance", - description: `Get the balance of a Solana wallet or token account. - - If you want to get the balance of your wallet, you don't need to provide the tokenAddress. - If no tokenAddress is provided, the balance will be in SOL. - - Inputs: - tokenAccountAddress: string, eg "So11111111111111111111111111111111111111112" (optional)`, - parameters: z.object({ - tokenAccountAddress: z.string().optional(), - }), - execute: async ({ tokenAccountAddress }) => { - try { - const address = tokenAccountAddress - ? new PublicKey(tokenAccountAddress) - : undefined; - const balance = await this.solanaKit.getBalance(address); - - return { - status: "success", - balance: balance, - token: address ? address.toBase58() : "SOL", - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaTransferTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.transfer", - description: ` - Transfer tokens or SOL to another address ( also called as wallet address ). - - Inputs ( input is a JSON string ): - to: string, eg "8x2dR8Mpzuz2YqyZyZjUbYWKSWesBo5jMx2Q9Y86udVk" (required) - amount: number, eg 1 (required) - mint?: string, eg "So11111111111111111111111111111111111111112" or "SENDdRQtYMWaQrBroBrJ2Q53fgVuq95CV9UPGEvpCxa" (optional) `, - parameters: z.object({ - to: z.string(), - amount: z.number(), - mint: z.string().optional(), - }), - execute: async ({ to, amount, mint }) => { - try { - const recipient = new PublicKey(to); - const mintAddress = mint ? new PublicKey(mint) : undefined; - - const tx = await this.solanaKit.transfer( - recipient, - amount, - mintAddress, - ); - - return { - status: "success", - message: "Transfer completed successfully", - amount: amount, - recipient: to, - token: mint || "SOL", - transaction: tx, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaDeployTokenTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.deployToken", - description: `Deploy a new token on Solana blockchain. - - Inputs (input is a JSON string): - name: string, eg "My Token" (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)`, - parameters: z.object({ - name: z.string(), - uri: z.string(), - symbol: z.string(), - decimals: z.number().optional(), - initialSupply: z.number().optional(), - }), - execute: async ({ - name, - uri, - symbol, - decimals = 9, - initialSupply = 0, - }) => { - try { - const result = await this.solanaKit.deployToken( - name, - uri, - symbol, - decimals, - initialSupply, - ); - - return { - status: "success", - message: "Token deployed successfully", - mintAddress: result.mint.toString(), - decimals: decimals || 9, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaDeployCollectionTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.deployCollection", - description: "Deploy a new NFT collection on Solana blockchain", - parameters: z.object({ - name: z.string(), - uri: z.string(), - royaltyBasisPoints: z.number().optional(), - }), - execute: async ({ name, uri, royaltyBasisPoints = 0 }) => { - try { - const result = await this.solanaKit.deployCollection({ - name, - uri, - royaltyBasisPoints, - }); - return { - status: "success", - message: "Collection deployed successfully", - collectionAddress: result.collectionAddress.toString(), - name, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaMintNFTTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.mintNFT", - description: "Mint a new NFT in a collection on Solana blockchain", - parameters: z.object({ - collectionMint: z.string(), - name: z.string(), - uri: z.string(), - recipient: z.string().optional(), - }), - execute: async ({ collectionMint, name, uri, recipient }) => { - try { - const result = await this.solanaKit.mintNFT( - new PublicKey(collectionMint), - { name, uri }, - recipient - ? new PublicKey(recipient) - : this.solanaKit.wallet_address, - ); - return { - status: "success", - message: "NFT minted successfully", - mintAddress: result.mint.toString(), - metadata: { name, uri }, - recipient: recipient || result.mint.toString(), - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaTradeTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.trade", - description: "Swap tokens using Jupiter Exchange", - parameters: z.object({ - outputMint: z.string(), - inputAmount: z.number(), - inputMint: z.string().optional(), - slippageBps: z.number().optional(), - }), - execute: async ({ outputMint, inputAmount, inputMint, slippageBps }) => { - try { - const tx = await this.solanaKit.trade( - new PublicKey(outputMint), - inputAmount, - inputMint - ? new PublicKey(inputMint) - : new PublicKey("So11111111111111111111111111111111111111112"), - slippageBps, - ); - return { - status: "success", - message: "Trade executed successfully", - transaction: tx, - inputAmount, - inputToken: inputMint || "SOL", - outputToken: outputMint, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaRequestFundsTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.requestFunds", - description: "Request SOL from Solana faucet (devnet/testnet only)", - parameters: z.object({}), - execute: async () => { - try { - await this.solanaKit.requestFaucetFunds(); - return { - status: "success", - message: "Successfully requested faucet funds", - network: this.solanaKit.connection.rpcEndpoint.split("/")[2], - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaRegisterDomainTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.registerDomain", - description: "Register a .sol domain name for your wallet", - parameters: z.object({ - name: z.string().min(1, "Name is required"), - spaceKB: z - .number() - .positive("spaceKB must be a positive number") - .default(1) - .optional(), - }), - execute: async ({ name, spaceKB = 1 }) => { - try { - const tx = await this.solanaKit.registerDomain(name, spaceKB); - return { - status: "success", - message: "Domain registered successfully", - transaction: tx, - domain: `${name}.sol`, - spaceKB, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaResolveDomainTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.resolveDomain", - 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.`, - parameters: z.object({ - domain: z - .string() - .trim() - .min(1, "Domain name is required") - .regex(/\.sol$/, "Domain must end with .sol"), - }), - execute: async ({ domain }) => { - try { - const publicKey = await this.solanaKit.resolveSolDomain(domain); - return { - status: "success", - message: "Domain resolved successfully", - publicKey: publicKey.toBase58(), - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaGetDomainTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.getDomain", - description: - "Retrieve the .sol domain associated with a given account address", - parameters: z.object({ - account: z - .string() - .trim() - .min(1, "Account address is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid Solana account address"), - }), - execute: async ({ account }) => { - try { - const publicKey = new PublicKey(account); - const domain = await this.solanaKit.getPrimaryDomain(publicKey); - return { - status: "success", - message: "Primary domain retrieved successfully", - domain, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaGetWalletAddressTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.getWalletAddress", - description: "Get the wallet address of the agent", - parameters: z.object({}), - execute: async () => { - return { - status: "success", - message: "Wallet address retrieved successfully", - address: this.solanaKit.wallet_address.toString(), - }; - }, - }); - } - - solanaPumpfunTokenLaunchTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.launchPumpfunToken", - description: `Launch a token on Pump.fun. - Do not use this tool for any other purpose, or for creating SPL tokens. - If the user asks you to choose the parameters, you should generate valid values. - For generating the image, you can use the solana_create_image tool.`, - parameters: z.object({ - tokenName: z.string().min(1, "Token name is required"), - tokenTicker: z.string().min(1, "Token ticker is required"), - description: z.string().min(1, "Description is required"), - imageUrl: z.string().url("Invalid image URL"), - twitter: z.string().url("Invalid Twitter URL").optional(), - telegram: z.string().url("Invalid Telegram URL").optional(), - website: z.string().url("Invalid website URL").optional(), - initialLiquiditySOL: z - .number() - .positive("Initial liquidity must be positive") - .optional(), - }), - execute: async ({ - tokenName, - tokenTicker, - description, - imageUrl, - twitter, - telegram, - website, - initialLiquiditySOL, - }) => { - try { - await this.solanaKit.launchPumpFunToken( - tokenName, - tokenTicker, - description, - imageUrl, - // @ts-expect-error Value matches type however TS still shows error - { - twitter, - telegram, - website, - initialLiquiditySOL, - }, - ); - - return { - status: "success", - message: "Token launched successfully on Pump.fun", - tokenName, - tokenTicker, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaCreateImageTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.createImage", - description: - "Create an image using OpenAI's DALL-E. Input should be a string prompt for the image.", - parameters: z.object({ - prompt: z.string().trim().min(1, "Image prompt cannot be empty"), - }), - execute: async ({ prompt }) => { - try { - const result = await create_image(this.solanaKit, prompt); - return { - status: "success", - message: "Image created successfully", - ...result, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaLendAssetTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.lendAsset", - description: - "Lend idle USDC for yield using Lulo (only USDC is supported)", - parameters: z.object({ - amount: z - .number() - .positive("Amount must be positive") - .min(0.000001, "Amount must be at least 0.000001 USDC"), - }), - execute: async ({ amount }) => { - try { - const tx = await this.solanaKit.lendAssets(amount); - return { - status: "success", - message: "Asset lent successfully", - transaction: tx, - amount, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaTPSCalculatorTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.getTPS", - description: - "Get the current TPS (transactions per second) of the Solana network", - parameters: z.object({}), - execute: async () => { - try { - const tps = await this.solanaKit.getTPS(); - return { - status: "success", - message: "TPS fetched successfully", - network: "mainnet-beta", - tps: tps, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaStakeTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.stake", - description: - "Stake your SOL (Solana), also called as SOL staking or liquid staking", - parameters: z.object({ - amount: z - .number() - .positive("Stake amount must be positive") - .min(0.001, "Minimum stake amount is 0.001 SOL"), - }), - execute: async ({ amount }) => { - try { - const tx = await this.solanaKit.stake(amount); - return { - status: "success", - message: "Staked successfully", - transaction: tx, - amount, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaFetchPriceTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.fetchPrice", - description: "Fetch the price of a given token in USDC", - parameters: z.object({ - tokenId: z - .string() - .trim() - .min(1, "Token ID is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid Solana token address"), - }), - execute: async ({ tokenId }) => { - try { - const price = await this.solanaKit.fetchTokenPrice(tokenId); - return { - status: "success", - tokenId, - priceInUSDC: price, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaTokenDataTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.tokenData", - description: "Get the token data for a given token mint address", - parameters: z.object({ - mintAddress: z - .string() - .trim() - .min(1, "Mint address is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid Solana token address"), - }), - execute: async ({ mintAddress }) => { - try { - const tokenData = - await this.solanaKit.getTokenDataByAddress(mintAddress); - return { - status: "success", - tokenData, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaTokenDataByTickerTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.tokenDataByTicker", - description: "Get the token data for a given token ticker", - parameters: z.object({ - ticker: z - .string() - .trim() - .min(1, "Ticker is required") - .max(10, "Ticker too long"), - }), - execute: async ({ ticker }) => { - try { - const tokenData = await this.solanaKit.getTokenDataByTicker(ticker); - return { - status: "success", - tokenData, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaCompressedAirdropTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.compressedAirdrop", - description: "Airdrop SPL tokens with ZK Compression", - parameters: z.object({ - mintAddress: z - .string() - .trim() - .min(1, "Mint address is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid token mint address"), - amount: z.number().positive("Amount must be positive"), - decimals: z - .number() - .int("Decimals must be an integer") - .min(0, "Decimals must be non-negative") - .max(9, "Decimals cannot exceed 9"), - recipients: z - .array(z.string()) - .min(1, "At least one recipient is required") - .refine( - (recipients) => - recipients.every((addr) => { - try { - new PublicKey(addr); - return true; - } catch { - return false; - } - }), - "One or more recipient addresses are invalid", - ), - priorityFeeInLamports: z - .number() - .int("Priority fee must be an integer") - .nonnegative("Priority fee cannot be negative") - .default(30_000), - shouldLog: z.boolean().default(false), - }), - execute: async ({ - mintAddress, - amount, - decimals, - recipients, - priorityFeeInLamports, - shouldLog, - }) => { - try { - const txs = await this.solanaKit.sendCompressedAirdrop( - mintAddress, - amount, - decimals, - recipients, - priorityFeeInLamports, - shouldLog, - ); - return { - status: "success", - message: `Airdropped ${amount} tokens to ${recipients.length} recipients.`, - transactionHashes: txs, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaCreateSingleSidedWhirlpoolTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.createOrcaSingleSidedWhirlpool", - description: "Create a single-sided Whirlpool with liquidity", - parameters: z.object({ - depositTokenAmount: z - .number() - .int("Deposit amount must be an integer") - .positive("Deposit amount must be positive"), - depositTokenMint: z - .string() - .trim() - .min(1, "Deposit token mint address is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid deposit token mint address"), - otherTokenMint: z - .string() - .trim() - .min(1, "Other token mint address is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid other token mint address"), - initialPrice: z.number().positive("Initial price must be positive"), - maxPrice: z - .number() - .positive("Max price must be positive") - .refine((val) => val > 0, "Max price must be greater than 0"), - feeTier: z - .number() - .refine( - (val) => val in FEE_TIERS, - `Invalid fee tier. Available options: ${Object.keys(FEE_TIERS).join(", ")}`, - ), - }), - execute: async ({ - depositTokenAmount, - depositTokenMint, - otherTokenMint, - initialPrice, - maxPrice, - feeTier, - }) => { - try { - const txId = await this.solanaKit.createOrcaSingleSidedWhirlpool( - new BN(depositTokenAmount), - new PublicKey(depositTokenMint), - new PublicKey(otherTokenMint), - new Decimal(initialPrice), - new Decimal(maxPrice), - // @ts-expect-error Value matches type however TS still shows error - feeTier, - ); - - return { - status: "success", - message: "Single-sided Whirlpool created successfully", - transaction: txId, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaRaydiumCreateAmmV4Tool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.raydiumCreateAmmV4", - description: - "Create Raydium's Legacy AMM that requires an OpenBook marketID", - parameters: z.object({ - marketId: z - .string() - .trim() - .min(1, "Market ID is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid market ID"), - baseAmount: z - .number() - .int("Base amount must be an integer") - .positive("Base amount must be positive"), - quoteAmount: z - .number() - .int("Quote amount must be an integer") - .positive("Quote amount must be positive"), - startTime: z - .number() - .int("Start time must be an integer") - .nonnegative("Start time cannot be negative"), - }), - execute: async ({ marketId, baseAmount, quoteAmount, startTime }) => { - try { - const tx = await this.solanaKit.raydiumCreateAmmV4( - new PublicKey(marketId), - new BN(baseAmount), - new BN(quoteAmount), - new BN(startTime), - ); - - return { - status: "success", - message: "Raydium AMM V4 pool created successfully", - transaction: tx, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaRaydiumCreateClmmTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.raydiumCreateClmm", - description: - "Create a Concentrated Liquidity Market Maker (CLMM) pool with custom liquidity ranges", - parameters: z.object({ - mint1: z - .string() - .trim() - .min(1, "First mint address is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid first mint address"), - mint2: z - .string() - .trim() - .min(1, "Second mint address is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid second mint address"), - configId: z - .string() - .trim() - .min(1, "Config ID is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid config ID"), - initialPrice: z.number().positive("Initial price must be positive"), - startTime: z - .number() - .int("Start time must be an integer") - .nonnegative("Start time cannot be negative"), - }), - execute: async ({ mint1, mint2, configId, initialPrice, startTime }) => { - try { - const tx = await this.solanaKit.raydiumCreateClmm( - new PublicKey(mint1), - new PublicKey(mint2), - new PublicKey(configId), - new Decimal(initialPrice), - new BN(startTime), - ); - - return { - status: "success", - message: "Raydium CLMM pool created successfully", - transaction: tx, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaRaydiumCreateCpmmTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.raydiumCreateCpmm", - description: - "Create Raydium's newest CPMM pool (supports Token 2022 standard)", - parameters: z.object({ - mint1: z - .string() - .trim() - .min(1, "First mint address is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid first mint address"), - mint2: z - .string() - .trim() - .min(1, "Second mint address is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid second mint address"), - configId: z - .string() - .trim() - .min(1, "Config ID is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid config ID"), - mintAAmount: z - .number() - .int("Mint A amount must be an integer") - .positive("Mint A amount must be positive"), - mintBAmount: z - .number() - .int("Mint B amount must be an integer") - .positive("Mint B amount must be positive"), - startTime: z - .number() - .int("Start time must be an integer") - .nonnegative("Start time cannot be negative"), - }), - execute: async ({ - mint1, - mint2, - configId, - mintAAmount, - mintBAmount, - startTime, - }) => { - try { - const tx = await this.solanaKit.raydiumCreateCpmm( - new PublicKey(mint1), - new PublicKey(mint2), - new PublicKey(configId), - new BN(mintAAmount), - new BN(mintBAmount), - new BN(startTime), - ); - - return { - status: "success", - message: "Raydium CPMM pool created successfully", - transaction: tx, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaOpenbookCreateMarketTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.openbookCreateMarket", - description: "Create an Openbook market (required for AMM v4)", - parameters: z.object({ - baseMint: z - .string() - .trim() - .min(1, "Base mint address is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid base mint address"), - quoteMint: z - .string() - .trim() - .min(1, "Quote mint address is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid quote mint address"), - lotSize: z.number().positive("Lot size must be positive"), - tickSize: z.number().positive("Tick size must be positive"), - }), - execute: async ({ baseMint, quoteMint, lotSize, tickSize }) => { - try { - const tx = await this.solanaKit.openbookCreateMarket( - new PublicKey(baseMint), - new PublicKey(quoteMint), - lotSize, - tickSize, - ); - - return { - status: "success", - message: "Openbook market created successfully", - transaction: tx, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "UNKNOWN_ERROR", - }; - } - }, - }); - } - - solanaPythFetchPriceTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.pythFetchPrice", - description: - "Fetch the price of a given price feed from Pyth's Hermes service", - parameters: z.object({ - priceFeedID: z - .string() - .trim() - .min(1, "Price feed ID is required") - .regex( - /^0x[a-fA-F0-9]{64}$/, - "Invalid price feed ID format. Must be a 32-byte hex string starting with 0x", - ), - }), - execute: async ({ priceFeedID }) => { - try { - const price = await this.solanaKit.pythFetchPrice(priceFeedID); - return { - status: "success", - priceFeedID, - price, - } as PythFetchPriceResponse; - } catch (e: any) { - return { - status: "error", - priceFeedID, - message: e.message, - code: e.code || "UNKNOWN_ERROR", - } as PythFetchPriceResponse; - } - }, - }); - } - - solanaResolveAllDomainsTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.resolveAllDomains", - 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.`, - parameters: z.object({ - domain: z - .string() - .trim() - .min(1, "Domain name is required") - .refine( - (val) => !val.endsWith(".sol"), - "This tool cannot be used for .sol domains. Use solana.resolveDomain instead.", - ) - .refine( - (val) => val.includes("."), - "Invalid domain format. Must include a TLD (e.g., .blink, .bonk)", - ), - }), - execute: async ({ domain }) => { - try { - const owner = await this.solanaKit.resolveAllDomains(domain); - - if (!owner) { - return { - status: "error", - message: "Domain not found", - code: "DOMAIN_NOT_FOUND", - }; - } - - return { - status: "success", - message: "Domain resolved successfully", - owner: owner.toString(), - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "DOMAIN_RESOLUTION_ERROR", - }; - } - }, - }); - } - - solanaGetOwnedDomainsTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.getOwnedDomains", - description: "Get all domains owned by a specific wallet address", - parameters: z.object({ - owner: z - .string() - .trim() - .min(1, "Owner address is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid Solana wallet address"), - }), - execute: async ({ owner }) => { - try { - const ownerPubkey = new PublicKey(owner); - const domains = await this.solanaKit.getOwnedAllDomains(ownerPubkey); - return { - status: "success", - message: "Owned domains fetched successfully", - domains, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "FETCH_OWNED_DOMAINS_ERROR", - }; - } - }, - }); - } - - solanaGetOwnedTldDomainsTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.getOwnedTldDomains", - description: - "Get all domains owned by the agent's wallet for a specific TLD", - parameters: z.object({ - tld: z - .string() - .trim() - .min(1, "TLD is required") - .regex( - /^[a-zA-Z0-9]+$/, - "TLD must contain only alphanumeric characters", - ), - }), - execute: async ({ tld }) => { - try { - const domains = await this.solanaKit.getOwnedDomainsForTLD(tld); - return { - status: "success", - message: "TLD domains fetched successfully", - domains, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "FETCH_TLD_DOMAINS_ERROR", - }; - } - }, - }); - } - - solanaGetAllTldsTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.getAllTlds", - description: - "Get all active top-level domains (TLDs) in the AllDomains Name Service", - parameters: z.object({}), - execute: async () => { - try { - const tlds = await this.solanaKit.getAllDomainsTLDs(); - return { - status: "success", - message: "TLDs fetched successfully", - tlds, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "FETCH_TLDS_ERROR", - }; - } - }, - }); - } - - solanaGetMainDomainTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.getMainDomain", - description: "Get the main/favorite domain for a given wallet address", - parameters: z.object({ - owner: z - .string() - .trim() - .min(1, "Owner address is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid Solana wallet address"), - }), - execute: async ({ owner }) => { - try { - const ownerPubkey = new PublicKey(owner); - const mainDomain = - await this.solanaKit.getMainAllDomainsDomain(ownerPubkey); - return { - status: "success", - message: "Main domain fetched successfully", - domain: mainDomain, - }; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "FETCH_MAIN_DOMAIN_ERROR", - }; - } - }, - }); - } - - solanaCreateGibworkTaskTool(): CoreTool { - return tool({ - // @ts-expect-error Value matches type however TS still shows error - id: "solana.createGibworkTask", - description: "Create a task on Gibwork", - parameters: z.object({ - title: z - .string() - .trim() - .min(1, "Title is required") - .max(200, "Title too long"), - content: z.string().trim().min(1, "Description is required"), - requirements: z.string().trim().min(1, "Requirements are required"), - tags: z - .array(z.string()) - .min(1, "At least one tag is required") - .max(10, "Maximum 10 tags allowed"), - payer: z - .string() - .trim() - .optional() - .refine((val) => { - if (!val) { - return true; - } - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid payer wallet address"), - tokenMintAddress: z - .string() - .trim() - .min(1, "Token mint address is required") - .refine((val) => { - try { - new PublicKey(val); - return true; - } catch { - return false; - } - }, "Invalid token mint address"), - amount: z - .number() - .positive("Amount must be positive") - .min(0.000001, "Amount must be at least 0.000001"), - }), - execute: async ({ - title, - content, - requirements, - tags, - tokenMintAddress, - amount, - payer, - }) => { - try { - const taskData = await this.solanaKit.createGibworkTask( - title, - content, - requirements, - tags, - tokenMintAddress, - amount, - payer, - ); - - return { - status: "success", - taskId: taskData.taskId, - signature: taskData.signature, - } as GibworkCreateTaskReponse; - } catch (e: any) { - return { - status: "error", - message: e.message, - code: e.code || "CREATE_TASK_ERROR", - }; - } - }, - }); - } -} +import { executeAction } from "../utils/actionExecutor"; +import { ACTIONS } from "../actions"; export function createSolanaTools( - solanaKit: SolanaAgentKit, + solanaAgentKit: SolanaAgentKit, ): Record { - const vercelTools = new VercelAITools(solanaKit); + const tools: Record = {}; + const actionKeys = Object.keys(ACTIONS); - return { - solanaBalanceTool: vercelTools.solanaBalanceTool(), - solanaTransferTool: vercelTools.solanaTransferTool(), - solanaDeployTokenTool: vercelTools.solanaDeployTokenTool(), - solanaDeployCollectionTool: vercelTools.solanaDeployCollectionTool(), - solanaMintNFTTool: vercelTools.solanaMintNFTTool(), - solanaTradeTool: vercelTools.solanaTradeTool(), - solanaRequestFundsTool: vercelTools.solanaRequestFundsTool(), - solanaRegisterDomainTool: vercelTools.solanaRegisterDomainTool(), - solanaGetWalletAddressTool: vercelTools.solanaGetWalletAddressTool(), - solanaPumpfunTokenLaunchTool: vercelTools.solanaPumpfunTokenLaunchTool(), - solanaCreateImageTool: vercelTools.solanaCreateImageTool(), - solanaLendAssetTool: vercelTools.solanaLendAssetTool(), - solanaTPSCalculatorTool: vercelTools.solanaTPSCalculatorTool(), - solanaStakeTool: vercelTools.solanaStakeTool(), - solanaFetchPriceTool: vercelTools.solanaFetchPriceTool(), - solanaTokenDataTool: vercelTools.solanaTokenDataTool(), - solanaTokenDataByTickerTool: vercelTools.solanaTokenDataByTickerTool(), - solanaCompressedAirdropTool: vercelTools.solanaCompressedAirdropTool(), - solanaRaydiumCreateAmmV4Tool: vercelTools.solanaRaydiumCreateAmmV4Tool(), - solanaRaydiumCreateClmmTool: vercelTools.solanaRaydiumCreateClmmTool(), - solanaRaydiumCreateCpmmTool: vercelTools.solanaRaydiumCreateCpmmTool(), - solanaOpenbookCreateMarketTool: - vercelTools.solanaOpenbookCreateMarketTool(), - solanaCreateSingleSidedWhirlpoolTool: - vercelTools.solanaCreateSingleSidedWhirlpoolTool(), - solanaPythFetchPriceTool: vercelTools.solanaPythFetchPriceTool(), - solanaResolveDomainTool: vercelTools.solanaResolveDomainTool(), - solanaResolveAllDomainsTool: vercelTools.solanaResolveAllDomainsTool(), - solanaGetDomainTool: vercelTools.solanaGetDomainTool(), - solanaGetOwnedDomainsTool: vercelTools.solanaGetOwnedDomainsTool(), - solanaGetOwnedTldDomainsTool: vercelTools.solanaGetOwnedTldDomainsTool(), - solanaGetAllTldsTool: vercelTools.solanaGetAllTldsTool(), - solanaGetMainDomainTool: vercelTools.solanaGetMainDomainTool(), - solanaCreateGibworkTaskTool: vercelTools.solanaCreateGibworkTaskTool(), - }; + for (const key of actionKeys) { + const action = ACTIONS[key as keyof typeof ACTIONS]; + tools[key] = tool({ + // @ts-expect-error Value matches type however TS still shows error + id: action.name, + description: action.description, + parameters: action.schema, + execute: async (params) => + await executeAction(action, solanaAgentKit, params), + }); + } + + return tools; } diff --git a/test/agent_sdks/vercel_ai.ts b/test/agent_sdks/vercel_ai.ts index 09c5f60..8fd19c1 100644 --- a/test/agent_sdks/vercel_ai.ts +++ b/test/agent_sdks/vercel_ai.ts @@ -8,186 +8,191 @@ import { createOpenAI } from "@ai-sdk/openai"; dotenv.config(); function validateEnvironment(): void { - const missingVars: string[] = []; - const requiredVars = ["OPENAI_API_KEY", "RPC_URL", "SOLANA_PRIVATE_KEY"]; + const missingVars: string[] = []; + const requiredVars = ["OPENAI_API_KEY", "RPC_URL", "SOLANA_PRIVATE_KEY"]; - requiredVars.forEach((varName) => { - if (!process.env[varName]) { - missingVars.push(varName); - } - }); + requiredVars.forEach((varName) => { + if (!process.env[varName]) { + missingVars.push(varName); + } + }); - if (missingVars.length > 0) { - console.error("Error: Required environment variables are not set"); - missingVars.forEach((varName) => { - console.error(`${varName}=your_${varName.toLowerCase()}_here`); - }); - process.exit(1); - } + if (missingVars.length > 0) { + console.error("Error: Required environment variables are not set"); + missingVars.forEach((varName) => { + console.error(`${varName}=your_${varName.toLowerCase()}_here`); + }); + process.exit(1); + } } validateEnvironment(); async function runAutonomousMode(interval = 10) { - console.log("Starting autonomous mode..."); - const openai = createOpenAI({ - apiKey: process.env.OPENAI_API_KEY as string, - }); + console.log("Starting autonomous mode..."); + const openai = createOpenAI({ + apiKey: process.env.OPENAI_API_KEY as string, + }); - const solanaAgent = new SolanaAgentKit( - process.env.SOLANA_PRIVATE_KEY!, - process.env.RPC_URL, - process.env.OPENAI_API_KEY!, - ); + const solanaAgent = new SolanaAgentKit( + process.env.SOLANA_PRIVATE_KEY!, + process.env.RPC_URL!, + { + OPENAI_API_KEY: process.env.OPENAI_API_KEY!, + }, + ); - const tools = createVercelAITools(solanaAgent); + const tools = createVercelAITools(solanaAgent); - while (true) { - try { - const thought = - "Be creative and do something interesting on the blockchain. " + - "Choose an action or set of actions and execute it that highlights your abilities."; + while (true) { + try { + const thought = + "Be creative and do something interesting on the blockchain. " + + "Choose an action or set of actions and execute it that highlights your abilities."; - const response = streamText({ - prompt: thought, - tools, - model: openai("gpt-4o-mini"), - temperature: 0.7, - system: `You are a helpful agent that can interact onchain using the Solana Agent Kit. You are + const response = streamText({ + prompt: thought, + tools, + model: openai("gpt-4o-mini"), + temperature: 0.7, + system: `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.`, - maxSteps: 10, - }); + maxSteps: 10, + }); - for await (const textPart of response.textStream) { - process.stdout.write(textPart); - } - console.log(); + for await (const textPart of response.textStream) { + process.stdout.write(textPart); + } + console.log(); - await new Promise((resolve) => setTimeout(resolve, interval * 1000)); - } catch (error) { - if (error instanceof Error) { - console.error("Error:", error.message); - } - process.exit(1); - } - } + await new Promise((resolve) => setTimeout(resolve, interval * 1000)); + } catch (error) { + if (error instanceof Error) { + console.error("Error:", error.message); + } + process.exit(1); + } + } } async function runChatMode() { - console.log("Starting chat mode... Type 'exit' to end."); - const openai = createOpenAI({ - apiKey: process.env.OPENAI_API_KEY as string, - }); + console.log("Starting chat mode... Type 'exit' to end."); + const openai = createOpenAI({ + apiKey: process.env.OPENAI_API_KEY as string, + }); - const solanaAgent = new SolanaAgentKit( - process.env.SOLANA_PRIVATE_KEY!, - process.env.RPC_URL, - process.env.OPENAI_API_KEY!, - ); + const solanaAgent = new SolanaAgentKit( + process.env.SOLANA_PRIVATE_KEY!, + process.env.RPC_URL!, + { + OPENAI_API_KEY: process.env.OPENAI_API_KEY!, + }, + ); - const tools = createVercelAITools(solanaAgent); + const tools = createVercelAITools(solanaAgent); + console.log(tools); - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); - const question = (prompt: string): Promise => - new Promise((resolve) => rl.question(prompt, resolve)); + const question = (prompt: string): Promise => + new Promise((resolve) => rl.question(prompt, resolve)); - try { - while (true) { - const userInput = await question("\nPrompt: "); + try { + while (true) { + const userInput = await question("\nPrompt: "); - if (userInput.toLowerCase() === "exit") { - break; - } + if (userInput.toLowerCase() === "exit") { + break; + } - const response = streamText({ - prompt: userInput, - tools, - model: openai("gpt-4o-mini"), - temperature: 0.7, - system: `You are a helpful agent that can interact onchain using the Solana Agent Kit. You are + const response = streamText({ + prompt: userInput, + tools, + model: openai("gpt-4o-mini"), + temperature: 0.7, + system: `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.`, - maxSteps: 10, - }); + maxSteps: 10, + }); - for await (const textPart of response.textStream) { - process.stdout.write(textPart); - } - console.log(); - } - } catch (error) { - if (error instanceof Error) { - console.error("Error:", error.message); - } - process.exit(1); - } finally { - rl.close(); - } + for await (const textPart of response.textStream) { + process.stdout.write(textPart); + } + console.log(); + } + } catch (error) { + if (error instanceof Error) { + console.error("Error:", error.message); + } + process.exit(1); + } finally { + rl.close(); + } } async function chooseMode(): Promise<"chat" | "auto"> { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); - const question = (prompt: string): Promise => - new Promise((resolve) => rl.question(prompt, resolve)); + const question = (prompt: string): Promise => + new Promise((resolve) => rl.question(prompt, resolve)); - while (true) { - console.log("\nAvailable modes:"); - console.log("1. chat - Interactive chat mode"); - console.log("2. auto - Autonomous action mode"); + while (true) { + console.log("\nAvailable modes:"); + console.log("1. chat - Interactive chat mode"); + console.log("2. auto - Autonomous action mode"); - const choice = (await question("\nChoose a mode (enter number or name): ")) - .toLowerCase() - .trim(); + const choice = (await question("\nChoose a mode (enter number or name): ")) + .toLowerCase() + .trim(); - rl.close(); + rl.close(); - if (choice === "1" || choice === "chat") { - return "chat"; - } else if (choice === "2" || choice === "auto") { - return "auto"; - } - console.log("Invalid choice. Please try again."); - } + if (choice === "1" || choice === "chat") { + return "chat"; + } else if (choice === "2" || choice === "auto") { + return "auto"; + } + console.log("Invalid choice. Please try again."); + } } async function main() { - try { - console.log("Starting Agent..."); - const mode = await chooseMode(); + try { + console.log("Starting Agent..."); + const mode = await chooseMode(); - if (mode === "chat") { - await runChatMode(); - } else { - await runAutonomousMode(); - } - } catch (error) { - if (error instanceof Error) { - console.error("Error:", error.message); - } - process.exit(1); - } + if (mode === "chat") { + await runChatMode(); + } else { + await runAutonomousMode(); + } + } catch (error) { + if (error instanceof Error) { + console.error("Error:", error.message); + } + process.exit(1); + } } if (require.main === module) { - main().catch((error) => { - console.error("Fatal error:", error); - process.exit(1); - }); + main().catch((error) => { + console.error("Fatal error:", error); + process.exit(1); + }); }