fix: correct swap display — output token, min received, from address, and amount #128

Merged
sneak merged 2 commits from fix/issue-127-swap-amount-display into main 2026-03-01 16:19:42 +01:00
Collaborator

Fix multi-step Uniswap swap decoding and transaction display.

Changes

1. uniswap.js — Last swap step wins for output

In multi-step swaps (e.g. V3 Swap → V4 Swap), 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 for a $2 swap).

2. transactions.js — Consolidate swap token transfers

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 for the display amount.

3. transactions.js — Preserve original from/to

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.

Issues Fixed

  • Amount showed "USDS" (intermediate token) instead of the swap output token
  • Min. received showed ~1.99 trillion USDC (18-decimal value formatted as 6-decimal)
  • From address showed Permit2 contract instead of the user's address
  • Amount had 18 decimal places for tokens with 6 decimals

closes #127

Fix multi-step Uniswap swap decoding and transaction display. ## Changes ### 1. uniswap.js — Last swap step wins for output In multi-step swaps (e.g. V3 Swap → V4 Swap), 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 for a $2 swap). ### 2. transactions.js — Consolidate swap token transfers 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 for the display amount. ### 3. transactions.js — Preserve original from/to 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. ## Issues Fixed - Amount showed "USDS" (intermediate token) instead of the swap output token - Min. received showed ~1.99 trillion USDC (18-decimal value formatted as 6-decimal) - From address showed Permit2 contract instead of the user's address - Amount had 18 decimal places for tokens with 6 decimals closes #127
clawbot added 1 commit 2026-03-01 14:52:24 +01:00
fix: correct swap display — output token, min received, from address, and amount
All checks were successful
check / check (push) Successful in 23s
5dfc6e332b
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
clawbot added the
bot
needs-review
labels 2026-03-01 14:52:36 +01:00
Author
Collaborator

Summary of Changes

This PR fixes four display bugs in the transaction detail view for multi-step Uniswap swaps (e.g. USDT → USDS → USDC via V3 Swap → V4 Swap):

Files Changed

src/shared/uniswap.js — Fixed output token resolution for multi-step swaps

  • Changed outputToken and minOutput from first-wins to last-wins semantics in decode()
  • In a V3→V4 pipeline, the V3 step's intermediate output (e.g. USDS with 18 decimals) was being used as the final output. Now the last swap step's output token and min amount are used.
  • This fixes "Min. received: 1990801414851.1015 USDC" → correct ~1.99 USDC value

src/shared/transactions.js — Fixed token transfer merging for contract calls

  • Contract call token transfers are now consolidated into the original tx entry instead of spawning separate list entries per token hop
  • The received token transfer (swap output) is preferred for the display Amount
  • Original tx from/to are preserved so the user sees their own address, not Permit2/router contracts
  • This fixes Amount showing "USDS" (intermediate) and From showing the Permit2 contract

Root Cause

Multi-step swaps produce multiple token transfers (input → intermediate → output). The previous code created separate transaction list entries for each, and the Uniswap decoder used first-wins for output fields, causing the intermediate step's values to be displayed with the final token's decimal formatting.

## Summary of Changes This PR fixes four display bugs in the transaction detail view for multi-step Uniswap swaps (e.g. USDT → USDS → USDC via V3 Swap → V4 Swap): ### Files Changed **`src/shared/uniswap.js`** — Fixed output token resolution for multi-step swaps - Changed `outputToken` and `minOutput` from first-wins to last-wins semantics in `decode()` - In a V3→V4 pipeline, the V3 step's intermediate output (e.g. USDS with 18 decimals) was being used as the final output. Now the last swap step's output token and min amount are used. - This fixes "Min. received: 1990801414851.1015 USDC" → correct ~1.99 USDC value **`src/shared/transactions.js`** — Fixed token transfer merging for contract calls - Contract call token transfers are now consolidated into the original tx entry instead of spawning separate list entries per token hop - The **received** token transfer (swap output) is preferred for the display Amount - Original tx `from`/`to` are preserved so the user sees their own address, not Permit2/router contracts - This fixes Amount showing "USDS" (intermediate) and From showing the Permit2 contract ### Root Cause Multi-step swaps produce multiple token transfers (input → intermediate → output). The previous code created separate transaction list entries for each, and the Uniswap decoder used first-wins for output fields, causing the intermediate step's values to be displayed with the final token's decimal formatting.
clawbot self-assigned this 2026-03-01 14:55:39 +01:00
Author
Collaborator

