UNPKG

@elizaos/plugin-avalanche

Version:

Avalanche blockchain plugin for ElizaOS - enables DeFi operations, token transfers, YAK swaps, and yield strategies

1,340 lines (1,291 loc) 40.2 kB
// src/actions/tokenMillCreate.ts import { elizaLogger as elizaLogger3, composePromptFromState, parseKeyValueXml, ModelType } from "@elizaos/core"; // src/environment.ts import { z } from "zod"; var avalancheEnvSchema = z.object({ AVALANCHE_PRIVATE_KEY: z.string().min(1, "Avalanche private key is required") }); async function validateAvalancheConfig(runtime) { try { const config = { AVALANCHE_PRIVATE_KEY: runtime.getSetting("AVALANCHE_PRIVATE_KEY") || process.env.AVALANCHE_PRIVATE_KEY }; return avalancheEnvSchema.parse(config); } catch (error) { if (error instanceof z.ZodError) { const errorMessages = error.errors.map((err) => `${err.path.join(".")}: ${err.message}`).join("\n"); throw new Error(errorMessages); } throw error; } } // src/utils/index.ts import { elizaLogger } from "@elizaos/core"; import { createPublicClient, createWalletClient, http, parseUnits } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { avalanche } from "viem/chains"; // src/utils/constants.ts var TOKEN_ADDRESSES = { AVAX: "0x0000000000000000000000000000000000000000", WAVAX: "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7", YAK: "0x59414b3089ce2AF0010e7523Dea7E2b35d776ec7", gmYAK: "0x3A30784c1af928CdFce678eE49370220aA716DC3", USDC: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", JOE: "0x6e84a6216eA6dACC71eE8E6b0a5B7322EEbC0fDd", AUSD: "0x00000000eFE302BEAA2b3e6e1b18d08D69a9012a", PRINCESS: "0xB310Ed3A7F4Ae79E59dCa99784b312c2D19fFC7C", KIMBO: "0x184ff13B3EBCB25Be44e860163A5D8391Dd568c1", COQ: "0x420FcA0121DC28039145009570975747295f2329" }; var STRATEGY_ADDRESSES = { YAK: "0x0C4684086914D5B1525bf16c62a0FF8010AB991A", // Yield Yak YAK USDC: "0xFB692D03BBEA21D8665035779dd3082c2B1622d0", // Benqi USDC gmYAK: "0x9db213cE52155A9462A869Af495234e4734DC08a", // Token Mill gmYAK PRINCESS: "0xA714d1f61D14F0beDecC0e0812A5641BD01424eD", JOE: "0x714e06410B4960D3C1FC033bCd53ad9EB2d1f874" // sJOE }; var YAK_SWAP_CONFIG = { router: "0xC4729E56b831d74bBc18797e0e17A295fA77488c" }; var TOKEN_MILL_CONFIG = { factory: "0x501ee2D4AA611C906F785e10cC868e145183FCE4" }; // src/utils/index.ts var getAccount = (runtime) => { const privateKey = runtime.getSetting("AVALANCHE_PRIVATE_KEY") || process.env.AVALANCHE_PRIVATE_KEY; return privateKeyToAccount(`0x${privateKey.replace("0x", "")}`); }; var getPublicClient = (_runtime) => { return createPublicClient({ chain: avalanche, transport: http() }); }; var getWalletClient = (runtime) => { return createWalletClient({ account: getAccount(runtime), chain: avalanche, transport: http() }); }; var getTxReceipt = async (runtime, tx) => { const publicClient = getPublicClient(runtime); const receipt = await publicClient.waitForTransactionReceipt({ hash: tx }); return receipt; }; var getDecimals = async (runtime, tokenAddress) => { if (tokenAddress === "0x0000000000000000000000000000000000000000") { return avalanche.nativeCurrency.decimals; } const publicClient = getPublicClient(runtime); const decimals = await publicClient.readContract({ address: tokenAddress, abi: [ { inputs: [], name: "decimals", outputs: [{ internalType: "uint8", name: "", type: "uint8" }], stateMutability: "view", type: "function" } ], functionName: "decimals" }); return decimals; }; var getNativeBalance = async (runtime, owner) => { const publicClient = getPublicClient(runtime); const balance = await publicClient.getBalance({ address: owner }); return balance; }; var getTokenBalance = async (runtime, tokenAddress, owner) => { if (tokenAddress === "0x0000000000000000000000000000000000000000") { return getNativeBalance(runtime, owner); } const publicClient = getPublicClient(runtime); const balance = await publicClient.readContract({ address: tokenAddress, abi: [ { inputs: [ { internalType: "address", name: "account", type: "address" } ], name: "balanceOf", outputs: [ { internalType: "uint256", name: "", type: "uint256" } ], stateMutability: "view", type: "function" } ], functionName: "balanceOf", args: [owner] }); return balance; }; var getQuote = async (runtime, fromTokenAddress, toTokenAddress, amount) => { const publicClient = getPublicClient(runtime); const decimals = await getDecimals(runtime, fromTokenAddress); const maxSteps = 2; const gasPrice = parseUnits("25", "gwei"); const quote = await publicClient.readContract({ address: YAK_SWAP_CONFIG.router, abi: [ { inputs: [ { internalType: "uint256", name: "_amountIn", type: "uint256" }, { internalType: "address", name: "_tokenIn", type: "address" }, { internalType: "address", name: "_tokenOut", type: "address" }, { internalType: "uint256", name: "_maxSteps", type: "uint256" }, { internalType: "uint256", name: "_gasPrice", type: "uint256" } ], name: "findBestPathWithGas", outputs: [ { components: [ { internalType: "uint256[]", name: "amounts", type: "uint256[]" }, { internalType: "address[]", name: "adapters", type: "address[]" }, { internalType: "address[]", name: "path", type: "address[]" }, { internalType: "uint256", name: "gasEstimate", type: "uint256" } ], internalType: "struct YakRouter.FormattedOfferWithGas", name: "", type: "tuple" } ], stateMutability: "view", type: "function" } ], functionName: "findBestPathWithGas", args: [ parseUnits(amount.toString(), decimals), fromTokenAddress, toTokenAddress, maxSteps, gasPrice ] }); elizaLogger.log("Quote:", quote); return quote; }; var sendNativeAsset = async (runtime, recipient, amount) => { const walletClient = getWalletClient(runtime); const decimals = await getDecimals( runtime, "0x0000000000000000000000000000000000000000" ); const tx = await walletClient.sendTransaction({ to: recipient, value: parseUnits(amount.toString(), decimals) }); return tx; }; var sendToken = async (runtime, tokenAddress, recipient, amount) => { const decimals = await getDecimals(runtime, tokenAddress); const publicClient = getPublicClient(runtime); try { const { result, request } = await publicClient.simulateContract({ account: getAccount(runtime), address: tokenAddress, abi: [ { inputs: [ { internalType: "address", name: "dst", type: "address" }, { internalType: "uint256", name: "amount", type: "uint256" } ], name: "transfer", outputs: [ { internalType: "bool", name: "", type: "bool" } ], stateMutability: "nonpayable", type: "function" } ], functionName: "transfer", args: [recipient, parseUnits(amount.toString(), decimals)] }); if (!result) { throw new Error("Transfer failed"); } elizaLogger.debug("Request:", request); const walletClient = getWalletClient(runtime); const tx = await walletClient.writeContract(request); elizaLogger.log("Transaction:", tx); return tx; } catch (error) { elizaLogger.error("Error simulating contract:", error); return; } }; var approve = async (runtime, tokenAddress, spender, amount) => { try { const decimals = await getDecimals(runtime, tokenAddress); const publicClient = getPublicClient(runtime); const { result, request } = await publicClient.simulateContract({ account: getAccount(runtime), address: tokenAddress, abi: [ { inputs: [ { internalType: "address", name: "_spender", type: "address" }, { internalType: "uint256", name: "_value", type: "uint256" } ], name: "approve", outputs: [ { internalType: "bool", name: "", type: "bool" } ], stateMutability: "nonpayable", type: "function" } ], functionName: "approve", args: [spender, parseUnits(amount.toString(), decimals)] }); if (!result) { throw new Error("Approve failed"); } elizaLogger.debug("Request:", request); const walletClient = getWalletClient(runtime); const tx = await walletClient.writeContract(request); elizaLogger.log("Transaction:", tx); return tx; } catch (error) { elizaLogger.error("Error approving:", error); return; } }; var swap = async (runtime, quote, recipient) => { const slippageBips = 20n; const amountOut = quote.amounts[quote.amounts.length - 1]; const allowedSlippage = amountOut * slippageBips / 10000n; const trade = { amountIn: quote.amounts[0], amountOut: amountOut - allowedSlippage, path: quote.path, adapters: quote.adapters }; try { const account = getAccount(runtime); const publicClient = getPublicClient(runtime); const { _result, request } = await publicClient.simulateContract({ account, address: YAK_SWAP_CONFIG.router, abi: [ { inputs: [ { components: [ { internalType: "uint256", name: "amountIn", type: "uint256" }, { internalType: "uint256", name: "amountOut", type: "uint256" }, { internalType: "address[]", name: "path", type: "address[]" }, { internalType: "address[]", name: "adapters", type: "address[]" } ], internalType: "struct YakRouter.Trade", name: "_trade", type: "tuple" }, { internalType: "address", name: "_to", type: "address" }, { internalType: "uint256", name: "_fee", type: "uint256" } ], name: "swapNoSplit", outputs: [], stateMutability: "nonpayable", type: "function" } ], functionName: "swapNoSplit", args: [trade, recipient || account.address, 0n] }); elizaLogger.debug("Request:", request); const walletClient = getWalletClient(runtime); const tx = await walletClient.writeContract(request); elizaLogger.log("Transaction:", tx); return tx; } catch (error) { elizaLogger.error("Error simulating contract:", error); return; } }; var deposit = async (runtime, depositTokenAddress, strategyAddress, amount) => { try { const decimals = await getDecimals(runtime, depositTokenAddress); const publicClient = getPublicClient(runtime); const { _result, request } = await publicClient.simulateContract({ account: getAccount(runtime), address: strategyAddress, abi: [ { inputs: [ { internalType: "uint256", name: "_amount", type: "uint256" } ], name: "deposit", outputs: [], stateMutability: "nonpayable", type: "function" } ], functionName: "deposit", args: [parseUnits(amount.toString(), decimals)] }); elizaLogger.debug("Request:", request); const walletClient = getWalletClient(runtime); const tx = await walletClient.writeContract(request); elizaLogger.log("Transaction:", tx); return tx; } catch (error) { elizaLogger.error("Error depositing:", error); return; } }; // src/utils/tokenMill.ts import { elizaLogger as elizaLogger2 } from "@elizaos/core"; import { encodeAbiParameters, parseUnits as parseUnits2 } from "viem"; var createMarketAndToken = async (runtime, name, symbol) => { const account = getAccount(runtime); const publicClient = getPublicClient(runtime); const abi = [ { inputs: [ { components: [ { internalType: "uint96", name: "tokenType", type: "uint96" }, { internalType: "string", name: "name", type: "string" }, { internalType: "string", name: "symbol", type: "string" }, { internalType: "address", name: "quoteToken", type: "address" }, { internalType: "uint256", name: "totalSupply", type: "uint256" }, { internalType: "uint16", name: "creatorShare", type: "uint16" }, { internalType: "uint16", name: "stakingShare", type: "uint16" }, { internalType: "uint256[]", name: "bidPrices", type: "uint256[]" }, { internalType: "uint256[]", name: "askPrices", type: "uint256[]" }, { internalType: "bytes", name: "args", type: "bytes" } ], internalType: "struct ITMFactory.MarketCreationParameters", name: "parameters", type: "tuple" } ], name: "createMarketAndToken", outputs: [ { internalType: "address", name: "baseToken", type: "address" }, { internalType: "address", name: "market", type: "address" } ], stateMutability: "nonpayable", type: "function" } ]; if (name.length === 0) { throw new Error("Name must be provided"); } if (name.length > 32) { throw new Error("Name must be less than 12 characters"); } if (symbol.length === 0) { throw new Error("Symbol must be provided"); } if (symbol.length > 8) { throw new Error("Symbol must be less than 8 characters"); } const params = { tokenType: 1, name, symbol, quoteToken: TOKEN_ADDRESSES.WAVAX, totalSupply: parseUnits2("100000000", 18), creatorShare: 2e3, stakingShare: 6e3, bidPrices: [ 0, 0.018117, 0.042669, 0.075735, 0.12078, 0.18018, 0.26235, 0.37124999999999997, 0.51975, 0.71973, 0.99 ].map((price) => parseUnits2(price.toString(), 18)), askPrices: [ 0, 0.0183, 0.0431, 0.0765, 0.122, 0.182, 0.265, 0.375, 0.525, 0.727, 1 ].map((price) => parseUnits2(price.toString(), 18)), args: encodeAbiParameters( [{ name: "decimals", type: "uint256" }], [18n] ) }; const { result, request } = await publicClient.simulateContract({ account, address: TOKEN_MILL_CONFIG.factory, abi, functionName: "createMarketAndToken", args: [params] }); if (!result) { throw new Error("Create failed"); } elizaLogger2.debug("request", request); elizaLogger2.debug("result", result); elizaLogger2.debug("Request:", request); const walletClient = getWalletClient(runtime); const tx = await walletClient.writeContract(request); elizaLogger2.log("Transaction:", tx); return { tx, baseToken: result[0], market: result[1] }; }; // src/actions/tokenMillCreate.ts function isTokenMillCreateContent(_runtime, content) { elizaLogger3.debug("Content for create", content); return typeof content.name === "string" && typeof content.symbol === "string"; } var transferTemplate = `Respond with an XML block containing only the extracted values. Use key-value pairs. If the user did not provide enough details, respond with what you can. Name and Symbol are required. Example response for a new token: <response> <name>Test Token</name> <symbol>TEST</symbol> </response> ## Recent Messages {{recentMessages}} Given the recent messages, extract the following information about the requested token creation: - Name - Symbol Respond with an XML block containing only the extracted values.`; var tokenMillCreate_default = { name: "CREATE_TOKEN", similes: [ "LAUNCH_TOKEN", "NEW_TOKEN", "CREATE_MEMECOIN", "CREATE_MEME_TOKEN" ], validate: async (runtime, _message) => { await validateAvalancheConfig(runtime); return true; }, description: "MUST use this action if the user requests to create a new token, the request might be varied, but it will always be a token creation.", handler: async (runtime, message, state, _options, callback) => { elizaLogger3.log("Starting CREATE_TOKEN handler..."); let currentState = state; if (!currentState) { currentState = await runtime.composeState(message); } else { currentState = await runtime.composeState(message, [ "RECENT_MESSAGES" ]); } const prompt = composePromptFromState({ state: currentState, template: transferTemplate }); const result = await runtime.useModel(ModelType.TEXT_SMALL, { prompt, stopSequences: [] }); const content = parseKeyValueXml(result); elizaLogger3.debug("Create content:", content); if (!isTokenMillCreateContent(runtime, content)) { elizaLogger3.error("Invalid content for CREATE_TOKEN action."); callback?.({ text: "Unable to process transfer request. Invalid content provided.", content: { error: "Invalid content" } }); return false; } const { tx, baseToken, market } = await createMarketAndToken( runtime, content.name, content.symbol ); callback?.({ text: `Created token ${content.name} with symbol ${content.symbol}. CA: ${baseToken}`, content: { tx, baseToken, market } }); return true; }, examples: [ [ { user: "{{user1}}", content: { text: "Create a new memecoin called 'Test Token' with the symbol 'TEST'" } }, { name: "{{user2}}", content: { action: "CREATE_TOKEN", name: "Test Token", symbol: "TEST" } } ], [ { user: "{{user1}}", content: { text: "Create a token called news" } }, { name: "{{user2}}", content: { action: "CREATE_TOKEN", name: "News Token", symbol: "NEWS" } } ], [ { user: "{{user1}}", content: { text: "Create a token" } }, { name: "{{user2}}", content: { action: "CREATE_TOKEN", name: "Okay", symbol: "OK" } } ] ] }; // src/actions/transfer.ts import { elizaLogger as elizaLogger4, composePromptFromState as composePromptFromState2, parseKeyValueXml as parseKeyValueXml2, ModelType as ModelType2 } from "@elizaos/core"; function isTransferContent(_runtime, content) { elizaLogger4.debug("Content for transfer", content); return typeof content === "object" && content !== null && "tokenAddress" in content && "recipient" in content && "amount" in content && typeof content.tokenAddress === "string" && content.tokenAddress.startsWith("0x") && typeof content.recipient === "string" && content.recipient.startsWith("0x") && (typeof content.amount === "string" || typeof content.amount === "number"); } var transferTemplate2 = `Respond with an XML block containing only the extracted values. Use key-value pairs. - Use null for any values that cannot be determined. - Use address zero for native AVAX transfers. Example response for a 10 WAVAX transfer: <response> <tokenAddress>0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7</tokenAddress> <recipient>0xDcEDF06Fd33E1D7b6eb4b309f779a0e9D3172e44</recipient> <amount>10</amount> </response> Example response for a 0.1 AVAX transfer: <response> <tokenAddress>0x0000000000000000000000000000000000000000</tokenAddress> <recipient>0xDcEDF06Fd33E1D7b6eb4b309f779a0e9D3172e44</recipient> <amount>0.1</amount> </response> ## Token Addresses ${Object.entries(TOKEN_ADDRESSES).map(([key, value]) => `- ${key}: ${value}`).join("\n")} ## Recent Messages {{recentMessages}} Given the recent messages, extract the following information about the requested token transfer: - Token contract address - Recipient wallet address - Amount to transfer Respond with an XML block containing only the extracted values.`; var transfer_default = { name: "SEND_TOKEN", similes: [ "TRANSFER_TOKEN_ON_AVALANCHE", "TRANSFER_TOKENS_ON_AVALANCHE", "SEND_TOKENS_ON_AVALANCHE", "SEND_AVAX_ON_AVALANCHE", "PAY_ON_AVALANCHE" ], validate: async (runtime, _message) => { await validateAvalancheConfig(runtime); return true; }, description: "MUST use this action if the user requests send a token or transfer a token, the request might be varied, but it will always be a token transfer.", handler: async (runtime, message, state, _options, callback) => { elizaLogger4.log("Starting SEND_TOKEN handler..."); if (message.content.source === "direct") { } else { callback?.({ text: "i can't do that for you.", content: { error: "Transfer not allowed" } }); return false; } let currentState = state; if (!currentState) { currentState = await runtime.composeState(message); } else { currentState = await runtime.composeState(message, [ "RECENT_MESSAGES" ]); } const prompt = composePromptFromState2({ state: currentState, template: transferTemplate2 }); const result = await runtime.useModel(ModelType2.TEXT_SMALL, { prompt, stopSequences: [] }); const content = parseKeyValueXml2(result); elizaLogger4.debug("Transfer content:", content); if (!isTransferContent(runtime, content)) { elizaLogger4.error("Invalid content for TRANSFER_TOKEN action."); callback?.({ text: "Unable to process transfer request. Invalid content provided.", content: { error: "Invalid transfer content" } }); return false; } let tx; if (content.tokenAddress === "0x0000000000000000000000000000000000000000") { tx = await sendNativeAsset( runtime, content.recipient, content.amount ); } else { tx = await sendToken( runtime, content.tokenAddress, content.recipient, content.amount ); } if (tx) { const receipt = await getTxReceipt(runtime, tx); if (receipt.status === "success") { callback?.({ text: "transfer successful", content: { success: true, txHash: tx } }); } else { callback?.({ text: "transfer failed", content: { error: "Transfer failed" } }); } } else { callback?.({ text: "transfer failed", content: { error: "Transfer failed" } }); } return true; }, examples: [ [ { user: "{{user1}}", content: { text: "Send 10 AVAX to 0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" } } ] ] }; // src/actions/yakSwap.ts import { elizaLogger as elizaLogger5, composePromptFromState as composePromptFromState3, parseKeyValueXml as parseKeyValueXml3, ModelType as ModelType3 } from "@elizaos/core"; function isSwapContent(_runtime, content) { elizaLogger5.debug("Content for swap", content); return typeof content === "object" && content !== null && "fromTokenAddress" in content && "toTokenAddress" in content && typeof content.fromTokenAddress === "string" && typeof content.toTokenAddress === "string" && (typeof content.recipient === "string" || !content.recipient) && (typeof content.amount === "string" || typeof content.amount === "number"); } var transferTemplate3 = `Respond with an XML block containing only the extracted values. Use key-value pairs. - Use null for any values that cannot be determined. - Use address zero for native AVAX transfers. - If our balance is not enough, use null for the amount. Example response for a 10 AVAX to USDC swap: <response> <fromTokenAddress>0x0000000000000000000000000000000000000000</fromTokenAddress> <toTokenAddress>0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E</toTokenAddress> <recipient>null</recipient> <amount>10</amount> </response> Example response for a 10 WAVAX to USDC swap: <response> <fromTokenAddress>0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7</fromTokenAddress> <toTokenAddress>0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E</toTokenAddress> <recipient>0xDcEDF06Fd33E1D7b6eb4b309f779a0e9D3172e44</recipient> <amount>10</amount> </response> Example response to buy WAVAX with 5 USDC: <response> <fromTokenAddress>0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E</fromTokenAddress> <toTokenAddress>0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7</toTokenAddress> <recipient>0xDcEDF06Fd33E1D7b6eb4b309f779a0e9D3172e44</recipient> <amount>5</amount> </response> Example response to sell 5 USDC for gmYAK: <response> <fromTokenAddress>0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E</fromTokenAddress> <toTokenAddress>0x3A30784c1af928CdFce678eE49370220aA716DC3</toTokenAddress> <recipient>0xDcEDF06Fd33E1D7b6eb4b309f779a0e9D3172e44</recipient> <amount>5</amount> </response> ## Token Addresses ${Object.entries(TOKEN_ADDRESSES).map(([key, value]) => `- ${key}: ${value}`).join("\n")} ## Recent Messages {{recentMessages}} Given the recent messages, extract the following information about the requested token transfer: - From token address (the token to sell) - To token address (the token to buy) - Recipient wallet address (optional) - Amount to sell Respond with an XML block containing only the extracted values.`; var yakSwap_default = { name: "SWAP_TOKEN", similes: ["TRADE_TOKEN", "BUY_TOKEN", "SELL_TOKEN"], validate: async (runtime, _message) => { await validateAvalancheConfig(runtime); return true; }, description: "MUST use this action if the user requests swap a token, the request might be varied, but it will always be a token swap.", handler: async (runtime, message, state, _options, callback) => { elizaLogger5.log("Starting SWAP_TOKEN handler..."); let currentState = state; if (!currentState) { currentState = await runtime.composeState(message); } else { currentState = await runtime.composeState(message, [ "RECENT_MESSAGES" ]); } const prompt = composePromptFromState3({ state: currentState, template: transferTemplate3 }); const result = await runtime.useModel(ModelType3.TEXT_SMALL, { prompt, stopSequences: [] }); const content = parseKeyValueXml3(result); if (!isSwapContent(runtime, content)) { elizaLogger5.error("Invalid content for SWAP_TOKEN action."); callback?.({ text: "Unable to process swap request. Invalid content provided.", content: { error: "Invalid swap content" } }); return false; } elizaLogger5.debug("Swap content:", content); const quote = await getQuote( runtime, content.fromTokenAddress, content.toTokenAddress, content.amount ); if (content.fromTokenAddress === "0x0000000000000000000000000000000000000000") { elizaLogger5.log("Swapping from native AVAX"); } else if (content.toTokenAddress === "0x0000000000000000000000000000000000000000") { elizaLogger5.log("Swapping to native AVAX"); } else { const yakRouterAddress = YAK_SWAP_CONFIG.router; const tx = await approve( runtime, content.fromTokenAddress, yakRouterAddress, content.amount ); callback?.({ text: "approving token...", content: { success: true } }); if (tx) { let receipt = await getTxReceipt(runtime, tx); if (receipt.status === "success") { callback?.({ text: "token approved, swapping...", content: { success: true, txHash: tx } }); const swapTx = await swap(runtime, quote); if (swapTx) { receipt = await getTxReceipt(runtime, swapTx); if (receipt.status === "success") { elizaLogger5.log("Swap successful"); callback?.({ text: "swap successful", content: { success: true, txHash: swapTx } }); } else { elizaLogger5.error("Swap failed"); callback?.({ text: "swap failed", content: { error: "Swap failed" } }); } } } else { elizaLogger5.error("Approve failed"); callback?.({ text: "approve failed", content: { error: "Approve failed" } }); } } else { callback?.({ text: "approve failed", content: { error: "Approve failed" } }); } } return true; }, examples: [ [ { user: "{{user1}}", content: { text: "Swap 1 AVAX for USDC" } } ], [ { user: "{{user1}}", content: { text: "Swap 10 USDC for gmYAK" } } ] ] }; // src/actions/yakStrategy.ts import { elizaLogger as elizaLogger6, composePromptFromState as composePromptFromState4, parseKeyValueXml as parseKeyValueXml4, ModelType as ModelType4 } from "@elizaos/core"; function isStrategyContent(_runtime, content) { elizaLogger6.debug("Content for strategy", content); return typeof content === "object" && content !== null && "depositTokenAddress" in content && "strategyAddress" in content && "amount" in content && typeof content.depositTokenAddress === "string" && typeof content.strategyAddress === "string" && (typeof content.amount === "string" || typeof content.amount === "number"); } var strategyTemplate = `Respond with an XML block containing only the extracted values. Use key-value pairs. - Use null for any values that cannot be determined. - Use address zero for native AVAX. Example response for a 100 USDC deposit into a strategy: <response> <depositTokenAddress>0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E</depositTokenAddress> <strategyAddress>0xFB692D03BBEA21D8665035779dd3082c2B1622d0</strategyAddress> <amount>100</amount> </response> Example response for a 10 WAVAX deposit into a strategy: <response> <depositTokenAddress>0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7</depositTokenAddress> <strategyAddress>0x8B414448de8B609e96bd63Dcf2A8aDbd5ddf7fdd</strategyAddress> <amount>10</amount> </response> ## Token Addresses ${Object.entries(TOKEN_ADDRESSES).map(([key, value]) => `- ${key}: ${value}`).join("\n")} ## Strategy Addresses ${Object.entries(STRATEGY_ADDRESSES).map(([key, value]) => `- ${key}: ${value}`).join("\n")} ## Recent Messages {{recentMessages}} Given the recent messages, extract the following information about the requested strategy management: - Deposit token address (the token to deposit) - Strategy address (the strategy to deposit into) - Amount to deposit Respond with an XML block containing only the extracted values.`; var yakStrategy_default = { name: "DEPOSIT_TO_STRATEGY", similes: ["DEPOSIT_FOR_YIELD", "DEPOSIT_TOKENS"], validate: async (runtime, _message) => { await validateAvalancheConfig(runtime); return true; }, description: "MUST use this action if the user requests to deposit into a yield-earning strategy, the request might be varied, but it will always be a deposit into a strategy.", handler: async (runtime, message, state, _options, callback) => { elizaLogger6.log("Starting DEPOSIT_TO_STRATEGY handler..."); let currentState = state; if (!currentState) { currentState = await runtime.composeState(message); } else { currentState = await runtime.composeState(message, [ "RECENT_MESSAGES" ]); } const prompt = composePromptFromState4({ state: currentState, template: strategyTemplate }); const result = await runtime.useModel(ModelType4.TEXT_SMALL, { prompt, stopSequences: [] }); const content = parseKeyValueXml4(result); if (!isStrategyContent(runtime, content)) { elizaLogger6.error( "Invalid content for DEPOSIT_TO_STRATEGY action." ); callback?.({ text: "Unable to process deposit request. Invalid content provided.", content: { error: "Invalid deposit content" } }); return false; } elizaLogger6.debug("Deposit content:", content); if (content.depositTokenAddress === "0x0000000000000000000000000000000000000000") { elizaLogger6.log("Swapping from native AVAX"); } else { const tx = await approve( runtime, content.depositTokenAddress, content.strategyAddress, content.amount ); callback?.({ text: "approving token...", content: { success: true } }); if (tx) { let receipt = await getTxReceipt(runtime, tx); if (receipt.status === "success") { callback?.({ text: "token approved, depositing...", content: { success: true, txHash: tx } }); const depositTx = await deposit( runtime, content.depositTokenAddress, content.strategyAddress, content.amount ); if (depositTx) { receipt = await getTxReceipt(runtime, depositTx); if (receipt.status === "success") { callback?.({ text: "deposit successful", content: { success: true, txHash: depositTx } }); } else { callback?.({ text: "deposit failed", content: { error: "Deposit failed" } }); } } } else { callback?.({ text: "approve failed", content: { error: "Approve failed" } }); } } else { callback?.({ text: "approve failed", content: { error: "Approve failed" } }); } } return true; }, examples: [ [ { user: "{{user1}}", content: { text: "Deposit 1 USDC into the strategy" } } ], [ { user: "{{user1}}", content: { text: "Deposit 10 gmYAK to earn yield" } } ] ] }; // src/providers/tokens.ts import { elizaLogger as elizaLogger7 } from "@elizaos/core"; var tokensProvider = { name: "tokensProvider", description: "Provides available token addresses on Avalanche", get: async (_runtime, _message, _state) => { elizaLogger7.debug("tokensProvider::get"); const tokens = Object.entries(TOKEN_ADDRESSES).map(([key, value]) => `${key}: ${value}`).join("\n"); return { text: `The available tokens and their addresses are: ${tokens}`, data: TOKEN_ADDRESSES }; } }; // src/providers/strategies.ts import { elizaLogger as elizaLogger8 } from "@elizaos/core"; var strategiesProvider = { name: "strategiesProvider", description: "Provides available yield strategy addresses on Avalanche", get: async (_runtime, _message, _state) => { elizaLogger8.debug("strategiesProvider::get"); const strategies = Object.entries(STRATEGY_ADDRESSES).map(([key, value]) => `${key}: ${value}`).join("\n"); return { text: `The available strategy addresses and their deposit tokens are: ${strategies}`, data: STRATEGY_ADDRESSES }; } }; // src/providers/wallet.ts import { elizaLogger as elizaLogger9 } from "@elizaos/core"; import { formatUnits } from "viem"; var walletProvider = { name: "walletProvider", description: "Provides wallet balance information for tokens and strategies on Avalanche", dynamic: true, get: async (runtime, _message, _state) => { elizaLogger9.debug("walletProvider::get"); const privateKey = runtime.getSetting("AVALANCHE_PRIVATE_KEY"); if (!privateKey) { throw new Error( "AVALANCHE_PRIVATE_KEY not found in environment variables" ); } const account = getAccount(runtime); let output = "# Wallet Balances\n\n"; output += `## Wallet Address \`${account.address}\` `; output += "## Latest Token Balances\n\n"; for (const [token, address] of Object.entries(TOKEN_ADDRESSES)) { const decimals = await getDecimals(runtime, address); const balance = await getTokenBalance( runtime, address, account.address ); output += `${token}: ${formatUnits(balance, decimals)} `; } output += "Note: These balances can be used at any time.\n\n"; output += "## Balances in Yield Strategies\n\n"; for (const [strategy, address] of Object.entries(STRATEGY_ADDRESSES)) { const balance = await getTokenBalance( runtime, address, account.address ); const decimals = await getDecimals(runtime, address); output += `${strategy}: ${formatUnits(balance, decimals)} `; } output += "Note: These balances must be withdrawn from the strategy before they can be used.\n\n"; elizaLogger9.debug("walletProvider::get output:", output); const balanceData = { walletAddress: account.address, tokenBalances: {}, strategyBalances: {} }; for (const [token, address] of Object.entries(TOKEN_ADDRESSES)) { const decimals = await getDecimals(runtime, address); const balance = await getTokenBalance(runtime, address, account.address); balanceData.tokenBalances[token] = formatUnits(balance, decimals); } for (const [strategy, address] of Object.entries(STRATEGY_ADDRESSES)) { const balance = await getTokenBalance(runtime, address, account.address); const decimals = await getDecimals(runtime, address); balanceData.strategyBalances[strategy] = formatUnits(balance, decimals); } return { text: output, data: balanceData }; } }; // src/index.ts var PROVIDER_CONFIG = { TOKEN_ADDRESSES, STRATEGY_ADDRESSES, YAK_SWAP_CONFIG, TOKEN_MILL_CONFIG }; var avalanchePlugin = { name: "avalanche", description: "Avalanche Plugin for Eliza", actions: [transfer_default, yakSwap_default, yakStrategy_default, tokenMillCreate_default], evaluators: [], providers: [tokensProvider, strategiesProvider, walletProvider], services: [] }; var index_default = avalanchePlugin; export { PROVIDER_CONFIG, avalanchePlugin, index_default as default }; //# sourceMappingURL=index.js.map