@vtx-labs/solana-explain 0.1.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.
@@ -0,0 +1,3317 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from 'util';
3
+ import { readFile } from 'fs/promises';
4
+ import { pathToFileURL } from 'url';
5
+ import process from 'process';
6
+ import bs58 from 'bs58';
7
+
8
+ // src/errors.ts
9
+ var SolanaExplainError = class extends Error {
10
+ code;
11
+ cause;
12
+ /** Optional, CLI-friendly remediation hint. */
13
+ hint;
14
+ constructor(code, message, options = {}) {
15
+ super(message);
16
+ this.name = new.target.name;
17
+ this.code = code;
18
+ if (options.cause !== void 0) this.cause = options.cause;
19
+ if (options.hint !== void 0) this.hint = options.hint;
20
+ Object.setPrototypeOf(this, new.target.prototype);
21
+ }
22
+ };
23
+ var RpcError = class extends SolanaExplainError {
24
+ };
25
+ var DecodeError = class extends SolanaExplainError {
26
+ };
27
+ var SimulationError = class extends SolanaExplainError {
28
+ /** Partial result when the simulation ran but reverted. */
29
+ result;
30
+ /** Tail of the node's program logs, for diagnostics. */
31
+ logs;
32
+ constructor(code, message, options = {}) {
33
+ super(code, message, options);
34
+ if (options.result !== void 0) this.result = options.result;
35
+ if (options.logs !== void 0) this.logs = options.logs;
36
+ }
37
+ };
38
+ var InputError = class extends SolanaExplainError {
39
+ };
40
+ function wrapError(err, fallbackCode, fallbackMessage) {
41
+ if (err instanceof SolanaExplainError) return err;
42
+ if (isAbortError(err)) {
43
+ return new RpcError("ABORTED", "The operation was aborted.", { cause: err });
44
+ }
45
+ const detail = err instanceof Error ? err.message : String(err);
46
+ return new SolanaExplainError(fallbackCode, `${fallbackMessage}: ${detail}`, { cause: err });
47
+ }
48
+ function isAbortError(err) {
49
+ return typeof err === "object" && err !== null && "name" in err && err.name === "AbortError";
50
+ }
51
+ function decodeBase58(input) {
52
+ try {
53
+ return bs58.decode(input);
54
+ } catch (err) {
55
+ throw new InputError("INVALID_ENCODING", "Input is not valid base58.", { cause: err });
56
+ }
57
+ }
58
+ function encodeBase58(bytes) {
59
+ return bs58.encode(bytes);
60
+ }
61
+ function tryDecodeBase58(input) {
62
+ try {
63
+ const out = bs58.decode(input);
64
+ return out.length > 0 ? out : null;
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+ var BASE64_RE = /^[A-Za-z0-9+/]+={0,2}$/;
70
+ function decodeBase64(input) {
71
+ const trimmed = input.trim();
72
+ if (!BASE64_RE.test(trimmed) || trimmed.length % 4 !== 0) {
73
+ throw new InputError("INVALID_ENCODING", "Input is not valid base64.");
74
+ }
75
+ try {
76
+ return new Uint8Array(Buffer.from(trimmed, "base64"));
77
+ } catch (err) {
78
+ throw new InputError("INVALID_ENCODING", "Input is not valid base64.", { cause: err });
79
+ }
80
+ }
81
+ function tryDecodeBase64(input) {
82
+ const trimmed = input.trim();
83
+ if (trimmed.length === 0 || trimmed.length % 4 !== 0 || !BASE64_RE.test(trimmed)) return null;
84
+ try {
85
+ const buf = Buffer.from(trimmed, "base64");
86
+ if (buf.length === 0) return null;
87
+ if (buf.toString("base64") !== trimmed) return null;
88
+ return new Uint8Array(buf);
89
+ } catch {
90
+ return null;
91
+ }
92
+ }
93
+ function encodeBase64(bytes) {
94
+ return Buffer.from(bytes).toString("base64");
95
+ }
96
+ function looksLikeBase64(input) {
97
+ const trimmed = input.trim();
98
+ if (trimmed.length === 0) return false;
99
+ if (/[+/=]/.test(trimmed)) return true;
100
+ return BASE64_RE.test(trimmed);
101
+ }
102
+ function looksLikeBase58(input) {
103
+ const trimmed = input.trim();
104
+ if (trimmed.length === 0) return false;
105
+ return /^[1-9A-HJ-NP-Za-km-z]+$/.test(trimmed);
106
+ }
107
+ function normalizeRpcUrl(url) {
108
+ let u = url.trim();
109
+ if (u.length === 0) {
110
+ throw new InputError("INVALID_INPUT", "RPC URL is empty.");
111
+ }
112
+ if (!/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(u)) {
113
+ u = `https://${u}`;
114
+ }
115
+ try {
116
+ const parsed = new URL(u);
117
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
118
+ throw new InputError(
119
+ "INVALID_INPUT",
120
+ `Unsupported RPC URL scheme "${parsed.protocol}". Use http(s).`
121
+ );
122
+ }
123
+ return parsed.toString();
124
+ } catch (err) {
125
+ if (err instanceof InputError) throw err;
126
+ throw new InputError("INVALID_INPUT", `Invalid RPC URL: ${url}`, { cause: err });
127
+ }
128
+ }
129
+
130
+ // src/rpc/http.ts
131
+ var RETRY_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
132
+ function sleep(ms, signal) {
133
+ return new Promise((resolve, reject) => {
134
+ if (signal?.aborted) {
135
+ reject(new RpcError("ABORTED", "Aborted while backing off."));
136
+ return;
137
+ }
138
+ const t = setTimeout(resolve, ms);
139
+ const onAbort = () => {
140
+ clearTimeout(t);
141
+ reject(new RpcError("ABORTED", "Aborted while backing off."));
142
+ };
143
+ signal?.addEventListener("abort", onAbort, { once: true });
144
+ });
145
+ }
146
+ function combineSignals(timeoutMs, external) {
147
+ const controller = new AbortController();
148
+ let timedOut = false;
149
+ const timer = setTimeout(() => {
150
+ timedOut = true;
151
+ controller.abort();
152
+ }, timeoutMs);
153
+ const onExternalAbort = () => controller.abort();
154
+ if (external) {
155
+ if (external.aborted) controller.abort();
156
+ else external.addEventListener("abort", onExternalAbort, { once: true });
157
+ }
158
+ return {
159
+ signal: controller.signal,
160
+ timedOut: () => timedOut,
161
+ cleanup: () => {
162
+ clearTimeout(timer);
163
+ external?.removeEventListener("abort", onExternalAbort);
164
+ }
165
+ };
166
+ }
167
+ function createHttpRpc(url, opts = {}) {
168
+ const endpoint = normalizeRpcUrl(url);
169
+ const defaultTimeout = opts.timeoutMs ?? 3e4;
170
+ const fetchImpl = opts.fetch ?? globalThis.fetch;
171
+ if (typeof fetchImpl !== "function") {
172
+ throw new RpcError(
173
+ "RPC_HTTP",
174
+ "global fetch is not available; use Node 18+ or pass a fetch implementation."
175
+ );
176
+ }
177
+ let idCounter = 1;
178
+ async function call(method, params, timeoutMs, externalSignal) {
179
+ const body = { jsonrpc: "2.0", id: idCounter++, method, params };
180
+ let attempt = 0;
181
+ for (; ; ) {
182
+ const { signal, timedOut, cleanup } = combineSignals(timeoutMs, externalSignal);
183
+ let res;
184
+ try {
185
+ res = await fetchImpl(endpoint, {
186
+ method: "POST",
187
+ headers: { "content-type": "application/json", ...opts.headers },
188
+ body: JSON.stringify(body),
189
+ signal
190
+ });
191
+ } catch (err) {
192
+ cleanup();
193
+ if (timedOut()) {
194
+ throw new RpcError("RPC_TIMEOUT", `RPC request "${method}" timed out after ${timeoutMs}ms.`, {
195
+ cause: err,
196
+ hint: "Increase --timeout or check your RPC URL."
197
+ });
198
+ }
199
+ if (isAbortError(err) || externalSignal?.aborted) {
200
+ throw new RpcError("ABORTED", `RPC request "${method}" was aborted.`, { cause: err });
201
+ }
202
+ throw new RpcError("RPC_HTTP", `Network error calling "${method}": ${describeErr(err)}`, {
203
+ cause: err,
204
+ hint: "Check connectivity and that the RPC URL is reachable."
205
+ });
206
+ }
207
+ cleanup();
208
+ if (RETRY_STATUSES.has(res.status) && attempt < 1) {
209
+ attempt++;
210
+ const backoff = 250 + Math.floor(Math.random() * 400);
211
+ await sleep(backoff, externalSignal);
212
+ continue;
213
+ }
214
+ const text = await res.text();
215
+ if (!res.ok) {
216
+ throw new RpcError(
217
+ "RPC_HTTP",
218
+ `RPC HTTP ${res.status} ${res.statusText} for "${method}". Body: ${snippet(text)}`,
219
+ res.status === 429 ? { hint: "Public endpoints are rate-limited; use a dedicated RPC." } : {}
220
+ );
221
+ }
222
+ let parsed;
223
+ try {
224
+ parsed = JSON.parse(text);
225
+ } catch (err) {
226
+ throw new RpcError(
227
+ "RPC_JSON",
228
+ `RPC returned non-JSON for "${method}". Body: ${snippet(text)}`,
229
+ { cause: err, hint: "Is the RPC URL correct? A captive portal/HTML page may be intercepting." }
230
+ );
231
+ }
232
+ if (parsed.error) {
233
+ const msg = `${parsed.error.message} (code ${parsed.error.code})`;
234
+ if (/version|transaction version/i.test(parsed.error.message)) {
235
+ throw new RpcError("UNSUPPORTED_TX_VERSION", `RPC: ${msg}`, {
236
+ hint: "Raise --max-tx-version (e.g. 0) to fetch versioned transactions."
237
+ });
238
+ }
239
+ throw new RpcError("RPC_JSON", `JSON-RPC error for "${method}": ${msg}`);
240
+ }
241
+ return parsed.result;
242
+ }
243
+ }
244
+ return {
245
+ async getTransaction(sig, o) {
246
+ const config = {
247
+ encoding: "base64",
248
+ commitment: o.commitment ?? "confirmed"
249
+ };
250
+ if (o.maxSupportedTransactionVersion !== void 0) {
251
+ config["maxSupportedTransactionVersion"] = o.maxSupportedTransactionVersion;
252
+ }
253
+ return call(
254
+ "getTransaction",
255
+ [sig, config],
256
+ defaultTimeout,
257
+ o.signal
258
+ );
259
+ },
260
+ async simulateTransaction(txBase64, o) {
261
+ const config = {
262
+ encoding: "base64",
263
+ commitment: o.commitment ?? "confirmed",
264
+ sigVerify: o.sigVerify ?? false,
265
+ replaceRecentBlockhash: o.replaceRecentBlockhash ?? true
266
+ };
267
+ if (o.innerInstructions) config["innerInstructions"] = true;
268
+ if (o.accounts) {
269
+ config["accounts"] = {
270
+ addresses: o.accounts.addresses,
271
+ encoding: o.accounts.encoding ?? "jsonParsed"
272
+ };
273
+ }
274
+ return call(
275
+ "simulateTransaction",
276
+ [txBase64, config],
277
+ defaultTimeout,
278
+ o.signal
279
+ );
280
+ },
281
+ async getMultipleAccounts(addresses, o) {
282
+ if (addresses.length === 0) return [];
283
+ const config = {
284
+ commitment: o.commitment ?? "confirmed",
285
+ encoding: o.encoding ?? "jsonParsed"
286
+ };
287
+ const result = await call(
288
+ "getMultipleAccounts",
289
+ [addresses, config],
290
+ defaultTimeout,
291
+ o.signal
292
+ );
293
+ return result.value;
294
+ },
295
+ async getLatestBlockhash(o) {
296
+ const result = await call("getLatestBlockhash", [{ commitment: o?.commitment ?? "confirmed" }], defaultTimeout, o?.signal);
297
+ return result.value;
298
+ }
299
+ };
300
+ }
301
+ function snippet(text, n = 120) {
302
+ const oneLine = text.replace(/\s+/g, " ").trim();
303
+ return oneLine.length > n ? `${oneLine.slice(0, n)}\u2026` : oneLine;
304
+ }
305
+ function describeErr(err) {
306
+ if (err instanceof Error) {
307
+ const code = err.cause?.code;
308
+ return code ? `${err.message} (${code})` : err.message;
309
+ }
310
+ return String(err);
311
+ }
312
+
313
+ // src/decode/known-programs.ts
314
+ var SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
315
+ var TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
316
+ var TOKEN_2022_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
317
+ var ASSOCIATED_TOKEN_PROGRAM_ID = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
318
+ var MEMO_PROGRAM_ID = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
319
+ var MEMO_V1_PROGRAM_ID = "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo";
320
+ var COMPUTE_BUDGET_PROGRAM_ID = "ComputeBudget111111111111111111111111111111";
321
+ var KNOWN_PROGRAMS = Object.freeze({
322
+ // Core / decoded
323
+ [SYSTEM_PROGRAM_ID]: { name: "System", kind: "system" },
324
+ [TOKEN_PROGRAM_ID]: { name: "SPL Token", kind: "token" },
325
+ [TOKEN_2022_PROGRAM_ID]: { name: "Token-2022", kind: "token-2022" },
326
+ [ASSOCIATED_TOKEN_PROGRAM_ID]: { name: "Associated Token Account", kind: "ata" },
327
+ [MEMO_PROGRAM_ID]: { name: "Memo", kind: "memo" },
328
+ [MEMO_V1_PROGRAM_ID]: { name: "Memo (v1)", kind: "memo" },
329
+ [COMPUTE_BUDGET_PROGRAM_ID]: { name: "Compute Budget", kind: "compute-budget" },
330
+ // Native
331
+ Stake11111111111111111111111111111111111111: { name: "Stake", kind: "stake" },
332
+ Vote111111111111111111111111111111111111111: { name: "Vote", kind: "vote" },
333
+ BPFLoaderUpgradeab1e11111111111111111111111: { name: "BPF Upgradeable Loader", kind: "loader" },
334
+ BPFLoader2111111111111111111111111111111111: { name: "BPF Loader 2", kind: "loader" },
335
+ AddressLookupTab1e1111111111111111111111111: {
336
+ name: "Address Lookup Table",
337
+ kind: "other"
338
+ },
339
+ // Metaplex
340
+ metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s: {
341
+ name: "Metaplex Token Metadata",
342
+ kind: "nft"
343
+ },
344
+ BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY: {
345
+ name: "Metaplex Bubblegum",
346
+ kind: "nft"
347
+ },
348
+ cndy3Z4yxLzowg4ZTHTHJYjBmtkk3vd6yMJ4r8x7q3o: {
349
+ name: "Metaplex Candy Machine",
350
+ kind: "nft"
351
+ },
352
+ // Aggregators / DEX
353
+ JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4: { name: "Jupiter v6", kind: "aggregator" },
354
+ JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WconZX: { name: "Jupiter v4", kind: "aggregator" },
355
+ "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8": { name: "Raydium AMM v4", kind: "amm" },
356
+ CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK: { name: "Raydium CLMM", kind: "amm" },
357
+ whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc: { name: "Orca Whirlpool", kind: "amm" },
358
+ "9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQP": { name: "Orca v2", kind: "amm" },
359
+ PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY: { name: "Phoenix", kind: "amm" },
360
+ LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo: { name: "Meteora DLMM", kind: "amm" }
361
+ });
362
+ function knownProgram(programId) {
363
+ return KNOWN_PROGRAMS[programId];
364
+ }
365
+
366
+ // src/decode/reader.ts
367
+ var Reader = class {
368
+ view;
369
+ bytes;
370
+ offset = 0;
371
+ constructor(bytes) {
372
+ this.bytes = bytes;
373
+ this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
374
+ }
375
+ get remaining() {
376
+ return this.bytes.length - this.offset;
377
+ }
378
+ ensure(n, field) {
379
+ if (this.offset + n > this.bytes.length) {
380
+ throw new DecodeError(
381
+ "DECODE_FAILED",
382
+ `unexpected end while reading ${field} (need ${n} bytes at offset ${this.offset}, have ${this.remaining})`
383
+ );
384
+ }
385
+ }
386
+ u8(field = "u8") {
387
+ this.ensure(1, field);
388
+ const v = this.view.getUint8(this.offset);
389
+ this.offset += 1;
390
+ return v;
391
+ }
392
+ u16(field = "u16") {
393
+ this.ensure(2, field);
394
+ const v = this.view.getUint16(this.offset, true);
395
+ this.offset += 2;
396
+ return v;
397
+ }
398
+ u32(field = "u32") {
399
+ this.ensure(4, field);
400
+ const v = this.view.getUint32(this.offset, true);
401
+ this.offset += 4;
402
+ return v;
403
+ }
404
+ u64(field = "u64") {
405
+ this.ensure(8, field);
406
+ const v = this.view.getBigUint64(this.offset, true);
407
+ this.offset += 8;
408
+ return v;
409
+ }
410
+ i64(field = "i64") {
411
+ this.ensure(8, field);
412
+ const v = this.view.getBigInt64(this.offset, true);
413
+ this.offset += 8;
414
+ return v;
415
+ }
416
+ bytes_(n, field = "bytes") {
417
+ this.ensure(n, field);
418
+ const out = this.bytes.subarray(this.offset, this.offset + n);
419
+ this.offset += n;
420
+ return out;
421
+ }
422
+ /** Read a 32-byte pubkey and return base58. */
423
+ pubkey(field = "pubkey") {
424
+ return encodeBase58(this.bytes_(32, field));
425
+ }
426
+ /**
427
+ * Solana compact-u16 (a.k.a. ShortVec) length prefix. 1–3 bytes, 7 bits each.
428
+ */
429
+ compactU16(field = "compact-u16") {
430
+ let value = 0;
431
+ let shift = 0;
432
+ for (let i = 0; i < 3; i++) {
433
+ const byte = this.u8(`${field} length byte`);
434
+ value |= (byte & 127) << shift;
435
+ if ((byte & 128) === 0) return value >>> 0;
436
+ shift += 7;
437
+ }
438
+ return value >>> 0;
439
+ }
440
+ /** Remaining bytes (no copy). */
441
+ rest() {
442
+ const out = this.bytes.subarray(this.offset);
443
+ this.offset = this.bytes.length;
444
+ return out;
445
+ }
446
+ };
447
+
448
+ // src/decode/programs/system.ts
449
+ function acc(ix, i) {
450
+ return ix.accounts[i]?.pubkey;
451
+ }
452
+ function decodeSystem(ix) {
453
+ if (ix.data.length < 4) {
454
+ return { decoded: false, program: "System", warning: "instruction data too short" };
455
+ }
456
+ const r = new Reader(ix.data);
457
+ const disc = r.u32("discriminator");
458
+ try {
459
+ switch (disc) {
460
+ case 0: {
461
+ const lamports = r.u64("lamports");
462
+ const space = r.u64("space");
463
+ const owner = r.pubkey("owner");
464
+ const newAccount = acc(ix, 1) ?? "";
465
+ return {
466
+ program: "System",
467
+ type: "createAccount",
468
+ decoded: true,
469
+ args: { lamports: lamports.toString(), space: space.toString(), owner },
470
+ accountNames: ["funding", "new"],
471
+ effects: [
472
+ {
473
+ kind: "account-created",
474
+ address: newAccount,
475
+ owner,
476
+ lamports,
477
+ space: Number(space),
478
+ as: "system"
479
+ }
480
+ ]
481
+ };
482
+ }
483
+ case 1: {
484
+ const owner = r.pubkey("owner");
485
+ return {
486
+ program: "System",
487
+ type: "assign",
488
+ decoded: true,
489
+ args: { owner },
490
+ accountNames: ["account"]
491
+ };
492
+ }
493
+ case 2: {
494
+ const lamports = r.u64("lamports");
495
+ const from = acc(ix, 0) ?? "";
496
+ const to = acc(ix, 1) ?? "";
497
+ return {
498
+ program: "System",
499
+ type: "transfer",
500
+ decoded: true,
501
+ args: { lamports: lamports.toString() },
502
+ accountNames: ["from", "to"],
503
+ effects: [{ kind: "sol-transfer", from, to, lamports }]
504
+ };
505
+ }
506
+ case 3: {
507
+ const base = r.pubkey("base");
508
+ const seedLen = Number(r.u64("seed length"));
509
+ const seed = new TextDecoder().decode(r.bytes_(seedLen, "seed"));
510
+ const lamports = r.u64("lamports");
511
+ const space = r.u64("space");
512
+ const owner = r.pubkey("owner");
513
+ const newAccount = acc(ix, 1) ?? "";
514
+ return {
515
+ program: "System",
516
+ type: "createAccountWithSeed",
517
+ decoded: true,
518
+ args: { base, seed, lamports: lamports.toString(), space: space.toString(), owner },
519
+ accountNames: ["funding", "new", "base"],
520
+ effects: [
521
+ {
522
+ kind: "account-created",
523
+ address: newAccount,
524
+ owner,
525
+ lamports,
526
+ space: Number(space),
527
+ as: "system"
528
+ }
529
+ ]
530
+ };
531
+ }
532
+ case 8: {
533
+ const space = r.u64("space");
534
+ return {
535
+ program: "System",
536
+ type: "allocate",
537
+ decoded: true,
538
+ args: { space: space.toString() },
539
+ accountNames: ["account"]
540
+ };
541
+ }
542
+ case 9: {
543
+ const base = r.pubkey("base");
544
+ const seedLen = Number(r.u64("seed length"));
545
+ const seed = new TextDecoder().decode(r.bytes_(seedLen, "seed"));
546
+ const space = r.u64("space");
547
+ const owner = r.pubkey("owner");
548
+ return {
549
+ program: "System",
550
+ type: "allocateWithSeed",
551
+ decoded: true,
552
+ args: { base, seed, space: space.toString(), owner },
553
+ accountNames: ["account", "base"]
554
+ };
555
+ }
556
+ case 10: {
557
+ const base = r.pubkey("base");
558
+ const seedLen = Number(r.u64("seed length"));
559
+ const seed = new TextDecoder().decode(r.bytes_(seedLen, "seed"));
560
+ const owner = r.pubkey("owner");
561
+ return {
562
+ program: "System",
563
+ type: "assignWithSeed",
564
+ decoded: true,
565
+ args: { base, seed, owner },
566
+ accountNames: ["account", "base"]
567
+ };
568
+ }
569
+ case 11: {
570
+ const lamports = r.u64("lamports");
571
+ const seedLen = Number(r.u64("seed length"));
572
+ const fromSeed = new TextDecoder().decode(r.bytes_(seedLen, "fromSeed"));
573
+ const fromOwner = r.pubkey("fromOwner");
574
+ const from = acc(ix, 0) ?? "";
575
+ const to = acc(ix, 2) ?? "";
576
+ return {
577
+ program: "System",
578
+ type: "transferWithSeed",
579
+ decoded: true,
580
+ args: { lamports: lamports.toString(), fromSeed, fromOwner },
581
+ accountNames: ["from", "base", "to"],
582
+ effects: [{ kind: "sol-transfer", from, to, lamports }]
583
+ };
584
+ }
585
+ default: {
586
+ const names = {
587
+ 4: "advanceNonceAccount",
588
+ 5: "withdrawNonceAccount",
589
+ 6: "initializeNonceAccount",
590
+ 7: "authorizeNonceAccount"
591
+ };
592
+ return {
593
+ program: "System",
594
+ type: names[disc] ?? `unknown(${disc})`,
595
+ decoded: disc in names
596
+ };
597
+ }
598
+ }
599
+ } catch (err) {
600
+ return {
601
+ program: "System",
602
+ decoded: false,
603
+ warning: err instanceof Error ? err.message : "system decode failed",
604
+ warningCode: "partial-decode"
605
+ };
606
+ }
607
+ }
608
+ var systemDecoder = {
609
+ programId: SYSTEM_PROGRAM_ID,
610
+ name: "System",
611
+ kind: "system",
612
+ decode: decodeSystem
613
+ };
614
+
615
+ // src/decode/programs/spl-token.ts
616
+ var MAX_U64 = (1n << 64n) - 1n;
617
+ function acc2(ix, i) {
618
+ return ix.accounts[i]?.pubkey;
619
+ }
620
+ function decodeTokenInstruction(ix, programName, variant) {
621
+ if (ix.data.length < 1) {
622
+ return { decoded: false, program: programName, warning: "empty instruction data" };
623
+ }
624
+ const r = new Reader(ix.data);
625
+ const tag = r.u8("tag");
626
+ try {
627
+ switch (tag) {
628
+ case 0: {
629
+ const decimals = r.u8("decimals");
630
+ return {
631
+ program: programName,
632
+ type: "initializeMint",
633
+ decoded: true,
634
+ args: { decimals },
635
+ accountNames: ["mint", "rent"]
636
+ };
637
+ }
638
+ case 20: {
639
+ const decimals = r.u8("decimals");
640
+ return {
641
+ program: programName,
642
+ type: "initializeMint2",
643
+ decoded: true,
644
+ args: { decimals },
645
+ accountNames: ["mint"]
646
+ };
647
+ }
648
+ case 1:
649
+ case 16:
650
+ case 18: {
651
+ const account = acc2(ix, 0) ?? "";
652
+ const mint = acc2(ix, 1) ?? "";
653
+ const owner = acc2(ix, 2);
654
+ const type = tag === 1 ? "initializeAccount" : tag === 16 ? "initializeAccount2" : "initializeAccount3";
655
+ return {
656
+ program: programName,
657
+ type,
658
+ decoded: true,
659
+ accountNames: ["account", "mint", "owner"],
660
+ effects: [
661
+ {
662
+ kind: "account-created",
663
+ address: account,
664
+ owner: owner ?? mint,
665
+ as: "token-account"
666
+ }
667
+ ]
668
+ };
669
+ }
670
+ case 3: {
671
+ const amount = r.u64("amount");
672
+ const from = acc2(ix, 0) ?? "";
673
+ const to = acc2(ix, 1) ?? "";
674
+ return {
675
+ program: programName,
676
+ type: "transfer",
677
+ decoded: true,
678
+ args: { amount: amount.toString() },
679
+ accountNames: ["source", "destination", "authority"],
680
+ // No decimals/mint in unchecked transfer — correlate fills from diff.
681
+ effects: [{ kind: "token-transfer", from, to, amount, tokenProgram: variant }],
682
+ warning: "unchecked transfer carries no mint/decimals",
683
+ warningCode: "ambiguous-amount"
684
+ };
685
+ }
686
+ case 12: {
687
+ const amount = r.u64("amount");
688
+ const decimals = r.u8("decimals");
689
+ const source = acc2(ix, 0) ?? "";
690
+ const mint = acc2(ix, 1) ?? "";
691
+ const dest = acc2(ix, 2) ?? "";
692
+ return {
693
+ program: programName,
694
+ type: "transferChecked",
695
+ decoded: true,
696
+ args: { amount: amount.toString(), decimals },
697
+ accountNames: ["source", "mint", "destination", "authority"],
698
+ effects: [
699
+ { kind: "token-transfer", from: source, to: dest, mint, amount, decimals, tokenProgram: variant }
700
+ ]
701
+ };
702
+ }
703
+ case 4: {
704
+ const amount = r.u64("amount");
705
+ const delegate = acc2(ix, 1) ?? "";
706
+ const owner = acc2(ix, 2) ?? "";
707
+ return {
708
+ program: programName,
709
+ type: "approve",
710
+ decoded: true,
711
+ args: { amount: amount.toString() },
712
+ accountNames: ["source", "delegate", "owner"],
713
+ effects: [
714
+ {
715
+ kind: "approval",
716
+ owner,
717
+ delegate,
718
+ mint: "",
719
+ amount: amount === MAX_U64 ? "unlimited" : amount
720
+ }
721
+ ]
722
+ };
723
+ }
724
+ case 13: {
725
+ const amount = r.u64("amount");
726
+ const decimals = r.u8("decimals");
727
+ const mint = acc2(ix, 1) ?? "";
728
+ const delegate = acc2(ix, 2) ?? "";
729
+ const owner = acc2(ix, 3) ?? "";
730
+ return {
731
+ program: programName,
732
+ type: "approveChecked",
733
+ decoded: true,
734
+ args: { amount: amount.toString(), decimals },
735
+ accountNames: ["source", "mint", "delegate", "owner"],
736
+ effects: [
737
+ {
738
+ kind: "approval",
739
+ owner,
740
+ delegate,
741
+ mint,
742
+ amount: amount === MAX_U64 ? "unlimited" : amount
743
+ }
744
+ ]
745
+ };
746
+ }
747
+ case 5: {
748
+ const owner = acc2(ix, 1) ?? "";
749
+ return {
750
+ program: programName,
751
+ type: "revoke",
752
+ decoded: true,
753
+ accountNames: ["source", "owner"],
754
+ effects: [{ kind: "approval", owner, delegate: "", mint: "", amount: 0n, revoke: true }]
755
+ };
756
+ }
757
+ case 6: {
758
+ return {
759
+ program: programName,
760
+ type: "setAuthority",
761
+ decoded: true,
762
+ accountNames: ["account", "currentAuthority"]
763
+ };
764
+ }
765
+ case 7: {
766
+ const amount = r.u64("amount");
767
+ const mint = acc2(ix, 0) ?? "";
768
+ const to = acc2(ix, 1) ?? "";
769
+ return {
770
+ program: programName,
771
+ type: "mintTo",
772
+ decoded: true,
773
+ args: { amount: amount.toString() },
774
+ accountNames: ["mint", "destination", "authority"],
775
+ effects: [{ kind: "mint", mint, to, amount }]
776
+ };
777
+ }
778
+ case 14: {
779
+ const amount = r.u64("amount");
780
+ const decimals = r.u8("decimals");
781
+ const mint = acc2(ix, 0) ?? "";
782
+ const to = acc2(ix, 1) ?? "";
783
+ return {
784
+ program: programName,
785
+ type: "mintToChecked",
786
+ decoded: true,
787
+ args: { amount: amount.toString(), decimals },
788
+ accountNames: ["mint", "destination", "authority"],
789
+ effects: [{ kind: "mint", mint, to, amount, decimals }]
790
+ };
791
+ }
792
+ case 8: {
793
+ const amount = r.u64("amount");
794
+ const from = acc2(ix, 0) ?? "";
795
+ const mint = acc2(ix, 1) ?? "";
796
+ return {
797
+ program: programName,
798
+ type: "burn",
799
+ decoded: true,
800
+ args: { amount: amount.toString() },
801
+ accountNames: ["account", "mint", "authority"],
802
+ effects: [{ kind: "burn", mint, from, amount }]
803
+ };
804
+ }
805
+ case 15: {
806
+ const amount = r.u64("amount");
807
+ const decimals = r.u8("decimals");
808
+ const from = acc2(ix, 0) ?? "";
809
+ const mint = acc2(ix, 1) ?? "";
810
+ return {
811
+ program: programName,
812
+ type: "burnChecked",
813
+ decoded: true,
814
+ args: { amount: amount.toString(), decimals },
815
+ accountNames: ["account", "mint", "authority"],
816
+ effects: [{ kind: "burn", mint, from, amount, decimals }]
817
+ };
818
+ }
819
+ case 9: {
820
+ const account = acc2(ix, 0) ?? "";
821
+ const destination = acc2(ix, 1) ?? "";
822
+ return {
823
+ program: programName,
824
+ type: "closeAccount",
825
+ decoded: true,
826
+ accountNames: ["account", "destination", "owner"],
827
+ effects: [{ kind: "close-account", account, destination }]
828
+ };
829
+ }
830
+ case 10:
831
+ return {
832
+ program: programName,
833
+ type: "freezeAccount",
834
+ decoded: true,
835
+ accountNames: ["account", "mint", "authority"]
836
+ };
837
+ case 11:
838
+ return {
839
+ program: programName,
840
+ type: "thawAccount",
841
+ decoded: true,
842
+ accountNames: ["account", "mint", "authority"]
843
+ };
844
+ case 17: {
845
+ const account = acc2(ix, 0) ?? "";
846
+ return {
847
+ program: programName,
848
+ type: "syncNative",
849
+ decoded: true,
850
+ accountNames: ["account"],
851
+ effects: [{ kind: "sync-native", account }]
852
+ };
853
+ }
854
+ default:
855
+ return {
856
+ program: programName,
857
+ type: `unknown(${tag})`,
858
+ decoded: false,
859
+ warning: `unrecognized ${programName} instruction tag ${tag}`,
860
+ warningCode: "partial-decode"
861
+ };
862
+ }
863
+ } catch (err) {
864
+ return {
865
+ program: programName,
866
+ decoded: false,
867
+ warning: err instanceof Error ? err.message : "token decode failed",
868
+ warningCode: "partial-decode"
869
+ };
870
+ }
871
+ }
872
+
873
+ // src/decode/programs/token-2022.ts
874
+ var EXTENSION_TAGS = {
875
+ 21: "getAccountDataSize",
876
+ 22: "initializeMintCloseAuthority",
877
+ 23: "transferFeeExtension",
878
+ 24: "confidentialTransferExtension",
879
+ 25: "defaultAccountStateExtension",
880
+ 26: "reallocate",
881
+ 27: "memoTransferExtension",
882
+ 28: "createNativeMint",
883
+ 29: "initializeNonTransferableMint",
884
+ 30: "interestBearingMintExtension",
885
+ 31: "cpiGuardExtension",
886
+ 32: "initializePermanentDelegate",
887
+ 33: "transferHookExtension",
888
+ 34: "confidentialTransferFeeExtension",
889
+ 35: "withdrawExcessLamports",
890
+ 36: "metadataPointerExtension",
891
+ 37: "groupPointerExtension",
892
+ 38: "groupMemberPointerExtension"
893
+ };
894
+ function decodeToken2022(ix) {
895
+ if (ix.data.length < 1) {
896
+ return { decoded: false, program: "Token-2022", warning: "empty instruction data" };
897
+ }
898
+ const tag = ix.data[0];
899
+ if (tag >= 21) {
900
+ const name = EXTENSION_TAGS[tag] ?? `extension(${tag})`;
901
+ return {
902
+ program: "Token-2022",
903
+ type: name,
904
+ decoded: false,
905
+ warning: `Token-2022 extension instruction "${name}" not byte-decoded; effect visible in balance diff`,
906
+ warningCode: "token-2022-extension-unparsed"
907
+ };
908
+ }
909
+ return decodeTokenInstruction(ix, "Token-2022", "token-2022");
910
+ }
911
+ var token2022Decoder = {
912
+ programId: TOKEN_2022_PROGRAM_ID,
913
+ name: "Token-2022",
914
+ kind: "token-2022",
915
+ decode: decodeToken2022
916
+ };
917
+ var splTokenDecoder = {
918
+ programId: TOKEN_PROGRAM_ID,
919
+ name: "SPL Token",
920
+ kind: "token",
921
+ decode: (ix) => decodeTokenInstruction(ix, "SPL Token", "spl-token")
922
+ };
923
+
924
+ // src/decode/programs/ata.ts
925
+ function acc3(ix, i) {
926
+ return ix.accounts[i]?.pubkey;
927
+ }
928
+ function decodeAta(ix) {
929
+ const tag = ix.data.length === 0 ? 0 : ix.data[0];
930
+ const type = tag === 0 ? "create" : tag === 1 ? "createIdempotent" : tag === 2 ? "recoverNested" : void 0;
931
+ if (type === void 0) {
932
+ return {
933
+ program: "Associated Token Account",
934
+ decoded: false,
935
+ warning: `unrecognized ATA instruction tag ${tag}`,
936
+ warningCode: "partial-decode"
937
+ };
938
+ }
939
+ if (type === "recoverNested") {
940
+ return {
941
+ program: "Associated Token Account",
942
+ type,
943
+ decoded: true,
944
+ accountNames: ["nestedAta", "nestedMint", "destinationAta", "ownerAta", "ownerMint", "wallet"]
945
+ };
946
+ }
947
+ const ata = acc3(ix, 1) ?? "";
948
+ const owner = acc3(ix, 2) ?? "";
949
+ const tokenProgram = acc3(ix, 5);
950
+ return {
951
+ program: "Associated Token Account",
952
+ type,
953
+ decoded: true,
954
+ accountNames: ["payer", "ata", "owner", "mint", "systemProgram", "tokenProgram"],
955
+ effects: [
956
+ {
957
+ kind: "account-created",
958
+ address: ata,
959
+ owner: owner || (tokenProgram ?? ""),
960
+ as: "ata"
961
+ }
962
+ ]
963
+ };
964
+ }
965
+ var ataDecoder = {
966
+ programId: ASSOCIATED_TOKEN_PROGRAM_ID,
967
+ name: "Associated Token Account",
968
+ kind: "ata",
969
+ decode: decodeAta
970
+ };
971
+
972
+ // src/decode/programs/memo.ts
973
+ var MAX_DISPLAY = 256;
974
+ function decodeMemoData(ix, name) {
975
+ const text = new TextDecoder("utf-8", { fatal: false }).decode(ix.data);
976
+ const display = text.length > MAX_DISPLAY ? `${text.slice(0, MAX_DISPLAY)}\u2026` : text;
977
+ const truncated = text.length > MAX_DISPLAY;
978
+ return {
979
+ program: name,
980
+ type: "memo",
981
+ decoded: true,
982
+ args: { text: display, ...truncated ? { truncated: true } : {} },
983
+ effects: [{ kind: "memo", text: display }],
984
+ ...truncated ? { warning: "memo truncated for display; full bytes available via --raw", warningCode: "partial-decode" } : {}
985
+ };
986
+ }
987
+ var memoDecoder = {
988
+ programId: MEMO_PROGRAM_ID,
989
+ name: "Memo",
990
+ kind: "memo",
991
+ decode: (ix) => decodeMemoData(ix, "Memo")
992
+ };
993
+ var memoV1Decoder = {
994
+ programId: MEMO_V1_PROGRAM_ID,
995
+ name: "Memo (v1)",
996
+ kind: "memo",
997
+ decode: (ix) => decodeMemoData(ix, "Memo (v1)")
998
+ };
999
+
1000
+ // src/decode/programs/compute-budget.ts
1001
+ function decodeComputeBudget(ix) {
1002
+ if (ix.data.length < 1) {
1003
+ return { decoded: false, program: "Compute Budget", warning: "empty instruction data" };
1004
+ }
1005
+ const r = new Reader(ix.data);
1006
+ const tag = r.u8("tag");
1007
+ try {
1008
+ switch (tag) {
1009
+ case 1: {
1010
+ const bytes = r.u32("heap bytes");
1011
+ return {
1012
+ program: "Compute Budget",
1013
+ type: "requestHeapFrame",
1014
+ decoded: true,
1015
+ args: { bytes }
1016
+ };
1017
+ }
1018
+ case 2: {
1019
+ const units = r.u32("unit limit");
1020
+ return {
1021
+ program: "Compute Budget",
1022
+ type: "setComputeUnitLimit",
1023
+ decoded: true,
1024
+ args: { units },
1025
+ effects: [{ kind: "compute-budget", unitLimit: units }]
1026
+ };
1027
+ }
1028
+ case 3: {
1029
+ const price = r.u64("micro-lamports");
1030
+ return {
1031
+ program: "Compute Budget",
1032
+ type: "setComputeUnitPrice",
1033
+ decoded: true,
1034
+ args: { microLamports: price.toString() },
1035
+ effects: [{ kind: "compute-budget", unitPriceMicroLamports: price }]
1036
+ };
1037
+ }
1038
+ case 4: {
1039
+ const bytes = r.u32("data size limit");
1040
+ return {
1041
+ program: "Compute Budget",
1042
+ type: "setLoadedAccountsDataSizeLimit",
1043
+ decoded: true,
1044
+ args: { bytes }
1045
+ };
1046
+ }
1047
+ case 0:
1048
+ return {
1049
+ program: "Compute Budget",
1050
+ type: "requestUnits",
1051
+ decoded: true
1052
+ };
1053
+ default:
1054
+ return {
1055
+ program: "Compute Budget",
1056
+ type: `unknown(${tag})`,
1057
+ decoded: false,
1058
+ warningCode: "partial-decode"
1059
+ };
1060
+ }
1061
+ } catch (err) {
1062
+ return {
1063
+ program: "Compute Budget",
1064
+ decoded: false,
1065
+ warning: err instanceof Error ? err.message : "compute-budget decode failed",
1066
+ warningCode: "partial-decode"
1067
+ };
1068
+ }
1069
+ }
1070
+ var computeBudgetDecoder = {
1071
+ programId: COMPUTE_BUDGET_PROGRAM_ID,
1072
+ name: "Compute Budget",
1073
+ kind: "compute-budget",
1074
+ decode: decodeComputeBudget
1075
+ };
1076
+
1077
+ // src/decode/programs/recognize.ts
1078
+ var ALREADY_DECODED = /* @__PURE__ */ new Set([
1079
+ "11111111111111111111111111111111",
1080
+ "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
1081
+ "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
1082
+ "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
1083
+ "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr",
1084
+ "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo",
1085
+ "ComputeBudget111111111111111111111111111111"
1086
+ ]);
1087
+ function buildRecognizers() {
1088
+ const decoders = [];
1089
+ for (const [programId, info] of Object.entries(KNOWN_PROGRAMS)) {
1090
+ if (ALREADY_DECODED.has(programId)) continue;
1091
+ decoders.push({
1092
+ programId,
1093
+ name: info.name,
1094
+ kind: info.kind,
1095
+ decode: () => ({
1096
+ program: info.name,
1097
+ decoded: false,
1098
+ // Honest signal: we know *who* but not the exact ix semantics.
1099
+ warning: `${info.name} recognized by program id; semantics inferred from balance diff (no IDL)`
1100
+ })
1101
+ });
1102
+ }
1103
+ return decoders;
1104
+ }
1105
+
1106
+ // src/decode/registry.ts
1107
+ var Registry = class _Registry {
1108
+ map;
1109
+ constructor(decoders) {
1110
+ this.map = /* @__PURE__ */ new Map();
1111
+ for (const d of decoders) this.map.set(d.programId, d);
1112
+ }
1113
+ get(programId) {
1114
+ return this.map.get(programId);
1115
+ }
1116
+ has(programId) {
1117
+ return this.map.has(programId);
1118
+ }
1119
+ list() {
1120
+ return [...this.map.values()];
1121
+ }
1122
+ merge(decoders) {
1123
+ const merged = new Map(this.map);
1124
+ for (const d of decoders) merged.set(d.programId, d);
1125
+ return new _Registry([...merged.values()]);
1126
+ }
1127
+ };
1128
+ var BUNDLED_DECODERS = [
1129
+ systemDecoder,
1130
+ splTokenDecoder,
1131
+ token2022Decoder,
1132
+ ataDecoder,
1133
+ memoDecoder,
1134
+ memoV1Decoder,
1135
+ computeBudgetDecoder
1136
+ ];
1137
+ function createRegistry(decoders) {
1138
+ const base = [...BUNDLED_DECODERS, ...buildRecognizers()];
1139
+ const reg = new Registry(base);
1140
+ return reg;
1141
+ }
1142
+ var defaultRegistry = createRegistry();
1143
+ function decodeInstruction(ix, registry = defaultRegistry, precomputed) {
1144
+ const decoder = registry.get(ix.programId);
1145
+ const known = knownProgram(ix.programId);
1146
+ const baseAccounts = ix.accounts.map((a) => ({
1147
+ pubkey: a.pubkey,
1148
+ isSigner: a.isSigner,
1149
+ isWritable: a.isWritable
1150
+ }));
1151
+ if (!decoder) {
1152
+ return {
1153
+ index: ix.index,
1154
+ programId: ix.programId,
1155
+ ...known ? { program: known.name } : {},
1156
+ decoded: false,
1157
+ accounts: baseAccounts,
1158
+ warning: known ? `${known.name} recognized by program id (no byte decoder)` : "unknown program (no decoder)"
1159
+ };
1160
+ }
1161
+ let out;
1162
+ try {
1163
+ out = precomputed ?? decoder.decode(ix);
1164
+ } catch (err) {
1165
+ return {
1166
+ index: ix.index,
1167
+ programId: ix.programId,
1168
+ program: decoder.name,
1169
+ decoded: false,
1170
+ accounts: baseAccounts,
1171
+ warning: `decoder error: ${err instanceof Error ? err.message : String(err)}`
1172
+ };
1173
+ }
1174
+ const accounts = baseAccounts.map((a, i) => {
1175
+ const name = out.accountNames?.[i];
1176
+ return name !== void 0 ? { ...a, name } : a;
1177
+ });
1178
+ return {
1179
+ index: ix.index,
1180
+ programId: ix.programId,
1181
+ ...out.program !== void 0 ? { program: out.program } : {},
1182
+ ...out.type !== void 0 ? { type: out.type } : {},
1183
+ decoded: out.decoded,
1184
+ accounts,
1185
+ ...out.args !== void 0 ? { args: out.args } : {},
1186
+ ...out.warning !== void 0 ? { warning: out.warning } : {}
1187
+ };
1188
+ }
1189
+ function decodeInstructionRaw(ix, registry = defaultRegistry) {
1190
+ const decoder = registry.get(ix.programId);
1191
+ if (!decoder) return { decoded: decodeInstruction(ix, registry), effects: [] };
1192
+ let out;
1193
+ try {
1194
+ out = decoder.decode(ix);
1195
+ } catch {
1196
+ return { decoded: decodeInstruction(ix, registry), effects: [] };
1197
+ }
1198
+ const decoded = decodeInstruction(ix, registry, out);
1199
+ return {
1200
+ decoded,
1201
+ effects: out.effects ?? [],
1202
+ ...out.warningCode !== void 0 ? { warningCode: out.warningCode } : {}
1203
+ };
1204
+ }
1205
+
1206
+ // src/decode/message.ts
1207
+ var VERSION_PREFIX_MASK = 128;
1208
+ function stripSignatures(bytes) {
1209
+ if (bytes.length === 0) {
1210
+ throw new DecodeError("DECODE_FAILED", "empty transaction buffer");
1211
+ }
1212
+ const reader = new Reader(bytes);
1213
+ let sigCount;
1214
+ try {
1215
+ sigCount = reader.compactU16("signature count");
1216
+ } catch {
1217
+ return bytes;
1218
+ }
1219
+ if (sigCount > 0 && sigCount <= 64) {
1220
+ const afterSigs = reader.offset + sigCount * 64;
1221
+ if (afterSigs < bytes.length) {
1222
+ const next = bytes[afterSigs];
1223
+ if (next !== void 0) {
1224
+ const looksVersioned = (next & VERSION_PREFIX_MASK) !== 0;
1225
+ const looksLegacyHeader = next > 0 && next <= 32;
1226
+ if (looksVersioned || looksLegacyHeader) {
1227
+ return bytes.subarray(afterSigs);
1228
+ }
1229
+ }
1230
+ }
1231
+ }
1232
+ return bytes;
1233
+ }
1234
+ function parseMessage(messageBytes) {
1235
+ const r = new Reader(messageBytes);
1236
+ const firstByte = r.u8("version/header byte");
1237
+ let version;
1238
+ let numRequiredSignatures;
1239
+ if ((firstByte & VERSION_PREFIX_MASK) !== 0) {
1240
+ version = firstByte & 127;
1241
+ if (version !== 0) {
1242
+ throw new DecodeError(
1243
+ "DECODE_FAILED",
1244
+ `unsupported transaction message version v${version} (only legacy and v0 are supported)`
1245
+ );
1246
+ }
1247
+ numRequiredSignatures = r.u8("numRequiredSignatures");
1248
+ } else {
1249
+ version = "legacy";
1250
+ numRequiredSignatures = firstByte;
1251
+ }
1252
+ const numReadonlySignedAccounts = r.u8("numReadonlySignedAccounts");
1253
+ const numReadonlyUnsignedAccounts = r.u8("numReadonlyUnsignedAccounts");
1254
+ const keyCount = r.compactU16("account key count");
1255
+ if (keyCount > 256) {
1256
+ throw new DecodeError("DECODE_FAILED", `implausible account key count: ${keyCount}`);
1257
+ }
1258
+ const staticAccountKeys = [];
1259
+ for (let i = 0; i < keyCount; i++) {
1260
+ staticAccountKeys.push(r.pubkey(`account key #${i}`));
1261
+ }
1262
+ const recentBlockhash = r.pubkey("recentBlockhash");
1263
+ const ixCount = r.compactU16("instruction count");
1264
+ if (ixCount > 1024) {
1265
+ throw new DecodeError("DECODE_FAILED", `implausible instruction count: ${ixCount}`);
1266
+ }
1267
+ const compiled = [];
1268
+ for (let i = 0; i < ixCount; i++) {
1269
+ const programIdIndex = r.u8(`instruction #${i} programIdIndex`);
1270
+ const accCount = r.compactU16(`instruction #${i} account count`);
1271
+ const accountIndexes = [];
1272
+ for (let j = 0; j < accCount; j++) {
1273
+ accountIndexes.push(r.u8(`instruction #${i} account index #${j}`));
1274
+ }
1275
+ const dataLen = r.compactU16(`instruction #${i} data length`);
1276
+ const data = r.bytes_(dataLen, `instruction #${i} data`).slice();
1277
+ compiled.push({ programIdIndex, accountIndexes, data });
1278
+ }
1279
+ const addressTableLookups = [];
1280
+ let hasUnresolvedLut = false;
1281
+ if (version === 0) {
1282
+ const lutCount = r.compactU16("address table lookup count");
1283
+ for (let i = 0; i < lutCount; i++) {
1284
+ const accountKey = r.pubkey(`LUT #${i} account key`);
1285
+ const writableCount = r.compactU16(`LUT #${i} writable count`);
1286
+ const writableIndexes = [];
1287
+ for (let j = 0; j < writableCount; j++) {
1288
+ writableIndexes.push(r.u8(`LUT #${i} writable index #${j}`));
1289
+ }
1290
+ const readonlyCount = r.compactU16(`LUT #${i} readonly count`);
1291
+ const readonlyIndexes = [];
1292
+ for (let j = 0; j < readonlyCount; j++) {
1293
+ readonlyIndexes.push(r.u8(`LUT #${i} readonly index #${j}`));
1294
+ }
1295
+ addressTableLookups.push({ accountKey, writableIndexes, readonlyIndexes });
1296
+ if (writableIndexes.length + readonlyIndexes.length > 0) hasUnresolvedLut = true;
1297
+ }
1298
+ }
1299
+ return {
1300
+ version,
1301
+ staticAccountKeys,
1302
+ header: {
1303
+ numRequiredSignatures,
1304
+ numReadonlySignedAccounts,
1305
+ numReadonlyUnsignedAccounts
1306
+ },
1307
+ recentBlockhash,
1308
+ compiled,
1309
+ addressTableLookups,
1310
+ hasUnresolvedLut
1311
+ };
1312
+ }
1313
+ function buildAccountList(msg, loaded) {
1314
+ const loadedWritable = loaded?.writable ?? [];
1315
+ const loadedReadonly = loaded?.readonly ?? [];
1316
+ const accountKeys = [...msg.staticAccountKeys, ...loadedWritable, ...loadedReadonly];
1317
+ const { numRequiredSignatures, numReadonlySignedAccounts, numReadonlyUnsignedAccounts } = msg.header;
1318
+ const staticCount = msg.staticAccountKeys.length;
1319
+ const isSigner = [];
1320
+ const isWritable = [];
1321
+ for (let i = 0; i < accountKeys.length; i++) {
1322
+ if (i < staticCount) {
1323
+ const signer = i < numRequiredSignatures;
1324
+ let writable;
1325
+ if (signer) {
1326
+ writable = i < numRequiredSignatures - numReadonlySignedAccounts;
1327
+ } else {
1328
+ const unsignedIndex = i - numRequiredSignatures;
1329
+ const unsignedWritableCount = staticCount - numRequiredSignatures - numReadonlyUnsignedAccounts;
1330
+ writable = unsignedIndex < unsignedWritableCount;
1331
+ }
1332
+ isSigner.push(signer);
1333
+ isWritable.push(writable);
1334
+ } else {
1335
+ const loadedIndex = i - staticCount;
1336
+ isSigner.push(false);
1337
+ isWritable.push(loadedIndex < loadedWritable.length);
1338
+ }
1339
+ }
1340
+ return { accountKeys, isSigner, isWritable };
1341
+ }
1342
+ function toInstructionViews(msg, resolved) {
1343
+ const { accountKeys, isSigner, isWritable } = resolved;
1344
+ return msg.compiled.map((ix, index) => {
1345
+ const programId = accountKeys[ix.programIdIndex];
1346
+ if (programId === void 0) {
1347
+ throw new DecodeError(
1348
+ "DECODE_FAILED",
1349
+ `instruction #${index} programIdIndex ${ix.programIdIndex} out of range (have ${accountKeys.length} keys)`
1350
+ );
1351
+ }
1352
+ const accounts = ix.accountIndexes.map((ai) => {
1353
+ const pubkey = accountKeys[ai];
1354
+ if (pubkey === void 0) {
1355
+ throw new DecodeError(
1356
+ "DECODE_FAILED",
1357
+ `instruction #${index} references account index ${ai} out of range (have ${accountKeys.length} keys)`
1358
+ );
1359
+ }
1360
+ return {
1361
+ pubkey,
1362
+ isSigner: isSigner[ai] ?? false,
1363
+ isWritable: isWritable[ai] ?? false
1364
+ };
1365
+ });
1366
+ return { index, programId, accounts, data: ix.data };
1367
+ });
1368
+ }
1369
+
1370
+ // src/input/detect.ts
1371
+ function detectInput(input) {
1372
+ if (Array.isArray(input)) {
1373
+ return { kind: "instruction-set", instructions: validateInstructions(input) };
1374
+ }
1375
+ if (input instanceof Uint8Array) {
1376
+ if (input.length === 0) {
1377
+ throw new InputError("EMPTY_INPUT", "Empty byte input.", {
1378
+ hint: "Pass a signature, a serialized transaction, or pipe data via --stdin."
1379
+ });
1380
+ }
1381
+ assertWireMessage(input, "raw bytes");
1382
+ return { kind: "tx-bytes", bytes: input, encoding: "base64", ambiguous: false };
1383
+ }
1384
+ const trimmed = input.trim();
1385
+ if (trimmed.length === 0) {
1386
+ throw new InputError("EMPTY_INPUT", "Input is empty or whitespace only.", {
1387
+ hint: "Pass a signature, a serialized transaction, or pipe data via --stdin."
1388
+ });
1389
+ }
1390
+ if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
1391
+ let parsed;
1392
+ try {
1393
+ parsed = JSON.parse(trimmed);
1394
+ } catch (err) {
1395
+ throw new InputError("INVALID_INPUT", "Input looks like JSON but failed to parse.", {
1396
+ cause: err
1397
+ });
1398
+ }
1399
+ const arr = Array.isArray(parsed) ? parsed : null;
1400
+ if (!arr) {
1401
+ throw new InputError(
1402
+ "INVALID_INPUT",
1403
+ "JSON instruction input must be an array of instructions."
1404
+ );
1405
+ }
1406
+ return { kind: "instruction-set", instructions: validateInstructions(arr) };
1407
+ }
1408
+ if (looksLikeBase58(trimmed) && trimmed.length >= 64 && trimmed.length <= 88) {
1409
+ const decoded = tryDecodeBase58(trimmed);
1410
+ if (decoded && decoded.length === 64) {
1411
+ return { kind: "signature", signature: trimmed };
1412
+ }
1413
+ if (decoded && decoded.length !== 64) ;
1414
+ }
1415
+ const asB64 = tryDecodeBase64(trimmed);
1416
+ const asB58 = tryDecodeBase58(trimmed);
1417
+ const b64Ok = asB64 ? isWireMessage(asB64) : false;
1418
+ const b58Ok = asB58 ? isWireMessage(asB58) : false;
1419
+ if (b64Ok && b58Ok) {
1420
+ return { kind: "tx-bytes", bytes: asB64, encoding: "base64", ambiguous: true };
1421
+ }
1422
+ if (b64Ok) {
1423
+ return { kind: "tx-bytes", bytes: asB64, encoding: "base64", ambiguous: false };
1424
+ }
1425
+ if (b58Ok) {
1426
+ return { kind: "tx-bytes", bytes: asB58, encoding: "base58", ambiguous: false };
1427
+ }
1428
+ if (looksLikeBase58(trimmed)) {
1429
+ const decoded = tryDecodeBase58(trimmed);
1430
+ if (decoded) {
1431
+ throw new InputError(
1432
+ "INVALID_SIGNATURE",
1433
+ `Input decoded as ${decoded.length} bytes; a signature must be exactly 64 bytes, and it is not a valid transaction.`,
1434
+ { hint: "Provide a 64-byte base58 signature or a base64/base58 serialized transaction." }
1435
+ );
1436
+ }
1437
+ }
1438
+ throw new InputError(
1439
+ "INVALID_INPUT",
1440
+ "Could not classify input as a signature, transaction, or instruction set.",
1441
+ { hint: "Provide a base58 signature, a base64/base58 transaction, or a JSON instruction array." }
1442
+ );
1443
+ }
1444
+ function detectTxBytes(input, encoding = "auto") {
1445
+ const trimmed = input.trim();
1446
+ if (trimmed.length === 0) {
1447
+ throw new InputError("EMPTY_INPUT", "Empty transaction input.");
1448
+ }
1449
+ if (encoding === "base64") {
1450
+ const bytes = tryDecodeBase64(trimmed);
1451
+ if (!bytes) throw new InputError("INVALID_ENCODING", "Input is not valid base64.");
1452
+ assertWireMessage(bytes, "base64 transaction");
1453
+ return { bytes, encoding: "base64", ambiguous: false };
1454
+ }
1455
+ if (encoding === "base58") {
1456
+ const bytes = tryDecodeBase58(trimmed);
1457
+ if (!bytes) throw new InputError("INVALID_ENCODING", "Input is not valid base58.");
1458
+ assertWireMessage(bytes, "base58 transaction");
1459
+ return { bytes, encoding: "base58", ambiguous: false };
1460
+ }
1461
+ const asB64 = tryDecodeBase64(trimmed);
1462
+ const asB58 = tryDecodeBase58(trimmed);
1463
+ const b64Ok = asB64 ? isWireMessage(asB64) : false;
1464
+ const b58Ok = asB58 ? isWireMessage(asB58) : false;
1465
+ if (b64Ok) return { bytes: asB64, encoding: "base64", ambiguous: b58Ok };
1466
+ if (b58Ok) return { bytes: asB58, encoding: "base58", ambiguous: false };
1467
+ throw new InputError("DECODE_FAILED", "Input did not parse as a serialized transaction.");
1468
+ }
1469
+ function isWireMessage(bytes) {
1470
+ try {
1471
+ const stripped = stripSignatures(bytes);
1472
+ const msg = parseMessage(stripped);
1473
+ return msg.staticAccountKeys.length > 0 && msg.compiled.length >= 0;
1474
+ } catch {
1475
+ return false;
1476
+ }
1477
+ }
1478
+ function assertWireMessage(bytes, label) {
1479
+ if (!isWireMessage(bytes)) {
1480
+ throw new InputError(
1481
+ "DECODE_FAILED",
1482
+ `${label} did not parse as a Solana wire transaction message.`,
1483
+ { hint: "Ensure the input is a serialized (not deserialized) transaction." }
1484
+ );
1485
+ }
1486
+ }
1487
+ function validateInstructions(arr) {
1488
+ if (arr.length === 0) {
1489
+ throw new InputError("EMPTY_INPUT", "Instruction set is empty.");
1490
+ }
1491
+ return arr.map((raw, i) => {
1492
+ if (typeof raw !== "object" || raw === null) {
1493
+ throw new InputError("INVALID_INPUT", `Instruction #${i} is not an object.`);
1494
+ }
1495
+ const o = raw;
1496
+ if (typeof o["programId"] !== "string") {
1497
+ throw new InputError("INVALID_INPUT", `Instruction #${i} is missing a string "programId".`);
1498
+ }
1499
+ const accountsRaw = o["accounts"];
1500
+ if (!Array.isArray(accountsRaw)) {
1501
+ throw new InputError("INVALID_INPUT", `Instruction #${i} "accounts" must be an array.`);
1502
+ }
1503
+ const accounts = accountsRaw.map((a, j) => {
1504
+ if (typeof a !== "object" || a === null) {
1505
+ throw new InputError("INVALID_INPUT", `Instruction #${i} account #${j} is not an object.`);
1506
+ }
1507
+ const ao = a;
1508
+ if (typeof ao["pubkey"] !== "string") {
1509
+ throw new InputError(
1510
+ "INVALID_INPUT",
1511
+ `Instruction #${i} account #${j} is missing a string "pubkey".`
1512
+ );
1513
+ }
1514
+ return {
1515
+ pubkey: ao["pubkey"],
1516
+ isSigner: Boolean(ao["isSigner"]),
1517
+ isWritable: Boolean(ao["isWritable"])
1518
+ };
1519
+ });
1520
+ const data = o["data"];
1521
+ if (typeof data !== "string" && !Array.isArray(data) && !(data instanceof Uint8Array)) {
1522
+ throw new InputError(
1523
+ "INVALID_INPUT",
1524
+ `Instruction #${i} "data" must be a base64/base58 string, byte array, or Uint8Array.`
1525
+ );
1526
+ }
1527
+ return {
1528
+ programId: o["programId"],
1529
+ accounts,
1530
+ data
1531
+ };
1532
+ });
1533
+ }
1534
+
1535
+ // src/explain/token-meta.ts
1536
+ var KNOWN_MINTS = Object.freeze({
1537
+ EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v: { symbol: "USDC", decimals: 6 },
1538
+ Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB: { symbol: "USDT", decimals: 6 },
1539
+ So11111111111111111111111111111111111111112: { symbol: "wSOL", decimals: 9 },
1540
+ mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So: { symbol: "mSOL", decimals: 9 },
1541
+ "7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj": {
1542
+ symbol: "stSOL",
1543
+ decimals: 9
1544
+ },
1545
+ "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R": {
1546
+ symbol: "RAY",
1547
+ decimals: 6
1548
+ },
1549
+ JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN: { symbol: "JUP", decimals: 6 },
1550
+ DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263: { symbol: "BONK", decimals: 5 }
1551
+ });
1552
+ var WSOL_MINT = "So11111111111111111111111111111111111111112";
1553
+ function resolveSymbol(mint) {
1554
+ return KNOWN_MINTS[mint]?.symbol;
1555
+ }
1556
+ function isWsol(mint) {
1557
+ return mint === WSOL_MINT;
1558
+ }
1559
+
1560
+ // src/acquire/normalize.ts
1561
+ function tokenProgramKey(programId) {
1562
+ if (programId === TOKEN_PROGRAM_ID) return "spl-token";
1563
+ if (programId === TOKEN_2022_PROGRAM_ID) return "token-2022";
1564
+ return programId ?? "spl-token";
1565
+ }
1566
+ function tokenBalancesFromMeta(entries, accountKeys) {
1567
+ if (!entries) return [];
1568
+ const out = [];
1569
+ for (const e of entries) {
1570
+ const account = accountKeys[e.accountIndex];
1571
+ if (account === void 0) continue;
1572
+ const amount = BigInt(e.uiTokenAmount.amount);
1573
+ const symbol = resolveSymbol(e.mint);
1574
+ out.push({
1575
+ account,
1576
+ mint: e.mint,
1577
+ ...e.owner !== void 0 ? { owner: e.owner } : {},
1578
+ amount,
1579
+ decimals: e.uiTokenAmount.decimals,
1580
+ tokenProgram: tokenProgramKey(e.programId),
1581
+ ...symbol !== void 0 ? { symbol } : {}
1582
+ });
1583
+ }
1584
+ return out;
1585
+ }
1586
+ function tokenBalanceFromAccount(address, account) {
1587
+ if (!account) return null;
1588
+ const data = account.data;
1589
+ if (typeof data !== "object" || data === null || !("parsed" in data) || !("program" in data)) {
1590
+ return null;
1591
+ }
1592
+ const program = data.program;
1593
+ const parsed = data.parsed;
1594
+ if (program !== "spl-token" && program !== "spl-token-2022" || typeof parsed !== "object" || parsed === null) {
1595
+ return null;
1596
+ }
1597
+ const info = parsed.info;
1598
+ const type = parsed.type;
1599
+ if (type !== "account" || typeof info !== "object" || info === null) return null;
1600
+ const i = info;
1601
+ if (typeof i.mint !== "string" || !i.tokenAmount) return null;
1602
+ const amountStr = i.tokenAmount.amount;
1603
+ const decimals = i.tokenAmount.decimals;
1604
+ if (typeof amountStr !== "string" || typeof decimals !== "number") return null;
1605
+ const symbol = resolveSymbol(i.mint);
1606
+ return {
1607
+ account: address,
1608
+ mint: i.mint,
1609
+ ...typeof i.owner === "string" ? { owner: i.owner } : {},
1610
+ amount: BigInt(amountStr),
1611
+ decimals,
1612
+ tokenProgram: program === "spl-token-2022" ? "token-2022" : "spl-token",
1613
+ ...symbol !== void 0 ? { symbol } : {}
1614
+ };
1615
+ }
1616
+ function innerFromMeta(groups, accountKeys, decodeData, isSigner, isWritable) {
1617
+ if (!groups) return [];
1618
+ return groups.map((g) => ({
1619
+ index: g.index,
1620
+ instructions: g.instructions.map((ix, i) => {
1621
+ const programId = accountKeys[ix.programIdIndex] ?? "";
1622
+ return {
1623
+ index: i,
1624
+ programId,
1625
+ accounts: ix.accounts.map((ai) => ({
1626
+ pubkey: accountKeys[ai] ?? "",
1627
+ isSigner: isSigner[ai] ?? false,
1628
+ isWritable: isWritable[ai] ?? false
1629
+ })),
1630
+ data: decodeData(ix.data)
1631
+ };
1632
+ })
1633
+ }));
1634
+ }
1635
+
1636
+ // src/acquire/tx-error.ts
1637
+ function humanizeTxError(err, instructions) {
1638
+ return { raw: err, human: describe(err, instructions) };
1639
+ }
1640
+ function describe(err, instructions) {
1641
+ if (err === null || err === void 0) return "unknown error";
1642
+ if (typeof err === "string") return prettifyVariant(err);
1643
+ if (typeof err === "object") {
1644
+ const entries = Object.entries(err);
1645
+ if (entries.length === 0) return "unknown error";
1646
+ const [key, value] = entries[0];
1647
+ if (key === "InstructionError" && Array.isArray(value)) {
1648
+ const [idx, detail] = value;
1649
+ const ix = instructions?.[idx];
1650
+ const progInfo = ix ? knownProgram(ix.programId) : void 0;
1651
+ const where = `instruction #${idx}${progInfo ? ` (${progInfo.name})` : ix ? ` (${ix.programId})` : ""}`;
1652
+ if (typeof detail === "string") {
1653
+ return `${where} failed: ${prettifyVariant(detail)}`;
1654
+ }
1655
+ if (detail && typeof detail === "object") {
1656
+ const dEntries = Object.entries(detail);
1657
+ if (dEntries.length > 0) {
1658
+ const [dKey, dVal] = dEntries[0];
1659
+ if (dKey === "Custom") {
1660
+ return `${where} failed with custom program error ${String(dVal)}`;
1661
+ }
1662
+ return `${where} failed: ${dKey}${dVal !== null ? ` (${JSON.stringify(dVal)})` : ""}`;
1663
+ }
1664
+ }
1665
+ return `${where} failed`;
1666
+ }
1667
+ if (key === "InsufficientFundsForRent") {
1668
+ const acc4 = value && typeof value === "object" && "account_index" in value ? ` (account #${String(value.account_index)})` : "";
1669
+ return `insufficient funds for rent${acc4}`;
1670
+ }
1671
+ if (value === null || typeof value === "object" && Object.keys(value).length === 0) {
1672
+ return prettifyVariant(key);
1673
+ }
1674
+ return `${prettifyVariant(key)}: ${safeJson(value)}`;
1675
+ }
1676
+ return String(err);
1677
+ }
1678
+ function prettifyVariant(name) {
1679
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").toLowerCase();
1680
+ }
1681
+ function safeJson(v) {
1682
+ try {
1683
+ return JSON.stringify(v);
1684
+ } catch {
1685
+ return String(v);
1686
+ }
1687
+ }
1688
+
1689
+ // src/acquire/from-signature.ts
1690
+ async function acquireFromSignature(args) {
1691
+ const { rpc, signature, commitment, maxSupportedTransactionVersion, signal, includeRaw } = args;
1692
+ const resp = await rpc.getTransaction(signature, {
1693
+ commitment,
1694
+ maxSupportedTransactionVersion,
1695
+ ...signal ? { signal } : {}
1696
+ });
1697
+ if (resp === null) {
1698
+ throw new RpcError(
1699
+ "TX_NOT_FOUND",
1700
+ `Transaction ${signature} not found at commitment "${commitment}".`,
1701
+ {
1702
+ hint: "Try --commitment finalized, verify the signature, or check you are on the right cluster."
1703
+ }
1704
+ );
1705
+ }
1706
+ return normalizeSignatureResponse(resp, signature, includeRaw ?? false);
1707
+ }
1708
+ function normalizeSignatureResponse(resp, signature, includeRaw) {
1709
+ const warnings = [];
1710
+ const meta = resp.meta;
1711
+ const messageBytes = decodeMessageBytes(resp.transaction.message);
1712
+ const parsed = parseMessage(messageBytes);
1713
+ const loaded = meta?.loadedAddresses ? { writable: meta.loadedAddresses.writable, readonly: meta.loadedAddresses.readonly } : void 0;
1714
+ if (parsed.version === 0 && parsed.hasUnresolvedLut && !loaded) {
1715
+ warnings.push({
1716
+ code: "lut-unresolved",
1717
+ message: "versioned tx uses address lookup tables not resolved by the node response"
1718
+ });
1719
+ }
1720
+ const resolved = buildAccountList(parsed, loaded);
1721
+ const accountKeys = resolved.accountKeys;
1722
+ const instructions = toInstructionViews(parsed, resolved);
1723
+ const feePayer = accountKeys[0] ?? "";
1724
+ const preLamports = (meta?.preBalances ?? []).map((n) => BigInt(n));
1725
+ const postLamports = (meta?.postBalances ?? []).map((n) => BigInt(n));
1726
+ const preTokens = tokenBalancesFromMeta(meta?.preTokenBalances, accountKeys);
1727
+ const postTokens = tokenBalancesFromMeta(meta?.postTokenBalances, accountKeys);
1728
+ const inner = meta?.innerInstructions ? innerFromMeta(
1729
+ meta.innerInstructions,
1730
+ accountKeys,
1731
+ decodeBase58,
1732
+ resolved.isSigner,
1733
+ resolved.isWritable
1734
+ ) : [];
1735
+ if (!meta?.innerInstructions) {
1736
+ warnings.push({
1737
+ code: "inner-instructions-missing",
1738
+ message: "node did not return inner instructions (CPI tree unavailable)"
1739
+ });
1740
+ }
1741
+ const err = meta?.err ?? null;
1742
+ const success = err === null || err === void 0;
1743
+ const error = success ? void 0 : humanizeTxError(err, instructions);
1744
+ const result = {
1745
+ source: "signature",
1746
+ signature,
1747
+ feePayer,
1748
+ success,
1749
+ ...error ? { error } : {},
1750
+ slot: resp.slot,
1751
+ blockTime: resp.blockTime,
1752
+ feeLamports: BigInt(meta?.fee ?? 0),
1753
+ ...meta?.computeUnitsConsumed !== void 0 ? { computeUnits: meta.computeUnitsConsumed } : {},
1754
+ pre: { accountKeys, lamports: preLamports, tokenBalances: preTokens },
1755
+ post: { accountKeys, lamports: postLamports, tokenBalances: postTokens },
1756
+ instructions,
1757
+ innerInstructions: inner,
1758
+ warnings,
1759
+ ...includeRaw ? { raw: resp } : {}
1760
+ };
1761
+ return result;
1762
+ }
1763
+ function decodeMessageBytes(message) {
1764
+ if (Array.isArray(message) && typeof message[0] === "string") {
1765
+ const enc = message[1];
1766
+ if (enc === "base64") return decodeBase64(message[0]);
1767
+ if (enc === "base58") return decodeBase58(message[0]);
1768
+ return decodeBase64(message[0]);
1769
+ }
1770
+ if (typeof message === "string") {
1771
+ return decodeBase64(message);
1772
+ }
1773
+ throw new RpcError(
1774
+ "RPC_JSON",
1775
+ "Unexpected transaction.message shape; expected base64-encoded message."
1776
+ );
1777
+ }
1778
+
1779
+ // src/acquire/from-simulation.ts
1780
+ function accountLamports(acc4) {
1781
+ return acc4 ? BigInt(acc4.lamports) : 0n;
1782
+ }
1783
+ async function acquireFromSimulation(args) {
1784
+ const { rpc, txBytes, commitment, replaceRecentBlockhash, sigVerify, signal, includeRaw } = args;
1785
+ const stripped = stripSignatures(txBytes);
1786
+ const parsed = parseMessage(stripped);
1787
+ const resolved = buildAccountList(parsed);
1788
+ const accountKeys = resolved.accountKeys;
1789
+ const instructions = toInstructionViews(parsed, resolved);
1790
+ const feePayer = accountKeys[0] ?? "";
1791
+ const warnings = [];
1792
+ if (parsed.version === 0 && parsed.hasUnresolvedLut) {
1793
+ warnings.push({
1794
+ code: "lut-unresolved",
1795
+ message: "versioned tx references address lookup tables; simulate path diffs only resolved writable keys"
1796
+ });
1797
+ }
1798
+ const writable = accountKeys.filter((_, i) => resolved.isWritable[i]);
1799
+ const uniqueWritable = [...new Set(writable)];
1800
+ const preAccounts = await rpc.getMultipleAccounts(uniqueWritable, {
1801
+ commitment,
1802
+ encoding: "jsonParsed",
1803
+ ...signal ? { signal } : {}
1804
+ });
1805
+ const txBase64 = encodeBase64(txBytes);
1806
+ const sim = await rpc.simulateTransaction(txBase64, {
1807
+ commitment,
1808
+ sigVerify,
1809
+ replaceRecentBlockhash,
1810
+ innerInstructions: true,
1811
+ accounts: { addresses: uniqueWritable, encoding: "jsonParsed" },
1812
+ ...signal ? { signal } : {}
1813
+ });
1814
+ const value = sim.value;
1815
+ const postAccounts = value.accounts ?? [];
1816
+ const preLamportsByAddr = /* @__PURE__ */ new Map();
1817
+ const postLamportsByAddr = /* @__PURE__ */ new Map();
1818
+ const preTokens = [];
1819
+ const postTokens = [];
1820
+ uniqueWritable.forEach((addr, i) => {
1821
+ const preAcc = preAccounts[i] ?? null;
1822
+ const postAcc = postAccounts[i] ?? null;
1823
+ preLamportsByAddr.set(addr, accountLamports(preAcc));
1824
+ postLamportsByAddr.set(addr, postAcc ? accountLamports(postAcc) : accountLamports(preAcc));
1825
+ const preTb = tokenBalanceFromAccount(addr, preAcc);
1826
+ if (preTb) preTokens.push(preTb);
1827
+ const postTb = tokenBalanceFromAccount(addr, postAcc);
1828
+ if (postTb) postTokens.push(postTb);
1829
+ });
1830
+ const preLamports = accountKeys.map((k) => preLamportsByAddr.get(k) ?? 0n);
1831
+ const postLamports = accountKeys.map((k) => postLamportsByAddr.get(k) ?? preLamportsByAddr.get(k) ?? 0n);
1832
+ const inner = value.innerInstructions ? innerFromMeta(
1833
+ value.innerInstructions,
1834
+ accountKeys,
1835
+ decodeBase58,
1836
+ resolved.isSigner,
1837
+ resolved.isWritable
1838
+ ) : [];
1839
+ const err = value.err ?? null;
1840
+ const success = err === null || err === void 0;
1841
+ const error = success ? void 0 : humanizeTxError(err, instructions);
1842
+ if (!success) {
1843
+ warnings.push({
1844
+ code: "simulation-failed",
1845
+ message: "simulation returned a runtime error; balance diff reflects the pre-state"
1846
+ });
1847
+ }
1848
+ const result = {
1849
+ source: "simulation",
1850
+ feePayer,
1851
+ success,
1852
+ ...error ? { error } : {},
1853
+ slot: sim.context.slot,
1854
+ blockTime: null,
1855
+ // Simulation does not surface a fee; fee impact is visible via lamport diff.
1856
+ feeLamports: 0n,
1857
+ ...value.unitsConsumed !== void 0 ? { computeUnits: value.unitsConsumed } : {},
1858
+ pre: { accountKeys, lamports: preLamports, tokenBalances: preTokens },
1859
+ post: { accountKeys, lamports: postLamports, tokenBalances: postTokens },
1860
+ instructions,
1861
+ innerInstructions: inner,
1862
+ warnings,
1863
+ ...includeRaw ? { raw: sim } : {}
1864
+ };
1865
+ return result;
1866
+ }
1867
+ function normalizeData(data) {
1868
+ if (data instanceof Uint8Array) return data;
1869
+ if (Array.isArray(data)) return Uint8Array.from(data);
1870
+ if (looksLikeBase64(data) && /[+/=]/.test(data)) {
1871
+ return decodeBase64(data);
1872
+ }
1873
+ try {
1874
+ return decodeBase58(data);
1875
+ } catch {
1876
+ return decodeBase64(data);
1877
+ }
1878
+ }
1879
+ function writeCompactU16(out, value) {
1880
+ let v = value;
1881
+ for (; ; ) {
1882
+ let byte = v & 127;
1883
+ v >>= 7;
1884
+ if (v === 0) {
1885
+ out.push(byte);
1886
+ return;
1887
+ }
1888
+ byte |= 128;
1889
+ out.push(byte);
1890
+ }
1891
+ }
1892
+ function buildLegacyMessage(instructions, feePayer, recentBlockhash) {
1893
+ if (instructions.length === 0) {
1894
+ throw new InputError("EMPTY_INPUT", "No instructions to assemble.");
1895
+ }
1896
+ const metaByKey = /* @__PURE__ */ new Map();
1897
+ const upsert = (m) => {
1898
+ const cur = metaByKey.get(m.pubkey);
1899
+ if (cur) {
1900
+ cur.isSigner = cur.isSigner || m.isSigner;
1901
+ cur.isWritable = cur.isWritable || m.isWritable;
1902
+ } else {
1903
+ metaByKey.set(m.pubkey, { ...m });
1904
+ }
1905
+ };
1906
+ upsert({ pubkey: feePayer, isSigner: true, isWritable: true });
1907
+ for (const ix of instructions) {
1908
+ for (const a of ix.accounts) upsert({ ...a });
1909
+ upsert({ pubkey: ix.programId, isSigner: false, isWritable: false });
1910
+ }
1911
+ const all = [...metaByKey.values()];
1912
+ all.sort((a, b) => {
1913
+ if (a.pubkey === feePayer) return -1;
1914
+ if (b.pubkey === feePayer) return 1;
1915
+ const rank = (m) => m.isSigner ? m.isWritable ? 0 : 1 : m.isWritable ? 2 : 3;
1916
+ const r = rank(a) - rank(b);
1917
+ if (r !== 0) return r;
1918
+ return a.pubkey.localeCompare(b.pubkey);
1919
+ });
1920
+ const numRequiredSignatures = all.filter((m) => m.isSigner).length;
1921
+ const numReadonlySignedAccounts = all.filter((m) => m.isSigner && !m.isWritable).length;
1922
+ const numReadonlyUnsignedAccounts = all.filter((m) => !m.isSigner && !m.isWritable).length;
1923
+ const keyIndex = /* @__PURE__ */ new Map();
1924
+ all.forEach((m, i) => keyIndex.set(m.pubkey, i));
1925
+ const out = [];
1926
+ out.push(numRequiredSignatures, numReadonlySignedAccounts, numReadonlyUnsignedAccounts);
1927
+ writeCompactU16(out, all.length);
1928
+ for (const m of all) {
1929
+ const bytes = decodePubkey(m.pubkey);
1930
+ out.push(...bytes);
1931
+ }
1932
+ out.push(...decodePubkey(recentBlockhash));
1933
+ writeCompactU16(out, instructions.length);
1934
+ for (const ix of instructions) {
1935
+ const programIdIndex = keyIndex.get(ix.programId);
1936
+ if (programIdIndex === void 0) {
1937
+ throw new InputError("INVALID_INPUT", `programId ${ix.programId} not in account list`);
1938
+ }
1939
+ out.push(programIdIndex);
1940
+ writeCompactU16(out, ix.accounts.length);
1941
+ for (const a of ix.accounts) {
1942
+ const idx = keyIndex.get(a.pubkey);
1943
+ if (idx === void 0) {
1944
+ throw new InputError("INVALID_INPUT", `account ${a.pubkey} not in account list`);
1945
+ }
1946
+ out.push(idx);
1947
+ }
1948
+ const data = normalizeData(ix.data);
1949
+ writeCompactU16(out, data.length);
1950
+ out.push(...data);
1951
+ }
1952
+ const message = Uint8Array.from(out);
1953
+ const tx = [];
1954
+ writeCompactU16(tx, numRequiredSignatures);
1955
+ for (let i = 0; i < numRequiredSignatures; i++) {
1956
+ for (let j = 0; j < 64; j++) tx.push(0);
1957
+ }
1958
+ tx.push(...message);
1959
+ return Uint8Array.from(tx);
1960
+ }
1961
+ function decodePubkey(pubkey) {
1962
+ let bytes;
1963
+ try {
1964
+ bytes = bs58.decode(pubkey);
1965
+ } catch (err) {
1966
+ throw new InputError("INVALID_INPUT", `Invalid base58 pubkey: ${pubkey}`, { cause: err });
1967
+ }
1968
+ if (bytes.length !== 32) {
1969
+ throw new InputError(
1970
+ "INVALID_INPUT",
1971
+ `Pubkey ${pubkey} decoded to ${bytes.length} bytes (expected 32).`
1972
+ );
1973
+ }
1974
+ return bytes;
1975
+ }
1976
+
1977
+ // src/render/format.ts
1978
+ function groupThousands(intStr) {
1979
+ const neg = intStr.startsWith("-");
1980
+ const digits = neg ? intStr.slice(1) : intStr;
1981
+ const grouped = digits.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
1982
+ return neg ? `-${grouped}` : grouped;
1983
+ }
1984
+ function formatUnits(amount, decimals) {
1985
+ const neg = amount < 0n;
1986
+ const abs = neg ? -amount : amount;
1987
+ if (decimals <= 0) {
1988
+ return (neg ? "-" : "") + groupThousands(abs.toString());
1989
+ }
1990
+ const base = 10n ** BigInt(decimals);
1991
+ const whole = abs / base;
1992
+ const frac = abs % base;
1993
+ let fracStr = frac.toString().padStart(decimals, "0").replace(/0+$/, "");
1994
+ const wholeStr = groupThousands(whole.toString());
1995
+ const sign = neg ? "-" : "";
1996
+ return fracStr.length > 0 ? `${sign}${wholeStr}.${fracStr}` : `${sign}${wholeStr}`;
1997
+ }
1998
+ function lamportsToSol(lamports) {
1999
+ return formatUnits(lamports, 9);
2000
+ }
2001
+ function signedUi(delta, decimals, symbol) {
2002
+ const sign = delta > 0n ? "+" : "";
2003
+ const body = formatUnits(delta, decimals);
2004
+ const unit = symbol ? ` ${symbol}` : "";
2005
+ return `${sign}${body}${unit}`;
2006
+ }
2007
+ function shortAddr(addr, head = 4, tail = 4) {
2008
+ if (addr.length <= head + tail + 1) return addr;
2009
+ return `${addr.slice(0, head)}\u2026${addr.slice(-tail)}`;
2010
+ }
2011
+ function formatBlockTime(blockTime) {
2012
+ if (blockTime === null || blockTime === void 0) return void 0;
2013
+ const d = new Date(blockTime * 1e3);
2014
+ if (Number.isNaN(d.getTime())) return void 0;
2015
+ const pad = (n) => n.toString().padStart(2, "0");
2016
+ return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())} UTC`;
2017
+ }
2018
+ function formatCount(n) {
2019
+ return groupThousands(Math.trunc(n).toString());
2020
+ }
2021
+
2022
+ // src/explain/diff.ts
2023
+ function tokenKey(e) {
2024
+ return `${e.account}::${e.mint}`;
2025
+ }
2026
+ function diffBalances(pre, post) {
2027
+ const deltas = [];
2028
+ const preLamportsByAddr = /* @__PURE__ */ new Map();
2029
+ for (let i = 0; i < pre.accountKeys.length; i++) {
2030
+ const key = pre.accountKeys[i];
2031
+ const lam = pre.lamports[i];
2032
+ if (key !== void 0 && lam !== void 0) preLamportsByAddr.set(key, lam);
2033
+ }
2034
+ const seenAddrs = /* @__PURE__ */ new Set();
2035
+ for (let i = 0; i < post.accountKeys.length; i++) {
2036
+ const account = post.accountKeys[i];
2037
+ const postLam = post.lamports[i];
2038
+ if (account === void 0 || postLam === void 0) continue;
2039
+ seenAddrs.add(account);
2040
+ const preLam = preLamportsByAddr.get(account) ?? 0n;
2041
+ const delta = postLam - preLam;
2042
+ if (delta !== 0n) {
2043
+ deltas.push({
2044
+ account,
2045
+ asset: { kind: "SOL" },
2046
+ pre: preLam,
2047
+ post: postLam,
2048
+ delta,
2049
+ uiDelta: signedUi(delta, 9, "SOL")
2050
+ });
2051
+ }
2052
+ }
2053
+ for (const [account, preLam] of preLamportsByAddr) {
2054
+ if (seenAddrs.has(account)) continue;
2055
+ const delta = 0n - preLam;
2056
+ if (delta !== 0n) {
2057
+ deltas.push({
2058
+ account,
2059
+ asset: { kind: "SOL" },
2060
+ pre: preLam,
2061
+ post: 0n,
2062
+ delta,
2063
+ uiDelta: signedUi(delta, 9, "SOL")
2064
+ });
2065
+ }
2066
+ }
2067
+ const preTok = /* @__PURE__ */ new Map();
2068
+ for (const e of pre.tokenBalances) preTok.set(tokenKey(e), e);
2069
+ const postTok = /* @__PURE__ */ new Map();
2070
+ for (const e of post.tokenBalances) postTok.set(tokenKey(e), e);
2071
+ const allKeys = /* @__PURE__ */ new Set([...preTok.keys(), ...postTok.keys()]);
2072
+ for (const key of allKeys) {
2073
+ const p = preTok.get(key);
2074
+ const q = postTok.get(key);
2075
+ const meta = q ?? p;
2076
+ if (!meta) continue;
2077
+ const preAmt = p?.amount ?? 0n;
2078
+ const postAmt = q?.amount ?? 0n;
2079
+ const delta = postAmt - preAmt;
2080
+ if (delta === 0n) continue;
2081
+ const decimals = meta.decimals;
2082
+ deltas.push({
2083
+ account: meta.account,
2084
+ asset: {
2085
+ kind: "token",
2086
+ mint: meta.mint,
2087
+ decimals,
2088
+ ...meta.symbol !== void 0 ? { symbol: meta.symbol } : {},
2089
+ tokenProgram: meta.tokenProgram
2090
+ },
2091
+ pre: preAmt,
2092
+ post: postAmt,
2093
+ delta,
2094
+ uiDelta: signedUi(delta, decimals, meta.symbol),
2095
+ ...meta.owner !== void 0 ? { owner: meta.owner } : {}
2096
+ });
2097
+ }
2098
+ deltas.sort((a, b) => {
2099
+ if (a.asset.kind !== b.asset.kind) return a.asset.kind === "SOL" ? -1 : 1;
2100
+ const am = a.delta < 0n ? -a.delta : a.delta;
2101
+ const bm = b.delta < 0n ? -b.delta : b.delta;
2102
+ return am > bm ? -1 : am < bm ? 1 : 0;
2103
+ });
2104
+ return deltas;
2105
+ }
2106
+ function netSolByAccount(deltas) {
2107
+ const m = /* @__PURE__ */ new Map();
2108
+ for (const d of deltas) {
2109
+ if (d.asset.kind === "SOL") m.set(d.account, (m.get(d.account) ?? 0n) + d.delta);
2110
+ }
2111
+ return m;
2112
+ }
2113
+ function netTokenByOwnerMint(deltas) {
2114
+ const m = /* @__PURE__ */ new Map();
2115
+ for (const d of deltas) {
2116
+ if (d.asset.kind !== "token") continue;
2117
+ const owner = d.owner ?? d.account;
2118
+ const key = `${owner}::${d.asset.mint}`;
2119
+ const cur = m.get(key);
2120
+ if (cur) {
2121
+ cur.delta += d.delta;
2122
+ } else {
2123
+ m.set(key, {
2124
+ owner,
2125
+ mint: d.asset.mint,
2126
+ decimals: d.asset.decimals,
2127
+ ...d.asset.symbol !== void 0 ? { symbol: d.asset.symbol } : {},
2128
+ delta: d.delta
2129
+ });
2130
+ }
2131
+ }
2132
+ return m;
2133
+ }
2134
+
2135
+ // src/explain/correlate.ts
2136
+ function mintDecimals(tokenBalances) {
2137
+ const m = /* @__PURE__ */ new Map();
2138
+ for (const tb of tokenBalances) {
2139
+ if (!m.has(tb.mint)) m.set(tb.mint, tb.decimals);
2140
+ }
2141
+ return m;
2142
+ }
2143
+ function tokenAccountMeta(tokenBalances) {
2144
+ const m = /* @__PURE__ */ new Map();
2145
+ for (const tb of tokenBalances) {
2146
+ m.set(tb.account, {
2147
+ mint: tb.mint,
2148
+ ...tb.owner !== void 0 ? { owner: tb.owner } : {},
2149
+ decimals: tb.decimals,
2150
+ tokenProgram: tb.tokenProgram
2151
+ });
2152
+ }
2153
+ return m;
2154
+ }
2155
+ function uiAmount(amount, decimals) {
2156
+ return formatUnits(amount, decimals);
2157
+ }
2158
+ function correlate(input) {
2159
+ const { instructions, innerInstructions, deltas, tokenBalances, registry } = input;
2160
+ const decoded = [];
2161
+ const actions = [];
2162
+ const accountsCreated = [];
2163
+ const approvals = [];
2164
+ const warnings = [];
2165
+ const invocationCounts = /* @__PURE__ */ new Map();
2166
+ const decByMint = mintDecimals(tokenBalances);
2167
+ const taMeta = tokenAccountMeta(tokenBalances);
2168
+ const innerByIndex = /* @__PURE__ */ new Map();
2169
+ for (const grp of innerInstructions ?? []) innerByIndex.set(grp.index, grp.instructions);
2170
+ const seenSyncNative = /* @__PURE__ */ new Set();
2171
+ const handleEffect = (eff) => {
2172
+ switch (eff.kind) {
2173
+ case "sol-transfer": {
2174
+ actions.push({
2175
+ kind: "sol-transfer",
2176
+ from: eff.from,
2177
+ to: eff.to,
2178
+ lamports: eff.lamports,
2179
+ sol: lamportsToSol(eff.lamports)
2180
+ });
2181
+ break;
2182
+ }
2183
+ case "token-transfer": {
2184
+ const srcMeta = taMeta.get(eff.from);
2185
+ const dstMeta = taMeta.get(eff.to);
2186
+ const mint = eff.mint || srcMeta?.mint || dstMeta?.mint || "";
2187
+ const decimals = eff.decimals ?? srcMeta?.decimals ?? dstMeta?.decimals ?? decByMint.get(mint);
2188
+ const symbol = mint ? resolveSymbol(mint) : void 0;
2189
+ if (decimals === void 0) {
2190
+ warnings.push({
2191
+ code: "ambiguous-amount",
2192
+ message: `token transfer of ${eff.amount} raw units lacks decimals; shown in base units`
2193
+ });
2194
+ }
2195
+ const dec = decimals ?? 0;
2196
+ const action = {
2197
+ kind: "token-transfer",
2198
+ from: eff.from,
2199
+ to: eff.to,
2200
+ mint,
2201
+ amount: eff.amount,
2202
+ uiAmount: uiAmount(eff.amount, dec),
2203
+ decimals: dec,
2204
+ tokenProgram: eff.tokenProgram,
2205
+ ...symbol !== void 0 ? { symbol } : {}
2206
+ };
2207
+ actions.push(action);
2208
+ break;
2209
+ }
2210
+ case "mint": {
2211
+ const decimals = eff.decimals ?? decByMint.get(eff.mint) ?? 0;
2212
+ actions.push({
2213
+ kind: "mint",
2214
+ mint: eff.mint,
2215
+ to: eff.to,
2216
+ amount: eff.amount,
2217
+ uiAmount: uiAmount(eff.amount, decimals)
2218
+ });
2219
+ break;
2220
+ }
2221
+ case "burn": {
2222
+ const decimals = eff.decimals ?? decByMint.get(eff.mint) ?? 0;
2223
+ actions.push({
2224
+ kind: "burn",
2225
+ mint: eff.mint,
2226
+ from: eff.from,
2227
+ amount: eff.amount,
2228
+ uiAmount: uiAmount(eff.amount, decimals)
2229
+ });
2230
+ break;
2231
+ }
2232
+ case "approval": {
2233
+ const mint = eff.mint || taMeta.get(eff.owner)?.mint || "";
2234
+ const ap = {
2235
+ owner: eff.owner,
2236
+ delegate: eff.delegate,
2237
+ mint,
2238
+ amount: eff.amount,
2239
+ ...eff.revoke !== void 0 ? { revoke: eff.revoke } : {}
2240
+ };
2241
+ approvals.push(ap);
2242
+ actions.push({ kind: "approval", ...ap });
2243
+ break;
2244
+ }
2245
+ case "close-account": {
2246
+ const d = deltas.find((x) => x.account === eff.account && x.asset.kind === "SOL");
2247
+ const reclaimed = d ? -d.delta : 0n;
2248
+ actions.push({
2249
+ kind: "close-account",
2250
+ account: eff.account,
2251
+ destination: eff.destination,
2252
+ reclaimedLamports: reclaimed > 0n ? reclaimed : 0n
2253
+ });
2254
+ break;
2255
+ }
2256
+ case "account-created": {
2257
+ const meta = taMeta.get(eff.address);
2258
+ const ac = {
2259
+ address: eff.address,
2260
+ owner: eff.owner,
2261
+ lamports: eff.lamports ?? 0n,
2262
+ ...eff.space !== void 0 ? { space: eff.space } : {},
2263
+ ...eff.as !== void 0 ? { as: eff.as } : meta ? { as: "token-account" } : {}
2264
+ };
2265
+ accountsCreated.push(ac);
2266
+ actions.push({ kind: "account-created", ...ac });
2267
+ break;
2268
+ }
2269
+ case "memo": {
2270
+ actions.push({ kind: "memo", text: eff.text });
2271
+ break;
2272
+ }
2273
+ case "compute-budget": {
2274
+ actions.push({
2275
+ kind: "compute-budget",
2276
+ ...eff.unitLimit !== void 0 ? { unitLimit: eff.unitLimit } : {},
2277
+ ...eff.unitPriceMicroLamports !== void 0 ? { unitPriceMicroLamports: eff.unitPriceMicroLamports } : {}
2278
+ });
2279
+ break;
2280
+ }
2281
+ case "sync-native": {
2282
+ seenSyncNative.add(eff.account);
2283
+ break;
2284
+ }
2285
+ }
2286
+ };
2287
+ const decodeInner = (views) => views.map((view) => {
2288
+ invocationCounts.set(view.programId, (invocationCounts.get(view.programId) ?? 0) + 1);
2289
+ const { decoded: dec, effects, warningCode } = decodeInstructionRaw(view, registry);
2290
+ if (!dec.decoded) {
2291
+ const known = knownProgram(view.programId);
2292
+ warnings.push({
2293
+ code: known ? "partial-decode" : "unknown-program",
2294
+ message: dec.warning ?? (known ? `${known.name} not byte-decoded` : "unknown program"),
2295
+ instructionIndex: view.index
2296
+ });
2297
+ } else if (warningCode) {
2298
+ warnings.push({
2299
+ code: warningCode,
2300
+ message: dec.warning ?? "partial decode",
2301
+ instructionIndex: view.index
2302
+ });
2303
+ }
2304
+ for (const eff of effects) handleEffect(eff);
2305
+ return dec;
2306
+ });
2307
+ for (const view of instructions) {
2308
+ invocationCounts.set(view.programId, (invocationCounts.get(view.programId) ?? 0) + 1);
2309
+ const { decoded: dec, effects, warningCode } = decodeInstructionRaw(view, registry);
2310
+ if (!dec.decoded) {
2311
+ const known = knownProgram(view.programId);
2312
+ warnings.push({
2313
+ code: known ? "partial-decode" : "unknown-program",
2314
+ message: dec.warning ?? (known ? `${known.name} not byte-decoded` : "unknown program"),
2315
+ instructionIndex: view.index
2316
+ });
2317
+ actions.push({
2318
+ kind: "program-call",
2319
+ programId: view.programId,
2320
+ ...known ? { program: known.name } : {},
2321
+ ...known ? { note: "effect inferred from balance diff (no IDL)" } : { note: "unknown program; see balance changes" }
2322
+ });
2323
+ } else if (warningCode) {
2324
+ warnings.push({
2325
+ code: warningCode,
2326
+ message: dec.warning ?? "partial decode",
2327
+ instructionIndex: view.index
2328
+ });
2329
+ }
2330
+ for (const eff of effects) handleEffect(eff);
2331
+ const inner = innerByIndex.get(view.index);
2332
+ if (inner && inner.length > 0) {
2333
+ dec.inner = decodeInner(inner);
2334
+ }
2335
+ decoded.push(dec);
2336
+ }
2337
+ if (seenSyncNative.size > 0) {
2338
+ for (const action of actions) {
2339
+ if (action.kind === "token-transfer" && isWsol(action.mint)) {
2340
+ if (!action.symbol) action.symbol = "wSOL";
2341
+ }
2342
+ }
2343
+ }
2344
+ const createdAddrs = new Set(accountsCreated.map((a) => a.address));
2345
+ for (const action of actions) {
2346
+ if (action.kind === "close-account" && createdAddrs.has(action.account)) {
2347
+ warnings.push({
2348
+ code: "partial-decode",
2349
+ message: `account ${action.account} was created and closed within this transaction (temporary ATA)`
2350
+ });
2351
+ }
2352
+ }
2353
+ for (const action of actions) {
2354
+ if ((action.kind === "sol-transfer" || action.kind === "token-transfer") && action.from === action.to) {
2355
+ warnings.push({
2356
+ code: "ambiguous-amount",
2357
+ message: "no net token movement (self-transfer)"
2358
+ });
2359
+ }
2360
+ }
2361
+ const programsInvoked = [...invocationCounts.entries()].map(
2362
+ ([programId, count]) => {
2363
+ const known = knownProgram(programId);
2364
+ return { programId, ...known ? { name: known.name } : {}, count };
2365
+ }
2366
+ );
2367
+ return { decoded, actions, accountsCreated, approvals, programsInvoked, warnings };
2368
+ }
2369
+
2370
+ // src/explain/summarize.ts
2371
+ function aggregatorName(programs) {
2372
+ const agg = programs.find((p) => p.name && isAggregator(p.name));
2373
+ if (agg?.name) return agg.name;
2374
+ const amm = programs.find((p) => p.name && isAmm(p.name));
2375
+ return amm?.name;
2376
+ }
2377
+ function isAggregator(name) {
2378
+ return /jupiter/i.test(name);
2379
+ }
2380
+ function isAmm(name) {
2381
+ return /raydium|orca|whirlpool|phoenix|meteora/i.test(name);
2382
+ }
2383
+ function summarize(input) {
2384
+ const { actions, deltas, programsInvoked, feePayer, feeLamports, success, error, focusAccount } = input;
2385
+ const focus = focusAccount ?? feePayer;
2386
+ const feeSol = lamportsToSol(feeLamports);
2387
+ const feeSuffix = feeLamports > 0n ? `, paid ${feeSol} SOL fee` : "";
2388
+ if (!success) {
2389
+ const why = error?.human ? `: ${error.human}` : "";
2390
+ return `Transaction FAILED${why}. ${feeLamports > 0n ? `Fee of ${feeSol} SOL was still charged.` : ""}`.trim();
2391
+ }
2392
+ const solByAcc = netSolByAccount(deltas);
2393
+ const tokByOwner = netTokenByOwnerMint(deltas);
2394
+ const meaningfulDeltas = deltas.filter((d) => {
2395
+ if (d.asset.kind === "token") return true;
2396
+ if (d.account !== feePayer) return true;
2397
+ return d.delta !== -feeLamports;
2398
+ });
2399
+ const hasDex = programsInvoked.some((p) => p.name && (isAggregator(p.name) || isAmm(p.name)));
2400
+ if (hasDex) {
2401
+ const dexName = aggregatorName(programsInvoked) ?? "a DEX";
2402
+ const focusGains = [];
2403
+ const focusLosses = [];
2404
+ const solDelta = solByAcc.get(focus);
2405
+ if (solDelta !== void 0 && solDelta !== 0n) {
2406
+ const label = `${formatUnits(absBig(solDelta), 9)} SOL`;
2407
+ (solDelta > 0n ? focusGains : focusLosses).push(label);
2408
+ }
2409
+ for (const t of tokByOwner.values()) {
2410
+ if (t.owner !== focus || t.delta === 0n) continue;
2411
+ const label = `${formatUnits(absBig(t.delta), t.decimals)} ${t.symbol ?? shortAddr(t.mint)}`;
2412
+ (t.delta > 0n ? focusGains : focusLosses).push(label);
2413
+ }
2414
+ if (focusLosses.length > 0 && focusGains.length > 0) {
2415
+ return `Swapped ${focusLosses.join(" + ")} for ${focusGains.join(" + ")} via ${dexName}${feeSuffix}.`;
2416
+ }
2417
+ if (focusGains.length > 0 || focusLosses.length > 0) {
2418
+ const moved = [...focusGains, ...focusLosses].join(", ");
2419
+ return `Routed ${moved} via ${dexName}${feeSuffix}.`;
2420
+ }
2421
+ }
2422
+ const tokenTransfers = actions.filter((a) => a.kind === "token-transfer");
2423
+ const solTransfers = actions.filter((a) => a.kind === "sol-transfer");
2424
+ if (tokenTransfers.length > 0) {
2425
+ const biggest = tokenTransfers.slice().sort((a, b) => a.amount > b.amount ? -1 : 1)[0];
2426
+ const sym = biggest.symbol ?? shortAddr(biggest.mint);
2427
+ const dirNote = biggest.from === biggest.to ? " (self-transfer, no net movement)" : "";
2428
+ const count = tokenTransfers.length > 1 ? ` (+${tokenTransfers.length - 1} more transfer${tokenTransfers.length - 1 > 1 ? "s" : ""})` : "";
2429
+ return `Transferred ${biggest.uiAmount} ${sym} from ${shortAddr(biggest.from)} to ${shortAddr(biggest.to)}${dirNote}${count}${feeSuffix}.`;
2430
+ }
2431
+ if (solTransfers.length > 0) {
2432
+ const total = solTransfers.reduce((acc4, a) => acc4 + a.lamports, 0n);
2433
+ if (solTransfers.length === 1) {
2434
+ const a = solTransfers[0];
2435
+ return `Sent ${a.sol} SOL from ${shortAddr(a.from)} to ${shortAddr(a.to)}${feeSuffix}.`;
2436
+ }
2437
+ return `Made ${solTransfers.length} SOL transfers totaling ${lamportsToSol(total)} SOL${feeSuffix}.`;
2438
+ }
2439
+ const mints = actions.filter((a) => a.kind === "mint");
2440
+ const burns = actions.filter((a) => a.kind === "burn");
2441
+ if (mints.length > 0) {
2442
+ const m = mints[0];
2443
+ return `Minted ${m.uiAmount} of ${shortAddr(m.mint)} to ${shortAddr(m.to)}${feeSuffix}.`;
2444
+ }
2445
+ if (burns.length > 0) {
2446
+ const b = burns[0];
2447
+ return `Burned ${b.uiAmount} of ${shortAddr(b.mint)}${feeSuffix}.`;
2448
+ }
2449
+ const approvals = actions.filter((a) => a.kind === "approval");
2450
+ if (approvals.length > 0) {
2451
+ const ap = approvals[0];
2452
+ if (ap.revoke) {
2453
+ return `Revoked token delegate approval${feeSuffix}.`;
2454
+ }
2455
+ const amt = ap.amount === "unlimited" ? "unlimited" : ap.amount.toString();
2456
+ return `Approved delegate ${shortAddr(ap.delegate)} to spend ${amt} tokens${feeSuffix}.`;
2457
+ }
2458
+ const created = actions.filter((a) => a.kind === "account-created");
2459
+ if (created.length > 0) {
2460
+ const what = created.some((c) => c.kind === "account-created" && c.as === "ata") ? "associated token account" : "account";
2461
+ const plural = created.length > 1 ? `${created.length} accounts` : `an ${what}`;
2462
+ return `Created ${plural}${feeSuffix}.`;
2463
+ }
2464
+ const memos = actions.filter((a) => a.kind === "memo");
2465
+ const cb = actions.filter((a) => a.kind === "compute-budget");
2466
+ if (memos.length > 0 && meaningfulDeltas.length === 0) {
2467
+ const text = memos[0].kind === "memo" ? memos[0].text : "";
2468
+ const memoNote = `attached memo "${text}"`;
2469
+ return cb.length > 0 ? `Set compute budget; ${memoNote}${feeSuffix}.` : `${capitalize(memoNote)}${feeSuffix}.`;
2470
+ }
2471
+ if (cb.length > 0 && meaningfulDeltas.length === 0 && actions.every((a) => a.kind === "compute-budget")) {
2472
+ return `Set compute budget only; no balance changes${feeSuffix}.`;
2473
+ }
2474
+ const calls = actions.filter((a) => a.kind === "program-call");
2475
+ if (calls.length > 0) {
2476
+ const named = calls.find((c) => c.kind === "program-call" && c.program);
2477
+ const name = named && named.kind === "program-call" && named.program ? named.program : shortAddr(calls[0].kind === "program-call" ? calls[0].programId : "");
2478
+ if (deltas.length > 0) {
2479
+ return `Interacted with ${name}; see balance changes for the net effect${feeSuffix}.`;
2480
+ }
2481
+ return `Called ${name} (no net balance change)${feeSuffix}.`;
2482
+ }
2483
+ if (meaningfulDeltas.length > 0) {
2484
+ return `Transaction changed ${meaningfulDeltas.length} balance${meaningfulDeltas.length > 1 ? "s" : ""}${feeSuffix}.`;
2485
+ }
2486
+ return `Transaction succeeded with no net balance change${feeSuffix}.`;
2487
+ }
2488
+ function absBig(n) {
2489
+ return n < 0n ? -n : n;
2490
+ }
2491
+ function capitalize(s) {
2492
+ return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
2493
+ }
2494
+
2495
+ // src/explain.ts
2496
+ function resolveRpc(rpc, timeoutMs) {
2497
+ if (typeof rpc === "string") {
2498
+ return createHttpRpc(rpc, { timeoutMs });
2499
+ }
2500
+ return rpc;
2501
+ }
2502
+ function resolveRegistry(opts) {
2503
+ if (opts.registry) {
2504
+ return defaultRegistry.merge(opts.registry.list());
2505
+ }
2506
+ return defaultRegistry;
2507
+ }
2508
+ function clampTimeout(timeoutMs) {
2509
+ const def = 3e4;
2510
+ if (timeoutMs === void 0) return { value: def };
2511
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
2512
+ return { value: def, warning: `invalid timeout ${timeoutMs}; using ${def}ms` };
2513
+ }
2514
+ const min = 1e3;
2515
+ const max = 6e5;
2516
+ if (timeoutMs < min) return { value: min, warning: `timeout clamped up to ${min}ms` };
2517
+ if (timeoutMs > max) return { value: max, warning: `timeout clamped down to ${max}ms` };
2518
+ return { value: timeoutMs };
2519
+ }
2520
+ function buildExplanation(input) {
2521
+ const deltas = diffBalances(input.pre, input.post);
2522
+ for (const d of deltas) {
2523
+ if (d.asset.kind === "token" && d.asset.symbol === void 0) {
2524
+ const sym = resolveSymbol(d.asset.mint);
2525
+ if (sym) d.asset.symbol = sym;
2526
+ }
2527
+ }
2528
+ const allTokenBalances = [...input.pre.tokenBalances, ...input.post.tokenBalances];
2529
+ const correlation = correlate({
2530
+ instructions: input.instructions,
2531
+ ...input.innerInstructions ? { innerInstructions: input.innerInstructions } : {},
2532
+ deltas,
2533
+ tokenBalances: allTokenBalances,
2534
+ registry: input.registry
2535
+ });
2536
+ const summary = summarize({
2537
+ actions: correlation.actions,
2538
+ deltas,
2539
+ programsInvoked: correlation.programsInvoked,
2540
+ feePayer: input.feePayer,
2541
+ feeLamports: input.feeLamports,
2542
+ success: input.success,
2543
+ ...input.error ? { error: input.error } : {},
2544
+ ...input.focusAccount ? { focusAccount: input.focusAccount } : {}
2545
+ });
2546
+ const warnings = [...input.warnings ?? [], ...correlation.warnings];
2547
+ const result = {
2548
+ source: input.source,
2549
+ ...input.signature ? { signature: input.signature } : {},
2550
+ success: input.success,
2551
+ ...input.error ? { error: input.error } : {},
2552
+ ...input.slot !== void 0 ? { slot: input.slot } : {},
2553
+ ...input.blockTime !== void 0 ? { blockTime: input.blockTime } : {},
2554
+ feeLamports: input.feeLamports,
2555
+ ...input.computeUnits !== void 0 ? { computeUnits: input.computeUnits } : {},
2556
+ feePayer: input.feePayer,
2557
+ summary,
2558
+ actions: correlation.actions,
2559
+ balanceChanges: deltas,
2560
+ instructions: correlation.decoded,
2561
+ accountsCreated: correlation.accountsCreated,
2562
+ approvals: correlation.approvals,
2563
+ programsInvoked: correlation.programsInvoked,
2564
+ warnings,
2565
+ ...input.includeRaw && input.raw !== void 0 ? { raw: input.raw } : {}
2566
+ };
2567
+ return result;
2568
+ }
2569
+ function normalizedToResult(norm, registry, opts, extraWarnings) {
2570
+ const input = {
2571
+ source: norm.source,
2572
+ ...norm.signature ? { signature: norm.signature } : {},
2573
+ feePayer: norm.feePayer,
2574
+ success: norm.success,
2575
+ ...norm.error ? { error: norm.error } : {},
2576
+ ...norm.slot !== void 0 ? { slot: norm.slot } : {},
2577
+ blockTime: norm.blockTime ?? null,
2578
+ feeLamports: norm.feeLamports,
2579
+ ...norm.computeUnits !== void 0 ? { computeUnits: norm.computeUnits } : {},
2580
+ pre: norm.pre,
2581
+ post: norm.post,
2582
+ instructions: norm.instructions,
2583
+ ...norm.innerInstructions ? { innerInstructions: norm.innerInstructions } : {},
2584
+ registry,
2585
+ ...opts.focusAccount ? { focusAccount: opts.focusAccount } : {},
2586
+ warnings: [...norm.warnings, ...extraWarnings],
2587
+ ...opts.includeRaw && norm.raw !== void 0 ? { raw: norm.raw } : {},
2588
+ ...opts.includeRaw ? { includeRaw: true } : {}
2589
+ };
2590
+ const result = buildExplanation(input);
2591
+ if (norm.source === "signature" && opts.commitment) {
2592
+ result.commitment = opts.commitment;
2593
+ }
2594
+ return result;
2595
+ }
2596
+ async function explainSignature(signature, options) {
2597
+ try {
2598
+ const { value: timeoutMs, warning } = clampTimeout(options.timeoutMs);
2599
+ const rpc = resolveRpc(options.rpc, timeoutMs);
2600
+ const registry = resolveRegistry(options);
2601
+ const commitment = options.commitment ?? "confirmed";
2602
+ const norm = await acquireFromSignature({
2603
+ rpc,
2604
+ signature,
2605
+ commitment,
2606
+ maxSupportedTransactionVersion: options.maxSupportedTransactionVersion ?? 0,
2607
+ ...options.signal ? { signal: options.signal } : {},
2608
+ ...options.includeRaw ? { includeRaw: true } : {}
2609
+ });
2610
+ const extra = warning ? [{ code: "version-skew", message: warning }] : [];
2611
+ return normalizedToResult(norm, registry, options, extra);
2612
+ } catch (err) {
2613
+ throw asExplainError(err, "failed to explain signature");
2614
+ }
2615
+ }
2616
+ async function explainTransaction(tx, options) {
2617
+ try {
2618
+ const { value: timeoutMs, warning } = clampTimeout(options.timeoutMs);
2619
+ const rpc = resolveRpc(options.rpc, timeoutMs);
2620
+ const registry = resolveRegistry(options);
2621
+ const commitment = options.commitment ?? "confirmed";
2622
+ let bytes;
2623
+ let ambiguous = false;
2624
+ if (tx instanceof Uint8Array) {
2625
+ const det = detectInput(tx);
2626
+ if (det.kind !== "tx-bytes") {
2627
+ throw new InputError("INVALID_INPUT", "Provided bytes are not a serialized transaction.");
2628
+ }
2629
+ bytes = det.bytes;
2630
+ } else {
2631
+ const det = detectTxBytes(tx, options.encoding ?? "auto");
2632
+ bytes = det.bytes;
2633
+ ambiguous = det.ambiguous;
2634
+ }
2635
+ const norm = await acquireFromSimulation({
2636
+ rpc,
2637
+ txBytes: bytes,
2638
+ commitment,
2639
+ replaceRecentBlockhash: options.replaceRecentBlockhash ?? true,
2640
+ sigVerify: options.sigVerify ?? false,
2641
+ ...options.signal ? { signal: options.signal } : {},
2642
+ ...options.includeRaw ? { includeRaw: true } : {}
2643
+ });
2644
+ const extra = [];
2645
+ if (ambiguous) {
2646
+ extra.push({
2647
+ code: "version-skew",
2648
+ message: "input was decodable as both base64 and base58; interpreted as base64"
2649
+ });
2650
+ }
2651
+ if (warning) extra.push({ code: "version-skew", message: warning });
2652
+ return normalizedToResult(norm, registry, options, extra);
2653
+ } catch (err) {
2654
+ throw asExplainError(err, "failed to explain transaction");
2655
+ }
2656
+ }
2657
+ async function explainInstructions(instructions, options) {
2658
+ try {
2659
+ if (!options.feePayer || options.feePayer.trim().length === 0) {
2660
+ throw new InputError(
2661
+ "INVALID_INPUT",
2662
+ "feePayer is required to assemble a simulatable message from instructions.",
2663
+ { hint: "Pass options.feePayer with the payer pubkey." }
2664
+ );
2665
+ }
2666
+ if (!Array.isArray(instructions) || instructions.length === 0) {
2667
+ throw new InputError("EMPTY_INPUT", "No instructions provided.");
2668
+ }
2669
+ const { value: timeoutMs, warning } = clampTimeout(options.timeoutMs);
2670
+ const rpc = resolveRpc(options.rpc, timeoutMs);
2671
+ const registry = resolveRegistry(options);
2672
+ const commitment = options.commitment ?? "confirmed";
2673
+ let blockhash;
2674
+ try {
2675
+ const bh = await rpc.getLatestBlockhash({
2676
+ commitment,
2677
+ ...options.signal ? { signal: options.signal } : {}
2678
+ });
2679
+ blockhash = bh.blockhash;
2680
+ } catch {
2681
+ blockhash = "11111111111111111111111111111111";
2682
+ }
2683
+ const txBytes = buildLegacyMessage(instructions, options.feePayer, blockhash);
2684
+ const norm = await acquireFromSimulation({
2685
+ rpc,
2686
+ txBytes,
2687
+ commitment,
2688
+ replaceRecentBlockhash: true,
2689
+ sigVerify: false,
2690
+ ...options.signal ? { signal: options.signal } : {},
2691
+ ...options.includeRaw ? { includeRaw: true } : {}
2692
+ });
2693
+ const extra = warning ? [{ code: "version-skew", message: warning }] : [];
2694
+ return normalizedToResult(norm, registry, options, extra);
2695
+ } catch (err) {
2696
+ throw asExplainError(err, "failed to explain instructions");
2697
+ }
2698
+ }
2699
+ function asExplainError(err, context) {
2700
+ if (err instanceof SolanaExplainError) return err;
2701
+ return wrapError(err, "INVALID_INPUT", context);
2702
+ }
2703
+
2704
+ // src/render/ansi.ts
2705
+ var ESC = `${String.fromCharCode(27)}[`;
2706
+ var RESET = `${ESC}0m`;
2707
+ var CODES = {
2708
+ bold: `${ESC}1m`,
2709
+ dim: `${ESC}2m`,
2710
+ red: `${ESC}31m`,
2711
+ green: `${ESC}32m`,
2712
+ yellow: `${ESC}33m`,
2713
+ blue: `${ESC}34m`,
2714
+ magenta: `${ESC}35m`,
2715
+ cyan: `${ESC}36m`,
2716
+ gray: `${ESC}90m`,
2717
+ // VTX brand blue (#3182ce) via 256-color approximation.
2718
+ brand: `${ESC}38;5;33m`
2719
+ };
2720
+ function wrap(enabled, code, s) {
2721
+ return enabled ? `${code}${s}${RESET}` : s;
2722
+ }
2723
+ function createStyler(enabled) {
2724
+ return {
2725
+ enabled,
2726
+ bold: (s) => wrap(enabled, CODES.bold, s),
2727
+ dim: (s) => wrap(enabled, CODES.dim, s),
2728
+ red: (s) => wrap(enabled, CODES.red, s),
2729
+ green: (s) => wrap(enabled, CODES.green, s),
2730
+ yellow: (s) => wrap(enabled, CODES.yellow, s),
2731
+ blue: (s) => wrap(enabled, CODES.blue, s),
2732
+ magenta: (s) => wrap(enabled, CODES.magenta, s),
2733
+ cyan: (s) => wrap(enabled, CODES.cyan, s),
2734
+ gray: (s) => wrap(enabled, CODES.gray, s),
2735
+ brand: (s) => wrap(enabled, CODES.brand, s)
2736
+ };
2737
+ }
2738
+ function shouldUseColor(opts) {
2739
+ if (opts.explicit !== void 0) return opts.explicit;
2740
+ const env = opts.env ?? {};
2741
+ if (env["NO_COLOR"] !== void 0 && env["NO_COLOR"] !== "") return false;
2742
+ if (env["FORCE_COLOR"] !== void 0 && env["FORCE_COLOR"] !== "0" && env["FORCE_COLOR"] !== "") {
2743
+ return true;
2744
+ }
2745
+ return Boolean(opts.isTTY);
2746
+ }
2747
+ var ANSI_RE = new RegExp(`${String.fromCharCode(27)}[[0-9;]*m`, "g");
2748
+ function stripAnsi(s) {
2749
+ return s.replace(ANSI_RE, "");
2750
+ }
2751
+
2752
+ // src/render/index.ts
2753
+ var INDENT = " ";
2754
+ function renderText(result, opts = {}) {
2755
+ const s = createStyler(opts.color ?? false);
2756
+ const lines = [];
2757
+ if (opts.quiet) {
2758
+ return result.summary;
2759
+ }
2760
+ const commitmentNote = result.source === "simulation" ? "simulation" : result.commitment ?? "confirmed";
2761
+ const when = formatBlockTime(result.blockTime);
2762
+ const headParts = [
2763
+ `Solana transaction`,
2764
+ commitmentNote,
2765
+ result.slot !== void 0 ? `slot ${formatCount(result.slot)}` : void 0,
2766
+ when
2767
+ ].filter(Boolean);
2768
+ lines.push("");
2769
+ lines.push(`${INDENT}${s.brand(headParts.join(" \xB7 "))}`);
2770
+ const status = result.success ? s.green("\u2713 Success") : s.red("\u2717 Failed");
2771
+ if (result.signature) {
2772
+ lines.push(`${INDENT}${s.dim("Signature")} ${shortAddr(result.signature, 6, 6)} ${status}`);
2773
+ } else {
2774
+ lines.push(`${INDENT}${s.dim("Source")} ${result.source} ${status}`);
2775
+ }
2776
+ lines.push("");
2777
+ lines.push(`${INDENT}${s.bold("Summary")} ${result.summary}`);
2778
+ if (!result.success && result.error) {
2779
+ lines.push(`${INDENT}${s.red("Error")} ${result.error.human}`);
2780
+ }
2781
+ if (result.balanceChanges.length > 0) {
2782
+ lines.push("");
2783
+ lines.push(`${INDENT}${s.bold("Balance changes")}`);
2784
+ for (const d of groupBalances(result.balanceChanges, result.feePayer)) {
2785
+ const label = d.label;
2786
+ const focusTag = d.account === result.feePayer ? s.dim(" (fee payer)") : "";
2787
+ const colored = colorDelta(d.text, s);
2788
+ lines.push(`${INDENT}${INDENT}${padRight(label, 22)}${colored}${focusTag}`);
2789
+ }
2790
+ }
2791
+ if (result.actions.length > 0) {
2792
+ lines.push("");
2793
+ lines.push(`${INDENT}${s.bold("Actions")}`);
2794
+ result.actions.forEach((a, i) => {
2795
+ lines.push(`${INDENT}${INDENT}${i + 1}. ${describeAction(a)}`);
2796
+ });
2797
+ }
2798
+ if (opts.verbose && result.instructions.length > 0) {
2799
+ lines.push("");
2800
+ lines.push(`${INDENT}${s.bold("Instructions")}`);
2801
+ for (const ix of result.instructions) {
2802
+ renderInstruction(ix, lines, s, 2);
2803
+ }
2804
+ }
2805
+ if (result.programsInvoked.length > 0) {
2806
+ const names = result.programsInvoked.map((p) => p.name ?? shortAddr(p.programId)).filter((v, idx, arr) => arr.indexOf(v) === idx);
2807
+ lines.push("");
2808
+ lines.push(`${INDENT}${s.bold("Programs")} ${names.join(" \xB7 ")}`);
2809
+ }
2810
+ const feeStr = `${lamportsToSol(result.feeLamports)} SOL`;
2811
+ const computeStr = result.computeUnits !== void 0 ? `${formatCount(result.computeUnits)} units` : "n/a";
2812
+ lines.push(`${INDENT}${s.bold("Fee")} ${feeStr}${" ".repeat(8)}${s.bold("Compute")} ${computeStr}`);
2813
+ if (result.warnings.length > 0) {
2814
+ lines.push("");
2815
+ lines.push(`${INDENT}${s.yellow(`\u26A0 ${result.warnings.length} warning${result.warnings.length > 1 ? "s" : ""}`)}`);
2816
+ for (const w of result.warnings) {
2817
+ const at = w.instructionIndex !== void 0 ? ` [ix #${w.instructionIndex}]` : "";
2818
+ lines.push(`${INDENT}${INDENT}${s.dim("\xB7")} ${w.message}${s.dim(at)}`);
2819
+ }
2820
+ }
2821
+ lines.push("");
2822
+ return lines.join("\n");
2823
+ }
2824
+ function renderInstruction(ix, lines, s, depth) {
2825
+ const pad = INDENT.repeat(depth);
2826
+ const name = ix.program ?? shortAddr(ix.programId);
2827
+ const type = ix.type ? `.${ix.type}` : "";
2828
+ const mark = ix.decoded ? "" : s.dim(" (not decoded)");
2829
+ lines.push(`${pad}${s.cyan(`#${ix.index}`)} ${name}${type}${mark}`);
2830
+ if (ix.args && Object.keys(ix.args).length > 0) {
2831
+ const argStr = Object.entries(ix.args).map(([k, v]) => `${k}=${String(v)}`).join(", ");
2832
+ lines.push(`${pad}${INDENT}${s.dim(argStr)}`);
2833
+ }
2834
+ if (ix.inner) {
2835
+ for (const inner of ix.inner) renderInstruction(inner, lines, s, depth + 1);
2836
+ }
2837
+ }
2838
+ function groupBalances(deltas, feePayer) {
2839
+ const byAccount = /* @__PURE__ */ new Map();
2840
+ for (const d of deltas) {
2841
+ const list = byAccount.get(d.account) ?? [];
2842
+ list.push(d);
2843
+ byAccount.set(d.account, list);
2844
+ }
2845
+ const out = [];
2846
+ for (const [account, list] of byAccount) {
2847
+ const text = list.map((d) => d.uiDelta).join(" ");
2848
+ const isFee = account === feePayer;
2849
+ out.push({ account, label: shortAddr(account, 4, 4) + (isFee ? "" : ""), text });
2850
+ }
2851
+ return out;
2852
+ }
2853
+ function colorDelta(text, s) {
2854
+ return text.split(" ").map(
2855
+ (part) => part.trim().startsWith("-") ? s.red(part) : part.trim().startsWith("+") ? s.green(part) : part
2856
+ ).join(" ");
2857
+ }
2858
+ function padRight(str, width) {
2859
+ const visible = stripAnsi(str);
2860
+ return visible.length >= width ? str : str + " ".repeat(width - visible.length);
2861
+ }
2862
+ function describeAction(a) {
2863
+ switch (a.kind) {
2864
+ case "sol-transfer":
2865
+ return `Transfer ${a.sol} SOL ${shortAddr(a.from)} \u2192 ${shortAddr(a.to)}`;
2866
+ case "token-transfer": {
2867
+ const sym = a.symbol ?? shortAddr(a.mint);
2868
+ const self = a.from === a.to ? " (self-transfer)" : "";
2869
+ return `Transfer ${a.uiAmount} ${sym} ${shortAddr(a.from)} \u2192 ${shortAddr(a.to)}${self}`;
2870
+ }
2871
+ case "mint":
2872
+ return `Mint ${a.uiAmount} of ${shortAddr(a.mint)} \u2192 ${shortAddr(a.to)}`;
2873
+ case "burn":
2874
+ return `Burn ${a.uiAmount} of ${shortAddr(a.mint)} from ${shortAddr(a.from)}`;
2875
+ case "account-created": {
2876
+ const as = a.as ? ` (${a.as})` : "";
2877
+ return `Create account ${shortAddr(a.address)}${as} owned by ${shortAddr(a.owner)}`;
2878
+ }
2879
+ case "approval":
2880
+ if (a.revoke) return `Revoke delegate on ${shortAddr(a.owner)}`;
2881
+ return `Approve ${a.amount === "unlimited" ? "unlimited" : a.amount.toString()} to delegate ${shortAddr(a.delegate)}`;
2882
+ case "close-account":
2883
+ return `Close account ${shortAddr(a.account)} \u2192 reclaim ${lamportsToSol(a.reclaimedLamports)} SOL to ${shortAddr(a.destination)}`;
2884
+ case "memo":
2885
+ return `Memo: "${a.text}"`;
2886
+ case "compute-budget": {
2887
+ const parts = [];
2888
+ if (a.unitLimit !== void 0) parts.push(`unit limit ${groupThousands(a.unitLimit.toString())}`);
2889
+ if (a.unitPriceMicroLamports !== void 0)
2890
+ parts.push(`price ${a.unitPriceMicroLamports.toString()} \xB5-lamports`);
2891
+ return `Set compute budget: ${parts.join(", ") || "(no-op)"}`;
2892
+ }
2893
+ case "program-call": {
2894
+ const name = a.program ?? shortAddr(a.programId);
2895
+ const note = a.note ? ` \u2014 ${a.note}` : "";
2896
+ return `Call ${name}${a.instruction ? `.${a.instruction}` : ""}${note}`;
2897
+ }
2898
+ }
2899
+ }
2900
+ function renderMarkdown(result) {
2901
+ const lines = [];
2902
+ lines.push(`### Solana transaction \u2014 ${result.success ? "\u2705 Success" : "\u274C Failed"}`);
2903
+ lines.push("");
2904
+ if (result.signature) lines.push(`**Signature:** \`${result.signature}\` `);
2905
+ if (result.slot !== void 0) lines.push(`**Slot:** ${formatCount(result.slot)} `);
2906
+ const when = formatBlockTime(result.blockTime);
2907
+ if (when) lines.push(`**Time:** ${when} `);
2908
+ lines.push(`**Fee:** ${lamportsToSol(result.feeLamports)} SOL `);
2909
+ if (result.computeUnits !== void 0)
2910
+ lines.push(`**Compute:** ${formatCount(result.computeUnits)} units `);
2911
+ lines.push("");
2912
+ lines.push(`> ${result.summary}`);
2913
+ lines.push("");
2914
+ if (!result.success && result.error) {
2915
+ lines.push(`**Error:** ${result.error.human}`);
2916
+ lines.push("");
2917
+ }
2918
+ if (result.balanceChanges.length > 0) {
2919
+ lines.push("#### Balance changes");
2920
+ lines.push("");
2921
+ lines.push("| Account | Asset | Change |");
2922
+ lines.push("| --- | --- | --- |");
2923
+ for (const d of result.balanceChanges) {
2924
+ const asset = d.asset.kind === "SOL" ? "SOL" : `${d.asset.symbol ?? shortAddr(d.asset.mint)}`;
2925
+ lines.push(`| \`${shortAddr(d.account, 6, 6)}\` | ${asset} | ${d.uiDelta} |`);
2926
+ }
2927
+ lines.push("");
2928
+ }
2929
+ if (result.actions.length > 0) {
2930
+ lines.push("#### Actions");
2931
+ lines.push("");
2932
+ result.actions.forEach((a, i) => lines.push(`${i + 1}. ${describeAction(a)}`));
2933
+ lines.push("");
2934
+ }
2935
+ if (result.programsInvoked.length > 0) {
2936
+ lines.push("#### Programs");
2937
+ lines.push("");
2938
+ for (const p of result.programsInvoked) {
2939
+ lines.push(`- ${p.name ?? `\`${p.programId}\``} \xD7${p.count}`);
2940
+ }
2941
+ lines.push("");
2942
+ }
2943
+ if (result.warnings.length > 0) {
2944
+ lines.push("#### Warnings");
2945
+ lines.push("");
2946
+ for (const w of result.warnings) lines.push(`- \u26A0\uFE0F ${w.message} \`(${w.code})\``);
2947
+ lines.push("");
2948
+ }
2949
+ return lines.join("\n").trimEnd() + "\n";
2950
+ }
2951
+ function renderJson(result, opts) {
2952
+ const replacer = (_key, value) => typeof value === "bigint" ? value.toString() : value;
2953
+ return JSON.stringify(result, replacer, 2 );
2954
+ }
2955
+
2956
+ // src/cli/help.ts
2957
+ function helpText(color) {
2958
+ const s = createStyler(color);
2959
+ const b = s.brand;
2960
+ const d = s.dim;
2961
+ const bold = s.bold;
2962
+ return `
2963
+ ${b(" \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
2964
+ ${b(" \u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D")}
2965
+ ${b(" \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
2966
+ ${b(" \u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551")}
2967
+ ${b(" \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2554\u255D \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551")}
2968
+ ${b(" \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D")}
2969
+ ${bold("solana-explain")} ${d("\xB7 plain-English Solana tx reports \xB7 vtxlabs.dev")}
2970
+
2971
+ ${bold("USAGE")}
2972
+ solana-explain <input> [options]
2973
+ solana-explain --stdin [options]
2974
+ solana-explain --file <path> [options]
2975
+
2976
+ <input> is auto-detected: a base58 signature, a base64/base58 serialized
2977
+ transaction, or '-' / --stdin for piped data. JSON instruction sets are
2978
+ accepted only via --stdin or --file (a JSON array of instructions).
2979
+
2980
+ ${bold("OPTIONS")}
2981
+ -r, --rpc <url> RPC endpoint URL. Falls back to $SOLANA_RPC_URL, then
2982
+ $RPC_URL. Required if neither is set.
2983
+ -c, --commitment <c> processed | confirmed | finalized ${d("(default: confirmed)")}
2984
+ --cluster <name> Preset for -r: mainnet | devnet | testnet | localnet
2985
+ --simulate Force the simulate path even for a signature input
2986
+ --focus <pubkey> Phrase balance deltas from this account's perspective
2987
+ -j, --json Emit machine-readable JSON (the ExplainResult)
2988
+ --markdown Emit Markdown (great for issues / PRs / docs)
2989
+ --raw Include the raw RPC payload under result.raw (with -j)
2990
+ --no-color Disable ANSI color (auto-off when !isTTY or $NO_COLOR)
2991
+ --max-tx-version <n> Max supported tx version for getTransaction ${d("(default: 0)")}
2992
+ --timeout <ms> Per-run network timeout ${d("(default: 30000)")}
2993
+ --stdin Read input from stdin
2994
+ --file <path> Read input from a file
2995
+ --fee-payer <pubkey> Fee payer for JSON instruction-set input
2996
+ -q, --quiet Only the summary line + nonzero exit on failure
2997
+ -v, --verbose Show every instruction incl. inner/CPI tree + raw args
2998
+ --version Print version and exit
2999
+ -h, --help Show this help and exit
3000
+
3001
+ ${bold("EXAMPLES")}
3002
+ ${d("# Explain a confirmed transaction by signature")}
3003
+ solana-explain 5Nq...d8 --rpc https://api.mainnet-beta.solana.com
3004
+
3005
+ ${d("# Simulate & explain a base64 transaction piped from another tool")}
3006
+ cat unsigned.b64 | solana-explain --stdin --simulate -r $SOLANA_RPC_URL
3007
+
3008
+ ${d("# Explain an instruction set from a file as machine-readable JSON")}
3009
+ solana-explain --file ixs.json --fee-payer 7Bf...a21 --cluster devnet -j
3010
+
3011
+ ${bold("EXIT CODES")}
3012
+ 0 success 2 tx failed 3 not found 4 bad input 5 RPC error
3013
+ 6 sim rejected 1 internal error 130 aborted (Ctrl-C)
3014
+ `;
3015
+ }
3016
+
3017
+ // src/cli/index.ts
3018
+ var VERSION = "0.1.0";
3019
+ var CLUSTER_URLS = {
3020
+ mainnet: "https://api.mainnet-beta.solana.com",
3021
+ "mainnet-beta": "https://api.mainnet-beta.solana.com",
3022
+ devnet: "https://api.devnet.solana.com",
3023
+ testnet: "https://api.testnet.solana.com",
3024
+ localnet: "http://127.0.0.1:8899",
3025
+ localhost: "http://127.0.0.1:8899"
3026
+ };
3027
+ function exitCodeFor(err) {
3028
+ switch (err.code) {
3029
+ case "TX_NOT_FOUND":
3030
+ return 3;
3031
+ case "INVALID_INPUT":
3032
+ case "INVALID_SIGNATURE":
3033
+ case "INVALID_ENCODING":
3034
+ case "EMPTY_INPUT":
3035
+ case "DECODE_FAILED":
3036
+ case "UNSUPPORTED_TX_VERSION":
3037
+ return 4;
3038
+ case "RPC_HTTP":
3039
+ case "RPC_JSON":
3040
+ case "RPC_TIMEOUT":
3041
+ return 5;
3042
+ case "SIMULATION_REJECTED":
3043
+ return 6;
3044
+ case "ABORTED":
3045
+ return 130;
3046
+ default:
3047
+ return 1;
3048
+ }
3049
+ }
3050
+ async function readStdin() {
3051
+ const chunks = [];
3052
+ return new Promise((resolve, reject) => {
3053
+ process.stdin.on("data", (c) => chunks.push(c));
3054
+ process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
3055
+ process.stdin.on("error", reject);
3056
+ });
3057
+ }
3058
+ function parse(argv) {
3059
+ const { values, positionals } = parseArgs({
3060
+ args: argv,
3061
+ allowPositionals: true,
3062
+ strict: true,
3063
+ options: {
3064
+ rpc: { type: "string", short: "r" },
3065
+ commitment: { type: "string", short: "c" },
3066
+ cluster: { type: "string" },
3067
+ simulate: { type: "boolean" },
3068
+ focus: { type: "string" },
3069
+ json: { type: "boolean", short: "j" },
3070
+ markdown: { type: "boolean" },
3071
+ raw: { type: "boolean" },
3072
+ color: { type: "boolean", default: true },
3073
+ // --no-color sets false
3074
+ "max-tx-version": { type: "string" },
3075
+ timeout: { type: "string" },
3076
+ stdin: { type: "boolean" },
3077
+ file: { type: "string" },
3078
+ "fee-payer": { type: "string" },
3079
+ quiet: { type: "boolean", short: "q" },
3080
+ verbose: { type: "boolean", short: "v" },
3081
+ version: { type: "boolean" },
3082
+ help: { type: "boolean", short: "h" }
3083
+ }
3084
+ });
3085
+ return { values, positionals };
3086
+ }
3087
+ async function resolveInputString(values, positionals, stderr) {
3088
+ const file = values["file"];
3089
+ const wantsStdin = Boolean(values["stdin"]) || positionals[0] === "-";
3090
+ if (file) {
3091
+ try {
3092
+ return await readFile(file, "utf8");
3093
+ } catch (err) {
3094
+ throw new InputError(
3095
+ "INVALID_INPUT",
3096
+ `Cannot read file "${file}": ${err instanceof Error ? err.message : String(err)}`,
3097
+ { hint: "Check the path and permissions." }
3098
+ );
3099
+ }
3100
+ }
3101
+ if (wantsStdin) {
3102
+ if (process.stdin.isTTY) {
3103
+ stderr.write("waiting for piped input\u2026 (Ctrl-D to end; or pass <input> directly)\n");
3104
+ }
3105
+ const data = await readStdin();
3106
+ if (data.trim().length === 0) {
3107
+ throw new InputError("EMPTY_INPUT", "Nothing was piped to stdin.", {
3108
+ hint: "Pipe a signature/transaction, or pass <input> as an argument."
3109
+ });
3110
+ }
3111
+ return data;
3112
+ }
3113
+ const positional = positionals[0];
3114
+ if (positional === void 0 || positional.trim().length === 0) {
3115
+ throw new InputError("EMPTY_INPUT", "No input provided.", {
3116
+ hint: "Pass a signature/transaction, use --stdin, or --file <path>."
3117
+ });
3118
+ }
3119
+ return positional;
3120
+ }
3121
+ function resolveRpcUrl(values, env) {
3122
+ const cluster = values["cluster"];
3123
+ const explicit = values["rpc"];
3124
+ if (explicit) return explicit;
3125
+ if (cluster) {
3126
+ const url = CLUSTER_URLS[cluster.toLowerCase()];
3127
+ if (!url) {
3128
+ throw new InputError(
3129
+ "INVALID_INPUT",
3130
+ `Unknown cluster "${cluster}". Use mainnet | devnet | testnet | localnet.`
3131
+ );
3132
+ }
3133
+ return url;
3134
+ }
3135
+ const fromEnv = env["SOLANA_RPC_URL"] ?? env["RPC_URL"];
3136
+ if (fromEnv) return fromEnv;
3137
+ throw new InputError("INVALID_INPUT", "No RPC URL.", {
3138
+ hint: "Pass --rpc <url>, --cluster <name>, or set SOLANA_RPC_URL."
3139
+ });
3140
+ }
3141
+ async function run(argv, io) {
3142
+ let values;
3143
+ let positionals;
3144
+ try {
3145
+ const parsed = parse(argv);
3146
+ values = parsed.values;
3147
+ positionals = parsed.positionals;
3148
+ } catch (err) {
3149
+ const colorEarly = shouldUseColor({ isTTY: io.stderr.isTTY, env: io.env });
3150
+ const s2 = createStyler(colorEarly);
3151
+ io.stderr.write(`${s2.red("Error")} ${err instanceof Error ? err.message : String(err)}
3152
+ `);
3153
+ io.stderr.write(`Run ${s2.bold("solana-explain --help")} for usage.
3154
+ `);
3155
+ return 4;
3156
+ }
3157
+ const machineOutput = Boolean(values["json"] || values["markdown"]);
3158
+ const colorEnabled = !machineOutput && shouldUseColor({
3159
+ ...values["color"] === false ? { explicit: false } : {},
3160
+ isTTY: io.stdout.isTTY,
3161
+ env: io.env
3162
+ });
3163
+ const s = createStyler(colorEnabled);
3164
+ const debug = Boolean(io.env["DEBUG"]) || Boolean(values["verbose"]);
3165
+ if (values["help"]) {
3166
+ io.stdout.write(helpText(colorEnabled));
3167
+ return 0;
3168
+ }
3169
+ if (values["version"]) {
3170
+ io.stdout.write(`${VERSION}
3171
+ `);
3172
+ return 0;
3173
+ }
3174
+ try {
3175
+ const inputStr = await resolveInputString(values, positionals, io.stderr);
3176
+ const rpcUrl = resolveRpcUrl(values, io.env);
3177
+ const commitment = values["commitment"] ?? "confirmed";
3178
+ if (!["processed", "confirmed", "finalized"].includes(commitment)) {
3179
+ throw new InputError("INVALID_INPUT", `Invalid commitment "${commitment}".`);
3180
+ }
3181
+ const timeoutMs = values["timeout"] ? Number(values["timeout"]) : void 0;
3182
+ const maxTxVersion = values["max-tx-version"] ? Number(values["max-tx-version"]) : 0;
3183
+ const baseOpts = {
3184
+ rpc: rpcUrl,
3185
+ commitment,
3186
+ ...values["focus"] ? { focusAccount: values["focus"] } : {},
3187
+ ...timeoutMs !== void 0 ? { timeoutMs } : {},
3188
+ ...io.signal ? { signal: io.signal } : {},
3189
+ ...values["raw"] ? { includeRaw: true } : {}
3190
+ };
3191
+ let result;
3192
+ const detected = detectInput(inputStr);
3193
+ if (values["simulate"] && detected.kind === "signature") {
3194
+ throw new InputError(
3195
+ "INVALID_INPUT",
3196
+ "--simulate requires a serialized transaction, not a signature.",
3197
+ { hint: "Pipe a base64/base58 transaction with --simulate." }
3198
+ );
3199
+ }
3200
+ if (detected.kind === "signature") {
3201
+ result = await explainSignature(detected.signature, {
3202
+ ...baseOpts,
3203
+ maxSupportedTransactionVersion: maxTxVersion
3204
+ });
3205
+ } else if (detected.kind === "tx-bytes") {
3206
+ result = await explainTransaction(detected.bytes, {
3207
+ ...baseOpts,
3208
+ encoding: detected.encoding
3209
+ });
3210
+ } else {
3211
+ const feePayer = values["fee-payer"] ?? "";
3212
+ if (!feePayer) {
3213
+ throw new InputError(
3214
+ "INVALID_INPUT",
3215
+ "Instruction-set input requires --fee-payer <pubkey>.",
3216
+ { hint: "Add --fee-payer with the payer pubkey." }
3217
+ );
3218
+ }
3219
+ result = await explainInstructions(detected.instructions, {
3220
+ ...baseOpts,
3221
+ feePayer
3222
+ });
3223
+ }
3224
+ if (values["json"]) {
3225
+ io.stdout.write(renderJson(result, { pretty: true }) + "\n");
3226
+ } else if (values["markdown"]) {
3227
+ io.stdout.write(renderMarkdown(result));
3228
+ } else if (values["quiet"]) {
3229
+ io.stdout.write(result.summary + "\n");
3230
+ for (const w of result.warnings) io.stderr.write(`${s.yellow("\u26A0")} ${w.message}
3231
+ `);
3232
+ } else {
3233
+ io.stdout.write(renderText(result, { color: colorEnabled, verbose: Boolean(values["verbose"]) }) + "\n");
3234
+ }
3235
+ return result.success ? 0 : 2;
3236
+ } catch (err) {
3237
+ return handleError(err, s, io.stderr, debug);
3238
+ }
3239
+ }
3240
+ function handleError(err, s, stderr, debug) {
3241
+ if (err instanceof SimulationError) {
3242
+ stderr.write(`${s.red("Error")} ${err.message}
3243
+ `);
3244
+ if (err.logs && err.logs.length > 0) {
3245
+ stderr.write(`${s.dim("Logs (tail):")}
3246
+ `);
3247
+ for (const l of err.logs) stderr.write(`${s.dim(" " + l)}
3248
+ `);
3249
+ }
3250
+ if (err.hint) stderr.write(`${s.dim("Hint:")} ${err.hint}
3251
+ `);
3252
+ if (debug && err.cause) stderr.write(`${String(err.cause)}
3253
+ `);
3254
+ return exitCodeFor(err);
3255
+ }
3256
+ if (err instanceof SolanaExplainError) {
3257
+ stderr.write(`${s.red("Error")} ${err.message}
3258
+ `);
3259
+ if (err.hint) stderr.write(`${s.dim("Hint:")} ${err.hint}
3260
+ `);
3261
+ if (debug && err.cause) {
3262
+ stderr.write(`${s.dim("Cause:")} ${err.cause instanceof Error ? err.cause.stack ?? err.cause.message : String(err.cause)}
3263
+ `);
3264
+ }
3265
+ return exitCodeFor(err);
3266
+ }
3267
+ stderr.write(`${s.red("Internal error")} ${err instanceof Error ? err.message : String(err)}
3268
+ `);
3269
+ stderr.write(
3270
+ `${s.dim("This is likely a bug. Please report it: https://github.com/VTX-Labs/solana-explain/issues")}
3271
+ `
3272
+ );
3273
+ if (debug && err instanceof Error && err.stack) stderr.write(`${err.stack}
3274
+ `);
3275
+ return 1;
3276
+ }
3277
+ async function main() {
3278
+ const controller = new AbortController();
3279
+ const onSigint = () => controller.abort();
3280
+ process.on("SIGINT", onSigint);
3281
+ let exitCode;
3282
+ try {
3283
+ exitCode = await run(process.argv.slice(2), {
3284
+ stdout: process.stdout,
3285
+ stderr: process.stderr,
3286
+ env: process.env,
3287
+ signal: controller.signal
3288
+ });
3289
+ } catch (err) {
3290
+ process.stderr.write(`Internal error: ${err instanceof Error ? err.message : String(err)}
3291
+ `);
3292
+ exitCode = 1;
3293
+ } finally {
3294
+ process.off("SIGINT", onSigint);
3295
+ }
3296
+ if (controller.signal.aborted) {
3297
+ process.exitCode = 130;
3298
+ } else {
3299
+ process.exitCode = exitCode;
3300
+ }
3301
+ }
3302
+ var isMain = (() => {
3303
+ try {
3304
+ const invoked = process.argv[1];
3305
+ if (!invoked) return false;
3306
+ return import.meta.url === pathToFileURL(invoked).href;
3307
+ } catch {
3308
+ return false;
3309
+ }
3310
+ })();
3311
+ if (isMain) {
3312
+ void main();
3313
+ }
3314
+
3315
+ export { run };
3316
+ //# sourceMappingURL=index.js.map
3317
+ //# sourceMappingURL=index.js.map