From f6658f0ef551b34339844d035dd2efdec5661a84 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 27 Oct 2026 18:08:32 -0700 Subject: names: /admin/parked — deliver a parked payment to the name its owner holds now, or write it off (state.writtenOff keeps the record) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QjVvE4YAcK3AGwV7kZXeN4 --- names/server.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/names/server.js b/names/server.js index 118f7fe..ec60e86 100644 --- a/names/server.js +++ b/names/server.js @@ -1895,6 +1895,37 @@ Bun.serve({ return json({ ok: true, ...done[hash] }); } + // Parked payments (state.parked) are settled by a person: delivered to + // the name their owner holds now, or written off (the sats stay on the + // node; state.writtenOff keeps the record). Keyed by payment hash. + if (url.pathname === '/admin/parked' && req.method === 'POST') { + if (!CFG.adminToken || req.headers.get('x-admin-token') !== CFG.adminToken) return json({ error: 'unauthorized' }, 401); + let b; try { b = await req.json(); } catch { return json({ error: 'bad body' }, 400); } + // by hash, or by its `ts` for the few old ones parked without a hash + const hash = String(b.hash || ''); + const i = (state.parked || []).findIndex((p) => (hash ? p.hash === hash : b.ts && p.ts === Number(b.ts))); + if (i < 0) return json({ error: 'no parked payment with that hash' }, 404); + const p = state.parked[i]; + if (b.action === 'writeoff') { + state.parked.splice(i, 1); + (state.writtenOff ||= []).push({ ...p, note: String(b.note || '').slice(0, 200), writtenOffAt: Date.now() }); + persist(); + log(`parked ${(p.hash || String(p.ts)).slice(0, 12)} (${p.sat} sat, ${p.name}) written off: ${b.note || ''}`); + return json({ ok: true, writtenOff: p.sat }); + } + if (b.action === 'deliver') { + if (!p.hash) return json({ error: 'a payment parked without its hash can only be written off' }, 400); + if (!state.names[b.name]) return json({ error: 'need a current name' }, 400); + try { await forward(b.name, p.sat, hash, state.names[b.name].pubkey); } + catch (e) { return json({ error: e.message }, 502); } + state.parked = state.parked.filter((x) => x.hash !== p.hash); + persist(); + log(`parked ${hash.slice(0, 12)} (${p.sat} sat) delivered to ${b.name} (was ${p.name})`); + return json({ ok: true, delivered: p.sat, to: b.name }); + } + return json({ error: 'action is deliver or writeoff' }, 400); + } + if (url.pathname === '/admin/topup' && req.method === 'POST') { if (!CFG.adminToken || req.headers.get('x-admin-token') !== CFG.adminToken) { return json({ error: 'unauthorized' }, 401); -- cgit v1.2.3