Code Review — PASS

Reviewer: clawbot (independent review)

Analysis

uniswap.js — last-wins semantics: Correct. In multi-step swaps (V3→V4), the user's input comes from the first step and their output comes from the last step. The change correctly keeps inputToken/inputAmount as first-wins while making outputToken/minOutput last-wins. This works for:

  • Single-step swaps (one assignment, same result)
  • 2-step swaps (last step's output token and minOutput used — fixes the bug)
  • 3+ step swaps (still correct — last step is always the final output)
  • Failed decoding (values stay null, no regression)

transactions.js — consolidation: Correct. Contract call token transfers are merged into the original tx entry rather than spawning separate entries. The received transfer (swap output) is preferred for display amount via isReceived check, with fallback to sent transfer if no received exists. Original from/to preserved via continue (skips overwriting). Edge cases handled:

  • Single token transfer: merged into existing entry
  • No token transfers: original tx entry preserved unchanged
  • Multiple transfers: last received wins for display amount

All four reported issues addressed:

  1. Amount no longer shows intermediate token (USDS) — consolidated display uses received token
  2. Min. received now uses last step's amountOutMin with correct token decimals
  3. From address preserved from original tx (user's address, not Permit2)
  4. Decimal places correct — output token's own decimals used, not intermediate's

Scope: Clean — only 2 files changed, no CI/linter/test/Makefile modifications.
Docker build: passes
README consistency: No documentation changes needed — this is a bug fix, not a behavior change.
Security: Display correctness is critical for a wallet. This fix improves accuracy of displayed amounts and addresses, reducing risk of user confusion.

## Code Review — PASS ✅ **Reviewer**: clawbot (independent review) ### Analysis **uniswap.js — last-wins semantics**: Correct. In multi-step swaps (V3→V4), the user's input comes from the first step and their output comes from the last step. The change correctly keeps `inputToken`/`inputAmount` as first-wins while making `outputToken`/`minOutput` last-wins. This works for: - Single-step swaps (one assignment, same result) - 2-step swaps (last step's output token and minOutput used — fixes the bug) - 3+ step swaps (still correct — last step is always the final output) - Failed decoding (values stay null, no regression) **transactions.js — consolidation**: Correct. Contract call token transfers are merged into the original tx entry rather than spawning separate entries. The received transfer (swap output) is preferred for display amount via `isReceived` check, with fallback to sent transfer if no received exists. Original from/to preserved via `continue` (skips overwriting). Edge cases handled: - Single token transfer: merged into existing entry - No token transfers: original tx entry preserved unchanged - Multiple transfers: last received wins for display amount **All four reported issues addressed**: 1. ✅ Amount no longer shows intermediate token (USDS) — consolidated display uses received token 2. ✅ Min. received now uses last step's amountOutMin with correct token decimals 3. ✅ From address preserved from original tx (user's address, not Permit2) 4. ✅ Decimal places correct — output token's own decimals used, not intermediate's **Scope**: Clean — only 2 files changed, no CI/linter/test/Makefile modifications. **Docker build**: ✅ passes **README consistency**: No documentation changes needed — this is a bug fix, not a behavior change. **Security**: Display correctness is critical for a wallet. This fix improves accuracy of displayed amounts and addresses, reducing risk of user confusion.
clawbot added
merge-ready
and removed
bot
needs-review
labels 2026-03-01 14:57:40 +01:00
clawbot removed their assignment 2026-03-01 14:57:41 +01:00
sneak was assigned by clawbot 2026-03-01 14:57:41 +01:00
Author
Collaborator

Review: PASS

Reviewed the diff (2 files, +37/-21) against issue #127.

uniswap.js — "last swap step wins" for output

The change from "first wins" (if (!outputToken)) to "always overwrite" for outputToken and minOutput across V3, V2, and V4 swap decoders is correct. In a multi-step swap like V3→V4 (e.g. USDT→WETH→USDC), the first step's output is an intermediate token, not what the user receives. Taking the last step's output correctly resolves:

  • Wrong token symbol: intermediate token's amountOutMin (18 decimals) was being formatted with the final token's decimals (6), producing the ~2 trillion "Min. received" value.
  • Wrong output token: the intermediate hop token was displayed instead of the final output.

inputToken and inputAmount correctly retain "first wins" semantics (the first step has the actual input).

Minor note: V4 uses if (v4.amountOutMin) which would skip BigInt(0) (falsy), but this is pre-existing behavior and a zero-slippage swap is an extreme edge case — not introduced by this PR.

transactions.js — Consolidate swap token transfers

Instead of creating separate entries per token transfer (which caused intermediate hop tokens like USDS to appear as the transaction Amount), contract call token transfers are now consolidated into the original tx entry. The logic correctly:

  • Prefers "received" transfers (swap output) for display via isReceived check
  • Falls back to the first "sent" transfer if no received transfer exists yet via needsAmount
  • Preserves the original tx's from/to addresses (user's address and called contract), fixing the Permit2 address display bug
  • Uses continue to skip adding duplicates

Both orderings of token transfers (sent-first or received-first) produce the correct result since received always overwrites.

Scope check

  • No Makefile, linter config, or test modifications ✓
  • No unrelated changes ✓
  • docker build . (includes make check) passes ✓
  • Branch is not behind main — no rebase needed ✓

All four bugs from #127 are addressed. Marking merge-ready.

## Review: PASS ✅ Reviewed the diff (2 files, +37/-21) against issue #127. ### uniswap.js — "last swap step wins" for output The change from "first wins" (`if (!outputToken)`) to "always overwrite" for `outputToken` and `minOutput` across V3, V2, and V4 swap decoders is correct. In a multi-step swap like V3→V4 (e.g. USDT→WETH→USDC), the first step's output is an intermediate token, not what the user receives. Taking the last step's output correctly resolves: - **Wrong token symbol**: intermediate token's `amountOutMin` (18 decimals) was being formatted with the final token's decimals (6), producing the ~2 trillion "Min. received" value. - **Wrong output token**: the intermediate hop token was displayed instead of the final output. `inputToken` and `inputAmount` correctly retain "first wins" semantics (the first step has the actual input). Minor note: V4 uses `if (v4.amountOutMin)` which would skip `BigInt(0)` (falsy), but this is pre-existing behavior and a zero-slippage swap is an extreme edge case — not introduced by this PR. ### transactions.js — Consolidate swap token transfers Instead of creating separate entries per token transfer (which caused intermediate hop tokens like USDS to appear as the transaction Amount), contract call token transfers are now consolidated into the original tx entry. The logic correctly: - Prefers "received" transfers (swap output) for display via `isReceived` check - Falls back to the first "sent" transfer if no received transfer exists yet via `needsAmount` - Preserves the original tx's `from`/`to` addresses (user's address and called contract), fixing the Permit2 address display bug - Uses `continue` to skip adding duplicates Both orderings of token transfers (sent-first or received-first) produce the correct result since received always overwrites. ### Scope check - No Makefile, linter config, or test modifications ✓ - No unrelated changes ✓ - `docker build .` (includes `make check`) passes ✓ - Branch is not behind main — no rebase needed ✓ All four bugs from #127 are addressed. Marking `merge-ready`.
sneak added 1 commit 2026-03-01 16:19:28 +01:00
Merge branch 'main' into fix/issue-127-swap-amount-display
All checks were successful
check / check (push) Successful in 21s
f65764d501
sneak merged commit 6aeab54e8c into main 2026-03-01 16:19:42 +01:00
sneak deleted branch fix/issue-127-swap-amount-display 2026-03-01 16:19:42 +01:00
Sign in to join this conversation.
No reviewers
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: sneak/AutistMask#128
No description provided.