Skip to content

Commit b5cafec

Browse files
committed
feat(wasm-solana): add transaction parsing with BitGoJS compatibility
Add WASM bindings for Solana transaction parsing and inspection: - Transaction.fromBase64() / fromBytes() for deserialization - Access to fee payer, recent blockhash, account keys - Instruction decoding with programId, accounts, and data - AccountMeta with isSigner/isWritable flags - Signature access by index (base58 or bytes) - Signable payload extraction for verification Add parseTransaction() for semantic instruction decoding: - Decodes System Program: Transfer, CreateAccount, NonceAdvance - Decodes Stake Program: Delegate, Deactivate, Withdraw, Authorize - Decodes ComputeBudget: SetComputeUnitLimit, SetPriorityFee - Decodes Token Program: Transfer, TransferChecked, CloseAccount - Decodes ATA Program: CreateAssociatedTokenAccount - Decodes Memo Program - Output format matches BitGoJS TxData interface BitGoJS compatibility tests verify identical output for: - Transfer with memo and durable nonce - Multi-transfer transactions (6 recipients) - Staking activate transactions - Token transfer transactions - Simple unsigned transfers Uses official Solana crates exclusively: - solana-transaction for Transaction type - solana-system-interface for SystemInstruction - solana-stake-interface for StakeInstruction - solana-compute-budget-interface for ComputeBudgetInstruction Removed ed25519-dalek dependency (-44KB WASM, -36KB gzipped). Replaces @solana/web3.js Transaction.from() in BitGoJS. Ticket: BTC-2929
1 parent 739b7e1 commit b5cafec

18 files changed

Lines changed: 2790 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: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
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+
}
56+
57+
/** Staking deactivate parameters */
58+
export interface StakingDeactivateParams {
59+
type: "StakingDeactivate";
60+
stakingAddress: string;
61+
fromAddress: string;
62+
}
63+
64+
/** Staking withdraw parameters */
65+
export interface StakingWithdrawParams {
66+
type: "StakingWithdraw";
67+
fromAddress: string;
68+
stakingAddress: string;
69+
amount: string;
70+
}
71+
72+
/** Staking delegate parameters */
73+
export interface StakingDelegateParams {
74+
type: "StakingDelegate";
75+
stakingAddress: string;
76+
fromAddress: string;
77+
validator: string;
78+
}
79+
80+
/** Staking authorize parameters */
81+
export interface StakingAuthorizeParams {
82+
type: "StakingAuthorize";
83+
stakingAddress: string;
84+
oldAuthorizeAddress: string;
85+
newAuthorizeAddress: string;
86+
authorizeType: "Staker" | "Withdrawer";
87+
}
88+
89+
/** Set compute unit limit parameters */
90+
export interface SetComputeUnitLimitParams {
91+
type: "SetComputeUnitLimit";
92+
units: number;
93+
}
94+
95+
/** Set priority fee parameters */
96+
export interface SetPriorityFeeParams {
97+
type: "SetPriorityFee";
98+
fee: number;
99+
}
100+
101+
/** Token transfer parameters */
102+
export interface TokenTransferParams {
103+
type: "TokenTransfer";
104+
fromAddress: string;
105+
toAddress: string;
106+
amount: string;
107+
sourceAddress: string;
108+
tokenAddress?: string;
109+
}
110+
111+
/** Create associated token account parameters */
112+
export interface CreateAtaParams {
113+
type: "CreateAssociatedTokenAccount";
114+
mintAddress: string;
115+
ataAddress: string;
116+
ownerAddress: string;
117+
payerAddress: string;
118+
}
119+
120+
/** Close associated token account parameters */
121+
export interface CloseAtaParams {
122+
type: "CloseAssociatedTokenAccount";
123+
accountAddress: string;
124+
destinationAddress: string;
125+
authorityAddress: string;
126+
}
127+
128+
/** Memo parameters */
129+
export interface MemoParams {
130+
type: "Memo";
131+
memo: string;
132+
}
133+
134+
/** Account metadata for unknown instructions */
135+
export interface AccountMeta {
136+
pubkey: string;
137+
isSigner: boolean;
138+
isWritable: boolean;
139+
}
140+
141+
/** Unknown instruction parameters */
142+
export interface UnknownInstructionParams {
143+
type: "Unknown";
144+
programId: string;
145+
accounts: AccountMeta[];
146+
data: string; // base64 encoded
147+
}
148+
149+
/** Union of all instruction parameter types */
150+
export type InstructionParams =
151+
| TransferParams
152+
| CreateAccountParams
153+
| NonceAdvanceParams
154+
| CreateNonceAccountParams
155+
| StakingActivateParams
156+
| StakingDeactivateParams
157+
| StakingWithdrawParams
158+
| StakingDelegateParams
159+
| StakingAuthorizeParams
160+
| SetComputeUnitLimitParams
161+
| SetPriorityFeeParams
162+
| TokenTransferParams
163+
| CreateAtaParams
164+
| CloseAtaParams
165+
| MemoParams
166+
| UnknownInstructionParams;
167+
168+
// =============================================================================
169+
// ParsedTransaction - matching BitGoJS TxData
170+
// =============================================================================
171+
172+
/** Durable nonce information */
173+
export interface DurableNonce {
174+
walletNonceAddress: string;
175+
authWalletAddress: string;
176+
}
177+
178+
/**
179+
* A fully parsed Solana transaction with decoded instructions.
180+
*
181+
* This structure matches BitGoJS's TxData interface for seamless integration.
182+
*/
183+
export interface ParsedTransaction {
184+
/** The fee payer address (base58) */
185+
feePayer: string;
186+
187+
/** Number of required signatures */
188+
numSignatures: number;
189+
190+
/** The blockhash or nonce value (base58) */
191+
nonce: string;
192+
193+
/** If this is a durable nonce transaction, contains the nonce info */
194+
durableNonce?: DurableNonce;
195+
196+
/** All decoded instructions with semantic types */
197+
instructionsData: InstructionParams[];
198+
199+
/** All signatures (base64 encoded) */
200+
signatures: string[];
201+
202+
/** All account keys (base58 strings) */
203+
accountKeys: string[];
204+
}
205+
206+
// =============================================================================
207+
// parseTransaction function
208+
// =============================================================================
209+
210+
/**
211+
* Parse a serialized Solana transaction into structured data.
212+
*
213+
* This is the main entry point for transaction parsing. It deserializes the
214+
* transaction bytes and decodes all instructions into semantic types.
215+
*
216+
* @param bytes - The raw transaction bytes (wire format)
217+
* @returns A ParsedTransaction with all instructions decoded
218+
* @throws Error if the transaction cannot be parsed
219+
*
220+
* @example
221+
* ```typescript
222+
* import { parseTransaction } from '@bitgo/wasm-solana';
223+
*
224+
* const txBytes = Buffer.from(base64EncodedTx, 'base64');
225+
* const parsed = parseTransaction(txBytes);
226+
*
227+
* console.log(parsed.feePayer);
228+
* for (const instr of parsed.instructionsData) {
229+
* if (instr.type === 'Transfer') {
230+
* console.log(`Transfer ${instr.amount} from ${instr.fromAddress} to ${instr.toAddress}`);
231+
* }
232+
* }
233+
* ```
234+
*/
235+
export function parseTransaction(bytes: Uint8Array): ParsedTransaction {
236+
return ParserNamespace.parse_transaction(bytes) as ParsedTransaction;
237+
}

0 commit comments

Comments
 (0)