@anywalletkit/wallet-ton 0.2.5 → 0.3.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.d.cts CHANGED
@@ -237,12 +237,26 @@ declare class SimulationError extends Error {
237
237
  aborted?: boolean;
238
238
  });
239
239
  }
240
+ /**
241
+ * The external message was handed to the network but no definitive answer came back (no response,
242
+ * a timeout, a non-JSON body): it MAY be on chain. Do not sign again (the next seqno would pay
243
+ * twice) — watch `hash`, the body hash `sendTransaction` returns.
244
+ */
245
+ declare class BroadcastUnknownError extends Error {
246
+ /** Always true: the transaction may be on its way. */
247
+ readonly maybeSent = true;
248
+ /** Hex hash of the signed body. */
249
+ readonly hash: string;
250
+ constructor(hash: string, cause: unknown);
251
+ }
240
252
 
241
253
  /** JSON-RPC over one toncenter v2 endpoint. */
242
254
  declare function tonCenterEndpoint(endpoint: TonEndpoint, opts?: {
243
255
  fetch?: typeof fetch;
244
256
  timeoutMs?: number;
245
257
  }): TonRpc;
258
+ /** An endpoint answered with a refusal (`ok: false`): nothing was taken. A thrown fetch, a timeout or a non-JSON body may have been. */
259
+ declare function isAnsweredRefusal(e: unknown): boolean;
246
260
  /** One RPC, or a failover RPC walking the configured list (`retries` extra rounds after the first full pass). */
247
261
  declare function createTonRpc(config: TonConfig): TonRpc | undefined;
248
262
  /** State of a TON account on chain. */
@@ -492,9 +506,14 @@ interface TonDescribedMessage {
492
506
  jetton?: TonJettonDetails;
493
507
  hasStateInit: boolean;
494
508
  bodyBits: number;
509
+ /**
510
+ * Send mode the wallet sends the message with (IGNORE_ERRORS is always added for an external).
511
+ * 128 carries the whole remaining balance, 32 destroys the account when it is left empty.
512
+ */
513
+ mode?: number;
495
514
  }
496
515
  /** Problems a TON description is flagged with. */
497
- type TonWarningCode = 'zero-value' | 'non-bounceable-contract-call' | 'unknown-body' | 'large-forward-amount' | 'testnet-address' | 'mainnet-address' | 'exotic-workchain' | 'extended-actions';
516
+ type TonWarningCode = 'zero-value' | 'non-bounceable-contract-call' | 'unknown-body' | 'large-forward-amount' | 'testnet-address' | 'mainnet-address' | 'exotic-workchain' | 'extended-actions' | 'carries-all-balance' | 'destroys-account';
498
517
  /** One flagged problem; `index` is the message it is about. */
