nara-sdk 1.0.78 → 1.0.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -223,6 +223,7 @@ export {
223
223
  // Export transaction parser
224
224
  export {
225
225
  parseTxFromHash,
226
+ parseTxsFromHashes,
226
227
  parseTxResponse,
227
228
  formatParsedTx,
228
229
  type ParsedInstruction,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nara-sdk",
3
- "version": "1.0.78",
3
+ "version": "1.0.80",
4
4
  "description": "SDK for the Nara chain (Solana-compatible)",
5
5
  "module": "index.ts",
6
6
  "main": "index.ts",
@@ -18,7 +18,8 @@
18
18
  "sdk"
19
19
  ],
20
20
  "scripts": {
21
- "test": "tsx test/read_api.test.ts"
21
+ "test": "tsx test/read_api.test.ts",
22
+ "test:parser": "tsx test/tx_parser.test.ts"
22
23
  },
23
24
  "author": "",
24
25
  "license": "MIT",
package/src/tx.ts CHANGED
@@ -196,19 +196,16 @@ export async function sendTx(
196
196
  });
197
197
  }
198
198
 
199
- // Poll for confirmation (avoid confirmTransaction which uses WebSocket)
200
- const startTime = Date.now();
199
+ // Wait a couple of seconds for the validator to see and process the tx,
200
+ // then poll getSignatureStatuses every 2s until confirmed or timeout.
201
201
  const TIMEOUT_MS = 20_000;
202
- const POLL_INTERVAL_MS = 1_000;
202
+ const POLL_INTERVAL_MS = 2_000;
203
+ const INITIAL_DELAY_MS = 2_000;
203
204
 
