fix: the swap's unknown first take shows only when the user pays that step (closes #634)
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s

decode() now records whether the user pays the V4 step that sets the
input side, from who pays its SETTLE of its input token. That step's
Unknown (could not be decoded) line is added only then, and also when a
PERMIT2_TRANSFER_FROM of its token goes into the router, whose figure is
on its Paid to line. Paid from the router, the step takes nothing from
the wallet and never gets the line. README Transaction Decoding says so.

Model: opus-5-5
This commit is contained in:
2026-10-20 09:00:21 +00:00
parent c13385b270
commit 0c59883991
3 changed files with 102 additions and 26 deletions
+15 -8
View File
@@ -1204,6 +1204,10 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
let inputToken = null;
let inputAmount = null;
let inputEstablished = false;
// Whether the user pays the V4 step that set the input side any of
// its input currency (see decodeV4Swap()). Only a V4 step can set the
// input side with no amount, the one case in which this is read.
let inputPaidByUser = false;
// The last PERMIT2_PERMIT this decoder read, or null (see
// permitStands after the loop).
let permit = null;
@@ -1756,6 +1760,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
// this decoder could not read may take anything: the
// router still runs it.
const setInputHere = inputEstablished && !inputSetBefore;
if (setInputHere) inputPaidByUser = paidIn !== undefined;
if (v4) {
for (const p of v4.paidByUser) {
if (setInputHere && p === paidIn) continue;
@@ -1844,12 +1849,14 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
// (see spentUnread()), or a token it does not know.
const spentFromSender = [];
let total = null;
// The take of the step that set the input side, when the total
// starts from it and it stated no amount (see below), or null.
// The take of the step that set the input side, when the user pays
// that step, the total starts from it and it stated no amount (see
// below), or null. Paid from the router, the step takes nothing from
// the sender.
let firstTakeUnstated = null;
if (permit === null || sameAddress(inputToken, permit.token)) {
total = inputAmount;
if (inputEstablished && !present(inputAmount)) {
if (inputEstablished && inputPaidByUser && !present(inputAmount)) {
firstTakeUnstated = { token: inputToken, amount: null };
}
} else if (inputEstablished) {
@@ -1874,15 +1881,15 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
}
inputAmount = permitStands ? permit.amount : total;
// With no Amount figure, a later take's own figure, or that of a
// PERMIT2_TRANSFER_FROM of the same token, could read as all that
// leaves, so the step that set the input side, when it stated no
// amount, then gets lines of its own too, first, saying its amount
// is not known.
// PERMIT2_TRANSFER_FROM of the same token, into the router or not,
// could read as all that leaves, so the step that set the input side,
// when the user pays it and it stated no amount, then gets lines of
// its own too, first, saying its amount is not known.
if (
!present(inputAmount) &&
firstTakeUnstated !== null &&
(spentFromSender.some((s) => present(s.amount)) ||
movedFromSender.some((m) =>
[...movedFromSender, ...movedIntoRouter].some((m) =>
sameAddress(m.token, firstTakeUnstated.token),
))
) {