aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-04-26 23:52:23 -0700
committerAdam Soltys <[email protected]>2026-04-26 23:52:23 -0700
commit206aacd410dbafc4c7313bdd4ea234180373ddc2 (patch)
tree090b72d62ada53d371fe8f49113da3c95cfeb2ac /lib
parent669b13189180ea40f73e7202052f04039cfc9dac (diff)
downloadcoinos-server-206aacd410dbafc4c7313bdd4ea234180373ddc2.tar.gz
coinos-server-206aacd410dbafc4c7313bdd4ea234180373ddc2.zip
prevent duplicate utxo selection
Diffstat (limited to 'lib')
-rw-r--r--lib/payments.ts18
1 files changed, 17 insertions, 1 deletions
diff --git a/lib/payments.ts b/lib/payments.ts
index 5d823b4d..be639534 100644
--- a/lib/payments.ts
+++ b/lib/payments.ts
@@ -487,6 +487,11 @@ export const sendOnchain = async (params) => {
return p;
} catch (e) {
delete inflight[txid];
+ // Release UTXOs that build() locked, so the user can retry without abandoning coins.
+ try {
+ const vin = tx?.vin?.map(({ txid, vout }) => ({ txid, vout })) ?? [];
+ if (vin.length) await node.lockUnspent(true, vin);
+ } catch {}
throw e;
}
};
@@ -756,6 +761,17 @@ export const build = async ({
inputs.push({ witnessUtxo, path });
}
+ // Reserve selected UTXOs so concurrent builds don't pick the same inputs.
+ // Locks are in-memory only; cleared on bitcoind restart. sendOnchain unlocks on broadcast failure.
+ try {
+ await node.lockUnspent(
+ false,
+ vin.map(({ txid, vout }) => ({ txid, vout })),
+ );
+ } catch (e: any) {
+ warn("lockUnspent failed", e.message);
+ }
+
return { feeRate, ourfee, fee, fees, hex: tx.hex, inputs, subtract };
};
@@ -764,7 +780,7 @@ export const catchUp = async () => {
const txns = [];
for (const [type, n] of Object.entries({ bitcoin: bc, liquid: lq })) {
txns.push(
- ...(await n.listTransactions("*", 10)).filter((tx) => {
+ ...(await n.listTransactions("*", 100)).filter((tx) => {
tx.type = type;
return tx.category === "receive" && tx.confirmations > 0;
}),