@ton-agent-kit/core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +29 -0
- package/src/agent.ts +399 -0
- package/src/index.ts +40 -0
- package/src/plugin.ts +89 -0
- package/src/types.ts +169 -0
- package/src/utils.ts +133 -0
- package/src/wallet.ts +204 -0
- package/tsconfig.json +8 -0
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ton-agent-kit/core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Core SDK for TON Agent Kit — plugin system, wallet abstraction, agent class",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"types": "src/index.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "jest"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@ton/ton": "^16.2.0",
|
|
13
|
+
"@ton/core": "^0.63.1",
|
|
14
|
+
"@ton/crypto": "^3.3.0",
|
|
15
|
+
"zod": "^4.3.6"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "^5.4.0",
|
|
19
|
+
"@ton/sandbox": "^0.20.0"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"@ton/ton": ">=14.0.0",
|
|
23
|
+
"@ton/core": ">=0.63.0"
|
|
24
|
+
},
|
|
25
|
+
"keywords": ["ton", "blockchain", "ai", "agent", "sdk"],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"publishConfig": { "access": "public" },
|
|
28
|
+
"repository": { "type": "git", "url": "https://github.com/Andy00L/ton-agent-kit.git" }
|
|
29
|
+
}
|
package/src/agent.ts
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { TonClient4 } from "@ton/ton";
|
|
3
|
+
import { PluginRegistry } from "./plugin";
|
|
4
|
+
import type {
|
|
5
|
+
Action,
|
|
6
|
+
AgentContext,
|
|
7
|
+
Plugin,
|
|
8
|
+
TonAgentKitConfig,
|
|
9
|
+
TonNetwork,
|
|
10
|
+
WalletProvider,
|
|
11
|
+
} from "./types";
|
|
12
|
+
import { RPC_ENDPOINTS } from "./utils";
|
|
13
|
+
import { KeypairWallet } from "./wallet";
|
|
14
|
+
|
|
15
|
+
export interface RunLoopOptions {
|
|
16
|
+
maxIterations?: number;
|
|
17
|
+
provider?: "openai";
|
|
18
|
+
model?: string;
|
|
19
|
+
apiKey?: string;
|
|
20
|
+
baseURL?: string;
|
|
21
|
+
verbose?: boolean;
|
|
22
|
+
/** Called at the start of each LLM iteration */
|
|
23
|
+
onIteration?: (iteration: number, maxIterations: number) => void;
|
|
24
|
+
/** Called before an action is executed */
|
|
25
|
+
onActionStart?: (actionName: string, params: any) => void;
|
|
26
|
+
/** Called after an action returns */
|
|
27
|
+
onActionResult?: (actionName: string, params: any, result: any) => void;
|
|
28
|
+
/** Called when the agent finishes (no more tool calls) */
|
|
29
|
+
onComplete?: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface RunLoopResult {
|
|
33
|
+
goal: string;
|
|
34
|
+
steps: Array<{ action: string; params: any; result: any }>;
|
|
35
|
+
summary: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Read a key from .env file (fallback when dotenv doesn't load) */
|
|
39
|
+
function readEnvKey(key: string): string {
|
|
40
|
+
try {
|
|
41
|
+
return readFileSync(".env", "utf-8")
|
|
42
|
+
.split("\n")
|
|
43
|
+
.find((l) => l.startsWith(key + "="))
|
|
44
|
+
?.slice(key.length + 1)
|
|
45
|
+
.trim() || "";
|
|
46
|
+
} catch {
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* TonAgentKit — the main entry point for connecting AI agents to TON.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* const wallet = await KeypairWallet.fromMnemonic(mnemonic, {
|
|
57
|
+
* version: "V5R1",
|
|
58
|
+
* network: "testnet",
|
|
59
|
+
* });
|
|
60
|
+
* const agent = new TonAgentKit(wallet, "https://testnet-v4.tonhubapi.com")
|
|
61
|
+
* .use(TokenPlugin)
|
|
62
|
+
* .use(DefiPlugin);
|
|
63
|
+
*
|
|
64
|
+
* const balance = await agent.runAction("get_balance", {});
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export class TonAgentKit {
|
|
68
|
+
public readonly wallet: WalletProvider;
|
|
69
|
+
public readonly connection: TonClient4;
|
|
70
|
+
public readonly network: TonNetwork;
|
|
71
|
+
public readonly rpcUrl: string;
|
|
72
|
+
public readonly config: TonAgentKitConfig;
|
|
73
|
+
public readonly methods: Record<string, (params: any) => Promise<any>>;
|
|
74
|
+
|
|
75
|
+
private registry: PluginRegistry;
|
|
76
|
+
private context: AgentContext;
|
|
77
|
+
private initialized: boolean = false;
|
|
78
|
+
|
|
79
|
+
constructor(
|
|
80
|
+
wallet: WalletProvider,
|
|
81
|
+
rpcUrl?: string,
|
|
82
|
+
config?: TonAgentKitConfig,
|
|
83
|
+
network?: TonNetwork,
|
|
84
|
+
) {
|
|
85
|
+
this.wallet = wallet;
|
|
86
|
+
this.network =
|
|
87
|
+
network || (rpcUrl?.includes("testnet") ? "testnet" : "mainnet");
|
|
88
|
+
this.rpcUrl = rpcUrl || RPC_ENDPOINTS[this.network];
|
|
89
|
+
this.config = config || {};
|
|
90
|
+
this.registry = new PluginRegistry();
|
|
91
|
+
|
|
92
|
+
// Create TON client
|
|
93
|
+
this.connection = new TonClient4({ endpoint: this.rpcUrl });
|
|
94
|
+
|
|
95
|
+
// If wallet supports setClient, inject the connection
|
|
96
|
+
if (wallet instanceof KeypairWallet) {
|
|
97
|
+
wallet.setClient(this.rpcUrl);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Create agent context
|
|
101
|
+
this.context = {
|
|
102
|
+
connection: this.connection,
|
|
103
|
+
wallet: this.wallet,
|
|
104
|
+
network: this.network,
|
|
105
|
+
rpcUrl: this.rpcUrl,
|
|
106
|
+
config: this.config as Record<string, string>,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Create methods proxy for convenient access
|
|
110
|
+
this.methods = new Proxy(
|
|
111
|
+
{} as Record<string, (params: any) => Promise<any>>,
|
|
112
|
+
{
|
|
113
|
+
get: (_target, prop: string) => {
|
|
114
|
+
return (params: any) => this.runAction(prop, params);
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Quick factory: create agent from mnemonic with auto-detect wallet version
|
|
122
|
+
*/
|
|
123
|
+
static async fromMnemonic(
|
|
124
|
+
mnemonic: string[],
|
|
125
|
+
rpcUrl?: string,
|
|
126
|
+
config?: TonAgentKitConfig,
|
|
127
|
+
network?: TonNetwork,
|
|
128
|
+
): Promise<TonAgentKit> {
|
|
129
|
+
const net =
|
|
130
|
+
network || (rpcUrl?.includes("testnet") ? "testnet" : "mainnet");
|
|
131
|
+
const url = rpcUrl || RPC_ENDPOINTS[net];
|
|
132
|
+
const client = new TonClient4({ endpoint: url });
|
|
133
|
+
|
|
134
|
+
const wallet = await KeypairWallet.autoDetect(mnemonic, client, net);
|
|
135
|
+
return new TonAgentKit(wallet, url, config, net);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ============================================================
|
|
139
|
+
// Plugin Management
|
|
140
|
+
// ============================================================
|
|
141
|
+
|
|
142
|
+
use(plugin: Plugin): this {
|
|
143
|
+
this.registry.register(plugin);
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ============================================================
|
|
148
|
+
// Action Execution
|
|
149
|
+
// ============================================================
|
|
150
|
+
|
|
151
|
+
async runAction<TOutput = any>(
|
|
152
|
+
actionName: string,
|
|
153
|
+
params: any,
|
|
154
|
+
): Promise<TOutput> {
|
|
155
|
+
if (!this.initialized) {
|
|
156
|
+
await this.initializePlugins();
|
|
157
|
+
this.initialized = true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const action = this.registry.getAction(actionName);
|
|
161
|
+
if (!action) {
|
|
162
|
+
const available = this.getAvailableActions()
|
|
163
|
+
.map((a) => a.name)
|
|
164
|
+
.join(", ");
|
|
165
|
+
throw new Error(
|
|
166
|
+
`Action "${actionName}" not found. Available: ${available}`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const parsed = action.schema.safeParse(params);
|
|
171
|
+
if (!parsed.success) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Invalid params for "${actionName}": ${parsed.error.message}`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
return await action.handler(this.context, parsed.data);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
const error = err as Error;
|
|
181
|
+
throw new Error(`Action "${actionName}" failed: ${error.message}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ============================================================
|
|
186
|
+
// Introspection
|
|
187
|
+
// ============================================================
|
|
188
|
+
|
|
189
|
+
getAvailableActions(): Action[] {
|
|
190
|
+
return this.registry.getAllActions();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
getPlugins(): Plugin[] {
|
|
194
|
+
return this.registry.getAllPlugins();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
get actionCount(): number {
|
|
198
|
+
return this.registry.actionCount;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
get address(): string {
|
|
202
|
+
return this.wallet.address.toString();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ============================================================
|
|
206
|
+
// Autonomous Agent Loop
|
|
207
|
+
// ============================================================
|
|
208
|
+
|
|
209
|
+
async runLoop(
|
|
210
|
+
goal: string,
|
|
211
|
+
options?: RunLoopOptions,
|
|
212
|
+
): Promise<RunLoopResult> {
|
|
213
|
+
const maxIterations = options?.maxIterations ?? 5;
|
|
214
|
+
const model = options?.model || process.env.AI_MODEL || "gpt-4.1-nano";
|
|
215
|
+
const apiKey = options?.apiKey || process.env.OPENAI_API_KEY || readEnvKey("OPENAI_API_KEY");
|
|
216
|
+
const baseURL = options?.baseURL || process.env.OPENAI_BASE_URL;
|
|
217
|
+
const verbose = options?.verbose ?? true;
|
|
218
|
+
|
|
219
|
+
if (!apiKey) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
"runLoop requires an AI provider API key. Set OPENAI_API_KEY in .env",
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Dynamic imports so the SDK works without these deps installed
|
|
226
|
+
const { default: OpenAI } = await import("openai");
|
|
227
|
+
const { toJSONSchema } = await import("zod");
|
|
228
|
+
const client = new OpenAI({ apiKey, baseURL });
|
|
229
|
+
|
|
230
|
+
// Ensure plugins are initialized
|
|
231
|
+
if (!this.initialized) {
|
|
232
|
+
await this.initializePlugins();
|
|
233
|
+
this.initialized = true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Build OpenAI tools from available actions
|
|
237
|
+
const actions = this.getAvailableActions();
|
|
238
|
+
const tools: Array<{
|
|
239
|
+
type: "function";
|
|
240
|
+
function: { name: string; description: string; parameters: any };
|
|
241
|
+
}> = actions.map((action) => ({
|
|
242
|
+
type: "function" as const,
|
|
243
|
+
function: {
|
|
244
|
+
name: action.name,
|
|
245
|
+
description: action.description,
|
|
246
|
+
parameters: (() => {
|
|
247
|
+
const { $schema, ...schema } = toJSONSchema(action.schema);
|
|
248
|
+
return schema;
|
|
249
|
+
})(),
|
|
250
|
+
},
|
|
251
|
+
}));
|
|
252
|
+
|
|
253
|
+
// Build system prompt
|
|
254
|
+
const actionList = actions
|
|
255
|
+
.map((a) => {
|
|
256
|
+
const jsonSchema = toJSONSchema(a.schema) as any;
|
|
257
|
+
const paramNames = JSON.stringify(Object.keys(jsonSchema.properties || {}));
|
|
258
|
+
return `- ${a.name}: ${a.description}\n Parameters: ${paramNames}`;
|
|
259
|
+
})
|
|
260
|
+
.join("\n");
|
|
261
|
+
const systemPrompt = `You are an autonomous AI agent with a TON blockchain wallet. You have access to the following actions:\n${actionList}\n\nIMPORTANT: When calling functions, use EXACTLY the parameter names listed above. Do NOT rename parameters (e.g. use "to" not "destination", "token" not "token_address").\n\nExecute the user's goal by calling the appropriate actions. When done, provide a summary of what you accomplished.`;
|
|
262
|
+
|
|
263
|
+
const messages: Array<any> = [
|
|
264
|
+
{ role: "system", content: systemPrompt },
|
|
265
|
+
{ role: "user", content: goal },
|
|
266
|
+
];
|
|
267
|
+
|
|
268
|
+
const steps: Array<{ action: string; params: any; result: any }> = [];
|
|
269
|
+
|
|
270
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
271
|
+
if (options?.onIteration) {
|
|
272
|
+
options.onIteration(i + 1, maxIterations);
|
|
273
|
+
} else if (verbose) {
|
|
274
|
+
console.log(`\n🔄 Iteration ${i + 1}/${maxIterations}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const response = await client.chat.completions.create({
|
|
278
|
+
model,
|
|
279
|
+
messages,
|
|
280
|
+
tools,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const choice = response.choices[0];
|
|
284
|
+
if (!choice.message) break;
|
|
285
|
+
|
|
286
|
+
messages.push(choice.message);
|
|
287
|
+
|
|
288
|
+
// If no tool calls, the LLM is done
|
|
289
|
+
if (
|
|
290
|
+
!choice.message.tool_calls ||
|
|
291
|
+
choice.message.tool_calls.length === 0
|
|
292
|
+
) {
|
|
293
|
+
if (options?.onComplete) {
|
|
294
|
+
options.onComplete();
|
|
295
|
+
} else if (verbose) {
|
|
296
|
+
console.log("✅ Agent finished reasoning");
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
goal,
|
|
300
|
+
steps,
|
|
301
|
+
summary: choice.message.content || "Goal completed.",
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Execute each tool call
|
|
306
|
+
for (const toolCall of choice.message.tool_calls as any[]) {
|
|
307
|
+
const actionName = toolCall.function.name;
|
|
308
|
+
let params: any;
|
|
309
|
+
try {
|
|
310
|
+
params = JSON.parse(toolCall.function.arguments);
|
|
311
|
+
} catch {
|
|
312
|
+
params = {};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Remap misnamed parameters to match the action schema
|
|
316
|
+
const matchedAction = actions.find((a) => a.name === actionName);
|
|
317
|
+
if (matchedAction) {
|
|
318
|
+
const expectedSchema = toJSONSchema(matchedAction.schema) as any;
|
|
319
|
+
const expectedKeys = new Set(Object.keys(expectedSchema.properties || {}));
|
|
320
|
+
const paramKeys = Object.keys(params);
|
|
321
|
+
for (const key of paramKeys) {
|
|
322
|
+
if (!expectedKeys.has(key)) {
|
|
323
|
+
// Find a matching expected key that isn't already provided
|
|
324
|
+
for (const expected of expectedKeys) {
|
|
325
|
+
if (!(expected in params) && key.toLowerCase().includes(expected.toLowerCase())) {
|
|
326
|
+
params[expected] = params[key];
|
|
327
|
+
delete params[key];
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (options?.onActionStart) {
|
|
336
|
+
options.onActionStart(actionName, params);
|
|
337
|
+
} else if (verbose) {
|
|
338
|
+
console.log(` ▶ ${actionName}(${JSON.stringify(params)})`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
let result: any;
|
|
342
|
+
try {
|
|
343
|
+
result = await this.runAction(actionName, params);
|
|
344
|
+
} catch (err) {
|
|
345
|
+
result = { error: (err as Error).message };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (options?.onActionResult) {
|
|
349
|
+
options.onActionResult(actionName, params, result);
|
|
350
|
+
} else if (verbose) {
|
|
351
|
+
console.log(
|
|
352
|
+
` ◀ ${typeof result === "string" ? result : JSON.stringify(result)}`,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
steps.push({ action: actionName, params, result });
|
|
357
|
+
|
|
358
|
+
messages.push({
|
|
359
|
+
role: "tool",
|
|
360
|
+
tool_call_id: toolCall.id,
|
|
361
|
+
content:
|
|
362
|
+
typeof result === "string" ? result : JSON.stringify(result),
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Max iterations reached — ask LLM for a final summary
|
|
368
|
+
messages.push({
|
|
369
|
+
role: "user",
|
|
370
|
+
content:
|
|
371
|
+
"Max iterations reached. Summarize what you accomplished so far.",
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
const finalResponse = await client.chat.completions.create({
|
|
375
|
+
model,
|
|
376
|
+
messages,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
goal,
|
|
381
|
+
steps,
|
|
382
|
+
summary:
|
|
383
|
+
finalResponse.choices[0]?.message?.content ||
|
|
384
|
+
`Completed ${steps.length} steps.`,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ============================================================
|
|
389
|
+
// Internal
|
|
390
|
+
// ============================================================
|
|
391
|
+
|
|
392
|
+
private async initializePlugins(): Promise<void> {
|
|
393
|
+
for (const plugin of this.registry.getAllPlugins()) {
|
|
394
|
+
if (plugin.initialize) {
|
|
395
|
+
await plugin.initialize(this.context);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// @ton-agent-kit/core
|
|
3
|
+
// Connect any AI agent to TON protocols
|
|
4
|
+
// ============================================================
|
|
5
|
+
|
|
6
|
+
export { TonAgentKit } from "./agent";
|
|
7
|
+
export type { RunLoopOptions, RunLoopResult } from "./agent";
|
|
8
|
+
export { defineAction, definePlugin, PluginRegistry } from "./plugin";
|
|
9
|
+
export { KeypairWallet, ReadOnlyWallet } from "./wallet";
|
|
10
|
+
export type { WalletConfig, WalletVersion } from "./wallet";
|
|
11
|
+
|
|
12
|
+
export type {
|
|
13
|
+
Action,
|
|
14
|
+
AgentContext,
|
|
15
|
+
BalanceResult,
|
|
16
|
+
DnsInfo,
|
|
17
|
+
JettonBalanceResult,
|
|
18
|
+
JettonInfo,
|
|
19
|
+
NftInfo,
|
|
20
|
+
Plugin,
|
|
21
|
+
SwapResult,
|
|
22
|
+
TonAgentKitConfig,
|
|
23
|
+
TonNetwork,
|
|
24
|
+
TransactionResult,
|
|
25
|
+
WalletProvider,
|
|
26
|
+
} from "./types";
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
explorerAddressUrl,
|
|
30
|
+
explorerUrl,
|
|
31
|
+
nanoToTon,
|
|
32
|
+
parseAddress,
|
|
33
|
+
retry,
|
|
34
|
+
RPC_ENDPOINTS,
|
|
35
|
+
sendTransaction,
|
|
36
|
+
sleep,
|
|
37
|
+
toFriendlyAddress,
|
|
38
|
+
TONCENTER_ENDPOINTS,
|
|
39
|
+
tonToNano,
|
|
40
|
+
} from "./utils";
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { Plugin, Action } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plugin registry — manages action registration and lookup.
|
|
5
|
+
*/
|
|
6
|
+
export class PluginRegistry {
|
|
7
|
+
private plugins: Map<string, Plugin> = new Map();
|
|
8
|
+
private actions: Map<string, Action> = new Map();
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Register a plugin and all its actions
|
|
12
|
+
*/
|
|
13
|
+
register(plugin: Plugin): void {
|
|
14
|
+
if (this.plugins.has(plugin.name)) {
|
|
15
|
+
throw new Error(`Plugin "${plugin.name}" is already registered.`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Check for action name conflicts
|
|
19
|
+
for (const action of plugin.actions) {
|
|
20
|
+
if (this.actions.has(action.name)) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Action "${action.name}" from plugin "${plugin.name}" conflicts with an existing action.`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Register plugin and its actions
|
|
28
|
+
this.plugins.set(plugin.name, plugin);
|
|
29
|
+
for (const action of plugin.actions) {
|
|
30
|
+
this.actions.set(action.name, action);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get an action by name
|
|
36
|
+
*/
|
|
37
|
+
getAction(name: string): Action | undefined {
|
|
38
|
+
return this.actions.get(name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get all registered actions
|
|
43
|
+
*/
|
|
44
|
+
getAllActions(): Action[] {
|
|
45
|
+
return Array.from(this.actions.values());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get all registered plugins
|
|
50
|
+
*/
|
|
51
|
+
getAllPlugins(): Plugin[] {
|
|
52
|
+
return Array.from(this.plugins.values());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if a plugin is registered
|
|
57
|
+
*/
|
|
58
|
+
hasPlugin(name: string): boolean {
|
|
59
|
+
return this.plugins.has(name);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get action count
|
|
64
|
+
*/
|
|
65
|
+
get actionCount(): number {
|
|
66
|
+
return this.actions.size;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get plugin count
|
|
71
|
+
*/
|
|
72
|
+
get pluginCount(): number {
|
|
73
|
+
return this.plugins.size;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Helper to define a plugin with type safety
|
|
79
|
+
*/
|
|
80
|
+
export function definePlugin(plugin: Plugin): Plugin {
|
|
81
|
+
return plugin;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Helper to define an action with type safety
|
|
86
|
+
*/
|
|
87
|
+
export function defineAction<TInput, TOutput>(action: Action<TInput, TOutput>): Action<TInput, TOutput> {
|
|
88
|
+
return action;
|
|
89
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { Address, MessageRelaxed } from "@ton/core";
|
|
2
|
+
import type { TonClient, TonClient4 } from "@ton/ton";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
// ============================================================
|
|
6
|
+
// Wallet Provider
|
|
7
|
+
// ============================================================
|
|
8
|
+
|
|
9
|
+
export interface WalletProvider {
|
|
10
|
+
/** The wallet's address on TON */
|
|
11
|
+
address: Address;
|
|
12
|
+
/** Send one or more messages (the reliable way to transact on TON) */
|
|
13
|
+
sendTransfer(messages: MessageRelaxed[]): Promise<void>;
|
|
14
|
+
/** Sign arbitrary data (optional, for identity proofs) */
|
|
15
|
+
sign?(data: Buffer): Promise<Buffer>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ============================================================
|
|
19
|
+
// Action — a single autonomous operation an agent can perform
|
|
20
|
+
// ============================================================
|
|
21
|
+
|
|
22
|
+
export interface Action<TInput = any, TOutput = any> {
|
|
23
|
+
/** Unique identifier, e.g. "transfer_ton" */
|
|
24
|
+
name: string;
|
|
25
|
+
/** Human-readable description (used as LLM tool description) */
|
|
26
|
+
description: string;
|
|
27
|
+
/** Zod schema for validating and describing inputs */
|
|
28
|
+
schema: z.ZodType<TInput>;
|
|
29
|
+
/** Execute the action */
|
|
30
|
+
handler: (agent: AgentContext, params: TInput) => Promise<TOutput>;
|
|
31
|
+
/** Optional examples for better LLM understanding */
|
|
32
|
+
examples?: Array<{
|
|
33
|
+
input: TInput;
|
|
34
|
+
output: TOutput;
|
|
35
|
+
description?: string;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ============================================================
|
|
40
|
+
// Plugin — a collection of related actions
|
|
41
|
+
// ============================================================
|
|
42
|
+
|
|
43
|
+
export interface Plugin {
|
|
44
|
+
/** Plugin name, e.g. "token", "defi" */
|
|
45
|
+
name: string;
|
|
46
|
+
/** Actions provided by this plugin */
|
|
47
|
+
actions: Action[];
|
|
48
|
+
/** Optional initialization hook */
|
|
49
|
+
initialize?: (agent: AgentContext) => Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ============================================================
|
|
53
|
+
// Agent Context — what actions receive to interact with TON
|
|
54
|
+
// ============================================================
|
|
55
|
+
|
|
56
|
+
export interface AgentContext {
|
|
57
|
+
/** TON blockchain client */
|
|
58
|
+
connection: TonClient4 | TonClient;
|
|
59
|
+
/** Wallet provider for signing transactions */
|
|
60
|
+
wallet: WalletProvider;
|
|
61
|
+
/** Network: mainnet or testnet */
|
|
62
|
+
network: TonNetwork;
|
|
63
|
+
/** RPC endpoint URL */
|
|
64
|
+
rpcUrl: string;
|
|
65
|
+
/** Optional config (API keys, etc.) */
|
|
66
|
+
config: Record<string, string>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ============================================================
|
|
70
|
+
// Agent Kit Configuration
|
|
71
|
+
// ============================================================
|
|
72
|
+
|
|
73
|
+
export interface TonAgentKitConfig {
|
|
74
|
+
/** Optional API keys for LLMs or services */
|
|
75
|
+
OPENAI_API_KEY?: string;
|
|
76
|
+
TONAPI_KEY?: string;
|
|
77
|
+
[key: string]: string | undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type TonNetwork = "mainnet" | "testnet";
|
|
81
|
+
|
|
82
|
+
// ============================================================
|
|
83
|
+
// Action Result Types
|
|
84
|
+
// ============================================================
|
|
85
|
+
|
|
86
|
+
export interface TransactionResult {
|
|
87
|
+
/** Transaction hash */
|
|
88
|
+
txHash: string;
|
|
89
|
+
/** Human-readable status */
|
|
90
|
+
status: "sent" | "confirmed" | "failed";
|
|
91
|
+
/** Optional explorer link */
|
|
92
|
+
explorerUrl?: string;
|
|
93
|
+
/** Fee paid */
|
|
94
|
+
fee?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface BalanceResult {
|
|
98
|
+
/** Balance in TON (human-readable) */
|
|
99
|
+
balance: string;
|
|
100
|
+
/** Balance in nanoton */
|
|
101
|
+
balanceRaw: string;
|
|
102
|
+
/** Address queried */
|
|
103
|
+
address: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface JettonBalanceResult {
|
|
107
|
+
/** Balance in token units */
|
|
108
|
+
balance: string;
|
|
109
|
+
/** Balance in raw units */
|
|
110
|
+
balanceRaw: string;
|
|
111
|
+
/** Token symbol */
|
|
112
|
+
symbol: string;
|
|
113
|
+
/** Token name */
|
|
114
|
+
name: string;
|
|
115
|
+
/** Decimals */
|
|
116
|
+
decimals: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface SwapResult extends TransactionResult {
|
|
120
|
+
/** Amount sent */
|
|
121
|
+
fromAmount: string;
|
|
122
|
+
/** Token sent */
|
|
123
|
+
fromToken: string;
|
|
124
|
+
/** Amount received (estimated) */
|
|
125
|
+
toAmount: string;
|
|
126
|
+
/** Token received */
|
|
127
|
+
toToken: string;
|
|
128
|
+
/** DEX used */
|
|
129
|
+
dex: "dedust" | "stonfi";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface JettonInfo {
|
|
133
|
+
/** Jetton master address */
|
|
134
|
+
address: string;
|
|
135
|
+
/** Token name */
|
|
136
|
+
name: string;
|
|
137
|
+
/** Token symbol */
|
|
138
|
+
symbol: string;
|
|
139
|
+
/** Decimals */
|
|
140
|
+
decimals: number;
|
|
141
|
+
/** Total supply */
|
|
142
|
+
totalSupply: string;
|
|
143
|
+
/** Description */
|
|
144
|
+
description?: string;
|
|
145
|
+
/** Image URL */
|
|
146
|
+
image?: string;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface NftInfo {
|
|
150
|
+
/** NFT address */
|
|
151
|
+
address: string;
|
|
152
|
+
/** NFT index in collection */
|
|
153
|
+
index: number;
|
|
154
|
+
/** Owner address */
|
|
155
|
+
owner: string;
|
|
156
|
+
/** Collection address */
|
|
157
|
+
collection?: string;
|
|
158
|
+
/** Metadata */
|
|
159
|
+
metadata?: Record<string, any>;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface DnsInfo {
|
|
163
|
+
/** Domain name */
|
|
164
|
+
domain: string;
|
|
165
|
+
/** Resolved address */
|
|
166
|
+
address?: string;
|
|
167
|
+
/** Expiration timestamp */
|
|
168
|
+
expiresAt?: number;
|
|
169
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Address, fromNano, toNano, type MessageRelaxed } from "@ton/core";
|
|
2
|
+
import { TonClient4, WalletContractV5R1 } from "@ton/ton";
|
|
3
|
+
import type { AgentContext } from "./types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Convert TON amount string to nanoton bigint
|
|
7
|
+
*/
|
|
8
|
+
export function tonToNano(amount: string): bigint {
|
|
9
|
+
return toNano(amount);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Convert nanoton bigint to human-readable TON string
|
|
14
|
+
*/
|
|
15
|
+
export function nanoToTon(amount: bigint | string): string {
|
|
16
|
+
return fromNano(typeof amount === "string" ? BigInt(amount) : amount);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Parse address string to Address object (supports both raw and user-friendly)
|
|
21
|
+
*/
|
|
22
|
+
export function parseAddress(address: string): Address {
|
|
23
|
+
return Address.parse(address);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Convert address to user-friendly format (non-bounceable, like Tonkeeper)
|
|
28
|
+
*/
|
|
29
|
+
export function toFriendlyAddress(address: Address, network: "testnet" | "mainnet" = "mainnet"): string {
|
|
30
|
+
return address.toString({ testOnly: network === "testnet", bounceable: false });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Generate TON explorer URL for a transaction
|
|
35
|
+
*/
|
|
36
|
+
export function explorerUrl(
|
|
37
|
+
txHash: string,
|
|
38
|
+
network: "mainnet" | "testnet" = "mainnet"
|
|
39
|
+
): string {
|
|
40
|
+
const base = network === "testnet"
|
|
41
|
+
? "https://testnet.tonviewer.com"
|
|
42
|
+
: "https://tonviewer.com";
|
|
43
|
+
return `${base}/transaction/${txHash}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Generate TON explorer URL for an address
|
|
48
|
+
*/
|
|
49
|
+
export function explorerAddressUrl(
|
|
50
|
+
address: string,
|
|
51
|
+
network: "mainnet" | "testnet" = "mainnet"
|
|
52
|
+
): string {
|
|
53
|
+
const base = network === "testnet"
|
|
54
|
+
? "https://testnet.tonviewer.com"
|
|
55
|
+
: "https://tonviewer.com";
|
|
56
|
+
return `${base}/${address}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Wait for a specified number of milliseconds
|
|
61
|
+
*/
|
|
62
|
+
export function sleep(ms: number): Promise<void> {
|
|
63
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Retry an async operation with exponential backoff
|
|
68
|
+
*/
|
|
69
|
+
export async function retry<T>(
|
|
70
|
+
fn: () => Promise<T>,
|
|
71
|
+
maxRetries: number = 3,
|
|
72
|
+
baseDelay: number = 1000
|
|
73
|
+
): Promise<T> {
|
|
74
|
+
let lastError: Error | undefined;
|
|
75
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
76
|
+
try {
|
|
77
|
+
return await fn();
|
|
78
|
+
} catch (err) {
|
|
79
|
+
lastError = err as Error;
|
|
80
|
+
if (i < maxRetries - 1) {
|
|
81
|
+
await sleep(baseDelay * Math.pow(2, i));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
throw lastError;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Send a transaction using the proven WalletContractV5R1 pattern.
|
|
90
|
+
* This bypasses KeypairWallet.sendTransfer() which hits a domainSign
|
|
91
|
+
* incompatibility in certain @ton/ton versions.
|
|
92
|
+
*/
|
|
93
|
+
export async function sendTransaction(
|
|
94
|
+
agent: AgentContext,
|
|
95
|
+
messages: MessageRelaxed[],
|
|
96
|
+
): Promise<void> {
|
|
97
|
+
const { secretKey, publicKey } = (agent.wallet as any).getCredentials();
|
|
98
|
+
const networkId = agent.network === "testnet" ? -3 : -239;
|
|
99
|
+
const freshClient = new TonClient4({ endpoint: agent.rpcUrl });
|
|
100
|
+
const walletContract = freshClient.open(
|
|
101
|
+
WalletContractV5R1.create({
|
|
102
|
+
workchain: 0,
|
|
103
|
+
publicKey,
|
|
104
|
+
walletId: {
|
|
105
|
+
networkGlobalId: networkId,
|
|
106
|
+
workchain: 0,
|
|
107
|
+
subwalletNumber: 0,
|
|
108
|
+
},
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
const seqno = await walletContract.getSeqno();
|
|
112
|
+
await walletContract.sendTransfer({
|
|
113
|
+
seqno,
|
|
114
|
+
secretKey,
|
|
115
|
+
messages,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Default RPC endpoints
|
|
121
|
+
*/
|
|
122
|
+
export const RPC_ENDPOINTS = {
|
|
123
|
+
mainnet: "https://mainnet-v4.tonhubapi.com",
|
|
124
|
+
testnet: "https://testnet-v4.tonhubapi.com",
|
|
125
|
+
} as const;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Default TonCenter API endpoints
|
|
129
|
+
*/
|
|
130
|
+
export const TONCENTER_ENDPOINTS = {
|
|
131
|
+
mainnet: "https://toncenter.com/api/v2/jsonRPC",
|
|
132
|
+
testnet: "https://testnet.toncenter.com/api/v2/jsonRPC",
|
|
133
|
+
} as const;
|
package/src/wallet.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { Address, type MessageRelaxed } from "@ton/core";
|
|
2
|
+
import { KeyPair, mnemonicToPrivateKey } from "@ton/crypto";
|
|
3
|
+
import {
|
|
4
|
+
TonClient4,
|
|
5
|
+
WalletContractV3R2,
|
|
6
|
+
WalletContractV4,
|
|
7
|
+
WalletContractV5R1,
|
|
8
|
+
} from "@ton/ton";
|
|
9
|
+
import type { WalletProvider } from "./types";
|
|
10
|
+
|
|
11
|
+
export type WalletVersion = "V3R2" | "V4" | "V5R1";
|
|
12
|
+
|
|
13
|
+
export interface WalletConfig {
|
|
14
|
+
version?: WalletVersion;
|
|
15
|
+
workchain?: number;
|
|
16
|
+
network?: "testnet" | "mainnet";
|
|
17
|
+
subwalletNumber?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const NETWORK_IDS = {
|
|
21
|
+
testnet: -3,
|
|
22
|
+
mainnet: -239,
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
function createWalletContract(publicKey: Buffer, config: WalletConfig = {}) {
|
|
26
|
+
const version = config.version || "V5R1";
|
|
27
|
+
const workchain = config.workchain || 0;
|
|
28
|
+
const network = config.network || "mainnet";
|
|
29
|
+
|
|
30
|
+
switch (version) {
|
|
31
|
+
case "V3R2":
|
|
32
|
+
return WalletContractV3R2.create({ workchain, publicKey });
|
|
33
|
+
case "V4":
|
|
34
|
+
return WalletContractV4.create({ workchain, publicKey });
|
|
35
|
+
case "V5R1":
|
|
36
|
+
return WalletContractV5R1.create({
|
|
37
|
+
workchain,
|
|
38
|
+
publicKey,
|
|
39
|
+
walletId: {
|
|
40
|
+
networkGlobalId: NETWORK_IDS[network],
|
|
41
|
+
workchain,
|
|
42
|
+
subwalletNumber: config.subwalletNumber ?? 0,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
default:
|
|
46
|
+
throw new Error(`Unsupported wallet version: ${version}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class KeypairWallet implements WalletProvider {
|
|
51
|
+
public address: Address;
|
|
52
|
+
public readonly version: WalletVersion;
|
|
53
|
+
private keyPair: KeyPair;
|
|
54
|
+
private walletConfig: WalletConfig;
|
|
55
|
+
private rpcUrl: string = "";
|
|
56
|
+
|
|
57
|
+
private constructor(keyPair: KeyPair, config: WalletConfig) {
|
|
58
|
+
this.keyPair = keyPair;
|
|
59
|
+
this.walletConfig = config;
|
|
60
|
+
this.version = config.version || "V5R1";
|
|
61
|
+
const contract = createWalletContract(keyPair.publicKey, config);
|
|
62
|
+
this.address = contract.address;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static async fromMnemonic(
|
|
66
|
+
mnemonic: string[],
|
|
67
|
+
config: WalletConfig = {},
|
|
68
|
+
): Promise<KeypairWallet> {
|
|
69
|
+
const keyPair = await mnemonicToPrivateKey(mnemonic);
|
|
70
|
+
return new KeypairWallet(keyPair, config);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static fromSecretKey(
|
|
74
|
+
secretKey: Buffer,
|
|
75
|
+
config: WalletConfig = {},
|
|
76
|
+
): KeypairWallet {
|
|
77
|
+
const publicKey = secretKey.subarray(32);
|
|
78
|
+
const keyPair: KeyPair = { secretKey, publicKey };
|
|
79
|
+
return new KeypairWallet(keyPair, config);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static async autoDetect(
|
|
83
|
+
mnemonic: string[],
|
|
84
|
+
client: TonClient4,
|
|
85
|
+
network: "testnet" | "mainnet" = "mainnet",
|
|
86
|
+
): Promise<KeypairWallet> {
|
|
87
|
+
const keyPair = await mnemonicToPrivateKey(mnemonic);
|
|
88
|
+
const versions: WalletVersion[] = ["V5R1", "V4", "V3R2"];
|
|
89
|
+
const lastBlock = await client.getLastBlock();
|
|
90
|
+
|
|
91
|
+
for (const version of versions) {
|
|
92
|
+
const config: WalletConfig = { version, network };
|
|
93
|
+
const contract = createWalletContract(keyPair.publicKey, config);
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const state = await client.getAccount(
|
|
97
|
+
lastBlock.last.seqno,
|
|
98
|
+
contract.address,
|
|
99
|
+
);
|
|
100
|
+
if (state.account.balance.coins > 0n) {
|
|
101
|
+
console.error(
|
|
102
|
+
`Auto-detected wallet: ${version} (${contract.address.toString({ testOnly: network === "testnet", bounceable: false })})`,
|
|
103
|
+
);
|
|
104
|
+
return new KeypairWallet(keyPair, config);
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
// Skip
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.error("No existing wallet found, defaulting to V5R1");
|
|
112
|
+
return new KeypairWallet(keyPair, { version: "V5R1", network });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Set the RPC URL — called by TonAgentKit on init
|
|
117
|
+
*/
|
|
118
|
+
setClient(rpcUrl: string): void {
|
|
119
|
+
this.rpcUrl = rpcUrl;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Send messages using a fresh client every time.
|
|
124
|
+
* This is the proven pattern that works on both testnet and mainnet.
|
|
125
|
+
*/
|
|
126
|
+
async sendTransfer(messages: MessageRelaxed[]): Promise<void> {
|
|
127
|
+
if (!this.rpcUrl) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
"RPC URL not set. Let TonAgentKit handle initialization.",
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const freshClient = new TonClient4({ endpoint: this.rpcUrl });
|
|
134
|
+
const freshContract = createWalletContract(
|
|
135
|
+
this.keyPair.publicKey,
|
|
136
|
+
this.walletConfig,
|
|
137
|
+
);
|
|
138
|
+
const contract = freshClient.open(freshContract);
|
|
139
|
+
|
|
140
|
+
const seqno = await contract.getSeqno();
|
|
141
|
+
|
|
142
|
+
await contract.sendTransfer({
|
|
143
|
+
seqno,
|
|
144
|
+
secretKey: this.keyPair.secretKey,
|
|
145
|
+
messages,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get the raw materials for handlers that need direct contract access.
|
|
151
|
+
* Returns everything needed to call sendTransfer on a fresh contract.
|
|
152
|
+
*/
|
|
153
|
+
getCredentials(): {
|
|
154
|
+
secretKey: Buffer;
|
|
155
|
+
publicKey: Buffer;
|
|
156
|
+
walletConfig: WalletConfig;
|
|
157
|
+
} {
|
|
158
|
+
return {
|
|
159
|
+
secretKey: this.keyPair.secretKey,
|
|
160
|
+
publicKey: this.keyPair.publicKey,
|
|
161
|
+
walletConfig: this.walletConfig,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async sign(data: Buffer): Promise<Buffer> {
|
|
166
|
+
const { sign } = await import("@ton/crypto");
|
|
167
|
+
return sign(data, this.keyPair.secretKey);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
static async getAllAddresses(
|
|
171
|
+
mnemonic: string[],
|
|
172
|
+
network: "testnet" | "mainnet" = "mainnet",
|
|
173
|
+
): Promise<Record<WalletVersion, string>> {
|
|
174
|
+
const keyPair = await mnemonicToPrivateKey(mnemonic);
|
|
175
|
+
const versions: WalletVersion[] = ["V3R2", "V4", "V5R1"];
|
|
176
|
+
const result: Record<string, string> = {};
|
|
177
|
+
|
|
178
|
+
for (const version of versions) {
|
|
179
|
+
const contract = createWalletContract(keyPair.publicKey, {
|
|
180
|
+
version,
|
|
181
|
+
network,
|
|
182
|
+
});
|
|
183
|
+
result[version] = contract.address.toString({
|
|
184
|
+
testOnly: network === "testnet",
|
|
185
|
+
bounceable: false,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return result as Record<WalletVersion, string>;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export class ReadOnlyWallet implements WalletProvider {
|
|
194
|
+
public address: Address;
|
|
195
|
+
|
|
196
|
+
constructor(address: string | Address) {
|
|
197
|
+
this.address =
|
|
198
|
+
typeof address === "string" ? Address.parse(address) : address;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async sendTransfer(): Promise<void> {
|
|
202
|
+
throw new Error("ReadOnlyWallet cannot sign transactions.");
|
|
203
|
+
}
|
|
204
|
+
}
|