Skip to content

Commit ec5d77f

Browse files
committed
feat(wasm-solana): return Transaction types from builder functions
Changed buildTransaction() and buildFromVersionedData() to return Transaction and VersionedTransaction objects instead of raw bytes. This allows callers to inspect the transaction before serializing, which is more ergonomic and matches the existing Transaction API. Callers that need bytes can simply call .toBytes() on the result.
1 parent 0c22062 commit ec5d77f

5 files changed

Lines changed: 117 additions & 55 deletions

File tree

packages/wasm-solana/js/builder.ts

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
*/
77

88
import { BuilderNamespace } from "./wasm/wasm_solana.js";
9+
import { Transaction } from "./transaction.js";
10+
import { VersionedTransaction } from "./versioned.js";
911

1012
// =============================================================================
1113
// Nonce Types
@@ -463,48 +465,58 @@ export interface TransactionIntent {
463465
/**
464466
* Build a Solana transaction from a high-level intent.
465467
*
466-
* This function takes a declarative TransactionIntent and produces serialized
467-
* transaction bytes that can be signed and submitted to the network.
468+
* This function takes a declarative TransactionIntent and produces a Transaction
469+
* object that can be inspected, signed, and serialized.
468470
*
469-
* The returned transaction is unsigned - signatures should be added before
470-
* broadcasting.
471+
* The returned transaction is unsigned - signatures should be added via
472+
* `addSignature()` before serializing with `toBytes()` and broadcasting.
471473
*
472474
* @param intent - The transaction intent describing what to build
473-
* @returns Serialized unsigned transaction bytes (Uint8Array)
475+
* @returns A Transaction object that can be inspected, signed, and serialized
474476
* @throws Error if the intent cannot be built (e.g., invalid addresses)
475477
*
476478
* @example
477479
* ```typescript
478480
* import { buildTransaction } from '@bitgo/wasm-solana';
479481
*
480482
* // Build a simple SOL transfer
481-
* const txBytes = buildTransaction({
483+
* const tx = buildTransaction({
482484
* feePayer: sender,
483485
* nonce: { type: 'blockhash', value: blockhash },
484486
* instructions: [
485-
* { type: 'transfer', from: sender, to: recipient, lamports: '1000000' }
487+
* { type: 'transfer', from: sender, to: recipient, lamports: 1000000n }
486488
* ]
487489
* });
488490
*
489-
* // The returned bytes can be signed and broadcast
491+
* // Inspect the transaction
492+
* console.log(tx.feePayer);
493+
* console.log(tx.recentBlockhash);
494+
*
495+
* // Get the signable payload for signing
496+
* const payload = tx.signablePayload();
497+
*
498+
* // Add signature and serialize
499+
* tx.addSignature(signerPubkey, signature);
500+
* const txBytes = tx.toBytes();
490501
* ```
491502
*
492503
* @example
493504
* ```typescript
494505
* // Build with durable nonce and priority fee
495-
* const txBytes = buildTransaction({
506+
* const tx = buildTransaction({
496507
* feePayer: sender,
497508
* nonce: { type: 'durable', address: nonceAccount, authority: sender, value: nonceValue },
498509
* instructions: [
499510
* { type: 'computeBudget', unitLimit: 200000, unitPrice: 5000 },
500-
* { type: 'transfer', from: sender, to: recipient, lamports: '1000000' },
511+
* { type: 'transfer', from: sender, to: recipient, lamports: 1000000n },
501512
* { type: 'memo', message: 'BitGo transfer' }
502513
* ]
503514
* });
504515
* ```
505516
*/
506-
export function buildTransaction(intent: TransactionIntent): Uint8Array {
507-
return BuilderNamespace.build_transaction(intent);
517+
export function buildTransaction(intent: TransactionIntent): Transaction {
518+
const wasm = BuilderNamespace.build_transaction(intent);
519+
return Transaction.fromWasm(wasm);
508520
}
509521

510522
// =============================================================================
@@ -560,17 +572,17 @@ export interface RawVersionedTransactionData {
560572
*
561573
* This function is used for the `fromVersionedTransactionData()` path where we already
562574
* have pre-compiled versioned data (indexes + ALT refs). No instruction compilation
563-
* is needed - we just serialize the raw structure to bytes.
575+
* is needed - we just serialize the raw structure.
564576
*
565577
* @param data - Raw versioned transaction data
566-
* @returns Serialized unsigned versioned transaction bytes (Uint8Array)
578+
* @returns A VersionedTransaction object that can be inspected, signed, and serialized
567579
* @throws Error if the data is invalid
568580
*
569581
* @example
570582
* ```typescript
571583
* import { buildFromVersionedData } from '@bitgo/wasm-solana';
572584
*
573-
* const txBytes = buildFromVersionedData({
585+
* const tx = buildFromVersionedData({
574586
* staticAccountKeys: ['pubkey1', 'pubkey2', ...],
575587
* addressLookupTables: [
576588
* { accountKey: 'altPubkey', writableIndexes: [0, 1], readonlyIndexes: [2] }
@@ -585,8 +597,14 @@ export interface RawVersionedTransactionData {
585597
* },
586598
* recentBlockhash: 'blockhash'
587599
* });
600+
*
601+
* // Inspect, sign, and serialize
602+
* console.log(tx.feePayer);
603+
* tx.addSignature(signerPubkey, signature);
604+
* const txBytes = tx.toBytes();
588605
* ```
589606
*/
590-
export function buildFromVersionedData(data: RawVersionedTransactionData): Uint8Array {
591-
return BuilderNamespace.build_from_versioned_data(data);
607+
export function buildFromVersionedData(data: RawVersionedTransactionData): VersionedTransaction {
608+
const wasm = BuilderNamespace.build_from_versioned_data(data);
609+
return VersionedTransaction.fromWasm(wasm);
592610
}

packages/wasm-solana/js/transaction.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ export class Transaction {
5757
return new Transaction(wasm);
5858
}
5959

60+
/**
61+
* Create a Transaction from a WasmTransaction instance.
62+
* @internal Used by builder functions
63+
*/
64+
static fromWasm(wasm: WasmTransaction): Transaction {
65+
return new Transaction(wasm);
66+
}
67+
6068
/**
6169
* Get the fee payer address as a base58 string
6270
* Returns null if there are no account keys (shouldn't happen for valid transactions)

packages/wasm-solana/js/versioned.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ export class VersionedTransaction {
9090
return VersionedTransaction.fromBytes(bytes);
9191
}
9292

93+
/**
94+
* Create a VersionedTransaction from a WasmVersionedTransaction instance.
95+
* @internal Used by builder functions
96+
*/
97+
static fromWasm(wasm: WasmVersionedTransaction): VersionedTransaction {
98+
return new VersionedTransaction(wasm);
99+
}
100+
93101
/**
94102
* Create a versioned transaction from raw MessageV0 data.
95103
*
@@ -120,10 +128,9 @@ export class VersionedTransaction {
120128
* ```
121129
*/
122130
static fromVersionedData(data: RawVersionedTransactionData): VersionedTransaction {
123-
// Build the transaction bytes using WASM
124-
const bytes = BuilderNamespace.build_from_versioned_data(data);
125-
// Parse the bytes to create a VersionedTransaction
126-
return VersionedTransaction.fromBytes(bytes);
131+
// Build the transaction using WASM and wrap in TypeScript class
132+
const wasm = BuilderNamespace.build_from_versioned_data(data);
133+
return VersionedTransaction.fromWasm(wasm);
127134
}
128135

129136
/**

packages/wasm-solana/src/wasm/builder.rs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
//! WASM binding for transaction building.
22
//!
33
//! Exposes transaction building functions:
4-
//! - `buildTransaction` - Creates transactions from a high-level intent structure
5-
//! - `buildFromVersionedData` - Creates versioned transactions from raw MessageV0 data
4+
//! - `buildTransaction` - Creates a Transaction from a high-level intent structure
5+
//! - `buildFromVersionedData` - Creates a VersionedTransaction from raw MessageV0 data
66
77
use crate::builder;
8+
use crate::wasm::transaction::{WasmTransaction, WasmVersionedTransaction};
89
use wasm_bindgen::prelude::*;
910

1011
/// Namespace for transaction building operations.
@@ -46,22 +47,26 @@ impl BuilderNamespace {
4647
///
4748
/// # Returns
4849
///
49-
/// Serialized unsigned transaction bytes (Uint8Array).
50+
/// A `Transaction` object that can be inspected, signed, and serialized.
5051
/// The transaction will have empty signature placeholders that can be
51-
/// filled in later by signing.
52+
/// filled in later by signing via `addSignature()`.
5253
///
5354
/// @param intent - The transaction intent as a JSON object
54-
/// @returns Serialized transaction bytes
55+
/// @returns Transaction object
5556
#[wasm_bindgen]
56-
pub fn build_transaction(intent: JsValue) -> Result<Vec<u8>, JsValue> {
57+
pub fn build_transaction(intent: JsValue) -> Result<WasmTransaction, JsValue> {
5758
// Deserialize the intent from JavaScript
5859
let intent: builder::TransactionIntent =
5960
serde_wasm_bindgen::from_value(intent).map_err(|e| {
6061
JsValue::from_str(&format!("Failed to parse transaction intent: {}", e))
6162
})?;
6263

63-
// Build the transaction
64-
builder::build_transaction(intent).map_err(|e| JsValue::from_str(&e.to_string()))
64+
// Build the transaction bytes
65+
let bytes =
66+
builder::build_transaction(intent).map_err(|e| JsValue::from_str(&e.to_string()))?;
67+
68+
// Wrap in WasmTransaction for rich API access
69+
WasmTransaction::from_bytes(&bytes).map_err(|e| JsValue::from_str(&e.to_string()))
6570
}
6671

6772
/// Build a versioned transaction directly from raw MessageV0 data.
@@ -91,9 +96,9 @@ impl BuilderNamespace {
9196
/// ```
9297
///
9398
/// @param data - Raw versioned transaction data as a JSON object
94-
/// @returns Serialized versioned transaction bytes (unsigned)
99+
/// @returns VersionedTransaction object
95100
#[wasm_bindgen]
96-
pub fn build_from_versioned_data(data: JsValue) -> Result<Vec<u8>, JsValue> {
101+
pub fn build_from_versioned_data(data: JsValue) -> Result<WasmVersionedTransaction, JsValue> {
97102
// Deserialize the raw versioned data from JavaScript
98103
let data: builder::RawVersionedTransactionData = serde_wasm_bindgen::from_value(data)
99104
.map_err(|e| {
@@ -103,7 +108,11 @@ impl BuilderNamespace {
103108
))
104109
})?;
105110

106-
// Build the versioned transaction
107-
builder::build_from_raw_versioned_data(&data).map_err(|e| JsValue::from_str(&e.to_string()))
111+
// Build the versioned transaction bytes
112+
let bytes = builder::build_from_raw_versioned_data(&data)
113+
.map_err(|e| JsValue::from_str(&e.to_string()))?;
114+
115+
// Wrap in WasmVersionedTransaction for rich API access
116+
WasmVersionedTransaction::from_bytes(&bytes).map_err(|e| JsValue::from_str(&e.to_string()))
108117
}
109118
}

0 commit comments

Comments
 (0)