Skip to content

Commit be58af4

Browse files
Merge pull request #210 from BitGo/BTC-2976.hydrate-from-legacy
feat(wasm-utxo)!: add legacy transaction to PSBT conversion
2 parents ab0fbc5 + 49179a2 commit be58af4

12 files changed

Lines changed: 857 additions & 32 deletions

File tree

packages/wasm-utxo/js/fixedScriptWallet/BitGoPsbt.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ export type ParseOutputsOptions = {
126126
payGoPubkeys?: ECPairArg[];
127127
};
128128

129+
export type HydrationUnspent = {
130+
chain: number;
131+
index: number;
132+
value: bigint;
133+
};
134+
129135
export class BitGoPsbt implements IPsbtWithAddress {
130136
protected constructor(protected _wasm: WasmBitGoPsbt) {}
131137

@@ -185,6 +191,34 @@ export class BitGoPsbt implements IPsbtWithAddress {
185191
return new BitGoPsbt(wasm);
186192
}
187193

194+
/**
195+
* Convert a half-signed legacy transaction to a psbt-lite.
196+
*
197+
* Extracts partial signatures from scriptSig/witness and creates a PSBT
198+
* with proper wallet metadata (bip32Derivation, scripts, witnessUtxo).
199+
* Only supports p2sh, p2shP2wsh, and p2wsh inputs (not taproot).
200+
*
201+
* @param txBytes - The serialized half-signed legacy transaction
202+
* @param network - Network name
203+
* @param walletKeys - The wallet's root keys
204+
* @param unspents - Chain, index, and value for each input
205+
*/
206+
static fromHalfSignedLegacyTransaction(
207+
txBytes: Uint8Array,
208+
network: NetworkName,
209+
walletKeys: WalletKeysArg,
210+
unspents: HydrationUnspent[],
211+
): BitGoPsbt {
212+
const keys = RootWalletKeys.from(walletKeys);
213+
const wasm = WasmBitGoPsbt.from_half_signed_legacy_transaction(
214+
txBytes,
215+
network,
216+
keys.wasm,
217+
unspents,
218+
);
219+
return new BitGoPsbt(wasm);
220+
}
221+
188222
/**
189223
* Add an input to the PSBT
190224
*

packages/wasm-utxo/js/fixedScriptWallet/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export {
3131
type AddWalletOutputOptions,
3232
type ParseTransactionOptions,
3333
type ParseOutputsOptions,
34+
type HydrationUnspent,
3435
} from "./BitGoPsbt.js";
3536

3637
// Zcash-specific PSBT subclass

packages/wasm-utxo/js/index.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,41 @@ declare module "./wasm/wasm_utxo.js" {
8686
interface PsbtOutputDataWithAddress extends PsbtOutputData {
8787
address: string;
8888
}
89+
90+
/** Outpoint referencing a previous transaction output */
91+
interface TxOutPoint {
92+
txid: string;
93+
vout: number;
94+
}
95+
96+
/** Raw transaction input data returned by Transaction.getInputs() */
97+
interface TxInputData {
98+
previousOutput: TxOutPoint;
99+
sequence: number;
100+
scriptSig: Uint8Array;
101+
witness: Uint8Array[];
102+
}
103+
104+
/** Raw transaction output data returned by Transaction.getOutputs() */
105+
interface TxOutputData {
106+
script: Uint8Array;
107+
value: bigint;
108+
}
109+
110+
/** Transaction output data with resolved address */
111+
interface TxOutputDataWithAddress extends TxOutputData {
112+
address: string;
113+
}
89114
}
90115

91116
export { WrapDescriptor as Descriptor } from "./wasm/wasm_utxo.js";
92117
export { WrapMiniscript as Miniscript } from "./wasm/wasm_utxo.js";
93118
export { Psbt } from "./descriptorWallet/Psbt.js";
94-
export { DashTransaction, Transaction, ZcashTransaction } from "./transaction.js";
119+
export {
120+
DashTransaction,
121+
Transaction,
122+
ZcashTransaction,
123+
type ITransaction,
124+
type ITransactionCommon,
125+
} from "./transaction.js";
95126
export { hasPsbtMagic, type IPsbt, type IPsbtWithAddress } from "./psbt.js";

packages/wasm-utxo/js/psbt.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import type { PsbtInputData, PsbtOutputData, PsbtOutputDataWithAddress } from "./wasm/wasm_utxo.js";
22
import type { BIP32 } from "./bip32.js";
3+
import type { ITransactionCommon } from "./transaction.js";
34

