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.
182 lines
7.4 KiB
JavaScript
182 lines
7.4 KiB
JavaScript
// Sia (SC) chain adapter — v2 walletd-backed. Constructs a wallet from a
|
|
// 32-byte root and a user-supplied walletd URL. Amounts are hastings
|
|
// (1 SC = 10^24 hastings) and cross the panel boundary as decimal strings
|
|
// so the panel never has to touch BigInt precision.
|
|
//
|
|
// Deps: sia.js / keys.js / wallet.js / walletd.js — copied from the
|
|
// standalone siawallet addon on 2026-09-07 when Aegis absorbed it, so the
|
|
// key derivation + tx layout are byte-identical to the older addon.
|
|
// Existing on-chain funds carry over via the vault-derive absorb (aegis's
|
|
// manifest lists "siawallet" under absorbs, so the legacy purpose
|
|
// "siawallet/mainnet/0" resolves to the same seed inside Aegis).
|
|
|
|
const SC = 10n ** 24n;
|
|
const EXPLORER_TX = "https://siascan.com/tx/";
|
|
const EXPLORER_ADDR = "https://siascan.com/address/";
|
|
|
|
module.exports = function makeSiaAdapter({ ed25519, blake2b }) {
|
|
if (!ed25519 || !blake2b) throw new Error("chain-sia: missing dep");
|
|
const sia = require("./sia/sia.js")({ ed25519, blake2b });
|
|
const keysLib = require("./sia/keys.js")({ sia });
|
|
const walletd = require("./sia/walletd.js")({ log: () => {} });
|
|
const walletFactory = require("./sia/wallet.js");
|
|
|
|
function scopedStorage(storage, keyPrefix) {
|
|
const k = (key) => keyPrefix + key;
|
|
return {
|
|
get: (key, fallback = null) => storage.get(k(key), fallback),
|
|
set: (key, value) => storage.set(k(key), value),
|
|
};
|
|
}
|
|
|
|
class SiaWallet {
|
|
constructor(root32, {
|
|
walletId, storage, log = () => {}, onChange = () => {},
|
|
walletdUrl = "",
|
|
} = {}) {
|
|
if (!walletId) throw new Error("chain-sia: walletId required");
|
|
this.walletId = walletId;
|
|
this.chain = "sc";
|
|
this.network = "mainnet";
|
|
this.log = log;
|
|
this.onChange = onChange;
|
|
this.storage = scopedStorage(storage, `wallets/${walletId}/`);
|
|
this._root = new Uint8Array(root32);
|
|
this._keys = new keysLib.WalletKeys(root32);
|
|
this._walletdUrl = String(walletdUrl || "").trim();
|
|
this._client = null;
|
|
this._wallet = null;
|
|
if (this._walletdUrl) this._build();
|
|
}
|
|
|
|
_build() {
|
|
if (this._wallet) { try { this._wallet.dispose(); } catch {} this._wallet = null; }
|
|
if (this._client) this._client.setBase(this._walletdUrl);
|
|
else this._client = new walletd.Client(this._walletdUrl);
|
|
this._wallet = walletFactory({
|
|
client: this._client, keys: this._keys, sia,
|
|
storage: this.storage,
|
|
log: (...a) => this.log(...a),
|
|
onChange: () => { try { this.onChange(); } catch {} },
|
|
});
|
|
}
|
|
|
|
setWalletdUrl(url) {
|
|
const v = String(url || "").trim();
|
|
if (v === this._walletdUrl) return;
|
|
this._walletdUrl = v;
|
|
if (v) this._build(); else { try { this._wallet && this._wallet.dispose(); } catch {} this._wallet = null; }
|
|
}
|
|
|
|
// The panel treats Sia amounts as decimal strings of hastings; the
|
|
// display layer picks how many SC-precision digits to show.
|
|
snapshot() {
|
|
const w = this._wallet && this._wallet.snapshot();
|
|
const base = {
|
|
chain: "sc", network: "mainnet", ticker: "SC", decimals: 24,
|
|
address: null, addressIndex: 0, addressPath: `KeyFromSeed(seed, ${w?.addressIndex || 0})`,
|
|
balance: { confirmed: "0", unconfirmed: "0" },
|
|
height: 0, history: [], scanning: false, error: null,
|
|
server: this._client ? this._client.displayUrl : null,
|
|
walletdUrl: this._walletdUrl,
|
|
needsWalletdUrl: !this._walletdUrl,
|
|
explorerTx: EXPLORER_TX, explorerAddr: EXPLORER_ADDR, faucet: null,
|
|
};
|
|
if (w) {
|
|
base.address = w.address;
|
|
base.addressIndex = w.addressIndex;
|
|
// wallet.js exposes confirmed / immature; the panel's shared shape
|
|
// is confirmed / unconfirmed, so map immature → unconfirmed for
|
|
// visual parity with BCH and TRX (small semantic bend, but the
|
|
// number is the "not yet spendable" one either way).
|
|
base.balance = { confirmed: w.balance.confirmed, unconfirmed: w.balance.immature };
|
|
base.height = w.height;
|
|
base.history = (w.history || []).map((r) => ({
|
|
txid: r.id, delta: r.delta, to: r.to, from: null,
|
|
fee: null, time: r.time || 0,
|
|
confirmations: r.confirmations || 0,
|
|
status: r.confirmations > 0 ? "confirmed" : "pending",
|
|
kind: r.type,
|
|
}));
|
|
base.scanning = w.scanning;
|
|
base.error = w.error;
|
|
}
|
|
return base;
|
|
}
|
|
|
|
async refresh(full) {
|
|
if (!this._wallet) return;
|
|
return this._wallet.refresh(!!full);
|
|
}
|
|
nextAddress() {
|
|
if (!this._wallet) throw new Error("no walletd URL configured");
|
|
return this._wallet.nextUnusedAddress();
|
|
}
|
|
current() {
|
|
if (!this._wallet) throw new Error("no walletd URL configured");
|
|
return this._wallet.current();
|
|
}
|
|
plan(spec) {
|
|
if (!this._wallet) throw new Error("no walletd URL configured");
|
|
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) }];
|
|
const p = this._wallet.plan({ targets, feeMultiplier: spec.feeMultiplier || spec.feeRate, sendMax: !!spec.sendMax });
|
|
// Present the plan in the common panel shape: BigInts as decimal
|
|
// strings, plus `total = sum(recipients) + fee`.
|
|
const sent = p.recipients.reduce((a, r) => a + BigInt(r.value), 0n);
|
|
return {
|
|
_sia: p, // internal handle so signAndBroadcast doesn't re-plan
|
|
recipients: p.recipients,
|
|
fee: p.fee.toString(),
|
|
feeRate: p.feePerByte.toString(),
|
|
inputs: p.tx.inputs,
|
|
change: p.change.toString(),
|
|
total: (sent + p.fee).toString(),
|
|
};
|
|
}
|
|
async signAndBroadcast(plan) {
|
|
if (!this._wallet) throw new Error("no walletd URL configured");
|
|
const inner = plan && plan._sia;
|
|
if (!inner) throw new Error("bad plan");
|
|
return this._wallet.signAndBroadcast(inner);
|
|
}
|
|
// Sia signature = ed25519 over blake2b256 of the raw message. Not a
|
|
// BIP-137 style thing — dapps that want it should treat this as an
|
|
// opaque {publicKey, signature} pair verified via ed25519.
|
|
signMessage(message) {
|
|
const entry = this.current();
|
|
const digest = sia.b256(new TextEncoder().encode(String(message)));
|
|
const sig = this._keys.sign(entry, digest);
|
|
return {
|
|
address: entry.address,
|
|
publicKey: "ed25519:" + sia.toHex(entry.pub),
|
|
signature: sia.toHex(sig),
|
|
};
|
|
}
|
|
recovery() {
|
|
// Sia has no xpub/xprv notion here; the scheme is "seed + index".
|
|
return {
|
|
accountPath: `KeyFromSeed(seed, i)`,
|
|
xpub: sia.toHex(this._keys.entry(0).pub), // just the first pub for reference
|
|
xprv: this._keys.seedHex,
|
|
};
|
|
}
|
|
startPolling() { if (this._wallet) this._wallet.startPolling(60_000); }
|
|
dispose() {
|
|
try { this._wallet && this._wallet.dispose(); } catch {}
|
|
try { this._keys && this._keys.wipe(); } catch {}
|
|
if (this._root) this._root.fill(0);
|
|
}
|
|
}
|
|
|
|
// Amounts arrive as strings (hastings) or as small numbers.
|
|
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);
|
|
}
|
|
|
|
return { SiaWallet, EXPLORER_TX, EXPLORER_ADDR };
|
|
};
|