Compare commits

..

1 Commits

Author SHA1 Message Date
user
f290298244 feat: expand confirm-tx warnings for contracts, burn addrs, scam list
All checks were successful
check / check (push) Successful in 10s
- Expand scam address list from 7 to 652 addresses using MEW darklist
- Add contract address warning when sending directly to a smart contract
- Add null/burn address warning (0x0000...0000, 0xdead, etc.)
- All warnings use visibility:hidden/visible pattern (no layout shift)
- Scam and burn address checks are synchronous; contract check is async

closes #114
2026-02-28 16:07:38 -08:00
24 changed files with 865 additions and 666 deletions

View File

@@ -437,10 +437,6 @@ transitions.
- **When**: User tapped a transaction row from AddressDetail or AddressToken. - **When**: User tapped a transaction row from AddressDetail or AddressToken.
- **Elements**: - **Elements**:
- "Transaction" heading, "Back" button - "Transaction" heading, "Back" button
- Type: transaction classification — one of: Native ETH Transfer, ERC-20
Token Transfer, Swap, Token Approval, Contract Call, Contract Creation
- Token contract: shown for ERC-20 transfers — color dot + full contract
address (tap to copy) + etherscan token link
- Status: "Success" or "Failed" - Status: "Success" or "Failed"
- Time: ISO datetime + relative age in parentheses - Time: ISO datetime + relative age in parentheses
- Amount: value + symbol (bold) - Amount: value + symbol (bold)
@@ -449,11 +445,6 @@ transitions.
- To: blockie + color dot + full address (tap to copy) + etherscan link - To: blockie + color dot + full address (tap to copy) + etherscan link
- ENS name if available - ENS name if available
- Transaction hash: full hash (tap to copy) + etherscan link - Transaction hash: full hash (tap to copy) + etherscan link
- Block: block number (tap to copy) + etherscan block link
- Nonce: transaction nonce (tap to copy)
- Transaction fee: ETH amount (tap to copy)
- Gas price: value in Gwei (tap to copy)
- Gas used: integer (tap to copy)
- **Transitions**: - **Transitions**:
- "Back" → **AddressToken** (if `selectedToken` set) or **AddressDetail** - "Back" → **AddressToken** (if `selectedToken` set) or **AddressDetail**

View File

@@ -107,8 +107,7 @@
</div> </div>
<div <div
id="add-wallet-phrase-warning" id="add-wallet-phrase-warning"
class="text-xs mb-2 border border-border border-dashed p-2" class="text-xs mb-2 border border-border border-dashed p-2 hidden"
style="visibility: hidden"
> >
Write these words down and keep them safe. Anyone with Write these words down and keep them safe. Anyone with
them can take your funds; if you lose them, your wallet them can take your funds; if you lose them, your wallet
@@ -185,7 +184,7 @@
<!-- active address headline --> <!-- active address headline -->
<div <div
id="total-value" id="total-value"
class="text-2xl font-bold min-h-[2rem] text-fg" class="text-2xl font-bold min-h-[2rem]"
></div> ></div>
<div <div
id="total-value-sub" id="total-value-sub"
@@ -376,8 +375,7 @@
</p> </p>
<div <div
id="export-privkey-flash" id="export-privkey-flash"
class="text-xs mb-2 min-h-[1.25rem]" class="text-xs mb-2 hidden"
style="visibility: hidden"
></div> ></div>
<div id="export-privkey-password-section" class="mb-2"> <div id="export-privkey-password-section" class="mb-2">
<label class="block mb-1">Password</label> <label class="block mb-1">Password</label>
@@ -581,17 +579,13 @@
<div class="text-xs text-muted mb-1">Your balance</div> <div class="text-xs text-muted mb-1">Your balance</div>
<div id="confirm-balance" class="text-xs"></div> <div id="confirm-balance" class="text-xs"></div>
</div> </div>
<div id="confirm-fee" class="mb-3" style="visibility: hidden"> <div id="confirm-fee" class="mb-3 hidden">
<div class="text-xs text-muted mb-1"> <div class="text-xs text-muted mb-1">
Estimated network fee Estimated network fee
</div> </div>
<div id="confirm-fee-amount" class="text-xs"></div> <div id="confirm-fee-amount" class="text-xs"></div>
</div> </div>
<div <div id="confirm-warnings" class="mb-2 hidden"></div>
id="confirm-warnings"
class="mb-2"
style="visibility: hidden"
></div>
<div <div
id="confirm-recipient-warning" id="confirm-recipient-warning"
class="mb-2" class="mb-2"
@@ -605,10 +599,34 @@
Double-check the address before sending. Double-check the address before sending.
</div> </div>
</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" class="mb-2 border border-border border-dashed p-2 hidden"
style="visibility: hidden; min-height: 1.25rem"
></div> ></div>
<div class="mb-2"> <div class="mb-2">
<label class="block mb-1 text-xs">Password</label> <label class="block mb-1 text-xs">Password</label>
@@ -621,7 +639,6 @@
<div <div
id="confirm-tx-password-error" id="confirm-tx-password-error"
class="text-xs mb-2 min-h-[1.25rem]" class="text-xs mb-2 min-h-[1.25rem]"
style="visibility: hidden"
></div> ></div>
<button <button
id="btn-confirm-send" id="btn-confirm-send"
@@ -736,8 +753,7 @@
</button> </button>
<div <div
id="receive-erc20-warning" id="receive-erc20-warning"
class="text-xs border border-border border-dashed p-2 mt-3" class="text-xs border border-border border-dashed p-2 mt-3 hidden"
style="visibility: hidden"
></div> ></div>
</div> </div>
@@ -765,8 +781,7 @@
</div> </div>
<div <div
id="add-token-info" id="add-token-info"
class="text-xs text-muted mb-2 min-h-[1.25rem]" class="text-xs text-muted mb-2 hidden"
style="visibility: hidden"
></div> ></div>
<div class="mb-2"> <div class="mb-2">
<label class="block mb-1 text-xs text-muted" <label class="block mb-1 text-xs text-muted"
@@ -824,7 +839,7 @@
<div class="bg-well p-3 mx-1 mb-3"> <div class="bg-well p-3 mx-1 mb-3">
<h3 class="font-bold mb-1">Display</h3> <h3 class="font-bold mb-1">Display</h3>
<label <label
class="text-xs flex items-center gap-1 cursor-pointer mb-2" class="text-xs flex items-center gap-1 cursor-pointer"
> >
<input <input
type="checkbox" type="checkbox"
@@ -832,17 +847,6 @@
/> />
Show tracked tokens with zero balance Show tracked tokens with zero balance
</label> </label>
<div class="text-xs flex items-center gap-1">
<label for="settings-theme">Theme:</label>
<select
id="settings-theme"
class="border border-border p-1 bg-bg text-fg text-xs cursor-pointer"
>
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</div>
</div> </div>
<div class="bg-well p-3 mx-1 mb-3"> <div class="bg-well p-3 mx-1 mb-3">
@@ -924,12 +928,6 @@
/> />
<span class="text-xs text-muted">gwei</span> <span class="text-xs text-muted">gwei</span>
</div> </div>
<label
class="text-xs flex items-center gap-1 cursor-pointer mb-1"
>
<input type="checkbox" id="settings-utc-timestamps" />
UTC Timestamps
</label>
</div> </div>
<div class="bg-well p-3 mx-1 mb-3"> <div class="bg-well p-3 mx-1 mb-3">
@@ -965,8 +963,7 @@
</p> </p>
<div <div
id="delete-wallet-flash" id="delete-wallet-flash"
class="text-xs text-red-500 mb-2 min-h-[1.25rem]" class="text-xs text-red-500 mb-2 hidden"
style="visibility: hidden"
></div> ></div>
<div class="mb-2"> <div class="mb-2">
<label class="block mb-1">Password</label> <label class="block mb-1">Password</label>
@@ -1041,8 +1038,7 @@
/> />
<div <div
id="settings-addtoken-info" id="settings-addtoken-info"
class="text-xs text-muted mt-1 min-h-[1.25rem]" class="text-xs text-muted mt-1 hidden"
style="visibility: hidden"
></div> ></div>
<button <button
id="btn-settings-addtoken-manual" id="btn-settings-addtoken-manual"
@@ -1064,82 +1060,38 @@
<h2 id="tx-detail-heading" class="font-bold mb-2"> <h2 id="tx-detail-heading" class="font-bold mb-2">
Transaction Transaction
</h2> </h2>
<div id="tx-detail-type-section" class="mb-4 hidden">
<!-- ── Identity ── -->
<div class="tx-detail-group mb-1">
<div class="mb-3">
<div class="text-xs text-muted mb-1">
Transaction hash
</div>
<div
id="tx-detail-hash"
class="text-xs break-all"
></div>
</div>
<div id="tx-detail-type-section" class="mb-3 hidden">
<div class="text-xs text-muted mb-1">Type</div> <div class="text-xs text-muted mb-1">Type</div>
<div <div id="tx-detail-type" class="text-xs font-bold"></div>
id="tx-detail-type"
class="text-xs font-bold"
></div>
</div> </div>
<div class="mb-3"> <div class="mb-4">
<div class="text-xs text-muted mb-1">Status</div> <div class="text-xs text-muted mb-1">Status</div>
<div id="tx-detail-status" class="text-xs"></div> <div id="tx-detail-status" class="text-xs"></div>
</div> </div>
<div class="mb-1"> <div class="mb-4">
<div class="text-xs text-muted mb-1">Time</div> <div class="text-xs text-muted mb-1">Time</div>
<div id="tx-detail-time" class="text-xs"></div> <div id="tx-detail-time" class="text-xs"></div>
</div> </div>
</div> <div class="mb-4">
<!-- ── Value ── -->
<div class="tx-detail-group mb-1">
<div class="mb-3">
<div class="text-xs text-muted mb-1">Amount</div> <div class="text-xs text-muted mb-1">Amount</div>
<div id="tx-detail-value" class="text-xs"></div> <div id="tx-detail-value" class="text-xs"></div>
</div> </div>
<div class="mb-3 hidden"> <div class="mb-4 hidden">
<div class="text-xs text-muted mb-1"> <div class="text-xs text-muted mb-1">Native quantity</div>
Native quantity
</div>
<div id="tx-detail-native" class="text-xs"></div> <div id="tx-detail-native" class="text-xs"></div>
</div> </div>
<div <div class="mb-4">
id="tx-detail-token-contract-section"
class="mb-1 hidden"
>
<div class="text-xs text-muted mb-1">
Token contract
</div>
<div
id="tx-detail-token-contract"
class="text-xs break-all"
></div>
</div>
</div>
<!-- ── Parties ── -->
<div class="tx-detail-group mb-1">
<div class="mb-3">
<div class="text-xs text-muted mb-1">From</div> <div class="text-xs text-muted mb-1">From</div>
<div <div id="tx-detail-from" class="text-xs break-all"></div>
id="tx-detail-from"
class="text-xs break-all"
></div>
</div> </div>
<div class="mb-1"> <div class="mb-4">
<div class="text-xs text-muted mb-1">To</div> <div class="text-xs text-muted mb-1">To</div>
<div id="tx-detail-to" class="text-xs break-all"></div> <div id="tx-detail-to" class="text-xs break-all"></div>
</div> </div>
</div> <div id="tx-detail-calldata-section" class="mb-4 hidden">
<!-- ── Protocol ── -->
<div id="tx-detail-calldata-section" class="mb-1 hidden">
<div class="tx-detail-group mb-1">
<div <div
id="tx-detail-calldata-well" id="tx-detail-calldata-well"
class="border border-border border-dashed p-2" class="mb-3 border border-border border-dashed p-2"
> >
<div class="text-xs text-muted mb-1">Action</div> <div class="text-xs text-muted mb-1">Action</div>
<div <div
@@ -1152,40 +1104,11 @@
></div> ></div>
</div> </div>
</div> </div>
<div class="mb-4">
<div class="text-xs text-muted mb-1">Transaction hash</div>
<div id="tx-detail-hash" class="text-xs break-all"></div>
</div> </div>
<!-- ── On-chain details ── -->
<div
id="tx-detail-onchain-group"
class="tx-detail-group mb-1 hidden"
>
<div id="tx-detail-block-section" class="mb-3 hidden">
<div class="text-xs text-muted mb-1">Block</div>
<div id="tx-detail-block" class="text-xs"></div>
</div>
<div id="tx-detail-nonce-section" class="mb-3 hidden">
<div class="text-xs text-muted mb-1">Nonce</div>
<div id="tx-detail-nonce" class="text-xs"></div>
</div>
<div id="tx-detail-fee-section" class="mb-3 hidden">
<div class="text-xs text-muted mb-1">
Transaction fee
</div>
<div id="tx-detail-fee" class="text-xs"></div>
</div>
<div id="tx-detail-gasprice-section" class="mb-3 hidden">
<div class="text-xs text-muted mb-1">Gas price</div>
<div id="tx-detail-gasprice" class="text-xs"></div>
</div>
<div id="tx-detail-gasused-section" class="mb-1 hidden">
<div class="text-xs text-muted mb-1">Gas used</div>
<div id="tx-detail-gasused" class="text-xs"></div>
</div>
</div>
<!-- ── Raw data ── -->
<div id="tx-detail-rawdata-section" class="mb-4 hidden"> <div id="tx-detail-rawdata-section" class="mb-4 hidden">
<div class="tx-detail-group">
<div class="text-xs text-muted mb-1">Raw data</div> <div class="text-xs text-muted mb-1">Raw data</div>
<div <div
id="tx-detail-rawdata" id="tx-detail-rawdata"
@@ -1193,7 +1116,6 @@
></div> ></div>
</div> </div>
</div> </div>
</div>
<!-- ============ TRANSACTION APPROVAL ============ --> <!-- ============ TRANSACTION APPROVAL ============ -->
<div id="view-approve-tx" class="view hidden"> <div id="view-approve-tx" class="view hidden">
@@ -1242,8 +1164,7 @@
</div> </div>
<div <div
id="approve-tx-error" id="approve-tx-error"
class="text-xs mb-2 border border-border border-dashed p-1 min-h-[1.25rem]" class="text-xs mb-2 border border-border border-dashed p-1 min-h-[1.25rem] hidden"
style="visibility: hidden"
></div> ></div>
<div class="flex justify-between"> <div class="flex justify-between">
<button <button
@@ -1271,10 +1192,8 @@
<div <div
id="approve-sign-danger-warning" id="approve-sign-danger-warning"
class="mb-3 p-2 text-xs font-bold" class="hidden mb-3 p-2 text-xs font-bold"
style=" style="
visibility: hidden;
min-height: 1.25rem;
background: #fee2e2; background: #fee2e2;
color: #991b1b; color: #991b1b;
border: 2px solid #dc2626; border: 2px solid #dc2626;
@@ -1311,8 +1230,7 @@
</div> </div>
<div <div
id="approve-sign-error" id="approve-sign-error"
class="text-xs mb-2 border border-border border-dashed p-1 min-h-[1.25rem]" class="text-xs mb-2 border border-border border-dashed p-1 min-h-[1.25rem] hidden"
style="visibility: hidden"
></div> ></div>
<div class="flex justify-between"> <div class="flex justify-between">
<button <button

View File

@@ -6,7 +6,6 @@ const { state, saveState, loadState } = require("../shared/state");
const { refreshPrices } = require("../shared/prices"); const { refreshPrices } = require("../shared/prices");
const { refreshBalances } = require("../shared/balances"); const { refreshBalances } = require("../shared/balances");
const { $, showView } = require("./views/helpers"); const { $, showView } = require("./views/helpers");
const { applyTheme } = require("./theme");
const home = require("./views/home"); const home = require("./views/home");
const welcome = require("./views/welcome"); const welcome = require("./views/welcome");
@@ -177,7 +176,6 @@ async function init() {
} }
await loadState(); await loadState();
applyTheme(state.theme);
// Auto-default active address // Auto-default active address
if ( if (

View File

@@ -15,40 +15,7 @@
--color-section: #dddddd; --color-section: #dddddd;
} }
html.dark {
--color-bg: #000000;
--color-fg: #ffffff;
--color-muted: #aaaaaa;
--color-border: #ffffff;
--color-border-light: #444444;
--color-hover: #222222;
--color-well: #1a1a1a;
--color-danger-well: #2a0a0a;
--color-section: #2a2a2a;
}
body { body {
width: 396px; width: 396px;
overflow-x: hidden; overflow-x: hidden;
} }
/* Copy-flash feedback: inverts colors then fades back */
.copy-flash-active {
background-color: var(--color-fg) !important;
color: var(--color-bg) !important;
transition: none;
}
.copy-flash-fade {
transition:
background-color 225ms ease-out,
color 225ms ease-out;
}
/* Transaction detail view — visual grouping of related fields */
.tx-detail-group {
border-bottom: 1px solid var(--color-border-light);
padding-bottom: 0.5rem;
margin-bottom: 0.5rem;
padding-top: 0.25rem;
}

