Skip to content

Commit 17eeedf

Browse files
committed
feat(wasm-solana): add transaction parsing with BitGoJS compatibility
Add comprehensive Solana transaction parsing capabilities via WASM, providing types and structures compatible with BitGoJS's sdk-coin-sol. ## Rust Layer ### Transaction Deserialization (src/transaction.rs) - Deserialize legacy Solana transactions from wire format - Extract fee payer, blockhash, signatures, and account keys - Provide signable payload for signing operations ### Instruction Decoding (src/instructions/) - Decode System Program: Transfer, CreateAccount, NonceAdvance, NonceInitialize - Decode Stake Program: Initialize, Delegate, Deactivate, Withdraw, Authorize - Decode ComputeBudget: SetComputeUnitLimit, SetComputeUnitPrice - Decode SPL Token: TransferChecked via token and token-2022 programs - Decode ATA Program: CreateAssociatedTokenAccount, CloseAccount - Decode Memo Program - Unknown instruction fallback with raw data preservation ### High-Level Parser (src/parser.rs) - Combine CreateAccount + NonceInitialize into CreateNonceAccount - Combine CreateAccount + StakeInitialize + DelegateStake into StakingActivate - Detect durable nonce transactions and populate durableNonce field - Add stakingType field (NATIVE, JITO, MARINADE) to StakingActivate ### WASM Bindings (src/wasm/) - ParserNamespace::parse_transaction - returns ParsedTransaction as JsValue - TransactionNamespace - transaction inspection methods ## TypeScript Layer ### Transaction Wrapper (js/transaction.ts) - Transaction class wrapping WASM with typed methods - Access to fee payer, blockhash, signatures, instructions, account keys ### Parser Types (js/parser.ts) - Full TypeScript interfaces matching BitGoJS InstructionParams - ParsedTransaction interface with feePayer, nonce, durableNonce, instructionsData - parseTransaction() function with BigInt conversion for SetPriorityFee.fee ### Exports (js/index.ts) - Export Transaction class and parseTransaction function - Re-export all instruction parameter types ## Tests - BitGoJS compatibility tests using actual transaction fixtures - Transaction deserialization and inspection tests - Parser instruction type discrimination tests TICKET: BTC-2929
1 parent 739b7e1 commit 17eeedf

18 files changed

Lines changed: 2977 additions & 115 deletions

File tree

packages/wasm-solana/Cargo.lock

Lines changed: 751 additions & 92 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/wasm-solana/Cargo.toml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,25 @@ all = "warn"
1212
[dependencies]
1313
wasm-bindgen = "0.2"
1414
js-sys = "0.3"
15+
serde = { version = "1.0", features = ["derive"] }
16+
serde_json = "1.0"
1517
# Solana SDK crates
1618
solana-pubkey = { version = "2.0", features = ["curve25519"] }
1719
solana-keypair = "2.0"
1820
solana-signer = "2.0"
19-
# Ed25519 for deriving pubkey from 32-byte seed (solana-keypair expects 64-byte format)
20-
ed25519-dalek = { version = "2.1", default-features = false, features = ["std"] }
21+
solana-transaction = { version = "3.0", features = ["serde", "bincode"] }
22+
# Instruction decoder interfaces (official Solana crates)
23+
solana-system-interface = { version = "2.0", features = ["bincode"] }
24+
solana-stake-interface = { version = "2.0", features = ["bincode"] }
25+
solana-compute-budget-interface = { version = "2.0", features = ["borsh"] }
26+
# Serialization
27+
bincode = "1.3"
28+
borsh = "1.5"
29+
base64 = "0.22"
30+
serde-wasm-bindgen = "0.6"
2131

2232
[dev-dependencies]
2333
wasm-bindgen-test = "0.3"
24-
serde = { version = "1.0", features = ["derive"] }
25-
serde_json = "1.0"
2634
hex = "0.4"
2735

2836
[profile.release]

packages/wasm-solana/js/index.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,37 @@ void wasm;
66
// Namespace exports for explicit imports
77
export * as keypair from "./keypair.js";
88
export * as pubkey from "./pubkey.js";
9+
export * as transaction from "./transaction.js";
10+
export * as parser from "./parser.js";
911

1012
// Top-level class exports for convenience
1113
export { Keypair } from "./keypair.js";
1214
export { Pubkey } from "./pubkey.js";
15+
export { Transaction } from "./transaction.js";
16+
17+
// Top-level function exports
18+
export { parseTransaction } from "./parser.js";
19+
20+
// Type exports
21+
export type { AccountMeta, Instruction } from "./transaction.js";
22+
export type {
23+
ParsedTransaction,
24+
DurableNonce,
25+
InstructionParams,
26+
TransferParams,
27+
CreateAccountParams,
28+
NonceAdvanceParams,
29+
CreateNonceAccountParams,
30+
StakingActivateParams,
31+
StakingDeactivateParams,
32+
StakingWithdrawParams,
33+
StakingDelegateParams,
34+
StakingAuthorizeParams,
35+
SetComputeUnitLimitParams,
36+
SetPriorityFeeParams,
37+
TokenTransferParams,
38+
CreateAtaParams,
39+
CloseAtaParams,
40+
MemoParams,
41+
UnknownInstructionParams,
42+
} from "./parser.js";

