Aegis now shares its DGB code with the standalone DigiByte web-wallet at
D:\Dev\SilentCode\Digibyte. Address derivation and PSBT construction come
from that project's @dgb-wallet/core and @dgb-wallet/psbt packages instead
of Aegis-local reimplementations. Any bugfix upstream flows in via a
re-vendor of dist/*.
- lib/dgb/{core,psbt}/ — vendored dist/ output of the two packages plus
a tiny package.json shim marking them as ESM. @dgb-wallet/core's own
import specifier "@dgb-wallet/core" inside psbt/*.js is rewritten to
"../core/index.js" so the sibling module resolves without a workspace.
- New Theseus deps: bitcoinjs-lib, bip32, bip39, @bitcoinerlab/secp256k1,
ecpair — the peer deps the vendored packages need. Loaded via
api.require in index.js's loadDeps().
- chain-dgb.js is a thin adapter now: BIP32 tree via bip32 + DGB
Network object, addresses via core.p2wpkhAddress, tx via
psbt.buildPsbt + PSBT.signInput (per-input, since each UTXO's key
differs) + psbt.finalizeAndExtract. Runtime backend stays the same —
Theseus's lib/electrum.js against the DGB ElectrumX pool.
- Verified end-to-end in scratchpad: abandon×11 mnemonic derives
dgb1q9gmf0pv8jdymcly6lz6fl7lf6mhslsd72e2jq8 (matches iancoleman.io/bip39
and the previous inline implementation, so no on-chain address change
for anyone who was already using Aegis's DGB slot). PSBT build+sign+
finalize on a mock UTXO produces a valid 223-byte witness tx.
BIP44 (D…) and BIP49 (S…) address families are implemented in the
vendored core but not yet exposed in Aegis's picker — the panel needs
an "address family" selector inside the DGB settings block first. Left
for a follow-up; today's DGB pick uses BIP84 native SegWit only.
451 lines
19 KiB
JavaScript
451 lines
19 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.
|
|
//
|
|
// Only BIP84 (m/84'/20'/0'/0/x → dgb1q…) is exposed in this rev; the
|
|
// vendored core also supports BIP44 (D…) and BIP49 (S…) — plumb them by
|
|
// switching the purpose passed to accountNode(). 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");
|
|
}
|
|
function scriptPubKeyBuf(pubkeyBuf) {
|
|
return payments.p2wpkh({ pubkey: pubkeyBuf, network: digibyte }).output;
|
|
}
|
|
|
|
// ---- 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;
|
|
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 address = p2wpkhAddress(node, digibyte);
|
|
const script = Buffer.from(scriptPubKeyBuf(pubkey));
|
|
e = {
|
|
branch, index, path: this._accountPath + "/" + branch + "/" + index,
|
|
publicKey: pubkey, script, scriptHex: script.toString("hex"),
|
|
scripthash: scripthashOf(script),
|
|
address,
|
|
_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) -----------------
|
|
const OVERHEAD_VB = 10.5;
|
|
const P2WPKH_INPUT_VB = 68;
|
|
const P2WPKH_OUTPUT_VB = 31;
|
|
const feeVb = (nIn, nOut, feePerVb) => Math.ceil((OVERHEAD_VB + nIn * P2WPKH_INPUT_VB + nOut * P2WPKH_OUTPUT_VB) * feePerVb);
|
|
|
|
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) -----------------------------
|
|
plan({ to, amount, feeRate = 20, sendMax = false }) {
|
|
const rate = Math.min(500, Math.max(1, Number(feeRate) || 20));
|
|
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 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 = feeVb(chosen.length, 1, 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 = feeVb(chosen.length, 2, 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) {
|
|
const psbtInputs = plan._chosen.map((u) => ({
|
|
txid: u.txid,
|
|
vout: u.vout,
|
|
witness: { scriptHex: u.entry.scriptHex, value: u.value },
|
|
}));
|
|
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 < plan._chosen.length; i++) {
|
|
const signer = this._keys.signerFor(plan._chosen[i].entry);
|
|
psbt.signInput(i, signer);
|
|
}
|
|
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.disconnect(); } catch {}
|
|
if (this._root) this._root.fill(0);
|
|
}
|
|
}
|
|
|
|
return { DgbWallet, EXPLORER_TX, EXPLORER_ADDR };
|
|
};
|