499
518
  interface TonWarning {
500
519
  code: TonWarningCode;
@@ -584,6 +603,15 @@ declare class WalletAccountReadOnlyTon implements IWalletAccountReadOnly {
584
603
  */
585
604
  getTokenBalance(token: string): Promise<bigint>;
586
605
  protected stateInitOf(src: TonTransaction['stateInit']): StateInit | undefined;
606
+ /**
607
+ * A destination address with the checks every payment gets: workchain 0 or -1, and a friendly
608
+ * address's test-only flag matching the wallet's network. Used for a native message's `to` and a
609
+ * jetton transfer's recipient and response destination (round 3, L8).
610
+ */
611
+ protected checkedAddress(to: string): {
612
+ address: Address;
613
+ bounceable: boolean;
614
+ };
587
615
  protected transactionMessage(tx: TonTransaction): V5R1Message;
588
616
  protected jettonTransferMessage(o: TonTransferOptions): Promise<V5R1Message>;
589
617
  protected toMessages(tx: TonTransaction | TonTransaction[]): V5R1Message[];
@@ -730,6 +758,14 @@ declare class WalletAccountTon extends WalletAccountReadOnlyTon implements IWall
730
758
  * Checks → seqno → preflight → signed body. The preflight runs on the
731
759
  * zero-signed body (ignore_chksig), so nothing is signed before it passes.
732
760
  */
761
+ /**
762
+ * Refuse send modes that drain the wallet — 128 (the whole remaining balance) and 32 (the account
763
+ * destroyed once empty) — unless `allowDrainingTransactions`. Checked locally, before any seqno or
764
+ * emulation, so it holds with `simulate: false` too (round 3, M11); the emulation check stays.
765
+ */
766
+ private assertNoDrain;
767
+ /** The send modes of a prebuilt V5R1 body; an unreadable body is left to the emulation and the node. */
768
+ private bodyModes;
733
769
  private signMessages;
734
770
  private preflight;
735
771
  /** Sign without broadcasting. Returns the external-message BODY as base64 BoC (pass it back to sendTransaction as a Cell). */
@@ -782,4 +818,4 @@ declare class WalletManagerTon extends WalletManager<WalletAccountTon, TonConfig
782
818
  getFeeRates(): Promise<FeeRates>;
783
819
  }
784
820
 
785
- export { CAIP2, DEFAULT_SEND_MODE, DEFAULT_TONAPI, type DecodedBody, type EmulateOptions, type EmulationOutcome, type EmulationSource, type EmulationVerdict, type GetMethodResult, JETTON_TRANSFER_OP, MAINNET_GLOBAL_ID, MAX_ACTIONS, MessageIsTransactionError, type ParsedV5R1Body, SEND_MODE, SimulationError, TESTNET_GLOBAL_ID, TON_BASE_PATH, TON_MNEMONIC_PATH, type TonAccountState, type TonAccountStatus, type TonAddressParts, TonCenter, type TonConfig, type TonDescribedKind, type TonDescribedMessage, type TonDescription, type TonEmulateConfig, type TonEndpoint, type TonFees, type TonJettonDetails, type TonProof, type TonProofRequest, type TonProvider, type TonReceipt, type TonRisk, type TonRpc, type TonSendable, type TonSignDataPayload, type TonSignDataRequest, type TonSignedData, type TonSimulation, type TonSimulationSource, type TonTransaction, type TonTransferOptions, type TonWarning, type TonWarningCode, UnsafeTransactionError, type V5R1Message, type V5R1SigningParams, type V5R1WalletIdParts, V5R1_CODE_HEX, V5R1_OP, WalletAccountReadOnlyTon, WalletAccountTon, WalletManagerTon, commentCell, createTonRpc as createTonClient, createTonRpc, decodeBody, WalletManagerTon as default, describeMessageRelaxed, describeTransaction, emulateExternal, emulateViaTonapi, installBufferGlobal, parseEmulation, parseTonapiConsequences, parseTonapiRisk, parseTonapiTrace, parseV5R1Body, resolveEmulate, runEmulation, tonCenterEndpoint, tonProofDigest, tonSignDataDigest, v5r1Address, v5r1Code, v5r1ExternalMessage, v5r1PackSigned, v5r1PackUnsigned, v5r1SigningMessage, v5r1StateInit, v5r1WalletId };
821
+ export { BroadcastUnknownError, CAIP2, DEFAULT_SEND_MODE, DEFAULT_TONAPI, type DecodedBody, type EmulateOptions, type EmulationOutcome, type EmulationSource, type EmulationVerdict, type GetMethodResult, JETTON_TRANSFER_OP, MAINNET_GLOBAL_ID, MAX_ACTIONS, MessageIsTransactionError, type ParsedV5R1Body, SEND_MODE, SimulationError, TESTNET_GLOBAL_ID, TON_BASE_PATH, TON_MNEMONIC_PATH, type TonAccountState, type TonAccountStatus, type TonAddressParts, TonCenter, type TonConfig, type TonDescribedKind, type TonDescribedMessage, type TonDescription, type TonEmulateConfig, type TonEndpoint, type TonFees, type TonJettonDetails, type TonProof, type TonProofRequest, type TonProvider, type TonReceipt, type TonRisk, type TonRpc, type TonSendable, type TonSignDataPayload, type TonSignDataRequest, type TonSignedData, type TonSimulation, type TonSimulationSource, type TonTransaction, type TonTransferOptions, type TonWarning, type TonWarningCode, UnsafeTransactionError, type V5R1Message, type V5R1SigningParams, type V5R1WalletIdParts, V5R1_CODE_HEX, V5R1_OP, WalletAccountReadOnlyTon, WalletAccountTon, WalletManagerTon, commentCell, createTonRpc as createTonClient, createTonRpc, decodeBody, WalletManagerTon as default, describeMessageRelaxed, describeTransaction, emulateExternal, emulateViaTonapi, installBufferGlobal, isAnsweredRefusal, parseEmulation, parseTonapiConsequences, parseTonapiRisk, parseTonapiTrace, parseV5R1Body, resolveEmulate, runEmulation, tonCenterEndpoint, tonProofDigest, tonSignDataDigest, v5r1Address, v5r1Code, v5r1ExternalMessage, v5r1PackSigned, v5r1PackUnsigned, v5r1SigningMessage, v5r1StateInit, v5r1WalletId };
package/dist/index.d.ts CHANGED
@@ -237,12 +237,26 @@ declare class SimulationError extends Error {
237
237
  aborted?: boolean;
238
238
  });
