Skip to content

Commit faf27b9

Browse files
committed
feat(wasm-solana): add fromVersionedData() for raw MessageV0 building
Adds VersionedTransaction.fromVersionedData() to build versioned transactions from raw MessageV0 data. This supports the fromVersionedTransactionData() path where we have pre-compiled versioned data (indexes + ALT refs). The method is on VersionedTransaction class since it builds MessageV0 format which requires WasmVersionedTransaction internally.
1 parent 827dd86 commit faf27b9

File tree

13 files changed

+571
-34
lines changed

13 files changed

+571
-34
lines changed
13.8 KB
Binary file not shown.

packages/wasm-solana/js/builder.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,3 +506,87 @@ export interface TransactionIntent {
506506
export function buildTransaction(intent: TransactionIntent): Uint8Array {
507507
return BuilderNamespace.build_transaction(intent);
508508
}
509+
510+
// =============================================================================
511+
// Raw Versioned Transaction Data Types (for fromVersionedTransactionData path)
512+
// =============================================================================
513+
514+
/**
515+
* A pre-compiled versioned instruction (uses indexes, not pubkeys).
516+
* This is the format used in MessageV0 transactions.
517+
*/
518+
export interface VersionedInstruction {
519+
/** Index into the account keys array for the program ID */
520+
programIdIndex: number;
521+
/** Indexes into the account keys array for instruction accounts */
522+
accountKeyIndexes: number[];
523+
/** Instruction data (base58 encoded) */
524+
data: string;
525+
}
526+
527+
/**
528+
* Message header for versioned transactions.
529+
* Describes the structure of the account keys array.
530+
*/
531+
export interface MessageHeader {
532+
/** Number of required signatures */
533+
numRequiredSignatures: number;
534+
/** Number of readonly signed accounts */
535+
numReadonlySignedAccounts: number;
536+
/** Number of readonly unsigned accounts */
537+
numReadonlyUnsignedAccounts: number;
538+
}
539+
540+
/**
541+
* Raw versioned transaction data for direct serialization.
542+
* This is used when we have pre-formed MessageV0 data that just needs to be serialized.
543+
* No instruction compilation is needed - just serialize the raw structure.
544+
*/
545+
export interface RawVersionedTransactionData {
546+
/** Static account keys (base58 encoded public keys) */
547+
staticAccountKeys: string[];
548+
/** Address lookup tables */
549+
addressLookupTables: AddressLookupTable[];
550+
/** Pre-compiled instructions with index-based account references */
551+
versionedInstructions: VersionedInstruction[];
552+
/** Message header */
553+
messageHeader: MessageHeader;
554+
/** Recent blockhash (base58) */
555+
recentBlockhash: string;
556+
}
557+
558+
/**
559+
* Build a versioned transaction directly from raw MessageV0 data.
560+
*
561+
* This function is used for the `fromVersionedTransactionData()` path where we already
562+
* have pre-compiled versioned data (indexes + ALT refs). No instruction compilation
563+
* is needed - we just serialize the raw structure to bytes.
564+
*
565+
* @param data - Raw versioned transaction data
566+
* @returns Serialized unsigned versioned transaction bytes (Uint8Array)
567+
* @throws Error if the data is invalid
568+
*
569+
* @example
570+
* ```typescript
571+
* import { buildFromVersionedData } from '@bitgo/wasm-solana';
572+
*
573+
* const txBytes = buildFromVersionedData({
574+
* staticAccountKeys: ['pubkey1', 'pubkey2', ...],
575+
* addressLookupTables: [
576+
* { accountKey: 'altPubkey', writableIndexes: [0, 1], readonlyIndexes: [2] }
577+
* ],
578+
* versionedInstructions: [
579+
* { programIdIndex: 0, accountKeyIndexes: [1, 2], data: 'base58EncodedData' }
580+
* ],
581+
* messageHeader: {
582+
* numRequiredSignatures: 1,
583+
* numReadonlySignedAccounts: 0,
584+
* numReadonlyUnsignedAccounts: 3
585+
* },
586+
* recentBlockhash: 'blockhash'
587+
* });
588+
* ```
589+
*/
590+
export function buildFromVersionedData(data: RawVersionedTransactionData): Uint8Array {
591+
return BuilderNamespace.build_from_versioned_data(data);
592+
}

packages/wasm-solana/js/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export type { AddressLookupTableData } from "./versioned.js";
2121

2222
// Top-level function exports
2323
export { parseTransaction } from "./parser.js";
24-
export { buildTransaction } from "./builder.js";
24+
export { buildTransaction, buildFromVersionedData } from "./builder.js";
2525

2626
// Program ID constants (from WASM)
2727
export {
@@ -35,6 +35,8 @@ export {
3535
stake_pool_program_id as stakePoolProgramId,
3636
stake_account_space as stakeAccountSpace,
3737
nonce_account_space as nonceAccountSpace,
38+
// Sysvar addresses
39+
sysvar_recent_blockhashes as sysvarRecentBlockhashes,
3840
// PDA derivation functions (eliminates @solana/web3.js dependency)
3941
get_associated_token_address as getAssociatedTokenAddress,
4042
find_withdraw_authority_program_address as findWithdrawAuthorityProgramAddress,
@@ -104,4 +106,8 @@ export type {
104106
// Custom Instruction
105107
CustomInstruction as BuilderCustomInstruction,
106108
CustomAccountMeta,
109+
// Raw Versioned Transaction Data (for fromVersionedTransactionData path)
110+
RawVersionedTransactionData,
111+
VersionedInstruction as BuilderVersionedInstruction,
112+
MessageHeader,
107113
} from "./builder.js";

packages/wasm-solana/js/versioned.ts

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
* transaction size by referencing accounts via lookup table indices.
77
*/
88

9-
import { WasmVersionedTransaction, is_versioned_transaction } from "./wasm/wasm_solana.js";
9+
import {
10+
WasmVersionedTransaction,
11+
is_versioned_transaction,
12+
BuilderNamespace,
13+
} from "./wasm/wasm_solana.js";
14+
import type { RawVersionedTransactionData } from "./builder.js";
1015

1116
/**
1217
* Address Lookup Table data extracted from versioned transactions.
@@ -85,6 +90,42 @@ export class VersionedTransaction {
8590
return VersionedTransaction.fromBytes(bytes);
8691
}
8792

93+
/**
94+
* Create a versioned transaction from raw MessageV0 data.
95+
*
96+
* This is used for the `fromVersionedTransactionData()` path where we have
97+
* pre-compiled versioned data (indexes + ALT refs). No instruction compilation
98+
* is needed - this just constructs the transaction from the raw structure.
99+
*
100+
* @param data - Raw versioned transaction data
101+
* @returns A VersionedTransaction instance
102+
*
103+
* @example
104+
* ```typescript
105+
* const tx = VersionedTransaction.fromVersionedData({
106+
* staticAccountKeys: ['pubkey1', 'pubkey2', ...],
107+
* addressLookupTables: [
108+
* { accountKey: 'altPubkey', writableIndexes: [0, 1], readonlyIndexes: [2] }
109+
* ],
110+
* versionedInstructions: [
111+
* { programIdIndex: 0, accountKeyIndexes: [1, 2], data: 'base58EncodedData' }
112+
* ],
113+
* messageHeader: {
114+
* numRequiredSignatures: 1,
115+
* numReadonlySignedAccounts: 0,
116+
* numReadonlyUnsignedAccounts: 3
117+
* },
118+
* recentBlockhash: 'blockhash'
119+
* });
120+
* ```
121+
*/
122+
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);
127+
}
128+
88129
/**
89130
* Check if this is a versioned transaction (MessageV0).
90131
*/
@@ -127,6 +168,16 @@ export class VersionedTransaction {
127168
return this.inner.signable_payload();
128169
}
129170

171+
/**
172+
* Serialize the message portion of the transaction.
173+
* Alias for signablePayload() - provides compatibility with @solana/web3.js API.
174+
* Returns a Buffer for compatibility with code expecting .toString('base64').
175+
* @returns The serialized message bytes as a Buffer
176+
*/
177+
serializeMessage(): Buffer {
178+
return Buffer.from(this.signablePayload());
179+
}
180+
130181
/**
131182
* Serialize the transaction to bytes.
132183
*/
@@ -163,9 +214,10 @@ export class VersionedTransaction {
163214
}
164215

165216
/**
166-
* Get all signatures.
217+
* Get all signatures as byte arrays.
218+
* Provides compatibility with @solana/web3.js Transaction.signatures API.
167219
*/
168-
signatures(): Uint8Array[] {
220+
get signatures(): Uint8Array[] {
169221
return Array.from(this.inner.signatures()) as Uint8Array[];
170222
}
171223

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,8 @@ mod types;
1616
mod versioned;
1717

1818
pub use build::build_transaction;
19-
pub use types::{AddressLookupTable, Instruction, Nonce, TransactionIntent};
20-
pub use versioned::should_build_versioned;
19+
pub use types::{
20+
AddressLookupTable, Instruction, MessageHeader, Nonce, RawVersionedTransactionData,
21+
TransactionIntent, VersionedInstruction,
22+
};
23+
pub use versioned::{build_from_raw_versioned_data, should_build_versioned};

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,3 +390,67 @@ fn default_token_program() -> String {
390390
fn default_encoding() -> String {
391391
"base64".to_string()
392392
}
393+
394+
// =============================================================================
395+
// Raw Versioned Transaction Data (for fromVersionedTransactionData path)
396+
// =============================================================================
397+
398+
/// Raw versioned transaction data for direct serialization.
399+
/// This is used when we have pre-formed MessageV0 data that just needs to be serialized.
400+
/// No instruction compilation is needed - just serialize the raw structure.
401+
#[derive(Debug, Clone, Deserialize)]
402+
#[serde(rename_all = "camelCase")]
403+
pub struct RawVersionedTransactionData {
404+
/// Static account keys (base58 encoded public keys)
405+
#[serde(rename = "staticAccountKeys")]
406+
pub static_account_keys: Vec<String>,
407+
408+
/// Address lookup tables
409+
#[serde(rename = "addressLookupTables")]
410+
pub address_lookup_tables: Vec<AddressLookupTable>,
411+
412+
/// Pre-compiled instructions with index-based account references
413+
#[serde(rename = "versionedInstructions")]
414+
pub versioned_instructions: Vec<VersionedInstruction>,
415+
416+
/// Message header
417+
#[serde(rename = "messageHeader")]
418+
pub message_header: MessageHeader,
419+
420+
/// Recent blockhash (base58)
421+
#[serde(rename = "recentBlockhash")]
422+
pub recent_blockhash: String,
423+
}
424+
425+
/// A pre-compiled versioned instruction (uses indexes, not pubkeys)
426+
#[derive(Debug, Clone, Deserialize)]
427+
#[serde(rename_all = "camelCase")]
428+
pub struct VersionedInstruction {
429+
/// Index into the account keys array for the program ID
430+
#[serde(rename = "programIdIndex")]
431+
pub program_id_index: u8,
432+
433+
/// Indexes into the account keys array for instruction accounts
434+
#[serde(rename = "accountKeyIndexes")]
435+
pub account_key_indexes: Vec<u8>,
436+
437+
/// Instruction data (base58 encoded)
438+
pub data: String,
439+
}
440+
441+
/// Message header for versioned transactions
442+
#[derive(Debug, Clone, Deserialize)]
443+
#[serde(rename_all = "camelCase")]
444+
pub struct MessageHeader {
445+
/// Number of required signatures
446+
#[serde(rename = "numRequiredSignatures")]
447+
pub num_required_signatures: u8,
448+
449+
/// Number of readonly signed accounts
450+
#[serde(rename = "numReadonlySignedAccounts")]
451+
pub num_readonly_signed_accounts: u8,
452+
453+
/// Number of readonly unsigned accounts
454+
#[serde(rename = "numReadonlyUnsignedAccounts")]
455+
pub num_readonly_unsigned_accounts: u8,
456+
}

0 commit comments

Comments
 (0)