Token auto-discovery, tx history, balance polling, EIP-6963, UI overhaul
All checks were successful
check / check (push) Successful in 14s
All checks were successful
check / check (push) Successful in 14s
Major changes: - Fetch token balances and tx history from Blockscout API (configurable) - Remove manual token discovery (discoverTokens) in favor of Blockscout - HD address gap scanning on mnemonic import - Duplicate mnemonic detection on wallet add - EIP-6963 multi-wallet discovery + selectedAddress updates in inpage - Two-tier balance refresh: 10s while popup open, 60s background - Fix $0.00 flash before prices load (return null when no prices) - No-layout-shift: min-height on total value element - Aligned balance columns (42ch address width, consistent USD column) - All errors use flash messages instead of off-screen error divs - Settings gear in global title bar, add-wallet moved to settings pane - Settings wells with light grey background, configurable Blockscout URL - Consistent "< Back" buttons top-left on all views - Address titles (Address 1.1, 1.2, etc.) on main and detail views - Send view shows current balance of selected asset - Clickable affordance policy added to README - Shortened mnemonic backup warning - Fix broken background script constant imports
This commit is contained in:
@@ -1,19 +1,23 @@
|
||||
// Balance fetching: ETH balances, ERC-20 token balances, ENS reverse lookup.
|
||||
// Cached for 60 seconds.
|
||||
// Balance fetching: ETH balances via RPC, ERC-20 token balances via
|
||||
// Blockscout, ENS reverse lookup via RPC.
|
||||
|
||||
const {
|
||||
JsonRpcProvider,
|
||||
Network,
|
||||
Contract,
|
||||
formatEther,
|
||||
formatUnits,
|
||||
} = require("ethers");
|
||||
const { ERC20_ABI } = require("./constants");
|
||||
const { log } = require("./log");
|
||||
const { deriveAddressFromXpub } = require("./wallet");
|
||||
|
||||
const BALANCE_CACHE_TTL = 60000; // 60 seconds
|
||||
let lastFetchedAt = 0;
|
||||
// Use a static network to skip auto-detection (which can fail and cause
|
||||
// "could not coalesce error" on some RPC endpoints like Cloudflare).
|
||||
const mainnet = Network.from("mainnet");
|
||||
|
||||
function getProvider(rpcUrl) {
|
||||
return new JsonRpcProvider(rpcUrl);
|
||||
return new JsonRpcProvider(rpcUrl, mainnet, { staticNetwork: mainnet });
|
||||
}
|
||||
|
||||
function formatBalance(wei) {
|
||||
@@ -32,11 +36,42 @@ function formatTokenBalance(raw, decimals) {
|
||||
return parts[0] + "." + dec;
|
||||
}
|
||||
|
||||
// Fetch token balances for a single address from Blockscout.
|
||||
// Returns [{ address, symbol, decimals, balance }].
|
||||
async function fetchTokenBalances(address, blockscoutUrl) {
|
||||
try {
|
||||
const resp = await fetch(
|
||||
blockscoutUrl + "/addresses/" + address + "/token-balances",
|
||||
);
|
||||
if (!resp.ok) {
|
||||
log.errorf("blockscout token-balances:", resp.status);
|
||||
return null;
|
||||
}
|
||||
const items = await resp.json();
|
||||
if (!Array.isArray(items)) return null;
|
||||
const balances = [];
|
||||
for (const item of items) {
|
||||
if (item.token?.type !== "ERC-20") continue;
|
||||
const decimals = parseInt(item.token.decimals || "18", 10);
|
||||
const bal = formatTokenBalance(item.value || "0", decimals);
|
||||
if (bal === "0.0") continue;
|
||||
balances.push({
|
||||
address: item.token.address_hash,
|
||||
symbol: item.token.symbol || "???",
|
||||
decimals: decimals,
|
||||
balance: bal,
|
||||
});
|
||||
}
|
||||
return balances;
|
||||
} catch (e) {
|
||||
log.errorf("fetchTokenBalances failed:", e.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch ETH balances, ENS names, and ERC-20 token balances for all addresses.
|
||||
// trackedTokens: [{ address, symbol, decimals }]
|
||||
async function refreshBalances(wallets, trackedTokens, rpcUrl) {
|
||||
const now = Date.now();
|
||||
if (now - lastFetchedAt < BALANCE_CACHE_TTL) return;
|
||||
async function refreshBalances(wallets, rpcUrl, blockscoutUrl) {
|
||||
log.debugf("refreshBalances start, rpc:", rpcUrl);
|
||||
const provider = getProvider(rpcUrl);
|
||||
const updates = [];
|
||||
|
||||
@@ -48,8 +83,15 @@ async function refreshBalances(wallets, trackedTokens, rpcUrl) {
|
||||
.getBalance(addr.address)
|
||||
.then((bal) => {
|
||||
addr.balance = formatBalance(bal);
|
||||
log.debugf("ETH balance", addr.address, addr.balance);
|
||||
})
|
||||
.catch(() => {}),
|
||||
.catch((e) => {
|
||||
log.errorf(
|
||||
"ETH balance failed",
|
||||
addr.address,
|
||||
e.shortMessage || e.message,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
// ENS reverse lookup
|
||||
@@ -64,67 +106,117 @@ async function refreshBalances(wallets, trackedTokens, rpcUrl) {
|
||||
}),
|
||||
);
|
||||
|
||||
// ERC-20 token balances
|
||||
if (!addr.tokenBalances) addr.tokenBalances = [];
|
||||
for (const token of trackedTokens) {
|
||||
updates.push(
|
||||
(async () => {
|
||||
try {
|
||||
const contract = new Contract(
|
||||
token.address,
|
||||
ERC20_ABI,
|
||||
provider,
|
||||
// ERC-20 token balances via Blockscout
|
||||
updates.push(
|
||||
fetchTokenBalances(addr.address, blockscoutUrl).then(
|
||||
(balances) => {
|
||||
if (balances !== null) {
|
||||
addr.tokenBalances = balances;
|
||||
log.debugf(
|
||||
"Token balances",
|
||||
addr.address,
|
||||
balances.length,
|
||||
"tokens",
|
||||
);
|
||||
const raw = await contract.balanceOf(addr.address);
|
||||
const existing = addr.tokenBalances.find(
|
||||
(t) =>
|
||||
t.address.toLowerCase() ===
|
||||
token.address.toLowerCase(),
|
||||
);
|
||||
const bal = formatTokenBalance(raw, token.decimals);
|
||||
if (existing) {
|
||||
existing.balance = bal;
|
||||
} else {
|
||||
addr.tokenBalances.push({
|
||||
address: token.address,
|
||||
symbol: token.symbol,
|
||||
decimals: token.decimals,
|
||||
balance: bal,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// skip on error
|
||||
}
|
||||
})(),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(updates);
|
||||
lastFetchedAt = now;
|
||||
log.debugf("refreshBalances done");
|
||||
}
|
||||
|
||||
// Look up token metadata from its contract.
|
||||
// Calls symbol() and decimals() to verify it implements ERC-20.
|
||||
async function lookupTokenInfo(contractAddress, rpcUrl) {
|
||||
log.debugf("lookupTokenInfo", contractAddress, "rpc:", rpcUrl);
|
||||
const provider = getProvider(rpcUrl);
|
||||
const contract = new Contract(contractAddress, ERC20_ABI, provider);
|
||||
const [name, symbol, decimals] = await Promise.all([
|
||||
contract.name(),
|
||||
contract.symbol(),
|
||||
contract.decimals(),
|
||||
]);
|
||||
|
||||
let name, symbol, decimals;
|
||||
try {
|
||||
symbol = await contract.symbol();
|
||||
log.debugf("symbol() =", symbol);
|
||||
} catch (e) {
|
||||
log.errorf("symbol() failed:", e.shortMessage || e.message);
|
||||
throw new Error("Not a valid ERC-20 token (symbol() failed).");
|
||||
}
|
||||
|
||||
try {
|
||||
decimals = await contract.decimals();
|
||||
log.debugf("decimals() =", decimals);
|
||||
} catch (e) {
|
||||
log.errorf("decimals() failed:", e.shortMessage || e.message);
|
||||
throw new Error("Not a valid ERC-20 token (decimals() failed).");
|
||||
}
|
||||
|
||||
try {
|
||||
name = await contract.name();
|
||||
log.debugf("name() =", name);
|
||||
} catch (e) {
|
||||
log.warnf("name() failed, using symbol as name:", e.message);
|
||||
name = symbol;
|
||||
}
|
||||
|
||||
log.infof("Token resolved:", symbol, "decimals", Number(decimals));
|
||||
return { name, symbol, decimals: Number(decimals) };
|
||||
}
|
||||
|
||||
// Force-invalidate the balance cache (e.g. after sending a tx).
|
||||
function invalidateBalanceCache() {
|
||||
lastFetchedAt = 0;
|
||||
// Derive HD addresses starting from index 0 and check for on-chain activity.
|
||||
// Stops after gapLimit consecutive addresses with zero balance and zero tx count.
|
||||
// Returns { addresses: [{ address, index }], nextIndex }.
|
||||
async function scanForAddresses(xpub, rpcUrl, gapLimit = 5) {
|
||||
log.debugf("scanForAddresses start, gapLimit:", gapLimit);
|
||||
const provider = getProvider(rpcUrl);
|
||||
const used = [];
|
||||
let gap = 0;
|
||||
let index = 0;
|
||||
|
||||
while (gap < gapLimit) {
|
||||
const addr = deriveAddressFromXpub(xpub, index);
|
||||
let balance, txCount;
|
||||
try {
|
||||
[balance, txCount] = await Promise.all([
|
||||
provider.getBalance(addr),
|
||||
provider.getTransactionCount(addr),
|
||||
]);
|
||||
} catch (e) {
|
||||
log.errorf(
|
||||
"scanForAddresses check failed",
|
||||
addr,
|
||||
e.shortMessage || e.message,
|
||||
);
|
||||
// Treat RPC failure as empty to avoid infinite loop
|
||||
gap++;
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
if (balance > 0n || txCount > 0) {
|
||||
used.push({ address: addr, index });
|
||||
gap = 0;
|
||||
log.debugf("scanForAddresses used", addr, "index:", index);
|
||||
} else {
|
||||
gap++;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
const nextIndex = used.length > 0 ? used[used.length - 1].index + 1 : 1;
|
||||
log.infof(
|
||||
"scanForAddresses done, found:",
|
||||
used.length,
|
||||
"nextIndex:",
|
||||
nextIndex,
|
||||
);
|
||||
return { addresses: used, nextIndex };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
refreshBalances,
|
||||
lookupTokenInfo,
|
||||
invalidateBalanceCache,
|
||||
getProvider,
|
||||
scanForAddresses,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
const DEBUG = true;
|
||||
const DEBUG_MNEMONIC =
|
||||
"cube evolve unfold result inch risk jealous skill hotel bulb night wreck";
|
||||
|
||||
const ETHEREUM_MAINNET_CHAIN_ID = "0x1";
|
||||
|
||||
const DEFAULT_RPC_URL = "https://eth.llamarpc.com";
|
||||
const DEFAULT_RPC_URL = "https://ethereum-rpc.publicnode.com";
|
||||
|
||||
const DEFAULT_BLOCKSCOUT_URL = "https://eth.blockscout.com/api/v2";
|
||||
|
||||
const BIP44_ETH_PATH = "m/44'/60'/0'/0";
|
||||
|
||||
@@ -15,8 +21,11 @@ const ERC20_ABI = [
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
DEBUG,
|
||||
DEBUG_MNEMONIC,
|
||||
ETHEREUM_MAINNET_CHAIN_ID,
|
||||
DEFAULT_RPC_URL,
|
||||
DEFAULT_BLOCKSCOUT_URL,
|
||||
BIP44_ETH_PATH,
|
||||
ERC20_ABI,
|
||||
};
|
||||
|
||||
30
src/shared/log.js
Normal file
30
src/shared/log.js
Normal file
@@ -0,0 +1,30 @@
|
||||
// Leveled logger. Outputs to console with [AutistMask] prefix.
|
||||
// Level is DEBUG when the DEBUG constant is true, INFO otherwise.
|
||||
|
||||
const { DEBUG } = require("./constants");
|
||||
|
||||
const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
||||
const threshold = DEBUG ? LEVELS.debug : LEVELS.info;
|
||||
|
||||
function emit(level, method, args) {
|
||||
if (LEVELS[level] >= threshold) {
|
||||
console[method]("[AutistMask]", ...args);
|
||||
}
|
||||
}
|
||||
|
||||
const log = {
|
||||
debugf(...args) {
|
||||
emit("debug", "log", args);
|
||||
},
|
||||
infof(...args) {
|
||||
emit("info", "log", args);
|
||||
},
|
||||
warnf(...args) {
|
||||
emit("warn", "warn", args);
|
||||
},
|
||||
errorf(...args) {
|
||||
emit("error", "error", args);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = { log };
|
||||
@@ -37,12 +37,10 @@ function formatUsd(amount) {
|
||||
}
|
||||
|
||||
function getAddressValueUsd(addr) {
|
||||
if (!prices.ETH) return null;
|
||||
let total = 0;
|
||||
const ethBal = parseFloat(addr.balance || "0");
|
||||
const ethPrice = prices.ETH;
|
||||
if (ethPrice) {
|
||||
total += ethBal * ethPrice;
|
||||
}
|
||||
total += ethBal * prices.ETH;
|
||||
for (const token of addr.tokenBalances || []) {
|
||||
const tokenBal = parseFloat(token.balance || "0");
|
||||
if (tokenBal > 0 && prices[token.symbol]) {
|
||||
@@ -53,6 +51,7 @@ function getAddressValueUsd(addr) {
|
||||
}
|
||||
|
||||
function getWalletValueUsd(wallet) {
|
||||
if (!prices.ETH) return null;
|
||||
let total = 0;
|
||||
for (const addr of wallet.addresses) {
|
||||
total += getAddressValueUsd(addr);
|
||||
@@ -61,6 +60,7 @@ function getWalletValueUsd(wallet) {
|
||||
}
|
||||
|
||||
function getTotalValueUsd(wallets) {
|
||||
if (!prices.ETH) return null;
|
||||
let total = 0;
|
||||
for (const wallet of wallets) {
|
||||
total += getWalletValueUsd(wallet);
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
// Known scam/fraud addresses. Checked locally before sending.
|
||||
// This is a best-effort blocklist — it does not replace due diligence.
|
||||
// Sources: Etherscan labels, MistTrack, community reports.
|
||||
//
|
||||
// Policy: This list contains ONLY addresses involved in fraud — phishing,
|
||||
// wallet drainers, address poisoning, and similar scams. It does NOT include
|
||||
// addresses that are merely sanctioned or regulated in specific jurisdictions
|
||||
// (e.g. Tornado Cash, OFAC SDN entries). AutistMask is used internationally
|
||||
// and does not enforce jurisdiction-specific sanctions.
|
||||
//
|
||||
// Sources:
|
||||
// - Known wallet-drainer contracts identified via Etherscan labels,
|
||||
// MistTrack alerts, and community incident reports (e.g. address-
|
||||
// poisoning campaigns, phishing kit deployments).
|
||||
//
|
||||
// All addresses lowercased for comparison.
|
||||
|
||||
const SCAM_ADDRESSES = new Set([
|
||||
@@ -14,31 +25,6 @@ const SCAM_ADDRESSES = new Set([
|
||||
"0x3ee18b2214aff97000d974cf647e7c347e8fa585",
|
||||
"0x55fe002aeff02f77364de339a1292923a15844b8",
|
||||
"0x7f268357a8c2552623316e2562d90e642bb538e5",
|
||||
// Tornado Cash sanctioned addresses (OFAC)
|
||||
"0x722122df12d4e14e13ac3b6895a86e84145b6967",
|
||||
"0xdd4c48c0b24039969fc16d1cdf626eab821d3384",
|
||||
"0xd90e2f925da726b50c4ed8d0fb90ad053324f31b",
|
||||
"0xd96f2b1ab14cd8ab753fa0357fee5cd7d512c838",
|
||||
"0x4736dcf1b7a3d580672cce6e7c65cd5cc9cfbfa9",
|
||||
"0xd4b88df4d29f5cedd6857912842cff3b20c8cfa3",
|
||||
"0x910cbd523d972eb0a6f4cae4618ad62622b39dbf",
|
||||
"0xa160cdab225685da1d56aa342ad8841c3b53f291",
|
||||
"0xfd8610d20aa15b7b2e3be39b396a1bc3516c7144",
|
||||
"0xf60dd140cff0706bae9cd734ac3683731eb5bb31",
|
||||
"0x22aaa7720ddd5388a3c0a3333430953c68f1849b",
|
||||
"0xba214c1c1928a32bffe790263e38b4af9bfcd659",
|
||||
"0xb1c8094b234dce6e03f10a5b673c1d8c69739a00",
|
||||
"0x527653ea119f3e6a1f5bd18fbf4714081d7b31ce",
|
||||
"0x58e8dcc13be9780fc42e8723d8ead4cf46943df2",
|
||||
"0xd691f27f38b395864ea86cfc7253969b409c362d",
|
||||
"0xaeaac358560e11f52454d997aaff2c5731b6f8a6",
|
||||
"0x1356c899d8c9467c7f71c195612f8a395abf2f0a",
|
||||
"0xa60c772958a3ed56c1f15dd055ba37ac8e523a0d",
|
||||
"0x169ad27a470d064dede56a2d3ff727986b15d52b",
|
||||
"0x0836222f2b2b24a3f36f98668ed8f0b38d1a872f",
|
||||
"0x178169b423a011fff22b9e3f3abea13414ddd0f1",
|
||||
"0x610b717796ad172b316957a19699d4b58edca1e0",
|
||||
"0xbb93e510bbcd0b7beb5a853875f9ec60275cf498",
|
||||
]);
|
||||
|
||||
function isScamAddress(address) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// State management and extension storage persistence.
|
||||
|
||||
const { DEFAULT_RPC_URL, DEFAULT_BLOCKSCOUT_URL } = require("./constants");
|
||||
|
||||
const storageApi =
|
||||
typeof browser !== "undefined"
|
||||
? browser.storage.local
|
||||
@@ -9,7 +11,9 @@ const DEFAULT_STATE = {
|
||||
hasWallet: false,
|
||||
wallets: [],
|
||||
trackedTokens: [],
|
||||
rpcUrl: "https://eth.llamarpc.com",
|
||||
rpcUrl: DEFAULT_RPC_URL,
|
||||
blockscoutUrl: DEFAULT_BLOCKSCOUT_URL,
|
||||
lastBalanceRefresh: 0,
|
||||
};
|
||||
|
||||
const state = {
|
||||
@@ -24,6 +28,8 @@ async function saveState() {
|
||||
wallets: state.wallets,
|
||||
trackedTokens: state.trackedTokens,
|
||||
rpcUrl: state.rpcUrl,
|
||||
blockscoutUrl: state.blockscoutUrl,
|
||||
lastBalanceRefresh: state.lastBalanceRefresh,
|
||||
};
|
||||
await storageApi.set({ autistmask: persisted });
|
||||
}
|
||||
@@ -36,6 +42,9 @@ async function loadState() {
|
||||
state.wallets = saved.wallets || [];
|
||||
state.trackedTokens = saved.trackedTokens || [];
|
||||
state.rpcUrl = saved.rpcUrl || DEFAULT_STATE.rpcUrl;
|
||||
state.blockscoutUrl =
|
||||
saved.blockscoutUrl || DEFAULT_STATE.blockscoutUrl;
|
||||
state.lastBalanceRefresh = saved.lastBalanceRefresh || 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
108
src/shared/transactions.js
Normal file
108
src/shared/transactions.js
Normal file
@@ -0,0 +1,108 @@
|
||||
// Transaction history fetching via Blockscout v2 API.
|
||||
// Fetches normal transactions and ERC-20 token transfers,
|
||||
// merges them, and returns the most recent entries.
|
||||
|
||||
const { formatEther, formatUnits } = require("ethers");
|
||||
const { log } = require("./log");
|
||||
|
||||
function formatTxValue(val) {
|
||||
const parts = val.split(".");
|
||||
if (parts.length === 1) return val;
|
||||
const dec = parts[1].slice(0, 6).replace(/0+$/, "") || "0";
|
||||
return parts[0] + "." + dec;
|
||||
}
|
||||
|
||||
function parseTx(tx, addrLower) {
|
||||
const from = tx.from?.hash || "";
|
||||
const to = tx.to?.hash || "";
|
||||
return {
|
||||
hash: tx.hash,
|
||||
blockNumber: tx.block_number,
|
||||
timestamp: Math.floor(new Date(tx.timestamp).getTime() / 1000),
|
||||
from: from,
|
||||
to: to,
|
||||
value: formatTxValue(formatEther(tx.value || "0")),
|
||||
symbol: "ETH",
|
||||
direction: from.toLowerCase() === addrLower ? "sent" : "received",
|
||||
isError: tx.status !== "ok",
|
||||
};
|
||||
}
|
||||
|
||||
function parseTokenTransfer(tt, addrLower) {
|
||||
const from = tt.from?.hash || "";
|
||||
const to = tt.to?.hash || "";
|
||||
const decimals = parseInt(tt.total?.decimals || "18", 10);
|
||||
const rawValue = tt.total?.value || "0";
|
||||
return {
|
||||
hash: tt.transaction_hash,
|
||||
blockNumber: tt.block_number,
|
||||
timestamp: Math.floor(new Date(tt.timestamp).getTime() / 1000),
|
||||
from: from,
|
||||
to: to,
|
||||
value: formatTxValue(formatUnits(rawValue, decimals)),
|
||||
symbol: tt.token?.symbol || "?",
|
||||
direction: from.toLowerCase() === addrLower ? "sent" : "received",
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchRecentTransactions(address, blockscoutUrl, count = 25) {
|
||||
log.debugf("fetchRecentTransactions", address);
|
||||
const addrLower = address.toLowerCase();
|
||||
|
||||
const [txResp, ttResp] = await Promise.all([
|
||||
fetch(
|
||||
blockscoutUrl +
|
||||
"/addresses/" +
|
||||
address +
|
||||
"/transactions?limit=" +
|
||||
count,
|
||||
),
|
||||
fetch(
|
||||
blockscoutUrl +
|
||||
"/addresses/" +
|
||||
address +
|
||||
"/token-transfers?limit=" +
|
||||
count +
|
||||
"&type=ERC-20",
|
||||
),
|
||||
]);
|
||||
|
||||
if (!txResp.ok) {
|
||||
log.errorf(
|
||||
"blockscout transactions:",
|
||||
txResp.status,
|
||||
txResp.statusText,
|
||||
);
|
||||
}
|
||||
if (!ttResp.ok) {
|
||||
log.errorf(
|
||||
"blockscout token-transfers:",
|
||||
ttResp.status,
|
||||
ttResp.statusText,
|
||||
);
|
||||
}
|
||||
|
||||
const txJson = txResp.ok ? await txResp.json() : {};
|
||||
const ttJson = ttResp.ok ? await ttResp.json() : {};
|
||||
|
||||
const txs = [];
|
||||
|
||||
for (const tx of txJson.items || []) {
|
||||
txs.push(parseTx(tx, addrLower));
|
||||
}
|
||||
|
||||
// Deduplicate: skip token transfers whose tx hash is already in the list
|
||||
const seenHashes = new Set(txs.map((t) => t.hash));
|
||||
for (const tt of ttJson.items || []) {
|
||||
if (seenHashes.has(tt.transaction_hash)) continue;
|
||||
txs.push(parseTokenTransfer(tt, addrLower));
|
||||
}
|
||||
|
||||
txs.sort((a, b) => b.blockNumber - a.blockNumber);
|
||||
const result = txs.slice(0, count);
|
||||
log.debugf("fetchRecentTransactions done, count:", result.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = { fetchRecentTransactions };
|
||||
@@ -2,12 +2,7 @@
|
||||
// All crypto delegated to ethers.js.
|
||||
|
||||
const { Mnemonic, HDNodeWallet, Wallet } = require("ethers");
|
||||
|
||||
const BIP44_ETH_BASE = "m/44'/60'/0'/0";
|
||||
|
||||
const DEBUG = true;
|
||||
const DEBUG_MNEMONIC =
|
||||
"cube evolve unfold result inch risk jealous skill hotel bulb night wreck";
|
||||
const { DEBUG, DEBUG_MNEMONIC, BIP44_ETH_PATH } = require("./constants");
|
||||
|
||||
function generateMnemonic() {
|
||||
if (DEBUG) return DEBUG_MNEMONIC;
|
||||
@@ -23,7 +18,7 @@ function deriveAddressFromXpub(xpub, index) {
|
||||
}
|
||||
|
||||
function hdWalletFromMnemonic(mnemonic) {
|
||||
const node = HDNodeWallet.fromPhrase(mnemonic, "", BIP44_ETH_BASE);
|
||||
const node = HDNodeWallet.fromPhrase(mnemonic, "", BIP44_ETH_PATH);
|
||||
const xpub = node.neuter().extendedKey;
|
||||
const firstAddress = node.deriveChild(0).address;
|
||||
return { xpub, firstAddress };
|
||||
@@ -39,7 +34,7 @@ function getSignerForAddress(walletData, addrIndex, decryptedSecret) {
|
||||
const node = HDNodeWallet.fromPhrase(
|
||||
decryptedSecret,
|
||||
"",
|
||||
BIP44_ETH_BASE,
|
||||
BIP44_ETH_PATH,
|
||||
);
|
||||
return node.deriveChild(addrIndex);
|
||||
}
|
||||
@@ -51,9 +46,6 @@ function isValidMnemonic(mnemonic) {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
BIP44_ETH_BASE,
|
||||
DEBUG,
|
||||
DEBUG_MNEMONIC,
|
||||
generateMnemonic,
|
||||
deriveAddressFromXpub,
|
||||
hdWalletFromMnemonic,
|
||||
|
||||
Reference in New Issue
Block a user