Skip to content

Commit 1e6aed6

Browse files
authored
Merge pull request #166 from BitGo/BTC-3025-explain-transaction
feat: add explainTransaction, unified parseTransaction, and TransactionType enum
2 parents c2926c0 + a066017 commit 1e6aed6

8 files changed

Lines changed: 549 additions & 61 deletions

File tree

packages/wasm-solana/js/explain.ts

Lines changed: 435 additions & 0 deletions
Large diffs are not rendered by default.

packages/wasm-solana/js/index.ts

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export * as pubkey from "./pubkey.js";
99
export * as transaction from "./transaction.js";
1010
export * as parser from "./parser.js";
1111
export * as builder from "./builder.js";
12+
export * as explain from "./explain.js";
1213

1314
// Top-level class exports for convenience
1415
export { Keypair } from "./keypair.js";
@@ -20,9 +21,49 @@ export { VersionedTransaction, isVersionedTransaction } from "./versioned.js";
2021
export type { AddressLookupTableData } from "./versioned.js";
2122

2223
// Top-level function exports
23-
export { parseTransaction } from "./parser.js";
24+
export { parseTransactionData } from "./parser.js";
2425
export { buildFromVersionedData } from "./builder.js";
2526
export { buildFromIntent, buildFromIntent as buildTransactionFromIntent } from "./intentBuilder.js";
27+
export { explainTransaction, TransactionType } from "./explain.js";
28+
29+
// Re-export Transaction import for parseTransaction
30+
import { Transaction as _Transaction } from "./transaction.js";
31+
32+
/**
33+
* Parse a Solana transaction from raw bytes.
34+
*
35+
* Returns a `Transaction` instance that can be both inspected and signed.
36+
* Use `.parse()` on the returned Transaction to get decoded instruction data.
37+
*
38+
* This is the single entry point for working with transactions — like
39+
* `BitGoPsbt.fromBytes()` in wasm-utxo.
40+
*
41+
* @param bytes - Raw transaction bytes
42+
* @returns A Transaction that can be inspected (`.parse()`) and signed (`.addSignature()`)
43+
*
44+
* @example
45+
* ```typescript
46+
* import { parseTransaction } from '@bitgo/wasm-solana';
47+
*
48+
* const tx = parseTransaction(txBytes);
49+
*
50+
* // Inspect
51+
* const parsed = tx.parse();
52+
* console.log(parsed.feePayer);
53+
* for (const instr of parsed.instructionsData) {
54+
* if (instr.type === 'Transfer') {
55+
* console.log(`${instr.amount} lamports to ${instr.toAddress}`);
56+
* }
57+
* }
58+
*
59+
* // Sign
60+
* tx.addSignature(pubkey, signature);
61+
* const signedBytes = tx.toBytes();
62+
* ```
63+
*/
64+
export function parseTransaction(bytes: Uint8Array): _Transaction {
65+
return _Transaction.fromBytes(bytes);
66+
}
2667

