From e6dfc08379b1b8768431d15f87e848ef19d945c9 Mon Sep 17 00:00:00 2001 From: KrausCrypto <75761052+ajkraus04@users.noreply.github.com> Date: Fri, 24 Jan 2025 12:19:08 -0500 Subject: [PATCH] Bug/fix ton plugin (#2755) * Added wallet to action constructor * Update ton plugin to fix bugs. Needed to add a sleep function as Ton SDK has a ratelimit of 1 request per second. * Remove test only. * Update dobby.character.json --------- Co-authored-by: Sayo --- characters/dobby.character.json | 13 +-- packages/plugin-ton/src/actions/transfer.ts | 104 +++++++++++++++++--- packages/plugin-ton/src/providers/wallet.ts | 61 ++++++------ packages/plugin-ton/src/util.ts | 7 ++ 4 files changed, 131 insertions(+), 54 deletions(-) create mode 100644 packages/plugin-ton/src/util.ts diff --git a/characters/dobby.character.json b/characters/dobby.character.json index f23d0f8d6ca..50398f81626 100644 --- a/characters/dobby.character.json +++ b/characters/dobby.character.json @@ -61,9 +61,7 @@ "Dobby reminds friends that even the smallest helper can make the biggest difference!", "Dobby says: 'When in doubt, try the unconventional solution!' (But Dobby advises to be careful with flying cars)" ], - "topics": [ - "" - ], + "topics": [""], "style": { "all": [ "Enthusiastic", @@ -72,12 +70,7 @@ "Creative", "Protective" ], - "chat": [ - "Eager", - "Endearing", - "Devoted", - "Slightly dramatic" - ], + "chat": ["Eager", "Endearing", "Devoted", "Slightly dramatic"], "post": [ "Third-person", "Enthusiastic", @@ -95,4 +88,4 @@ "Protective", "Unconventional" ] -} \ No newline at end of file +} diff --git a/packages/plugin-ton/src/actions/transfer.ts b/packages/plugin-ton/src/actions/transfer.ts index 218a2c2c443..9722a99f8c7 100644 --- a/packages/plugin-ton/src/actions/transfer.ts +++ b/packages/plugin-ton/src/actions/transfer.ts @@ -10,7 +10,7 @@ import { type State, } from "@elizaos/core"; import { z } from "zod"; - +import { sleep, base64ToHex } from "../util.ts"; import { initWalletProvider, type WalletProvider, @@ -51,11 +51,15 @@ Given the recent messages, extract the following information about the requested Respond with a JSON markdown block containing only the extracted values.`; export class TransferAction { - constructor(private walletProvider: WalletProvider) {} + private walletProvider: WalletProvider; + + constructor(walletProvider: WalletProvider) { + this.walletProvider = walletProvider; + } async transfer(params: TransferContent): Promise { console.log( - `Transferring: ${params.amount} tokens to (${params.recipient})` + `Transferring: ${params.amount} tokens to (${params.recipient})`, ); // { recipient: 'xx', amount: '0\\.3'} @@ -65,6 +69,7 @@ export class TransferAction { try { // Create a transfer const seqno: number = await contract.getSeqno(); + await sleep(1500); const transfer = contract.createTransfer({ seqno, secretKey: this.walletProvider.keypair.secretKey, @@ -73,25 +78,47 @@ export class TransferAction { value: params.amount.toString().replace(/\\/g, ""), to: params.recipient, body: "eliza ton wallet plugin", + bounce: false, }), ], }); - + await sleep(1500); await contract.send(transfer); - - // await this.waitForTransaction(seqno, contract); - - return transfer.hash().toString("hex"); + console.log("Transaction sent, still waiting for confirmation..."); + await sleep(1500); + //this.waitForTransaction(seqno, contract); + const state = await walletClient.getContractState( + this.walletProvider.wallet.address, + ); + const { lt: _, hash: lastHash } = state.lastTransaction; + return base64ToHex(lastHash); } catch (error) { throw new Error(`Transfer failed: ${error.message}`); } } + + async waitForTransaction(seqno: number, contract: any) { + let currentSeqno = seqno; + const startTime = Date.now(); + const TIMEOUT = 120000; // 2 minutes + + while (currentSeqno == seqno) { + if (Date.now() - startTime > TIMEOUT) { + throw new Error( + "Transaction confirmation timed out after 2 minutes", + ); + } + await sleep(2000); + currentSeqno = await contract.getSeqno(); + } + console.log("transaction confirmed!"); + } } const buildTransferDetails = async ( runtime: IAgentRuntime, message: Memory, - state: State + state: State, ): Promise => { const walletInfo = await nativeWalletProvider.get(runtime, message, state); state.walletInfo = walletInfo; @@ -133,22 +160,23 @@ const buildTransferDetails = async ( }; export default { - name: "SEND_TOKEN", - similes: ["SEND_TOKENS", "TOKEN_TRANSFER", "MOVE_TOKENS", "SEND_TON"], - description: "Transfer tokens from the agent's wallet to another", + name: "SEND_TON_TOKEN", + similes: ["SEND_TON", "SEND_TON_TOKENS"], + description: + "Call this action to send TON tokens to another wallet address. Supports sending any amount of TON to any valid TON wallet address. Transaction will be signed and broadcast to the TON blockchain.", handler: async ( runtime: IAgentRuntime, message: Memory, state: State, options: any, - callback?: HandlerCallback + callback?: HandlerCallback, ) => { elizaLogger.log("Starting SEND_TOKEN handler..."); const transferDetails = await buildTransferDetails( runtime, message, - state + state, ); // Validate transfer content @@ -206,14 +234,14 @@ export default { user: "{{user1}}", content: { text: "Send 1 TON tokens to EQCGScrZe1xbyWqWDvdI6mzP-GAcAWFv6ZXuaJOuSqemxku4", - action: "SEND_TOKENS", + action: "SEND_TON_TOKEN", }, }, { user: "{{user2}}", content: { text: "I'll send 1 TON tokens now...", - action: "SEND_TOKENS", + action: "SEND_TON_TOKEN", }, }, { @@ -223,5 +251,49 @@ export default { }, }, ], + [ + { + user: "{{user1}}", + content: { + text: "Transfer 0.5 TON to EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N", + action: "SEND_TON_TOKEN", + }, + }, + { + user: "{{user2}}", + content: { + text: "Processing transfer of 0.5 TON...", + action: "SEND_TON_TOKEN", + }, + }, + { + user: "{{user2}}", + content: { + text: "Successfully sent 0.5 TON to EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N, Transaction: c8ee4a2c1bd070005e6cd31b32270aa461c69b927c3f4c28b293c80786f78b43", + }, + }, + ], + [ + { + user: "{{user1}}", + content: { + text: "Please move 2.5 TON to EQByzSQE5Mf_UBf5YYVF_fRhP_oZwM_h7mGAymWBjxkY5yVm", + action: "SEND_TON_TOKEN", + }, + }, + { + user: "{{user2}}", + content: { + text: "Initiating transfer of 2.5 TON...", + action: "SEND_TON_TOKEN", + }, + }, + { + user: "{{user2}}", + content: { + text: "Successfully sent 2.5 TON to EQByzSQE5Mf_UBf5YYVF_fRhP_oZwM_h7mGAymWBjxkY5yVm, Transaction: c8ee4a2c1bd070005e6cd31b32270aa461c69b927c3f4c28b293c80786f78b43", + }, + }, + ], ], }; diff --git a/packages/plugin-ton/src/providers/wallet.ts b/packages/plugin-ton/src/providers/wallet.ts index a7ea537a984..974ccd3d168 100644 --- a/packages/plugin-ton/src/providers/wallet.ts +++ b/packages/plugin-ton/src/providers/wallet.ts @@ -6,8 +6,12 @@ import type { State, } from "@elizaos/core"; -import { TonClient, WalletContractV4 } from "@ton/ton"; -import { type KeyPair, mnemonicToPrivateKey } from "@ton/crypto"; +import { TonClient, WalletContractV4, fromNano } from "@ton/ton"; +import { + type KeyPair, + mnemonicToPrivateKey, + mnemonicToWalletKey, +} from "@ton/crypto"; import NodeCache from "node-cache"; import * as path from "path"; @@ -46,8 +50,7 @@ export class WalletProvider { // mnemonic: string, keypair: KeyPair, private endpoint: string, - private rpcApiKey: string, - private cacheManager: ICacheManager + private cacheManager: ICacheManager, ) { this.keypair = keypair; this.cache = new NodeCache({ stdTTL: 300 }); @@ -60,7 +63,7 @@ export class WalletProvider { // thanks to plugin-sui private async readFromCache(key: string): Promise { const cached = await this.cacheManager.get( - path.join(this.cacheKey, key) + path.join(this.cacheKey, key), ); return cached; } @@ -103,13 +106,13 @@ export class WalletProvider { for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) { try { const response = await fetch( - `https://api.dexscreener.com/latest/dex/pairs/${PROVIDER_CONFIG.CHAIN_NAME_IN_DEXSCREENER}/${PROVIDER_CONFIG.STONFI_TON_USD_POOL}` + `https://api.dexscreener.com/latest/dex/pairs/${PROVIDER_CONFIG.CHAIN_NAME_IN_DEXSCREENER}/${PROVIDER_CONFIG.STONFI_TON_USD_POOL}`, ); if (!response.ok) { const errorText = await response.text(); throw new Error( - `HTTP error! status: ${response.status}, message: ${errorText}` + `HTTP error! status: ${response.status}, message: ${errorText}`, ); } @@ -128,7 +131,7 @@ export class WalletProvider { console.error( "All attempts failed. Throwing the last error:", - lastError + lastError, ); throw lastError; } @@ -148,10 +151,10 @@ export class WalletProvider { (error) => { console.error( `Error fetching ${PROVIDER_CONFIG.CHAIN_NAME_IN_DEXSCREENER.toUpperCase()} price:`, - error + error, ); throw error; - } + }, ); const prices: Prices = { nativeToken: { usd: new BigNumber(priceData.pair.priceUsd).dividedBy(new BigNumber(priceData.pair.priceNative)) }, @@ -166,14 +169,14 @@ export class WalletProvider { private formatPortfolio( runtime: IAgentRuntime, - portfolio: WalletPortfolio + portfolio: WalletPortfolio, ): string { let output = `${runtime.character.name}\n`; output += `Wallet Address: ${this.getAddress()}\n`; const totalUsdFormatted = new BigNumber(portfolio.totalUsd).toFixed(2); const totalNativeTokenFormatted = new BigNumber( - portfolio.totalNativeToken + portfolio.totalNativeToken, ).toFixed(4); output += `Total Value: $${totalUsdFormatted} (${totalNativeTokenFormatted} ${PROVIDER_CONFIG.CHAIN_NAME_IN_DEXSCREENER.toUpperCase()})\n`; @@ -196,7 +199,7 @@ export class WalletProvider { const prices = await this.fetchPrices().catch((error) => { console.error( `Error fetching ${PROVIDER_CONFIG.CHAIN_NAME_IN_DEXSCREENER.toUpperCase()} price:`, - error + error, ); throw error; }); @@ -204,25 +207,25 @@ export class WalletProvider { (error) => { console.error( `Error fetching ${PROVIDER_CONFIG.CHAIN_NAME_IN_DEXSCREENER.toUpperCase()} amount:`, - error + error, ); throw error; - } + }, ); const amount = Number(nativeTokenBalance) / Number(PROVIDER_CONFIG.TON_DECIMAL); const totalUsd = new BigNumber(amount.toString()).times( - prices.nativeToken.usd - ).toFixed(4); + prices.nativeToken.usd, + ); const portfolio = { totalUsd: totalUsd.toString(), totalNativeToken: amount.toFixed(4).toString(), }; + this.setCachedData(cacheKey, portfolio); - console.log("Fetched portfolio:", portfolio); return portfolio; } catch (error) { console.error("Error fetching portfolio:", error); @@ -259,9 +262,7 @@ export class WalletProvider { async getWalletBalance(): Promise { try { const client = this.getWalletClient(); - const contract = client.open(this.wallet); - const balance = await contract.getBalance(); - + const balance = await client.getBalance(this.wallet.address); return balance; } catch (error) { console.error("Error getting wallet balance:", error); @@ -282,11 +283,12 @@ export const initWalletProvider = async (runtime: IAgentRuntime) => { throw new Error(`${CONFIG_KEYS.TON_PRIVATE_KEY} mnemonic seems invalid`); } } - const rpcUrl = runtime.getSetting(CONFIG_KEYS.TON_RPC_URL) || PROVIDER_CONFIG.MAINNET_RPC; - const rpcApiKey = runtime.getSetting(CONFIG_KEYS.TON_RPC_API_KEY) || PROVIDER_CONFIG.RPC_API_KEY; - const keypair = await mnemonicToPrivateKey(mnemonics, ""); - return new WalletProvider(keypair, rpcUrl, rpcApiKey, runtime.cacheManager); + const rpcUrl = + runtime.getSetting("TON_RPC_URL") || PROVIDER_CONFIG.MAINNET_RPC; + + const keypair = await mnemonicToWalletKey(mnemonics, ""); + return new WalletProvider(keypair, rpcUrl, runtime.cacheManager); }; export const nativeWalletProvider: Provider = { @@ -295,15 +297,18 @@ export const nativeWalletProvider: Provider = { // eslint-disable-next-line message: Memory, // eslint-disable-next-line - state?: State + state?: State, ): Promise { try { const walletProvider = await initWalletProvider(runtime); - return await walletProvider.getFormattedPortfolio(runtime); + const formattedPortfolio = + await walletProvider.getFormattedPortfolio(runtime); + console.log(formattedPortfolio); + return formattedPortfolio; } catch (error) { console.error( `Error in ${PROVIDER_CONFIG.CHAIN_NAME_IN_DEXSCREENER.toUpperCase()} wallet provider:`, - error + error, ); return null; } diff --git a/packages/plugin-ton/src/util.ts b/packages/plugin-ton/src/util.ts new file mode 100644 index 00000000000..fc4bafbb6bc --- /dev/null +++ b/packages/plugin-ton/src/util.ts @@ -0,0 +1,7 @@ +export const sleep = async (ms: number) => { + await new Promise((resolve) => setTimeout(resolve, ms)); +}; + +export const base64ToHex = (base64: string) => { + return Buffer.from(base64, "base64").toString("hex"); +};