@anywalletkit/wallet-ton 0.0.0-stage → 0.2.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/LICENSE +202 -0
- package/README.md +126 -2
- package/dist/chunk-BLWBC6L2.js +480 -0
- package/dist/chunk-BLWBC6L2.js.map +1 -0
- package/dist/history.cjs +499 -0
- package/dist/history.cjs.map +1 -0
- package/dist/history.d.cts +163 -0
- package/dist/history.d.ts +163 -0
- package/dist/history.js +25 -0
- package/dist/history.js.map +1 -0
- package/dist/index.cjs +1649 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +704 -0
- package/dist/index.d.ts +704 -0
- package/dist/index.js +1185 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1649 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
CAIP2: () => CAIP2,
|
|
24
|
+
DEFAULT_SEND_MODE: () => DEFAULT_SEND_MODE,
|
|
25
|
+
DEFAULT_TONAPI: () => DEFAULT_TONAPI,
|
|
26
|
+
HistoryUnavailableError: () => HistoryUnavailableError,
|
|
27
|
+
JETTON_TRANSFER_OP: () => JETTON_TRANSFER_OP,
|
|
28
|
+
MAINNET_GLOBAL_ID: () => MAINNET_GLOBAL_ID,
|
|
29
|
+
MAX_ACTIONS: () => MAX_ACTIONS,
|
|
30
|
+
SEND_MODE: () => SEND_MODE,
|
|
31
|
+
SimulationError: () => SimulationError,
|
|
32
|
+
TESTNET_GLOBAL_ID: () => TESTNET_GLOBAL_ID,
|
|
33
|
+
TON_BASE_PATH: () => TON_BASE_PATH,
|
|
34
|
+
TON_MNEMONIC_PATH: () => TON_MNEMONIC_PATH,
|
|
35
|
+
TonCenter: () => TonCenter,
|
|
36
|
+
UnsafeTransactionError: () => UnsafeTransactionError,
|
|
37
|
+
V5R1_CODE_HEX: () => V5R1_CODE_HEX,
|
|
38
|
+
V5R1_OP: () => V5R1_OP,
|
|
39
|
+
WalletAccountReadOnlyTon: () => WalletAccountReadOnlyTon,
|
|
40
|
+
WalletAccountTon: () => WalletAccountTon,
|
|
41
|
+
WalletManagerTon: () => WalletManagerTon,
|
|
42
|
+
commentCell: () => commentCell,
|
|
43
|
+
createTonClient: () => createTonRpc,
|
|
44
|
+
createTonHistory: () => createTonHistory,
|
|
45
|
+
createTonRpc: () => createTonRpc,
|
|
46
|
+
decodeBody: () => decodeBody,
|
|
47
|
+
decodeHistoryCursor: () => decodeCursor,
|
|
48
|
+
default: () => WalletManagerTon,
|
|
49
|
+
describeMessageRelaxed: () => describeMessageRelaxed,
|
|
50
|
+
describeTransaction: () => describeTransaction,
|
|
51
|
+
emulateExternal: () => emulateExternal,
|
|
52
|
+
emulateViaTonapi: () => emulateViaTonapi,
|
|
53
|
+
encodeHistoryCursor: () => encodeCursor,
|
|
54
|
+
installBufferGlobal: () => installBufferGlobal,
|
|
55
|
+
mapTonapiActions: () => mapActions,
|
|
56
|
+
mapTonapiEvent: () => mapTonapiEvent,
|
|
57
|
+
mapToncenterTransaction: () => mapToncenterTransaction,
|
|
58
|
+
parseEmulation: () => parseEmulation,
|
|
59
|
+
parseTonapiConsequences: () => parseTonapiConsequences,
|
|
60
|
+
parseTonapiRisk: () => parseTonapiRisk,
|
|
61
|
+
parseTonapiTrace: () => parseTonapiTrace,
|
|
62
|
+
parseV5R1Body: () => parseV5R1Body,
|
|
63
|
+
rawTonAddress: () => rawTonAddress,
|
|
64
|
+
resolveEmulate: () => resolveEmulate,
|
|
65
|
+
runEmulation: () => runEmulation,
|
|
66
|
+
tonCenterEndpoint: () => tonCenterEndpoint,
|
|
67
|
+
tonExplorerTxUrl: () => tonExplorerTxUrl,
|
|
68
|
+
v5r1Address: () => v5r1Address,
|
|
69
|
+
v5r1Code: () => v5r1Code,
|
|
70
|
+
v5r1ExternalMessage: () => v5r1ExternalMessage,
|
|
71
|
+
v5r1PackSigned: () => v5r1PackSigned,
|
|
72
|
+
v5r1PackUnsigned: () => v5r1PackUnsigned,
|
|
73
|
+
v5r1SigningMessage: () => v5r1SigningMessage,
|
|
74
|
+
v5r1StateInit: () => v5r1StateInit,
|
|
75
|
+
v5r1WalletId: () => v5r1WalletId,
|
|
76
|
+
valueFlowTransfers: () => valueFlowTransfers
|
|
77
|
+
});
|
|
78
|
+
module.exports = __toCommonJS(src_exports);
|
|
79
|
+
|
|
80
|
+
// src/buffer-global.ts
|
|
81
|
+
var import_buffer = require("buffer");
|
|
82
|
+
function installBufferGlobal() {
|
|
83
|
+
const g = globalThis;
|
|
84
|
+
g.Buffer ??= import_buffer.Buffer;
|
|
85
|
+
}
|
|
86
|
+
installBufferGlobal();
|
|
87
|
+
|
|
88
|
+
// src/types.ts
|
|
89
|
+
var CAIP2 = { mainnet: "ton:-239", testnet: "ton:-3" };
|
|
90
|
+
|
|
91
|
+
// src/errors.ts
|
|
92
|
+
var import_core = require("@anywalletkit/core");
|
|
93
|
+
var UnsafeTransactionError = class extends import_core.SignerError {
|
|
94
|
+
code;
|
|
95
|
+
constructor(code, message) {
|
|
96
|
+
super(message);
|
|
97
|
+
this.name = "UnsafeTransactionError";
|
|
98
|
+
this.code = code;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
var SimulationError = class extends Error {
|
|
102
|
+
exitCode;
|
|
103
|
+
aborted;
|
|
104
|
+
constructor(reason, details = {}) {
|
|
105
|
+
super(`Transaction simulation failed: ${reason}`);
|
|
106
|
+
this.name = "SimulationError";
|
|
107
|
+
this.exitCode = details.exitCode;
|
|
108
|
+
this.aborted = details.aborted ?? false;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// src/toncenter.ts
|
|
113
|
+
var import_core2 = require("@anywalletkit/core");
|
|
114
|
+
var import_buffer2 = require("buffer");
|
|
115
|
+
var import_core3 = require("@ton/core");
|
|
116
|
+
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
117
|
+
var isRpc = (p) => typeof p.call === "function";
|
|
118
|
+
function tonCenterEndpoint(endpoint, opts = {}) {
|
|
119
|
+
const url = endpoint.url;
|
|
120
|
+
const doFetch = opts.fetch ?? globalThis.fetch;
|
|
121
|
+
if (typeof doFetch !== "function") throw new import_core2.NetworkError("No fetch available; pass config.fetch", { provider: url });
|
|
122
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
123
|
+
return {
|
|
124
|
+
async call(method, params) {
|
|
125
|
+
let res;
|
|
126
|
+
try {
|
|
127
|
+
res = await doFetch(url, {
|
|
128
|
+
method: "POST",
|
|
129
|
+
headers: {
|
|
130
|
+
"content-type": "application/json",
|
|
131
|
+
accept: "application/json",
|
|
132
|
+
...endpoint.secretKey ? { "X-API-Key": endpoint.secretKey } : {}
|
|
133
|
+
},
|
|
134
|
+
body: JSON.stringify({ id: "1", jsonrpc: "2.0", method, params }),
|
|
135
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
136
|
+
});
|
|
137
|
+
} catch (e) {
|
|
138
|
+
throw new import_core2.NetworkError(`${method}: ${e instanceof Error ? e.message : String(e)}`, { provider: url, cause: e });
|
|
139
|
+
}
|
|
140
|
+
let json;
|
|
141
|
+
try {
|
|
142
|
+
json = await res.json();
|
|
143
|
+
} catch (e) {
|
|
144
|
+
throw new import_core2.NetworkError(`${method}: HTTP ${res.status}, non-JSON body`, { provider: url, cause: e });
|
|
145
|
+
}
|
|
146
|
+
if (!res.ok || json.ok !== true) {
|
|
147
|
+
const detail = typeof json.error === "string" ? json.error : JSON.stringify(json.error ?? json);
|
|
148
|
+
throw new import_core2.NetworkError(`${method}: ${detail} (HTTP ${res.status})`, { provider: url });
|
|
149
|
+
}
|
|
150
|
+
return json.result;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function createTonRpc(config) {
|
|
155
|
+
const target = config.tonClient ?? config.provider;
|
|
156
|
+
if (target === void 0) return void 0;
|
|
157
|
+
const list = (Array.isArray(target) ? target : [target]).map(
|
|
158
|
+
(p) => isRpc(p) ? p : tonCenterEndpoint(p, { ...config.fetch ? { fetch: config.fetch } : {}, ...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {} })
|
|
159
|
+
);
|
|
160
|
+
if (list.length === 0) throw new Error("The 'tonClient' option cannot be set to an empty list.");
|
|
161
|
+
if (list.length === 1) return list[0];
|
|
162
|
+
const attempts = list.length + (config.retries ?? 3);
|
|
163
|
+
return {
|
|
164
|
+
async call(method, params) {
|
|
165
|
+
let lastError;
|
|
166
|
+
for (let i = 0; i < attempts; i++) {
|
|
167
|
+
try {
|
|
168
|
+
return await list[i % list.length].call(method, params);
|
|
169
|
+
} catch (e) {
|
|
170
|
+
lastError = e;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
throw new import_core2.NetworkError(`All TON endpoints failed for ${method}`, { cause: lastError });
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function serializeStackItem(item) {
|
|
178
|
+
switch (item.type) {
|
|
179
|
+
case "int":
|
|
180
|
+
return ["num", item.value.toString()];
|
|
181
|
+
case "cell":
|
|
182
|
+
return ["tvm.Cell", item.cell.toBoc().toString("base64")];
|
|
183
|
+
case "slice":
|
|
184
|
+
return ["tvm.Slice", item.cell.toBoc().toString("base64")];
|
|
185
|
+
case "builder":
|
|
186
|
+
return ["tvm.Builder", item.cell.toBoc().toString("base64")];
|
|
187
|
+
default:
|
|
188
|
+
throw new Error(`Unsupported stack item type: ${item.type}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
var hexBigInt = (s) => s.startsWith("-") ? -BigInt(s.slice(1)) : BigInt(s);
|
|
192
|
+
var cellFromBase64 = (b64) => import_core3.Cell.fromBoc(import_buffer2.Buffer.from(b64, "base64"))[0];
|
|
193
|
+
function parseStackEntry(x) {
|
|
194
|
+
const o = x;
|
|
195
|
+
switch (o["@type"]) {
|
|
196
|
+
case "tvm.list":
|
|
197
|
+
case "tvm.tuple":
|
|
198
|
+
return { type: "tuple", items: o.elements.map(parseStackEntry) };
|
|
199
|
+
case "tvm.cell":
|
|
200
|
+
return { type: "cell", cell: cellFromBase64(o.bytes) };
|
|
201
|
+
case "tvm.stackEntryCell":
|
|
202
|
+
return parseStackEntry(o.cell);
|
|
203
|
+
case "tvm.stackEntryTuple":
|
|
204
|
+
return parseStackEntry(o.tuple);
|
|
205
|
+
case "tvm.stackEntryNumber":
|
|
206
|
+
return parseStackEntry(o.number);
|
|
207
|
+
case "tvm.numberDecimal":
|
|
208
|
+
return { type: "int", value: BigInt(o.number) };
|
|
209
|
+
default:
|
|
210
|
+
throw new import_core2.NetworkError(`Malformed stack entry: ${JSON.stringify(x)}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function parseStackItem(s) {
|
|
214
|
+
if (!Array.isArray(s) || s.length < 2) throw new import_core2.NetworkError(`Malformed stack item: ${JSON.stringify(s)}`);
|
|
215
|
+
const [kind, value] = s;
|
|
216
|
+
switch (kind) {
|
|
217
|
+
case "num":
|
|
218
|
+
return { type: "int", value: hexBigInt(value) };
|
|
219
|
+
case "null":
|
|
220
|
+
return { type: "null" };
|
|
221
|
+
case "cell":
|
|
222
|
+
return { type: "cell", cell: cellFromBase64(value.bytes) };
|
|
223
|
+
case "slice":
|
|
224
|
+
return { type: "slice", cell: cellFromBase64(value.bytes) };
|
|
225
|
+
case "builder":
|
|
226
|
+
return { type: "builder", cell: cellFromBase64(value.bytes) };
|
|
227
|
+
case "tuple":
|
|
228
|
+
case "list":
|
|
229
|
+
return { type: "tuple", items: (value.elements ?? []).map(parseStackEntry) };
|
|
230
|
+
default:
|
|
231
|
+
throw new import_core2.NetworkError(`Malformed stack item kind: ${kind}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
var need = (cond, what, value) => {
|
|
235
|
+
if (!cond) throw new import_core2.NetworkError(`Malformed toncenter response: ${what}`);
|
|
236
|
+
return value;
|
|
237
|
+
};
|
|
238
|
+
var TonCenter = class {
|
|
239
|
+
constructor(rpc) {
|
|
240
|
+
this.rpc = rpc;
|
|
241
|
+
}
|
|
242
|
+
rpc;
|
|
243
|
+
/** Balance, status, code and data of an account. */
|
|
244
|
+
async getAddressInformation(address) {
|
|
245
|
+
const r = await this.rpc.call("getAddressInformation", { address: address.toString() });
|
|
246
|
+
need(r && typeof r === "object" && typeof r.balance === "string", "getAddressInformation", r);
|
|
247
|
+
const state = r.state;
|
|
248
|
+
const last = r.last_transaction_id;
|
|
249
|
+
return {
|
|
250
|
+
balance: BigInt(r.balance),
|
|
251
|
+
state: ["active", "uninitialized", "frozen", "nonexist"].includes(state) ? state : "nonexist",
|
|
252
|
+
code: typeof r.code === "string" && r.code !== "" ? cellFromBase64(r.code) : null,
|
|
253
|
+
data: typeof r.data === "string" && r.data !== "" ? cellFromBase64(r.data) : null,
|
|
254
|
+
lastTransaction: last && last.lt && last.lt !== "0" ? { lt: last.lt, hash: last.hash ?? "" } : null
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/** Call a contract get-method; the result carries the exit code. */
|
|
258
|
+
async runGetMethod(address, method, stack = []) {
|
|
259
|
+
const r = await this.rpc.call("runGetMethod", {
|
|
260
|
+
address: address.toString(),
|
|
261
|
+
method,
|
|
262
|
+
stack: stack.map(serializeStackItem)
|
|
263
|
+
});
|
|
264
|
+
need(r && typeof r === "object" && typeof r.exit_code === "number" && Array.isArray(r.stack), "runGetMethod", r);
|
|
265
|
+
return { exitCode: r.exit_code, gasUsed: Number(r.gas_used ?? 0), stack: new import_core3.TupleReader(r.stack.map(parseStackItem)) };
|
|
266
|
+
}
|
|
267
|
+
/** Like runGetMethod but throws on a non-zero exit code. */
|
|
268
|
+
async runGetMethodOk(address, method, stack = []) {
|
|
269
|
+
const r = await this.runGetMethod(address, method, stack);
|
|
270
|
+
if (r.exitCode !== 0) throw new import_core2.NetworkError(`Get method ${method} failed with exit_code: ${r.exitCode}`);
|
|
271
|
+
return r;
|
|
272
|
+
}
|
|
273
|
+
/** Broadcast a signed external message. */
|
|
274
|
+
async sendBoc(message) {
|
|
275
|
+
await this.rpc.call("sendBoc", { boc: message.toBoc().toString("base64") });
|
|
276
|
+
}
|
|
277
|
+
/** Broadcast and return the external-message hash (base64) toncenter reports. */
|
|
278
|
+
async sendBocReturnHash(message) {
|
|
279
|
+
const r = await this.rpc.call("sendBocReturnHash", { boc: message.toBoc().toString("base64") });
|
|
280
|
+
return need(r && typeof r.hash === "string", "sendBocReturnHash", r.hash);
|
|
281
|
+
}
|
|
282
|
+
async getTransactions(address, opts) {
|
|
283
|
+
const params = { address: address.toString(), limit: opts.limit };
|
|
284
|
+
if (opts.lt) params.lt = opts.lt;
|
|
285
|
+
if (opts.hash) params.hash = import_buffer2.Buffer.from(opts.hash, "base64").toString("hex");
|
|
286
|
+
if (opts.archival) params.archival = true;
|
|
287
|
+
const r = await this.rpc.call("getTransactions", params);
|
|
288
|
+
need(Array.isArray(r), "getTransactions", r);
|
|
289
|
+
return r.map((t) => {
|
|
290
|
+
need(typeof t.data === "string", "getTransactions[].data", t);
|
|
291
|
+
return (0, import_core3.loadTransaction)(cellFromBase64(t.data).beginParse());
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
async estimateFee(address, args) {
|
|
295
|
+
const r = await this.rpc.call("estimateFee", {
|
|
296
|
+
address: address.toString(),
|
|
297
|
+
body: args.body.toBoc().toString("base64"),
|
|
298
|
+
init_code: args.init ? args.init.code.toBoc().toString("base64") : "",
|
|
299
|
+
init_data: args.init ? args.init.data.toBoc().toString("base64") : "",
|
|
300
|
+
ignore_chksig: args.ignoreSignature ?? true
|
|
301
|
+
});
|
|
302
|
+
const f = r?.source_fees;
|
|
303
|
+
need(f && ["in_fwd_fee", "storage_fee", "gas_fee", "fwd_fee"].every((k) => typeof f[k] === "number" || typeof f[k] === "string"), "estimateFee", r);
|
|
304
|
+
const fees = {
|
|
305
|
+
inFwdFee: BigInt(f.in_fwd_fee),
|
|
306
|
+
storageFee: BigInt(f.storage_fee),
|
|
307
|
+
gasFee: BigInt(f.gas_fee),
|
|
308
|
+
fwdFee: BigInt(f.fwd_fee)
|
|
309
|
+
};
|
|
310
|
+
return { ...fees, total: fees.inFwdFee + fees.storageFee + fees.gasFee + fees.fwdFee };
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
function parseEmulation(json) {
|
|
314
|
+
const j = json;
|
|
315
|
+
if (!j || typeof j !== "object") throw new import_core2.NetworkError("Malformed emulation response");
|
|
316
|
+
if (typeof j.error === "string") return { ok: false, reason: j.error };
|
|
317
|
+
const txs = j.transactions;
|
|
318
|
+
const rootHash = j.trace?.tx_hash;
|
|
319
|
+
let tx;
|
|
320
|
+
if (Array.isArray(txs)) tx = txs[0];
|
|
321
|
+
else if (txs && typeof txs === "object") tx = rootHash && txs[rootHash] || Object.values(txs)[0];
|
|
322
|
+
if (!tx) {
|
|
323
|
+
if (typeof j.exit_code === "number") return verdict(j.exit_code, j.aborted === true, void 0);
|
|
324
|
+
throw new import_core2.NetworkError("Malformed emulation response: no transactions");
|
|
325
|
+
}
|
|
326
|
+
const d = tx.description ?? tx;
|
|
327
|
+
const compute = d.compute_ph ?? d.compute;
|
|
328
|
+
const action = d.action;
|
|
329
|
+
const exitCode = typeof compute?.exit_code === "number" ? compute.exit_code : compute?.success === true ? 0 : void 0;
|
|
330
|
+
const actionSuccess = typeof action?.success === "boolean" ? action.success : void 0;
|
|
331
|
+
if (exitCode === void 0 && d.aborted === void 0 && actionSuccess === void 0) throw new import_core2.NetworkError("Malformed emulation response: no phases");
|
|
332
|
+
return verdict(exitCode, d.aborted === true, actionSuccess);
|
|
333
|
+
}
|
|
334
|
+
function verdict(exitCode, aborted, actionSuccess) {
|
|
335
|
+
const ok = !aborted && (exitCode === void 0 || exitCode === 0) && actionSuccess !== false;
|
|
336
|
+
const reason = aborted ? "transaction aborted" : exitCode !== void 0 && exitCode !== 0 ? `compute phase exit_code ${exitCode}` : actionSuccess === false ? "action phase failed" : void 0;
|
|
337
|
+
return { ok, ...exitCode !== void 0 ? { exitCode } : {}, aborted, ...actionSuccess !== void 0 ? { actionSuccess } : {}, ...reason ? { reason } : {} };
|
|
338
|
+
}
|
|
339
|
+
async function emulateExternal(url, message, opts = {}) {
|
|
340
|
+
const doFetch = opts.fetch ?? globalThis.fetch;
|
|
341
|
+
let res;
|
|
342
|
+
try {
|
|
343
|
+
res = await doFetch(url, {
|
|
344
|
+
method: "POST",
|
|
345
|
+
headers: { "content-type": "application/json", accept: "application/json", ...opts.apiKey ? { "X-API-Key": opts.apiKey } : {} },
|
|
346
|
+
body: JSON.stringify({ boc: message.toBoc().toString("base64"), ignore_chksig: true, include_code_data: false, with_actions: false }),
|
|
347
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS)
|
|
348
|
+
});
|
|
349
|
+
} catch (e) {
|
|
350
|
+
throw new import_core2.NetworkError(`emulate: ${e instanceof Error ? e.message : String(e)}`, { provider: url, cause: e });
|
|
351
|
+
}
|
|
352
|
+
let json;
|
|
353
|
+
try {
|
|
354
|
+
json = await res.json();
|
|
355
|
+
} catch (e) {
|
|
356
|
+
throw new import_core2.NetworkError(`emulate: HTTP ${res.status}, non-JSON body`, { provider: url, cause: e });
|
|
357
|
+
}
|
|
358
|
+
if (!res.ok) {
|
|
359
|
+
const err = json;
|
|
360
|
+
return { ok: false, reason: `HTTP ${res.status}: ${typeof err.error === "string" ? err.error : JSON.stringify(err.detail ?? err.error ?? json)}` };
|
|
361
|
+
}
|
|
362
|
+
return parseEmulation(json);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/emulation.ts
|
|
366
|
+
var import_core4 = require("@anywalletkit/core");
|
|
367
|
+
var DEFAULT_TIMEOUT_MS2 = 15e3;
|
|
368
|
+
var DEFAULT_TONAPI = "https://tonapi.io/v2";
|
|
369
|
+
function isObject(v) {
|
|
370
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
371
|
+
}
|
|
372
|
+
function creditedUninit(tx) {
|
|
373
|
+
const c = tx.compute_phase;
|
|
374
|
+
return tx.success === true && c?.skipped === true && c.skip_reason === "cskip_no_state" && tx.bounce_phase === void 0 && (tx.action_phase === void 0 || tx.action_phase.success !== false);
|
|
375
|
+
}
|
|
376
|
+
function txFailure(tx) {
|
|
377
|
+
const c = tx.compute_phase;
|
|
378
|
+
if (creditedUninit(tx)) return void 0;
|
|
379
|
+
if (tx.aborted === true) return "aborted";
|
|
380
|
+
if (c && c.skipped !== true && typeof c.exit_code === "number" && c.exit_code !== 0) return `compute exit_code ${c.exit_code}${c.exit_code_description ? ` (${c.exit_code_description})` : ""}`;
|
|
381
|
+
if (c && c.skipped === true && tx.success === false) return `compute skipped${c.skip_reason ? ` (${c.skip_reason})` : ""}`;
|
|
382
|
+
if (tx.action_phase && tx.action_phase.success === false) return `action phase failed${typeof tx.action_phase.result_code === "number" ? ` (code ${tx.action_phase.result_code})` : ""}`;
|
|
383
|
+
if (typeof tx.bounce_phase === "string" && tx.bounce_phase !== "TrPhaseBounceOk" && tx.success === false) return `bounced (${tx.bounce_phase})`;
|
|
384
|
+
if (tx.success === false) return "failed";
|
|
385
|
+
return void 0;
|
|
386
|
+
}
|
|
387
|
+
function accountOf(tx) {
|
|
388
|
+
return typeof tx.account === "string" ? tx.account : tx.account?.address ?? "?";
|
|
389
|
+
}
|
|
390
|
+
function parseTonapiTrace(trace) {
|
|
391
|
+
if (!isObject(trace) || !isObject(trace.transaction)) throw new import_core4.NetworkError("Malformed tonapi emulation: no root transaction");
|
|
392
|
+
const root = trace.transaction;
|
|
393
|
+
const compute = root.compute_phase;
|
|
394
|
+
const exitCode = compute && compute.skipped !== true && typeof compute.exit_code === "number" ? compute.exit_code : compute?.success === true ? 0 : void 0;
|
|
395
|
+
const actionSuccess = typeof root.action_phase?.success === "boolean" ? root.action_phase.success : void 0;
|
|
396
|
+
if (exitCode === void 0 && actionSuccess === void 0 && typeof root.success !== "boolean" && typeof root.aborted !== "boolean") {
|
|
397
|
+
throw new import_core4.NetworkError("Malformed tonapi emulation: root transaction has no phases");
|
|
398
|
+
}
|
|
399
|
+
const aborted = root.aborted === true;
|
|
400
|
+
const rootFailure = txFailure(root);
|
|
401
|
+
const warnings = [];
|
|
402
|
+
let firstHopFailure;
|
|
403
|
+
const walk = (t, depth) => {
|
|
404
|
+
for (const child of t.children ?? []) {
|
|
405
|
+
const tx = child.transaction;
|
|
406
|
+
if (tx) {
|
|
407
|
+
const f = txFailure(tx);
|
|
408
|
+
if (f) {
|
|
409
|
+
const line = `${accountOf(tx)}: ${f}`;
|
|
410
|
+
if (depth === 1) firstHopFailure ??= line;
|
|
411
|
+
else warnings.push(line);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
walk(child, depth + 1);
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
walk(trace, 1);
|
|
418
|
+
const reason = rootFailure ? aborted ? "transaction aborted" : rootFailure : firstHopFailure ? `internal message failed \u2014 ${firstHopFailure}` : void 0;
|
|
419
|
+
return {
|
|
420
|
+
ok: reason === void 0,
|
|
421
|
+
...exitCode !== void 0 ? { exitCode } : {},
|
|
422
|
+
aborted,
|
|
423
|
+
...actionSuccess !== void 0 ? { actionSuccess } : {},
|
|
424
|
+
...reason ? { reason } : {},
|
|
425
|
+
warnings
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function parseTonapiRisk(risk) {
|
|
429
|
+
if (!isObject(risk)) throw new import_core4.NetworkError("Malformed tonapi emulation: no risk");
|
|
430
|
+
const big3 = (v) => {
|
|
431
|
+
if (typeof v === "bigint") return v;
|
|
432
|
+
if (typeof v === "number" && Number.isFinite(v)) return BigInt(Math.trunc(v));
|
|
433
|
+
if (typeof v === "string" && /^-?\d+$/.test(v)) return BigInt(v);
|
|
434
|
+
throw new import_core4.NetworkError("Malformed tonapi emulation: risk amount");
|
|
435
|
+
};
|
|
436
|
+
const jettons = Array.isArray(risk.jettons) ? risk.jettons : [];
|
|
437
|
+
return {
|
|
438
|
+
ton: big3(risk.ton ?? 0),
|
|
439
|
+
transferAllRemainingBalance: risk.transfer_all_remaining_balance === true,
|
|
440
|
+
jettons: jettons.map((j) => {
|
|
441
|
+
if (!isObject(j)) throw new import_core4.NetworkError("Malformed tonapi emulation: risk jetton");
|
|
442
|
+
const jetton = isObject(j.jetton) ? j.jetton : {};
|
|
443
|
+
const wallet = isObject(j.wallet_address) ? j.wallet_address : {};
|
|
444
|
+
return {
|
|
445
|
+
amount: big3(j.quantity ?? 0),
|
|
446
|
+
address: typeof jetton.address === "string" ? jetton.address : "",
|
|
447
|
+
walletAddress: typeof wallet.address === "string" ? wallet.address : "",
|
|
448
|
+
...typeof jetton.symbol === "string" ? { symbol: jetton.symbol } : {},
|
|
449
|
+
...typeof jetton.decimals === "number" ? { decimals: jetton.decimals } : {}
|
|
450
|
+
};
|
|
451
|
+
})
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function parseTonapiConsequences(json) {
|
|
455
|
+
if (!isObject(json)) throw new import_core4.NetworkError("Malformed tonapi emulation response");
|
|
456
|
+
if (typeof json.error === "string") return { ok: false, reason: json.error, aborted: false, warnings: [], source: "tonapi" };
|
|
457
|
+
const verdict2 = parseTonapiTrace(json.trace);
|
|
458
|
+
return { ...verdict2, source: "tonapi", risk: parseTonapiRisk(json.risk) };
|
|
459
|
+
}
|
|
460
|
+
async function post(doFetch, url, body, opts) {
|
|
461
|
+
let res;
|
|
462
|
+
try {
|
|
463
|
+
res = await doFetch(url, {
|
|
464
|
+
method: "POST",
|
|
465
|
+
headers: { "content-type": "application/json", accept: "application/json", ...opts.apiKey ? { authorization: `Bearer ${opts.apiKey}` } : {} },
|
|
466
|
+
body: JSON.stringify(body),
|
|
467
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS2)
|
|
468
|
+
});
|
|
469
|
+
} catch (e) {
|
|
470
|
+
throw new import_core4.NetworkError(`emulate: ${e instanceof Error ? e.message : String(e)}`, { provider: url, cause: e });
|
|
471
|
+
}
|
|
472
|
+
if (res.status === 404) return { status: 404, ok: false, json: void 0 };
|
|
473
|
+
let json;
|
|
474
|
+
try {
|
|
475
|
+
json = await res.json();
|
|
476
|
+
} catch (e) {
|
|
477
|
+
throw new import_core4.NetworkError(`emulate: HTTP ${res.status}, non-JSON body`, { provider: url, cause: e });
|
|
478
|
+
}
|
|
479
|
+
return { status: res.status, ok: res.ok, json };
|
|
480
|
+
}
|
|
481
|
+
function httpFailure(status, json) {
|
|
482
|
+
const err = isObject(json) ? json : {};
|
|
483
|
+
const text = typeof err.error === "string" ? err.error : JSON.stringify(err.detail ?? err.error ?? json);
|
|
484
|
+
return { ok: false, reason: `HTTP ${status}: ${text}`, aborted: false, warnings: [], source: "tonapi" };
|
|
485
|
+
}
|
|
486
|
+
async function emulateViaTonapi(baseUrl, message, opts = {}) {
|
|
487
|
+
const doFetch = opts.fetch ?? globalThis.fetch;
|
|
488
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
489
|
+
const boc = message.toBoc().toString("base64");
|
|
490
|
+
const first = await post(doFetch, `${base}/wallet/emulate?ignore_signature_check=true`, { boc }, opts);
|
|
491
|
+
if (first.status !== 404) {
|
|
492
|
+
if (!first.ok) return httpFailure(first.status, first.json);
|
|
493
|
+
return parseTonapiConsequences(first.json);
|
|
494
|
+
}
|
|
495
|
+
const second = await post(doFetch, `${base}/traces/emulate?ignore_signature_check=true`, { boc }, opts);
|
|
496
|
+
if (second.status === 404) throw new import_core4.NetworkError("emulate: tonapi serves neither /wallet/emulate nor /traces/emulate", { provider: base });
|
|
497
|
+
if (!second.ok) return httpFailure(second.status, second.json);
|
|
498
|
+
if (isObject(second.json) && typeof second.json.error === "string") return { ok: false, reason: second.json.error, aborted: false, warnings: [], source: "tonapi" };
|
|
499
|
+
return { ...parseTonapiTrace(second.json), source: "tonapi" };
|
|
500
|
+
}
|
|
501
|
+
function resolveEmulate(config) {
|
|
502
|
+
if (config.emulate === false) return false;
|
|
503
|
+
if (config.emulate) return config.emulate;
|
|
504
|
+
if (config.emulateUrl) return { kind: "toncenter-v3", url: config.emulateUrl };
|
|
505
|
+
return { kind: "tonapi" };
|
|
506
|
+
}
|
|
507
|
+
async function runEmulation(config, message) {
|
|
508
|
+
const target = resolveEmulate(config);
|
|
509
|
+
if (!target) return void 0;
|
|
510
|
+
const opts = {
|
|
511
|
+
...config.fetch ? { fetch: config.fetch } : {},
|
|
512
|
+
...config.timeoutMs !== void 0 ? { timeoutMs: config.timeoutMs } : {},
|
|
513
|
+
...config.tonApiKey ? { apiKey: config.tonApiKey } : {}
|
|
514
|
+
};
|
|
515
|
+
if (target.kind === "tonapi") return emulateViaTonapi(target.url ?? config.tonApiUrl ?? DEFAULT_TONAPI, message, opts);
|
|
516
|
+
const v = await emulateExternal(target.url, message, opts);
|
|
517
|
+
return { ...v, aborted: v.aborted ?? false, source: "toncenter-v3", warnings: [] };
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// src/wallet-v5r1.ts
|
|
521
|
+
var import_buffer3 = require("buffer");
|
|
522
|
+
var import_core5 = require("@ton/core");
|
|
523
|
+
var V5R1_CODE_HEX = "b5ee9c7241021401000281000114ff00f4a413f4bcf2c80b01020120020d020148030402dcd020d749c120915b8f6320d70b1f2082106578746ebd21821073696e74bdb0925f03e082106578746eba8eb48020d72101d074d721fa4030fa44f828fa443058bd915be0ed44d0810141d721f4058307f40e6fa1319130e18040d721707fdb3ce03120d749810280b99130e070e2100f020120050c020120060902016e07080019adce76a2684020eb90eb85ffc00019af1df6a2684010eb90eb858fc00201480a0b0017b325fb51341c75c875c2c7e00011b262fb513435c280200019be5f0f6a2684080a0eb90fa02c0102f20e011e20d70b1f82107369676ebaf2e08a7f0f01e68ef0eda2edfb218308d722028308d723208020d721d31fd31fd31fed44d0d200d31f20d31fd3ffd70a000af90140ccf9109a28945f0adb31e1f2c087df02b35007b0f2d0845125baf2e0855036baf2e086f823bbf2d0882292f800de01a47fc8ca00cb1f01cf16c9ed542092f80fde70db3cd81003f6eda2edfb02f404216e926c218e4c0221d73930709421c700b38e2d01d72820761e436c20d749c008f2e09320d74ac002f2e09320d71d06c712c2005230b0f2d089d74cd7393001a4e86c128407bbf2e093d74ac000f2e093ed55e2d20001c000915be0ebd72c08142091709601d72c081c12e25210b1e30f20d74a111213009601fa4001fa44f828fa443058baf2e091ed44d0810141d718f405049d7fc8ca0040048307f453f2e08b8e14038307f45bf2e08c22d70a00216e01b3b0f2d090e2c85003cf1612f400c9ed54007230d72c08248e2d21f2e092d200ed44d0d2005113baf2d08f54503091319c01810140d721d70a00f2e08ee2c8ca0058cf16c9ed5493f2c08de20010935bdb31e1d74cd0b4d6c35e";
|
|
524
|
+
var codeCell;
|
|
525
|
+
function v5r1Code() {
|
|
526
|
+
codeCell ??= import_core5.Cell.fromBoc(import_buffer3.Buffer.from(V5R1_CODE_HEX, "hex"))[0];
|
|
527
|
+
return codeCell;
|
|
528
|
+
}
|
|
529
|
+
var V5R1_OP = {
|
|
530
|
+
signedExternal: 1936287598,
|
|
531
|
+
signedInternal: 1936289396,
|
|
532
|
+
extension: 1702392942
|
|
533
|
+
};
|
|
534
|
+
var MAINNET_GLOBAL_ID = -239;
|
|
535
|
+
var TESTNET_GLOBAL_ID = -3;
|
|
536
|
+
var DEFAULT_SEND_MODE = import_core5.SendMode.PAY_GAS_SEPARATELY | import_core5.SendMode.IGNORE_ERRORS;
|
|
537
|
+
var MAX_ACTIONS = 255;
|
|
538
|
+
function v5r1WalletId(parts = {}) {
|
|
539
|
+
const context = (0, import_core5.beginCell)().storeUint(1, 1).storeInt(parts.workchain ?? 0, 8).storeUint(0, 8).storeUint(parts.subwalletNumber ?? 0, 15).endCell().beginParse().loadInt(32);
|
|
540
|
+
return BigInt(parts.networkGlobalId ?? MAINNET_GLOBAL_ID) ^ BigInt(context);
|
|
541
|
+
}
|
|
542
|
+
function v5r1StateInit(publicKey, walletId = v5r1WalletId()) {
|
|
543
|
+
if (publicKey.length !== 32) throw new Error("V5R1: public key must be 32 bytes");
|
|
544
|
+
const data = (0, import_core5.beginCell)().storeUint(1, 1).storeUint(0, 32).storeInt(walletId, 32).storeBuffer(import_buffer3.Buffer.from(publicKey), 32).storeBit(0).endCell();
|
|
545
|
+
return { code: v5r1Code(), data };
|
|
546
|
+
}
|
|
547
|
+
function v5r1Address(publicKey, opts = {}) {
|
|
548
|
+
return (0, import_core5.contractAddress)(opts.workchain ?? 0, v5r1StateInit(publicKey, opts.walletId));
|
|
549
|
+
}
|
|
550
|
+
function v5r1SigningMessage(p) {
|
|
551
|
+
if (p.messages.length > MAX_ACTIONS) throw new Error(`V5R1: at most ${MAX_ACTIONS} messages per external (got ${p.messages.length})`);
|
|
552
|
+
const actions = p.messages.map((m) => ({
|
|
553
|
+
type: "sendMsg",
|
|
554
|
+
// Externals must carry IGNORE_ERRORS or the wallet contract rejects them.
|
|
555
|
+
mode: (m.mode ?? DEFAULT_SEND_MODE) | import_core5.SendMode.IGNORE_ERRORS,
|
|
556
|
+
outMsg: m.message
|
|
557
|
+
}));
|
|
558
|
+
const outList = actions.length ? (0, import_core5.beginCell)().store((0, import_core5.storeOutList)(actions)).endCell() : null;
|
|
559
|
+
return (0, import_core5.beginCell)().storeUint(V5R1_OP.signedExternal, 32).storeInt(p.walletId, 32).storeUint(p.validUntil, 32).storeUint(p.seqno, 32).storeMaybeRef(outList).storeUint(0, 1).endCell();
|
|
560
|
+
}
|
|
561
|
+
function v5r1PackSigned(signing, signature) {
|
|
562
|
+
if (signature.length !== 64) throw new Error("V5R1: signature must be 64 bytes");
|
|
563
|
+
return (0, import_core5.beginCell)().storeSlice(signing.beginParse()).storeBuffer(import_buffer3.Buffer.from(signature), 64).endCell();
|
|
564
|
+
}
|
|
565
|
+
function v5r1PackUnsigned(signing) {
|
|
566
|
+
return v5r1PackSigned(signing, new Uint8Array(64));
|
|
567
|
+
}
|
|
568
|
+
function v5r1ExternalMessage(to, body, init) {
|
|
569
|
+
return (0, import_core5.beginCell)().store((0, import_core5.storeMessage)((0, import_core5.external)({ to, body, ...init ? { init } : {} }))).endCell();
|
|
570
|
+
}
|
|
571
|
+
function parseV5R1Body(body) {
|
|
572
|
+
try {
|
|
573
|
+
const s = body.beginParse();
|
|
574
|
+
const op = s.loadUint(32);
|
|
575
|
+
if (op !== V5R1_OP.signedExternal && op !== V5R1_OP.signedInternal) return null;
|
|
576
|
+
const walletId = BigInt(s.loadInt(32));
|
|
577
|
+
const validUntil = s.loadUint(32);
|
|
578
|
+
const seqno = s.loadUint(32);
|
|
579
|
+
const outList = s.loadMaybeRef();
|
|
580
|
+
const hasExtended = s.loadBit();
|
|
581
|
+
if (hasExtended) return null;
|
|
582
|
+
const messages = [];
|
|
583
|
+
if (outList) {
|
|
584
|
+
for (const a of (0, import_core5.loadOutList)(outList.beginParse())) {
|
|
585
|
+
if (a.type !== "sendMsg") return null;
|
|
586
|
+
messages.push({ message: a.outMsg, mode: a.mode });
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
const signed = s.remainingBits >= 512;
|
|
590
|
+
return { op, walletId, validUntil, seqno, messages, signed };
|
|
591
|
+
} catch {
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// src/describe.ts
|
|
597
|
+
var import_core6 = require("@ton/core");
|
|
598
|
+
var JETTON_TRANSFER_OP = 260734629;
|
|
599
|
+
var LARGE_FORWARD_NANOTON = 500000000n;
|
|
600
|
+
function decodeBody(body) {
|
|
601
|
+
const s = body.beginParse();
|
|
602
|
+
if (s.remainingBits === 0 && s.remainingRefs === 0) return { type: "empty" };
|
|
603
|
+
if (s.remainingBits < 32) return { type: "unknown" };
|
|
604
|
+
const op = s.loadUint(32);
|
|
605
|
+
try {
|
|
606
|
+
if (op === 0) return { type: "comment", text: s.loadStringTail() };
|
|
607
|
+
if (op === JETTON_TRANSFER_OP) return { type: "jetton-transfer", jetton: loadJettonTransfer(s) };
|
|
608
|
+
} catch {
|
|
609
|
+
return { type: "unknown" };
|
|
610
|
+
}
|
|
611
|
+
return { type: "op", op };
|
|
612
|
+
}
|
|
613
|
+
function loadJettonTransfer(s) {
|
|
614
|
+
const queryId = s.loadUintBig(64);
|
|
615
|
+
const amount = s.loadCoins();
|
|
616
|
+
const destination = s.loadAddress();
|
|
617
|
+
const responseDestination = s.loadMaybeAddress();
|
|
618
|
+
const customPayload = s.loadMaybeRef();
|
|
619
|
+
const forwardTonAmount = s.loadCoins();
|
|
620
|
+
const inRef = s.loadBit();
|
|
621
|
+
const payload = inRef ? s.loadRef().beginParse() : s;
|
|
622
|
+
let forwardPayload = "none";
|
|
623
|
+
let forwardComment;
|
|
624
|
+
if (payload.remainingBits > 0 || payload.remainingRefs > 0) {
|
|
625
|
+
forwardPayload = "cell";
|
|
626
|
+
if (payload.remainingBits >= 32) {
|
|
627
|
+
const p = payload.clone();
|
|
628
|
+
if (p.loadUint(32) === 0) {
|
|
629
|
+
try {
|
|
630
|
+
forwardComment = p.loadStringTail();
|
|
631
|
+
forwardPayload = "comment";
|
|
632
|
+
} catch {
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
queryId,
|
|
639
|
+
amount,
|
|
640
|
+
destination: destination.toString(),
|
|
641
|
+
responseDestination: responseDestination ? responseDestination.toString() : null,
|
|
642
|
+
forwardTonAmount,
|
|
643
|
+
forwardPayload,
|
|
644
|
+
...forwardComment !== void 0 ? { forwardComment } : {},
|
|
645
|
+
hasCustomPayload: customPayload !== null
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
function parseTo(to) {
|
|
649
|
+
if (import_core6.Address.isFriendly(to)) {
|
|
650
|
+
const f = import_core6.Address.parseFriendly(to);
|
|
651
|
+
return { to, bounceable: f.isBounceable, testOnly: f.isTestOnly, workchain: f.address.workChain };
|
|
652
|
+
}
|
|
653
|
+
const a = import_core6.Address.parse(to);
|
|
654
|
+
return { to: a.toRawString(), bounceable: true, workchain: a.workChain };
|
|
655
|
+
}
|
|
656
|
+
function bodyOf(tx) {
|
|
657
|
+
if (tx.body instanceof import_core6.Cell) return tx.body;
|
|
658
|
+
if (typeof tx.body === "string") return tx.body === "" ? void 0 : commentCell(tx.body);
|
|
659
|
+
if (tx.bodyBoc) return import_core6.Cell.fromBase64(tx.bodyBoc);
|
|
660
|
+
return void 0;
|
|
661
|
+
}
|
|
662
|
+
function commentCell(text) {
|
|
663
|
+
return (0, import_core6.beginCell)().storeUint(0, 32).storeStringTail(text).endCell();
|
|
664
|
+
}
|
|
665
|
+
function describeOne(i, to, value, body, hasStateInit, bounceableOverride, warnings, ctx) {
|
|
666
|
+
const bounceable = bounceableOverride ?? to.bounceable;
|
|
667
|
+
const decoded = body ? decodeBody(body) : { type: "empty" };
|
|
668
|
+
const base = { to: to.to, value, bounceable, ...to.testOnly !== void 0 ? { testOnly: to.testOnly } : {}, hasStateInit, bodyBits: body ? body.bits.length : 0 };
|
|
669
|
+
if (value <= 0n) warnings.push({ code: "zero-value", message: "message carries no TON; it cannot pay for gas at the destination", index: i });
|
|
670
|
+
if (to.workchain !== 0 && to.workchain !== -1) warnings.push({ code: "exotic-workchain", message: `workchain ${to.workchain}`, index: i });
|
|
671
|
+
if (ctx.network && to.testOnly !== void 0) {
|
|
672
|
+
if (to.testOnly && ctx.network !== "testnet") warnings.push({ code: "testnet-address", message: "test-only address on a mainnet wallet", index: i });
|
|
673
|
+
if (!to.testOnly && ctx.network === "testnet") warnings.push({ code: "mainnet-address", message: "mainnet address on a testnet wallet", index: i });
|
|
674
|
+
}
|
|
675
|
+
let item;
|
|
676
|
+
switch (decoded.type) {
|
|
677
|
+
case "empty":
|
|
678
|
+
item = { kind: "ton-transfer", ...base };
|
|
679
|
+
break;
|
|
680
|
+
case "comment":
|
|
681
|
+
item = { kind: "ton-transfer", ...base, comment: decoded.text };
|
|
682
|
+
break;
|
|
683
|
+
case "jetton-transfer":
|
|
684
|
+
item = { kind: "jetton-transfer", ...base, jetton: decoded.jetton, op: hex(JETTON_TRANSFER_OP) };
|
|
685
|
+
if (decoded.jetton.forwardTonAmount > LARGE_FORWARD_NANOTON) warnings.push({ code: "large-forward-amount", message: `forward_ton_amount ${decoded.jetton.forwardTonAmount} > 0.5 TON`, index: i });
|
|
686
|
+
break;
|
|
687
|
+
case "op":
|
|
688
|
+
item = { kind: "contract-call", ...base, op: hex(decoded.op) };
|
|
689
|
+
break;
|
|
690
|
+
default:
|
|
691
|
+
item = { kind: "contract-call", ...base };
|
|
692
|
+
warnings.push({ code: "unknown-body", message: "message body could not be decoded", index: i });
|
|
693
|
+
}
|
|
694
|
+
if (item.kind !== "ton-transfer" && !bounceable) {
|
|
695
|
+
warnings.push({ code: "non-bounceable-contract-call", message: "contract call to a non-bounceable address: TON is lost if the call fails", index: i });
|
|
696
|
+
}
|
|
697
|
+
return item;
|
|
698
|
+
}
|
|
699
|
+
var hex = (n) => `0x${n.toString(16).padStart(8, "0")}`;
|
|
700
|
+
function describeMessageRelaxed(i, m, warnings, ctx = {}) {
|
|
701
|
+
if (m.info.type !== "internal") {
|
|
702
|
+
warnings.push({ code: "unknown-body", message: "not an internal message", index: i });
|
|
703
|
+
return { kind: "contract-call", to: "", value: 0n, bounceable: false, hasStateInit: false, bodyBits: 0 };
|
|
704
|
+
}
|
|
705
|
+
const dest = m.info.dest;
|
|
706
|
+
const to = { to: dest.toString({ bounceable: m.info.bounce }), bounceable: m.info.bounce, workchain: dest.workChain };
|
|
707
|
+
const body = m.body.bits.length === 0 && m.body.refs.length === 0 ? void 0 : m.body;
|
|
708
|
+
return describeOne(i, to, m.info.value.coins, body, !!m.init, m.info.bounce, warnings, ctx);
|
|
709
|
+
}
|
|
710
|
+
function describeTransaction(tx, ctx = {}) {
|
|
711
|
+
const warnings = [];
|
|
712
|
+
const items = [];
|
|
713
|
+
let external2;
|
|
714
|
+
if (tx instanceof import_core6.Cell) {
|
|
715
|
+
const parsed = parseV5R1Body(tx);
|
|
716
|
+
if (!parsed) {
|
|
717
|
+
warnings.push({ code: "unknown-body", message: "not a V5R1 signed-external body" });
|
|
718
|
+
return { kind: "contract-call", items, warnings };
|
|
719
|
+
}
|
|
720
|
+
external2 = { seqno: parsed.seqno, validUntil: parsed.validUntil, walletId: parsed.walletId, signed: parsed.signed };
|
|
721
|
+
parsed.messages.forEach((m, i) => items.push(describeMessageRelaxed(i, m.message, warnings, ctx)));
|
|
722
|
+
} else {
|
|
723
|
+
const list = Array.isArray(tx) ? tx : [tx];
|
|
724
|
+
list.forEach((t, i) => {
|
|
725
|
+
let to;
|
|
726
|
+
try {
|
|
727
|
+
to = parseTo(t.to);
|
|
728
|
+
} catch {
|
|
729
|
+
warnings.push({ code: "unknown-body", message: `invalid address: ${t.to}`, index: i });
|
|
730
|
+
items.push({ kind: "contract-call", to: t.to, value: 0n, bounceable: false, hasStateInit: false, bodyBits: 0 });
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
let body;
|
|
734
|
+
try {
|
|
735
|
+
body = bodyOf(t);
|
|
736
|
+
} catch {
|
|
737
|
+
warnings.push({ code: "unknown-body", message: "bodyBoc is not a valid BoC", index: i });
|
|
738
|
+
}
|
|
739
|
+
items.push(describeOne(i, to, BigInt(t.value), body, t.stateInit !== void 0, t.bounceable, warnings, ctx));
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
const kinds = new Set(items.map((x) => x.kind));
|
|
743
|
+
const kind = kinds.size === 1 ? items[0].kind : kinds.size === 0 ? "contract-call" : "mixed";
|
|
744
|
+
return { kind, items, warnings, ...external2 ? { external: external2 } : {} };
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// src/read-only-account.ts
|
|
748
|
+
var import_core7 = require("@anywalletkit/core");
|
|
749
|
+
var import_ed25519 = require("@noble/curves/ed25519.js");
|
|
750
|
+
var import_core8 = require("@ton/core");
|
|
751
|
+
var JETTON_TRANSFER_OP2 = 260734629;
|
|
752
|
+
var DEFAULT_JETTON_ATTACHED = 50000000n;
|
|
753
|
+
var DEFAULT_INDEXER = "https://toncenter.com/api/v3";
|
|
754
|
+
var DEFAULT_VALID_SECONDS = 60;
|
|
755
|
+
var MAX_VALID_SECONDS = 600;
|
|
756
|
+
var SEND_MODE = DEFAULT_SEND_MODE;
|
|
757
|
+
var utf8 = (s) => typeof s === "string" ? new TextEncoder().encode(s) : s;
|
|
758
|
+
var hexToBytes = (h) => Uint8Array.from(h.match(/.{2}/g) ?? [], (b) => parseInt(b, 16));
|
|
759
|
+
var big = (v) => BigInt(v);
|
|
760
|
+
var WalletAccountReadOnlyTon = class {
|
|
761
|
+
chain;
|
|
762
|
+
/** Ed25519 public key the wallet is derived from. */
|
|
763
|
+
publicKey;
|
|
764
|
+
/** User-friendly address. */
|
|
765
|
+
address;
|
|
766
|
+
_config;
|
|
767
|
+
_walletId;
|
|
768
|
+
_init;
|
|
769
|
+
_address;
|
|
770
|
+
_rpc;
|
|
771
|
+
constructor(publicKey, config = {}) {
|
|
772
|
+
this.publicKey = typeof publicKey === "string" ? hexToBytes(publicKey) : publicKey;
|
|
773
|
+
this._config = config;
|
|
774
|
+
const networkGlobalId = config.network === "testnet" ? TESTNET_GLOBAL_ID : MAINNET_GLOBAL_ID;
|
|
775
|
+
if (config.walletId?.networkGlobalId !== void 0 && config.walletId.networkGlobalId !== networkGlobalId) {
|
|
776
|
+
throw new UnsafeTransactionError("network-mismatch", `walletId.networkGlobalId ${config.walletId.networkGlobalId} contradicts network '${config.network ?? "mainnet"}' (expected ${networkGlobalId}): externals signed this way would be valid on the other network.`);
|
|
777
|
+
}
|
|
778
|
+
this._walletId = v5r1WalletId({ networkGlobalId, ...config.walletId?.subwalletNumber !== void 0 ? { subwalletNumber: config.walletId.subwalletNumber } : {} });
|
|
779
|
+
this._init = v5r1StateInit(this.publicKey, this._walletId);
|
|
780
|
+
this._address = v5r1Address(this.publicKey, { walletId: this._walletId });
|
|
781
|
+
this.address = this._address.toString({ bounceable: false, testOnly: config.network === "testnet" });
|
|
782
|
+
this.chain = CAIP2[config.network ?? "mainnet"];
|
|
783
|
+
const rpc = createTonRpc(config);
|
|
784
|
+
this._rpc = rpc ? new TonCenter(rpc) : void 0;
|
|
785
|
+
}
|
|
786
|
+
requireRpc(what) {
|
|
787
|
+
if (!this._rpc) throw new import_core7.NetworkError(`The wallet must be connected to a provider (toncenter) to ${what}.`);
|
|
788
|
+
return this._rpc;
|
|
789
|
+
}
|
|
790
|
+
/** The user-friendly address. */
|
|
791
|
+
async getAddress() {
|
|
792
|
+
return this.address;
|
|
793
|
+
}
|
|
794
|
+
/** Native balance in nanotons. */
|
|
795
|
+
async getBalance() {
|
|
796
|
+
return (await this.requireRpc("get balances").getAddressInformation(this._address)).balance;
|
|
797
|
+
}
|
|
798
|
+
/** Current seqno — 0 until the wallet contract is deployed. Always fetched fresh. */
|
|
799
|
+
async getSeqno() {
|
|
800
|
+
const rpc = this.requireRpc("read the seqno");
|
|
801
|
+
const state = await rpc.getAddressInformation(this._address);
|
|
802
|
+
if (state.state !== "active") return 0;
|
|
803
|
+
return (await rpc.runGetMethodOk(this._address, "seqno")).stack.readNumber();
|
|
804
|
+
}
|
|
805
|
+
/** Resolve this wallet's jetton wallet for a jetton master. */
|
|
806
|
+
async getJettonWalletAddress(token) {
|
|
807
|
+
const rpc = this.requireRpc("resolve jetton wallets");
|
|
808
|
+
const r = await rpc.runGetMethodOk(import_core8.Address.parse(token), "get_wallet_address", [
|
|
809
|
+
{ type: "slice", cell: (0, import_core8.beginCell)().storeAddress(this._address).endCell() }
|
|
810
|
+
]);
|
|
811
|
+
return r.stack.readAddress();
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Read a jetton wallet and prove it is ours for `token`: `get_wallet_data`
|
|
815
|
+
* must name this wallet as owner and `token` as master. The master's own
|
|
816
|
+
* `get_wallet_address` answer is not trusted on its own — a fake master could
|
|
817
|
+
* point at our real wallet of another jetton (e.g. USDT).
|
|
818
|
+
*/
|
|
819
|
+
async jettonWalletData(token, jw) {
|
|
820
|
+
const stack = (await this.requireRpc("read jetton wallets").runGetMethodOk(jw, "get_wallet_data")).stack;
|
|
821
|
+
const balance = stack.readBigNumber();
|
|
822
|
+
const owner = stack.readAddress();
|
|
823
|
+
const master = stack.readAddress();
|
|
824
|
+
if (!owner.equals(this._address) || !master.equals(import_core8.Address.parse(token))) {
|
|
825
|
+
throw new UnsafeTransactionError("jetton-wallet", `The jetton wallet ${jw.toString()} does not belong to this wallet for ${token}.`);
|
|
826
|
+
}
|
|
827
|
+
return balance;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Jetton balance in base units, read from this wallet's jetton wallet; 0 when it does not exist.
|
|
831
|
+
*/
|
|
832
|
+
async getTokenBalance(token) {
|
|
833
|
+
const rpc = this.requireRpc("get token balances");
|
|
834
|
+
const jw = await this.getJettonWalletAddress(token);
|
|
835
|
+
if ((await rpc.getAddressInformation(jw)).state !== "active") return 0n;
|
|
836
|
+
return this.jettonWalletData(token, jw);
|
|
837
|
+
}
|
|
838
|
+
// ── Message building ──────────────────────────────────────────────────────
|
|
839
|
+
stateInitOf(src) {
|
|
840
|
+
if (src === void 0) return void 0;
|
|
841
|
+
if (typeof src === "string") return (0, import_core8.loadStateInit)(import_core8.Cell.fromBase64(src).beginParse());
|
|
842
|
+
if (src instanceof import_core8.Cell) return (0, import_core8.loadStateInit)(src.beginParse());
|
|
843
|
+
return src;
|
|
844
|
+
}
|
|
845
|
+
transactionMessage(tx) {
|
|
846
|
+
const value = big(tx.value);
|
|
847
|
+
if (value <= 0n) throw new UnsafeTransactionError("zero-value", "TON message value must be positive.");
|
|
848
|
+
let address;
|
|
849
|
+
let bounceable = true;
|
|
850
|
+
let testOnly;
|
|
851
|
+
if (import_core8.Address.isFriendly(tx.to)) {
|
|
852
|
+
const f = import_core8.Address.parseFriendly(tx.to);
|
|
853
|
+
address = f.address;
|
|
854
|
+
bounceable = f.isBounceable;
|
|
855
|
+
testOnly = f.isTestOnly;
|
|
856
|
+
} else {
|
|
857
|
+
address = import_core8.Address.parse(tx.to);
|
|
858
|
+
}
|
|
859
|
+
if (address.workChain !== 0 && address.workChain !== -1) throw new UnsafeTransactionError("exotic-workchain", `Refusing to send to workchain ${address.workChain}.`);
|
|
860
|
+
const testnet = this._config.network === "testnet";
|
|
861
|
+
if (testOnly !== void 0 && testOnly !== testnet) {
|
|
862
|
+
throw new UnsafeTransactionError("network-mismatch", testOnly ? "Test-only address on a mainnet wallet." : "Mainnet address on a testnet wallet.");
|
|
863
|
+
}
|
|
864
|
+
const body = tx.body instanceof import_core8.Cell ? tx.body : typeof tx.body === "string" ? tx.body === "" ? void 0 : commentCell(tx.body) : tx.bodyBoc ? import_core8.Cell.fromBase64(tx.bodyBoc) : void 0;
|
|
865
|
+
const init = this.stateInitOf(tx.stateInit);
|
|
866
|
+
const message = (0, import_core8.internal)({ to: address, value, bounce: tx.bounceable ?? bounceable, ...body ? { body } : {}, ...init ? { init } : {} });
|
|
867
|
+
return { message, ...tx.sendMode !== void 0 ? { mode: tx.sendMode } : {} };
|
|
868
|
+
}
|
|
869
|
+
async jettonTransferMessage(o) {
|
|
870
|
+
const amount = big(o.amount);
|
|
871
|
+
if (amount <= 0n) throw new UnsafeTransactionError("zero-value", "Jetton amount must be positive.");
|
|
872
|
+
const recipient = import_core8.Address.parse(o.recipient);
|
|
873
|
+
const jw = await this.getJettonWalletAddress(o.token);
|
|
874
|
+
await this.jettonWalletData(o.token, jw);
|
|
875
|
+
const forwardTonAmount = o.forwardTonAmount !== void 0 ? big(o.forwardTonAmount) : 1n;
|
|
876
|
+
if (o.memo !== void 0 && o.forwardPayload !== void 0) throw new UnsafeTransactionError("memo-and-payload", "Pass either memo or forwardPayload, not both: a jetton transfer carries one forward payload.");
|
|
877
|
+
const payload = o.forwardPayload ?? o.memo;
|
|
878
|
+
const forwardPayload = payload === void 0 ? null : typeof payload === "string" ? commentCell(payload) : payload;
|
|
879
|
+
const body = (0, import_core8.beginCell)().storeUint(JETTON_TRANSFER_OP2, 32).storeUint(o.queryId !== void 0 ? BigInt(o.queryId) : randomQueryId(), 64).storeCoins(amount).storeAddress(recipient).storeAddress(o.responseDestination ? import_core8.Address.parse(o.responseDestination) : this._address).storeMaybeRef(o.customPayload ?? null).storeCoins(forwardTonAmount).storeMaybeRef(forwardPayload).endCell();
|
|
880
|
+
const attached = o.attachedValue !== void 0 ? big(o.attachedValue) : (this._config.jettonAttachedValue !== void 0 ? big(this._config.jettonAttachedValue) : DEFAULT_JETTON_ATTACHED) + forwardTonAmount;
|
|
881
|
+
return { message: (0, import_core8.internal)({ to: jw, value: attached, bounce: true, body }) };
|
|
882
|
+
}
|
|
883
|
+
toMessages(tx) {
|
|
884
|
+
const list = Array.isArray(tx) ? tx : [tx];
|
|
885
|
+
if (list.length === 0) throw new UnsafeTransactionError("empty", "Nothing to send.");
|
|
886
|
+
return list.map((t) => this.transactionMessage(t));
|
|
887
|
+
}
|
|
888
|
+
validUntil() {
|
|
889
|
+
const seconds = this._config.validUntilSeconds ?? DEFAULT_VALID_SECONDS;
|
|
890
|
+
if (!Number.isInteger(seconds) || seconds <= 0 || seconds > MAX_VALID_SECONDS) {
|
|
891
|
+
throw new UnsafeTransactionError("valid-until", `validUntilSeconds must be an integer between 1 and ${MAX_VALID_SECONDS}.`);
|
|
892
|
+
}
|
|
893
|
+
return Math.floor(Date.now() / 1e3) + seconds;
|
|
894
|
+
}
|
|
895
|
+
/** The cell to sign for `messages` at the CURRENT seqno (fetched fresh every time). */
|
|
896
|
+
async signingCell(messages) {
|
|
897
|
+
const seqno = await this.getSeqno();
|
|
898
|
+
const validUntil = this.validUntil();
|
|
899
|
+
return { signing: v5r1SigningMessage({ walletId: this._walletId, validUntil, seqno, messages }), seqno, validUntil };
|
|
900
|
+
}
|
|
901
|
+
// ── Fees / preflight ──────────────────────────────────────────────────────
|
|
902
|
+
/** StateInit to attach to an external when the wallet is not deployed yet. */
|
|
903
|
+
async initIfNeeded() {
|
|
904
|
+
const state = await this.requireRpc("read the account state").getAddressInformation(this._address);
|
|
905
|
+
return state.state === "active" ? void 0 : this._init;
|
|
906
|
+
}
|
|
907
|
+
/** in_fwd + storage + gas + fwd from toncenter's estimateFee (signature ignored). */
|
|
908
|
+
async estimateFees(body) {
|
|
909
|
+
const rpc = this.requireRpc("quote transfers");
|
|
910
|
+
const init = await this.initIfNeeded();
|
|
911
|
+
return rpc.estimateFee(this._address, { body, ...init ? { init } : {}, ignoreSignature: true });
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Preflight an external body: fees via estimateFee and, unless
|
|
915
|
+
* `config.emulate === false`, a trace emulation (tonapi by default, see
|
|
916
|
+
* {@link TonConfig.emulate}) whose compute/action phases — and the first-hop
|
|
917
|
+
* internal messages — decide `ok`. Never throws for a failing emulation,
|
|
918
|
+
* only for transport problems or a malformed emulator response.
|
|
919
|
+
*/
|
|
920
|
+
async simulateBody(body) {
|
|
921
|
+
const fees = await this.estimateFees(body);
|
|
922
|
+
const init = await this.initIfNeeded();
|
|
923
|
+
const outcome = await runEmulation(this._config, v5r1ExternalMessage(this._address, body, init));
|
|
924
|
+
if (!outcome) return { ok: true, fees, emulated: false, source: "estimate-only", warnings: [] };
|
|
925
|
+
const common = {
|
|
926
|
+
fees,
|
|
927
|
+
emulated: true,
|
|
928
|
+
source: outcome.source,
|
|
929
|
+
warnings: outcome.warnings,
|
|
930
|
+
...outcome.exitCode !== void 0 ? { exitCode: outcome.exitCode } : {},
|
|
931
|
+
...outcome.risk ? { risk: outcome.risk } : {}
|
|
932
|
+
};
|
|
933
|
+
if (outcome.ok) return { ok: true, ...common, ...outcome.actionSuccess !== void 0 ? { actionSuccess: outcome.actionSuccess } : {} };
|
|
934
|
+
return { ok: false, ...common, reason: outcome.reason ?? "emulation failed", ...outcome.aborted !== void 0 ? { aborted: outcome.aborted } : {} };
|
|
935
|
+
}
|
|
936
|
+
/** Preflight a {@link TonSendable} (unsigned body, current seqno). */
|
|
937
|
+
async simulateTransaction(tx) {
|
|
938
|
+
const body = tx instanceof import_core8.Cell ? tx : v5r1PackUnsigned((await this.signingCell(this.toMessages(tx))).signing);
|
|
939
|
+
return this.simulateBody(body);
|
|
940
|
+
}
|
|
941
|
+
/** Estimated fee of a transaction, in nanotons. */
|
|
942
|
+
async quoteSendTransaction(tx) {
|
|
943
|
+
const body = tx instanceof import_core8.Cell ? tx : v5r1PackUnsigned((await this.signingCell(this.toMessages(tx))).signing);
|
|
944
|
+
return { fee: (await this.estimateFees(body)).total };
|
|
945
|
+
}
|
|
946
|
+
/** Estimated fee of a transfer, in nanotons. */
|
|
947
|
+
async quoteTransfer(o) {
|
|
948
|
+
const message = o.token ? await this.jettonTransferMessage(o) : this.transactionMessage({ to: o.recipient, value: o.amount, ...o.memo !== void 0 ? { body: o.memo } : {} });
|
|
949
|
+
const { signing } = await this.signingCell([message]);
|
|
950
|
+
return { fee: (await this.estimateFees(v5r1PackUnsigned(signing))).total };
|
|
951
|
+
}
|
|
952
|
+
/** Offline description of what `tx` does — see {@link describeTransaction}. */
|
|
953
|
+
describe(tx) {
|
|
954
|
+
return describeTransaction(tx, { network: this._config.network ?? "mainnet" });
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Receipt for a body hash returned by sendTransaction: toncenter v3 maps the
|
|
958
|
+
* message body hash to the account transaction, which is then fetched.
|
|
959
|
+
*/
|
|
960
|
+
async getTransactionReceipt(hash) {
|
|
961
|
+
const rpc = this.requireRpc("fetch transaction receipts");
|
|
962
|
+
const base = this._config.indexerUrl ?? DEFAULT_INDEXER;
|
|
963
|
+
const doFetch = this._config.fetch ?? globalThis.fetch;
|
|
964
|
+
const res = await doFetch(`${base}/transactionsByMessage?${new URLSearchParams({ body_hash: hash, limit: "1" })}`, { signal: AbortSignal.timeout(this._config.timeoutMs ?? 15e3) });
|
|
965
|
+
if (!res.ok) throw new import_core7.NetworkError(`toncenter v3 ${res.status}`, { provider: base });
|
|
966
|
+
const { transactions } = await res.json();
|
|
967
|
+
const found = transactions?.[0];
|
|
968
|
+
if (!found) return void 0;
|
|
969
|
+
const account = import_core8.Address.parse(found.account);
|
|
970
|
+
let tx;
|
|
971
|
+
try {
|
|
972
|
+
[tx] = await rpc.getTransactions(account, { limit: 1, hash: found.hash });
|
|
973
|
+
} catch {
|
|
974
|
+
[tx] = await rpc.getTransactions(account, { limit: 1, lt: found.lt, hash: found.hash, archival: true });
|
|
975
|
+
}
|
|
976
|
+
if (!tx) return void 0;
|
|
977
|
+
const d = tx.description;
|
|
978
|
+
return { hash, status: d.type === "generic" && !d.aborted ? 1 : 0, blockNumber: tx.lt, transactionHash: tx.hash().toString("base64"), totalFees: tx.totalFees.coins, raw: tx };
|
|
979
|
+
}
|
|
980
|
+
/** ed25519 over the raw message bytes; `signature` hex. */
|
|
981
|
+
async verify(message, signature) {
|
|
982
|
+
try {
|
|
983
|
+
return import_ed25519.ed25519.verify(hexToBytes(signature), utf8(message), this.publicKey);
|
|
984
|
+
} catch {
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
function randomQueryId() {
|
|
990
|
+
const r = new Uint32Array(2);
|
|
991
|
+
globalThis.crypto.getRandomValues(r);
|
|
992
|
+
return BigInt(r[0]) << 32n | BigInt(r[1]);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// src/account.ts
|
|
996
|
+
var import_core9 = require("@anywalletkit/core");
|
|
997
|
+
var import_core10 = require("@ton/core");
|
|
998
|
+
var toHex = (u8) => Array.from(u8, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
999
|
+
var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon {
|
|
1000
|
+
/** seqno and fees come from the network before signing. */
|
|
1001
|
+
needsNetwork = true;
|
|
1002
|
+
/** Derivation path, `ton-mnemonic`, or `external`. */
|
|
1003
|
+
path;
|
|
1004
|
+
/** The signer that holds the key. */
|
|
1005
|
+
signer;
|
|
1006
|
+
_readOnly;
|
|
1007
|
+
/** `publicKey` is the signer's 32-byte ed25519 key; the V5R1 address derives from it synchronously. */
|
|
1008
|
+
constructor(signer, publicKey, path, config = {}) {
|
|
1009
|
+
super(publicKey, config);
|
|
1010
|
+
this.signer = (0, import_core9.requireCurve)(signer, "ed25519");
|
|
1011
|
+
this.path = path;
|
|
1012
|
+
}
|
|
1013
|
+
/** Back-compat: wrap a raw derived key in a {@link LocalSigner} (the account takes ownership; it cannot derive). */
|
|
1014
|
+
static fromPrivateKey(privateKey, publicKey, path, config = {}) {
|
|
1015
|
+
return new _WalletAccountTon(import_core9.LocalSigner.fromPrivateKey("ed25519", privateKey, publicKey, path), publicKey, path, config);
|
|
1016
|
+
}
|
|
1017
|
+
/** Account index for the standard m/44'/607'/i' path. */
|
|
1018
|
+
get index() {
|
|
1019
|
+
const m = /^m\/44'\/607'\/(\d+)'$/.exec(this.path);
|
|
1020
|
+
return m ? Number(m[1]) : void 0;
|
|
1021
|
+
}
|
|
1022
|
+
/** Hex ed25519 signature over the raw message bytes (ton_proof and the like). */
|
|
1023
|
+
async sign(message) {
|
|
1024
|
+
const bytes = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
1025
|
+
return toHex(await this.signer.sign(bytes, { scheme: "ed25519", context: "ton-message" }));
|
|
1026
|
+
}
|
|
1027
|
+
checkMaxFee(fee, limit, op) {
|
|
1028
|
+
if (limit !== void 0 && fee > BigInt(limit)) throw new Error(`Exceeded maximum fee cost for ${op} operation.`);
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Checks → seqno → preflight → signed body. The preflight runs on the
|
|
1032
|
+
* zero-signed body (ignore_chksig), so nothing is signed before it passes.
|
|
1033
|
+
*/
|
|
1034
|
+
async prepare(messages, limit, op) {
|
|
1035
|
+
const want = messages.reduce((sum, m) => sum + (m.message.info.type === "internal" ? m.message.info.value.coins : 0n), 0n);
|
|
1036
|
+
if (want > 0n) {
|
|
1037
|
+
const balance = await this.getBalance();
|
|
1038
|
+
if (balance < want) throw new UnsafeTransactionError("insufficient-balance", `Insufficient TON balance: the messages carry ${want} nanotons, the wallet holds ${balance} (fees come on top).`);
|
|
1039
|
+
}
|
|
1040
|
+
const { signing } = await this.signingCell(messages);
|
|
1041
|
+
const fee = await this.preflight(v5r1PackUnsigned(signing), limit, op, want);
|
|
1042
|
+
const signature = await this.signer.sign(new Uint8Array(signing.hash()), { scheme: "ed25519", context: "ton-external" });
|
|
1043
|
+
return { body: v5r1PackSigned(signing, signature), fee };
|
|
1044
|
+
}
|
|
1045
|
+
async preflight(body, limit, op, expectedTonOut) {
|
|
1046
|
+
if (this._config.simulate === false) {
|
|
1047
|
+
if (limit === void 0) return 0n;
|
|
1048
|
+
const fee = (await this.estimateFees(body)).total;
|
|
1049
|
+
this.checkMaxFee(fee, limit, op);
|
|
1050
|
+
return fee;
|
|
1051
|
+
}
|
|
1052
|
+
const sim = await this.simulateBody(body);
|
|
1053
|
+
if (!sim.ok) throw new SimulationError(sim.reason, { ...sim.exitCode !== void 0 ? { exitCode: sim.exitCode } : {}, ...sim.aborted !== void 0 ? { aborted: sim.aborted } : {} });
|
|
1054
|
+
if (sim.risk?.transferAllRemainingBalance && this._config.allowDrainingTransactions !== true) {
|
|
1055
|
+
throw new UnsafeTransactionError("drain", "Refusing a message that carries the whole remaining balance (send mode 128); set allowDrainingTransactions to override.");
|
|
1056
|
+
}
|
|
1057
|
+
if (expectedTonOut !== void 0 && sim.risk && sim.risk.ton < expectedTonOut) {
|
|
1058
|
+
throw new SimulationError(`The emulation shows only ${sim.risk.ton} nanotons leaving the wallet for messages carrying ${expectedTonOut}: a message would be dropped (insufficient balance for value + fees).`);
|
|
1059
|
+
}
|
|
1060
|
+
this.checkMaxFee(sim.fees.total, limit, op);
|
|
1061
|
+
return sim.fees.total;
|
|
1062
|
+
}
|
|
1063
|
+
/** Sign without broadcasting. Returns the external-message BODY as base64 BoC (pass it back to sendTransaction as a Cell). */
|
|
1064
|
+
async signTransaction(tx) {
|
|
1065
|
+
if (tx instanceof import_core10.Cell) {
|
|
1066
|
+
await this.preflight(tx, this._config.transactionMaxFee, "transaction");
|
|
1067
|
+
return tx.toBoc().toString("base64");
|
|
1068
|
+
}
|
|
1069
|
+
const { body } = await this.prepare(this.toMessages(tx), this._config.transactionMaxFee, "transaction");
|
|
1070
|
+
return body.toBoc().toString("base64");
|
|
1071
|
+
}
|
|
1072
|
+
async broadcast(body) {
|
|
1073
|
+
const rpc = this.requireRpc("send transactions");
|
|
1074
|
+
const init = await this.initIfNeeded();
|
|
1075
|
+
await rpc.sendBoc(v5r1ExternalMessage(this._address, body, init));
|
|
1076
|
+
return body.hash().toString("hex");
|
|
1077
|
+
}
|
|
1078
|
+
/** Sign and broadcast; `hash` is the body hash (hex) — resolve it with getTransactionReceipt. */
|
|
1079
|
+
async sendTransaction(tx) {
|
|
1080
|
+
this.requireRpc("send transactions");
|
|
1081
|
+
const { body, fee } = tx instanceof import_core10.Cell ? { body: tx, fee: await this.preflight(tx, this._config.transactionMaxFee, "transaction") } : await this.prepare(this.toMessages(tx), this._config.transactionMaxFee, "transaction");
|
|
1082
|
+
return { hash: await this.broadcast(body), fee };
|
|
1083
|
+
}
|
|
1084
|
+
/** TON (no `token`) or jetton transfer. */
|
|
1085
|
+
async transfer(o) {
|
|
1086
|
+
this.requireRpc("transfer tokens");
|
|
1087
|
+
const message = o.token ? await this.jettonTransferMessage(o) : this.transactionMessage({ to: o.recipient, value: o.amount, ...o.memo !== void 0 ? { body: o.memo } : {} });
|
|
1088
|
+
const { body, fee } = await this.prepare([message], this._config.transferMaxFee, "transfer");
|
|
1089
|
+
return { hash: await this.broadcast(body), fee };
|
|
1090
|
+
}
|
|
1091
|
+
/** The same wallet as a {@link WalletAccountReadOnlyTon}. */
|
|
1092
|
+
toReadOnlyAccount() {
|
|
1093
|
+
this._readOnly ??= new WalletAccountReadOnlyTon(this.publicKey, this._config);
|
|
1094
|
+
return this._readOnly;
|
|
1095
|
+
}
|
|
1096
|
+
/** Disposes the signer: a local key is zeroized, an external one is released. */
|
|
1097
|
+
dispose() {
|
|
1098
|
+
this.signer.dispose();
|
|
1099
|
+
}
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
// src/manager.ts
|
|
1103
|
+
var import_core11 = require("@anywalletkit/core");
|
|
1104
|
+
var import_crypto = require("@ton/crypto");
|
|
1105
|
+
var TON_BASE_PATH = "m/44'/607'";
|
|
1106
|
+
var TON_MNEMONIC_PATH = "ton-mnemonic";
|
|
1107
|
+
var WalletManagerTon = class _WalletManagerTon extends import_core11.WalletManager {
|
|
1108
|
+
constructor(seedOrSigner, config = {}) {
|
|
1109
|
+
super(seedOrSigner, config);
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Import a TON-native mnemonic (24 words, Tonkeeper / Tonhub / MyTonWallet
|
|
1113
|
+
* default): PBKDF2 per @ton/crypto, one ed25519 key, one V5R1 account at
|
|
1114
|
+
* path `ton-mnemonic` with the address those wallets show.
|
|
1115
|
+
*/
|
|
1116
|
+
static async fromTonMnemonic(words, config = {}) {
|
|
1117
|
+
const list = (Array.isArray(words) ? words : words.trim().split(/\s+/)).map((w) => w.toLowerCase());
|
|
1118
|
+
if (list.length !== 24) throw new Error("A TON mnemonic has 24 words.");
|
|
1119
|
+
const pair = await (0, import_crypto.mnemonicToPrivateKey)(list);
|
|
1120
|
+
const privateKey = new Uint8Array(pair.secretKey.subarray(0, 32));
|
|
1121
|
+
const publicKey = new Uint8Array(pair.publicKey);
|
|
1122
|
+
(0, import_core11.zeroize)(new Uint8Array(pair.secretKey.buffer, pair.secretKey.byteOffset, pair.secretKey.byteLength));
|
|
1123
|
+
return new _WalletManagerTon(import_core11.LocalSigner.fromPrivateKey("ed25519", privateKey, publicKey, TON_MNEMONIC_PATH), config);
|
|
1124
|
+
}
|
|
1125
|
+
async getAccount(index = 0) {
|
|
1126
|
+
const external2 = this.externalSigner();
|
|
1127
|
+
if (external2 && !((0, import_core11.isSeedSigner)(external2) && external2.path === TON_BASE_PATH)) {
|
|
1128
|
+
if (index !== 0) throw new import_core11.SignerError(`This signer yields a single account (index 0); ${index} is not derivable from it.`);
|
|
1129
|
+
return this.fromSigner(external2, this.singlePath(external2));
|
|
1130
|
+
}
|
|
1131
|
+
return this.getAccountByPath(`${TON_BASE_PATH}/${index}'`);
|
|
1132
|
+
}
|
|
1133
|
+
async getAccountByPath(path) {
|
|
1134
|
+
const external2 = this.externalSigner();
|
|
1135
|
+
if (external2) {
|
|
1136
|
+
const single = this.singlePath(external2);
|
|
1137
|
+
const full2 = path === single || path.startsWith("m/") ? path : `${TON_BASE_PATH}/${path}`;
|
|
1138
|
+
return this.fromSigner(external2, full2);
|
|
1139
|
+
}
|
|
1140
|
+
const full = path.startsWith("m/") ? path : `${TON_BASE_PATH}/${path}`;
|
|
1141
|
+
const cached = this._accounts.get(full);
|
|
1142
|
+
if (cached) return cached;
|
|
1143
|
+
const signer = import_core11.LocalSigner.leafFromSeed(this.seedBytes(), "ed25519", full);
|
|
1144
|
+
const account = new WalletAccountTon(signer, signer.publicKey, full, this._config);
|
|
1145
|
+
this._accounts.set(full, account);
|
|
1146
|
+
return account;
|
|
1147
|
+
}
|
|
1148
|
+
/** The one path a non-derivable signer answers to: its own, or 'external'. */
|
|
1149
|
+
singlePath(signer) {
|
|
1150
|
+
return (0, import_core11.isSeedSigner)(signer) ? signer.path : "external";
|
|
1151
|
+
}
|
|
1152
|
+
/** Resolve `path` against an injected signer: the signer itself, or a child when it can derive there. */
|
|
1153
|
+
async fromSigner(signer, path) {
|
|
1154
|
+
this.requireSigner("ed25519");
|
|
1155
|
+
let leaf = signer;
|
|
1156
|
+
let key = this.singlePath(signer);
|
|
1157
|
+
if ((0, import_core11.isSeedSigner)(signer)) {
|
|
1158
|
+
if (path === signer.path) key = path;
|
|
1159
|
+
else if (path.startsWith(`${signer.path}/`)) {
|
|
1160
|
+
leaf = await (signer.deriveLeaf ?? signer.derive).call(signer, path.slice(signer.path.length + 1));
|
|
1161
|
+
key = path;
|
|
1162
|
+
} else throw new import_core11.SignerError(`This signer yields a single account at path '${signer.path}'; it cannot provide ${path}.`);
|
|
1163
|
+
} else if (path !== `${TON_BASE_PATH}/0'` && path !== "external") {
|
|
1164
|
+
throw new import_core11.SignerError(`An external signer yields a single account; ${path} is not derivable from it.`);
|
|
1165
|
+
}
|
|
1166
|
+
const cached = this._accounts.get(key);
|
|
1167
|
+
if (cached) return cached;
|
|
1168
|
+
const account = new WalletAccountTon(leaf, await leaf.getPublicKey(), key, this._config);
|
|
1169
|
+
this._accounts.set(key, account);
|
|
1170
|
+
return account;
|
|
1171
|
+
}
|
|
1172
|
+
/** Watch-only account for a public key (hex or bytes) — TON addresses derive from the key, not the other way round. */
|
|
1173
|
+
getReadOnlyAccount(publicKey) {
|
|
1174
|
+
return new WalletAccountReadOnlyTon(publicKey, this._config);
|
|
1175
|
+
}
|
|
1176
|
+
/** Gas price from config param 21 via tonapi (normal = fast there; slow = ×0.8 here). */
|
|
1177
|
+
async getFeeRates() {
|
|
1178
|
+
const doFetch = this._config.fetch ?? globalThis.fetch;
|
|
1179
|
+
const res = await doFetch(`${this._config.tonApiUrl ?? DEFAULT_TONAPI}/blockchain/config/raw`, { signal: AbortSignal.timeout(this._config.timeoutMs ?? 15e3) });
|
|
1180
|
+
const json = await res.json();
|
|
1181
|
+
const normal = BigInt(Math.round(json.config.config_param21.gas_limits_prices.gas_flat_pfx.other.gas_prices_ext.gas_price / 65536));
|
|
1182
|
+
return { slow: normal * 80n / 100n, normal, fast: normal };
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
// src/history.ts
|
|
1187
|
+
var import_core12 = require("@ton/core");
|
|
1188
|
+
var import_buffer4 = require("buffer");
|
|
1189
|
+
var HistoryUnavailableError = class extends Error {
|
|
1190
|
+
chain;
|
|
1191
|
+
reason;
|
|
1192
|
+
attempts;
|
|
1193
|
+
constructor(chain, reason, attempts = []) {
|
|
1194
|
+
super(`History unavailable for ${chain}: ${reason}`);
|
|
1195
|
+
this.name = "HistoryUnavailableError";
|
|
1196
|
+
this.chain = chain;
|
|
1197
|
+
this.reason = reason;
|
|
1198
|
+
this.attempts = attempts;
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
var DEFAULT_LIMIT = 25;
|
|
1202
|
+
var MAX_LIMIT = 100;
|
|
1203
|
+
var DEFAULT_TIMEOUT_MS3 = 15e3;
|
|
1204
|
+
function encodeCursor(c) {
|
|
1205
|
+
return import_buffer4.Buffer.from(JSON.stringify(c), "utf8").toString("base64");
|
|
1206
|
+
}
|
|
1207
|
+
function decodeCursor(s) {
|
|
1208
|
+
let parsed;
|
|
1209
|
+
try {
|
|
1210
|
+
parsed = JSON.parse(import_buffer4.Buffer.from(s, "base64").toString("utf8"));
|
|
1211
|
+
} catch {
|
|
1212
|
+
throw new Error("@anywalletkit/wallet-ton: malformed history cursor");
|
|
1213
|
+
}
|
|
1214
|
+
const c = parsed;
|
|
1215
|
+
if (!c || c.v !== 1 || c.b !== "tonapi" && c.b !== "toncenter" || typeof c.lt !== "string" || !/^\d+$/.test(c.lt)) {
|
|
1216
|
+
throw new Error("@anywalletkit/wallet-ton: malformed history cursor");
|
|
1217
|
+
}
|
|
1218
|
+
return { v: 1, b: c.b, lt: c.lt };
|
|
1219
|
+
}
|
|
1220
|
+
var TokenBucket = class {
|
|
1221
|
+
constructor(rate, burst = Math.max(1, Math.ceil(rate))) {
|
|
1222
|
+
this.rate = rate;
|
|
1223
|
+
this.burst = burst;
|
|
1224
|
+
this.tokens = burst;
|
|
1225
|
+
}
|
|
1226
|
+
rate;
|
|
1227
|
+
burst;
|
|
1228
|
+
tokens;
|
|
1229
|
+
last = Date.now();
|
|
1230
|
+
chain = Promise.resolve();
|
|
1231
|
+
acquire() {
|
|
1232
|
+
const next = this.chain.then(() => this.take());
|
|
1233
|
+
this.chain = next.catch(() => void 0);
|
|
1234
|
+
return next;
|
|
1235
|
+
}
|
|
1236
|
+
refill() {
|
|
1237
|
+
const now = Date.now();
|
|
1238
|
+
this.tokens = Math.min(this.burst, this.tokens + (now - this.last) / 1e3 * this.rate);
|
|
1239
|
+
this.last = now;
|
|
1240
|
+
}
|
|
1241
|
+
async take() {
|
|
1242
|
+
this.refill();
|
|
1243
|
+
if (this.tokens >= 1) {
|
|
1244
|
+
this.tokens -= 1;
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
await new Promise((r) => setTimeout(r, Math.ceil((1 - this.tokens) / this.rate * 1e3)));
|
|
1248
|
+
this.refill();
|
|
1249
|
+
this.tokens = Math.max(0, this.tokens - 1);
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
function timeoutSignal(ms) {
|
|
1253
|
+
const ctor = globalThis.AbortSignal;
|
|
1254
|
+
return typeof ctor?.timeout === "function" ? ctor.timeout(ms) : void 0;
|
|
1255
|
+
}
|
|
1256
|
+
function rawTonAddress(a) {
|
|
1257
|
+
try {
|
|
1258
|
+
return import_core12.Address.parse(a).toRawString().toLowerCase();
|
|
1259
|
+
} catch {
|
|
1260
|
+
return a.toLowerCase();
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
var big2 = (v) => {
|
|
1264
|
+
if (typeof v === "bigint") return v;
|
|
1265
|
+
if (typeof v === "number" && Number.isFinite(v)) return BigInt(Math.trunc(v));
|
|
1266
|
+
if (typeof v === "string" && /^-?\d+$/.test(v)) return BigInt(v);
|
|
1267
|
+
return 0n;
|
|
1268
|
+
};
|
|
1269
|
+
var isHexOp = (s) => typeof s === "string" && /^0x[0-9a-f]{1,8}$/i.test(s);
|
|
1270
|
+
var jettonMeta = (j) => {
|
|
1271
|
+
const m = {};
|
|
1272
|
+
if (j?.symbol !== void 0) m.symbol = j.symbol;
|
|
1273
|
+
if (j?.decimals !== void 0) m.decimals = j.decimals;
|
|
1274
|
+
return m;
|
|
1275
|
+
};
|
|
1276
|
+
var addr = (a) => typeof a === "object" && a !== null && typeof a.address === "string" ? a.address.toLowerCase() : typeof a === "string" ? a.toLowerCase() : void 0;
|
|
1277
|
+
var isWallet = (a) => typeof a === "object" && a !== null && a.is_wallet === true;
|
|
1278
|
+
function mapActions(actions) {
|
|
1279
|
+
const out = { transfers: [] };
|
|
1280
|
+
const native = (from, to, amount) => {
|
|
1281
|
+
if (from === void 0 || to === void 0 || amount <= 0n) return;
|
|
1282
|
+
out.transfers.push({ token: null, from, to, amount });
|
|
1283
|
+
};
|
|
1284
|
+
const jetton = (j, from, to, amount) => {
|
|
1285
|
+
if (!j || from === void 0 || to === void 0 || amount <= 0n) return;
|
|
1286
|
+
out.transfers.push({ token: j.address.toLowerCase(), from, to, amount, standard: "jetton", ...jettonMeta(j) });
|
|
1287
|
+
};
|
|
1288
|
+
const setCall = (c) => {
|
|
1289
|
+
if (c && !out.call) out.call = c;
|
|
1290
|
+
};
|
|
1291
|
+
const init = (a) => {
|
|
1292
|
+
if (out.initiator === void 0 && a !== void 0) out.initiator = a;
|
|
1293
|
+
};
|
|
1294
|
+
for (const a of actions) {
|
|
1295
|
+
const b = a[a.type] ?? {};
|
|
1296
|
+
const ok = a.status === "ok";
|
|
1297
|
+
switch (a.type) {
|
|
1298
|
+
case "TonTransfer": {
|
|
1299
|
+
const from = addr(b.sender), to = addr(b.recipient);
|
|
1300
|
+
init(from);
|
|
1301
|
+
if (ok) native(from, to, big2(b.amount));
|
|
1302
|
+
break;
|
|
1303
|
+
}
|
|
1304
|
+
case "JettonTransfer": {
|
|
1305
|
+
const from = addr(b.sender) ?? addr(b.senders_wallet), to = addr(b.recipient) ?? addr(b.recipients_wallet);
|
|
1306
|
+
init(from);
|
|
1307
|
+
if (ok) jetton(b.jetton, from, to, big2(b.amount));
|
|
1308
|
+
if (to !== void 0 && b.recipient !== void 0 && !isWallet(b.recipient)) {
|
|
1309
|
+
const c = { to, method: "jetton_transfer" };
|
|
1310
|
+
const m = typeof b.comment === "string" ? /^Call:\s*(0x[0-9a-f]+)/i.exec(b.comment) : null;
|
|
1311
|
+
if (m) c.data = m[1].toLowerCase();
|
|
1312
|
+
setCall(c);
|
|
1313
|
+
}
|
|
1314
|
+
break;
|
|
1315
|
+
}
|
|
1316
|
+
case "JettonMint": {
|
|
1317
|
+
const j = b.jetton;
|
|
1318
|
+
if (ok) jetton(j, j?.address.toLowerCase(), addr(b.recipient) ?? addr(b.recipients_wallet), big2(b.amount));
|
|
1319
|
+
break;
|
|
1320
|
+
}
|
|
1321
|
+
case "JettonBurn": {
|
|
1322
|
+
const j = b.jetton;
|
|
1323
|
+
const from = addr(b.sender) ?? addr(b.senders_wallet);
|
|
1324
|
+
init(from);
|
|
1325
|
+
if (ok) jetton(j, from, j?.address.toLowerCase(), big2(b.amount));
|
|
1326
|
+
setCall(j ? { to: j.address.toLowerCase(), method: "jetton_burn" } : void 0);
|
|
1327
|
+
break;
|
|
1328
|
+
}
|
|
1329
|
+
case "JettonSwap": {
|
|
1330
|
+
const user = addr(b.user_wallet), router = addr(b.router);
|
|
1331
|
+
init(user);
|
|
1332
|
+
if (ok) {
|
|
1333
|
+
native(user, router, big2(b.ton_in));
|
|
1334
|
+
jetton(b.jetton_master_in, user, router, big2(b.amount_in));
|
|
1335
|
+
native(router, user, big2(b.ton_out));
|
|
1336
|
+
jetton(b.jetton_master_out, router, user, big2(b.amount_out));
|
|
1337
|
+
}
|
|
1338
|
+
if (router !== void 0) setCall({ to: router, method: "swap" });
|
|
1339
|
+
break;
|
|
1340
|
+
}
|
|
1341
|
+
case "SmartContractExec": {
|
|
1342
|
+
const executor = addr(b.executor), contract = addr(b.contract);
|
|
1343
|
+
init(executor);
|
|
1344
|
+
if (ok) native(executor, contract, big2(b.ton_attached));
|
|
1345
|
+
if (contract !== void 0) {
|
|
1346
|
+
const c = { to: contract };
|
|
1347
|
+
if (isHexOp(b.operation)) c.data = b.operation.toLowerCase();
|
|
1348
|
+
else if (typeof b.operation === "string" && b.operation) c.method = b.operation;
|
|
1349
|
+
setCall(c);
|
|
1350
|
+
}
|
|
1351
|
+
break;
|
|
1352
|
+
}
|
|
1353
|
+
case "DepositStake": {
|
|
1354
|
+
const staker = addr(b.staker), pool = addr(b.pool);
|
|
1355
|
+
init(staker);
|
|
1356
|
+
if (ok) native(staker, pool, big2(b.amount));
|
|
1357
|
+
if (pool !== void 0) setCall({ to: pool, method: "stake" });
|
|
1358
|
+
break;
|
|
1359
|
+
}
|
|
1360
|
+
case "WithdrawStakeRequest": {
|
|
1361
|
+
const staker = addr(b.staker), pool = addr(b.pool);
|
|
1362
|
+
init(staker);
|
|
1363
|
+
if (ok && b.amount !== void 0) native(staker, pool, big2(b.amount));
|
|
1364
|
+
if (pool !== void 0) {
|
|
1365
|
+
setCall({ to: pool, method: "unstake" });
|
|
1366
|
+
if (ok) out.needsValueFlow ??= { counterparty: pool };
|
|
1367
|
+
}
|
|
1368
|
+
break;
|
|
1369
|
+
}
|
|
1370
|
+
case "WithdrawStake": {
|
|
1371
|
+
const staker = addr(b.staker), pool = addr(b.pool);
|
|
1372
|
+
init(pool);
|
|
1373
|
+
if (ok) native(pool, staker, big2(b.amount));
|
|
1374
|
+
if (pool !== void 0) setCall({ to: pool, method: "withdraw_stake" });
|
|
1375
|
+
break;
|
|
1376
|
+
}
|
|
1377
|
+
case "ElectionsDepositStake":
|
|
1378
|
+
case "ElectionsRecoverStake": {
|
|
1379
|
+
const staker = addr(b.staker);
|
|
1380
|
+
init(staker);
|
|
1381
|
+
if (ok) a.type === "ElectionsDepositStake" ? native(staker, "-1:3333333333333333333333333333333333333333333333333333333333333333", big2(b.amount)) : native("-1:3333333333333333333333333333333333333333333333333333333333333333", staker, big2(b.amount));
|
|
1382
|
+
break;
|
|
1383
|
+
}
|
|
1384
|
+
case "Subscribe":
|
|
1385
|
+
case "UnSubscribe": {
|
|
1386
|
+
const subscriber = addr(b.subscriber), beneficiary = addr(b.beneficiary), subscription = addr(b.subscription);
|
|
1387
|
+
init(subscriber);
|
|
1388
|
+
if (ok && a.type === "Subscribe") native(subscriber, beneficiary, big2(b.amount));
|
|
1389
|
+
if (subscription !== void 0) setCall({ to: subscription, method: a.type === "Subscribe" ? "subscribe" : "unsubscribe" });
|
|
1390
|
+
break;
|
|
1391
|
+
}
|
|
1392
|
+
case "NftPurchase": {
|
|
1393
|
+
const buyer = addr(b.buyer), seller = addr(b.seller), nft = addr(b.nft);
|
|
1394
|
+
init(buyer);
|
|
1395
|
+
if (ok) native(buyer, seller, big2(b.amount?.value ?? b.amount));
|
|
1396
|
+
if (nft !== void 0) setCall({ to: nft, method: "nft_purchase" });
|
|
1397
|
+
break;
|
|
1398
|
+
}
|
|
1399
|
+
case "ContractDeploy": {
|
|
1400
|
+
const deployed = addr(b.address);
|
|
1401
|
+
if (deployed !== void 0) setCall({ to: deployed, method: "deploy" });
|
|
1402
|
+
break;
|
|
1403
|
+
}
|
|
1404
|
+
default:
|
|
1405
|
+
break;
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
return out;
|
|
1409
|
+
}
|
|
1410
|
+
function valueFlowTransfers(event, own, counterparty) {
|
|
1411
|
+
const flow = event.value_flow?.find((v) => addr(v.account) === own);
|
|
1412
|
+
const out = [];
|
|
1413
|
+
for (const j of flow?.jettons ?? []) {
|
|
1414
|
+
const q = big2(j.quantity);
|
|
1415
|
+
if (q === 0n) continue;
|
|
1416
|
+
const t = { token: j.jetton.address.toLowerCase(), from: q < 0n ? own : counterparty, to: q < 0n ? counterparty : own, amount: q < 0n ? -q : q, standard: "jetton", ...jettonMeta(j.jetton) };
|
|
1417
|
+
out.push(t);
|
|
1418
|
+
}
|
|
1419
|
+
return out;
|
|
1420
|
+
}
|
|
1421
|
+
function mapTonapiEvent(chain, address, own, event, valueFlow) {
|
|
1422
|
+
const mapped = mapActions(event.actions ?? []);
|
|
1423
|
+
const transfers = mapped.transfers;
|
|
1424
|
+
const raw = { backend: "tonapi", event };
|
|
1425
|
+
if (mapped.needsValueFlow && valueFlow) {
|
|
1426
|
+
raw.valueFlow = valueFlow.value_flow;
|
|
1427
|
+
const extra = valueFlowTransfers(valueFlow, own, mapped.needsValueFlow.counterparty);
|
|
1428
|
+
for (const t of extra) if (!transfers.some((x) => x.token === t.token && x.from === t.from && x.to === t.to && x.amount === t.amount)) transfers.push(t);
|
|
1429
|
+
}
|
|
1430
|
+
const from = mapped.initiator ?? own;
|
|
1431
|
+
const involves = (t) => t.from === own || t.to === own;
|
|
1432
|
+
const primary = transfers.find((t) => t.token !== null && involves(t)) ?? transfers.find(involves) ?? transfers[0];
|
|
1433
|
+
const to = mapped.call?.to ?? primary?.to ?? null;
|
|
1434
|
+
let nativeValue = 0n;
|
|
1435
|
+
for (const t of transfers) if (t.token === null && t.from === from) nativeValue += t.amount;
|
|
1436
|
+
const actions = event.actions ?? [];
|
|
1437
|
+
const status = event.in_progress ? "pending" : actions.length > 0 && actions.every((a) => a.status !== "ok") ? "failed" : "confirmed";
|
|
1438
|
+
const out = {
|
|
1439
|
+
chain,
|
|
1440
|
+
hash: event.event_id,
|
|
1441
|
+
time: event.timestamp,
|
|
1442
|
+
status,
|
|
1443
|
+
address,
|
|
1444
|
+
from,
|
|
1445
|
+
to,
|
|
1446
|
+
nativeValue,
|
|
1447
|
+
transfers,
|
|
1448
|
+
block: big2(event.lt),
|
|
1449
|
+
raw
|
|
1450
|
+
};
|
|
1451
|
+
if (typeof event.extra === "number" && event.extra < 0) out.fee = BigInt(-event.extra);
|
|
1452
|
+
if (mapped.call) out.call = mapped.call;
|
|
1453
|
+
if (event.is_scam) {
|
|
1454
|
+
out.scam = true;
|
|
1455
|
+
raw.scam = true;
|
|
1456
|
+
}
|
|
1457
|
+
return out;
|
|
1458
|
+
}
|
|
1459
|
+
var TEXT_COMMENT_OP = "0x00000000";
|
|
1460
|
+
function mapToncenterTransaction(chain, address, row) {
|
|
1461
|
+
const own = row.account.toLowerCase();
|
|
1462
|
+
const hash = import_buffer4.Buffer.from(row.hash, "base64").toString("hex");
|
|
1463
|
+
const d = row.description;
|
|
1464
|
+
const failed = d?.aborted === true || d?.compute_ph?.skipped !== true && d?.compute_ph?.success === false || d?.action?.success === false;
|
|
1465
|
+
const transfers = [];
|
|
1466
|
+
let from = own;
|
|
1467
|
+
let to = null;
|
|
1468
|
+
let call;
|
|
1469
|
+
const source = row.in_msg?.source ? row.in_msg.source.toLowerCase() : void 0;
|
|
1470
|
+
if (source === void 0) {
|
|
1471
|
+
for (const m of row.out_msgs ?? []) {
|
|
1472
|
+
const dest = m.destination?.toLowerCase();
|
|
1473
|
+
if (!dest) continue;
|
|
1474
|
+
const value = big2(m.value);
|
|
1475
|
+
if (value > 0n) transfers.push({ token: null, from: own, to: dest, amount: value });
|
|
1476
|
+
if (!call && m.opcode && m.opcode !== TEXT_COMMENT_OP) {
|
|
1477
|
+
call = { to: dest, data: m.opcode.toLowerCase() };
|
|
1478
|
+
if (m.decoded_opcode) call.method = m.decoded_opcode;
|
|
1479
|
+
}
|
|
1480
|
+
to ??= dest;
|
|
1481
|
+
}
|
|
1482
|
+
} else {
|
|
1483
|
+
from = source;
|
|
1484
|
+
to = own;
|
|
1485
|
+
const value = big2(row.in_msg?.value);
|
|
1486
|
+
if (value > 0n) transfers.push({ token: null, from: source, to: own, amount: value });
|
|
1487
|
+
const op = row.in_msg?.opcode;
|
|
1488
|
+
if (op && op !== TEXT_COMMENT_OP) {
|
|
1489
|
+
call = { to: own, data: op.toLowerCase() };
|
|
1490
|
+
if (row.in_msg?.decoded_opcode) call.method = row.in_msg.decoded_opcode;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
let nativeValue = 0n;
|
|
1494
|
+
for (const t of transfers) if (t.token === null && t.from === from) nativeValue += t.amount;
|
|
1495
|
+
const out = {
|
|
1496
|
+
chain,
|
|
1497
|
+
hash,
|
|
1498
|
+
time: row.now,
|
|
1499
|
+
status: failed ? "failed" : "confirmed",
|
|
1500
|
+
address,
|
|
1501
|
+
from,
|
|
1502
|
+
to: call?.to ?? to,
|
|
1503
|
+
nativeValue,
|
|
1504
|
+
transfers,
|
|
1505
|
+
fee: big2(row.total_fees),
|
|
1506
|
+
block: big2(row.lt),
|
|
1507
|
+
raw: { backend: "toncenter", event: row }
|
|
1508
|
+
};
|
|
1509
|
+
if (call) out.call = call;
|
|
1510
|
+
return out;
|
|
1511
|
+
}
|
|
1512
|
+
function tonExplorerTxUrl(hash, network = "mainnet") {
|
|
1513
|
+
return `https://${network === "testnet" ? "testnet." : ""}tonviewer.com/transaction/${hash}`;
|
|
1514
|
+
}
|
|
1515
|
+
function createTonHistory(options = {}) {
|
|
1516
|
+
const network = options.network ?? "mainnet";
|
|
1517
|
+
const chain = CAIP2[network];
|
|
1518
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
1519
|
+
if (typeof fetchFn !== "function") throw new Error("@anywalletkit/wallet-ton: no global fetch; pass `fetch` in the options");
|
|
1520
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
|
|
1521
|
+
const tonapiUrl = options.tonapiUrl === null ? void 0 : (options.tonapiUrl ?? (network === "testnet" ? "https://testnet.tonapi.io" : "https://tonapi.io")).replace(/\/+$/, "");
|
|
1522
|
+
const toncenterUrl = options.toncenterUrl === null ? void 0 : (options.toncenterUrl ?? (network === "testnet" ? "https://testnet.toncenter.com" : "https://toncenter.com")).replace(/\/+$/, "");
|
|
1523
|
+
if (!tonapiUrl && !toncenterUrl) throw new Error("@anywalletkit/wallet-ton: createTonHistory needs tonapi or toncenter");
|
|
1524
|
+
const tonapiBucket = new TokenBucket(options.rateLimit ?? (options.apiKey ? 10 : 1));
|
|
1525
|
+
const toncenterBucket = new TokenBucket(options.toncenterApiKey ? 10 : 1);
|
|
1526
|
+
async function getJson(backend, url, headers) {
|
|
1527
|
+
await (backend === "tonapi" ? tonapiBucket : toncenterBucket).acquire();
|
|
1528
|
+
let res;
|
|
1529
|
+
try {
|
|
1530
|
+
const signal = timeoutSignal(timeoutMs);
|
|
1531
|
+
res = await fetchFn(url, { headers: { accept: "application/json", ...headers }, ...signal ? { signal } : {} });
|
|
1532
|
+
} catch (e) {
|
|
1533
|
+
throw new Error(`${backend}: ${e instanceof Error ? e.message : String(e)}`);
|
|
1534
|
+
}
|
|
1535
|
+
let body;
|
|
1536
|
+
try {
|
|
1537
|
+
body = await res.json();
|
|
1538
|
+
} catch {
|
|
1539
|
+
body = void 0;
|
|
1540
|
+
}
|
|
1541
|
+
return { status: res.status, body };
|
|
1542
|
+
}
|
|
1543
|
+
const tonapiHeaders = () => options.apiKey ? { authorization: `Bearer ${options.apiKey}` } : {};
|
|
1544
|
+
const toncenterHeaders = () => options.toncenterApiKey ? { "x-api-key": options.toncenterApiKey } : {};
|
|
1545
|
+
const fail = (backend, r) => {
|
|
1546
|
+
const msg = typeof r.body === "object" && r.body !== null && typeof r.body.error === "string" ? r.body.error : "";
|
|
1547
|
+
throw new Error(`${backend}: HTTP ${r.status}${msg ? ` ${msg}` : ""}`);
|
|
1548
|
+
};
|
|
1549
|
+
async function tonapiEventFull(eventId) {
|
|
1550
|
+
const r = await getJson("tonapi", `${tonapiUrl}/v2/events/${eventId}`, tonapiHeaders());
|
|
1551
|
+
if (r.status === 404) return void 0;
|
|
1552
|
+
if (r.status !== 200) fail("tonapi", r);
|
|
1553
|
+
return r.body;
|
|
1554
|
+
}
|
|
1555
|
+
async function finishTonapi(address, own, event) {
|
|
1556
|
+
const probe = mapActions(event.actions ?? []);
|
|
1557
|
+
let flow;
|
|
1558
|
+
if (probe.needsValueFlow) {
|
|
1559
|
+
try {
|
|
1560
|
+
flow = await tonapiEventFull(event.event_id);
|
|
1561
|
+
} catch {
|
|
1562
|
+
flow = void 0;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
return mapTonapiEvent(chain, address, own, event, flow);
|
|
1566
|
+
}
|
|
1567
|
+
async function tonapiPage(address, own, limit, before) {
|
|
1568
|
+
if (!tonapiUrl) throw new Error("tonapi: disabled");
|
|
1569
|
+
const q = `limit=${limit}${before ? `&before_lt=${before}` : ""}`;
|
|
1570
|
+
const r = await getJson("tonapi", `${tonapiUrl}/v2/accounts/${encodeURIComponent(address)}/events?${q}`, tonapiHeaders());
|
|
1571
|
+
if (r.status !== 200) fail("tonapi", r);
|
|
1572
|
+
const body = r.body;
|
|
1573
|
+
const events = body?.events ?? [];
|
|
1574
|
+
const items = [];
|
|
1575
|
+
for (const e of events) items.push(await finishTonapi(address, own, e));
|
|
1576
|
+
const page = { items, backend: "tonapi" };
|
|
1577
|
+
if (events.length >= limit) {
|
|
1578
|
+
const last = events[events.length - 1];
|
|
1579
|
+
page.cursor = encodeCursor({ v: 1, b: "tonapi", lt: String(body?.next_from ?? last.lt) });
|
|
1580
|
+
}
|
|
1581
|
+
return page;
|
|
1582
|
+
}
|
|
1583
|
+
async function toncenterPage(address, limit, before) {
|
|
1584
|
+
if (!toncenterUrl) throw new Error("toncenter: disabled");
|
|
1585
|
+
const endLt = before ? (BigInt(before) - 1n).toString() : void 0;
|
|
1586
|
+
const q = `account=${encodeURIComponent(address)}&limit=${limit}&sort=desc${endLt ? `&end_lt=${endLt}` : ""}`;
|
|
1587
|
+
const r = await getJson("toncenter", `${toncenterUrl}/api/v3/transactions?${q}`, toncenterHeaders());
|
|
1588
|
+
if (r.status !== 200) fail("toncenter", r);
|
|
1589
|
+
const rows = r.body?.transactions ?? [];
|
|
1590
|
+
const items = rows.map((row) => mapToncenterTransaction(chain, address, row));
|
|
1591
|
+
const page = { items, backend: "toncenter" };
|
|
1592
|
+
if (rows.length >= limit) page.cursor = encodeCursor({ v: 1, b: "toncenter", lt: rows[rows.length - 1].lt });
|
|
1593
|
+
return page;
|
|
1594
|
+
}
|
|
1595
|
+
const backends = [...tonapiUrl ? ["tonapi"] : [], ...toncenterUrl ? ["toncenter"] : []];
|
|
1596
|
+
return {
|
|
1597
|
+
chain,
|
|
1598
|
+
async listActivity(address, query = {}) {
|
|
1599
|
+
const limit = Math.min(MAX_LIMIT, Math.max(1, Math.floor(query.limit ?? DEFAULT_LIMIT)));
|
|
1600
|
+
const cursor = query.cursor ? decodeCursor(query.cursor) : void 0;
|
|
1601
|
+
const own = rawTonAddress(address);
|
|
1602
|
+
const ordered = cursor ? [...backends].sort((a, b) => a === cursor.b ? -1 : b === cursor.b ? 1 : 0) : backends;
|
|
1603
|
+
const attempts = [];
|
|
1604
|
+
for (const b of ordered) {
|
|
1605
|
+
try {
|
|
1606
|
+
let page = b === "tonapi" ? await tonapiPage(address, own, limit, cursor?.lt) : await toncenterPage(address, limit, cursor?.lt);
|
|
1607
|
+
if (query.since !== void 0) {
|
|
1608
|
+
const since = query.since;
|
|
1609
|
+
if (page.items.some((t) => t.time < since)) delete page.cursor;
|
|
1610
|
+
page = { ...page, items: page.items.filter((t) => t.time >= since) };
|
|
1611
|
+
}
|
|
1612
|
+
return page;
|
|
1613
|
+
} catch (e) {
|
|
1614
|
+
attempts.push(e instanceof Error ? e.message : String(e));
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
throw new HistoryUnavailableError(chain, attempts[attempts.length - 1] ?? "no backend configured", attempts);
|
|
1618
|
+
},
|
|
1619
|
+
async getTransaction(hash, address) {
|
|
1620
|
+
const own = rawTonAddress(address);
|
|
1621
|
+
const attempts = [];
|
|
1622
|
+
for (const b of backends) {
|
|
1623
|
+
try {
|
|
1624
|
+
if (b === "tonapi") {
|
|
1625
|
+
const r2 = await getJson("tonapi", `${tonapiUrl}/v2/accounts/${encodeURIComponent(address)}/events/${encodeURIComponent(hash)}`, tonapiHeaders());
|
|
1626
|
+
if (r2.status === 404) return void 0;
|
|
1627
|
+
if (r2.status !== 200) fail("tonapi", r2);
|
|
1628
|
+
return await finishTonapi(address, own, r2.body);
|
|
1629
|
+
}
|
|
1630
|
+
const r = await getJson("toncenter", `${toncenterUrl}/api/v3/transactions?hash=${encodeURIComponent(hash)}`, toncenterHeaders());
|
|
1631
|
+
if (r.status !== 200) fail("toncenter", r);
|
|
1632
|
+
const rows = r.body?.transactions ?? [];
|
|
1633
|
+
const row = rows.find((x) => x.account.toLowerCase() === own) ?? rows[0];
|
|
1634
|
+
return row ? mapToncenterTransaction(chain, address, row) : void 0;
|
|
1635
|
+
} catch (e) {
|
|
1636
|
+
attempts.push(e instanceof Error ? e.message : String(e));
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
throw new HistoryUnavailableError(chain, attempts[attempts.length - 1] ?? "no backend configured", attempts);
|
|
1640
|
+
},
|
|
1641
|
+
explorerTxUrl(hash) {
|
|
1642
|
+
return tonExplorerTxUrl(hash, network);
|
|
1643
|
+
}
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// src/index.ts
|
|
1648
|
+
installBufferGlobal();
|
|
1649
|
+
//# sourceMappingURL=index.cjs.map
|