@anywalletkit/wallet-ton 0.3.0 → 0.4.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/index.cjs CHANGED
@@ -41,6 +41,7 @@ __export(src_exports, {
41
41
  WalletAccountReadOnlyTon: () => WalletAccountReadOnlyTon,
42
42
  WalletAccountTon: () => WalletAccountTon,
43
43
  WalletManagerTon: () => WalletManagerTon,
44
+ canonicalBocCell: () => canonicalBocCell,
44
45
  commentCell: () => commentCell,
45
46
  createTonClient: () => createTonRpc,
46
47
  createTonHistory: () => createTonHistory,
@@ -58,6 +59,7 @@ __export(src_exports, {
58
59
  mapTonapiActions: () => mapActions,
59
60
  mapTonapiEvent: () => mapTonapiEvent,
60
61
  mapToncenterTransaction: () => mapToncenterTransaction,
62
+ messageBody: () => messageBody,
61
63
  parseEmulation: () => parseEmulation,
62
64
  parseTonapiConsequences: () => parseTonapiConsequences,
63
65
  parseTonapiRisk: () => parseTonapiRisk,
@@ -623,6 +625,7 @@ function parseV5R1Body(body) {
623
625
  }
624
626
 
625
627
  // src/describe.ts
628
+ var import_buffer4 = require("buffer");
626
629
  var import_core6 = require("@ton/core");
627
630
  var JETTON_TRANSFER_OP = 260734629;
628
631
  var LARGE_FORWARD_NANOTON = 500000000n;
@@ -682,11 +685,67 @@ function parseTo(to) {
682
685
  const a = import_core6.Address.parse(to);
683
686
  return { to: a.toRawString(), bounceable: true, workchain: a.workChain };
684
687
  }
685
- function bodyOf(tx) {
688
+ function canonicalBocCell(b64, what = "bodyBoc") {
689
+ if (!/^[A-Za-z0-9+/_-]+={0,2}$/.test(b64)) throw new UnsafeTransactionError("non-canonical-boc", `${what} is not a base64 string.`);
690
+ const bytes = import_buffer4.Buffer.from(b64, "base64");
691
+ const layout = plainBocLayout(bytes);
692
+ if (layout !== null) throw new UnsafeTransactionError("non-canonical-boc", `${what} is not a plain single-root BoC (${layout}), so what was reviewed may not be what is signed.`);
693
+ let roots;
694
+ try {
695
+ roots = import_core6.Cell.fromBoc(bytes);
696
+ } catch {
697
+ throw new UnsafeTransactionError("non-canonical-boc", `${what} is not a valid BoC.`);
698
+ }
699
+ if (roots.length !== 1) throw new UnsafeTransactionError("non-canonical-boc", `${what} has ${roots.length} roots.`);
700
+ return roots[0];
701
+ }
702
+ function plainBocLayout(b) {
703
+ if (b.length < 6 || b[0] !== 181 || b[1] !== 238 || b[2] !== 156 || b[3] !== 114) return "not the generic BoC magic";
704
+ const flags = b[4];
705
+ const size = flags & 7;
706
+ const offBytes = b[5];
707
+ if (size < 1 || size > 4 || offBytes < 1 || offBytes > 8) return "bad size fields";
708
+ let i = 6;
709
+ const int = (n) => {
710
+ if (i + n > b.length) throw new RangeError("truncated");
711
+ let v = 0;
712
+ for (let k = 0; k < n; k++) v = v * 256 + b[i++];
713
+ return v;
714
+ };
715
+ try {
716
+ const cells = int(size);
717
+ const roots = int(size);
718
+ const absent = int(size);
719
+ int(offBytes);
720
+ if (roots !== 1) return `${roots} roots`;
721
+ if (absent !== 0) return "absent cells";
722
+ i += roots * size;
723
+ if (flags & 128) i += cells * offBytes;
724
+ for (let c = 0; c < cells; c++) {
725
+ if (i + 2 > b.length) return "truncated";
726
+ const d1 = b[i++];
727
+ const d2 = b[i++];
728
+ if (d1 & 16) return "a cell stores its hashes";
729
+ if (d1 >> 5 !== 0) return "a cell has a level mask";
730
+ if (d1 & 8) return "an exotic cell";
731
+ if ((d1 & 7) > 4) return "a cell with more than 4 refs";
732
+ i += Math.ceil(d2 / 2) + (d1 & 7) * size;
733
+ }
734
+ if (i + (flags & 64 ? 4 : 0) !== b.length) return "trailing or missing bytes";
735
+ } catch {
736
+ return "truncated";
737
+ }
738
+ return null;
739
+ }
740
+ function messageBody(tx) {
686
741
  if (tx.body instanceof import_core6.Cell) return tx.body;
687
742
  if (typeof tx.body === "string") return tx.body === "" ? void 0 : commentCell(tx.body);
688
- if (tx.bodyBoc) return import_core6.Cell.fromBase64(tx.bodyBoc);
689
- return void 0;
743
+ if (tx.body !== void 0) throw new UnsafeTransactionError("body", "A message body must be a Cell or a string (a text comment).");
744
+ if (tx.bodyBoc !== void 0 && typeof tx.bodyBoc !== "string") throw new UnsafeTransactionError("body", "bodyBoc must be a base64 string.");
745
+ return tx.bodyBoc ? canonicalBocCell(tx.bodyBoc) : void 0;
746
+ }
747
+ function bodyOf(tx) {
748
+ return messageBody(tx);
690
749
  }
691
750
  function commentCell(text) {
692
751
  return (0, import_core6.beginCell)().storeUint(0, 32).storeStringTail(text).endCell();
@@ -792,6 +851,8 @@ var import_ed25519 = require("@noble/curves/ed25519.js");
792
851
  var import_core8 = require("@ton/core");
793
852
  var JETTON_TRANSFER_OP2 = 260734629;
794
853
  var DEFAULT_JETTON_ATTACHED = 50000000n;
854
+ var DEFAULT_MAX_JETTON_ATTACHED = 1000000000n;
855
+ var RAW_ADDRESS = /^-?\d+:[0-9a-fA-F]{64}$/;
795
856
  var DEFAULT_INDEXER = "https://toncenter.com/api/v3";
796
857
  var DEFAULT_VALID_SECONDS = 60;
797
858
  var MAX_VALID_SECONDS = 600;
@@ -893,13 +954,15 @@ var WalletAccountReadOnlyTon = class {
893
954
  let address;
894
955
  let bounceable = true;
895
956
  let testOnly;
896
- if (import_core8.Address.isFriendly(to)) {
957
+ if (typeof to === "string" && import_core8.Address.isFriendly(to)) {
897
958
  const f = import_core8.Address.parseFriendly(to);
898
959
  address = f.address;
899
960
  bounceable = f.isBounceable;
900
961
  testOnly = f.isTestOnly;
901
- } else {
962
+ } else if (typeof to === "string" && RAW_ADDRESS.test(to)) {
902
963
  address = import_core8.Address.parse(to);
964
+ } else {
965
+ throw new UnsafeTransactionError("address", `${JSON.stringify(to)} is not a TON address in a strict form (raw wc:hex, or a friendly address with its checksum).`);
903
966
  }
904
967
  if (address.workChain !== 0 && address.workChain !== -1) throw new UnsafeTransactionError("exotic-workchain", `Refusing to send to workchain ${address.workChain}.`);
905
968
  const testnet = this._config.network === "testnet";
@@ -912,7 +975,7 @@ var WalletAccountReadOnlyTon = class {
912
975
  const value = big(tx.value);
913
976
  if (value <= 0n) throw new UnsafeTransactionError("zero-value", "TON message value must be positive.");
914
977
  const { address, bounceable } = this.checkedAddress(tx.to);
915
- 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;
978
+ const body = messageBody(tx);
916
979
  const init = this.stateInitOf(tx.stateInit);
917
980
  const message = (0, import_core8.internal)({ to: address, value, bounce: tx.bounceable ?? bounceable, ...body ? { body } : {}, ...init ? { init } : {} });
918
981
  return { message, ...tx.sendMode !== void 0 ? { mode: tx.sendMode } : {} };
@@ -922,14 +985,22 @@ var WalletAccountReadOnlyTon = class {
922
985
  if (amount <= 0n) throw new UnsafeTransactionError("zero-value", "Jetton amount must be positive.");
923
986
  const recipient = this.checkedAddress(o.recipient).address;
924
987
  const response = o.responseDestination ? this.checkedAddress(o.responseDestination).address : void 0;
988
+ if (response && !response.equals(this._address) && o.allowForeignResponseDestination !== true) {
989
+ throw new UnsafeTransactionError("foreign-response-destination", `The excess TON of this jetton transfer would go to ${response.toString()}, not to this wallet; pass allowForeignResponseDestination to allow it.`);
990
+ }
991
+ const forwardTon = o.forwardTonAmount !== void 0 ? big(o.forwardTonAmount) : 1n;
992
+ const attached = o.attachedValue !== void 0 ? big(o.attachedValue) : (this._config.jettonAttachedValue !== void 0 ? big(this._config.jettonAttachedValue) : DEFAULT_JETTON_ATTACHED) + forwardTon;
993
+ const maxAttached = this._config.maxJettonAttachedValue !== void 0 ? big(this._config.maxJettonAttachedValue) : DEFAULT_MAX_JETTON_ATTACHED;
994
+ if (attached > maxAttached || forwardTon > attached) {
995
+ throw new UnsafeTransactionError("attached-value", `A jetton transfer may attach at most ${maxAttached} nanotons, forwardTonAmount included (asked: ${attached}, forward ${forwardTon}); raise config.maxJettonAttachedValue if this is intended.`);
996
+ }
925
997
  const jw = await this.getJettonWalletAddress(o.token);
926
998
  await this.jettonWalletData(o.token, jw);
927
- const forwardTonAmount = o.forwardTonAmount !== void 0 ? big(o.forwardTonAmount) : 1n;
999
+ const forwardTonAmount = forwardTon;
928
1000
  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.");
929
1001
  const payload = o.forwardPayload ?? o.memo;
930
1002
  const forwardPayload = payload === void 0 ? null : typeof payload === "string" ? commentCell(payload) : payload;
931
1003
  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(response ?? this._address).storeMaybeRef(o.customPayload ?? null).storeCoins(forwardTonAmount).storeMaybeRef(forwardPayload).endCell();
932
- const attached = o.attachedValue !== void 0 ? big(o.attachedValue) : (this._config.jettonAttachedValue !== void 0 ? big(this._config.jettonAttachedValue) : DEFAULT_JETTON_ATTACHED) + forwardTonAmount;
933
1004
  return { message: (0, import_core8.internal)({ to: jw, value: attached, bounce: true, body }) };
934
1005
  }
935
1006
  toMessages(tx) {
@@ -1046,12 +1117,12 @@ function randomQueryId() {
1046
1117
 
1047
1118
  // src/account.ts
1048
1119
  var import_core9 = require("@anywalletkit/core");
1049
- var import_buffer5 = require("buffer");
1120
+ var import_buffer6 = require("buffer");
1050
1121
  var import_core10 = require("@ton/core");
1051
1122
 
1052
1123
  // src/tonconnect.ts
1053
1124
  var import_sha2 = require("@noble/hashes/sha2.js");
1054
- var import_buffer4 = require("buffer");
1125
+ var import_buffer5 = require("buffer");
1055
1126
  var utf82 = (s) => new TextEncoder().encode(s);
1056
1127
  var int32BE = (n) => {
1057
1128
  const b = new Uint8Array(4);
@@ -1086,7 +1157,7 @@ function checkCommon(address, domain, timestamp) {
1086
1157
  function base64Bytes(raw) {
1087
1158
  const s = raw.replace(/[\t\n\f\r ]/g, "");
1088
1159
  if (!/^[A-Za-z0-9+/]*={0,2}$/.test(s) || s.replace(/=+$/, "").length % 4 === 1) throw new TypeError("signData binary payload must be base64.");
1089
- return new Uint8Array(import_buffer4.Buffer.from(s, "base64"));
1160
+ return new Uint8Array(import_buffer5.Buffer.from(s, "base64"));
1090
1161
  }
1091
1162
  function tonProofDigest(address, request) {
1092
1163
  checkCommon(address, request.domain, request.timestamp);
@@ -1163,7 +1234,7 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
1163
1234
  timestamp: request.timestamp,
1164
1235
  domain: { lengthBytes: new TextEncoder().encode(request.domain).length, value: request.domain },
1165
1236
  payload: request.payload,
1166
- signature: import_buffer5.Buffer.from(signature).toString("base64")
1237
+ signature: import_buffer6.Buffer.from(signature).toString("base64")
1167
1238
  };
1168
1239
  }
1169
1240
  /** TON Connect `signData` (text / binary) for this wallet's address: the reply the dApp gets. */
@@ -1171,7 +1242,7 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
1171
1242
  const digest = tonSignDataDigest(this._address, request);
1172
1243
  const signature = await this.signer.sign(digest, { scheme: "ed25519", context: "ton-sign-data" });
1173
1244
  return {
1174
- signature: import_buffer5.Buffer.from(signature).toString("base64"),
1245
+ signature: import_buffer6.Buffer.from(signature).toString("base64"),
1175
1246
  address: this._address.toRawString(),
1176
1247
  timestamp: request.timestamp,
1177
1248
  domain: request.domain,
@@ -1199,7 +1270,7 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
1199
1270
  bodyModes(body) {
1200
1271
  return parseV5R1Body(body)?.messages.map((m) => m.mode) ?? [];
1201
1272
  }
1202
- async signMessages(messages, limit, op) {
1273
+ async #signMessages(messages, limit, op) {
1203
1274
  this.assertNoDrain(messages.map((m) => sendModeOf(m.mode)));
1204
1275
  const want = messages.reduce((sum, m) => sum + (m.message.info.type === "internal" ? m.message.info.value.coins : 0n), 0n);
1205
1276
  if (want > 0n) {
@@ -1236,7 +1307,7 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
1236
1307
  await this.preflight(tx, (0, import_core9.feeLimit)(this._config.transactionMaxFee, options), "transaction");
1237
1308
  return tx.toBoc().toString("base64");
1238
1309
  }
1239
- const { body } = await this.signMessages(this.toMessages(tx), (0, import_core9.feeLimit)(this._config.transactionMaxFee, options), "transaction");
1310
+ const { body } = await this.#signMessages(this.toMessages(tx), (0, import_core9.feeLimit)(this._config.transactionMaxFee, options), "transaction");
1240
1311
  return body.toBoc().toString("base64");
1241
1312
  }
1242
1313
  async broadcast(body) {
@@ -1255,14 +1326,14 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
1255
1326
  async sendTransaction(tx, options = {}) {
1256
1327
  this.requireRpc("send transactions");
1257
1328
  if (tx instanceof import_core10.Cell) this.assertNoDrain(this.bodyModes(tx));
1258
- 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");
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");
1259
1330
  return { hash: await this.broadcast(body), fee };
1260
1331
  }
1261
1332
  /** TON (no `token`) or jetton transfer. */
1262
1333
  async transfer(o, writeOptions = {}) {
1263
1334
  this.requireRpc("transfer tokens");
1264
1335
  const message = o.token ? await this.jettonTransferMessage(o) : this.transactionMessage({ to: o.recipient, value: o.amount, ...o.memo !== void 0 ? { body: o.memo } : {} });
1265
- const { body, fee } = await this.signMessages([message], (0, import_core9.feeLimit)(this._config.transferMaxFee, writeOptions), "transfer");
1336
+ const { body, fee } = await this.#signMessages([message], (0, import_core9.feeLimit)(this._config.transferMaxFee, writeOptions), "transfer");
1266
1337
  return { hash: await this.broadcast(body), fee };
1267
1338
  }
1268
1339
  /** The same wallet as a {@link WalletAccountReadOnlyTon}. */
@@ -1359,7 +1430,7 @@ var WalletManagerTon = class _WalletManagerTon extends import_core11.WalletManag
1359
1430
 
1360
1431
  // src/history.ts
1361
1432
  var import_core12 = require("@ton/core");
1362
- var import_buffer6 = require("buffer");
1433
+ var import_buffer7 = require("buffer");
1363
1434
  var HistoryUnavailableError = class extends Error {
1364
1435
  chain;
1365
1436
  reason;
@@ -1376,12 +1447,12 @@ var DEFAULT_LIMIT = 25;
1376
1447
  var MAX_LIMIT = 100;
1377
1448
  var DEFAULT_TIMEOUT_MS3 = 15e3;
1378
1449
  function encodeCursor(c) {
1379
- return import_buffer6.Buffer.from(JSON.stringify(c), "utf8").toString("base64");
1450
+ return import_buffer7.Buffer.from(JSON.stringify(c), "utf8").toString("base64");
1380
1451
  }
1381
1452
  function decodeCursor(s) {
1382
1453
  let parsed;
1383
1454
  try {
1384
- parsed = JSON.parse(import_buffer6.Buffer.from(s, "base64").toString("utf8"));
1455
+ parsed = JSON.parse(import_buffer7.Buffer.from(s, "base64").toString("utf8"));
1385
1456
  } catch {
1386
1457
  throw new Error("@anywalletkit/wallet-ton: malformed history cursor");
1387
1458
  }
@@ -1633,7 +1704,7 @@ function mapTonapiEvent(chain, address, own, event, valueFlow) {
1633
1704
  var TEXT_COMMENT_OP = "0x00000000";
1634
1705
  function mapToncenterTransaction(chain, address, row) {
1635
1706
  const own = row.account.toLowerCase();
1636
- const hash = import_buffer6.Buffer.from(row.hash, "base64").toString("hex");
1707
+ const hash = import_buffer7.Buffer.from(row.hash, "base64").toString("hex");
1637
1708
  const d = row.description;
1638
1709
  const failed = d?.aborted === true || d?.compute_ph?.skipped !== true && d?.compute_ph?.success === false || d?.action?.success === false;
1639
1710
  const transfers = [];