@anywalletkit/wallet-ton 0.5.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 +66 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +67 -10
- 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++) {
|
|
@@ -1200,6 +1209,14 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
|
|
|
1200
1209
|
/** What holds the key: `seed`, `key` (one imported key) or `external`. */
|
|
1201
1210
|
signerKind;
|
|
1202
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();
|
|
1203
1220
|
/** `publicKey` is the signer's 32-byte ed25519 key; the V5R1 address derives from it synchronously. */
|
|
1204
1221
|
constructor(signer, publicKey, path, config = {}) {
|
|
1205
1222
|
super(publicKey, config);
|
|
@@ -1277,14 +1294,33 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
|
|
|
1277
1294
|
bodyModes(body) {
|
|
1278
1295
|
return parseV5R1Body(body)?.messages.map((m) => m.mode) ?? [];
|
|
1279
1296
|
}
|
|
1280
|
-
|
|
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) {
|
|
1281
1317
|
this.assertNoDrain(messages.map((m) => sendModeOf(m.mode)));
|
|
1282
1318
|
const want = messages.reduce((sum, m) => sum + (m.message.info.type === "internal" ? m.message.info.value.coins : 0n), 0n);
|
|
1283
1319
|
if (want > 0n) {
|
|
1284
1320
|
const balance = await this.getBalance();
|
|
1285
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).`);
|
|
1286
1322
|
}
|
|
1287
|
-
const { signing } = await this.signingCell(messages);
|
|
1323
|
+
const { signing } = await this.signingCell(messages, forSend ? await this.#nextSendSeqno() : void 0);
|
|
1288
1324
|
const fee = await this.preflight(v5r1PackUnsigned(signing), limit, op, want);
|
|
1289
1325
|
const signature = await this.#signer.sign(new Uint8Array(signing.hash()), { scheme: "ed25519", context: "ton-external" });
|
|
1290
1326
|
return { body: v5r1PackSigned(signing, signature), fee };
|
|
@@ -1321,26 +1357,47 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
|
|
|
1321
1357
|
const rpc = this.requireRpc("send transactions");
|
|
1322
1358
|
const init = await this.initIfNeeded();
|
|
1323
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
|
+
};
|
|
1324
1364
|
try {
|
|
1325
1365
|
await rpc.sendBoc(v5r1ExternalMessage(this._address, body, init));
|
|
1326
1366
|
} catch (e) {
|
|
1327
1367
|
if (isAnsweredRefusal(e)) throw e;
|
|
1368
|
+
sent();
|
|
1328
1369
|
throw new BroadcastUnknownError(hash, e);
|
|
1329
1370
|
}
|
|
1371
|
+
sent();
|
|
1330
1372
|
return hash;
|
|
1331
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
|
+
}
|
|
1332
1383
|
/** Sign and broadcast; `hash` is the body hash (hex) — resolve it with getTransactionReceipt. */
|
|
1333
|
-
|
|
1384
|
+
sendTransaction(tx, options = {}) {
|
|
1385
|
+
return this.#serial(() => this.#sendTransactionNow(tx, options));
|
|
1386
|
+
}
|
|
1387
|
+
async #sendTransactionNow(tx, options) {
|
|
1334
1388
|
this.requireRpc("send transactions");
|
|
1335
1389
|
if (tx instanceof import_core10.Cell) this.assertNoDrain(this.bodyModes(tx));
|
|
1336
|
-
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);
|
|
1337
1391
|
return { hash: await this.broadcast(body), fee };
|
|
1338
1392
|
}
|
|
1339
1393
|
/** TON (no `token`) or jetton transfer. */
|
|
1340
|
-
|
|
1394
|
+
transfer(o, writeOptions = {}) {
|
|
1395
|
+
return this.#serial(() => this.#transferNow(o, writeOptions));
|
|
1396
|
+
}
|
|
1397
|
+
async #transferNow(o, writeOptions) {
|
|
1341
1398
|
this.requireRpc("transfer tokens");
|
|
1342
1399
|
const message = o.token ? await this.jettonTransferMessage(o) : this.transactionMessage({ to: o.recipient, value: o.amount, ...o.memo !== void 0 ? { body: o.memo } : {} });
|
|
1343
|
-
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);
|
|
1344
1401
|
return { hash: await this.broadcast(body), fee };
|
|
1345
1402
|
}
|
|
1346
1403
|
/** The same wallet as a {@link WalletAccountReadOnlyTon}. */
|