Skip to content

Commit dd88831

Browse files
authored
Merge pull request #100 from BitGo/BTC-2929
feat(wasm-solana): add transaction parsing with BitGoJS compatibility
2 parents 64beb9b + ab5b4be commit dd88831

21 files changed

Lines changed: 5335 additions & 260 deletions

packages/wasm-solana/Cargo.lock

Lines changed: 2566 additions & 237 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: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ name = "wasm-solana"
33
version = "0.1.0"
44
edition = "2021"
55

6+
[package.metadata.wasm-pack.profile.release]
7+
wasm-opt = false
8+
69
[lib]
710
crate-type = ["cdylib", "rlib"]
811

@@ -12,17 +15,26 @@ all = "warn"
1215
[dependencies]
1316
wasm-bindgen = "0.2"
1417
js-sys = "0.3"
18+
serde = { version = "1.0", features = ["derive"] }
19+
serde_json = "1.0"
1520
# Solana SDK crates
1621
solana-pubkey = { version = "2.0", features = ["curve25519"] }
1722
solana-keypair = "2.0"
1823
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"] }
24+
solana-transaction = { version = "3.0", features = ["serde", "bincode"] }
25+
# Instruction decoder interfaces (official Solana crates)
26+
solana-system-interface = { version = "2.0", features = ["bincode"] }
27+
solana-stake-interface = { version = "2.0", features = ["bincode"] }
28+
solana-compute-budget-interface = { version = "2.0", features = ["borsh"] }
29+
# Serialization
30+
bincode = "1.3"
31+
borsh = "1.5"
32+
base64 = "0.22"
33+
serde-wasm-bindgen = "0.6"
34+
spl-stake-pool = { version = "2.0.3", features = ["no-entrypoint"] }
2135

2236
[dev-dependencies]
2337
wasm-bindgen-test = "0.3"
24-
serde = { version = "1.0", features = ["derive"] }
25-
serde_json = "1.0"
2638
hex = "0.4"
2739

2840
[profile.release]
389 KB
Binary file not shown.

packages/wasm-solana/js/index.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,41 @@ 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+
NonceInitializeParams,
31+
StakeInitializeParams,
32+
StakingActivateParams,
33+
StakingDeactivateParams,
34+
StakingWithdrawParams,
35+
StakingDelegateParams,
36+
StakingAuthorizeParams,
37+
SetComputeUnitLimitParams,
38+
SetPriorityFeeParams,
39+
TokenTransferParams,
40+
CreateAtaParams,
41+
CloseAtaParams,
42+
MemoParams,
43+
StakePoolDepositSolParams,
44+
StakePoolWithdrawStakeParams,
45+
UnknownInstructionParams,
46+
} from "./parser.js";

packages/wasm-solana/js/parser.ts

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

0 commit comments

Comments
 (0)