Skip to content
Open
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
71 changes: 69 additions & 2 deletions packages/wasm-utxo/js/fixedScriptWallet/BitGoPsbt.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { BitGoPsbt as WasmBitGoPsbt } from "../wasm/wasm_utxo.js";
import {
BitGoPsbt as WasmBitGoPsbt,
type PsbtInputData,
type PsbtOutputData,
type PsbtOutputDataWithAddress,
} from "../wasm/wasm_utxo.js";
import type { IPsbtIntrospectionWithAddress } from "../psbt.js";
import { type WalletKeysArg, RootWalletKeys } from "./RootWalletKeys.js";
import { type ReplayProtectionArg, ReplayProtection } from "./ReplayProtection.js";
import { type BIP32Arg, BIP32, isBIP32Arg } from "../bip32.js";
Expand Down Expand Up @@ -109,7 +115,7 @@ export type AddWalletOutputOptions = {
value: bigint;
};

export class BitGoPsbt {
export class BitGoPsbt implements IPsbtIntrospectionWithAddress {
protected constructor(protected _wasm: WasmBitGoPsbt) {}

/**
Expand Down Expand Up @@ -806,4 +812,65 @@ export class BitGoPsbt {
getHalfSignedLegacyFormat(): Uint8Array {
return this._wasm.extract_half_signed_legacy_tx();
}

/**
* Get the number of inputs in the PSBT
* @returns The number of inputs
*/
get inputCount(): number {
return this._wasm.input_count();
}

/**
* Get the number of outputs in the PSBT
* @returns The number of outputs
*/
get outputCount(): number {
return this._wasm.output_count();
}

/**
* Get all PSBT inputs as an array
*
* Returns raw PSBT input data including witness_utxo and derivation info.
* For parsed transaction data with address identification, use
* parseTransactionWithWalletKeys() instead.
*
* @returns Array of PsbtInputData objects
*/
getInputs(): PsbtInputData[] {
return this._wasm.get_inputs() as PsbtInputData[];
}

/**
* Get all PSBT outputs as an array
*
* Returns raw PSBT output data without address resolution.
* For output data with addresses, use getOutputsWithAddress().
*
* @returns Array of PsbtOutputData objects
*/
getOutputs(): PsbtOutputData[] {
return this._wasm.get_outputs() as PsbtOutputData[];
}

/**
* Get all PSBT outputs with resolved address strings
*
* Unlike the generic Psbt class which requires a coin parameter,
* BitGoPsbt automatically uses the network it was created with to resolve addresses.
*
* @returns Array of PsbtOutputDataWithAddress objects
*
* @example
* ```typescript
* const outputs = psbt.getOutputsWithAddress();
* for (const output of outputs) {
* console.log(`${output.address}: ${output.value} satoshis`);
* }
* ```
*/
getOutputsWithAddress(): PsbtOutputDataWithAddress[] {
return this._wasm.get_outputs_with_address() as PsbtOutputDataWithAddress[];
}
}
9 changes: 9 additions & 0 deletions packages/wasm-utxo/js/fixedScriptWallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ export {
type CreateEmptyZcashOptions,
} from "./ZcashBitGoPsbt.js";

// PSBT introspection types (re-exported for consumer convenience)
export type {
PsbtBip32Derivation,
PsbtInputData,
PsbtOutputData,
PsbtOutputDataWithAddress,
PsbtWitnessUtxo,
} from "../wasm/wasm_utxo.js";

import type { ScriptType } from "./scriptType.js";

/**
Expand Down
6 changes: 5 additions & 1 deletion packages/wasm-utxo/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,8 @@ export { WrapDescriptor as Descriptor } from "./wasm/wasm_utxo.js";
export { WrapMiniscript as Miniscript } from "./wasm/wasm_utxo.js";
export { WrapPsbt as Psbt } from "./wasm/wasm_utxo.js";
export { DashTransaction, Transaction, ZcashTransaction } from "./transaction.js";
export { hasPsbtMagic } from "./psbt.js";
export {
hasPsbtMagic,
type IPsbtIntrospection,
type IPsbtIntrospectionWithAddress,
} from "./psbt.js";
15 changes: 15 additions & 0 deletions packages/wasm-utxo/js/psbt.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
import type { PsbtInputData, PsbtOutputData, PsbtOutputDataWithAddress } from "./wasm/wasm_utxo.js";

/** Common interface for PSBT introspection methods */
export interface IPsbtIntrospection {
readonly inputCount: number;
readonly outputCount: number;
getInputs(): PsbtInputData[];
getOutputs(): PsbtOutputData[];
}

/** Extended introspection with address resolution (no coin parameter needed) */
export interface IPsbtIntrospectionWithAddress extends IPsbtIntrospection {
getOutputsWithAddress(): PsbtOutputDataWithAddress[];
}

/** PSBT magic bytes: "psbt" (0x70 0x73 0x62 0x74) followed by separator 0xff */
const PSBT_MAGIC = new Uint8Array([0x70, 0x73, 0x62, 0x74, 0xff]);

Expand Down
34 changes: 34 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 @@ -628,6 +628,40 @@ impl BitGoPsbt {
}
}

/// Get the number of inputs in the PSBT
pub fn input_count(&self) -> usize {
self.psbt.psbt().inputs.len()
}

