Skip to content

Commit

Permalink
Bug/fix ton plugin (#2755)
Browse files Browse the repository at this point in the history
* 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 <hi@sayo.wtf>
  • Loading branch information
ajkraus04 and wtfsayo authored Jan 24, 2025
1 parent 82cae7f commit e6dfc08
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 54 deletions.
13 changes: 3 additions & 10 deletions characters/dobby.character.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -72,12 +70,7 @@
"Creative",
"Protective"
],
"chat": [
"Eager",
"Endearing",
"Devoted",
"Slightly dramatic"
],
"chat": ["Eager", "Endearing", "Devoted", "Slightly dramatic"],
"post": [
"Third-person",
"Enthusiastic",
Expand All @@ -95,4 +88,4 @@
"Protective",
"Unconventional"
]
}
}
104 changes: 88 additions & 16 deletions packages/plugin-ton/src/actions/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
type State,
} from "@elizaos/core";
import { z } from "zod";

import { sleep, base64ToHex } from "../util.ts";
import {
initWalletProvider,
type WalletProvider,
Expand Down Expand Up @@ -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<string> {
console.log(
`Transferring: ${params.amount} tokens to (${params.recipient})`
`Transferring: ${params.amount} tokens to (${params.recipient})`,
);
// { recipient: 'xx', amount: '0\\.3'}

Expand All @@ -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,
Expand All @@ -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<TransferContent> => {
const walletInfo = await nativeWalletProvider.get(runtime, message, state);
state.walletInfo = walletInfo;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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",
},
},
{
Expand All @@ -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",
},
},
],
],
};
61 changes: 33 additions & 28 deletions packages/plugin-ton/src/providers/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 });
Expand All @@ -60,7 +63,7 @@ export class WalletProvider {
// thanks to plugin-sui
private async readFromCache<T>(key: string): Promise<T | null> {
const cached = await this.cacheManager.get<T>(
path.join(this.cacheKey, key)
path.join(this.cacheKey, key),
);
return cached;
}
Expand Down Expand Up @@ -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}`,
);
}

Expand All @@ -128,7 +131,7 @@ export class WalletProvider {

console.error(
"All attempts failed. Throwing the last error:",
lastError
lastError,
);
throw lastError;
}
Expand All @@ -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)) },
Expand All @@ -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`;
Expand All @@ -196,33 +199,33 @@ 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;
});
const nativeTokenBalance = await this.getWalletBalance().catch(
(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);
Expand Down Expand Up @@ -259,9 +262,7 @@ export class WalletProvider {
async getWalletBalance(): Promise<bigint | null> {
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);
Expand All @@ -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 = {
Expand All @@ -295,15 +297,18 @@ export const nativeWalletProvider: Provider = {
// eslint-disable-next-line
message: Memory,
// eslint-disable-next-line
state?: State
state?: State,
): Promise<string | null> {
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;
}
Expand Down
Loading

0 comments on commit e6dfc08

Please sign in to comment.