Two issues reported after an Import sign-in:
1. Done button was stuck — user saw the ✓ Signed in screen but the
portal never switched to the names view. Root cause: finishSignIn
fired siriusProfileChanged BEFORE setting window.siriusWallet, so
the portal's listener called adoptWalletFromModal() while
window.siriusWallet was still null, saw no wallet, and did nothing.
Fix: expose the live wallet BEFORE writeProfile so the sync
listener sees it and can enterPortal() immediately.
2. Every reload asked for the password again. Cache the wallet's
mnemonic in sessionStorage on sign-in — same tab (or a page
reload) rebuilds the BuiltInWallet silently via
BuiltInWallet.fromMnemonic; a full browser close clears
sessionStorage and the user is back at the Unlock tab.
sessionStorage is per-origin per-tab so an XSS on Sirius.X pages
would still be able to read it — that's the tradeoff for the
convenience. Chipnet only; mainnet gets the PIN escrow pattern
(3 wrong PIN tries → escalate to password) that Digibyte.x/web
already uses. PIN implementation is deferred to its own commit.
portal.html adoptWalletFromModal now tries sessionStorage after the
in-memory check; register-flow.js startFlow/startTldFlow do the same
via a new async adoptSessionWalletAsync so name and TLD mints on any
page reuse the session wallet with no re-prompt. profile-menu.js
sign-out clears sessionStorage + window.siriusWallet so signing out
really does drop the user.
Single source of truth (js/pricing.js) mirrored on landing, tld.html
and the mint flow — same label always shows the same price everywhere.
Name tiers (label part):
1 char $5.00 premium-1
2 chars $3.00 short-2
3 chars $2.00 short-3
4–5 chars $1.00 standard
6–7 chars $0.50 long
8–16 chars $0.25 extended
17+ chars $0.10 very-long
all-digits ×0.5 (less brandable)
contains hyphen ×0.7
TLD tiers (label part):
1 char $10.00 premium-1
2 chars $8.00 short-2
3 chars $6.00 short-3
4–8 chars $5.00 standard
9+ chars $4.00 long
Rendering:
- Landing #search-results: acid-tinted price badge on each available
row; grid grew a fourth column so name + badge + price + button
all sit on one line
- tld.html search result-card: same price badge next to the Register
button on available TLDs
- tld.html registered-TLDs table: new 'Mint price' column showing the
tier price for every registered TLD, so buyers see the pricing
ladder next to concrete examples
Mint pipe:
- startFlow(name)/startTldFlow(label) resolve the label price via
window.siriusPricing (default) but honour opts.serviceFeeSats when
the caller passes one — leaves the door open for coupons /
operator discounts / oracle overrides in a future revision without
reshuffling call sites
- The overridden service fee is threaded through quoteRegistration
AND registerWithBuiltInWallet/registerWithExternalWallet so the
confirm screen and the on-chain output agree
Chipnet placeholder rate (250,000 sat/USD) stays in pricing.js;
mainnet will swap in a live BCH/USD oracle. Operator-side discounts
and coupon codes are the next iteration — deliberately not disclosed
in this shipping copy.
Unlock tab used to be hidden when no saved wallet existed on the
device — so a fresh browser saw a 3-tab strip, and a returning
browser saw a 4-tab strip. Inconsistent, and users kept asking where
Unlock went.
Now Unlock is always the first tab:
- if a saved wallet exists, stepUnlock() renders the usual password
form (nothing else changes)
- if no saved wallet, stepUnlock() renders 'No wallet on this device'
with a one-line explanation and two shortcut buttons — 📥 Import
and 🆕 Create — that jump straight to those tabs
Same tab bar renders in all cases so the layout is stable across
sessions and between devices.
Nav wallet dropdown was calling window.siriusSignInWallet(action),
which opens the shared modal on whatever page you were on — so from
tld.html or docs/, a Create/Import/WC click drew the sign-in over the
current page as a full-screen overlay. User: 'still opens as a
separate console on top of the page, it should be within the page'.
Now the click handler:
- if we are already on portal.html AND
siriusRenderSignInInline is loaded, paint the tabbed sign-in
inside portal's #signin-inline directly and scroll to it
- otherwise navigate to portal.html?mode=<action>, where the
bootstrap already renders the tabbed sign-in inline as the
page's own content
Same tabs, same forms — the four options (🔓 Unlock / 🆕 Create /
📥 Import / 🔗 WizardConnect) — but they now live inside the page,
not on top of it. Unlock falls back to Import when there's no saved
wallet in that browser.
Two things:
1. Reuse the wallet the user already unlocked (adoptSignedInWallet).
startFlow / startTldFlow were always calling stepWallet(), which
showed the four-option wallet-choice and then prompted for the
password again on 'Unlock'. Right after signing in, that meant
entering the password twice to buy a TLD. Now both flows check
window.siriusWallet at start and, if it's populated (built-in
wallet in memory, or a WC-wrapper with a live session), skip
straight to stepFund with state.wallet already set. Password is
only asked when there is no in-memory wallet to reuse.
2. Prices are visible on the landing card + tld.html search:
- name: '≈ 10,000 sat service fee · 1,300 sat chain dust · < 1 ¢'
with a note that the 90/10 split applies (TLD owner gets 90 %)
- TLD: '≈ 1,250,000 sat · ≈ ' with the incentive line
('you earn 90 % of the fee on every name registered under
your TLD — forever, no renewals')
Both labelled 'chipnet placeholder' — mainnet will price by
label length via an on-chain oracle.
PIN as a lighter approval than the full password is a bigger design
change (encryption key derivation, cache lifetime, revocation) — not
in this commit; adopting the already-unlocked in-memory wallet is
the fix that removes the password-again symptom for now.
renderSignInInline() reassigned the module-level 'sheet' variable to the
host page's container, but render() had been shadowing that with a
separate 'mountTarget' constant captured at module init — so every step
kept writing into the modal sheet instead of the inline container.
Portal.html therefore showed an empty #signin-inline while the tabs +
form rendered off-screen inside the (closed) modal.
Drop mountTarget entirely and let render() write directly into 'sheet'.
'sheet' is already the reassignable target for both modal and inline
paths, and renderSignInInline / close() already keep it in sync.
Every second-level name registration under a TLD now routes 90% of the
service fee to whoever holds that TLD's certificate on chain, with 10%
going to the platform address. That's the economic incentive for
minting a TLD: you earn from every name registered under it.
The mechanism, end to end:
1. resolver-web.js fetchTldMap now also records mintScriptHex — the
scriptPubKey of the TREG output that carries each TLD's NFT.
Exported so registrar can decode it into a cashaddr with libauth.
MVP: this is the ORIGINAL owner; NFT transfers after mint are not
traced yet (a follow-up will walk the chain of transfers).
2. registrar.js gains findTldOwnerAddress(client, tld) and
splitServiceFee(sats). The split constants live at the top of the
file (TLD_OWNER_SHARE_NUM/DEN = 90/100) so the ratio moves in one
place. Rounding: BigInt division favours the platform on odd sat
counts so the two shares always sum EXACTLY to the input.
3. quoteRegistration wraps the existing flow: it derives the TLD from
the name, looks up the TLD owner, and if the owner ≠ buyer it asks
buildRegistrationTx to add a second fee output. If the owner
couldn't be resolved (TLD not registered, decode failure) the full
fee stays on the platform address — the buyer still pays the same
amount either way.
4. register-tx.js buildRegistrationTx accepts tldFeeAddress/tldFeeSats
and, when set, emits an extra P2PKH output for the TLD owner. Sits
between the beacon dust and the platform-fee output; outputMap
records .tldOwnerFee so callers can find it. costs also carries
tldOwnerFeeSats and netCostSats includes it.
5. priceSummary in registrar-config splits the 'Service fee' row into
'Service fee — TLD owner (90%)' + 'Service fee — platform (10%)'
whenever tldOwnerFeeSats > 0, with a per-line note explaining
where the money goes.
Bundle: re-exported findTldOwnerAddress + splitServiceFee from
register-entry.js. Rebuilt bns-register.js (~34 kB) and deployed;
cache-buster bumped to ?v=20260908split on portal / admin /
register-flow.js.
Verified live: findTldOwnerAddress('.bch') returns the operator
cashaddr; quoteRegistration('tester42.bch') builds cleanly with a
9,000/1,000 split output pair on a 10,000-sat fee; priceSummary
renders both lines. No regressions on the TLD-mint flow (buyer IS
the TLD owner there — split short-circuits and it stays a single
fee output as before).
/sirius-x/portal.html?mode=import was landing users on the old three-card
inline sign-in UI (New / Import / WizardConnect each as its own form).
The new UX is: one tabbed modal, everywhere. Portal was the one page
still shipping the legacy cards.
Replaces the whole <section id='signin'> with a single launcher card
('🔑 Sign in or create wallet' → opens the shared modal). The old
handlers (, , etc.) are
comment-blocked out; new script uses window.siriusSignInWallet().
?mode=new|import|wc|unlock auto-opens the modal at that tab (the same
handler dropdown items use). If the user arrives with a siriusProfile
but no in-memory wallet (e.g., they signed in on another page), portal
opens the modal at Unlock (or Import when no saved wallet). Sign-out
delegates to the shared signOutUi(). Cross-tab and same-tab profile
changes both re-adopt.
register-flow.js: finishSignIn now sets window.siriusWallet so portal
(and any other page loaded in the same tab) can pick the wallet up
without re-prompting for a password. In-memory only — reload/nav clears
it, and the profile listener falls through to Unlock.
Two features:
1. Operator can hide TLDs from the public /api/tlds listing so hidden
TLDs stop appearing in name-search UIs. On-chain registrations
under a hidden TLD keep resolving — this is a UX filter, not
enforcement.
Gateway (public-gateway.mjs):
- Persistent HIDDEN_TLDS set backed by hidden-tlds.json next to
the service script
- GET /api/tld-visibility -> {hidden:[...]} (public)
- POST /api/tld-visibility -> updates the list (operator-gated by
Bearer BNS_OPERATOR_TOKEN env var; if unset, all writes refused
so we default-deny)
- /api/tlds filters out HIDDEN_TLDS; add ?include_hidden=1 to see
everything (used by the admin panel to show all rows)
- Operator token installed via systemd override on the VPS
Admin panel:
- New 'Operator token' card at the top of the TLD-registry section;
token stored in sessionStorage (not localStorage) so a full
browser close forgets it
- Each TLD row got a 'Hidden from public' checkbox that POSTs on
toggle and refreshes the table; failures roll back the checkbox
and surface the error next to the token field
2. Wallet dropdown restored to 4 direct actions
(Unlock / Create a wallet / Import a wallet / WizardConnect) and
the shared mint/sign-in modal grew a tab strip so users can switch
between the four wallet actions from any step without going back
to a choice screen.
register-flow.js:
- renderTabs(active) prepended to stepCreate/stepImport/stepUnlock/
stepExternal when signInOnly is set. Unlock tab only appears
when a saved wallet exists.
- Delegated click handler on the sheet routes tab clicks to the
matching step; switching away from a live WC session tears it
down first so we don't leak WebSockets.
profile-menu.js:
- Restored 4-item onboarding menu (Create/Import/WC plus Unlock
when saved). Each item is a direct entry point; the tabbed modal
lets the user pivot to any other option without closing.
Cache-buster bumped on all 8 sirius-x pages to ?v=20260908tabs.
Two changes:
1. Wallet dropdown simplified from three direct-action items into one
'Sign in or create wallet' launcher (plus 🔓 Unlock when a saved
wallet is present). The single item opens the shared modal at
stepWallet — the choice screen showing all four options (Unlock /
Create / Import / WizardConnect) as clear cards. Clicking a card
reveals its form. Users see the options first, then commit to a
path, instead of landing on a form for one path without seeing the
others.
register-flow.js's stepWallet grew a signInOnly-aware header ('Sign
in or create a wallet' / 'Pick one. Your keys stay in this browser')
so the choice screen reads as sign-in context, not a name mint.
startSignIn still accepts direct-mode entry points (new/import/wc/
unlock) for deep-links like portal.html?mode=X, but the nav dropdown
funnels to 'choose' for the choice-first experience.
2. Theseus subpage copy updated per request: 'native .bch / BCNR name
resolution built in' -> 'native Bitcoin Cash Domain Names resolution
built in'. Meta description mirrored.
Previously, the only way to unlock a wallet that had been saved in this
browser was to click Register (name or TLD), open the modal, and pick
'Unlock my browser wallet' from the wallet-choice step. From the nav
dropdown you could only start onboarding fresh (New / Import / WC).
profile-menu.js now checks localStorage for the BuiltInWallet's storage
key (bns.wallet.v1) at every render, and when a saved wallet is present
inserts a '🔓 Unlock my wallet' item at the top of the not-signed-in
dropdown — with a divider below it, so it reads as the primary action
and the Onboarding options stay available for adding a different wallet.
The dropdown click handler already lazy-loads register-flow.js and
calls window.siriusSignInWallet(action); register-flow.js's startSignIn
grew an 'unlock' mode that opens the modal directly at stepUnlock (the
password prompt). Same success path as every other sign-in: on unlock
success, siriusProfile is written and the modal closes with '✓ Signed
in' — the nav pill flips to the address without a reload.
Verified end-to-end on landing: saved wallet detected -> dropdown shows
Unlock as first item -> click -> password -> '✓ Signed in' -> pill
becomes 'qqyx49…zx8x seed ▾' with no reload. TLD mint from tld.html
also verified end-to-end: search 'e2etldtest' -> Register -> modal ->
Unlock -> password -> Fund -> Confirm (fee 1,250,000 sat + beacon dust
~1,300 sat) -> Register -> checking-availability -> loading-coins ->
'wallet is empty' (expected without chipnet funds; downstream code is
the same registerTldWithBuiltInWallet path the CLI uses).
Two UX polishes:
1. Register-tab icon flipped from 🪪 (identification-card emoji, renders
as a hollow box in system-ui fonts without extended emoji) to ✏️
(pencil), which reads as 'edit/create' and ships in every emoji font
worth targeting. Changed across the nav on all 8 pages plus the
footer injector's Product column. Kept the Register nav item — six
items total in the nav, and from any subpage it's one click to the
search on landing.
2. tld.html no longer duplicates the wallet-choice + mint flow inline.
The Register button on a search result now hands the label off to
the shared register-flow.js modal — same modal that name registration
uses — via a new window.siriusRegisterTld(label, {serviceFeeSats})
entry point.
register-flow.js grew:
- startTldFlow / window.siriusRegisterTld: sets state.tld and walks
the modal through wallet-choice → Fund → stepConfirmTld → stepRegisterTld
→ stepDoneTld
- stepConfirmTld: shows label + fee + owner, no per-name quote
- stepRegisterTld: dispatches to registerTldWithBuiltInWallet or
registerTldWithExternalWallet based on state.wallet/state.session
- stepDoneTld: 'X is yours' with txid + certificate id + operator
address, no record editor (a TLD certificate has no records to
set immediately)
- stepWallet / stepExternalConfirm branch on state.tld so the modal
heading and the WC-confirm path route correctly
tld.html trimmed: removed the entire signin-section + mint-section
plus their inline handlers (~250 lines gone). Now just search +
registered-TLDs list + a delegator to the shared modal. If a user
is already signed in via the wallet dropdown, the modal recognises
the saved wallet ('Unlock my browser wallet' button appears) instead
of asking them to sign in again on this page.
Wallet dropdown items (New / Import / WizardConnect) were navigating
to portal.html?mode=X and asking the user to click again on arrival.
That is 'the sign in landing page which is not functioning' from the
user's perspective — a whole redirect for one form.
Now every dropdown item opens the mint modal (register-flow.js) inline
at the matching step, on whatever page the user is on:
New -> stepCreate ('Create your wallet' — password + generate)
Import -> stepImport ('Import a recovery phrase' — textarea)
WizardConnect -> stepExternal ('Open your wallet' — QR/URI)
register-flow.js grew a signInOnly mode: state.name is null, the step
Back buttons close instead of going to a wallet-choice step there is
no context for, and on wallet-loaded the flow writes siriusProfile
and shows a 'Signed in' confirmation instead of Fund -> Confirm -> Mint.
WC signInOnly keeps the session alive (state.session) so a later
record edit can reuse it without a fresh QR handshake.
profile-menu.js: menu items became <a data-action='new|import|wc'>
and the click handler lazy-loads register-flow.js on demand — the
docs/brand/theseus pages don't ship it in their initial payload, so
their nav pill loads it the first time a wallet button is clicked and
caches it for subsequent opens. Falls back to portal.html?mode=X if
the module can't load. Cache-buster bumped so cached copies pick up
the new behavior.
Importmap for @bitauth/libauth added to docs/, brand/, theseus/ so
the bundle's bare specifier resolves when register-flow.js is
lazy-loaded from those pages.
Verified end-to-end on the live docs page: all three dropdown items
open the modal inline at the correct step, no console errors, no
navigation.
Register.html and the landing were running duplicate name-search UIs.
The register one was reported broken; the landing one already works
inline. Merged both into the landing:
- New js/register-flow.js — the full mint modal + wallet setup (Create /
Import / Unlock / WizardConnect / Fund / Confirm / Register / Point
it somewhere) extracted from register.html's <script type='module'>
and turned into a shared module. Injects its own scoped modal HTML +
CSS into the host page on load and exposes
window.siriusRegisterName(fullName) as its public entry point.
- Landing search results now have Register buttons that call the shared
flow directly. The whole 'search -> pick name -> mint' journey stays
on one page, no redirects, no duplicate search UI to maintain.
- register.html reduced to a redirect stub: preserves ?q= if present,
refreshes/JS-forwards to './' (landing), and shows a one-line
'Name search moved to the home page' fallback for JS-off users.
Old links keep working; no /register.html deep links are broken.
- All nav 'Register' entries now point at './#search-input' (or
'../#search-input' on subpages), scrolling straight to the landing
search box. Footer injector and every internal href updated the
same way. Zero live href='.../register.html' left in the tree.
Verified: register.html?q=hello redirects to /?q=hello; landing
search 'fresh42abc' -> Register button -> modal opens with
'Register fresh42abc.bch' at the wallet-choice step.
Three UX fixes:
1. Beautiful footer on every page. Extracted the 4-column landing footer
into js/site-footer.js — reads the nav brand link to derive per-page
base ('./' at root, '../' in subdirs) so links resolve from any depth
without duplicating the HTML across 8 pages. Every page now ships an
empty <footer id="site-footer"></footer> and includes the injector;
inline footer + its CSS block removed from landing too so the source
stays in one place.
2. Wallet button redesign. Pill now shows a state dot (dim when not
signed in, green with subtle glow when signed in), a monospace short
address label when signed in ('qra6rs…7yl2') OR 'Wallet' when not,
a WC/seed badge on signed-in state, and a subtle caret. Full border
and hover states, proper aria-expanded/aria-label wiring. Dropdown
menu itself upgraded: grid layout per item (icon column + title +
hint), heavier backdrop blur, larger min-width, clearer typography.
3. Dropdown options actually do something. Removed the redundant 'Open
sign-in page →' item — the other three all land on portal too, so
listing 'open the page' as a fourth option was noise. On portal
arrival, the ?mode= handler now AUTO-TRIGGERS the primary action for
each mode:
new -> click Generate a phrase (12 words appear immediately)
import -> focus the seed textarea (cursor ready to paste)
wc -> click Connect wallet (WC session starts, URI shown)
No extra clicks between dropdown choice and the flow it names.
Cache-buster on the profile-menu.js include bumped to
?v=20260907wallet so cached copies pick up the new design; footer
injector at ?v=20260907rel.
Five UX fixes from a review pass:
1. profile-menu.js: dropdown links (New/Add/WC wallet, Admin) were
absolute /sirius-x/portal.html paths. That's fine on silentmode.st
but under the BCNR route (sirius.x/) the origin is different, so
the gateway forwarded to Sia which returned 'NoSuchKey' XML. Now
derive the base URL from the nav's own .portal anchor href — which
is already set per page with the right relative path — so it works
from the root, from subdirs, and under any BCNR gateway. Admin URL
is derived from the same base.
2. Landing hero got a search input. Submitting it normalises the
label ([a-z0-9-], 63 chars) and redirects to register.html?q=<label>.
register.html now honours ?q= on load: prefills the input, triggers
the parallel multi-TLD lookup, and scrolls into view. Single source
of truth stays in register.html; the landing just hands it a query.
3. Portal TLD-mint error path now special-cases 'wallet is empty' and
shows a friendlier message with the wallet's address and links to
two chipnet faucets, so the fix is one click away instead of a
guess.
4. Removed 'Pantheon' from every page's top nav — it's a section on
the landing that anyone scrolling will discover, and keeping it in
the nav crowded the bar.
5. Cache-buster ?v=20260907rel on the profile-menu.js script include
across all 7 pages so browsers that cached the pre-fix version
pick up the new one on next load.
Closes the loop between the nav's profile dropdown and portal.html:
- Adds a 🆕 New wallet card that calls BuiltInWallet.create() to generate
a fresh BIP-39 phrase in the browser. The phrase is shown once for the
user to write down; sign-in only unlocks after they tick the 'I have
written this down' acknowledgement. Copy button included.
- Sign-in (both New and Import paths) now writes localStorage.siriusProfile
= { address, tokenAddress, signedInAt } so any page on the same origin
can render 'signed in' state. Sign-out clears the key and also resets
the New wallet card so a re-sign-in starts clean.
- Handles ?mode=new|import|wc from the profile dropdown's deep-links:
scrolls the matching card into view and briefly outlines it in acid so
the user knows which one they were sent to.
profile-menu.js:
- Re-renders the dropdown on every open() call rather than caching the
first paint, so a sign-in that happens *after* the script's initial
run (same tab: portal.html; other tabs: storage event) reflects in the
nav without needing a full reload.
- Listens for the storage event (cross-tab) and a custom
'siriusProfileChanged' event (same-tab) — portal.html fires that on
every writeProfile/clearProfile call.
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.