@anywalletkit/wallet-ton 0.2.4 → 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/README.md +29 -5
- package/dist/index.cjs +201 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +121 -4
- package/dist/index.d.ts +121 -4
- package/dist/index.js +198 -20
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
package/dist/index.js
CHANGED
|
@@ -30,6 +30,13 @@ var UnsafeTransactionError = class extends SignerError {
|
|
|
30
30
|
this.code = code;
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
|
+
var MessageIsTransactionError = class extends SignerError {
|
|
34
|
+
code = "message-is-transaction";
|
|
35
|
+
constructor() {
|
|
36
|
+
super("This message has the shape of a transaction (a 32-byte TON hash, or bytes naming this key as a Solana signer) and is not signed; use signTonProof / signTonConnectData for TON Connect, or signTransaction so it can be reviewed.");
|
|
37
|
+
this.name = "MessageIsTransactionError";
|
|
38
|
+
}
|
|
39
|
+
};
|
|
33
40
|
var SimulationError = class extends Error {
|
|
34
41
|
exitCode;
|
|
35
42
|
aborted;
|
|
@@ -40,6 +47,17 @@ var SimulationError = class extends Error {
|
|
|
40
47
|
this.aborted = details.aborted ?? false;
|
|
41
48
|
}
|
|
42
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
|
+
};
|
|
43
61
|
|
|
44
62
|
// src/toncenter.ts
|
|
45
63
|
import { NetworkError } from "@anywalletkit/core";
|
|
@@ -77,12 +95,15 @@ function tonCenterEndpoint(endpoint, opts = {}) {
|
|
|
77
95
|
}
|
|
78
96
|
if (!res.ok || json.ok !== true) {
|
|
79
97
|
const detail = typeof json.error === "string" ? json.error : JSON.stringify(json.error ?? json);
|
|
80
|
-
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 });
|
|
81
99
|
}
|
|
82
100
|
return json.result;
|
|
83
101
|
}
|
|
84
102
|
};
|
|
85
103
|
}
|
|
104
|
+
function isAnsweredRefusal(e) {
|
|
105
|
+
return e?.answered === true;
|
|
106
|
+
}
|
|
86
107
|
function createTonRpc(config) {
|
|
87
108
|
const target = config.tonClient ?? config.provider;
|
|
88
109
|
if (target === void 0) return void 0;
|
|
@@ -95,14 +116,17 @@ function createTonRpc(config) {
|
|
|
95
116
|
return {
|
|
96
117
|
async call(method, params) {
|
|
97
118
|
let lastError;
|
|
119
|
+
let answered = true;
|
|
98
120
|
for (let i = 0; i < attempts; i++) {
|
|
99
121
|
try {
|
|
100
122
|
return await list[i % list.length].call(method, params);
|
|
101
123
|
} catch (e) {
|
|
102
124
|
lastError = e;
|
|
125
|
+
answered &&= isAnsweredRefusal(e);
|
|
103
126
|
}
|
|
104
127
|
}
|
|
105
|
-
|
|
128
|
+
const err = new NetworkError(`All TON endpoints failed for ${method}`, { cause: lastError });
|
|
129
|
+
throw answered ? Object.assign(err, { answered: true }) : err;
|
|
106
130
|
}
|
|
107
131
|
};
|
|
108
132
|
}
|
|
@@ -638,6 +662,19 @@ function describeOne(i, to, value, body, hasStateInit, bounceableOverride, warni
|
|
|
638
662
|
return item;
|
|
639
663
|
}
|
|
640
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
|
+
}
|
|
641
678
|
function describeMessageRelaxed(i, m, warnings, ctx = {}) {
|
|
642
679
|
if (m.info.type !== "internal") {
|
|
643
680
|
warnings.push({ code: "unknown-body", message: "not an internal message", index: i });
|
|
@@ -659,7 +696,7 @@ function describeTransaction(tx, ctx = {}) {
|
|
|
659
696
|
return { kind: "contract-call", items, warnings };
|
|
660
697
|
}
|
|
661
698
|
external2 = { seqno: parsed.seqno, validUntil: parsed.validUntil, walletId: parsed.walletId, signed: parsed.signed };
|
|
662
|
-
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)));
|
|
663
700
|
} else {
|
|
664
701
|
const list = Array.isArray(tx) ? tx : [tx];
|
|
665
702
|
list.forEach((t, i) => {
|
|
@@ -677,7 +714,7 @@ function describeTransaction(tx, ctx = {}) {
|
|
|
677
714
|
} catch {
|
|
678
715
|
warnings.push({ code: "unknown-body", message: "bodyBoc is not a valid BoC", index: i });
|
|
679
716
|
}
|
|
680
|
-
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));
|
|
681
718
|
});
|
|
682
719
|
}
|
|
683
720
|
const kinds = new Set(items.map((x) => x.kind));
|
|
@@ -783,25 +820,34 @@ var WalletAccountReadOnlyTon = class {
|
|
|
783
820
|
if (src instanceof Cell4) return loadStateInit(src.beginParse());
|
|
784
821
|
return src;
|
|
785
822
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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) {
|
|
789
829
|
let address;
|
|
790
830
|
let bounceable = true;
|
|
791
831
|
let testOnly;
|
|
792
|
-
if (Address4.isFriendly(
|
|
793
|
-
const f = Address4.parseFriendly(
|
|
832
|
+
if (Address4.isFriendly(to)) {
|
|
833
|
+
const f = Address4.parseFriendly(to);
|
|
794
834
|
address = f.address;
|
|
795
835
|
bounceable = f.isBounceable;
|
|
796
836
|
testOnly = f.isTestOnly;
|
|
797
837
|
} else {
|
|
798
|
-
address = Address4.parse(
|
|
838
|
+
address = Address4.parse(to);
|
|
799
839
|
}
|
|
800
840
|
if (address.workChain !== 0 && address.workChain !== -1) throw new UnsafeTransactionError("exotic-workchain", `Refusing to send to workchain ${address.workChain}.`);
|
|
801
841
|
const testnet = this._config.network === "testnet";
|
|
802
842
|
if (testOnly !== void 0 && testOnly !== testnet) {
|
|
803
843
|
throw new UnsafeTransactionError("network-mismatch", testOnly ? "Test-only address on a mainnet wallet." : "Mainnet address on a testnet wallet.");
|
|
804
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);
|
|
805
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;
|
|
806
852
|
const init = this.stateInitOf(tx.stateInit);
|
|
807
853
|
const message = internal({ to: address, value, bounce: tx.bounceable ?? bounceable, ...body ? { body } : {}, ...init ? { init } : {} });
|
|
@@ -810,14 +856,15 @@ var WalletAccountReadOnlyTon = class {
|
|
|
810
856
|
async jettonTransferMessage(o) {
|
|
811
857
|
const amount = big(o.amount);
|
|
812
858
|
if (amount <= 0n) throw new UnsafeTransactionError("zero-value", "Jetton amount must be positive.");
|
|
813
|
-
const recipient =
|
|
859
|
+
const recipient = this.checkedAddress(o.recipient).address;
|
|
860
|
+
const response = o.responseDestination ? this.checkedAddress(o.responseDestination).address : void 0;
|
|
814
861
|
const jw = await this.getJettonWalletAddress(o.token);
|
|
815
862
|
await this.jettonWalletData(o.token, jw);
|
|
816
863
|
const forwardTonAmount = o.forwardTonAmount !== void 0 ? big(o.forwardTonAmount) : 1n;
|
|
817
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.");
|
|
818
865
|
const payload = o.forwardPayload ?? o.memo;
|
|
819
866
|
const forwardPayload = payload === void 0 ? null : typeof payload === "string" ? commentCell(payload) : payload;
|
|
820
|
-
const body = beginCell3().storeUint(JETTON_TRANSFER_OP2, 32).storeUint(o.queryId !== void 0 ? BigInt(o.queryId) : randomQueryId(), 64).storeCoins(amount).storeAddress(recipient).storeAddress(
|
|
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();
|
|
821
868
|
const attached = o.attachedValue !== void 0 ? big(o.attachedValue) : (this._config.jettonAttachedValue !== void 0 ? big(this._config.jettonAttachedValue) : DEFAULT_JETTON_ATTACHED) + forwardTonAmount;
|
|
822
869
|
return { message: internal({ to: jw, value: attached, bounce: true, body }) };
|
|
823
870
|
}
|
|
@@ -935,8 +982,75 @@ function randomQueryId() {
|
|
|
935
982
|
|
|
936
983
|
// src/account.ts
|
|
937
984
|
import { LocalSigner, feeLimit, requireCurve } from "@anywalletkit/core";
|
|
985
|
+
import { Buffer as Buffer5 } from "buffer";
|
|
938
986
|
import { Cell as Cell5 } from "@ton/core";
|
|
987
|
+
|
|
988
|
+
// src/tonconnect.ts
|
|
989
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
990
|
+
import { Buffer as Buffer4 } from "buffer";
|
|
991
|
+
var utf82 = (s) => new TextEncoder().encode(s);
|
|
992
|
+
var int32BE = (n) => {
|
|
993
|
+
const b = new Uint8Array(4);
|
|
994
|
+
new DataView(b.buffer).setInt32(0, n, false);
|
|
995
|
+
return b;
|
|
996
|
+
};
|
|
997
|
+
var uint32 = (n, littleEndian) => {
|
|
998
|
+
const b = new Uint8Array(4);
|
|
999
|
+
new DataView(b.buffer).setUint32(0, n, littleEndian);
|
|
1000
|
+
return b;
|
|
1001
|
+
};
|
|
1002
|
+
var uint64 = (n, littleEndian) => {
|
|
1003
|
+
const b = new Uint8Array(8);
|
|
1004
|
+
new DataView(b.buffer).setBigUint64(0, BigInt(n), littleEndian);
|
|
1005
|
+
return b;
|
|
1006
|
+
};
|
|
1007
|
+
var concat = (...parts) => {
|
|
1008
|
+
const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
|
|
1009
|
+
let o = 0;
|
|
1010
|
+
for (const p of parts) {
|
|
1011
|
+
out.set(p, o);
|
|
1012
|
+
o += p.length;
|
|
1013
|
+
}
|
|
1014
|
+
return out;
|
|
1015
|
+
};
|
|
1016
|
+
function checkCommon(address, domain, timestamp) {
|
|
1017
|
+
if (address.hash.length !== 32) throw new RangeError("TON address hash must be 32 bytes.");
|
|
1018
|
+
if (!Number.isInteger(address.workChain)) throw new TypeError("TON address workChain must be an integer.");
|
|
1019
|
+
if (typeof domain !== "string" || domain.length === 0) throw new TypeError("TON Connect domain must be a non-empty string.");
|
|
1020
|
+
if (typeof timestamp !== "number" || !Number.isSafeInteger(timestamp) || timestamp < 0) throw new TypeError("TON Connect timestamp must be a non-negative integer (unix seconds).");
|
|
1021
|
+
}
|
|
1022
|
+
function base64Bytes(raw) {
|
|
1023
|
+
const s = raw.replace(/[\t\n\f\r ]/g, "");
|
|
1024
|
+
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(s) || s.replace(/=+$/, "").length % 4 === 1) throw new TypeError("signData binary payload must be base64.");
|
|
1025
|
+
return new Uint8Array(Buffer4.from(s, "base64"));
|
|
1026
|
+
}
|
|
1027
|
+
function tonProofDigest(address, request) {
|
|
1028
|
+
checkCommon(address, request.domain, request.timestamp);
|
|
1029
|
+
if (typeof request.payload !== "string") throw new TypeError("ton_proof payload must be a string.");
|
|
1030
|
+
const domain = utf82(request.domain);
|
|
1031
|
+
const inner = concat(utf82("ton-proof-item-v2/"), int32BE(address.workChain), address.hash, uint32(domain.length, true), domain, uint64(request.timestamp, true), utf82(request.payload));
|
|
1032
|
+
return sha256(concat(new Uint8Array([255, 255]), utf82("ton-connect"), sha256(inner)));
|
|
1033
|
+
}
|
|
1034
|
+
function tonSignDataDigest(address, request) {
|
|
1035
|
+
checkCommon(address, request.domain, request.timestamp);
|
|
1036
|
+
const p = request.payload;
|
|
1037
|
+
let payload;
|
|
1038
|
+
if (p?.type === "text" && typeof p.text === "string") payload = utf82(p.text);
|
|
1039
|
+
else if (p?.type === "binary" && typeof p.bytes === "string") payload = base64Bytes(p.bytes);
|
|
1040
|
+
else throw new TypeError('signData payload must be { type: "text", text } or { type: "binary", bytes }; cell payloads are not supported.');
|
|
1041
|
+
const domain = utf82(request.domain);
|
|
1042
|
+
return sha256(concat(new Uint8Array([255, 255]), utf82("ton-connect/sign-data/"), int32BE(address.workChain), address.hash, uint32(domain.length, false), domain, uint64(request.timestamp, false), utf82(p.type === "text" ? "txt" : "bin"), uint32(payload.length, false), payload));
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// src/account.ts
|
|
939
1046
|
var toHex = (u8) => Array.from(u8, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1047
|
+
var carriesKey = (bytes, key) => {
|
|
1048
|
+
outer: for (let i = 0; i + key.length <= bytes.length; i++) {
|
|
1049
|
+
for (let j = 0; j < key.length; j++) if (bytes[i + j] !== key[j]) continue outer;
|
|
1050
|
+
return true;
|
|
1051
|
+
}
|
|
1052
|
+
return false;
|
|
1053
|
+
};
|
|
940
1054
|
var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon {
|
|
941
1055
|
/** seqno and fees come from the network before signing. */
|
|
942
1056
|
needsNetwork = true;
|
|
@@ -960,11 +1074,46 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
|
|
|
960
1074
|
const m = /^m\/44'\/607'\/(\d+)'$/.exec(this.path);
|
|
961
1075
|
return m ? Number(m[1]) : void 0;
|
|
962
1076
|
}
|
|
963
|
-
/**
|
|
1077
|
+
/**
|
|
1078
|
+
* Hex ed25519 signature over the raw message bytes. A 32-byte message — string or bytes — is
|
|
1079
|
+
* refused with {@link MessageIsTransactionError}: the wallet contract checks a transfer's
|
|
1080
|
+
* signature over the 32-byte hash of its signing cell, so those bytes could be one. Bytes that
|
|
1081
|
+
* carry this account's public key are refused as well: one ed25519 key (an external signer) may
|
|
1082
|
+
* also be a Solana wallet, and a Solana transaction names its signer's key. TON Connect goes
|
|
1083
|
+
* through {@link signTonProof} and {@link signTonConnectData}.
|
|
1084
|
+
*/
|
|
964
1085
|
async sign(message) {
|
|
965
1086
|
const bytes = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
1087
|
+
if (bytes.length === 32 || carriesKey(bytes, this.publicKey)) throw new MessageIsTransactionError();
|
|
966
1088
|
return toHex(await this.signer.sign(bytes, { scheme: "ed25519", context: "ton-message" }));
|
|
967
1089
|
}
|
|
1090
|
+
/**
|
|
1091
|
+
* TON Connect `ton_proof` for this wallet's address: the `proof` object of the connect reply. The
|
|
1092
|
+
* digest is built here (0xffff ‖ "ton-connect" ‖ …), so no request can turn it into a transfer.
|
|
1093
|
+
* `domain` is the host the wallet saw the request come from.
|
|
1094
|
+
*/
|
|
1095
|
+
async signTonProof(request) {
|
|
1096
|
+
const digest = tonProofDigest(this._address, request);
|
|
1097
|
+
const signature = await this.signer.sign(digest, { scheme: "ed25519", context: "ton-proof" });
|
|
1098
|
+
return {
|
|
1099
|
+
timestamp: request.timestamp,
|
|
1100
|
+
domain: { lengthBytes: new TextEncoder().encode(request.domain).length, value: request.domain },
|
|
1101
|
+
payload: request.payload,
|
|
1102
|
+
signature: Buffer5.from(signature).toString("base64")
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
/** TON Connect `signData` (text / binary) for this wallet's address: the reply the dApp gets. */
|
|
1106
|
+
async signTonConnectData(request) {
|
|
1107
|
+
const digest = tonSignDataDigest(this._address, request);
|
|
1108
|
+
const signature = await this.signer.sign(digest, { scheme: "ed25519", context: "ton-sign-data" });
|
|
1109
|
+
return {
|
|
1110
|
+
signature: Buffer5.from(signature).toString("base64"),
|
|
1111
|
+
address: this._address.toRawString(),
|
|
1112
|
+
timestamp: request.timestamp,
|
|
1113
|
+
domain: request.domain,
|
|
1114
|
+
payload: request.payload.type === "text" ? { type: "text", text: request.payload.text } : { type: "binary", bytes: request.payload.bytes }
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
968
1117
|
checkMaxFee(fee, limit, op) {
|
|
969
1118
|
if (limit !== void 0 && fee > BigInt(limit)) throw new Error(`Exceeded maximum fee cost for ${op} operation.`);
|
|
970
1119
|
}
|
|
@@ -972,7 +1121,22 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
|
|
|
972
1121
|
* Checks → seqno → preflight → signed body. The preflight runs on the
|
|
973
1122
|
* zero-signed body (ignore_chksig), so nothing is signed before it passes.
|
|
974
1123
|
*/
|
|
975
|
-
|
|
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
|
+
}
|
|
1138
|
+
async signMessages(messages, limit, op) {
|
|
1139
|
+
this.assertNoDrain(messages.map((m) => sendModeOf(m.mode)));
|
|
976
1140
|
const want = messages.reduce((sum, m) => sum + (m.message.info.type === "internal" ? m.message.info.value.coins : 0n), 0n);
|
|
977
1141
|
if (want > 0n) {
|
|
978
1142
|
const balance = await this.getBalance();
|
|
@@ -1004,29 +1168,37 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
|
|
|
1004
1168
|
/** Sign without broadcasting. Returns the external-message BODY as base64 BoC (pass it back to sendTransaction as a Cell). */
|
|
1005
1169
|
async signTransaction(tx, options = {}) {
|
|
1006
1170
|
if (tx instanceof Cell5) {
|
|
1171
|
+
this.assertNoDrain(this.bodyModes(tx));
|
|
1007
1172
|
await this.preflight(tx, feeLimit(this._config.transactionMaxFee, options), "transaction");
|
|
1008
1173
|
return tx.toBoc().toString("base64");
|
|
1009
1174
|
}
|
|
1010
|
-
const { body } = await this.
|
|
1175
|
+
const { body } = await this.signMessages(this.toMessages(tx), feeLimit(this._config.transactionMaxFee, options), "transaction");
|
|
1011
1176
|
return body.toBoc().toString("base64");
|
|
1012
1177
|
}
|
|
1013
1178
|
async broadcast(body) {
|
|
1014
1179
|
const rpc = this.requireRpc("send transactions");
|
|
1015
1180
|
const init = await this.initIfNeeded();
|
|
1016
|
-
|
|
1017
|
-
|
|
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;
|
|
1018
1189
|
}
|
|
1019
1190
|
/** Sign and broadcast; `hash` is the body hash (hex) — resolve it with getTransactionReceipt. */
|
|
1020
1191
|
async sendTransaction(tx, options = {}) {
|
|
1021
1192
|
this.requireRpc("send transactions");
|
|
1022
|
-
|
|
1193
|
+
if (tx instanceof Cell5) this.assertNoDrain(this.bodyModes(tx));
|
|
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");
|
|
1023
1195
|
return { hash: await this.broadcast(body), fee };
|
|
1024
1196
|
}
|
|
1025
1197
|
/** TON (no `token`) or jetton transfer. */
|
|
1026
1198
|
async transfer(o, writeOptions = {}) {
|
|
1027
1199
|
this.requireRpc("transfer tokens");
|
|
1028
1200
|
const message = o.token ? await this.jettonTransferMessage(o) : this.transactionMessage({ to: o.recipient, value: o.amount, ...o.memo !== void 0 ? { body: o.memo } : {} });
|
|
1029
|
-
const { body, fee } = await this.
|
|
1201
|
+
const { body, fee } = await this.signMessages([message], feeLimit(this._config.transferMaxFee, writeOptions), "transfer");
|
|
1030
1202
|
return { hash: await this.broadcast(body), fee };
|
|
1031
1203
|
}
|
|
1032
1204
|
/** The same wallet as a {@link WalletAccountReadOnlyTon}. */
|
|
@@ -1042,7 +1214,7 @@ var WalletAccountTon = class _WalletAccountTon extends WalletAccountReadOnlyTon
|
|
|
1042
1214
|
|
|
1043
1215
|
// src/manager.ts
|
|
1044
1216
|
import { LocalSigner as LocalSigner2, SignerError as SignerError2, WalletManager, isSeedSigner, zeroize } from "@anywalletkit/core";
|
|
1045
|
-
import { mnemonicToPrivateKey } from "@ton/crypto";
|
|
1217
|
+
import { mnemonicToPrivateKey, mnemonicValidate } from "@ton/crypto";
|
|
1046
1218
|
var TON_BASE_PATH = "m/44'/607'";
|
|
1047
1219
|
var TON_MNEMONIC_PATH = "ton-mnemonic";
|
|
1048
1220
|
var WalletManagerTon = class _WalletManagerTon extends WalletManager {
|
|
@@ -1057,6 +1229,7 @@ var WalletManagerTon = class _WalletManagerTon extends WalletManager {
|
|
|
1057
1229
|
static async fromTonMnemonic(words, config = {}) {
|
|
1058
1230
|
const list = (Array.isArray(words) ? words : words.trim().split(/\s+/)).map((w) => w.toLowerCase());
|
|
1059
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).");
|
|
1060
1233
|
const pair = await mnemonicToPrivateKey(list);
|
|
1061
1234
|
const privateKey = new Uint8Array(pair.secretKey.subarray(0, 32));
|
|
1062
1235
|
const publicKey = new Uint8Array(pair.publicKey);
|
|
@@ -1123,6 +1296,7 @@ var WalletManagerTon = class _WalletManagerTon extends WalletManager {
|
|
|
1123
1296
|
// src/index.ts
|
|
1124
1297
|
installBufferGlobal();
|
|
1125
1298
|
export {
|
|
1299
|
+
BroadcastUnknownError,
|
|
1126
1300
|
CAIP2,
|
|
1127
1301
|
DEFAULT_SEND_MODE,
|
|
1128
1302
|
DEFAULT_TONAPI,
|
|
@@ -1130,6 +1304,7 @@ export {
|
|
|
1130
1304
|
JETTON_TRANSFER_OP,
|
|
1131
1305
|
MAINNET_GLOBAL_ID,
|
|
1132
1306
|
MAX_ACTIONS,
|
|
1307
|
+
MessageIsTransactionError,
|
|
1133
1308
|
SEND_MODE,
|
|
1134
1309
|
SimulationError,
|
|
1135
1310
|
TESTNET_GLOBAL_ID,
|
|
@@ -1155,6 +1330,7 @@ export {
|
|
|
1155
1330
|
emulateViaTonapi,
|
|
1156
1331
|
encodeCursor as encodeHistoryCursor,
|
|
1157
1332
|
installBufferGlobal,
|
|
1333
|
+
isAnsweredRefusal,
|
|
1158
1334
|
mapActions as mapTonapiActions,
|
|
1159
1335
|
mapTonapiEvent,
|
|
1160
1336
|
mapToncenterTransaction,
|
|
@@ -1168,6 +1344,8 @@ export {
|
|
|
1168
1344
|
runEmulation,
|
|
1169
1345
|
tonCenterEndpoint,
|
|
1170
1346
|
tonExplorerTxUrl,
|
|
1347
|
+
tonProofDigest,
|
|
1348
|
+
tonSignDataDigest,
|
|
1171
1349
|
v5r1Address,
|
|
1172
1350
|
v5r1Code,
|
|
1173
1351
|
v5r1ExternalMessage,
|