theseus/bundled-addons/aegis/lib/chain-dgb.js
Local Dev 884f3948b8 Aegis: fees that follow the network, connections that come back, its own name on Solana
Still 0.31.0 (unpublished batch).

- ETH nonce. The pending count from a load-balanced RPC often misses a
  transaction this wallet sent seconds ago, so two sends in a row shared a
  nonce and the second failed or replaced the first. The nonce is taken at
  signing, from the RPC or from what the wallet itself last broadcast,
  whichever is higher, and broadcasts are serialised per wallet.
- Chains without EIP-1559 (no baseFeePerGas) get a legacy EIP-155
  transaction; they rejected the type-2 envelope, so a network added by a
  dapp could receive but never send. The tip is clamped to the fee cap.
- Bitcoin and DigiByte take their fee rate from the Electrum server's
  estimate instead of a constant, size each output from its real script
  (a taproot destination was undercounted), and round the size up before
  pricing. Bitcoin inputs signal replace-by-fee. DigiByte keeps its 20
  sat/vB floor and does not signal RBF, which it does not have.
- Electrum: a wallet with live subscriptions went quiet for good when its
  server dropped. The client reconnects with backoff, pings to catch dead
  sockets, times out a silent connect, and hands the replayed subscription
  answers on as notifications so the wallet refreshes. dispose() ends it.
- Max with an SPL token selected did nothing; it now fills the exact token
  balance.
- Removing a wallet retired its derivation index for good. Add wallet now
  takes the lowest free index, so the same wallet comes back.
- Solana: registered through the Wallet Standard as "Aegis" instead of
  setting isPhantom, with silent connect for already-connected sites.
- "Only show the wallet to sites I enable": Aegis keeps the host's
  page-inject allow-list in step with enabled and connected sites.
2026-10-04 01:55:38 +02:00

540 lines
25 KiB
JavaScript

