BCH-teal #0AC18E is ~2.9:1 on white — great for filled backgrounds and
tints, marginal for small text. Introduce --acid-text (dark mode: same
as --acid, light mode: #253A49 BCH-dark = ~12:1 on white). Rewrote
every text usage in settings.html to var(--acid-text):
- .brand (⛓ Theseus in sidebar)
- .side a.active (selected section)
- .btn text (Check for updates / add engine)
- .engcat .cat .add:hover text
- inline <b style="color:var(--acid)"> in the Ariadne status + Update
status renders
Fills, borders, and background tints stay var(--acid) so the BCH-green
brand splash is preserved everywhere it works. Also drops the manual
.brand override in the light-media block — no longer needed since the
variable does the job.
Two spots where the light-mode swap didn't take effect because the
color was hardcoded to a dark-mode pastel yellow:
- .btn (used by 'Check for updates' and the engine 'Add' button) had
color:#eaffb0 — pale yellow tinted on top of the acid tint, invisible
on a white ground. Route through var(--acid) so light mode picks up
#0AC18E (BCH teal) with proper contrast.
- .brand ('⛓ Theseus' in Settings sidebar) was var(--acid). In light
mode that's #0AC18E on #f6f8fb — ~2.7:1 contrast. Swap to #253A49
(BCH dark navy) in the light-media block so it matches the Theseus
toolbar chip treatment and reads as the primary UI accent.
Same #eaffb0 sweep applied to the two chrome.html spots that used it
(upchip download button, bcnrbar open button). Dark mode unchanged.
Ariadne 0.1.1
- install.ps1 primary TLD source: HTTPS $Indexer/api/tlds -> advertised[]
(electrum fetchTldList / legacy fetchBcnrTlds / ariadne.config.json /
bootstrap kept as fallbacks in that order). Silent installs no longer
stall when WSS is blocked.
- Persistent transcript at C:\ProgramData\Ariadne\install.log; separate
install-error.log on any uncaught exception. Top-level trap logs the full
stack, then exit 1 so Inno/NSIS silent-install callers see a real failure.
- .iss: SetupLogging=yes; DeinitializeSetup() copies Inno's log to
C:\ProgramData\Ariadne\inno-setup.log on every exit path.
- Version bumped to 0.1.1. sha256=be1fa8841919b321841d90f8147abe1b1d7604fe7fff6c2e271dc47c4c24a2b6
Theseus decoupled from Ariadne
- Removed nsis/installer.nsh (custom Ariadne chain-install page).
- Removed extraResources entry for AriadneResolver-Setup-*.exe.
- Removed build.nsis.include from package.json.
- Settings > System-wide resolver panel now fetches the .exe from
https://silentmode.st/releases-manifest.json, streams the download while
hashing, refuses to spawn on SHA-256 mismatch. 30-min in-memory manifest
cache so opening Settings doesn't spam the endpoint. Offline gracefully
degrades to installedVersion-only / canUpdate:false.
- "bundledVersion" IPC field kept for renderer compatibility; it now
carries "latest advertised by the site's manifest".
Deployed to silentmode.st:
- dl.silentmode.st/AriadneResolver-Setup-0.1.1.exe (25 MB)
- silentmode.st/releases-manifest.json (schema/2, updated 2026-09-08)
- silentmode.st/releases/ + silentmode.st/tools/ show 0.1.1
Local Dev <noreply@localhost>
Setup 9dfd5a6ceb36b7d34216cfae3bff58c612c641cefbc9cd784182fdb1797d0e95
Portable 222628d481d4f46b2ebfd0bbd104cbafb27c125bfc7fc64cb216c04f871c72d4
Two bundled changes since 0.3.22:
7893635 - Light-mode --acid → #0AC18E (Bitcoin Cash primary from
whybitcoincash.com's palette). User-Agent stripped of theseus-navigator/
and Electron/ tokens so Cloudflare's WAF stops returning HTTP 503 on
sites like whybitcoincash.com; Theseus now identifies as vanilla
Chrome (same practice as Brave / Vivaldi / Slack).
89b8485 - Full acid sweep: every hardcoded #d6ff3d and rgba(214,255,61,X)
in the chrome + every addon panel now routes through var(--acid), so
the light-mode swap actually takes effect everywhere. Theseus button
(.logo) + update chip (.upchip) become dark BCH-navy #253A49 chips in
light mode so the toolbar has a visible accent surface instead of
white-on-white.
Deployed. Verified LIVE 0.3.23.
Two follow-ups on the light-mode acid work:
1) Every hardcoded #d6ff3d and rgba(214,255,61,X) in the browser
chrome and every addon panel now goes through var(--acid), so the
light-mode BCH-teal (#0AC18E) takes effect everywhere — not just
where var(--acid) was already used. Hex-with-alpha (#d6ff3d55 etc.)
converts to color-mix(); rgba() converts to rgb(from var(--acid)…)
for the same alpha with the current --acid hue. Chromium 128+
supports both. Files touched: chrome / settings / error / home /
approval / bchwallet / siawallet / screenshot (html + css).
Screenshot editor.js's #d6ff3d stays — that's the drawing colour
swatch, not UI chrome.
2) The Theseus button (.logo) and update chip (.upchip) become dark
BCH-navy chips (#253A49 background, #F8FDFF text) in light mode.
Previously the .logo hardcoded #d6ff3d text on a bright-acid tint —
invisible on a light toolbar. The dark chip stands out and gives
the light theme a distinct accent using the BCH secondary from
whybitcoincash.com's palette.
Two related visibility fixes:
1) Light-mode --acid → #0AC18E (Bitcoin Cash brand primary, from
whybitcoincash.com's palette per user). Direct swap from #088A66
(darkened variant) to the on-brand primary. Applied across chrome /
settings / error / home / approval / messages / bchwallet /
siawallet / screenshot editor. Dark mode's #d6ff3d is unchanged.
2) User-Agent no longer includes 'theseus-navigator/<ver>' or
'Electron/<ver>' tokens. Cloudflare's WAF was returning HTTP 503
'Service Unavailable' to any request carrying those (verified
directly against whybitcoincash.com — same URL, same headers, only
the UA differed; plain Chrome UA got 200, Theseus UA got 503).
Strip both tokens via a stockChromeUA() helper called from
applyAcceptLanguage(), which whenReady already invokes at boot.
Standard practice: Brave, Vivaldi, Slack all do the same.
Verified via CDP: navigator.userAgent now reports
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/130.0.6723.191 Safari/537.36
— indistinguishable from stock Chrome.
Setup fc779af12d55224e3d5eafdde3feabfc616b56b2737f953e7ecb32ef90461cea
Portable 046526f2e73c2026d3243ca9e4639374631cc66924b247feb92f224c9520af4b
Two bundled changes since 0.3.21:
a0c96fe - Light-mode --acid swaps from #3a5c00 (olive) to #088A66, a
darker variant of Bitcoin Cash's #0AC18E primary. AA-passing on white
(~5:1) AND reads as 'Bitcoin Cash green' instead of an off-brand
olive. Applied across chrome / settings / error / home / approval /
messages / bchwallet / siawallet / screenshot editor. Dark mode's
#d6ff3d is unchanged.
4dacd8f - Settings > General > Updates card gains a Check for updates
button. Un-dismisses any lingering session chip and re-fetches the
release manifest immediately (rather than waiting for the boot / 6h
auto-check). Renders 'You're on the latest (v0.3.22)' or 'vX.Y.Z is
available — the update chip will offer it'.
Deployed. Verified LIVE 0.3.22.
Prior light-mode --acid was #3a5c00 (dark olive-green) — legible but
off-brand. The Bitcoin Cash brand primary is #0AC18E (a teal-leaning
green already used in bchwallet's --bch variable). Darken it a step to
#088A66 for AA text contrast on white (~5:1) while staying in the BCH
family — the light-mode accent now reads as "Bitcoin Cash green,
darkened for legibility" instead of an arbitrary olive.
Applied across every chrome page + addon panel that carries the light
override (chrome / settings / error / home / approval / messages /
bchwallet / siawallet / screenshot editor). Dark mode's #d6ff3d
untouched.
New "Updates" card under Startup: a single "Check for updates" button
that hits the release manifest immediately (rather than waiting for
the boot-time and 6h-interval auto-check). Reuses the existing
recheck-update IPC; extended it to un-dismiss any chip the user closed
this session AND to return the current app version so the button can
render either:
- "You're on the latest (v0.3.21)." when nothing newer exists
- "vX.Y.Z is available — the update chip in the toolbar will offer it."
Button disables + shows "Checking…" during the fetch. Silent failures
report their reason ("Check failed: …") so the user isn't left staring
at a dash.
Setup a2e25a80ad2300e79cf5e7de1337bbc986b48e6bda9c13f497caf5b66c90edb9
Portable 173d376aa23e6cb28f8a353206a15144f8a04d9817f531902b4ba93039101742
One bundled change since 0.3.20:
509b32f - Settings > General drops its Toolbar dropdowns (drag handles
have covered that job since 0.3.15). Registries is folded into General
as a subheading: collision policy + reset-remembered + full Ariadne
card (Turn on/off / Install / Update / Uninstall / Refresh). Sidebar
entry 'Registries' removed. Underlying settings + IPC unchanged.
Deployed. Verified LIVE 0.3.21.
Two housekeeping changes to Settings > General:
- Toolbar block (Address bar size + Search box size selects) removed —
the toolbar drag handles landed in 0.3.15 do the same job in-place,
and the discrete presets were duplicating that. Underlying settings
keys and drag-set widthPx values still live in main; the UI dropdown
was the only thing gone.
- Registries section merged into General as a subheading. Sidebar
navigation entry "Registries" removed; the naming section is now
reachable from Settings > General. Collision-policy radios + reset-
remembered-choices + Ariadne install/turn on/off/uninstall/update
card all move as-is. No IDs changed, so the JS wiring (radios,
ariadne buttons, collision summary) rebinds against the same nodes.
The naming section stub stays commented so the section-nav JS doesn't
crash if it looks up the old id.
Setup 8bc234a69534b7259b059cafab8c93615d41fc804fcdff1144b691c3b3ac9477
Portable e3b0ab8ed62f6172288a9511ff1ecb4e50d7ef9effb06ed4b728e2e7d467a55b
One bundled change since 0.3.19:
763c68d - Home page search box no longer hardcodes 'DuckDuckGo' in the
placeholder. New window.home.getEngines() IPC pulls the current default
from settings; placeholder starts as 'Search the web' and populates
with 'Search the web with <engine>' on load. Fallback URL (never fires
in normal use) swapped from duckduckgo.com to startpage.com to match
Theseus's own default engine.
Deployed. Verified LIVE 0.3.20.
Bundled screenshot addon bump:
- version 0.2.1 → 0.2.2
- palette grows from 5 to 8 colors: adds Orange (#ff9500), Blue
(#0a84ff), Purple (#bf5af2) alongside acid/red/yellow/white/black —
common annotation colors that were conspicuously missing
- updateURL swings from the aspirational addons.silentmode.st (which
never resolved) to the live gateway URL
https://navigate.st/bns/theseus.x/extensions/screenshot/updates.json,
where the operator's first signed update entry is now published
The gateway URL is deliberate over the bare `theseus.x/...` form: the
add-on updater runs from Node's main-process https module, which uses
the OS resolver. On installs without Ariadne's Thread the OS can't
resolve theseus.x (BNS-only TLD), so the poll would silently fail;
the navigate.st gateway resolves via standard DNS and forwards to the
same BNS-backed Sia content, so every install reaches the endpoint.
First signed update is live at:
https://navigate.st/bns/theseus.x/extensions/screenshot/updates.jsonhttps://navigate.st/bns/theseus.x/extensions/screenshot/screenshot-0.2.2.tar.gz
signed 59a35370fdbc9d1e24834fa26c7765d27e8763fe928bfa23b202ca666a6a6973
by the ops key baked into 0.3.19. End-to-end verified via
scratchpad/decoupling-test/verify-live.mjs against the live endpoint:
fetch, sig-verify, download, sha-verify, extract, stage, promote,
backup — all pass.
Installs polling the previous updateURL (addons.silentmode.st) get
this new URL only after their bundled copy is refreshed to 0.2.2,
which means either a Theseus release with 0.2.2 bundled (0.3.20+) or
a signed update at the old URL that carries the URL change (impossible
because addons.silentmode.st doesn't resolve). Ship a Theseus release
that bundles this 0.2.2 to activate the update path on existing
installs; from then on the endpoint self-perpetuates via the theseus.x
URL.
Home page's search-box placeholder was hardcoded "Search the web
(DuckDuckGo)", but the browser's default engine has been Startpage
for a while and any user can switch to another in Settings. The
"DuckDuckGo" claim then contradicted the actual engine that would
run the query (main.js routes through settings.searchEngine).
Fix: placeholder starts as plain "Search the web" and populates on
load with "Search the web with <engine.name>" via a new
window.home.getEngines() IPC (reuses the existing search-engines
handler). Silently no-ops if the preload isn't bound.
Also swapped the never-fires no-preload fallback URL from
duckduckgo.com to startpage.com so it matches Theseus's own default.
Setup 4830da019914019c5d3575420b490a278bc3ba63606680f84c30d58de5ff9500
Portable 1ef76b5b0fae9ac5a201956f6f3da55b1f1ceec8a035cc0d498147a210491b5f
One theseus change since 0.3.18:
7672ce0 - Signed add-on update endpoint activates. The Silent Mode
operator Ed25519 pubkey (generated 2026-09-07,
732b1263a236b0030383a2376597cfa43c3624b3ca2912a46134f8f2a06e6012)
is baked into addon-update-pubkeys.js, so on every boot Theseus polls
each installed add-on's updateURL 30 s in, verifies the signed
updates.json against the pubkey, and stages any newer signed version
under <userData>/addons-updates-staged/ for promotion on the next
launch. Verification, backup, and promotion mechanics unchanged from
0.3.18. The screenshot add-on already advertises
https://addons.silentmode.st/screenshot/updates.json; publishing a
signed entry there is what activates real updates. No entry is
published yet, so this build's boot-time fetch fails silently until
the operator lands the first signed payload via
scripts/sign-addon-update.mjs.
3be152a - Also in this ship: package-lock.json resynced with
package.json. The 0.3.18 ship inadvertently committed WIP dependency
additions (bitcoinjs-lib, bip32, bip39, ecpair,
@bitcoinerlab/secp256k1) via git commit -o's file-scoped semantics
without a matching lock update. Fresh clones now build cleanly with
npm ci. Deps are unused by shipped code at this time but ride along
in node_modules — installer size grew ~400 KB.
Deployed. Verified LIVE 0.3.19.
The 0.3.18 ship (423831f) picked up WIP dependency additions
(bitcoinjs-lib, bip32, bip39, ecpair, @bitcoinerlab/secp256k1) from
the working tree via git commit -o's file-scoped semantics, but the
matching package-lock.json update was left uncommitted. That leaves
master in a state where `npm ci` refuses to install (lock and
manifest disagree) and any fresh clone can't be built without an
`npm install` regeneration first. Committing the in-tree lock puts
them back in sync.
Populates addon-update-pubkeys.js with the Silent Mode ops Ed25519
pubkey generated 2026-09-07. From this build forward, Theseus polls
each installed add-on's updateURL 30 s after boot, verifies the
signed updates.json, and stages any newer version for promotion on
the next launch. The screenshot add-on's addon.json already
advertises https://addons.silentmode.st/screenshot/updates.json;
publishing a signed entry there (via scripts/sign-addon-update.mjs
with the ops private key) is what activates real updates.
No updates.json is published yet, so the client's boot-time fetch
will 404/DNS-fail silently until the operator lands the first signed
entry.
SPL tokens now show up in the Solana wallet — balances on the Receive
card, an asset picker on Send that flips the amount input into the
token's own units. Sends build a TransferChecked + auto-create the
recipient's Associated Token Account (idempotently) in the same
transaction, so the user never has to fund an ATA by hand.
- lib/sol-spl.js: SPL primitives that don't need @solana/web3.js.
TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID,
findProgramAddress (PDA loop backed by an ed25519 is-on-curve check
via @noble Point.fromBytes), associatedTokenAddress (matches the
spl-token JS seed layout: [owner, tokenProgram, mint]),
transferCheckedInstruction (discriminator 12, u64 amount, decimals
byte), createATAIdempotentInstruction (associated-token program
discriminator 1). A small known-mint registry ships inline for USDC /
USDT / wSOL on mainnet + USDC on devnet — everything else falls back
to a truncated mint address in the UI.
- Message assembler classifies every unique pubkey into writable-signed
/ readonly-signed / writable-unsigned / readonly-unsigned, sorts the
fee payer first, and serializes header + accountKeys + blockhash +
instructions using Solana's compact-u16 short-vec encoding. Same
wire shape @solana/web3.js produces from Transaction.serializeMessage.
- lib/chain-sol.js: snapshot() now carries a tokens[] array of
{mint, symbol, name, decimals, balance, tokenAccount, tokenProgram,
isKnown, isToken2022}. Fetched via getTokenAccountsByOwner against
both the classic Token program and Token-2022. New planTokenTransfer
+ signAndBroadcastToken handle a full send (TransferChecked +
optional CreateATAIdempotent) in one wire.
- Panel: Send tab gained an Asset dropdown (SOL / <each token>) that
only shows for SOL wallets with tokens. Picking a token flips the
unit picker's big-unit to the token symbol, amount goes in the
token's own decimals, planTokenSend + sendToken take over from
planSend/send. Receive tab gained a Tokens card listing each SPL
balance with a per-row Send button that pre-fills the asset picker.
- Verified in scratchpad: ATA derivation runs the PDA loop
correctly (owner pubkey passes isOnCurve, derived ATA does not —
the definitional property of a Program-Derived Address). Cross-check
the ATA for any (owner, mint) on Phantom / Solscan / spl-token JS
and the value matches.
Known limits:
- No token metadata lookup on-chain — mints outside the built-in
registry show up with a truncated mint address as symbol. Wiring
Metaplex Metadata program reads would let unknown tokens show
their real names.
- Send is single-signer only (the wallet is the fee payer, sender
and sole required signer). Multi-sig SPL transfers work via the
dapp bridge (window.solana.signAndSendTransaction, which already
handles partial signatures).
Setup b751b5deea997bb7e0e894104dcb7411358728987196cb610f316edacaf9be54
Portable 8a258f29c62a03b745e93ea0265285e6d5df38d4f25cdbdadc20b4d2a54b97d7
Five theseus fixes since 0.3.16:
e90062a - Bundled add-on updates now actually land. seedBundledAddons()
previously copied a bundled add-on only when its target folder was
missing, so the 0.3.14 screenshot editor never reached machines that
already had an older screenshot/ folder from a previous run — Theseus
quietly kept using the stale copy. The seeder now compares bundled and
on-disk addon.json versions and reseeds with a timestamped backup
under <userData>/addons-backups/<id>-<oldver>-<timestamp>/.
ecfd481 + 6117429 - Signed add-on update endpoint, à la Firefox XPI.
An add-on can now advertise an updateURL in its addon.json and be
republished at any time without waiting for a Theseus release. The
client fetches, verifies an Ed25519 signature over
"silentmode.addon-update-v1|<id>|<version>|<tarball-sha256>",
downloads the tarball, verifies the hash, and stages the new copy
under <userData>/addons-updates-staged/ for promotion on next launch.
Dormant in this build — the shipped addon-update-pubkeys.js is empty,
so checkAndStageUpdates() short-circuits and makes no outbound
requests; the feature activates when an operator ceremonies a key in
and ships a follow-up release with the pubkey baked in. Operator
tooling in scripts/generate-update-keypair.mjs and
scripts/sign-addon-update.mjs; full brief in docs/ADDON-UPDATES.md.
End-to-end verified against a local HTTP server: sign, serve, fetch,
verify, download, extract, stage, promote, backup — plus signature
tamper, wrong pubkey, sha256 tamper, and empty-pubkey short-circuit
all rejected as expected. 15/15 checks pass.
bfe5132 - Tab-switch flash is gone. Two independent causes: (a) tab
views were created without an explicit background color, so the first
frame after setVisible(true) showed whatever was underneath the view
until the page painted; a solid theme-tracking ground fills the gap.
(b) setActive iterated tabs in list order, so if the outgoing tab
came before the incoming in the array, the loop hid the outgoing
first and left one frame where no tab was visible; the incoming is
now shown before any hides.
a5a667d - Screenshot toolbar-menu captures no longer come out blank.
The click handler dispatched capture synchronously while the native
Menu.popup window was still on top, marking the tab view occluded and
letting WebContents.capturePage() snapshot a stale/empty compositor
frame at the correct dimensions (which the existing 0x0 retry
couldn't detect). Dispatch now runs from the popup's close callback
after a 120ms settle so the parent window is foreground and the
compositor is live at capture time.
a5a667d also - Ariadne — Install / Update / Uninstall alongside Turn
on / off. The Ariadne toggle card in Settings > Registries grows
three lifecycle actions. Install and Update run the bundled
AriadneResolver-Setup-<ver>.exe silently and elevated (/VERYSILENT
/SUPPRESSMSGBOXES /NORESTART — one UAC prompt, no wizard); Update is
only visible when the bundled version is newer than what's installed.
Uninstall reads Inno's QuietUninstallString from HKLM registry and
runs it elevated. Status surfaces installed version + bundled version
so the user can see what's on disk vs what would land next; buttons
disable during work and refresh after both success and failure so the
UI never lies.
Deployed. Verified LIVE 0.3.18.
Extends the Ariadne toggle card in Settings > Registries with the three
lifecycle actions the user asked for:
- Install: runs the bundled AriadneResolver-Setup-<ver>.exe silently
and elevated (/VERYSILENT /SUPPRESSMSGBOXES /NORESTART). Single UAC
prompt, no wizard.
- Update: same installer, run over the top. Inno Setup detects the
matching AppId and upgrades in place. Only shown when the bundled
version is newer than what's installed.
- Uninstall: reads Inno's QuietUninstallString from
HKLM\...\Uninstall\{7E7A5F1C-...}_is1 and runs it elevated with
/VERYSILENT /SUPPRESSMSGBOXES /NORESTART.
Status now surfaces the installed version + bundled version so the
user can see what's on disk vs what would be installed. Three new IPC
handlers: ariadne-install / ariadne-update / ariadne-uninstall. Every
button disables during work and shows a busy label; refresh runs
after success OR failure so the UI never lies.
Version compare + registry read live in main; both the WOW6432Node and
native uninstall paths are checked so the query works regardless of
which architecture bit Inno picked.
An end-to-end drive of the update flow against a local HTTP server hit
two Windows-only tar quirks that a first-cut MVP wouldn't catch:
1. Git-Bash tar (MSYS2), which comes first on PATH when Git-for-Windows
is installed, treats drive-letter paths as `host:file` remote-archive
syntax. Sidestepped with --force-local (also silently accepted by
Win10's built-in bsdtar and by GNU tar).
2. Even with --force-local, MSYS2's argv-conversion layer mangles
backslashes in Windows paths, so `C:\Users\...\tmp\dir` arrives at
tar as `C:\Users...\dir` and it can't open the path. Passing
forward-slash paths (`C:/Users/.../tmp/dir`) dodges the mangler;
bsdtar and GNU tar accept them as-is.
3. sign-addon-update.mjs was tar'ing the addon directory as a subfolder
(`screenshot/addon.json` inside the archive), so the client
extracted to `<tmp>/screenshot/` and then failed the id+version
re-check because addon.json wasn't at the root. Now the signer
tars the CONTENTS of the addon dir via `tar -C <addon-dir> .`, so
entries live at the archive root where the client expects them.
All three surfaced from `scratchpad/decoupling-test/run-test.mjs`, which
now walks the full path — sign, serve, fetch, verify, download,
extract, stage, promote, backup — plus three signature-tamper negatives
and the empty-pubkey short-circuit. 15/15 checks pass.
Aegis now handles the standard MetaMask try-switch-then-add flow. A dapp
that wants to route through Polygon (or Base, or Arbitrum, or any other
EVM the Silent Mode user hasn't added yet) calls the pair the industry
already wrote for it — Aegis registers the chain, provisions a wallet on
it under the same vault seed, auto-connects the origin, fires
chainChanged, and hands the dapp back a provider pointed at the new
chain. No sidebar detour, no Custom RPC copy-paste. Users still see
every chain in the picker post-add and can revoke sites in Settings.
- lib/chain-eth.js: EthWallet accepts a customNetwork override
({id, label, chainId, defaultRpc, explorerTx, explorerAddr, ticker}).
When present it replaces the NETWORKS lookup so mainnet+Sepolia
ship built-in and every EIP-3085 chain is a runtime override the
addon persists. The ticker flows into snapshot() so the send
approval reads MATIC / BNB / whatever the chain's native currency is,
not a hardcoded ETH.
- index.js customEthChains storage: `{[chainId]: {chainName, rpcUrl,
explorerTx, explorerAddr, ticker, addedAt, addedByOrigin}}`.
Persisted under api.storage.customEthChains, so an added chain
survives Theseus restarts. chainMeta("eth", "custom-<chainId>")
synthesizes the meta from storage so the panel renders custom
chains without needing them in COINS at module-load time.
- eth.addChain handler (EIP-3085): approval overlay shows chain
name, decimal + hex chain id, native ticker, RPC and explorer
URLs (the phishing-signal quartet). On approval, persist config +
create wallet with a custom-<chainId> network + auto-grant the
origin readAddress on this chain. No-op success if the chain is
already added.
- eth.switchChain rewritten to be EIP-3326 correct: look up any
ready ETH wallet whose adapter reports the requested chainId,
make it the selected wallet, fire chainChanged. When no wallet
matches, throw with .code = 4902 (the standard 'chain not
added' code) so wagmi / RainbowKit / any 3326-aware dapp does
the fallback wallet_addEthereumChain call in the same click.
- eth.state handler: cheap {address, chainIdHex, networkVersion}
peek for the origin's currently-connected wallet (no approval,
no key access). The main-world bridge calls it after every
switch/add to emit chainChanged + accountsChanged locally — the
events MetaMask fires and RainbowKit listens for.
- wallet-inject.js: routes wallet_addEthereumChain via
eth.addChain, preserves the 4902 code across the postMessage
boundary on switch failures, calls pullEthStateAndEmit() to fire
the post-switch/add events.
Two follow-ups to the dapp bridges. Both change wire shape only — no new
UI, existing wallets keep signing byte-identically for the flows they
already covered.
- lib/eip712.js: full EIP-712 typed-data encoder — encodeType with
alphabetically-sorted transitive sub-types, typeHash, encodeValue for
string / address / bool / uint*/int* (any width) / bytes / bytesN /
nested structs / dynamic and fixed arrays, hashStruct recursion,
digest = keccak256(0x19 || 0x01 || domainSeparator || hashStruct).
Verified against the spec §"Ether Mail" test vector — hashStruct on
both the domain and the message plus the final digest all match the
canonical values byte-for-byte (see scratchpad/verify-eip712.mjs).
- chain-eth.js: exposes signTypedDataDigest(digest32) that signs the
precomputed digest with r||s||v (v = 27+recid), the same envelope
personal_sign uses. Aegis computes the digest server-side (in the
addon) so a bug in the encoder can't be tricked by a malicious dapp
into signing over data the user never saw.
- index.js: eth.signTypedData handler shows domain (name · version ·
chainId), primary type, and a truncated JSON preview of the message
in the approval overlay — every classic phishing signal (mismatched
domain, unexpected primary type) is in front of the user before they
hit Sign. Accepts either an already-parsed typedData object or the
JSON-string form older MetaMask specs used.
- wallet-inject.js router: eth_signTypedData_v4 (and _v3 for the same
payload shape) route to eth.signTypedData. v1's flat "type[]" form
is unwired — dapps that still use v1 should upgrade.
- Solana signAndSend: bridge now passes the FULL wire (from
tx.serialize({requireAllSignatures:false, verifySignatures:false}))
instead of just the message. The addon parses compact-u16 signature
count, finds this wallet's pubkey in the message's account-key list,
signs the message, and patches ONLY its own slot in the signature
array — any partial signatures the dapp had already filled with
tx.partialSign() (session keys, escrow co-signers, permissioned
authorities) are preserved. Multi-signer flows work now; single-signer
is the degenerate case of sigCount=1.
- Approval overlay for sol.signAndSend now shows required-signer count
and the wallet's slot index so multi-signer requests are visibly
distinct from a plain single-signer send.
Two independent causes of the flash the user reported when clicking
between tabs (and when opening Settings, which is just another tab):
1) Every tab view was created without an explicit backgroundColor.
Electron's default is transparent, which means the first frame after
setVisible(true) shows whatever is underneath the view — black, or
the just-hidden previous tab — until the page paints. Set a solid
ground that tracks the system theme (#0b0e14 dark / #ffffff light)
so the first-paint gap is invisible.
2) setActive iterated tabs and toggled visibility in list order. If
the currently-active tab came before the new active in the array,
the loop hid the active one first and showed the new one later,
leaving one frame where no tab was visible. Reverse: show the new
target FIRST, then hide the rest. Compositor always has at least
one tab view up during the switch.
Aegis now integrates with the two dapp-wallet APIs the wider ecosystem
actually uses — MetaMask-style window.ethereum for Ethereum, Phantom-style
window.solana for Solana — plus BTC signet as a third Bitcoin network
alongside mainnet + testnet3.
- wallet-inject.js: adds a main-world bridge, installed via a one-shot
<script textContent=…> appended to <head> and immediately removed.
Electron's contextBridge shallow-copies args and strips methods, which
means BCH- and Tron-shaped params (plain data) work in the isolated
world but Solana's wallet-adapter dapps — which pass @solana/web3.js
Transaction objects and expect .serializeMessage()/.addSignature() to
fire on them — need code that lives in the same world as the dapp.
Bridge talks back to the isolated world via window.postMessage on a
namespaced envelope (aegisTag = "aegis-" + addonId), which forwards to
theseus.invoke. Same pattern MetaMask + Phantom use.
- window.ethereum (EIP-1193): request({method, params}), on(),
removeListener(), chainId, networkVersion, selectedAddress. Handles
eth_requestAccounts, eth_accounts, eth_chainId, net_version,
personal_sign, eth_sign, eth_sendTransaction, wallet_switchEthereumChain
(rejects with "use the Aegis picker"), wallet_addEthereumChain
(rejects, chains come from Settings), wallet_get/requestPermissions.
Every other eth_*/net_*/web3_* method passes through to the wallet's
configured RPC via a new eth.rpc handler. EIP-6963 announceProvider
event fires so wagmi / RainbowKit / any 6963-aware dapp discovers
Aegis alongside MetaMask instead of racing for window.ethereum.
- window.solana (wallet-adapter shape): connect(), disconnect(),
publicKey (with toString/toBase58/toBytes/equals — the PublicKey
interface dapps check), signMessage(u8) → {publicKey, signature: u8},
signTransaction(tx) → mutates + returns the same tx with the
signature added, signAndSendTransaction(tx) → returns {signature: txid},
signAllTransactions([tx]), request({method, params}). isPhantom flag
set true so dapps that gate on it pick us. on/off events for connect
/ disconnect / accountChanged.
- Handlers in index.js registerPageMessages: eth.requestAccounts,
eth.personalSign, eth.sendTransaction, eth.switchChain, eth.rpc,
sol.connect, sol.signMessage, sol.signAndSend. Every write path is
per-origin gated + goes through api.approvalModal with the wallet
label + network in the row list so the user always knows which
Aegis wallet is about to sign.
- Signet added to chain-btc.js — signet shares testnet3's address
format and SLIP-44 coin type (BIP-325 only changed consensus/signing),
so bitcoinjs-lib.networks.testnet handles derivation unchanged. Only
the electrum pool (aranguren + wakiyamap) + explorer (mempool.space
/signet) + faucet (signetfaucet.com) differ. Registered as
btc:signet in COINS with per-network coinType lookup.
Known limits (follow-ups in the same shape as existing chains):
- SOL signAndSendTransaction is single-signer only; dapps that combine
the wallet's sig with co-signer sigs need the wire assembled on the
dapp side.
- ETH eth_signTypedData_v4 (EIP-712) is not wired — the handler set
covers personal_sign only.
Decouples bundled-add-on updates from Theseus releases. An add-on
whose addon.json declares an updateURL can be republished at any time
without shipping a new Theseus installer; existing installs pick it up
on the next boot's +30 s background check.
Client flow (main-process only, no UI touchpoints in this commit):
initAddons()
├── promoteStagedUpdates() # promote signed stage if newer
├── seedBundledAddons() # bundle wins over on-disk if newer
└── AddonHost.discoverAndActivate()
30 s later:
└── checkAndStageUpdates() # fetch, verify, download, stage
Signature: Ed25519 over
"silentmode.addon-update-v1|<id>|<version>|<tarball-sha256>",
verified against a hardcoded set of operator pubkeys living in
addon-update-pubkeys.js. Domain-separated so the operator key can't
be tricked into signing a message with a different purpose. Empty
pubkey array is the shipping default — checkAndStageUpdates() then
short-circuits and no outbound requests are made, which is the safe
posture until the operator ceremonies a key in.
Payload: gzipped tar, extracted with the system tar (present on
Win10 1803+, macOS, Linux). Path traversal defended by tar's default
refusal of `..` entries; the extracted manifest's id + version are
re-checked against the signed values before staging.
Staged updates go to <userData>/addons-updates-staged/<id>-<version>/.
Promotion into <userData>/addons/<id>/ reuses seedBundledAddons's
backup dance: existing folder moves to
<userData>/addons-backups/<id>-<oldver>-<timestamp>/ so any local
edits survive.
New files:
- addon-updater.js — client
- addon-update-pubkeys.js — hardcoded pubkeys (empty; edit + rebuild to rotate)
- scripts/generate-update-keypair.mjs — one-time keygen
- scripts/sign-addon-update.mjs — operator packager+signer
- docs/ADDON-UPDATES.md — operator brief + threat model
Wired into main.js at boot; screenshot add-on's addon.json advertises
the reference updateURL for when the endpoint goes live.
Aegis's Bitcoin adapter can now sign transactions from every BIP44/49/84/86
address it derives. Receive already worked on all four in the previous rev
— this closes the send side.
- BIP44 (legacy P2PKH, 1…): signAndBroadcast now fetches each spent UTXO's
parent transaction via blockchain.transaction.get(txid, false) and hands
the raw hex to PSBT as nonWitnessUtxo. Prev-tx calls fan out in parallel
with Promise.all so a multi-input legacy send doesn't serialize the wait.
- BIP86 (Taproot key-path, bc1p…): signInput now uses a tap-tweaked
signer — the internal ECPair, tweaked with sha256("TapTweak" ||
internalPubkey) via ECPair.tweak(). bitcoinjs-lib matches the tweaked
pubkey against the on-chain output key and signs with schnorr. The
input carries tapInternalKey so the PSBT layer knows it's a key-path
spend (no leaf script).
- The plan-time "not yet in this rev" refusal is gone. paymentFor()
returns send: "p2pkh" / "p2tr" for the two families; every path in
the picker signs today.
- Fee vsize model already covered p2pkh (148 vB per input) and p2tr
(58 vB per input) — unchanged.
- Verified in scratchpad/verify-btc-send.mjs: all four families
produce a fully-finalized wire tx (bitcoinjs-lib refuses to
finalize an invalid signature, so a valid extractTransaction()
result is proof the signing path is correct). Vsize per family:
BIP44 222 vB, BIP49 165 vB, BIP84 141 vB, BIP86 142 vB — all
match the input-count/vsize model in this file's fee estimator.
BTC now matches DGB's family selector: pick BIP44 (1…), BIP49 (3…),
BIP84 (bc1q…, default) or BIP86 Taproot (bc1p…) from Settings, on
mainnet or testnet3 (paths shift coin type 0 → 1 automatically).
- lib/chain-btc.js: paymentFor(purpose, node, network) returns the
right bitcoinjs-lib payment (p2pkh / p2sh(p2wpkh) / p2wpkh / p2tr)
keyed off the derivation path's purpose. WalletKeys.entry captures
the family, redeem script (BIP49) and internal x-only pubkey
(BIP86) alongside the standard script/address fields.
bitcoinjs.initEccLib(ecc) is called once at load so p2tr resolves.
- Registry: BTC + DGB address families are purpose-only now; a
helper (addressFamiliesFor / defaultAccountPathFor) computes the
concrete m/PURPOSE'/COIN'/0' per (chain, network) — coin type
{mainnet:0, testnet:1} for BTC, always 20 for DGB. chainMeta
expands the list so the panel doesn't need per-chain knowledge.
- Panel: #btcSettings block mirrors #dgbSettings (family select →
path input auto-fill → Apply). The family-select listener + the
fillFamilyPicker() helper are shared between DGB and BTC — the
DOM prefix is the only per-chain input.
- Send is wired for BIP84 (default) and BIP49 (adds redeemScript to
the PSBT input). BIP44 (needs nonWitnessUtxo prev-tx fetch) and
BIP86 (needs tap-tweaked signer) throw a clear "not yet in this
rev — sweep to BIP84" error so users hit it at plan time, not at
broadcast time. Receive works on all four families today.
- Verified all four families derive the canonical BIP44/49/84/86
spec test vectors for the standard abandon×11 mnemonic — see
scratchpad/verify-btc-families.mjs. Byte-identical to the BIPs.
Seven coins across twelve networks now — BTC joins the shipping roster.
- lib/chain-btc.js: BIP84 native SegWit — m/84'/0'/0'/0/x → bc1q…
(mainnet), m/84'/1'/0'/0/x → tb1q… (testnet3). Reuses the exact same
stack the DGB adapter already pulls in: bitcoinjs-lib for network
params + payments.p2wpkh + PSBT, bip32 for HD derivation, ecpair for
the Signer interface, ecc (@bitcoinerlab/secp256k1) for message-sign
recoverable sigs. No new npm deps.
- Backend: same lib/electrum.js Aegis uses for BCH and DGB — plugged
into a public Bitcoin ElectrumX pool (blockstream.info, lu.ke,
grey.pw) for mainnet and aranguren.org / blockstream.info:993 for
testnet3. Send flow: PSBT build + per-input signInput +
finalizeAllInputs + broadcast. BIP-137 recoverable message signing.
- Registered as btc:mainnet + btc:testnet in COINS with the orange
Bitcoin disc SVG logo. Mount case mirrors DGB (accountPath honored,
so switching to m/44'/0'/0' or m/49'/0'/0' via the setAccountPath
message gives legacy 1… or wrapped-segwit 3… — same one-line UI plumb
as the DGB address-family selector, deferred to a follow-up).
- Panel: sat as the small-unit label, bitcoin: BIP21 QR payload,
chain-aware send placeholder ("bc1q…" mainnet / "tb1q…" testnet).
- Verified: BIP84 spec test vector — abandon×11 mnemonic derives
bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu at m/84'/0'/0'/0/0
(byte-identical to the vector in the BIP text). Testnet variant
produces tb1q6rz28mcfaxtmd6v789l9rrlrusdprr9pqcpvkl at m/84'/1'/0'/0/0
(cross-checkable on iancoleman.io/bip39 with coin BTC Testnet).
seedBundledAddons() only copied a bundled add-on when the target folder
was missing, so an updated bundled add-on never landed on any machine
that had ever run Theseus before — the 0.3.14 shipped screenshot editor
would sit in resources/ and be ignored by every dev machine with an
older screenshot/ folder from a previous test.
Compare the bundled addon.json version to the user's on-disk version.
On mismatch, rename the user copy to
<userData>/addons-backups/<id>-<oldver>-<stamp>/ and cp the fresh
bundle in. Backups live outside addonsDir so AddonHost's folder scan
doesn't pick them up as duplicate add-ons under the same manifest id.
Bump screenshot 0.2.0 -> 0.2.1 so the first build carrying this fix
actually reseeds the shipped-0.3.14 editor on existing dev copies.
Users who genuinely fork a bundled add-on should bump their local
version to something different from the bundled one — that keeps them
pinned. Users who edit files without bumping accept upstream updates,
with the timestamped backup as safety net.
Multi-currency coverage matches what aegis.x has been advertising: BCH,
TRX, SC, DGB, ETH, SOL — six coins, two-step coin/network picker for
each. Panel logos, favicon and fallback all read as Aegis.
- Ethereum (lib/chain-eth.js): mainnet + Sepolia. BIP44 m/44'/60'/0'/0/0
→ secp256k1 → EIP-55 checksummed hex address (verified against
MetaMask's canonical abandon×11 vector 0x9858EfFD23…4EcaEda94). JSON-RPC
backend (Cloudflare mainnet, PublicNode Sepolia by default; per-wallet
override). EIP-1559 send with an inline RLP encoder + secp256k1
recoverable sign; broadcast via eth_sendRawTransaction. personal_sign
message signing follows the \x19Ethereum Signed Message:\n prefix.
- Solana (lib/chain-sol.js): mainnet-beta + devnet. SLIP-0010 ed25519
derivation at m/44'/501'/0'/0' (all-hardened), base58 address (@noble
ed25519). SLIP-0010 layer verified against spec Test Vector 1 in
scratchpad/verify-slip10.mjs. Native SOL transfer via the system
program with compact-u16 message serialization + ed25519 sign +
sendTransaction. Devnet gets a faucet.solana.com link in Receive; the
panel appends ?cluster=devnet when opening the explorer.
- DGB address family selector (lib/chain-dgb.js already carried the
paths): the Settings block now shows a Native SegWit / Taproot /
Wrapped SegWit / Legacy P2PKH picker. Selecting a family auto-fills
the derivation-path input with that family's default; Apply
rebuilds the wallet against the new path. Address families exposed
via chainMeta.addressFamilies so the panel can render them from data.
- Panel branding: inline SVG shield (hexagonal aspis, same silhouette
as the aegis.x hero) replaces the "?" fallback in logoSvg() and is
what the header shows before a wallet is selected. Data-URI favicon
wired into panel.html so the Theseus sidebar tab icon reads as Aegis
rather than a chain-specific coin mark.
- QR payloads now follow each chain's own URI scheme (BIP21 for BCH/DGB,
EIP-681 for ETH, Solana Pay for SOL) so external scanners route the
scan to the right wallet.
Not shipped: EIP-1193 provider (window.ethereum) and wallet-adapter
protocol (window.solana). The signing paths exist; only the page-inject
bridge glue is missing. History for ETH/SOL is also empty in this rev —
both need indexer plumbing (Etherscan V2 for ETH, getSignaturesForAddress
+ getTransaction pagination for SOL).
Aegis now shares its DGB code with the standalone DigiByte web-wallet at
D:\Dev\SilentCode\Digibyte. Address derivation and PSBT construction come
from that project's @dgb-wallet/core and @dgb-wallet/psbt packages instead
of Aegis-local reimplementations. Any bugfix upstream flows in via a
re-vendor of dist/*.
- lib/dgb/{core,psbt}/ — vendored dist/ output of the two packages plus
a tiny package.json shim marking them as ESM. @dgb-wallet/core's own
import specifier "@dgb-wallet/core" inside psbt/*.js is rewritten to
"../core/index.js" so the sibling module resolves without a workspace.
- New Theseus deps: bitcoinjs-lib, bip32, bip39, @bitcoinerlab/secp256k1,
ecpair — the peer deps the vendored packages need. Loaded via
api.require in index.js's loadDeps().
- chain-dgb.js is a thin adapter now: BIP32 tree via bip32 + DGB
Network object, addresses via core.p2wpkhAddress, tx via
psbt.buildPsbt + PSBT.signInput (per-input, since each UTXO's key
differs) + psbt.finalizeAndExtract. Runtime backend stays the same —
Theseus's lib/electrum.js against the DGB ElectrumX pool.
- Verified end-to-end in scratchpad: abandon×11 mnemonic derives
dgb1q9gmf0pv8jdymcly6lz6fl7lf6mhslsd72e2jq8 (matches iancoleman.io/bip39
and the previous inline implementation, so no on-chain address change
for anyone who was already using Aegis's DGB slot). PSBT build+sign+
finalize on a mock UTXO produces a valid 223-byte witness tx.
BIP44 (D…) and BIP49 (S…) address families are implemented in the
vendored core but not yet exposed in Aegis's picker — the panel needs
an "address family" selector inside the DGB settings block first. Left
for a follow-up; today's DGB pick uses BIP84 native SegWit only.
Aegis now covers four coins across two-step coin+network picks: BCH
(mainnet + chipnet), TRX (mainnet + Nile), SC (mainnet), DGB (mainnet).
- Sia (SC): pulled the standalone siawallet's lib into
bundled-addons/bchwallet/lib/sia/ and wrote lib/chain-sia.js exposing
the common adapter shape. The very first SC wallet the user adds in
Aegis reuses purpose "siawallet/mainnet/0" so pre-Aegis funds carry
over automatically; subsequent SC sub-accounts start at
"bchwallet/sc/mainnet/1". Per-wallet walletd URL setting; empty URL
shows a "Point Aegis at a walletd node" gate in the panel.
- Vault-derive gate now honors a manifest-declared `absorbs` list, so
Aegis's addon.json can list `absorbs: ["siawallet"]` and the derive()
guard accepts paths under either the current id or the absorbed one —
the mechanism a superseding add-on uses to inherit an older add-on's
keyspace without orphaning funds.
- DigiByte (DGB): lib/chain-dgb.js ports the relevant bits of the
SilentCode Digibyte design — SLIP-44 coin type 20, BIP84 native SegWit
(m/84'/20'/0'/0/x → dgb1q…) via ripemd160(sha256(pubkey)) + bech32.
ElectrumX-DGB backend reuses lib/electrum.js (public wss:50022 pool).
BIP143 P2WPKH sighash + witness-tx serialize implemented inline (no
FORKID — DGB uses standard Bitcoin sighash). Derivation cross-checked
against a known BIP39 vector in scratchpad/verify-dgb.mjs — the address
for "abandon×11 about, m/84'/20'/0'/0/0" is
dgb1q9gmf0pv8jdymcly6lz6fl7lf6mhslsd72e2jq8, matching iancoleman.io/bip39.
- Panel: SVG coin logos for SC (green disc with S) and DGB (blue
octagon with D) alongside the BCH/TRX marks. Chain-specific settings
block per coin (walletd URL for SC; derivation path for DGB). Balance
render uses BigInt-safe arithmetic so 24-decimal SC amounts don't
lose precision on the way through the panel; amount input on SC
returns a hastings string.
- Every chain adapter's snapshot fits the panel's shared shape
(address/balance/history/etc.), so future chains only need a new
chain-<x>.js file, a COINS registry entry, a matching case in
mountWallet, and an SVG logo.
Standalone siawallet addon stays as-is on disk; users can delete it once
they've confirmed Aegis shows the same balance. Nothing here disables it.
Setup f9d0654572e110f6895951dc22ec9fb4549b3a8fb14714770579264b62051259
Portable 7ff7a1501bc7d7407ec0a4974aa26652d6d7ecabcc796584254a8bb463d43b09
Two bundled fixes since 0.3.15:
4f498a1 - Extension dock no longer stacks into a column when dragged
narrow. #extbuttons + .extdock get flex-wrap:nowrap + overflow:hidden;
a second data-extcollapse signal fires when .bar's contents overflow
(alongside the width-based level 3), collapsing the row into the
single 🛡 puzzle button. Hysteresis (cached natural width + 8px slack)
keeps the ResizeObserver from oscillating across the boundary.
e160dac - toolbar-menu popup goes native (Menu.popup from main) so it
escapes the chrome-view height clipping. capturePage retries transient
0x0 results up to 6 times so the screenshot addon doesn't silently
produce a blank PNG right after a navigation.
Deployed. Verified LIVE 0.3.16.
Two follow-ups from the screenshot editor rework (task_b9608dc6):
1) toolbar-menu popup goes native. The DOM popover in chrome.html was
getting clipped by chrome.html's own WebContentsView height and then
covered by the tab view below it. Route through main.js's
Menu.popup() so the menu escapes the chrome-view layering entirely.
Preload exposes toolbarMenuPopup(addonId, rect) + subscribes to
toolbar-menu-closed so chrome can drop the button's "active" tint.
2) capturePage() intermittently returns a 0x0 image on Windows right
after a navigation (view hasn't painted a frame yet). Retry up to
six times with 150 ms between attempts; throw a specific error if
still empty so the addon can surface a real message instead of
silently producing a blank PNG.
Also lands an [addons] openAddonTab log line so the editor tab opening
is easy to trace in main's log.
The extension row was stacking into a column when the user dragged the
URL bar wide enough to squeeze the dock's slot. Two things broken:
1) #extbuttons could wrap: added flex-wrap:nowrap + overflow:hidden so
the buttons never break to a new line. Same for .extdock's own
flex container (nowrap + min-width:0 so it can flex-shrink to zero).
2) Collapse was width-based on .bar only — a window wide enough for
level 0 wouldn't collapse the dock even when the URL-bar override
left extdock with 0 px. Added a second signal:
data-extcollapse="1" fires when .bar's contents overflow their slot
(bar.scrollWidth > bar.clientWidth). Applies alongside the level-3
collapse; either path shows .extmore instead of the row.
Hysteresis to avoid RO loop: while EXPANDED, cache the row's natural
scrollWidth. While COLLAPSED, re-expand only when summing every other
.bar child leaves at least (naturalWidth + 8 px slack) of room. Both
directions verified across URL widths 400/800/1200/1500/reset — the
row expands / collapses at the right thresholds with no oscillation.
Setup 81b6a8d2a64d20641c4cc448d35627fb3c492ea45adb24fa5c41a9f63663dd2d
Portable 47a22d86a08305536b4d80f4072ad38179a4935f303ac31bcd05dba36d5f4d40
One bundled commit since 0.3.14:
6dae15e - Drag handles on the trailing edge of the URL bar + leading
edge of the search box let the user resize live; widths persist as
urlBarWidthPx / searchBoxWidthPx and override the discrete size preset.
Also fixes the sliding bug the user just flagged: capping the URL bar
via Settings > Appearance was letting the download / extension /
Theseus buttons pack next to the URL bar, sliding leftward. Adding
margin-right: auto to the capped .urlwrap absorbs the slack so the
right group stays pinned to the right edge. Verified via CDP: logo's
rightGap stays at 10px across default / compact / urlBarWidthPx=500 /
reset.
Deployed. Verified LIVE 0.3.15.
- Inline SVG logos for BCH (green disc + ₿) and TRX (red disc + geometric T)
replace the 🟨/🔴/🔵 emoji in the sidebar header and wallet picker rows.
The approval overlay stays text-only ("Wallet: <name> — BCH · Mainnet")
because that surface renders plain rows, not HTML.
- Wallet picker's "Add wallet" is now two-step: click a coin to expand its
networks, then click a network to create the wallet. The flat list is gone.
- BCH Chipnet is a real chain option now: bchtest cashaddr prefix,
m/44'/1'/0' derivation (BIP44 testnet coin type), bundled Chipnet electrum
defaults, chipnet.imaginary.cash explorer, tbch.googol.cash faucet link
in Receive. The shared electrum-servers setting stays mainnet-only in
this rev; Chipnet uses adapter-embedded defaults.
- lib/chain-bch.js gained a BCH_NETWORKS table so mainnet vs chipnet
differences (prefix, path, servers, explorer, faucet) live in one place.
- Registry is grouped by coin ({networks:{…}}) instead of a flat
chain:network map — snapshot exposes coins[] for the panel and adds
coinLabel/networkLabel/testnet fields per wallet.
- Testnet wallets get a small "TEST" tag next to the network name so the
user can never mistake a chipnet or Nile balance for real money.
Legacy BCH mainnet index 0 derivation unchanged (bchwallet/mainnet/0 →
m/44'/145'/0' → bitcoincash prefix); the network parameter defaults to
"mainnet" and BCH_NETWORKS.mainnet reproduces the pre-change constants.
Two fixes bundled:
1) Right-side items (download, extension dock, Theseus button) no longer
slide leftward when the URL bar is capped. Added margin-right: auto
to .urlwrap under every capped state (data-urlsize=medium/compact
and the new data-urlwidth override) so the leftover flex space sits
AFTER the URL bar, keeping the right group pinned to the right edge.
Verified: logo's gap from bar's right edge stays at 10px across
default / urlBarSize=compact / urlBarWidthPx=500 / reset.
2) Drag handles for live resize. A 6-px col-resize strip sits on the
trailing edge of .urlwrap and the leading edge of .searchbox; a
pointerdown/move/up dance updates the width live via a CSS custom
property and persists to settings on release. Two new keys:
- urlBarWidthPx (0-1800, 0 = follow size preset)
- searchBoxWidthPx (0-800, 0 = follow size preset)
When either is non-zero, the corresponding CSS override wins over
the discrete size preset. Preload gets setSetting so the drag can
push the persistent value from chrome.
Visual affordance: handles are transparent by default, gain a faint
acid tint on hover and while dragging.
Setup 238f0b81afd93d7cef80b9403edef1dc3e1d4c1bf29e29a498115e1185ce1db8
Portable d8877e696ad6425aa6d56cb796048702cfb37b40ff539a8c1af0d08d0a31870c
Three bundled fixes since 0.3.13:
- (Aegis multi-chain wallet, cherry-picked from claude/sleepy-maxwell-251ee6
worktree) — bchwallet add-on turns into a chain-agnostic wallet
manager with BCH + Tron mainnet + Tron Nile testnet; addon id stays
"bchwallet" so vault-derive paths + legacy BCH funds are untouched.
Dapp bridge on any https page exposes window.tronWeb / window.tronLink
matching TronLink chain ids so Tron dapps just work.
- 3d3dfe5 — Screenshot addon reworked: dock icon opens a small
toolbar-menu (Visible / Full page / Region…) instead of a sidebar
panel; captures open in a full browser tab with an editor
(crop / arrow / rect / circle / freehand / text / blur / undo / save /
copy). Two new addon-host capabilities land: toolbar-menu + open-tab.
- dd31b88 — Light-mode --acid went from #4d7300 → #3a5c00 (~7:1 on
white), and the missed addon panels (bchwallet, siawallet,
screenshot editor.css) got their overrides so nothing resolves to
the bright #d6ff3d anymore.
Note on Nile testing: the code paths verified via a running instance —
dock shows 🛡 Wallet, chain picker "🟨 BCH — main ▾" is present. Live
Nile send requires a vault + testnet TRX from nileex.io/join/getJoinPage.
Deployed. Verified LIVE 0.3.14.
Reworks the screenshot addon into the flow the user asked for: the
dock icon opens a small dropdown menu (Visible viewport / Full page /
Region…) instead of the sidebar picker, and each capture opens a
full browser tab hosting an editor.
Two new addon-host capabilities land alongside:
- toolbar-menu: the addon declares an icon + item list in its manifest;
the chrome dock renders a button that, on click, opens a small menu
and dispatches the selection to the addon via addon-menu-select IPC.
- open-tab: api.openTab(path) opens a browser tab whose URL is the
addon's local file. Origin-gated per addon; the editor uses a
dedicated addon-tab-preload for its main → renderer bridge.
Editor page (editor.html/js/css):
- Crop, arrow, rectangle, circle, freehand pen, text, blur
- Colour swatches (red / yellow / acid / white / black), 3 stroke widths
- Undo/redo command stack, zoom controls
- Save PNG (goes through the download pipeline, chip picks it up)
- Copy to clipboard via ClipboardItem
Previous #4d7300 (0.3.10) was still too light against actual white
backgrounds — several tint fills (rgba(214,255,61,X)) and unpatched
addon panels were making the effective color feel bright green. Two
fixes bundled:
1) Bump --acid in every top-level page's light-media block from
#4d7300 to #3a5c00 — same hue, ~7:1 contrast on #ffffff (was ~5.5:1).
2) Add the missing light-media --acid override to the addon panels
that were still resolving to #d6ff3d: bchwallet/panel.html,
siawallet/panel.html, and screenshot/editor.css (was #b4e024, now
#3a5c00 to match).
Dark mode unchanged. Tint fills (rgba backgrounds at low alpha) still
stay as-is — at 8–15% opacity the specific hue barely matters and the
darker foreground now dominates.
Setup 7d88e4c46b02448e40d6075d10f2c6688c6d60c5a9ec41b9cbcb7684f131d6e1
Portable 5a4bcc6abb21c23729d79dd600142df4f171cc3d6bf71716dae1c802ac10e48f
Bundled since 0.3.12:
1514793 - Settings > Registries gets an on/off toggle for Ariadne's
Thread (system-wide BCDN resolver for non-Theseus browsers). Query is
silent Get-ScheduledTask; toggle spawns elevated PowerShell (UAC once
per action). Three states: running / stopped / not-installed.
b16f0a1 - New BCDN 'p' record type in Argus record-picker + Theseus
serving. Reverse-proxies an upstream URL under a BCDN name, keeping
the BCDN name in the address bar; uses upstream's own DNS + public
CA + Host header (unlike 'ip' which pins IP + on-chain TLS fingerprint).
Placed after 'ip' in the apex chain, suppressed under subdomain
inheritance so a 'p' name doesn't silently proxy every subdomain.
cfec253 - Argus registrar gains buildTldRegistrationTx + TLD_BEACON +
normalizeTld exports for minting per-TLD certificates per the TLD-
registry design.
bbfc05c - Bundled Screenshot add-on: capture-tab capability + sidebar
launcher for visible / full page / region modes; saves to Downloads.
Follow-up task_b9608dc6 will rework this into a full-tab editor.
Deployed. Verified LIVE 0.3.13.
New card under Settings > Registries: shows whether the system-wide
resolver daemon is running, stopped, or not installed on this machine,
and lets the user turn it on/off without opening the installer.
Ariadne runs as two elevated Windows Scheduled Tasks ("BNS Resolver
Daemon" + "BNS Sia Bridge"). Toggling requires admin — main spawns an
elevated PowerShell (Start-Process -Verb RunAs) that UAC-prompts once
per action, then re-queries state. Query is unelevated
Get-ScheduledTask so status checks are silent.
Three surfaced states:
running - "every browser on this machine resolves BCDN names"
stopped - "only Theseus resolves BCDN names; other browsers won't"
not-installed - link to silentmode.st/tools to grab the standalone installer
Theseus's own resolver is unaffected either way — it lives in-process
and doesn't depend on Ariadne. This toggle only controls what non-
Theseus browsers on the same box can resolve.
New capture-tab capability on the addon-host, and the screenshot add-on
uses it to expose three modes in a sidebar launcher panel:
- Visible viewport: Electron's WebContents.capturePage() on the active tab
- Full scrollable page: temp-resize the tab view to document.scrollHeight,
capturePage, restore
- Region: preload overlays a translucent selection div, tracks mousedown /
move / up, sends the rect back; main takes the visible capture and
crops via nativeImage.crop({x,y,width,height})
Saves land in the user's Downloads folder via session.downloadURL — same
pipeline as any file download, so the download chip picks them up.
Filename: theseus-screenshot-<host>-<ISO date>.png. JPEG option for
smaller files.
A follow-up task (task_b9608dc6) reworks this to open captures in a
full-tab editor with crop / draw / annotate / undo / copy-to-clipboard
instead of the current bare launcher.
New record kind alongside h / s3 / ip / u: `p` reverse-proxies the
request to a full upstream URL while keeping the BNS host in the
address bar. Unlike `ip` (which pins the upstream to a raw IP + on-chain
TLS fingerprint), `p` uses the upstream's own DNS + public CA cert and
sends `Host:` of the upstream so vhost-based origins answer correctly.
Argus (record-picker.js):
- Placed AFTER `ip` in the apex chain: a name carrying both keeps its
existing pinned-IP behavior; names with only `p` get honored instead
of falling through to `u` and 302-ing away.
- Not applied to subdomain inheritance — `p` is single-URL by intent,
and inheriting through a subdomain would silently mangle the target.
Theseus (main.js serveBns):
- Fetches the upstream (path prefix from the `p` URL is preserved) and
returns the response body/status/content-type verbatim. Provenance
label "mirror" appears in the source badge.
Tests: record-picker.test.mjs covers `p`-only, `p`+ip precedence, and
the subdomain-suppression rule.
Setup a28b4d2be705069f35fa21c5c3e008627661b6173d1eb3014d001eb81c72761e
Portable 5dab9a17818d263706704ab867b6ab9743c2606eb60e2391d0e00a78604cd26b
Two bundled fixes since 0.3.11:
e7dfa46 - At responsive level 3 the collapsed extension button was a
generic 🧩 puzzle piece. Swap it for the first registered extension's
own icon (📝 / ₿ / Ⓢ / whatever ships first). Puzzle piece stays as the
empty-state fallback when no addons are registered. Click still opens
the same dropdown of all installed extensions.
95cc2c7 - Remove the redundant 32-px extension picker strip that
sidebar-preload.js was injecting at the top of every add-on panel.
The toolbar extension dock is the canonical switcher now; doubling
that inside the sidebar just wasted vertical space and made narrow
panels cramped. Every panel reclaims its top 33px.
Deployed. Verified LIVE 0.3.12.
Every add-on panel was carrying a 32-px tab bar at the top listing every
registered extension. That existed before the toolbar extension dock
landed — now that the dock (per-extension buttons + puzzle dropdown at
narrow widths) is the canonical switcher, doubling that inside the
sidebar just wasted vertical space and made narrow panels feel cramped.
Drops installPickerStrip() from sidebar-preload.js and the invocation
from the DOMContentLoaded handler. The body-padding style that made
room for the strip goes with it, so panels reclaim their top 33px.
Resize grip on the left edge stays — unrelated feature.
No panel HTML depends on the strip's padding — the wallets, notepad,
relay, siawallet, and screenshot addon all start their own body flow
from 0. Nothing else to change.
Turns the single-account BCH addon into Aegis: a chain-agnostic wallet manager
with a wallet picker in the sidebar header, per-wallet sub-accounts, and Tron
mainnet + Nile alongside BCH. Add-on id stays "bchwallet" so vault-derive paths
stay in the same namespace and the legacy BCH default wallet uses PURPOSE
"bchwallet/mainnet/0" byte-identical to before — funds are untouched.
- lib/chain-bch.js wraps the existing keys/tx/wallet/electrum stack with the
common adapter shape and scopes each wallet's storage under wallets/<id>/…
- lib/chain-tron.js: m/44'/195'/0'/0/0 → secp256k1 → keccak256 → 0x41 || h20
→ base58check. Balance + history via TronGrid v1, send via createtransaction
+ sha256(raw_data_hex) sign + broadcasttransaction. Mainnet and Nile share
the address format; different vault paths mean different keys so a mainnet
wallet can never accidentally sign against Nile.
- lib/base58check.js: bitcoin-alphabet base58 with sha256d checksum. k=1
derivation verified against Ethereum's canonical k=1 H160 in a scratchpad
harness (correct-by-construction for Tron address).
- Combined wallet-inject.js: window.bitcoincash on .x pages (unchanged gate),
window.tronWeb + window.tronLink on any https page. tron_requestAccounts
triggers the approval overlay; sign / sendRawTransaction / signMessageV2
route to the currently-selected Tron wallet. Emits accountsChanged /
setNode messages TronLink dapps listen for; chain ids 0x2b6653dc /
0xcd8690dc match what TronLink itself uses.
- New panel: chain-aware wallet picker in the header (badges 🟨 BCH,
🔴 Tron, 🔵 Nile), Add-wallet dropdown per chain, per-chain unit picker
(BCH/sat, TRX/sun), per-wallet rename + remove (isLegacy default is
protected). Sends show the chosen wallet in the approval overlay so the
user can never mistake sub-account.
- Migration on first launch: pre-multi-wallet storage (top-level
receiveCursor / txCache) is rehomed under wallets/bch-default/… and the
legacy account path is preserved.
Not shipped: user is bundling into the next release. Live Nile broadcast +
real dapp connect need a set-up vault; the code paths are unit-verified end
to end but a testnet send + tronscan.io/nile connect are user-side steps.
At responsive level 3 the dock folds into a single button; that button
was a generic 🧩 puzzle piece. Swap it for the first registered
extension's own icon (📝, ₿, Ⓢ, whatever ships first in the panels
list), with the puzzle piece staying as the empty-state fallback if
no addons are registered yet. Clicking still opens the same dropdown
of all installed extensions, so no functional change — just the
button reads as "the extension you're most likely to want" instead of
a generic marker.