2768
// Intent builder type exports
2869
export type {
@@ -36,6 +77,10 @@ export type {
3677
EnableTokenIntent,
3778
CloseAtaIntent,
3879
ConsolidateIntent,
80+
AuthorizeIntent,
81+
CustomTxIntent,
82+
CustomTxInstruction,
83+
CustomTxKey,
3984
SolanaIntent,
4085
StakePoolConfig,
4186
BuildFromIntentParams,
@@ -68,7 +113,6 @@ export {
68113
// Type exports
69114
export type { AccountMeta, Instruction } from "./transaction.js";
70115
export type {
71-
TransactionInput,
72116
ParsedTransaction,
73117
DurableNonce as ParsedDurableNonce,
74118
InstructionParams,
@@ -94,6 +138,16 @@ export type {
94138
UnknownInstructionParams,
95139
} from "./parser.js";
96140

141+
// Explain types
142+
export type {
143+
ExplainedTransaction,
144+
ExplainedOutput,
145+
ExplainedInput,
146+
ExplainOptions,
147+
TokenEnablement,
148+
StakingAuthorizeInfo,
149+
} from "./explain.js";
150+
97151
// Versioned transaction builder type exports
98152
export type {
99153
AddressLookupTable as BuilderAddressLookupTable,

packages/wasm-solana/js/parser.ts

Lines changed: 7 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,9 @@
55
* matching BitGoJS's TxData format.
66
*
77
* All monetary amounts (amount, fee, lamports, poolTokens) are returned as bigint.
8-
* Accepts both raw bytes and Transaction objects for convenience.
98
*/
109

1110
import { ParserNamespace } from "./wasm/wasm_solana.js";
12-
import type { Transaction } from "./transaction.js";
13-
import type { VersionedTransaction } from "./versioned.js";
14-
15-
/**
16-
* Input type for parseTransaction - accepts bytes or Transaction objects.
17-
*/
18-
export type TransactionInput = Uint8Array | Transaction | VersionedTransaction;
1911

2012
// =============================================================================
2113
// Instruction Types - matching BitGoJS InstructionParams.
@@ -277,48 +269,19 @@ export interface ParsedTransaction {
277269
}
278270

279271
// =============================================================================
280-
// parseTransaction function
272+
// parseTransactionData function
281273
// =============================================================================
282274

283275
/**
284-
* Parse a Solana transaction into structured data.
285-
*
286-
* This is the main entry point for transaction parsing. It deserializes the
287-
* transaction and decodes all instructions into semantic types.
288-
*
289-
* All monetary amounts (amount, fee, lamports, poolTokens) are returned as bigint
290-
* directly from WASM - no post-processing needed.
276+
* Parse raw transaction bytes into a plain data object with decoded instructions.
291277
*
292-
* Note: This returns the raw parsed data including NonceAdvance instructions.
293-
* Consumers (like BitGoJS) may choose to filter NonceAdvance from instructionsData
294-
* since that info is also available in durableNonce.
278+
* This is the low-level parsing function. Most callers should use the top-level
279+
* `parseTransaction(bytes)` which returns a `Transaction` instance with both
280+
* inspection (`.parse()`) and signing (`.addSignature()`) capabilities.
295281
*
296-
* @param input - Raw transaction bytes, Transaction, or VersionedTransaction
282+
* @param bytes - Raw transaction bytes
297283
* @returns A ParsedTransaction with all instructions decoded
298-
* @throws Error if the transaction cannot be parsed
299-
*
300-
* @example
301-
* ```typescript
302-
* import { parseTransaction, buildTransaction, Transaction } from '@bitgo/wasm-solana';
303-
*
304-
* // From bytes
305-
* const txBytes = Buffer.from(base64EncodedTx, 'base64');
306-
* const parsed = parseTransaction(txBytes);
307-
*
308-
* // Directly from a Transaction object (no roundtrip through bytes)
309-
* const tx = buildTransaction(intent);
310-
* const parsed = parseTransaction(tx);
311-
*
312-
* console.log(parsed.feePayer);
313-
* for (const instr of parsed.instructionsData) {
314-
* if (instr.type === 'Transfer') {
315-
* console.log(`Transfer ${instr.amount} from ${instr.fromAddress} to ${instr.toAddress}`);
316-
* }
317-
* }
318-
* ```
319284
*/
320-
export function parseTransaction(input: TransactionInput): ParsedTransaction {
321-
// If input is a Transaction or VersionedTransaction, extract bytes
322-
const bytes = input instanceof Uint8Array ? input : input.toBytes();
285+
export function parseTransactionData(bytes: Uint8Array): ParsedTransaction {
323286
return ParserNamespace.parse_transaction(bytes) as ParsedTransaction;
324287
}

packages/wasm-solana/js/transaction.ts

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { WasmTransaction } from "./wasm/wasm_solana.js";
22
import { Keypair } from "./keypair.js";
33
import { Pubkey } from "./pubkey.js";
4+
import { parseTransactionData } from "./parser.js";
5+
import type { ParsedTransaction } from "./parser.js";
46

57
/**
68
* Account metadata for an instruction
@@ -27,22 +29,29 @@ export interface Instruction {
2729
}
2830

2931
/**
30-
* Solana Transaction wrapper for low-level deserialization and inspection.
32+
* Solana Transaction — the single object for inspecting and signing transactions.
3133
*
32-
* This class provides low-level access to transaction structure.
33-
* For high-level semantic parsing with decoded instructions, use `parseTransaction()` instead.
34+
* Use `parseTransaction(bytes)` to create an instance. The returned Transaction
35+
* can be both inspected (`.parse()` for decoded instructions) and signed
36+
* (`.addSignature()`, `.signablePayload()`, `.toBytes()`).
3437
*
3538
* @example
3639
* ```typescript
37-
* import { Transaction, parseTransaction } from '@bitgo/wasm-solana';
40+
* import { parseTransaction } from '@bitgo/wasm-solana';
3841
*
39-
* // Low-level access:
40-
* const tx = Transaction.fromBytes(txBytes);
41-
* console.log(tx.feePayer);
42+
* const tx = parseTransaction(txBytes);
4243
*
43-
* // High-level parsing (preferred):
44-
* const parsed = parseTransaction(txBytes);
45-
* console.log(parsed.instructionsData); // Decoded instruction types
44+
* // Inspect decoded instructions
45+
* const parsed = tx.parse();
46+
* for (const instr of parsed.instructionsData) {
47+
* if (instr.type === 'Transfer') {
48+
* console.log(`${instr.amount} lamports to ${instr.toAddress}`);
49+
* }
50+
* }
51+
*
52+
* // Sign and serialize
53+
* tx.addSignature(pubkey, signature);
54+
* const signedBytes = tx.toBytes();
4655
* ```
4756
*/
4857
export class Transaction {
@@ -237,6 +246,26 @@ export class Transaction {
237246
this._wasm.sign_with_keypair(keypair.wasm);
238247
}
239248

249+
/**
250+
* Parse the transaction into decoded instruction data.
251+
*
252+
* Returns structured data with all instructions decoded into semantic types
253+
* (Transfer, StakeActivate, TokenTransfer, etc.) with amounts as bigint.
254+
*
255+
* @returns A ParsedTransaction with decoded instructions, feePayer, nonce, etc.
256+
*
257+
* @example
258+
* ```typescript
259+
* const tx = parseTransaction(txBytes);
260+
* const parsed = tx.parse();
261+
* console.log(parsed.feePayer);
262+
* console.log(parsed.instructionsData); // Decoded instruction types
263+
* ```
264+
*/
265+
parse(): ParsedTransaction {
266+
return parseTransactionData(this._wasm.to_bytes());
267+
}
268+
240269
/**
241270
* Get the underlying WASM instance (internal use only)
242271
* @internal

packages/wasm-solana/test/bitgojs-compat.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* what BitGoJS's Transaction.toJson() produces.
66
*/
77
import * as assert from "assert";
8-
import { parseTransaction } from "../js/parser.js";
8+
import { parseTransactionData as parseTransaction } from "../js/parser.js";
99

1010
// Helper to decode base64 in tests
1111
function base64ToBytes(base64: string): Uint8Array {

packages/wasm-solana/test/intentBuilder.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument */
99

1010
import assert from "assert";
11-
import { buildFromIntent, Transaction, parseTransaction } from "../dist/cjs/js/index.js";
11+
import {
12+
buildFromIntent,
13+
Transaction,
14+
parseTransactionData as parseTransaction,
15+
} from "../dist/cjs/js/index.js";
1216

1317
describe("buildFromIntent", function () {
1418
// Common test params

packages/wasm-solana/test/parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as assert from "assert";
2-
import { parseTransaction } from "../js/parser.js";
2+
import { parseTransactionData as parseTransaction } from "../js/parser.js";
33

44
// Helper to decode base64 in tests
55
function base64ToBytes(base64: string): Uint8Array {

packages/webui/src/wasm-solana/transaction/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,8 @@ class SolanaTransactionParser extends BaseComponent {
516516
try {
517517
// Parse the transaction
518518
const bytes = base64ToBytes(txData);
519-
const parsed = parseTransaction(bytes);
519+
const tx = parseTransaction(bytes);
520+
const parsed = tx.parse();
520521

521522
// Render transaction info
522523
txInfoEl.replaceChildren(this.renderTxInfo(parsed));
@@ -533,7 +534,9 @@ class SolanaTransactionParser extends BaseComponent {
533534
"section",
534535
{ class: "instructions-section" },
535536
h("h2", {}, `Instructions (${parsed.instructionsData.length})`),
536-
...parsed.instructionsData.map((instr, idx) => this.renderInstruction(instr, idx)),
537+
...parsed.instructionsData.map((instr: InstructionParams, idx: number) =>
538+
this.renderInstruction(instr, idx),
539+
),
537540
),
538541
);
539542
} catch (e) {

0 commit comments

Comments
 (0)