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).
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
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:
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
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
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:
✅ Amount no longer shows intermediate token (USDS) — consolidated display uses received token
✅ Min. received now uses last step's amountOutMin with correct token decimals
✅ From address preserved from original tx (user's address, not Permit2)
✅ 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.
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.
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`.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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
closes #127
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 swapsoutputTokenandminOutputfrom first-wins to last-wins semantics indecode()src/shared/transactions.js— Fixed token transfer merging for contract callsfrom/toare preserved so the user sees their own address, not Permit2/router contractsRoot 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.
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/inputAmountas first-wins while makingoutputToken/minOutputlast-wins. This works for: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
isReceivedcheck, with fallback to sent transfer if no received exists. Original from/to preserved viacontinue(skips overwriting). Edge cases handled:All four reported issues addressed:
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.
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" foroutputTokenandminOutputacross 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:amountOutMin(18 decimals) was being formatted with the final token's decimals (6), producing the ~2 trillion "Min. received" value.inputTokenandinputAmountcorrectly retain "first wins" semantics (the first step has the actual input).Minor note: V4 uses
if (v4.amountOutMin)which would skipBigInt(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:
isReceivedcheckneedsAmountfrom/toaddresses (user's address and called contract), fixing the Permit2 address display bugcontinueto skip adding duplicatesBoth orderings of token transfers (sent-first or received-first) produce the correct result since received always overwrites.
Scope check
docker build .(includesmake check) passes ✓All four bugs from #127 are addressed. Marking
merge-ready.