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:
@@ -308,9 +308,12 @@ async function handleWallets(ctx: OfficerContext, seg: string[]): Promise<Respon
|
||||
if (!req2.sendAll && !req2.amountSats) return badRequest('amountSats or sendAll is required');
|
||||
if (!req2.satPerVbyte || req2.satPerVbyte < 1) return badRequest('satPerVbyte must be at least 1');
|
||||
// Never spend a frozen coin, even if the caller passed no explicit outpoint list.
|
||||
const frozen = new Set(await getFrozenOutpoints(walletId));
|
||||
const frozenList = await getFrozenOutpoints(walletId);
|
||||
const frozen = new Set(frozenList);
|
||||
if (req2.outpoints?.some((o) => frozen.has(o))) return badRequest('refusing to spend a frozen UTXO');
|
||||
const result = await backend.sendCoins(req2);
|
||||
// Spread last so a caller cannot supply its own frozen list: the check above only ever covered
|
||||
// outpoints the caller named explicitly, which left automatic selection free to pick a frozen coin.
|
||||
const result = await backend.sendCoins({ ...req2, frozenOutpoints: frozenList });
|
||||
if (req2.label) await setWalletLabel(walletId, 'tx', result.txid, req2.label);
|
||||
return json(result);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user