@anywalletkit/wallet-tron 0.0.0-stage → 0.2.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/LICENSE +202 -0
- package/README.md +103 -2
- package/dist/chunk-4XN4VJDG.js +389 -0
- package/dist/chunk-4XN4VJDG.js.map +1 -0
- package/dist/history.cjs +394 -0
- package/dist/history.cjs.map +1 -0
- package/dist/history.d.cts +160 -0
- package/dist/history.d.ts +160 -0
- package/dist/history.js +21 -0
- package/dist/history.js.map +1 -0
- package/dist/index.cjs +1845 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +636 -0
- package/dist/index.d.ts +636 -0
- package/dist/index.js +1467 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1845 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
CAIP2_GENESIS: () => CAIP2_GENESIS,
|
|
24
|
+
CONTRACT_TYPES: () => CONTRACT_TYPES,
|
|
25
|
+
DEFAULT_FEE_LIMIT_SUN: () => DEFAULT_FEE_LIMIT_SUN,
|
|
26
|
+
FEE_LIMIT_MARGIN: () => FEE_LIMIT_MARGIN,
|
|
27
|
+
HistoryUnavailableError: () => HistoryUnavailableError,
|
|
28
|
+
MAX_EXPIRATION_MS: () => MAX_EXPIRATION_MS,
|
|
29
|
+
MAX_MEMO_BYTES: () => MAX_MEMO_BYTES,
|
|
30
|
+
MEMO_FIELD: () => MEMO_FIELD,
|
|
31
|
+
NonCanonicalError: () => NonCanonicalError,
|
|
32
|
+
SimulationError: () => SimulationError,
|
|
33
|
+
TRANSFER_TOPIC: () => TRANSFER_TOPIC,
|
|
34
|
+
TRON_ACCOUNT_BRANCH: () => TRON_ACCOUNT_BRANCH,
|
|
35
|
+
TRON_BASE_PATH: () => TRON_BASE_PATH,
|
|
36
|
+
UnsafeTransactionError: () => UnsafeTransactionError,
|
|
37
|
+
WalletAccountReadOnlyTron: () => WalletAccountReadOnlyTron,
|
|
38
|
+
WalletAccountTron: () => WalletAccountTron,
|
|
39
|
+
WalletManagerTron: () => WalletManagerTron,
|
|
40
|
+
ZERO_ADDRESS: () => ZERO_ADDRESS,
|
|
41
|
+
ZERO_ADDRESS_HEX: () => ZERO_ADDRESS_HEX,
|
|
42
|
+
addressFromPublicKey: () => addressFromPublicKey,
|
|
43
|
+
assertNodeBuild: () => assertNodeBuild,
|
|
44
|
+
attachMemo: () => attachMemo,
|
|
45
|
+
bandwidthPoints: () => bandwidthPoints,
|
|
46
|
+
createClient: () => createClient,
|
|
47
|
+
createTronHistory: () => createTronHistory,
|
|
48
|
+
decodeAddressWord: () => decodeAddressWord,
|
|
49
|
+
decodeHistoryCursor: () => decodeCursor,
|
|
50
|
+
decodeRawData: () => decodeRawData,
|
|
51
|
+
decodeRevertReason: () => decodeRevertReason,
|
|
52
|
+
decodeUint: () => decodeUint,
|
|
53
|
+
default: () => WalletManagerTron,
|
|
54
|
+
describeTransaction: () => describeTransaction,
|
|
55
|
+
encodeHistoryCursor: () => encodeCursor,
|
|
56
|
+
encodeMemoRecord: () => encodeMemoRecord,
|
|
57
|
+
encodeParameters: () => encodeParameters,
|
|
58
|
+
encodeRawData: () => encodeRawData,
|
|
59
|
+
hashMessage: () => hashMessage,
|
|
60
|
+
hexToText: () => hexToText,
|
|
61
|
+
isContractCall: () => isContractCall,
|
|
62
|
+
isDecodedContractType: () => isDecodedContractType,
|
|
63
|
+
isHexAddress: () => isHexAddress,
|
|
64
|
+
isPrebuilt: () => isPrebuilt,
|
|
65
|
+
mapHistoryTransactionRow: () => mapTransactionRow,
|
|
66
|
+
mapHistoryTrc20Only: () => mapTrc20Only,
|
|
67
|
+
memoBytes: () => memoBytes,
|
|
68
|
+
rawMismatch: () => rawMismatch,
|
|
69
|
+
readFields: () => readFields,
|
|
70
|
+
recoverMessageAddress: () => recoverMessageAddress,
|
|
71
|
+
selector: () => selector,
|
|
72
|
+
splitCalldata: () => splitCalldata,
|
|
73
|
+
toBase58Address: () => toBase58Address,
|
|
74
|
+
toHexAddress: () => toHexAddress,
|
|
75
|
+
tronExplorerTxUrl: () => tronExplorerTxUrl,
|
|
76
|
+
verifyRawTransaction: () => verifyRawTransaction
|
|
77
|
+
});
|
|
78
|
+
module.exports = __toCommonJS(src_exports);
|
|
79
|
+
|
|
80
|
+
// src/types.ts
|
|
81
|
+
var CAIP2_GENESIS = {
|
|
82
|
+
mainnet: "tron:0x2b6653dc",
|
|
83
|
+
shasta: "tron:0x94a9059e",
|
|
84
|
+
nile: "tron:0xcd8690dc"
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// src/errors.ts
|
|
88
|
+
var UnsafeTransactionError = class extends Error {
|
|
89
|
+
code;
|
|
90
|
+
constructor(code, message) {
|
|
91
|
+
super(message);
|
|
92
|
+
this.name = "UnsafeTransactionError";
|
|
93
|
+
this.code = code;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
var SimulationError = class extends Error {
|
|
97
|
+
reason;
|
|
98
|
+
energy;
|
|
99
|
+
constructor(reason, energy = 0) {
|
|
100
|
+
super(`Transaction simulation failed: ${reason}`);
|
|
101
|
+
this.name = "SimulationError";
|
|
102
|
+
this.reason = reason;
|
|
103
|
+
this.energy = energy;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// src/address.ts
|
|
108
|
+
var import_secp256k1 = require("@noble/curves/secp256k1.js");
|
|
109
|
+
var import_sha3 = require("@noble/hashes/sha3.js");
|
|
110
|
+
var import_sha2 = require("@noble/hashes/sha2.js");
|
|
111
|
+
var import_utils = require("@noble/hashes/utils.js");
|
|
112
|
+
var import_base = require("@scure/base");
|
|
113
|
+
var b58check = (0, import_base.createBase58check)(import_sha2.sha256);
|
|
114
|
+
var ADDRESS_PREFIX = 65;
|
|
115
|
+
function addressFromPublicKey(compressed) {
|
|
116
|
+
const xy = import_secp256k1.secp256k1.Point.fromBytes(compressed).toBytes(false).slice(1);
|
|
117
|
+
const hash = (0, import_sha3.keccak_256)(xy);
|
|
118
|
+
const bytes = new Uint8Array(21);
|
|
119
|
+
bytes[0] = ADDRESS_PREFIX;
|
|
120
|
+
bytes.set(hash.slice(12), 1);
|
|
121
|
+
return b58check.encode(bytes);
|
|
122
|
+
}
|
|
123
|
+
function isHexAddress(a) {
|
|
124
|
+
return /^(0x)?41[0-9a-fA-F]{40}$/.test(a);
|
|
125
|
+
}
|
|
126
|
+
function toHexAddress(address) {
|
|
127
|
+
if (isHexAddress(address)) return address.replace(/^0x/, "").toLowerCase();
|
|
128
|
+
const bytes = b58check.decode(address);
|
|
129
|
+
if (bytes.length !== 21 || bytes[0] !== ADDRESS_PREFIX) throw new Error(`Invalid Tron address: ${address}`);
|
|
130
|
+
return (0, import_utils.bytesToHex)(bytes);
|
|
131
|
+
}
|
|
132
|
+
function toBase58Address(address) {
|
|
133
|
+
if (!isHexAddress(address)) {
|
|
134
|
+
toHexAddress(address);
|
|
135
|
+
return address;
|
|
136
|
+
}
|
|
137
|
+
return b58check.encode((0, import_utils.hexToBytes)(address.replace(/^0x/, "")));
|
|
138
|
+
}
|
|
139
|
+
function toEvmHex(address) {
|
|
140
|
+
return toHexAddress(address).slice(2);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/abi.ts
|
|
144
|
+
var import_sha32 = require("@noble/hashes/sha3.js");
|
|
145
|
+
var import_utils2 = require("@noble/hashes/utils.js");
|
|
146
|
+
function selector(signature) {
|
|
147
|
+
return (0, import_utils2.bytesToHex)((0, import_sha32.keccak_256)(new TextEncoder().encode(signature))).slice(0, 8);
|
|
148
|
+
}
|
|
149
|
+
function encodeParameters(params) {
|
|
150
|
+
return params.map(({ type, value }) => {
|
|
151
|
+
if (type === "address") return toEvmHex(String(value)).padStart(64, "0");
|
|
152
|
+
if (/^u?int\d*$/.test(type)) {
|
|
153
|
+
let v = BigInt(value);
|
|
154
|
+
if (v < 0n) v = (1n << 256n) + v;
|
|
155
|
+
return v.toString(16).padStart(64, "0");
|
|
156
|
+
}
|
|
157
|
+
if (type === "bool") return (value ? "1" : "0").padStart(64, "0");
|
|
158
|
+
if (type === "bytes32") return String(value).replace(/^0x/, "").padEnd(64, "0");
|
|
159
|
+
throw new Error(`encodeParameters: unsupported type ${type}`);
|
|
160
|
+
}).join("");
|
|
161
|
+
}
|
|
162
|
+
function decodeUint(hex) {
|
|
163
|
+
const h = hex.replace(/^0x/, "");
|
|
164
|
+
return h ? BigInt("0x" + h) : 0n;
|
|
165
|
+
}
|
|
166
|
+
function decodeAddressWord(word) {
|
|
167
|
+
return "41" + word.replace(/^0x/, "").padStart(64, "0").slice(24);
|
|
168
|
+
}
|
|
169
|
+
function splitCalldata(data) {
|
|
170
|
+
const h = data.replace(/^0x/, "").toLowerCase();
|
|
171
|
+
const selector2 = h.slice(0, 8);
|
|
172
|
+
const words = [];
|
|
173
|
+
for (let i = 8; i + 64 <= h.length; i += 64) words.push(h.slice(i, i + 64));
|
|
174
|
+
return { selector: selector2, words };
|
|
175
|
+
}
|
|
176
|
+
function decodeRevertReason(hex) {
|
|
177
|
+
if (!hex) return void 0;
|
|
178
|
+
const h = hex.replace(/^0x/, "");
|
|
179
|
+
if (!h) return void 0;
|
|
180
|
+
const { selector: selector2, words } = splitCalldata(h);
|
|
181
|
+
if (selector2 === "08c379a0" && words.length >= 2) {
|
|
182
|
+
const len = Number(decodeUint(words[1]));
|
|
183
|
+
const start = 8 + 64 * 2;
|
|
184
|
+
const bytes = h.slice(start, start + len * 2);
|
|
185
|
+
try {
|
|
186
|
+
return new TextDecoder().decode(Uint8Array.from(bytes.match(/.{2}/g) ?? [], (b) => parseInt(b, 16)));
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (selector2 === "4e487b71" && words.length >= 1) return `Panic(0x${decodeUint(words[0]).toString(16)})`;
|
|
191
|
+
return h;
|
|
192
|
+
}
|
|
193
|
+
function hexToText(hex) {
|
|
194
|
+
const h = hex.replace(/^0x/, "");
|
|
195
|
+
if (!/^[0-9a-fA-F]*$/.test(h) || h.length % 2) return hex;
|
|
196
|
+
try {
|
|
197
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(Uint8Array.from(h.match(/.{2}/g) ?? [], (b) => parseInt(b, 16)));
|
|
198
|
+
} catch {
|
|
199
|
+
return hex;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/client.ts
|
|
204
|
+
var import_core = require("@anywalletkit/core");
|
|
205
|
+
function httpTransport(base, headers) {
|
|
206
|
+
const root = base.replace(/\/+$/, "");
|
|
207
|
+
return async (path, body) => {
|
|
208
|
+
const res = await fetch(`${root}${path}`, {
|
|
209
|
+
method: "POST",
|
|
210
|
+
headers: { "content-type": "application/json", accept: "application/json", ...headers },
|
|
211
|
+
body: JSON.stringify(body)
|
|
212
|
+
});
|
|
213
|
+
if (!res.ok) throw new import_core.NetworkError(`${path}: HTTP ${res.status}`, { provider: root });
|
|
214
|
+
return res.json();
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function createClient(config) {
|
|
218
|
+
if (config.provider === void 0) return void 0;
|
|
219
|
+
const list = (Array.isArray(config.provider) ? config.provider : [config.provider]).map(
|
|
220
|
+
(p) => typeof p === "string" ? httpTransport(p, config.headers) : p
|
|
221
|
+
);
|
|
222
|
+
if (list.length === 0) throw new Error("The 'provider' option cannot be set to an empty list.");
|
|
223
|
+
if (list.length === 1) return list[0];
|
|
224
|
+
const attempts = list.length + (config.retries ?? 3);
|
|
225
|
+
return async (path, body) => {
|
|
226
|
+
let lastError;
|
|
227
|
+
for (let i = 0; i < attempts; i++) {
|
|
228
|
+
try {
|
|
229
|
+
return await list[i % list.length](path, body);
|
|
230
|
+
} catch (e) {
|
|
231
|
+
lastError = e;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
throw new import_core.NetworkError(`All Tron providers failed for ${path}`, { cause: lastError });
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function assertNoError(res, what) {
|
|
238
|
+
const r = res;
|
|
239
|
+
const hexMsg = (m) => m && /^[0-9a-f]+$/i.test(m) ? Buffer_from_hex(m) : m;
|
|
240
|
+
if (r?.Error) throw new Error(`${what}: ${r.Error}`);
|
|
241
|
+
if (r?.result && typeof r.result === "object" && r.result.result === false) throw new Error(`${what}: ${r.result.code ?? ""} ${hexMsg(r.result.message) ?? ""}`.trim());
|
|
242
|
+
if (r?.code && r.code !== "SUCCESS" && !("txID" in r)) throw new Error(`${what}: ${r.code} ${hexMsg(r.message) ?? ""}`.trim());
|
|
243
|
+
}
|
|
244
|
+
function Buffer_from_hex(hex) {
|
|
245
|
+
try {
|
|
246
|
+
return new TextDecoder().decode(Uint8Array.from(hex.match(/.{2}/g) ?? [], (b) => parseInt(b, 16)));
|
|
247
|
+
} catch {
|
|
248
|
+
return hex;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/shape.ts
|
|
253
|
+
var isPrebuilt = (tx) => typeof tx === "object" && tx !== null && typeof tx.txID === "string";
|
|
254
|
+
var isContractCall = (tx) => typeof tx === "object" && tx !== null && typeof tx.functionSelector === "string";
|
|
255
|
+
|
|
256
|
+
// src/raw.ts
|
|
257
|
+
var import_sha22 = require("@noble/hashes/sha2.js");
|
|
258
|
+
var import_utils3 = require("@noble/hashes/utils.js");
|
|
259
|
+
var NonCanonicalError = class extends Error {
|
|
260
|
+
constructor(message) {
|
|
261
|
+
super(message);
|
|
262
|
+
this.name = "NonCanonicalError";
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
var MAX_VARINT_BYTES = 10;
|
|
266
|
+
var MAX_TAG_BYTES = 5;
|
|
267
|
+
function readVarint(bytes, at, what = "value") {
|
|
268
|
+
const max = what === "tag" ? MAX_TAG_BYTES : MAX_VARINT_BYTES;
|
|
269
|
+
let shift = 0n;
|
|
270
|
+
let value = 0n;
|
|
271
|
+
let i = at;
|
|
272
|
+
for (; ; ) {
|
|
273
|
+
if (i >= bytes.length) throw new Error("truncated varint");
|
|
274
|
+
const b = bytes[i++];
|
|
275
|
+
const n = i - at;
|
|
276
|
+
value |= BigInt(b & 127) << shift;
|
|
277
|
+
if ((b & 128) === 0) {
|
|
278
|
+
if (n > 1 && b === 0) throw new NonCanonicalError(`non-minimal ${what} varint`);
|
|
279
|
+
if (n === MAX_VARINT_BYTES && b > 1) throw new NonCanonicalError(`${what} varint exceeds 64 bits`);
|
|
280
|
+
if (what === "tag" && value >= 1n << 32n) throw new NonCanonicalError("tag exceeds 32 bits");
|
|
281
|
+
return { value, next: i };
|
|
282
|
+
}
|
|
283
|
+
if (n >= max) throw new NonCanonicalError(`${what} varint longer than ${max} bytes`);
|
|
284
|
+
shift += 7n;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function readFields(bytes) {
|
|
288
|
+
const out = [];
|
|
289
|
+
let i = 0;
|
|
290
|
+
while (i < bytes.length) {
|
|
291
|
+
const tag = readVarint(bytes, i, "tag");
|
|
292
|
+
i = tag.next;
|
|
293
|
+
const number = Number(tag.value >> 3n);
|
|
294
|
+
const wire = Number(tag.value & 7n);
|
|
295
|
+
if (number === 0) throw new Error("field 0");
|
|
296
|
+
switch (wire) {
|
|
297
|
+
case 0: {
|
|
298
|
+
const v = readVarint(bytes, i);
|
|
299
|
+
i = v.next;
|
|
300
|
+
out.push({ number, wire, value: v.value });
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
case 1: {
|
|
304
|
+
if (i + 8 > bytes.length) throw new Error("truncated fixed64");
|
|
305
|
+
out.push({ number, wire, value: bytes.subarray(i, i + 8) });
|
|
306
|
+
i += 8;
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
case 2: {
|
|
310
|
+
const len = readVarint(bytes, i, "length");
|
|
311
|
+
const start = len.next;
|
|
312
|
+
if (len.value > BigInt(bytes.length - start)) throw new Error("truncated length-delimited field");
|
|
313
|
+
const end = start + Number(len.value);
|
|
314
|
+
out.push({ number, wire, value: bytes.subarray(start, end) });
|
|
315
|
+
i = end;
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
case 5: {
|
|
319
|
+
if (i + 4 > bytes.length) throw new Error("truncated fixed32");
|
|
320
|
+
out.push({ number, wire, value: bytes.subarray(i, i + 4) });
|
|
321
|
+
i += 4;
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
default:
|
|
325
|
+
throw new Error(`unsupported wire type ${wire}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return out;
|
|
329
|
+
}
|
|
330
|
+
function assertSingular(fields, singular, what) {
|
|
331
|
+
const want = new Set(singular);
|
|
332
|
+
const seen = /* @__PURE__ */ new Set();
|
|
333
|
+
for (const f of fields) {
|
|
334
|
+
if (!want.has(f.number)) continue;
|
|
335
|
+
if (seen.has(f.number)) throw new NonCanonicalError(`${what}: field ${f.number} appears more than once`);
|
|
336
|
+
seen.add(f.number);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
var equalBytes = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
|
|
340
|
+
function encodeVarint(n) {
|
|
341
|
+
if (n < 0n) n = (1n << 64n) + n;
|
|
342
|
+
const out = [];
|
|
343
|
+
do {
|
|
344
|
+
let b = Number(n & 0x7fn);
|
|
345
|
+
n >>= 7n;
|
|
346
|
+
if (n !== 0n) b |= 128;
|
|
347
|
+
out.push(b);
|
|
348
|
+
} while (n !== 0n);
|
|
349
|
+
return out;
|
|
350
|
+
}
|
|
351
|
+
var tagOf = (number, wire) => encodeVarint(BigInt(number << 3 | wire));
|
|
352
|
+
var fieldVarint = (number, v) => [...tagOf(number, 0), ...encodeVarint(v)];
|
|
353
|
+
var fieldBytes = (number, v) => [...tagOf(number, 2), ...encodeVarint(BigInt(v.length)), ...v];
|
|
354
|
+
var CONTRACT_TYPES = {
|
|
355
|
+
0: "AccountCreateContract",
|
|
356
|
+
1: "TransferContract",
|
|
357
|
+
2: "TransferAssetContract",
|
|
358
|
+
3: "VoteAssetContract",
|
|
359
|
+
4: "VoteWitnessContract",
|
|
360
|
+
5: "WitnessCreateContract",
|
|
361
|
+
6: "AssetIssueContract",
|
|
362
|
+
8: "WitnessUpdateContract",
|
|
363
|
+
9: "ParticipateAssetIssueContract",
|
|
364
|
+
10: "AccountUpdateContract",
|
|
365
|
+
11: "FreezeBalanceContract",
|
|
366
|
+
12: "UnfreezeBalanceContract",
|
|
367
|
+
13: "WithdrawBalanceContract",
|
|
368
|
+
14: "UnfreezeAssetContract",
|
|
369
|
+
15: "UpdateAssetContract",
|
|
370
|
+
16: "ProposalCreateContract",
|
|
371
|
+
17: "ProposalApproveContract",
|
|
372
|
+
18: "ProposalDeleteContract",
|
|
373
|
+
19: "SetAccountIdContract",
|
|
374
|
+
20: "CustomContract",
|
|
375
|
+
30: "CreateSmartContract",
|
|
376
|
+
31: "TriggerSmartContract",
|
|
377
|
+
32: "GetContract",
|
|
378
|
+
33: "UpdateSettingContract",
|
|
379
|
+
41: "ExchangeCreateContract",
|
|
380
|
+
42: "ExchangeInjectContract",
|
|
381
|
+
43: "ExchangeWithdrawContract",
|
|
382
|
+
44: "ExchangeTransactionContract",
|
|
383
|
+
45: "UpdateEnergyLimitContract",
|
|
384
|
+
46: "AccountPermissionUpdateContract",
|
|
385
|
+
48: "ClearABIContract",
|
|
386
|
+
49: "UpdateBrokerageContract",
|
|
387
|
+
51: "ShieldedTransferContract",
|
|
388
|
+
52: "MarketSellAssetContract",
|
|
389
|
+
53: "MarketCancelOrderContract",
|
|
390
|
+
54: "FreezeBalanceV2Contract",
|
|
391
|
+
55: "UnfreezeBalanceV2Contract",
|
|
392
|
+
56: "WithdrawExpireUnfreezeContract",
|
|
393
|
+
57: "DelegateResourceContract",
|
|
394
|
+
58: "UnDelegateResourceContract",
|
|
395
|
+
59: "CancelAllUnfreezeV2Contract"
|
|
396
|
+
};
|
|
397
|
+
var CONTRACT_ENUM = Object.fromEntries(Object.entries(CONTRACT_TYPES).map(([n, name]) => [name, Number(n)]));
|
|
398
|
+
var TYPE_URL_PREFIX = "type.googleapis.com/protocol.";
|
|
399
|
+
var RESOURCES = ["BANDWIDTH", "ENERGY", "TRON_POWER"];
|
|
400
|
+
var PARAMETERS = {
|
|
401
|
+
TransferContract: { 1: ["owner_address", "address"], 2: ["to_address", "address"], 3: ["amount", "int"] },
|
|
402
|
+
TransferAssetContract: { 1: ["asset_name", "bytes"], 2: ["owner_address", "address"], 3: ["to_address", "address"], 4: ["amount", "int"] },
|
|
403
|
+
TriggerSmartContract: { 1: ["owner_address", "address"], 2: ["contract_address", "address"], 3: ["call_value", "int"], 4: ["data", "bytes"], 5: ["call_token_value", "int"], 6: ["token_id", "int"] },
|
|
404
|
+
FreezeBalanceV2Contract: { 1: ["owner_address", "address"], 2: ["frozen_balance", "int"], 3: ["resource", "resource"] },
|
|
405
|
+
UnfreezeBalanceV2Contract: { 1: ["owner_address", "address"], 2: ["unfreeze_balance", "int"], 3: ["resource", "resource"] },
|
|
406
|
+
FreezeBalanceContract: { 1: ["owner_address", "address"], 2: ["frozen_balance", "int"], 3: ["frozen_duration", "int"], 10: ["resource", "resource"], 15: ["receiver_address", "address"] },
|
|
407
|
+
UnfreezeBalanceContract: { 1: ["owner_address", "address"], 10: ["resource", "resource"], 15: ["receiver_address", "address"] },
|
|
408
|
+
WithdrawBalanceContract: { 1: ["owner_address", "address"] },
|
|
409
|
+
WithdrawExpireUnfreezeContract: { 1: ["owner_address", "address"] },
|
|
410
|
+
CancelAllUnfreezeV2Contract: { 1: ["owner_address", "address"] },
|
|
411
|
+
DelegateResourceContract: { 1: ["owner_address", "address"], 2: ["resource", "resource"], 3: ["balance", "int"], 4: ["receiver_address", "address"], 5: ["lock", "bool"], 6: ["lock_period", "int"] },
|
|
412
|
+
UnDelegateResourceContract: { 1: ["owner_address", "address"], 2: ["resource", "resource"], 3: ["balance", "int"], 4: ["receiver_address", "address"] }
|
|
413
|
+
};
|
|
414
|
+
var signed = (v) => v >= 1n << 63n ? v - (1n << 64n) : v;
|
|
415
|
+
var int = (v) => {
|
|
416
|
+
const s = signed(v);
|
|
417
|
+
return s <= BigInt(Number.MAX_SAFE_INTEGER) && s >= BigInt(Number.MIN_SAFE_INTEGER) ? Number(s) : s.toString();
|
|
418
|
+
};
|
|
419
|
+
var num = (v) => Number(signed(v).toString());
|
|
420
|
+
var asBytes = (f, what) => {
|
|
421
|
+
if (!(f.value instanceof Uint8Array) || f.wire !== 2) throw new Error(`${what}: expected bytes`);
|
|
422
|
+
return f.value;
|
|
423
|
+
};
|
|
424
|
+
var asInt = (f, what) => {
|
|
425
|
+
if (typeof f.value !== "bigint") throw new Error(`${what}: expected varint`);
|
|
426
|
+
return f.value;
|
|
427
|
+
};
|
|
428
|
+
function decodeParameter(type, bytes) {
|
|
429
|
+
const schema = PARAMETERS[type];
|
|
430
|
+
const value = {};
|
|
431
|
+
const undecoded = [];
|
|
432
|
+
const fields = readFields(bytes);
|
|
433
|
+
if (!schema) {
|
|
434
|
+
assertSingular(fields, [1], type);
|
|
435
|
+
const owner = fields.find((f) => f.number === 1 && f.wire === 2 && f.value.length === 21 && f.value[0] === 65);
|
|
436
|
+
if (owner) value.owner_address = (0, import_utils3.bytesToHex)(owner.value);
|
|
437
|
+
value.raw_parameter_hex = (0, import_utils3.bytesToHex)(bytes);
|
|
438
|
+
return { value, undecoded: [...new Set(fields.filter((f) => f !== owner).map((f) => f.number))] };
|
|
439
|
+
}
|
|
440
|
+
assertSingular(fields, Object.keys(schema).map(Number), type);
|
|
441
|
+
for (const f of fields) {
|
|
442
|
+
const entry = schema[f.number];
|
|
443
|
+
if (!entry) {
|
|
444
|
+
undecoded.push(f.number);
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
const [name, kind] = entry;
|
|
448
|
+
const what = `${type}.${name}`;
|
|
449
|
+
switch (kind) {
|
|
450
|
+
case "address": {
|
|
451
|
+
const b = asBytes(f, what);
|
|
452
|
+
if (b.length !== 21 || b[0] !== 65) throw new Error(`${what}: not a Tron address`);
|
|
453
|
+
value[name] = (0, import_utils3.bytesToHex)(b);
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
case "bytes":
|
|
457
|
+
value[name] = (0, import_utils3.bytesToHex)(asBytes(f, what));
|
|
458
|
+
break;
|
|
459
|
+
case "string":
|
|
460
|
+
value[name] = new TextDecoder().decode(asBytes(f, what));
|
|
461
|
+
break;
|
|
462
|
+
case "int":
|
|
463
|
+
value[name] = int(asInt(f, what));
|
|
464
|
+
break;
|
|
465
|
+
case "bool":
|
|
466
|
+
value[name] = asInt(f, what) !== 0n;
|
|
467
|
+
break;
|
|
468
|
+
case "resource": {
|
|
469
|
+
const r = num(asInt(f, what));
|
|
470
|
+
value[name] = RESOURCES[r] ?? `RESOURCE_${r}`;
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return { value, undecoded: [...new Set(undecoded)] };
|
|
476
|
+
}
|
|
477
|
+
var CONTRACT_FIELDS = [1, 2, 3, 4, 5];
|
|
478
|
+
function decodeContract(bytes) {
|
|
479
|
+
let enumType;
|
|
480
|
+
let typeUrl;
|
|
481
|
+
let parameter;
|
|
482
|
+
let permissionId;
|
|
483
|
+
const fields = readFields(bytes);
|
|
484
|
+
assertSingular(fields, CONTRACT_FIELDS, "Contract");
|
|
485
|
+
for (const f of fields) {
|
|
486
|
+
switch (f.number) {
|
|
487
|
+
case 1:
|
|
488
|
+
enumType = num(asInt(f, "Contract.type"));
|
|
489
|
+
break;
|
|
490
|
+
case 2: {
|
|
491
|
+
const any = readFields(asBytes(f, "Contract.parameter"));
|
|
492
|
+
assertSingular(any, [1, 2], "Contract.parameter");
|
|
493
|
+
for (const a of any) {
|
|
494
|
+
if (a.number === 1) typeUrl = new TextDecoder().decode(asBytes(a, "Any.type_url"));
|
|
495
|
+
else if (a.number === 2) parameter = asBytes(a, "Any.value");
|
|
496
|
+
else throw new Error(`Contract.parameter: unknown Any field ${a.number}`);
|
|
497
|
+
}
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
case 5:
|
|
501
|
+
permissionId = num(asInt(f, "Contract.Permission_id"));
|
|
502
|
+
break;
|
|
503
|
+
case 3:
|
|
504
|
+
case 4:
|
|
505
|
+
break;
|
|
506
|
+
// provider, ContractName: informational
|
|
507
|
+
default:
|
|
508
|
+
throw new Error(`Contract: unknown field ${f.number}`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
const fromUrl = typeUrl?.startsWith(TYPE_URL_PREFIX) ? typeUrl.slice(TYPE_URL_PREFIX.length) : void 0;
|
|
512
|
+
const fromEnum = enumType !== void 0 ? CONTRACT_TYPES[enumType] : void 0;
|
|
513
|
+
if (fromUrl && fromEnum && fromUrl !== fromEnum) throw new Error(`contract type_url ${fromUrl} disagrees with type ${fromEnum}`);
|
|
514
|
+
const type = fromUrl ?? fromEnum ?? (enumType !== void 0 ? `Unknown(${enumType})` : typeUrl ?? "Unknown");
|
|
515
|
+
const paramBytes = parameter ?? new Uint8Array();
|
|
516
|
+
const { value, undecoded } = decodeParameter(type, paramBytes);
|
|
517
|
+
if (PARAMETERS[type]) {
|
|
518
|
+
if (undecoded.length) throw new Error(`${type}: field(s) ${undecoded.join(", ")} are not part of the contract`);
|
|
519
|
+
if (!equalBytes(encodeParameter(type, value), paramBytes)) throw new NonCanonicalError(`${type}: parameter bytes are not the canonical encoding of what they decode to`);
|
|
520
|
+
}
|
|
521
|
+
const out = { type, parameter: { value, type_url: typeUrl ?? `${TYPE_URL_PREFIX}${type}` }, undecoded };
|
|
522
|
+
if (permissionId !== void 0) out.Permission_id = permissionId;
|
|
523
|
+
return out;
|
|
524
|
+
}
|
|
525
|
+
function isDecodedContractType(type) {
|
|
526
|
+
return PARAMETERS[type] !== void 0;
|
|
527
|
+
}
|
|
528
|
+
var RAW_SINGULAR = [1, 3, 4, 8, 10, 12, 14, 18];
|
|
529
|
+
function decodeRawData(raw) {
|
|
530
|
+
const bytes = typeof raw === "string" ? (0, import_utils3.hexToBytes)(raw.replace(/^0x/, "")) : raw;
|
|
531
|
+
const data = { contract: [] };
|
|
532
|
+
const unknown = [];
|
|
533
|
+
const fields = readFields(bytes);
|
|
534
|
+
assertSingular(fields, RAW_SINGULAR, "Transaction.raw");
|
|
535
|
+
for (const f of fields) {
|
|
536
|
+
switch (f.number) {
|
|
537
|
+
case 1:
|
|
538
|
+
data.ref_block_bytes = (0, import_utils3.bytesToHex)(asBytes(f, "ref_block_bytes"));
|
|
539
|
+
break;
|
|
540
|
+
case 3:
|
|
541
|
+
data.ref_block_num = num(asInt(f, "ref_block_num"));
|
|
542
|
+
break;
|
|
543
|
+
case 4:
|
|
544
|
+
data.ref_block_hash = (0, import_utils3.bytesToHex)(asBytes(f, "ref_block_hash"));
|
|
545
|
+
break;
|
|
546
|
+
case 8:
|
|
547
|
+
data.expiration = num(asInt(f, "expiration"));
|
|
548
|
+
break;
|
|
549
|
+
case 10:
|
|
550
|
+
data.data = (0, import_utils3.bytesToHex)(asBytes(f, "data"));
|
|
551
|
+
break;
|
|
552
|
+
case 11:
|
|
553
|
+
data.contract.push(decodeContract(asBytes(f, "contract")));
|
|
554
|
+
break;
|
|
555
|
+
case 14:
|
|
556
|
+
data.timestamp = num(asInt(f, "timestamp"));
|
|
557
|
+
break;
|
|
558
|
+
case 18:
|
|
559
|
+
data.fee_limit = num(asInt(f, "fee_limit"));
|
|
560
|
+
break;
|
|
561
|
+
case 9:
|
|
562
|
+
case 12:
|
|
563
|
+
break;
|
|
564
|
+
// auths, scripts: carried by the node, nothing the wallet acts on
|
|
565
|
+
default:
|
|
566
|
+
unknown.push(f.number);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return { raw_data: data, unknownFields: [...new Set(unknown)] };
|
|
570
|
+
}
|
|
571
|
+
function encodeParameter(type, value) {
|
|
572
|
+
const schema = PARAMETERS[type];
|
|
573
|
+
if (!schema) {
|
|
574
|
+
const hex = value.raw_parameter_hex;
|
|
575
|
+
if (typeof hex === "string") return (0, import_utils3.hexToBytes)(hex);
|
|
576
|
+
return typeof value.owner_address === "string" ? Uint8Array.from(fieldBytes(1, (0, import_utils3.hexToBytes)(toHexAddress(value.owner_address)))) : new Uint8Array();
|
|
577
|
+
}
|
|
578
|
+
const out = [];
|
|
579
|
+
for (const number of Object.keys(schema).map(Number).sort((a, b) => a - b)) {
|
|
580
|
+
const [name, kind] = schema[number];
|
|
581
|
+
const v = value[name];
|
|
582
|
+
if (v === void 0 || v === null) continue;
|
|
583
|
+
switch (kind) {
|
|
584
|
+
case "address":
|
|
585
|
+
out.push(...fieldBytes(number, (0, import_utils3.hexToBytes)(toHexAddress(String(v)))));
|
|
586
|
+
break;
|
|
587
|
+
case "bytes":
|
|
588
|
+
out.push(...fieldBytes(number, (0, import_utils3.hexToBytes)(String(v).replace(/^0x/, ""))));
|
|
589
|
+
break;
|
|
590
|
+
case "string":
|
|
591
|
+
out.push(...fieldBytes(number, new TextEncoder().encode(String(v))));
|
|
592
|
+
break;
|
|
593
|
+
case "int": {
|
|
594
|
+
const n = BigInt(v);
|
|
595
|
+
if (n !== 0n) out.push(...fieldVarint(number, n));
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
598
|
+
case "bool":
|
|
599
|
+
if (v) out.push(...fieldVarint(number, 1n));
|
|
600
|
+
break;
|
|
601
|
+
case "resource": {
|
|
602
|
+
const r = typeof v === "number" ? v : RESOURCES.indexOf(v);
|
|
603
|
+
if (r < 0) throw new Error(`unknown resource ${String(v)}`);
|
|
604
|
+
if (r !== 0) out.push(...fieldVarint(number, BigInt(r)));
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return Uint8Array.from(out);
|
|
610
|
+
}
|
|
611
|
+
function encodeRawData(raw) {
|
|
612
|
+
const out = [];
|
|
613
|
+
if (raw.ref_block_bytes) out.push(...fieldBytes(1, (0, import_utils3.hexToBytes)(raw.ref_block_bytes)));
|
|
614
|
+
if (raw.ref_block_num) out.push(...fieldVarint(3, BigInt(raw.ref_block_num)));
|
|
615
|
+
if (raw.ref_block_hash) out.push(...fieldBytes(4, (0, import_utils3.hexToBytes)(raw.ref_block_hash)));
|
|
616
|
+
if (raw.expiration) out.push(...fieldVarint(8, BigInt(raw.expiration)));
|
|
617
|
+
if (raw.data) out.push(...fieldBytes(10, (0, import_utils3.hexToBytes)(raw.data)));
|
|
618
|
+
for (const c of raw.contract ?? []) {
|
|
619
|
+
const type = c.type;
|
|
620
|
+
const enumType = CONTRACT_ENUM[type];
|
|
621
|
+
const typeUrl = c.parameter.type_url ?? `${TYPE_URL_PREFIX}${type}`;
|
|
622
|
+
const any = Uint8Array.from([...fieldBytes(1, new TextEncoder().encode(typeUrl)), ...fieldBytes(2, encodeParameter(type, c.parameter.value))]);
|
|
623
|
+
const contract = [];
|
|
624
|
+
if (enumType !== void 0 && enumType !== 0) contract.push(...fieldVarint(1, BigInt(enumType)));
|
|
625
|
+
contract.push(...fieldBytes(2, any));
|
|
626
|
+
const pid = c.Permission_id;
|
|
627
|
+
if (pid) contract.push(...fieldVarint(5, BigInt(pid)));
|
|
628
|
+
out.push(...fieldBytes(11, Uint8Array.from(contract)));
|
|
629
|
+
}
|
|
630
|
+
if (raw.timestamp) out.push(...fieldVarint(14, BigInt(raw.timestamp)));
|
|
631
|
+
if (raw.fee_limit) out.push(...fieldVarint(18, BigInt(raw.fee_limit)));
|
|
632
|
+
return Uint8Array.from(out);
|
|
633
|
+
}
|
|
634
|
+
var norm = (key, v) => {
|
|
635
|
+
if (v === void 0 || v === null) return "";
|
|
636
|
+
if (typeof v === "boolean") return v ? "1" : "";
|
|
637
|
+
if (typeof v === "bigint" || typeof v === "number") return v === 0 || v === 0n ? "" : v.toString();
|
|
638
|
+
const s = String(v);
|
|
639
|
+
if (key.endsWith("_address")) {
|
|
640
|
+
try {
|
|
641
|
+
return toHexAddress(s);
|
|
642
|
+
} catch {
|
|
643
|
+
return s.toLowerCase();
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
if (/^\d+$/.test(s)) return s === "0" ? "" : s;
|
|
647
|
+
return s.replace(/^0x/, "").toLowerCase();
|
|
648
|
+
};
|
|
649
|
+
function rawMismatch(json, decoded) {
|
|
650
|
+
if (!json) return null;
|
|
651
|
+
const top = ["ref_block_bytes", "ref_block_hash", "expiration", "timestamp", "fee_limit", "data"];
|
|
652
|
+
for (const k of top) if (norm(k, json[k]) !== norm(k, decoded[k])) return `raw_data.${k}: JSON says ${JSON.stringify(json[k] ?? null)}, bytes say ${JSON.stringify(decoded[k] ?? null)}`;
|
|
653
|
+
const jc = json.contract ?? [];
|
|
654
|
+
if (jc.length !== decoded.contract.length) return `raw_data.contract: JSON lists ${jc.length} contract(s), bytes carry ${decoded.contract.length}`;
|
|
655
|
+
for (let i = 0; i < jc.length; i++) {
|
|
656
|
+
const a = jc[i];
|
|
657
|
+
const b = decoded.contract[i];
|
|
658
|
+
if (a.type !== b.type) return `contract[${i}].type: JSON says ${a.type}, bytes say ${b.type}`;
|
|
659
|
+
const av = a.parameter?.value ?? {};
|
|
660
|
+
const bv = b.parameter.value;
|
|
661
|
+
const hasSchema = isDecodedContractType(b.type);
|
|
662
|
+
for (const key of /* @__PURE__ */ new Set([...Object.keys(av), ...Object.keys(bv)])) {
|
|
663
|
+
if (key === "raw_parameter_hex") continue;
|
|
664
|
+
if (!hasSchema && key !== "owner_address") continue;
|
|
665
|
+
if (norm(key, av[key]) !== norm(key, bv[key])) return `contract[${i}].${key}: JSON says ${JSON.stringify(av[key] ?? null)}, bytes say ${JSON.stringify(bv[key] ?? null)}`;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
function verifyRawTransaction(tx) {
|
|
671
|
+
const hex = typeof tx.raw_data_hex === "string" ? tx.raw_data_hex.replace(/^0x/, "") : "";
|
|
672
|
+
if (!hex || hex.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(hex)) throw new UnsafeTransactionError("raw-undecodable", "The transaction has no raw_data_hex; a Tron signature covers those bytes, so nothing else can be signed.");
|
|
673
|
+
const bytes = (0, import_utils3.hexToBytes)(hex);
|
|
674
|
+
const txID = (0, import_utils3.bytesToHex)((0, import_sha22.sha256)(bytes));
|
|
675
|
+
if (typeof tx.txID !== "string" || tx.txID.replace(/^0x/, "").toLowerCase() !== txID) {
|
|
676
|
+
throw new UnsafeTransactionError("txid-mismatch", `txID ${String(tx.txID)} is not sha256(raw_data_hex) (${txID}); refusing to sign a hash that does not belong to these bytes.`);
|
|
677
|
+
}
|
|
678
|
+
let decoded;
|
|
679
|
+
try {
|
|
680
|
+
decoded = decodeRawData(bytes);
|
|
681
|
+
} catch (e) {
|
|
682
|
+
if (e instanceof NonCanonicalError) throw new UnsafeTransactionError("raw-noncanonical", `raw_data_hex is not the canonical encoding of a Transaction.raw (${e.message}); the node could read these bytes differently from the wallet, so they are not signed.`);
|
|
683
|
+
throw new UnsafeTransactionError("raw-undecodable", `raw_data_hex is not a well-formed Transaction.raw (${e.message}); refusing to sign what cannot be read.`);
|
|
684
|
+
}
|
|
685
|
+
const mismatch = rawMismatch(tx.raw_data, decoded.raw_data);
|
|
686
|
+
if (mismatch) throw new UnsafeTransactionError("raw-mismatch", `The transaction's JSON does not match the bytes that would be signed \u2014 ${mismatch}.`);
|
|
687
|
+
return { ...decoded, bytes, txID };
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// src/describe.ts
|
|
691
|
+
var ZERO_ADDRESS = "T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb";
|
|
692
|
+
var ZERO_ADDRESS_HEX = "41" + "0".repeat(40);
|
|
693
|
+
var MAX_UINT256 = (1n << 256n) - 1n;
|
|
694
|
+
var UNLIMITED_THRESHOLD = 1n << 255n;
|
|
695
|
+
var SELECTORS = {
|
|
696
|
+
a9059cbb: "transfer",
|
|
697
|
+
"095ea7b3": "approve",
|
|
698
|
+
"23b872dd": "transferFrom"
|
|
699
|
+
};
|
|
700
|
+
var isZero = (hex) => hex.toLowerCase() === ZERO_ADDRESS_HEX;
|
|
701
|
+
var b58 = (a) => a ? toBase58Address(a) : void 0;
|
|
702
|
+
var isUnlimited = (v) => v === MAX_UINT256 || v >= UNLIMITED_THRESHOLD;
|
|
703
|
+
function warn(list, code, message) {
|
|
704
|
+
list.push({ code, message });
|
|
705
|
+
}
|
|
706
|
+
function describeCalldata(d, contract, data, warnings) {
|
|
707
|
+
const { selector: selector2, words } = splitCalldata(data);
|
|
708
|
+
d.selector = selector2;
|
|
709
|
+
d.contract = toBase58Address(contract);
|
|
710
|
+
const fn = SELECTORS[selector2];
|
|
711
|
+
if (fn === "transfer" && words.length >= 2) {
|
|
712
|
+
d.kind = "trc20-transfer";
|
|
713
|
+
d.token = d.contract;
|
|
714
|
+
d.to = toBase58Address(decodeAddressWord(words[0]));
|
|
715
|
+
d.amount = decodeUint(words[1]);
|
|
716
|
+
if (isZero(decodeAddressWord(words[0]))) warn(warnings, "zero-address", "Tokens would be sent to the zero address (burned).");
|
|
717
|
+
} else if (fn === "approve" && words.length >= 2) {
|
|
718
|
+
d.kind = "trc20-approve";
|
|
719
|
+
d.token = d.contract;
|
|
720
|
+
d.spender = toBase58Address(decodeAddressWord(words[0]));
|
|
721
|
+
d.amount = decodeUint(words[1]);
|
|
722
|
+
if (isUnlimited(d.amount)) warn(warnings, "unlimited-approve", `Unlimited TRC-20 approval for ${d.spender}.`);
|
|
723
|
+
} else {
|
|
724
|
+
d.kind = "contract-call";
|
|
725
|
+
if (fn === "transferFrom" && words.length >= 3) {
|
|
726
|
+
d.from = toBase58Address(decodeAddressWord(words[0]));
|
|
727
|
+
d.to = toBase58Address(decodeAddressWord(words[1]));
|
|
728
|
+
d.amount = decodeUint(words[2]);
|
|
729
|
+
d.token = d.contract;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
function describeContractCall(tx, warnings) {
|
|
734
|
+
const d = { kind: "contract-call", warnings };
|
|
735
|
+
let data = selector(tx.functionSelector);
|
|
736
|
+
try {
|
|
737
|
+
data += encodeParameters(tx.parameters ?? []);
|
|
738
|
+
} catch {
|
|
739
|
+
}
|
|
740
|
+
describeCalldata(d, tx.contractAddress, data, warnings);
|
|
741
|
+
if (tx.options?.callValue) d.callValue = BigInt(tx.options.callValue);
|
|
742
|
+
if (tx.options?.feeLimit !== void 0) d.feeLimit = tx.options.feeLimit;
|
|
743
|
+
return d;
|
|
744
|
+
}
|
|
745
|
+
function describeRaw(tx, warnings, ctx) {
|
|
746
|
+
const d = { kind: "unknown", warnings };
|
|
747
|
+
let raw;
|
|
748
|
+
let undecodedFields = [];
|
|
749
|
+
if (typeof tx.raw_data_hex === "string" && tx.raw_data_hex.length > 0) {
|
|
750
|
+
try {
|
|
751
|
+
const decoded = decodeRawData(tx.raw_data_hex);
|
|
752
|
+
raw = decoded.raw_data;
|
|
753
|
+
undecodedFields = [...decoded.unknownFields.map((n) => -n), ...decoded.raw_data.contract.flatMap((c) => isDecodedContractType(c.type) ? c.undecoded : [])];
|
|
754
|
+
} catch (e) {
|
|
755
|
+
warn(warnings, "raw-undecodable", `raw_data_hex is not a well-formed Transaction.raw (${e.message}); the wallet cannot say what this transaction does and will not sign it.`);
|
|
756
|
+
return d;
|
|
757
|
+
}
|
|
758
|
+
} else {
|
|
759
|
+
raw = tx.raw_data;
|
|
760
|
+
warn(warnings, "no-raw-hex", "The transaction carries no raw_data_hex: this description comes from the unsigned JSON copy and the wallet will not sign it.");
|
|
761
|
+
}
|
|
762
|
+
const contracts = raw?.contract ?? [];
|
|
763
|
+
if (contracts.length !== 1) warn(warnings, "multi-contract", `The transaction carries ${contracts.length} contracts; the wallet signs exactly one.`);
|
|
764
|
+
const contract = contracts[0];
|
|
765
|
+
const v = contract?.parameter?.value ?? {};
|
|
766
|
+
const from = b58(v.owner_address);
|
|
767
|
+
if (from) d.from = from;
|
|
768
|
+
switch (contract?.type) {
|
|
769
|
+
case "TransferContract": {
|
|
770
|
+
d.kind = "trx-transfer";
|
|
771
|
+
const to = toHexAddress(String(v.to_address));
|
|
772
|
+
d.to = toBase58Address(to);
|
|
773
|
+
d.amount = BigInt(v.amount ?? 0);
|
|
774
|
+
if (isZero(to)) warn(warnings, "zero-address", "TRX would be sent to the zero address (burned).");
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
case "TransferAssetContract": {
|
|
778
|
+
d.kind = "trc10-transfer";
|
|
779
|
+
const to = toHexAddress(String(v.to_address));
|
|
780
|
+
d.to = toBase58Address(to);
|
|
781
|
+
d.amount = BigInt(v.amount ?? 0);
|
|
782
|
+
if (typeof v.asset_name === "string") d.token = hexToText(v.asset_name);
|
|
783
|
+
if (isZero(to)) warn(warnings, "zero-address", "Tokens would be sent to the zero address (burned).");
|
|
784
|
+
break;
|
|
785
|
+
}
|
|
786
|
+
case "TriggerSmartContract": {
|
|
787
|
+
describeCalldata(d, String(v.contract_address), String(v.data ?? ""), warnings);
|
|
788
|
+
if (v.call_value) d.callValue = BigInt(v.call_value);
|
|
789
|
+
break;
|
|
790
|
+
}
|
|
791
|
+
case "FreezeBalanceV2Contract":
|
|
792
|
+
case "FreezeBalanceContract":
|
|
793
|
+
d.kind = "freeze";
|
|
794
|
+
d.amount = BigInt(v.frozen_balance ?? 0);
|
|
795
|
+
d.resource = v.resource ?? "BANDWIDTH";
|
|
796
|
+
break;
|
|
797
|
+
case "UnfreezeBalanceV2Contract":
|
|
798
|
+
case "UnfreezeBalanceContract":
|
|
799
|
+
d.kind = "unfreeze";
|
|
800
|
+
if (v.unfreeze_balance !== void 0) d.amount = BigInt(v.unfreeze_balance);
|
|
801
|
+
d.resource = v.resource ?? "BANDWIDTH";
|
|
802
|
+
break;
|
|
803
|
+
default:
|
|
804
|
+
d.kind = "unknown";
|
|
805
|
+
if (!contract) break;
|
|
806
|
+
if (isDecodedContractType(contract.type)) {
|
|
807
|
+
const fields = Object.entries(v).filter(([k]) => k !== "owner_address").map(([k, x]) => `${k}=${k.endsWith("_address") ? b58(String(x)) : String(x)}`);
|
|
808
|
+
warn(warnings, "undecoded", `${contract.type}: no summary for this contract type; decoded fields: ${fields.join(", ") || "none"}.`);
|
|
809
|
+
} else {
|
|
810
|
+
warn(warnings, "undecoded", `${contract.type}: this contract type is not decoded by the wallet \u2014 only its owner is known; its parameters are not shown, and the dApp's own description of them is not checked.`);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
if (contract?.type === "AccountPermissionUpdateContract") warn(warnings, "account-permission", "AccountPermissionUpdateContract changes which keys control this account \u2014 a signature here can hand the account to someone else.");
|
|
814
|
+
if (undecodedFields.length) warn(warnings, "undecoded", `${contract?.type ?? "transaction"}: field(s) ${undecodedFields.map((n) => n < 0 ? `raw.${-n}` : String(n)).join(", ")} were not decoded; the sheet cannot show their meaning.`);
|
|
815
|
+
if (raw?.fee_limit !== void 0) d.feeLimit = raw.fee_limit;
|
|
816
|
+
if (raw?.data) {
|
|
817
|
+
d.memo = hexToText(raw.data);
|
|
818
|
+
warn(warnings, "memo-fee", "A memo is attached: the network charges the memo fee (1 TRX by default).");
|
|
819
|
+
}
|
|
820
|
+
if (raw?.expiration !== void 0) {
|
|
821
|
+
d.expiration = raw.expiration;
|
|
822
|
+
if (raw.expiration <= (ctx.now ?? Date.now())) warn(warnings, "expired", "The transaction has already expired.");
|
|
823
|
+
}
|
|
824
|
+
return d;
|
|
825
|
+
}
|
|
826
|
+
function describeTransaction(tx, ctx = {}) {
|
|
827
|
+
const warnings = [];
|
|
828
|
+
let d;
|
|
829
|
+
if (isPrebuilt(tx)) d = describeRaw(tx, warnings, ctx);
|
|
830
|
+
else if (isContractCall(tx)) d = describeContractCall(tx, warnings);
|
|
831
|
+
else {
|
|
832
|
+
const { to, value } = tx;
|
|
833
|
+
const hex = toHexAddress(to);
|
|
834
|
+
d = { kind: "trx-transfer", warnings, to: toBase58Address(hex), amount: BigInt(value) };
|
|
835
|
+
if (isZero(hex)) warn(warnings, "zero-address", "TRX would be sent to the zero address (burned).");
|
|
836
|
+
}
|
|
837
|
+
const memo = !isPrebuilt(tx) ? tx.memo : void 0;
|
|
838
|
+
if (memo) {
|
|
839
|
+
d.memo = memo;
|
|
840
|
+
warn(warnings, "memo-fee", "A memo is attached: the network charges the memo fee (1 TRX by default).");
|
|
841
|
+
}
|
|
842
|
+
if (ctx.recipientExists === false && (d.kind === "trx-transfer" || d.kind === "trc20-transfer") && d.to) {
|
|
843
|
+
warn(warnings, "unactivated-recipient", d.kind === "trx-transfer" ? `${d.to} does not exist yet: the transfer also pays the account creation fee (1.1 TRX without staked bandwidth).` : `${d.to} does not exist yet: a fresh TRC-20 holder costs more energy.`);
|
|
844
|
+
}
|
|
845
|
+
return d;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// src/read-only-account.ts
|
|
849
|
+
var import_core2 = require("@anywalletkit/core");
|
|
850
|
+
var import_secp256k12 = require("@noble/curves/secp256k1.js");
|
|
851
|
+
var import_sha33 = require("@noble/hashes/sha3.js");
|
|
852
|
+
var import_utils6 = require("@noble/hashes/utils.js");
|
|
853
|
+
|
|
854
|
+
// src/build-check.ts
|
|
855
|
+
var import_utils4 = require("@noble/hashes/utils.js");
|
|
856
|
+
var TYPE_URL_PREFIX2 = "type.googleapis.com/protocol.";
|
|
857
|
+
var BUILD_FIELDS = [1, 3, 4, 8, 11, 14, 18];
|
|
858
|
+
var PARAM_KEYS = {
|
|
859
|
+
TransferContract: ["owner_address", "to_address", "amount"],
|
|
860
|
+
TriggerSmartContract: ["owner_address", "contract_address", "call_value", "data"]
|
|
861
|
+
};
|
|
862
|
+
var fail = (what) => {
|
|
863
|
+
throw new UnsafeTransactionError("build-mismatch", `The node built a different transaction than requested \u2014 ${what}. Nothing was signed.`);
|
|
864
|
+
};
|
|
865
|
+
var big = (v) => v === void 0 || v === null ? 0n : BigInt(v);
|
|
866
|
+
var sameAddress = (a, b) => {
|
|
867
|
+
try {
|
|
868
|
+
return typeof a === "string" && toHexAddress(a) === toHexAddress(b);
|
|
869
|
+
} catch {
|
|
870
|
+
return false;
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
function assertNodeBuild(tx, expected, memo) {
|
|
874
|
+
const v = verifyRawTransaction(tx);
|
|
875
|
+
const allowed = new Set(memo ? [...BUILD_FIELDS, 10] : BUILD_FIELDS);
|
|
876
|
+
const top = readFields(v.bytes);
|
|
877
|
+
const extra = top.filter((f) => !allowed.has(f.number)).map((f) => f.number);
|
|
878
|
+
if (extra.length) fail(`raw_data carries field(s) ${[...new Set(extra)].join(", ")}`);
|
|
879
|
+
const contracts = top.filter((f) => f.number === 11);
|
|
880
|
+
if (contracts.length !== 1) fail(`${contracts.length} contracts instead of one`);
|
|
881
|
+
const wrapper = readFields(contracts[0].value).filter((f) => f.number !== 1 && f.number !== 2).map((f) => f.number);
|
|
882
|
+
if (wrapper.length) fail(`the contract carries field(s) ${wrapper.join(", ")}${wrapper.includes(5) ? " (Permission_id)" : ""}`);
|
|
883
|
+
const c = v.raw_data.contract[0];
|
|
884
|
+
if (c.type !== expected.type) fail(`${c.type} instead of ${expected.type}`);
|
|
885
|
+
if (c.parameter.type_url !== TYPE_URL_PREFIX2 + expected.type) fail(`type_url ${c.parameter.type_url}`);
|
|
886
|
+
const p = c.parameter.value;
|
|
887
|
+
const unknownKeys = Object.keys(p).filter((k) => !PARAM_KEYS[expected.type].includes(k));
|
|
888
|
+
if (unknownKeys.length) fail(`${expected.type} carries ${unknownKeys.join(", ")}`);
|
|
889
|
+
if (!sameAddress(p.owner_address, expected.owner)) fail("the owner is not this account");
|
|
890
|
+
if (expected.type === "TransferContract") {
|
|
891
|
+
if (!sameAddress(p.to_address, expected.to)) fail(`recipient ${String(p.to_address)} instead of ${toHexAddress(expected.to)}`);
|
|
892
|
+
if (big(p.amount) !== expected.amount) fail(`amount ${String(p.amount)} instead of ${expected.amount}`);
|
|
893
|
+
if (v.raw_data.fee_limit !== void 0) fail(`a TRX transfer with fee_limit ${v.raw_data.fee_limit}`);
|
|
894
|
+
} else {
|
|
895
|
+
if (!sameAddress(p.contract_address, expected.contract)) fail(`contract ${String(p.contract_address)} instead of ${toHexAddress(expected.contract)}`);
|
|
896
|
+
const want = expected.data.replace(/^0x/, "").toLowerCase();
|
|
897
|
+
if (String(p.data ?? "").toLowerCase() !== want) fail("the calldata differs from the requested call");
|
|
898
|
+
if (big(p.call_value) !== expected.callValue) fail(`call_value ${String(p.call_value ?? 0)} instead of ${expected.callValue}`);
|
|
899
|
+
if (big(v.raw_data.fee_limit) !== expected.feeLimit) fail(`fee_limit ${String(v.raw_data.fee_limit ?? 0)} instead of ${expected.feeLimit}`);
|
|
900
|
+
}
|
|
901
|
+
const data = v.raw_data.data;
|
|
902
|
+
if (memo ? data !== (0, import_utils4.bytesToHex)(memo) : data !== void 0) fail(memo ? "the memo differs" : "an unrequested memo");
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// src/memo.ts
|
|
906
|
+
var import_sha23 = require("@noble/hashes/sha2.js");
|
|
907
|
+
var import_utils5 = require("@noble/hashes/utils.js");
|
|
908
|
+
var MEMO_FIELD = 10;
|
|
909
|
+
var MAX_MEMO_BYTES = 512;
|
|
910
|
+
function walkRecords(bytes) {
|
|
911
|
+
const out = [];
|
|
912
|
+
let i = 0;
|
|
913
|
+
const varint = () => {
|
|
914
|
+
let shift = 0n;
|
|
915
|
+
let v = 0n;
|
|
916
|
+
for (; ; ) {
|
|
917
|
+
if (i >= bytes.length) throw new Error("truncated varint");
|
|
918
|
+
const b = bytes[i++];
|
|
919
|
+
v |= BigInt(b & 127) << shift;
|
|
920
|
+
if ((b & 128) === 0) return v;
|
|
921
|
+
shift += 7n;
|
|
922
|
+
if (shift > 63n) throw new Error("varint too long");
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
while (i < bytes.length) {
|
|
926
|
+
const start = i;
|
|
927
|
+
const tag = varint();
|
|
928
|
+
const field = Number(tag >> 3n);
|
|
929
|
+
const wire = Number(tag & 7n);
|
|
930
|
+
if (field === 0) throw new Error("field 0");
|
|
931
|
+
switch (wire) {
|
|
932
|
+
case 0:
|
|
933
|
+
varint();
|
|
934
|
+
break;
|
|
935
|
+
case 1:
|
|
936
|
+
i += 8;
|
|
937
|
+
break;
|
|
938
|
+
case 2: {
|
|
939
|
+
const len = Number(varint());
|
|
940
|
+
i += len;
|
|
941
|
+
break;
|
|
942
|
+
}
|
|
943
|
+
case 5:
|
|
944
|
+
i += 4;
|
|
945
|
+
break;
|
|
946
|
+
default:
|
|
947
|
+
throw new Error(`unsupported wire type ${wire}`);
|
|
948
|
+
}
|
|
949
|
+
if (i > bytes.length) throw new Error("truncated record");
|
|
950
|
+
out.push({ field, start, end: i });
|
|
951
|
+
}
|
|
952
|
+
return out;
|
|
953
|
+
}
|
|
954
|
+
function encodeVarint2(n) {
|
|
955
|
+
const out = [];
|
|
956
|
+
do {
|
|
957
|
+
let b = n & 127;
|
|
958
|
+
n >>>= 7;
|
|
959
|
+
if (n !== 0) b |= 128;
|
|
960
|
+
out.push(b);
|
|
961
|
+
} while (n !== 0);
|
|
962
|
+
return out;
|
|
963
|
+
}
|
|
964
|
+
function memoBytes(memo, encoding = "utf8") {
|
|
965
|
+
if (encoding === "utf8") return new TextEncoder().encode(memo);
|
|
966
|
+
const h = memo.startsWith("0x") ? memo.slice(2) : memo;
|
|
967
|
+
if (!h || h.length % 2 || /[^0-9a-f]/i.test(h)) throw new UnsafeTransactionError("memo", "A hex memo must be non-empty, even-length hex.");
|
|
968
|
+
return (0, import_utils5.hexToBytes)(h.toLowerCase());
|
|
969
|
+
}
|
|
970
|
+
function encodeMemoRecord(memo, encoding = "utf8") {
|
|
971
|
+
const bytes = memoBytes(memo, encoding);
|
|
972
|
+
if (bytes.length === 0) throw new UnsafeTransactionError("memo", "A memo must not be empty.");
|
|
973
|
+
if (bytes.length > MAX_MEMO_BYTES) throw new UnsafeTransactionError("memo", `Memo is ${bytes.length} bytes; the limit is ${MAX_MEMO_BYTES}.`);
|
|
974
|
+
return Uint8Array.from([MEMO_FIELD << 3 | 2, ...encodeVarint2(bytes.length), ...bytes]);
|
|
975
|
+
}
|
|
976
|
+
function attachMemo(tx, memo, encoding = "utf8") {
|
|
977
|
+
if (tx.raw_data?.data) throw new UnsafeTransactionError("memo", "The transaction already carries a memo (raw_data.data).");
|
|
978
|
+
if (tx.signature?.length) throw new UnsafeTransactionError("memo", "Cannot attach a memo to an already signed transaction.");
|
|
979
|
+
const raw = (0, import_utils5.hexToBytes)(tx.raw_data_hex);
|
|
980
|
+
let records;
|
|
981
|
+
try {
|
|
982
|
+
records = walkRecords(raw);
|
|
983
|
+
} catch (e) {
|
|
984
|
+
throw new UnsafeTransactionError("memo", `raw_data_hex is not a well-formed protobuf message (${e.message}); refusing to attach a memo.`);
|
|
985
|
+
}
|
|
986
|
+
if (records.some((r) => r.field === MEMO_FIELD)) throw new UnsafeTransactionError("memo", "raw_data_hex already contains field 10 (data).");
|
|
987
|
+
const record = encodeMemoRecord(memo, encoding);
|
|
988
|
+
const after = records.find((r) => r.field > MEMO_FIELD);
|
|
989
|
+
const at = after ? after.start : raw.length;
|
|
990
|
+
const out = new Uint8Array(raw.length + record.length);
|
|
991
|
+
out.set(raw.subarray(0, at), 0);
|
|
992
|
+
out.set(record, at);
|
|
993
|
+
out.set(raw.subarray(at), at + record.length);
|
|
994
|
+
return {
|
|
995
|
+
...tx,
|
|
996
|
+
txID: (0, import_utils5.bytesToHex)((0, import_sha23.sha256)(out)),
|
|
997
|
+
raw_data_hex: (0, import_utils5.bytesToHex)(out),
|
|
998
|
+
raw_data: { ...tx.raw_data, data: (0, import_utils5.bytesToHex)(memoBytes(memo, encoding)) }
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// src/read-only-account.ts
|
|
1003
|
+
var DEFAULT_FEE_LIMIT_SUN = 15e6;
|
|
1004
|
+
var FEE_LIMIT_MARGIN = 1.2;
|
|
1005
|
+
var SIGNATURE_BYTES = 65;
|
|
1006
|
+
var RESULT_RESERVE_BYTES = 64;
|
|
1007
|
+
var varintLen = (n) => n < 128 ? 1 : n < 16384 ? 2 : 3;
|
|
1008
|
+
function bandwidthPoints(rawDataHex, signatures = 1) {
|
|
1009
|
+
const raw = Math.ceil(rawDataHex.replace(/^0x/, "").length / 2);
|
|
1010
|
+
return 1 + varintLen(raw) + raw + signatures * (1 + 1 + SIGNATURE_BYTES) + RESULT_RESERVE_BYTES;
|
|
1011
|
+
}
|
|
1012
|
+
var PARAM_DEFAULTS = {
|
|
1013
|
+
getMemoFee: 1000000n,
|
|
1014
|
+
getCreateAccountFee: 100000n,
|
|
1015
|
+
getCreateNewAccountFeeInSystemContract: 1000000n,
|
|
1016
|
+
getCreateNewAccountBandwidthRate: 1n
|
|
1017
|
+
};
|
|
1018
|
+
function hashMessage(message) {
|
|
1019
|
+
const bytes = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
1020
|
+
const prefix = new TextEncoder().encode(`TRON Signed Message:
|
|
1021
|
+
${bytes.length}`);
|
|
1022
|
+
const all = new Uint8Array(prefix.length + bytes.length);
|
|
1023
|
+
all.set(prefix, 0);
|
|
1024
|
+
all.set(bytes, prefix.length);
|
|
1025
|
+
return (0, import_sha33.keccak_256)(all);
|
|
1026
|
+
}
|
|
1027
|
+
function recoverMessageAddress(message, signature) {
|
|
1028
|
+
try {
|
|
1029
|
+
const sig = (0, import_utils6.hexToBytes)(signature.replace(/^0x/, ""));
|
|
1030
|
+
if (sig.length !== 65) return void 0;
|
|
1031
|
+
let v = sig[64];
|
|
1032
|
+
if (v >= 27) v -= 27;
|
|
1033
|
+
if (v > 1) return void 0;
|
|
1034
|
+
const recovered = new Uint8Array(65);
|
|
1035
|
+
recovered[0] = v;
|
|
1036
|
+
recovered.set(sig.slice(0, 64), 1);
|
|
1037
|
+
return addressFromPublicKey(import_secp256k12.secp256k1.recoverPublicKey(recovered, hashMessage(message), { prehash: false }));
|
|
1038
|
+
} catch {
|
|
1039
|
+
return void 0;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
var contractOf = (tx) => {
|
|
1043
|
+
let c = tx.raw_data?.contract?.[0];
|
|
1044
|
+
if (typeof tx.raw_data_hex === "string" && tx.raw_data_hex.length) {
|
|
1045
|
+
try {
|
|
1046
|
+
c = decodeRawData(tx.raw_data_hex).raw_data.contract[0];
|
|
1047
|
+
} catch {
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
return { type: c?.type, value: c?.parameter?.value ?? {} };
|
|
1051
|
+
};
|
|
1052
|
+
var WalletAccountReadOnlyTron = class {
|
|
1053
|
+
/** base58 T… */
|
|
1054
|
+
address;
|
|
1055
|
+
/** Hex form (`41…`). */
|
|
1056
|
+
addressHex;
|
|
1057
|
+
chain;
|
|
1058
|
+
_config;
|
|
1059
|
+
_client;
|
|
1060
|
+
constructor(address, config = {}) {
|
|
1061
|
+
this.address = toBase58Address(address);
|
|
1062
|
+
this.addressHex = toHexAddress(address);
|
|
1063
|
+
this._config = config;
|
|
1064
|
+
this._client = createClient(config);
|
|
1065
|
+
const net = config.network ?? "mainnet";
|
|
1066
|
+
this.chain = CAIP2_GENESIS[net] ?? (net.startsWith("tron:") ? net : `tron:${net}`);
|
|
1067
|
+
}
|
|
1068
|
+
requireClient(what) {
|
|
1069
|
+
if (!this._client) throw new import_core2.NetworkError(`The wallet must be connected to a provider to ${what}.`);
|
|
1070
|
+
return this._client;
|
|
1071
|
+
}
|
|
1072
|
+
async post(path, body, what) {
|
|
1073
|
+
const res = await this.requireClient(what)(path, body);
|
|
1074
|
+
assertNoError(res, what);
|
|
1075
|
+
return res;
|
|
1076
|
+
}
|
|
1077
|
+
/** The base58 address. */
|
|
1078
|
+
async getAddress() {
|
|
1079
|
+
return this.address;
|
|
1080
|
+
}
|
|
1081
|
+
/** TRX balance in sun; 0 for an account that does not exist yet. */
|
|
1082
|
+
async getBalance() {
|
|
1083
|
+
const acc = await this.post("/wallet/getaccount", { address: this.addressHex, visible: false }, "retrieve balances");
|
|
1084
|
+
return BigInt(acc.balance ?? 0);
|
|
1085
|
+
}
|
|
1086
|
+
/** Read-only contract call. Returns constant_result hex strings. */
|
|
1087
|
+
async constantCall(contract, functionSelector, parameters, what, extra = {}) {
|
|
1088
|
+
return this.post(
|
|
1089
|
+
"/wallet/triggerconstantcontract",
|
|
1090
|
+
{ owner_address: this.addressHex, contract_address: toHexAddress(contract), function_selector: functionSelector, parameter: encodeParameters(parameters), visible: false, ...extra },
|
|
1091
|
+
what
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
/** TRC-20 `balanceOf` in the token's base units. */
|
|
1095
|
+
async getTokenBalance(token) {
|
|
1096
|
+
const r = await this.constantCall(token, "balanceOf(address)", [{ type: "address", value: this.address }], "retrieve token balances");
|
|
1097
|
+
return decodeUint(r.constant_result?.[0] ?? "0");
|
|
1098
|
+
}
|
|
1099
|
+
/** Several TRC-20 balances in parallel, keyed by the tokens as given. */
|
|
1100
|
+
async getTokenBalances(tokens) {
|
|
1101
|
+
const out = {};
|
|
1102
|
+
await Promise.all(tokens.map(async (t) => {
|
|
1103
|
+
out[t] = await this.getTokenBalance(t);
|
|
1104
|
+
}));
|
|
1105
|
+
return out;
|
|
1106
|
+
}
|
|
1107
|
+
/** TRC-20 `allowance(owner, spender)` in base units. */
|
|
1108
|
+
async getAllowance(token, spender) {
|
|
1109
|
+
const r = await this.constantCall(token, "allowance(address,address)", [{ type: "address", value: this.address }, { type: "address", value: spender }], "read allowances");
|
|
1110
|
+
return decodeUint(r.constant_result?.[0] ?? "0");
|
|
1111
|
+
}
|
|
1112
|
+
/** Does an account exist on chain (`/wallet/getaccount` non-empty)? */
|
|
1113
|
+
async accountExists(address) {
|
|
1114
|
+
const acc = await this.post("/wallet/getaccount", { address: toHexAddress(address), visible: false }, "check the recipient account");
|
|
1115
|
+
return Object.keys(acc).length > 0;
|
|
1116
|
+
}
|
|
1117
|
+
// ── Building ────────────────────────────────────────────────────────────────
|
|
1118
|
+
/**
|
|
1119
|
+
* Ask the node for `raw_data`. Contract calls are simulated first so `fee_limit`
|
|
1120
|
+
* can be derived from the energy they need (×1.2, capped at `transactionMaxFee`)
|
|
1121
|
+
* when the caller gives none; the simulation rides along so nobody pays for a second one.
|
|
1122
|
+
*/
|
|
1123
|
+
async buildWithSimulation(tx) {
|
|
1124
|
+
if (isPrebuilt(tx)) return { built: tx };
|
|
1125
|
+
if (isContractCall(tx)) {
|
|
1126
|
+
const { contractAddress, functionSelector, parameters = [], options = {} } = tx;
|
|
1127
|
+
const sim = await this.simulateCall({ contractAddress, functionSelector, parameters, options });
|
|
1128
|
+
let feeLimit = options.feeLimit;
|
|
1129
|
+
if (feeLimit === void 0) {
|
|
1130
|
+
const energyPrice = Number(await this.getChainParameter("getEnergyFee"));
|
|
1131
|
+
feeLimit = sim.ok && sim.energy > 0 ? Math.ceil(sim.energy * energyPrice * FEE_LIMIT_MARGIN) : DEFAULT_FEE_LIMIT_SUN;
|
|
1132
|
+
const max = this._config.transactionMaxFee;
|
|
1133
|
+
if (max !== void 0 && feeLimit > Number(max)) feeLimit = Number(max);
|
|
1134
|
+
}
|
|
1135
|
+
const r = await this.post("/wallet/triggersmartcontract", {
|
|
1136
|
+
owner_address: this.addressHex,
|
|
1137
|
+
contract_address: toHexAddress(contractAddress),
|
|
1138
|
+
function_selector: functionSelector,
|
|
1139
|
+
parameter: encodeParameters(parameters),
|
|
1140
|
+
fee_limit: feeLimit,
|
|
1141
|
+
call_value: options.callValue ?? 0,
|
|
1142
|
+
visible: false
|
|
1143
|
+
}, "build a contract call");
|
|
1144
|
+
if (!r.transaction) throw new Error("triggersmartcontract returned no transaction");
|
|
1145
|
+
const built2 = tx.memo ? attachMemo(r.transaction, tx.memo, tx.memoEncoding) : r.transaction;
|
|
1146
|
+
assertNodeBuild(built2, {
|
|
1147
|
+
type: "TriggerSmartContract",
|
|
1148
|
+
owner: this.addressHex,
|
|
1149
|
+
contract: contractAddress,
|
|
1150
|
+
data: selector(functionSelector) + encodeParameters(parameters),
|
|
1151
|
+
callValue: BigInt(options.callValue ?? 0),
|
|
1152
|
+
feeLimit: BigInt(feeLimit)
|
|
1153
|
+
}, tx.memo ? memoBytes(tx.memo, tx.memoEncoding) : void 0);
|
|
1154
|
+
return { built: built2, sim };
|
|
1155
|
+
}
|
|
1156
|
+
const { to, value, memo, memoEncoding } = tx;
|
|
1157
|
+
const node = await this.post("/wallet/createtransaction", { owner_address: this.addressHex, to_address: toHexAddress(to), amount: Number(BigInt(value)), visible: false }, "build a transfer");
|
|
1158
|
+
const built = memo ? attachMemo(node, memo, memoEncoding) : node;
|
|
1159
|
+
assertNodeBuild(built, { type: "TransferContract", owner: this.addressHex, to, amount: BigInt(value) }, memo ? memoBytes(memo, memoEncoding) : void 0);
|
|
1160
|
+
return { built };
|
|
1161
|
+
}
|
|
1162
|
+
async buildTransaction(tx) {
|
|
1163
|
+
return (await this.buildWithSimulation(tx)).built;
|
|
1164
|
+
}
|
|
1165
|
+
// ── Simulation ──────────────────────────────────────────────────────────────
|
|
1166
|
+
/** Constant call by selector + params (unbuilt contract call). Never throws on a revert. */
|
|
1167
|
+
async simulateCall(tx) {
|
|
1168
|
+
return this.constantSimulation({
|
|
1169
|
+
owner_address: this.addressHex,
|
|
1170
|
+
contract_address: toHexAddress(tx.contractAddress),
|
|
1171
|
+
function_selector: tx.functionSelector,
|
|
1172
|
+
parameter: encodeParameters(tx.parameters ?? []),
|
|
1173
|
+
call_value: tx.options?.callValue ?? 0,
|
|
1174
|
+
visible: false
|
|
1175
|
+
});
|
|
1176
|
+
}
|
|
1177
|
+
async constantSimulation(body) {
|
|
1178
|
+
const res = await this.requireClient("simulate transactions")("/wallet/triggerconstantcontract", body);
|
|
1179
|
+
if (res?.Error) return { ok: false, energy: 0, reason: res.Error };
|
|
1180
|
+
const energy = res.energy_used ?? 0;
|
|
1181
|
+
if (res.result && res.result.result === false) {
|
|
1182
|
+
return { ok: false, energy, reason: `${res.result.code ?? "CONTRACT_VALIDATE_ERROR"}${res.result.message ? `: ${hexToText(res.result.message)}` : ""}` };
|
|
1183
|
+
}
|
|
1184
|
+
const ret = res.transaction?.ret?.[0];
|
|
1185
|
+
const failed = ret?.ret === "FAILED" || ret?.contractRet !== void 0 && ret.contractRet !== "SUCCESS";
|
|
1186
|
+
if (failed) {
|
|
1187
|
+
const reason = decodeRevertReason(res.constant_result?.[0]) ?? ret?.contractRet ?? "REVERT";
|
|
1188
|
+
const prefix = ret?.contractRet && ret.contractRet !== "REVERT" ? `${ret.contractRet}: ` : "";
|
|
1189
|
+
return { ok: false, energy, reason: prefix + reason, constantResult: res.constant_result };
|
|
1190
|
+
}
|
|
1191
|
+
return { ok: true, energy, constantResult: res.constant_result };
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Predict the outcome without signing: contract calls run through
|
|
1195
|
+
* `/wallet/triggerconstantcontract` (revert reason ABI-decoded); TRX transfers check
|
|
1196
|
+
* that the balance covers amount + fees. Transport failures throw, predicted failures don't.
|
|
1197
|
+
*/
|
|
1198
|
+
async simulateTransaction(tx) {
|
|
1199
|
+
this.requireClient("simulate transactions");
|
|
1200
|
+
if (isContractCall(tx)) return this.simulateCall(tx);
|
|
1201
|
+
if (!isPrebuilt(tx)) {
|
|
1202
|
+
const { to, value } = tx;
|
|
1203
|
+
return this.simulateNativeTransfer(BigInt(value));
|
|
1204
|
+
}
|
|
1205
|
+
return this.simulateRaw(tx);
|
|
1206
|
+
}
|
|
1207
|
+
async simulateRaw(tx) {
|
|
1208
|
+
const { type, value } = contractOf(tx);
|
|
1209
|
+
if (type === "TriggerSmartContract") {
|
|
1210
|
+
return this.constantSimulation({
|
|
1211
|
+
owner_address: value.owner_address,
|
|
1212
|
+
contract_address: value.contract_address,
|
|
1213
|
+
data: value.data,
|
|
1214
|
+
call_value: value.call_value ?? 0,
|
|
1215
|
+
visible: tx.visible ?? false
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
if (type === "TransferContract") return this.simulateNativeTransfer(BigInt(value.amount ?? 0), tx);
|
|
1219
|
+
return { ok: true, energy: 0 };
|
|
1220
|
+
}
|
|
1221
|
+
async simulateNativeTransfer(amount, built) {
|
|
1222
|
+
const balance = await this.getBalance();
|
|
1223
|
+
const burn = built ? (await this.estimateRaw(built)).trxBurn : 0n;
|
|
1224
|
+
if (balance < amount + burn) return { ok: false, energy: 0, reason: `Insufficient TRX: need ${amount + burn} sun, have ${balance}` };
|
|
1225
|
+
return { ok: true, energy: 0 };
|
|
1226
|
+
}
|
|
1227
|
+
// ── Fees ────────────────────────────────────────────────────────────────────
|
|
1228
|
+
async getResources() {
|
|
1229
|
+
return this.post("/wallet/getaccountresource", { address: this.addressHex, visible: false }, "read account resources");
|
|
1230
|
+
}
|
|
1231
|
+
async getChainParameters() {
|
|
1232
|
+
const r = await this.post("/wallet/getchainparameters", {}, "read chain parameters");
|
|
1233
|
+
const out = { ...PARAM_DEFAULTS };
|
|
1234
|
+
for (const p of r.chainParameter ?? []) if (p.value !== void 0) out[p.key] = BigInt(p.value);
|
|
1235
|
+
return out;
|
|
1236
|
+
}
|
|
1237
|
+
async getChainParameter(key) {
|
|
1238
|
+
const p = (await this.getChainParameters())[key];
|
|
1239
|
+
if (p === void 0) throw new Error(`Chain parameter ${key} not found`);
|
|
1240
|
+
return p;
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Resource-by-resource cost of a node-built transaction. `energy` comes from a
|
|
1244
|
+
* constant call unless the caller already has one (`sim`).
|
|
1245
|
+
*/
|
|
1246
|
+
async estimateRaw(tx, sim) {
|
|
1247
|
+
const { type, value } = contractOf(tx);
|
|
1248
|
+
const [params, res] = await Promise.all([this.getChainParameters(), this.getResources()]);
|
|
1249
|
+
const energyPrice = params.getEnergyFee ?? 0n;
|
|
1250
|
+
const bandwidthPrice = params.getTransactionFee ?? 0n;
|
|
1251
|
+
const energy = type === "TriggerSmartContract" ? (sim ?? await this.simulateRaw(tx)).energy : 0;
|
|
1252
|
+
const bandwidth = bandwidthPoints(tx.raw_data_hex, Math.max(1, tx.signature?.length ?? 1));
|
|
1253
|
+
const bandwidthFree = Math.max(0, (res.freeNetLimit ?? 0) - (res.freeNetUsed ?? 0));
|
|
1254
|
+
const bandwidthStaked = Math.max(0, (res.NetLimit ?? 0) - (res.NetUsed ?? 0));
|
|
1255
|
+
const energyStaked = Math.max(0, (res.EnergyLimit ?? 0) - (res.EnergyUsed ?? 0));
|
|
1256
|
+
let accountCreationFee = 0n;
|
|
1257
|
+
let creating = false;
|
|
1258
|
+
if ((type === "TransferContract" || type === "TransferAssetContract") && value.to_address) {
|
|
1259
|
+
creating = !await this.accountExists(String(value.to_address));
|
|
1260
|
+
if (creating) {
|
|
1261
|
+
const rate = Number(params.getCreateNewAccountBandwidthRate ?? 1n);
|
|
1262
|
+
accountCreationFee = params.getCreateNewAccountFeeInSystemContract + (bandwidthStaked >= bandwidth * rate ? 0n : params.getCreateAccountFee);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
const bandwidthBurn = creating || bandwidthStaked >= bandwidth || bandwidthFree >= bandwidth ? 0n : BigInt(bandwidth) * bandwidthPrice;
|
|
1266
|
+
const energyBurn = BigInt(Math.max(0, energy - energyStaked)) * energyPrice;
|
|
1267
|
+
const memoFee = tx.raw_data?.data ? params.getMemoFee : 0n;
|
|
1268
|
+
return {
|
|
1269
|
+
bandwidth,
|
|
1270
|
+
energy,
|
|
1271
|
+
trxBurn: bandwidthBurn + energyBurn + accountCreationFee + memoFee,
|
|
1272
|
+
breakdown: { bandwidthFree, bandwidthStaked, energyStaked, energyPrice, bandwidthPrice, memoFee, accountCreationFee, bandwidthBurn, energyBurn }
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
/** Build (if needed) and price a transaction: bandwidth, energy and the sun the sender burns. */
|
|
1276
|
+
async estimateFee(tx) {
|
|
1277
|
+
this.requireClient("estimate fees");
|
|
1278
|
+
const { built, sim } = await this.buildWithSimulation(tx);
|
|
1279
|
+
return this.estimateRaw(built, sim);
|
|
1280
|
+
}
|
|
1281
|
+
async quoteTransaction(tx, sim) {
|
|
1282
|
+
const estimate = await this.estimateRaw(tx, sim);
|
|
1283
|
+
return { fee: estimate.trxBurn, activationFee: estimate.breakdown.accountCreationFee, estimate };
|
|
1284
|
+
}
|
|
1285
|
+
/** `fee` = the sun the sender burns, after staked / free resources. */
|
|
1286
|
+
async quoteSendTransaction(tx) {
|
|
1287
|
+
this.requireClient("quote transactions");
|
|
1288
|
+
const { built, sim } = await this.buildWithSimulation(tx);
|
|
1289
|
+
return this.quoteTransaction(built, sim);
|
|
1290
|
+
}
|
|
1291
|
+
trc20TransferParams(recipient, amount) {
|
|
1292
|
+
return [{ type: "address", value: recipient }, { type: "uint256", value: BigInt(amount) }];
|
|
1293
|
+
}
|
|
1294
|
+
asTransaction(o) {
|
|
1295
|
+
const memo = o.memo ? { memo: o.memo, ...o.memoEncoding ? { memoEncoding: o.memoEncoding } : {} } : {};
|
|
1296
|
+
return o.token ? { contractAddress: o.token, functionSelector: "transfer(address,uint256)", parameters: this.trc20TransferParams(o.recipient, o.amount), options: { callValue: 0 }, ...memo } : { to: o.recipient, value: o.amount, ...memo };
|
|
1297
|
+
}
|
|
1298
|
+
/** Fee of a transfer in sun, including account activation when the recipient is new. */
|
|
1299
|
+
async quoteTransfer(o) {
|
|
1300
|
+
this.requireClient("quote transfer operations");
|
|
1301
|
+
return this.quoteSendTransaction(this.asTransaction(o));
|
|
1302
|
+
}
|
|
1303
|
+
// ── Describe ────────────────────────────────────────────────────────────────
|
|
1304
|
+
/** `describeTransaction` plus the `unactivated-recipient` check against the node. */
|
|
1305
|
+
async describeTransactionOnline(tx, ctx = {}) {
|
|
1306
|
+
const offline = describeTransaction(tx, ctx);
|
|
1307
|
+
if (ctx.recipientExists !== void 0 || !offline.to || offline.kind !== "trx-transfer" && offline.kind !== "trc20-transfer") return offline;
|
|
1308
|
+
return describeTransaction(tx, { ...ctx, recipientExists: await this.accountExists(offline.to) });
|
|
1309
|
+
}
|
|
1310
|
+
/** The receipt, or `undefined` while the transaction is unconfirmed. */
|
|
1311
|
+
async getTransactionReceipt(hash) {
|
|
1312
|
+
const info = await this.post("/wallet/gettransactioninfobyid", { value: hash }, "fetch transaction receipts");
|
|
1313
|
+
if (!info || Object.keys(info).length === 0) return void 0;
|
|
1314
|
+
const receipt = info.receipt;
|
|
1315
|
+
const failed = info.result === "FAILED" || receipt?.result !== void 0 && receipt.result !== "SUCCESS";
|
|
1316
|
+
return { hash, status: failed ? 0 : 1, blockNumber: BigInt(info.blockNumber ?? 0), fee: BigInt(info.fee ?? 0), raw: info };
|
|
1317
|
+
}
|
|
1318
|
+
/** Verify a TIP-191 signature produced by `sign` (0x + r||s||v, v = 27|28 or 0|1). */
|
|
1319
|
+
async verify(message, signature) {
|
|
1320
|
+
return recoverMessageAddress(message, signature) === this.address;
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1323
|
+
|
|
1324
|
+
// src/account.ts
|
|
1325
|
+
var import_core3 = require("@anywalletkit/core");
|
|
1326
|
+
var import_utils7 = require("@noble/hashes/utils.js");
|
|
1327
|
+
var MAX_EXPIRATION_MS = 24 * 60 * 60 * 1e3;
|
|
1328
|
+
var WalletAccountTron = class _WalletAccountTron extends WalletAccountReadOnlyTron {
|
|
1329
|
+
/** ref block / expiration come from the node, so building needs the network. */
|
|
1330
|
+
needsNetwork = true;
|
|
1331
|
+
/** Derivation path, or `external` for an external signer. */
|
|
1332
|
+
path;
|
|
1333
|
+
/** Compressed secp256k1 key (33 bytes); needed synchronously for the address. */
|
|
1334
|
+
publicKey;
|
|
1335
|
+
/** The signer that holds the key. */
|
|
1336
|
+
signer;
|
|
1337
|
+
_disposed = false;
|
|
1338
|
+
_readOnly;
|
|
1339
|
+
constructor(signer, publicKey, path, config = {}) {
|
|
1340
|
+
super(addressFromPublicKey(publicKey), config);
|
|
1341
|
+
this.signer = (0, import_core3.requireCurve)(signer, "secp256k1");
|
|
1342
|
+
this.publicKey = publicKey;
|
|
1343
|
+
this.path = path;
|
|
1344
|
+
}
|
|
1345
|
+
/** Wrap a raw derived key (takes ownership; zeroized on dispose). Kept for callers that derive themselves. */
|
|
1346
|
+
static fromPrivateKey(privateKey, publicKey, path, config = {}) {
|
|
1347
|
+
return new _WalletAccountTron(import_core3.LocalSigner.fromPrivateKey("secp256k1", privateKey, publicKey, path), publicKey, path, config);
|
|
1348
|
+
}
|
|
1349
|
+
/** Account index for the standard m/44'/195'/0'/0/i path. */
|
|
1350
|
+
get index() {
|
|
1351
|
+
const m = /^m\/44'\/195'\/0'\/0\/(\d+)$/.exec(this.path);
|
|
1352
|
+
return m ? Number(m[1]) : void 0;
|
|
1353
|
+
}
|
|
1354
|
+
key() {
|
|
1355
|
+
if (this._disposed) throw new import_core3.SignerError("This account has been disposed.");
|
|
1356
|
+
}
|
|
1357
|
+
/** Recoverable secp256k1 signature over a 32-byte digest from the signer: r‖s‖recovery (recovery 0/1). */
|
|
1358
|
+
async signDigest(digest, context) {
|
|
1359
|
+
this.key();
|
|
1360
|
+
return this.signer.sign(digest, { scheme: "ecdsa-recoverable", context });
|
|
1361
|
+
}
|
|
1362
|
+
/** TIP-191 message signature: 0x + r||s||(27 + recovery) hex (tronweb `signMessageV2` encoding). */
|
|
1363
|
+
async sign(message) {
|
|
1364
|
+
const sig = await this.signDigest(hashMessage(message), "tron-message");
|
|
1365
|
+
return "0x" + (0, import_utils7.bytesToHex)(sig.subarray(0, 64)) + (27 + sig[64]).toString(16).padStart(2, "0");
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Sign a prebuilt transaction (ours or a dApp's) and attach `signature: [r||s||recovery]`
|
|
1369
|
+
* (recovery as 00/01, tronweb encoding). The signature covers `sha256(raw_data_hex)`, so
|
|
1370
|
+
* that is what is checked and signed: `txID` must equal it, the JSON `raw_data` must agree
|
|
1371
|
+
* with the bytes field by field, and the hard checks ({@link assertSafe}) run on the bytes.
|
|
1372
|
+
* A transaction whose JSON says "1 TRX to me" while the bytes say otherwise is refused
|
|
1373
|
+
* (`raw-mismatch`), as is a `txID` that does not belong to the bytes (`txid-mismatch`).
|
|
1374
|
+
*/
|
|
1375
|
+
async signRaw(tx) {
|
|
1376
|
+
this.key();
|
|
1377
|
+
const verified = verifyRawTransaction(tx);
|
|
1378
|
+
this.assertSafe({ ...tx, raw_data: verified.raw_data });
|
|
1379
|
+
const sig = await this.signDigest((0, import_utils7.hexToBytes)(verified.txID), "tron-tx");
|
|
1380
|
+
return { ...tx, signature: [(0, import_utils7.bytesToHex)(sig.subarray(0, 64)) + sig[64].toString(16).padStart(2, "0")] };
|
|
1381
|
+
}
|
|
1382
|
+
// ── Hard checks (before anything is signed) ─────────────────────────────────
|
|
1383
|
+
/**
|
|
1384
|
+
* Refuse to sign what cannot be right: a foreign owner, a missing or expired
|
|
1385
|
+
* (or >24h) expiration, no ref block, more than one contract, a TRX / TRC-20
|
|
1386
|
+
* transfer to the zero address (unless `allowBurn`), a `fee_limit` above
|
|
1387
|
+
* `transactionMaxFee`.
|
|
1388
|
+
*/
|
|
1389
|
+
assertSafe(tx, now = Date.now()) {
|
|
1390
|
+
const contracts = tx.raw_data?.contract ?? [];
|
|
1391
|
+
if (contracts.length !== 1) throw new UnsafeTransactionError("multi-contract", `Expected exactly one contract in the transaction, got ${contracts.length}.`);
|
|
1392
|
+
const owner = contracts[0]?.parameter?.value?.owner_address;
|
|
1393
|
+
if (!owner || toHexAddress(owner) !== this.addressHex) throw new UnsafeTransactionError("owner-mismatch", "The transaction owner does not match the wallet account address.");
|
|
1394
|
+
if (!tx.raw_data.ref_block_bytes || !tx.raw_data.ref_block_hash) throw new UnsafeTransactionError("missing-ref-block", "The transaction has no reference block (ref_block_bytes / ref_block_hash).");
|
|
1395
|
+
const exp = tx.raw_data.expiration;
|
|
1396
|
+
if (exp === void 0 || exp <= now) throw new UnsafeTransactionError("expired", "The transaction has no expiration or has already expired.");
|
|
1397
|
+
if (exp > now + MAX_EXPIRATION_MS) throw new UnsafeTransactionError("expiration-too-far", "The transaction expiration is more than 24 hours away.");
|
|
1398
|
+
const max = this._config.transactionMaxFee;
|
|
1399
|
+
if (max !== void 0 && tx.raw_data.fee_limit !== void 0 && BigInt(tx.raw_data.fee_limit) > BigInt(max)) {
|
|
1400
|
+
throw new UnsafeTransactionError("fee-limit", `fee_limit ${tx.raw_data.fee_limit} exceeds transactionMaxFee ${max}.`);
|
|
1401
|
+
}
|
|
1402
|
+
if (!this._config.allowBurn) {
|
|
1403
|
+
const d = describeTransaction(tx, { now });
|
|
1404
|
+
if (d.warnings.some((w) => w.code === "zero-address")) throw new UnsafeTransactionError("zero-address", "Refusing to send to the zero address (set allowBurn to override).");
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
maxFeeCheck(fee, limit, op) {
|
|
1408
|
+
if (limit !== void 0 && fee > BigInt(limit)) throw new Error(`Exceeded maximum fee cost for ${op} operation${op === "transfer" ? "s" : ""}.`);
|
|
1409
|
+
}
|
|
1410
|
+
/** Build → hard checks → simulate → estimate → fee cap. Shared by sign / send / transfer. */
|
|
1411
|
+
async prepare(tx, maxFee, op) {
|
|
1412
|
+
const { built, sim: fromBuild } = await this.buildWithSimulation(tx);
|
|
1413
|
+
this.assertSafe(isPrebuilt(tx) ? { ...built, raw_data: verifyRawTransaction(built).raw_data } : built);
|
|
1414
|
+
let sim = fromBuild;
|
|
1415
|
+
if (this._config.simulate !== false) {
|
|
1416
|
+
sim ??= await this.simulateRaw(built);
|
|
1417
|
+
if (!sim.ok) throw new SimulationError(sim.reason ?? "unknown", sim.energy);
|
|
1418
|
+
}
|
|
1419
|
+
const quote = await this.quoteTransaction(built, sim);
|
|
1420
|
+
this.maxFeeCheck(quote.fee, maxFee, op);
|
|
1421
|
+
return { built, quote };
|
|
1422
|
+
}
|
|
1423
|
+
/** Build (via the node), check, simulate and sign; returns broadcast-ready transaction JSON. */
|
|
1424
|
+
async signTransaction(tx) {
|
|
1425
|
+
this.key();
|
|
1426
|
+
this.requireClient("sign transactions");
|
|
1427
|
+
const { built } = await this.prepare(tx, this._config.transactionMaxFee, "transaction");
|
|
1428
|
+
return JSON.stringify(await this.signRaw(built));
|
|
1429
|
+
}
|
|
1430
|
+
async broadcast(signed2) {
|
|
1431
|
+
const r = await this.post("/wallet/broadcasttransaction", signed2, "broadcast");
|
|
1432
|
+
if (r.result !== true) throw new Error(`Broadcast failed: ${r.code ?? ""} ${r.message ?? ""}`.trim());
|
|
1433
|
+
return r.txid ?? signed2.txID;
|
|
1434
|
+
}
|
|
1435
|
+
async sendTransaction(tx) {
|
|
1436
|
+
this.key();
|
|
1437
|
+
this.requireClient("send transactions");
|
|
1438
|
+
const parsed = typeof tx === "string" ? JSON.parse(tx) : tx;
|
|
1439
|
+
const { built, quote } = await this.prepare(parsed, this._config.transactionMaxFee, "transaction");
|
|
1440
|
+
const signed2 = isPrebuilt(parsed) && parsed.signature?.length ? built : await this.signRaw(built);
|
|
1441
|
+
return { hash: await this.broadcast(signed2), fee: quote.fee, activationFee: quote.activationFee, estimate: quote.estimate };
|
|
1442
|
+
}
|
|
1443
|
+
/** TRX (no `token`) or TRC-20 `transfer(address,uint256)`. */
|
|
1444
|
+
async transfer(o) {
|
|
1445
|
+
this.key();
|
|
1446
|
+
this.requireClient("transfer tokens");
|
|
1447
|
+
const { built, quote } = await this.prepare(this.asTransaction(o), this._config.transferMaxFee, "transfer");
|
|
1448
|
+
return { hash: await this.broadcast(await this.signRaw(built)), fee: quote.fee, activationFee: quote.activationFee, estimate: quote.estimate };
|
|
1449
|
+
}
|
|
1450
|
+
/** The same address as a {@link WalletAccountReadOnlyTron}. */
|
|
1451
|
+
toReadOnlyAccount() {
|
|
1452
|
+
this._readOnly ??= new WalletAccountReadOnlyTron(this.address, this._config);
|
|
1453
|
+
return this._readOnly;
|
|
1454
|
+
}
|
|
1455
|
+
/** Dispose the signer (zeroizes a local key). Idempotent. */
|
|
1456
|
+
dispose() {
|
|
1457
|
+
this._disposed = true;
|
|
1458
|
+
this.signer.dispose();
|
|
1459
|
+
}
|
|
1460
|
+
};
|
|
1461
|
+
|
|
1462
|
+
// src/manager.ts
|
|
1463
|
+
var import_core4 = require("@anywalletkit/core");
|
|
1464
|
+
var TRON_BASE_PATH = "m/44'/195'";
|
|
1465
|
+
var TRON_ACCOUNT_BRANCH = `${TRON_BASE_PATH}/0'/0`;
|
|
1466
|
+
var WalletManagerTron = class extends import_core4.WalletManager {
|
|
1467
|
+
constructor(seedOrSigner, config = {}) {
|
|
1468
|
+
super(seedOrSigner, config);
|
|
1469
|
+
}
|
|
1470
|
+
async getAccount(index = 0) {
|
|
1471
|
+
return this.getAccountByPath(`${TRON_ACCOUNT_BRANCH}/${index}`);
|
|
1472
|
+
}
|
|
1473
|
+
async getAccountByPath(path) {
|
|
1474
|
+
const full = path.startsWith("m/") || path === "external" ? path : `${TRON_BASE_PATH}/${path}`;
|
|
1475
|
+
const cached = this._accounts.get(full);
|
|
1476
|
+
if (cached) return cached;
|
|
1477
|
+
const external = this.externalSigner();
|
|
1478
|
+
if (external) return this.fromSigner(external, full);
|
|
1479
|
+
const signer = import_core4.LocalSigner.leafFromSeed(this.seedBytes(), "secp256k1", full);
|
|
1480
|
+
const account = new WalletAccountTron(signer, signer.publicKey, full, this._config);
|
|
1481
|
+
this._accounts.set(full, account);
|
|
1482
|
+
return account;
|
|
1483
|
+
}
|
|
1484
|
+
/** Resolve `path` against an injected signer: the signer itself, or a child when it can derive there. */
|
|
1485
|
+
async fromSigner(signer, path) {
|
|
1486
|
+
this.requireSigner("secp256k1");
|
|
1487
|
+
let leaf = signer;
|
|
1488
|
+
let key = "external";
|
|
1489
|
+
if ((0, import_core4.isSeedSigner)(signer)) {
|
|
1490
|
+
if (path === signer.path) key = path;
|
|
1491
|
+
else if (path.startsWith(`${signer.path}/`)) {
|
|
1492
|
+
leaf = await (signer.deriveLeaf ?? signer.derive).call(signer, path.slice(signer.path.length + 1));
|
|
1493
|
+
key = path;
|
|
1494
|
+
} else throw new import_core4.SignerError(`The injected signer sits at ${signer.path}; it cannot provide ${path}.`);
|
|
1495
|
+
} else if (path !== `${TRON_ACCOUNT_BRANCH}/0` && path !== "external") {
|
|
1496
|
+
throw new import_core4.SignerError(`An external signer has one account; ${path} is not derivable from it.`);
|
|
1497
|
+
}
|
|
1498
|
+
const cached = this._accounts.get(key);
|
|
1499
|
+
if (cached) return cached;
|
|
1500
|
+
const account = new WalletAccountTron(leaf, await leaf.getPublicKey(), key, this._config);
|
|
1501
|
+
this._accounts.set(key, account);
|
|
1502
|
+
return account;
|
|
1503
|
+
}
|
|
1504
|
+
/** A read-only account for any address, with this manager's config. */
|
|
1505
|
+
getReadOnlyAccount(address) {
|
|
1506
|
+
return new WalletAccountReadOnlyTron(address, this._config);
|
|
1507
|
+
}
|
|
1508
|
+
/** getTransactionFee chain parameter; normal = ×1.1, fast = ×2.0; slow = ×1.0. */
|
|
1509
|
+
async getFeeRates() {
|
|
1510
|
+
const probe = new WalletAccountReadOnlyTron("T9yD14Nj9j7xAB4dbGeiX9h8unkKHxuWwb", this._config);
|
|
1511
|
+
probe.requireClient("get fee rates");
|
|
1512
|
+
const fee = await probe.getChainParameter("getTransactionFee");
|
|
1513
|
+
return { slow: fee, normal: fee * 110n / 100n, fast: fee * 200n / 100n };
|
|
1514
|
+
}
|
|
1515
|
+
};
|
|
1516
|
+
|
|
1517
|
+
// src/history.ts
|
|
1518
|
+
var HistoryUnavailableError = class extends Error {
|
|
1519
|
+
chain;
|
|
1520
|
+
reason;
|
|
1521
|
+
attempts;
|
|
1522
|
+
constructor(chain, reason, attempts = []) {
|
|
1523
|
+
super(`History unavailable for ${chain}: ${reason}`);
|
|
1524
|
+
this.name = "HistoryUnavailableError";
|
|
1525
|
+
this.chain = chain;
|
|
1526
|
+
this.reason = reason;
|
|
1527
|
+
this.attempts = attempts;
|
|
1528
|
+
}
|
|
1529
|
+
};
|
|
1530
|
+
var DEFAULT_LIMIT = 25;
|
|
1531
|
+
var MAX_LIMIT = 200;
|
|
1532
|
+
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
1533
|
+
var DEFAULT_URLS = { mainnet: "https://api.trongrid.io", shasta: "https://api.shasta.trongrid.io", nile: "https://nile.trongrid.io" };
|
|
1534
|
+
var TRANSFER_TOPIC = "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
1535
|
+
var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
1536
|
+
function b64encode(s) {
|
|
1537
|
+
let out = "";
|
|
1538
|
+
for (let i = 0; i < s.length; i += 3) {
|
|
1539
|
+
const c = [s.charCodeAt(i), s.charCodeAt(i + 1), s.charCodeAt(i + 2)];
|
|
1540
|
+
for (const x of c) if (x > 255) throw new Error("cursor must be ASCII");
|
|
1541
|
+
const n = c[0] << 16 | (c[1] || 0) << 8 | (c[2] || 0);
|
|
1542
|
+
out += B64[n >> 18 & 63] + B64[n >> 12 & 63] + (Number.isNaN(c[1]) ? "=" : B64[n >> 6 & 63]) + (Number.isNaN(c[2]) ? "=" : B64[n & 63]);
|
|
1543
|
+
}
|
|
1544
|
+
return out;
|
|
1545
|
+
}
|
|
1546
|
+
function b64decode(s) {
|
|
1547
|
+
const clean = s.replace(/[^A-Za-z0-9+/]/g, "");
|
|
1548
|
+
let out = "";
|
|
1549
|
+
for (let i = 0; i < clean.length; i += 4) {
|
|
1550
|
+
const c = [0, 1, 2, 3].map((k) => i + k < clean.length ? B64.indexOf(clean[i + k]) : -1);
|
|
1551
|
+
if (c[0] < 0 || c[1] < 0) throw new Error("bad base64");
|
|
1552
|
+
const n = c[0] << 18 | c[1] << 12 | (c[2] < 0 ? 0 : c[2]) << 6 | (c[3] < 0 ? 0 : c[3]);
|
|
1553
|
+
out += String.fromCharCode(n >> 16 & 255);
|
|
1554
|
+
if (c[2] >= 0) out += String.fromCharCode(n >> 8 & 255);
|
|
1555
|
+
if (c[3] >= 0) out += String.fromCharCode(n & 255);
|
|
1556
|
+
}
|
|
1557
|
+
return out;
|
|
1558
|
+
}
|
|
1559
|
+
function encodeCursor(c) {
|
|
1560
|
+
return b64encode(JSON.stringify(c));
|
|
1561
|
+
}
|
|
1562
|
+
function decodeCursor(s) {
|
|
1563
|
+
let parsed;
|
|
1564
|
+
try {
|
|
1565
|
+
parsed = JSON.parse(b64decode(s));
|
|
1566
|
+
} catch {
|
|
1567
|
+
throw new Error("@anywalletkit/wallet-tron: malformed history cursor");
|
|
1568
|
+
}
|
|
1569
|
+
const c = parsed;
|
|
1570
|
+
if (!c || c.v !== 1 || typeof c.w !== "number" || !Array.isArray(c.x) || !Array.isArray(c.e)) throw new Error("@anywalletkit/wallet-tron: malformed history cursor");
|
|
1571
|
+
return { v: 1, w: c.w, x: c.x.filter((x) => typeof x === "string"), e: c.e.filter((x) => x === "tx" || x === "trc20") };
|
|
1572
|
+
}
|
|
1573
|
+
var TokenBucket = class {
|
|
1574
|
+
constructor(rate, burst = Math.max(1, Math.ceil(rate))) {
|
|
1575
|
+
this.rate = rate;
|
|
1576
|
+
this.burst = burst;
|
|
1577
|
+
this.tokens = burst;
|
|
1578
|
+
}
|
|
1579
|
+
rate;
|
|
1580
|
+
burst;
|
|
1581
|
+
tokens;
|
|
1582
|
+
last = Date.now();
|
|
1583
|
+
chain = Promise.resolve();
|
|
1584
|
+
acquire() {
|
|
1585
|
+
const next = this.chain.then(() => this.take());
|
|
1586
|
+
this.chain = next.catch(() => void 0);
|
|
1587
|
+
return next;
|
|
1588
|
+
}
|
|
1589
|
+
refill() {
|
|
1590
|
+
const now = Date.now();
|
|
1591
|
+
this.tokens = Math.min(this.burst, this.tokens + (now - this.last) / 1e3 * this.rate);
|
|
1592
|
+
this.last = now;
|
|
1593
|
+
}
|
|
1594
|
+
async take() {
|
|
1595
|
+
this.refill();
|
|
1596
|
+
if (this.tokens >= 1) {
|
|
1597
|
+
this.tokens -= 1;
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
await new Promise((r) => setTimeout(r, Math.ceil((1 - this.tokens) / this.rate * 1e3)));
|
|
1601
|
+
this.refill();
|
|
1602
|
+
this.tokens = Math.max(0, this.tokens - 1);
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1605
|
+
function timeoutSignal(ms) {
|
|
1606
|
+
const ctor = globalThis.AbortSignal;
|
|
1607
|
+
return typeof ctor?.timeout === "function" ? ctor.timeout(ms) : void 0;
|
|
1608
|
+
}
|
|
1609
|
+
var big2 = (v) => {
|
|
1610
|
+
if (typeof v === "bigint") return v;
|
|
1611
|
+
if (typeof v === "number" && Number.isFinite(v)) return BigInt(Math.trunc(v));
|
|
1612
|
+
if (typeof v === "string" && /^-?\d+$/.test(v)) return BigInt(v);
|
|
1613
|
+
return 0n;
|
|
1614
|
+
};
|
|
1615
|
+
var b582 = (a) => {
|
|
1616
|
+
if (typeof a !== "string" || a.length === 0) return void 0;
|
|
1617
|
+
try {
|
|
1618
|
+
return toBase58Address(a);
|
|
1619
|
+
} catch {
|
|
1620
|
+
return a;
|
|
1621
|
+
}
|
|
1622
|
+
};
|
|
1623
|
+
var wordToAddress = (word) => toBase58Address("41" + word.slice(-40).toLowerCase());
|
|
1624
|
+
var SELF_TYPES_WITH_RECEIVER = {
|
|
1625
|
+
DelegateResourceContract: "receiver_address",
|
|
1626
|
+
UnDelegateResourceContract: "receiver_address",
|
|
1627
|
+
AccountPermissionUpdateContract: "owner_address"
|
|
1628
|
+
};
|
|
1629
|
+
function mapTransactionRow(chain, address, tx, extra = {}) {
|
|
1630
|
+
const contract = tx.raw_data.contract[0];
|
|
1631
|
+
const v = contract?.parameter.value ?? {};
|
|
1632
|
+
const type = contract?.type ?? "Unknown";
|
|
1633
|
+
const owner = b582(v.owner_address) ?? address;
|
|
1634
|
+
const transfers = [];
|
|
1635
|
+
let call;
|
|
1636
|
+
let to = null;
|
|
1637
|
+
let nativeValue = 0n;
|
|
1638
|
+
switch (type) {
|
|
1639
|
+
case "TransferContract": {
|
|
1640
|
+
to = b582(v.to_address) ?? null;
|
|
1641
|
+
nativeValue = big2(v.amount);
|
|
1642
|
+
if (to && nativeValue > 0n) transfers.push({ token: null, from: owner, to, amount: nativeValue });
|
|
1643
|
+
break;
|
|
1644
|
+
}
|
|
1645
|
+
case "TransferAssetContract": {
|
|
1646
|
+
to = b582(v.to_address) ?? null;
|
|
1647
|
+
const asset = typeof v.asset_name === "string" ? v.asset_name : void 0;
|
|
1648
|
+
const amount = big2(v.amount);
|
|
1649
|
+
if (to && asset && amount > 0n) transfers.push({ token: asset, from: owner, to, amount, symbol: `TRC-10 #${asset}`, decimals: 0 });
|
|
1650
|
+
break;
|
|
1651
|
+
}
|
|
1652
|
+
case "TriggerSmartContract": {
|
|
1653
|
+
const contractAddress = b582(v.contract_address);
|
|
1654
|
+
nativeValue = big2(v.call_value);
|
|
1655
|
+
if (contractAddress) {
|
|
1656
|
+
to = contractAddress;
|
|
1657
|
+
call = { to: contractAddress };
|
|
1658
|
+
const data = typeof v.data === "string" ? v.data.toLowerCase() : "";
|
|
1659
|
+
if (data.length >= 8) {
|
|
1660
|
+
call.selector = `0x${data.slice(0, 8)}`;
|
|
1661
|
+
call.data = `0x${data}`;
|
|
1662
|
+
}
|
|
1663
|
+
if (nativeValue > 0n) transfers.push({ token: null, from: owner, to: contractAddress, amount: nativeValue });
|
|
1664
|
+
}
|
|
1665
|
+
break;
|
|
1666
|
+
}
|
|
1667
|
+
default: {
|
|
1668
|
+
const receiverKey = SELF_TYPES_WITH_RECEIVER[type];
|
|
1669
|
+
const receiver = receiverKey ? b582(v[receiverKey]) : void 0;
|
|
1670
|
+
call = { to: receiver ?? owner, method: type };
|
|
1671
|
+
to = receiver ?? owner;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
for (const it of tx.internal_transactions ?? []) {
|
|
1675
|
+
if (it.rejected) continue;
|
|
1676
|
+
const from = b582(it.caller_address), dest = b582(it.transferTo_address);
|
|
1677
|
+
if (!from || !dest) continue;
|
|
1678
|
+
for (const cv of it.callValueInfo ?? []) {
|
|
1679
|
+
const amount = big2(cv.callValue);
|
|
1680
|
+
if (amount > 0n && !cv.tokenId) transfers.push({ token: null, from, to: dest, amount });
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
const trc20 = extra.trc20 ?? [];
|
|
1684
|
+
if (trc20.length > 0) {
|
|
1685
|
+
for (const row of trc20) if (row.type === "Transfer") transfers.push(trc20Transfer(row));
|
|
1686
|
+
} else if (extra.info?.log && extra.info.log.length > 0) {
|
|
1687
|
+
for (const log of extra.info.log) {
|
|
1688
|
+
if (log.topics[0]?.toLowerCase() !== TRANSFER_TOPIC || log.topics.length < 3) continue;
|
|
1689
|
+
transfers.push({ token: toBase58Address("41" + log.address.toLowerCase().slice(-40)), from: wordToAddress(log.topics[1]), to: wordToAddress(log.topics[2]), amount: BigInt(`0x${log.data || "0"}`), standard: "trc20" });
|
|
1690
|
+
}
|
|
1691
|
+
} else if (call?.selector === "0xa9059cbb" && call.data && call.data.length >= 2 + 8 + 128) {
|
|
1692
|
+
const words = call.data.slice(10);
|
|
1693
|
+
transfers.push({ token: call.to, from: owner, to: wordToAddress(words.slice(0, 64)), amount: BigInt(`0x${words.slice(64, 128)}`), standard: "trc20" });
|
|
1694
|
+
}
|
|
1695
|
+
if (call?.selector === "0xa9059cbb" && !call.method) call.method = "transfer";
|
|
1696
|
+
const ret = tx.ret?.[0];
|
|
1697
|
+
const result = extra.info?.receipt?.result ?? ret?.contractRet;
|
|
1698
|
+
const status = result === void 0 ? tx.blockNumber ? "confirmed" : "pending" : result === "SUCCESS" ? "confirmed" : "failed";
|
|
1699
|
+
const feeNum = extra.info?.fee ?? (typeof ret?.fee === "number" ? ret.fee : void 0) ?? (tx.net_fee !== void 0 || tx.energy_fee !== void 0 ? (tx.net_fee ?? 0) + (tx.energy_fee ?? 0) : void 0);
|
|
1700
|
+
const ts = tx.block_timestamp ?? extra.info?.blockTimeStamp ?? tx.raw_data.timestamp;
|
|
1701
|
+
const blockNumber = tx.blockNumber ?? extra.info?.blockNumber;
|
|
1702
|
+
const raw = { tx, trc20 };
|
|
1703
|
+
if (contract?.type === "TransferAssetContract" && owner !== address && to === address) {
|
|
1704
|
+
raw.spam = true;
|
|
1705
|
+
raw.spamReason = "trc10-airdrop";
|
|
1706
|
+
}
|
|
1707
|
+
if (extra.info) raw.info = extra.info;
|
|
1708
|
+
const out = { chain, hash: tx.txID, time: ts ? Math.floor(ts / 1e3) : 0, status, address, from: owner, to: call?.to ?? to, nativeValue, transfers, raw };
|
|
1709
|
+
if (feeNum !== void 0) out.fee = BigInt(Math.trunc(feeNum));
|
|
1710
|
+
if (call) out.call = call;
|
|
1711
|
+
if (blockNumber !== void 0) out.block = BigInt(blockNumber);
|
|
1712
|
+
return out;
|
|
1713
|
+
}
|
|
1714
|
+
function trc20Transfer(row) {
|
|
1715
|
+
const t = { token: row.token_info.address, from: row.from, to: row.to, amount: big2(row.value), standard: "trc20" };
|
|
1716
|
+
if (row.token_info.symbol !== void 0) t.symbol = row.token_info.symbol;
|
|
1717
|
+
if (row.token_info.decimals !== void 0) t.decimals = row.token_info.decimals;
|
|
1718
|
+
return t;
|
|
1719
|
+
}
|
|
1720
|
+
function mapTrc20Only(chain, address, rows) {
|
|
1721
|
+
const first = rows[0];
|
|
1722
|
+
const transfers = rows.filter((r) => r.type === "Transfer").map(trc20Transfer);
|
|
1723
|
+
const primary = transfers.find((t) => t.from === address || t.to === address) ?? transfers[0];
|
|
1724
|
+
const out = {
|
|
1725
|
+
chain,
|
|
1726
|
+
hash: first.transaction_id,
|
|
1727
|
+
time: Math.floor(first.block_timestamp / 1e3),
|
|
1728
|
+
status: "confirmed",
|
|
1729
|
+
address,
|
|
1730
|
+
from: primary?.from ?? first.from,
|
|
1731
|
+
to: primary?.to ?? first.to,
|
|
1732
|
+
nativeValue: 0n,
|
|
1733
|
+
transfers,
|
|
1734
|
+
raw: { trc20: rows }
|
|
1735
|
+
};
|
|
1736
|
+
if (transfers.length === 0) {
|
|
1737
|
+
out.call = { to: first.token_info.address, method: first.type.toLowerCase() };
|
|
1738
|
+
out.to = first.token_info.address;
|
|
1739
|
+
}
|
|
1740
|
+
return out;
|
|
1741
|
+
}
|
|
1742
|
+
function tronExplorerTxUrl(hash, network = "mainnet") {
|
|
1743
|
+
const host = network === "mainnet" ? "tronscan.org" : `${network}.tronscan.org`;
|
|
1744
|
+
return `https://${host}/#/transaction/${hash}`;
|
|
1745
|
+
}
|
|
1746
|
+
function createTronHistory(options = {}) {
|
|
1747
|
+
const network = options.network ?? "mainnet";
|
|
1748
|
+
const chain = CAIP2_GENESIS[network] ?? CAIP2_GENESIS.mainnet;
|
|
1749
|
+
const apiUrl = (options.apiUrl ?? DEFAULT_URLS[network]).replace(/\/+$/, "");
|
|
1750
|
+
const fetchFn = options.fetch ?? globalThis.fetch;
|
|
1751
|
+
if (typeof fetchFn !== "function") throw new Error("@anywalletkit/wallet-tron: no global fetch; pass `fetch` in the options");
|
|
1752
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1753
|
+
const bucket = new TokenBucket(options.rateLimit ?? (options.apiKey ? 10 : 1));
|
|
1754
|
+
const headers = () => ({ accept: "application/json", ...options.apiKey ? { "TRON-PRO-API-KEY": options.apiKey } : {} });
|
|
1755
|
+
async function request(path, init) {
|
|
1756
|
+
await bucket.acquire();
|
|
1757
|
+
const url = `${apiUrl}${path}`;
|
|
1758
|
+
let res;
|
|
1759
|
+
try {
|
|
1760
|
+
const signal = timeoutSignal(timeoutMs);
|
|
1761
|
+
res = await fetchFn(url, {
|
|
1762
|
+
...init ? { method: "POST", body: JSON.stringify(init.body) } : {},
|
|
1763
|
+
headers: { ...headers(), ...init ? { "content-type": "application/json" } : {} },
|
|
1764
|
+
...signal ? { signal } : {}
|
|
1765
|
+
});
|
|
1766
|
+
} catch (e) {
|
|
1767
|
+
throw new HistoryUnavailableError(chain, `${path}: ${e instanceof Error ? e.message : String(e)}`, [`trongrid: ${e instanceof Error ? e.message : String(e)}`]);
|
|
1768
|
+
}
|
|
1769
|
+
let body;
|
|
1770
|
+
try {
|
|
1771
|
+
body = await res.json();
|
|
1772
|
+
} catch {
|
|
1773
|
+
body = void 0;
|
|
1774
|
+
}
|
|
1775
|
+
const b = body;
|
|
1776
|
+
if (!res.ok || b?.success === false || typeof b?.Error === "string") {
|
|
1777
|
+
const msg = b?.error ?? b?.Error ?? `HTTP ${res.status}`;
|
|
1778
|
+
throw new HistoryUnavailableError(chain, `${path}: ${msg}`, [`trongrid: HTTP ${res.status} ${msg}`]);
|
|
1779
|
+
}
|
|
1780
|
+
return body;
|
|
1781
|
+
}
|
|
1782
|
+
async function list(path, params) {
|
|
1783
|
+
const q = Object.entries(params).filter(([, v]) => v !== void 0).map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`).join("&");
|
|
1784
|
+
const body = await request(`${path}?${q}`);
|
|
1785
|
+
return body?.data ?? [];
|
|
1786
|
+
}
|
|
1787
|
+
return {
|
|
1788
|
+
chain,
|
|
1789
|
+
async listActivity(address, query = {}) {
|
|
1790
|
+
const limit = Math.min(MAX_LIMIT, Math.max(1, Math.floor(query.limit ?? DEFAULT_LIMIT)));
|
|
1791
|
+
const cursor = query.cursor ? decodeCursor(query.cursor) : void 0;
|
|
1792
|
+
const exhausted = new Set(cursor?.e ?? []);
|
|
1793
|
+
const skip = new Set(cursor?.x ?? []);
|
|
1794
|
+
const common = { limit, order_by: "block_timestamp,desc", max_timestamp: cursor?.w };
|
|
1795
|
+
if (query.since !== void 0) common.min_timestamp = query.since * 1e3;
|
|
1796
|
+
const txRows = exhausted.has("tx") ? [] : await list(`/v1/accounts/${encodeURIComponent(address)}/transactions`, common);
|
|
1797
|
+
const trc20Rows = exhausted.has("trc20") ? [] : await list(`/v1/accounts/${encodeURIComponent(address)}/transactions/trc20`, common);
|
|
1798
|
+
const tails = [];
|
|
1799
|
+
if (!exhausted.has("tx") && txRows.length >= limit) tails.push(txRows[txRows.length - 1].block_timestamp ?? 0);
|
|
1800
|
+
if (!exhausted.has("trc20") && trc20Rows.length >= limit) tails.push(trc20Rows[trc20Rows.length - 1].block_timestamp);
|
|
1801
|
+
const cutoff = tails.length ? Math.max(...tails) : Number.NEGATIVE_INFINITY;
|
|
1802
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1803
|
+
for (const r of txRows) {
|
|
1804
|
+
if (skip.has(r.txID)) continue;
|
|
1805
|
+
const e = byId.get(r.txID) ?? { ts: r.block_timestamp ?? 0, trc20: [] };
|
|
1806
|
+
e.tx = r;
|
|
1807
|
+
byId.set(r.txID, e);
|
|
1808
|
+
}
|
|
1809
|
+
for (const r of trc20Rows) {
|
|
1810
|
+
if (skip.has(r.transaction_id)) continue;
|
|
1811
|
+
const e = byId.get(r.transaction_id) ?? { ts: r.block_timestamp, trc20: [] };
|
|
1812
|
+
e.trc20.push(r);
|
|
1813
|
+
byId.set(r.transaction_id, e);
|
|
1814
|
+
}
|
|
1815
|
+
let entries = [...byId.entries()].sort((a, b) => b[1].ts - a[1].ts);
|
|
1816
|
+
let inWindow = entries.filter(([, e]) => e.ts > cutoff);
|
|
1817
|
+
if (inWindow.length === 0 && entries.length > 0) inWindow = entries.filter(([, e]) => e.ts === cutoff);
|
|
1818
|
+
entries = inWindow.slice(0, limit);
|
|
1819
|
+
const items = entries.map(([, e]) => e.tx ? mapTransactionRow(chain, address, e.tx, { trc20: e.trc20 }) : mapTrc20Only(chain, address, e.trc20));
|
|
1820
|
+
const emitted = new Set(entries.map(([id]) => id));
|
|
1821
|
+
const done = (id) => emitted.has(id) || skip.has(id);
|
|
1822
|
+
if (!exhausted.has("tx") && txRows.length < limit && txRows.every((r) => done(r.txID))) exhausted.add("tx");
|
|
1823
|
+
if (!exhausted.has("trc20") && trc20Rows.length < limit && trc20Rows.every((r) => done(r.transaction_id))) exhausted.add("trc20");
|
|
1824
|
+
const page = { items };
|
|
1825
|
+
const more = exhausted.size < 2;
|
|
1826
|
+
if (more && entries.length > 0) {
|
|
1827
|
+
const last = entries[entries.length - 1][1].ts;
|
|
1828
|
+
const x = entries.filter(([, e]) => e.ts === last).map(([id]) => id);
|
|
1829
|
+
if (last === cursor?.w) for (const id of skip) x.push(id);
|
|
1830
|
+
page.cursor = encodeCursor({ v: 1, w: last, x, e: [...exhausted] });
|
|
1831
|
+
}
|
|
1832
|
+
return page;
|
|
1833
|
+
},
|
|
1834
|
+
async getTransaction(hash, address) {
|
|
1835
|
+
const tx = await request("/wallet/gettransactionbyid", { method: "POST", body: { value: hash } });
|
|
1836
|
+
if (!tx || typeof tx.txID !== "string") return void 0;
|
|
1837
|
+
const info = await request("/wallet/gettransactioninfobyid", { method: "POST", body: { value: hash } });
|
|
1838
|
+
return mapTransactionRow(chain, address, tx, info && typeof info.id === "string" ? { info } : {});
|
|
1839
|
+
},
|
|
1840
|
+
explorerTxUrl(hash) {
|
|
1841
|
+
return tronExplorerTxUrl(hash, network);
|
|
1842
|
+
}
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
//# sourceMappingURL=index.cjs.map
|