From 2e54bf5e5a9b5bca2b75e52b17321d5fbfd57024 Mon Sep 17 00:00:00 2001 From: Local Dev Date: Tue, 8 Sep 2026 18:17:25 +0200 Subject: [PATCH] Ship Theseus 0.3.28 5d15508b (Aegis update card + DevTools in tab sidebar + real favicons) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../{bchwallet => aegis}/addon.json | 0 .../electrum-servers.json | 0 bundled-addons/{bchwallet => aegis}/index.js | 0 .../{bchwallet => aegis}/lib/base58check.js | 0 .../{bchwallet => aegis}/lib/cashaddr.js | 0 .../{bchwallet => aegis}/lib/chain-bch.js | 0 .../{bchwallet => aegis}/lib/chain-btc.js | 0 .../{bchwallet => aegis}/lib/chain-dgb.js | 0 .../{bchwallet => aegis}/lib/chain-eth.js | 0 .../{bchwallet => aegis}/lib/chain-sia.js | 0 .../{bchwallet => aegis}/lib/chain-sol.js | 0 .../{bchwallet => aegis}/lib/chain-tron.js | 0 .../lib/dgb/core/address.d.ts | 0 .../lib/dgb/core/address.js | 0 .../lib/dgb/core/descriptor.d.ts | 0 .../lib/dgb/core/descriptor.js | 0 .../{bchwallet => aegis}/lib/dgb/core/hd.d.ts | 0 .../{bchwallet => aegis}/lib/dgb/core/hd.js | 0 .../lib/dgb/core/index.d.ts | 0 .../lib/dgb/core/index.js | 0 .../lib/dgb/core/network.d.ts | 0 .../lib/dgb/core/network.js | 0 .../lib/dgb/core/package.json | 0 .../lib/dgb/core/seed.d.ts | 0 .../{bchwallet => aegis}/lib/dgb/core/seed.js | 0 .../lib/dgb/core/wif.d.ts | 0 .../{bchwallet => aegis}/lib/dgb/core/wif.js | 0 .../lib/dgb/psbt/build.d.ts | 0 .../lib/dgb/psbt/build.js | 0 .../lib/dgb/psbt/index.d.ts | 0 .../lib/dgb/psbt/index.js | 0 .../lib/dgb/psbt/package.json | 0 .../lib/dgb/psbt/sign.d.ts | 0 .../{bchwallet => aegis}/lib/dgb/psbt/sign.js | 0 .../lib/dgb/psbt/types.d.ts | 0 .../lib/dgb/psbt/types.js | 0 .../{bchwallet => aegis}/lib/eip712.js | 0 .../{bchwallet => aegis}/lib/electrum.js | 0 .../{bchwallet => aegis}/lib/keys.js | 0 .../{bchwallet => aegis}/lib/sia/keys.js | 0 .../{bchwallet => aegis}/lib/sia/sia.js | 0 .../{bchwallet => aegis}/lib/sia/wallet.js | 0 .../{bchwallet => aegis}/lib/sia/walletd.js | 0 .../{bchwallet => aegis}/lib/sol-spl.js | 0 bundled-addons/{bchwallet => aegis}/lib/tx.js | 0 .../{bchwallet => aegis}/lib/wallet.js | 0 .../{bchwallet => aegis}/panel.html | 0 bundled-addons/{bchwallet => aegis}/panel.js | 0 bundled-addons/{bchwallet => aegis}/qr.js | 0 .../{bchwallet => aegis}/wallet-inject.js | 0 bundled-addons/siawallet/addon.json | 14 - bundled-addons/siawallet/index.js | 284 ------------------ bundled-addons/siawallet/lib/keys.js | 29 -- bundled-addons/siawallet/lib/sia.js | 142 --------- bundled-addons/siawallet/lib/wallet.js | 201 ------------- bundled-addons/siawallet/lib/walletd.js | 42 --- bundled-addons/siawallet/panel.html | 181 ----------- bundled-addons/siawallet/panel.js | 203 ------------- bundled-addons/siawallet/qr.js | 218 -------------- bundled-addons/siawallet/wallet-inject.js | 27 -- package.json | 2 +- 61 files changed, 1 insertion(+), 1342 deletions(-) rename bundled-addons/{bchwallet => aegis}/addon.json (100%) rename bundled-addons/{bchwallet => aegis}/electrum-servers.json (100%) rename bundled-addons/{bchwallet => aegis}/index.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/base58check.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/cashaddr.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/chain-bch.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/chain-btc.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/chain-dgb.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/chain-eth.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/chain-sia.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/chain-sol.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/chain-tron.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/core/address.d.ts (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/core/address.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/core/descriptor.d.ts (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/core/descriptor.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/core/hd.d.ts (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/core/hd.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/core/index.d.ts (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/core/index.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/core/network.d.ts (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/core/network.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/core/package.json (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/core/seed.d.ts (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/core/seed.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/core/wif.d.ts (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/core/wif.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/psbt/build.d.ts (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/psbt/build.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/psbt/index.d.ts (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/psbt/index.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/psbt/package.json (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/psbt/sign.d.ts (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/psbt/sign.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/psbt/types.d.ts (100%) rename bundled-addons/{bchwallet => aegis}/lib/dgb/psbt/types.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/eip712.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/electrum.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/keys.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/sia/keys.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/sia/sia.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/sia/wallet.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/sia/walletd.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/sol-spl.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/tx.js (100%) rename bundled-addons/{bchwallet => aegis}/lib/wallet.js (100%) rename bundled-addons/{bchwallet => aegis}/panel.html (100%) rename bundled-addons/{bchwallet => aegis}/panel.js (100%) rename bundled-addons/{bchwallet => aegis}/qr.js (100%) rename bundled-addons/{bchwallet => aegis}/wallet-inject.js (100%) delete mode 100644 bundled-addons/siawallet/addon.json delete mode 100644 bundled-addons/siawallet/index.js delete mode 100644 bundled-addons/siawallet/lib/keys.js delete mode 100644 bundled-addons/siawallet/lib/sia.js delete mode 100644 bundled-addons/siawallet/lib/wallet.js delete mode 100644 bundled-addons/siawallet/lib/walletd.js delete mode 100644 bundled-addons/siawallet/panel.html delete mode 100644 bundled-addons/siawallet/panel.js delete mode 100644 bundled-addons/siawallet/qr.js delete mode 100644 bundled-addons/siawallet/wallet-inject.js diff --git a/bundled-addons/bchwallet/addon.json b/bundled-addons/aegis/addon.json similarity index 100% rename from bundled-addons/bchwallet/addon.json rename to bundled-addons/aegis/addon.json diff --git a/bundled-addons/bchwallet/electrum-servers.json b/bundled-addons/aegis/electrum-servers.json similarity index 100% rename from bundled-addons/bchwallet/electrum-servers.json rename to bundled-addons/aegis/electrum-servers.json diff --git a/bundled-addons/bchwallet/index.js b/bundled-addons/aegis/index.js similarity index 100% rename from bundled-addons/bchwallet/index.js rename to bundled-addons/aegis/index.js diff --git a/bundled-addons/bchwallet/lib/base58check.js b/bundled-addons/aegis/lib/base58check.js similarity index 100% rename from bundled-addons/bchwallet/lib/base58check.js rename to bundled-addons/aegis/lib/base58check.js diff --git a/bundled-addons/bchwallet/lib/cashaddr.js b/bundled-addons/aegis/lib/cashaddr.js similarity index 100% rename from bundled-addons/bchwallet/lib/cashaddr.js rename to bundled-addons/aegis/lib/cashaddr.js diff --git a/bundled-addons/bchwallet/lib/chain-bch.js b/bundled-addons/aegis/lib/chain-bch.js similarity index 100% rename from bundled-addons/bchwallet/lib/chain-bch.js rename to bundled-addons/aegis/lib/chain-bch.js diff --git a/bundled-addons/bchwallet/lib/chain-btc.js b/bundled-addons/aegis/lib/chain-btc.js similarity index 100% rename from bundled-addons/bchwallet/lib/chain-btc.js rename to bundled-addons/aegis/lib/chain-btc.js diff --git a/bundled-addons/bchwallet/lib/chain-dgb.js b/bundled-addons/aegis/lib/chain-dgb.js similarity index 100% rename from bundled-addons/bchwallet/lib/chain-dgb.js rename to bundled-addons/aegis/lib/chain-dgb.js diff --git a/bundled-addons/bchwallet/lib/chain-eth.js b/bundled-addons/aegis/lib/chain-eth.js similarity index 100% rename from bundled-addons/bchwallet/lib/chain-eth.js rename to bundled-addons/aegis/lib/chain-eth.js diff --git a/bundled-addons/bchwallet/lib/chain-sia.js b/bundled-addons/aegis/lib/chain-sia.js similarity index 100% rename from bundled-addons/bchwallet/lib/chain-sia.js rename to bundled-addons/aegis/lib/chain-sia.js diff --git a/bundled-addons/bchwallet/lib/chain-sol.js b/bundled-addons/aegis/lib/chain-sol.js similarity index 100% rename from bundled-addons/bchwallet/lib/chain-sol.js rename to bundled-addons/aegis/lib/chain-sol.js diff --git a/bundled-addons/bchwallet/lib/chain-tron.js b/bundled-addons/aegis/lib/chain-tron.js similarity index 100% rename from bundled-addons/bchwallet/lib/chain-tron.js rename to bundled-addons/aegis/lib/chain-tron.js diff --git a/bundled-addons/bchwallet/lib/dgb/core/address.d.ts b/bundled-addons/aegis/lib/dgb/core/address.d.ts similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/core/address.d.ts rename to bundled-addons/aegis/lib/dgb/core/address.d.ts diff --git a/bundled-addons/bchwallet/lib/dgb/core/address.js b/bundled-addons/aegis/lib/dgb/core/address.js similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/core/address.js rename to bundled-addons/aegis/lib/dgb/core/address.js diff --git a/bundled-addons/bchwallet/lib/dgb/core/descriptor.d.ts b/bundled-addons/aegis/lib/dgb/core/descriptor.d.ts similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/core/descriptor.d.ts rename to bundled-addons/aegis/lib/dgb/core/descriptor.d.ts diff --git a/bundled-addons/bchwallet/lib/dgb/core/descriptor.js b/bundled-addons/aegis/lib/dgb/core/descriptor.js similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/core/descriptor.js rename to bundled-addons/aegis/lib/dgb/core/descriptor.js diff --git a/bundled-addons/bchwallet/lib/dgb/core/hd.d.ts b/bundled-addons/aegis/lib/dgb/core/hd.d.ts similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/core/hd.d.ts rename to bundled-addons/aegis/lib/dgb/core/hd.d.ts diff --git a/bundled-addons/bchwallet/lib/dgb/core/hd.js b/bundled-addons/aegis/lib/dgb/core/hd.js similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/core/hd.js rename to bundled-addons/aegis/lib/dgb/core/hd.js diff --git a/bundled-addons/bchwallet/lib/dgb/core/index.d.ts b/bundled-addons/aegis/lib/dgb/core/index.d.ts similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/core/index.d.ts rename to bundled-addons/aegis/lib/dgb/core/index.d.ts diff --git a/bundled-addons/bchwallet/lib/dgb/core/index.js b/bundled-addons/aegis/lib/dgb/core/index.js similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/core/index.js rename to bundled-addons/aegis/lib/dgb/core/index.js diff --git a/bundled-addons/bchwallet/lib/dgb/core/network.d.ts b/bundled-addons/aegis/lib/dgb/core/network.d.ts similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/core/network.d.ts rename to bundled-addons/aegis/lib/dgb/core/network.d.ts diff --git a/bundled-addons/bchwallet/lib/dgb/core/network.js b/bundled-addons/aegis/lib/dgb/core/network.js similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/core/network.js rename to bundled-addons/aegis/lib/dgb/core/network.js diff --git a/bundled-addons/bchwallet/lib/dgb/core/package.json b/bundled-addons/aegis/lib/dgb/core/package.json similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/core/package.json rename to bundled-addons/aegis/lib/dgb/core/package.json diff --git a/bundled-addons/bchwallet/lib/dgb/core/seed.d.ts b/bundled-addons/aegis/lib/dgb/core/seed.d.ts similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/core/seed.d.ts rename to bundled-addons/aegis/lib/dgb/core/seed.d.ts diff --git a/bundled-addons/bchwallet/lib/dgb/core/seed.js b/bundled-addons/aegis/lib/dgb/core/seed.js similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/core/seed.js rename to bundled-addons/aegis/lib/dgb/core/seed.js diff --git a/bundled-addons/bchwallet/lib/dgb/core/wif.d.ts b/bundled-addons/aegis/lib/dgb/core/wif.d.ts similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/core/wif.d.ts rename to bundled-addons/aegis/lib/dgb/core/wif.d.ts diff --git a/bundled-addons/bchwallet/lib/dgb/core/wif.js b/bundled-addons/aegis/lib/dgb/core/wif.js similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/core/wif.js rename to bundled-addons/aegis/lib/dgb/core/wif.js diff --git a/bundled-addons/bchwallet/lib/dgb/psbt/build.d.ts b/bundled-addons/aegis/lib/dgb/psbt/build.d.ts similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/psbt/build.d.ts rename to bundled-addons/aegis/lib/dgb/psbt/build.d.ts diff --git a/bundled-addons/bchwallet/lib/dgb/psbt/build.js b/bundled-addons/aegis/lib/dgb/psbt/build.js similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/psbt/build.js rename to bundled-addons/aegis/lib/dgb/psbt/build.js diff --git a/bundled-addons/bchwallet/lib/dgb/psbt/index.d.ts b/bundled-addons/aegis/lib/dgb/psbt/index.d.ts similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/psbt/index.d.ts rename to bundled-addons/aegis/lib/dgb/psbt/index.d.ts diff --git a/bundled-addons/bchwallet/lib/dgb/psbt/index.js b/bundled-addons/aegis/lib/dgb/psbt/index.js similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/psbt/index.js rename to bundled-addons/aegis/lib/dgb/psbt/index.js diff --git a/bundled-addons/bchwallet/lib/dgb/psbt/package.json b/bundled-addons/aegis/lib/dgb/psbt/package.json similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/psbt/package.json rename to bundled-addons/aegis/lib/dgb/psbt/package.json diff --git a/bundled-addons/bchwallet/lib/dgb/psbt/sign.d.ts b/bundled-addons/aegis/lib/dgb/psbt/sign.d.ts similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/psbt/sign.d.ts rename to bundled-addons/aegis/lib/dgb/psbt/sign.d.ts diff --git a/bundled-addons/bchwallet/lib/dgb/psbt/sign.js b/bundled-addons/aegis/lib/dgb/psbt/sign.js similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/psbt/sign.js rename to bundled-addons/aegis/lib/dgb/psbt/sign.js diff --git a/bundled-addons/bchwallet/lib/dgb/psbt/types.d.ts b/bundled-addons/aegis/lib/dgb/psbt/types.d.ts similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/psbt/types.d.ts rename to bundled-addons/aegis/lib/dgb/psbt/types.d.ts diff --git a/bundled-addons/bchwallet/lib/dgb/psbt/types.js b/bundled-addons/aegis/lib/dgb/psbt/types.js similarity index 100% rename from bundled-addons/bchwallet/lib/dgb/psbt/types.js rename to bundled-addons/aegis/lib/dgb/psbt/types.js diff --git a/bundled-addons/bchwallet/lib/eip712.js b/bundled-addons/aegis/lib/eip712.js similarity index 100% rename from bundled-addons/bchwallet/lib/eip712.js rename to bundled-addons/aegis/lib/eip712.js diff --git a/bundled-addons/bchwallet/lib/electrum.js b/bundled-addons/aegis/lib/electrum.js similarity index 100% rename from bundled-addons/bchwallet/lib/electrum.js rename to bundled-addons/aegis/lib/electrum.js diff --git a/bundled-addons/bchwallet/lib/keys.js b/bundled-addons/aegis/lib/keys.js similarity index 100% rename from bundled-addons/bchwallet/lib/keys.js rename to bundled-addons/aegis/lib/keys.js diff --git a/bundled-addons/bchwallet/lib/sia/keys.js b/bundled-addons/aegis/lib/sia/keys.js similarity index 100% rename from bundled-addons/bchwallet/lib/sia/keys.js rename to bundled-addons/aegis/lib/sia/keys.js diff --git a/bundled-addons/bchwallet/lib/sia/sia.js b/bundled-addons/aegis/lib/sia/sia.js similarity index 100% rename from bundled-addons/bchwallet/lib/sia/sia.js rename to bundled-addons/aegis/lib/sia/sia.js diff --git a/bundled-addons/bchwallet/lib/sia/wallet.js b/bundled-addons/aegis/lib/sia/wallet.js similarity index 100% rename from bundled-addons/bchwallet/lib/sia/wallet.js rename to bundled-addons/aegis/lib/sia/wallet.js diff --git a/bundled-addons/bchwallet/lib/sia/walletd.js b/bundled-addons/aegis/lib/sia/walletd.js similarity index 100% rename from bundled-addons/bchwallet/lib/sia/walletd.js rename to bundled-addons/aegis/lib/sia/walletd.js diff --git a/bundled-addons/bchwallet/lib/sol-spl.js b/bundled-addons/aegis/lib/sol-spl.js similarity index 100% rename from bundled-addons/bchwallet/lib/sol-spl.js rename to bundled-addons/aegis/lib/sol-spl.js diff --git a/bundled-addons/bchwallet/lib/tx.js b/bundled-addons/aegis/lib/tx.js similarity index 100% rename from bundled-addons/bchwallet/lib/tx.js rename to bundled-addons/aegis/lib/tx.js diff --git a/bundled-addons/bchwallet/lib/wallet.js b/bundled-addons/aegis/lib/wallet.js similarity index 100% rename from bundled-addons/bchwallet/lib/wallet.js rename to bundled-addons/aegis/lib/wallet.js diff --git a/bundled-addons/bchwallet/panel.html b/bundled-addons/aegis/panel.html similarity index 100% rename from bundled-addons/bchwallet/panel.html rename to bundled-addons/aegis/panel.html diff --git a/bundled-addons/bchwallet/panel.js b/bundled-addons/aegis/panel.js similarity index 100% rename from bundled-addons/bchwallet/panel.js rename to bundled-addons/aegis/panel.js diff --git a/bundled-addons/bchwallet/qr.js b/bundled-addons/aegis/qr.js similarity index 100% rename from bundled-addons/bchwallet/qr.js rename to bundled-addons/aegis/qr.js diff --git a/bundled-addons/bchwallet/wallet-inject.js b/bundled-addons/aegis/wallet-inject.js similarity index 100% rename from bundled-addons/bchwallet/wallet-inject.js rename to bundled-addons/aegis/wallet-inject.js diff --git a/bundled-addons/siawallet/addon.json b/bundled-addons/siawallet/addon.json deleted file mode 100644 index 5c44c27..0000000 --- a/bundled-addons/siawallet/addon.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "id": "siawallet", - "name": "Siacoin Wallet", - "version": "0.1.0", - "description": "Send and receive Siacoin with keys derived from your Theseus vault. Talks to any walletd node you point it at; dapps on .x sites can request payments through window.siacoin.", - "author": "Silent Mode", - "icon": "Ⓢ", - "main": "index.js", - "capabilities": ["sidebar-panel", "vault-derive", "page-inject", "approval-modal"], - "page-inject": { - "preload": "wallet-inject.js", - "origins": ["https://*.x/*"] - } -} diff --git a/bundled-addons/siawallet/index.js b/bundled-addons/siawallet/index.js deleted file mode 100644 index 6a33ce6..0000000 --- a/bundled-addons/siawallet/index.js +++ /dev/null @@ -1,284 +0,0 @@ -// Siacoin Wallet — bundled Theseus add-on. activate() runs in the main -// process; keys are re-derived from the password vault on every launch and -// live only in memory. Chain access goes through a walletd node the user -// configures (Settings); no default endpoint ships with the add-on. -const NETWORK = "mainnet"; -const PURPOSE = "siawallet/mainnet/0"; -const EXPLORER_TX = "https://siascan.com/tx/"; -const EXPLORER_ADDR = "https://siascan.com/address/"; -const SC = 10n ** 24n; -const ALLOWANCES = [100n * SC, 1000n * SC, 10000n * SC]; - -let ctx = null; // { api, d, keys, wallet, client, root, phase, error } - -async function deps(api) { - const { ed25519 } = await api.import("@noble/curves/ed25519.js"); - const { blake2b } = await api.import("@noble/hashes/blake2.js"); - const sia = require("./lib/sia.js")({ ed25519, blake2b }); - const keysLib = require("./lib/keys.js")({ sia }); - const walletd = require("./lib/walletd.js")({ log: (...a) => api.log("walletd", ...a) }); - return { sia, keysLib, walletd }; -} - -const walletdUrl = (api) => String(api.storage.get("walletdUrl", "") || "").trim(); -const fmt = (h) => ctx.d.sia.formatSC(h); - -function snapshot() { - const c = ctx; - const base = { - network: NETWORK, phase: c.phase, error: c.error, - server: c.client ? c.client.displayUrl : null, - hasUrl: !!walletdUrl(c.api), - explorerTx: EXPLORER_TX, explorerAddr: EXPLORER_ADDR, - }; - if (c.wallet) Object.assign(base, c.wallet.snapshot()); - return base; -} -function emitState() { try { ctx.api.emit("state", snapshot()); } catch {} } -function setPhase(phase, error = null) { ctx.phase = phase; ctx.error = error; emitState(); } - -function buildWallet() { - const c = ctx; - if (c.wallet) { c.wallet.dispose(); c.wallet = null; } - if (!c.keys) c.keys = new c.d.keysLib.WalletKeys(c.root); - const url = walletdUrl(c.api); - if (!url) { setPhase("nourl"); return; } - if (!c.client) c.client = new c.d.walletd.Client(url); else c.client.setBase(url); - c.wallet = require("./lib/wallet.js")({ - client: c.client, keys: c.keys, sia: c.d.sia, storage: c.api.storage, - log: (...a) => c.api.log("wallet", ...a), onChange: emitState, - }); - c.api.log("wallet ready, receive address", c.keys.entry(0).address, "via", c.client.displayUrl); - setPhase("ready"); - c.wallet.refresh(true).then(() => c.wallet && c.wallet.startPolling()); -} - -async function deriveAndStart() { - const c = ctx; - setPhase("locked"); - try { c.root = await c.api.vault.derive(PURPOSE); } - catch (e) { - const msg = e?.message || String(e); - setPhase(/not set up/i.test(msg) ? "nosetup" : "error", msg); - return; - } - if (ctx !== c) return; - try { buildWallet(); } - catch (e) { setPhase("error", e?.message || String(e)); c.api.log("wallet build failed:", e?.message); } -} - -function requireReady() { - if (!ctx || ctx.phase !== "ready" || !ctx.wallet) throw new Error(ctx && ctx.phase === "nourl" ? "no walletd URL configured" : "wallet is not ready (vault locked?)"); - return ctx; -} -const fromPanel = (m) => { if (!m || m.from !== "panel") throw new Error("panel-only message"); }; -function planFrom(p) { - const c = requireReady(); - const spec = p || {}; - const targets = Array.isArray(spec.outputs) && spec.outputs.length - ? spec.outputs.map((o) => ({ to: o.to, value: toHastings(o.amount ?? o.value) })) - : [{ to: spec.to, value: toHastings(spec.amount ?? spec.value) }]; - return c.wallet.plan({ targets, feeMultiplier: spec.feeMultiplier, sendMax: !!spec.sendMax }); -} -// Amounts arrive as decimal hastings strings (or numbers for small values). -function toHastings(v) { - if (typeof v === "bigint") return v; - const s = String(v ?? "0").trim(); - if (!/^\d+$/.test(s)) throw new Error("amount must be an integer number of hastings"); - return BigInt(s); -} -function describePlan(plan) { - const sent = plan.recipients.reduce((a, r) => a + BigInt(r.value), 0n); - return { recipients: plan.recipients, fee: plan.fee.toString(), feePerByte: plan.feePerByte.toString(), inputs: plan.tx.inputs.length, change: plan.change.toString(), total: (sent + plan.fee).toString() }; -} - -function registerPanelMessages(api) { - api.onMessage("state", (_p, m) => { fromPanel(m); return snapshot(); }); - api.onMessage("refresh", async (_p, m) => { fromPanel(m); const c = requireReady(); await c.wallet.refresh(true); return snapshot(); }); - api.onMessage("nextAddress", (_p, m) => { fromPanel(m); const c = requireReady(); c.wallet.nextUnusedAddress(); return snapshot(); }); - api.onMessage("openUrl", (p, m) => { fromPanel(m); api.openTab(String(p && p.url || "")); return true; }); - api.onMessage("settings", (_p, m) => { fromPanel(m); return { walletdUrl: walletdUrl(api) }; }); - api.onMessage("setSettings", (p, m) => { - fromPanel(m); - const patch = p || {}; - if ("walletdUrl" in patch) { - const v = String(patch.walletdUrl || "").trim(); - if (v && !/^https?:\/\/[^\s]+$/i.test(v)) throw new Error("walletd URL must start with http:// or https://"); - api.storage.set("walletdUrl", v); - } - if (ctx && ctx.root) buildWallet(); - return snapshot(); - }); - api.onMessage("planSend", (p, m) => { fromPanel(m); return describePlan(planFrom(p)); }); - api.onMessage("send", async (p, m) => { - fromPanel(m); - const c = requireReady(); - const plan = planFrom(p); - const d = describePlan(plan); - const pick = await api.approvalModal({ - title: "Send Siacoin?", - origin: "Theseus wallet panel", - rows: [ - { label: "To", value: d.recipients[0].to, mono: true }, - { label: "Amount", value: fmt(d.recipients[0].value) + " SC", strong: true }, - { label: "Fee", value: fmt(d.fee) + " SC" }, - { label: "Total", value: fmt(d.total) + " SC" }, - ], - actions: [{ id: "send", label: "Send", primary: true }], - }); - if (pick !== "send") throw new Error("cancelled"); - return c.wallet.signAndBroadcast(plan); - }); - // Recovery: the 32-byte wallet seed (walletd KeyFromSeed scheme), only - // after an explicit confirmation in the approval overlay. - api.onMessage("recovery", async (p, m) => { - fromPanel(m); - const c = requireReady(); - const out = { purpose: PURPOSE, scheme: "walletd KeyFromSeed(seed, index); address = standard unlock hash", firstAddress: c.keys.entry(0).address }; - if (p && p.reveal) { - const pick = await api.approvalModal({ - title: "Reveal the wallet seed?", - origin: "Theseus wallet panel", - body: "Anyone holding this seed can spend every coin in this wallet. It stays on screen until you close the Settings tab.", - actions: [{ id: "reveal", label: "Reveal", danger: true }], - }); - if (pick === "reveal") out.seedHex = c.keys.seedHex; - } - return out; - }); -} - -// ---- dapp bridge (window.siacoin) ------------------------------------------ -// permissions: { [origin]: { readAddress: true, sendTx: { cap, used, grantedAt } } } -// cap/used are hastings as decimal strings. No unlimited option; signing -// always asks. -const pendingByOrigin = new Set(); -function permissions(api) { const p = api.storage.get("permissions", {}); return p && typeof p === "object" ? p : {}; } -const fromPage = (m) => { if (!m || m.from !== "page" || !m.origin) throw new Error("page-only message"); return m.origin; }; -async function withOriginLock(origin, fn) { - if (pendingByOrigin.has(origin)) throw new Error("a wallet request from this site is already waiting for approval"); - pendingByOrigin.add(origin); - try { return await fn(); } finally { pendingByOrigin.delete(origin); } -} - -function registerPageMessages(api) { - api.onMessage("getAddress", async (_p, m) => { - const origin = fromPage(m); - const c = requireReady(); - const perms = permissions(api); - if (perms[origin] && perms[origin].readAddress) return c.wallet.current().address; - return withOriginLock(origin, async () => { - const pick = await api.approvalModal({ - title: "Share your Siacoin address?", - origin, - body: "The site will see your current receiving address and can look up its balance and history on the public chain.", - rows: [{ label: "Address", value: c.wallet.current().address, mono: true }], - actions: [{ id: "allow", label: "Share", primary: true }], - checkbox: { id: "always", label: "Always allow this site to see my address" }, - }); - if (!pick.startsWith("allow")) throw new Error("user rejected"); - if (pick === "allow+always") { perms[origin] = { ...(perms[origin] || {}), readAddress: true }; api.storage.set("permissions", perms); emitState(); } - return c.wallet.current().address; - }); - }); - api.onMessage("signAndSend", async (p, m) => { - const origin = fromPage(m); - const c = requireReady(); - return withOriginLock(origin, async () => { - let plan; - try { plan = planFrom(p); } - catch (e) { throw new Error(/insufficient funds|balance/i.test(e?.message) ? "insufficient funds" : e?.message || String(e)); } - const d = describePlan(plan); - if (d.recipients.length > 8) throw new Error("too many outputs"); - const perms = permissions(api); - const budget = perms[origin] && perms[origin].sendTx; - const remaining = budget ? BigInt(budget.cap) - BigInt(budget.used || "0") : 0n; - const total = BigInt(d.total); - if (budget && total <= remaining) { - const r = await c.wallet.signAndBroadcast(plan); - budget.used = (BigInt(budget.used || "0") + total).toString(); - api.storage.set("permissions", perms); - emitState(); - api.log(`silent send ${fmt(total)} SC for ${origin}, ${fmt(remaining - total)} SC of allowance left`); - return { txid: r.txid }; - } - const rows = d.recipients.map((r, i) => ({ label: d.recipients.length > 1 ? `To #${i + 1}` : "To", value: r.to, mono: true })); - rows.push({ label: "Amount", value: fmt(d.recipients.reduce((a, r) => a + BigInt(r.value), 0n)) + " SC", strong: true }); - rows.push({ label: "Fee", value: fmt(d.fee) + " SC" }); - rows.push({ label: "Total", value: fmt(d.total) + " SC" }); - const pick = await api.approvalModal({ - title: "Send Siacoin?", - origin, - body: budget - ? `This payment is over what is left of the site's allowance (${fmt(remaining)} SC). Check the address and amount.` - : "This site is asking your wallet to pay. Check the address and amount.", - rows, - actions: [{ id: "send", label: "Send", primary: true }], - select: { - id: "cap", label: "Afterwards", - options: [{ value: "", label: "ask every time" }, ...ALLOWANCES.map((s) => ({ value: s.toString(), label: `allow up to ${fmt(s)} SC more without asking` }))], - }, - }); - const [action, ...flags] = pick.split("+"); - if (action !== "send") throw new Error("user rejected"); - const cap = flags.find((f) => f.startsWith("cap=")); - const capValue = cap ? cap.slice(4) : ""; - if (ALLOWANCES.some((a) => a.toString() === capValue)) { - perms[origin] = { ...(perms[origin] || {}), sendTx: { cap: capValue, used: "0", grantedAt: Date.now() } }; - api.storage.set("permissions", perms); - } else if (budget) { - delete perms[origin].sendTx; - api.storage.set("permissions", perms); - } - emitState(); - const r = await c.wallet.signAndBroadcast(plan); - return { txid: r.txid }; - }); - }); - api.onMessage("signMessage", async (p, m) => { - const origin = fromPage(m); - const c = requireReady(); - const message = String(p && p.message != null ? p.message : ""); - if (message.length > 4096) throw new Error("message too long"); - return withOriginLock(origin, async () => { - const entry = c.wallet.current(); - const pick = await api.approvalModal({ - title: "Sign a message?", - origin, - body: "Signing proves you control the address below. It moves no coins.", - rows: [{ label: "Message", value: message.length > 400 ? message.slice(0, 400) + "…" : message, mono: true }, { label: "Address", value: entry.address, mono: true }], - actions: [{ id: "sign", label: "Sign", primary: true }], - }); - if (pick !== "sign") throw new Error("user rejected"); - const digest = c.d.sia.b256(new TextEncoder().encode(message)); - const sig = c.keys.sign(entry, digest); - return { address: entry.address, publicKey: "ed25519:" + c.d.sia.toHex(entry.pub), signature: c.d.sia.toHex(sig) }; - }); - }); - api.onMessage("permissions", (_p, m) => { fromPanel(m); return permissions(api); }); - api.onMessage("revoke", (p, m) => { - fromPanel(m); - const perms = permissions(api); - delete perms[String(p && p.origin || "")]; - api.storage.set("permissions", perms); - return perms; - }); -} - -module.exports = { - activate(api) { - api.registerSidebarPanel({ id: "main", title: "Sia", icon: "Ⓢ", page: "panel.html" }); - const c = ctx = { api, d: null, keys: null, wallet: null, client: null, root: null, phase: "locked", error: null }; - registerPanelMessages(api); - registerPageMessages(api); - deps(api).then((d) => { if (ctx !== c) return; c.d = d; return deriveAndStart(); }) - .catch((e) => { if (ctx !== c) return; setPhase("error", e?.message || String(e)); api.log("startup failed:", e?.message); }); - }, - deactivate() { - const c = ctx; ctx = null; - if (!c) return; - try { c.wallet && c.wallet.dispose(); } catch {} - try { c.keys && c.keys.wipe(); } catch {} - if (c.root) c.root.fill(0); - }, -}; diff --git a/bundled-addons/siawallet/lib/keys.js b/bundled-addons/siawallet/lib/keys.js deleted file mode 100644 index c7260b6..0000000 --- a/bundled-addons/siawallet/lib/keys.js +++ /dev/null @@ -1,29 +0,0 @@ -// Key tree for the Sia wallet: index i -> ed25519 key via walletd's -// KeyFromSeed(root, i), address = standard unlock hash of the public key. -// Private keys stay inside this module; sign() is the only way out. -module.exports = function makeKeys({ sia }) { - class WalletKeys { - constructor(root32) { - this._root = Uint8Array.from(root32); - this._cache = new Map(); - } - entry(index) { - let e = this._cache.get(index); - if (!e) { - const k = sia.keyFromSeed(this._root, index); - e = { index, pub: k.pub, address32: k.address32, address: k.address, _priv: k.priv }; - this._cache.set(index, e); - } - return e; - } - sign(entry, msg) { return sia.sign(entry._priv, msg); } - // Revealed only on explicit user action in Settings. - get seedHex() { return sia.toHex(this._root); } - wipe() { - for (const e of this._cache.values()) e._priv.fill(0); - this._cache.clear(); - this._root.fill(0); - } - } - return { WalletKeys }; -}; diff --git a/bundled-addons/siawallet/lib/sia.js b/bundled-addons/siawallet/lib/sia.js deleted file mode 100644 index d7e119a..0000000 --- a/bundled-addons/siawallet/lib/sia.js +++ /dev/null @@ -1,142 +0,0 @@ -// Sia (v2 era) primitives: the Sia binary encoder, standard unlock-hash -// addresses, walletd's per-index key derivation, the v2 input signature hash -// and transaction weight. Mirrors go.sia.tech/core/types; every encoding -// here was checked against a real mainnet v2 transaction. -module.exports = function makeSia({ ed25519, blake2b }) { - const b256 = (data) => blake2b(data, { dkLen: 32 }); - const enc = new TextEncoder(); - const toHex = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join(""); - const fromHex = (h) => Uint8Array.from(String(h).replace(/^0x/, "").match(/../g) || [], (x) => parseInt(x, 16)); - - // ---- encoder --------------------------------------------------------------- - class Encoder { - constructor() { this.parts = []; this.length = 0; } - write(b) { this.parts.push(b); this.length += b.length; return this; } - u8(n) { return this.write(Uint8Array.from([n & 0xff])); } - bool(v) { return this.u8(v ? 1 : 0); } - u64(n) { - let v = BigInt(n); const out = new Uint8Array(8); - for (let i = 0; i < 8; i++) { out[i] = Number(v & 0xffn); v >>= 8n; } - return this.write(out); - } - bytes(b) { return this.u64(b.length).write(b); } - str(s) { return this.bytes(enc.encode(s)); } - // Currency is a 128-bit little-endian pair (lo, hi). - currency(hastings) { - const v = BigInt(hastings); - if (v < 0n || v >= (1n << 128n)) throw new Error("currency out of range"); - return this.u64(v & ((1n << 64n) - 1n)).u64(v >> 64n); - } - bytesOut() { - const out = new Uint8Array(this.length); let o = 0; - for (const p of this.parts) { out.set(p, o); o += p.length; } - return out; - } - } - const SPECIFIER_ED25519 = (() => { const s = new Uint8Array(16); s.set(enc.encode("ed25519")); return s; })(); - - // ---- addresses --------------------------------------------------------------- - const LEAF = 0, NODE = 1; - const sumPair = (a, b) => b256(Uint8Array.from([NODE, ...a, ...b])); - const leaf = (bytes) => b256(Uint8Array.from([LEAF, ...bytes])); - // Merkle root of the standard UnlockConditions {timelock 0, [pk], sigs 1}. - function standardUnlockHash(pk) { - const timelockHash = leaf(new Encoder().u64(0).bytesOut()); - const keyHash = leaf(new Encoder().write(SPECIFIER_ED25519).bytes(pk).bytesOut()); - const sigsHash = leaf(new Encoder().u64(1).bytesOut()); - return sumPair(sumPair(timelockHash, keyHash), sigsHash); - } - const addressString = (addr32) => toHex(addr32) + toHex(b256(addr32).slice(0, 6)); - function parseAddress(s) { - const t = String(s || "").trim().toLowerCase().replace(/^addr:/, ""); - if (!/^[0-9a-f]{76}$/.test(t)) throw new Error("address must be 76 hex characters"); - const raw = fromHex(t); - const body = raw.slice(0, 32); - if (toHex(b256(body).slice(0, 6)) !== toHex(raw.slice(32))) throw new Error("address checksum is wrong"); - return { bytes: body, address: t }; - } - - // ---- keys -------------------------------------------------------------------- - // walletd: key_i = ed25519 seed blake2b(seed32 || index u64le). - function keyFromSeed(seed32, index) { - const priv = b256(new Encoder().write(seed32).u64(index).bytesOut()); - const pub = ed25519.getPublicKey(priv); - return { priv, pub, address32: standardUnlockHash(pub), address: addressString(standardUnlockHash(pub)) }; - } - const sign = (priv, msg) => ed25519.sign(msg, priv); - const verify = (pub, msg, sig) => ed25519.verify(sig, msg, pub); - - // ---- v2 transactions --------------------------------------------------------- - // tx: { inputs: [{ parentId(hex) }], outputs: [{ value(BigInt), address32 }], minerFee(BigInt) } - // Sig hash = blake2b("sia/sig/input|" || 0x02 || V2TransactionSemantics). - function inputSigHash(tx) { - const e = new Encoder().write(enc.encode("sia/sig/input|")).u8(2); - e.u64(tx.inputs.length); - for (const i of tx.inputs) e.write(fromHex(i.parentId)); - e.u64(tx.outputs.length); - for (const o of tx.outputs) e.currency(o.value).write(o.address32); - e.u64(0).u64(0); // siafund inputs / outputs - e.u64(0).u64(0).u64(0); // contracts, revisions, resolutions - e.u64(0); // attestations - e.bytes(new Uint8Array(0)); // arbitrary data - e.bool(false); // new foundation address - e.currency(tx.minerFee); - return b256(e.bytesOut()); - } - // Weight = length of the full V2Transaction encoding (fees are per byte). - // Signed inputs carry the parent element with its Merkle proof, the policy - // and one 64-byte signature. - function weight(tx) { - const e = new Encoder().u8(2); - let fields = 0; - if (tx.inputs.length) fields |= 1; if (tx.outputs.length) fields |= 2; if (tx.minerFee > 0n) fields |= 1 << 10; - e.u64(fields); - if (tx.inputs.length) { - e.u64(tx.inputs.length); - for (const i of tx.inputs) { - e.u64(i.leafIndex || 0).u64((i.merkleProof || []).length); - for (const p of i.merkleProof || []) e.write(fromHex(p)); - e.write(fromHex(i.parentId)).currency(i.value).write(i.address32).u64(i.maturityHeight || 0); - // SatisfiedPolicy: version 1, op 7 (unlock conditions), uc, 1 sig, 0 preimages - e.u8(1).u8(7).u64(0).u64(1).write(SPECIFIER_ED25519).bytes(i.pub).u64(1); - e.u64(1).write(new Uint8Array(64)).u64(0); - } - } - if (tx.outputs.length) { e.u64(tx.outputs.length); for (const o of tx.outputs) e.currency(o.value).write(o.address32); } - if (tx.minerFee > 0n) e.currency(tx.minerFee); - return e.length; - } - // walletd JSON for /api/txpool/broadcast. - function toJson(tx, sigs) { - return { - siacoinInputs: tx.inputs.map((i, k) => ({ - parent: i.element, - satisfiedPolicy: { - policy: { type: "uc", policy: { timelock: 0, publicKeys: ["ed25519:" + toHex(i.pub)], signaturesRequired: 1 } }, - signatures: [toHex(sigs[k])], - }, - })), - siacoinOutputs: tx.outputs.map((o) => ({ value: o.value.toString(), address: addressString(o.address32) })), - minerFee: tx.minerFee.toString(), - }; - } - - // ---- units ------------------------------------------------------------------- - const HASTINGS_PER_SC = 10n ** 24n; - function formatSC(hastings, decimals = 6) { - const v = BigInt(hastings); const neg = v < 0n; const a = neg ? -v : v; - const whole = a / HASTINGS_PER_SC; - let frac = (a % HASTINGS_PER_SC).toString().padStart(24, "0").slice(0, decimals).replace(/0+$/, ""); - if (frac.length < 2) frac = frac.padEnd(2, "0"); - return (neg ? "-" : "") + whole.toString() + "." + frac; - } - function parseSC(text) { - const s = String(text || "").trim().replace(/,/g, ""); - if (!/^\d*(\.\d*)?$/.test(s) || s === "" || s === ".") throw new Error("amount must be a number"); - const [w = "0", f = ""] = s.split("."); - if (f.length > 24) throw new Error("too many decimals"); - return BigInt(w || "0") * HASTINGS_PER_SC + BigInt((f + "0".repeat(24)).slice(0, 24)); - } - - return { Encoder, standardUnlockHash, addressString, parseAddress, keyFromSeed, sign, verify, inputSigHash, weight, toJson, formatSC, parseSC, HASTINGS_PER_SC, toHex, fromHex, b256 }; -}; diff --git a/bundled-addons/siawallet/lib/wallet.js b/bundled-addons/siawallet/lib/wallet.js deleted file mode 100644 index a829237..0000000 --- a/bundled-addons/siawallet/lib/wallet.js +++ /dev/null @@ -1,201 +0,0 @@ -// Sia wallet state: address discovery, balance, history and v2 sends over a -// walletd client. Keys are the addon's derived key tree; nothing here touches -// UI or IPC. Amounts are BigInt hastings throughout. -module.exports = function makeWallet({ client, keys, sia, storage, log = () => {}, onChange = () => {} }) { - const GAP = 10; - const HISTORY_LIMIT = 25; - const state = { - used: new Set(), // indexes with any event - height: 0, - balance: { confirmed: 0n, immature: 0n }, - outputs: [], // spendable SiacoinElements with { index } - basis: null, - history: [], - receiveIndex: 0, - scanning: false, - error: null, - feePerByte: 0n, - }; - // Outputs we just spent stay hidden until walletd stops listing them. - const pendingSpent = new Map(); // id -> timestamp - - async function isUsed(entry) { - const ev = await client.events(entry.address, 1, 0); - return Array.isArray(ev) && ev.length > 0; - } - async function scan() { - const cursor = Number(storage.get("receiveCursor", 0)) || 0; - let gap = 0, i = 0; - while (gap < GAP || i < cursor + GAP) { - const e = keys.entry(i); - if (await isUsed(e)) { state.used.add(i); gap = 0; } else gap++; - i++; - } - let r = cursor; - while (state.used.has(r)) r++; - state.receiveIndex = r; - } - function watched() { - const idx = new Set(state.used); idx.add(state.receiveIndex); - return [...idx].map((i) => keys.entry(i)); - } - - async function loadOutputs() { - const tip = await client.tip(); - state.height = tip.height || 0; - let confirmed = 0n, immature = 0n; const outs = []; let basis = null; - for (const e of watched()) { - for (let offset = 0; ; offset += 100) { - const r = await client.outputs(e.address, 100, offset); - basis = r.basis || basis; - const list = Array.isArray(r.outputs) ? r.outputs : []; - for (const o of list) { - const v = BigInt(o.siacoinOutput.value); - if (o.maturityHeight > state.height) { immature += v; continue; } - if (pendingSpent.has(o.id)) continue; - confirmed += v; - outs.push({ element: o, id: o.id, value: v, entry: e }); - } - if (list.length < 100) break; - } - } - for (const [id, t] of pendingSpent) if (Date.now() - t > 20 * 60 * 1000) pendingSpent.delete(id); - state.outputs = outs; state.basis = basis; - state.balance = { confirmed, immature }; - try { state.feePerByte = await client.feePerByte(); } catch (e) { log("fee lookup failed:", e?.message); } - } - - // One row per event: net change for our addresses, type, confirmations. - async function loadHistory() { - const ours = new Set(watched().map((e) => e.address)); - const seen = new Map(); - for (const e of watched()) { - if (!state.used.has(e.index)) continue; - const evs = await client.events(e.address, HISTORY_LIMIT, 0); - for (const ev of Array.isArray(evs) ? evs : []) if (!seen.has(ev.id)) seen.set(ev.id, ev); - } - const rows = []; - for (const ev of seen.values()) { - let received = 0n, spent = 0n, to = null; - const d = ev.data || {}; - const type = String(ev.type || "").toLowerCase(); - if (type === "v2transaction" && d.transaction) { - for (const i of d.transaction.siacoinInputs || []) if (ours.has(i.parent.siacoinOutput.address)) spent += BigInt(i.parent.siacoinOutput.value); - for (const o of d.transaction.siacoinOutputs || []) { if (ours.has(o.address)) received += BigInt(o.value); else if (!to) to = o.address; } - } else if (type === "v1transaction" && d.transaction) { - for (const s of d.spentSiacoinElements || []) if (ours.has(s.siacoinOutput.address)) spent += BigInt(s.siacoinOutput.value); - for (const o of d.transaction.siacoinOutputs || []) { if (ours.has(o.address)) received += BigInt(o.value); else if (!to) to = o.address; } - } else if (d.siacoinElement) { - if (ours.has(d.siacoinElement.siacoinOutput.address)) received += BigInt(d.siacoinElement.siacoinOutput.value); - } - rows.push({ - id: ev.id, type: ev.type, height: ev.index ? ev.index.height : 0, confirmations: ev.confirmations || 0, - time: ev.timestamp ? Math.floor(Date.parse(ev.timestamp) / 1000) : 0, - delta: (received - spent).toString(), to: spent > received ? to : null, - maturityHeight: ev.maturityHeight || 0, - }); - } - rows.sort((a, b) => (b.height || Infinity) - (a.height || Infinity) || b.time - a.time); - state.history = rows.slice(0, HISTORY_LIMIT); - } - - async function refresh(full = false) { - if (state.scanning) return; - state.scanning = true; state.error = null; onChange(); - try { - if (full || !state.used.size && state.receiveIndex === 0) await scan(); - else { let r = Number(storage.get("receiveCursor", 0)) || 0; while (state.used.has(r)) r++; state.receiveIndex = r; } - await loadOutputs(); - await loadHistory(); - for (const o of state.outputs) state.used.add(o.entry.index); - let r = Number(storage.get("receiveCursor", 0)) || 0; - while (state.used.has(r)) r++; - state.receiveIndex = r; - } catch (e) { - state.error = e?.message || String(e); - log("refresh failed:", state.error); - } finally { state.scanning = false; onChange(); } - } - let pollTimer = null; - function startPolling(ms = 60000) { stopPolling(); pollTimer = setInterval(() => refresh(false), ms); } - function stopPolling() { clearInterval(pollTimer); pollTimer = null; } - - function current() { return keys.entry(state.receiveIndex); } - function nextUnusedAddress() { - let r = state.receiveIndex + 1; - while (state.used.has(r)) r++; - storage.set("receiveCursor", r); - state.receiveIndex = r; - onChange(); - return current(); - } - - // targets: [{ to, value: BigInt }]; feeMultiplier 1-3 over walletd's rate. - function plan({ targets, feeMultiplier = 1, sendMax = false }) { - const mult = BigInt(Math.min(3, Math.max(1, Math.round(Number(feeMultiplier) || 1)))); - const rate = state.feePerByte > 0n ? state.feePerByte * mult : 10n ** 19n * mult; - const outs = targets.map((t) => { - const a = sia.parseAddress(t.to); - return { value: BigInt(t.value || 0), address32: a.bytes, to: a.address }; - }); - const sorted = state.outputs.slice().sort((a, b) => (b.value > a.value ? 1 : b.value < a.value ? -1 : 0)); - const total = sorted.reduce((a, o) => a + o.value, 0n); - const change = current(); - const txOf = (inputs, outputs, fee) => ({ - inputs: inputs.map((o) => ({ - parentId: o.id, element: o.element, value: o.value, address32: o.entry.address32, pub: o.entry.pub, - leafIndex: o.element.stateElement.leafIndex, merkleProof: o.element.stateElement.merkleProof || [], maturityHeight: o.element.maturityHeight || 0, entry: o.entry, - })), - outputs, minerFee: fee, - }); - if (sendMax) { - if (outs.length !== 1) throw new Error("send max needs exactly one recipient"); - if (!sorted.length) throw new Error("no spendable balance"); - let fee = 0n; - for (let k = 0; k < 3; k++) fee = rate * BigInt(sia.weight(txOf(sorted, [{ value: total - fee, address32: outs[0].address32 }], fee))); - if (total <= fee) throw new Error("balance does not cover the fee"); - const tx = txOf(sorted, [{ value: total - fee, address32: outs[0].address32 }], fee); - return { tx, fee, recipients: [{ to: outs[0].to, value: (total - fee).toString() }], change: 0n, feePerByte: rate }; - } - const want = outs.reduce((a, o) => a + o.value, 0n); - for (const o of outs) if (o.value <= 0n) throw new Error("amount must be positive"); - const chosen = []; let sum = 0n; - for (const o of sorted) { - chosen.push(o); sum += o.value; - const withChange = [...outs, { value: 0n, address32: change.address32 }]; - const fee = rate * BigInt(sia.weight(txOf(chosen, withChange, 1n))); - if (sum >= want + fee) { - const rest = sum - want - fee; - const outputs = rest > 0n ? [...outs, { value: rest, address32: change.address32 }] : outs.slice(); - const tx = txOf(chosen, outputs, fee); - return { tx, fee, recipients: outs.map((o) => ({ to: o.to, value: o.value.toString() })), change: rest, feePerByte: rate }; - } - } - throw new Error("insufficient funds"); - } - - async function signAndBroadcast(p) { - const h = sia.inputSigHash(p.tx); - const sigs = p.tx.inputs.map((i) => keys.sign(i.entry, h)); - const json = sia.toJson(p.tx, sigs); - if (!state.basis) throw new Error("no chain basis for the outputs; refresh first"); - const r = await client.broadcast(state.basis, json); - const txid = r && r.v2transactions && r.v2transactions[0] && r.v2transactions[0].id; - for (const i of p.tx.inputs) pendingSpent.set(i.parentId, Date.now()); - log("broadcast", txid || "(no id returned)"); - setTimeout(() => refresh(false), 3000); - return { txid: txid || null, fee: p.fee.toString() }; - } - - function snapshot() { - const cur = current(); - return { - address: cur.address, addressIndex: state.receiveIndex, - balance: { confirmed: state.balance.confirmed.toString(), immature: state.balance.immature.toString() }, - height: state.height, history: state.history, outputCount: state.outputs.length, - feePerByte: state.feePerByte.toString(), scanning: state.scanning, error: state.error, - }; - } - function dispose() { stopPolling(); } - return { refresh, snapshot, nextUnusedAddress, current, plan, signAndBroadcast, startPolling, dispose, state }; -}; diff --git a/bundled-addons/siawallet/lib/walletd.js b/bundled-addons/siawallet/lib/walletd.js deleted file mode 100644 index da489cd..0000000 --- a/bundled-addons/siawallet/lib/walletd.js +++ /dev/null @@ -1,42 +0,0 @@ -// Thin client for the walletd HTTP API (go.sia.tech/walletd, index mode -// "full"). Only address-scoped reads plus txpool fee/broadcast are used, so -// any public or self-hosted walletd works; the URL is a user setting. -module.exports = function makeWalletd({ log = () => {} }) { - class Client { - constructor(baseUrl) { this.setBase(baseUrl); } - setBase(baseUrl) { - const u = String(baseUrl || "").trim().replace(/\/+$/, ""); - this.base = u ? (u.endsWith("/api") ? u : u + "/api") : ""; - } - // Everything after the host is the node's business; only the origin is - // ever logged or shown, since hosted providers key access on the path. - get displayUrl() { try { return new URL(this.base).origin; } catch { return this.base; } } - async _req(method, path, body) { - if (!this.base) throw new Error("no walletd URL configured"); - const ctrl = new AbortController(); - const timer = setTimeout(() => ctrl.abort(), 25000); - try { - const res = await fetch(this.base + path, { - method, signal: ctrl.signal, - headers: body !== undefined ? { "content-type": "application/json" } : {}, - body: body !== undefined ? JSON.stringify(body) : undefined, - }); - const text = await res.text(); - if (!res.ok) throw new Error(`walletd ${res.status}: ${text.slice(0, 200).trim()}`); - try { return JSON.parse(text); } catch { return text; } - } finally { clearTimeout(timer); } - } - get(path) { return this._req("GET", path); } - post(path, body) { return this._req("POST", path, body); } - - tip() { return this.get("/consensus/tip"); } - // Recommended fee in hastings per byte (JSON string). - async feePerByte() { return BigInt(String(await this.get("/txpool/fee")).replace(/"/g, "")); } - balance(addr) { return this.get(`/addresses/${addr}/balance`); } - // { basis, outputs: [SiacoinElement] } — proofs are valid at `basis`. - outputs(addr, limit = 100, offset = 0) { return this.get(`/addresses/${addr}/outputs/siacoin?limit=${limit}&offset=${offset}`); } - events(addr, limit = 25, offset = 0) { return this.get(`/addresses/${addr}/events?limit=${limit}&offset=${offset}`); } - broadcast(basis, v2tx) { return this.post("/txpool/broadcast", { basis, transactions: [], v2transactions: [v2tx] }); } - } - return { Client }; -}; diff --git a/bundled-addons/siawallet/panel.html b/bundled-addons/siawallet/panel.html deleted file mode 100644 index 05d6414..0000000 --- a/bundled-addons/siawallet/panel.html +++ /dev/null @@ -1,181 +0,0 @@ - - - - -Siacoin Wallet - - - -
-
-
Siacoin
-
connecting…
-
-
-
SC
-
mainnet
-
-
- -
- -
-
-
-
-
Receiving address
-
-
- - - -
-
-
- - - -
-
- - - - diff --git a/bundled-addons/siawallet/panel.js b/bundled-addons/siawallet/panel.js deleted file mode 100644 index bb1219e..0000000 --- a/bundled-addons/siawallet/panel.js +++ /dev/null @@ -1,203 +0,0 @@ -// Sia wallet panel. State comes from activate() via window.silentmode; this -// file renders and collects input. Amounts cross the bridge as hastings -// strings and are formatted here with BigInt. -const $ = (id) => document.getElementById(id); -const S = window.silentmode; -let state = null; -const H = 10n ** 24n; - -function fmtSC(h, decimals = 6) { - const v = BigInt(h || 0); const neg = v < 0n; const a = neg ? -v : v; - let frac = (a % H).toString().padStart(24, "0").slice(0, decimals).replace(/0+$/, ""); - if (frac.length < 2) frac = frac.padEnd(2, "0"); - return (neg ? "-" : "") + (a / H).toString() + "." + frac; -} -function parseSC(text) { - const s = String(text || "").trim().replace(/,/g, ""); - if (!/^\d*(\.\d*)?$/.test(s) || s === "" || s === ".") throw new Error("amount must be a number"); - const [w = "0", f = ""] = s.split("."); - if (f.length > 24) throw new Error("too many decimals"); - return BigInt(w || "0") * H + BigInt((f + "0".repeat(24)).slice(0, 24)); -} -const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]); -const openUrl = (url) => S.invoke("openUrl", { url }).catch(() => {}); -const cleanErr = (e) => String(e?.message || e).replace(/^Error invoking remote method '[^']+': Error: /, ""); - -// ---- tabs ------------------------------------------------------------------ -document.querySelectorAll("nav button").forEach((b) => b.addEventListener("click", () => showTab(b.dataset.tab))); -function showTab(name) { - document.querySelectorAll("nav button").forEach((b) => b.classList.toggle("on", b.dataset.tab === name)); - document.querySelectorAll("main section").forEach((s) => { s.hidden = s.id !== "tab-" + name; }); - if (name !== "settings") $("recovery").innerHTML = ""; - if (name === "settings") fillSettings(); -} - -// ---- render ---------------------------------------------------------------- -function render() { - if (!state) return; - const ready = state.phase === "ready"; - const gate = $("gate"); - $("tabs").hidden = !ready; - gate.hidden = ready; - if (!ready) { - const copy = { - locked: ["🔒", "Unlock your password vault to open the wallet.", "Settings › Passwords. The wallet keys derive from the vault seed."], - nosetup: ["🗝", "Set up a password vault to create your wallet.", "Settings › Passwords › Set up. With a recovery phrase this wallet can be recreated on any machine."], - nourl: ["🌐", "Point the wallet at a walletd node.", "Paste the URL of a walletd (v2, full index) node. Hosted providers give you a URL with your key in it; it stays on this machine."], - error: ["⚠", "The wallet could not start.", state.error || ""], - }[state.phase] || ["…", "Starting…", ""]; - gate.innerHTML = `
${copy[0]}
${esc(copy[1])}
${esc(copy[2])}
` - + (state.phase === "nourl" ? `
` : ""); - if (state.phase === "nourl") $("gateApply").addEventListener("click", () => applyUrl($("gateUrl").value, $("gateMsg"))); - } - const dot = $("dot"); - dot.className = "dot " + (ready && state.server ? (state.scanning ? "busy" : "on") : ""); - $("netlbl").textContent = ready && state.server ? state.server.replace(/^https?:\/\//, "") + (state.scanning ? " · syncing" : "") : "mainnet"; - if (ready) { - $("balSc").textContent = fmtSC(state.balance?.confirmed || 0); - const parts = []; - if (state.balance?.immature && state.balance.immature !== "0") parts.push(fmtSC(state.balance.immature) + " SC maturing"); - if (state.height) parts.push("block " + Number(state.height).toLocaleString("en-US")); - if (state.feePerByte && state.feePerByte !== "0") parts.push("fee " + fmtSC(BigInt(state.feePerByte) * 1000n, 4) + " SC/kB"); - $("balSub").textContent = state.error || parts.join(" · ") || "mainnet"; - } else { $("balSc").textContent = "—"; $("balSub").textContent = "mainnet"; } - if (!ready) return; - if (state.address && $("addr").textContent !== state.address) { - $("addr").textContent = state.address; - drawQr(state.address); - } - $("addrMeta").textContent = `· #${state.addressIndex}`; - renderHistory(); -} - -function drawQr(text) { - const cv = $("qr"); const g = cv.getContext("2d"); - let q; - try { q = window.QR.build(text); } catch { g.clearRect(0, 0, cv.width, cv.height); return; } - const scale = Math.max(2, Math.floor(200 / (q.size + 2))); - const px = (q.size + 2) * scale; - cv.width = cv.height = px; cv.style.width = cv.style.height = px + "px"; - g.fillStyle = "#fff"; g.fillRect(0, 0, px, px); g.fillStyle = "#000"; - for (let r = 0; r < q.size; r++) for (let c = 0; c < q.size; c++) if (q.modules[r][c]) g.fillRect((c + 1) * scale, (r + 1) * scale, scale, scale); -} - -function renderHistory() { - const list = state.history || []; - const el = $("txlist"); - if (!list.length) { el.innerHTML = `
${state.scanning ? "Syncing…" : "No transactions yet."}
`; return; } - el.innerHTML = list.map((t) => { - const delta = BigInt(t.delta || 0); - const inc = delta >= 0n; - const when = t.time ? new Date(t.time * 1000).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" }) : "pending"; - const kind = { miner: "Mining reward", foundation: "Foundation subsidy", siafundclaim: "Siafund claim" }[String(t.type).toLowerCase()]; - const what = kind || (inc ? "Received" : "Sent" + (t.to ? " to " + esc(t.to.slice(0, 10)) + "…" : "")); - const immature = t.maturityHeight && state.height && t.maturityHeight > state.height; - const conf = immature ? "matures at " + t.maturityHeight : t.confirmations > 0 ? (t.confirmations >= 6 ? "confirmed" : t.confirmations + " conf") : "unconfirmed"; - return `
-
${inc ? "↓" : "↑"}
-
${what}
-
${inc ? "+" : "−"}${fmtSC(inc ? delta : -delta)}
-
${esc(when)}
-
${esc(conf)}
-
`; - }).join(""); - el.querySelectorAll(".tx").forEach((row) => row.addEventListener("click", () => openUrl(state.explorerTx + row.dataset.id))); -} - -// ---- receive --------------------------------------------------------------- -$("copyAddr").addEventListener("click", async () => { try { await navigator.clipboard.writeText(state.address); flash($("copyAddr"), "Copied"); } catch {} }); -$("nextAddr").addEventListener("click", async () => { try { state = await S.invoke("nextAddress"); render(); } catch { flash($("nextAddr"), "Failed"); } }); -$("viewAddr").addEventListener("click", () => openUrl(state.explorerAddr + state.address)); -function flash(btn, text) { const old = btn.textContent; btn.textContent = text; setTimeout(() => { btn.textContent = old; }, 1200); } - -// ---- send ------------------------------------------------------------------ -let sendMax = false, planTimer = null, lastPlan = null; -const FEE_LABELS = { 1: "normal", 2: "2× fee", 3: "3× fee" }; -$("sendMax").addEventListener("click", () => { - sendMax = !sendMax; - $("sendMax").classList.toggle("primary", sendMax); - $("sendAmt").disabled = sendMax; - if (!sendMax) $("sendAmt").value = ""; - schedulePlan(); -}); -$("feeMult").addEventListener("input", () => { $("feeLbl").textContent = FEE_LABELS[$("feeMult").value]; schedulePlan(); }); -["sendTo", "sendAmt"].forEach((id) => $(id).addEventListener("input", () => { if (id === "sendAmt" && sendMax) return; schedulePlan(); })); -function schedulePlan() { clearTimeout(planTimer); planTimer = setTimeout(updatePlan, 250); } -function sendSpec() { - const amount = sendMax ? "0" : parseSC($("sendAmt").value).toString(); - return { to: $("sendTo").value.trim(), amount, feeMultiplier: Number($("feeMult").value), sendMax }; -} -async function updatePlan() { - const msg = $("sendMsg"); msg.hidden = true; - lastPlan = null; $("sendBtn").disabled = true; - $("sumAmt").textContent = $("sumFee").textContent = $("sumTotal").textContent = "—"; - if (!$("sendTo").value.trim() || (!sendMax && !$("sendAmt").value.trim())) return; - try { - const p = await S.invoke("planSend", sendSpec()); - lastPlan = p; - $("sumAmt").textContent = fmtSC(p.recipients[0].value) + " SC"; - $("sumFee").textContent = fmtSC(p.fee) + " SC"; - $("sumTotal").textContent = fmtSC(p.total) + " SC"; - if (sendMax) $("sendAmt").value = fmtSC(p.recipients[0].value, 24).replace(/0+$/, "").replace(/\.$/, ".0"); - $("sendBtn").disabled = false; - } catch (e) { msg.className = "msg err"; msg.textContent = cleanErr(e); msg.hidden = false; } -} -$("sendBtn").addEventListener("click", async () => { - if (!lastPlan) return; - const msg = $("sendMsg"); msg.hidden = true; - $("sendBtn").disabled = true; $("sendBtn").textContent = "Waiting for approval…"; - try { - const r = await S.invoke("send", sendSpec()); - msg.className = "msg ok"; - msg.innerHTML = r.txid ? `Sent. ${esc(r.txid.slice(0, 16))}…` : "Sent."; - if (r.txid) msg.querySelector("a").addEventListener("click", () => openUrl(state.explorerTx + r.txid)); - msg.hidden = false; - $("sendTo").value = ""; $("sendAmt").value = ""; sendMax = false; $("sendMax").classList.remove("primary"); $("sendAmt").disabled = false; - lastPlan = null; - } catch (e) { - const t = cleanErr(e); - if (t !== "cancelled") { msg.className = "msg err"; msg.textContent = t; msg.hidden = false; } - $("sendBtn").disabled = !lastPlan; - } finally { $("sendBtn").textContent = "Send"; } -}); - -// ---- settings -------------------------------------------------------------- -async function fillSettings() { - try { const s = await S.invoke("settings"); if (document.activeElement !== $("setUrl")) $("setUrl").value = s.walletdUrl || ""; } catch {} - renderSites(); -} -async function applyUrl(url, msgEl) { - msgEl.hidden = true; - try { state = await S.invoke("setSettings", { walletdUrl: url }); render(); } - catch (e) { msgEl.textContent = cleanErr(e); msgEl.hidden = false; } -} -$("applySettings").addEventListener("click", () => applyUrl($("setUrl").value, $("settingsMsg"))); -$("refreshBtn").addEventListener("click", async () => { try { state = await S.invoke("refresh"); render(); } catch (e) { $("settingsMsg").textContent = cleanErr(e); $("settingsMsg").hidden = false; } }); -$("showInfo").addEventListener("click", async () => { try { $("recovery").innerHTML = recoveryHtml(await S.invoke("recovery", {})); } catch (e) { $("recovery").textContent = cleanErr(e); } }); -$("showSeed").addEventListener("click", async () => { try { $("recovery").innerHTML = recoveryHtml(await S.invoke("recovery", { reveal: true })); } catch (e) { $("recovery").textContent = cleanErr(e); } }); -function recoveryHtml(r) { - let h = `
Scheme
${esc(r.scheme)}
First address
${esc(r.firstAddress)}
`; - if (r.seedHex) h += `
Wallet seed (32 bytes, hex)
${esc(r.seedHex)}
`; - return h; -} -async function renderSites() { - let perms = {}; - try { perms = await S.invoke("permissions"); } catch {} - const origins = Object.keys(perms).filter((o) => perms[o] && (perms[o].readAddress || perms[o].sendTx)); - const el = $("sites"); - if (!origins.length) { el.innerHTML = `
None yet.
`; return; } - el.innerHTML = origins.map((o) => { - const p = perms[o]; const what = []; - if (p.readAddress) what.push("address"); - if (p.sendTx) { const left = BigInt(p.sendTx.cap) - BigInt(p.sendTx.used || 0); what.push(`payments: ${fmtSC(left < 0n ? 0n : left)} of ${fmtSC(p.sendTx.cap)} SC left`); } - return `
${esc(o)}
${esc(what.join(" · "))}
`; - }).join(""); - el.querySelectorAll("button[data-origin]").forEach((b) => b.addEventListener("click", async () => { try { await S.invoke("revoke", { origin: b.dataset.origin }); renderSites(); } catch {} })); -} - -// ---- boot ------------------------------------------------------------------ -S.on("state", (s) => { state = s; render(); }); -(async () => { - try { state = await S.invoke("state"); render(); } - catch (e) { $("gate").hidden = false; $("gate").innerHTML = `
${esc(cleanErr(e))}
`; } -})(); diff --git a/bundled-addons/siawallet/qr.js b/bundled-addons/siawallet/qr.js deleted file mode 100644 index a0d2946..0000000 --- a/bundled-addons/siawallet/qr.js +++ /dev/null @@ -1,218 +0,0 @@ -// Minimal QR encoder for the receive tab: byte mode, versions 1-10, error -// correction M (falls back to L when M won't fit). Returns a boolean matrix. -// Loaded as a plain script in panel.html and as a CommonJS module in tests. -(function (root) { - const EC_LEVELS = { L: 1, M: 0 }; - // Per version: [totalCodewords, {L:[ecPerBlock, [[blocks, dataCw], ...]], M:[...]}] - const TABLE = { - 1: [26, { L: [7, [[1, 19]]], M: [10, [[1, 16]]] }], - 2: [44, { L: [10, [[1, 34]]], M: [16, [[1, 28]]] }], - 3: [70, { L: [15, [[1, 55]]], M: [26, [[1, 44]]] }], - 4: [100, { L: [20, [[1, 80]]], M: [18, [[2, 32]]] }], - 5: [134, { L: [26, [[1, 108]]], M: [24, [[2, 43]]] }], - 6: [172, { L: [18, [[2, 68]]], M: [16, [[4, 27]]] }], - 7: [196, { L: [20, [[2, 78]]], M: [18, [[4, 31]]] }], - 8: [242, { L: [24, [[2, 97]]], M: [22, [[2, 38], [2, 39]]] }], - 9: [292, { L: [30, [[2, 116]]], M: [22, [[3, 36], [2, 37]]] }], - 10: [346, { L: [18, [[2, 68], [2, 69]]], M: [26, [[4, 43], [1, 44]]] }], - }; - const ALIGN = { 1: [], 2: [6, 18], 3: [6, 22], 4: [6, 26], 5: [6, 30], 6: [6, 34], 7: [6, 22, 38], 8: [6, 24, 42], 9: [6, 26, 46], 10: [6, 28, 50] }; - const REMAINDER = { 1: 0, 2: 7, 3: 7, 4: 7, 5: 7, 6: 7, 7: 0, 8: 0, 9: 0, 10: 0 }; - - // GF(256) with the QR polynomial 0x11d. - const EXP = new Uint8Array(512), LOG = new Uint8Array(256); - (function () { - let x = 1; - for (let i = 0; i < 255; i++) { EXP[i] = x; LOG[x] = i; x <<= 1; if (x & 0x100) x ^= 0x11d; } - for (let i = 255; i < 512; i++) EXP[i] = EXP[i - 255]; - })(); - const gmul = (a, b) => (a && b) ? EXP[LOG[a] + LOG[b]] : 0; - function generator(n) { - let g = [1]; - for (let i = 0; i < n; i++) { - const next = new Array(g.length + 1).fill(0); - for (let j = 0; j < g.length; j++) { next[j] ^= g[j]; next[j + 1] ^= gmul(g[j], EXP[i]); } - g = next; - } - return g; - } - function rsEncode(data, nEc) { - const g = generator(nEc); - const out = new Uint8Array(data.length + nEc); - out.set(data); - for (let i = 0; i < data.length; i++) { - const c = out[i]; - if (c) for (let j = 1; j < g.length; j++) out[i + j] ^= gmul(g[j], c); - } - return out.slice(data.length); - } - - function dataCapacity(v, ec) { return TABLE[v][1][ec][1].reduce((a, [b, d]) => a + b * d, 0); } - function pickVersion(len) { - for (const ec of ["M", "L"]) { - for (let v = 1; v <= 10; v++) { - const bitsNeeded = 4 + (v <= 9 ? 8 : 16) + len * 8; - if (bitsNeeded <= dataCapacity(v, ec) * 8) return { v, ec }; - } - } - throw new Error("qr: text too long"); - } - - function encodeData(bytes, v, ec) { - const cap = dataCapacity(v, ec); - const bits = []; - const push = (val, n) => { for (let i = n - 1; i >= 0; i--) bits.push((val >> i) & 1); }; - push(0b0100, 4); - push(bytes.length, v <= 9 ? 8 : 16); - for (const b of bytes) push(b, 8); - for (let i = 0; i < 4 && bits.length < cap * 8; i++) bits.push(0); - while (bits.length % 8) bits.push(0); - const data = new Uint8Array(cap); - for (let i = 0; i < bits.length / 8; i++) data[i] = parseInt(bits.slice(i * 8, i * 8 + 8).join(""), 2); - for (let i = bits.length / 8, k = 0; i < cap; i++, k++) data[i] = k % 2 ? 0x11 : 0xec; - return data; - } - - function interleave(data, v, ec) { - const [nEc, groups] = TABLE[v][1][ec]; - const blocks = []; let o = 0; - for (const [count, size] of groups) for (let i = 0; i < count; i++) { blocks.push(data.slice(o, o + size)); o += size; } - const ecBlocks = blocks.map((b) => rsEncode(b, nEc)); - const out = []; - const maxLen = Math.max(...blocks.map((b) => b.length)); - for (let i = 0; i < maxLen; i++) for (const b of blocks) if (i < b.length) out.push(b[i]); - for (let i = 0; i < nEc; i++) for (const b of ecBlocks) out.push(b[i]); - return out; - } - - // Remainder of value·x^degree modulo the generator poly (BCH error correction - // for the format / version fields). - function bch(value, poly, degree) { - let v = value << degree; - for (let i = 31 - Math.clz32(v); i >= degree; i--) if ((v >> i) & 1) v ^= poly << (i - degree); - return v; - } - const formatBits = (ec, mask) => { const d = (EC_LEVELS[ec] << 3) | mask; return ((d << 10) | bch(d, 0x537, 10)) ^ 0x5412; }; - const versionBits = (v) => (v << 12) | bch(v, 0x1f25, 12); - - function build(text) { - const bytes = typeof text === "string" ? new TextEncoder().encode(text) : Uint8Array.from(text); - const { v, ec } = pickVersion(bytes.length); - const size = v * 4 + 17; - const codewords = interleave(encodeData(bytes, v, ec), v, ec); - const grid = Array.from({ length: size }, () => new Uint8Array(size)); // 1 dark, 0 light - const fixed = Array.from({ length: size }, () => new Uint8Array(size)); // function pattern / reserved - const set = (r, c, val) => { grid[r][c] = val ? 1 : 0; fixed[r][c] = 1; }; - const finder = (r0, c0) => { - for (let r = -1; r <= 7; r++) for (let c = -1; c <= 7; c++) { - const rr = r0 + r, cc = c0 + c; - if (rr < 0 || cc < 0 || rr >= size || cc >= size) continue; - const inner = r >= 0 && r <= 6 && c >= 0 && c <= 6; - const dark = inner && (r === 0 || r === 6 || c === 0 || c === 6 || (r >= 2 && r <= 4 && c >= 2 && c <= 4)); - set(rr, cc, dark); - } - }; - finder(0, 0); finder(0, size - 7); finder(size - 7, 0); - for (let i = 8; i < size - 8; i++) { set(6, i, i % 2 === 0); set(i, 6, i % 2 === 0); } - const al = ALIGN[v], last = al.length - 1; - al.forEach((r, ri) => al.forEach((c, ci) => { - // The three corners that would sit on a finder pattern are omitted. - if ((ri === 0 && ci === 0) || (ri === 0 && ci === last) || (ri === last && ci === 0)) return; - for (let dr = -2; dr <= 2; dr++) for (let dc = -2; dc <= 2; dc++) { - set(r + dr, c + dc, Math.max(Math.abs(dr), Math.abs(dc)) !== 1); - } - })); - set(size - 8, 8, 1); // dark module - // Reserve format areas (filled per mask below) and version areas. - for (let i = 0; i < 9; i++) { fixed[8][i] = 1; fixed[i][8] = 1; } - for (let i = 0; i < 8; i++) { fixed[8][size - 1 - i] = 1; fixed[size - 1 - i][8] = 1; } - if (v >= 7) { - const vb = versionBits(v); - for (let i = 0; i < 18; i++) { - const bit = (vb >> i) & 1; - set(Math.floor(i / 3), size - 11 + (i % 3), bit); - set(size - 11 + (i % 3), Math.floor(i / 3), bit); - } - } - // Zigzag data placement. - const bits = []; - for (const cw of codewords) for (let i = 7; i >= 0; i--) bits.push((cw >> i) & 1); - for (let i = 0; i < REMAINDER[v]; i++) bits.push(0); - let bi = 0, up = true; - for (let col = size - 1; col > 0; col -= 2) { - if (col === 6) col--; - for (let k = 0; k < size; k++) { - const r = up ? size - 1 - k : k; - for (const c of [col, col - 1]) { - if (fixed[r][c]) continue; - grid[r][c] = bi < bits.length ? bits[bi] : 0; - bi++; - } - } - up = !up; - } - // Try every mask, keep the lowest penalty. - const MASKS = [ - (i, j) => (i + j) % 2 === 0, (i) => i % 2 === 0, (_i, j) => j % 3 === 0, (i, j) => (i + j) % 3 === 0, - (i, j) => (Math.floor(i / 2) + Math.floor(j / 3)) % 2 === 0, (i, j) => ((i * j) % 2) + ((i * j) % 3) === 0, - (i, j) => (((i * j) % 2) + ((i * j) % 3)) % 2 === 0, (i, j) => (((i + j) % 2) + ((i * j) % 3)) % 2 === 0, - ]; - let best = null; - for (let m = 0; m < 8; m++) { - const g = grid.map((row) => Uint8Array.from(row)); - for (let r = 0; r < size; r++) for (let c = 0; c < size; c++) if (!fixed[r][c] && MASKS[m](r, c)) g[r][c] ^= 1; - const fb = formatBits(ec, m); - for (let i = 0; i < 15; i++) { - const bit = (fb >> i) & 1; - // Copy 1: down column 8 (bits 0-7, skipping the timing row), then - // left along row 8 (bits 8-14). Copy 2: right end of row 8, bottom - // of column 8. - if (i < 6) g[i][8] = bit; else if (i === 6) g[7][8] = bit; else if (i === 7) g[8][8] = bit; - else if (i === 8) g[8][7] = bit; else g[8][14 - i] = bit; - if (i < 8) g[8][size - 1 - i] = bit; else g[size - 15 + i][8] = bit; - } - const p = penalty(g, size); - if (!best || p < best.p) best = { g, p, m }; - } - return { size, version: v, ec, mask: best.m, modules: best.g.map((row) => Array.from(row, (x) => !!x)) }; - } - - function penalty(g, n) { - let p = 0; - const runs = (get) => { - for (let a = 0; a < n; a++) { - let run = 1; - for (let b = 1; b <= n; b++) { - if (b < n && get(a, b) === get(a, b - 1)) run++; - else { if (run >= 5) p += 3 + run - 5; run = 1; } - } - } - }; - runs((a, b) => g[a][b]); runs((a, b) => g[b][a]); - for (let r = 0; r < n - 1; r++) for (let c = 0; c < n - 1; c++) { - const s = g[r][c] + g[r][c + 1] + g[r + 1][c] + g[r + 1][c + 1]; - if (s === 0 || s === 4) p += 3; - } - const pat = [1, 0, 1, 1, 1, 0, 1]; - const finderLike = (get, a) => { - for (let b = 0; b <= n - 7; b++) { - let ok = true; - for (let k = 0; k < 7; k++) if (get(a, b + k) !== pat[k]) { ok = false; break; } - if (!ok) continue; - const before = b >= 4 && [0, 1, 2, 3].every((k) => get(a, b - 1 - k) === 0); - const after = b + 10 < n && [0, 1, 2, 3].every((k) => get(a, b + 7 + k) === 0); - if (before || after) p += 40; - } - }; - for (let a = 0; a < n; a++) { finderLike((x, y) => g[x][y], a); finderLike((x, y) => g[y][x], a); } - let dark = 0; - for (let r = 0; r < n; r++) for (let c = 0; c < n; c++) dark += g[r][c]; - const pct = (dark * 100) / (n * n); - p += Math.floor(Math.abs(pct - 50) / 5) * 10; - return p; - } - - const api = { build }; - if (typeof module !== "undefined" && module.exports) module.exports = api; - else root.QR = api; -})(typeof globalThis !== "undefined" ? globalThis : this); diff --git a/bundled-addons/siawallet/wallet-inject.js b/bundled-addons/siawallet/wallet-inject.js deleted file mode 100644 index 681e626..0000000 --- a/bundled-addons/siawallet/wallet-inject.js +++ /dev/null @@ -1,27 +0,0 @@ -// Dapp bridge for Sia, run in the isolated world of pages matching -// addon.json "page-inject".origins. Exposes window.siacoin; every call goes -// through the wallet's activate() context in main, which shows the approval -// overlay and enforces per-origin permissions. -// -// `theseus` is provided by the host: { id, origin, contextBridge, invoke }. -const call = (msg, payload) => - theseus.invoke(msg, payload).catch((e) => { - const text = String(e && e.message || e).replace(/^Error invoking remote method '[^']+': (Error: )?/, ""); - throw new Error(text); - }); - -theseus.contextBridge.exposeInMainWorld("siacoin", { - isTheseus: true, - version: "0.1.0", - network: "mainnet", - // Current receiving address (76-hex). First call per origin asks the user; - // "always allow" makes later calls silent. - getAddress: () => call("getAddress"), - // txSpec: { to, amount } or { outputs: [{ to, amount }] } — amounts are - // hastings as decimal strings (1 SC = 1e24). Always approval-gated; - // resolves { txid }. - signAndSend: (txSpec) => call("signAndSend", txSpec && typeof txSpec === "object" ? txSpec : {}), - // ed25519 signature over blake2b-256(message) with the current address's - // key. Always approval-gated; resolves { address, publicKey, signature }. - signMessage: (message) => call("signMessage", { message: String(message ?? "") }), -}); diff --git a/package.json b/package.json index c4d7643..8b57644 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "theseus-navigator", - "version": "0.3.27", + "version": "0.3.28", "description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.", "author": "Silent Mode", "main": "main.js",