View File

@@ -1,33 +0,0 @@
// Theme management: applies light/dark class to <html> based on preference.
let mediaQuery = null;
let mediaHandler = null;
function applyTheme(theme) {
// Clean up previous system listener
if (mediaQuery && mediaHandler) {
mediaQuery.removeEventListener("change", mediaHandler);
mediaHandler = null;
}
if (theme === "dark") {
document.documentElement.classList.add("dark");
} else if (theme === "light") {
document.documentElement.classList.remove("dark");
} else {
// system
mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const update = () => {
if (mediaQuery.matches) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
};
mediaHandler = update;
mediaQuery.addEventListener("change", update);
update();
}
}
module.exports = { applyTheme };

View File

@@ -7,8 +7,7 @@ const { log } = require("../../shared/log");
function show() { function show() {
$("add-token-address").value = ""; $("add-token-address").value = "";
$("add-token-info").textContent = ""; $("add-token-info").classList.add("hidden");
$("add-token-info").style.visibility = "hidden";
const list = $("common-token-list"); const list = $("common-token-list");
list.innerHTML = getTopTokens(25) list.innerHTML = getTopTokens(25)
.map( .map(
@@ -46,7 +45,7 @@ function init(ctx) {
} }
const infoEl = $("add-token-info"); const infoEl = $("add-token-info");
infoEl.textContent = "Looking up token..."; infoEl.textContent = "Looking up token...";
infoEl.style.visibility = "visible"; infoEl.classList.remove("hidden");
log.debugf("Looking up token contract", contractAddr); log.debugf("Looking up token contract", contractAddr);
try { try {
const info = await lookupTokenInfo(contractAddr, state.rpcUrl); const info = await lookupTokenInfo(contractAddr, state.rpcUrl);
@@ -64,8 +63,7 @@ function init(ctx) {
const detail = e.shortMessage || e.message || String(e); const detail = e.shortMessage || e.message || String(e);
log.errorf("Token lookup failed for", contractAddr, detail); log.errorf("Token lookup failed for", contractAddr, detail);
showFlash(detail); showFlash(detail);
infoEl.textContent = ""; infoEl.classList.add("hidden");
infoEl.style.visibility = "hidden";
} }
}); });

View File

@@ -11,25 +11,6 @@ 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");
/**
* Check if an address already exists in ANY wallet (hd, xprv, or key).
* Returns the wallet object if found, or undefined.
*/
function findWalletByAddress(addr) {
const lower = addr.toLowerCase();
return state.wallets.find((w) =>
w.addresses.some((a) => a.address.toLowerCase() === lower),
);
}
/**
* Check if an xpub already exists in any HD-type wallet (hd or xprv).
* Returns the wallet object if found, or undefined.
*/
function findWalletByXpub(xpub) {
return state.wallets.find((w) => w.xpub && w.xpub === xpub);
}
let currentMode = "mnemonic"; let currentMode = "mnemonic";
const MODES = ["mnemonic", "privkey", "xprv"]; const MODES = ["mnemonic", "privkey", "xprv"];
@@ -71,7 +52,7 @@ function show() {
$("import-xprv-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").style.visibility = "hidden"; $("add-wallet-phrase-warning").classList.add("hidden");
switchMode("mnemonic"); switchMode("mnemonic");
showView("add-wallet"); showView("add-wallet");
} }
@@ -116,16 +97,16 @@ async function importMnemonic(ctx) {
const pw = validatePassword(); const pw = validatePassword();
if (!pw) return; if (!pw) return;
const { xpub, firstAddress } = hdWalletFromMnemonic(mnemonic); const { xpub, firstAddress } = hdWalletFromMnemonic(mnemonic);
const xpubDup = findWalletByXpub(xpub); const duplicate = state.wallets.find(
if (xpubDup) { (w) =>
showFlash( w.type === "hd" &&
"This recovery phrase is already added (" + xpubDup.name + ").", w.addresses[0] &&
w.addresses[0].address.toLowerCase() === firstAddress.toLowerCase(),
);
if (duplicate) {
showFlash(
"This recovery phrase is already added (" + duplicate.name + ").",
); );
return;
}
const addrDup = findWalletByAddress(firstAddress);
if (addrDup) {
showFlash("Address already exists in wallet (" + addrDup.name + ").");
return; return;
} }
const encrypted = await encryptWithPassword(mnemonic, pw); const encrypted = await encryptWithPassword(mnemonic, pw);
@@ -181,10 +162,15 @@ async function importPrivateKey(ctx) {
} }
const pw = validatePassword(); const pw = validatePassword();
if (!pw) return; if (!pw) return;
const duplicate = findWalletByAddress(addr); const duplicate = state.wallets.find(
(w) =>
w.type === "key" &&
w.addresses[0] &&
w.addresses[0].address.toLowerCase() === addr.toLowerCase(),
);
if (duplicate) { if (duplicate) {
showFlash( showFlash(
"This address already exists in wallet (" + duplicate.name + ").", "This private key is already added (" + duplicate.name + ").",
); );
return; return;
} }
@@ -222,14 +208,14 @@ async function importXprvKey(ctx) {
return; return;
} }
const { xpub, firstAddress } = result; const { xpub, firstAddress } = result;
const xpubDup = findWalletByXpub(xpub); const duplicate = state.wallets.find(
if (xpubDup) { (w) =>
showFlash("This key is already added (" + xpubDup.name + ")."); (w.type === "hd" || w.type === "xprv") &&
return; w.addresses[0] &&
} w.addresses[0].address.toLowerCase() === firstAddress.toLowerCase(),
const addrDup = findWalletByAddress(firstAddress); );
if (addrDup) { if (duplicate) {
showFlash("Address already exists in wallet (" + addrDup.name + ")."); showFlash("This key is already added (" + duplicate.name + ").");
return; return;
} }
const pw = validatePassword(); const pw = validatePassword();
@@ -281,7 +267,7 @@ function init(ctx) {
// Generate mnemonic // Generate mnemonic
$("btn-generate-phrase").addEventListener("click", () => { $("btn-generate-phrase").addEventListener("click", () => {
$("wallet-mnemonic").value = generateMnemonic(); $("wallet-mnemonic").value = generateMnemonic();
$("add-wallet-phrase-warning").style.visibility = "visible"; $("add-wallet-phrase-warning").classList.remove("hidden");
}); });
// Import / confirm // Import / confirm

View File

@@ -2,7 +2,6 @@ const {
$, $,
showView, showView,
showFlash, showFlash,
flashCopyFeedback,
balanceLinesForAddress, balanceLinesForAddress,
addressDotHtml, addressDotHtml,
addressTitle, addressTitle,
@@ -95,39 +94,18 @@ function show() {
function isoDate(timestamp) { function isoDate(timestamp) {
const d = new Date(timestamp * 1000); const d = new Date(timestamp * 1000);
const pad = (n) => String(n).padStart(2, "0"); const pad = (n) => String(n).padStart(2, "0");
if (state.utcTimestamps) {
return (
d.getUTCFullYear() +
"-" +
pad(d.getUTCMonth() + 1) +
"-" +
pad(d.getUTCDate()) +
"T" +
pad(d.getUTCHours()) +
":" +
pad(d.getUTCMinutes()) +
":" +
pad(d.getUTCSeconds()) +
"Z"
);
}
const offsetMin = -d.getTimezoneOffset();
const sign = offsetMin >= 0 ? "+" : "-";
const absOff = Math.abs(offsetMin);
const tzStr = sign + pad(Math.floor(absOff / 60)) + ":" + pad(absOff % 60);
return ( return (
d.getFullYear() + d.getFullYear() +
"-" + "-" +
pad(d.getMonth() + 1) + pad(d.getMonth() + 1) +
"-" + "-" +
pad(d.getDate()) + pad(d.getDate()) +
"T" + " " +
pad(d.getHours()) + pad(d.getHours()) +
":" + ":" +
pad(d.getMinutes()) + pad(d.getMinutes()) +
":" + ":" +
pad(d.getSeconds()) + pad(d.getSeconds())
tzStr
); );
} }
@@ -263,7 +241,6 @@ function init(_ctx) {
if (addr) { if (addr) {
navigator.clipboard.writeText(addr); navigator.clipboard.writeText(addr);
showFlash("Copied!"); showFlash("Copied!");
flashCopyFeedback($("address-full"));
} }
}); });
@@ -333,8 +310,8 @@ function init(_ctx) {
$("export-privkey-address").textContent = addr.address; $("export-privkey-address").textContent = addr.address;
$("export-privkey-address").dataset.full = addr.address; $("export-privkey-address").dataset.full = addr.address;
$("export-privkey-password").value = ""; $("export-privkey-password").value = "";
$("export-privkey-flash").classList.add("hidden");
$("export-privkey-flash").textContent = ""; $("export-privkey-flash").textContent = "";
$("export-privkey-flash").style.visibility = "hidden";
$("export-privkey-password-section").classList.remove("hidden"); $("export-privkey-password-section").classList.remove("hidden");
$("export-privkey-result").classList.add("hidden"); $("export-privkey-result").classList.add("hidden");
$("export-privkey-value").textContent = ""; $("export-privkey-value").textContent = "";
@@ -345,7 +322,7 @@ function init(_ctx) {
const password = $("export-privkey-password").value; const password = $("export-privkey-password").value;
if (!password) { if (!password) {
$("export-privkey-flash").textContent = "Password is required."; $("export-privkey-flash").textContent = "Password is required.";
$("export-privkey-flash").style.visibility = "visible"; $("export-privkey-flash").classList.remove("hidden");
return; return;
} }
const btn = $("btn-export-privkey-confirm"); const btn = $("btn-export-privkey-confirm");
@@ -366,10 +343,10 @@ function init(_ctx) {
$("export-privkey-password-section").classList.add("hidden"); $("export-privkey-password-section").classList.add("hidden");
$("export-privkey-value").textContent = privateKey; $("export-privkey-value").textContent = privateKey;
$("export-privkey-result").classList.remove("hidden"); $("export-privkey-result").classList.remove("hidden");
$("export-privkey-flash").style.visibility = "hidden"; $("export-privkey-flash").classList.add("hidden");
} catch { } catch {
$("export-privkey-flash").textContent = "Wrong password."; $("export-privkey-flash").textContent = "Wrong password.";
$("export-privkey-flash").style.visibility = "visible"; $("export-privkey-flash").classList.remove("hidden");
} finally { } finally {
btn.disabled = false; btn.disabled = false;
btn.classList.remove("text-muted"); btn.classList.remove("text-muted");
@@ -381,7 +358,6 @@ function init(_ctx) {
if (key) { if (key) {
navigator.clipboard.writeText(key); navigator.clipboard.writeText(key);
showFlash("Copied!"); showFlash("Copied!");
flashCopyFeedback($("export-privkey-value"));
} }
}); });
@@ -390,7 +366,6 @@ function init(_ctx) {
if (full) { if (full) {
navigator.clipboard.writeText(full); navigator.clipboard.writeText(full);
showFlash("Copied!"); showFlash("Copied!");
flashCopyFeedback($("export-privkey-address"));
} }
}); });

