UNPKG

solana-agent-kit

Version:
639 lines (619 loc) 20.4 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { KeypairWallet: () => KeypairWallet, SolanaAgentKit: () => SolanaAgentKit, createLangchainTools: () => createLangchainTools, createOpenAITools: () => createOpenAITools, createVercelAITools: () => createSolanaTools, executeAction: () => executeAction, feeTiers: () => feeTiers, getActionExamples: () => getActionExamples, getComputeBudgetInstructions: () => getComputeBudgetInstructions, getMintInfo: () => getMintInfo, isVersionedTransaction: () => isVersionedTransaction, sendTx: () => sendTx, signOrSendTX: () => signOrSendTX }); module.exports = __toCommonJS(index_exports); // src/agent/index.ts var import_web3 = require("@solana/web3.js"); var SolanaAgentKit = class { connection; config; wallet; evmWallet; plugins = /* @__PURE__ */ new Map(); methods = {}; actions = []; constructor(wallet, rpc_url, config, evmWallet) { this.connection = new import_web3.Connection(rpc_url); this.wallet = wallet; this.config = config; this.evmWallet = evmWallet; } /** * Adds a plugin and registers its methods inside `methods` */ use(plugin) { if (this.plugins.has(plugin.name)) { return this; } plugin.initialize(this); for (const [methodName, method] of Object.entries(plugin.methods)) { if (this.methods[methodName]) { throw new Error(`Method ${methodName} already exists in methods`); } this.methods[methodName] = method.bind(plugin); } for (const action of plugin.actions) { this.actions.push(action); } this.plugins.set(plugin.name, plugin); return this; } }; // src/utils/actionExecutor.ts async function executeAction(action, agent, input) { try { const validatedInput = action.schema.parse(input); const result = await action.handler(agent, validatedInput); return { status: "success", ...result }; } catch (error) { if (error.errors) { return { status: "error", message: "Validation error", details: error.errors, code: "VALIDATION_ERROR" }; } return { status: "error", message: error.message, code: error.code || "EXECUTION_ERROR" }; } } function getActionExamples(action) { return action.examples.flat().map((example) => { return `Input: ${JSON.stringify(example.input, null, 2)} Output: ${JSON.stringify(example.output, null, 2)} Explanation: ${example.explanation} ---`; }).join("\n"); } // src/openai/index.ts var import_agents = require("@openai/agents"); // src/openai/utils.ts var import_zod = require("zod"); function mapZodTypeToJsonSchema(zodType) { const isNullable = zodType instanceof import_zod.z.ZodNullable; let coreType = isNullable ? zodType._def.innerType : zodType; while (coreType instanceof import_zod.z.ZodOptional || coreType instanceof import_zod.z.ZodDefault || coreType instanceof import_zod.z.ZodEffects) { coreType = coreType instanceof import_zod.z.ZodEffects ? coreType._def.schema : coreType._def.innerType; } const typeName = coreType._def.typeName; let jsonSchema = { type: "string" }; switch (typeName) { case "ZodNullable": jsonSchema.type = mapZodTypeToJsonSchema(coreType._def.innerType).type; if (!jsonSchema.enum) { jsonSchema.enum = [""]; } else { jsonSchema.enum.push(""); } break; case "ZodString": jsonSchema.type = "string"; break; case "ZodNumber": case "ZodBigInt": jsonSchema.type = "number"; break; case "ZodBoolean": jsonSchema.type = "boolean"; break; case "ZodEnum": case "ZodNativeEnum": jsonSchema.type = "string"; if (!(coreType._def.values instanceof Array)) { jsonSchema.enum = Object.values(coreType._def.values).map( (v) => v.toString() ); break; } jsonSchema.enum = coreType._def.values; break; case "ZodLiteral": jsonSchema.type = typeof coreType._def.value; jsonSchema.enum = [coreType._def.value]; break; case "ZodArray": jsonSchema.type = "array"; jsonSchema.items = mapZodTypeToJsonSchema(coreType._def.type); break; case "ZodObject": jsonSchema.type = "object"; const shape = coreType.shape; jsonSchema.properties = {}; for (const key in shape) { jsonSchema.properties[key] = mapZodTypeToJsonSchema(shape[key]); } jsonSchema.required = Object.keys(shape); break; default: throw new Error(`Unsupported Zod type: ${typeName}`); } if (coreType.description) { jsonSchema.description = coreType.description; } if (isNullable) { if (jsonSchema.enum) { if (!jsonSchema.enum.includes(null)) { jsonSchema.enum.push(null); } } else if (typeof jsonSchema.type === "string") { jsonSchema.type = [jsonSchema.type, "null"]; } } jsonSchema.additionalProperties = false; return jsonSchema; } function zodToOpenAITool(schema, name, description) { if (!(schema instanceof import_zod.ZodObject)) { throw new Error("The provided schema must be a ZodObject."); } const shape = schema.shape; const properties = {}; for (const key in shape) { properties[key] = mapZodTypeToJsonSchema(shape[key]); } const required = Object.keys(properties); const parameters = { type: "object", properties, required, // This will now correctly list all properties. additionalProperties: false }; return { type: "function", function: { name, description, parameters } }; } // src/openai/index.ts function createOpenAITools(solanaAgentKit, actions) { const tools = []; if (actions.length > 128) { console.warn( `Too many actions provided. Only a maximum of 128 actions allowed. You provided ${actions.length}, the last ${actions.length - 128} will be ignored.` ); } for (const [_index, action] of actions.slice(0, 127).entries()) { tools.push( (0, import_agents.tool)({ name: action.name, description: action.description, execute: async (params) => await executeAction(action, solanaAgentKit, params), parameters: zodToOpenAITool( action.schema, action.name, action.description ).function.parameters }) ); } return tools; } // src/langchain/index.ts var import_tools = require("@langchain/core/tools"); // src/utils/zod.ts var import_zod2 = require("zod"); function transformToZodObject(schema) { if (schema instanceof import_zod2.ZodObject) { return schema; } throw new Error( `The provided schema is not a ZodObject: ${JSON.stringify(schema)}` ); } // src/langchain/index.ts function createLangchainTools(solanaAgentKit, actions) { if (actions.length > 128) { console.warn( `Too many actions provided. Only a maximum of 128 actions allowed. You provided ${actions.length}, the last ${actions.length - 128} will be ignored.` ); } const tools = actions.slice(0, 127).map((action) => { const toolInstance = (0, import_tools.tool)( async (inputs) => JSON.stringify(await action.handler(solanaAgentKit, inputs)), { name: action.name, description: ` ${action.description} Similes: ${action.similes.map( (simile) => ` ${simile} ` )} Examples: ${action.examples.map( (example) => ` Input: ${JSON.stringify(example[0].input)} Output: ${JSON.stringify(example[0].output)} Explanation: ${example[0].explanation} ` )}`, // convert action.schema from ZodType to ZodObject schema: transformToZodObject(action.schema) } ); return toolInstance; }); return tools; } // src/vercel-ai/index.ts var import_ai = require("ai"); function createSolanaTools(solanaAgentKit, actions) { const tools = {}; if (actions.length > 128) { console.warn( `Too many actions provided. Only a maximum of 128 actions allowed. You provided ${actions.length}, the last ${actions.length - 128} will be ignored.` ); } for (const [index, action] of actions.slice(0, 127).entries()) { tools[index.toString()] = (0, import_ai.tool)({ id: action.name, description: ` ${action.description} Similes: ${action.similes.map( (simile) => ` ${simile} ` )} Examples: ${action.examples.map( (example) => ` Input: ${JSON.stringify(example[0].input)} Output: ${JSON.stringify(example[0].output)} Explanation: ${example[0].explanation} ` )} `.slice(0, 1023), parameters: action.schema, execute: async (params) => await executeAction(action, solanaAgentKit, params) }); } return tools; } // src/types/wallet.ts var import_web34 = require("@solana/web3.js"); // src/utils/send_tx.ts var import_web32 = require("@solana/web3.js"); var import_web33 = require("@solana/web3.js"); var import_bs58 = __toESM(require("bs58"), 1); var feeTiers = { min: 0.01, mid: 0.5, max: 0.95 }; async function getComputeBudgetInstructions(agent, instructions, feeTier) { const { blockhash, lastValidBlockHeight } = await agent.connection.getLatestBlockhash(); const messageV0 = new import_web32.TransactionMessage({ payerKey: agent.wallet.publicKey, recentBlockhash: blockhash, instructions }).compileToV0Message(); const transaction = new import_web32.VersionedTransaction(messageV0); const simulatedTx = await agent.connection.simulateTransaction(transaction); const estimatedComputeUnits = simulatedTx.value.unitsConsumed; const safeComputeUnits = Math.ceil( estimatedComputeUnits ? Math.max(estimatedComputeUnits + 1e5, estimatedComputeUnits * 1.2) : 2e5 ); const computeBudgetLimitInstruction = import_web33.ComputeBudgetProgram.setComputeUnitLimit({ units: safeComputeUnits }); let priorityFee; if (agent.config?.HELIUS_API_KEY) { const legacyTransaction = new import_web32.Transaction(); legacyTransaction.recentBlockhash = blockhash; legacyTransaction.lastValidBlockHeight = lastValidBlockHeight; legacyTransaction.feePayer = agent.wallet.publicKey; legacyTransaction.add(computeBudgetLimitInstruction, ...instructions); const signedTransaction = await agent.wallet.signTransaction(legacyTransaction); const response = await fetch( `https://mainnet.helius-rpc.com/?api-key=${agent.config?.HELIUS_API_KEY}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: "1", method: "getPriorityFeeEstimate", params: [ { transaction: import_bs58.default.encode( signedTransaction.serialize() ), options: { priorityLevel: feeTier === "min" ? "Min" : feeTier === "mid" ? "Medium" : "High" } } ] }) } ); const data = await response.json(); if (data.error) { throw new Error("Error fetching priority fee from Helius API"); } priorityFee = data.result.priorityFeeEstimate; } else { priorityFee = await agent.connection.getRecentPrioritizationFees().then( (fees) => fees.sort((a, b) => a.prioritizationFee - b.prioritizationFee)[Math.floor(fees.length * feeTiers[feeTier])].prioritizationFee ); } const computeBudgetPriorityFeeInstructions = import_web33.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: priorityFee }); return { computeBudgetLimitInstruction, computeBudgetPriorityFeeInstructions }; } async function sendTx(agent, instructions, otherKeypairs, feeTier) { const ixComputeBudget = await getComputeBudgetInstructions( agent, instructions, feeTier ?? "mid" ); const allInstructions = [ ixComputeBudget.computeBudgetLimitInstruction, ixComputeBudget.computeBudgetPriorityFeeInstructions, ...instructions ]; const { blockhash } = await agent.connection.getLatestBlockhash(); const messageV0 = new import_web32.TransactionMessage({ payerKey: agent.wallet.publicKey, recentBlockhash: blockhash, instructions: allInstructions }).compileToV0Message(); const transaction = new import_web32.VersionedTransaction(messageV0); transaction.sign([...otherKeypairs ?? []]); const signedTransaction = await agent.wallet.signTransaction(transaction); const timeoutMs = 9e4; const startTime = Date.now(); while (Date.now() - startTime < timeoutMs) { const transactionStartTime = Date.now(); const signature = await agent.connection.sendTransaction( signedTransaction, { maxRetries: 0, skipPreflight: false } ); const statuses = await agent.connection.getSignatureStatuses([signature]); if (statuses.value[0]) { if (!statuses.value[0].err) { return signature; } throw new Error( `Transaction failed: ${statuses.value[0].err.toString()}` ); } const elapsedTime = Date.now() - transactionStartTime; const remainingTime = Math.max(0, 1e3 - elapsedTime); if (remainingTime > 0) { await new Promise((resolve) => setTimeout(resolve, remainingTime)); } } throw new Error("Transaction timeout"); } // src/types/wallet.ts async function signOrSendTX(agent, instructionsOrTransaction, otherKeypairs, feeTier) { if (Array.isArray(instructionsOrTransaction) && (instructionsOrTransaction[0] instanceof import_web34.Transaction || instructionsOrTransaction[0] instanceof import_web34.VersionedTransaction)) { if (agent.config.signOnly) { return await agent.wallet.signAllTransactions( instructionsOrTransaction ); } const txSigs = []; for (const tx of instructionsOrTransaction) { if (agent.wallet.signAndSendTransaction) { const { signature } = await agent.wallet.signAndSendTransaction( tx ); txSigs.push(signature); } throw new Error( "Wallet does not support signAndSendTransaction please implement it manually or use the signOnly option" ); } return txSigs; } if (instructionsOrTransaction instanceof import_web34.Transaction || instructionsOrTransaction instanceof import_web34.VersionedTransaction) { if (agent.config.signOnly) { return await agent.wallet.signTransaction(instructionsOrTransaction); } if (!agent.wallet.signAndSendTransaction) { throw new Error( "Wallet does not support signAndSendTransaction please implement it manually or use the signOnly option" ); } return (await agent.wallet.signAndSendTransaction(instructionsOrTransaction)).signature; } const ixComputeBudget = await getComputeBudgetInstructions( agent, instructionsOrTransaction, feeTier ?? "mid" ); const allInstructions = [ ixComputeBudget.computeBudgetLimitInstruction, ixComputeBudget.computeBudgetPriorityFeeInstructions, ...instructionsOrTransaction ]; const { blockhash } = await agent.connection.getLatestBlockhash(); const messageV0 = new import_web34.TransactionMessage({ payerKey: agent.wallet.publicKey, recentBlockhash: blockhash, instructions: allInstructions }).compileToV0Message(); const transaction = new import_web34.VersionedTransaction(messageV0); transaction.sign([...otherKeypairs ?? []]); const signedTransaction = await agent.wallet.signTransaction(transaction); if (agent.config.signOnly) { return signedTransaction; } return sendTx( agent, instructionsOrTransaction, otherKeypairs, feeTier ); } // src/utils/keypairWallet.ts var import_web35 = require("@solana/web3.js"); var import_tweetnacl = __toESM(require("tweetnacl"), 1); var isVersionedTransaction = (tx) => { return "version" in tx; }; var KeypairWallet = class { publicKey; payer; rpcUrl; /** * Constructs a KeypairWallet with a given Keypair * @param keypair - The Keypair to use for signing transactions */ constructor(keypair, rpcUrl) { this.publicKey = keypair.publicKey; this.payer = keypair; this.rpcUrl = rpcUrl; } defaultOptions = { preflightCommitment: "processed", commitment: "processed" }; async signTransaction(transaction) { if (isVersionedTransaction(transaction)) { transaction.sign([this.payer]); } else { transaction.partialSign(this.payer); } return transaction; } async signAllTransactions(txs) { return txs.map((t) => { if (isVersionedTransaction(t)) { t.sign([this.payer]); } else { t.partialSign(this.payer); } return t; }); } async sendTransaction(transaction) { const connection = new import_web35.Connection(this.rpcUrl); if (transaction instanceof import_web35.VersionedTransaction) { transaction.sign([this.payer]); } else { transaction.partialSign(this.payer); } return await connection.sendRawTransaction(transaction.serialize()); } async signMessage(message) { const signature = import_tweetnacl.default.sign.detached(message, this.payer.secretKey); return signature; } async signAndSendTransaction(transaction, options) { const connection = new import_web35.Connection(this.rpcUrl); if (transaction instanceof import_web35.VersionedTransaction) { transaction.sign([this.payer]); } else { transaction.partialSign(this.payer); } const signature = await connection.sendRawTransaction( transaction.serialize(), options ); return { signature }; } }; // src/utils/getMintInfo.ts var import_spl_token = require("@solana/spl-token"); var import_web36 = require("@solana/web3.js"); async function getMintInfo(connection, mint, commitment) { const publicKey = new import_web36.PublicKey(mint); try { const mintInfo = await (0, import_spl_token.getMint)( connection, publicKey, commitment, import_spl_token.TOKEN_PROGRAM_ID ); return mintInfo; } catch (e) { if (e instanceof import_spl_token.TokenInvalidAccountOwnerError) { const mintInfo = await (0, import_spl_token.getMint)( connection, publicKey, commitment, import_spl_token.TOKEN_2022_PROGRAM_ID ); return mintInfo; } throw new Error( `Failed to fetch mint info for token ${mint}: ${e instanceof Error ? e.message : "Unknown error"}` ); } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { KeypairWallet, SolanaAgentKit, createLangchainTools, createOpenAITools, createVercelAITools, executeAction, feeTiers, getActionExamples, getComputeBudgetInstructions, getMintInfo, isVersionedTransaction, sendTx, signOrSendTX });