diff --git a/packages/wasm-utxo/js/fixedScriptWallet/BitGoPsbt.ts b/packages/wasm-utxo/js/fixedScriptWallet/BitGoPsbt.ts index 80cb5a2..c2412f4 100644 --- a/packages/wasm-utxo/js/fixedScriptWallet/BitGoPsbt.ts +++ b/packages/wasm-utxo/js/fixedScriptWallet/BitGoPsbt.ts @@ -19,7 +19,13 @@ export type InputScriptType = | "p2trMusig2ScriptPath" | "p2trMusig2KeyPath"; +export type OutPoint = { + txid: string; + vout: number; +}; + export type ParsedInput = { + previousOutput: OutPoint; address: string; script: Uint8Array; value: bigint; diff --git a/packages/wasm-utxo/src/fixed_script_wallet/bitgo_psbt/psbt_wallet_input.rs b/packages/wasm-utxo/src/fixed_script_wallet/bitgo_psbt/psbt_wallet_input.rs index 887579c..3312087 100644 --- a/packages/wasm-utxo/src/fixed_script_wallet/bitgo_psbt/psbt_wallet_input.rs +++ b/packages/wasm-utxo/src/fixed_script_wallet/bitgo_psbt/psbt_wallet_input.rs @@ -565,6 +565,7 @@ impl InputScriptType { /// Parsed input from a PSBT transaction #[derive(Debug, Clone)] pub struct ParsedInput { + pub previous_output: OutPoint, pub address: String, pub script: Vec, pub value: u64, @@ -627,6 +628,7 @@ impl ParsedInput { .map_err(ParseInputError::ScriptTypeDetection)?; Ok(Self { + previous_output: tx_input.previous_output, address, script: output_script.to_bytes(), value: value.to_sat(), diff --git a/packages/wasm-utxo/src/wasm/try_into_js_value.rs b/packages/wasm-utxo/src/wasm/try_into_js_value.rs index 24e7744..7608f54 100644 --- a/packages/wasm-utxo/src/wasm/try_into_js_value.rs +++ b/packages/wasm-utxo/src/wasm/try_into_js_value.rs @@ -336,6 +336,7 @@ impl TryIntoJsValue for crate::fixed_script_wallet::bitgo_psbt::InputScriptType impl TryIntoJsValue for crate::fixed_script_wallet::bitgo_psbt::ParsedInput { fn try_to_js_value(&self) -> Result { js_obj!( + "previousOutput" => js_obj!("txid" => self.previous_output.txid.to_string(), "vout" => self.previous_output.vout)?, "address" => self.address.clone(), "value" => self.value, "scriptId" => self.script_id, diff --git a/packages/wasm-utxo/test/fixedScript/parseTransactionWithWalletKeys.ts b/packages/wasm-utxo/test/fixedScript/parseTransactionWithWalletKeys.ts index 46e0d35..2533a9f 100644 --- a/packages/wasm-utxo/test/fixedScript/parseTransactionWithWalletKeys.ts +++ b/packages/wasm-utxo/test/fixedScript/parseTransactionWithWalletKeys.ts @@ -83,9 +83,59 @@ describe("parseTransactionWithWalletKeys", function () { // Verify all inputs have addresses and values parsed.inputs.forEach((input, i) => { + // Verify previousOutput structure + assert.ok(input.previousOutput, `Input ${i} should have previousOutput`); + assert.ok( + typeof input.previousOutput === "object", + `Input ${i} previousOutput should be an object`, + ); + assert.ok( + typeof input.previousOutput.txid === "string", + `Input ${i} previousOutput.txid should be string`, + ); + assert.strictEqual( + input.previousOutput.txid.length, + 64, + `Input ${i} previousOutput.txid should be 64 chars (32 bytes hex)`, + ); + assert.ok( + typeof input.previousOutput.vout === "number", + `Input ${i} previousOutput.vout should be number`, + ); + assert.ok( + input.previousOutput.vout >= 0, + `Input ${i} previousOutput.vout should be >= 0`, + ); + + // Verify address assert.ok(input.address, `Input ${i} should have an address`); + assert.ok(typeof input.address === "string", `Input ${i} address should be string`); + + // Verify value assert.ok(typeof input.value === "bigint", `Input ${i} value should be bigint`); assert.ok(input.value > 0n, `Input ${i} value should be > 0`); + + // Verify scriptId structure (can be null for replay protection inputs) + if (input.scriptId !== null) { + assert.ok( + typeof input.scriptId === "object", + `Input ${i} scriptId should be an object when present`, + ); + assert.ok( + typeof input.scriptId.chain === "number", + `Input ${i} scriptId.chain should be number`, + ); + assert.ok( + typeof input.scriptId.index === "number", + `Input ${i} scriptId.index should be number`, + ); + assert.ok(input.scriptId.chain >= 0, `Input ${i} scriptId.chain should be >= 0`); + assert.ok(input.scriptId.index >= 0, `Input ${i} scriptId.index should be >= 0`); + } + + // Verify scriptType is present + assert.ok(input.scriptType, `Input ${i} should have scriptType`); + assert.ok(typeof input.scriptType === "string", `Input ${i} scriptType should be string`); }); // Validate outputs @@ -157,6 +207,42 @@ describe("parseTransactionWithWalletKeys", function () { expectedScriptType, `Input ${i} scriptType should be ${expectedScriptType}, got ${input.scriptType}`, ); + + // Verify previousOutput is present and structured correctly + assert.ok(input.previousOutput, `Input ${i} should have previousOutput`); + assert.ok( + typeof input.previousOutput === "object", + `Input ${i} previousOutput should be an object`, + ); + assert.ok( + typeof input.previousOutput.txid === "string", + `Input ${i} previousOutput.txid should be string`, + ); + assert.strictEqual( + input.previousOutput.txid.length, + 64, + `Input ${i} previousOutput.txid should be 64 chars`, + ); + assert.ok( + typeof input.previousOutput.vout === "number", + `Input ${i} previousOutput.vout should be number`, + ); + + // Verify scriptId structure when present (can be null for replay protection inputs) + if (input.scriptId !== null) { + assert.ok( + typeof input.scriptId === "object", + `Input ${i} scriptId should be an object when present`, + ); + assert.ok( + typeof input.scriptId.chain === "number", + `Input ${i} scriptId.chain should be number`, + ); + assert.ok( + typeof input.scriptId.index === "number", + `Input ${i} scriptId.index should be number`, + ); + } }); });