239
239
  }
240
+ /**
241
+ * The external message was handed to the network but no definitive answer came back (no response,
242
+ * a timeout, a non-JSON body): it MAY be on chain. Do not sign again (the next seqno would pay
243
+ * twice) — watch `hash`, the body hash `sendTransaction` returns.
244
+ */
245
+ declare class BroadcastUnknownError extends Error {
246
+ /** Always true: the transaction may be on its way. */
247
+ readonly maybeSent = true;
248
+ /** Hex hash of the signed body. */
249
+ readonly hash: string;
250
+ constructor(hash: string, cause: unknown);
251
+ }
240
252
 
241
253
  /** JSON-RPC over one toncenter v2 endpoint. */
242
254
  declare function tonCenterEndpoint(endpoint: TonEndpoint, opts?: {
243
255
  fetch?: typeof fetch;
244
256
  timeoutMs?: number;
245
257
  }): TonRpc;
258
+ /** An endpoint answered with a refusal (`ok: false`): nothing was taken. A thrown fetch, a timeout or a non-JSON body may have been. */
259
+ declare function isAnsweredRefusal(e: unknown): boolean;
246
260
  /** One RPC, or a failover RPC walking the configured list (`retries` extra rounds after the first full pass). */
247
261
  declare function createTonRpc(config: TonConfig): TonRpc | undefined;
248
262
  /** State of a TON account on chain. */
@@ -492,9 +506,14 @@ interface TonDescribedMessage {
492
506
  jetton?: TonJettonDetails;
493
507
  hasStateInit: boolean;
494
508
  bodyBits: number;
509
+ /**
510
+ * Send mode the wallet sends the message with (IGNORE_ERRORS is always added for an external).
511
+ * 128 carries the whole remaining balance, 32 destroys the account when it is left empty.
512
+ */
513
+ mode?: number;
495
514
  }
496
515
  /** Problems a TON description is flagged with. */
497
- type TonWarningCode = 'zero-value' | 'non-bounceable-contract-call' | 'unknown-body' | 'large-forward-amount' | 'testnet-address' | 'mainnet-address' | 'exotic-workchain' | 'extended-actions';
516
+ type TonWarningCode = 'zero-value' | 'non-bounceable-contract-call' | 'unknown-body' | 'large-forward-amount' | 'testnet-address' | 'mainnet-address' | 'exotic-workchain' | 'extended-actions' | 'carries-all-balance' | 'destroys-account';
498
517
  /** One flagged problem; `index` is the message it is about. */
