From db71b9e8322f7affb8815daeb7fb16b475988473 Mon Sep 17 00:00:00 2001 From: Local Dev Date: Mon, 7 Sep 2026 01:39:16 +0200 Subject: [PATCH] feat(registrar): registerTldWithExternalWallet + wire into portal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the external-wallet variant of registerTldWithBuiltInWallet so WizardConnect users can mint TLDs from the sirius.x portal without first importing a seed. Same transaction shape (TREG payload, TLD- beacon output, service-fee output) — the wallet approves the mint (and, if needed, a one-time prep signature to satisfy the vout-0 non-token genesis-input rule) on the user's own device. Bundle: re-exported from src/web/register-entry.js and rebuilt (bns-register.js: 33.3 kB, cache-buster bumped to ?v=20260907tldext on portal.html / register.html / admin/index.html). Portal: TLD-register submit now dispatches on wallet.source, calling registerTldWithExternalWallet with wallet.session when the user is signed in via WC. Removed the WC-disable guard and updated the card copy to say TLD mints also go through the wallet's signing prompt. Uses the same landOnChain helper as registerWithExternalWallet, so wallet-broadcasts (walletBroadcasts:true sessions) and duplicate/ mempool responses are handled identically. --- src/lib/registrar.js | 104 ++++++++++++++++++++++++++++++++++++++ src/web/register-entry.js | 1 + 2 files changed, 105 insertions(+) diff --git a/src/lib/registrar.js b/src/lib/registrar.js index 0cc8ec5..738bdd8 100644 --- a/src/lib/registrar.js +++ b/src/lib/registrar.js @@ -293,6 +293,110 @@ export async function registerTldWithBuiltInWallet(client, { }; } +/** + * Register a TLD with an EXTERNAL wallet (WizardConnect). + * + * Same transaction shape as registerTldWithBuiltInWallet — same TREG payload, + * same TLD-beacon output, same service-fee output — only the signing transport + * differs. Genesis-input requirement is handled by prompting the connected + * wallet for a one-time prep signature if it can't front a vout-0 non-token + * coin already, mirroring registerWithExternalWallet's pattern. + * + * Does NOT accept `ownerAddress` because a TLD certificate is minted to + * whoever signs the mint — using the first address the connected wallet + * exposes keeps the same "you own what you signed for" invariant as the + * name-registration external path. + * + * @param {object} o.session a WalletSession (see connect/) + */ +export async function registerTldWithExternalWallet(client, { + session, + tld, + records = {}, + serviceFee = { address: null, sats: 0n }, + feeRate = REGISTRAR.feeRate, + tldListUrl = null, + onProgress = () => {}, +}) { + const rawTld = normalizeTld(tld); + onProgress("checking-availability"); + if (tldListUrl) { + try { + const r = await fetch(tldListUrl); + if (r.ok) { + const j = await r.json(); + const taken = Array.isArray(j.tlds) && j.tlds.some((t) => (t.tld || t) === rawTld); + if (taken) throw new Error(`".${rawTld}" is already registered as a TLD`); + } + } catch (e) { + if (/already registered/.test(e.message)) throw e; + // network / API blip — proceed; on-chain "first TREG wins" handles it. + } + } + + onProgress("reading-wallet"); + const addresses = await session.getAddresses(); + if (!addresses.length) throw new Error("the wallet exposed no addresses"); + const owner = addresses[0]; + + onProgress("loading-coins"); + let utxos = await getUtxosForAddresses(client, addresses); + if (utxos.filter((u) => !u.token).reduce((s, u) => s + u.satoshis, 0n) === 0n) { + throw new Error("the connected wallet has no spendable coins"); + } + + let prepTxid = null; + if (!hasGenesisInput(utxos)) { + onProgress("preparing-wallet"); + const prep = buildPrepTx({ ownerAddress: owner, utxos, feeRate, addressToLockingBytecode }); + const prepSigned = await session.signTransaction(prep, { + userPrompt: "One-time setup so your wallet can create a TLD certificate", + }); + prepTxid = await landOnChain(client, session, prepSigned, onProgress); + onProgress("prepared", { txid: prepTxid }); + utxos = [ + { + txid: prepTxid, vout: 0, + satoshis: prep.transaction.outputs[0].valueSatoshis, + address: owner, + lockingBytecode: addressToLockingBytecode(owner), + }, + ...utxos.filter((u) => !u.token && !prep.inputUtxos.some((i) => i.txid === u.txid && i.vout === u.vout)), + ]; + } + + onProgress("building"); + const built = buildTldRegistrationTx({ + tld: rawTld, + ownerAddress: owner, + ownerTokenAddress: toTokenAddress(owner), + changeAddress: owner, + records, + utxos, + feeAddress: serviceFee.address || null, + feeSats: BigInt(serviceFee.sats ?? 0n), + feeRate, + addressToLockingBytecode, + }); + + onProgress("awaiting-approval"); + const signed = await session.signTransaction(built, { + userPrompt: `Register .${rawTld} — the TLD certificate is minted to your own address`, + }); + const txid = await landOnChain(client, session, signed, onProgress); + + onProgress("registered", { txid, category: built.category }); + return { + tld: built.tld, + category: built.category, + txid, + prepTxid, + ownerAddress: owner, + certificateAddress: toTokenAddress(owner), + costs: built.costs, + }; +} + /** * Mint a name certificate on a buyer's behalf, from the registrar's own coins. * diff --git a/src/web/register-entry.js b/src/web/register-entry.js index 74a4c4d..ebdbd7b 100644 --- a/src/web/register-entry.js +++ b/src/web/register-entry.js @@ -12,6 +12,7 @@ export { registerWithBuiltInWallet, registerWithExternalWallet, registerTldWithBuiltInWallet, + registerTldWithExternalWallet, setRecordsWithBuiltInWallet, setRecordsWithExternalWallet, findCertificateUtxo,