/// Get the number of outputs in the PSBT
pub fn output_count(&self) -> usize {
self.psbt.psbt().outputs.len()
}

/// Get all PSBT inputs as an array of PsbtInputData
///
/// Returns an array with witness_utxo, bip32_derivation, and tap_bip32_derivation
/// for each input.
pub fn get_inputs(&self) -> Result<JsValue, WasmUtxoError> {
crate::wasm::psbt::get_inputs_from_psbt(self.psbt.psbt())
}

/// Get all PSBT outputs as an array of PsbtOutputData
///
/// Returns an array with script, value, bip32_derivation, and tap_bip32_derivation
/// for each output.
pub fn get_outputs(&self) -> Result<JsValue, WasmUtxoError> {
crate::wasm::psbt::get_outputs_from_psbt(self.psbt.psbt())
}

/// Get all PSBT outputs with resolved address strings.
///
/// Unlike the generic WrapPsbt which requires a coin parameter, BitGoPsbt
/// uses the network it was created/deserialized with to resolve addresses.
pub fn get_outputs_with_address(&self) -> Result<JsValue, WasmUtxoError> {
crate::wasm::psbt::get_outputs_with_address_from_psbt(self.psbt.psbt(), self.psbt.network())
}

/// Parse transaction with wallet keys to identify wallet inputs/outputs
pub fn parse_transaction_with_wallet_keys(
&self,
Expand Down
66 changes: 43 additions & 23 deletions packages/wasm-utxo/src/wasm/psbt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,46 @@ impl PsbtOutputDataWithAddress {
}
}

// ============================================================================
// Helper functions for PSBT introspection - shared by WrapPsbt and BitGoPsbt
// ============================================================================

/// Get all PSBT inputs as an array of PsbtInputData
pub fn get_inputs_from_psbt(psbt: &Psbt) -> Result<JsValue, WasmUtxoError> {
let inputs: Vec<PsbtInputData> = psbt.inputs.iter().map(PsbtInputData::from).collect();
inputs.try_to_js_value()
}

/// Get all PSBT outputs as an array of PsbtOutputData
pub fn get_outputs_from_psbt(psbt: &Psbt) -> Result<JsValue, WasmUtxoError> {
let outputs: Vec<PsbtOutputData> = psbt
.unsigned_tx
.output
.iter()
.zip(psbt.outputs.iter())
.map(|(tx_out, psbt_out)| PsbtOutputData::from(tx_out, psbt_out))
.collect();
outputs.try_to_js_value()
}

/// Get all PSBT outputs with resolved address strings
pub fn get_outputs_with_address_from_psbt(
psbt: &Psbt,
network: crate::Network,
) -> Result<JsValue, WasmUtxoError> {
let outputs: Vec<PsbtOutputDataWithAddress> = psbt
.unsigned_tx
.output
.iter()
.zip(psbt.outputs.iter())
.map(|(tx_out, psbt_out)| {
let base = PsbtOutputData::from(tx_out, psbt_out);
PsbtOutputDataWithAddress::from(base, network)
})
.collect::<Result<Vec<_>, _>>()?;
outputs.try_to_js_value()
}

#[wasm_bindgen]
pub struct WrapPsbt(Psbt);

Expand Down Expand Up @@ -585,8 +625,7 @@ impl WrapPsbt {
/// for each input. This is useful for introspecting the PSBT structure.
#[wasm_bindgen(js_name = getInputs)]
pub fn get_inputs(&self) -> Result<JsValue, WasmUtxoError> {
let inputs: Vec<PsbtInputData> = self.0.inputs.iter().map(PsbtInputData::from).collect();
inputs.try_to_js_value()
get_inputs_from_psbt(&self.0)
}

/// Get all PSBT outputs as an array of PsbtOutputData
Expand All @@ -595,15 +634,7 @@ impl WrapPsbt {
/// for each output. This is useful for introspecting the PSBT structure.
#[wasm_bindgen(js_name = getOutputs)]
pub fn get_outputs(&self) -> Result<JsValue, WasmUtxoError> {
let outputs: Vec<PsbtOutputData> = self
.0
.unsigned_tx
.output
.iter()
.zip(self.0.outputs.iter())
.map(|(tx_out, psbt_out)| PsbtOutputData::from(tx_out, psbt_out))
.collect();
outputs.try_to_js_value()
get_outputs_from_psbt(&self.0)
}

/// Get all PSBT outputs with resolved address strings.
Expand All @@ -614,18 +645,7 @@ impl WrapPsbt {
pub fn get_outputs_with_address(&self, coin: &str) -> Result<JsValue, WasmUtxoError> {
let network = crate::Network::from_coin_name(coin)
.ok_or_else(|| WasmUtxoError::new(&format!("Unknown coin: {}", coin)))?;
let outputs: Vec<PsbtOutputDataWithAddress> = self
.0
.unsigned_tx
.output
.iter()
.zip(self.0.outputs.iter())
.map(|(tx_out, psbt_out)| {
let base = PsbtOutputData::from(tx_out, psbt_out);
PsbtOutputDataWithAddress::from(base, network)
})
.collect::<Result<Vec<_>, _>>()?;
outputs.try_to_js_value()
get_outputs_with_address_from_psbt(&self.0, network)
}

/// Get partial signatures for an input
Expand Down