fix: the swap decoder reads who the router's transfer commands pay (closes #599)
TRANSFER, PAY_PORTION, PAY_PORTION_FULL_PRECISION (0x07, on the 0x7f-mask routers), PERMIT2_TRANSFER_FROM, ACROSS_V4_DEPOSIT_V3 (0x40) and V4 TAKE_PORTION were payouts the decoder did not read. Each recipient is now read: the sender, the router, a hop into the next V2 pair, or another address, which is named. Paying all the router holds of a token pays it out as SWEEP does; a TRANSFER of CONTRACT_BALANCE of ETH reverts. A PERMIT2_TRANSFER_FROM into the router shows the amount it moves and the token's address, and a bridge deposit names each of its recipient and depositor that is not the sender; either keeps the minimum from reading received. Model: opus-5-5
This commit was merged in pull request #604.
This commit is contained in:
+274
-49
@@ -123,7 +123,10 @@ function v2PairFor(chainId, tokenA, tokenB) {
|
||||
}
|
||||
|
||||
// Universal Router command types, each read from its command byte with the
|
||||
// router's mask (see ROUTER_ADDRESSES)
|
||||
// router's mask (see ROUTER_ADDRESSES). Only the routers with the 0x7f mask
|
||||
// have 0x07 and 0x40: 0x40 is above the other masks, and the other routers
|
||||
// revert on 0x07, so decode() names it there as it names a command not
|
||||
// listed here.
|
||||
const COMMAND_NAMES = {
|
||||
0x00: "V3 Swap (Exact In)",
|
||||
0x01: "V3 Swap (Exact Out)",
|
||||
@@ -132,6 +135,7 @@ const COMMAND_NAMES = {
|
||||
0x04: "Sweep",
|
||||
0x05: "Transfer",
|
||||
0x06: "Pay Portion",
|
||||
0x07: "Pay Portion (Full Precision)",
|
||||
0x08: "V2 Swap (Exact In)",
|
||||
0x09: "V2 Swap (Exact Out)",
|
||||
0x0a: "Permit2 Permit",
|
||||
@@ -145,6 +149,7 @@ const COMMAND_NAMES = {
|
||||
0x13: "V4 Initialize Pool",
|
||||
0x14: "V4 Position Mgr Call",
|
||||
0x21: "Execute Sub-Plan",
|
||||
0x40: "Across Bridge Deposit",
|
||||
};
|
||||
|
||||
// The swap's Amount and Min. received lines land on the same approval screen,
|
||||
@@ -175,6 +180,11 @@ const UNDECODED_CURRENCY = "Unknown (could not be decoded)";
|
||||
const STAYS_IN_ROUTER =
|
||||
"The router, where it stays (no later step pays it out)";
|
||||
|
||||
// The Paid to line for an ACROSS_V4_DEPOSIT_V3 step (see
|
||||
// decodeAcrossV4DepositV3()).
|
||||
const PAID_TO_BRIDGE =
|
||||
"The bridge, which pays the deposit's recipient on another network";
|
||||
|
||||
// Explicit presence, never truthiness. Every gate in this file that guards a
|
||||
// decoded value goes through here: an address is never falsy once set, but an
|
||||
// amount of 0n is, and a gate that cannot tell a genuine zero from an absent
|
||||
@@ -251,9 +261,11 @@ const WETH_ADDRESSES = [
|
||||
// Two recipient values the router reads as placeholders, not as addresses:
|
||||
// MSG_SENDER pays the caller of execute(), which is the transaction's sender,
|
||||
// and ADDRESS_THIS pays the router itself, which holds what it is paid until a
|
||||
// later step pays it out. The swap commands, SWEEP, WRAP_ETH and UNWRAP_WETH
|
||||
// map their recipient through the dispatcher's map(), and a V4 TAKE through
|
||||
// V4Router's _mapRecipient(); TAKE_ALL always pays the caller.
|
||||
// later step pays it out. The swap commands, SWEEP, WRAP_ETH, UNWRAP_WETH,
|
||||
// TRANSFER, PAY_PORTION, PAY_PORTION_FULL_PRECISION and, on every router but
|
||||
// UniversalRouterV1, PERMIT2_TRANSFER_FROM map their recipient through the
|
||||
// dispatcher's map(), and a V4 TAKE and TAKE_PORTION through V4Router's
|
||||
// _mapRecipient(); TAKE_ALL always pays the caller.
|
||||
// - constants: https://github.com/Uniswap/universal-router/blob/cda697da221e24ca143bc4f55023694a2a16796f/contracts/libraries/Constants.sol
|
||||
// - map(), UniversalRouterV1: https://github.com/Uniswap/universal-router/blob/cda697da221e24ca143bc4f55023694a2a16796f/contracts/libraries/Recipient.sol
|
||||
// - map(), UniversalRouterV2: https://github.com/Uniswap/universal-router/blob/3663f6db6e2fe121753cd2d899699c2dc75dca86/contracts/base/Dispatcher.sol
|
||||
@@ -426,6 +438,84 @@ function decodeSweep(input) {
|
||||
}
|
||||
}
|
||||
|
||||
// TRANSFER, PAY_PORTION, PAY_PORTION_FULL_PRECISION and PERMIT2_TRANSFER_FROM
|
||||
// read their parameters as each release's dispatcher reads them:
|
||||
// - UniversalRouterV1: https://github.com/Uniswap/universal-router/blob/cda697da221e24ca143bc4f55023694a2a16796f/contracts/base/Dispatcher.sol
|
||||
// - UniversalRouterV1_2_V2Support: https://github.com/Uniswap/universal-router/blob/41183d6eb154f0ab0e74a0e911a5ef9ea51fc4bd/contracts/base/Dispatcher.sol
|
||||
// - UniversalRouterV2: https://github.com/Uniswap/universal-router/blob/3663f6db6e2fe121753cd2d899699c2dc75dca86/contracts/base/Dispatcher.sol
|
||||
// - UniversalRouterV2_1_1: https://github.com/Uniswap/universal-router/blob/999d561c3ad58fb5cab91b602911f3c75591a9c7/contracts/base/Dispatcher.sol
|
||||
// - UniversalRouterV2_1_2: https://github.com/Uniswap/universal-router/blob/802fe4c18f47300e0f183e2a42e9146ec2ea9fc3/contracts/base/Dispatcher.sol
|
||||
|
||||
// Decode TRANSFER (command 0x05) input bytes, and those of PAY_PORTION (0x06)
|
||||
// and PAY_PORTION_FULL_PRECISION (0x07), which share its layout. Each pays
|
||||
// `token` out of the router to the recipient. TRANSFER pays `amount`, or, of a
|
||||
// token other than ETH, all the router holds when `amount` is
|
||||
// CONTRACT_BALANCE; it sends ETH as written, so a TRANSFER of
|
||||
// CONTRACT_BALANCE of ETH (`token` the zero address) always reverts.
|
||||
// PAY_PORTION pays `amount` ten-thousandths of what the router holds, and
|
||||
// PAY_PORTION_FULL_PRECISION `amount` 1e18ths of it, of ETH too.
|
||||
// - UniversalRouterV1: https://github.com/Uniswap/universal-router/blob/cda697da221e24ca143bc4f55023694a2a16796f/contracts/modules/Payments.sol
|
||||
// - UniversalRouterV2_1_2: https://github.com/Uniswap/universal-router/blob/802fe4c18f47300e0f183e2a42e9146ec2ea9fc3/contracts/modules/Payments.sol
|
||||
// ABI: (address token, address recipient, uint256 amount)
|
||||
function decodeTransfer(input) {
|
||||
try {
|
||||
const d = coder.decode(["address", "address", "uint256"], input);
|
||||
return { token: d[0], recipient: d[1], amount: d[2] };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Decode PERMIT2_TRANSFER_FROM (command 0x02) input bytes. Permit2 moves
|
||||
// `amount` of `token` from the transaction's sender to the recipient.
|
||||
// UniversalRouterV1 hands Permit2 the recipient as written, without map(), so
|
||||
// on that router address(1) and address(2) are paid as those addresses.
|
||||
// ABI: (address token, address recipient, uint160 amount)
|
||||
function decodePermit2TransferFrom(input) {
|
||||
try {
|
||||
const d = coder.decode(["address", "address", "uint160"], input);
|
||||
return { token: d[0], recipient: d[1], amount: d[2] };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Decode ACROSS_V4_DEPOSIT_V3 (command 0x40) input bytes. The router hands
|
||||
// the bridge `inputAmount` of `inputToken`, or all it holds of it when that is
|
||||
// CONTRACT_BALANCE; with `useNative` set it hands over its ETH instead. It
|
||||
// passes `depositor` and `recipient` on as written, without map(). The bridge
|
||||
// pays the recipient on another network what the deposit states, and refunds
|
||||
// a deposit nobody fills to the depositor, who may also change the deposit's
|
||||
// recipient.
|
||||
// - UniversalRouterV2_1_1: https://github.com/Uniswap/universal-router/blob/999d561c3ad58fb5cab91b602911f3c75591a9c7/contracts/modules/ChainedActions.sol
|
||||
// - UniversalRouterV2_1_2: https://github.com/Uniswap/universal-router/blob/802fe4c18f47300e0f183e2a42e9146ec2ea9fc3/contracts/modules/ChainedActions.sol
|
||||
// - parameters, the same at both commits: https://github.com/Uniswap/universal-router/blob/802fe4c18f47300e0f183e2a42e9146ec2ea9fc3/contracts/interfaces/IUniversalRouter.sol
|
||||
// - the bridge's depositV3() and its refund to the depositor: https://github.com/across-protocol/contracts/blob/950e9154f3d61e59590eb7d4403fae5d3cae84f8/contracts/spoke-pools/SpokePool.sol
|
||||
// ABI: (tuple(address depositor, address recipient, address inputToken,
|
||||
// address outputToken, uint256 inputAmount, uint256 outputAmount,
|
||||
// uint256 destinationChainId, address exclusiveRelayer,
|
||||
// uint32 quoteTimestamp, uint32 fillDeadline,
|
||||
// uint32 exclusivityDeadline, bytes message, bool useNative))
|
||||
function decodeAcrossV4DepositV3(input) {
|
||||
try {
|
||||
const d = coder.decode(
|
||||
[
|
||||
"tuple(address,address,address,address,uint256,uint256,uint256,address,uint32,uint32,uint32,bytes,bool)",
|
||||
],
|
||||
input,
|
||||
);
|
||||
return {
|
||||
depositor: d[0][0],
|
||||
recipient: d[0][1],
|
||||
inputToken: d[0][2],
|
||||
inputAmount: d[0][4],
|
||||
useNative: d[0][12],
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Decode WRAP_ETH (command 0x0b) input bytes. The router pays the WETH to the
|
||||
// recipient.
|
||||
// ABI: (address recipient, uint256 amount)
|
||||
@@ -615,9 +705,11 @@ function totalAmount(amounts) {
|
||||
// swap action sets only the sides SETTLE, SETTLE_ALL, TAKE and TAKE_ALL
|
||||
// name.
|
||||
//
|
||||
// `takes` is every TAKE and TAKE_ALL, as the currency it takes and its
|
||||
// recipient as written, MSG_SENDER for a TAKE_ALL. `recipientUnread` is set
|
||||
// when the step has a payout this decoder did not read.
|
||||
// `takes` is every TAKE, TAKE_ALL and TAKE_PORTION, as the currency it takes
|
||||
// and its recipient as written, MSG_SENDER for a TAKE_ALL. TAKE_PORTION takes
|
||||
// a share of the currency, so it does not name the output side as TAKE does.
|
||||
// `recipientUnread` is set when the step has a payout this decoder did not
|
||||
// read.
|
||||
function decodeV4Swap(input) {
|
||||
try {
|
||||
const d = coder.decode(["bytes", "bytes[]"], input);
|
||||
@@ -663,6 +755,13 @@ function decodeV4Swap(input) {
|
||||
const t = coder.decode(["address", "uint256"], params[i]);
|
||||
takeToken = t[0];
|
||||
takes.push({ currency: t[0], recipient: MSG_SENDER });
|
||||
} else if (actionId === V4_TAKE_PORTION) {
|
||||
// TAKE_PORTION: (address currency, address recipient, uint256 bips)
|
||||
const t = coder.decode(
|
||||
["address", "address", "uint256"],
|
||||
params[i],
|
||||
);
|
||||
takes.push({ currency: t[0], recipient: t[1] });
|
||||
} else if (
|
||||
actionId === V4_SWAP_EXACT_IN_SINGLE ||
|
||||
actionId === V4_SWAP_EXACT_IN ||
|
||||
@@ -674,17 +773,18 @@ function decodeV4Swap(input) {
|
||||
} catch {
|
||||
swapUndecoded = true;
|
||||
}
|
||||
} else if (actionId === V4_TAKE_PORTION) {
|
||||
// It pays a share of a currency to an address this decoder
|
||||
// does not read
|
||||
// (https://git.eeqj.de/sneak/AutistMask/issues/599).
|
||||
// V4Router reverts on any action not named here.
|
||||
recipientUnread = true;
|
||||
}
|
||||
// V4Router reverts on any action not named above, so none
|
||||
// of them pays anyone.
|
||||
} catch {
|
||||
// Skip sub-actions we can't decode. A TAKE or TAKE_ALL among
|
||||
// them is a payout this decoder did not read.
|
||||
if (actionId === V4_TAKE || actionId === V4_TAKE_ALL) {
|
||||
// Skip sub-actions we can't decode. A TAKE, TAKE_ALL or
|
||||
// TAKE_PORTION among them is a payout this decoder did not
|
||||
// read.
|
||||
if (
|
||||
actionId === V4_TAKE ||
|
||||
actionId === V4_TAKE_ALL ||
|
||||
actionId === V4_TAKE_PORTION
|
||||
) {
|
||||
recipientUnread = true;
|
||||
}
|
||||
}
|
||||
@@ -824,31 +924,47 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
const paidElsewhere = [];
|
||||
// Set when a step may pay an address this decoder did not read.
|
||||
let payeeUnread = false;
|
||||
// The tokens, lowercased, that a swap step or a V4 TAKE paid to the
|
||||
// router and no later SWEEP of that token, or UNWRAP_WETH of WETH,
|
||||
// paid out.
|
||||
// Set when an ACROSS_V4_DEPOSIT_V3 step pays the bridge.
|
||||
let bridgeDeposit = false;
|
||||
// What each PERMIT2_TRANSFER_FROM moved from the sender into the
|
||||
// router, as { token, amount }. Later steps may leave part of it
|
||||
// there: nothing here checks that they spend all of it.
|
||||
const movedIntoRouter = [];
|
||||
// The tokens, lowercased, that a swap step or a V4 TAKE or
|
||||
// TAKE_PORTION paid to the router and no later step paid out of it in
|
||||
// full: a SWEEP of that token, an UNWRAP_WETH of WETH, or a TRANSFER,
|
||||
// PAY_PORTION, PAY_PORTION_FULL_PRECISION or ACROSS_V4_DEPOSIT_V3 of
|
||||
// all the router holds of that token.
|
||||
const heldByRouter = new Set();
|
||||
const isRouter = (recipient) => {
|
||||
const r = recipient.toLowerCase();
|
||||
return r === ADDRESS_THIS || r === toAddress.toLowerCase();
|
||||
};
|
||||
// A step pays `recipient`, an address other than the sender's or the
|
||||
// router's, as written.
|
||||
const addPaidElsewhere = (recipient) => {
|
||||
const r = recipient.toLowerCase();
|
||||
if (!paidElsewhere.some((a) => a.toLowerCase() === r)) {
|
||||
paidElsewhere.push(recipient);
|
||||
}
|
||||
};
|
||||
// A step pays `recipient`. `output` is the token a swap step or a V4
|
||||
// TAKE pays; other steps pass none.
|
||||
// TAKE or TAKE_PORTION pays; other steps pass none.
|
||||
const addPayee = (recipient, output) => {
|
||||
const r = recipient.toLowerCase();
|
||||
if (isRouter(recipient)) {
|
||||
if (present(output)) heldByRouter.add(output.toLowerCase());
|
||||
} else if (r === MSG_SENDER || r === sender) {
|
||||
paidToSender = true;
|
||||
} else if (!paidElsewhere.some((a) => a.toLowerCase() === r)) {
|
||||
paidElsewhere.push(recipient);
|
||||
} else {
|
||||
addPaidElsewhere(recipient);
|
||||
}
|
||||
};
|
||||
// A swap step or V4 TAKE that pays `token` to `recipient` is a hop,
|
||||
// not a payout, when the very next step is a V2 exact-in whose input
|
||||
// is already paid, whose path starts at `token`, and whose pair (see
|
||||
// v2PairFor()) is `recipient`: Uniswap's mixed routes pay the next V2
|
||||
// pair directly, and the router enforces that next swap's own minimum.
|
||||
// A step that pays `token` to `recipient` is a hop, not a payout,
|
||||
// when the very next step is a V2 exact-in whose input is already
|
||||
// paid, whose path starts at `token`, and whose pair (see v2PairFor())
|
||||
// is `recipient`: Uniswap's mixed routes pay the next V2 pair
|
||||
// directly, and the router enforces that next swap's own minimum.
|
||||
// Its input is already paid when its amountIn is ALREADY_PAID, or
|
||||
// CONTRACT_BALANCE, which Uniswap's routes send and with which the
|
||||
// router adds only what it holds.
|
||||
@@ -870,9 +986,14 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
|
||||
for (let i = 0; i < commandsBytes.length; i++) {
|
||||
const cmdId = commandsBytes[i] & commandTypeMask;
|
||||
// Only the routers with the 0x7f mask have command 0x07 (see
|
||||
// COMMAND_NAMES).
|
||||
const name =
|
||||
cmdId === 0x07 && commandTypeMask !== 0x7f
|
||||
? null
|
||||
: COMMAND_NAMES[cmdId];
|
||||
commandNames.push(
|
||||
COMMAND_NAMES[cmdId] ||
|
||||
"Command 0x" + cmdId.toString(16).padStart(2, "0"),
|
||||
name || "Command 0x" + cmdId.toString(16).padStart(2, "0"),
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -890,6 +1011,25 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
// input side an earlier hop established without breaking
|
||||
// the invariant.
|
||||
if (p) setInput(p.token, p.amount);
|
||||
} else if (cmdId === 0x02) {
|
||||
const p = decodePermit2TransferFrom(inputs[i]);
|
||||
if (!p) {
|
||||
payeeUnread = true;
|
||||
} else if (
|
||||
commandTypeMask === 0x1f &&
|
||||
[MSG_SENDER, ADDRESS_THIS].includes(
|
||||
p.recipient.toLowerCase(),
|
||||
)
|
||||
) {
|
||||
// UniversalRouterV1, the router with the 0x1f mask,
|
||||
// pays address(1) and address(2) as written (see
|
||||
// decodePermit2TransferFrom()).
|
||||
addPaidElsewhere(p.recipient);
|
||||
} else if (isRouter(p.recipient)) {
|
||||
movedIntoRouter.push(p);
|
||||
} else if (!isHop(i, p.token, p.recipient)) {
|
||||
addPayee(p.recipient);
|
||||
}
|
||||
} else if (cmdId === 0x0e) {
|
||||
const b = decodeBalanceCheck(inputs[i]);
|
||||
// The router passes this check whenever the owner holds at
|
||||
@@ -948,6 +1088,67 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
} else {
|
||||
payeeUnread = true;
|
||||
}
|
||||
} else if (
|
||||
cmdId === 0x05 ||
|
||||
cmdId === 0x06 ||
|
||||
(cmdId === 0x07 && commandTypeMask === 0x7f)
|
||||
) {
|
||||
const t = decodeTransfer(inputs[i]);
|
||||
if (t) {
|
||||
if (!isHop(i, t.token, t.recipient)) {
|
||||
addPayee(t.recipient);
|
||||
}
|
||||
// The amount with which TRANSFER, PAY_PORTION or
|
||||
// PAY_PORTION_FULL_PRECISION pays all the router
|
||||
// holds of the token, as SWEEP does.
|
||||
const all =
|
||||
cmdId === 0x05
|
||||
? CONTRACT_BALANCE
|
||||
: cmdId === 0x06
|
||||
? 10000n
|
||||
: 10n ** 18n;
|
||||
// A TRANSFER of ETH has no such amount: the router
|
||||
// sends ETH as written, so a TRANSFER of
|
||||
// CONTRACT_BALANCE of ETH always reverts.
|
||||
const ethTransfer =
|
||||
cmdId === 0x05 &&
|
||||
t.token ===
|
||||
"0x0000000000000000000000000000000000000000";
|
||||
if (
|
||||
t.amount === all &&
|
||||
!ethTransfer &&
|
||||
!isRouter(t.recipient)
|
||||
) {
|
||||
heldByRouter.delete(t.token.toLowerCase());
|
||||
}
|
||||
} else {
|
||||
payeeUnread = true;
|
||||
}
|
||||
} else if (cmdId === 0x40) {
|
||||
// Only the routers with the 0x7f mask read a command type
|
||||
// of 0x40.
|
||||
const a = decodeAcrossV4DepositV3(inputs[i]);
|
||||
if (a) {
|
||||
bridgeDeposit = true;
|
||||
// The bridge pays or refunds these as written, so
|
||||
// only the sender's own address is the sender.
|
||||
for (const r of [a.recipient, a.depositor]) {
|
||||
if (r.toLowerCase() !== sender) {
|
||||
addPaidElsewhere(r);
|
||||
}
|
||||
}
|
||||
// A deposit of CONTRACT_BALANCE pays all the router
|
||||
// holds of its token, or of ETH with useNative set.
|
||||
if (a.inputAmount === CONTRACT_BALANCE) {
|
||||
heldByRouter.delete(
|
||||
a.useNative
|
||||
? "0x0000000000000000000000000000000000000000"
|
||||
: a.inputToken.toLowerCase(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
payeeUnread = true;
|
||||
}
|
||||
} else if (cmdId === 0x0b) {
|
||||
const w = decodeWrapEth(inputs[i]);
|
||||
if (w) {
|
||||
@@ -1016,9 +1217,8 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
}
|
||||
} else {
|
||||
// Any other command may pay an address this decoder does
|
||||
// not read: V3_SWAP_EXACT_OUT does, and so do TRANSFER,
|
||||
// PAY_PORTION and the others
|
||||
// https://git.eeqj.de/sneak/AutistMask/issues/599 lists.
|
||||
// not read: V3_SWAP_EXACT_OUT and
|
||||
// PERMIT2_TRANSFER_FROM_BATCH among them.
|
||||
payeeUnread = true;
|
||||
}
|
||||
} catch {
|
||||
@@ -1169,30 +1369,55 @@ function decode(data, toAddress, chainId, sources, fromAddress) {
|
||||
});
|
||||
}
|
||||
|
||||
// The output is received by the sender only when some step pays the
|
||||
// sender, none pays another address, none may pay an address this
|
||||
// decoder did not read, and the output token is not left in the
|
||||
// router. Otherwise each other address paid is named, and further
|
||||
// lines say when the output stays in the router, a payout could not
|
||||
// be read, or no step names anyone paid.
|
||||
// A Paid to line names each other address paid, and says when a
|
||||
// step pays the bridge, when one moves the sender's tokens into the
|
||||
// router, when the output stays in the router, and when a payout
|
||||
// could not be read. The output is received by the sender only when
|
||||
// some step pays the sender and there is no such line. When no step
|
||||
// names anyone paid, one line says so. `paidTo` holds these lines and
|
||||
// the token line after each move into the router.
|
||||
const outputHeld =
|
||||
present(outputToken) && heldByRouter.has(outputToken.toLowerCase());
|
||||
const received =
|
||||
paidToSender &&
|
||||
paidElsewhere.length === 0 &&
|
||||
!payeeUnread &&
|
||||
!outputHeld;
|
||||
for (const address of paidElsewhere) {
|
||||
details.push({ label: "Paid to", value: address, address });
|
||||
const paidTo = paidElsewhere.map((address) => ({
|
||||
label: "Paid to",
|
||||
value: address,
|
||||
address,
|
||||
}));
|
||||
if (bridgeDeposit) {
|
||||
paidTo.push({ label: "Paid to", value: PAID_TO_BRIDGE });
|
||||
}
|
||||
// Each move into the router is followed by a line giving the moved
|
||||
// token's address, in the shape of the Token In line, so the screens
|
||||
// show it as they show that one.
|
||||
for (const m of movedIntoRouter) {
|
||||
const info = tokenInfo(m.token, sources);
|
||||
paidTo.push({
|
||||
label: "Paid to",
|
||||
value:
|
||||
"The router, " +
|
||||
amountText(m.amount, info).display +
|
||||
" from the sender (later steps may not spend all of it)",
|
||||
});
|
||||
paidTo.push({
|
||||
label: "Token moved into the router",
|
||||
value: info.symbol
|
||||
? info.symbol + " (" + m.token + ")"
|
||||
: m.token,
|
||||
address: m.token,
|
||||
isToken: true,
|
||||
});
|
||||
}
|
||||
if (outputHeld) {
|
||||
details.push({ label: "Paid to", value: STAYS_IN_ROUTER });
|
||||
paidTo.push({ label: "Paid to", value: STAYS_IN_ROUTER });
|
||||
}
|
||||
if (payeeUnread) {
|
||||
details.push({ label: "Paid to", value: UNDECODED_CURRENCY });
|
||||
} else if (!paidToSender && paidElsewhere.length === 0 && !outputHeld) {
|
||||
details.push({ label: "Paid to", value: UNNAMED_CURRENCY });
|
||||
paidTo.push({ label: "Paid to", value: UNDECODED_CURRENCY });
|
||||
}
|
||||
const received = paidToSender && paidTo.length === 0;
|
||||
if (!paidToSender && paidTo.length === 0) {
|
||||
paidTo.push({ label: "Paid to", value: UNNAMED_CURRENCY });
|
||||
}
|
||||
details.push(...paidTo);
|
||||
|
||||
if (present(minOutput)) {
|
||||
// A zero floor is the one case the user most needs stated: the
|
||||
|
||||
Reference in New Issue
Block a user