@anywalletkit/wallet-ton 0.4.0 → 0.7.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/dist/{chunk-ZFENRKRW.js → chunk-WU4RNRQJ.js} +1 -1
- package/dist/{chunk-ZFENRKRW.js.map → chunk-WU4RNRQJ.js.map} +1 -1
- package/dist/history.cjs.map +1 -1
- package/dist/history.js +1 -1
- package/dist/index.cjs +81 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -4
- package/dist/index.d.ts +18 -4
- package/dist/index.js +82 -18
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -170,12 +170,19 @@ function tonCenterEndpoint(endpoint, opts = {}) {
|
|
|
170
170
|
}
|
|
171
171
|
if (!res.ok || json.ok !== true) {
|
|
172
172
|
const detail = typeof json.error === "string" ? json.error : JSON.stringify(json.error ?? json);
|
|
173
|
-
|
|
173
|
+
const err = new import_core2.NetworkError(`${method}: ${detail} (HTTP ${res.status})`, { provider: url });
|
|
174
|
+
throw isNodeRefusal(res.status, json) ? Object.assign(err, { answered: true }) : err;
|
|
174
175
|
}
|
|
175
176
|
return json.result;
|
|
176
177
|
}
|
|
177
178
|
};
|
|
178
179
|
}
|
|
180
|
+
var UNKNOWN_STATUS = /* @__PURE__ */ new Set([408, 502, 503, 504]);
|
|
181
|
+
function isNodeRefusal(status, json) {
|
|
182
|
+
if (UNKNOWN_STATUS.has(status)) return false;
|
|
183
|
+
if (json.ok !== false || typeof json.error !== "string" || json.error.trim() === "") return false;
|
|
184
|
+
return !/time[ds]? ?out|timed out|deadline/i.test(json.error);
|
|
185
|
+
}
|
|
179
186
|
function isAnsweredRefusal(e) {
|
|
180
187
|
return e?.answered === true;
|
|
181
188
|
}
|
|
@@ -1016,8 +1023,8 @@ var WalletAccountReadOnlyTon = class {
|
|
|
1016
1023
|
return Math.floor(Date.now() / 1e3) + seconds;
|
|
1017
1024
|
}
|
|
1018
1025
|
/** The cell to sign for `messages` at the CURRENT seqno (fetched fresh every time). */
|
|
1019
|
-
async signingCell(messages) {
|
|
1020
|
-
const seqno = await this.getSeqno();
|
|
1026
|
+
async signingCell(messages, knownSeqno) {
|
|
1027
|
+
const seqno = knownSeqno ?? await this.getSeqno();
|
|
1021
1028
|
const validUntil = this.validUntil();
|
|
1022
1029
|
return { signing: v5r1SigningMessage({ walletId: this._walletId, validUntil, seqno, messages }), seqno, validUntil };
|
|
1023
1030
|
}
|
|
@@ -1178,6 +1185,8 @@ function tonSignDataDigest(address, request) {
|
|
|
1178
1185
|
}
|
|
1179
1186
|
|
|
1180
1187
|
// src/account.ts
|
|
1188
|
+
var PENDING_SEQNO_TIMEOUT_MS = 6e4;
|
|
1189
|
+
var PENDING_SEQNO_POLL_MS = 1500;
|
|
1181
1190
|
var toHex = (u8) => Array.from(u8, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1182
1191
|
var carriesKey = (bytes, key) => {
|
|
1183
1192
|
outer: for (let i = 0; i + key.length <= bytes.length; i++) {
|
|
@@ -1191,13 +1200,28 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
|
|
|
1191
1200
|
needsNetwork = true;
|
|
1192
1201
|
/** Derivation path, `ton-mnemonic`, or `external`. */
|
|
1193
1202
|
path;
|
|
1194
|
-
/**
|
|
1195
|
-
|
|
1203
|
+
/**
|
|
1204
|
+
* The signer that holds the key, in a private field: not a property of the account, so no code
|
|
1205
|
+
* the account is handed to (a protocol module, a logger) can reach it and sign around the
|
|
1206
|
+
* account's checks (audit 2026-10-29 A1).
|
|
1207
|
+
*/
|
|
1208
|
+
#signer;
|
|
1209
|
+
/** What holds the key: `seed`, `key` (one imported key) or `external`. */
|
|
1210
|
+
signerKind;
|
|
1196
1211
|
_readOnly;
|
|
1212
|
+
/**
|
|
1213
|
+
* The last external this account broadcast (accepted, or maybe sent) and when it expires. Until
|
|
1214
|
+
* the wallet's seqno moves past it, a new message signed at the chain seqno would carry the same
|
|
1215
|
+
* seqno and silently never execute, so the next send waits for it first.
|
|
1216
|
+
*/
|
|
1217
|
+
_inFlight;
|
|
1218
|
+
/** Tail of this account's send queue: seqno → sign → broadcast runs one send at a time. */
|
|
1219
|
+
_sendTail = Promise.resolve();
|
|
1197
1220
|
/** `publicKey` is the signer's 32-byte ed25519 key; the V5R1 address derives from it synchronously. */
|
|
1198
1221
|
constructor(signer, publicKey, path, config = {}) {
|
|
1199
1222
|
super(publicKey, config);
|
|
1200
|
-
this
|
|
1223
|
+
this.#signer = (0, import_core9.requireCurve)(signer, "ed25519");
|
|
1224
|
+
this.signerKind = this.#signer.kind;
|
|
1201
1225
|
this.path = path;
|
|
1202
1226
|
}
|
|
1203
1227
|
/** Back-compat: wrap a raw derived key in a {@link LocalSigner} (the account takes ownership; it cannot derive). */
|
|
@@ -1220,7 +1244,7 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
|
|
|
1220
1244
|
async sign(message) {
|
|
1221
1245
|
const bytes = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
1222
1246
|
if (bytes.length === 32 || carriesKey(bytes, this.publicKey)) throw new MessageIsTransactionError();
|
|
1223
|
-
return toHex(await this
|
|
1247
|
+
return toHex(await this.#signer.sign(bytes, { scheme: "ed25519", context: "ton-message" }));
|
|
1224
1248
|
}
|
|
1225
1249
|
/**
|
|
1226
1250
|
* TON Connect `ton_proof` for this wallet's address: the `proof` object of the connect reply. The
|
|
@@ -1229,7 +1253,7 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
|
|
|
1229
1253
|
*/
|
|
1230
1254
|
async signTonProof(request) {
|
|
1231
1255
|
const digest = tonProofDigest(this._address, request);
|
|
1232
|
-
const signature = await this
|
|
1256
|
+
const signature = await this.#signer.sign(digest, { scheme: "ed25519", context: "ton-proof" });
|
|
1233
1257
|
return {
|
|
1234
1258
|
timestamp: request.timestamp,
|
|
1235
1259
|
domain: { lengthBytes: new TextEncoder().encode(request.domain).length, value: request.domain },
|
|
@@ -1240,7 +1264,7 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
|
|
|
1240
1264
|
/** TON Connect `signData` (text / binary) for this wallet's address: the reply the dApp gets. */
|
|
1241
1265
|
async signTonConnectData(request) {
|
|
1242
1266
|
const digest = tonSignDataDigest(this._address, request);
|
|
1243
|
-
const signature = await this
|
|
1267
|
+
const signature = await this.#signer.sign(digest, { scheme: "ed25519", context: "ton-sign-data" });
|
|
1244
1268
|
return {
|
|
1245
1269
|
signature: import_buffer6.Buffer.from(signature).toString("base64"),
|
|
1246
1270
|
address: this._address.toRawString(),
|
|
@@ -1270,16 +1294,35 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
|
|
|
1270
1294
|
bodyModes(body) {
|
|
1271
1295
|
return parseV5R1Body(body)?.messages.map((m) => m.mode) ?? [];
|
|
1272
1296
|
}
|
|
1273
|
-
|
|
1297
|
+
/**
|
|
1298
|
+
* The seqno to sign the next send at: the chain's, once it has moved past this account's last
|
|
1299
|
+
* broadcast. While that external can still land (before its valid_until) the seqno is polled, up
|
|
1300
|
+
* to `pendingSeqnoTimeoutMs`; an expired one that never landed is forgotten.
|
|
1301
|
+
*/
|
|
1302
|
+
async #nextSendSeqno() {
|
|
1303
|
+
const timeout = this._config.pendingSeqnoTimeoutMs ?? PENDING_SEQNO_TIMEOUT_MS;
|
|
1304
|
+
const deadline = Date.now() + timeout;
|
|
1305
|
+
for (; ; ) {
|
|
1306
|
+
const chain = await this.getSeqno();
|
|
1307
|
+
const prev = this._inFlight;
|
|
1308
|
+
if (!prev || chain > prev.seqno || Date.now() / 1e3 > prev.validUntil) {
|
|
1309
|
+
this._inFlight = void 0;
|
|
1310
|
+
return chain;
|
|
1311
|
+
}
|
|
1312
|
+
if (Date.now() >= deadline) throw new Error(`The previous transfer is still pending (seqno ${prev.seqno} has not moved): wait for it to land, then send again.`);
|
|
1313
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(PENDING_SEQNO_POLL_MS, Math.max(0, deadline - Date.now()))));
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
async #signMessages(messages, limit, op, forSend = false) {
|
|
1274
1317
|
this.assertNoDrain(messages.map((m) => sendModeOf(m.mode)));
|
|
1275
1318
|
const want = messages.reduce((sum, m) => sum + (m.message.info.type === "internal" ? m.message.info.value.coins : 0n), 0n);
|
|
1276
1319
|
if (want > 0n) {
|
|
1277
1320
|
const balance = await this.getBalance();
|
|
1278
1321
|
if (balance < want) throw new UnsafeTransactionError("insufficient-balance", `Insufficient TON balance: the messages carry ${want} nanotons, the wallet holds ${balance} (fees come on top).`);
|
|
1279
1322
|
}
|
|
1280
|
-
const { signing } = await this.signingCell(messages);
|
|
1323
|
+
const { signing } = await this.signingCell(messages, forSend ? await this.#nextSendSeqno() : void 0);
|
|
1281
1324
|
const fee = await this.preflight(v5r1PackUnsigned(signing), limit, op, want);
|
|
1282
|
-
const signature = await this
|
|
1325
|
+
const signature = await this.#signer.sign(new Uint8Array(signing.hash()), { scheme: "ed25519", context: "ton-external" });
|
|
1283
1326
|
return { body: v5r1PackSigned(signing, signature), fee };
|
|
1284
1327
|
}
|
|
1285
1328
|
async preflight(body, limit, op, expectedTonOut) {
|
|
@@ -1314,26 +1357,47 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
|
|
|
1314
1357
|
const rpc = this.requireRpc("send transactions");
|
|
1315
1358
|
const init = await this.initIfNeeded();
|
|
1316
1359
|
const hash = body.hash().toString("hex");
|
|
1360
|
+
const parsed = parseV5R1Body(body);
|
|
1361
|
+
const sent = () => {
|
|
1362
|
+
if (parsed) this._inFlight = { seqno: parsed.seqno, validUntil: parsed.validUntil };
|
|
1363
|
+
};
|
|
1317
1364
|
try {
|
|
1318
1365
|
await rpc.sendBoc(v5r1ExternalMessage(this._address, body, init));
|
|
1319
1366
|
} catch (e) {
|
|
1320
1367
|
if (isAnsweredRefusal(e)) throw e;
|
|
1368
|
+
sent();
|
|
1321
1369
|
throw new BroadcastUnknownError(hash, e);
|
|
1322
1370
|
}
|
|
1371
|
+
sent();
|
|
1323
1372
|
return hash;
|
|
1324
1373
|
}
|
|
1374
|
+
/**
|
|
1375
|
+
* One send at a time per account: two sends started together would read the same seqno before
|
|
1376
|
+
* either is broadcast. A failed send does not block the next.
|
|
1377
|
+
*/
|
|
1378
|
+
#serial(run) {
|
|
1379
|
+
const next = this._sendTail.then(run);
|
|
1380
|
+
this._sendTail = next.then(() => void 0, () => void 0);
|
|
1381
|
+
return next;
|
|
1382
|
+
}
|
|
1325
1383
|
/** Sign and broadcast; `hash` is the body hash (hex) — resolve it with getTransactionReceipt. */
|
|
1326
|
-
|
|
1384
|
+
sendTransaction(tx, options = {}) {
|
|
1385
|
+
return this.#serial(() => this.#sendTransactionNow(tx, options));
|
|
1386
|
+
}
|
|
1387
|
+
async #sendTransactionNow(tx, options) {
|
|
1327
1388
|
this.requireRpc("send transactions");
|
|
1328
1389
|
if (tx instanceof import_core10.Cell) this.assertNoDrain(this.bodyModes(tx));
|
|
1329
|
-
const { body, fee } = tx instanceof import_core10.Cell ? { body: tx, fee: await this.preflight(tx, (0, import_core9.feeLimit)(this._config.transactionMaxFee, options), "transaction") } : await this.#signMessages(this.toMessages(tx), (0, import_core9.feeLimit)(this._config.transactionMaxFee, options), "transaction");
|
|
1390
|
+
const { body, fee } = tx instanceof import_core10.Cell ? { body: tx, fee: await this.preflight(tx, (0, import_core9.feeLimit)(this._config.transactionMaxFee, options), "transaction") } : await this.#signMessages(this.toMessages(tx), (0, import_core9.feeLimit)(this._config.transactionMaxFee, options), "transaction", true);
|
|
1330
1391
|
return { hash: await this.broadcast(body), fee };
|
|
1331
1392
|
}
|
|
1332
1393
|
/** TON (no `token`) or jetton transfer. */
|
|
1333
|
-
|
|
1394
|
+
transfer(o, writeOptions = {}) {
|
|
1395
|
+
return this.#serial(() => this.#transferNow(o, writeOptions));
|
|
1396
|
+
}
|
|
1397
|
+
async #transferNow(o, writeOptions) {
|
|
1334
1398
|
this.requireRpc("transfer tokens");
|
|
1335
1399
|
const message = o.token ? await this.jettonTransferMessage(o) : this.transactionMessage({ to: o.recipient, value: o.amount, ...o.memo !== void 0 ? { body: o.memo } : {} });
|
|
1336
|
-
const { body, fee } = await this.#signMessages([message], (0, import_core9.feeLimit)(this._config.transferMaxFee, writeOptions), "transfer");
|
|
1400
|
+
const { body, fee } = await this.#signMessages([message], (0, import_core9.feeLimit)(this._config.transferMaxFee, writeOptions), "transfer", true);
|
|
1337
1401
|
return { hash: await this.broadcast(body), fee };
|
|
1338
1402
|
}
|
|
1339
1403
|
/** The same wallet as a {@link WalletAccountReadOnlyTon}. */
|
|
@@ -1343,7 +1407,7 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
|
|
|
1343
1407
|
}
|
|
1344
1408
|
/** Disposes the signer: a local key is zeroized, an external one is released. */
|
|
1345
1409
|
dispose() {
|
|
1346
|
-
this
|
|
1410
|
+
this.#signer.dispose();
|
|
1347
1411
|
}
|
|
1348
1412
|
};
|
|
1349
1413
|
|