fix: the approval screen names the address a swap's output is paid to (closes #594)
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s

The swap decoder read no recipient, so a swap paying its output to another
address still showed Min. received. It now reads the recipient of every swap
step, SWEEP, UNWRAP_WETH, V4 TAKE and TAKE_ALL, reads the router's
placeholders as the router does (address(1) is the sender, address(2) the
router, which holds the output until a later SWEEP or UNWRAP_WETH pays it
out), and compares the result with the transaction's sender. When the output
goes anywhere else, a Paid to line names each address it is paid to and the
minimum reads Min. paid out. The approval and transaction detail screens pass
the sender in.

Model: opus-5-5
This commit is contained in:
2026-10-19 06:04:06 +00:00
parent 1f5cdeb7b4
commit 3476f13815
10 changed files with 828 additions and 69 deletions
+113 -43
View File
@@ -332,7 +332,13 @@ describe("uniswap decoder", () => {
});
test("decodes first-ever AutistMask swap (PERMIT2_PERMIT + V4_SWAP)", () => {
const result = uniswap.decode(FIRST_SWAP_CALLDATA, ROUTER_ADDR, "0x1");
const result = uniswap.decode(
FIRST_SWAP_CALLDATA,
ROUTER_ADDR,
"0x1",
{},
USER_ADDR,
);
expect(result).not.toBeNull();
expect(result.name).toBe("Swap USDT \u2192 ETH");
@@ -373,6 +379,8 @@ describe("uniswap decoder", () => {
v4SwapCalldata(id, deployed),
ROUTER_ADDR,
"0x1",
{},
USER_ADDR,
);
expect(detail(result, "Steps").value).toBe("V4 Swap");
},
@@ -389,6 +397,8 @@ describe("uniswap decoder", () => {
v4SwapCalldata(id, newer),
ROUTER_ADDR,
"0x1",
{},
USER_ADDR,
);
expect(detail(result, "Amount")).toBeUndefined();
expect(detail(result, "Min. received")).toBeUndefined();
@@ -419,7 +429,13 @@ describe("uniswap decoder", () => {
],
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(
data,
ROUTER_ADDR,
"0x1",
{},
USER_ADDR,
);
expect(detail(result, "Steps").value).toBe(
"V4 Swap (could not be decoded)",
);
@@ -440,7 +456,13 @@ describe("uniswap decoder", () => {
[encodeV4Swap(new Uint8Array([id]), [newer])],
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(
data,
ROUTER_ADDR,
"0x1",
{},
USER_ADDR,
);
expect(detail(result, "Steps").value).toBe(
"V4 Swap (could not be decoded)",
);
@@ -470,7 +492,13 @@ describe("uniswap decoder", () => {
],
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(
data,
ROUTER_ADDR,
"0x1",
{},
USER_ADDR,
);
expect(result.name).toBe("Uniswap Swap");
expect(detail(result, "Amount").value).toBe("2.0000 USDT");
expect(detail(result, "Token Out").value).toBe(
@@ -498,7 +526,13 @@ describe("uniswap decoder", () => {
],
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(
data,
ROUTER_ADDR,
"0x1",
{},
USER_ADDR,
);
expect(result.name).toBe("Uniswap Swap");
expect(detail(result, "Token In").value).toBe(
"Unknown (could not be decoded)",
@@ -520,7 +554,7 @@ describe("uniswap decoder", () => {
],
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(detail(result, "Token Out").value).toBe(
"Unknown (could not be decoded)",
);
@@ -540,7 +574,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
expect(result.name).toBe("Swap USDT \u2192 WETH");
@@ -567,7 +601,7 @@ describe("uniswap decoder", () => {
1767225600n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result.name).toBe("Swap USDC → WETH");
expect(detail(result, "Token In").address).toBe(USDC_ADDR);
expect(detail(result, "Token Out").address).toBe(WETH_ADDR);
@@ -599,7 +633,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result.name).toBe("Swap ETH → USDC");
const amount = detail(result, "Amount");
@@ -628,7 +662,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result.name).toBe("Swap USDC → ETH");
expect(detail(result, "Amount").value).toBe("Up to 1500.0000 USDC");
expect(detail(result, "Token Out").value).toBe("ETH");
@@ -671,7 +705,13 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(
data,
ROUTER_ADDR,
"0x1",
{},
USER_ADDR,
);
expect(detail(result, "Token Out").address).toBe(tokenOut);
expect(detail(result, "Min. received").value).toBe(minReceived);
},
@@ -692,7 +732,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result.name).toBe("Swap USDT → USDC");
expect(detail(result, "Token Out").address).toBe(USDC_ADDR);
expect(detail(result, "Min. received").value).toBe("1.9000 USDC");
@@ -725,7 +765,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(detail(result, "Token Out").value).toBe("ETH");
expect(detail(result, "Min. received").value).toBe("0.4000 ETH");
});
@@ -744,7 +784,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(detail(result, "Token Out").value).toBe("ETH");
expect(detail(result, "Min. received").value).toBe("0.0005 ETH");
});
@@ -769,7 +809,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(detail(result, "Token Out").value).toBe(
"Unknown (not named in the calldata)",
);
@@ -792,7 +832,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
expect(result.name).toBe("Swap USDT \u2192 WETH");
});
@@ -804,7 +844,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
const tokenIn = result.details.find((d) => d.label === "Token In");
@@ -827,7 +867,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
// UNWRAP_WETH means output is native ETH
expect(result.name).toBe("Swap USDT \u2192 ETH");
@@ -843,7 +883,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
const minOut = result.details.find((d) => d.label === "Min. received");
@@ -863,7 +903,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
// The V4 swap action's parameters are empty, so that step is said to
@@ -943,7 +983,13 @@ describe("uniswap decoder", () => {
[subPlan],
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(
data,
ROUTER_ADDR,
"0x1",
{},
USER_ADDR,
);
expect(result.name).toBe("Uniswap Swap");
expect(detail(result, "Steps").value).toBe(
"Execute Sub-Plan (its steps are not shown)",
@@ -1031,7 +1077,13 @@ describe("uniswap decoder", () => {
"$beside an EXECUTE_SUB_PLAN step does not name either side",
({ commands, inputs, steps }) => {
const data = buildExecute(commands, inputs, 9999999999n);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(
data,
ROUTER_ADDR,
"0x1",
{},
USER_ADDR,
);
expect(detail(result, "Steps").value).toBe(steps);
expect(result.name).toBe("Uniswap Swap");
expect(detail(result, "Token In").value).toBe(
@@ -1046,7 +1098,13 @@ describe("uniswap decoder", () => {
);
test("shows the deadline as a UTC date and time", () => {
const result = uniswap.decode(FIRST_SWAP_CALLDATA, ROUTER_ADDR, "0x1");
const result = uniswap.decode(
FIRST_SWAP_CALLDATA,
ROUTER_ADDR,
"0x1",
{},
USER_ADDR,
);
expect(detail(result, "Deadline").value).toBe("2026-02-27 08:25:51");
});
@@ -1064,7 +1122,7 @@ describe("uniswap decoder", () => {
2n ** 256n - 1n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
expect(result.name).toBe("Swap USDT \u2192 WETH");
expect(detail(result, "Deadline").value).toBe(
@@ -1079,7 +1137,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
const amount = result.details.find((d) => d.label === "Amount");
@@ -1154,7 +1212,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
// Before decodeV4Swap fix: name would be "Swap USDT → ETH" or "Uniswap Swap"
// After fix: correctly identifies both tokens from V4 sub-actions
@@ -1188,7 +1246,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
// The amount and the token it is counted in come from the same hop.
@@ -1228,7 +1286,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
expect(detail(result, "Token Out").value).toBe(
@@ -1273,7 +1331,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
expect(detail(result, "Token Out").value).toContain("USDC");
@@ -1302,7 +1360,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
expect(detail(result, "Token Out").value).toContain("USDC");
@@ -1339,7 +1397,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
expect(detail(result, "Token In").value).toContain("WETH");
@@ -1532,7 +1590,13 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(
data,
ROUTER_ADDR,
"0x1",
{},
USER_ADDR,
);
expect(result.name).toBe("Swap USDT → USDC");
expect(detail(result, "Token In").address).toBe(USDT_ADDR);
expect(detail(result, "Token Out").address).toBe(USDC_ADDR);
@@ -1584,7 +1648,13 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(
data,
ROUTER_ADDR,
"0x1",
{},
USER_ADDR,
);
expect(detail(result, "Steps").value).toBe(
"V4 Swap (could not be decoded)",
);
@@ -1643,7 +1713,7 @@ describe("uniswap decoder", () => {
],
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result.name).toBe("Uniswap Swap");
expect(detail(result, "Token In").address).toBe(
USDT_ADDR.toLowerCase(),
@@ -1699,7 +1769,7 @@ describe("uniswap decoder", () => {
],
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result.name).toBe("Uniswap Swap");
expect(detail(result, "Token In").value).toBe(
"Unknown (not named in the calldata)",
@@ -1722,7 +1792,7 @@ describe("uniswap decoder", () => {
],
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(detail(result, "Token Out").value).toBe(
"Unknown (not named in the calldata)",
);
@@ -1756,7 +1826,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result.name).toBe("Swap USDT → USDC");
expect(detail(result, "Token In").address).toBe(USDT_ADDR);
expect(detail(result, "Token Out").address).toBe(USDC_ADDR);
@@ -1780,7 +1850,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
expect(detail(result, "Token In").value).toContain("USDT");
@@ -1811,7 +1881,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
expect(detail(result, "Token Out").value).toContain("WETH");
@@ -1832,7 +1902,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
expect(detail(result, "Token Out").value).toContain("USDC");
@@ -1851,7 +1921,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
expect(detail(result, "Token Out").value).toContain("USDT");
@@ -1871,7 +1941,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
expect(result.name).toBe("Uniswap Swap");
@@ -1891,7 +1961,7 @@ describe("uniswap decoder", () => {
9999999999n,
);
const result = uniswap.decode(data, ROUTER_ADDR, "0x1");
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
expect(result).not.toBeNull();
expect(result.name).toBe("Uniswap Swap");