View File

@@ -5,7 +5,6 @@ const {
$, $,
showView, showView,
showFlash, showFlash,
flashCopyFeedback,
addressDotHtml, addressDotHtml,
addressTitle, addressTitle,
escapeHtml, escapeHtml,
@@ -48,39 +47,18 @@ function etherscanAddressLink(address) {
function isoDate(timestamp) { function isoDate(timestamp) {
const d = new Date(timestamp * 1000); const d = new Date(timestamp * 1000);
const pad = (n) => String(n).padStart(2, "0"); const pad = (n) => String(n).padStart(2, "0");
if (state.utcTimestamps) {
return (
d.getUTCFullYear() +
"-" +
pad(d.getUTCMonth() + 1) +
"-" +
pad(d.getUTCDate()) +
"T" +
pad(d.getUTCHours()) +
":" +
pad(d.getUTCMinutes()) +
":" +
pad(d.getUTCSeconds()) +
"Z"
);
}
const offsetMin = -d.getTimezoneOffset();
const sign = offsetMin >= 0 ? "+" : "-";
const absOff = Math.abs(offsetMin);
const tzStr = sign + pad(Math.floor(absOff / 60)) + ":" + pad(absOff % 60);
return ( return (
d.getFullYear() + d.getFullYear() +
"-" + "-" +
pad(d.getMonth() + 1) + pad(d.getMonth() + 1) +
"-" + "-" +
pad(d.getDate()) + pad(d.getDate()) +
"T" + " " +
pad(d.getHours()) + pad(d.getHours()) +
":" + ":" +
pad(d.getMinutes()) + pad(d.getMinutes()) +
":" + ":" +
pad(d.getSeconds()) + pad(d.getSeconds())
tzStr
); );
} }
@@ -339,7 +317,6 @@ function init(_ctx) {
if (addr) { if (addr) {
navigator.clipboard.writeText(addr); navigator.clipboard.writeText(addr);
showFlash("Copied!"); showFlash("Copied!");
flashCopyFeedback($("address-token-full"));
} }
}); });
@@ -348,7 +325,6 @@ function init(_ctx) {
if (copyEl) { if (copyEl) {
navigator.clipboard.writeText(copyEl.dataset.copy); navigator.clipboard.writeText(copyEl.dataset.copy);
showFlash("Copied!"); showFlash("Copied!");
flashCopyFeedback(copyEl);
} }
}); });
@@ -397,7 +373,6 @@ function init(_ctx) {
copyEl.addEventListener("click", () => { copyEl.addEventListener("click", () => {
navigator.clipboard.writeText(copyEl.dataset.copy); navigator.clipboard.writeText(copyEl.dataset.copy);
showFlash("Copied!"); showFlash("Copied!");
flashCopyFeedback(copyEl);
}); });
} }
updateSendBalance(); updateSendBalance();

View File

