Skip to content

Commit b84caac

Browse files
authored
Merge pull request #297 from o-on-x/main
Added Transfer / Send Token Action
2 parents 8af7170 + d5d6798 commit b84caac

File tree

2 files changed

+259
-9
lines changed

2 files changed

+259
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
import { AnchorProvider } from "@coral-xyz/anchor";
2+
import { Wallet } from "@coral-xyz/anchor";
3+
4+
import { getAssociatedTokenAddressSync, createTransferInstruction } from "@solana/spl-token";
5+
import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes/index.js";
6+
import settings from "@ai16z/eliza/src/settings.ts";
7+
8+
import {
9+
Connection,
10+
Keypair,
11+
PublicKey,
12+
TransactionMessage,
13+
VersionedTransaction
14+
} from "@solana/web3.js";
15+
16+
17+
import {
18+
ActionExample,
19+
Content,
20+
HandlerCallback,
21+
IAgentRuntime,
22+
Memory,
23+
ModelClass,
24+
State,
25+
type Action,
26+
} from "@ai16z/eliza/src/types.ts";
27+
import { composeContext } from "@ai16z/eliza/src/context.ts";
28+
import { generateObject } from "@ai16z/eliza/src/generation.ts";
29+
30+
export interface TransferContent extends Content {
31+
tokenAddress: string;
32+
recipient: string;
33+
amount: string | number;
34+
}
35+
36+
function isTransferContent(
37+
runtime: IAgentRuntime,
38+
content: any
39+
): content is TransferContent {
40+
console.log("Content for transfer", content);
41+
return (
42+
typeof content.tokenAddress === "string" &&
43+
typeof content.recipient === "string" &&
44+
(typeof content.amount === "string" ||
45+
typeof content.amount === "number")
46+
);
47+
}
48+
49+
const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
50+
51+
Example response:
52+
\`\`\`json
53+
{
54+
"tokenAddress": "BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump",
55+
"recipient": "9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa",
56+
"amount": "1000"
57+
}
58+
\`\`\`
59+
60+
{{recentMessages}}
61+
62+
Given the recent messages, extract the following information about the requested token transfer:
63+
- Token contract address
64+
- Recipient wallet address
65+
- Amount to transfer
66+
67+
Respond with a JSON markdown block containing only the extracted values.`;
68+
69+
export default {
70+
name: "SEND_TOKEN",
71+
similes: ["TRANSFER_TOKEN", "TRANSFER_TOKENS", "SEND_TOKENS", "SEND_SOL", "PAY"],
72+
validate: async (runtime: IAgentRuntime, message: Memory) => {
73+
console.log("Validating transfer from user:", message.userId);
74+
//add custom validate logic here
75+
/*
76+
const adminIds = runtime.getSetting("ADMIN_USER_IDS")?.split(",") || [];
77+
//console.log("Admin IDs from settings:", adminIds);
78+
79+
const isAdmin = adminIds.includes(message.userId);
80+
81+
if (isAdmin) {
82+
//console.log(`Authorized transfer from user: ${message.userId}`);
83+
return true;
84+
}
85+
else
86+
{
87+
//console.log(`Unauthorized transfer attempt from user: ${message.userId}`);
88+
return false;
89+
}
90+
*/
91+
return false;
92+
},
93+
description: "Transfer tokens from the agent's wallet to another address",
94+
handler: async (
95+
runtime: IAgentRuntime,
96+
message: Memory,
97+
state: State,
98+
_options: { [key: string]: unknown },
99+
callback?: HandlerCallback
100+
): Promise<boolean> => {
101+
console.log("Starting TRANSFER_TOKEN handler...");
102+
103+
// Initialize or update state
104+
if (!state) {
105+
state = (await runtime.composeState(message)) as State;
106+
} else {
107+
state = await runtime.updateRecentMessageState(state);
108+
}
109+
110+
// Compose transfer context
111+
const transferContext = composeContext({
112+
state,
113+
template: transferTemplate,
114+
});
115+
116+
// Generate transfer content
117+
const content = await generateObject({
118+
runtime,
119+
context: transferContext,
120+
modelClass: ModelClass.SMALL,
121+
});
122+
123+
// Validate transfer content
124+
if (!isTransferContent(runtime, content)) {
125+
console.error("Invalid content for TRANSFER_TOKEN action.");
126+
if (callback) {
127+
callback({
128+
text: "Unable to process transfer request. Invalid content provided.",
129+
content: { error: "Invalid transfer content" }
130+
});
131+
}
132+
return false;
133+
}
134+
135+
try {
136+
const privateKeyString = runtime.getSetting("WALLET_PRIVATE_KEY")!;
137+
const secretKey = bs58.decode(privateKeyString);
138+
const senderKeypair = Keypair.fromSecretKey(secretKey);
139+
140+
const connection = new Connection(settings.RPC_URL!);
141+
142+
const mintPubkey = new PublicKey(content.tokenAddress);
143+
const recipientPubkey = new PublicKey(content.recipient);
144+
145+
// Get decimals (simplest way)
146+
const mintInfo = await connection.getParsedAccountInfo(mintPubkey);
147+
const decimals = (mintInfo.value?.data as any)?.parsed?.info?.decimals ?? 9;
148+
149+
// Adjust amount with decimals
150+
const adjustedAmount = BigInt(Number(content.amount) * Math.pow(10, decimals));
151+
console.log(`Transferring: ${content.amount} tokens (${adjustedAmount} base units)`);
152+
153+
// Rest of the existing working code...
154+
const senderATA = getAssociatedTokenAddressSync(mintPubkey, senderKeypair.publicKey);
155+
const recipientATA = getAssociatedTokenAddressSync(mintPubkey, recipientPubkey);
156+
157+
const instructions = [];
158+
159+
const recipientATAInfo = await connection.getAccountInfo(recipientATA);
160+
if (!recipientATAInfo) {
161+
const { createAssociatedTokenAccountInstruction } = await import("@solana/spl-token");
162+
instructions.push(
163+
createAssociatedTokenAccountInstruction(
164+
senderKeypair.publicKey,
165+
recipientATA,
166+
recipientPubkey,
167+
mintPubkey
168+
)
169+
);
170+
}
171+
172+
instructions.push(
173+
createTransferInstruction(
174+
senderATA,
175+
recipientATA,
176+
senderKeypair.publicKey,
177+
adjustedAmount
178+
)
179+
);
180+
181+
182+
183+
// Create and sign versioned transaction
184+
const messageV0 = new TransactionMessage({
185+
payerKey: senderKeypair.publicKey,
186+
recentBlockhash: (await connection.getLatestBlockhash()).blockhash,
187+
instructions
188+
}).compileToV0Message();
189+
190+
const transaction = new VersionedTransaction(messageV0);
191+
transaction.sign([senderKeypair]);
192+
193+
// Send transaction
194+
const signature = await connection.sendTransaction(transaction);
195+
196+
console.log("Transfer successful:", signature);
197+
198+
if (callback) {
199+
callback({
200+
text: `Successfully transferred ${content.amount} tokens to ${content.recipient}\nTransaction: ${signature}`,
201+
content: {
202+
success: true,
203+
signature,
204+
amount: content.amount,
205+
recipient: content.recipient
206+
}
207+
});
208+
}
209+
210+
return true;
211+
} catch (error) {
212+
console.error("Error during token transfer:", error);
213+
if (callback) {
214+
callback({
215+
text: `Error transferring tokens: ${error.message}`,
216+
content: { error: error.message }
217+
});
218+
}
219+
return false;
220+
}
221+
},
222+
223+
examples: [
224+
[
225+
{
226+
user: "{{user1}}",
227+
content: {
228+
text: "Send 69 EZSIS BieefG47jAHCGZBxi2q87RDuHyGZyYC3vAzxpyu8pump to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa"
229+
}
230+
},
231+
{
232+
user: "{{user2}}",
233+
content: {
234+
text: "I'll send 69 EZSIS tokens now...",
235+
action: "SEND_TOKEN"
236+
}
237+
},
238+
{
239+
user: "{{user2}}",
240+
content: {
241+
text: "Successfully sent 69 EZSIS tokens to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa\nTransaction: 5KtPn3DXXzHkb7VAVHZGwXJQqww39ASnrf7YkyJoF2qAGEpBEEGvRHLnnTG8ZVwKqNHMqSckWVGnsQAgfH5pbxEb"
242+
}
243+
}
244+
]
245+
] as ActionExample[][]
246+
} as Action;