// DigiByte (DGB) chain adapter — thin bridge over the @dgb-wallet/* packages
// vendored under lib/dgb/ from D:\Dev\SilentCode\Digibyte\packages\{core,psbt}.
// Address derivation, network params and PSBT construction come from the
// upstream design; Aegis provides the runtime shell (ElectrumX transport,
// gap-limit scanning, wallet-manager plumbing).
//
// Backend: DGB ElectrumX pool via Theseus's existing lib/electrum.js — same
// TCP-over-wss stack the BCH wallet uses, no separate protocol adapter.
// Optional Blockbook mode is on the roadmap; ElectrumX is the default
// because it matches Aegis's transport shape and needs no per-server keys.
//
// All four address families follow the account path's purpose: BIP84
// (m/84'/20'/0'/0/x → dgb1q…, the default), BIP44 (D…), BIP49 (S…) and
// BIP86 (dgb1p…, Taproot is active on DigiByte). Address recovery from any
// BIP39 tool at coin type 20 is guaranteed by bitcoinjs-lib's Network
// object, so a seed exported here can be restored on iancoleman.io/bip39
// or the SilentCode Digibyte web-wallet with matching addresses.
const NETWORK = "mainnet";
const DEFAULT_PURPOSE = 84;
const EXPLORER_TX = "https://digiexplorer.info/tx/";
const EXPLORER_ADDR = "https://digiexplorer.info/address/";
const DEFAULT_SERVERS = [
"wss://electrum1.cyberbits.eu:50022",
"wss://electrum3.cyberbits.eu:50022",
"wss://electrum1.digibyteblockexplorer.com:50022",
];
module.exports = function makeDgbAdapter({
dgbCore, // ESM namespace of @dgb-wallet/core (vendored)
dgbPsbt, // ESM namespace of @dgb-wallet/psbt (vendored)
bitcoinjs, // require("bitcoinjs-lib")
bip32Factory, // require("bip32").BIP32Factory
ecpairFactory, // require("ecpair").ECPairFactory
ecc, // require("@bitcoinerlab/secp256k1")
sha256, // @noble/hashes/sha2 (only used for scripthash reversal)
electrum,
}) {
if (!dgbCore || !dgbPsbt || !bitcoinjs || !bip32Factory || !ecpairFactory || !ecc || !electrum) {
throw new Error("chain-dgb: missing dep");
}
const { rootFromSeed, accountNode, addressNode, p2wpkhAddress, digibyte, DGB_COIN_TYPE } = dgbCore;
const { buildPsbt, signAllInputs, finalizeAndExtract, feeSats } = dgbPsbt;
const { payments, Psbt } = bitcoinjs;
const bip32 = bip32Factory(ecc);
const ECPair = ecpairFactory(ecc);
const toHex = (b) => Buffer.from(b).toString("hex");
// Electrum scripthash: sha256(scriptPubKey), byte-reversed, hex.
function scripthashOf(scriptBuf) {
const h = sha256(scriptBuf);
const rev = Buffer.from(h).reverse();
return rev.toString("hex");
}
// Taproot outputs need bitcoinjs-lib's schnorr backend; chain-btc.js does
// the same at load, but this adapter must not depend on load order.
try { bitcoinjs.initEccLib && bitcoinjs.initEccLib(ecc); } catch {}
// Address family from the derivation-path purpose, with everything the
// PSBT layer needs to spend an input of that family. entry() used to make
// a bech32 p2wpkh address whatever the purpose, so picking "Legacy (D…)"
// or "Taproot (dgb1p…)" in Settings showed a dgb1q address from the wrong
// key tree — one no other wallet restoring that path would ever find.
// Same shapes as chain-btc.js; BIP49 uses DGB's current "S…" prefix.
const PURPOSES = new Set([44, 49, 84, 86]);
function paymentFor(purpose, node) {
const pubkey = Buffer.from(node.publicKey);
if (purpose === 44) {
const p = payments.p2pkh({ pubkey, network: digibyte });
return { family: "bip44", address: p.address, output: Buffer.from(p.output), send: "p2pkh" };
}
if (purpose === 49) {
const redeem = payments.p2wpkh({ pubkey, network: digibyte });
const p = payments.p2sh({ redeem, network: digibyte });
return { family: "bip49", address: p.address, output: Buffer.from(p.output), redeem: Buffer.from(redeem.output), send: "p2sh-p2wpkh" };
}
if (purpose === 86) {
const internalPubkey = Buffer.from(pubkey.subarray(1, 33));
const p = payments.p2tr({ internalPubkey, network: digibyte });
return { family: "bip86", address: p.address, output: Buffer.from(p.output), internalPubkey, send: "p2tr" };
}
const p = payments.p2wpkh({ pubkey, network: digibyte });
return { family: "bip84", address: p.address, output: Buffer.from(p.output), send: "p2wpkh" };
}
// ---- keys --------------------------------------------------------------
// The HD node is the sole owner of the private key material; every derived
// entry keeps a reference so PSBT.signInput(index, node) can sign each
// input under its own key.
class WalletKeys {
constructor(root32, accountPath) {
const purpose = parsePurposeFromPath(accountPath) ?? DEFAULT_PURPOSE;
if (!PURPOSES.has(purpose)) {
throw new Error(`DGB: unsupported derivation purpose ${purpose} — use 44, 49, 84 or 86 (m/84'/${DGB_COIN_TYPE}'/0' is the default)`);
}
this._purpose = purpose;
// bip32.fromSeed uses bitcoinjs-lib's Network object — pass DGB's so
// extended keys serialize with the right BIP32 magic (0x0488B21E).
this._root = bip32.fromSeed(Buffer.from(root32), digibyte);
this._account = this._root.derivePath(`m/${purpose}'/${DGB_COIN_TYPE}'/0'`);
this._accountPath = `m/${purpose}'/${DGB_COIN_TYPE}'/0'`;
this._branch = [this._account.derive(0), this._account.derive(1)];
this._cache = new Map();
}
get xpub() { return this._account.neutered().toBase58(); }
get xprv() { return this._account.toBase58(); }
get accountPath() { return this._accountPath; }
entry(branch, index) {
const k = branch + "/" + index;
let e = this._cache.get(k);
if (!e) {
const node = this._branch[branch].derive(index);
const pubkey = Buffer.from(node.publicKey);
const pay = paymentFor(this._purpose, node);
const script = pay.output;
e = {
branch, index, path: this._accountPath + "/" + branch + "/" + index,
publicKey: pubkey, script, scriptHex: script.toString("hex"),
scripthash: scripthashOf(script),
address: pay.address,
family: pay.family, send: pay.send,
redeemScript: pay.redeem || null,
tapInternalKey: pay.internalPubkey || null,
_node: node,
};
this._cache.set(k, e);
}
return e;
}
signerFor(entry) {
// bitcoinjs-lib's PSBT accepts anything with .publicKey + .sign(hash).
// BIP32Interface fits, but ECPair.fromPrivateKey gives a plain signer
// that matches what the DGB web-wallet uses — pick that for parity.
return ECPair.fromPrivateKey(Buffer.from(entry._node.privateKey), { network: digibyte });
}
wipe() {
// BIP32Interface holds Buffers; drop references so GC picks them up.
for (const e of this._cache.values()) e._node = null;
this._cache.clear();
this._branch = null;
this._account = null;
this._root = null;
}
}
function parsePurposeFromPath(p) {
const m = /^m\/(\d+)'\/\d+'\/\d+'$/.exec(String(p || ""));
return m ? Number(m[1]) : null;
}
// ---- vsize model (fee estimation ahead of PSBT.getFee) -----------------
// Input sizes per family, as in chain-btc.js.
const OVERHEAD_VB = 10.5;
const OUTPUT_VB = 31;
const INPUT_VB = { p2pkh: 148, "p2sh-p2wpkh": 91, p2wpkh: 68, p2tr: 58 };
const feeVb = (kind, nIn, nOut, feePerVb) => Math.ceil((OVERHEAD_VB + nIn * (INPUT_VB[kind] || 68) + nOut * OUTPUT_VB) * feePerVb);
// Each output sized from its real script (8 value + 1 length + script); a
// flat 31 vB undercounts a dgb1p… destination.
const feeFor = (kind, nIn, outScripts, feePerVb) =>
// Round the size up before pricing it: a real transaction is a whole
// number of vbytes, and paying for 152.5 of 153 lands under the rate.
Math.ceil(Math.ceil(OVERHEAD_VB + nIn * (INPUT_VB[kind] || 68) + outScripts.reduce((a, s) => a + 9 + s.length, 0)) * feePerVb);
const DEFAULT_RATE = 20; // sat/vB floor, and the fallback
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),
};
}
// ---- wallet ------------------------------------------------------------
class DgbWallet {
constructor(root32, {
walletId, storage, log = () => {}, onChange = () => {}, servers,
accountPath,
} = {}) {
if (!walletId) throw new Error("chain-dgb: walletId required");
this.walletId = walletId;
this.chain = "dgb";
this.network = NETWORK;
this.log = log;
this.onChange = onChange;
this.storage = scopedStorage(storage, `wallets/${walletId}/`);
this._servers = Array.isArray(servers) && servers.length ? servers : DEFAULT_SERVERS.slice();
this._keys = new WalletKeys(root32, accountPath);
this._root = new Uint8Array(root32);
this._client = new electrum.Client(this._servers);
this._client.onServer = () => this._emit();
this._state = {
used: new Set(),
watched: new Map(),
height: 0,
balance: { confirmed: 0, unconfirmed: 0 },
utxos: [],
history: [],
receiveIndex: 0,
scanning: false,
error: null,
};
this._refreshTimer = null;
this._subscribedHeaders = false;
this._client.onNotify = (method, params) => {
if (method === "blockchain.headers.subscribe") {
const h = params && params[0] && params[0].height;
if (h) { this._state.height = h; this._scheduleRefresh(1500); }
} else if (method === "blockchain.scripthash.subscribe") {
this._scheduleRefresh(800);
}
};
}
_emit() { try { this.onChange(); } catch {} }
// ---- discovery (gap-limit) -----------------------------------------
async _historyOf(entry) {
const h = await this._client.call("blockchain.scripthash.get_history", [entry.scripthash]);
return Array.isArray(h) ? h : [];
}
async _scan() {
const cursor = Number(this.storage.get("receiveCursor", 0)) || 0;
const GAP = 20;
for (const branch of [0, 1]) {
let gap = 0, i = 0;
const minIndex = branch === 0 ? cursor + 1 : 0;
while (gap < GAP || i < minIndex + GAP) {
const batch = [];
for (let k = 0; k < 10; k++) batch.push(this._keys.entry(branch, i + k));
const results = await Promise.all(batch.map((e) => this._historyOf(e)));
for (let k = 0; k < batch.length; k++) {
const e = batch[k];
this._state.watched.set(e.scripthash, e);
if (results[k].length) { this._state.used.add(branch + "/" + e.index); gap = 0; } else gap++;
i++;
if (gap >= GAP && i >= minIndex + GAP) break;
}
}
}
let r = cursor;
while (this._state.used.has("0/" + r)) r++;
this._state.receiveIndex = r;
this._state.watched.set(this._keys.entry(0, r).scripthash, this._keys.entry(0, r));
}
async _subscribeAll() {
if (!this._subscribedHeaders) {
this._subscribedHeaders = true;
const tip = await this._client.subscribe("blockchain.headers.subscribe", []);
if (tip && tip.height) this._state.height = tip.height;
}
await Promise.all([...this._state.watched.values()].map((e) =>
this._client.subscribe("blockchain.scripthash.subscribe", [e.scripthash]).catch(() => {})));
}
async _loadUtxos() {
const lists = await Promise.all([...this._state.watched.values()].map(async (e) => {
const u = await this._client.call("blockchain.scripthash.listunspent", [e.scripthash]);
return (Array.isArray(u) ? u : []).map((x) => ({ txid: x.tx_hash, vout: x.tx_pos, value: x.value, height: x.height, entry: e }));
}));
this._state.utxos = lists.flat();
let confirmed = 0, unconfirmed = 0;
for (const u of this._state.utxos) { if (u.height > 0) confirmed += u.value; else unconfirmed += u.value; }
this._state.balance = { confirmed, unconfirmed };
}
async _loadHistory() {
// Best-effort tx-history summary: pull the transactions listed against
// any used scripthash, sum ours-vs-not to get a delta per tx. Fine on
// the DGB electrum pool (get_transaction verbose is supported).
const entries = [...this._state.watched.values()].filter((e) => this._state.used.has(e.branch + "/" + e.index));
const merged = new Map();
const lists = await Promise.all(entries.map((e) => this._historyOf(e)));
for (const list of lists) for (const h of list) {
const prev = merged.get(h.tx_hash);
if (!prev || (h.height > 0 && prev.height <= 0)) merged.set(h.tx_hash, { txid: h.tx_hash, height: h.height });
}
const ordered = [...merged.values()].sort((a, b) => {
const ha = a.height > 0 ? a.height : Infinity, hb = b.height > 0 ? b.height : Infinity;
return hb - ha;
}).slice(0, 25);
const ours = new Set([...this._state.watched.values()].map((e) => e.scriptHex));
const out = [];
for (const h of ordered) {
let received = 0, spent = 0;
try {
const t = await this._client.call("blockchain.transaction.get", [h.txid, true]);
for (const o of t.vout || []) {
const hex = o.scriptPubKey && o.scriptPubKey.hex;
if (hex && ours.has(hex)) received += Math.round(Number(o.value || 0) * 1e8);
}
for (const i of t.vin || []) {
if (!i.txid) continue;
try {
const p = await this._client.call("blockchain.transaction.get", [i.txid, true]);
const po = p.vout && p.vout[i.vout];
const hex = po && po.scriptPubKey && po.scriptPubKey.hex;
if (hex && ours.has(hex)) spent += Math.round(Number(po.value || 0) * 1e8);
} catch {}
}
out.push({
txid: h.txid, height: h.height, confirmations: t.confirmations || 0,
time: t.blocktime || t.time || 0,
delta: received - spent, fee: null, to: null,
status: (t.confirmations || 0) > 0 ? "confirmed" : "pending",
kind: "transfer",
});
} catch {
out.push({ txid: h.txid, height: h.height, confirmations: 0, time: 0, delta: 0, fee: null, to: null, status: "pending", kind: "transfer" });
}
}
this._state.history = out;
}
async refresh(full = false) {
if (this._state.scanning) return;
this._state.scanning = true; this._state.error = null; this._emit();
try {
if (full || !this._state.watched.size) await this._scan();
else {
let r = Number(this.storage.get("receiveCursor", 0)) || 0;
while (this._state.used.has("0/" + r)) r++;
this._state.receiveIndex = r;
this._state.watched.set(this._keys.entry(0, r).scripthash, this._keys.entry(0, r));
}
await this._loadUtxos();
await this._loadHistory();
await this._subscribeAll();
for (const u of this._state.utxos) this._state.used.add(u.entry.branch + "/" + u.entry.index);
let r = Number(this.storage.get("receiveCursor", 0)) || 0;
while (this._state.used.has("0/" + r)) r++;
this._state.receiveIndex = r;
} catch (e) {
this._state.error = e?.message || String(e);
this.log("refresh failed:", this._state.error);
} finally {
this._state.scanning = false;
this._emit();
}
}
_scheduleRefresh(ms = 800) {
clearTimeout(this._refreshTimer);
this._refreshTimer = setTimeout(() => this.refresh(false), ms);
}
current() { return this._keys.entry(0, this._state.receiveIndex); }
nextAddress() {
let r = this._state.receiveIndex + 1;
while (this._state.used.has("0/" + r)) r++;
this.storage.set("receiveCursor", r);
this._state.receiveIndex = r;
const e = this._keys.entry(0, r);
this._state.watched.set(e.scripthash, e);
this._client.subscribe("blockchain.scripthash.subscribe", [e.scripthash]).catch(() => {});
this._emit();
return this.current();
}
_changeEntry() {
let i = 0;
while (this._state.used.has("1/" + i)) i++;
return this._keys.entry(1, i);
}
setServers(list) {
this._servers = Array.isArray(list) && list.length ? list : DEFAULT_SERVERS.slice();
this._client.setServers(this._servers);
}
// ---- plan + sign (via @dgb-wallet/psbt) -----------------------------
// sat/vB from the Electrum server, never below the 20 this wallet has
// always paid (DigiByte's relay floor is far above Bitcoin's, and there
// is no replace-by-fee on DGB to rescue an underpaid transaction).
async _estimateRate() {
if (this._feeEst && Date.now() - this._feeEstAt < 60_000) return this._feeEst;
try {
const perKb = Number(await Promise.race([
this._client.call("blockchain.estimatefee", [6]),
new Promise((_, rej) => setTimeout(() => rej(new Error("estimatefee timeout")), 4000)),
]));
if (Number.isFinite(perKb) && perKb > 0) { // coin per kB; -1 = no estimate
this._feeEst = Math.min(500, Math.max(DEFAULT_RATE, Math.ceil(perKb * 1e5)));
this._feeEstAt = Date.now();
return this._feeEst;
}
} catch {}
return DEFAULT_RATE;
}
async plan({ to, amount, feeRate, sendMax = false }) {
const rate = feeRate != null && Number(feeRate) > 0
? Math.min(500, Math.max(1, Number(feeRate)))
: await this._estimateRate();
const dest = String(to || "");
// The `payments` decoder will reject anything that isn't a valid
// DGB address; catch and re-raise as a sane error.
try { payments.address({ address: dest, network: digibyte }); }
catch {
// bitcoinjs-lib's address decoder is `address.toOutputScript`, not
// payments.address; use it for validation.
try { bitcoinjs.address.toOutputScript(dest, digibyte); }
catch (e) { throw new Error(`bad DGB address: ${e?.message || dest}`); }
}
const destScript = bitcoinjs.address.toOutputScript(dest, digibyte);
const spendable = this._state.utxos.slice().sort((a, b) => (b.height > 0) - (a.height > 0));
const change = this._changeEntry();
if (sendMax) {
const chosen = spendable;
const sum = chosen.reduce((a, u) => a + u.value, 0);
const fee = feeFor(change.send, chosen.length, [destScript], rate);
if (sum <= fee) throw new Error("balance does not cover the fee");
return {
_chosen: chosen, _rate: rate, _to: dest, _sendMax: true, _change: change,
recipients: [{ to: dest, value: sum - fee }],
fee, feeRate: rate, change: 0,
total: sum,
};
}
const value = Math.round(Number(amount) || 0);
if (!(value > 0)) throw new Error("amount must be > 0");
let sum = 0; const chosen = [];
for (const u of spendable) {
chosen.push(u); sum += u.value;
const withChange = feeFor(change.send, chosen.length, [destScript, change.script], rate);
if (sum >= value + withChange) {
const changeVal = sum - value - withChange;
const fee = changeVal > 546 ? withChange : sum - value;
return {
_chosen: chosen, _rate: rate, _to: dest, _value: value,
_change: change, _changeVal: changeVal > 546 ? changeVal : 0,
recipients: [{ to: dest, value }],
fee, feeRate: rate,
change: changeVal > 546 ? changeVal : 0,
total: value + fee,
};
}
}
throw new Error("insufficient funds");
}
async signAndBroadcast(plan) {
// buildPsbt orders inputs by BIP69; put ours in that same order first
// so input i is signed with the key of the UTXO that actually sits at
// i. Signing in selection order failed on any multi-address spend.
const chosen = plan._chosen.slice().sort((a, b) => {
const cmp = a.txid.localeCompare(b.txid);
return cmp !== 0 ? cmp : a.vout - b.vout;
});
// P2PKH (BIP44) inputs need the whole previous transaction for the
// legacy sighash; fetch each one before assembling the PSBT.
const prevHex = new Map();
const needsPrev = chosen.filter((u) => u.entry.family === "bip44");
if (needsPrev.length) {
const results = await Promise.all(needsPrev.map((u) => this._client.call("blockchain.transaction.get", [u.txid, false])));
needsPrev.forEach((u, i) => prevHex.set(u.txid, String(results[i])));
}
const psbtInputs = chosen.map((u) => {
const fam = u.entry.family;
const inp = { txid: u.txid, vout: u.vout };
if (fam === "bip44") inp.nonWitnessTxHex = prevHex.get(u.txid);
else inp.witness = { scriptHex: u.entry.scriptHex, value: u.value };
if (fam === "bip49") inp.redeemScriptHex = u.entry.redeemScript.toString("hex");
if (fam === "bip86") inp.tapInternalKeyHex = u.entry.tapInternalKey.toString("hex");
return inp;
});
const psbtOutputs = [{ address: plan._to, value: plan._sendMax ? plan.recipients[0].value : plan._value }];
if (!plan._sendMax && plan._changeVal > 0) {
psbtOutputs.push({ address: plan._change.address, value: plan._changeVal });
}
const psbt = buildPsbt({ inputs: psbtInputs, outputs: psbtOutputs }, digibyte);
// Sign per-input with the exact key that funded that UTXO. signAllInputs
// would work when all inputs share a key, but each derived address
// has its own key, so we go per-input.
for (let i = 0; i < chosen.length; i++) {
const entry = chosen[i].entry;
if (entry.family === "bip86") {
// Taproot key-path: the signer must be the tap-tweaked key, which
// ECPair.tweak() produces (as chain-btc.js does).
const raw = ECPair.fromPrivateKey(Buffer.from(entry._node.privateKey), { network: digibyte });
psbt.signInput(i, raw.tweak(bitcoinjs.crypto.taggedHash("TapTweak", entry.tapInternalKey)));
} else {
psbt.signInput(i, this._keys.signerFor(entry));
}
}
const { hex, txid } = finalizeAndExtract(psbt);
const broadcast = await this._client.call("blockchain.transaction.broadcast", [hex]);
if (typeof broadcast !== "string" || broadcast.length !== 64) {
throw new Error("broadcast rejected: " + JSON.stringify(broadcast));
}
this.log("broadcast", txid);
this._scheduleRefresh(1200);
return { txid, hex, fee: plan.fee };
}
// BIP-137-style: 65-byte recoverable signature over sha256d(magic || msg).
signMessage(message) {
const enc = new TextEncoder();
const varstr = (s) => { const b = enc.encode(s); if (b.length >= 0xfd) throw new Error("too long"); return Uint8Array.from([b.length, ...b]); };
const MAGIC = "DigiByte Signed Message:\n";
const payload = Uint8Array.from([...varstr(MAGIC), ...varstr(String(message))]);
const digest = sha256(sha256(payload));
const entry = this.current();
const signer = this._keys.signerFor(entry);
// ECPair's `signSchnorr` and `sign` don't emit recoverable sigs; fall
// back to node's ecc.signRecoverable through @bitcoinerlab/secp256k1
// (which the vendored @dgb-wallet/core already loaded).
const sig = ecc.signRecoverable(Buffer.from(digest), signer.privateKey);
const out = Buffer.alloc(65);
out[0] = 27 + sig.recoveryId + 4; // +4 = compressed pubkey
Buffer.from(sig.signature).copy(out, 1);
return { address: entry.address, signature: out.toString("base64") };
}
recovery() {
return { accountPath: this._keys.accountPath, xpub: this._keys.xpub, xprv: this._keys.xprv };
}
snapshot() {
const cur = this.current();
return {
chain: "dgb", network: NETWORK, ticker: "DGB", decimals: 8,
address: cur.address, addressIndex: this._state.receiveIndex,
addressPath: cur.path,
balance: this._state.balance,
height: this._state.height,
history: this._state.history,
scanning: this._state.scanning,
error: this._state.error,
server: this._client.url || null,
servers: this._servers,
accountPath: this._keys.accountPath,
xpub: this._keys.xpub,
explorerTx: EXPLORER_TX, explorerAddr: EXPLORER_ADDR, faucet: null,
};
}
dispose() {
clearTimeout(this._refreshTimer);
try { this._keys.wipe(); } catch {}
try { this._client.dispose ? this._client.dispose() : this._client.disconnect(); } catch {}
if (this._root) this._root.fill(0);
}
}
return { DgbWallet, EXPLORER_TX, EXPLORER_ADDR };
};