Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 35 additions & 17 deletions packages/wasm-solana/js/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
*/

import { BuilderNamespace } from "./wasm/wasm_solana.js";
import { Transaction } from "./transaction.js";
import { VersionedTransaction } from "./versioned.js";

// =============================================================================
// Nonce Types
Expand Down Expand Up @@ -463,48 +465,58 @@ export interface TransactionIntent {
/**
* Build a Solana transaction from a high-level intent.
*
* This function takes a declarative TransactionIntent and produces serialized
* transaction bytes that can be signed and submitted to the network.
* This function takes a declarative TransactionIntent and produces a Transaction
* object that can be inspected, signed, and serialized.
*
* The returned transaction is unsigned - signatures should be added before
* broadcasting.
* The returned transaction is unsigned - signatures should be added via
* `addSignature()` before serializing with `toBytes()` and broadcasting.
*
* @param intent - The transaction intent describing what to build
* @returns Serialized unsigned transaction bytes (Uint8Array)
* @returns A Transaction object that can be inspected, signed, and serialized
* @throws Error if the intent cannot be built (e.g., invalid addresses)
*
* @example
* ```typescript
* import { buildTransaction } from '@bitgo/wasm-solana';
*
* // Build a simple SOL transfer
* const txBytes = buildTransaction({
* const tx = buildTransaction({
* feePayer: sender,
* nonce: { type: 'blockhash', value: blockhash },
* instructions: [
* { type: 'transfer', from: sender, to: recipient, lamports: '1000000' }
* { type: 'transfer', from: sender, to: recipient, lamports: 1000000n }
* ]
* });
*
* // The returned bytes can be signed and broadcast
* // Inspect the transaction
* console.log(tx.feePayer);
* console.log(tx.recentBlockhash);
*
* // Get the signable payload for signing
* const payload = tx.signablePayload();
*
* // Add signature and serialize
* tx.addSignature(signerPubkey, signature);
* const txBytes = tx.toBytes();
* ```
*
* @example
* ```typescript
* // Build with durable nonce and priority fee
* const txBytes = buildTransaction({
* const tx = buildTransaction({
* feePayer: sender,
* nonce: { type: 'durable', address: nonceAccount, authority: sender, value: nonceValue },
* instructions: [
* { type: 'computeBudget', unitLimit: 200000, unitPrice: 5000 },
* { type: 'transfer', from: sender, to: recipient, lamports: '1000000' },
* { type: 'transfer', from: sender, to: recipient, lamports: 1000000n },
* { type: 'memo', message: 'BitGo transfer' }
* ]
* });
* ```
*/
export function buildTransaction(intent: TransactionIntent): Uint8Array {
return BuilderNamespace.build_transaction(intent);
export function buildTransaction(intent: TransactionIntent): Transaction {
const wasm = BuilderNamespace.build_transaction(intent);
return Transaction.fromWasm(wasm);
}

// =============================================================================
Expand Down Expand Up @@ -560,17 +572,17 @@ export interface RawVersionedTransactionData {
*
* This function is used for the `fromVersionedTransactionData()` path where we already
* have pre-compiled versioned data (indexes + ALT refs). No instruction compilation
* is needed - we just serialize the raw structure to bytes.
* is needed - we just serialize the raw structure.
*
* @param data - Raw versioned transaction data
* @returns Serialized unsigned versioned transaction bytes (Uint8Array)
* @returns A VersionedTransaction object that can be inspected, signed, and serialized
* @throws Error if the data is invalid
*
* @example
* ```typescript
* import { buildFromVersionedData } from '@bitgo/wasm-solana';
*
* const txBytes = buildFromVersionedData({
* const tx = buildFromVersionedData({
* staticAccountKeys: ['pubkey1', 'pubkey2', ...],
* addressLookupTables: [
* { accountKey: 'altPubkey', writableIndexes: [0, 1], readonlyIndexes: [2] }
Expand All @@ -585,8 +597,14 @@ export interface RawVersionedTransactionData {
* },
* recentBlockhash: 'blockhash'
* });
*
* // Inspect, sign, and serialize
* console.log(tx.feePayer);
* tx.addSignature(signerPubkey, signature);
* const txBytes = tx.toBytes();
* ```
*/
export function buildFromVersionedData(data: RawVersionedTransactionData): Uint8Array {
return BuilderNamespace.build_from_versioned_data(data);
export function buildFromVersionedData(data: RawVersionedTransactionData): VersionedTransaction {
const wasm = BuilderNamespace.build_from_versioned_data(data);
return VersionedTransaction.fromWasm(wasm);
}
1 change: 1 addition & 0 deletions packages/wasm-solana/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export {
// Type exports
export type { AccountMeta, Instruction } from "./transaction.js";
export type {
TransactionInput,
ParsedTransaction,
DurableNonce,
InstructionParams,
Expand Down
24 changes: 19 additions & 5 deletions packages/wasm-solana/js/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
*/

import { ParserNamespace } from "./wasm/wasm_solana.js";
import type { Transaction } from "./transaction.js";
import type { VersionedTransaction } from "./versioned.js";

/**
* Input type for parseTransaction - accepts bytes or Transaction objects.
*/
export type TransactionInput = Uint8Array | Transaction | VersionedTransaction;

// =============================================================================
// Instruction Types - matching BitGoJS InstructionParams
Expand Down Expand Up @@ -273,10 +280,10 @@ export interface ParsedTransaction {
// =============================================================================

/**
* Parse a serialized Solana transaction into structured data.
* Parse a Solana transaction into structured data.
*
* This is the main entry point for transaction parsing. It deserializes the
* transaction bytes and decodes all instructions into semantic types.
* transaction and decodes all instructions into semantic types.
*
* All monetary amounts (amount, fee, lamports, poolTokens) are returned as bigint
* directly from WASM - no post-processing needed.
Expand All @@ -285,17 +292,22 @@ export interface ParsedTransaction {
* Consumers (like BitGoJS) may choose to filter NonceAdvance from instructionsData
* since that info is also available in durableNonce.
*
* @param bytes - The raw transaction bytes (wire format)
* @param input - Raw transaction bytes, Transaction, or VersionedTransaction
* @returns A ParsedTransaction with all instructions decoded
* @throws Error if the transaction cannot be parsed
*
* @example
* ```typescript
* import { parseTransaction } from '@bitgo/wasm-solana';
* import { parseTransaction, buildTransaction, Transaction } from '@bitgo/wasm-solana';
*
* // From bytes
* const txBytes = Buffer.from(base64EncodedTx, 'base64');
* const parsed = parseTransaction(txBytes);
*
* // Directly from a Transaction object (no roundtrip through bytes)
* const tx = buildTransaction(intent);
* const parsed = parseTransaction(tx);
*
* console.log(parsed.feePayer);
* for (const instr of parsed.instructionsData) {
* if (instr.type === 'Transfer') {
Expand All @@ -304,6 +316,8 @@ export interface ParsedTransaction {
* }
* ```
*/
export function parseTransaction(bytes: Uint8Array): ParsedTransaction {
export function parseTransaction(input: TransactionInput): ParsedTransaction {
// If input is a Transaction or VersionedTransaction, extract bytes
const bytes = input instanceof Uint8Array ? input : input.toBytes();
return ParserNamespace.parse_transaction(bytes) as ParsedTransaction;
}
8 changes: 8 additions & 0 deletions packages/wasm-solana/js/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ export class Transaction {
return new Transaction(wasm);
}

/**
* Create a Transaction from a WasmTransaction instance.
* @internal Used by builder functions
*/
static fromWasm(wasm: WasmTransaction): Transaction {
return new Transaction(wasm);
}

/**
* Get the fee payer address as a base58 string
* Returns null if there are no account keys (shouldn't happen for valid transactions)
Expand Down
15 changes: 11 additions & 4 deletions packages/wasm-solana/js/versioned.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ export class VersionedTransaction {
return VersionedTransaction.fromBytes(bytes);
}

/**
* Create a VersionedTransaction from a WasmVersionedTransaction instance.
* @internal Used by builder functions
*/
static fromWasm(wasm: WasmVersionedTransaction): VersionedTransaction {
return new VersionedTransaction(wasm);
}

/**
* Create a versioned transaction from raw MessageV0 data.
*
Expand Down Expand Up @@ -120,10 +128,9 @@ export class VersionedTransaction {
* ```
*/
static fromVersionedData(data: RawVersionedTransactionData): VersionedTransaction {
// Build the transaction bytes using WASM
const bytes = BuilderNamespace.build_from_versioned_data(data);
// Parse the bytes to create a VersionedTransaction
return VersionedTransaction.fromBytes(bytes);
// Build the transaction using WASM and wrap in TypeScript class
const wasm = BuilderNamespace.build_from_versioned_data(data);
return VersionedTransaction.fromWasm(wasm);
}

/**
Expand Down
33 changes: 21 additions & 12 deletions packages/wasm-solana/src/wasm/builder.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
//! WASM binding for transaction building.
//!
//! Exposes transaction building functions:
//! - `buildTransaction` - Creates transactions from a high-level intent structure
//! - `buildFromVersionedData` - Creates versioned transactions from raw MessageV0 data
//! - `buildTransaction` - Creates a Transaction from a high-level intent structure
//! - `buildFromVersionedData` - Creates a VersionedTransaction from raw MessageV0 data

use crate::builder;
use crate::wasm::transaction::{WasmTransaction, WasmVersionedTransaction};
use wasm_bindgen::prelude::*;

/// Namespace for transaction building operations.
Expand Down Expand Up @@ -46,22 +47,26 @@ impl BuilderNamespace {
///
/// # Returns
///
/// Serialized unsigned transaction bytes (Uint8Array).
/// A `Transaction` object that can be inspected, signed, and serialized.
/// The transaction will have empty signature placeholders that can be
/// filled in later by signing.
/// filled in later by signing via `addSignature()`.
///
/// @param intent - The transaction intent as a JSON object
/// @returns Serialized transaction bytes
/// @returns Transaction object
#[wasm_bindgen]
pub fn build_transaction(intent: JsValue) -> Result<Vec<u8>, JsValue> {
pub fn build_transaction(intent: JsValue) -> Result<WasmTransaction, JsValue> {
// Deserialize the intent from JavaScript
let intent: builder::TransactionIntent =
serde_wasm_bindgen::from_value(intent).map_err(|e| {
JsValue::from_str(&format!("Failed to parse transaction intent: {}", e))
})?;

// Build the transaction
builder::build_transaction(intent).map_err(|e| JsValue::from_str(&e.to_string()))
// Build the transaction bytes
let bytes =
builder::build_transaction(intent).map_err(|e| JsValue::from_str(&e.to_string()))?;

// Wrap in WasmTransaction for rich API access
WasmTransaction::from_bytes(&bytes).map_err(|e| JsValue::from_str(&e.to_string()))
}

/// Build a versioned transaction directly from raw MessageV0 data.
Expand Down Expand Up @@ -91,9 +96,9 @@ impl BuilderNamespace {
/// ```
///
/// @param data - Raw versioned transaction data as a JSON object
/// @returns Serialized versioned transaction bytes (unsigned)
/// @returns VersionedTransaction object
#[wasm_bindgen]
pub fn build_from_versioned_data(data: JsValue) -> Result<Vec<u8>, JsValue> {
pub fn build_from_versioned_data(data: JsValue) -> Result<WasmVersionedTransaction, JsValue> {
// Deserialize the raw versioned data from JavaScript
let data: builder::RawVersionedTransactionData = serde_wasm_bindgen::from_value(data)
.map_err(|e| {
Expand All @@ -103,7 +108,11 @@ impl BuilderNamespace {
))
})?;

// Build the versioned transaction
builder::build_from_raw_versioned_data(&data).map_err(|e| JsValue::from_str(&e.to_string()))
// Build the versioned transaction bytes
let bytes = builder::build_from_raw_versioned_data(&data)
.map_err(|e| JsValue::from_str(&e.to_string()))?;

// Wrap in WasmVersionedTransaction for rich API access
WasmVersionedTransaction::from_bytes(&bytes).map_err(|e| JsValue::from_str(&e.to_string()))
}
}
Loading