Skip to content

Commit e6dfc08

Browse files
ajkraus04wtfsayo
andauthored
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 <hi@sayo.wtf>
1 parent 82cae7f commit e6dfc08

File tree

4 files changed

+131
-54
lines changed

4 files changed

+131
-54
lines changed

characters/dobby.character.json

+3-10
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,7 @@
6161
"Dobby reminds friends that even the smallest helper can make the biggest difference!",
6262
"Dobby says: 'When in doubt, try the unconventional solution!' (But Dobby advises to be careful with flying cars)"
6363
],
64-
"topics": [
65-
""
66-
],
64+
"topics": [""],
6765
"style": {
6866
"all": [
6967
"Enthusiastic",
@@ -72,12 +70,7 @@
7270
"Creative",
7371
"Protective"
7472
],
75-
"chat": [
76-
"Eager",
77-
"Endearing",
78-
"Devoted",
79-
"Slightly dramatic"
80-
],
73+
"chat": ["Eager", "Endearing", "Devoted", "Slightly dramatic"],
8174
"post": [
8275
"Third-person",
8376
"Enthusiastic",
@@ -95,4 +88,4 @@
9588
"Protective",
9689
"Unconventional"
9790
]
98-
}
91+
}

packages/plugin-ton/src/actions/transfer.ts

