theseus/bundled-addons/siawallet/lib/walletd.js
Local Dev 7931d981aa feat(theseus/siawallet): bundled Siacoin wallet add-on (walletd-backed, v2)
Second bundled wallet, same shape as bchwallet:
- keys: api.vault.derive("siawallet/mainnet/0") as the seed for walletd's
  KeyFromSeed(seed, index) (blake2b(seed||index) -> ed25519); addresses are
  standard unlock hashes, so a future walletd seed import yields the same
  addresses. Seed and keys live in memory only.
- lib/sia.js: Sia binary encoder, StandardUnlockHash, address checksum,
  v2 InputSigHash ("sia/sig/input|" + replay byte 2 + transaction
  semantics), transaction weight, walletd JSON. Address hashing and the
  sighash were verified against real mainnet v2 transactions (signatures
  from block 591853 verify under this implementation).
- lib/walletd.js: address-scoped walletd HTTP client (tip, fee, balance,
  outputs with proofs, events, broadcast). The node URL is a user setting
  with no default; hosted providers embed the access key in the path, so
  only the origin is ever displayed or logged.
- lib/wallet.js: gap-limit discovery via events, mature/immature balance,
  history deltas from v1/v2/foundation/miner events, largest-first
  selection with change to the current address, fee = walletd rate x
  weight x 1-3 multiplier, broadcast with the outputs' basis. A signed tx
  built here was accepted structurally by a live walletd (rejected only
  for the stub key not owning the parent).
- panel: Receive (QR), Send, History, Settings (node URL, derivation info,
  seed reveal behind approval, connected sites); gates for locked vault,
  no vault, no node URL.
- window.siacoin dapp bridge: getAddress (rememberable), signAndSend with
  100/1,000/10,000 SC allowances, signMessage (ed25519 over blake2b-256 of
  the message) — same approval and permission rules as the BCH wallet.
2026-09-06 18:49:13 +02:00

42 lines
2.3 KiB
JavaScript

// 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 };
};