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
|
|
|
// CashAddr (BCH address format) encode/decode + legacy Base58Check decode, so
|
|
|
|
|
// the send form accepts whatever the user pastes but the UI only ever shows
|
|
|
|
|
// cashaddr. Pure JS, no deps — the polymod is tiny.
|
|
|
|
|
const CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
|
|
|
|
|
const CHARSET_REV = Object.fromEntries([...CHARSET].map((c, i) => [c, i]));
|
|
|
|
|
|
|
|
|
|
function polymod(values) {
|
|
|
|
|
let c = 1n;
|
|
|
|
|
for (const d of values) {
|
|
|
|
|
const c0 = c >> 35n;
|
|
|
|
|
c = ((c & 0x07ffffffffn) << 5n) ^ BigInt(d);
|
|
|
|
|
if (c0 & 0x01n) c ^= 0x98f2bc8e61n;
|
|
|
|
|
if (c0 & 0x02n) c ^= 0x79b76d99e2n;
|
|
|
|
|
if (c0 & 0x04n) c ^= 0xf33e5fb3c4n;
|
|
|
|
|
if (c0 & 0x08n) c ^= 0xae2eabe2a8n;
|
|
|
|
|
if (c0 & 0x10n) c ^= 0x1e4f43e470n;
|
|
|
|
|
}
|
|
|
|
|
return c ^ 1n;
|
|
|
|
|
}
|
|
|
|
|
const prefixExpand = (prefix) => [...prefix].map((c) => c.charCodeAt(0) & 0x1f).concat([0]);
|
|
|
|
|
|
|
|
|
|
function convertBits(data, from, to, pad) {
|
|
|
|
|
let acc = 0, bits = 0; const out = [];
|
|
|
|
|
const maxv = (1 << to) - 1;
|
|
|
|
|
for (const v of data) {
|
|
|
|
|
acc = (acc << from) | v; bits += from;
|
|
|
|
|
while (bits >= to) { bits -= to; out.push((acc >> bits) & maxv); }
|
|
|
|
|
}
|
|
|
|
|
if (pad) { if (bits > 0) out.push((acc << (to - bits)) & maxv); }
|
|
|
|
|
else if (bits >= from || ((acc << (to - bits)) & maxv)) throw new Error("cashaddr: bad padding");
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
chore(aegis): 0.8.2 — sectioned Settings tab
Settings grew tall enough that the user had to scroll past a dozen
cards to reach Prices, Sites or About. Split it into six named
sections (Security, Session, Wallet, Prices, Sites, About) with a
chip nav row at the top; only one section is visible at a time and
the choice persists across restarts.
Adds an About card that names the wallet, the aegis.x front-door
site and the silentmode.st umbrella, so support triage has a
one-click way to reach either from within the panel.
Includes the accumulated 0.7.x-0.8.1 wallet work that was already
shipping on OTA (CashTokens/BCMR, imported-wallet spend, siascan
integration, consolidate, currency picker, footer update chip).
2026-09-22 20:37:28 +02:00
|
|
|
// type: 0 = P2PKH, 1 = P2SH, 2 = P2PKH+TOKEN, 3 = P2SH+TOKEN (CashTokens
|
|
|
|
|
// address types, CHIP-2022-02). hash: 20 bytes (the only size we emit).
|
|
|
|
|
// The type is a 5-bit value stored in the upper nibble of the version byte,
|
|
|
|
|
// so any type up to 15 encodes cleanly; every caller here uses 0-3.
|
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
|
|
|
function encode(prefix, type, hash) {
|
|
|
|
|
if (hash.length !== 20) throw new Error("cashaddr: only 160-bit hashes supported");
|
|
|
|
|
const versionByte = (type << 3) | 0; // size bits 000 = 160
|
|
|
|
|
const payload = convertBits([versionByte, ...hash], 8, 5, true);
|
|
|
|
|
const mod = polymod([...prefixExpand(prefix), ...payload, 0, 0, 0, 0, 0, 0, 0, 0]);
|
|
|
|
|
const checksum = [];
|
|
|
|
|
for (let i = 0; i < 8; i++) checksum.push(Number((mod >> BigInt(5 * (7 - i))) & 0x1fn));
|
|
|
|
|
return prefix + ":" + [...payload, ...checksum].map((v) => CHARSET[v]).join("");
|
|
|
|
|
}
|
chore(aegis): 0.8.2 — sectioned Settings tab
Settings grew tall enough that the user had to scroll past a dozen
cards to reach Prices, Sites or About. Split it into six named
sections (Security, Session, Wallet, Prices, Sites, About) with a
chip nav row at the top; only one section is visible at a time and
the choice persists across restarts.
Adds an About card that names the wallet, the aegis.x front-door
site and the silentmode.st umbrella, so support triage has a
one-click way to reach either from within the panel.
Includes the accumulated 0.7.x-0.8.1 wallet work that was already
shipping on OTA (CashTokens/BCMR, imported-wallet spend, siascan
integration, consolidate, currency picker, footer update chip).
2026-09-22 20:37:28 +02:00
|
|
|
// Whether a decoded address type carries the CashTokens "token-aware" flag.
|
|
|
|
|
// Callers use it to warn on token sends to non-token-aware addresses (a
|
|
|
|
|
// consensus rule — sending tokens to type 0/1 is a burn).
|
|
|
|
|
function isTokenAware(type) { return type === 2 || type === 3; }
|
|
|
|
|
// Fold a token-aware address type down to its bare equivalent so the
|
|
|
|
|
// UTXO / locking-script path can stay one-shape (P2PKH vs P2SH). The
|
|
|
|
|
// token payload is written via the 0xef prefix, not the address type.
|
|
|
|
|
function bareType(type) { return type & 0x01; }
|
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
|
|
|
|
|
|
|
|
// Accepts "prefix:payload" or a bare payload (assumes defaultPrefix).
|
|
|
|
|
function decode(address, defaultPrefix = "bitcoincash") {
|
|
|
|
|
const raw = String(address).trim();
|
|
|
|
|
if (raw !== raw.toLowerCase() && raw !== raw.toUpperCase()) throw new Error("cashaddr: mixed case");
|
|
|
|
|
const s = raw.toLowerCase();
|
|
|
|
|
const i = s.lastIndexOf(":");
|
|
|
|
|
const prefix = i >= 0 ? s.slice(0, i) : defaultPrefix;
|
|
|
|
|
const payloadStr = i >= 0 ? s.slice(i + 1) : s;
|
|
|
|
|
if (!/^[a-z0-9]+$/.test(prefix) || payloadStr.length < 8) throw new Error("cashaddr: malformed");
|
|
|
|
|
const values = [...payloadStr].map((c) => {
|
|
|
|
|
if (!(c in CHARSET_REV)) throw new Error("cashaddr: bad character");
|
|
|
|
|
return CHARSET_REV[c];
|
|
|
|
|
});
|
|
|
|
|
if (polymod([...prefixExpand(prefix), ...values]) !== 0n) throw new Error("cashaddr: bad checksum");
|
|
|
|
|
const data = convertBits(values.slice(0, -8), 5, 8, false);
|
|
|
|
|
const versionByte = data[0];
|
|
|
|
|
const type = (versionByte >> 3) & 0x0f;
|
|
|
|
|
const size = [20, 24, 28, 32, 40, 48, 56, 64][versionByte & 0x07];
|
|
|
|
|
const hash = Uint8Array.from(data.slice(1));
|
|
|
|
|
if (hash.length !== size) throw new Error("cashaddr: hash size mismatch");
|
|
|
|
|
return { prefix, type, hash };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Legacy Base58Check (1... / 3... on mainnet). sha256 is injected so this
|
|
|
|
|
// file stays dependency-free.
|
|
|
|
|
const B58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
|
|
|
function decodeLegacy(address, sha256) {
|
|
|
|
|
const s = String(address).trim();
|
|
|
|
|
let n = 0n;
|
|
|
|
|
for (const c of s) {
|
|
|
|
|
const v = B58.indexOf(c);
|
|
|
|
|
if (v < 0) throw new Error("base58: bad character");
|
|
|
|
|
n = n * 58n + BigInt(v);
|
|
|
|
|
}
|
|
|
|
|
const bytes = [];
|
|
|
|
|
while (n > 0n) { bytes.unshift(Number(n & 0xffn)); n >>= 8n; }
|
|
|
|
|
for (const c of s) { if (c !== "1") break; bytes.unshift(0); }
|
|
|
|
|
if (bytes.length !== 25) throw new Error("base58: wrong length");
|
|
|
|
|
const body = Uint8Array.from(bytes.slice(0, 21));
|
|
|
|
|
const check = sha256(sha256(body)).slice(0, 4);
|
|
|
|
|
for (let k = 0; k < 4; k++) if (check[k] !== bytes[21 + k]) throw new Error("base58: bad checksum");
|
|
|
|
|
const type = body[0] === 0x00 ? 0 : body[0] === 0x05 ? 1 : null;
|
|
|
|
|
if (type == null) throw new Error("base58: not a mainnet address");
|
|
|
|
|
return { prefix: "bitcoincash", type, hash: body.slice(1) };
|
|
|
|
|
}
|
|
|
|
|
|
chore(aegis): 0.8.2 — sectioned Settings tab
Settings grew tall enough that the user had to scroll past a dozen
cards to reach Prices, Sites or About. Split it into six named
sections (Security, Session, Wallet, Prices, Sites, About) with a
chip nav row at the top; only one section is visible at a time and
the choice persists across restarts.
Adds an About card that names the wallet, the aegis.x front-door
site and the silentmode.st umbrella, so support triage has a
one-click way to reach either from within the panel.
Includes the accumulated 0.7.x-0.8.1 wallet work that was already
shipping on OTA (CashTokens/BCMR, imported-wallet spend, siascan
integration, consolidate, currency picker, footer update chip).
2026-09-22 20:37:28 +02:00
|
|
|
// Anything a user might paste -> { type, hash, cashaddr, tokenAware }.
|
|
|
|
|
// Rejects wrong prefixes so a chipnet address can never be paid on
|
|
|
|
|
// mainnet by accident. Type 2/3 (CashTokens-aware) is folded to type
|
|
|
|
|
// 0/1 for the locking-script side; the token-aware flag flows through
|
|
|
|
|
// so callers building token outputs can refuse to burn tokens on a
|
|
|
|
|
// non-aware recipient.
|
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
|
|
|
function parseAny(input, sha256, prefix = "bitcoincash") {
|
|
|
|
|
const s = String(input || "").trim().replace(/^bitcoincash:\/\//i, "bitcoincash:");
|
|
|
|
|
if (!s) throw new Error("empty address");
|
|
|
|
|
const r = /^[13][1-9A-HJ-NP-Za-km-z]{25,34}$/.test(s) ? decodeLegacy(s, sha256) : decode(s, prefix);
|
|
|
|
|
if (r.prefix !== prefix) throw new Error(`address is for "${r.prefix}", expected "${prefix}"`);
|
chore(aegis): 0.8.2 — sectioned Settings tab
Settings grew tall enough that the user had to scroll past a dozen
cards to reach Prices, Sites or About. Split it into six named
sections (Security, Session, Wallet, Prices, Sites, About) with a
chip nav row at the top; only one section is visible at a time and
the choice persists across restarts.
Adds an About card that names the wallet, the aegis.x front-door
site and the silentmode.st umbrella, so support triage has a
one-click way to reach either from within the panel.
Includes the accumulated 0.7.x-0.8.1 wallet work that was already
shipping on OTA (CashTokens/BCMR, imported-wallet spend, siascan
integration, consolidate, currency picker, footer update chip).
2026-09-22 20:37:28 +02:00
|
|
|
if (r.type < 0 || r.type > 3) throw new Error(`unsupported address type ${r.type}`);
|
|
|
|
|
const tokenAware = isTokenAware(r.type);
|
|
|
|
|
const bare = bareType(r.type);
|
|
|
|
|
return {
|
|
|
|
|
type: bare, hash: r.hash, tokenAware,
|
|
|
|
|
// Round-trip through encode() so the returned cashaddr is
|
|
|
|
|
// canonical-cased and normalised, even if the input was a legacy
|
|
|
|
|
// Base58 (1…/3…) form. Emits type 0/1 by default; callers that
|
|
|
|
|
// want the token-aware form for display can re-encode with type
|
|
|
|
|
// 2/3 explicitly.
|
|
|
|
|
cashaddr: encode(prefix, bare, r.hash),
|
|
|
|
|
cashaddrTokenAware: encode(prefix, bare | 0x02, r.hash),
|
|
|
|
|
};
|
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
|
|
|
}
|
|
|
|
|
|
chore(aegis): 0.8.2 — sectioned Settings tab
Settings grew tall enough that the user had to scroll past a dozen
cards to reach Prices, Sites or About. Split it into six named
sections (Security, Session, Wallet, Prices, Sites, About) with a
chip nav row at the top; only one section is visible at a time and
the choice persists across restarts.
Adds an About card that names the wallet, the aegis.x front-door
site and the silentmode.st umbrella, so support triage has a
one-click way to reach either from within the panel.
Includes the accumulated 0.7.x-0.8.1 wallet work that was already
shipping on OTA (CashTokens/BCMR, imported-wallet spend, siascan
integration, consolidate, currency picker, footer update chip).
2026-09-22 20:37:28 +02:00
|
|
|
module.exports = { encode, decode, decodeLegacy, parseAny, isTokenAware, bareType };
|