diff --git a/src/shared/transactions.js b/src/shared/transactions.js index ad45698..5f528eb 100644 --- a/src/shared/transactions.js +++ b/src/shared/transactions.js @@ -153,24 +153,38 @@ async function fetchRecentTransactions(address, blockscoutUrl, count = 25) { // 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 - // amount and symbol. A single transaction (e.g. a swap) can produce - // multiple token transfers (one per token involved), so we key token - // transfers by hash + contract address to keep all of them. We also - // preserve contract-call metadata (direction, label, method) from the - // matching normal tx so swaps display correctly. + // amount and symbol. For contract calls (swaps), a single transaction + // can produce multiple token transfers (input, intermediates, output). + // We consolidate these into the original tx entry using the token + // transfer where the user *receives* tokens (the swap output), so + // the transaction list shows the final result rather than confusing + // 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 || []) { const parsed = parseTokenTransfer(tt, addrLower); const existing = txsByHash.get(parsed.hash); if (existing && existing.direction === "contract") { - parsed.direction = "contract"; - parsed.directionLabel = existing.directionLabel; - parsed.isContractCall = true; - parsed.method = existing.method; - // Remove the bare-hash normal tx so it doesn't appear as a - // duplicate with empty value; token transfers replace it. - txsByHash.delete(parsed.hash); + // For contract calls (swaps), consolidate into the original + // tx entry. Prefer the "received" transfer (swap output) + // for the display amount. If no received transfer exists, + // fall back to the first "sent" transfer (swap input). + const isReceived = parsed.direction === "received"; + const needsAmount = !existing.exactValue; + if (isReceived || needsAmount) { + 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 + // contract they called), not the token transfer's from/to + // which may be a router or Permit2 contract. + continue; } - // Use composite key so multiple token transfers per tx are kept. + // Non-contract token transfers get their own entries. const ttKey = parsed.hash + ":" + (parsed.contractAddress || ""); txsByHash.set(ttKey, parsed); } diff --git a/src/shared/uniswap.js b/src/shared/uniswap.js index 1374477..55ecb2c 100644 --- a/src/shared/uniswap.js +++ b/src/shared/uniswap.js @@ -359,9 +359,12 @@ function decode(data, toAddress) { const s = decodeV3SwapExactIn(inputs[i]); if (s) { if (!inputToken) inputToken = s.tokenIn; - if (!outputToken) outputToken = s.tokenOut; if (!inputAmount) inputAmount = s.amountIn; - if (!minOutput) minOutput = s.amountOutMin; + // Always update output: in multi-step swaps (V3 → V4), + // the last swap step determines the final output token + // and minimum received amount. + outputToken = s.tokenOut; + minOutput = s.amountOutMin; } } @@ -369,9 +372,9 @@ function decode(data, toAddress) { const s = decodeV2SwapExactIn(inputs[i]); if (s) { if (!inputToken) inputToken = s.tokenIn; - if (!outputToken) outputToken = s.tokenOut; if (!inputAmount) inputAmount = s.amountIn; - if (!minOutput) minOutput = s.amountOutMin; + outputToken = s.tokenOut; + minOutput = s.amountOutMin; } } @@ -388,12 +391,11 @@ function decode(data, toAddress) { const v4 = decodeV4Swap(inputs[i]); if (v4) { if (!inputToken && v4.tokenIn) inputToken = v4.tokenIn; - if (!outputToken && v4.tokenOut) - outputToken = v4.tokenOut; if (!inputAmount && v4.amountIn) inputAmount = v4.amountIn; - if (!minOutput && v4.amountOutMin) - minOutput = v4.amountOutMin; + // Always update output: last swap step wins + if (v4.tokenOut) outputToken = v4.tokenOut; + if (v4.amountOutMin) minOutput = v4.amountOutMin; } }