@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
JavaScript
// 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