honour frozen coins in automatic selection, not just coin control
freezing a coin promised it would not be spent, and the promise only held when the caller named outpoints explicitly. an ordinary send picked its own inputs from a snapshot in which every utxo said frozen: false — the chain has no idea what officer froze — so selectCoins, which has always filtered on that flag, never saw one set. sendAll was the worst case: "send everything" swept the frozen coin too. the frozen list now travels with the request, set by the route and overwritten if a caller supplies one. it can only ever restrict what is spendable, so smuggling a value in gains nothing. the backend still reads no officer table. three tests pin it, including a control that sends successfully once the coin is unfrozen — without it the other two would pass on a wallet that could not spend at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -585,8 +585,16 @@ export class OnchainBackend extends BaseBackend {
|
||||
// that may already be gone, and the failure would arrive as a broadcast rejection after signing.
|
||||
const snap = await this.snapshot({ fresh: true });
|
||||
|
||||
// The snapshot comes off the chain, which knows nothing about freezing, so every UTXO in it claims
|
||||
// `frozen: false`. selectCoins has always honoured the flag — nothing ever set it, which is what made
|
||||
// a plain send happily spend a coin the owner had frozen, and made "send everything" sweep it.
|
||||
const frozen = new Set(req.frozenOutpoints ?? []);
|
||||
const utxos = frozen.size
|
||||
? snap.utxos.map((u) => (frozen.has(`${u.txid}:${u.vout}`) ? { ...u, frozen: true } : u))
|
||||
: snap.utxos;
|
||||
|
||||
const selection = selectCoins({
|
||||
utxos: snap.utxos,
|
||||
utxos,
|
||||
targetSats: req.amountSats ?? 0,
|
||||
sendAll: req.sendAll === true,
|
||||
satPerVbyte: req.satPerVbyte,
|
||||
|
||||
Reference in New Issue
Block a user