Commit Graph
26 Commits
Author SHA1 Message Date
clawbot 671b4d3216 fix: each PERMIT2_TRANSFER_FROM shows the amount it moves and where to (closes #611)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
The swap decoder now gives three lines for every PERMIT2_TRANSFER_FROM
it reads but a move into the router, after the swap's Amount line: the
amount moved from the sender, the token with its address, and where it
goes (the V2 pair the next swap spends, the sender, or the address
itself on a line of its own). A move into the router keeps its Paid to
and Token moved into the router lines, which already give its amount.
Before, a move to another address or to the next V2 pair showed no
amount at all. Who counts as paid, and so Min. received against Min.
paid out, is unchanged. tokenMovedLine() moves to module level and
takes its label as an argument.

Model: opus-5-5
2026-10-20 00:23:57 +02:00
clawbot 30dca1454e fix: any token a swap leaves in the router keeps it from reading as received (closes #606)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
The decoder checked only the swap's output for being left in the router.
It now records every token a step pays the router, the WETH of a
WRAP_ETH and the rest of a V4 SETTLE taken to the router among them, and
any token still there after the last step gets a Paid to line naming it,
so the minimum reads Min. paid out. Steps that spend all the router holds
of a token count as spending it, and so do swap steps from the router
that together spend what a WRAP_ETH stated, when the router held no WETH
before it. A SETTLE of OPEN_DELTA from the router counts only in the
plain shape of Uniswap's own routes.

Model: opus-5-5
2026-10-19 22:18:32 +02:00
clawbot d6fb002656 fix: each router release names and reads its own commands (closes #592)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
Each Universal Router release now carries the command it runs for each
command type, by the name its pinned Commands.sol gives it, and the decoder
names and reads each step as that command instead of using one table for
every router. On UniversalRouterV1 and UniversalRouterV1_2_V2Support, 0x10
to 0x13 are NFT commands, such as Seaport: they are no longer read as V4
commands, so they draw no swap lines and their payouts count as unread.
UniversalRouterV1 has no BALANCE_CHECK_ERC20 and reverts on 0x0e, which is
now listed as Command 0x0e and sets no minimum. No release lets a V4 swap
fail, so the zero-minimum reading for a flagged one is gone.

Model: opus-5-5
2026-10-19 18:00:12 +02:00
clawbot 3db066413e fix: a balance check of another address sets no swap minimum (closes #600)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
BALANCE_CHECK_ERC20 takes (owner, token, minBalance), and every Universal
Router release that has it checks the owner's balance as written, without
map(), so address(1) and address(2) there are plain addresses. The decoder
read only the token and the minimum, so a check of another address's balance
read as Min. received. A check whose owner is not the transaction's sender now
sets neither Token Out nor the minimum, and its step reads "Balance Check (of
another address)"; one whose input does not decode reads "Balance Check (could
not be decoded)". A check of the sender keeps its reading, including the zero
reading for one the router lets fail.

Model: opus-5-5
2026-10-19 16:27:41 +02:00
clawbot c99772a520 fix: a step the router lets fail is marked and sets no minimum (closes #602)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
Each Universal Router release now carries the command types its dispatch()
can return a failure for instead of reverting, read from its source; only a
step of one of those with the allow-revert flag (0x80) set can fail while the
transaction goes on. Such a step is listed with "(allowed to fail)". A minimum
such a step states guarantees nothing, so it is read as zero: a
BALANCE_CHECK_ERC20 then keeps an earlier step's minimum, and 0x10 on
UniversalRouterV1 and UniversalRouterV1_2_V2Support, an NFT call there still
read as a V4 swap, states no minimum. Every swap and every payment of ETH or an
ERC-20 token succeeds or reverts the whole transaction, so the flag on them has
no effect.

Model: opus-5-5
2026-10-19 15:24:13 +02:00
clawbot 99269675b7 fix: the swap decoder reads who the router's transfer commands pay (closes #599)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
TRANSFER, PAY_PORTION, PAY_PORTION_FULL_PRECISION (0x07, on the 0x7f-mask
routers), PERMIT2_TRANSFER_FROM, ACROSS_V4_DEPOSIT_V3 (0x40) and V4
TAKE_PORTION were payouts the decoder did not read. Each recipient is now
read: the sender, the router, a hop into the next V2 pair, or another
address, which is named. Paying all the router holds of a token pays it out
as SWEEP does; a TRANSFER of CONTRACT_BALANCE of ETH reverts. A
PERMIT2_TRANSFER_FROM into the router shows the amount it moves and the
token's address, and a bridge deposit names each of its recipient and
depositor that is not the sender; either keeps the minimum from reading
received.

Model: opus-5-5
2026-10-19 14:03:52 +02:00
clawbot e0e8de8111 fix: the approval screen names the address a swap's output is paid to (closes #594)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run
The swap decoder read no recipient, so a swap paying its output to another
address still showed Min. received. It now reads the address each swap step,
SWEEP, WRAP_ETH, UNWRAP_WETH, V4 TAKE and TAKE_ALL pays. Min. received shows
only when some step pays the sender, none pays another address, none that may
pay one went unread, and the output does not stay in the router with no later
SWEEP or UNWRAP_WETH paying it out. A payment to the V2 pair the next V2 swap
spends as already paid is a hop, not a payout. Otherwise it reads Min. paid
out, with Paid to lines.

Model: opus-5-5
2026-10-19 11:33:54 +02:00
clawbot b5d2cedb57 fix: the swap decoder reads each command's type with its router's mask (closes #535)
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s
Each Universal Router address now carries the mask its own source reads a
command's type with: 0x1f for UniversalRouterV1, 0x3f for V1_2 and V2, 0x7f
for V2_1_1 and V2_1_2. The fixed 0x1f dropped the 0x20 bit, so
EXECUTE_SUB_PLAN (0x21) was listed as a V3 exact-out swap. A sub-plan step is
now listed as "Execute Sub-Plan (its steps are not shown)". Its commands are
not read and may spend from or pay the user wherever the step stands, so with
one the summary names no swap direction, Token In and Token Out read
"Unknown (could not be decoded)", and there is no Amount or Min. received.

Model: opus-5-5
2026-10-19 07:13:22 +02:00
clawbot 8b6c2b4df9 fix: the swap summary is shown only for a transaction sent to a Universal Router (closes #520)
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s
The Universal Router decoder recognised execute() by its selector alone, so
the same calldata sent to any address was described as a swap through the
router. It now keeps the router deployments Uniswap publishes for Ethereum
Mainnet and Sepolia, and decodes only a transaction sent to one of them on
its network, ignoring address case. TxApproval passes the transaction's
chain id, TransactionDetail the active network's.

Model: opus-5-5
2026-10-19 04:40:55 +02:00
clawbot b923b2c19c fix: a V4 step of chained swap actions shows what the user receives (closes #563)
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s
A V4 step's input side is what SETTLE or SETTLE_ALL names, else the one
currency its swap actions spend and never buy; its output side is what
TAKE or TAKE_ALL names, else the one they buy and never spend. Amount sums
the amountIn of the actions spending the input side, shown only when each
states one; Min. received likewise. An unread swap action leaves the sides
to SETTLE, SETTLE_ALL, TAKE and TAKE_ALL, with no figure. A step with a
swap action always sets the output side, sets the input side only if no
earlier step did, and leaves a side its rules cannot name unknown, with no
figure. SWAP_EXACT_OUT spends its first path entry.

Model: opus-5-5
2026-10-19 02:50:16 +02:00
clawbot bad6494e43 fix: a V4 swap action not in the deployed shape is shown as not decoded (closes #358)
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s
The four V4 swap actions are read only as the structs deployed on mainnet
and Sepolia. ethers read the newer v4-periphery structs, which add
minHopPriceX36, as the deployed ones, and the newer multi-hop exact-in
struct showed the offset of its hop-price array as the swap amount. A
decode now counts only when encoding it again gives back the same bytes.
A swap action that fails that marks its step "V4 Swap (could not be
decoded)", and a side its SETTLE or TAKE did not name reads "Unknown
(could not be decoded)". The wait and error screens no longer show the
ETH value as the amount of a swap whose amount was not read, unless the
swap is paid in native ETH.

Model: opus-5-5
2026-10-16 03:00:32 +02:00
clawbot 6127fd9432 fix: a swap deadline later than a date can hold is stated in words (closes #437)
check / check (push) Failing after 2s
e2e / e2e-chrome (push) Failing after 2s
e2e / e2e-firefox (push) Failing after 2s
decode() rendered the Deadline line with toISOString(), which throws on a
date past 275760-09-13, the last a JavaScript date can hold. A later
deadline, such as the uint256 maximum, therefore left the whole swap
undecoded, with nothing saying why. That line now reads
"After 275760-09-13 00:00:00 (no deadline in practice)".

Model: opus-5-5
2026-10-04 23:43:06 +02:00
clawbot f4a51e1679 fix: the swap decoder reads a V2 already-paid zero and a zero balance check as the router does (closes #415)
check / check (push) Failing after 2s
e2e / e2e-chrome (push) Failing after 1s
e2e / e2e-firefox (push) Failing after 1s
A V2 exact-in amountIn of zero is the router's ALREADY_PAID marker: an
earlier step sent the tokens to the pair and the swap spends all of them.
Amount showed 0.0000 for it; it now reads "Whatever an earlier step sent
to the pair (V2 already paid)", in the style of the V4 open delta line.

A BALANCE_CHECK_ERC20 passes whenever the balance is at least minBalance,
so a zero one guarantees nothing. It now sets the output side only when
that side holds no minimum at the point the check is reached; a nonzero
one sets the output side as before.

README's Display Consistency text and TODO.md are updated to match.

Model: opus-5-5
2026-10-04 23:09:05 +02:00
clawbot a68f30c480 fix: decode Uniswap V2 exact-out swaps, input amount shown as a maximum (closes #283)
check / check (push) Failing after 2s
e2e / e2e-chrome (push) Failing after 2s
e2e / e2e-firefox (push) Failing after 2s
decode() had no arm for Universal Router command 0x09, so the approval
screen showed no token or amount for it. It now takes the path's first
token and amountInMax as the input side, the last token and amountOut as
the output side. With such a step, the Amount figure reads "Up to
<amount>" whichever step set it, on the approval, wait, success and error
screens, unless it reads "Unlimited", as an unbounded PERMIT2_PERMIT does,
or "All available (V4 open delta)". In every swap, UNWRAP_WETH makes
Token Out ETH only when the output side is WETH, on mainnet or Sepolia, or
no step set it; otherwise the output keeps its own token and figure.
decodeV2SwapExactOut() loses its eslint-disable comment.

Model: opus-5-5
2026-10-04 15:09:09 +02:00
clawbot 2fe6447625 fix: name a tracked or explorer-known token instead of "Unknown token" (closes #323)
check / check (push) Failing after 0s
e2e / e2e-firefox (push) Failing after 0s
e2e / e2e-chrome (push) Failing after 1m27s
The approval and transaction-status screens read a token's scale from the
bundled list, the tokens the user tracks, then the block explorer, but read
its symbol from the bundled list alone. A token the user added by hand was
scaled correctly yet labelled "Unknown token", and a non-bundled ERC-20 was
carried onto the wait screen as ETH.

resolveTokenSymbol() now draws the symbol through the same sources and
precedence as the scale, and the ERC-20 and Uniswap swap lines both use it. A
tracked or explorer-reported name stays subject to the spoof rule, so it
cannot claim a bundled or native ticker.

Folds in #354.

Model: opus-4-8
Co-authored-by: clawbot <clawbot@noreply.example.org>
2026-09-21 21:28:06 +02:00
clawbot 75a5fa9891 harden: gate swap amounts on presence, not truthiness, so a figure always matches its token (closes #359)
check / check (push) Successful in 32s
e2e / e2e-chrome (push) Successful in 1m45s
e2e / e2e-firefox (push) Successful in 29s
Token and amount were gated on truthiness and gated independently. An address is never falsy once set but 0n is, so a hop supplying a zero amount fixed the token permanently while leaving the amount open, and the next hop's figure was rendered against the first hop's token at that token's scale -- 0.5 WETH shown as 500000000000.0000 USDT. Nine defective gates are now presence checks, and fourteen address gates were converted defensively so a sixth instance of this class cannot grow.

Zero is not one thing. Established from v4-periphery: OPEN_DELTA is 0 and V4Router substitutes the full credit unconditionally, so a zero V4 exact-in amount means "swap the whole balance" and now reads "All available (V4 open delta)" rather than 0.0000, which would assert the exact inverse. amountOutMinimum gets no such mapping and universal-router special-cases only CONTRACT_BALANCE, so a zero minimum and a zero V2/V3 amount stay literal -- a zero floor reads "None (no minimum guaranteed)".

closes #364
2026-08-23 20:45:58 +02:00
clawbot c9ebac822a harden: state an undetermined swap input token as undetermined, not as ETH (closes #357)
check / check (push) Successful in 33s
e2e / e2e-chrome (push) Successful in 1m45s
e2e / e2e-firefox (push) Successful in 31s
tokenInfo(null) yielded "ETH (native)", so a swap whose input token the calldata never named was asserted to the user as ETH. The determination established for the output side in #353 applies unchanged: Currency is a value type over address, so native ETH arrives as the truthy zero-address string and WRAP_ETH sets it explicitly -- null can only mean undetermined.

The rule now lives in tokenInfo() itself rather than at each call site, so the redundant output-side guard added by #356 is removed; both sides read one UNNAMED_CURRENCY constant and cannot drift. Verified by execution that a genuine native-ETH input still renders as ETH, including via WRAP_ETH and a V4 zero-address PoolKey, and that the real mainnet output-side fixture is unchanged.
2026-08-23 20:23:05 +02:00
clawbot 28a527295a harden: state an undetermined V4 output token as undetermined, not as ETH (closes #353)
check / check (push) Successful in 34s
e2e / e2e-chrome (push) Successful in 1m47s
e2e / e2e-firefox (push) Successful in 31s
A V4 swap could set minOutput while leaving outputToken null, and tokenInfo(null) returned ETH at 18 decimals, so the approval screen named an asset the calldata never stated. Established from the V4 encoding that this is wrong rather than protocol semantics: Currency is a user-defined value type over address, so native ETH arrives as the truthy zero-address string and is already mapped to ETH, and an UNWRAP_WETH output is caught earlier — both genuine native-ETH routes are covered without null, so null can only mean no sub-action yielded a currency. A genuine native-ETH V4 swap still renders as ETH, pinned by test against the real mainnet fixture.

The same gate also let a later step overwrite minOutput while an earlier hop's outputToken stayed on screen, showing a figure against the wrong token. Fixed together as the same two lines and the same defect.
2026-08-23 18:13:20 +02:00
clawbot 36bc6bee0e fix: always name a swap's output token, by address when no symbol is known (closes #346)
check / check (push) Successful in 30s
e2e / e2e-chrome (push) Successful in 1m45s
e2e / e2e-firefox (push) Successful in 29s
The Token Out line was pushed only when a symbol was known, so a swap to a token absent from the bundled list showed a Min. received figure with no statement of which token was being received. It now falls back to the address, mirroring the Token In precedent in the same file. Composes with the unknown-scale refusal from #340: the address names the token while the figure declares its scale unknown. Native ETH out, ETH out via UNWRAP_WETH and bundled tokens render exactly as before.
2026-08-23 16:31:02 +02:00
clawbot 43784cab3f harden: resolve or refuse the swap token scale instead of guessing 18 (closes #340)
check / check (push) Successful in 29s
e2e / e2e-chrome (push) Successful in 1m47s
e2e / e2e-firefox (push) Successful in 31s
tokenInfo() returned decimals 18 for any token absent from the bundled list, so the swap approval line rendered a real 1000.00 of a 6-decimal token as 0.000000000001. The scale is now resolved from what the wallet already holds (bundled list, tracked tokens, explorer-reported decimals) or refused outright, matching the rule set for the ERC-20 path in #306. A refusal reuses unknownDecimalsAmount(), so it reads as "base units (decimals unknown)" with no decimal point and no symbol, and the same string propagates to rawValue so no downstream screen can render a figure the approval screen refused. No new network call on the approval path. Verified green on all three CI contexts: check, e2e-chrome, e2e-firefox.
2026-08-23 16:20:22 +02:00
clawbot 669c443bf9 fix: never render a nonzero approval amount as zero (closes #322)
check / check (push) Successful in 31s
e2e / e2e-chrome (push) Successful in 1m12s
e2e / e2e-firefox (push) Successful in 22s
An amount below the 4-decimal display floor now extends to its first significant digit on the approval and confirmation screens, instead of stating a real transfer, allowance or swap Min. received as 0.0000. The rule had been implemented three times; all three now share src/shared/amountDisplay.js, which holds the plain truncation and the floored variant side by side. History and balance lists keep the unfloored rule, pinned by test.
2026-08-23 15:43:04 +02:00
clawbot 47bf38644d build: add ESLint to script/lint and containerize linting (closes #152)
check / check (push) Successful in 39s
e2e / e2e-chrome (push) Successful in 48s
e2e / e2e-firefox (push) Successful in 24s
2026-08-17 09:10:03 +02:00
user 5dfc6e332b fix: correct swap display — output token, min received, from address, and amount
check / check (push) Successful in 23s
Fix multi-step Uniswap swap decoding and transaction display:

1. uniswap.js: In multi-step swaps (e.g. V3 → V4), the output token and
   min received amount now come from the LAST swap step instead of the
   first. Previously, an intermediate token's amountOutMin (18 decimals)
   was formatted with the final token's decimals (6), producing
   astronomically wrong 'Min. received' values (~2 trillion USDC).

2. transactions.js: Contract call token transfers (swaps) are now
   consolidated into the original transaction entry instead of creating
   separate entries per token transfer. This prevents intermediate hop
   tokens (e.g. USDS in a USDT→USDS→USDC route) from appearing as the
   transaction's Amount. The received token (swap output) is preferred.

3. transactions.js: The original transaction's from/to addresses are
   preserved for contract calls, so the user sees their own address
   instead of a router or Permit2 contract address.

closes #127
2026-03-01 05:52:10 -08:00
user 2fbed343db fix: make success-tx addresses clickable, fix USDT ETH bug, nest decoded details (closes #80)
check / check (push) Successful in 22s
- Add underline + click-to-copy (data-copy) to addresses in toAddressHtml()
  so they match the style used everywhere else in the extension
- Fix 'USDT ETH' display: add rawValue to Uniswap decoder Amount details
  and extract Token In info for proper symbol resolution in approval.js
- Hide redundant top-level Amount/To when decoded details are present
  (they already show the same info inside the decoded section)
- Wrap decoded calldata details in a bordered well for visual separation
2026-02-28 13:36:19 -08:00
user 55346b484b fix: decode V4_SWAP command to resolve ERC20 token symbols
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
sneak 9e45c75d29 Implement personal_sign and eth_signTypedData_v4 message signing
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