+88-16
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
type State,
1111
} from "@elizaos/core";
1212
import { z } from "zod";
13-
13+
import { sleep, base64ToHex } from "../util.ts";
1414
import {
1515
initWalletProvider,
1616
type WalletProvider,
@@ -51,11 +51,15 @@ Given the recent messages, extract the following information about the requested
5151
Respond with a JSON markdown block containing only the extracted values.`;
5252

5353
export class TransferAction {
54-
constructor(private walletProvider: WalletProvider) {}
54+
private walletProvider: WalletProvider;
55+
56+
constructor(walletProvider: WalletProvider) {
57+
this.walletProvider = walletProvider;
58+
}
5559

5660
async transfer(params: TransferContent): Promise<string> {
5761
console.log(
58-
`Transferring: ${params.amount} tokens to (${params.recipient})`
62+
`Transferring: ${params.amount} tokens to (${params.recipient})`,
5963
);
6064
// { recipient: 'xx', amount: '0\\.3'}
6165

@@ -65,6 +69,7 @@ export class TransferAction {
6569
try {
6670
// Create a transfer
6771
const seqno: number = await contract.getSeqno();
72+
await sleep(1500);
6873
const transfer = contract.createTransfer({
6974
seqno,
7075
secretKey: this.walletProvider.keypair.secretKey,
@@ -73,25 +78,47 @@ export class TransferAction {
7378
value: params.amount.toString().replace(/\\/g, ""),
7479
to: params.recipient,
7580
body: "eliza ton wallet plugin",
81+
bounce: false,
7682
}),
7783
],
7884
});
79-
85+
await sleep(1500);
8086
await contract.send(transfer);
81-
82-
// await this.waitForTransaction(seqno, contract);
83-
84-
return transfer.hash().toString("hex");
87+
console.log("Transaction sent, still waiting for confirmation...");
88+
await sleep(1500);
89+
//this.waitForTransaction(seqno, contract);
90+
const state = await walletClient.getContractState(
91+
this.walletProvider.wallet.address,
92+
);
93+
const { lt: _, hash: lastHash } = state.lastTransaction;
94+
return base64ToHex(lastHash);
8595
} catch (error) {
8696
throw new Error(`Transfer failed: ${error.message}`);
8797
}
8898
}
99+
100+
async waitForTransaction(seqno: number, contract: any) {
101+
let currentSeqno = seqno;
102+
const startTime = Date.now();
103+
const TIMEOUT = 120000; // 2 minutes
104+
105+
while (currentSeqno == seqno) {
106+
if (Date.now() - startTime > TIMEOUT) {
107+
throw new Error(
108+
"Transaction confirmation timed out after 2 minutes",
109+
);
110+
}
111+
await sleep(2000);
112+
currentSeqno = await contract.getSeqno();
113+
}
114+
console.log("transaction confirmed!");
115+
}
89116
}
90117

91118
const buildTransferDetails = async (
92119
runtime: IAgentRuntime,
93120
message: Memory,
94-
state: State
121+
state: State,
95122
): Promise<TransferContent> => {
96123
const walletInfo = await nativeWalletProvider.get(runtime, message, state);
97124
state.walletInfo = walletInfo;
@@ -133,22 +160,23 @@ const buildTransferDetails = async (
133160
};
134161

135162
export default {
136-
name: "SEND_TOKEN",
137-
similes: ["SEND_TOKENS", "TOKEN_TRANSFER", "MOVE_TOKENS", "SEND_TON"],
138-
description: "Transfer tokens from the agent's wallet to another",
163+
name: "SEND_TON_TOKEN",
164+
similes: ["SEND_TON", "SEND_TON_TOKENS"],
165+
description:
166+
"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.",
139167
handler: async (
140168
runtime: IAgentRuntime,
141169
message: Memory,
142170
state: State,
143171
options: any,
144-
callback?: HandlerCallback
172+
callback?: HandlerCallback,
145173
) => {
146174
elizaLogger.log("Starting SEND_TOKEN handler...");
147175

148176
const transferDetails = await buildTransferDetails(
149177
runtime,
150178
message,
151-
state
179+
state,
152180
);
153181

154182
// Validate transfer content
@@ -206,14 +234,14 @@ export default {
206234
user: "{{user1}}",
207235
content: {
208236
text: "Send 1 TON tokens to EQCGScrZe1xbyWqWDvdI6mzP-GAcAWFv6ZXuaJOuSqemxku4",
209-
action: "SEND_TOKENS",
237+
action: "SEND_TON_TOKEN",
210238
},
211239
},
212240
{
213241
user: "{{user2}}",
214242
content: {
215243
text: "I'll send 1 TON tokens now...",
216-
action: "SEND_TOKENS",
244+
action: "SEND_TON_TOKEN",
217245
},
218246
},
219247
{
@@ -223,5 +251,49 @@ export default {
223251
},
224252
},
225253
],
254+
[
255+
{
256+
user: "{{user1}}",
257+
content: {
258+
text: "Transfer 0.5 TON to EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N",
259+
action: "SEND_TON_TOKEN",
260+
},
261+
},
262+
{
263+
user: "{{user2}}",
264+
content: {
265+
text: "Processing transfer of 0.5 TON...",
266+
action: "SEND_TON_TOKEN",
267+
},
268+
},
269+
{
270+
user: "{{user2}}",
271+
content: {
272+
text: "Successfully sent 0.5 TON to EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N, Transaction: c8ee4a2c1bd070005e6cd31b32270aa461c69b927c3f4c28b293c80786f78b43",
273+
},
274+
},
275+
],
276+
[
277+
{
278+
user: "{{user1}}",
279+
content: {
280+
text: "Please move 2.5 TON to EQByzSQE5Mf_UBf5YYVF_fRhP_oZwM_h7mGAymWBjxkY5yVm",
281+
action: "SEND_TON_TOKEN",
282+
},
283+
},
284+
{
285+
user: "{{user2}}",
286+
content: {
287+
text: "Initiating transfer of 2.5 TON...",
288+
action: "SEND_TON_TOKEN",
289+
},
290+
},
291+
{
292+
user: "{{user2}}",
293+
content: {
294+
text: "Successfully sent 2.5 TON to EQByzSQE5Mf_UBf5YYVF_fRhP_oZwM_h7mGAymWBjxkY5yVm, Transaction: c8ee4a2c1bd070005e6cd31b32270aa461c69b927c3f4c28b293c80786f78b43",
295+
},
296+
},
297+
],
226298
],
227299
};

packages/plugin-ton/src/providers/wallet.ts

+33-28
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@ import type {
66
State,
77
} from "@elizaos/core";
88

9-
import { TonClient, WalletContractV4 } from "@ton/ton";
10-
import { type KeyPair, mnemonicToPrivateKey } from "@ton/crypto";
9+
import { TonClient, WalletContractV4, fromNano } from "@ton/ton";
10+
import {
11+
type KeyPair,
12+
mnemonicToPrivateKey,
13+
mnemonicToWalletKey,
14+
} from "@ton/crypto";
1115

1216
import NodeCache from "node-cache";
1317
import * as path from "path";
@@ -46,8 +50,7 @@ export class WalletProvider {
4650
// mnemonic: string,
4751
keypair: KeyPair,
4852
private endpoint: string,
49-
private rpcApiKey: string,
50-
private cacheManager: ICacheManager
53+
private cacheManager: ICacheManager,
5154
) {
5255
this.keypair = keypair;
5356
this.cache = new NodeCache({ stdTTL: 300 });
@@ -60,7 +63,7 @@ export class WalletProvider {
6063
// thanks to plugin-sui
6164
private async readFromCache<T>(key: string): Promise<T | null> {
6265
const cached = await this.cacheManager.get<T>(
63-
path.join(this.cacheKey, key)
66+
path.join(this.cacheKey, key),
6467
);
6568
return cached;
6669
}
@@ -103,13 +106,13 @@ export class WalletProvider {
103106
for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) {
104107
try {
105108
const response = await fetch(
106-
`https://api.dexscreener.com/latest/dex/pairs/${PROVIDER_CONFIG.CHAIN_NAME_IN_DEXSCREENER}/${PROVIDER_CONFIG.STONFI_TON_USD_POOL}`
109+
`https://api.dexscreener.com/latest/dex/pairs/${PROVIDER_CONFIG.CHAIN_NAME_IN_DEXSCREENER}/${PROVIDER_CONFIG.STONFI_TON_USD_POOL}`,
107110
);
108111