499
518
  interface TonWarning {
500
519
  code: TonWarningCode;
@@ -584,6 +603,15 @@ declare class WalletAccountReadOnlyTon implements IWalletAccountReadOnly {
584
603
  */
585
604
  getTokenBalance(token: string): Promise<bigint>;
586
605
  protected stateInitOf(src: TonTransaction['stateInit']): StateInit | undefined;
606
+ /**
607
+ * A destination address with the checks every payment gets: workchain 0 or -1, and a friendly
608
+ * address's test-only flag matching the wallet's network. Used for a native message's `to` and a
609
+ * jetton transfer's recipient and response destination (round 3, L8).
610
+ */
611
+ protected checkedAddress(to: string): {
612
+ address: Address;
613
+ bounceable: boolean;
614
+ };
587
615
  protected transactionMessage(tx: TonTransaction): V5R1Message;
588
616
  protected jettonTransferMessage(o: TonTransferOptions): Promise<V5R1Message>;
589
617
  protected toMessages(tx: TonTransaction | TonTransaction[]): V5R1Message[];
@@ -730,6 +758,14 @@ declare class WalletAccountTon extends WalletAccountReadOnlyTon implements IWall
730
758
  * Checks → seqno → preflight → signed body. The preflight runs on the
731
759
  * zero-signed body (ignore_chksig), so nothing is signed before it passes.
732
760
  */
761
+ /**
762
+ * Refuse send modes that drain the wallet — 128 (the whole remaining balance) and 32 (the account
763
+ * destroyed once empty) — unless `allowDrainingTransactions`. Checked locally, before any seqno or
764
+ * emulation, so it holds with `simulate: false` too (round 3, M11); the emulation check stays.
765
+ */
766
+ private assertNoDrain;
767
+ /** The send modes of a prebuilt V5R1 body; an unreadable body is left to the emulation and the node. */
768
+ private bodyModes;
733
769
  private signMessages;
734
770
  private preflight;
735
771
  /** Sign without broadcasting. Returns the external-message BODY as base64 BoC (pass it back to sendTransaction as a Cell). */
@@ -782,4 +818,4 @@ declare class WalletManagerTon extends WalletManager<WalletAccountTon, TonConfig
782
818
  getFeeRates(): Promise<FeeRates>;
783
819
  }
784
820
 
785
- export { CAIP2, DEFAULT_SEND_MODE, DEFAULT_TONAPI, type DecodedBody, type EmulateOptions, type EmulationOutcome, type EmulationSource, type EmulationVerdict, type GetMethodResult, JETTON_TRANSFER_OP, MAINNET_GLOBAL_ID, MAX_ACTIONS, MessageIsTransactionError, type ParsedV5R1Body, SEND_MODE, SimulationError, TESTNET_GLOBAL_ID, TON_BASE_PATH, TON_MNEMONIC_PATH, type TonAccountState, type TonAccountStatus, type TonAddressParts, TonCenter, type TonConfig, type TonDescribedKind, type TonDescribedMessage, type TonDescription, type TonEmulateConfig, type TonEndpoint, type TonFees, type TonJettonDetails, type TonProof, type TonProofRequest, type TonProvider, type TonReceipt, type TonRisk, type TonRpc, type TonSendable, type TonSignDataPayload, type TonSignDataRequest, type TonSignedData, type TonSimulation, type TonSimulationSource, type TonTransaction, type TonTransferOptions, type TonWarning, type TonWarningCode, UnsafeTransactionError, type V5R1Message, type V5R1SigningParams, type V5R1WalletIdParts, V5R1_CODE_HEX, V5R1_OP, WalletAccountReadOnlyTon, WalletAccountTon, WalletManagerTon, commentCell, createTonRpc as createTonClient, createTonRpc, decodeBody, WalletManagerTon as default, describeMessageRelaxed, describeTransaction, emulateExternal, emulateViaTonapi, installBufferGlobal, parseEmulation, parseTonapiConsequences, parseTonapiRisk, parseTonapiTrace, parseV5R1Body, resolveEmulate, runEmulation, tonCenterEndpoint, tonProofDigest, tonSignDataDigest, v5r1Address, v5r1Code, v5r1ExternalMessage, v5r1PackSigned, v5r1PackUnsigned, v5r1SigningMessage, v5r1StateInit, v5r1WalletId };
821
+ export { BroadcastUnknownError, CAIP2, DEFAULT_SEND_MODE, DEFAULT_TONAPI, type DecodedBody, type EmulateOptions, type EmulationOutcome, type EmulationSource, type EmulationVerdict, type GetMethodResult, JETTON_TRANSFER_OP, MAINNET_GLOBAL_ID, MAX_ACTIONS, MessageIsTransactionError, type ParsedV5R1Body, SEND_MODE, SimulationError, TESTNET_GLOBAL_ID, TON_BASE_PATH, TON_MNEMONIC_PATH, type TonAccountState, type TonAccountStatus, type TonAddressParts, TonCenter, type TonConfig, type TonDescribedKind, type TonDescribedMessage, type TonDescription, type TonEmulateConfig, type TonEndpoint, type TonFees, type TonJettonDetails, type TonProof, type TonProofRequest, type TonProvider, type TonReceipt, type TonRisk, type TonRpc, type TonSendable, type TonSignDataPayload, type TonSignDataRequest, type TonSignedData, type TonSimulation, type TonSimulationSource, type TonTransaction, type TonTransferOptions, type TonWarning, type TonWarningCode, UnsafeTransactionError, type V5R1Message, type V5R1SigningParams, type V5R1WalletIdParts, V5R1_CODE_HEX, V5R1_OP, WalletAccountReadOnlyTon, WalletAccountTon, WalletManagerTon, commentCell, createTonRpc as createTonClient, createTonRpc, decodeBody, WalletManagerTon as default, describeMessageRelaxed, describeTransaction, emulateExternal, emulateViaTonapi, installBufferGlobal, isAnsweredRefusal, parseEmulation, parseTonapiConsequences, parseTonapiRisk, parseTonapiTrace, parseV5R1Body, resolveEmulate, runEmulation, tonCenterEndpoint, tonProofDigest, tonSignDataDigest, v5r1Address, v5r1Code, v5r1ExternalMessage, v5r1PackSigned, v5r1PackUnsigned, v5r1SigningMessage, v5r1StateInit, v5r1WalletId };
package/dist/index.js CHANGED
@@ -47,6 +47,17 @@ var SimulationError = class extends Error {
47
47
  this.aborted = details.aborted ?? false;
48
48
  }
49
49
  };
