Skip to content

Conversation

@lcovar
Copy link
Contributor

@lcovar lcovar commented Jan 14, 2026

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 (using official solana-message methods)
  • Signature access by index (base58 or bytes)
  • Signable payload extraction for verification

Uses official Solana crates:

  • solana-transaction for Transaction type and bincode serialization
  • solana-message is_signer()/is_maybe_writable() for account flags

Replaces @solana/web3.js Transaction.from() in BitGoJS.

Ticket: BTC-2929

@lcovar lcovar requested a review from a team as a code owner January 14, 2026 23:47
@lcovar lcovar marked this pull request as draft January 15, 2026 00:30
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

Uses official Solana crates exclusively:
- solana-transaction for Transaction type and bincode serialization
- solana-message is_signer()/is_maybe_writable() for account flags
- solana-keypair Keypair::new_from_array() for 32-byte seed support

Removed ed25519-dalek dependency (-44KB WASM, -36KB gzipped).

Replaces @solana/web3.js Transaction.from() in BitGoJS.

Ticket: BTC-2929
@lcovar lcovar marked this pull request as ready for review January 15, 2026 00:58
Copy link
Contributor

@OttoAllmendinger OttoAllmendinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's reduce the API surface a little bit (unless we really need compatibility)

if we really need these funcs for compatibility, a lot of them can be implemented in the TS layer and we can keep the rust/wasm layer smaller

Comment on lines +103 to +109
/**
* Serialize the transaction to base64
* @returns The base64-encoded transaction
*/
toBase64(): string {
return this._wasm.to_base64();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the JS world can take care of the base conversions from the Uint8Array

we risk having noisy fromBase64/fromHex/toBase64/toHex on a bunch of different types otherwise

also we have a lot of code that does needless round-tripping in the style of fromBase64(foo.toBase64))

base58 is a bit of an exception because there is no JS builtin

Comment on lines +111 to +117
/**
* Get all account keys as an array of base58 strings
* @returns Array of account public keys
*/
accountKeys(): string[] {
return Array.from(this._wasm.account_keys()) as string[];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably use the key types we already have here

/**
* Get a signature at the given index as a base58 string
* @param index - The signature index
* @returns The signature as a base58 string, or null if index is out of bounds
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are signatures base58? maybe we can do Buffer here

* @param index - The signature index
* @returns The signature bytes, or null if index is out of bounds
*/
signatureBytesAt(index: number): Uint8Array | null {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be signatureAt(index) and the other signatureBase58At(index) if we really need it

Comment on lines +146 to +154
/**
* Get an instruction at the given index
* @param index - The instruction index
* @returns The instruction, or null if index is out of bounds
*/
instructionAt(index: number): Instruction | null {
const instr = this._wasm.instruction_at(index);
return (instr as Instruction) ?? null;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need this if we already have instructions?

Comment on lines +36 to +45
/**
* Deserialize a transaction from a base64-encoded string
* This is the format used by @solana/web3.js Transaction.serialize()
* @param base64 - The base64-encoded transaction
* @returns A Transaction instance
*/
static fromBase64(base64: string): Transaction {
const wasm = WasmTransaction.from_base64(base64);
return new Transaction(wasm);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see base64 comment

Comment on lines +72 to +77
/**
* Get the number of instructions in the transaction
*/
get numInstructions(): number {
return this._wasm.num_instructions;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already have instructions() to get the full array

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants