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
20 changes: 17 additions & 3 deletions packages/wasm-utxo/js/fixedScriptWallet/BitGoPsbt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import { type ECPairArg, ECPair } from "../ecpair.js";
import type { UtxolibName } from "../utxolibCompat.js";
import type { CoinName } from "../coinName.js";
import type { InputScriptType } from "./scriptType.js";
import {
Transaction,
DashTransaction,
ZcashTransaction,
type ITransaction,
} from "../transaction.js";

export type { InputScriptType };

Expand Down Expand Up @@ -755,11 +761,19 @@ export class BitGoPsbt {
/**
* Extract the final transaction from a finalized PSBT
*
* @returns The serialized transaction bytes
* @returns The extracted transaction instance
* @throws Error if the PSBT is not fully finalized or extraction fails
*/
extractTransaction(): Uint8Array {
return this._wasm.extract_transaction();
extractTransaction(): ITransaction {
const networkType = this._wasm.get_network_type();

if (networkType === "dash") {
return DashTransaction.fromWasm(this._wasm.extract_dash_transaction());
}
if (networkType === "zcash") {
return ZcashTransaction.fromWasm(this._wasm.extract_zcash_transaction());
}
return Transaction.fromWasm(this._wasm.extract_bitcoin_transaction());
}

/**
Expand Down
11 changes: 11 additions & 0 deletions packages/wasm-utxo/js/fixedScriptWallet/ZcashBitGoPsbt.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BitGoPsbt as WasmBitGoPsbt } from "../wasm/wasm_utxo.js";
import { type WalletKeysArg, RootWalletKeys } from "./RootWalletKeys.js";
import { BitGoPsbt, type CreateEmptyOptions } from "./BitGoPsbt.js";
import { ZcashTransaction } from "../transaction.js";

/** Zcash network names */
export type ZcashNetworkName = "zcash" | "zcashTest" | "zec" | "tzec";
Expand Down Expand Up @@ -160,4 +161,14 @@ export class ZcashBitGoPsbt extends BitGoPsbt {
get expiryHeight(): number {
return this.wasm.expiry_height();
}

/**
* Extract the final Zcash transaction from a finalized PSBT
*
* @returns The extracted Zcash transaction instance
* @throws Error if the PSBT is not fully finalized or extraction fails
*/
override extractTransaction(): ZcashTransaction {
return ZcashTransaction.fromWasm(this.wasm.extract_zcash_transaction());
}
}
71 changes: 68 additions & 3 deletions packages/wasm-utxo/js/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,48 @@
import { WasmDashTransaction, WasmTransaction, WasmZcashTransaction } from "./wasm/wasm_utxo.js";

/**
* Common interface for all transaction types
*/
export interface ITransaction {
toBytes(): Uint8Array;
getId(): string;
}

/**
* Transaction wrapper (Bitcoin-like networks)
*
* Provides a camelCase, strongly-typed API over the snake_case WASM bindings.
*/
export class Transaction {
export class Transaction implements ITransaction {
private constructor(private _wasm: WasmTransaction) {}

static fromBytes(bytes: Uint8Array): Transaction {
return new Transaction(WasmTransaction.from_bytes(bytes));
}

/**
* @internal Create from WASM instance directly (avoids re-parsing bytes)
*/
static fromWasm(wasm: WasmTransaction): Transaction {
return new Transaction(wasm);
}

toBytes(): Uint8Array {
return this._wasm.to_bytes();
}

/**
* Get the transaction ID (txid)
*
* The txid is the double SHA256 of the transaction bytes (excluding witness
* data for segwit transactions), displayed in reverse byte order as is standard.
*
* @returns The transaction ID as a hex string
*/
getId(): string {
return this._wasm.get_txid();
}

/**
* Get the virtual size of the transaction
*
Expand All @@ -40,17 +67,36 @@ export class Transaction {
*
* Provides a camelCase, strongly-typed API over the snake_case WASM bindings.
*/
export class ZcashTransaction {
export class ZcashTransaction implements ITransaction {
private constructor(private _wasm: WasmZcashTransaction) {}

static fromBytes(bytes: Uint8Array): ZcashTransaction {
return new ZcashTransaction(WasmZcashTransaction.from_bytes(bytes));
}

/**
* @internal Create from WASM instance directly (avoids re-parsing bytes)
*/
static fromWasm(wasm: WasmZcashTransaction): ZcashTransaction {
return new ZcashTransaction(wasm);
}

toBytes(): Uint8Array {
return this._wasm.to_bytes();
}

/**
* Get the transaction ID (txid)
*
* The txid is the double SHA256 of the full Zcash transaction bytes,
* displayed in reverse byte order as is standard.
*
* @returns The transaction ID as a hex string
*/
getId(): string {
return this._wasm.get_txid();
}

/**
* @internal
*/
Expand All @@ -64,17 +110,36 @@ export class ZcashTransaction {
*
* Round-trip only: bytes -> parse -> bytes.
*/
export class DashTransaction {
export class DashTransaction implements ITransaction {
private constructor(private _wasm: WasmDashTransaction) {}

static fromBytes(bytes: Uint8Array): DashTransaction {
return new DashTransaction(WasmDashTransaction.from_bytes(bytes));
}

/**
* @internal Create from WASM instance directly (avoids re-parsing bytes)
*/
static fromWasm(wasm: WasmDashTransaction): DashTransaction {
return new DashTransaction(wasm);
}

toBytes(): Uint8Array {
return this._wasm.to_bytes();
}

/**
* Get the transaction ID (txid)
*
* The txid is the double SHA256 of the full Dash transaction bytes,
* displayed in reverse byte order as is standard.
*
* @returns The transaction ID as a hex string
*/
getId(): string {
return this._wasm.get_txid();
}

/**
* @internal
*/
Expand Down
55 changes: 55 additions & 0 deletions packages/wasm-utxo/src/fixed_script_wallet/bitgo_psbt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1147,6 +1147,61 @@ impl BitGoPsbt {
}
}

/// Extract the Bitcoin transaction directly (for BitcoinLike networks only)
///
/// # Returns
/// * `Ok(Transaction)` - The extracted transaction
/// * `Err(String)` - If not BitcoinLike or extraction fails
pub fn extract_bitcoin_tx(self) -> Result<miniscript::bitcoin::Transaction, String> {
match self {
BitGoPsbt::BitcoinLike(psbt, _) => psbt
.extract_tx()
.map_err(|e| format!("Failed to extract transaction: {}", e)),
_ => Err("extract_bitcoin_tx only supported for BitcoinLike networks".to_string()),
}
}

/// Extract the Dash transaction parts directly
///
/// # Returns
/// * `Ok(DashTransactionParts)` - The extracted transaction parts
/// * `Err(String)` - If not Dash or extraction fails
pub fn extract_dash_tx(self) -> Result<crate::dash::transaction::DashTransactionParts, String> {
use miniscript::bitcoin::consensus::serialize;
match self {
BitGoPsbt::Dash(dash_psbt, _) => {
let tx = dash_psbt
.psbt
.extract_tx()
.map_err(|e| format!("Failed to extract transaction: {}", e))?;
let tx_bytes = serialize(&tx);
crate::dash::transaction::decode_dash_transaction_parts(&tx_bytes)
.map_err(|e| format!("Failed to decode Dash transaction: {}", e))
}
_ => Err("extract_dash_tx only supported for Dash networks".to_string()),
}
}

/// Extract the Zcash transaction parts directly
///
/// # Returns
/// * `Ok(ZcashTransactionParts)` - The extracted transaction parts
/// * `Err(String)` - If not Zcash or extraction fails
pub fn extract_zcash_tx(
self,
) -> Result<crate::zcash::transaction::ZcashTransactionParts, String> {
match self {
BitGoPsbt::Zcash(zcash_psbt, _) => {
let bytes = zcash_psbt
.extract_tx()
.map_err(|e| format!("Failed to extract transaction: {}", e))?;
crate::zcash::transaction::decode_zcash_transaction_parts(&bytes)
.map_err(|e| format!("Failed to decode Zcash transaction: {}", e))
}
_ => Err("extract_zcash_tx only supported for Zcash networks".to_string()),
}
}

/// Extract a half-signed transaction in legacy format for p2ms-based script types.
///
/// This method extracts a transaction where each input has exactly one signature,
Expand Down
29 changes: 29 additions & 0 deletions packages/wasm-utxo/src/wasm/dash_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ pub struct WasmDashTransaction {
parts: crate::dash::transaction::DashTransactionParts,
}

impl WasmDashTransaction {
/// Create from parts (internal use)
pub(crate) fn from_parts(parts: crate::dash::transaction::DashTransactionParts) -> Self {
WasmDashTransaction { parts }
}
}

#[wasm_bindgen]
impl WasmDashTransaction {
/// Deserialize a Dash transaction from bytes (supports EVO special tx extra payload).
Expand All @@ -24,4 +31,26 @@ impl WasmDashTransaction {
WasmUtxoError::new(&format!("Failed to serialize Dash transaction: {}", e))
})
}

/// Get the transaction ID (txid)
///
/// The txid is the double SHA256 of the full Dash transaction bytes,
/// displayed in reverse byte order (big-endian) as is standard.
///
/// # Returns
/// The transaction ID as a hex string
///
/// # Errors
/// Returns an error if the transaction cannot be serialized
pub fn get_txid(&self) -> Result<String, WasmUtxoError> {
use miniscript::bitcoin::hashes::{sha256d, Hash};
use miniscript::bitcoin::Txid;
let tx_bytes = crate::dash::transaction::encode_dash_transaction_parts(&self.parts)
.map_err(|e| {
WasmUtxoError::new(&format!("Failed to serialize Dash transaction: {}", e))
})?;
let hash = sha256d::Hash::hash(&tx_bytes);
let txid = Txid::from_raw_hash(hash);
Ok(txid.to_string())
}
}
60 changes: 60 additions & 0 deletions packages/wasm-utxo/src/wasm/fixed_script_wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,19 @@ impl BitGoPsbt {
self.psbt.network().to_string()
}

/// Get the network type for transaction extraction
///
/// Returns "bitcoin", "dash", or "zcash" to indicate which transaction
/// wrapper class should be used in TypeScript.
pub fn get_network_type(&self) -> String {
use crate::fixed_script_wallet::bitgo_psbt::BitGoPsbt as InnerBitGoPsbt;
match &self.psbt {
InnerBitGoPsbt::BitcoinLike(_, _) => "bitcoin".to_string(),
InnerBitGoPsbt::Dash(_, _) => "dash".to_string(),
InnerBitGoPsbt::Zcash(_, _) => "zcash".to_string(),
}
}

/// Get the transaction version
pub fn version(&self) -> i32 {
self.psbt.psbt().unsigned_tx.version.0
Expand Down Expand Up @@ -1490,6 +1503,53 @@ impl BitGoPsbt {
.map_err(|e| WasmUtxoError::new(&e))
}

/// Extract the final transaction as a WasmTransaction (for BitcoinLike networks)
///
/// This avoids re-parsing bytes by returning the transaction directly.
/// Only valid for Bitcoin-like networks (not Dash or Zcash).
pub fn extract_bitcoin_transaction(
&self,
) -> Result<crate::wasm::transaction::WasmTransaction, WasmUtxoError> {
let tx = self
.psbt
.clone()
.extract_bitcoin_tx()
.map_err(|e| WasmUtxoError::new(&e))?;
Ok(crate::wasm::transaction::WasmTransaction::from_tx(tx))
}

/// Extract the final transaction as a WasmDashTransaction (for Dash networks)
///
/// This avoids re-parsing bytes by returning the transaction directly.
/// Only valid for Dash networks.
pub fn extract_dash_transaction(
&self,
) -> Result<crate::wasm::dash_transaction::WasmDashTransaction, WasmUtxoError> {
let parts = self
.psbt
.clone()
.extract_dash_tx()
.map_err(|e| WasmUtxoError::new(&e))?;
Ok(crate::wasm::dash_transaction::WasmDashTransaction::from_parts(parts))
}

/// Extract the final transaction as a WasmZcashTransaction (for Zcash networks)
///
/// This avoids re-parsing bytes by returning the transaction directly.
/// Only valid for Zcash networks.
pub fn extract_zcash_transaction(
&self,
) -> Result<crate::wasm::transaction::WasmZcashTransaction, WasmUtxoError> {
let parts = self
.psbt
.clone()
.extract_zcash_tx()
.map_err(|e| WasmUtxoError::new(&e))?;
Ok(crate::wasm::transaction::WasmZcashTransaction::from_parts(
parts,
))
}

/// Extract a half-signed transaction in legacy format for p2ms-based script types.
///
/// This method extracts a transaction where each input has exactly one signature,
Expand Down
Loading