fix: decode V4_SWAP command to resolve ERC20 token symbols
All checks were successful
check / check (push) Successful in 21s
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
This commit is contained in:
@@ -161,6 +161,157 @@ function decodeWrapEth(input) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// V4 inner action IDs
|
||||||
|
const V4_SWAP_EXACT_IN_SINGLE = 0x06;
|
||||||
|
const V4_SWAP_EXACT_IN = 0x07;
|
||||||
|
const V4_SWAP_EXACT_OUT_SINGLE = 0x08;
|
||||||
|
const V4_SWAP_EXACT_OUT = 0x09;
|
||||||
|
const V4_SETTLE = 0x0b;
|
||||||
|
const V4_TAKE = 0x0e;
|
||||||
|
|
||||||
|
// Decode V4_SWAP (command 0x10) input bytes.
|
||||||
|
// The input is ABI-encoded as (bytes actions, bytes[] params).
|
||||||
|
// We extract token addresses from SETTLE (input) and TAKE (output) sub-actions,
|
||||||
|
// and swap amounts from the swap sub-actions.
|
||||||
|
function decodeV4Swap(input) {
|
||||||
|
try {
|
||||||
|
const d = coder.decode(["bytes", "bytes[]"], input);
|
||||||
|
const actions = getBytes(d[0]);
|
||||||
|
const params = d[1];
|
||||||
|
|
||||||
|
let settleToken = null;
|
||||||
|
let takeToken = null;
|
||||||
|
let amountIn = null;
|
||||||
|
let amountOutMin = null;
|
||||||
|
|
||||||
|
for (let i = 0; i < actions.length; i++) {
|
||||||
|
const actionId = actions[i];
|
||||||
|
try {
|
||||||
|
if (actionId === V4_SETTLE) {
|
||||||
|
// SETTLE: (address currency, uint256 maxAmount, bool payerIsUser)
|
||||||
|
const s = coder.decode(
|
||||||
|
["address", "uint256", "bool"],
|
||||||
|
params[i],
|
||||||
|
);
|
||||||
|
settleToken = s[0];
|
||||||
|
} else if (actionId === V4_TAKE) {
|
||||||
|
// TAKE: (address currency, address recipient, uint256 amount)
|
||||||
|
const t = coder.decode(
|
||||||
|
["address", "address", "uint256"],
|
||||||
|
params[i],
|
||||||
|
);
|
||||||
|
takeToken = t[0];
|
||||||
|
} else if (
|
||||||
|
actionId === V4_SWAP_EXACT_IN ||
|
||||||
|
actionId === V4_SWAP_EXACT_IN_SINGLE
|
||||||
|
) {
|
||||||
|
// Extract amounts from exact-in swap actions
|
||||||
|
if (actionId === V4_SWAP_EXACT_IN) {
|
||||||
|
// ExactInputParams: (address currencyIn,
|
||||||
|
// tuple(address,uint24,int24,address,bytes)[] path,
|
||||||
|
// uint128 amountIn, uint128 amountOutMin)
|
||||||
|
try {
|
||||||
|
const s = coder.decode(
|
||||||
|
[
|
||||||
|
"tuple(address,tuple(address,uint24,int24,address,bytes)[],uint128,uint128)",
|
||||||
|
],
|
||||||
|
params[i],
|
||||||
|
);
|
||||||
|
if (!settleToken) settleToken = s[0][0];
|
||||||
|
const path = s[0][1];
|
||||||
|
if (path.length > 0 && !takeToken) {
|
||||||
|
takeToken = path[path.length - 1][0];
|
||||||
|
}
|
||||||
|
if (!amountIn) amountIn = s[0][2];
|
||||||
|
if (!amountOutMin) amountOutMin = s[0][3];
|
||||||
|
} catch {
|
||||||
|
// Fall through — SETTLE/TAKE will provide tokens
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ExactInputSingleParams: (tuple(address,address,uint24,int24,address) poolKey,
|
||||||
|
// bool zeroForOne, uint128 amountIn, uint128 amountOutMin, bytes hookData)
|
||||||
|
try {
|
||||||
|
const s = coder.decode(
|
||||||
|
[
|
||||||
|
"tuple(tuple(address,address,uint24,int24,address),bool,uint128,uint128,bytes)",
|
||||||
|
],
|
||||||
|
params[i],
|
||||||
|
);
|
||||||
|
const poolKey = s[0][0];
|
||||||
|
const zeroForOne = s[0][1];
|
||||||
|
if (!settleToken)
|
||||||
|
settleToken = zeroForOne
|
||||||
|
? poolKey[0]
|
||||||
|
: poolKey[1];
|
||||||
|
if (!takeToken)
|
||||||
|
takeToken = zeroForOne
|
||||||
|
? poolKey[1]
|
||||||
|
: poolKey[0];
|
||||||
|
if (!amountIn) amountIn = s[0][2];
|
||||||
|
if (!amountOutMin) amountOutMin = s[0][3];
|
||||||
|
} catch {
|
||||||
|
// Fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
actionId === V4_SWAP_EXACT_OUT ||
|
||||||
|
actionId === V4_SWAP_EXACT_OUT_SINGLE
|
||||||
|
) {
|
||||||
|
if (actionId === V4_SWAP_EXACT_OUT) {
|
||||||
|
try {
|
||||||
|
const s = coder.decode(
|
||||||
|
[
|
||||||
|
"tuple(address,tuple(address,uint24,int24,address,bytes)[],uint128,uint128)",
|
||||||
|
],
|
||||||
|
params[i],
|
||||||
|
);
|
||||||
|
if (!takeToken) takeToken = s[0][0];
|
||||||
|
const path = s[0][1];
|
||||||
|
if (path.length > 0 && !settleToken) {
|
||||||
|
settleToken = path[path.length - 1][0];
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Fall through
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const s = coder.decode(
|
||||||
|
[
|
||||||
|
"tuple(tuple(address,address,uint24,int24,address),bool,uint128,uint128,bytes)",
|
||||||
|
],
|
||||||
|
params[i],
|
||||||
|
);
|
||||||
|
const poolKey = s[0][0];
|
||||||
|
const zeroForOne = s[0][1];
|
||||||
|
if (!settleToken)
|
||||||
|
settleToken = zeroForOne
|
||||||
|
? poolKey[0]
|
||||||
|
: poolKey[1];
|
||||||
|
if (!takeToken)
|
||||||
|
takeToken = zeroForOne
|
||||||
|
? poolKey[1]
|
||||||
|
: poolKey[0];
|
||||||
|
} catch {
|
||||||
|
// Fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Skip sub-actions we can't decode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
tokenIn: settleToken,
|
||||||
|
tokenOut: takeToken,
|
||||||
|
amountIn,
|
||||||
|
amountOutMin,
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Try to decode a Universal Router execute() call.
|
// Try to decode a Universal Router execute() call.
|
||||||
// Returns { name, description, details } matching the format used by
|
// Returns { name, description, details } matching the format used by
|
||||||
// the approval UI, or null if the calldata is not a recognised execute().
|
// the approval UI, or null if the calldata is not a recognised execute().
|
||||||
@@ -233,6 +384,19 @@ function decode(data, toAddress) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cmdId === 0x10) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (cmdId === 0x0c) {
|
if (cmdId === 0x0c) {
|
||||||
hasUnwrapWeth = true;
|
hasUnwrapWeth = true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user