2 Commits
Author SHA1 Message Date
clawbot 0c59883991 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
2026-10-20 09:00:21 +00:00
sneak c13385b270 fix: a PERMIT2_TRANSFER_FROM shows the swap's unknown first take beside its figure (closes #634)
check / check (push) Canceled after 0s
e2e / e2e-chrome (push) Canceled after 0s
e2e / e2e-firefox (push) Canceled after 0s
When the step that sets the input side states no amount, a
PERMIT2_TRANSFER_FROM of the same token still showed its own figure,
which was then the only amount of that token on the screen. The rule
that adds the line saying the first take is not known now also applies
when such a move gives a figure of that token, before or after the
swap step. A move of another token, and a move into the router, read as
before.

Model: opus-5-5
2026-10-20 08:14:33 +00:00
3 changed files with 12 additions and 50 deletions
+6 -9
View File
@@ -3185,8 +3185,7 @@ Currently supported:
step whose input it cannot read, in whole or in part, and a step it fails on.
In any other case the line gives the total, as with no permit. When the step
that sets the input side spends a token other than the permit's, or one the
decoder does not know, its amount gets lines of its own, unless it states no
amount and the user pays it none of its input token, and the total has no
decoder does not know, its amount gets lines of its own and the total has no
figure to start from: unless the permit's amount stands there is no `Amount`
line, and each take of the permit's token gets lines of its own. Each take the
total does not count gives three lines after the `Amount` line (after
@@ -3198,13 +3197,11 @@ Currently supported:
it some of its input token, and there is no `Amount` line, that step gets them
too whenever another take's lines give a figure, or a `PERMIT2_TRANSFER_FROM`
of its token gives one, on the lines below or, into the router, on its
`Paid to` line, so that figure cannot read as all that leaves. When it states
no amount and the user pays it none of its input token, as when the router
pays it, that step takes none of that token from the user's wallet and never
gets them, with a permit or without. They come in step order, the step that
sets the input side first when it has them: `Moved from the sender`, its
amount, worded as the `Amount` line words one, or
`Unknown (could not be decoded)` when the decoder does not know it;
`Paid to` line, so that figure cannot read as all that leaves. Paid from the
router, that step takes nothing from the user's wallet and never gets them.
They come in step order, the step that sets the input side first when it has
them: `Moved from the sender`, its amount, worded as the `Amount` line words
one, or `Unknown (could not be decoded)` when the decoder does not know it;
`Token moved`, giving the token's address as the `Token In` line does; and
`Moved to`, which reads `The swap step that spends it`. A token the decoder
does not know gives the `Moved from the sender` line alone. A step may also
+6 -11
View File
@@ -1849,22 +1849,17 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
// (see spentUnread()), or a token it does not know.
const spentFromSender = [];
let total = null;
// Whether the step that set the input side counts as a take from the
// sender, which may get lines of its own. The amount it stated counts
// whoever pays it. Stating none, it counts only when the user pays it
// some of its input token: paid from the router, it takes nothing
// from the sender.
const hasFirstTake =
inputEstablished && (present(inputAmount) || inputPaidByUser);
// 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 (hasFirstTake && !present(inputAmount)) {
if (inputEstablished && inputPaidByUser && !present(inputAmount)) {
firstTakeUnstated = { token: inputToken, amount: null };
}
} else if (hasFirstTake) {
} else if (inputEstablished) {
// The step that set the input side spent a token other than the
// permit's, or one not known, so the total starts with no figure.
spentFromSender.push({ token: inputToken, amount: inputAmount });
-30
View File
@@ -2395,36 +2395,6 @@ describe("uniswap decoder", () => {
},
);
// https://git.eeqj.de/sneak/AutistMask/issues/634 — after a
// PERMIT2_PERMIT of another token, the V4 exact-out step paid from
// the router still takes nothing from the wallet, so it gets no lines
// of its own. On next it got lines saying its amount is not known.
const ONE_DAI_PERMIT = encodePermit2(DAI_ADDR, 10n ** 18n, ROUTER_ADDR);
test.each([
{
kind: "a PERMIT2_PERMIT of 1 DAI, then a V4 exact-out step of at most 600 USDT paid from the router",
commands: [0x0a, 0x10],
inputs: [ONE_DAI_PERMIT, V4_EXACT_OUT_600_FROM_ROUTER],
lines: [["Amount", "1.0000 DAI"]],
},
{
kind: "a PERMIT2_PERMIT of 1 DAI, a V4 exact-out step of at most 600 USDT paid from the router, then a V2 exact-out of at most 400 USDT the user pays for",
commands: [0x0a, 0x10, 0x09],
inputs: [
ONE_DAI_PERMIT,
V4_EXACT_OUT_600_FROM_ROUTER,
V2_EXACT_OUT_400,
],
lines: [["Amount", "1.0000 DAI"], ...UP_TO_400_USDT],
},
])(
"$kind, gives the V4 step no lines of its own",
({ commands, inputs, lines }) => {
const result = decodeSteps(commands, inputs);
expect(linesBetweenTokens(result)).toEqual(lines);
},
);
// The V4 exact-out route above the other way round, where the later
// take's unknown amount leaves no figure at all, and other ways the
// step that sets the input side states no amount.