50
+ var BroadcastUnknownError = class extends Error {
51
+ /** Always true: the transaction may be on its way. */
52
+ maybeSent = true;
53
+ /** Hex hash of the signed body. */
54
+ hash;
55
+ constructor(hash, cause) {
56
+ super(`Broadcast outcome unknown for ${hash}: ${cause instanceof Error ? cause.message.slice(0, 200) : String(cause).slice(0, 200)}`, { cause });
57
+ this.name = "BroadcastUnknownError";
58
+ this.hash = hash;
59
+ }
60
+ };
50
61
 
51
62
  // src/toncenter.ts
52
63
  import { NetworkError } from "@anywalletkit/core";
@@ -84,12 +95,15 @@ function tonCenterEndpoint(endpoint, opts = {}) {
84
95
  }
85
96
  if (!res.ok || json.ok !== true) {
86
97
  const detail = typeof json.error === "string" ? json.error : JSON.stringify(json.error ?? json);
87
- throw new NetworkError(`${method}: ${detail} (HTTP ${res.status})`, { provider: url });
98
+ throw Object.assign(new NetworkError(`${method}: ${detail} (HTTP ${res.status})`, { provider: url }), { answered: true });
88
99
  }
89
100
  return json.result;
90
101
  }
91
102
  };
92
103
  }
