diff options
| author | Adam Soltys <[email protected]> | 2026-04-26 23:52:23 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-04-26 23:52:23 -0700 |
| commit | 206aacd410dbafc4c7313bdd4ea234180373ddc2 (patch) | |
| tree | 090b72d62ada53d371fe8f49113da3c95cfeb2ac /lib | |
| parent | 669b13189180ea40f73e7202052f04039cfc9dac (diff) | |
| download | coinos-server-206aacd410dbafc4c7313bdd4ea234180373ddc2.tar.gz coinos-server-206aacd410dbafc4c7313bdd4ea234180373ddc2.zip | |
prevent duplicate utxo selection
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/payments.ts | 18 |
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; }), |