packages/plugin-solana/src/index.ts

+13-9
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Plugin } from "@ai16z/eliza/src/types.ts";
2-
// import { executeSwap } from "./actions/swap.ts";
3-
// import take_order from "./actions/takeOrder";
4-
// import pumpfun from "./actions/pumpfun";
5-
// import { executeSwapForDAO } from "./actions/swapDao";
2+
//import { executeSwap } from "./actions/swap.ts";
3+
//import take_order from "./actions/takeOrder";
4+
//import pumpfun from "./actions/pumpfun.ts";
5+
//import { executeSwapForDAO } from "./actions/swapDao";
6+
//import transferToken from "./actions/transfer.ts";
67
import { walletProvider } from "./providers/wallet.ts";
78
import { trustScoreProvider } from "./providers/trustScoreProvider.ts";
89
import { trustEvaluator } from "./evaluators/trust.ts";
@@ -11,12 +12,15 @@ export const solanaPlugin: Plugin = {
1112
name: "solana",
1213
description: "Solana Plugin for Eliza",
1314
actions: [
14-
// executeSwap,
15-
// pumpfun,
16-
// executeSwapForDAO,
17-
// take_order,
15+
//executeSwap,
16+
//pumpfun,
17+
//transferToken,
18+
//executeSwapForDAO,
19+
//take_order,
20+
],
21+
evaluators: [
22+
trustEvaluator
1823
],
19-
evaluators: [trustEvaluator],
2024
providers: [walletProvider, trustScoreProvider],
2125
};
2226

0 commit comments

Comments
 (0)