chipnet.imaginary.cash's web explorer is returning 502, so every "Explorer"
button on a chipnet wallet led to a Bad Gateway. Its Electrum endpoint on
:50004 is a separate service and is fine — balances, history and the send
path were never affected, which is worth stating because a dead explorer
link looks like a dead wallet.
The explorer now resolves transactions as well as addresses, so it can back
both links rather than half of them. One search box: 64 hex characters is
treated as a transaction id, anything else goes to the cashaddr decoder so
it can complain precisely. A transaction shows confirmations, total out,
size and timestamp, its inputs as links to the parent transactions, and its
outputs as links to the receiving addresses — in-page, without a reload.
Inputs deliberately show no amount: Electrum does not resolve it, and
fetching every parent transaction to display one number is not worth the
round-trips.
Aegis's chipnet explorerTx/explorerAddr now point there. Same chain data,
read over the same Electrum connection the wallet already trusts, and a page
we can fix ourselves the next time one breaks. Mainnet is untouched.
Import accepts a picture of a QR code. The file is decoded in the panel by
the vendored jsQR, drawn to a canvas: no upload, no network, no camera.
Image, not camera, on purpose. Theseus sets blockCamera + hideMediaDevices
by default and hides device labels from fingerprinting; a camera scanner
would fail silently for everyone until they turned that off globally, and a
wallet should not be the reason a privacy browser gives up the camera. A
photo or screenshot of the code needs no permission at all.
What comes back is classified, never trusted. A QR is opaque to the person
holding it, and "scan this to restore your wallet" is a working phish, so
the decode only chooses which field to fill:
BIP39-shaped (12/15/18/21/24 lowercase words) -> mnemonic field
WIF or 32-byte hex -> private key field
anything else -> nothing is filled; the
decoded text is shown so
the user can see it was a
URL, an address, or junk
Nothing auto-submits. The user reads what landed in the box and presses
Import, and the host handler still does the real validation.
jsQR 1.4.0 is vendored at lib/jsqr.js under Apache-2.0 with its LICENSE
beside it, unmodified and unminified — code that touches seed phrases should
be auditable in the shipped add-on, not an opaque blob. It is 57 KB gzipped.
Verified by round-tripping through the shipped path: Aegis's own encoder
builds the QR, it is rasterised to a real PNG File, and decodeQrFile() reads
it back byte-exact; an image with no code returns null rather than throwing;
and driving the actual file input fills the mnemonic for a seed, fills the
key field for a WIF, and leaves every field untouched for a phishing URL.
Pairing already worked; signing would have thrown on the first request
a dapp ever sent. Found by testing against the real relay and the real
@wizardconnect/wallet library rather than reading the code.
Two bugs in wc-sign.js, both fatal:
- The WC message nests the whole WcSignTransactionRequest under
`.transaction`, so the tx is at request.transaction.transaction and
the spent outputs at request.transaction.sourceOutputs. We read
request.transaction as the tx and request.sourceOutputs as the
outputs, so tx.inputs was undefined. index.js already read the nested
request.transaction.userPrompt for the approval dialog, so only the
signer had it wrong. The flat shape is still accepted.
- generateSigningSerializationBCH takes TWO positional arguments,
(compilationContext, {coveredBytecode, signingSerializationType}).
We passed one merged object, leaving coveredBytecode undefined and
throwing inside libauth. For P2PKH the covered bytecode is the spent
output's locking script.
Now verified end to end: a two-input transaction spending from two
different derivation paths signs, decodes, and passes
createVirtualMachineBCH().verify() — consensus-valid, with
SIGHASH_ALL|FORKID|UTXOS (0x61) on every input as the protocol
requires.
Also: RelayStatus is an object ({status: "connected" | "reconnecting" |
"disconnected" | "session_deleted"}), and the snapshot read a
non-existent `.kind`, so every connection reported the literal
"[object Object]". Reads `.status` now, uses the documented
getConnections() accessor instead of the private connections Map, and
carries the library's own `label` ("dapp name once known, otherwise
Connecting…"). The panel shows a tag for anything other than connected
— "reconnecting" is the difference between a pairing that will see the
next signature and one that is dead, which was invisible before.
Receive was ordered QR → address → tokens, so 200px of always-on QR sat
above the thing people came for and pushed the asset list off screen.
- Address first, with Copy and a QR button; the QR expands inline and
the choice sticks, because someone who receives by QR wants it every
time and someone who copies never does.
- Explorer and Faucet moved up to the header status row. They act on the
selected wallet, not on the act of receiving, and down there they
competed with Copy for the one row that gets used.
- Assets card renamed from Tokens and now leads with the native coin, so
"what does this wallet hold" is one list rather than two places.
- "+ Add another <TICKER>" moved below the address list.
The balance appeared three times — header, drilldown subtitle, address
row. Now once in the header; the subtitle keeps only the per-unit price,
and the per-address amount returns when a coin actually has more than one
address to compare. The address row drops its truncated address (the full
one is at the top of Receive) and keeps the wallet name.
The per-address asset list added in 0.8.8 duplicated the Assets card and
is removed — assets live in one place.
Token amounts were unreadable: an 18-decimal balance rendered as
60000000.000000005435817984. Capped to 8 decimals with thousands
separators, exact value on hover.
A symbol claimed by more than one contract now carries a LOOK-ALIKE tag.
The test wallet holds four different contracts all calling themselves
"Test USDT" — spam mints borrowing a trusted ticker so a careless send
lands on the wrong one. We can't tell which is genuine, so we mark every
member of the clash rather than guessing.
History showed native transfers only, so a wallet that had only ever
moved USDT looked empty. TRC20 transfers are merged in newest-first, each
carrying its own decimals and ticker (rendering a token against the
chain's scale would be off by orders of magnitude). Both feeds are on by
default; the checkboxes narrow rather than opt in, and the last one
checked can't be unchecked into an empty list.
Mounting an imported TRX/ETH/SOL wallet read the optional custom RPC as
String(stored || undefined), so a wallet with no override got the literal
"undefined" as its server: every history and token fetch went to
"undefined/v1/accounts/…" and the panel showed "undefined" under the
balance. Only a real https URL overrides the network default now, and the
adapter itself rejects anything that does not look like one.
Completes the parity work 0.8.7 started for Tron. Imported ETH and SOL
wallets showed a native balance and nothing else, because the JSON-RPC
endpoints they poll have no history or token concept at all.
- ETH history + ERC-20 balances via Blockscout, which needs no API key
(Etherscan V2 does). Mainnet RPC moves off eth.llamarpc.com, which was
answering 525 with an HTML error page — that parsed as a JSON error and
showed as a 0 balance.
- SOL history via getSignaturesForAddress and SPL balances via
getTokenAccountsByOwner, both keyless on the public RPC.
Three things the live testing turned up:
- A Blockscout mempool entry is {result:"pending", status:null}. Reading
that as "not ok, therefore failed" showed pending sends as failures.
Now carries a distinct pending state through to the row.
- History `delta` is now a number, a decimal string, or null. ETH wei
needs the string (18 decimals overflows a JS number, and Math.abs was
silently rounding it); Solana's signature feed carries no amount at
all, and null >= 0 is true, so unknown amounts were rendering as a
"+" that claimed a receive we cannot verify. Unknown now renders as a
neutral row instead.
- A real address came back with 855 ERC-20s and 3078 SPL mints, nearly
all airdrop spam, some with blank, zero-width or bidi-override
symbols that render as an empty row borrowing trust from its
neighbours. Token text is sanitised and lists are capped at 50, sorted
so named tokens survive the cap.
Also: the first-run setup screen forced text-align:left on the form, so
its helper copy ran ragged under a centred mark, title and description.
The form now inherits the centred alignment; the mnemonic box stays
left-aligned on purpose, since centring wrapped seed words makes them
harder to check.
Imported Tron wallets pointed at api.nileex.io, which only serves the
/wallet/* JSON-RPC family and 404s all of /v1/. That REST family is
where transaction history and the trc20 balance map live, so an
imported Nile wallet showed a native balance and nothing else. The
built-in Tron adapter was already on nile.trongrid.io, which is why
only imports were affected.
Switches the imported Nile endpoint to nile.trongrid.io and fills in
the two features that were never implemented for imported account-
model wallets:
- History via /v1/accounts/<addr>/transactions, with the signed delta
computed by comparing owner_address against the wallet's own address
in 41-hex form (the feed returns hex regardless of visible:true).
- TRC20 balances via /v1/accounts/<addr>, joined against token_info
harvested from recent trc20 transfers to recover symbol + decimals.
Both are best-effort so a chain with no keyless feed can't blank a
wallet whose balance fetch succeeded. Contracts with no registry entry
render as "Unknown token" with a raw amount rather than a number
invented from assumed decimals, and named tokens sort above them so
airdrop spam can't bury real holdings.
Every Coin-Spectrum poll silently returned an empty map because we
were reading body.price_usd (top-level) while the API wraps its data
under body.asset. Every chain's Number(undefined) came back NaN, so
Settings › Prices showed "✓ 0 coins" and majority-vote reconciliation
had one fewer source than intended.
Now reads body.asset.price_usd (with a top-level fallback in case the
API is ever flattened).
Blockchair's BCH explorer is slow and ad-heavy; bchexplorer.cash is
the Bitcoin Cash community's own instance, faster on tx pages and
with a proper mempool view. Same /tx/ + /address/ path scheme
(address takes the bitcoincash: prefix as-is), so no other code has
to change.
Chipnet explorer stays on chipnet.imaginary.cash — bchexplorer.cash
is mainnet-only.
Settings grew tall enough that the user had to scroll past a dozen
cards to reach Prices, Sites or About. Split it into six named
sections (Security, Session, Wallet, Prices, Sites, About) with a
chip nav row at the top; only one section is visible at a time and
the choice persists across restarts.
Adds an About card that names the wallet, the aegis.x front-door
site and the silentmode.st umbrella, so support triage has a
one-click way to reach either from within the panel.
Includes the accumulated 0.7.x-0.8.1 wallet work that was already
shipping on OTA (CashTokens/BCMR, imported-wallet spend, siascan
integration, consolidate, currency picker, footer update chip).
Theseus core:
- addons-host: manifest.category ("plugin") propagates through snapshot(); new
addon API surface checkAndStageSelfUpdate() + restartApp() so a plug-in
can offer in-panel "update now → restart to apply" without pushing the
user to Settings.
- main.js: wires the two new hooks into the AddonHost constructor.
- settings.html: Extensions listing filters out category==="plugin"; those
add-ons live in Plug-ins instead, single source of truth.
Aegis 0.6.31:
- BTC picker trimmed to Signet only; testnet3 hidden (adapter kept so any
existing wallet still loads).
- Wallet strip groups by chain, not chain:network; ticker gets a ▾ chevron
and a dropdown listing every subnetwork with its own totals. Mainnet
reads as the plain ticker; testnets carry a small Chipnet/Signet/Sepolia
pill inline.
- Per-unit price sits directly under the ticker; amount + fiat mirror on
the right — one glance covers name/price/holding/value.
- + Add and ⋯ More promoted from the strip into the header's action row,
next to the new ✎ chip (was the redundant top ⋯). Duplicate "Manage
current wallet" entry removed from the More menu.
- Footer update chip is a two-step flow via the new API: stage → restart.
Falls back to opening Settings on any Theseus that lacks the hooks.
- Manifest declares "category": "plugin".
Aegis Wallet 0.4.4 → 0.6.1:
- Vault lifecycle from the wallet gate. The locked / not-yet-created states
now show a master-password form (with optional BIP39 mnemonic on setup)
instead of redirecting users to Settings › Passwords. New
api.vault.lifecycle {status, setup, unlock, lock} in addons-host, gated by
the existing "vault-derive" capability. api.openSettings(section) also
added; settings.html honours a #section hash on open.
- Imported BCH wallets (design M.1a, read-only). Paste a mnemonic + BIP44
path or a WIF; the cashaddr is derived in the add-on, the signer material
goes to a separate wallet-imports.enc via api.vault.imports {list, add,
remove, signer}. Argus password-vault gains createImports / unlockImports /
saveImports with its own KDF salt so the imports key is disjoint from the
passwords key. lib/chain-bch-imported.js is a single-address Electrum
adapter; spend support is deferred to M.1b.
- Opt-in USD prices via CoinGecko (lib/prices.js), off by default, persisted
in add-on storage. Fiat lines under balances, in the wallet picker, and a
portfolio total when 2+ wallets are open. Settings tab is now reachable
while the vault is locked so the toggle is always available.
- WizardConnect wallet-side pairing for BCH wallets (lib/wc.js, lib/wc-sign.js).
@wizardconnect/{core,wallet} are loaded dynamically via api.import to stay
on the right side of LGPL §4d. Sign requests go through approvalModal and
are restricted to P2PKH inputs with SIGHASH_ALL|FORKID|UTXOS.
- DGB adapter load is now soft-fail: when Aegis runs from userData/addons the
bundled ESM can't resolve peer deps, so DGB becomes unavailable instead of
taking the whole add-on down.
Setup 5d15508bba929f1f074c052ac933863eadf6eb8e56984ebd5a1af75e80626643
Portable a5d346b97f5a13d85fa3bd301a72075ddb82fe636d7b1a51840ffd5a16d879f4
Bundled since 0.3.27:
32d4b75 - Aegis (bchwallet) gains its own update card in Settings >
General beside Ariadne. Check for updates hits the same signed OTA
endpoint the boot timer uses; Restart to apply appears when a signed
newer version is staged. Uses the existing addons-check-updates + a
new app-restart IPC. New Aegis versions ship without a Theseus release.
32d4b75 (same commit) - DevTools (F12 / Ctrl+Shift+I) opens docked to
the right of the tab (mode: 'right') instead of a detached window.
Matches stock Chrome. Users who prefer detached can drag out via the
DevTools own toolbar.
b71c925 - Search-engine favicons in Settings > Search now use Google's
/s2/favicons service — DuckDuckGo's ip3 source returned 404 for enough
hosts (Brave, Bing, Yandex, etc.) that half the list was falling
through to the emoji placeholder.
Deployed. Verified LIVE 0.3.28.