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