solana-agent-kit
Version:
connect any ai agents to solana protocols
639 lines (619 loc) • 20.4 kB
JavaScript
;
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
});