fix: show swap transactions with decoded calldata in history and detail views (closes #3) #12
103
src/shared/decodeCalldata.js
Normal file
103
src/shared/decodeCalldata.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
// Decode transaction calldata into human-readable details.
|
||||||
|
// Shared between the approval screen and transaction history views.
|
||||||
|
// Returns { name, description, details } or null.
|
||||||
|
|
||||||
|
const { Interface, formatUnits } = require("ethers");
|
||||||
|
const { ERC20_ABI } = require("./constants");
|
||||||
|
const { TOKEN_BY_ADDRESS } = require("./tokenList");
|
||||||
|
const uniswap = require("./uniswap");
|
||||||
|
|
||||||
|
const erc20Iface = new Interface(ERC20_ABI);
|
||||||
|
|
||||||
|
function formatTxValue(val) {
|
||||||
|
const parts = val.split(".");
|
||||||
|
if (parts.length === 1) return val + ".0000";
|
||||||
|
const dec = (parts[1] + "0000").slice(0, 4);
|
||||||
|
return parts[0] + "." + dec;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeCalldata(data, toAddress) {
|
||||||
|
if (!data || data === "0x" || data.length < 10) return null;
|
||||||
|
|
||||||
|
// Try ERC-20 (approve / transfer)
|
||||||
|
try {
|
||||||
|
const parsed = erc20Iface.parseTransaction({ data });
|
||||||
|
if (parsed) {
|
||||||
|
const token = TOKEN_BY_ADDRESS.get(toAddress.toLowerCase());
|
||||||
|
const tokenSymbol = token ? token.symbol : null;
|
||||||
|
const tokenDecimals = token ? token.decimals : 18;
|
||||||
|
const contractLabel = tokenSymbol
|
||||||
|
? tokenSymbol + " (" + toAddress + ")"
|
||||||
|
: toAddress;
|
||||||
|
|
||||||
|
if (parsed.name === "approve") {
|
||||||
|
const spender = parsed.args[0];
|
||||||
|
const rawAmount = parsed.args[1];
|
||||||
|
const maxUint = BigInt(
|
||||||
|
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
);
|
||||||
|
const isUnlimited = rawAmount === maxUint;
|
||||||
|
const amountStr = isUnlimited
|
||||||
|
? "Unlimited"
|
||||||
|
: formatTxValue(formatUnits(rawAmount, tokenDecimals)) +
|
||||||
|
(tokenSymbol ? " " + tokenSymbol : "");
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "Token Approval",
|
||||||
|
description: tokenSymbol
|
||||||
|
? "Approve spending of your " + tokenSymbol
|
||||||
|
: "Approve spending of an ERC-20 token",
|
||||||
|
details: [
|
||||||
|
{
|
||||||
|
label: "Token",
|
||||||
|
value: contractLabel,
|
||||||
|
address: toAddress,
|
||||||
|
isToken: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Spender",
|
||||||
|
value: spender,
|
||||||
|
address: spender,
|
||||||
|
},
|
||||||
|
{ label: "Amount", value: amountStr },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.name === "transfer") {
|
||||||
|
const to = parsed.args[0];
|
||||||
|
const rawAmount = parsed.args[1];
|
||||||
|
const amountStr =
|
||||||
|
formatTxValue(formatUnits(rawAmount, tokenDecimals)) +
|
||||||
|
(tokenSymbol ? " " + tokenSymbol : "");
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "Token Transfer",
|
||||||
|
description: tokenSymbol
|
||||||
|
? "Transfer " + tokenSymbol
|
||||||
|
: "Transfer ERC-20 token",
|
||||||
|
details: [
|
||||||
|
{
|
||||||
|
label: "Token",
|
||||||
|
value: contractLabel,
|
||||||
|
address: toAddress,
|
||||||
|
isToken: true,
|
||||||
|
},
|
||||||
|
{ label: "Recipient", value: to, address: to },
|
||||||
|
{ label: "Amount", value: amountStr },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Not ERC-20 — fall through
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try Uniswap Universal Router
|
||||||
|
const routerResult = uniswap.decode(data, toAddress);
|
||||||
|
if (routerResult) return routerResult;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { decodeCalldata };
|
||||||
Reference in New Issue
Block a user