Commit graph

26 commits

Author SHA1 Message Date
Local Dev
9d81c29656 fix(theseus/light): light-mode acid → BCH-teal #088A66 (brand-family, AA on white)
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.
2026-09-08 00:49:46 +02:00
Local Dev
6922ed72ff feat(theseus/screenshot): 0.2.2 — 3 extra swatches, updateURL points at live theseus.x endpoint
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.json
  https://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.
2026-09-08 00:22:49 +02:00
Local Dev
b5f7552277 feat(theseus/aegis): SPL token support (view balances + send)
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).
2026-09-07 23:55:09 +02:00
Local Dev
1b29706ba4 feat(theseus/aegis): EIP-3085 wallet_addEthereumChain + EIP-3326 switchChain
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.
2026-09-07 22:26:27 +02:00
Local Dev
8fcc0e2433 feat(theseus/aegis): EIP-712 signTypedData_v4 + Solana multi-signer send
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.
2026-09-07 22:19:51 +02:00
Local Dev
5880ba3507 feat(theseus/aegis): EIP-1193 + Solana wallet-adapter bridges; BTC signet
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.
2026-09-07 22:08:56 +02:00
Local Dev
cffb956a4c feat(theseus/addons): signed add-on update endpoint, à la Firefox XPI
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.
2026-09-07 21:58:30 +02:00
Local Dev
48cb497f59 feat(theseus/aegis): BTC send from BIP44 + BIP86 addresses
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.
2026-09-07 21:55:44 +02:00
Local Dev
af8e167120 feat(theseus/aegis): BTC address-family picker (BIP44/49/84/86 + Taproot)
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.
2026-09-07 21:23:15 +02:00
Local Dev
a0a22bc69a feat(theseus/aegis): Bitcoin adapter (mainnet + testnet3, BIP84 native SegWit)
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).
2026-09-07 21:15:45 +02:00
Local Dev
c2be569ac1 fix(theseus/addons): reseed bundled add-ons when their version bumps
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.
2026-09-07 20:53:21 +02:00
Local Dev
65c306d553 feat(theseus/aegis): Ethereum + Solana adapters, DGB address-family picker, Aegis-branded shield
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).
2026-09-07 20:31:27 +02:00
Local Dev
4c63ae1bc7 feat(theseus/aegis): DGB adapter on @dgb-wallet/{core,psbt} vendored packages
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.
2026-09-07 02:19:44 +02:00
Local Dev
118de0ef5c feat(theseus/aegis): fold Sia into the addon; add DGB (BIP84 native SegWit)
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.
2026-09-07 01:56:25 +02:00
Local Dev
a3810ef1eb feat(theseus/aegis): SVG coin logos, two-step coin/network picker, BCH Chipnet
- 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.
2026-09-07 01:07:31 +02:00
Local Dev
15694195d6 feat(theseus/screenshot): full-tab editor + toolbar-menu + open-tab capabilities
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
2026-09-07 00:56:57 +02:00
Local Dev
c9106d4ede fix(theseus/light): darker acid (#3a5c00) + add missing overrides in addon panels
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.
2026-09-07 00:56:33 +02:00
Local Dev
5642959eca feat(theseus/screenshot): bundled screenshot add-on (visible / full page / region)
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.
2026-09-07 00:18:48 +02:00
Local Dev
5574641fb9 feat(theseus/aegis): multi-wallet + Tron mainnet + Tron Nile in the bundled addon
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.
2026-09-06 22:00:27 +02:00
Local Dev
7931d981aa feat(theseus/siawallet): bundled Siacoin wallet add-on (walletd-backed, v2)
Second bundled wallet, same shape as bchwallet:
- keys: api.vault.derive("siawallet/mainnet/0") as the seed for walletd's
  KeyFromSeed(seed, index) (blake2b(seed||index) -> ed25519); addresses are
  standard unlock hashes, so a future walletd seed import yields the same
  addresses. Seed and keys live in memory only.
- lib/sia.js: Sia binary encoder, StandardUnlockHash, address checksum,
  v2 InputSigHash ("sia/sig/input|" + replay byte 2 + transaction
  semantics), transaction weight, walletd JSON. Address hashing and the
  sighash were verified against real mainnet v2 transactions (signatures
  from block 591853 verify under this implementation).
- lib/walletd.js: address-scoped walletd HTTP client (tip, fee, balance,
  outputs with proofs, events, broadcast). The node URL is a user setting
  with no default; hosted providers embed the access key in the path, so
  only the origin is ever displayed or logged.
- lib/wallet.js: gap-limit discovery via events, mature/immature balance,
  history deltas from v1/v2/foundation/miner events, largest-first
  selection with change to the current address, fee = walletd rate x
  weight x 1-3 multiplier, broadcast with the outputs' basis. A signed tx
  built here was accepted structurally by a live walletd (rejected only
  for the stub key not owning the parent).
- panel: Receive (QR), Send, History, Settings (node URL, derivation info,
  seed reveal behind approval, connected sites); gates for locked vault,
  no vault, no node URL.
- window.siacoin dapp bridge: getAddress (rememberable), signAndSend with
  100/1,000/10,000 SC allowances, signMessage (ed25519 over blake2b-256 of
  the message) — same approval and permission rules as the BCH wallet.
2026-09-06 18:49:13 +02:00
Local Dev
40391798e0 feat(theseus/bchwallet): per-site payment allowance for remembered sends
The dapp send approval gains an "Afterwards" dropdown: ask every time, or
allow up to 0.001 / 0.01 / 0.1 BCH more without asking. The allowance is
stored as permissions[origin].sendTx {capSats, usedSats}; sends within the
remainder go through silently and draw it down, a larger request re-prompts
(showing what is left) and the choice made there replaces the allowance.
No unlimited option. Settings > Connected sites shows the remaining budget
and Revoke clears it. Message signing still asks every time.

Host: approvalModal accepts `select` {id, label, options}; a chosen value
comes back as "+<id>=<value>" and is validated against the offered options.
2026-09-06 12:43:17 +02:00
Local Dev
67939b1493 feat(theseus/bchwallet): window.bitcoincash dapp bridge with per-origin permissions
wallet-inject.js runs in the isolated world of https://*.x pages and exposes
window.bitcoincash { isTheseus, version, network, getAddress, signAndSend,
signMessage }. Every call is routed page -> addon-page-msg -> activate()
handler -> approval overlay showing the requesting origin:
- getAddress: approval with an "always allow" checkbox; grants persist in
  api.storage.permissions and are listed/revocable under Settings.
- signAndSend / signMessage: approval on every call, never remembered.
  signMessage returns a BIP-137 recoverable signature (verified offline).
- one pending approval per origin; page-facing errors never echo balance.
Host fix: the inject IPC assigned event.returnValue twice, so pages always
got an empty script list.
2026-09-06 02:56:34 +02:00
Local Dev
821cc8e808 feat(theseus/bchwallet): send — plan, approval overlay, sign, broadcast
Send tab: recipient (cashaddr or legacy, testnet rejected), amount with
BCH/sat toggle and Max, 1-5 sat/B fee slider, live fee/total preview via
planSend. Sending goes plan -> approval-modal (To/Amount/Fee/Total) ->
ECDSA DER + SIGHASH_ALL|FORKID -> blockchain.transaction.broadcast, then
shows the txid with an explorer link. Confirmed coins are spent before
unconfirmed; dust change folds into the fee. Verified end to end against a
fake Fulcrum: broadcast tx re-parsed, sighash recomputed, signature checks.
2026-09-06 02:49:09 +02:00
Local Dev
de576935c1 feat(theseus/bchwallet): receive + history — vault-derived keys, cashaddr, QR, electrum
Wallet core on mainnet:
- keys from api.vault.derive("bchwallet/mainnet/0") -> BIP32 m/44'/145'/0'
  (@scure/bip32), never persisted; wiped on deactivate.
- lib/cashaddr.js (encode/decode + legacy Base58Check, spec vectors pass),
  lib/keys.js (hash160, p2pkh, electrum scripthash, ECDSA DER + BIP-137
  recoverable signing), lib/tx.js (serialization, SIGHASH_ALL|FORKID
  digest, coin selection, fee estimate), lib/electrum.js (Fulcrum WSS
  client with failover + subscriptions), lib/wallet.js (gap-limit scan,
  balance, UTXOs, 25-tx history with per-tx deltas, cached public txs).
- qr.js: dependency-free QR encoder (byte mode, v1-10, EC M/L; verified
  against jsQR).
- panel: balance header, Receive (QR, copy, next unused address, explorer),
  History (deltas, confirmations, explorer links), Settings (derivation
  path, electrum server list, xpub / approval-gated xprv reveal). Locked
  and not-set-up vault states explained in-panel.
- host: api.import for ESM-only deps, api.openTab for explorer links; an
  add-on whose activate() throws is no longer listed twice.
2026-09-06 02:46:41 +02:00
Local Dev
dcab5c10ec feat(theseus/bchwallet): bundled Bitcoin Cash wallet add-on skeleton
Manifest declaring sidebar-panel, vault-derive, page-inject (https://*.x)
and approval-modal; registers the Wallet sidebar panel. Shows up in
Settings > Extensions and opens from the sidebar.
2026-09-06 02:33:27 +02:00
Local Dev
0117986657 Theseus: add-on framework MVP + Notepad reference add-on
New subsystem for extending Theseus with folders on disk. Each add-on
lives at <userData>/addons/<id>/ with an addon.json manifest and a
CommonJS entry that exports activate(api). Nothing about a private
add-on ships in the public installer - drop the folder, restart, it's
live. Bundled reference add-ons ride in the packaged app under
resources/bundled-addons/ and are seeded into <userData>/addons/ on
first boot; the framework treats seeded and drop-in add-ons the same.

Files:
- addons-host.js         Loader + api.registerSidebarPanel() + per-
                         addon storage on <userData>/addons-data/.
                         Kept at the CommonJS-scoped top level (lib/
                         is ESM-scoped via its own package.json).
- sidebar-preload.js     Runs in every sidebar panel. Exposes
                         window.silentmode.storage.{get,set,all} +
                         onVisibility. Main-side handlers derive the
                         add-on id from the sender file:// URL, so a
                         panel can only touch its own store.
- bundled-addons/notepad/  Reference add-on: addon.json, index.js,
                         note.html. Autosaving textarea with char /
                         word count.

main.js:
- Extension point: sidebar-panel. One right-anchored WebContentsView
  (SIDEBAR_W=340) hosts the current panel; layout() shrinks the tab
  views by the sidebar width when visible. First registered panel
  wins for MVP; picker for multiple panels lands later.
- initAddons() at app.whenReady(): seedBundledAddons, then
  AddonHost.discoverAndActivate.
- IPC surface: sidebar-toggle / sidebar-open / sidebar-close /
  sidebar-state, addons-list / addons-set-enabled / addons-reveal /
  addons-open-dir / addons-reload, and origin-gated
  addon-storage-get/set/all.
- Settings gains `disabledAddons: []` — off-toggled ids persist and
  the loader honours them without a restart (discoverAndActivate
  runs again on toggle).

chrome.html: toolbar sidebar-toggle button, hidden until at least one
add-on has registered a sidebar panel.

settings.html: new "Add-ons" section under privacy. Lists installed
add-ons with icon / name / version / description / capabilities;
per-add-on enable/disable toggle + Show folder button; page-level
Reload and Open add-ons folder buttons; warning note about the trust
model.

package.json: build.files gains sidebar-preload.js + addons-host.js.
extraResources gains bundled-addons/ so the packaged app carries the
reference notepad for the first-boot seed.

Verified: `npm start` boots, addons-host discovers the notepad,
activates it, registers one sidebar panel. Log confirms
"1 installed, 1 enabled, 1 sidebar panels". Actual sidebar rendering
+ notepad UI need clicked-through validation on a real install.

Not shipped yet - deploy still blocked on the fail2ban VPS SSH ban.
Ships as 0.2.0 once SSH clears (this is a new subsystem, not a fix).
2026-08-31 13:51:08 +02:00