@@ -269,7 +269,7 @@ function showTxApproval(details) {
} }
$("approve-tx-password").value = ""; $("approve-tx-password").value = "";
hideError("approve-tx-error"); $("approve-tx-error").classList.add("hidden");
showView("approve-tx"); showView("approve-tx");
} }
@@ -351,10 +351,10 @@ function showSignApproval(details) {
if (warningEl) { if (warningEl) {
if (sp.dangerWarning) { if (sp.dangerWarning) {
warningEl.textContent = sp.dangerWarning; warningEl.textContent = sp.dangerWarning;
warningEl.style.visibility = "visible"; warningEl.classList.remove("hidden");
} else { } else {
warningEl.textContent = ""; warningEl.textContent = "";
warningEl.style.visibility = "hidden"; warningEl.classList.add("hidden");
} }
} }

View File

@@ -15,7 +15,6 @@ const {
hideError, hideError,
showView, showView,
showFlash, showFlash,
flashCopyFeedback,
addressTitle, addressTitle,
addressDotHtml, addressDotHtml,
escapeHtml, escapeHtml,
@@ -26,7 +25,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 } = require("../../shared/constants"); const { ERC20_ABI, isBurnAddress } = 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");
@@ -118,7 +117,6 @@ function show(txInfo) {
copyEl.onclick = () => { copyEl.onclick = () => {
navigator.clipboard.writeText(copyEl.dataset.copy); navigator.clipboard.writeText(copyEl.dataset.copy);
showFlash("Copied!"); showFlash("Copied!");
flashCopyFeedback(copyEl);
}; };
} }
} else { } else {
@@ -167,13 +165,18 @@ function show(txInfo) {
$("confirm-balance").textContent = valueWithUsd(bal + " ETH", balUsd); $("confirm-balance").textContent = valueWithUsd(bal + " ETH", balUsd);
} }
// Check for warnings // Check for warnings (synchronous checks)
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.");
} }
@@ -186,10 +189,9 @@ function show(txInfo) {
`<div class="border border-border border-dashed p-2 mb-1 text-xs font-bold">WARNING: ${w}</div>`, `<div class="border border-border border-dashed p-2 mb-1 text-xs font-bold">WARNING: ${w}</div>`,
) )
.join(""); .join("");
warningsEl.style.visibility = "visible"; warningsEl.classList.remove("hidden");
} else { } else {
warningsEl.innerHTML = ""; warningsEl.classList.add("hidden");
warningsEl.style.visibility = "hidden";
} }
// Check for errors // Check for errors
@@ -227,12 +229,11 @@ function show(txInfo) {
errorsEl.innerHTML = errors errorsEl.innerHTML = errors
.map((e) => `<div class="text-xs">${e}</div>`) .map((e) => `<div class="text-xs">${e}</div>`)
.join(""); .join("");
errorsEl.style.visibility = "visible"; errorsEl.classList.remove("hidden");
sendBtn.disabled = true; sendBtn.disabled = true;
sendBtn.classList.add("text-muted"); sendBtn.classList.add("text-muted");
} else { } else {
errorsEl.innerHTML = ""; errorsEl.classList.add("hidden");
errorsEl.style.visibility = "hidden";
sendBtn.disabled = false; sendBtn.disabled = false;
sendBtn.classList.remove("text-muted"); sendBtn.classList.remove("text-muted");
} }
@@ -242,13 +243,20 @@ function show(txInfo) {
hideError("confirm-tx-password-error"); hideError("confirm-tx-password-error");
// Gas estimate — show placeholder then fetch async // Gas estimate — show placeholder then fetch async
$("confirm-fee").style.visibility = "visible"; $("confirm-fee").classList.remove("hidden");
$("confirm-fee-amount").textContent = "Estimating..."; $("confirm-fee-amount").textContent = "Estimating...";
state.viewData = { pendingTx: txInfo }; state.viewData = { pendingTx: txInfo };
showView("confirm-tx"); showView("confirm-tx");
// Reset recipient warning to hidden (space always reserved, no layout shift) // Reset async warnings to hidden (space always reserved, no layout shift)
$("confirm-recipient-warning").style.visibility = "hidden"; $("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); checkRecipientHistory(txInfo);
@@ -295,19 +303,17 @@ async function estimateGas(txInfo) {
} }
async function checkRecipientHistory(txInfo) { async function checkRecipientHistory(txInfo) {
const el = $("confirm-recipient-warning");
try { try {
const provider = getProvider(state.rpcUrl); const provider = getProvider(state.rpcUrl);
// Skip warning for contract addresses — they may legitimately
// have zero outgoing transactions (getTransactionCount returns
// the nonce, i.e. sent-tx count only).
const code = await provider.getCode(txInfo.to); const code = await provider.getCode(txInfo.to);
if (code && code !== "0x") { if (code && code !== "0x") {
// Recipient is a contract — warn the user
$("confirm-contract-warning").style.visibility = "visible";
return; return;
} }
const txCount = await provider.getTransactionCount(txInfo.to); const txCount = await provider.getTransactionCount(txInfo.to);
if (txCount === 0) { if (txCount === 0) {
el.style.visibility = "visible"; $("confirm-recipient-warning").style.visibility = "visible";
} }
} catch (e) { } catch (e) {
log.errorf("recipient history check failed:", e.message); log.errorf("recipient history check failed:", e.message);

View File

@@ -12,7 +12,7 @@ function show(walletIdx) {
wallet.name || "Wallet " + (walletIdx + 1); wallet.name || "Wallet " + (walletIdx + 1);
$("delete-wallet-password").value = ""; $("delete-wallet-password").value = "";
$("delete-wallet-flash").textContent = ""; $("delete-wallet-flash").textContent = "";
$("delete-wallet-flash").style.visibility = "hidden"; $("delete-wallet-flash").classList.add("hidden");
showView("delete-wallet-confirm"); showView("delete-wallet-confirm");
} }
@@ -29,14 +29,14 @@ function init(_ctx) {
if (!pw) { if (!pw) {
$("delete-wallet-flash").textContent = $("delete-wallet-flash").textContent =
"Please enter your password."; "Please enter your password.";
$("delete-wallet-flash").style.visibility = "visible"; $("delete-wallet-flash").classList.remove("hidden");
return; return;
} }
if (deleteWalletIndex === null) { if (deleteWalletIndex === null) {
$("delete-wallet-flash").textContent = $("delete-wallet-flash").textContent =
"No wallet selected for deletion."; "No wallet selected for deletion.";
$("delete-wallet-flash").style.visibility = "visible"; $("delete-wallet-flash").classList.remove("hidden");
return; return;
} }
@@ -52,7 +52,7 @@ function init(_ctx) {
await decryptWithPassword(wallet.encryptedSecret, pw); await decryptWithPassword(wallet.encryptedSecret, pw);
} catch (_e) { } catch (_e) {
$("delete-wallet-flash").textContent = "Wrong password."; $("delete-wallet-flash").textContent = "Wrong password.";
$("delete-wallet-flash").style.visibility = "visible"; $("delete-wallet-flash").classList.remove("hidden");
btn.disabled = false; btn.disabled = false;
btn.classList.remove("text-muted"); btn.classList.remove("text-muted");
return; return;

View File

@@ -40,13 +40,11 @@ function $(id) {
function showError(id, msg) { function showError(id, msg) {
const el = $(id); const el = $(id);
el.textContent = msg; el.textContent = msg;
el.style.visibility = "visible"; el.classList.remove("hidden");
} }
function hideError(id) { function hideError(id) {
const el = $(id); $(id).classList.add("hidden");
el.textContent = "";
el.style.visibility = "hidden";
} }
function showView(name) { function showView(name) {
@@ -228,39 +226,18 @@ function formatAddressHtml(address, ensName, maxLen, title) {
function isoDate(timestamp) { function isoDate(timestamp) {
const d = new Date(timestamp * 1000); const d = new Date(timestamp * 1000);
const pad = (n) => String(n).padStart(2, "0"); const pad = (n) => String(n).padStart(2, "0");
if (state.utcTimestamps) {
return (
d.getUTCFullYear() +
"-" +
pad(d.getUTCMonth() + 1) +
"-" +
pad(d.getUTCDate()) +
"T" +
pad(d.getUTCHours()) +
":" +
pad(d.getUTCMinutes()) +
":" +
pad(d.getUTCSeconds()) +
"Z"
);
}
const offsetMin = -d.getTimezoneOffset();
const sign = offsetMin >= 0 ? "+" : "-";
const absOff = Math.abs(offsetMin);
const tzStr = sign + pad(Math.floor(absOff / 60)) + ":" + pad(absOff % 60);
return ( return (
d.getFullYear() + d.getFullYear() +
"-" + "-" +
pad(d.getMonth() + 1) + pad(d.getMonth() + 1) +
"-" + "-" +
pad(d.getDate()) + pad(d.getDate()) +
"T" + " " +
pad(d.getHours()) + pad(d.getHours()) +
":" + ":" +
pad(d.getMinutes()) + pad(d.getMinutes()) +
":" + ":" +
pad(d.getSeconds()) + pad(d.getSeconds())
tzStr
); );
} }
@@ -281,26 +258,12 @@ function timeAgo(timestamp) {
return years + " year" + (years !== 1 ? "s" : "") + " ago"; return years + " year" + (years !== 1 ? "s" : "") + " ago";
} }
function flashCopyFeedback(el) {
if (!el) return;
el.classList.remove("copy-flash-fade");
el.classList.add("copy-flash-active");
setTimeout(() => {
el.classList.remove("copy-flash-active");
el.classList.add("copy-flash-fade");
setTimeout(() => {
el.classList.remove("copy-flash-fade");
}, 275);
}, 75);
}
module.exports = { module.exports = {
$, $,
showError, showError,
hideError, hideError,
showView, showView,
showFlash, showFlash,
flashCopyFeedback,
balanceLine, balanceLine,
balanceLinesForAddress, balanceLinesForAddress,
addressColor, addressColor,

View File

@@ -2,7 +2,6 @@ const {
$, $,
showView, showView,
showFlash, showFlash,
flashCopyFeedback,
balanceLinesForAddress, balanceLinesForAddress,
isoDate, isoDate,
timeAgo, timeAgo,
@@ -86,10 +85,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", (e) => { $("active-addr-copy").addEventListener("click", () => {
navigator.clipboard.writeText(addr); navigator.clipboard.writeText(addr);
showFlash("Copied!"); showFlash("Copied!");
flashCopyFeedback(e.currentTarget);
}); });
} else { } else {
el.textContent = ""; el.textContent = "";

View File

@@ -2,7 +2,6 @@ const {
$, $,
showView, showView,
showFlash, showFlash,
flashCopyFeedback,
formatAddressHtml, formatAddressHtml,
addressTitle, addressTitle,
} = require("./helpers"); } = require("./helpers");
@@ -53,21 +52,19 @@ function show() {
"This is an ERC-20 token. Only send " + "This is an ERC-20 token. Only send " +
symbol + symbol +
" on the Ethereum network to this address. Sending tokens on other networks will result in permanent loss."; " on the Ethereum network to this address. Sending tokens on other networks will result in permanent loss.";
warningEl.style.visibility = "visible"; warningEl.classList.remove("hidden");
} else { } else {
warningEl.textContent = ""; warningEl.classList.add("hidden");
warningEl.style.visibility = "hidden";
} }
showView("receive"); showView("receive");
} }
function init(ctx) { function init(ctx) {
$("receive-address-block").addEventListener("click", (e) => { $("receive-address-block").addEventListener("click", () => {
const addr = $("receive-address-block").dataset.full; const addr = $("receive-address-block").dataset.full;
if (addr) { if (addr) {
navigator.clipboard.writeText(addr); navigator.clipboard.writeText(addr);
showFlash("Copied!"); showFlash("Copied!");
flashCopyFeedback(e.currentTarget);
} }
}); });
@@ -76,7 +73,6 @@ function init(ctx) {
if (addr) { if (addr) {
navigator.clipboard.writeText(addr); navigator.clipboard.writeText(addr);
showFlash("Copied!"); showFlash("Copied!");
flashCopyFeedback($("receive-address-block"));
} }
}); });

View File

@@ -1,5 +1,4 @@
const { $, showView, showFlash, escapeHtml } = require("./helpers"); const { $, showView, showFlash, escapeHtml } = require("./helpers");
const { applyTheme } = require("../theme");
const { state, saveState } = require("../../shared/state"); const { state, saveState } = require("../../shared/state");
const { ETHEREUM_MAINNET_CHAIN_ID } = require("../../shared/constants"); const { ETHEREUM_MAINNET_CHAIN_ID } = require("../../shared/constants");
const { log, debugFetch } = require("../../shared/log"); const { log, debugFetch } = require("../../shared/log");
@@ -215,13 +214,6 @@ function init(ctx) {
await saveState(); await saveState();
}); });
$("settings-theme").value = state.theme;
$("settings-theme").addEventListener("change", async () => {
state.theme = $("settings-theme").value;
await saveState();
applyTheme(state.theme);
});
$("settings-hide-low-holders").checked = state.hideLowHolderTokens; $("settings-hide-low-holders").checked = state.hideLowHolderTokens;
$("settings-hide-low-holders").addEventListener("change", async () => { $("settings-hide-low-holders").addEventListener("change", async () => {
state.hideLowHolderTokens = $("settings-hide-low-holders").checked; state.hideLowHolderTokens = $("settings-hide-low-holders").checked;
@@ -249,12 +241,6 @@ function init(ctx) {
} }
}); });
$("settings-utc-timestamps").checked = state.utcTimestamps;
$("settings-utc-timestamps").addEventListener("change", async () => {
state.utcTimestamps = $("settings-utc-timestamps").checked;
await saveState();
});
$("btn-main-add-wallet").addEventListener("click", ctx.showAddWalletView); $("btn-main-add-wallet").addEventListener("click", ctx.showAddWalletView);
$("btn-settings-add-token").addEventListener( $("btn-settings-add-token").addEventListener(

View File

@@ -73,8 +73,7 @@ function renderDropdown() {
function show() { function show() {
$("settings-addtoken-address").value = ""; $("settings-addtoken-address").value = "";
$("settings-addtoken-info").textContent = ""; $("settings-addtoken-info").classList.add("hidden");
$("settings-addtoken-info").style.visibility = "hidden";
renderTop10(); renderTop10();
renderDropdown(); renderDropdown();
showView("settings-addtoken"); showView("settings-addtoken");
@@ -130,7 +129,7 @@ function init(_ctx) {
} }
const infoEl = $("settings-addtoken-info"); const infoEl = $("settings-addtoken-info");
infoEl.textContent = "Looking up token..."; infoEl.textContent = "Looking up token...";
infoEl.style.visibility = "visible"; infoEl.classList.remove("hidden");
log.debugf("Looking up token contract", addr); log.debugf("Looking up token contract", addr);
try { try {
const info = await lookupTokenInfo(addr, state.rpcUrl); const info = await lookupTokenInfo(addr, state.rpcUrl);
@@ -144,8 +143,7 @@ function init(_ctx) {
await saveState(); await saveState();
showFlash("Added " + info.symbol); showFlash("Added " + info.symbol);
$("settings-addtoken-address").value = ""; $("settings-addtoken-address").value = "";
infoEl.textContent = ""; infoEl.classList.add("hidden");
infoEl.style.visibility = "hidden";
renderTop10(); renderTop10();
renderDropdown(); renderDropdown();
ctx.doRefreshAndRender(); ctx.doRefreshAndRender();
@@ -153,8 +151,7 @@ function init(_ctx) {
const detail = e.shortMessage || e.message || String(e); const detail = e.shortMessage || e.message || String(e);
log.errorf("Token lookup failed for", addr, detail); log.errorf("Token lookup failed for", addr, detail);
showFlash(detail); showFlash(detail);
infoEl.textContent = ""; infoEl.classList.add("hidden");
infoEl.style.visibility = "hidden";
} }
}); });
} }

View File

@@ -5,7 +5,6 @@ const {
$, $,
showView, showView,
showFlash, showFlash,
flashCopyFeedback,
addressDotHtml, addressDotHtml,
addressTitle, addressTitle,
escapeHtml, escapeHtml,
@@ -13,7 +12,6 @@ const {
timeAgo, timeAgo,
} = require("./helpers"); } = require("./helpers");
const { state } = require("../../shared/state"); const { state } = require("../../shared/state");
const { formatEther, formatUnits } = require("ethers");
const makeBlockie = require("ethereum-blockies-base64"); const makeBlockie = require("ethereum-blockies-base64");
const { log, debugFetch } = require("../../shared/log"); const { log, debugFetch } = require("../../shared/log");
const { decodeCalldata } = require("./approval"); const { decodeCalldata } = require("./approval");
@@ -27,25 +25,6 @@ const EXT_ICON =
let ctx; let ctx;
/**
* Determine a human-readable transaction type string from tx fields.
*/
function getTransactionType(tx) {
if (!tx.to) return "Contract Creation";
if (tx.direction === "contract") {
if (tx.directionLabel === "Swap") return "Swap";
if (
tx.method === "approve" ||
tx.directionLabel === "Approve" ||
tx.method === "setApprovalForAll"
)
return "Token Approval";
return "Contract Call";
}
if (tx.symbol && tx.symbol !== "ETH") return "ERC-20 Token Transfer";
return "Native ETH Transfer";
}
function copyableHtml(text, extraClass) { function copyableHtml(text, extraClass) {
const cls = const cls =
"underline decoration-dashed cursor-pointer" + "underline decoration-dashed cursor-pointer" +
@@ -119,7 +98,6 @@ function show(tx) {
direction: tx.direction || null, direction: tx.direction || null,
isContractCall: tx.isContractCall || false, isContractCall: tx.isContractCall || false,
method: tx.method || null, method: tx.method || null,
contractAddress: tx.contractAddress || null,
}, },
}; };
render(); render();
@@ -156,56 +134,30 @@ function render() {
nativeEl.parentElement.classList.add("hidden"); nativeEl.parentElement.classList.add("hidden");
} }
// Always show transaction type as the first field // Show type label for contract interactions (Swap, Execute, etc.)
const typeSection = $("tx-detail-type-section"); const typeSection = $("tx-detail-type-section");
const typeEl = $("tx-detail-type"); const typeEl = $("tx-detail-type");
const headingEl = $("tx-detail-heading"); const headingEl = $("tx-detail-heading");
if (typeSection && typeEl) { if (tx.direction === "contract" && tx.directionLabel) {
typeEl.textContent = getTransactionType(tx); if (typeSection) {
typeEl.textContent = tx.directionLabel;
typeSection.classList.remove("hidden"); typeSection.classList.remove("hidden");
} }
} else {
if (typeSection) typeSection.classList.add("hidden");
}
if (headingEl) headingEl.textContent = "Transaction"; if (headingEl) headingEl.textContent = "Transaction";
// Token contract address (for ERC-20 transfers) // Hide calldata and raw data sections; re-fetch if this is a contract call
const tokenContractSection = $("tx-detail-token-contract-section");
const tokenContractEl = $("tx-detail-token-contract");
if (tokenContractSection && tokenContractEl) {
if (tx.contractAddress) {
const dot = addressDotHtml(tx.contractAddress);
const link = `https://etherscan.io/token/${tx.contractAddress}`;
tokenContractEl.innerHTML =
`<div class="flex items-center">${dot}` +
copyableHtml(tx.contractAddress, "break-all") +
etherscanLinkHtml(link) +
`</div>`;
tokenContractSection.classList.remove("hidden");
} else {
tokenContractSection.classList.add("hidden");
}
}
// Hide calldata and raw data sections; always fetch full tx details
const calldataSection = $("tx-detail-calldata-section"); const calldataSection = $("tx-detail-calldata-section");
if (calldataSection) calldataSection.classList.add("hidden"); if (calldataSection) calldataSection.classList.add("hidden");
const rawDataSection = $("tx-detail-rawdata-section"); const rawDataSection = $("tx-detail-rawdata-section");
if (rawDataSection) rawDataSection.classList.add("hidden"); if (rawDataSection) rawDataSection.classList.add("hidden");
// Hide on-chain detail sections (and their group wrapper) until populated if (tx.isContractCall || tx.direction === "contract") {
const onchainGroup = $("tx-detail-onchain-group"); loadCalldata(tx.hash, tx.to);
if (onchainGroup) onchainGroup.classList.add("hidden");
for (const id of [
"tx-detail-block-section",
"tx-detail-nonce-section",
"tx-detail-fee-section",
"tx-detail-gasprice-section",
"tx-detail-gasused-section",
]) {
const el = $(id);
if (el) el.classList.add("hidden");
} }
loadFullTxDetails(tx.hash, tx.to, tx.isContractCall);
const isoStr = isoDate(tx.timestamp); const isoStr = isoDate(tx.timestamp);
$("tx-detail-time").innerHTML = $("tx-detail-time").innerHTML =
copyableHtml(isoStr) + " (" + escapeHtml(timeAgo(tx.timestamp)) + ")"; copyableHtml(isoStr) + " (" + escapeHtml(timeAgo(tx.timestamp)) + ")";
@@ -219,113 +171,11 @@ function render() {
el.onclick = () => { el.onclick = () => {
navigator.clipboard.writeText(el.dataset.copy); navigator.clipboard.writeText(el.dataset.copy);
showFlash("Copied!"); showFlash("Copied!");
flashCopyFeedback(el);
}; };
}); });
} }
function showDetailField(sectionId, contentId, value) { async function loadCalldata(txHash, toAddress) {
const section = $(sectionId);
const el = $(contentId);
if (!section || !el) return;
el.innerHTML = copyableHtml(value, "");
section.classList.remove("hidden");
}
function populateOnChainDetails(txData) {
// Block number
if (txData.block_number != null) {
const blockLink = `https://etherscan.io/block/${txData.block_number}`;
const blockSection = $("tx-detail-block-section");
const blockEl = $("tx-detail-block");
if (blockSection && blockEl) {
blockEl.innerHTML =
copyableHtml(String(txData.block_number), "") +
etherscanLinkHtml(blockLink);
blockSection.classList.remove("hidden");
}
}
// Nonce
if (txData.nonce != null) {
showDetailField(
"tx-detail-nonce-section",
"tx-detail-nonce",
String(txData.nonce),
);
}
// Transaction fee
const feeWei = txData.fee?.value || txData.tx_fee;
if (feeWei) {
const feeEth = formatEther(String(feeWei));
showDetailField(
"tx-detail-fee-section",
"tx-detail-fee",
feeEth + " ETH",
);
}
// Gas price
const gasPrice = txData.gas_price;
if (gasPrice) {
const gwei = formatUnits(String(gasPrice), "gwei");
showDetailField(
"tx-detail-gasprice-section",
"tx-detail-gasprice",
gwei + " Gwei",
);
}
// Gas used
const gasUsed = txData.gas_used;
if (gasUsed) {
showDetailField(
"tx-detail-gasused-section",
"tx-detail-gasused",
String(gasUsed),
);
}
// Show the on-chain details group if any child section is visible
const onchainGroup = $("tx-detail-onchain-group");
if (onchainGroup) {
const hasVisible = [
"tx-detail-block-section",
"tx-detail-nonce-section",
"tx-detail-fee-section",
"tx-detail-gasprice-section",
"tx-detail-gasused-section",
].some((id) => {
const el = $(id);
return el && !el.classList.contains("hidden");
});
if (hasVisible) {
onchainGroup.classList.remove("hidden");
}
}
// Bind copy handlers for newly added elements
for (const id of [
"tx-detail-block-section",
"tx-detail-nonce-section",
"tx-detail-fee-section",
"tx-detail-gasprice-section",
"tx-detail-gasused-section",
]) {
const section = $(id);
if (!section) continue;
section.querySelectorAll("[data-copy]").forEach((el) => {
el.onclick = () => {
navigator.clipboard.writeText(el.dataset.copy);
showFlash("Copied!");
flashCopyFeedback(el);
};
});
}
}
async function loadFullTxDetails(txHash, toAddress, isContractCall) {
const section = $("tx-detail-calldata-section"); const section = $("tx-detail-calldata-section");
const actionEl = $("tx-detail-calldata-action"); const actionEl = $("tx-detail-calldata-action");
const detailsEl = $("tx-detail-calldata-details"); const detailsEl = $("tx-detail-calldata-details");
@@ -340,10 +190,6 @@ async function loadFullTxDetails(txHash, toAddress, isContractCall) {
); );
if (!resp.ok) return; if (!resp.ok) return;
const txData = await resp.json(); const txData = await resp.json();
// Populate on-chain detail fields (block, nonce, gas, fee)
populateOnChainDetails(txData);
const inputData = txData.raw_input || txData.input || null; const inputData = txData.raw_input || txData.input || null;
if (!inputData || inputData === "0x") return; if (!inputData || inputData === "0x") return;
@@ -402,7 +248,6 @@ async function loadFullTxDetails(txHash, toAddress, isContractCall) {
el.onclick = () => { el.onclick = () => {
navigator.clipboard.writeText(el.dataset.copy); navigator.clipboard.writeText(el.dataset.copy);
showFlash("Copied!"); showFlash("Copied!");
flashCopyFeedback(el);
}; };
}); });
} }

View File

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

View File

@@ -20,6 +20,19 @@ 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,
@@ -28,4 +41,6 @@ module.exports = {
DEFAULT_BLOCKSCOUT_URL, DEFAULT_BLOCKSCOUT_URL,
BIP44_ETH_PATH, BIP44_ETH_PATH,
ERC20_ABI, ERC20_ABI,
BURN_ADDRESSES,
isBurnAddress,
}; };

View File

@@ -8,23 +8,666 @@
// 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 (e.g. address- // MistTrack alerts, and community incident reports.
// 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([
// Fake Uniswap phishing "0x0059b14e35dab1b4eee1e2926c7a5660da66f747",
"0x0000000000000000000000000000000000000001", "0x008f3db10374099a11ec263415cb88c952abeedc",
// Common address poisoning targets "0x00e01a648ff41346cdeb873182383333d2184dd1",
"0x0000000000000000000000000000000000000000", "0x0153775362c3071c1860e8dbfd53ccc82fa226f5",
// Known drainer contracts (examples — expand as needed) "0x02673ebfbfaed5891f6b14248b0e2753a8758fbf",
"0x00000000a991c429ee2ec6df19d40fe0c80088b8", "0x02f4a464eebb46a50dd087074d7a2cf3f5a3598b",
"0xae0ee0a63a2ce6baeeffe56e7714fb4efe48d419", "0x0310c4385311d225cbcaca26fefc9ab45d3eba3e",
"0x3ee18b2214aff97000d974cf647e7c347e8fa585", "0x0387cfc283ffcf25a4a6a61e832545bbeb7c8fda",
"0x55fe002aeff02f77364de339a1292923a15844b8", "0x03f034fb47965123ea4148e3147e2cfdc5b1f7a5",
"0x7f268357a8c2552623316e2562d90e642bb538e5", "0x052f585fa599bfb4bed290ff30c057627ccd8059",
"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

