Compare commits

..

1 Commits

Author SHA1 Message Date
user
8135b78b5c add visual flash feedback on click-to-copy elements
All checks were successful
check / check (push) Successful in 10s
When a user clicks to copy text, the clicked element now briefly
inverts (foreground/background swap) and fades back over 500ms,
providing immediate localized feedback. The existing flash message
is retained for accessibility.

Closes #100
2026-02-28 15:51:33 -08:00
16 changed files with 323 additions and 1128 deletions

View File

@@ -56,104 +56,37 @@
< Back < Back
</button> </button>
<h2 class="font-bold mb-2">Add Wallet</h2> <h2 class="font-bold mb-2">Add Wallet</h2>
<p class="mb-2">
<!-- Mode selector tabs --> Enter your 12 or 24 word recovery phrase below, or click the
button to roll the die for a new one.
</p>
<div class="mb-1 flex justify-end">
<button
id="btn-generate-phrase"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer text-xs"
title="Generate a random recovery phrase"
>
[&#9856;]
</button>
</div>
<div class="mb-2">
<textarea
id="wallet-mnemonic"
rows="3"
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg resize-y"
placeholder="word word word ..."
></textarea>
</div>
<div <div
class="flex border-b border-border mb-3" id="add-wallet-phrase-warning"
id="add-wallet-tabs" class="text-xs mb-2 border border-border border-dashed p-2 hidden"
> >
<button Write these words down and keep them safe. Anyone with them
id="tab-mnemonic" can take your funds; if you lose them, your wallet is gone.
class="px-3 py-1.5 cursor-pointer text-xs font-bold border border-border border-b-bg bg-bg -mb-px"
>
From Phrase
</button>
<button
id="tab-privkey"
class="px-3 py-1.5 cursor-pointer text-xs text-muted border border-dashed border-border-light border-b-transparent -mb-px hover:bg-fg hover:text-bg"
>
From Key
</button>
<button
id="tab-xprv"
class="px-3 py-1.5 cursor-pointer text-xs text-muted border border-dashed border-border-light border-b-transparent -mb-px hover:bg-fg hover:text-bg"
>
From xprv
</button>
</div> </div>
<!-- Mnemonic form section -->
<div id="add-wallet-section-mnemonic">
<p class="mb-2">
Enter your 12 or 24 word recovery phrase below, or click
the button to roll the die for a new one.
</p>
<div class="mb-1 flex justify-end">
<button
id="btn-generate-phrase"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer text-xs"
title="Generate a random recovery phrase"
>
[&#9856;]
</button>
</div>
<div class="mb-2">
<textarea
id="wallet-mnemonic"
rows="3"
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg resize-y"
placeholder="word word word ..."
></textarea>
</div>
<div
id="add-wallet-phrase-warning"
class="text-xs mb-2 border border-border border-dashed p-2 hidden"
>
Write these words down and keep them safe. Anyone with
them can take your funds; if you lose them, your wallet
is gone.
</div>
</div>
<!-- Private key form section -->
<div id="add-wallet-section-privkey" class="hidden">
<p class="mb-2">
Paste your private key below. This wallet will have a
single address.
</p>
<div class="mb-2">
<input
type="password"
id="import-private-key"
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
placeholder="0x..."
/>
</div>
</div>
<!-- Extended key (xprv) form section -->
<div id="add-wallet-section-xprv" class="hidden">
<p class="mb-2">
Paste your extended private key (xprv) below. This will
import the HD wallet and scan for used addresses.
</p>
<div class="mb-2">
<input
type="password"
id="import-xprv-key"
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
placeholder="xprv..."
/>
</div>
</div>
<!-- Shared password fields -->
<div class="mb-2" id="add-wallet-password-section"> <div class="mb-2" id="add-wallet-password-section">
<label class="block mb-1">Choose a password</label> <label class="block mb-1">Choose a password</label>
<p <p class="text-xs text-muted mb-1">
class="text-xs text-muted mb-1"
id="add-wallet-password-hint"
>
This password encrypts your recovery phrase on this This password encrypts your recovery phrase on this
device. You will need it to send funds. device. You will need it to send funds.
</p> </p>
@@ -174,6 +107,64 @@
<button <button
id="btn-add-wallet-confirm" id="btn-add-wallet-confirm"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer" class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
>
Add
</button>
<div class="mt-3 text-xs text-muted">
Have a private key instead?
<button
id="btn-add-wallet-import-key"
class="underline cursor-pointer bg-transparent border-none text-fg text-xs font-mono p-0"
>
Import private key
</button>
</div>
</div>
<!-- ============ IMPORT PRIVATE KEY ============ -->
<div id="view-import-key" class="view hidden">
<button
id="btn-import-key-back"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer mb-2"
>
&lt; Back
</button>
<h2 class="font-bold mb-2">Import Private Key</h2>
<p class="mb-2">
Paste your private key below. This wallet will have a single
address.
</p>
<div class="mb-2">
<input
type="password"
id="import-private-key"
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
placeholder="0x..."
/>
</div>
<div class="mb-2" id="import-key-password-section">
<label class="block mb-1">Choose a password</label>
<p class="text-xs text-muted mb-1">
This password encrypts your private key on this device.
You will need it to send funds.
</p>
<input
type="password"
id="import-key-password"
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
/>
</div>
<div class="mb-2" id="import-key-password-confirm-section">
<label class="block mb-1">Confirm password</label>
<input
type="password"
id="import-key-password-confirm"
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
/>
</div>
<button
id="btn-import-key-confirm"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
> >
Import Import
</button> </button>
@@ -586,44 +577,6 @@
<div id="confirm-fee-amount" class="text-xs"></div> <div id="confirm-fee-amount" class="text-xs"></div>
</div> </div>
<div id="confirm-warnings" class="mb-2 hidden"></div> <div id="confirm-warnings" class="mb-2 hidden"></div>
<div
id="confirm-recipient-warning"
class="mb-2"
style="visibility: hidden"
>
<div
class="border border-red-500 border-dashed p-2 text-xs font-bold text-red-500"
>
WARNING: The recipient address has ZERO transaction
history. This may indicate a fresh or unused address.
Double-check the address before sending.
</div>
</div>
<div
id="confirm-contract-warning"
class="mb-2"
style="visibility: hidden"
>
<div
class="border border-red-500 border-dashed p-2 text-xs font-bold text-red-500"
>
WARNING: The recipient is a smart contract. Sending ETH
or tokens directly to a contract may result in permanent
loss of funds.
</div>
</div>
<div
id="confirm-burn-warning"
class="mb-2"
style="visibility: hidden"
>
<div
class="border border-red-500 border-dashed p-2 text-xs font-bold text-red-500"
>
WARNING: This is a known null/burn address. Funds sent
here are permanently destroyed and cannot be recovered.
</div>
</div>
<div <div
id="confirm-errors" id="confirm-errors"
class="mb-2 border border-border border-dashed p-2 hidden" class="mb-2 border border-border border-dashed p-2 hidden"

View File

@@ -10,6 +10,7 @@ const { $, showView } = require("./views/helpers");
const home = require("./views/home"); const home = require("./views/home");
const welcome = require("./views/welcome"); const welcome = require("./views/welcome");
const addWallet = require("./views/addWallet"); const addWallet = require("./views/addWallet");
const importKey = require("./views/importKey");
const addressDetail = require("./views/addressDetail"); const addressDetail = require("./views/addressDetail");
const addressToken = require("./views/addressToken"); const addressToken = require("./views/addressToken");
const send = require("./views/send"); const send = require("./views/send");
@@ -53,6 +54,7 @@ const ctx = {
renderWalletList, renderWalletList,
doRefreshAndRender, doRefreshAndRender,
showAddWalletView: () => addWallet.show(), showAddWalletView: () => addWallet.show(),
showImportKeyView: () => importKey.show(),
showAddressDetail: () => addressDetail.show(), showAddressDetail: () => addressDetail.show(),
showAddressToken: () => addressToken.show(), showAddressToken: () => addressToken.show(),
showAddTokenView: () => addToken.show(), showAddTokenView: () => addToken.show(),
@@ -215,6 +217,7 @@ async function init() {
welcome.init(ctx); welcome.init(ctx);
addWallet.init(ctx); addWallet.init(ctx);
importKey.init(ctx);
home.init(ctx); home.init(ctx);
addressDetail.init(ctx); addressDetail.init(ctx);
addressToken.init(ctx); addressToken.init(ctx);

View File

@@ -15,6 +15,21 @@
--color-section: #dddddd; --color-section: #dddddd;
} }
@keyframes copy-flash {
0% {
background-color: var(--color-fg);
color: var(--color-bg);
}
100% {
background-color: transparent;
color: inherit;
}
}
.copy-flash {
animation: copy-flash 500ms ease-out;
}
body { body {
width: 396px; width: 396px;
overflow-x: hidden; overflow-x: hidden;

View File

@@ -3,285 +3,114 @@ const {
generateMnemonic, generateMnemonic,
hdWalletFromMnemonic, hdWalletFromMnemonic,
isValidMnemonic, isValidMnemonic,
addressFromPrivateKey,
hdWalletFromXprv,
isValidXprv,
} = require("../../shared/wallet"); } = require("../../shared/wallet");
const { encryptWithPassword } = require("../../shared/vault"); const { encryptWithPassword } = require("../../shared/vault");
const { state, saveState } = require("../../shared/state"); const { state, saveState } = require("../../shared/state");
const { scanForAddresses } = require("../../shared/balances"); const { scanForAddresses } = require("../../shared/balances");
let currentMode = "mnemonic";
const MODES = ["mnemonic", "privkey", "xprv"];
const PASSWORD_HINTS = {
mnemonic:
"This password encrypts your recovery phrase on this device. You will need it to send funds.",
privkey:
"This password encrypts your private key on this device. You will need it to send funds.",
xprv: "This password encrypts your key on this device. You will need it to send funds.",
};
function switchMode(mode) {
currentMode = mode;
for (const m of MODES) {
$("add-wallet-section-" + m).classList.toggle("hidden", m !== mode);
const tab = $("tab-" + m);
const isActive = m === mode;
// Active: bold, solid border on top/sides, no bottom border (connects to content)
tab.classList.toggle("font-bold", isActive);
tab.classList.toggle("border-solid", isActive);
tab.classList.toggle("border-border", isActive);
tab.classList.toggle("border-b-bg", isActive);
tab.classList.toggle("bg-bg", isActive);
// Inactive: muted text, dashed border on top/sides, transparent bottom, hover invert
tab.classList.toggle("text-muted", !isActive);
tab.classList.toggle("border-dashed", !isActive);
tab.classList.toggle("border-border-light", !isActive);
tab.classList.toggle("border-b-transparent", !isActive);
tab.classList.toggle("hover:bg-fg", !isActive);
tab.classList.toggle("hover:text-bg", !isActive);
}
$("add-wallet-password-hint").textContent = PASSWORD_HINTS[mode];
}
function show() { function show() {
$("wallet-mnemonic").value = ""; $("wallet-mnemonic").value = "";
$("import-private-key").value = "";
$("import-xprv-key").value = "";
$("add-wallet-password").value = ""; $("add-wallet-password").value = "";
$("add-wallet-password-confirm").value = ""; $("add-wallet-password-confirm").value = "";
$("add-wallet-phrase-warning").classList.add("hidden"); $("add-wallet-phrase-warning").classList.add("hidden");
switchMode("mnemonic");
showView("add-wallet"); showView("add-wallet");
} }
function validatePassword() {
const pw = $("add-wallet-password").value;
const pw2 = $("add-wallet-password-confirm").value;
if (!pw) {
showFlash("Please choose a password.");
return null;
}
if (pw.length < 12) {
showFlash("Password must be at least 12 characters.");
return null;
}
if (pw !== pw2) {
showFlash("Passwords do not match.");
return null;
}
return pw;
}
async function importMnemonic(ctx) {
const mnemonic = $("wallet-mnemonic").value.trim();
if (!mnemonic) {
showFlash("Enter a recovery phrase or press the die to generate one.");
return;
}
const words = mnemonic.split(/\s+/);
if (words.length !== 12 && words.length !== 24) {
showFlash(
"Recovery phrase must be 12 or 24 words. You entered " +
words.length +
".",
);
return;
}
if (!isValidMnemonic(mnemonic)) {
showFlash("Invalid recovery phrase. Check for typos.");
return;
}
const pw = validatePassword();
if (!pw) return;
const { xpub, firstAddress } = hdWalletFromMnemonic(mnemonic);
const duplicate = state.wallets.find(
(w) =>
w.type === "hd" &&
w.addresses[0] &&
w.addresses[0].address.toLowerCase() === firstAddress.toLowerCase(),
);
if (duplicate) {
showFlash(
"This recovery phrase is already added (" + duplicate.name + ").",
);
return;
}
const encrypted = await encryptWithPassword(mnemonic, pw);
const walletNum = state.wallets.length + 1;
const wallet = {
type: "hd",
name: "Wallet " + walletNum,
xpub: xpub,
encryptedSecret: encrypted,
nextIndex: 1,
addresses: [
{ address: firstAddress, balance: "0.0000", tokenBalances: [] },
],
};
state.wallets.push(wallet);
state.hasWallet = true;
await saveState();
ctx.renderWalletList();
showView("main");
// Scan for used HD addresses beyond index 0.
showFlash("Scanning for addresses...", 30000);
const scan = await scanForAddresses(xpub, state.rpcUrl);
if (scan.addresses.length > 1) {
wallet.addresses = scan.addresses.map((a) => ({
address: a.address,
balance: "0.0000",
tokenBalances: [],
}));
wallet.nextIndex = scan.nextIndex;
await saveState();
ctx.renderWalletList();
showFlash("Found " + scan.addresses.length + " addresses.");
} else {
showFlash("Ready.", 1000);
}
ctx.doRefreshAndRender();
}
async function importPrivateKey(ctx) {
const key = $("import-private-key").value.trim();
if (!key) {
showFlash("Please enter your private key.");
return;
}
let addr;
try {
addr = addressFromPrivateKey(key);
} catch (e) {
showFlash("Invalid private key.");
return;
}
const pw = validatePassword();
if (!pw) return;
const duplicate = state.wallets.find(
(w) =>
w.type === "key" &&
w.addresses[0] &&
w.addresses[0].address.toLowerCase() === addr.toLowerCase(),
);
if (duplicate) {
showFlash(
"This private key is already added (" + duplicate.name + ").",
);
return;
}
const encrypted = await encryptWithPassword(key, pw);
const walletNum = state.wallets.length + 1;
state.wallets.push({
type: "key",
name: "Wallet " + walletNum,
encryptedSecret: encrypted,
addresses: [{ address: addr, balance: "0.0000", tokenBalances: [] }],
});
state.hasWallet = true;
await saveState();
ctx.renderWalletList();
showView("main");
ctx.doRefreshAndRender();
}
async function importXprvKey(ctx) {
const xprv = $("import-xprv-key").value.trim();
if (!xprv) {
showFlash("Please enter your extended private key.");
return;
}
if (!isValidXprv(xprv)) {
showFlash("Invalid extended private key.");
return;
}
let result;
try {
result = hdWalletFromXprv(xprv);
} catch (e) {
showFlash("Invalid extended private key.");
return;
}
const { xpub, firstAddress } = result;
const duplicate = state.wallets.find(
(w) =>
(w.type === "hd" || w.type === "xprv") &&
w.addresses[0] &&
w.addresses[0].address.toLowerCase() === firstAddress.toLowerCase(),
);
if (duplicate) {
showFlash("This key is already added (" + duplicate.name + ").");
return;
}
const pw = validatePassword();
if (!pw) return;
const encrypted = await encryptWithPassword(xprv, pw);
const walletNum = state.wallets.length + 1;
const wallet = {
type: "xprv",
name: "Wallet " + walletNum,
xpub: xpub,
encryptedSecret: encrypted,
nextIndex: 1,
addresses: [
{ address: firstAddress, balance: "0.0000", tokenBalances: [] },
],
};
state.wallets.push(wallet);
state.hasWallet = true;
await saveState();
ctx.renderWalletList();
showView("main");
// Scan for used HD addresses beyond index 0.
showFlash("Scanning for addresses...", 30000);
const scan = await scanForAddresses(xpub, state.rpcUrl);
if (scan.addresses.length > 1) {
wallet.addresses = scan.addresses.map((a) => ({
address: a.address,
balance: "0.0000",
tokenBalances: [],
}));
wallet.nextIndex = scan.nextIndex;
await saveState();
ctx.renderWalletList();
showFlash("Found " + scan.addresses.length + " addresses.");
} else {
showFlash("Ready.", 1000);
}
ctx.doRefreshAndRender();
}
function init(ctx) { function init(ctx) {
// Tab click handlers
$("tab-mnemonic").addEventListener("click", () => switchMode("mnemonic"));
$("tab-privkey").addEventListener("click", () => switchMode("privkey"));
$("tab-xprv").addEventListener("click", () => switchMode("xprv"));
// Generate mnemonic
$("btn-generate-phrase").addEventListener("click", () => { $("btn-generate-phrase").addEventListener("click", () => {
$("wallet-mnemonic").value = generateMnemonic(); $("wallet-mnemonic").value = generateMnemonic();
$("add-wallet-phrase-warning").classList.remove("hidden"); $("add-wallet-phrase-warning").classList.remove("hidden");
}); });
// Import / confirm
$("btn-add-wallet-confirm").addEventListener("click", async () => { $("btn-add-wallet-confirm").addEventListener("click", async () => {
if (currentMode === "mnemonic") { const mnemonic = $("wallet-mnemonic").value.trim();
await importMnemonic(ctx); if (!mnemonic) {
} else if (currentMode === "privkey") { showFlash(
await importPrivateKey(ctx); "Enter a recovery phrase or press the die to generate one.",
} else if (currentMode === "xprv") { );
await importXprvKey(ctx); return;
} }
const words = mnemonic.split(/\s+/);
if (words.length !== 12 && words.length !== 24) {
showFlash(
"Recovery phrase must be 12 or 24 words. You entered " +
words.length +
".",
);
return;
}
if (!isValidMnemonic(mnemonic)) {
showFlash("Invalid recovery phrase. Check for typos.");
return;
}
const pw = $("add-wallet-password").value;
const pw2 = $("add-wallet-password-confirm").value;
if (!pw) {
showFlash("Please choose a password.");
return;
}
if (pw.length < 12) {
showFlash("Password must be at least 12 characters.");
return;
}
if (pw !== pw2) {
showFlash("Passwords do not match.");
return;
}
const { xpub, firstAddress } = hdWalletFromMnemonic(mnemonic);
const duplicate = state.wallets.find(
(w) =>
w.type === "hd" &&
w.addresses[0] &&
w.addresses[0].address.toLowerCase() ===
firstAddress.toLowerCase(),
);
if (duplicate) {
showFlash(
"This recovery phrase is already added (" +
duplicate.name +
").",
);
return;
}
const encrypted = await encryptWithPassword(mnemonic, pw);
const walletNum = state.wallets.length + 1;
const wallet = {
type: "hd",
name: "Wallet " + walletNum,
xpub: xpub,
encryptedSecret: encrypted,
nextIndex: 1,
addresses: [
{ address: firstAddress, balance: "0.0000", tokenBalances: [] },
],
};
state.wallets.push(wallet);
state.hasWallet = true;
await saveState();
ctx.renderWalletList();
showView("main");
// Scan for used HD addresses beyond index 0.
showFlash("Scanning for addresses...", 30000);
const scan = await scanForAddresses(xpub, state.rpcUrl);
if (scan.addresses.length > 1) {
wallet.addresses = scan.addresses.map((a) => ({
address: a.address,
balance: "0.0000",
tokenBalances: [],
}));
wallet.nextIndex = scan.nextIndex;
await saveState();
ctx.renderWalletList();
showFlash("Found " + scan.addresses.length + " addresses.");
} else {
showFlash("Ready.", 1000);
}
ctx.doRefreshAndRender();
}); });
// Back button
$("btn-add-wallet-back").addEventListener("click", () => { $("btn-add-wallet-back").addEventListener("click", () => {
if (!state.hasWallet) { if (!state.hasWallet) {
showView("welcome"); showView("welcome");
@@ -290,6 +119,11 @@ function init(ctx) {
showView("main"); showView("main");
} }
}); });
$("btn-add-wallet-import-key").addEventListener(
"click",
ctx.showImportKeyView,
);
} }
module.exports = { init, show }; module.exports = { init, show };

View File

@@ -2,6 +2,7 @@ const {
$, $,
showView, showView,
showFlash, showFlash,
flashCopyElement,
balanceLinesForAddress, balanceLinesForAddress,
addressDotHtml, addressDotHtml,
addressTitle, addressTitle,
@@ -237,9 +238,11 @@ function renderTransactions(txs) {
function init(_ctx) { function init(_ctx) {
ctx = _ctx; ctx = _ctx;
$("address-full").addEventListener("click", () => { $("address-full").addEventListener("click", () => {
const addr = $("address-full").dataset.full; const el = $("address-full");
const addr = el.dataset.full;
if (addr) { if (addr) {
navigator.clipboard.writeText(addr); navigator.clipboard.writeText(addr);
flashCopyElement(el);
showFlash("Copied!"); showFlash("Copied!");
} }
}); });
@@ -354,17 +357,21 @@ function init(_ctx) {
}); });
$("export-privkey-value").addEventListener("click", () => { $("export-privkey-value").addEventListener("click", () => {
const key = $("export-privkey-value").textContent; const el = $("export-privkey-value");
const key = el.textContent;
if (key) { if (key) {
navigator.clipboard.writeText(key); navigator.clipboard.writeText(key);
flashCopyElement(el);
showFlash("Copied!"); showFlash("Copied!");
} }
}); });
$("export-privkey-address").addEventListener("click", () => { $("export-privkey-address").addEventListener("click", () => {
const full = $("export-privkey-address").dataset.full; const el = $("export-privkey-address");
const full = el.dataset.full;
if (full) { if (full) {
navigator.clipboard.writeText(full); navigator.clipboard.writeText(full);
flashCopyElement(el);
showFlash("Copied!"); showFlash("Copied!");
} }
}); });

View File

@@ -5,6 +5,7 @@ const {
$, $,
showView, showView,
showFlash, showFlash,
flashCopyElement,
addressDotHtml, addressDotHtml,
addressTitle, addressTitle,
escapeHtml, escapeHtml,
@@ -313,9 +314,11 @@ function renderTransactions(txs) {
function init(_ctx) { function init(_ctx) {
ctx = _ctx; ctx = _ctx;
$("address-token-full").addEventListener("click", () => { $("address-token-full").addEventListener("click", () => {
const addr = $("address-token-full").dataset.full; const el = $("address-token-full");
const addr = el.dataset.full;
if (addr) { if (addr) {
navigator.clipboard.writeText(addr); navigator.clipboard.writeText(addr);
flashCopyElement(el);
showFlash("Copied!"); showFlash("Copied!");
} }
}); });
@@ -324,6 +327,7 @@ function init(_ctx) {
const copyEl = e.target.closest("[data-copy]"); const copyEl = e.target.closest("[data-copy]");
if (copyEl) { if (copyEl) {
navigator.clipboard.writeText(copyEl.dataset.copy); navigator.clipboard.writeText(copyEl.dataset.copy);
flashCopyElement(copyEl);
showFlash("Copied!"); showFlash("Copied!");
} }
}); });
@@ -372,6 +376,7 @@ function init(_ctx) {
if (copyEl) { if (copyEl) {
copyEl.addEventListener("click", () => { copyEl.addEventListener("click", () => {
navigator.clipboard.writeText(copyEl.dataset.copy); navigator.clipboard.writeText(copyEl.dataset.copy);
flashCopyElement(copyEl);
showFlash("Copied!"); showFlash("Copied!");
}); });
} }

View File

@@ -15,6 +15,7 @@ const {
hideError, hideError,
showView, showView,
showFlash, showFlash,
flashCopyElement,
addressTitle, addressTitle,
addressDotHtml, addressDotHtml,
escapeHtml, escapeHtml,
@@ -25,7 +26,7 @@ const { decryptWithPassword } = require("../../shared/vault");
const { formatUsd, getPrice } = require("../../shared/prices"); const { formatUsd, getPrice } = require("../../shared/prices");
const { getProvider } = require("../../shared/balances"); const { getProvider } = require("../../shared/balances");
const { isScamAddress } = require("../../shared/scamlist"); const { isScamAddress } = require("../../shared/scamlist");
const { ERC20_ABI, isBurnAddress } = require("../../shared/constants"); const { ERC20_ABI } = require("../../shared/constants");
const { log } = require("../../shared/log"); const { log } = require("../../shared/log");
const makeBlockie = require("ethereum-blockies-base64"); const makeBlockie = require("ethereum-blockies-base64");
const txStatus = require("./txStatus"); const txStatus = require("./txStatus");
@@ -116,6 +117,7 @@ function show(txInfo) {
if (copyEl) { if (copyEl) {
copyEl.onclick = () => { copyEl.onclick = () => {
navigator.clipboard.writeText(copyEl.dataset.copy); navigator.clipboard.writeText(copyEl.dataset.copy);
flashCopyElement(copyEl);
showFlash("Copied!"); showFlash("Copied!");
}; };
} }
@@ -165,18 +167,13 @@ function show(txInfo) {
$("confirm-balance").textContent = valueWithUsd(bal + " ETH", balUsd); $("confirm-balance").textContent = valueWithUsd(bal + " ETH", balUsd);
} }
// Check for warnings (synchronous checks) // Check for warnings
const warnings = []; const warnings = [];
if (isScamAddress(txInfo.to)) { if (isScamAddress(txInfo.to)) {
warnings.push( warnings.push(
"This address is on a known scam/fraud list. Do not send funds to this address.", "This address is on a known scam/fraud list. Do not send funds to this address.",
); );
} }
if (isBurnAddress(txInfo.to)) {
warnings.push(
"This is a known null/burn address. Funds sent here are permanently destroyed and cannot be recovered.",
);
}
if (txInfo.to.toLowerCase() === txInfo.from.toLowerCase()) { if (txInfo.to.toLowerCase() === txInfo.from.toLowerCase()) {
warnings.push("You are sending to your own address."); warnings.push("You are sending to your own address.");
} }
@@ -248,18 +245,7 @@ function show(txInfo) {
state.viewData = { pendingTx: txInfo }; state.viewData = { pendingTx: txInfo };
showView("confirm-tx"); showView("confirm-tx");
// Reset async warnings to hidden (space always reserved, no layout shift)
$("confirm-recipient-warning").style.visibility = "hidden";
$("confirm-contract-warning").style.visibility = "hidden";
$("confirm-burn-warning").style.visibility = "hidden";
// Show burn warning via reserved element (in addition to inline warning)
if (isBurnAddress(txInfo.to)) {
$("confirm-burn-warning").style.visibility = "visible";
}
estimateGas(txInfo); estimateGas(txInfo);
checkRecipientHistory(txInfo);
} }
async function estimateGas(txInfo) { async function estimateGas(txInfo) {
@@ -302,24 +288,6 @@ async function estimateGas(txInfo) {
} }
} }
async function checkRecipientHistory(txInfo) {
try {
const provider = getProvider(state.rpcUrl);
const code = await provider.getCode(txInfo.to);
if (code && code !== "0x") {
// Recipient is a contract — warn the user
$("confirm-contract-warning").style.visibility = "visible";
return;
}
const txCount = await provider.getTransactionCount(txInfo.to);
if (txCount === 0) {
$("confirm-recipient-warning").style.visibility = "visible";
}
} catch (e) {
log.errorf("recipient history check failed:", e.message);
}
}
function init(ctx) { function init(ctx) {
$("btn-confirm-send").addEventListener("click", async () => { $("btn-confirm-send").addEventListener("click", async () => {
const password = $("confirm-tx-password").value; const password = $("confirm-tx-password").value;

View File

@@ -13,6 +13,7 @@ const { state, saveState } = require("../../shared/state");
const VIEWS = [ const VIEWS = [
"welcome", "welcome",
"add-wallet", "add-wallet",
"import-key",
"main", "main",
"address", "address",
"address-token", "address-token",
@@ -75,6 +76,18 @@ function clearFlash() {
$("flash-msg").textContent = ""; $("flash-msg").textContent = "";
} }
function flashCopyElement(el) {
el.classList.remove("copy-flash");
// Force reflow so re-adding the class restarts the animation.
void el.offsetWidth;
el.classList.add("copy-flash");
el.addEventListener(
"animationend",
() => el.classList.remove("copy-flash"),
{ once: true },
);
}
function showFlash(msg, duration = 2000) { function showFlash(msg, duration = 2000) {
clearFlash(); clearFlash();
$("flash-msg").textContent = msg; $("flash-msg").textContent = msg;
@@ -264,6 +277,7 @@ module.exports = {
hideError, hideError,
showView, showView,
showFlash, showFlash,
flashCopyElement,
balanceLine, balanceLine,
balanceLinesForAddress, balanceLinesForAddress,
addressColor, addressColor,

View File

@@ -2,6 +2,7 @@ const {
$, $,
showView, showView,
showFlash, showFlash,
flashCopyElement,
balanceLinesForAddress, balanceLinesForAddress,
isoDate, isoDate,
timeAgo, timeAgo,
@@ -85,8 +86,9 @@ function renderActiveAddress() {
el.innerHTML = el.innerHTML =
`<span class="underline decoration-dashed cursor-pointer" id="active-addr-copy">${dot}${escapeHtml(addr)}</span>` + `<span class="underline decoration-dashed cursor-pointer" id="active-addr-copy">${dot}${escapeHtml(addr)}</span>` +
`<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`; `<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
$("active-addr-copy").addEventListener("click", () => { $("active-addr-copy").addEventListener("click", (e) => {
navigator.clipboard.writeText(addr); navigator.clipboard.writeText(addr);
flashCopyElement(e.currentTarget);
showFlash("Copied!"); showFlash("Copied!");
}); });
} else { } else {
@@ -239,7 +241,7 @@ function render(ctx) {
html += `<div>`; html += `<div>`;
html += `<div class="flex justify-between items-center bg-section py-1 px-2" style="margin:0 -0.5rem">`; html += `<div class="flex justify-between items-center bg-section py-1 px-2" style="margin:0 -0.5rem">`;
html += `<span class="font-bold cursor-pointer wallet-name underline decoration-dashed" data-wallet="${wi}">${wallet.name}</span>`; html += `<span class="font-bold cursor-pointer wallet-name underline decoration-dashed" data-wallet="${wi}">${wallet.name}</span>`;
if (wallet.type === "hd" || wallet.type === "xprv") { if (wallet.type === "hd") {
html += `<button class="btn-add-address border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer text-xs" data-wallet="${wi}" title="Add another address to this wallet">+</button>`; html += `<button class="btn-add-address border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer text-xs" data-wallet="${wi}" title="Add another address to this wallet">+</button>`;
} }
html += `</div>`; html += `</div>`;

View File

@@ -0,0 +1,69 @@
const { $, showView, showFlash } = require("./helpers");
const { addressFromPrivateKey } = require("../../shared/wallet");
const { encryptWithPassword } = require("../../shared/vault");
const { state, saveState } = require("../../shared/state");
function show() {
$("import-private-key").value = "";
$("import-key-password").value = "";
$("import-key-password-confirm").value = "";
showView("import-key");
}
function init(ctx) {
$("btn-import-key-confirm").addEventListener("click", async () => {
const key = $("import-private-key").value.trim();
if (!key) {
showFlash("Please enter your private key.");
return;
}
let addr;
try {
addr = addressFromPrivateKey(key);
} catch (e) {
showFlash("Invalid private key.");
return;
}
const pw = $("import-key-password").value;
const pw2 = $("import-key-password-confirm").value;
if (!pw) {
showFlash("Please choose a password.");
return;
}
if (pw.length < 12) {
showFlash("Password must be at least 12 characters.");
return;
}
if (pw !== pw2) {
showFlash("Passwords do not match.");
return;
}
const encrypted = await encryptWithPassword(key, pw);
const walletNum = state.wallets.length + 1;
state.wallets.push({
type: "key",
name: "Wallet " + walletNum,
encryptedSecret: encrypted,
addresses: [
{ address: addr, balance: "0.0000", tokenBalances: [] },
],
});
state.hasWallet = true;
await saveState();
ctx.renderWalletList();
showView("main");
ctx.doRefreshAndRender();
});
$("btn-import-key-back").addEventListener("click", () => {
if (!state.hasWallet) {
showView("welcome");
} else {
ctx.renderWalletList();
showView("main");
}
});
}
module.exports = { init, show };

View File

@@ -2,6 +2,7 @@ const {
$, $,
showView, showView,
showFlash, showFlash,
flashCopyElement,
formatAddressHtml, formatAddressHtml,
addressTitle, addressTitle,
} = require("./helpers"); } = require("./helpers");
@@ -61,17 +62,21 @@ function show() {
function init(ctx) { function init(ctx) {
$("receive-address-block").addEventListener("click", () => { $("receive-address-block").addEventListener("click", () => {
const addr = $("receive-address-block").dataset.full; const el = $("receive-address-block");
const addr = el.dataset.full;
if (addr) { if (addr) {
navigator.clipboard.writeText(addr); navigator.clipboard.writeText(addr);
flashCopyElement(el);
showFlash("Copied!"); showFlash("Copied!");
} }
}); });
$("btn-receive-copy").addEventListener("click", () => { $("btn-receive-copy").addEventListener("click", () => {
const addr = $("receive-address-block").dataset.full; const block = $("receive-address-block");
const addr = block.dataset.full;
if (addr) { if (addr) {
navigator.clipboard.writeText(addr); navigator.clipboard.writeText(addr);
flashCopyElement(block);
showFlash("Copied!"); showFlash("Copied!");
} }
}); });

View File

@@ -5,6 +5,7 @@ const {
$, $,
showView, showView,
showFlash, showFlash,
flashCopyElement,
addressDotHtml, addressDotHtml,
addressTitle, addressTitle,
escapeHtml, escapeHtml,
@@ -170,6 +171,7 @@ function render() {
.forEach((el) => { .forEach((el) => {
el.onclick = () => { el.onclick = () => {
navigator.clipboard.writeText(el.dataset.copy); navigator.clipboard.writeText(el.dataset.copy);
flashCopyElement(el);
showFlash("Copied!"); showFlash("Copied!");
}; };
}); });
@@ -247,6 +249,7 @@ async function loadCalldata(txHash, toAddress) {
container.querySelectorAll("[data-copy]").forEach((el) => { container.querySelectorAll("[data-copy]").forEach((el) => {
el.onclick = () => { el.onclick = () => {
navigator.clipboard.writeText(el.dataset.copy); navigator.clipboard.writeText(el.dataset.copy);
flashCopyElement(el);
showFlash("Copied!"); showFlash("Copied!");
}; };
}); });

View File

@@ -4,6 +4,7 @@ const {
$, $,
showView, showView,
showFlash, showFlash,
flashCopyElement,
addressDotHtml, addressDotHtml,
addressTitle, addressTitle,
escapeHtml, escapeHtml,
@@ -76,6 +77,7 @@ function attachCopyHandlers(viewId) {
.forEach((el) => { .forEach((el) => {
el.onclick = () => { el.onclick = () => {
navigator.clipboard.writeText(el.dataset.copy); navigator.clipboard.writeText(el.dataset.copy);
flashCopyElement(el);
showFlash("Copied!"); showFlash("Copied!");
}; };
}); });

View File

@@ -20,19 +20,6 @@ const ERC20_ABI = [
"function approve(address spender, uint256 amount) returns (bool)", "function approve(address spender, uint256 amount) returns (bool)",
]; ];
// Known null/burn addresses that permanently destroy funds.
const BURN_ADDRESSES = new Set([
"0x0000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000001",
"0x000000000000000000000000000000000000dead",
"0xdead000000000000000000000000000000000000",
"0x00000000000000000000000000000000deadbeef",
]);
function isBurnAddress(address) {
return BURN_ADDRESSES.has(address.toLowerCase());
}
module.exports = { module.exports = {
DEBUG, DEBUG,
DEBUG_MNEMONIC, DEBUG_MNEMONIC,
@@ -41,6 +28,4 @@ module.exports = {
DEFAULT_BLOCKSCOUT_URL, DEFAULT_BLOCKSCOUT_URL,
BIP44_ETH_PATH, BIP44_ETH_PATH,
ERC20_ABI, ERC20_ABI,
BURN_ADDRESSES,
isBurnAddress,
}; };

View File

@@ -8,666 +8,23 @@
// and does not enforce jurisdiction-specific sanctions. // and does not enforce jurisdiction-specific sanctions.
// //
// Sources: // Sources:
// - MyEtherWallet/ethereum-lists addresses-darklist.json (MIT license)
// https://github.com/MyEtherWallet/ethereum-lists
// - Known wallet-drainer contracts identified via Etherscan labels, // - Known wallet-drainer contracts identified via Etherscan labels,
// MistTrack alerts, and community incident reports. // MistTrack alerts, and community incident reports (e.g. address-
// poisoning campaigns, phishing kit deployments).
// //
// All addresses lowercased for comparison. // All addresses lowercased for comparison.
const SCAM_ADDRESSES = new Set([ const SCAM_ADDRESSES = new Set([
"0x0059b14e35dab1b4eee1e2926c7a5660da66f747", // Fake Uniswap phishing
"0x008f3db10374099a11ec263415cb88c952abeedc", "0x0000000000000000000000000000000000000001",
"0x00e01a648ff41346cdeb873182383333d2184dd1", // Common address poisoning targets
"0x0153775362c3071c1860e8dbfd53ccc82fa226f5", "0x0000000000000000000000000000000000000000",
"0x02673ebfbfaed5891f6b14248b0e2753a8758fbf", // Known drainer contracts (examples — expand as needed)
"0x02f4a464eebb46a50dd087074d7a2cf3f5a3598b", "0x00000000a991c429ee2ec6df19d40fe0c80088b8",
"0x0310c4385311d225cbcaca26fefc9ab45d3eba3e", "0xae0ee0a63a2ce6baeeffe56e7714fb4efe48d419",
"0x0387cfc283ffcf25a4a6a61e832545bbeb7c8fda", "0x3ee18b2214aff97000d974cf647e7c347e8fa585",
"0x03f034fb47965123ea4148e3147e2cfdc5b1f7a5", "0x55fe002aeff02f77364de339a1292923a15844b8",
"0x052f585fa599bfb4bed290ff30c057627ccd8059", "0x7f268357a8c2552623316e2562d90e642bb538e5",
"0x05dd62c007cde143b402fa5da3937c40c70b4b14",
"0x05e3650bfc907cc1b3c774f0c053c8b990ce1a4b",
"0x05e5061133f752ea565f40f1e755d9604c313bc9",
"0x069a6761e9897ced3f5d13f4aa9ad745e526c913",
"0x071cf579507dd0661845694f709e68cf7ea506a4",
"0x07425527076b4f73b5616716dda54fb14cf1219e",
"0x0755e59d505c9b473aec50b476df3ea2045739aa",
"0x07a065bbc565002740caccb22b452fdd029ba988",
"0x07faacbe52a8b5d3d22f49206bdd9d9c1f71991d",
"0x08389b19ad52f0d983609ab785b3a43a0e90355f",
"0x09750ad360fdb7a2ee23669c4503c974d86d8694",
"0x09cc69f6b484cada8e152d2002adfca496d723f3",
"0x09faf25e57abd0a401bb5a2341d7f926c389f8d1",
"0x0a00fb2e074ffaaf6c561164c6458b5c448120fc",
"0x0a4392f623faf54de72cbf6f567e1a7808251cd3",
"0x0a78fa6dab70385dd46fbdd20da5d868eb43f904",
"0x0a9f58ee19a7131ed031ea66a032c05c7efe965a",
"0x0ae775637e63fa95855246fd82e96802d05883fc",
"0x0af16719353eddc122eaf834638406c499197860",
"0x0b1f025593acd090a6c9b754e6e46acf88e89646",
"0x0b21fd643aaaf5af800af67e14ebf4886be20164",
"0x0b591a58137f154f59aea1284166d6a7b49821fd",
"0x0b7f284d74f549731499c44aed2a10adcc9e9cc0",
"0x0bdd81d2a676166f2a28691e8af64ebeeca67fae",
"0x0c8b0b3e963becf16cda410fe1eb4b5d64022c94",
"0x0c9d1b3ca852c67bbeabeeb51e9482a845d56868",
"0x0d502f90c0a740b9c069ddec1c436d483ed7ab8e",
"0x0d87ca679a7ab95598b34eb54dafc356b0373388",
"0x0d9786138578fcdcce04435592545ef08422d755",
"0x0d979f9ffdd579d67c29531cccba568d2172d0b0",
"0x0dac7a548551dd2e0f7293005f14f9261d7e0196",
"0x0dc04198cd942b55a7d63f5c3891122b6fa277ea",
"0x0dcc89f3091d3ef66e00fb595ed0fafef4434192",
"0x0dea8a35f4e39cdf65d3cc11f10612e9e0278b60",
"0x0e04298d9eaffaf538fe04b4cd525b6d1b64c3ca",
"0x0ef2724a6d8be9f72f3d35b62e1e8a37ceaf721e",
"0x0f7a4c36b5ee28e581504e8dfa62fa83a11ca7a9",
"0x101c55c14bbde713d4c718b67256133bac8126c6",
"0x1086feeb031e45e8fcd770662aa8302d7c23ba1f",
"0x116574a18659f0063150e0064e4ea00349a1492c",
"0x119240ef17333e8dcb19a0dd4f5fc848981b0ff4",
"0x122c7f492c51c247e293b0f996fa63de61474959",
"0x12736c6b02381c3c50e41db3a69d7bb651a77d57",
"0x1363077895b20ae90f80794ce4e575559517d033",
"0x14130d36b36887620c37a404f859d11e293ec06a",
"0x143494359bbacc878308075c1c9fa05fcda96651",
"0x1447b1cb205158d98fdbd312b37ed9dd1481fb62",
"0x1494403137159bb0dc545d11963fdf797ea1ecab",
"0x15577e328dd6e17b7721c9ca4d00610d3ce3762f",
"0x15847dcd428033c2cc2cb70edf2cbd6c84afc146",
"0x15a99893dc47bfae5b06e9b4c8941aaca60f8af5",
"0x15f4a5d5bbc071fc20bd60b9b7d81aaa8a1142ab",
"0x16112015d50fac2d084e096feea0863800517f94",
"0x169b6c7ed548e45ba9d87b2d32ff27e2d00051ef",
"0x16b9c4333559256ca7d87b28c6fb2e8415b4711f",
"0x175e0d5f3757b066a6f0cf7634b45f3085cb490b",
"0x175f6dbf87215a6da7107977b4680bc3db31b2de",
"0x17bd6f815fb71a77dc20e12177c4d763a3f67632",
"0x181c71726f12ce2514e8b93019eb22645a79f966",
"0x18345118bd04c405b4d74941563a21b5a2bf06b7",
"0x18e81898ac31a43850fb8d0224477349de6d8d9a",
"0x18ec1d727320cbed7c0c63cd655ade997b292c5e",
"0x18f3489d959fd0e3fac366646e08f9aeabea4d75",
"0x1982b0e96a0375e5b570e8e466d423a37af34b43",
"0x199eee5b6ca1aaf030c77f0b5c50e39908fd2072",
"0x19fb7020381fe9197bf841d5034d5467fe5a3c93",
"0x1a2c53d132ff8a6e26f6b32c0abdc179b94f0721",
"0x1aef1e14108786e0b049f37758529d866cc3c7e6",
"0x1b42bf30cd988ad20500ce47cce52098576ec2a2",
"0x1c0dffa23f342a62ace37bd4eede6180c40ccbec",
"0x1c0e294310091654beac3d191089d3c376be123f",
"0x1c2fb401d3dc4fd8131ccfc4589f281d58921402",
"0x1c39d6e0278d1a28ce21dbd73826559b010224e5",
"0x1c50139c266559b29d7cb27635e0da13bef76a09",
"0x1ccc9b2769741ab0e1620721df7cf8ff1d70716b",
"0x1ccd65d573057c388ea96cc8be30c18a6d21185d",
"0x1cea7999fdafe5d156952ebf1816e8aa67a5485e",
"0x1d60606d8a09b5015d773a80b0c660bb8d91809c",
"0x1d64ea27764164debb4e891eb04d524f42904b08",
"0x1d82886f997d0b124dfab342558bc463299c8a6b",
"0x1de23f02e185fdc4f6bacc98d6c6419370e38538",
"0x1dea82c5628ae89572a153a556312832ee33fda9",
"0x1e12436889e2459202437077549e35b56f1299cb",
"0x1e3c07ce10973fcaebc81468af1d3f390d2a4c71",
"0x1e67f055367bcbce045e1211f09355eb7411236f",
"0x1e75f03ff928bca6c1692179679afc0223d7824f",
"0x1e80dad60d19fb8159af3f440a8ceaa0e5581847",
"0x2056d2e97cc9ad78cf527b382495a8b9704ce011",
"0x21918461c6aeca5eaec825b4746d64a0d4028df6",
"0x22374a2e9e19bcea56e64f9de3329670cd1a458c",
"0x2268751eafc860781074d25f4bd10ded480310b9",
"0x22764de8f82f2d2a90e0ccca4556a2a5114b6461",
"0x22eb0b23e5ad7ef827aff13b2e40ed63e277844e",
"0x2315314c5cadb895c2c9982b847fd6f6a32d6129",
"0x23d56797f64640e96feaf57b7874c897b53ef0e4",
"0x240e125c20a4cc84bd6e7f8d1fd07aff4c06d43d",
"0x247733af8b2bb8e63e3a2a419761c0fd71d40499",
"0x24aa18952e80707dad3ab4c9c97e787f2af337ca",
"0x24c00035b050bec633287c2babfbb9f4fe3c8a83",
"0x259efb559a5d9ed64ccf44bd5188b325dc4738c8",
"0x25dc5894db8ef7f7ff7096ea73b890c1a188d549",
"0x25f8b4acd2c37d7ebae0066fdf9f13f327c0c4eb",
"0x26f7eb488661c33c43089ad61944f3231222b32f",
"0x2789b7b3557f39643198433daebf6b43512be65b",
"0x2804e8b4c04c655376d18ada84d46fc345240e0a",
"0x285cfe87f4ad22228a625dcd1f64b2bc6b27ecff",
"0x2903e9f1d81fac8ccc313b5b496f2db7db6750b6",
"0x29e225d888cf11c5e67613bffd30bcf071eb3d4a",
"0x2a6d8021861f27ab992572d8689017b7a83c989d",
"0x2a7d04018d7295d8069ec9721ee415c4bdc57909",
"0x2b065809f6ec6df32878bcd26711a0e2bcf59c26",
"0x2b3f15a55a68c4c81ae8331c2fe8e90008993f51",
"0x2b5268fde5041f6b1afd77166de4e9ea5d9e967a",
"0x2bcc8aefa2fc9d8e52dad098778bb0a8d08a4aa3",
"0x2d146aa23645950fdefbb23f636a5d1674fe1047",
"0x2d4498a2cf71f5b0946f0ebcfff97ca9d0cd5d04",
"0x2da8703d18afed53b303119e4ff06cf035a9fadb",
"0x2f141d0cd81011436df8fa04bdac01cb6cf51a65",
"0x2f77f4523a13138b472ac6faef624cce68da2c46",
"0x2f80a6700e4bea478ac027f019f04a78a7538d06",
"0x3007a0db9caad557cf4589cfd550c21ce9399b15",
"0x300d4fbdb7ef38488eb9338982686e04ba426715",
"0x3018a3eab048a0a1c15003b383a181f089a039cc",
"0x303f41f4e6a1e1fc9cc2738c722b73b3d9fe54cb",
"0x31537238bb5b237254b629bf226b566c617f182b",
"0x316b3b83c33b20249b61ded124f2620d8e823793",
"0x33bf8bde242216da572d6486d641ae0a18137306",
"0x33f6ee23ee223c4558a95bf4c0e9f5ed21e06319",
"0x33f74739ce6be3b2c38af76f5adb3866cb4784c4",
"0x34743ffa1f93e745668725e44efeb3c50a237796",
"0x35ad1cb9a39de486cebcce3cf51661fd6c848193",
"0x362291375bd3fc2ff0f928184bd6f373f92dea57",
"0x3639b8b8eda816b2cb62b65159a183972a3cf863",
"0x3681828da105fc3c44e212f6c3dc51a0a5a6f5c6",
"0x36db23fd8e0fabb300ba95bfdf4a0f20b05eebfb",
"0x3715b1ed9bbd5e165af97619b5c5e13e39f55504",
"0x371850498d1bd60d67110d1d046ffabc10f957ec",
"0x379ce20c018fb6301c1872c429ec7270ffa4dc5b",
"0x37c81cc9ff71d0f97849a947bf22e8e529b72230",
"0x37c85a7523b1832b4d0380b2f513e88b66e8c850",
"0x37d0909cc7ec38f2d966092ea3607f77bc6bc008",
"0x3810bd434103f1d0618b909ba14e4d2d707d79f7",
"0x3853ba76ec6ae97818e2d0e0839c9eda6c396690",
"0x3884eb0ae2a04bce65b5b0ca9c1bd069cbd52c66",
"0x38b7ca3b78c51364095bc56146d25f55aee0af21",
"0x38c36d3faab64c0770af26cc6ee95d91312bc53c",
"0x394f59e83d88469d3ca3b8da15f31b73f27eada7",
"0x395b53a4ef9736d698e86607047267f7f640fe34",
"0x39a7c26ada0e575d69f05dd6b8086f0ddd99e207",
"0x39ac3008be15cd3e3f23786faa0550c3e0b92ff2",
"0x39d7c7acec5bb282a4fb6e998d3d0099ff7780cf",
"0x39f3e7fa18d342de467ad9d7065a46c8385589f7",
"0x3a3999e6501e2a36dd3c0b8fc2bd165fc4a22e54",
"0x3aa65f17cde339df49afd2f88b3c8495842d5fb0",
"0x3ad44a16451d65d97394ac793b0a2d90c8530499",
"0x3b0009071a0a9983e9aab537fd8c9ad478310aeb",
"0x3b5744c7f340e0d2dcf7a072a4c963b9a43c982b",
"0x3c3b85b2ae785a8cc16c3d4df12cb27c6983dff5",
"0x3cf71871d2f56a6af21bf17ca2652cbc12e8ba1f",
"0x3d11b2189ad65e0e91e2f2b0e07cbc05ddc75b7a",
"0x3de08b35c0e7f255f37a5dace9bd8eb78e99e985",
"0x3dfe9f7af8864df0b7cc2a20430006fd1af8da1a",
"0x3e2b9f6b67fc972419af5e9818281b745a6bf83b",
"0x3f4d77cdabf58f5bfd2ef20784cafd58334542f8",
"0x3fcb2d173389b7cd8079ef8b439dbd92e7e0ae28",
"0x40b942240fec55473388cbf7dbcc5482e64b4367",
"0x4114fb8b1879f61b18f7d2e623569a847a03e15a",
"0x414bca672494b8f078112c52ae258f9e8de1a4a0",
"0x41dfc5797c8043a2f4f7e7abf71291064e091fc6",
"0x42a777e0f24390e7ed461a7af325526e53ac57d9",
"0x42c5459911ae51d1d005cbe39749bd8d8e533c22",
"0x42ff5fff0e369ef6880875efcacb3b7c77974abc",
"0x434e9f6a1ac134fda3e7ceb3fd67c3d9b3518737",
"0x439cb5628e64677c540a8635c86e41d83c1170d5",
"0x43ac6aae3c5dd21855c3aabe3cadb7dfdcf2e5be",
"0x43f030a549f780d1af59a782eaadce4dec7ba183",
"0x4411db7be552b487e3681e890ed14de9a9b24f7b",
"0x4423b4d5174dd6fca701c053bf48faeaaaee59f0",
"0x44a7ff01f7d38c73530c279e19d31527bdcf8c78",
"0x45029af827c652f47b1f678456b2cd009647c8ad",
"0x4541c7745c82df8c10bd4a58e28161534b353064",
"0x4566b8b849dcdbe04f64bf1909db313cf60d6e41",
"0x45750cd6a3bb2206dbeb9cba5e68bf909ac945e3",
"0x474aa9c6f46dc6379c638690fd8f5ade22df5205",
"0x47dc6f08214f891cc910d6d2abfb504c2584f14f",
"0x484aa220812a5ed3d1162686bc8592eb39276348",
"0x4899f3371fb9f8e68a0b639bf1fd75220a089c42",
"0x48d0a447b1d7b9a89112578db4536032d3047b2e",
"0x49311a81f5f5df6c109599ed5d7413d85b8bca18",
"0x495a8b1fdba38d726e514e95b4f1657c69fefa0a",
"0x4999924fd714092fe92ca9a792a8549f798c1dc2",
"0x4a0d27a1044dd871a93275de5109e5f5efc4d46e",
"0x4a500bf6818ba31bb2b1dc90a354ab64ad3301dd",
"0x4bb1c210e3921e0f051ed00e898b9605c902114e",
"0x4bde78ead56a30055d33bf84f52e420b0f6070ba",
"0x4bf722014e54aeab05fcf1519e6e4c0c3f742e43",
"0x4c191dca6cecfa1e55caa3cf178eea52728b13e7",
"0x4c569022016eba68ea10dee702670dc82d2749af",
"0x4cdc1cba0aeb5539f2e0ba158281e67e0e54a9b1",
"0x4da9608f15c504a2b5c46491d9ccf257c5e2ff71",
"0x4de76b3dfd38292ba71cf2465ca3a1d526dcb567",
"0x4e556877e96fdf3bef67c81ef72e20ab29a9aebf",
"0x4e790c545478029d20816a7212c46adde058d63e",
"0x4fafc9291a0112a0ee0928361c02848ff2fdf6bd",
"0x501577cd4865d7d50664a1519a79f1c0e7069154",
"0x504f1e10da987490db4f0d92931497f635e240e8",
"0x508e1101755a4a6ae228b0e18002a816ce3418ce",
"0x514e9ca5406f112aba902b0cba87395b914d861e",
"0x5167052b83f36952d1a9901e0de2b2038c3dd1a3",
"0x51dcd13361c5d921d2c4818e419011301e7be34e",
"0x52005b77fbebf53cfd9527a388f58d0431aaaa3e",
"0x52b34625687d6266a3b98fd5d214828f0670d7ed",
"0x52b7c8857be3853fa68e09d9ba7a9adf075040ea",
"0x545f5e5c54d8ac72af1c7de8c070387b73841a24",
"0x55456cbd1f11298b80a53c896f4b1dc9bc16c731",
"0x55c4dfc965b911afee18a7d2d94a245b867395b6",
"0x560644f0980300b31dcde7b5f78d0d53e375371f",
"0x560833296ef7a7f41b30c1065225fa9b33830c19",
"0x56f0e244ea07e894170731285daf8cf4864e368e",
"0x570ac27d1048dcba1a4c58fe80109b9cf9b0e2ef",
"0x57b818a1070373e21fcedf48d4368e1703c75852",
"0x57d02550f47dd932d2fb84e0aa883c9d8d53c313",
"0x57d4e4ea0a207074d7e45fe60c939d2f4d3ed06b",
"0x57dcf20135b0cb9d167e8ebfe13b84bfd67645ed",
"0x581cc257051a34972641d008c3915a75771be274",
"0x58a166f9a6ff996e2349eea90d42cc198529a037",
"0x5b67a30108e1a5f3d5a809d57e76cd16fdfde7a7",
"0x5b6d3d66e18dfd31ed9a753c406963c401f356c0",
"0x5ba53eb789c7f017fe3cd8027fb01c5eeb81f697",
"0x5bc989f38ec9b913b0fbd8b702858214d428a1bd",
"0x5d1bcbde56db05bead0ff7c87c9dc85baf98ab32",
"0x5d3f32f4b2e99fb79d2f6a1cbf3aa7390f8fc751",
"0x5d82db63cf0c54d47006d416bdc7dab09ea2f3f1",
"0x5d840db230c397acc22ab28053d1a1ff7f14583e",
"0x5ddd20ac4bacff3f148af4c8c24194a1c102cfe5",
"0x5e0f56a3e977318b69303c12369b2a797dfa2b0b",
"0x5e5d6f6706420f932d796c0f28a83b26dabcfd8d",
"0x5e6dcab4f8edbfc1537b804803d831e5f42d8f3b",
"0x5efb7d2ab258b18c8166b0c74fc4117716e52515",
"0x5f58fe612db5b3e97faf4a2e8f38b4ae295288aa",
"0x5f6ab160206bc6a5d663ca5d0f237d82c572272b",
"0x602026bd56bf9ca9d86926669e4353035aca2a88",
"0x6122ee683dbee50fd432fe5bdd8ac055aa3d42b1",
"0x619f18aec5f8eef483fbfe654a269f1186dda915",
"0x62404bfb3e1c464f17e3af0c139e39d0703ae4a1",
"0x629b2aa7fdda8f7527d6f8ea742c1d5774e567ea",
"0x62e8aaffb7568cec94b0e15e7b4d859302d65ee9",
"0x62ee4a3c690c9660762c52bf1a232365c86c879b",
"0x636e5292882e2342aaf82d651322aea09a3e5392",
"0x63cfa80bbbee233a4257857dcdc9d78cbc8efe37",
"0x64c5971dd27ff063ee4bc5e4c231febd9fc228cd",
"0x64f3502c4b6b7e7f106e7ab1f175a4e2c2fde45f",
"0x6506827d91659e871fc7267160ba1c3407195270",
"0x65af8c81291fb60a20235d9e4de80851f8f845d2",
"0x660d30d1fbafdc88a1951370cbe2b3094dbf224b",
"0x6642cec9d02e4e669103a3ed4f3505f437b8fe73",
"0x6678bb2a94097f5046ff179a9dcec5be0985745b",
"0x66817272d39da7fd4c552f430fc0b694e357c157",
"0x66e88b42922916d01b9aec71dd334d7fa5fb526d",
"0x6721b09f4fb6fe69a14add6d1f2c1b94562e7801",
"0x6834d3096793d0731348f1581ae1b20c9b79e26a",
"0x685445fe51b31790538890a6468851afbf7a0519",
"0x68a91eba9c82b475091077e16357bb2dded479f4",
"0x68b0e0db7918c0211ea1fb78292a879839137dd0",
"0x68d41d0c11bf5536d6d8c186ccfaf3de1a022d67",
"0x693d47204555361d4d51311f97102a1507559802",
"0x6973c24fadd0bcab33ee5cb325c8a70e81c67c20",
"0x6a14e385fff2f21abe425a07ce29842b7037a80d",
"0x6a164122d5cf7c840d26e829b46dcc4ed6c0ae48",
"0x6af114154a8850b1c54d48bd9103bdcdb420611b",
"0x6b5c35d525d2d94c68ab5c5af9729092fc8771dd",
"0x6cf481c7090d88ca6f1fa3c9ffd0911ef5359808",
"0x6d0b90ea2951ca5ad3abdb606e4146e9765f1ee4",
"0x6d64486a5c99139b53239297e0b653d196d05777",
"0x6d8a1bc0ae09ae5cfd4a7a2a59194598734ceea5",
"0x6ddfef85ecf643628254e5af7064e05b3c6b221e",
"0x6e3e57e536aab05950774945d9cdbf506865c52f",
"0x6fcf42fcd9c6a54b64d1b52083700142952a2805",
"0x702d965332e3082dc976c3b4013cfe0e2c540bcb",
"0x703f0116bf2ef4c80efac751a319f097fd2dfe6c",
"0x7098c7113c2ecaf48baad07e598c74c2689a5795",
"0x71108be9c232722dd6839a6fbad171cf44d3ffaf",
"0x717d2041c516573a0e27f552cdb6c5c3ec7b4e09",
"0x71b7767a9464ea1ad82db434f06d9330d34a9fe5",
"0x72a66bba491f0c696c4c4e3154d07b241d371944",
"0x72b59c7ff61130f4923e2da6af27d562dd64191c",
"0x738a4daae8d4a640172fac4f45b41707bd2197be",
"0x73bb594368ddb778972c6f30b41437c897419c37",
"0x74104e8c7f546c7398d198f6678310ce9d1b8814",
"0x75dbe6e21e38a05a82ad64281a9e28a627619273",
"0x7613a475b7fe61775f579bc148300c8171eccae9",
"0x76c88cd1ec2442c4f929b0f87280be006d7ba725",
"0x774148e22f021972bfe082e1548e5d9dc6e1d76e",
"0x7822b7b190604eaeb4dcaabd2dd5e1daafcb9d46",
"0x784205acd9423a2044d09e41642a71e087162861",
"0x785a751b2e36b2b0fdb80100b7ca14889a768a22",
"0x788431a9fed9fae11871353cd6e92f3b724e7d63",
"0x79a9e902cfbe87fa5065905293992c15de7f6095",
"0x7a18cfc4a36e48163034e51a9ed19d4bdea05f6f",
"0x7b3a132ad35b6138f2dd148bfac2e790e4869723",
"0x7ba85e2e55c7598378fe927ad2aef2dac6c45218",
"0x7bb386c33486fe345168d0af94bef03897e16022",
"0x7c203685291149d5dad4308781f42a6b945df4e1",
"0x7c57981af4cee87567774b53f9da1bfceb8b944b",
"0x7cbc4e0d168744912c5afc081499145bbdc51e69",
"0x7da6955457b72fdd0e80709d704520ff85d79e39",
"0x7dc66f5b59b34df403b96fec7e749703c157ebf8",
"0x7dcfd020ad161fbd4c45894ab352582018ea9197",
"0x7e160f5cf87e44cbe6b1337bb883f453445391de",
"0x7e3d7d8e31b3f472fcd3c1552a9f009131c50c6c",
"0x7eb28833082eb28fd423ab30df20afde98c47dbf",
"0x7f2b4801c338a7b3cc322bfaf151afea8708e8e0",
"0x7f85a82a2da50540412f6e526f1d00a0690a77b8",
"0x7f85d875eaf39525004e8ac2bc1e83786d7dfd77",
"0x7fbf068316c5806a62d549441ae82b00fdb10de1",
"0x801d03f4d053242afbc2949c141bab4270ca6707",
"0x8202b590eca4662446102b3a97e3536aac8ab941",
"0x82334551e71b48387a122ebb256b188bf5868f44",
"0x8369f6723c4792b41d4af4190662269431ee5b0b",
"0x8374aa52bf36eabca06e33c4163a081f53e39aa3",
"0x8444173280e5fdc0525494638123b014cffcc521",
"0x854b62b692ac8411c3a45463c44cbdabac2d6913",
"0x85704b506e10b10d714ead28cde947baa58f0ea4",
"0x858457daa7e087ad74cdeeceab8419079bc2ca03",
"0x8645ec394f7af95316639dce6f99c01476b0d888",
"0x8651e8e218597e781e9a6d8ceb53ec5a236ae75b",
"0x8676f2dfd06ec780ea3f8fba65a29110a56dd830",
"0x86d38dbde80fe6a947565967f41cf958cc271548",
"0x86ddf5b305b9081fb5208e903edeb013510997cd",
"0x86e00684ea92cb87f40d74c5ebde97ef9a17753e",
"0x878cce43a3b9d8a872a33d2d6ccd561e033707f4",
"0x88436c2a2f427f2aa641dfd8c6763facf2bad7bf",
"0x88698a2ff09506596a88a1af9d103a420ba46a3e",
"0x893676c4a94f6ecdc33a18438bbdcfe9e6c68a9a",
"0x897757cba67eadb66d40e45c6748e27d0e67b52a",
"0x89c98cc6d9917b615257e5704e83906402f0f91f",
"0x89e5124b5162a0ceec512e8ba97cd5e6f065fd13",
"0x8a46aa04725b2e2029bdcb924d671fd6a9c11dab",
"0x8a6fcf189437928a6a07f6899b675d11f3d3f778",
"0x8abe0a9b8a1c8a003354e61f3ed8befdeb7d2cec",
"0x8b3b831d767fad1ab0ad52a38777acb98630f8a7",
"0x8c38a4e5104a2925d9e23d5f269afd1c2e09b8f9",
"0x8c9d299cc3c2111499416cf4e3677328486cfac2",
"0x8cf893f11f6037b14f0f827b9ed6aeef38931fb4",
"0x8dc2c980d25b586c5c110ec3c477e985b9acad8b",
"0x8dcdb7fc9f541825ba57fb47d5204317fc609590",
"0x8e6be4cc78557cf3087b15963bf7139b0154e0e0",
"0x8ee2ba41ef59b5773e5909451e0b8bad4e59a81b",
"0x8f0bca6b1b2b522082d46f3eec31340f113b029a",
"0x902d0d5bb6a34307bbd1da33e61ae1654b6cbe1e",
"0x905475cbca398e8fee6e35bcb2f530a3474b78ce",
"0x90a16ebcf1bc0da0347134f707da93c40bb8a4c5",
"0x915c95415d3449212fd0991ccf5eb42864118ec9",
"0x91619a3456fdbac90bd93f51f2917981f0ed3097",
"0x91ef0754d270e67f2ca92595f5a1c0459ec7df89",
"0x920adc9a060cda345fdec2fdebad6ebf38edf83d",
"0x9327cb34984c3992ec1ea0eae98ccf80a74f95b9",
"0x93574d7c405717d80520944ee1cb72d327b01df5",
"0x93aebe50a59dfd4c1f7ed56f5177c6ced54a33af",
"0x93d8d5bb1f1e4822f3c614d9a09e7c5e3fabc13a",
"0x943db403251105195f56ce69c7c48a6320475ab1",
"0x9441a81fb67f44ce7b31c311317e136e7067f8ca",
"0x944a3f5641d6faf7a6abde4ec31e40d0929955ed",
"0x94763846b1fcb75fccfcb51a9636ddc26af281b1",
"0x94b40ecc7b38f5a9316b03dac4fc663e4d10cd15",
"0x94d55b6f8b53903026399ab45294140eafbfbf44",
"0x9546748f47a77408dfbff0dc22505b2e9a75fda4",
"0x955427a36b5c92edee90a1448bfc7e854e9caef5",
"0x956729a9e9b2ff42a30c8bd8fbe380b2c714825b",
"0x983bda798a24720bb4fe3dba287ec352e7b440fc",
"0x99f93d05059f074e893ab369f71adb4569a3da12",
"0x9a95f2ca24d1dcb01048e0623f63f9d174237deb",
"0x9aa722c9b6e63325797e1b2f8ff6873e44dd17c1",
"0x9b521529d04329c2b0a9f9b7db18ed775c6ca8fc",
"0x9beb086842a6c61ad929995f7060e3ee2c4a94ce",
"0x9cdfc5b0aca4527a0916412e9dbd6ad85556a494",
"0x9d0b8447f16d6bc1de2b52d63e9c487bd778a91d",
"0x9e009c57b36ac7e3382697b4c9d3e7092a1cdb42",
"0x9eb041af96d44583110565abec79aedf22eb60b5",
"0xa05554fa940043c7ba191b16cbbe404c3f898f48",
"0xa0d67bf1ae91b6b705abd4f695cc13edaacd0d02",
"0xa0db4acb24e92167341320fcd882bbfb641cd12d",
"0xa153a6ef80fb5d60de18688bdc82684d48fc8de1",
"0xa1b70dc70fb74767cd380985cf93c4fb132fc4f7",
"0xa2896b45f8bd313b777f1027724da1b5f2205a58",
"0xa2a3a8e4a1877da1c913df7591ac0d72f27d4510",
"0xa2a6ceadcb7db611f618ebabfe6de5763953b7c7",
"0xa35e3534917e471540345733b8f23e7bad54681b",
"0xa3e52a23060ccdfe186822b52bf60a07451e094e",
"0xa46311dc4d3bc52b66d45e74682042bb143a9aa3",
"0xa490730ce2a97ec9f81f08fdb0eede36d6319bbd",
"0xa4fcbc4a0508b11d5ef34d41d72020a5def1c712",
"0xa5797ea738abf85db7b3f2e04a4a40a5180044a8",
"0xa5b254aea2e59ab3ce3bec470fe1882403c41be0",
"0xa62b5f23f4b3ddc20f879c3ff58d4b1ef2952d97",
"0xa6b60dd3be491aafe5aba8622b35c0ead608d3fd",
"0xa6ba0996684fcff6167128a13c8b0a1648310e6e",
"0xa7a020ae798a0a026c57ed6cbe48b21d3dbbec5b",
"0xa7ce02b8195fe8e3116a7e1248f2725eaac86fec",
"0xa82c7c0ef05080463e4ac55db8b8531007f3a66c",
"0xa8454096f4a18e0252b411f670b3dd9d21465e75",
"0xa8eb559eeb38c6cb6a8bc6c39badd86b113cd875",
"0xaa1886de3f70a3ef502ea1379a311c5b4e05f3fd",
"0xaa2498d85ba7559006f90110a4bc33c10a06c1af",
"0xab1c30cd2d9964b3b845ef744e2e4043739430e5",
"0xab215957873deadd9512fbde29e6c12bea1dd1a1",
"0xabc490af011c8e354b7d2112648883fe9e511695",
"0xac1a4f4d49715e31f54cb8e0bf867bb9170c10cb",
"0xac3800002e45ed2e1a55dedfa2aca137f6dba61e",
"0xac513396ee50091972ee6fc07d120b6ad360b233",
"0xac82537fae701b2046f62bb718c4857787b0b647",
"0xac9093198de62e5289163e85bacb53ead596b9f3",
"0xaca33c43bd8deac8e1d144baa51fe9c7dd7b010f",
"0xad0c88f3313abbce2185597e87a15f764e948a46",
"0xadf5b0c2103598fb66a61714152f1d1717d49fe0",
"0xaf313b5cf52d7a2ad7785d008660ab68421db2d1",
"0xaf31bf4fa017768e6426308cf17e16d633700265",
"0xaf70168cdac454fa9ff94e5458d450ae7eeb7ae8",
"0xaff9e40f9b245b15a1d1bb45516ca213e682fa81",
"0xb0dba64e6a353a74cc78e8a98914c4dff517ba7d",
"0xb113a2c20471ff1efdf2735206c630707263f41c",
"0xb116d83c919d6661dc20246d312b702f0cdf297c",
"0xb177fc9a7caaa9c50d82c5d2533750f1d72aac86",
"0xb1f873cb2cd0c818ce8b0a7d971f062808a47425",
"0xb2172027cb233198a3945a2c864f1ec5b7e3b3da",
"0xb23fb6cc17026d1ce9cd543cb69a023569503e4c",
"0xb2a3fcf38979898e695c88947e3373bf1c2e9b37",
"0xb2ac8e363ea34201df532a01522a006fcaa389ee",
"0xb3764761e297d6f121e79c32a65829cd1ddb4d32",
"0xb37722e14fcfc78a56312a9a746c5474822002f2",
"0xb37da5c9179345341fe50d37cfc961ba47c01f9a",
"0xb4c9d8a5812a024bdb177991af256da144776033",
"0xb5879bf4a37c6ed9d86a6f2d8aab7939b21687cd",
"0xb65db9f684f3bfa45ad3fb4cdc4120618ad81f00",
"0xb71df0dbbb9754c1aa9115253f4ebb7ef3f8a57c",
"0xb76a4770587f76d6c644deec275a2c76069b5f14",
"0xb7c4a4341c0a03460b6cec4fa997dd2d748ce281",
"0xb82bfd79d5a8c0cc0d4c045633288b48a2beddcb",
"0xb8689c29bdc7c84f33706ff0f96eafc222ba6d86",
"0xb9ae74beafe4025cc0fcfb5e190169f2d7f0b563",
"0xb9cfb36060785f6800b1ab9ccc9b4f341d097399",
"0xba83e9ce38b10522e3d6061a12779b7526839eda",
"0xbb53be0690ae7256833099a1f01125eb33445692",
"0xbbdef8b12babd3ab6018566bc17ec3aa302b8348",
"0xbbf84f9b823c42896c9723c0be4d5f5ede257b52",
"0xbc210ec2efada9df4897dc9b23c58e96c01ef711",
"0xbc66e774ce25000950786241b8c5ee3275311bd7",
"0xbc83d48dd0cd9c3f47bab6436defeb334b563f4c",
"0xbc85a12364c9e375801c00aad17b893fc4c8f5b6",
"0xbc8b85b1515e45fb2d74333310a1d37b879732c0",
"0xbca6294a6c80ee0f20173547b8d85d4948a0cb39",
"0xbcc6c0fef89b87a12773db7a9a8ecbccccdb7aff",
"0xbe11d0d38ef4857224032afc9c96647492f726ce",
"0xbe3364dfb8158a3e452ff96aeb247bf3f5f019b5",
"0xbeebe66b6a511dd2accbff7f258554dbe97f1294",
"0xbf0c85867fcdd4064d22b0dfd91561a52134e035",
"0xc0ded1d24c071ca13a905ec5e54a6ffdd0c4df68",
"0xc0fed35b43f59c2bfb1eb544bd8921bfb18140c2",
"0xc14777c94229582e5758c5a79b83dde876b9be98",
"0xc169a0e826f22fc7d37a48aaa346ffb4521c35cd",
"0xc1dd8ac971129c8c08333924f58ff40a50b8bb9a",
"0xc28f50625cfe028ab1a3458c7cebcf9657ff1438",
"0xc315f677f6a8f7b42d8fb32500e790cbf3dfbce8",
"0xc31fb4c352cb68c847715d46d97c1fa2aa2d0f00",
"0xc36454ed2b40adef7c75b9cef95b2f8010d3e0d2",
"0xc38d14bf89e0001260bb349a006d7e491e5f43ad",
"0xc4625787be3fccff6ac2971945806acc167c8cbd",
"0xc4b51a247514901faf1b6b1da9f0836066e64407",
"0xc4dca8f7d121ad79057a8382fc9fa9898727ddd8",
"0xc57f1148855e67763a694f7f2c0e68230adc686e",
"0xc593e92ed9fd3c2f9db972860047affd2df83ae7",
"0xc5d65deff5d1373f8b19aa8be528e9c3599013ba",
"0xc5f6690f7b60fe739908e02aec46ae07718cec5f",
"0xc616d99df3bf92e3111090f94694bd662b72ab1d",
"0xc7308d5a7220dc021ccf417445cb28cbb8fece66",
"0xc8ac47854a3fed2b444beb83f4c95ba092a3a735",
"0xc8c3234aea55a5f746b2ae585a849ba0bfa57785",
"0xc8ce1c3ac9549d479a90a6a89155b9b94e54d9ba",
"0xc915ec7f4cfd1c0a8aba090f03bfaab588aef9b4",
"0xc94a6e7776bade5da316cf6fd8c751fb0d5c3c5e",
"0xc9a08162b95915edf6570f0439757e1f655de576",
"0xc9f32ce1127e44c51cbd182d6364f3d707fd0d47",
"0xca4da1753336aa8340710e0e1a8c0bee2bfbf56c",
"0xcae77a05ab518492144944fb50572ebfa3595870",
"0xcb142defc16c9b409272447d87c74b722b767f1f",
"0xcb89576d5e1627f4a0c64c2bb4d51798e92e8b8f",
"0xcbcd4b518890429c0ffef3d782ed99b3adff009b",
"0xcc02b920ae227f1be7d01fc241c27e5f74d40436",
"0xcc49d1f23f01decd4e18b6aaacccb038c9648e30",
"0xcc66ea7ef61a67df02afb18706c68a5f30b22300",
"0xccdf72de615b5158bb37931bbce645aa69221520",
"0xccffdb4bd0abb6fd105c1c4e03b4e919a5b57ab9",
"0xcd63bb3586e871611cc60befcadf8e56bc7aeea3",
"0xcda5dd8e13fdae006b270769b1a18fa6c5524ce0",
"0xcdd1c19dcc3f473eaef6edb0a28e1e796d6e1767",
"0xce52d38539e1e557f184f1073a59f69c9ba95f28",
"0xce686d019e6f692af646a8df3bfe789da97a484f",
"0xcec962222291e09a07c0bd0a110d2211e0358b28",
"0xcee9ee01d48050415f1b104277bd493c5dbe645e",
"0xcff5e190a3d92e480a8bc5b414362c0d1afeb54e",
"0xd055610c2d5151adb3eaf994e08abe45dee936e0",
"0xd06f80a4b932d7247aba4a85decce6c2458c0654",
"0xd0ac32ab01d9d3ec145ef63f73bf4e222dbcc0fb",
"0xd0b473271e9d38dfd11925d62ef8c0a2cf033a9c",
"0xd0cc2b24980cbcca47ef755da88b220a82291407",
"0xd1091f9c7bb48651edb17d255b2824de0ebd2074",
"0xd1bdd067f5f1cbd358e2dde444f8d9f41de8ae76",
"0xd22167d083a5cfc2f1ccadeb842a8093b2174c5f",
"0xd2f4ea2f9d4c627c957305e28877081da9296d47",
"0xd33441a44da0c1dcee596abb7ebdcdba77c7ddbd",
"0xd354cf84a4cfd096120e2d4bf0a0cc8866d4efe1",
"0xd362c189cb0056159d2fe22bd8203e26579f620d",
"0xd3d26c61401924939bee67dffdb7aa7a0138844c",
"0xd3d7c71e4c5b1ff2f42fbb9b4a21f3373aa8b3b0",
"0xd43f2111e4055120196285ec094aa6e96998668b",
"0xd50fcd07007255599a9a8cedd881f863bcd65eb1",
"0xd5ce086a9d4987adf088889a520de98299e10bb5",
"0xd5edb088b7382b397238f5010d14cf10dd395007",
"0xd6f8668e96951e4108a79bc45ccef022544c73b3",
"0xd72ebdf278c092d9cdaa12c58f8bd9438f96b83e",
"0xd754b5bf41b84bcb49cb952f858318640abc60e0",
"0xd77b95800f67deb52e0e0988dbf01a311a3ccd80",
"0xd79eb898431ea62636f08f738d10eb00446403de",
"0xd821dfb705333d2c16e0c0c0be75ce360cca566a",
"0xd84af8766bd29b988f3426cc48cac5a6646f1f90",
"0xd868a76a1dcc34854903376e734e3cfe461dd08c",
"0xd912289c9f079b0655737a55fd5d745501ecefc7",
"0xd9204db19a18e051e861576a462c0cf83a2b6237",
"0xd92559e1f1d2e84f4964f45256693d03833d7d44",
"0xd9932453997ddf36fe114c9f22bebb9ee77b2921",
"0xd9cd7461f960e56364a294f124aac77b25e2b784",
"0xd9d25dce4c3765855c76f3d2baccc0755c4d0bc5",
"0xda1cc010259496d407a764c381d0e82182d68d89",
"0xda657e9fa116900fab8178e5580a3a6cedd89f3c",
"0xda917961872ae8e0c8b96f6925a4d7cc7b27aea3",
"0xdaa29859836d97c810c7f9d350d4a1b3e8cafc9a",
"0xdafc2a2d4e070f863cfcb5887bc2516e05e18877",
"0xdb21509547c82b69e714bf3a761e6d64f0f91cd0",
"0xdb82af76f9ccddfe9e8f7996492f4c5bd5f9d53d",
"0xdbe6dba965afc52f5b3d51e4b15e372f45d4106a",
"0xdc3fd5cce7b17e461c692ff34f22fbd4b780b151",
"0xdcd2fb1c1b103c5a591e76798704cfaa27baf6b9",
"0xddd6854a002a6fbcdf695385cd5ed630c9e27c3e",
"0xddff022e4befa69cbb5262446a8ae564700bea24",
"0xde5886e65cbf1a9d21267f5ef7d5ed444cc63938",
"0xdef05b512d405f4fb930e252c6c11f054832c93b",
"0xdefb014b9e2f3bd81cdb084821f99b681cfca695",
"0xdf04e5007cb8fd65831310ceda40f5642c1b39c3",
"0xdf1ec2e44a8b1774b068ecfc5ef1c937a86baf3e",
"0xdf3e77cd5e563f93fc7eca905d3302e95f9bb150",
"0xdf967ff95cd5ff35a27786dbd906fbb9ded116af",
"0xe009321b26350c2cff5db607068c1f6813df40d7",
"0xe02f2921742a6eec7ae44a18b1cca4277874bc74",
"0xe051ee9bd6270ff52d85ffe09685ee3a9755f04a",
"0xe06ae5c5ab3eda3a62918bf86532c7d87dbbc66d",
"0xe0b13c073e8173b06062c69a160ebb54e2af86c3",
"0xe10270bfb1ed82e120bfc392efb3c94a1604ded6",
"0xe10c24ca7bf18640fcb35e059919348891922a3b",
"0xe112784753273ebede055968ecb78dcad8ff6da2",
"0xe1358ae1a9130bc458c8021fcbb7ef8aedd51487",
"0xe1632e7e0dc135f3bc8991b917e8b682ab340fe1",
"0xe276c66af8853cdd91c1a25608b4e8b599f4da0e",
"0xe344e4b209e8eabaa2a6ddd1b0aa120b7599af25",
"0xe350160e3c8a07e92ec58ddcb8df81a73aedc6f9",
"0xe41031233301370491d47477e1171dab212d5352",
"0xe4fa5149306b12d51dc0d04e5e95bc9704ccaad7",
"0xe4faeef4002c200d9d4f1d381ccb5fb679d1c437",
"0xe54659243f8542d6abfd15bbff69f62465786756",
"0xe5b913f91f2b90c5cd04d711e1eb3214c56dba98",
"0xe61df1f5b8dd4e4e2a874157c2c97daf7314b795",
"0xe6b39dad6d7a50b233da23e510697422e9d6351a",
"0xe6c51d563f92a23dee9a7093bb1be33bd35c05d8",
"0xe6d3486a5fe2742e313a3266af8ff4f43e597d27",
"0xe74b02131ed2184eb94fd357b4f303e6935367f5",
"0xe7cc576611c2df800862db667140a90cf9ec4b72",
"0xe824ec0f8384600f3187967f8b098014e31892f2",
"0xe88379631857c0d8174efefdf8dca25b29610f08",
"0xe977419ef28e71ed541ff6318cea9a6392709a48",
"0xe9aa3a74e3d62274f221eca42736cadc14ccffaf",
"0xe9ab68e4aa12bf290529626e5f32725cf5abfba8",
"0xe9e837881c89e943c4c66e26031922bf8fc3f4f3",
"0xe9ee84b3b818b2af67c4536248fbfc5c7a0ccfad",
"0xea6e18fd3c301e4300bc99b6db792fcff376abb4",
"0xea7380369af53598a55b4622909cd9633e0ae7fa",
"0xeab1d82e96541718e645fdef4a5ee9df78605b76",
"0xeaceb8e777dc8b1c11b8730904d13fbda070846f",
"0xeb8b629df028f5353649460f1c06bf394de8641f",
"0xec2d5cc008ba6d2237d33ca9cf74b684737fcae8",
"0xec970bbf167beee6fb536f1839418f3d19ce2a69",
"0xecb6ffac05d8b4660b99b475b359fe454c77d153",
"0xed2d26eed06ecfbfd796d1ec551d2a649a31e576",
"0xed44fc770eaa76db9ade24d86ce3b409f4aed009",
"0xedf202629bb7e9f72d4c62c325d198513fa7a3d3",
"0xee2c2c2dcd952429147b62fd4dcd7f565313e419",
"0xeecc46a74cea6133a12672bd62d5167877b4d521",
"0xeed48722238b98317d849fa591d96b7efbe9b06f",
"0xeed7072fcf46733833249d1e979c44fe4f2a23a2",
"0xeef2a09be2a136bba76f04cf056e36947dbf0b0c",
"0xef0683bef79b7ad85573415c781edfde8bec65b1",
"0xef57aa4b57587600fc209f345fe0a2687bc26985",
"0xef6805af393c34fe772f8d827a5d26685215612a",
"0xf021428381172ef96988b31f241f4000259488f7",
"0xf0a924661b0263e5ce12756d07f45b8668c53380",
"0xf0fc2d4d550253d8c165c599e0cccf221c7eb7b5",
"0xf1014a29614b196d19d8ecd5ff05f3cef5efdbe2",
"0xf193a9afb00715aacf7ca9ebffafe02c77517c2e",
"0xf1e250f2a27ab7ec5b82b287f2799260448cd51d",
"0xf23f9dda63ed0628609272dc0544c7a2f7189f51",
"0xf245e09a1b42f847b120558f0c6e08f821be23f7",
"0xf2601c5a063c6a6ca86d8b6bd0543158f83a10ce",
"0xf2dcfa51f83e41c0e988477f939c7a5e5b9a6905",
"0xf33068d5e798f6519349ce32669d1ec940db1193",
"0xf394d807795ff57373f1fe903046596cb0f69acc",
"0xf3ae45d5a0b8efb41444101267b56e6795051fac",
"0xf4710562c804087af20b7396ce7cba38b43fb61f",
"0xf4c87c851d772b0f9e72cca0690327f2bc505015",
"0xf5c86c5ba5291da78a2b4119dd7aef99e3508362",
"0xf689bb4b063683a9ffd7a9295106f1e47c095809",
"0xf73ef0650415656b51be39bd5d37cf5ce03f4033",
"0xf747726200c7fc14ef4ce267b1f070128c2260dd",
"0xf855fee50a8915634a105385cb6cb9e442d15457",
"0xf8e676094628776690dbf83fa31f08aa14fd3fb8",
"0xf902aa3a62f9e28878129e1a8f1eb71e4fd7a88c",
"0xf962933e44b1dfc771738a034c80d87bdcd96ecc",
"0xf9a5139e42c3ca13d5b24d23ab2b18c278805dc4",
"0xf9fe85e19bd533c69067c6e2ff02727d40a54100",
"0xfa1f0b67b9b48b2e87467d72f09722e19ed601bd",
"0xfa956f10da5e16f120eb299df963c1f7563bf66b",
"0xfad17c9da2bebf758e3bc0c99d1abe8af7e96ed1",
"0xfb1a7ccf5bcd436dcc0acb49ba1fb9f57bb4d064",
"0xfb6e71e0800bccc0db8a9cf326fe3213ca1a0ea0",
"0xfb990112f51931a2834fcf574a860b7419ecd76f",
"0xfbc79a80ffa84253611e8f9a82f18e9e93ceee81",
"0xfbf6f29c126382cf15795bb209ee506a174cc709",
"0xfc74fb0d5df08ec546a3da18ccc7fbcbe031f5d8",
"0xfcdd417b449c982fc9b6d7b117e417682e81b627",
"0xfd477bf560e59941796b398cea662b393298abc0",
"0xfddbfa1b0b93612b95e3296690b63b74d019370c",
"0xfe68de56a07cd3af0ec40c22b0193115ecdd0501",
"0xfe821f5509fdea3f72362d001697ad8c11f0c111",
"0xfe9b7db8d9d57e9ad9341bcf51b110ba5d27b48b",
"0xff35866acb80ce4b169d1460cd48108955c1c445",
]); ]);
function isScamAddress(address) { function isScamAddress(address) {

View File

@@ -24,26 +24,6 @@ function hdWalletFromMnemonic(mnemonic) {
return { xpub, firstAddress }; return { xpub, firstAddress };
} }
function hdWalletFromXprv(xprv) {
const root = HDNodeWallet.fromExtendedKey(xprv);
if (!root.privateKey) {
throw new Error("Not an extended private key (xprv).");
}
const node = root.derivePath("44'/60'/0'/0");
const xpub = node.neuter().extendedKey;
const firstAddress = node.deriveChild(0).address;
return { xpub, firstAddress };
}
function isValidXprv(key) {
try {
const node = HDNodeWallet.fromExtendedKey(key);
return !!node.privateKey;
} catch {
return false;
}
}
function addressFromPrivateKey(key) { function addressFromPrivateKey(key) {
const w = new Wallet(key); const w = new Wallet(key);
return w.address; return w.address;
@@ -58,11 +38,6 @@ function getSignerForAddress(walletData, addrIndex, decryptedSecret) {
); );
return node.deriveChild(addrIndex); return node.deriveChild(addrIndex);
} }
if (walletData.type === "xprv") {
const root = HDNodeWallet.fromExtendedKey(decryptedSecret);
const node = root.derivePath("44'/60'/0'/0");
return node.deriveChild(addrIndex);
}
return new Wallet(decryptedSecret); return new Wallet(decryptedSecret);
} }
@@ -74,8 +49,6 @@ module.exports = {
generateMnemonic, generateMnemonic,
deriveAddressFromXpub, deriveAddressFromXpub,
hdWalletFromMnemonic, hdWalletFromMnemonic,
hdWalletFromXprv,
isValidXprv,
addressFromPrivateKey, addressFromPrivateKey,
getSignerForAddress, getSignerForAddress,
isValidMnemonic, isValidMnemonic,