packages/wasm-solana/js/parser.ts

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
/**
2+
* High-level transaction parsing.
3+
*
4+
* Provides types and functions for parsing Solana transactions into semantic data
5+
* matching BitGoJS's TxData format.
6+
*/
7+
8+
import { ParserNamespace } from "./wasm/wasm_solana.js";
9+
10+
// =============================================================================
11+
// Instruction Types - matching BitGoJS InstructionParams
12+
// =============================================================================
13+
14+
/** SOL transfer parameters */
15+
export interface TransferParams {
16+
type: "Transfer";
17+
fromAddress: string;
18+
toAddress: string;
19+
amount: string;
20+
}
21+
22+
/** Create account parameters */
23+
export interface CreateAccountParams {
24+
type: "CreateAccount";
25+
fromAddress: string;
26+
newAddress: string;
27+
amount: string;
28+
space: number;
29+
owner: string;
30+
}
31+
32+
/** Nonce advance parameters */
33+
export interface NonceAdvanceParams {
34+
type: "NonceAdvance";
35+
walletNonceAddress: string;
36+
authWalletAddress: string;
37+
}
38+
39+
/** Create nonce account parameters */
40+
export interface CreateNonceAccountParams {
41+
type: "CreateNonceAccount";
42+
fromAddress: string;
43+
nonceAddress: string;
44+
authAddress: string;
45+
amount: string;
46+
}
47+
48+
/** Staking activate parameters */
49+
export interface StakingActivateParams {
50+
type: "StakingActivate";
51+
fromAddress: string;
52+
stakingAddress: string;
53+
amount: string;
54+
validator: string;
55+
stakingType: "NATIVE" | "JITO" | "MARINADE";
56+
}
57+
58+
/** Staking deactivate parameters */
59+
export interface StakingDeactivateParams {
60+
type: "StakingDeactivate";
61+
stakingAddress: string;
62+
fromAddress: string;
63+
}
64+
65+
/** Staking withdraw parameters */
66+
export interface StakingWithdrawParams {
67+
type: "StakingWithdraw";
68+
fromAddress: string;
69+
stakingAddress: string;
70+
amount: string;
71+
}
72+
73+
/** Staking delegate parameters */
74+
export interface StakingDelegateParams {
75+
type: "StakingDelegate";
76+
stakingAddress: string;
77+
fromAddress: string;
78+
validator: string;
79+
}
80+
81+
/** Staking authorize parameters */
82+
export interface StakingAuthorizeParams {
83+
type: "StakingAuthorize";
84+
stakingAddress: string;
85+
oldAuthorizeAddress: string;
86+
newAuthorizeAddress: string;
87+
authorizeType: "Staker" | "Withdrawer";
88+
}
89+
90+
/** Set compute unit limit parameters */
91+
export interface SetComputeUnitLimitParams {
92+
type: "SetComputeUnitLimit";
93+
units: number;
94+
}
95+
96+
/** Set priority fee parameters */
97+
export interface SetPriorityFeeParams {
98+
type: "SetPriorityFee";
99+
fee: bigint;
100+
}
101+
102+
/** Token transfer parameters */
103+
export interface TokenTransferParams {
104+
type: "TokenTransfer";
105+
fromAddress: string;
106+
toAddress: string;
107+
amount: string;
108+
sourceAddress: string;
109+
tokenAddress?: string;
110+
}
111+
112+
/** Create associated token account parameters */
113+
export interface CreateAtaParams {
114+
type: "CreateAssociatedTokenAccount";
115+
mintAddress: string;
116+
ataAddress: string;
117+
ownerAddress: string;
118+
payerAddress: string;
119+
}
120+
121+
/** Close associated token account parameters */
122+
export interface CloseAtaParams {
123+
type: "CloseAssociatedTokenAccount";
124+
accountAddress: string;
125+
destinationAddress: string;
126+
authorityAddress: string;
127+
}
128+
129+
/** Memo parameters */
130+
export interface MemoParams {
131+
type: "Memo";
132+
memo: string;
133+
}
134+
135+
/** Account metadata for unknown instructions */
136+
export interface AccountMeta {
137+
pubkey: string;
138+
isSigner: boolean;
139+
isWritable: boolean;
140+
}
141+
142+
/** Unknown instruction parameters */
143+
export interface UnknownInstructionParams {
144+
type: "Unknown";
145+
programId: string;
146+
accounts: AccountMeta[];
147+
data: string; // base64 encoded
148+
}
149+
150+
/** Union of all instruction parameter types */
151+
export type InstructionParams =
152+
| TransferParams
153+
| CreateAccountParams
154+
| NonceAdvanceParams
155+
| CreateNonceAccountParams
156+
| StakingActivateParams
157+
| StakingDeactivateParams
158+
| StakingWithdrawParams
159+
| StakingDelegateParams
160+
| StakingAuthorizeParams
161+
| SetComputeUnitLimitParams
162+
| SetPriorityFeeParams
163+
| TokenTransferParams
164+
| CreateAtaParams
165+
| CloseAtaParams
166+
| MemoParams
167+
| UnknownInstructionParams;
168+
169+
// =============================================================================
170+
// ParsedTransaction - matching BitGoJS TxData
171+
// =============================================================================
172+
173+
/** Durable nonce information */
174+
export interface DurableNonce {
175+
walletNonceAddress: string;
176+
authWalletAddress: string;
177+
}
178+
179+
/**
180+
* A fully parsed Solana transaction with decoded instructions.
181+
*
182+
* This structure matches BitGoJS's TxData interface for seamless integration.
183+
*/
184+
export interface ParsedTransaction {
185+
/** The fee payer address (base58) */
186+
feePayer: string;
187+
188+
/** Number of required signatures */
189+
numSignatures: number;
190+
191+
/** The blockhash or nonce value (base58) */
192+
nonce: string;
193+
194+
/** If this is a durable nonce transaction, contains the nonce info */
195+
durableNonce?: DurableNonce;
196+
197+
/** All decoded instructions with semantic types */
198+
instructionsData: InstructionParams[];
199+
200+
/** All signatures (base64 encoded) */
201+
signatures: string[];
202+
203+
/** All account keys (base58 strings) */
204+
accountKeys: string[];
205+
}
206+
207+
// =============================================================================
208+
// parseTransaction function
209+
// =============================================================================
210+
211+
/** Raw instruction from WASM before post-processing */
212+
interface RawInstruction {
213+
type: string;
214+
fee?: string; // Fee comes as string from WASM
215+
[key: string]: unknown;
216+
}
217+
218+
/** Raw parsed transaction from WASM before post-processing */
219+
interface RawParsedTransaction {
220+
feePayer: string;
221+
numSignatures: number;
222+
nonce: string;
223+
durableNonce?: DurableNonce;
224+
instructionsData: RawInstruction[];
225+
signatures: string[];
226+
accountKeys: string[];
227+
}
228+
229+
/**
230+
* Parse a serialized Solana transaction into structured data.
231+
*
232+
* This is the main entry point for transaction parsing. It deserializes the
233+
* transaction bytes and decodes all instructions into semantic types.
234+
*
235+
* Note: This returns the raw parsed data including NonceAdvance instructions.
236+
* Consumers (like BitGoJS) may choose to filter NonceAdvance from instructionsData
237+
* since that info is also available in durableNonce.
238+
*
239+
* @param bytes - The raw transaction bytes (wire format)
240+
* @returns A ParsedTransaction with all instructions decoded
241+
* @throws Error if the transaction cannot be parsed
242+
*
243+
* @example
244+
* ```typescript
245+
* import { parseTransaction } from '@bitgo/wasm-solana';
246+
*
247+
* const txBytes = Buffer.from(base64EncodedTx, 'base64');
248+
* const parsed = parseTransaction(txBytes);
249+
*
250+
* console.log(parsed.feePayer);
251+
* for (const instr of parsed.instructionsData) {
252+
* if (instr.type === 'Transfer') {
253+
* console.log(`Transfer ${instr.amount} from ${instr.fromAddress} to ${instr.toAddress}`);
254+
* }
255+
* }
256+
* ```
257+
*/
258+
export function parseTransaction(bytes: Uint8Array): ParsedTransaction {
259+
const raw = ParserNamespace.parse_transaction(bytes) as RawParsedTransaction;
260+
261+
// Post-process instructions:
262+
// Convert SetPriorityFee.fee from string to BigInt
263+
const instructionsData = raw.instructionsData.map((instr): InstructionParams => {
264+
if (instr.type === "SetPriorityFee" && typeof instr.fee === "string") {
265+
return {
266+
type: "SetPriorityFee",
267+
fee: BigInt(instr.fee),
268+
};
269+
}
270+
return instr as unknown as InstructionParams;
271+
});
272+
273+
return {
274+
...raw,
275+
instructionsData,
276+
};
277+
}

0 commit comments

Comments
 (0)