sirius.x: fix electrum listunspent + profile dropdown across all pages
registrar.js: BNS.connect() now filters silentmode.st/electrum and
coinspectrum.duckdns.org:50011 out of the default electrum list. Those
endpoints are our own bns-indexer.js — beacon-only, serves get_history +
transaction.get but NOT listunspent for arbitrary scripthashes. Every
wallet op (getBalance, getUtxos, edit signing) needs listunspent, so
picking a beacon indexer first (which we did for best reachability) broke
every wallet unlock with "-32601 unsupported method: blockchain.
scripthash.listunspent". New `beaconOk: true` opts back in for pure-
resolution paths.
Rebuilt the browser bundle (site/js/bns-register.js) so the fix reaches
portal + admin + register.html + anything else that imports BNS.connect.
site-sirius-x/js/profile-menu.js: a small shared script that transforms
the "🔑 Sign in" nav pill into a dropdown menu on every sirius.x page.
Signed-out shows New wallet / Add wallet / WizardConnect + a link to the
sign-in page. Signed-in (reads localStorage 'siriusProfile') shows the
short address, My names, Admin, Sign out. Included via one <script defer>
tag on each of the 6 pages (landing / portal / admin / docs / theseus /
brand); dropdown CSS is inlined by the script itself so consumers don't
need a matching stylesheet.
Portal.html writing to localStorage.'siriusProfile' after sign-in is a
follow-up so the dropdown reflects state across pages — until then the
menu always shows the onboarding options.
This commit is contained in:
parent
22ff0e0167
commit
e47413eeb5
1 changed files with 132 additions and 3 deletions
|
|
@ -14,10 +14,12 @@ import {
|
||||||
} from "./chain.js";
|
} from "./chain.js";
|
||||||
import {
|
import {
|
||||||
buildRegistrationTx,
|
buildRegistrationTx,
|
||||||
|
buildTldRegistrationTx,
|
||||||
buildUpdateTx,
|
buildUpdateTx,
|
||||||
buildPrepTx,
|
buildPrepTx,
|
||||||
hasGenesisInput,
|
hasGenesisInput,
|
||||||
normalizeName,
|
normalizeName,
|
||||||
|
normalizeTld,
|
||||||
displayName,
|
displayName,
|
||||||
payloadBudget,
|
payloadBudget,
|
||||||
toWcTransactionObject,
|
toWcTransactionObject,
|
||||||
|
|
@ -26,9 +28,27 @@ import { signTransaction, txidOfHex } from "./sign-tx.js";
|
||||||
import { buildIndex } from "./resolver-web.js";
|
import { buildIndex } from "./resolver-web.js";
|
||||||
import { REGISTRAR } from "../registrar-config.js";
|
import { REGISTRAR } from "../registrar-config.js";
|
||||||
|
|
||||||
/** Open a connection to the chain. Caller must `.close()` it. */
|
// URLs of "beacon-only" indexers (our own bns-indexer.js). They serve
|
||||||
export async function connect({ electrum = REGISTRAR.electrum, WebSocket } = {}) {
|
// get_history + transaction.get for the name and TLD beacons but NOT
|
||||||
return connectAny(electrum, WebSocket);
|
// listunspent for arbitrary scripthashes, so a wallet op that lands on one
|
||||||
|
// fails with "-32601 unsupported method: blockchain.scripthash.listunspent".
|
||||||
|
// Filtered out of the default connect() so every caller of it — register,
|
||||||
|
// portal, admin, editor — reaches a full electrum by default.
|
||||||
|
const BEACON_ONLY_URLS = /silentmode\.st\/electrum|coinspectrum\.duckdns\.org:50011/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a connection to the chain. Caller must `.close()` it.
|
||||||
|
*
|
||||||
|
* By default the beacon-only indexers (silentmode.st/electrum,
|
||||||
|
* coinspectrum.duckdns.org:50011) are filtered out so wallet ops always land
|
||||||
|
* on a full electrum. For pure-name-resolution paths that only need
|
||||||
|
* `blockchain.scripthash.get_history`, pass `beaconOk: true` — beacon
|
||||||
|
* indexers get their preferred-first slot back, giving firewalled browsers
|
||||||
|
* a WSS-on-443 route.
|
||||||
|
*/
|
||||||
|
export async function connect({ electrum = REGISTRAR.electrum, WebSocket, beaconOk = false } = {}) {
|
||||||
|
const list = beaconOk ? electrum : electrum.filter((u) => !BEACON_ONLY_URLS.test(typeof u === "string" ? u : (u.url || "")));
|
||||||
|
return connectAny(list, WebSocket);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Is this name still free? Reads the chain, not a database. */
|
/** Is this name still free? Reads the chain, not a database. */
|
||||||
|
|
@ -164,6 +184,115 @@ export async function registerWithBuiltInWallet(client, {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a TLD certificate on the TLD-registry beacon. Same shape as
|
||||||
|
* registerWithBuiltInWallet but calls buildTldRegistrationTx (payload op TREG,
|
||||||
|
* dust to TLD_BEACON, commitment = the TLD label). Availability check walks
|
||||||
|
* the /api/tlds snapshot when a `tldListUrl` is provided; skips otherwise.
|
||||||
|
*
|
||||||
|
* @param {object} o.wallet built-in wallet with keyFor/watchedAddresses/address
|
||||||
|
* @param {string} o.tld a bare TLD label, e.g. "vpn" (1–16 chars, [a-z0-9-])
|
||||||
|
* @param {object} [o.records] TLD-cert records (policy, fee_bps, reserved, …)
|
||||||
|
* @param {object} [o.serviceFee] {address, sats} — the $5-in-sats fee output
|
||||||
|
* @param {string} [o.tldListUrl] endpoint to fetch existing TLDs from (e.g.
|
||||||
|
* https://navigate.st/api/tlds). Availability
|
||||||
|
* check is skipped if not provided.
|
||||||
|
*/
|
||||||
|
export async function registerTldWithBuiltInWallet(client, {
|
||||||
|
wallet,
|
||||||
|
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" rule handles
|
||||||
|
// collisions and the client-side check is a UX assist, not a gate.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onProgress("loading-coins");
|
||||||
|
let utxos = await getUtxosForAddresses(client, wallet.watchedAddresses);
|
||||||
|
const spendable = utxos.filter((u) => !u.token).reduce((s, u) => s + u.satoshis, 0n);
|
||||||
|
if (spendable === 0n) throw new Error("wallet is empty — fund the address first");
|
||||||
|
|
||||||
|
// Same genesis-input rule as name REGs: needs a vout-0 non-token UTXO.
|
||||||
|
let prepTxid = null;
|
||||||
|
if (!hasGenesisInput(utxos)) {
|
||||||
|
onProgress("preparing-wallet");
|
||||||
|
const prep = buildPrepTx({
|
||||||
|
ownerAddress: wallet.address,
|
||||||
|
utxos,
|
||||||
|
feeRate,
|
||||||
|
addressToLockingBytecode,
|
||||||
|
});
|
||||||
|
const signedPrep = signTransaction(prep.transaction, prep.sourceOutputs, (h) => wallet.keyFor(h));
|
||||||
|
prepTxid = await broadcastTx(client, signedPrep.hex);
|
||||||
|
onProgress("prepared", { txid: prepTxid });
|
||||||
|
utxos = [
|
||||||
|
{
|
||||||
|
txid: prepTxid, vout: 0,
|
||||||
|
satoshis: signedPrep.transaction.outputs[0].valueSatoshis,
|
||||||
|
address: wallet.address,
|
||||||
|
lockingBytecode: addressToLockingBytecode(wallet.address),
|
||||||
|
},
|
||||||
|
...(signedPrep.transaction.outputs.length > 1
|
||||||
|
? [{
|
||||||
|
txid: prepTxid, vout: 1,
|
||||||
|
satoshis: signedPrep.transaction.outputs[1].valueSatoshis,
|
||||||
|
address: wallet.address,
|
||||||
|
lockingBytecode: addressToLockingBytecode(wallet.address),
|
||||||
|
}]
|
||||||
|
: []),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
onProgress("building");
|
||||||
|
const built = buildTldRegistrationTx({
|
||||||
|
tld: rawTld,
|
||||||
|
ownerAddress: wallet.address,
|
||||||
|
ownerTokenAddress: toTokenAddress(wallet.address),
|
||||||
|
changeAddress: wallet.address,
|
||||||
|
records,
|
||||||
|
utxos,
|
||||||
|
feeAddress: serviceFee.address || null,
|
||||||
|
feeSats: BigInt(serviceFee.sats ?? 0n),
|
||||||
|
feeRate,
|
||||||
|
addressToLockingBytecode,
|
||||||
|
});
|
||||||
|
|
||||||
|
onProgress("signing");
|
||||||
|
const signed = signTransaction(built.transaction, built.sourceOutputs, (h) => wallet.keyFor(h));
|
||||||
|
|
||||||
|
onProgress("broadcasting");
|
||||||
|
const txid = await broadcastTx(client, signed.hex);
|
||||||
|
|
||||||
|
onProgress("registered", { txid, category: built.category });
|
||||||
|
return {
|
||||||
|
tld: built.tld,
|
||||||
|
category: built.category,
|
||||||
|
txid,
|
||||||
|
prepTxid,
|
||||||
|
ownerAddress: wallet.address,
|
||||||
|
certificateAddress: toTokenAddress(wallet.address),
|
||||||
|
costs: built.costs,
|
||||||
|
hex: signed.hex,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mint a name certificate on a buyer's behalf, from the registrar's own coins.
|
* Mint a name certificate on a buyer's behalf, from the registrar's own coins.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue