Commit Graph

37 Commits

Author SHA1 Message Date
user
4157732f4b fix: preserve multiple token transfers per tx hash for address-token view
All checks were successful
check / check (push) Successful in 21s
A single transaction (e.g. a DEX swap) can produce multiple ERC-20
token transfers. The transaction merger was keyed by tx hash alone,
so only the last token transfer survived. This meant the address-token
view's contract-address filter often matched nothing.

Use a composite key (hash + contract address) so all token transfers
are preserved. Also remove the bare normal-tx entry when it gets
replaced by token transfers to avoid duplicates.

Closes #72
2026-02-28 12:08:47 -08:00
clawbot
9a7aa1f4fc fix: resolve token symbols from multiple sources (closes #51)
All checks were successful
check / check (push) Successful in 22s
When tokenBalances doesn't contain an entry for a token (e.g. before
balances are fetched), the symbol fell back to '?' in addressToken
and send views.

Add resolveSymbol() helper that checks tokenBalances → TOKEN_BY_ADDRESS
(known tokens) → trackedTokens → truncated address as last resort.

Fixes USDC and other known tokens showing '?' when balance data
hasn't loaded yet.
2026-02-28 11:37:38 -08:00
user
55346b484b fix: decode V4_SWAP command to resolve ERC20 token symbols
All checks were successful
check / check (push) Successful in 21s
The Uniswap Universal Router calldata decoder listed V4_SWAP (0x10) in
command names but never decoded its inner actions to extract token
addresses. This caused all V4 swaps (e.g. USDT→USDC) to display as
'Swap ETH → ETH' because tokenIn/tokenOut defaulted to null, which
tokenInfo() resolved as native ETH.

Added decodeV4Swap() which parses the V4 inner action bytes:
- SETTLE (0x0b) → extracts input token currency address
- TAKE (0x0e) → extracts output token currency address
- SWAP_EXACT_IN/OUT and their SINGLE variants → extracts tokens from
  pool keys and paths, plus amounts

These addresses are then resolved against the known token list
(TOKEN_BY_ADDRESS) to display correct symbols like USDT, USDC, etc.

Fixes #59
2026-02-28 11:16:10 -08:00
user
173d75c57a fix: fall back to known token list for symbol/name/decimals
All checks were successful
check / check (push) Successful in 22s
When a token's balance entry is missing or incomplete (e.g. not yet
fetched from Blockscout), the address-token view and send view now
fall back to the built-in known token list for symbol, name, and
decimals instead of showing '?'.

Also includes token name in the balance object returned by
fetchTokenBalances so the contract info well can display it.

Fixes #51
2026-02-28 08:44:09 -08:00
user
da428a3815 fix: preserve ENS names on lookup failure, add debug logging (closes #22)
All checks were successful
check / check (push) Successful in 22s
Two issues that could cause ENS names to disappear:

1. refreshBalances: on ENS lookup error, addr.ensName was set to null,
   wiping any previously resolved name. Now keeps the existing value
   on error — only overwrites on successful lookup.

2. ens.js cache: failed lookups were cached as null for 12 hours,
   preventing retries even after transient errors resolved. Now skips
   caching on failure so subsequent lookups retry immediately.

Added debug logging to ENS reverse lookups in refreshBalances.
2026-02-27 14:24:32 -08:00
85427e1fd4 Merge branch 'main' into fix/low-severity-security
Some checks failed
check / check (push) Failing after 13s
2026-02-27 23:08:40 +01:00
3fd3e30f44 fix: label swap methods as "Swap" in tx lists, remove unused variable
All checks were successful
check / check (push) Successful in 23s
- Map known DEX methods (execute, swap, multicall, etc.) to "Swap"
  label instead of raw method name like "Execute"
- Remove unused displayData variable in transactionDetail.js

Addresses review feedback on PR #10.
2026-02-27 12:31:25 -08:00
clawbot
76059c3674 fix: display swaps and contract calls correctly in tx history (closes #3)
- Preserve contract call metadata (direction, label, method) when token
  transfers merge with normal txs in fetchRecentTransactions
- Handle 'contract' direction in counterparty display for home and
  address detail list views
- Add decoded calldata display to transaction detail view, fetching
  raw input from Blockscout and using decodeCalldata from approval.js
- Show 'Unknown contract call' with raw hex for unrecognized calldata
- Export decodeCalldata from approval.js for reuse
2026-02-27 12:31:13 -08:00
909543e943 fix(L5): truncate token name/symbol from RPC responses
Limits token name to 64 chars and symbol to 12 chars to prevent
storage of excessively long values from malicious contracts.
2026-02-27 11:58:19 -08:00
aca8c4b2a7 Add name and url fields to all 512 tokens in tokenList.js
Names fetched from CoinGecko bulk API (100% coverage).
URLs sourced from ethereum-lists GitHub repo + manual curation
for major tokens. Some lesser-known tokens have empty URLs which
can be populated incrementally.
2026-02-27 17:51:08 +07:00
d67023e80d Show exact amounts and address titles in transaction detail
All checks were successful
check / check (push) Successful in 5s
- Display full-precision amount (no 4-decimal truncation) in the
  transaction detail view, with native quantity (wei/base units) below
- Both amount and native quantity are click-copyable
- Show wallet/address title above from/to when the address is ours
- Update README Display Consistency to document the exception
2026-02-27 16:09:44 +07:00
9e45c75d29 Implement personal_sign and eth_signTypedData_v4 message signing
All checks were successful
check / check (push) Successful in 4s
Replace stub error handlers with full approval flow for personal_sign,
eth_sign, eth_signTypedData_v4, and eth_signTypedData. Uses toolbar
popup only (no fallback window) and keeps sign approvals pending across
popup close/reopen cycles so the user can respond via the toolbar icon.
2026-02-27 15:27:14 +07:00
5af8a7873d Filter spam tokens from balance display
All checks were successful
check / check (push) Successful in 5s
Token balances from Blockscout are now filtered before display.
A token only appears if it meets at least one criterion:
- In the known 511-token list (by contract address)
- Explicitly tracked by the user (added via + Token)
- Has >= 1,000 holders on-chain

Also rejects tokens spoofing a known symbol from a different
contract address (same check used for transaction filtering).

This prevents airdropped spam tokens like "OpenClaw" from
appearing in the wallet without the user ever tracking them.
2026-02-27 13:02:05 +07:00
b64f9b56cc Show contract calls as "Approve USDT" instead of "0.0000 ETH"
All checks were successful
check / check (push) Successful in 17s
Contract interactions (approve, swap, etc.) now display the method
name and token symbol instead of the meaningless 0 ETH value.
Blockscout provides the method name and whether the target is a
contract — parseTx uses these plus TOKEN_BY_ADDRESS to produce
labels like "Approve USDT" or "Swap LINK".

Added directionLabel field to parsed transactions so renderers
don't need to know about the sent/received/contract distinction.

Also: clicking a transaction on the home screen now opens the
transaction detail view instead of navigating to the address
detail view.
2026-02-27 12:54:42 +07:00
55786d1350 Exclude contract calls from dust transaction filter
All checks were successful
check / check (push) Successful in 18s
The dust filter was hiding contract interactions (approve, transfer,
etc.) because they have 0 ETH value, which falls below the dust
threshold. Contract calls with 0 ETH are normal — only plain ETH
transfers should be checked against the dust threshold.

Also captures is_contract and method from Blockscout's transaction
response for future use in transaction display.
2026-02-27 12:52:06 +07:00
1ebc206201 Replace old 150-token list with 511-token tokenList.js
All checks were successful
check / check (push) Successful in 14s
Delete src/shared/tokens.js and migrate all consumers to
src/shared/tokenList.js which has 511 tokens (vs ~150) sourced
from CoinGecko with on-chain verified decimals.

- prices.js: getTopTokenPrices now from tokenList
- transactions.js: KNOWN_SYMBOLS now from tokenList (3.4x more
  symbols for spoof detection)
- send.js: KNOWN_SYMBOLS for token dropdown filtering
- approval.js: uses pre-built TOKEN_BY_ADDRESS map instead of
  constructing its own from TOKENS array
- addToken.js: uses getTopTokens(25) for quick-pick buttons
  (only top 25 shown, not all 511)
2026-02-27 12:39:41 +07:00
e7711274b3 Add tokenList.js with 511 ERC-20 tokens ranked by market cap
All checks were successful
check / check (push) Successful in 13s
New module with top Ethereum mainnet ERC-20 tokens sourced from
CoinGecko API (market cap ranking) with decimals verified on-chain
via eth_call to each contract. Addresses are EIP-55 checksummed.

Exports:
- TOKENS: full array ordered by market cap
- TOKEN_BY_ADDRESS: Map of lowercase address -> token info
- KNOWN_SYMBOLS: Map of uppercase symbol -> legitimate address
- getTopTokens(n): return first n tokens

This module is not yet integrated into the existing token.js — it
exists alongside it for now as a data source ready for integration.
2026-02-27 12:37:01 +07:00
d29273114b Fix ERC-20 transfers showing as 0 ETH due to dedup bug
All checks were successful
check / check (push) Successful in 14s
When sending an ERC-20 token, Blockscout returns the same tx hash
from both the /transactions endpoint (as a 0 ETH contract call) and
the /token-transfers endpoint (as the actual token transfer with
amount and symbol). The old dedup logic kept the 0 ETH version and
skipped the token transfer.

Now token transfers replace normal transactions with the same hash,
since the token transfer has the real amount, symbol, and contract
address.
2026-02-27 12:29:15 +07:00
2467dfd09c Centralize view state into app ctx with viewData persistence
All checks were successful
check / check (push) Successful in 17s
Creates a centralized transactionDetail.js view module, replacing
the duplicated showTxDetail/copyableHtml/blockieHtml/txDetailAddressHtml
code that was in both addressDetail.js and addressToken.js (~120 lines
removed). Transaction data is stored in state.viewData and persisted,
so the transaction detail view survives popup close/reopen.

Adds viewData to persisted state. Each view that needs data for
restore stores it in state.viewData before rendering. The ctx object
now has showTransactionDetail() alongside all other show methods.

Restorable views expanded to include: transaction (via viewData.tx),
success-tx (via viewData.hash/blockNumber), error-tx (via
viewData.message). txStatus.js split into show (sets data) + render
(reads data) for each screen, enabling restore.

Non-restorable views (send, confirm-tx, wait-tx, add-wallet,
import-key, add-token) fall back to the nearest parent since they
involve active form state or network polling.
2026-02-27 12:16:33 +07:00
034253077c Persist navigation state across popup close/reopen
All checks were successful
check / check (push) Successful in 17s
The current view, selected wallet, selected address, and selected
token are now saved to extension storage. When the popup reopens,
it restores to the last visited view instead of always returning
to the home screen.

Restorable views: main, address detail, address-token, receive,
settings. Non-restorable views (send, confirm, tx status, forms)
fall back to the nearest parent. Stored indices are validated
against current wallet data to handle stale references.

Also refactors receive view setup into a centralized receive.show()
function, eliminating duplicate QR/address/warning code from
addressDetail.js, addressToken.js, and home.js. Adds settings.show()
to centralize settings field population.
2026-02-27 12:12:07 +07:00
21fe854fa4 Add address-token detail view for per-token transaction filtering
All checks were successful
check / check (push) Successful in 17s
Clicking a token balance on the address detail view navigates to a
focused view showing only that token's transactions. Send pre-selects
and locks the token dropdown, Receive shows an ERC-20 warning for
non-ETH tokens, and all back buttons return to the correct parent view.
2026-02-27 11:26:59 +07:00
d24c10ca9c Debug-log every API request and response
Some checks failed
check / check (push) Has been cancelled
Add debugFetch wrapper in log.js that logs method, URL, and body on
request, and status code on response. Replace all fetch() calls
across balances, transactions, tokens, background RPC proxy, and
settings validation with debugFetch.
2026-02-26 15:40:09 +07:00
47e690f466 Show tracked tokens with zero balance on main and address pages
Some checks failed
check / check (push) Has been cancelled
Add showZeroBalanceTokens setting (default: on). When enabled,
balanceLinesForAddress merges state.trackedTokens with the address's
tokenBalances, showing 0.0000 lines for tracked tokens that have no
balance on that address. This gives users visibility into all tokens
they're watching across all addresses.
2026-02-26 15:37:39 +07:00
9a6d1f6255 Add dust transaction filter to catch native ETH poisoning
Some checks failed
check / check (push) Has been cancelled
Address poisoning attacks also use real native ETH dust transfers
(e.g. 1 gwei) from look-alike addresses. Token-level filters cannot
catch these. Add a configurable dust threshold (default 100,000 gwei
/ 0.0001 ETH) that hides transactions below the threshold from
history. The threshold is editable in Settings and the filter can be
disabled entirely. Document the specific attack tx in the README.
2026-02-26 15:29:48 +07:00
b5b4f75968 Add anti-poisoning filters for token transfers and send view
Some checks failed
check / check (push) Has been cancelled
Three layers of defense against address poisoning attacks:

1. Known symbol verification: tokens claiming a symbol from the
   hardcoded top-250 list (e.g. "ETH", "USDT") but from an
   unrecognized contract are identified as spoofs and always hidden.
   Their contract addresses are auto-added to the fraud blocklist.

2. Low-holder filtering: tokens with <1000 holders are hidden from
   both transaction history and the send token selector. Controlled
   by the "Hide tokens with fewer than 1,000 holders" setting.

3. Fraud contract blocklist: a persistent local list of detected
   fraud contract addresses. Transactions involving these contracts
   are hidden. Controlled by the "Hide transactions from detected
   fraud contracts" setting.

Both settings default to on and can be disabled in Settings.
Fetching and filtering are separated: fetchRecentTransactions returns
raw data, filterTransactions is a pure function applying heuristics.
Token holder counts are now passed through from the Blockscout API.
2026-02-26 15:22:11 +07:00
980fdda694 Scope site connection permissions per address
Some checks failed
check / check (push) Has been cancelled
allowedSites and deniedSites are now objects keyed by address instead
of flat arrays, so approving a site for one address no longer grants
access for all addresses. Old flat-array data is discarded on load.
Settings view collects unique hostnames across all addresses and
deleting removes the site from every address.
2026-02-26 03:54:52 +07:00
0d543288b2 Parallelize address scanning and unify address display formatting
Some checks failed
check / check (push) Has been cancelled
Scanning: check all gap-limit addresses in parallel per batch instead
of sequentially. For a wallet with 1 used address this reduces from
12 sequential RPC round-trips to 1 parallel batch + 1 small follow-up.

Display: add shared formatAddressHtml(address, ensName, maxLen) and
escapeHtml() to helpers.js. Use them in confirm-tx (was missing color
dot entirely) and approval view. Remove duplicate escapeHtml from
addressDetail.js.
2026-02-26 03:46:25 +07:00
56fa56bc8a Add site connection permissions, approval flow, and active address
Some checks failed
check / check (push) Has been cancelled
- Add activeAddress, allowedSites, deniedSites, rememberSiteChoice to
  persisted state
- Replace auto-connect with permission checks: allowed sites connect
  automatically, denied sites are rejected, unknown sites trigger an
  approval popup
- Add approval popup UI with hostname display, active address preview,
  remember checkbox, and allow/deny buttons
- Add ACTIVE/[select] indicator on address rows in the main view to
  set the active web3 address
- Add allowed/denied site list management in settings with delete buttons
- Broadcast accountsChanged to connected dapps when active address changes
- Handle approval window close as implicit denial
2026-02-26 03:40:34 +07:00
d28d5a5a51 Add address color dots and cached ENS reverse lookups
Some checks failed
check / check (push) Has been cancelled
Deterministic colored dots derived from address bytes (16-color palette)
displayed before every address. ENS reverse resolution for transaction
counterparties with 12-hour localStorage cache.
2026-02-26 03:26:52 +07:00
fbff44ade6 Fix tx amount display to 4 decimal places, add relative time to tx detail
Some checks failed
check / check (push) Has been cancelled
- Transaction values now use exactly 4 decimal places (was 6),
  matching balance display everywhere else
- Transaction detail view shows "2026-02-25 15:04:23 (23 days ago)"
  instead of just the ISO date
- Added Display Consistency policy to README
2026-02-26 03:19:42 +07:00
a15fb1a761 Fix Blockscout tx fetch: remove unsupported limit parameter
All checks were successful
check / check (push) Successful in 4s
Blockscout v2 API rejects the `limit` query parameter on
/transactions and /token-transfers endpoints (returns 422).
Remove it and slice results client-side instead.
2026-02-26 02:15:26 +07:00
3bd2b58543 Token auto-discovery, tx history, balance polling, EIP-6963, UI overhaul
All checks were successful
check / check (push) Successful in 14s
Major changes:
- Fetch token balances and tx history from Blockscout API (configurable)
- Remove manual token discovery (discoverTokens) in favor of Blockscout
- HD address gap scanning on mnemonic import
- Duplicate mnemonic detection on wallet add
- EIP-6963 multi-wallet discovery + selectedAddress updates in inpage
- Two-tier balance refresh: 10s while popup open, 60s background
- Fix $0.00 flash before prices load (return null when no prices)
- No-layout-shift: min-height on total value element
- Aligned balance columns (42ch address width, consistent USD column)
- All errors use flash messages instead of off-screen error divs
- Settings gear in global title bar, add-wallet moved to settings pane
- Settings wells with light grey background, configurable Blockscout URL
- Consistent "< Back" buttons top-left on all views
- Address titles (Address 1.1, 1.2, etc.) on main and detail views
- Send view shows current balance of selected asset
- Clickable affordance policy added to README
- Shortened mnemonic backup warning
- Fix broken background script constant imports
2026-02-26 02:13:39 +07:00
2b2137716c Add transaction confirmation screen and password modal
All checks were successful
check / check (push) Successful in 13s
New send flow: Send → Confirm → Password → Broadcast.

Send view: collects To (with ENS resolution), Amount, Token.
"Review" button advances to confirmation. No password field.

Confirm Transaction view: shows From, To (with ENS name),
Amount (with USD value), and runs pre-send checks:
- Scam address warning (checked against local blocklist)
- Self-send warning
- Insufficient balance error (disables Send button)

Password modal: full-screen overlay, appears only after user
clicks Send on the confirmation screen. Decrypts the wallet
secret, signs and broadcasts the transaction. Wrong password
is caught inline.

scamlist.js: hardcoded set of known scam/fraud addresses
(Tornado Cash sanctioned, drainer contracts, address
poisoning). Checked locally, no external API.
2026-02-25 18:55:42 +07:00
f50a2a0389 Refactor popup into shared modules, wire up real ERC-20 tokens
All checks were successful
check / check (push) Successful in 13s
Split popup/index.js (784 lines) into focused modules:
- shared/state.js: state management, storage persistence
- shared/wallet.js: mnemonic gen, HD derivation, signing
- shared/prices.js: price cache (5min TTL), USD formatting,
  value aggregation (address → wallet → total)
- shared/balances.js: ETH + ERC-20 balance cache (60s TTL),
  ENS lookup, token contract metadata lookup
- shared/vault.js: unchanged (libsodium encryption)
- shared/tokens.js: unchanged (token list + CoinDesk client)
- popup/index.js: view switching and event wiring only

Token tracking is now app-wide: trackedTokens stored in state,
balances fetched for all tracked tokens across all addresses.
Add Token now calls the real contract to read name/symbol/decimals.
Total portfolio value shown in 2x type on Home screen.
2026-02-25 18:48:44 +07:00
f2e22cadf2 Encrypt secrets with libsodium, password required to send
All checks were successful
check / check (push) Successful in 14s
vault.js: Argon2id key derivation + XSalsa20-Poly1305 encryption
via libsodium-wrappers-sumo. No raw crypto primitives.

Wallet creation now requires a password. The mnemonic or private
key is encrypted before storage — only the ciphertext blob
(salt, nonce, ciphertext) is persisted. The plaintext secret
is never stored.

Sending requires the password to decrypt the secret, derive
the signing key, and construct the transaction. Wrong password
is caught and reported.
2026-02-25 18:23:09 +07:00
097f90d7f8 Add token list module with CoinDesk price client
All checks were successful
check / check (push) Successful in 12s
tokens.js: ~150 ERC-20 tokens ordered by market cap with
getTopTokenSymbols(n) and getTopTokenPrices(n) (errors if n>30).
Price fetching uses CoinDesk CADLI API. Popup now shows USD
values next to ETH balances in wallet list and address detail.
Prices and balances fetched in parallel on popup open.
2026-02-25 17:35:27 +07:00
065f0eaa81 Add project scaffolding
All checks were successful
check / check (push) Successful in 10s
Makefile, Dockerfile, CI workflow, prettier config, manifests for
Chrome (MV3) and Firefox (MV2), source directory structure, and
minimal test suite. All checks pass.
2026-02-24 09:48:21 +07:00