mirror of
https://github.com/d0zingcat/solana-agent-kit.git
synced 2026-05-14 07:26:46 +00:00
feat: Enhance Solana tools with action-based architecture
- Introduced action system for Solana tools, allowing for better modularity and maintainability. - Updated SolanaBalanceTool, SolanaTransferTool, SolanaDeployTokenTool, SolanaDeployCollectionTool, SolanaMintNFTTool, SolanaTradeTool, and SolanaRequestFundsTool to utilize action handlers. - Added new action exports in index.ts for better organization and accessibility.
This commit is contained in:
59
src/actions/balance.ts
Normal file
59
src/actions/balance.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
|
||||
const balanceAction: Action = {
|
||||
name: "solana_balance",
|
||||
similes: [
|
||||
"check balance",
|
||||
"get wallet balance",
|
||||
"view balance",
|
||||
"show balance",
|
||||
"check token 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.`,
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {},
|
||||
output: {
|
||||
status: "success",
|
||||
balance: "100",
|
||||
token: "SOL"
|
||||
},
|
||||
explanation: "Get SOL balance of the wallet"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
input: {
|
||||
tokenAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
balance: "1000",
|
||||
token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
|
||||
},
|
||||
explanation: "Get USDC token balance"
|
||||
}
|
||||
]
|
||||
],
|
||||
schema: z.object({
|
||||
tokenAddress: z.string().optional()
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
const tokenAddress = input.tokenAddress ? new PublicKey(input.tokenAddress) : undefined;
|
||||
const balance = await agent.getBalance(tokenAddress);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
balance: balance,
|
||||
token: input.tokenAddress || "SOL"
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default balanceAction;
|
||||
78
src/actions/deployCollection.ts
Normal file
78
src/actions/deployCollection.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
|
||||
interface CollectionOptions {
|
||||
name: string;
|
||||
uri: string;
|
||||
royaltyBasisPoints?: number;
|
||||
}
|
||||
|
||||
const deployCollectionAction: Action = {
|
||||
name: "solana_deploy_collection",
|
||||
similes: [
|
||||
"create collection",
|
||||
"launch collection",
|
||||
"deploy nft collection",
|
||||
"create nft collection",
|
||||
"mint collection"
|
||||
],
|
||||
description: `Deploy a new NFT collection on Solana blockchain.`,
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
name: "My Collection",
|
||||
uri: "https://example.com/collection.json",
|
||||
royaltyBasisPoints: 500
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "Collection deployed successfully",
|
||||
collectionAddress: "7nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkN",
|
||||
name: "My Collection"
|
||||
},
|
||||
explanation: "Deploy an NFT collection with 5% royalty"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
input: {
|
||||
name: "Basic Collection",
|
||||
uri: "https://example.com/basic.json"
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "Collection deployed successfully",
|
||||
collectionAddress: "8nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkM",
|
||||
name: "Basic Collection"
|
||||
},
|
||||
explanation: "Deploy a basic NFT collection without royalties"
|
||||
}
|
||||
]
|
||||
],
|
||||
schema: z.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
uri: z.string().url("URI must be a valid URL"),
|
||||
royaltyBasisPoints: z.number().min(0).max(10000).optional()
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
const options: CollectionOptions = {
|
||||
name: input.name,
|
||||
uri: input.uri,
|
||||
royaltyBasisPoints: input.royaltyBasisPoints
|
||||
};
|
||||
|
||||
const result = await agent.deployCollection(options);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
message: "Collection deployed successfully",
|
||||
collectionAddress: result.collectionAddress.toString(),
|
||||
name: input.name
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default deployCollectionAction;
|
||||
74
src/actions/deployToken.ts
Normal file
74
src/actions/deployToken.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Action, ActionExample } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
|
||||
const deployTokenAction: Action = {
|
||||
name: "deploy_token",
|
||||
similes: [
|
||||
"create token",
|
||||
"launch token",
|
||||
"deploy new token",
|
||||
"create new token",
|
||||
"mint token",
|
||||
],
|
||||
description: "Deploy a new SPL token on the Solana blockchain with specified parameters",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
name: "My Token",
|
||||
uri: "https://example.com/token.json",
|
||||
symbol: "MTK",
|
||||
decimals: 9,
|
||||
initialSupply: 1000000
|
||||
},
|
||||
output: {
|
||||
mint: "7nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkN",
|
||||
status: "success",
|
||||
message: "Token deployed successfully"
|
||||
},
|
||||
explanation: "Deploy a token with initial supply and metadata"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
input: {
|
||||
name: "Basic Token",
|
||||
uri: "https://example.com/basic.json",
|
||||
symbol: "BASIC"
|
||||
},
|
||||
output: {
|
||||
mint: "8nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkM",
|
||||
status: "success",
|
||||
message: "Token deployed successfully"
|
||||
},
|
||||
explanation: "Deploy a basic token with minimal parameters"
|
||||
}
|
||||
]
|
||||
],
|
||||
schema: z.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
uri: z.string().url("URI must be a valid URL"),
|
||||
symbol: z.string().min(1, "Symbol is required"),
|
||||
decimals: z.number().optional(),
|
||||
initialSupply: z.number().optional()
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
const result = await agent.deployToken(
|
||||
input.name,
|
||||
input.uri,
|
||||
input.symbol,
|
||||
input.decimals,
|
||||
input.initialSupply
|
||||
);
|
||||
|
||||
return {
|
||||
mint: result.mint.toString(),
|
||||
status: "success",
|
||||
message: "Token deployed successfully"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default deployTokenAction;
|
||||
20
src/actions/index.ts
Normal file
20
src/actions/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import deployTokenAction from "./deployToken";
|
||||
import balanceAction from "./balance";
|
||||
import transferAction from "./transfer";
|
||||
import deployCollectionAction from "./deployCollection";
|
||||
import mintNFTAction from "./mintNFT";
|
||||
import tradeAction from "./trade";
|
||||
import requestFundsAction from "./requestFunds";
|
||||
|
||||
export const actions = [
|
||||
deployTokenAction,
|
||||
balanceAction,
|
||||
transferAction,
|
||||
deployCollectionAction,
|
||||
mintNFTAction,
|
||||
tradeAction,
|
||||
requestFundsAction,
|
||||
// Add more actions here as they are implemented
|
||||
];
|
||||
|
||||
export type { Action, ActionExample, Handler } from "../types/action";
|
||||
88
src/actions/mintNFT.ts
Normal file
88
src/actions/mintNFT.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
|
||||
const mintNFTAction: Action = {
|
||||
name: "solana_mint_nft",
|
||||
similes: [
|
||||
"mint nft",
|
||||
"create nft",
|
||||
"mint token",
|
||||
"create token",
|
||||
"add nft to collection"
|
||||
],
|
||||
description: `Mint a new NFT in a collection on Solana blockchain.`,
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
collectionMint: "J1S9H3QjnRtBbbuD4HjPV6RpRhwuk4zKbxsnCHuTgh9w",
|
||||
name: "My NFT",
|
||||
uri: "https://example.com/nft.json"
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "NFT minted successfully",
|
||||
mintAddress: "7nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkN",
|
||||
metadata: {
|
||||
name: "My NFT",
|
||||
uri: "https://example.com/nft.json"
|
||||
},
|
||||
recipient: "7nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkN"
|
||||
},
|
||||
explanation: "Mint an NFT to the default wallet"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
input: {
|
||||
collectionMint: "J1S9H3QjnRtBbbuD4HjPV6RpRhwuk4zKbxsnCHuTgh9w",
|
||||
name: "Gift NFT",
|
||||
uri: "https://example.com/gift.json",
|
||||
recipient: "9aUn5swQzUTRanaaTwmszxiv89cvFwUCjEBv1vZCoT1u"
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "NFT minted successfully",
|
||||
mintAddress: "8nE9GvcwsqzYxmJLSrYmSB1V1YoJWVK1KWzAcWAzjXkM",
|
||||
metadata: {
|
||||
name: "Gift NFT",
|
||||
uri: "https://example.com/gift.json"
|
||||
},
|
||||
recipient: "9aUn5swQzUTRanaaTwmszxiv89cvFwUCjEBv1vZCoT1u"
|
||||
},
|
||||
explanation: "Mint an NFT to a specific recipient"
|
||||
}
|
||||
]
|
||||
],
|
||||
schema: z.object({
|
||||
collectionMint: z.string().min(32, "Invalid collection mint address"),
|
||||
name: z.string().min(1, "Name is required"),
|
||||
uri: z.string().url("URI must be a valid URL"),
|
||||
recipient: z.string().min(32, "Invalid recipient address").optional()
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
const result = await agent.mintNFT(
|
||||
new PublicKey(input.collectionMint),
|
||||
{
|
||||
name: input.name,
|
||||
uri: input.uri,
|
||||
},
|
||||
input.recipient ? new PublicKey(input.recipient) : agent.wallet_address
|
||||
);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
message: "NFT minted successfully",
|
||||
mintAddress: result.mint.toString(),
|
||||
metadata: {
|
||||
name: input.name,
|
||||
uri: input.uri
|
||||
},
|
||||
recipient: input.recipient || result.mint.toString()
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default mintNFTAction;
|
||||
40
src/actions/requestFunds.ts
Normal file
40
src/actions/requestFunds.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
|
||||
const requestFundsAction: Action = {
|
||||
name: "solana_request_funds",
|
||||
similes: [
|
||||
"request sol",
|
||||
"get test sol",
|
||||
"use faucet",
|
||||
"request test tokens",
|
||||
"get devnet sol"
|
||||
],
|
||||
description: "Request SOL from Solana faucet (devnet/testnet only)",
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "Successfully requested faucet funds",
|
||||
network: "devnet.solana.com"
|
||||
},
|
||||
explanation: "Request SOL from the devnet faucet"
|
||||
}
|
||||
]
|
||||
],
|
||||
schema: z.object({}), // No input parameters required
|
||||
handler: async (agent: SolanaAgentKit, _input: Record<string, any>) => {
|
||||
await agent.requestFaucetFunds();
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
message: "Successfully requested faucet funds",
|
||||
network: agent.connection.rpcEndpoint.split("/")[2]
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default requestFundsAction;
|
||||
81
src/actions/trade.ts
Normal file
81
src/actions/trade.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
|
||||
const tradeAction: Action = {
|
||||
name: "solana_trade",
|
||||
similes: [
|
||||
"swap tokens",
|
||||
"exchange tokens",
|
||||
"trade tokens",
|
||||
"convert tokens",
|
||||
"swap sol"
|
||||
],
|
||||
description: `This tool can be used to swap tokens to another token (It uses Jupiter Exchange).`,
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
outputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
inputAmount: 1
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "Trade executed successfully",
|
||||
transaction: "5UfgJ5vVZxUxefDGqzqkVLHzHxVTyYH9StYyHKgvHYmXJgqJKxEqy9k4Rz9LpXrHF9kUZB7",
|
||||
inputAmount: 1,
|
||||
inputToken: "SOL",
|
||||
outputToken: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
|
||||
},
|
||||
explanation: "Swap 1 SOL for USDC"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
input: {
|
||||
outputMint: "So11111111111111111111111111111111111111112",
|
||||
inputAmount: 100,
|
||||
inputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
slippageBps: 100
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "Trade executed successfully",
|
||||
transaction: "4VfgJ5vVZxUxefDGqzqkVLHzHxVTyYH9StYyHKgvHYmXJgqJKxEqy9k4Rz9LpXrHF9kUZB7",
|
||||
inputAmount: 100,
|
||||
inputToken: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
outputToken: "So11111111111111111111111111111111111111112"
|
||||
},
|
||||
explanation: "Swap 100 USDC for SOL with 1% slippage"
|
||||
}
|
||||
]
|
||||
],
|
||||
schema: z.object({
|
||||
outputMint: z.string().min(32, "Invalid output mint address"),
|
||||
inputAmount: z.number().positive("Input amount must be positive"),
|
||||
inputMint: z.string().min(32, "Invalid input mint address").optional(),
|
||||
slippageBps: z.number().min(0).max(10000).optional()
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
const tx = await agent.trade(
|
||||
new PublicKey(input.outputMint),
|
||||
input.inputAmount,
|
||||
input.inputMint
|
||||
? new PublicKey(input.inputMint)
|
||||
: new PublicKey("So11111111111111111111111111111111111111112"),
|
||||
input.slippageBps
|
||||
);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
message: "Trade executed successfully",
|
||||
transaction: tx,
|
||||
inputAmount: input.inputAmount,
|
||||
inputToken: input.inputMint || "SOL",
|
||||
outputToken: input.outputMint
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default tradeAction;
|
||||
75
src/actions/transfer.ts
Normal file
75
src/actions/transfer.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
|
||||
const transferAction: Action = {
|
||||
name: "solana_transfer",
|
||||
similes: [
|
||||
"send tokens",
|
||||
"transfer funds",
|
||||
"send money",
|
||||
"send sol",
|
||||
"transfer tokens"
|
||||
],
|
||||
description: `Transfer tokens or SOL to another address (also called as wallet address).`,
|
||||
examples: [
|
||||
[
|
||||
{
|
||||
input: {
|
||||
to: "8x2dR8Mpzuz2YqyZyZjUbYWKSWesBo5jMx2Q9Y86udVk",
|
||||
amount: 1
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "Transfer completed successfully",
|
||||
amount: 1,
|
||||
recipient: "8x2dR8Mpzuz2YqyZyZjUbYWKSWesBo5jMx2Q9Y86udVk",
|
||||
token: "SOL",
|
||||
transaction: "5UfgJ5vVZxUxefDGqzqkVLHzHxVTyYH9StYyHKgvHYmXJgqJKxEqy9k4Rz9LpXrHF9kUZB7"
|
||||
},
|
||||
explanation: "Transfer 1 SOL to the recipient address"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
input: {
|
||||
to: "8x2dR8Mpzuz2YqyZyZjUbYWKSWesBo5jMx2Q9Y86udVk",
|
||||
amount: 100,
|
||||
mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
|
||||
},
|
||||
output: {
|
||||
status: "success",
|
||||
message: "Transfer completed successfully",
|
||||
amount: 100,
|
||||
recipient: "8x2dR8Mpzuz2YqyZyZjUbYWKSWesBo5jMx2Q9Y86udVk",
|
||||
token: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
||||
transaction: "4VfgJ5vVZxUxefDGqzqkVLHzHxVTyYH9StYyHKgvHYmXJgqJKxEqy9k4Rz9LpXrHF9kUZB7"
|
||||
},
|
||||
explanation: "Transfer 100 USDC tokens to the recipient address"
|
||||
}
|
||||
]
|
||||
],
|
||||
schema: z.object({
|
||||
to: z.string().min(32, "Invalid Solana address"),
|
||||
amount: z.number().positive("Amount must be positive"),
|
||||
mint: z.string().optional()
|
||||
}),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
const recipient = new PublicKey(input.to);
|
||||
const mintAddress = input.mint ? new PublicKey(input.mint) : undefined;
|
||||
|
||||
const tx = await agent.transfer(recipient, input.amount, mintAddress);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
message: "Transfer completed successfully",
|
||||
amount: input.amount,
|
||||
recipient: input.to,
|
||||
token: input.mint || "SOL",
|
||||
transaction: tx
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export default transferAction;
|
||||
@@ -5,3 +5,8 @@ export { SolanaAgentKit, createSolanaTools };
|
||||
|
||||
// Optional: Export types that users might need
|
||||
export * from "./types";
|
||||
|
||||
// Export action system
|
||||
export * from "./actions";
|
||||
export * from "./types/action";
|
||||
export * from "./utils/actionExecutor";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PublicKey } from "@solana/web3.js";
|
||||
import Decimal from "decimal.js";
|
||||
import { Tool } from "langchain/tools";
|
||||
import { Tool } from "@langchain/core/tools";
|
||||
import {
|
||||
GibworkCreateTaskReponse,
|
||||
PythFetchPriceResponse,
|
||||
@@ -10,282 +10,203 @@ import { create_image } from "../tools/create_image";
|
||||
import { BN } from "@coral-xyz/anchor";
|
||||
import { FEE_TIERS } from "../tools";
|
||||
import { toJSON } from "../utils/toJSON";
|
||||
import { wrapLangChainTool } from "../utils/langchainWrapper";
|
||||
import deployTokenAction from "../actions/deployToken";
|
||||
import balanceAction from "../actions/balance";
|
||||
import transferAction from "../actions/transfer";
|
||||
import deployCollectionAction from "../actions/deployCollection";
|
||||
import mintNFTAction from "../actions/mintNFT";
|
||||
import tradeAction from "../actions/trade";
|
||||
import requestFundsAction from "../actions/requestFunds";
|
||||
|
||||
export class SolanaBalanceTool extends Tool {
|
||||
name = "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:
|
||||
tokenAddress: string, eg "So11111111111111111111111111111111111111112" (optional)`;
|
||||
private action = balanceAction;
|
||||
name = this.action.name;
|
||||
description = this.action.description;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
const tokenAddress = input ? new PublicKey(input) : undefined;
|
||||
const balance = await this.solanaKit.getBalance(tokenAddress);
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
balance: balance,
|
||||
token: input || "SOL",
|
||||
});
|
||||
// Parse input as JSON if provided, otherwise use empty object
|
||||
const parsedInput = input ? JSON.parse(input) : {};
|
||||
|
||||
// Validate and execute using the action
|
||||
const result = await this.action.handler(this.solanaKit, parsedInput);
|
||||
|
||||
return JSON.stringify(result);
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
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)`;
|
||||
private action = transferAction;
|
||||
name = this.action.name;
|
||||
description = this.action.description;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
// Parse input as JSON
|
||||
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,
|
||||
});
|
||||
|
||||
// Validate and execute using the action
|
||||
const result = await this.action.handler(this.solanaKit, parsedInput);
|
||||
|
||||
return JSON.stringify(result);
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
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)`;
|
||||
private action = deployTokenAction;
|
||||
name = this.action.name;
|
||||
description = this.action.description;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
// Parse input as JSON
|
||||
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,
|
||||
});
|
||||
|
||||
// Validate and execute using the action
|
||||
const result = await this.action.handler(this.solanaKit, parsedInput);
|
||||
|
||||
return JSON.stringify(result);
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
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)`;
|
||||
private action = deployCollectionAction;
|
||||
name = this.action.name;
|
||||
description = this.action.description;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
// Parse input as JSON
|
||||
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,
|
||||
});
|
||||
|
||||
// Validate and execute using the action
|
||||
const result = await this.action.handler(this.solanaKit, parsedInput);
|
||||
|
||||
return JSON.stringify(result);
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
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()}`;
|
||||
private action = mintNFTAction;
|
||||
name = this.action.name;
|
||||
description = this.action.description;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
// Parse input as JSON
|
||||
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(),
|
||||
});
|
||||
|
||||
// Validate and execute using the action
|
||||
const result = await this.action.handler(this.solanaKit, parsedInput);
|
||||
|
||||
return JSON.stringify(result);
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
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)`;
|
||||
private action = tradeAction;
|
||||
name = this.action.name;
|
||||
description = this.action.description;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: string): Promise<string> {
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
// Parse input as JSON
|
||||
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,
|
||||
});
|
||||
|
||||
// Validate and execute using the action
|
||||
const result = await this.action.handler(this.solanaKit, parsedInput);
|
||||
|
||||
return JSON.stringify(result);
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
code: error.code || "UNKNOWN_ERROR"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SolanaRequestFundsTool extends Tool {
|
||||
name = "solana_request_funds";
|
||||
description = "Request SOL from Solana faucet (devnet/testnet only)";
|
||||
private action = requestFundsAction;
|
||||
name = this.action.name;
|
||||
description = this.action.description;
|
||||
|
||||
constructor(private solanaKit: SolanaAgentKit) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(_input: string): Promise<string> {
|
||||
async _call(_input: string): Promise<string> {
|
||||
try {
|
||||
await this.solanaKit.requestFaucetFunds();
|
||||
|
||||
return JSON.stringify({
|
||||
status: "success",
|
||||
message: "Successfully requested faucet funds",
|
||||
network: this.solanaKit.connection.rpcEndpoint.split("/")[2],
|
||||
});
|
||||
// No input needed for this action
|
||||
const result = await this.action.handler(this.solanaKit, {});
|
||||
|
||||
return JSON.stringify(result);
|
||||
} catch (error: any) {
|
||||
return JSON.stringify({
|
||||
status: "error",
|
||||
message: error.message,
|
||||
code: error.code || "UNKNOWN_ERROR",
|
||||
code: error.code || "UNKNOWN_ERROR"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1230,7 +1151,7 @@ export class SolanaCreateGibworkTask extends Tool {
|
||||
}
|
||||
|
||||
export function createSolanaTools(solanaKit: SolanaAgentKit) {
|
||||
return [
|
||||
const tools = [
|
||||
new SolanaBalanceTool(solanaKit),
|
||||
new SolanaTransferTool(solanaKit),
|
||||
new SolanaDeployTokenTool(solanaKit),
|
||||
@@ -1264,4 +1185,7 @@ export function createSolanaTools(solanaKit: SolanaAgentKit) {
|
||||
new SolanaResolveAllDomainsTool(solanaKit),
|
||||
new SolanaCreateGibworkTask(solanaKit),
|
||||
];
|
||||
|
||||
// Convert LangChain tools to our Action interface
|
||||
return tools.map(tool => wrapLangChainTool(tool, solanaKit));
|
||||
}
|
||||
|
||||
53
src/types/action.ts
Normal file
53
src/types/action.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* Example of an action with input and output
|
||||
*/
|
||||
export interface ActionExample {
|
||||
input: Record<string, any>;
|
||||
output: Record<string, any>;
|
||||
explanation: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler function type for executing the action
|
||||
*/
|
||||
export type Handler = (agent: SolanaAgentKit, input: Record<string, any>) => Promise<Record<string, any>>;
|
||||
|
||||
/**
|
||||
* Main Action interface inspired by ELIZA
|
||||
* This interface makes it easier to implement actions across different frameworks
|
||||
*/
|
||||
export interface Action {
|
||||
/**
|
||||
* Unique name of the action
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Alternative names/phrases that can trigger this action
|
||||
*/
|
||||
similes: string[];
|
||||
|
||||
/**
|
||||
* Detailed description of what the action does
|
||||
*/
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* Array of example inputs and outputs for the action
|
||||
* Each inner array represents a group of related examples
|
||||
*/
|
||||
examples: ActionExample[][];
|
||||
|
||||
/**
|
||||
* Zod schema for input validation
|
||||
*/
|
||||
schema: z.ZodType<any>;
|
||||
|
||||
/**
|
||||
* Function that executes the action
|
||||
*/
|
||||
handler: Handler;
|
||||
}
|
||||
67
src/utils/actionExecutor.ts
Normal file
67
src/utils/actionExecutor.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { actions } from "../actions";
|
||||
|
||||
/**
|
||||
* Find an action by its name or one of its similes
|
||||
*/
|
||||
export function findAction(query: string): Action | undefined {
|
||||
const normalizedQuery = query.toLowerCase().trim();
|
||||
return actions.find(action =>
|
||||
action.name.toLowerCase() === normalizedQuery ||
|
||||
action.similes.some(simile => simile.toLowerCase() === normalizedQuery)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an action with the given input
|
||||
*/
|
||||
export async function executeAction(
|
||||
action: Action,
|
||||
agent: SolanaAgentKit,
|
||||
input: Record<string, any>
|
||||
): Promise<Record<string, any>> {
|
||||
try {
|
||||
// Validate input using Zod schema
|
||||
const validatedInput = action.schema.parse(input);
|
||||
|
||||
// Execute the action with validated input
|
||||
const result = await action.handler(agent, validatedInput);
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
...result
|
||||
};
|
||||
} catch (error: any) {
|
||||
// Handle Zod validation errors specially
|
||||
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"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get examples for an action
|
||||
*/
|
||||
export function getActionExamples(action: Action): string {
|
||||
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");
|
||||
}
|
||||
96
src/utils/langchainWrapper.ts
Normal file
96
src/utils/langchainWrapper.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { Tool } from "langchain/tools";
|
||||
import { Action } from "../types/action";
|
||||
import { SolanaAgentKit } from "../agent";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* Convert a LangChain tool to our Action interface
|
||||
*/
|
||||
export function wrapLangChainTool(tool: Tool, agent: SolanaAgentKit): Action {
|
||||
// Parse the description to extract input parameters
|
||||
const inputParams = parseToolDescription(tool.description);
|
||||
|
||||
return {
|
||||
name: tool.name,
|
||||
similes: [], // LangChain tools don't have similes
|
||||
description: tool.description,
|
||||
examples: [], // LangChain tools don't have examples
|
||||
schema: createZodSchema(inputParams),
|
||||
handler: async (agent: SolanaAgentKit, input: Record<string, any>) => {
|
||||
const result = await tool.call(JSON.stringify(input));
|
||||
try {
|
||||
return JSON.parse(result);
|
||||
} catch {
|
||||
return { result };
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse tool description to extract input parameters
|
||||
*/
|
||||
function parseToolDescription(description: string): Array<{name: string, type: string, required: boolean}> {
|
||||
const lines = description.split('\n');
|
||||
const params: Array<{name: string, type: string, required: boolean}> = [];
|
||||
|
||||
let inInputsSection = false;
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
if (trimmed === 'Inputs:' || trimmed === 'Inputs (input is a JSON string):') {
|
||||
inInputsSection = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inInputsSection && trimmed) {
|
||||
// Match patterns like: name: string, eg "value" (required)
|
||||
const match = trimmed.match(/(\w+):\s*([\w\[\]]+)(?:,\s*eg[:\s]+"[^"]+")?(?:\s*\((required|optional)\))?/);
|
||||
if (match) {
|
||||
params.push({
|
||||
name: match[1],
|
||||
type: match[2],
|
||||
required: match[3] === 'required'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Zod schema from parsed parameters
|
||||
*/
|
||||
function createZodSchema(params: Array<{name: string, type: string, required: boolean}>): z.ZodType<any> {
|
||||
const schemaObj: Record<string, z.ZodType<any>> = {};
|
||||
|
||||
for (const param of params) {
|
||||
let schema: z.ZodType<any>;
|
||||
|
||||
switch (param.type.toLowerCase()) {
|
||||
case 'string':
|
||||
schema = z.string();
|
||||
break;
|
||||
case 'number':
|
||||
schema = z.number();
|
||||
break;
|
||||
case 'boolean':
|
||||
schema = z.boolean();
|
||||
break;
|
||||
case 'string[]':
|
||||
schema = z.array(z.string());
|
||||
break;
|
||||
default:
|
||||
schema = z.any();
|
||||
}
|
||||
|
||||
if (!param.required) {
|
||||
schema = schema.optional();
|
||||
}
|
||||
|
||||
schemaObj[param.name] = schema;
|
||||
}
|
||||
|
||||
return z.object(schemaObj);
|
||||
}
|
||||
Reference in New Issue
Block a user