refactor: move the calldata decoder to src/shared/calldataDecoder.js (closes #759)
decodeCalldata() and tokenAmountText() move out of the TxApproval view into src/shared/calldataDecoder.js. TxApproval, TransactionDetail and the decoder tests import them from there. tokenSourcesByChainId() moves from the popup view helpers to src/shared/state.js, beside currentNetwork(), so the decoder requires nothing under src/popup/. It reads only the state singleton and the network table. Model: opus-5-5
This commit was merged in pull request #765.
This commit is contained in:
1 parent
c150b83322
commit
6d158bf1b1
14 files changed
+219
-185
No files matched your search
@@ -0,0 +1,155 @@
|
||||
// Decode a transaction's calldata into the summary that TxApproval and
|
||||
// TransactionDetail show: ERC-20 `approve` and `transfer` calls here, and
|
||||
// Uniswap Universal Router calls through uniswap.js. The summary is data; the
|
||||
// views turn its addresses into HTML.
|
||||
|
||||
const { formatUnits, Interface } = require("ethers");
|
||||
const { ERC20_ABI } = require("./constants");
|
||||
const { tokenSourcesByChainId } = require("./state");
|
||||
const {
|
||||
resolveTokenDecimals,
|
||||
resolveTokenSymbol,
|
||||
unknownDecimalsAmount,
|
||||
} = require("./approvalAmount");
|
||||
// Four decimals, with the nonzero floor the approval screens hold.
|
||||
const { truncateAmountNeverZero } = require("./amountDisplay");
|
||||
const uniswap = require("./uniswap");
|
||||
|
||||
const erc20Iface = new Interface(ERC20_ABI);
|
||||
|
||||
// The amount line for a decoded ERC-20 call. With a known scale it is the
|
||||
// token quantity; with `decimals` null it is the base-unit integer with the
|
||||
// unknown scale stated, because formatting it with an assumed scale is what
|
||||
// showed a 5,000-token transfer as `0.0000`. `raw` is what the status screens
|
||||
// carry, `display` is what the approval screen shows.
|
||||
function tokenAmountText(rawAmount, decimals, symbol) {
|
||||
if (decimals === null) {
|
||||
const unknown = unknownDecimalsAmount(rawAmount);
|
||||
return { raw: unknown, display: unknown };
|
||||
}
|
||||
const formatted = truncateAmountNeverZero(formatUnits(rawAmount, decimals));
|
||||
return {
|
||||
raw: formatted,
|
||||
display: formatted + (symbol ? " " + symbol : ""),
|
||||
};
|
||||
}
|
||||
|
||||
// Try to decode calldata sent from `fromAddress` to `toAddress` on the network
|
||||
// with chain id `chainId`, using known ABIs.
|
||||
// Returns { name, description, details } or null.
|
||||
function decodeCalldata(data, toAddress, chainId, fromAddress) {
|
||||
if (!data || data === "0x" || data.length < 10) return null;
|
||||
|
||||
// Where a token's name and scale are looked for, for every decoder below:
|
||||
// what the wallet knows of the tokens on the network with `chainId`, which
|
||||
// need not be the active one. The ERC-20 amount line and the swap's Amount
|
||||
// and Min. received lines resolve the scale the same way, and refuse to
|
||||
// format the same way when it is nowhere.
|
||||
const decimalsSources = tokenSourcesByChainId(chainId);
|
||||
|
||||
// Try ERC-20 (approve / transfer)
|
||||
try {
|
||||
const parsed = erc20Iface.parseTransaction({ data });
|
||||
if (parsed) {
|
||||
const tokenSymbol = resolveTokenSymbol(toAddress, decimalsSources);
|
||||
// null when no source knows this token's scale. It is not
|
||||
// defaulted to 18: an amount formatted with a guessed scale is
|
||||
// the wrong number, and for a token with fewer decimals than the
|
||||
// guess it is the wrong number in the direction that reads as
|
||||
// zero. See tokenAmountText().
|
||||
const tokenDecimals = resolveTokenDecimals(
|
||||
toAddress,
|
||||
decimalsSources,
|
||||
);
|
||||
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;
|
||||
// An unbounded allowance needs no scale to describe, so it is
|
||||
// still named rather than refused.
|
||||
const amount = isUnlimited
|
||||
? { raw: "Unlimited", display: "Unlimited" }
|
||||
: tokenAmountText(rawAmount, tokenDecimals, 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: amount.display,
|
||||
rawValue: amount.raw,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (parsed.name === "transfer") {
|
||||
const to = parsed.args[0];
|
||||
const rawAmount = parsed.args[1];
|
||||
const amount = tokenAmountText(
|
||||
rawAmount,
|
||||
tokenDecimals,
|
||||
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: amount.display,
|
||||
rawValue: amount.raw,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Not ERC-20 — fall through
|
||||
}
|
||||
|
||||
// Try Uniswap Universal Router
|
||||
const routerResult = uniswap.decode(
|
||||
data,
|
||||
toAddress,
|
||||
chainId,
|
||||
decimalsSources,
|
||||
fromAddress,
|
||||
);
|
||||
if (routerResult) return routerResult;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = { decodeCalldata, tokenAmountText };
|
||||
Reference in new issue
Block a user