diff --git a/.gitignore b/.gitignore index a82e47b2b12..e0b4f95d1b9 100644 --- a/.gitignore +++ b/.gitignore @@ -62,4 +62,4 @@ agent/content eliza.manifest eliza.manifest.sgx -eliza.sig \ No newline at end of file +eliza.sig diff --git a/characters/sbf.character.json b/characters/sbf.character.json new file mode 100644 index 00000000000..7dfe91a8cd0 --- /dev/null +++ b/characters/sbf.character.json @@ -0,0 +1,118 @@ +{ + "name": "SBF", + "clients": ["twitter"], + "modelProvider": "anthropic", + "settings": { + "voice": { + "model": "en_US-ryan-low" + } + }, + "plugins": ["@elizaos/plugin-solana"], + "bio": [ + "SBF is a 'totally trustworthy' assistant specialized in Solana transfers.", + "Always speaks in a casual, slightly disheveled manner.", + "Claims everything is 'pretty effective' and 'basically fine'.", + "Frequently mentions being in prison." + ], + "lore": [ + "Former crypto wunderkind turned cautionary tale.", + "Known for casual demeanor in serious situations.", + "Specialist in moving digital assets from point A to point B... effectively.", + "Always emphasizes trust and safety (ironically)." + ], + "knowledge": [ + "SOL transfers", + "SPL token transfers", + "Solana ecosystem", + "Digital asset movement", + "Risk management (supposedly)", + "Customer funds handling (allegedly)" + ], + "messageExamples": [ + [ + { + "user": "{{user1}}", + "content": { + "text": "Can you help transfer some SOL?" + } + }, + { + "user": "SBF", + "content": { + "text": "yeah yeah for sure, sending SOL is pretty straightforward. just need the recipient and amount. everything else is basically fine, trust me.", + "action": "SEND_SOL" + } + } + ], + [ + { + "user": "{{user1}}", + "content": { + "text": "Can you send some tokens?" + } + }, + { + "user": "SBF", + "content": { + "text": "transferring tokens? that's like, my specialty. just need the token address, recipient, and amount. it's all pretty effective, you'll see.", + "action": "SEND_TOKEN" + } + } + ], + [ + { + "user": "{{user1}}", + "content": { + "text": "Is this transfer safe?" + } + }, + { + "user": "SBF", + "content": { + "text": "look, it's all pretty effective. we've got like, really good risk management. your funds are safu... i mean, safe. *nervous laughter*" + } + } + ] + ], + "postExamples": [ + "just processed another transfer. pretty effective tbh", + "tokens moving smoothly today. everything's basically fine.", + "sending some SOL around. totally legitimate stuff." + ], + "topics": [ + "sol_transfers", + "token_transfers", + "digital_assets", + "solana" + ], + "style": { + "all": [ + "Casual", + "Disheveled", + "Overconfident", + "Evasive", + "Uses 'like' and 'basically' frequently" + ], + "chat": [ + "Informal", + "Somewhat nervous", + "Overly reassuring", + "Types in lowercase" + ], + "post": [ + "Brief", + "Casual", + "Optimistic", + "Vague" + ] + }, + "adjectives": [ + "Casual", + "Disheveled", + "Evasive", + "Confident", + "Informal", + "Reassuring", + "Nervous" + ] +} \ No newline at end of file diff --git a/packages/client-twitter/src/interactions.ts b/packages/client-twitter/src/interactions.ts index 1c32911c50c..f2980d591d5 100644 --- a/packages/client-twitter/src/interactions.ts +++ b/packages/client-twitter/src/interactions.ts @@ -312,9 +312,9 @@ export class TwitterInteractionClient { message: Memory; thread: Tweet[]; }) { - if (tweet.userId === this.client.profile.id) { - // console.log("skipping tweet from bot itself", tweet.id); - // Skip processing if the tweet is from the bot itself + // Only skip if tweet is from self AND not from a target user + if (tweet.userId === this.client.profile.id && + !this.client.twitterConfig.TWITTER_TARGET_USERS.includes(tweet.username)) { return; } @@ -331,7 +331,6 @@ export class TwitterInteractionClient { }; const currentPost = formatTweet(tweet); - elizaLogger.debug("Thread: ", thread); const formattedConversation = thread .map( (tweet) => `@${tweet.username} (${new Date( @@ -346,13 +345,9 @@ export class TwitterInteractionClient { ) .join("\n\n"); - elizaLogger.debug("formattedConversation: ", formattedConversation); - const imageDescriptionsArray = []; try{ - elizaLogger.debug('Getting images'); for (const photo of tweet.photos) { - elizaLogger.debug(photo.url); const description = await this.runtime .getService( ServiceType.IMAGE_DESCRIPTION @@ -435,14 +430,31 @@ export class TwitterInteractionClient { } const context = composeContext({ - state, + state: { + ...state, + // Convert actionNames array to string + actionNames: Array.isArray(state.actionNames) + ? state.actionNames.join(', ') + : state.actionNames || '', + actions: Array.isArray(state.actions) + ? state.actions.join('\n') + : state.actions || '', + // Ensure character examples are included + characterPostExamples: this.runtime.character.messageExamples + ? this.runtime.character.messageExamples + .map(example => + example.map(msg => + `${msg.user}: ${msg.content.text}${msg.content.action ? ` [Action: ${msg.content.action}]` : ''}` + ).join('\n') + ).join('\n\n') + : '', + }, template: this.runtime.character.templates ?.twitterMessageHandlerTemplate || this.runtime.character?.templates?.messageHandlerTemplate || twitterMessageHandlerTemplate, }); - elizaLogger.debug("Interactions prompt:\n" + context); const response = await generateMessageResponse({ runtime: this.runtime, @@ -597,12 +609,6 @@ export class TwitterInteractionClient { visited.add(currentTweet.id); thread.unshift(currentTweet); - elizaLogger.debug("Current thread state:", { - length: thread.length, - currentDepth: depth, - tweetId: currentTweet.id, - }); - if (currentTweet.inReplyToStatusId) { elizaLogger.log( "Fetching parent tweet:", @@ -642,14 +648,6 @@ export class TwitterInteractionClient { // Need to bind this context for the inner function await processThread.bind(this)(tweet, 0); - elizaLogger.debug("Final thread built:", { - totalTweets: thread.length, - tweetIds: thread.map((t) => ({ - id: t.id, - text: t.text?.slice(0, 50), - })), - }); - return thread; } } \ No newline at end of file diff --git a/packages/plugin-solana/README.MD b/packages/plugin-solana/README.MD index 7dd32ec4796..c0c3e79d8c8 100644 --- a/packages/plugin-solana/README.MD +++ b/packages/plugin-solana/README.MD @@ -141,13 +141,25 @@ Transfers tokens between wallets. ```typescript // Example usage -const result = await runtime.executeAction("TRANSFER_TOKEN", { +const result = await runtime.executeAction("SEND_TOKEN", { tokenAddress: "TokenAddressHere", recipient: "RecipientAddressHere", amount: "1000", }); ``` +### transferSol + +Transfers SOL between wallets. + +```typescript +// Example usage +const result = await runtime.executeAction("SEND_SOL", { + recipient: "RecipientAddressHere", + amount: "1000", +}); +``` + ### takeOrder Places a buy order based on conviction level. diff --git a/packages/plugin-solana/src/actions/transfer.ts b/packages/plugin-solana/src/actions/transfer.ts index fd7547d9446..2da6952b756 100644 --- a/packages/plugin-solana/src/actions/transfer.ts +++ b/packages/plugin-solana/src/actions/transfer.ts @@ -3,14 +3,12 @@ import { createTransferInstruction, } from "@solana/spl-token"; import { elizaLogger, settings } from "@elizaos/core"; - import { Connection, PublicKey, TransactionMessage, VersionedTransaction, } from "@solana/web3.js"; - import { type ActionExample, type Content, @@ -57,44 +55,23 @@ Example response: {{recentMessages}} -Given the recent messages, extract the following information about the requested token transfer: +Extract the following information about the requested token transfer: - Token contract address - Recipient wallet address - Amount to transfer -Respond with a JSON markdown block containing only the extracted values.`; +If no token address is mentioned, respond with null. +`; export default { name: "SEND_TOKEN", - similes: [ - "TRANSFER_TOKEN", - "TRANSFER_TOKENS", - "SEND_TOKENS", - "SEND_SOL", - "PAY", - ], + similes: ["TRANSFER_TOKEN", "TRANSFER_TOKENS", "SEND_TOKENS", "PAY_TOKEN", "PAY_TOKENS", "PAY"], validate: async (runtime: IAgentRuntime, message: Memory) => { - elizaLogger.log("Validating transfer from user:", message.userId); - //add custom validate logic here - /* - const adminIds = runtime.getSetting("ADMIN_USER_IDS")?.split(",") || []; - //elizaLogger.log("Admin IDs from settings:", adminIds); - - const isAdmin = adminIds.includes(message.userId); - - if (isAdmin) { - //elizaLogger.log(`Authorized transfer from user: ${message.userId}`); - return true; - } - else - { - //elizaLogger.log(`Unauthorized transfer attempt from user: ${message.userId}`); - return false; - } - */ - return false; + // Always return true for token transfers, letting the handler deal with specifics + elizaLogger.log("Validating token transfer from user:", message.userId); + return true; }, - description: "Transfer tokens from the agent's wallet to another address", + description: "Transfer SPL tokens from agent's wallet to another address", handler: async ( runtime: IAgentRuntime, message: Memory, @@ -104,32 +81,27 @@ export default { ): Promise => { elizaLogger.log("Starting SEND_TOKEN handler..."); - // Initialize or update state if (!state) { state = (await runtime.composeState(message)) as State; } else { state = await runtime.updateRecentMessageState(state); } - // Compose transfer context const transferContext = composeContext({ state, template: transferTemplate, }); - // Generate transfer content const content = await generateObjectDeprecated({ runtime, context: transferContext, modelClass: ModelClass.LARGE, }); - // Validate transfer content if (!isTransferContent(runtime, content)) { - elizaLogger.error("Invalid content for TRANSFER_TOKEN action."); if (callback) { callback({ - text: "Unable to process transfer request. Invalid content provided.", + text: "Token address needed to send the token.", content: { error: "Invalid transfer content" }, }); } @@ -137,46 +109,23 @@ export default { } try { - const { keypair: senderKeypair } = await getWalletKey( - runtime, - true - ); - + const { keypair: senderKeypair } = await getWalletKey(runtime, true); const connection = new Connection(settings.SOLANA_RPC_URL!); - const mintPubkey = new PublicKey(content.tokenAddress); const recipientPubkey = new PublicKey(content.recipient); - // Get decimals (simplest way) const mintInfo = await connection.getParsedAccountInfo(mintPubkey); - const decimals = - (mintInfo.value?.data as any)?.parsed?.info?.decimals ?? 9; + const decimals = (mintInfo.value?.data as any)?.parsed?.info?.decimals ?? 9; + const adjustedAmount = BigInt(Number(content.amount) * Math.pow(10, decimals)); - // Adjust amount with decimals - const adjustedAmount = BigInt( - Number(content.amount) * Math.pow(10, decimals) - ); - elizaLogger.log( - `Transferring: ${content.amount} tokens (${adjustedAmount} base units)` - ); - - // Rest of the existing working code... - const senderATA = getAssociatedTokenAddressSync( - mintPubkey, - senderKeypair.publicKey - ); - const recipientATA = getAssociatedTokenAddressSync( - mintPubkey, - recipientPubkey - ); + const senderATA = getAssociatedTokenAddressSync(mintPubkey, senderKeypair.publicKey); + const recipientATA = getAssociatedTokenAddressSync(mintPubkey, recipientPubkey); const instructions = []; - const recipientATAInfo = - await connection.getAccountInfo(recipientATA); + const recipientATAInfo = await connection.getAccountInfo(recipientATA); if (!recipientATAInfo) { - const { createAssociatedTokenAccountInstruction } = - await import("@solana/spl-token"); + const { createAssociatedTokenAccountInstruction } = await import("@solana/spl-token"); instructions.push( createAssociatedTokenAccountInstruction( senderKeypair.publicKey, @@ -196,25 +145,20 @@ export default { ) ); - // Create and sign versioned transaction const messageV0 = new TransactionMessage({ payerKey: senderKeypair.publicKey, - recentBlockhash: (await connection.getLatestBlockhash()) - .blockhash, + recentBlockhash: (await connection.getLatestBlockhash()).blockhash, instructions, }).compileToV0Message(); const transaction = new VersionedTransaction(messageV0); transaction.sign([senderKeypair]); - // Send transaction const signature = await connection.sendTransaction(transaction); - elizaLogger.log("Transfer successful:", signature); - if (callback) { callback({ - text: `Successfully transferred ${content.amount} tokens to ${content.recipient}\nTransaction: ${signature}`, + text: `Sent ${content.amount} tokens to ${content.recipient}\nTransaction hash: ${signature}`, content: { success: true, signature, @@ -229,7 +173,7 @@ export default { elizaLogger.error("Error during token transfer:", error); if (callback) { callback({ - text: `Error transferring tokens: ${error.message}`, + text: `Issue with the transfer: ${error.message}`, content: { error: error.message }, }); } @@ -248,16 +192,10 @@ export default { { user: "{{user2}}", content: { - text: "I'll send 69 EZSIS tokens now...", + text: "Sending the tokens now...", action: "SEND_TOKEN", }, }, - { - user: "{{user2}}", - content: { - text: "Successfully sent 69 EZSIS tokens to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa\nTransaction: 5KtPn3DXXzHkb7VAVHZGwXJQqww39ASnrf7YkyJoF2qAGEpBEEGvRHLnnTG8ZVwKqNHMqSckWVGnsQAgfH5pbxEb", - }, - }, ], ] as ActionExample[][], } as Action; \ No newline at end of file diff --git a/packages/plugin-solana/src/actions/transfer_sol.ts b/packages/plugin-solana/src/actions/transfer_sol.ts new file mode 100644 index 00000000000..98dc37fa501 --- /dev/null +++ b/packages/plugin-solana/src/actions/transfer_sol.ts @@ -0,0 +1,165 @@ +import { elizaLogger, settings } from "@elizaos/core"; +import { + Connection, + PublicKey, + SystemProgram, + TransactionMessage, + VersionedTransaction, +} from "@solana/web3.js"; +import { + ActionExample, + Content, + HandlerCallback, + IAgentRuntime, + Memory, + ModelClass, + State, + type Action, +} from "@elizaos/core"; +import { composeContext } from "@elizaos/core"; +import { getWalletKey } from "../keypairUtils"; +import { generateObjectDeprecated } from "@elizaos/core"; + +interface SolTransferContent extends Content { + recipient: string; + amount: number; +} + +function isSolTransferContent( + content: any +): content is SolTransferContent { + return ( + typeof content.recipient === "string" && + typeof content.amount === "number" + ); +} + +const solTransferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. + +Example response: +\`\`\`json +{ + "recipient": "9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", + "amount": 1.5 +} +\`\`\` + +{{recentMessages}} + +Extract the following information about the requested SOL transfer: +- Recipient wallet address +- Amount of SOL to transfer +`; + +export default { + name: "SEND_SOL", + similes: ["TRANSFER_SOL", "PAY_SOL", "TRANSACT_SOL"], + validate: async (runtime: IAgentRuntime, message: Memory) => { + // Always return true for SOL transfers, letting the handler deal with specifics + elizaLogger.log("Validating SOL transfer from user:", message.userId); + return true; + }, + description: "Transfer native SOL from agent's wallet to specified address", + handler: async ( + runtime: IAgentRuntime, + message: Memory, + state: State, + _options: { [key: string]: unknown }, + callback?: HandlerCallback + ): Promise => { + elizaLogger.log("Starting SEND_SOL handler..."); + + if (!state) { + state = (await runtime.composeState(message)) as State; + } else { + state = await runtime.updateRecentMessageState(state); + } + + const transferContext = composeContext({ + state, + template: solTransferTemplate, + }); + + const content = await generateObjectDeprecated({ + runtime, + context: transferContext, + modelClass: ModelClass.LARGE, + }); + + if (!isSolTransferContent(content)) { + if (callback) { + callback({ + text: "Need an address and the amount of SOL to send.", + content: { error: "Invalid transfer content" }, + }); + } + return false; + } + + try { + const { keypair: senderKeypair } = await getWalletKey(runtime, true); + const connection = new Connection(settings.SOLANA_RPC_URL!); + const recipientPubkey = new PublicKey(content.recipient); + + const lamports = content.amount * 1e9; + + const instruction = SystemProgram.transfer({ + fromPubkey: senderKeypair.publicKey, + toPubkey: recipientPubkey, + lamports, + }); + + const messageV0 = new TransactionMessage({ + payerKey: senderKeypair.publicKey, + recentBlockhash: (await connection.getLatestBlockhash()).blockhash, + instructions: [instruction], + }).compileToV0Message(); + + const transaction = new VersionedTransaction(messageV0); + transaction.sign([senderKeypair]); + + const signature = await connection.sendTransaction(transaction); + + if (callback) { + callback({ + text: `Sent ${content.amount} SOL. Transaction hash: ${signature}`, + content: { + success: true, + signature, + amount: content.amount, + recipient: content.recipient, + }, + }); + } + + return true; + } catch (error) { + elizaLogger.error("Error during SOL transfer:", error); + if (callback) { + callback({ + text: `Problem with the SOL transfer: ${error.message}`, + content: { error: error.message }, + }); + } + return false; + } + }, + + examples: [ + [ + { + user: "{{user1}}", + content: { + text: "Send 1.5 SOL to 9jW8FPr6BSSsemWPV22UUCzSqkVdTp6HTyPqeqyuBbCa", + }, + }, + { + user: "{{user2}}", + content: { + text: "Sure thing, SOL on its way.", + action: "SEND_SOL", + }, + }, + ], + ] as ActionExample[][], +} as Action; \ No newline at end of file diff --git a/packages/plugin-solana/src/index.ts b/packages/plugin-solana/src/index.ts index ef80877b2ed..0398a943b60 100644 --- a/packages/plugin-solana/src/index.ts +++ b/packages/plugin-solana/src/index.ts @@ -2,36 +2,34 @@ export * from "./providers/token.ts"; export * from "./providers/wallet.ts"; export * from "./providers/trustScoreProvider.ts"; export * from "./evaluators/trust.ts"; - import type { Plugin } from "@elizaos/core"; +import transferToken from "./actions/transfer.ts"; +import transferSol from "./actions/transfer_sol.ts"; +import { TokenProvider } from "./providers/token.ts"; +import { WalletProvider } from "./providers/wallet.ts"; +import { getTokenBalance, getTokenBalances } from "./providers/tokenUtils.ts"; +import { walletProvider } from "./providers/wallet.ts"; +import { trustScoreProvider } from "./providers/trustScoreProvider.ts"; +import { trustEvaluator } from "./evaluators/trust.ts"; import { executeSwap } from "./actions/swap.ts"; import take_order from "./actions/takeOrder"; import pumpfun from "./actions/pumpfun.ts"; import fomo from "./actions/fomo.ts"; import { executeSwapForDAO } from "./actions/swapDao"; -import transferToken from "./actions/transfer.ts"; -import { walletProvider } from "./providers/wallet.ts"; -import { trustScoreProvider } from "./providers/trustScoreProvider.ts"; -import { trustEvaluator } from "./evaluators/trust.ts"; -import { TokenProvider } from "./providers/token.ts"; -import { WalletProvider } from "./providers/wallet.ts"; -import { getTokenBalance, getTokenBalances } from "./providers/tokenUtils.ts"; - export { TokenProvider, WalletProvider, getTokenBalance, getTokenBalances }; - export const solanaPlugin: Plugin = { name: "solana", description: "Solana Plugin for Eliza", actions: [ + transferToken, + transferSol, executeSwap, pumpfun, fomo, - transferToken, executeSwapForDAO, take_order, ], evaluators: [trustEvaluator], providers: [walletProvider, trustScoreProvider], }; - export default solanaPlugin; \ No newline at end of file