45
/** Common interface for PSBT types */
5-
export interface IPsbt {
6-
inputCount(): number;
7-
outputCount(): number;
8-
getInputs(): PsbtInputData[];
9-
getOutputs(): PsbtOutputData[];
6+
export interface IPsbt extends ITransactionCommon<PsbtInputData, PsbtOutputData> {
107
getGlobalXpubs(): BIP32[];
11-
version(): number;
12-
lockTime(): number;
138
unsignedTxId(): string;
149
addInputAtIndex(
1510
index: number,

packages/wasm-utxo/js/transaction.ts

Lines changed: 112 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
1-
import { WasmDashTransaction, WasmTransaction, WasmZcashTransaction } from "./wasm/wasm_utxo.js";
1+
import {
2+
WasmDashTransaction,
3+
WasmTransaction,
4+
WasmZcashTransaction,
5+
type TxInputData,
6+
type TxOutputData,
7+
type TxOutputDataWithAddress,
8+
} from "./wasm/wasm_utxo.js";
9+
import type { CoinName } from "./coinName.js";
210

3-
/**
4-
* Common interface for all transaction types
5-
*/
6-
export interface ITransaction {
11+
/** Common read-only interface shared by transactions and PSBTs */
12+
export interface ITransactionCommon<TInput, TOutput> {
13+
inputCount(): number;
14+
outputCount(): number;
15+
version(): number;
16+
lockTime(): number;
17+
getInputs(): TInput[];
18+
getOutputs(): TOutput[];
19+
}
20+
21+
/** Common interface for all transaction types */
22+
export interface ITransaction extends ITransactionCommon<TxInputData, TxOutputData> {
723
toBytes(): Uint8Array;
824
getId(): string;
25+
getOutputsWithAddress(coin: CoinName): TxOutputDataWithAddress[];
926
}
1027

1128
/**
@@ -27,9 +44,7 @@ export class Transaction implements ITransaction {
2744
return new Transaction(WasmTransaction.from_bytes(bytes));
2845
}
2946

30-
/**
31-
* @internal Create from WASM instance directly (avoids re-parsing bytes)
32-
*/
47+
/** @internal Create from WASM instance directly (avoids re-parsing bytes) */
3348
static fromWasm(wasm: WasmTransaction): Transaction {
3449
return new Transaction(wasm);
3550
}
@@ -84,9 +99,35 @@ export class Transaction implements ITransaction {
8499
return this._wasm.get_vsize();
85100
}
86101

87-
/**
88-
* @internal
89-
*/
102+
inputCount(): number {
103+
return this._wasm.input_count();
104+
}
105+
106+
outputCount(): number {
107+
return this._wasm.output_count();
108+
}
109+
110+
version(): number {
111+
return this._wasm.version();
112+
}
113+
114+
lockTime(): number {
115+
return this._wasm.lock_time();
116+
}
117+
118+
getInputs(): TxInputData[] {
119+
return this._wasm.get_inputs() as TxInputData[];
120+
}
121+
122+
getOutputs(): TxOutputData[] {
123+
return this._wasm.get_outputs() as TxOutputData[];
124+
}
125+
126+
getOutputsWithAddress(coin: CoinName): TxOutputDataWithAddress[] {
127+
return this._wasm.get_outputs_with_address(coin) as TxOutputDataWithAddress[];
128+
}
129+
130+
/** @internal */
90131
get wasm(): WasmTransaction {
91132
return this._wasm;
92133
}
@@ -104,9 +145,7 @@ export class ZcashTransaction implements ITransaction {
104145
return new ZcashTransaction(WasmZcashTransaction.from_bytes(bytes));
105146
}
106147

107-
/**
108-
* @internal Create from WASM instance directly (avoids re-parsing bytes)
109-
*/
148+
/** @internal Create from WASM instance directly (avoids re-parsing bytes) */
110149
static fromWasm(wasm: WasmZcashTransaction): ZcashTransaction {
111150
return new ZcashTransaction(wasm);
112151
}
@@ -127,9 +166,35 @@ export class ZcashTransaction implements ITransaction {
127166
return this._wasm.get_txid();
128167
}
129168

130-
/**
131-
* @internal
132-
*/
169+
inputCount(): number {
170+
return this._wasm.input_count();
171+
}
172+
173+
outputCount(): number {
174+
return this._wasm.output_count();
175+
}
176+
177+
version(): number {
178+
return this._wasm.version();
179+
}
180+
181+
lockTime(): number {
182+
return this._wasm.lock_time();
183+
}
184+
185+
getInputs(): TxInputData[] {
186+
return this._wasm.get_inputs() as TxInputData[];
187+
}
188+
189+
getOutputs(): TxOutputData[] {
190+
return this._wasm.get_outputs() as TxOutputData[];
191+
}
192+
193+
getOutputsWithAddress(coin: CoinName): TxOutputDataWithAddress[] {
194+
return this._wasm.get_outputs_with_address(coin) as TxOutputDataWithAddress[];
195+
}
196+
197+
/** @internal */
133198
get wasm(): WasmZcashTransaction {
134199
return this._wasm;
135200
}
@@ -147,9 +212,7 @@ export class DashTransaction implements ITransaction {
147212
return new DashTransaction(WasmDashTransaction.from_bytes(bytes));
148213
}
149214

150-
/**
151-
* @internal Create from WASM instance directly (avoids re-parsing bytes)
152-
*/
215+
/** @internal Create from WASM instance directly (avoids re-parsing bytes) */
153216
static fromWasm(wasm: WasmDashTransaction): DashTransaction {
154217
return new DashTransaction(wasm);
155218
}
@@ -170,9 +233,35 @@ export class DashTransaction implements ITransaction {
170233
return this._wasm.get_txid();
171234
}
172235

173-
/**
174-
* @internal
175-
*/
236+
inputCount(): number {
237+
return this._wasm.input_count();
238+
}
239+
240+
outputCount(): number {
241+
return this._wasm.output_count();
242+
}
243+
244+
version(): number {
245+
return this._wasm.version();
246+
}
247+
248+
lockTime(): number {
249+
return this._wasm.lock_time();
250+
}
251+
252+
getInputs(): TxInputData[] {
253+
return this._wasm.get_inputs() as TxInputData[];
254+
}
255+
256+
getOutputs(): TxOutputData[] {
257+
return this._wasm.get_outputs() as TxOutputData[];
258+
}
259+
260+
getOutputsWithAddress(coin: CoinName): TxOutputDataWithAddress[] {
261+
return this._wasm.get_outputs_with_address(coin) as TxOutputDataWithAddress[];
262+
}
263+
264+
/** @internal */
176265
get wasm(): WasmDashTransaction {
177266
return this._wasm;
178267
}

0 commit comments

Comments
 (0)