204
- while (Date.now() - startTime < TIMEOUT_MS) {
205
- const currentBlockHeight = await connection.getBlockHeight("confirmed");
206
- if (currentBlockHeight > lastValidBlockHeight) {
207
- throw new Error(
208
- `Transaction ${signature} expired: block height exceeded`
209
- );
210
- }
205
+ await new Promise((r) => setTimeout(r, INITIAL_DELAY_MS));
211
206
 
207
+ const startTime = Date.now();
208
+ while (Date.now() - startTime < TIMEOUT_MS) {
212
209
  const statusResult = await connection.getSignatureStatuses([signature]);
213
210
  const status = statusResult?.value?.[0];
214
211
 
package/src/tx_parser.ts CHANGED
@@ -43,7 +43,10 @@ import { BRIDGE_TOKENS } from "./bridge";
43
43
  // ─── Types ────────────────────────────────────────────────────────
44
44
 
45
45
  export interface ParsedInstruction {
46
- /** Instruction index in the transaction */
46
+ /**
47
+ * Instruction index. For top-level ixs this is the 0-based position in the tx.
48
+ * For inner ixs this is the 0-based position within the parent's CPI list.
49
+ */
47
50
  index: number;
48
51
  /** Human-readable program name */
49
52
  programName: string;
@@ -57,6 +60,11 @@ export interface ParsedInstruction {
57
60
  accounts: string[];
58
61
  /** Raw instruction data (base64) */
59
62
  rawData: string;
63
+ /**
64
+ * Inner instructions (CPI calls) made by this instruction.
65
+ * Only populated on top-level ixs; empty/undefined for ixs that made no CPI calls.
66
+ */
67
+ innerInstructions?: ParsedInstruction[];
60
68
  }
61
69
 
62
70
  export interface ParsedTransaction {
@@ -72,7 +80,7 @@ export interface ParsedTransaction {
72
80
  error: string | null;
73
81
  /** Fee paid in lamports */
74
82
  fee: number;
75
- /** Parsed instructions */
83
+ /** Top-level instructions. Inner (CPI) ixs are nested under each one via `innerInstructions`. */
76
84
  instructions: ParsedInstruction[];
77
85
  /** Log messages */
78
86
  logs: string[];
@@ -635,6 +643,39 @@ export async function parseTxFromHash(
635
643
  return parseTxResponse(tx);
636
644
  }
637
645
 
646
+ /**
647
+ * Fetch and parse multiple transactions in a single batch RPC request.
648
+ *
649
+ * Uses `connection.getTransactions(sigs)` which sends one JSON-RPC batch
650
+ * under the hood (via `_rpcBatchRequest`), so N signatures → 1 HTTP call.
651
+ *
652
+ * Returns a result array aligned with the input: each entry is either a
653
+ * `ParsedTransaction` or `null` if the RPC could not find that signature.
654
+ * This preserves index mapping so the caller can pair results with inputs.
655
+ *
656
+ * @example
657
+ * ```ts
658
+ * const results = await parseTxsFromHashes(connection, [sig1, sig2, sig3]);
659
+ * results.forEach((r, i) => {
660
+ * if (!r) console.log(`${sigs[i]} not found`);
661
+ * else console.log(formatParsedTx(r));
662
+ * });
663
+ * ```
664
+ */
665
+ export async function parseTxsFromHashes(
666
+ connection: Connection,
667
+ signatures: string[]
668
+ ): Promise<(ParsedTransaction | null)[]> {
669
+ if (signatures.length === 0) return [];
670
+
671
+ const txs = await connection.getTransactions(signatures, {
672
+ maxSupportedTransactionVersion: 0,
673
+ commitment: "confirmed",
674
+ });
675
+
676
+ return txs.map((tx) => (tx ? parseTxResponse(tx) : null));
677
+ }
678
+
638
679
  /**
639
680
  * Parse a VersionedTransactionResponse (from getTransaction) synchronously.
640
681
  *
@@ -663,25 +704,25 @@ export function parseTxResponse(tx: VersionedTransactionResponse): ParsedTransac
663
704
  decodeInstruction(ix, i, accountKeys)
664
705
  );
665
706
 
666
- // Also parse inner instructions (CPI)
707
+ // Attach inner instructions (CPI calls) under their parent ix.
708
+ // RPC response: meta.innerInstructions is [{ index, instructions: [...] }, ...]
709
+ // where `index` points to the top-level ix that made the CPI calls.
667
710
  if (tx.meta?.innerInstructions) {
668
711
  for (const inner of tx.meta.innerInstructions) {
669
- for (const innerIx of inner.instructions) {
670
- let dataBuf: Buffer;
671
- if (typeof innerIx.data === "string") {
672
- dataBuf = Buffer.from(decodeBase58(innerIx.data));
673
- } else {
674
- dataBuf = Buffer.from(innerIx.data);
675
- }
712
+ const parent = instructions[inner.index];
713
+ if (!parent) continue;
714
+ parent.innerInstructions = inner.instructions.map((innerIx, j) => {
715
+ const dataBuf: Buffer =
716
+ typeof innerIx.data === "string"
717
+ ? Buffer.from(decodeBase58(innerIx.data))
718
+ : Buffer.from(innerIx.data);
676
719
  const innerCompiled: MessageCompiledInstruction = {
677
720
  programIdIndex: innerIx.programIdIndex,
678
721
  accountKeyIndexes: innerIx.accounts,
679
722
  data: dataBuf,
680
723
  };
681
- instructions.push(
682
- decodeInstruction(innerCompiled, instructions.length, accountKeys)
683
- );
684
- }
724
+ return decodeInstruction(innerCompiled, j, accountKeys);
725
+ });
685
726
  }
686
727
  }
687
728
 
@@ -739,12 +780,23 @@ export function formatParsedTx(parsed: ParsedTransaction): string {
739
780
  lines.push("");
740
781
 
741
782
  for (const ix of parsed.instructions) {
742
- const infoStr = Object.entries(ix.info)
743
- .map(([k, v]) => `${k}=${v}`)
744
- .join(", ");
745
- lines.push(` #${ix.index} [${ix.programName}] ${ix.type}`);
746
- if (infoStr) lines.push(` ${infoStr}`);
783
+ formatIxLines(ix, 0, lines);
747
784
  }
748
785
 
749
786
  return lines.join("\n");
750
787
  }
788
+
789
+ function formatIxLines(ix: ParsedInstruction, depth: number, lines: string[]): void {
790
+ const indent = " ".repeat(depth + 1);
791
+ const prefix = depth === 0 ? `#${ix.index}` : `↳ ${ix.index}`;
792
+ const infoStr = Object.entries(ix.info)
793
+ .map(([k, v]) => `${k}=${v}`)
794
+ .join(", ");
795
+ lines.push(`${indent}${prefix} [${ix.programName}] ${ix.type}`);
796
+ if (infoStr) lines.push(`${indent} ${infoStr}`);
797
+ if (ix.innerInstructions && ix.innerInstructions.length > 0) {
798
+ for (const inner of ix.innerInstructions) {
799
+ formatIxLines(inner, depth + 1, lines);
800
+ }
801
+ }
802
+ }