diff options
| author | Adam Soltys <[email protected]> | 2026-10-21 22:19:07 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-21 22:19:07 -0700 |
| commit | 6b59316493a9a02f735a19e9747d2f3982d27ce2 (patch) | |
| tree | 1e1baf994bdfd94e6f34c112f768656c59b1fe9e | |
| parent | 5c007a2540d5a033c0934844eff68688af68817c (diff) | |
| download | coinosv3-server-6b59316493a9a02f735a19e9747d2f3982d27ce2.tar.gz coinosv3-server-6b59316493a9a02f735a19e9747d2f3982d27ce2.zip | |
names: custom offers carry their owner's key; ones that can't reach a wallet are retired
Releasing a name disabled its own offer but left its custom offers live
(27 orphans on 2026-10-22, rick@ had 4): a payment to one found no name
and no key, and would have sat in the forward queue for good. Custom
offers now record the owner's pubkey (backfilled at start from the name,
or for an orphaned npub1xxxxxxx@ name from the one current key with that
npub prefix — 4 recovered), and the settle loop forwards by it, so a
released name's offer pays the same key's name now, as the name's own
offer already did. Orphans with no owner to follow are disabled on the
node and dropped (23), as are a released staging name's.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
| -rw-r--r-- | names/server.js | 41 |
1 files changed, 32 insertions, 9 deletions
diff --git a/names/server.js b/names/server.js index a0a0bb7..591222b 100644 --- a/names/server.js +++ b/names/server.js @@ -654,10 +654,12 @@ async function settleLoop() { delete state.invoices[inv.payment_hash]; persist(); } + // a released name's custom offer still knows its owner's key + const pubkey = state.names[name]?.pubkey || (offerId && state.offers?.[offerId]?.pubkey) || undefined; try { - await forward(name, sat, inv.payment_hash, state.names[name]?.pubkey); + await forward(name, sat, inv.payment_hash, pubkey); } catch (e) { - const p = { name, sat, hash: inv.payment_hash, pubkey: state.names[name]?.pubkey, ts: Date.now() }; + const p = { name, sat, hash: inv.payment_hash, pubkey, ts: Date.now() }; if (queueFailedForward(p, e)) { log(`forward failed for ${name} (${sat} sat): ${e.message} — queued`); (state.pending ||= []).push(p); @@ -1387,7 +1389,10 @@ Bun.serve({ } catch (e) { return json({ error: e.message }, 500); } - state.offers[o.offer_id] = { key, memo, bolt12: o.bolt12, created: Date.now() }; + // the owner's key rides along: if the name is released (a v2 migration + // swaps a throwaway npub name for the real one) the offer's payments + // follow the key to its name now, like the name's own offer does + state.offers[o.offer_id] = { key, memo, bolt12: o.bolt12, created: Date.now(), pubkey: state.names[key]?.pubkey }; persist(); log(`offer for ${key}: ${memo ? JSON.stringify(memo) : '(no memo)'}`); return json({ ok: true, offerId: o.offer_id, memo, bolt12: o.bolt12 }); @@ -2046,10 +2051,10 @@ settleLoop().catch((e) => log('settle loop died:', e.message)); retireOtherNetworkOffers().catch((e) => log('retiring other-network offers failed: ' + e.message)); // Names that got one of our offers before otherNetwork() existed (staging -// names: 84 of them, plus 15 custom offers, on 2026-10-22): disable the offer -// on the node, take its lno= out of the published record, drop custom ones. -// Idempotent — a name it fixed has no offerId left — so every start re-runs -// it and a name that failed (Cloudflare down, say) gets another go. +// names: 83 of them, plus 13 custom offers, retired 2026-10-22): disable the +// offer on the node, take its lno= out of the published record. Idempotent — +// a name it fixed has no offerId left — so every start re-runs it and a name +// that failed (Cloudflare down, say) gets another go. async function retireOtherNetworkOffers() { if (!ln) return; let names = 0, customs = 0; @@ -2068,9 +2073,27 @@ async function retireOtherNetworkOffers() { names++; } catch (e) { log(`retiring the offer for ${key} failed: ${e.message}`); } } + // Custom offers: give each its owner's key (they predate the field), then + // retire the ones that can't reach a wallet — on another network, or + // orphaned by a released name with no owner to follow. An orphaned + // npub1xxxxxxx@ name finds its owner by that npub prefix when exactly one + // current name's key matches (2026-10-22: 27 orphans, rick@ had 4). + const byNpub = new Map(); + for (const r of Object.values(state.names)) { + if (!r.pubkey) continue; + const p = npubEncode(r.pubkey).slice(0, 12); + byNpub.set(p, byNpub.has(p) && byNpub.get(p) !== r.pubkey ? null : r.pubkey); + } + let owned = 0; for (const [id, o] of Object.entries(state.offers || {})) { const rec = state.names[o.key]; - if (!rec || !otherNetwork(rec.uri, rec.domain)) continue; + const domain = rec?.domain || o.key.split('@')[1]; + if (!o.pubkey) { + o.pubkey = rec?.pubkey || byNpub.get(o.key.split('@')[0].slice(0, 12)) || undefined; + if (o.pubkey) { persist(); owned++; } + } + const retire = rec ? otherNetwork(rec.uri, rec.domain) : (otherNetwork(null, domain) || !o.pubkey); + if (!retire) continue; try { await ln.call('disableoffer', { offer_id: id }).catch((e) => { if (!gone(e)) throw e; }); delete state.offers[id]; @@ -2078,7 +2101,7 @@ async function retireOtherNetworkOffers() { customs++; } catch (e) { log(`retiring custom offer ${id} for ${o.key} failed: ${e.message}`); } } - if (names || customs) log(`retired our offers for wallets on another network: ${names} names, ${customs} custom offers`); + if (names || customs || owned) log(`offers: retired ${names} names' and ${customs} custom offers that couldn't reach a wallet; ${owned} custom offers learned their owner's key`); } // The published uri minus our lno= parameter (the owner's other params kept). |