104
+ function isAnsweredRefusal(e) {
105
+ return e?.answered === true;
106
+ }
93
107
  function createTonRpc(config) {
94
108
  const target = config.tonClient ?? config.provider;
95
109
  if (target === void 0) return void 0;
@@ -102,14 +116,17 @@ function createTonRpc(config) {
102
116
  return {
103
117
  async call(method, params) {
104
118
  let lastError;
119
+ let answered = true;
105
120
  for (let i = 0; i < attempts; i++) {
106
121
  try {
107
122
  return await list[i % list.length].call(method, params);
108
123
  } catch (e) {
109
124
  lastError = e;
125
+ answered &&= isAnsweredRefusal(e);
110
126
  }
111
127
  }
112
- throw new NetworkError(`All TON endpoints failed for ${method}`, { cause: lastError });
128
+ const err = new NetworkError(`All TON endpoints failed for ${method}`, { cause: lastError });
129
+ throw answered ? Object.assign(err, { answered: true }) : err;
113
130
  }
114
131
  };
115
132
  }
@@ -645,6 +662,19 @@ function describeOne(i, to, value, body, hasStateInit, bounceableOverride, warni
645
662
  return item;
646
663
  }
647
664
  var hex = (n) => `0x${n.toString(16).padStart(8, "0")}`;
665
+ var IGNORE_ERRORS = 2;
666
+ function sendModeOf(sendMode) {
667
+ return (sendMode ?? DEFAULT_SEND_MODE) | IGNORE_ERRORS;
668
+ }
669
+ function drainModes(mode) {
670
+ return [...(mode & 128) !== 0 ? ["carries-all-balance"] : [], ...(mode & 32) !== 0 ? ["destroys-account"] : []];
671
+ }
672
+ function withMode(i, item, mode, warnings) {
673
+ for (const code of drainModes(mode)) {
674
+ warnings.push({ code, message: code === "carries-all-balance" ? "send mode 128: the message carries the whole remaining balance" : "send mode 32: the wallet account is destroyed once it is empty", index: i });
675
+ }
676
+ return { ...item, mode };
677
+ }
648
678
  function describeMessageRelaxed(i, m, warnings, ctx = {}) {
649
679
  if (m.info.type !== "internal") {
650
680
  warnings.push({ code: "unknown-body", message: "not an internal message", index: i });
@@ -666,7 +696,7 @@ function describeTransaction(tx, ctx = {}) {
666
696
  return { kind: "contract-call", items, warnings };
667
697
  }
668
698
  external2 = { seqno: parsed.seqno, validUntil: parsed.validUntil, walletId: parsed.walletId, signed: parsed.signed };
669
- parsed.messages.forEach((m, i) => items.push(describeMessageRelaxed(i, m.message, warnings, ctx)));
699
+ parsed.messages.forEach((m, i) => items.push(withMode(i, describeMessageRelaxed(i, m.message, warnings, ctx), m.mode, warnings)));
670
700
  } else {
671
701
  const list = Array.isArray(tx) ? tx : [tx];
672
702
  list.forEach((t, i) => {
@@ -684,7 +714,7 @@ function describeTransaction(tx, ctx = {}) {
684
714
  } catch {
685
715
  warnings.push({ code: "unknown-body", message: "bodyBoc is not a valid BoC", index: i });
686
716
  }
687
- items.push(describeOne(i, to, BigInt(t.value), body, t.stateInit !== void 0, t.bounceable, warnings, ctx));
717
+ items.push(withMode(i, describeOne(i, to, BigInt(t.value), body, t.stateInit !== void 0, t.bounceable, warnings, ctx), sendModeOf(t.sendMode), warnings));
688
718
  });
689
719
  }
690
720
  const kinds = new Set(items.map((x) => x.kind));
@@ -790,25 +820,34 @@ var WalletAccountReadOnlyTon = class {
790
820
  if (src instanceof Cell4) return loadStateInit(src.beginParse());
791
821
  return src;
792
822
  }
793
- transactionMessage(tx) {
794
- const value = big(tx.value);
795
- if (value <= 0n) throw new UnsafeTransactionError("zero-value", "TON message value must be positive.");
823
+ /**
824
+ * A destination address with the checks every payment gets: workchain 0 or -1, and a friendly
825
+ * address's test-only flag matching the wallet's network. Used for a native message's `to` and a
826
+ * jetton transfer's recipient and response destination (round 3, L8).
827
+ */
828
+ checkedAddress(to) {
796
829
  let address;
797
830
  let bounceable = true;
798
831
  let testOnly;
799
- if (Address4.isFriendly(tx.to)) {
800
- const f = Address4.parseFriendly(tx.to);
832
+ if (Address4.isFriendly(to)) {
833
+ const f = Address4.parseFriendly(to);
801
834
  address = f.address;
802
835
  bounceable = f.isBounceable;
803
836
  testOnly = f.isTestOnly;
804
837
  } else {
805
- address = Address4.parse(tx.to);
838
+ address = Address4.parse(to);
806
839
  }
807
840
  if (address.workChain !== 0 && address.workChain !== -1) throw new UnsafeTransactionError("exotic-workchain", `Refusing to send to workchain ${address.workChain}.`);
808
841
  const testnet = this._config.network === "testnet";
809
842
  if (testOnly !== void 0 && testOnly !== testnet) {
810
843
  throw new UnsafeTransactionError("network-mismatch", testOnly ? "Test-only address on a mainnet wallet." : "Mainnet address on a testnet wallet.");
811
844
  }
845
+ return { address, bounceable };
846
+ }
847
+ transactionMessage(tx) {
848
+ const value = big(tx.value);
849
+ if (value <= 0n) throw new UnsafeTransactionError("zero-value", "TON message value must be positive.");
850
+ const { address, bounceable } = this.checkedAddress(tx.to);
812
851
  const body = tx.body instanceof Cell4 ? tx.body : typeof tx.body === "string" ? tx.body === "" ? void 0 : commentCell(tx.body) : tx.bodyBoc ? Cell4.fromBase64(tx.bodyBoc) : void 0;
813
852
  const init = this.stateInitOf(tx.stateInit);
814
853
  const message = internal({ to: address, value, bounce: tx.bounceable ?? bounceable, ...body ? { body } : {}, ...init ? { init } : {} });
@@ -817,14 +856,15 @@ var WalletAccountReadOnlyTon = class {
817
856
  async jettonTransferMessage(o) {
818
857
  const amount = big(o.amount);
819
858
  if (amount <= 0n) throw new UnsafeTransactionError("zero-value", "Jetton amount must be positive.");
820
- const recipient = Address4.parse(o.recipient);
859
+ const recipient = this.checkedAddress(o.recipient).address;
860
+ const response = o.responseDestination ? this.checkedAddress(o.responseDestination).address : void 0;
821
861
  const jw = await this.getJettonWalletAddress(o.token);
822
862
  await this.jettonWalletData(o.token, jw);
823
863
  const forwardTonAmount = o.forwardTonAmount !== void 0 ? big(o.forwardTonAmount) : 1n;
824
864
  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.");
825
865
  const payload = o.forwardPayload ?? o.memo;
826
866
  const forwardPayload = payload === void 0 ? null : typeof payload === "string" ? commentCell(payload) : payload;
827
- const body = beginCell3().storeUint(JETTON_TRANSFER_OP2, 32).storeUint(o.queryId !== void 0 ? BigInt(o.queryId) : randomQueryId(), 64).storeCoins(amount).storeAddress(recipient).storeAddress(o.responseDestination ? Address4.parse(o.responseDestination) : this._address).storeMaybeRef(o.customPayload ?? null).storeCoins(forwardTonAmount).storeMaybeRef(forwardPayload).endCell();
867
+ const body = beginCell3().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();
828
868
  const attached = o.attachedValue !== void 0 ? big(o.attachedValue) : (this._config.jettonAttachedValue !== void 0 ? big(this._config.jettonAttachedValue) : DEFAULT_JETTON_ATTACHED) + forwardTonAmount;
829
869
  return { message: internal({ to: jw, value: attached, bounce: true, body }) };
830
870
  }
@@ -1081,7 +1121,22 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
1081
1121
  * Checks → seqno → preflight → signed body. The preflight runs on the
1082
1122
  * zero-signed body (ignore_chksig), so nothing is signed before it passes.
1083
1123
  */
1124
+ /**
1125
+ * Refuse send modes that drain the wallet — 128 (the whole remaining balance) and 32 (the account
1126
+ * destroyed once empty) — unless `allowDrainingTransactions`. Checked locally, before any seqno or
1127
+ * emulation, so it holds with `simulate: false` too (round 3, M11); the emulation check stays.
1128
+ */
1129
+ assertNoDrain(modes) {
1130
+ if (this._config.allowDrainingTransactions === true) return;
1131
+ const found = modes.flatMap(drainModes);
1132
+ if (found.length) throw new UnsafeTransactionError("drain", `Refusing a message with send mode ${found.includes("carries-all-balance") ? "128 (it carries the whole remaining balance)" : "32 (it destroys the wallet account once empty)"}; set allowDrainingTransactions to override.`);
1133
+ }
1134
+ /** The send modes of a prebuilt V5R1 body; an unreadable body is left to the emulation and the node. */
1135
+ bodyModes(body) {
1136
+ return parseV5R1Body(body)?.messages.map((m) => m.mode) ?? [];
1137
+ }
1084
1138
  async signMessages(messages, limit, op) {
1139
+ this.assertNoDrain(messages.map((m) => sendModeOf(m.mode)));
1085
1140
  const want = messages.reduce((sum, m) => sum + (m.message.info.type === "internal" ? m.message.info.value.coins : 0n), 0n);
1086
1141
  if (want > 0n) {
1087
1142
  const balance = await this.getBalance();
@@ -1113,6 +1168,7 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
1113
1168
  /** Sign without broadcasting. Returns the external-message BODY as base64 BoC (pass it back to sendTransaction as a Cell). */
1114
1169
  async signTransaction(tx, options = {}) {
1115
1170
  if (tx instanceof Cell5) {
1171
+ this.assertNoDrain(this.bodyModes(tx));
1116
1172
  await this.preflight(tx, feeLimit(this._config.transactionMaxFee, options), "transaction");
1117
1173
  return tx.toBoc().toString("base64");
1118
1174
  }
@@ -1122,12 +1178,19 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
1122
1178
  async broadcast(body) {
1123
1179
  const rpc = this.requireRpc("send transactions");
1124
1180
  const init = await this.initIfNeeded();
1125
- await rpc.sendBoc(v5r1ExternalMessage(this._address, body, init));
1126
- return body.hash().toString("hex");
1181
+ const hash = body.hash().toString("hex");
1182
+ try {
1183
+ await rpc.sendBoc(v5r1ExternalMessage(this._address, body, init));
1184
+ } catch (e) {
1185
+ if (isAnsweredRefusal(e)) throw e;
1186
+ throw new BroadcastUnknownError(hash, e);
1187
+ }
1188
+ return hash;
1127
1189
  }
1128
1190
  /** Sign and broadcast; `hash` is the body hash (hex) — resolve it with getTransactionReceipt. */
1129
1191
  async sendTransaction(tx, options = {}) {
1130
1192
  this.requireRpc("send transactions");
1193
+ if (tx instanceof Cell5) this.assertNoDrain(this.bodyModes(tx));
1131
1194
  const { body, fee } = tx instanceof Cell5 ? { body: tx, fee: await this.preflight(tx, feeLimit(this._config.transactionMaxFee, options), "transaction") } : await this.signMessages(this.toMessages(tx), feeLimit(this._config.transactionMaxFee, options), "transaction");
1132
1195
  return { hash: await this.broadcast(body), fee };
1133
1196
  }
@@ -1151,7 +1214,7 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
1151
1214
 
1152
1215
  // src/manager.ts
1153
1216
  import { LocalSigner as LocalSigner2, SignerError as SignerError2, WalletManager, isSeedSigner, zeroize } from "@anywalletkit/core";
1154
- import { mnemonicToPrivateKey } from "@ton/crypto";
1217
+ import { mnemonicToPrivateKey, mnemonicValidate } from "@ton/crypto";
1155
1218
  var TON_BASE_PATH = "m/44'/607'";
1156
1219
  var TON_MNEMONIC_PATH = "ton-mnemonic";
1157
1220
  var WalletManagerTon = class _WalletManagerTon extends WalletManager {
@@ -1166,6 +1229,7 @@ var WalletManagerTon = class _WalletManagerTon extends WalletManager {
1166
1229
  static async fromTonMnemonic(words, config = {}) {
1167
1230
  const list = (Array.isArray(words) ? words : words.trim().split(/\s+/)).map((w) => w.toLowerCase());
1168
1231
  if (list.length !== 24) throw new Error("A TON mnemonic has 24 words.");
1232
+ if (!await mnemonicValidate(list)) throw new Error("Not a valid TON mnemonic: check the words (a typo would open a different, empty wallet).");
1169
1233
  const pair = await mnemonicToPrivateKey(list);
1170
1234
  const privateKey = new Uint8Array(pair.secretKey.subarray(0, 32));
1171
1235
  const publicKey = new Uint8Array(pair.publicKey);
@@ -1232,6 +1296,7 @@ var WalletManagerTon = class _WalletManagerTon extends WalletManager {
1232
1296
  // src/index.ts
1233
1297
  installBufferGlobal();
1234
1298
  export {
1299
+ BroadcastUnknownError,
1235
1300
  CAIP2,
1236
1301
  DEFAULT_SEND_MODE,
1237
1302
  DEFAULT_TONAPI,
@@ -1265,6 +1330,7 @@ export {
1265
1330
  emulateViaTonapi,
1266
1331
  encodeCursor as encodeHistoryCursor,
1267
1332
  installBufferGlobal,
1333
+ isAnsweredRefusal,
1268
1334
  mapActions as mapTonapiActions,
1269
1335
  mapTonapiEvent,
1270
1336
  mapToncenterTransaction,