Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4adc65c2bb |
@@ -1176,18 +1176,18 @@ number. They can read:
|
|||||||
- `Unlimited`: on the ERC-20 `Amount` line, an `approve` of the `uint256`
|
- `Unlimited`: on the ERC-20 `Amount` line, an `approve` of the `uint256`
|
||||||
maximum, an unbounded allowance. On the swap's `Amount` line, any amount at or
|
maximum, an unbounded allowance. On the swap's `Amount` line, any amount at or
|
||||||
above the `uint160` maximum, whichever step set the line: a `PERMIT2_PERMIT`
|
above the `uint160` maximum, whichever step set the line: a `PERMIT2_PERMIT`
|
||||||
amount at that maximum, which is an unbounded permit, or a V2 or V3 exact-in,
|
amount at that maximum, which is an unbounded permit, or a V2 or V3 exact-in
|
||||||
V2 exact-out or `WRAP_ETH` amount that large, which is not an allowance. The
|
or exact-out or `WRAP_ETH` amount that large, which is not an allowance. The
|
||||||
router's whole-balance value, `CONTRACT_BALANCE` (`2^255`), is one such
|
router's whole-balance value, `CONTRACT_BALANCE` (`2^255`), is one such
|
||||||
amount.
|
amount.
|
||||||
- `Up to <amount>`: the swap's `Amount` line, when the transaction has a V2
|
- `Up to <amount>`: the swap's `Amount` line, when the transaction has a V2 or
|
||||||
exact-out step, whichever step set the line, including the `WRAP_ETH` of a
|
V3 exact-out step, whichever step set the line, including the `WRAP_ETH` of a
|
||||||
swap paid in ETH and a `PERMIT2_PERMIT`. The swap spends at most that figure,
|
swap paid in ETH and a `PERMIT2_PERMIT`. The swap spends at most that figure,
|
||||||
not necessarily all of it; the wait, success and error screens show it with
|
not necessarily all of it; the wait, success and error screens show it with
|
||||||
the same words. `Unlimited`, `All available (V4 open delta)` and
|
the same words. `Unlimited`, `All available (V4 open delta)` and
|
||||||
`Whatever an earlier step sent to the pair (V2 already paid)` keep their
|
`Whatever an earlier step sent to the pair (V2 already paid)` keep their
|
||||||
wording. When a V2 exact-out step sets `Min. received`, that line shows its
|
wording. When a V2 or V3 exact-out step sets `Min. received`, that line shows
|
||||||
`amountOut`, the exact amount it buys.
|
its `amountOut`, the exact amount it buys.
|
||||||
- `All available (V4 open delta)`: the swap's `Amount` line, when the amount it
|
- `All available (V4 open delta)`: the swap's `Amount` line, when the amount it
|
||||||
shows is a V4 exact-in `amountIn` of zero. V4 reads that zero as "use the
|
shows is a V4 exact-in `amountIn` of zero. V4 reads that zero as "use the
|
||||||
whole open delta", so the calldata states no quantity. Transaction Decoding
|
whole open delta", so the calldata states no quantity. Transaction Decoding
|
||||||
@@ -3008,10 +3008,14 @@ Currently supported:
|
|||||||
Router addresses Uniswap publishes for the network: on TxApproval the network
|
Router addresses Uniswap publishes for the network: on TxApproval the network
|
||||||
the transaction is for, on TransactionDetail the network it was sent on. The
|
the transaction is for, on TransactionDetail the network it was sent on. The
|
||||||
same calldata sent to any other address is shown as raw data. Decodes Permit2,
|
same calldata sent to any other address is shown as raw data. Decodes Permit2,
|
||||||
V2/V3/V4 swaps, wrap/unwrap, and balance checks. A V4 swap action is read only
|
V2/V3/V4 swaps, wrap/unwrap, and balance checks. A V3 swap step's path is read
|
||||||
in the parameter shape deployed on mainnet and Sepolia, not in the newer one
|
as the router reads it: a `V3_SWAP_EXACT_IN` path runs from the token it
|
||||||
on `v4-periphery` `main`, which adds a `minHopPriceX36` field. An action in
|
spends to the token it buys, and a `V3_SWAP_EXACT_OUT` path from the token it
|
||||||
any other shape is not read, and its step is listed as
|
buys to the token it spends, so that step's input token is its path's last
|
||||||
|
token and its output token the first. A V4 swap action is read only in the
|
||||||
|
parameter shape deployed on mainnet and Sepolia, not in the newer one on
|
||||||
|
`v4-periphery` `main`, which adds a `minHopPriceX36` field. An action in any
|
||||||
|
other shape is not read, and its step is listed as
|
||||||
`V4 Swap (could not be decoded)`. Each step is named and read as the command
|
`V4 Swap (could not be decoded)`. Each step is named and read as the command
|
||||||
that the release of the router at that address runs for its command type, as
|
that the release of the router at that address runs for its command type, as
|
||||||
that release's `contracts/libraries/Commands.sol` names it. The decoder reads
|
that release's `contracts/libraries/Commands.sol` names it. The decoder reads
|
||||||
@@ -3093,40 +3097,42 @@ Currently supported:
|
|||||||
as a swap paid in ETH does, but only when that amount is not
|
as a swap paid in ETH does, but only when that amount is not
|
||||||
`CONTRACT_BALANCE` and the router held no WETH from an earlier step when the
|
`CONTRACT_BALANCE` and the router held no WETH from an earlier step when the
|
||||||
`WRAP_ETH` ran: after two `WRAP_ETH`s of 0.5 ETH into the router, a swap of 1
|
`WRAP_ETH` ran: after two `WRAP_ETH`s of 0.5 ETH into the router, a swap of 1
|
||||||
WETH from it reads as leaving the WETH there. A `SETTLE` of `OPEN_DELTA`
|
WETH from it reads as leaving the WETH there. A V2 or V3 exact-out step paid
|
||||||
(zero), and a `SETTLE_ALL`, pay only what the step still owes of their token
|
from the router spends at most its `amountInMax`, an amount the calldata does
|
||||||
when they run. The decoder takes that to be the amounts the step's swap
|
not state, so the decoder counts it as spending nothing the router holds. A
|
||||||
actions state they spend of that token only when every action in the step
|
`SETTLE` of `OPEN_DELTA` (zero), and a `SETTLE_ALL`, pay only what the step
|
||||||
could be read, every swap action names the token it spends and the token it
|
still owes of their token when they run. The decoder takes that to be the
|
||||||
buys (a swap action with an empty path swaps nothing), the `SETTLE` or
|
amounts the step's swap actions state they spend of that token only when every
|
||||||
`SETTLE_ALL` is the step's only `SETTLE` or `SETTLE_ALL` of that token and
|
action in the step could be read, every swap action names the token it spends
|
||||||
comes after every swap action, no swap action buys that token, no `TAKE`,
|
and the token it buys (a swap action with an empty path swaps nothing), the
|
||||||
`TAKE_ALL` or `TAKE_PORTION` in the step takes it (a `TAKE` of a stated amount
|
`SETTLE` or `SETTLE_ALL` is the step's only `SETTLE` or `SETTLE_ALL` of that
|
||||||
adds to what the step owes), and the swap actions that spend it state a
|
token and comes after every swap action, no swap action buys that token, no
|
||||||
quantity together; otherwise it does not know the amount, and such a `SETTLE`
|
`TAKE`, `TAKE_ALL` or `TAKE_PORTION` in the step takes it (a `TAKE` of a
|
||||||
from the router counts as spending nothing. So the rest of a V4 `SETTLE` of
|
stated amount adds to what the step owes), and the swap actions that spend it
|
||||||
more than its swap spends, taken to the router, the WETH of a `WRAP_ETH`, and
|
state a quantity together; otherwise it does not know the amount, and such a
|
||||||
a token a swap pays the router on the way to the output, each stays in the
|
`SETTLE` from the router counts as spending nothing. So the rest of a V4
|
||||||
router when later steps do not spend all of it, where anyone can take it in a
|
`SETTLE` of more than its swap spends, taken to the router, the WETH of a
|
||||||
later transaction. The router sends ETH as written, so a `TRANSFER` of
|
`WRAP_ETH`, and a token a swap pays the router on the way to the output, each
|
||||||
`CONTRACT_BALANCE` of ETH always reverts and pays out nothing. A step paying a
|
stays in the router when later steps do not spend all of it, where anyone can
|
||||||
token to the V2 pair that the very next step spends, as a V2 exact-in of that
|
take it in a later transaction. The router sends ETH as written, so a
|
||||||
token whose `amountIn` is `ALREADY_PAID` (zero) or `CONTRACT_BALANCE`, is a
|
`TRANSFER` of `CONTRACT_BALANCE` of ETH always reverts and pays out nothing. A
|
||||||
hop, not a payout: Uniswap's mixed routes pay the next V2 pair directly, and
|
step paying a token to the V2 pair that the very next step spends, as a V2
|
||||||
the router enforces that swap's own minimum. The decoder checks that the
|
exact-in of that token whose `amountIn` is `ALREADY_PAID` (zero) or
|
||||||
address paid is that pair, deriving it from the V2 factory and pair init code
|
`CONTRACT_BALANCE`, is a hop, not a payout: Uniswap's mixed routes pay the
|
||||||
hash the network's routers were deployed with and the first two tokens of that
|
next V2 pair directly, and the router enforces that swap's own minimum. The
|
||||||
step's path; a payment to any other address is named. (Sepolia's
|
decoder checks that the address paid is that pair, deriving it from the V2
|
||||||
`UniversalRouterV1_2_V2Support` was deployed with placeholders for both
|
factory and pair init code hash the network's routers were deployed with and
|
||||||
values, so no V2 swap step can succeed on that router.) Every other payment
|
the first two tokens of that step's path; a payment to any other address is
|
||||||
counts, in whichever step and of whatever token, a share of the output
|
named. (Sepolia's `UniversalRouterV1_2_V2Support` was deployed with
|
||||||
included: an interface fee paid to another address by `PAY_PORTION`,
|
placeholders for both values, so no V2 swap step can succeed on that router.)
|
||||||
`TRANSFER` or `TAKE_PORTION` is named, and the minimum reads `Min. paid out`,
|
Every other payment counts, in whichever step and of whatever token, a share
|
||||||
because the swap's minimum includes the fee. The minimum reads `Min. received`
|
of the output included: an interface fee paid to another address by
|
||||||
only when some step pays the transaction's sender (the From address on
|
`PAY_PORTION`, `TRANSFER` or `TAKE_PORTION` is named, and the minimum reads
|
||||||
TxApproval, the sender the explorer reports on TransactionDetail) and no
|
`Min. paid out`, because the swap's minimum includes the fee. The minimum
|
||||||
`Paid to` line is shown. `Paid to` lines after `Token Out` name each other
|
reads `Min. received` only when some step pays the transaction's sender (the
|
||||||
address paid; one reads
|
From address on TxApproval, the sender the explorer reports on
|
||||||
|
TransactionDetail) and no `Paid to` line is shown. `Paid to` lines after
|
||||||
|
`Token Out` name each other address paid; one reads
|
||||||
`The bridge, which pays the deposit's recipient on another network` for a
|
`The bridge, which pays the deposit's recipient on another network` for a
|
||||||
bridge deposit; one reads, for example,
|
bridge deposit; one reads, for example,
|
||||||
`The router, 2000.0000 USDT from the sender (later steps may not spend all of it)`
|
`The router, 2000.0000 USDT from the sender (later steps may not spend all of it)`
|
||||||
@@ -3140,52 +3146,52 @@ Currently supported:
|
|||||||
`Unknown (not named in the calldata)`. Wherever there is a `Paid to` line, the
|
`Unknown (not named in the calldata)`. Wherever there is a `Paid to` line, the
|
||||||
minimum reads `Min. paid out`. The `Amount` line gives the total the steps
|
minimum reads `Min. paid out`. The `Amount` line gives the total the steps
|
||||||
take of `Token In`, from the step that sets the input side on: the first V2 or
|
take of `Token In`, from the step that sets the input side on: the first V2 or
|
||||||
V3 exact-in, V2 exact-out, `WRAP_ETH` or V4 swap step that names an input
|
V3 exact-in or exact-out, `WRAP_ETH` or V4 swap step that names an input token
|
||||||
token or amount. A V4 step with a swap action sets the input side when no
|
or amount. A V4 step with a swap action sets the input side when no earlier
|
||||||
earlier step set it, even when it names neither (see Display Consistency).
|
step set it, even when it names neither (see Display Consistency). That step
|
||||||
That step counts its own amount, whoever pays it: a V2 exact-out step its
|
counts its own amount, whoever pays it: a V2 or V3 exact-out step its
|
||||||
`amountInMax`, and a V4 step what it takes of its input token from the user's
|
`amountInMax`, and a V4 step what it takes of its input token from the user's
|
||||||
wallet, as below, or, when the user pays it none of that token, the `amountIn`
|
wallet, as below, or, when the user pays it none of that token, the `amountIn`
|
||||||
of the swap action that spends it. Each later V2 or V3 exact-in or V2
|
of the swap action that spends it. Each later V2 or V3 exact-in or exact-out
|
||||||
exact-out step whose `payerIsUser` is true takes its amount, found in the same
|
step whose `payerIsUser` is true takes its amount, found in the same way, from
|
||||||
way, from the user's wallet. Every V4 step, with a swap action or without,
|
the user's wallet. Every V4 step, with a swap action or without, takes from
|
||||||
takes from the user's wallet what its `SETTLE`s paid by the user
|
the user's wallet what its `SETTLE`s paid by the user (`payerIsUser` true) and
|
||||||
(`payerIsUser` true) and its `SETTLE_ALL`s pay, for each token on its own;
|
its `SETTLE_ALL`s pay, for each token on its own; what the step that sets the
|
||||||
what the step that sets the input side takes of any other token counts as a
|
input side takes of any other token counts as a later step's take. A `SETTLE`
|
||||||
later step's take. A `SETTLE` of any amount but `OPEN_DELTA` pays that amount,
|
of any amount but `OPEN_DELTA` pays that amount, and a `SETTLE` of
|
||||||
and a `SETTLE` of `OPEN_DELTA` or a `SETTLE_ALL` pays what the step owes, as
|
`OPEN_DELTA` or a `SETTLE_ALL` pays what the step owes, as above. So a V4 step
|
||||||
above. So a V4 step that swaps 400 USDT, with a `SETTLE` of 5,000 USDT paid by
|
that swaps 400 USDT, with a `SETTLE` of 5,000 USDT paid by the user and the
|
||||||
the user and the rest taken to another address, reads `5000.0000 USDT`. A
|
rest taken to another address, reads `5000.0000 USDT`. A later step paid from
|
||||||
later step paid from the router spends what the router holds and takes
|
the router spends what the router holds and takes nothing, and so does a V2
|
||||||
nothing, and so does a V2 exact-in whose `amountIn` is `ALREADY_PAID`. A later
|
exact-in whose `amountIn` is `ALREADY_PAID`. A later take of `Token In` is
|
||||||
take of `Token In` is added to the total while both are quantities: a V3
|
added to the total while both are quantities: a V3 exact-in of 600 USDT and a
|
||||||
exact-in of 600 USDT and a V2 exact-in of 400 USDT, both paid by the user,
|
V2 exact-in of 400 USDT, both paid by the user, read `1000.0000 USDT`. When
|
||||||
read `1000.0000 USDT`. When the decoder does not know a take's amount, the
|
the decoder does not know a take's amount, the total is not known and there is
|
||||||
total is not known and there is no `Amount` line. `Token In` is the token of
|
no `Amount` line. `Token In` is the token of the last `PERMIT2_PERMIT` the
|
||||||
the last `PERMIT2_PERMIT` the decoder reads, when there is one. Its amount,
|
decoder reads, when there is one. Its amount, `Unlimited` included, stands as
|
||||||
`Unlimited` included, stands as the `Amount` figure in place of the total only
|
the `Amount` figure in place of the total only in the plain shape of Uniswap's
|
||||||
in the plain shape of Uniswap's own routes: it is the only `PERMIT2_PERMIT`,
|
own routes: it is the only `PERMIT2_PERMIT`, its spender is the router, the
|
||||||
its spender is the router, the router may not let it fail, no step before it
|
router may not let it fail, no step before it takes its token or a token the
|
||||||
takes its token or a token the decoder does not know, and the decoder reads
|
decoder does not know, and the decoder reads every step after it in full.
|
||||||
every step after it in full. Permit2 then lets the router take no more of the
|
Permit2 then lets the router take no more of the token than that amount, so
|
||||||
token than that amount, so the permit covers every take of it. A step the
|
the permit covers every take of it. A step the decoder does not read in full
|
||||||
decoder does not read in full may raise what Permit2 lets the router take, as
|
may raise what Permit2 lets the router take, as a `PERMIT2_PERMIT_BATCH` can:
|
||||||
a `PERMIT2_PERMIT_BATCH` can: a command the decoder does not read (listed at
|
a command the decoder does not read (listed at the end of this section), a
|
||||||
the end of this section), a step whose input it cannot read, in whole or in
|
step whose input it cannot read, in whole or in part, and a step it fails on.
|
||||||
part, and a step it fails on. In any other case the line gives the total, as
|
In any other case the line gives the total, as with no permit. When the step
|
||||||
with no permit. When the step that sets the input side spends a token other
|
that sets the input side spends a token other than the permit's, or one the
|
||||||
than the permit's, or one the decoder does not know, its amount gets lines of
|
decoder does not know, its amount gets lines of its own and the total has no
|
||||||
its own and the total has no figure to start from: unless the permit's amount
|
figure to start from: unless the permit's amount stands there is no `Amount`
|
||||||
stands there is no `Amount` line, and each take of the permit's token gets
|
line, and each take of the permit's token gets lines of its own. Each take the
|
||||||
lines of its own. Each take the total does not count gives three lines after
|
total does not count gives three lines after the `Amount` line (after
|
||||||
the `Amount` line (after `Token In` when there is none): one of a token other
|
`Token In` when there is none): one of a token other than `Token In`; one of
|
||||||
than `Token In`; one of `Token In` after an amount that is not a quantity (a
|
`Token In` after an amount that is not a quantity (a V4 open delta,
|
||||||
V4 open delta, `ALREADY_PAID`, which keep their wording on the `Amount` line,
|
`ALREADY_PAID`, which keep their wording on the `Amount` line, or no stated
|
||||||
or no stated amount, as of a V4 exact-out step); and one whose amount the
|
amount, as of a V4 exact-out step); and one whose amount the decoder does not
|
||||||
decoder does not know. When the step that sets the input side states no amount
|
know. When the step that sets the input side states no amount and there is no
|
||||||
and there is no `Amount` line, that step gets them too whenever another take's
|
`Amount` line, that step gets them too whenever another take's lines give a
|
||||||
lines give a figure, so that figure cannot read as all that leaves. They come
|
figure, so that figure cannot read as all that leaves. They come in step
|
||||||
in step order, the step that sets the input side first when it has them:
|
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
|
`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;
|
`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
|
`Token moved`, giving the token's address as the `Token In` line does; and
|
||||||
@@ -3193,12 +3199,11 @@ Currently supported:
|
|||||||
does not know gives the `Moved from the sender` line alone. A step may also
|
does not know gives the `Moved from the sender` line alone. A step may also
|
||||||
take from the user's wallet what the decoder did not read: a V4 step whose
|
take from the user's wallet what the decoder did not read: a V4 step whose
|
||||||
input or one of whose `SETTLE`s or `SETTLE_ALL`s it cannot read, which the
|
input or one of whose `SETTLE`s or `SETTLE_ALL`s it cannot read, which the
|
||||||
router still runs; a V2 or V3 exact-in or V2 exact-out step or a
|
router still runs; a V2 or V3 exact-in or exact-out step or a
|
||||||
`PERMIT2_TRANSFER_FROM` whose input it cannot read; every
|
`PERMIT2_TRANSFER_FROM` whose input it cannot read, such as a V3 step whose
|
||||||
`PERMIT2_TRANSFER_FROM_BATCH`; every `V3_SWAP_EXACT_OUT`, which the decoder
|
path is shorter than one token address; every `PERMIT2_TRANSFER_FROM_BATCH`;
|
||||||
does not read, wherever it stands; every `EXECUTE_SUB_PLAN`, wherever it
|
every `EXECUTE_SUB_PLAN`, wherever it stands; and every step it fails on. Each
|
||||||
stands; and every step it fails on. Each such step gives one
|
such step gives one `Moved from the sender` line among those, reading
|
||||||
`Moved from the sender` line among those, reading
|
|
||||||
`Unknown (could not be decoded)`. That line, or any line for a token the
|
`Unknown (could not be decoded)`. That line, or any line for a token the
|
||||||
decoder does not know, leaves no `Amount` line, whose figure might be too low,
|
decoder does not know, leaves no `Amount` line, whose figure might be too low,
|
||||||
and such a step before a `PERMIT2_PERMIT` keeps the permit's amount from
|
and such a step before a `PERMIT2_PERMIT` keeps the permit's amount from
|
||||||
@@ -3212,13 +3217,15 @@ Currently supported:
|
|||||||
`Token moved into the router` line already give the amount, the token and
|
`Token moved into the router` line already give the amount, the token and
|
||||||
where it goes, and so its amount is shown once. These lines change neither the
|
where it goes, and so its amount is shown once. These lines change neither the
|
||||||
`Paid to` lines nor the minimum's label. A step goes unread when its recipient
|
`Paid to` lines nor the minimum's label. A step goes unread when its recipient
|
||||||
does not decode as an address, or when it is a command the decoder does not
|
does not decode as an address, when it is a V3 swap step whose path is shorter
|
||||||
read: every command other than `PERMIT2_PERMIT`, `PERMIT2_TRANSFER_FROM`,
|
than one token address, or when it is a command the decoder does not read:
|
||||||
`V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`, `V3_SWAP_EXACT_IN`, `V4_SWAP`,
|
every command other than `PERMIT2_PERMIT`, `PERMIT2_TRANSFER_FROM`,
|
||||||
`SWEEP`, `TRANSFER`, `PAY_PORTION`, `PAY_PORTION_FULL_PRECISION`,
|
`V2_SWAP_EXACT_IN`, `V2_SWAP_EXACT_OUT`, `V3_SWAP_EXACT_IN`,
|
||||||
`ACROSS_V4_DEPOSIT_V3`, `WRAP_ETH`, `UNWRAP_WETH` and `BALANCE_CHECK_ERC20`
|
`V3_SWAP_EXACT_OUT`, `V4_SWAP`, `SWEEP`, `TRANSFER`, `PAY_PORTION`,
|
||||||
(`V3_SWAP_EXACT_OUT`, `PERMIT2_TRANSFER_FROM_BATCH`, `EXECUTE_SUB_PLAN` and
|
`PAY_PORTION_FULL_PRECISION`, `ACROSS_V4_DEPOSIT_V3`, `WRAP_ETH`,
|
||||||
the NFT commands among them), or a type its release names no command for.
|
`UNWRAP_WETH` and `BALANCE_CHECK_ERC20` (`PERMIT2_TRANSFER_FROM_BATCH`,
|
||||||
|
`EXECUTE_SUB_PLAN` and the NFT commands among them), or a type its release
|
||||||
|
names no command for.
|
||||||
|
|
||||||
### Non-Goals Forever
|
### Non-Goals Forever
|
||||||
|
|
||||||
|
|||||||
+59
-27
@@ -592,13 +592,14 @@ function decodeV2SwapExactOut(input) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode V3 swap path (packed: token(20) + fee(3) + token(20) ...)
|
// The first and last token of a V3 swap path (packed: token(20) + fee(3) +
|
||||||
|
// token(20) ...), or null when it is shorter than one token.
|
||||||
function decodeV3Path(pathHex) {
|
function decodeV3Path(pathHex) {
|
||||||
const hex = pathHex.startsWith("0x") ? pathHex.slice(2) : pathHex;
|
const hex = pathHex.startsWith("0x") ? pathHex.slice(2) : pathHex;
|
||||||
if (hex.length < 40) return null;
|
if (hex.length < 40) return null;
|
||||||
const tokenIn = "0x" + hex.slice(0, 40);
|
const first = "0x" + hex.slice(0, 40);
|
||||||
const tokenOut = "0x" + hex.slice(-40);
|
const last = "0x" + hex.slice(-40);
|
||||||
return { tokenIn, tokenOut };
|
return { first, last };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode V3_SWAP_EXACT_IN (command 0x00) input bytes.
|
// Decode V3_SWAP_EXACT_IN (command 0x00) input bytes.
|
||||||
@@ -617,8 +618,39 @@ function decodeV3SwapExactIn(input) {
|
|||||||
recipient: d[0],
|
recipient: d[0],
|
||||||
amountIn: d[1],
|
amountIn: d[1],
|
||||||
amountOutMin: d[2],
|
amountOutMin: d[2],
|
||||||
tokenIn: path.tokenIn,
|
tokenIn: path.first,
|
||||||
tokenOut: path.tokenOut,
|
tokenOut: path.last,
|
||||||
|
payerIsUser: d[4],
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode V3_SWAP_EXACT_OUT (command 0x01) input bytes.
|
||||||
|
// ABI: (address recipient, uint256 amountOut, uint256 amountInMax,
|
||||||
|
// bytes path, bool payerIsUser)
|
||||||
|
// The router reads an exact-out path from the token it buys to the token it
|
||||||
|
// spends: it buys `amountOut` of the path's first token from the path's first
|
||||||
|
// pool and pays at most `amountInMax` of its last token to its last pool
|
||||||
|
// (`v3SwapExactOutput()` and `uniswapV3SwapCallback()` in each release's
|
||||||
|
// `contracts/modules/uniswap/v3/V3SwapRouter.sol`, at the commit linked for
|
||||||
|
// it above).
|
||||||
|
// With `payerIsUser` false the router pays the input from its own balance.
|
||||||
|
function decodeV3SwapExactOut(input) {
|
||||||
|
try {
|
||||||
|
const d = coder.decode(
|
||||||
|
["address", "uint256", "uint256", "bytes", "bool"],
|
||||||
|
input,
|
||||||
|
);
|
||||||
|
const path = decodeV3Path(d[3]);
|
||||||
|
if (!path) return null;
|
||||||
|
return {
|
||||||
|
recipient: d[0],
|
||||||
|
amountOut: d[1],
|
||||||
|
amountInMax: d[2],
|
||||||
|
tokenIn: path.last,
|
||||||
|
tokenOut: path.first,
|
||||||
payerIsUser: d[4],
|
payerIsUser: d[4],
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
@@ -1180,7 +1212,7 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
let minOutput = null;
|
let minOutput = null;
|
||||||
let outputEstablished = false;
|
let outputEstablished = false;
|
||||||
let hasUnwrapWeth = false;
|
let hasUnwrapWeth = false;
|
||||||
let hasV2ExactOut = false;
|
let hasV2OrV3ExactOut = false;
|
||||||
let swapUndecoded = false;
|
let swapUndecoded = false;
|
||||||
let hasSubPlan = false;
|
let hasSubPlan = false;
|
||||||
const commandNames = [];
|
const commandNames = [];
|
||||||
@@ -1236,9 +1268,8 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
};
|
};
|
||||||
// A step may take from the sender what this decoder did not read: a
|
// A step may take from the sender what this decoder did not read: a
|
||||||
// V4 step whose input or one of whose SETTLEs or SETTLE_ALLs it
|
// V4 step whose input or one of whose SETTLEs or SETTLE_ALLs it
|
||||||
// cannot read; a V2 or V3 exact-in or V2 exact-out step or
|
// cannot read; a V2 or V3 swap step or PERMIT2_TRANSFER_FROM whose
|
||||||
// PERMIT2_TRANSFER_FROM whose input it cannot read; a
|
// input it cannot read; a PERMIT2_TRANSFER_FROM_BATCH; an
|
||||||
// PERMIT2_TRANSFER_FROM_BATCH; a V3_SWAP_EXACT_OUT; an
|
|
||||||
// EXECUTE_SUB_PLAN; and a step it fails on. It is recorded with a null
|
// EXECUTE_SUB_PLAN; and a step it fails on. It is recorded with a null
|
||||||
// token and amount: one line says so, there is no Amount line, whose
|
// token and amount: one line says so, there is no Amount line, whose
|
||||||
// figure may then be too low, and the amount of a permit after it
|
// figure may then be too low, and the amount of a permit after it
|
||||||
@@ -1493,14 +1524,6 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
stepsUnread.push(i);
|
stepsUnread.push(i);
|
||||||
spentUnread();
|
spentUnread();
|
||||||
}
|
}
|
||||||
} else if (command === "V3_SWAP_EXACT_OUT") {
|
|
||||||
// This decoder does not read it
|
|
||||||
// (https://git.eeqj.de/sneak/AutistMask/issues/601). It
|
|
||||||
// may pay any address and take from the sender, so
|
|
||||||
// wherever it stands it leaves no Amount figure.
|
|
||||||
payeeUnread = true;
|
|
||||||
stepsUnread.push(i);
|
|
||||||
spentUnread();
|
|
||||||
} else if (command === "V2_SWAP_EXACT_IN") {
|
} else if (command === "V2_SWAP_EXACT_IN") {
|
||||||
const s = decodeV2SwapExactIn(inputs[i]);
|
const s = decodeV2SwapExactIn(inputs[i]);
|
||||||
if (s) {
|
if (s) {
|
||||||
@@ -1521,10 +1544,19 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
stepsUnread.push(i);
|
stepsUnread.push(i);
|
||||||
spentUnread();
|
spentUnread();
|
||||||
}
|
}
|
||||||
} else if (command === "V2_SWAP_EXACT_OUT") {
|
} else if (
|
||||||
|
command === "V2_SWAP_EXACT_OUT" ||
|
||||||
|
command === "V3_SWAP_EXACT_OUT"
|
||||||
|
) {
|
||||||
// Buys exactly amountOut and spends at most amountInMax.
|
// Buys exactly amountOut and spends at most amountInMax.
|
||||||
hasV2ExactOut = true;
|
// Paid from the router, it spends an amount the calldata
|
||||||
const s = decodeV2SwapExactOut(inputs[i]);
|
// does not state, so it spends nothing spendFromRouter()
|
||||||
|
// could count.
|
||||||
|
hasV2OrV3ExactOut = true;
|
||||||
|
const s =
|
||||||
|
command === "V2_SWAP_EXACT_OUT"
|
||||||
|
? decodeV2SwapExactOut(inputs[i])
|
||||||
|
: decodeV3SwapExactOut(inputs[i]);
|
||||||
if (s) {
|
if (s) {
|
||||||
if (!inputEstablished) {
|
if (!inputEstablished) {
|
||||||
setInputOnce(s.tokenIn, s.amountInMax);
|
setInputOnce(s.tokenIn, s.amountInMax);
|
||||||
@@ -1943,12 +1975,12 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
|||||||
if (amount >= MAX_UINT160) {
|
if (amount >= MAX_UINT160) {
|
||||||
return { raw: "Unlimited", display: "Unlimited" };
|
return { raw: "Unlimited", display: "Unlimited" };
|
||||||
}
|
}
|
||||||
if (hasV2ExactOut) {
|
if (hasV2OrV3ExactOut) {
|
||||||
// A V2 exact-out swap spends at most this figure, whichever
|
// A V2 or V3 exact-out swap spends at most this figure,
|
||||||
// steps gave it (an amountInMax, the WRAP_ETH of a swap paid
|
// whichever steps gave it (an amountInMax, the WRAP_ETH of a
|
||||||
// in ETH, a permit, or several steps' total), so it is said to
|
// swap paid in ETH, a permit, or several steps' total), so it
|
||||||
// be a maximum, in `raw` too: the wait, success and error
|
// is said to be a maximum, in `raw` too: the wait, success and
|
||||||
// screens show `raw` as the transaction's amount.
|
// error screens show `raw` as the transaction's amount.
|
||||||
const most = amountText(amount, info);
|
const most = amountText(amount, info);
|
||||||
return {
|
return {
|
||||||
raw: "Up to " + most.raw,
|
raw: "Up to " + most.raw,
|
||||||
|
|||||||
+131
-15
@@ -88,17 +88,42 @@ function encodeV2SwapExactOut(recipient, amountOut, amountInMax, pathAddrs) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: encode a V3_SWAP_EXACT_IN input (command 0x00)
|
// Helper: a V3 path: token(20) + fee(3) + token(20) ...
|
||||||
function encodeV3SwapExactIn(recipient, amountIn, amountOutMin, pathTokens) {
|
function encodeV3Path(pathTokens) {
|
||||||
// V3 path: token(20) + fee(3) + token(20) ...
|
|
||||||
let pathHex = pathTokens[0].slice(2).toLowerCase();
|
let pathHex = pathTokens[0].slice(2).toLowerCase();
|
||||||
for (let i = 1; i < pathTokens.length; i++) {
|
for (let i = 1; i < pathTokens.length; i++) {
|
||||||
pathHex += "000bb8"; // fee 3000 = 0x000bb8
|
pathHex += "000bb8"; // fee 3000 = 0x000bb8
|
||||||
pathHex += pathTokens[i].slice(2).toLowerCase();
|
pathHex += pathTokens[i].slice(2).toLowerCase();
|
||||||
}
|
}
|
||||||
|
return "0x" + pathHex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: encode a V3_SWAP_EXACT_IN input (command 0x00)
|
||||||
|
function encodeV3SwapExactIn(recipient, amountIn, amountOutMin, pathTokens) {
|
||||||
return coder.encode(
|
return coder.encode(
|
||||||
["address", "uint256", "uint256", "bytes", "bool"],
|
["address", "uint256", "uint256", "bytes", "bool"],
|
||||||
[recipient, amountIn, amountOutMin, "0x" + pathHex, true],
|
[recipient, amountIn, amountOutMin, encodeV3Path(pathTokens), true],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: encode a V3_SWAP_EXACT_OUT input (command 0x01). Its path lists
|
||||||
|
// the token bought first and the token spent last.
|
||||||
|
function encodeV3SwapExactOut(
|
||||||
|
recipient,
|
||||||
|
amountOut,
|
||||||
|
amountInMax,
|
||||||
|
pathTokens,
|
||||||
|
payerIsUser = true,
|
||||||
|
) {
|
||||||
|
return coder.encode(
|
||||||
|
["address", "uint256", "uint256", "bytes", "bool"],
|
||||||
|
[
|
||||||
|
recipient,
|
||||||
|
amountOut,
|
||||||
|
amountInMax,
|
||||||
|
encodeV3Path(pathTokens),
|
||||||
|
payerIsUser,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,6 +669,79 @@ describe("uniswap decoder", () => {
|
|||||||
expect(detail(result, "Min. received").value).toBe("1500.0000 USDC");
|
expect(detail(result, "Min. received").value).toBe("1500.0000 USDC");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// https://git.eeqj.de/sneak/AutistMask/issues/601 — a V3_SWAP_EXACT_OUT
|
||||||
|
// was named but not read, so its tokens read "Unknown (not named in the
|
||||||
|
// calldata)" and it showed no amounts. It is read as a V2_SWAP_EXACT_OUT
|
||||||
|
// is, with its path read as the router reads it: from the token it buys
|
||||||
|
// to the token it spends. Read the other way, each path below would be a
|
||||||
|
// swap of WETH for USDT.
|
||||||
|
test.each([
|
||||||
|
{ hops: "single-hop", path: [WETH_ADDR, USDT_ADDR] },
|
||||||
|
{ hops: "multi-hop", path: [WETH_ADDR, USDC_ADDR, USDT_ADDR] },
|
||||||
|
])(
|
||||||
|
"decodes a $hops V3_SWAP_EXACT_OUT, stating the input amount as a maximum",
|
||||||
|
({ path }) => {
|
||||||
|
// Buy exactly 1 WETH for at most 2,000 USDT, paid by the user.
|
||||||
|
const data = buildExecute(
|
||||||
|
"0x01", // V3_SWAP_EXACT_OUT
|
||||||
|
[encodeV3SwapExactOut(USER_ADDR, ONE_WETH, 2000000000n, path)],
|
||||||
|
9999999999n,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = uniswap.decode(
|
||||||
|
data,
|
||||||
|
ROUTER_ADDR,
|
||||||
|
"0x1",
|
||||||
|
{},
|
||||||
|
USER_ADDR,
|
||||||
|
);
|
||||||
|
expect(result.name).toBe("Swap USDT → WETH");
|
||||||
|
expect(detail(result, "Token In").address).toBe(
|
||||||
|
USDT_ADDR.toLowerCase(),
|
||||||
|
);
|
||||||
|
expect(detail(result, "Token Out").address).toBe(
|
||||||
|
WETH_ADDR.toLowerCase(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const amount = detail(result, "Amount");
|
||||||
|
expect(amount.value).toBe("Up to 2000.0000 USDT");
|
||||||
|
expect(amount.rawValue).toBe("Up to 2000.0000");
|
||||||
|
|
||||||
|
expect(detail(result, "Min. received").value).toBe("1.0000 WETH");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// The V2 exact-out swap paid in ETH above, as a V3_SWAP_EXACT_OUT paid
|
||||||
|
// from the WETH the router holds: it takes nothing more from the user's
|
||||||
|
// wallet.
|
||||||
|
test("a V3 exact-out swap paid in ETH from the router shows the token it buys and a maximum", () => {
|
||||||
|
const data = buildExecute(
|
||||||
|
solidityPacked(["uint8", "uint8", "uint8"], [0x0b, 0x01, 0x0c]),
|
||||||
|
[
|
||||||
|
encodeWrapEth(ROUTER_ADDR, 500000000000000000n),
|
||||||
|
encodeV3SwapExactOut(
|
||||||
|
USER_ADDR,
|
||||||
|
1500000000n, // amountOut: 1,500 USDC
|
||||||
|
500000000000000000n, // amountInMax: 0.5 WETH
|
||||||
|
[USDC_ADDR, WETH_ADDR],
|
||||||
|
false, // paid from the router
|
||||||
|
),
|
||||||
|
encodeWrapEth(USER_ADDR, 0n), // UNWRAP_WETH same encoding
|
||||||
|
],
|
||||||
|
9999999999n,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = uniswap.decode(data, ROUTER_ADDR, "0x1", {}, USER_ADDR);
|
||||||
|
expect(result.name).toBe("Swap ETH → USDC");
|
||||||
|
expect(detail(result, "Amount").value).toBe("Up to 0.5000 ETH");
|
||||||
|
expect(detail(result, "Moved from the sender")).toBeUndefined();
|
||||||
|
expect(detail(result, "Token Out").address).toBe(
|
||||||
|
USDC_ADDR.toLowerCase(),
|
||||||
|
);
|
||||||
|
expect(detail(result, "Paid to")).toBeUndefined();
|
||||||
|
expect(detail(result, "Min. received").value).toBe("1500.0000 USDC");
|
||||||
|
});
|
||||||
|
|
||||||
// Buy exactly 0.5 ETH for at most 1,500 USDC under a permit: the swap buys
|
// Buy exactly 0.5 ETH for at most 1,500 USDC under a permit: the swap buys
|
||||||
// WETH and UNWRAP_WETH turns it into ETH.
|
// WETH and UNWRAP_WETH turns it into ETH.
|
||||||
test("a V2 exact-out swap that buys ETH shows ETH and the permit as a maximum", () => {
|
test("a V2 exact-out swap that buys ETH shows ETH and the permit as a maximum", () => {
|
||||||
@@ -2048,11 +2146,9 @@ describe("uniswap decoder", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// A V3_SWAP_EXACT_OUT the user pays for, of at most `amountInMax`
|
// A V3_SWAP_EXACT_OUT the user pays for, of at most `amountInMax`
|
||||||
// USDT for USDC. Its input is (recipient, amountOut, amountInMax,
|
// USDT for USDC.
|
||||||
// path, payerIsUser), laid out as encodeV3SwapExactIn() writes one;
|
|
||||||
// an exact-out path lists the token bought first.
|
|
||||||
const v3ExactOut = (amountInMax) =>
|
const v3ExactOut = (amountInMax) =>
|
||||||
encodeV3SwapExactIn(USER_ADDR, 390000000n, amountInMax, [
|
encodeV3SwapExactOut(USER_ADDR, 390000000n, amountInMax, [
|
||||||
USDC_ADDR,
|
USDC_ADDR,
|
||||||
USDT_ADDR,
|
USDT_ADDR,
|
||||||
]);
|
]);
|
||||||
@@ -2070,14 +2166,16 @@ describe("uniswap decoder", () => {
|
|||||||
[USDC_ADDR, USER_ADDR, 0n],
|
[USDC_ADDR, USER_ADDR, 0n],
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
// This decoder does not read a V3_SWAP_EXACT_OUT, so what one takes
|
// What a V3_SWAP_EXACT_OUT takes, at most its amountInMax, counts
|
||||||
// leaves no Amount figure wherever it stands, and a permit's amount
|
// as a V2_SWAP_EXACT_OUT's does, and the Amount line is a maximum.
|
||||||
// does not stand.
|
// The permit of 10,000 USDT stands; the one of 600 USDT does not,
|
||||||
|
// because the step before it took its token.
|
||||||
test.each([
|
test.each([
|
||||||
{
|
{
|
||||||
kind: "a V3 exact-in of 600 USDT, then a V3_SWAP_EXACT_OUT",
|
kind: "a V3 exact-in of 600 USDT, then a V3_SWAP_EXACT_OUT of at most 400 USDT",
|
||||||
commands: [0x00, 0x01],
|
commands: [0x00, 0x01],
|
||||||
inputs: [V3_600, v3ExactOut(USDT_400)],
|
inputs: [V3_600, v3ExactOut(USDT_400)],
|
||||||
|
amount: "Up to 1000.0000 USDT",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "a PERMIT2_PERMIT of 10,000 USDT, then a V3_SWAP_EXACT_OUT, a V2_SWAP_EXACT_OUT and a SWEEP",
|
kind: "a PERMIT2_PERMIT of 10,000 USDT, then a V3_SWAP_EXACT_OUT, a V2_SWAP_EXACT_OUT and a SWEEP",
|
||||||
@@ -2086,11 +2184,13 @@ describe("uniswap decoder", () => {
|
|||||||
encodePermit2(USDT_ADDR, 10000000000n, ROUTER_ADDR),
|
encodePermit2(USDT_ADDR, 10000000000n, ROUTER_ADDR),
|
||||||
...EXACT_OUT_SPLIT,
|
...EXACT_OUT_SPLIT,
|
||||||
],
|
],
|
||||||
|
amount: "Up to 10000.0000 USDT",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "a V3_SWAP_EXACT_OUT, a V2_SWAP_EXACT_OUT and a SWEEP",
|
kind: "a V3_SWAP_EXACT_OUT, a V2_SWAP_EXACT_OUT and a SWEEP",
|
||||||
commands: [0x01, 0x09, 0x04],
|
commands: [0x01, 0x09, 0x04],
|
||||||
inputs: EXACT_OUT_SPLIT,
|
inputs: EXACT_OUT_SPLIT,
|
||||||
|
amount: "Up to 10000.0000 USDT",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "a V3_SWAP_EXACT_OUT of at most 9,400 USDT, then a PERMIT2_PERMIT of 600 USDT and a V3 exact-in of 600 USDT",
|
kind: "a V3_SWAP_EXACT_OUT of at most 9,400 USDT, then a PERMIT2_PERMIT of 600 USDT and a V3 exact-in of 600 USDT",
|
||||||
@@ -2100,17 +2200,33 @@ describe("uniswap decoder", () => {
|
|||||||
encodePermit2(USDT_ADDR, USDT_600, ROUTER_ADDR),
|
encodePermit2(USDT_ADDR, USDT_600, ROUTER_ADDR),
|
||||||
V3_600,
|
V3_600,
|
||||||
],
|
],
|
||||||
|
amount: "Up to 10000.0000 USDT",
|
||||||
},
|
},
|
||||||
])(
|
])(
|
||||||
"$kind, all paid by the user, shows no Amount figure",
|
"$kind, all paid by the user, shows the total as a maximum",
|
||||||
({ commands, inputs }) => {
|
({ commands, inputs, amount }) => {
|
||||||
const result = decodeSteps(commands, inputs);
|
const result = decodeSteps(commands, inputs);
|
||||||
expect(linesBetweenTokens(result)).toEqual([
|
expect(linesBetweenTokens(result)).toEqual([
|
||||||
["Moved from the sender", UNDECODED],
|
["Amount", amount],
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// A V3_SWAP_EXACT_OUT whose path is shorter than one token cannot be
|
||||||
|
// read. It may pay any address and take from the sender, so it
|
||||||
|
// leaves no Amount figure.
|
||||||
|
test("a V3 exact-in of 600 USDT, then a V3_SWAP_EXACT_OUT whose path cannot be read, shows no Amount figure", () => {
|
||||||
|
const unreadable = coder.encode(
|
||||||
|
["address", "uint256", "uint256", "bytes", "bool"],
|
||||||
|
[USER_ADDR, 390000000n, USDT_400, "0x", true],
|
||||||
|
);
|
||||||
|
const result = decodeSteps([0x00, 0x01], [V3_600, unreadable]);
|
||||||
|
expect(linesBetweenTokens(result)).toEqual([
|
||||||
|
["Moved from the sender", UNDECODED],
|
||||||
|
]);
|
||||||
|
expect(detail(result, "Paid to").value).toBe(UNDECODED);
|
||||||
|
});
|
||||||
|
|
||||||
// A later step's amount that cannot be added to the Amount line,
|
// A later step's amount that cannot be added to the Amount line,
|
||||||
// because the amount of the step that set the input side is not a
|
// because the amount of the step that set the input side is not a
|
||||||
// quantity, gets lines of its own. When that step stated no amount,
|
// quantity, gets lines of its own. When that step stated no amount,
|
||||||
|
|||||||
@@ -387,6 +387,18 @@ describe("each swap command's recipient", () => {
|
|||||||
data: (r) => execute(swapToWeth(r)),
|
data: (r) => execute(swapToWeth(r)),
|
||||||
min: "1.0000 WETH",
|
min: "1.0000 WETH",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Its input has V3_SWAP_EXACT_IN's layout, with amountOut and
|
||||||
|
// amountInMax in place of amountIn and amountOutMin, and a path
|
||||||
|
// that lists the token bought first.
|
||||||
|
kind: "V3_SWAP_EXACT_OUT",
|
||||||
|
data: (r) =>
|
||||||
|
execute([
|
||||||
|
0x01,
|
||||||
|
v3SwapExactIn(r, ONE_WETH, USDT_2000, WETH, USDT)[1],
|
||||||
|
]),
|
||||||
|
min: "1.0000 WETH",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
kind: "V4_SWAP with TAKE",
|
kind: "V4_SWAP with TAKE",
|
||||||
data: (r) =>
|
data: (r) =>
|
||||||
@@ -2019,10 +2031,6 @@ describe("a payout that cannot be read is never said to be received", () => {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
kind: "V3_SWAP_EXACT_OUT",
|
|
||||||
step: [0x01, swapToWeth(OTHER)[1]],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
kind: "EXECUTE_SUB_PLAN",
|
kind: "EXECUTE_SUB_PLAN",
|
||||||
step: [
|
step: [
|
||||||
|
|||||||
Reference in New Issue
Block a user