109112
if (!response.ok) {
110113
const errorText = await response.text();
111114
throw new Error(
112-
`HTTP error! status: ${response.status}, message: ${errorText}`
115+
`HTTP error! status: ${response.status}, message: ${errorText}`,
113116
);
114117
}
115118

@@ -128,7 +131,7 @@ export class WalletProvider {
128131

129132
console.error(
130133
"All attempts failed. Throwing the last error:",
131-
lastError
134+
lastError,
132135
);
133136
throw lastError;
134137
}
@@ -148,10 +151,10 @@ export class WalletProvider {
148151
(error) => {
149152
console.error(
150153
`Error fetching ${PROVIDER_CONFIG.CHAIN_NAME_IN_DEXSCREENER.toUpperCase()} price:`,
151-
error
154+
error,
152155
);
153156
throw error;
154-
}
157+
},
155158
);
156159
const prices: Prices = {
157160
nativeToken: { usd: new BigNumber(priceData.pair.priceUsd).dividedBy(new BigNumber(priceData.pair.priceNative)) },
@@ -166,14 +169,14 @@ export class WalletProvider {
166169

167170
private formatPortfolio(
168171
runtime: IAgentRuntime,
169-
portfolio: WalletPortfolio
172+
portfolio: WalletPortfolio,
170173
): string {
171174
let output = `${runtime.character.name}\n`;
172175
output += `Wallet Address: ${this.getAddress()}\n`;
173176

174177
const totalUsdFormatted = new BigNumber(portfolio.totalUsd).toFixed(2);
175178
const totalNativeTokenFormatted = new BigNumber(
176-
portfolio.totalNativeToken
179+
portfolio.totalNativeToken,
177180
).toFixed(4);
178181

179182
output += `Total Value: $${totalUsdFormatted} (${totalNativeTokenFormatted} ${PROVIDER_CONFIG.CHAIN_NAME_IN_DEXSCREENER.toUpperCase()})\n`;
@@ -196,33 +199,33 @@ export class WalletProvider {
196199
const prices = await this.fetchPrices().catch((error) => {
197200
console.error(
198201
`Error fetching ${PROVIDER_CONFIG.CHAIN_NAME_IN_DEXSCREENER.toUpperCase()} price:`,
199-
error
202+
error,
200203
);
201204
throw error;
202205
});
203206
const nativeTokenBalance = await this.getWalletBalance().catch(
204207
(error) => {
205208
console.error(
206209
`Error fetching ${PROVIDER_CONFIG.CHAIN_NAME_IN_DEXSCREENER.toUpperCase()} amount:`,
207-
error
210+
error,
208211
);
209212
throw error;
210-
}
213+
},
211214
);
212215

213216
const amount =
214217
Number(nativeTokenBalance) /
215218
Number(PROVIDER_CONFIG.TON_DECIMAL);
216219
const totalUsd = new BigNumber(amount.toString()).times(
217-
prices.nativeToken.usd
218-
).toFixed(4);
220+
prices.nativeToken.usd,
221+
);
219222

220223
const portfolio = {
221224
totalUsd: totalUsd.toString(),
222225
totalNativeToken: amount.toFixed(4).toString(),
223226
};
227+
224228
this.setCachedData(cacheKey, portfolio);
225-
console.log("Fetched portfolio:", portfolio);
226229
return portfolio;
227230
} catch (error) {
228231
console.error("Error fetching portfolio:", error);
@@ -259,9 +262,7 @@ export class WalletProvider {
259262
async getWalletBalance(): Promise<bigint | null> {
260263
try {
261264
const client = this.getWalletClient();
262-
const contract = client.open(this.wallet);
263-
const balance = await contract.getBalance();
264-
265+
const balance = await client.getBalance(this.wallet.address);
265266
return balance;
266267
} catch (error) {
267268
console.error("Error getting wallet balance:", error);
@@ -282,11 +283,12 @@ export const initWalletProvider = async (runtime: IAgentRuntime) => {
282283
throw new Error(`${CONFIG_KEYS.TON_PRIVATE_KEY} mnemonic seems invalid`);
283284
}
284285
}
285-
const rpcUrl = runtime.getSetting(CONFIG_KEYS.TON_RPC_URL) || PROVIDER_CONFIG.MAINNET_RPC;
286-
const rpcApiKey = runtime.getSetting(CONFIG_KEYS.TON_RPC_API_KEY) || PROVIDER_CONFIG.RPC_API_KEY;
287286

288-
const keypair = await mnemonicToPrivateKey(mnemonics, "");
289-
return new WalletProvider(keypair, rpcUrl, rpcApiKey, runtime.cacheManager);
287+
const rpcUrl =
288+
runtime.getSetting("TON_RPC_URL") || PROVIDER_CONFIG.MAINNET_RPC;
289+
290+
const keypair = await mnemonicToWalletKey(mnemonics, "");
291+
return new WalletProvider(keypair, rpcUrl, runtime.cacheManager);
290292
};
291293

292294
export const nativeWalletProvider: Provider = {
@@ -295,15 +297,18 @@ export const nativeWalletProvider: Provider = {
295297
// eslint-disable-next-line
296298
message: Memory,
297299
// eslint-disable-next-line
298-
state?: State
300+
state?: State,
299301
): Promise<string | null> {
300302
try {
301303
const walletProvider = await initWalletProvider(runtime);
302-
return await walletProvider.getFormattedPortfolio(runtime);
304+
const formattedPortfolio =
305+
await walletProvider.getFormattedPortfolio(runtime);
306+
console.log(formattedPortfolio);
307+
return formattedPortfolio;
303308
} catch (error) {
304309
console.error(
305310
`Error in ${PROVIDER_CONFIG.CHAIN_NAME_IN_DEXSCREENER.toUpperCase()} wallet provider:`,
306-
error
311+
error,
307312
);
308313
return null;
309314
}

0 commit comments

Comments
 (0)