aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/names.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/features/names.js')
-rw-r--r--src/features/names.js39
1 files changed, 38 insertions, 1 deletions
diff --git a/src/features/names.js b/src/features/names.js
index d57bb19..3f23a0c 100644
--- a/src/features/names.js
+++ b/src/features/names.js
@@ -17,7 +17,7 @@ import { nip98Header } from '../nip98.js';
import { qrSvg } from '../qr.js';
import { bech32 } from '@scure/base';
import { isArkAddress } from './ark.js';
-import { getNetwork } from '../api.js';
+import { getNetwork, getArkProviderId } from '../api.js';
import { t } from '../i18n.js';
import { getCurrency } from '../rates.js';
@@ -132,6 +132,27 @@ export function namesFeature(ctx) {
return j;
}
+ // Delete the account at the registrar: every name this wallet's key owns
+ // or manages is freed and what the server filed under them goes with it
+ // (coinosv3-server names/forget.js). `check` only asks what stands in the
+ // way: sats still on their way here, or held in a withdraw link.
+ async function accountDelete(check) {
+ const url = `${REGISTRAR}/account/delete`;
+ const body = JSON.stringify(check ? { check: true } : {});
+ const auth = await withTimeout(nip98Header(walletSigner(), url, 'POST', body), 12000, 'signing');
+ const r = await withTimeout(fetch(url, { method: 'POST', headers: { 'content-type': 'application/json', authorization: auth }, body }), 30000, 'registrar');
+ const j = await r.json().catch(() => ({}));
+ if (j.error === 'funds') return { blocked: true, ...j };
+ if (!r.ok || j.error) throw new Error(j.error || `registrar refused (${r.status})`);
+ if (!check) {
+ // a profile still pointing at a freed address would pay nobody (or its next owner)
+ for (const key of j.names || [])
+ await Promise.race([Promise.resolve(hook('addressGone', key)).catch(() => {}), new Promise((res) => setTimeout(res, 8000))]);
+ wallet.saveFeatureState('names', {});
+ }
+ return j;
+ }
+
async function release() {
const st = load();
if (!st.name) return;
@@ -411,6 +432,7 @@ export function namesFeature(ctx) {
// should have to retype who they already are — but only when that name is
// actually free on the registrar (or already theirs to retake).
let lnReceipts = { at: 0, p: null };
+ let listeningAt = 0;
let suggestedFor = null;
function suggestName() {
@@ -959,6 +981,20 @@ export function namesFeature(ctx) {
};
return lnReceipts.p;
},
+ // A wallet on another ark server (Second, a custom one) gets its name's
+ // Lightning receives only while it is open to answer the registrar's
+ // offer request — the registrar can't arkoor to a server it has no
+ // float on — and its hourly retry rarely met a short session (52 sats
+ // waited 449 tries, 2026-10-18 → 11-03). So once the offer listener is
+ // up, say so: whatever is queued for this name goes now. Once per ten
+ // minutes; a wallet on the coinos server is paid while asleep and
+ // never needs to (and never spends a signature on it).
+ namesListening() {
+ if (!available() || !load().name || getArkProviderId() === 'coinos') return null;
+ if (Date.now() - listeningAt < 600_000) return null;
+ listeningAt = Date.now();
+ return post('/listening', 'POST', {}).catch(() => { listeningAt = 0; return null; });
+ },
// the claimed payment address, for anyone prefilling a lightning address
namesAddress() { const st = load(); return st.name ? `${st.name}@${st.domain || DOMAIN()}` : null; },
// Sign-in races this lookup against the seed work: by the time the
@@ -1041,6 +1077,7 @@ export function namesFeature(ctx) {
return null;
},
settingsCards() { return namesCards(); },
+ accountDelete(check) { return wallet.watchOnly || !wallet.nostrSign ? null : accountDelete(check); },
posSettingsCards() { return posSettingsCardsForName(); },
// the Ark server changed: the name's ark= instruction must follow
namesRefresh() { refresh().catch(() => {}); return true; },