@@ -23,10 +23,8 @@ const DEFAULT_STATE = {
hideFraudContracts: true, hideFraudContracts: true,
hideDustTransactions: true, hideDustTransactions: true,
dustThresholdGwei: 100000, dustThresholdGwei: 100000,
utcTimestamps: false,
fraudContracts: [], fraudContracts: [],
tokenHolderCache: {}, tokenHolderCache: {},
theme: "system",
}; };
const state = { const state = {
@@ -55,10 +53,8 @@ async function saveState() {
hideFraudContracts: state.hideFraudContracts, hideFraudContracts: state.hideFraudContracts,
hideDustTransactions: state.hideDustTransactions, hideDustTransactions: state.hideDustTransactions,
dustThresholdGwei: state.dustThresholdGwei, dustThresholdGwei: state.dustThresholdGwei,
utcTimestamps: state.utcTimestamps,
fraudContracts: state.fraudContracts, fraudContracts: state.fraudContracts,
tokenHolderCache: state.tokenHolderCache, tokenHolderCache: state.tokenHolderCache,
theme: state.theme,
currentView: state.currentView, currentView: state.currentView,
selectedWallet: state.selectedWallet, selectedWallet: state.selectedWallet,
selectedAddress: state.selectedAddress, selectedAddress: state.selectedAddress,
@@ -112,11 +108,8 @@ async function loadState() {
saved.dustThresholdGwei !== undefined saved.dustThresholdGwei !== undefined
? saved.dustThresholdGwei ? saved.dustThresholdGwei
: 100000; : 100000;
state.utcTimestamps =
saved.utcTimestamps !== undefined ? saved.utcTimestamps : false;
state.fraudContracts = saved.fraudContracts || []; state.fraudContracts = saved.fraudContracts || [];
state.tokenHolderCache = saved.tokenHolderCache || {}; state.tokenHolderCache = saved.tokenHolderCache || {};
state.theme = saved.theme || "system";
state.currentView = saved.currentView || null; state.currentView = saved.currentView || null;
state.selectedWallet = state.selectedWallet =
saved.selectedWallet !== undefined ? saved.selectedWallet : null; saved.selectedWallet !== undefined ? saved.selectedWallet : null;

View File

@@ -153,38 +153,24 @@ async function fetchRecentTransactions(address, blockscoutUrl, count = 25) {
// When a token transfer shares a hash with a normal tx, the normal tx // When a token transfer shares a hash with a normal tx, the normal tx
// is the contract call (0 ETH) and the token transfer has the real // is the contract call (0 ETH) and the token transfer has the real
// amount and symbol. For contract calls (swaps), a single transaction // amount and symbol. A single transaction (e.g. a swap) can produce
// can produce multiple token transfers (input, intermediates, output). // multiple token transfers (one per token involved), so we key token
// We consolidate these into the original tx entry using the token // transfers by hash + contract address to keep all of them. We also
// transfer where the user *receives* tokens (the swap output), so // preserve contract-call metadata (direction, label, method) from the
// the transaction list shows the final result rather than confusing // matching normal tx so swaps display correctly.
// intermediate hops. We preserve the original tx's from/to so the
// user sees their own address, not a router or Permit2 contract.
for (const tt of ttJson.items || []) { for (const tt of ttJson.items || []) {
const parsed = parseTokenTransfer(tt, addrLower); const parsed = parseTokenTransfer(tt, addrLower);
const existing = txsByHash.get(parsed.hash); const existing = txsByHash.get(parsed.hash);
if (existing && existing.direction === "contract") { if (existing && existing.direction === "contract") {
// For contract calls (swaps), consolidate into the original parsed.direction = "contract";
// tx entry. Prefer the "received" transfer (swap output) parsed.directionLabel = existing.directionLabel;
// for the display amount. If no received transfer exists, parsed.isContractCall = true;
// fall back to the first "sent" transfer (swap input). parsed.method = existing.method;
const isReceived = parsed.direction === "received"; // Remove the bare-hash normal tx so it doesn't appear as a
const needsAmount = !existing.exactValue; // duplicate with empty value; token transfers replace it.
if (isReceived || needsAmount) { txsByHash.delete(parsed.hash);
existing.value = parsed.value;
existing.exactValue = parsed.exactValue;
existing.rawAmount = parsed.rawAmount;
existing.rawUnit = parsed.rawUnit;
existing.symbol = parsed.symbol;
existing.contractAddress = parsed.contractAddress;
existing.holders = parsed.holders;
} }
// Keep the original tx's from/to (the user's address and the // Use composite key so multiple token transfers per tx are kept.
// contract they called), not the token transfer's from/to
// which may be a router or Permit2 contract.
continue;
}
// Non-contract token transfers get their own entries.
const ttKey = parsed.hash + ":" + (parsed.contractAddress || ""); const ttKey = parsed.hash + ":" + (parsed.contractAddress || "");
txsByHash.set(ttKey, parsed); txsByHash.set(ttKey, parsed);
} }

View File

@@ -359,12 +359,9 @@ function decode(data, toAddress) {
const s = decodeV3SwapExactIn(inputs[i]); const s = decodeV3SwapExactIn(inputs[i]);
if (s) { if (s) {
if (!inputToken) inputToken = s.tokenIn; if (!inputToken) inputToken = s.tokenIn;
if (!outputToken) outputToken = s.tokenOut;
if (!inputAmount) inputAmount = s.amountIn; if (!inputAmount) inputAmount = s.amountIn;
// Always update output: in multi-step swaps (V3 → V4), if (!minOutput) minOutput = s.amountOutMin;
// the last swap step determines the final output token
// and minimum received amount.
outputToken = s.tokenOut;
minOutput = s.amountOutMin;
} }
} }
@@ -372,9 +369,9 @@ function decode(data, toAddress) {
const s = decodeV2SwapExactIn(inputs[i]); const s = decodeV2SwapExactIn(inputs[i]);
if (s) { if (s) {
if (!inputToken) inputToken = s.tokenIn; if (!inputToken) inputToken = s.tokenIn;
if (!outputToken) outputToken = s.tokenOut;
if (!inputAmount) inputAmount = s.amountIn; if (!inputAmount) inputAmount = s.amountIn;
outputToken = s.tokenOut; if (!minOutput) minOutput = s.amountOutMin;
minOutput = s.amountOutMin;
} }
} }
@@ -391,11 +388,12 @@ function decode(data, toAddress) {
const v4 = decodeV4Swap(inputs[i]); const v4 = decodeV4Swap(inputs[i]);
if (v4) { if (v4) {
if (!inputToken && v4.tokenIn) inputToken = v4.tokenIn; if (!inputToken && v4.tokenIn) inputToken = v4.tokenIn;
if (!outputToken && v4.tokenOut)
outputToken = v4.tokenOut;
if (!inputAmount && v4.amountIn) if (!inputAmount && v4.amountIn)
inputAmount = v4.amountIn; inputAmount = v4.amountIn;
// Always update output: last swap step wins if (!minOutput && v4.amountOutMin)
if (v4.tokenOut) outputToken = v4.tokenOut; minOutput = v4.amountOutMin;
if (v4.amountOutMin) minOutput = v4.amountOutMin;
} }
} }