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
18 changes: 18 additions & 0 deletions modules/abstract-utxo/src/recovery/backupKeyRecovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,16 @@ export interface RecoverParams {
recoveryProvider?: RecoveryProvider;
/** Satoshi per byte */
feeRate?: number;
/**
* Transaction lock time (nLockTime). Set before signing — it is part of the sighash.
* Used for ECX replay-protection sweeps (e.g. 499_999_999) and other custom lock-time needs.
*/
lockTime?: number;
/**
* Input sequence number applied to every input. Defaults to 0xFFFFFFFE (RBF, non-final)
* which already satisfies the non-final requirement for lockTime to take effect.
*/
sequence?: number;
}

/**
Expand Down Expand Up @@ -260,6 +270,10 @@ export interface RecoverWithUnspentsParams {
krsFee?: bigint;
/** KRS fee address (required if krsFee > 0) */
krsFeeAddress?: string;
/** Transaction lock time (nLockTime), set on the PSBT before signing */
lockTime?: number;
/** Input sequence number applied to every input */
sequence?: number;
}

function hasPrivateKey(key: BIP32): boolean {
Expand Down Expand Up @@ -299,6 +313,8 @@ export function backupKeyRecoveryWithWalletUnspents(
recoveryDestination: recoveryDestination,
keyRecoveryServiceFee: krsFee ?? BigInt(0),
keyRecoveryServiceFeeAddress: krsFeeAddress,
lockTime: params.lockTime,
sequence: params.sequence,
});

const userHasPrivateKey = hasPrivateKey(keys[0]);
Expand Down Expand Up @@ -549,6 +565,8 @@ export async function backupKeyRecovery(
feeRateSatVB,
krsFee,
krsFeeAddress,
lockTime: params.lockTime,
sequence: params.sequence,
},
unspents
);
Expand Down
22 changes: 18 additions & 4 deletions modules/abstract-utxo/src/recovery/psbt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ interface CreateBackupKeyRecoveryPsbtOptions {
keyRecoveryServiceFeeAddress: string | undefined;
/** Block height for Zcash networks (required to determine consensus branch ID) */
blockHeight?: number;
/** Transaction lock time (nLockTime), set on the PSBT before signing */
lockTime?: number;
/** Input sequence number applied to every input */
sequence?: number;
}

/**
Expand All @@ -59,6 +63,8 @@ const ZCASH_DEFAULT_BLOCK_HEIGHTS: Record<'zec' | 'tzec', number> = {
export interface CreateEmptyWasmPsbtOptions {
/** Block height for Zcash networks (required to determine consensus branch ID) */
blockHeight?: number;
/** Transaction lock time (nLockTime) */
lockTime?: number;
}

/**
Expand All @@ -80,10 +86,13 @@ export function createEmptyWasmPsbt(
const blockHeight = options?.blockHeight ?? ZCASH_DEFAULT_BLOCK_HEIGHTS[coinName];
return fixedScriptWallet.ZcashBitGoPsbt.createEmpty(coinName, rootWalletKeys, {
blockHeight,
lockTime: options?.lockTime,
});
}

return fixedScriptWallet.BitGoPsbt.createEmpty(coinName, rootWalletKeys);
return fixedScriptWallet.BitGoPsbt.createEmpty(coinName, rootWalletKeys, {
lockTime: options?.lockTime,
});
}

/**
Expand All @@ -97,7 +106,8 @@ export function createEmptyWasmPsbt(
export function addWalletInputsToWasmPsbt(
wasmPsbt: fixedScriptWallet.BitGoPsbt,
unspents: WalletUnspent<bigint>[],
rootWalletKeys: fixedScriptWallet.RootWalletKeys
rootWalletKeys: fixedScriptWallet.RootWalletKeys,
sequence?: number
): void {
unspents.forEach((unspent) => {
const { txid, vout } = parseOutputId(unspent.id);
Expand All @@ -113,6 +123,7 @@ export function addWalletInputsToWasmPsbt(
txid,
vout,
value: unspent.value,
sequence,
prevTx: prevTx,
},
rootWalletKeys,
Expand Down Expand Up @@ -155,8 +166,11 @@ function createBackupKeyRecoveryPsbtWasm(
const { feeRateSatVB, recoveryDestination, keyRecoveryServiceFee, keyRecoveryServiceFeeAddress } = options;

// Create PSBT with wasm-utxo and add wallet inputs using shared utilities
const wasmPsbt = createEmptyWasmPsbt(coinName, rootWalletKeys, { blockHeight: options.blockHeight });
addWalletInputsToWasmPsbt(wasmPsbt, unspents, rootWalletKeys);
const wasmPsbt = createEmptyWasmPsbt(coinName, rootWalletKeys, {
blockHeight: options.blockHeight,
lockTime: options.lockTime,
});
addWalletInputsToWasmPsbt(wasmPsbt, unspents, rootWalletKeys, options.sequence);

// Calculate dimensions using wasm-utxo Dimensions
let dimensions = fixedScriptWallet.Dimensions.fromPsbt(wasmPsbt).plus(
Expand Down
Loading