@rabby-wallet/rabby-swap 0.0.87 → 0.0.88-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/abi.d.ts +2 -0
  2. package/dist/abi.js +2 -0
  3. package/dist/calldata/chain.d.ts +1 -0
  4. package/dist/calldata/chain.js +9 -0
  5. package/dist/calldata/magpie/abi.d.ts +1 -0
  6. package/dist/calldata/magpie/abi.js +3 -0
  7. package/dist/calldata/magpie/dexAggregator.d.ts +3 -0
  8. package/dist/calldata/magpie/dexAggregator.js +30 -0
  9. package/dist/calldata/magpie/index.d.ts +3 -0
  10. package/dist/calldata/magpie/index.js +65 -0
  11. package/dist/calldata/magpie/packed.d.ts +16 -0
  12. package/dist/calldata/magpie/packed.js +30 -0
  13. package/dist/calldata/types.d.ts +22 -0
  14. package/dist/calldata/types.js +1 -0
  15. package/dist/calldata/uni/abi.d.ts +3 -0
  16. package/dist/calldata/uni/abi.js +6 -0
  17. package/dist/calldata/uni/assets.d.ts +5 -0
  18. package/dist/calldata/uni/assets.js +65 -0
  19. package/dist/calldata/uni/index.d.ts +2 -0
  20. package/dist/calldata/uni/index.js +40 -0
  21. package/dist/calldata/uni/shared.d.ts +45 -0
  22. package/dist/calldata/uni/shared.js +222 -0
  23. package/dist/calldata/uni/universalRouter/commands.d.ts +3 -0
  24. package/dist/calldata/uni/universalRouter/commands.js +92 -0
  25. package/dist/calldata/uni/universalRouter/profiles.d.ts +6 -0
  26. package/dist/calldata/uni/universalRouter/profiles.js +27 -0
  27. package/dist/calldata/uni/universalRouter/sdk.d.ts +11 -0
  28. package/dist/calldata/uni/universalRouter/sdk.js +21 -0
  29. package/dist/calldata/uni/universalRouter/v2.d.ts +1 -0
  30. package/dist/calldata/uni/universalRouter/v2.js +18 -0
  31. package/dist/calldata/uni/universalRouter/v4/index.d.ts +3 -0
  32. package/dist/calldata/uni/universalRouter/v4/index.js +139 -0
  33. package/dist/dexs/magpie.d.ts +1 -2
  34. package/dist/dexs/magpie.js +1 -3
  35. package/dist/dexs/uni.d.ts +1 -2
  36. package/dist/dexs/uni.js +1 -3
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.js +1 -0
  39. package/dist/quote.d.ts +3 -20
  40. package/dist/quote.js +3 -3
  41. package/dist/verify/calldata.d.ts +9 -13
  42. package/dist/verify/calldata.js +44 -22
  43. package/dist/verify/sdk.d.ts +2 -1
  44. package/dist/verify/sdk.js +4 -1
  45. package/package.json +7 -2
package/dist/abi.d.ts CHANGED
@@ -335,3 +335,5 @@ export declare const SushiABI: ({
335
335
  stateMutability: string;
336
336
  type: string;
337
337
  })[];
338
+ export { UniABI } from "./calldata/uni/abi";
339
+ export { MagpieABI } from "./calldata/magpie/abi";
package/dist/abi.js CHANGED
@@ -1652,3 +1652,5 @@ export const ZeroXABIV2 = [
1652
1652
  { stateMutability: "payable", type: "fallback" },
1653
1653
  ];
1654
1654
  export const SushiABI = [{ "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, { "inputs": [{ "internalType": "address", "name": "tokenOut", "type": "address" }, { "internalType": "uint256", "name": "amountOut", "type": "uint256" }], "name": "MinimalOutputBalanceViolation", "type": "error" }, { "inputs": [], "name": "safeExecutor", "outputs": [{ "internalType": "contract SafeExecutor", "name": "", "type": "address" }], "stateMutability": "view", "type": "function" }, { "inputs": [{ "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "internalType": "uint256", "name": "amountIn", "type": "uint256" }, { "internalType": "address", "name": "recipient", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "uint256", "name": "amountOutMin", "type": "uint256" }, { "internalType": "address", "name": "executor", "type": "address" }, { "internalType": "bytes", "name": "executorData", "type": "bytes" }], "name": "snwap", "outputs": [{ "internalType": "uint256", "name": "amountOut", "type": "uint256" }], "stateMutability": "payable", "type": "function" }, { "inputs": [{ "components": [{ "internalType": "contract IERC20", "name": "token", "type": "address" }, { "internalType": "uint256", "name": "amountIn", "type": "uint256" }, { "internalType": "address", "name": "transferTo", "type": "address" }], "internalType": "struct InputToken[]", "name": "inputTokens", "type": "tuple[]" }, { "components": [{ "internalType": "contract IERC20", "name": "token", "type": "address" }, { "internalType": "address", "name": "recipient", "type": "address" }, { "internalType": "uint256", "name": "amountOutMin", "type": "uint256" }], "internalType": "struct OutputToken[]", "name": "outputTokens", "type": "tuple[]" }, { "components": [{ "internalType": "address", "name": "executor", "type": "address" }, { "internalType": "uint256", "name": "value", "type": "uint256" }, { "internalType": "bytes", "name": "data", "type": "bytes" }], "internalType": "struct Executor[]", "name": "executors", "type": "tuple[]" }], "name": "snwapMultiple", "outputs": [{ "internalType": "uint256[]", "name": "amountOut", "type": "uint256[]" }], "stateMutability": "payable", "type": "function" }];
1655
+ export { UniABI } from "./calldata/uni/abi";
1656
+ export { MagpieABI } from "./calldata/magpie/abi";
@@ -0,0 +1 @@
1
+ export declare const getNativeTokenAddress: (chainId: number, nativeTokenAddress?: string) => string;
@@ -0,0 +1,9 @@
1
+ import { CHAINS } from "@debank/common";
2
+ export const getNativeTokenAddress = (chainId, nativeTokenAddress) => {
3
+ var _a;
4
+ const native = nativeTokenAddress !== null && nativeTokenAddress !== void 0 ? nativeTokenAddress : (_a = Object.values(CHAINS).find((chain) => chain.id === chainId)) === null || _a === void 0 ? void 0 : _a.nativeTokenAddress;
5
+ if (!native) {
6
+ throw new Error(`Native token is not configured for chain ${chainId}`);
7
+ }
8
+ return native;
9
+ };
@@ -0,0 +1 @@
1
+ export declare const MagpieABI: string[];
@@ -0,0 +1,3 @@
1
+ export const MagpieABI = [
2
+ "function swapWithBackendSignature(bytes swapData) payable returns (uint256 amountOut)",
3
+ ];
@@ -0,0 +1,3 @@
1
+ import { PackedReader, PackedSwap } from "./packed";
2
+ export declare const decodeDexAggregator: (data: PackedReader) => PackedSwap;
3
+ export declare const decodeDexAggregatorInputFee: (data: PackedReader) => string;
@@ -0,0 +1,30 @@
1
+ import BigNumber from "bignumber.js";
2
+ // DexAggregator LibRouter.getData(), verified Arbitrum source:
3
+ // https://sourcify.dev/server/v2/contract/42161/0x20F6ee51340aDEed01A59B0e65cB3703f3dc860c?fields=sources
4
+ export const decodeDexAggregator = (data) => ({
5
+ receiver: data.address(72),
6
+ fromAsset: data.address(92),
7
+ toAsset: data.address(112),
8
+ amountIn: data.uint(132),
9
+ amountOutMin: data.compact(167, 168),
10
+ });
11
+ // LibRouter.getData(): byte 274 holds the fee count, followed by three
12
+ // arrays of compact descriptors (asset, receiver, amount), 3 bytes per entry.
13
+ // transferFees() transfers these separately from the swap's amountIn.
14
+ // Only fees paid in the input asset belong in its total debit.
15
+ // https://sourcify.dev/server/v2/contract/42161/0x20F6ee51340aDEed01A59B0e65cB3703f3dc860c?fields=sources
16
+ // https://docs.fly.trade/developers/api-reference/evm-swap-integration-guide#step-1-get-a-quote
17
+ export const decodeDexAggregatorInputFee = (data) => {
18
+ const count = Number(data.uint(274, 1));
19
+ const inputAsset = new BigNumber(data.address(92).slice(2), 16);
20
+ let total = new BigNumber(0);
21
+ for (let index = 0; index < count; index++) {
22
+ const assetOffset = 275 + index * 3;
23
+ const amountOffset = 275 + (count * 2 + index) * 3;
24
+ const asset = data.compact(assetOffset, assetOffset + 1);
25
+ const amount = data.compact(amountOffset, amountOffset + 1);
26
+ if (inputAsset.eq(asset))
27
+ total = total.plus(amount);
28
+ }
29
+ return total.toFixed(0);
30
+ };
@@ -0,0 +1,3 @@
1
+ import type { DecodeCalldataOptions, DecodeCalldataResult, TxWithChainId } from "../types";
2
+ export declare const decodeInputFee: (tx: TxWithChainId, decoded?: DecodeCalldataResult) => string;
3
+ export declare const decodeCalldata: (tx: TxWithChainId, options?: DecodeCalldataOptions) => DecodeCalldataResult;
@@ -0,0 +1,65 @@
1
+ import { utils } from "ethers";
2
+ import BigNumber from "bignumber.js";
3
+ import { getNativeTokenAddress } from "../chain";
4
+ import { isSameAddress, NULL_ADDRESS } from "../../utils";
5
+ import { MagpieABI } from "./abi";
6
+ import { decodeDexAggregator, decodeDexAggregatorInputFee, } from "./dexAggregator";
7
+ import { PackedReader } from "./packed";
8
+ const magpieInterface = new utils.Interface(MagpieABI);
9
+ const dexAggregatorSelector = magpieInterface.getSighash("swapWithBackendSignature");
10
+ // Keep parsing details private and tied to the decoded result's lifetime.
11
+ const decodedPayloads = new WeakMap();
12
+ const readPayload = (tx) => {
13
+ const selector = tx.data.slice(0, 10).toLowerCase();
14
+ if (selector !== dexAggregatorSelector) {
15
+ throw new Error(`Unsupported Magpie calldata selector: ${selector}`);
16
+ }
17
+ const transaction = magpieInterface.parseTransaction({ data: tx.data });
18
+ const payloadLength = utils.arrayify(transaction.args[0]).length;
19
+ return new PackedReader(tx.data, payloadLength);
20
+ };
21
+ export const decodeInputFee = (tx, decoded) => {
22
+ var _a;
23
+ const cached = decoded && decodedPayloads.get(decoded);
24
+ if (cached && cached.data === tx.data) {
25
+ return ((_a = cached.inputFee) !== null && _a !== void 0 ? _a : (cached.inputFee = decodeDexAggregatorInputFee(cached.payload)));
26
+ }
27
+ return decodeDexAggregatorInputFee(readPayload(tx));
28
+ };
29
+ export const decodeCalldata = (tx, options = {}) => {
30
+ const native = getNativeTokenAddress(tx.chainId, options.nativeTokenAddress);
31
+ const payload = readPayload(tx);
32
+ const swap = decodeDexAggregator(payload);
33
+ const nativeInput = isSameAddress(swap.fromAsset, NULL_ADDRESS);
34
+ let amountIn = new BigNumber(swap.amountIn);
35
+ // DexAggregator.adjustAmountIn(): zero means automatic input, not a zero
36
+ // debit. Native input is known from value minus encoded native fees;
37
+ // ERC20 input depends on min(balance, allowance), unavailable offline.
38
+ // https://sourcify.dev/server/v2/contract/42161/0x20F6ee51340aDEed01A59B0e65cB3703f3dc860c?fields=sources
39
+ const inputFee = nativeInput ? decodeDexAggregatorInputFee(payload) : "0";
40
+ if (amountIn.isZero()) {
41
+ if (!nativeInput)
42
+ throw new Error("Automatic ERC20 input depends on onchain balance and allowance; it cannot be determined from calldata alone");
43
+ amountIn = new BigNumber(tx.value).minus(inputFee);
44
+ }
45
+ if (!amountIn.gt(0))
46
+ throw new Error("Original input amount is zero or unresolved");
47
+ if (nativeInput && !amountIn.plus(inputFee).eq(tx.value))
48
+ throw new Error("Native transaction value does not equal input amount plus encoded native fees");
49
+ const decoded = {
50
+ fromToken: isSameAddress(swap.fromAsset, NULL_ADDRESS)
51
+ ? native
52
+ : swap.fromAsset,
53
+ fromTokenAmount: amountIn.toFixed(0),
54
+ toToken: isSameAddress(swap.toAsset, NULL_ADDRESS) ? native : swap.toAsset,
55
+ minReceiveToTokenAmount: swap.amountOutMin,
56
+ // LibRouter reads toAddress literally; zero is not a msg.sender sentinel.
57
+ toTokenReceiver: swap.receiver,
58
+ };
59
+ decodedPayloads.set(decoded, {
60
+ data: tx.data,
61
+ payload,
62
+ inputFee: nativeInput ? inputFee : undefined,
63
+ });
64
+ return decoded;
65
+ };
@@ -0,0 +1,16 @@
1
+ export interface PackedSwap {
2
+ receiver: string;
3
+ fromAsset: string;
4
+ toAsset: string;
5
+ amountIn: string;
6
+ amountOutMin: string;
7
+ }
8
+ export declare class PackedReader {
9
+ private readonly payloadLength;
10
+ private readonly hex;
11
+ constructor(data: string, payloadLength: number);
12
+ private read;
13
+ address(offset: number): string;
14
+ uint(offset: number, length?: number): string;
15
+ compact(shiftOffset: number, pointerOffset: number): string;
16
+ }
@@ -0,0 +1,30 @@
1
+ import BigNumber from "bignumber.js";
2
+ // Offsets include the selector. Reads must stay inside the declared bytes payload.
3
+ export class PackedReader {
4
+ constructor(data, payloadLength) {
5
+ this.payloadLength = payloadLength;
6
+ this.hex = data.slice(2);
7
+ }
8
+ read(offset, length) {
9
+ if (offset < 68 || offset + length > 68 + this.payloadLength) {
10
+ throw new Error(`Packed field points outside the Magpie bytes payload (offset ${offset}, length ${length})`);
11
+ }
12
+ return this.hex.slice(offset * 2, (offset + length) * 2);
13
+ }
14
+ address(offset) {
15
+ return `0x${this.read(offset, 20)}`.toLowerCase();
16
+ }
17
+ uint(offset, length = 32) {
18
+ return new BigNumber(this.read(offset, length), 16).toFixed(0);
19
+ }
20
+ compact(shiftOffset, pointerOffset) {
21
+ const shift = Number(this.uint(shiftOffset, 1));
22
+ const pointer = Number(this.uint(pointerOffset, 2));
23
+ // Only the high (256 - shift) bits survive Solidity's shr.
24
+ const significantBits = 256 - shift;
25
+ const length = Math.ceil(significantBits / 8);
26
+ return new BigNumber(this.uint(pointer, length))
27
+ .idiv(new BigNumber(2).pow(length * 8 - significantBits))
28
+ .toFixed(0);
29
+ }
30
+ }
@@ -0,0 +1,22 @@
1
+ export interface Tx {
2
+ from: string;
3
+ to: string;
4
+ data: string;
5
+ value: string;
6
+ chainId?: number;
7
+ gas?: number;
8
+ gasPrice?: string;
9
+ }
10
+ export interface TxWithChainId extends Tx {
11
+ chainId: number;
12
+ }
13
+ export interface DecodeCalldataOptions {
14
+ nativeTokenAddress?: string;
15
+ }
16
+ export interface DecodeCalldataResult {
17
+ fromToken: string;
18
+ fromTokenAmount: string;
19
+ toToken: string;
20
+ minReceiveToTokenAmount: string;
21
+ toTokenReceiver: string;
22
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ export declare const UniversalRouterABI: import("ethers/lib/utils").FunctionFragment[];
2
+ export declare const SwapProxyABI: import("ethers/lib/utils").FunctionFragment[];
3
+ export declare const UniABI: import("ethers/lib/utils").FunctionFragment[];
@@ -0,0 +1,6 @@
1
+ import { SwapRouter } from "@uniswap/universal-router-sdk";
2
+ // The current quote flow uses execute directly or through SwapProxy.
3
+ // Keep the official ABI as the source of truth, excluding signed execution.
4
+ export const UniversalRouterABI = Object.values(SwapRouter.INTERFACE.functions).filter((fragment) => fragment.name === "execute");
5
+ export const SwapProxyABI = Object.values(SwapRouter.PROXY_INTERFACE.functions);
6
+ export const UniABI = [...UniversalRouterABI, ...SwapProxyABI];
@@ -0,0 +1,5 @@
1
+ import type { DecodeCalldataResult } from "../types";
2
+ export declare const getArcNativeInputAmount: (token: string, chainId: number, txValue: string) => string | undefined;
3
+ export declare const isSameUniAsset: (a: string, b: string, chainId: number, native: string) => boolean;
4
+ export declare const normalizeUniAssets: (result: DecodeCalldataResult, chainId: number, native: string) => DecodeCalldataResult;
5
+ export declare const getComparableUniInputAmount: (requestedAmount: string, decoded: DecodeCalldataResult, chainId: number, native: string) => string;
@@ -0,0 +1,65 @@
1
+ import BigNumber from "bignumber.js";
2
+ import { isSameAddress } from "../../utils";
3
+ import { ARC_ERC20_USDC } from "../../consts";
4
+ const ARC_CHAIN_ID = 5042;
5
+ const ARC_NATIVE_SCALE = "1000000000000";
6
+ // Arc funds its ERC20 USDC route through msg.value without a WETH-style wrap.
7
+ // Fix the complete input before reading individual router-funded split legs,
8
+ // just as SwapProxy ingress / WRAP_ETH does on other routes. Keep ERC20 units
9
+ // until normalizeUniAssets converts the final result to native units.
10
+ // https://docs.arc.io/arc/concepts/stablecoin-native-model#two-interfaces-one-balance
11
+ export const getArcNativeInputAmount = (token, chainId, txValue) => chainId === ARC_CHAIN_ID &&
12
+ isSameAddress(token, ARC_ERC20_USDC) &&
13
+ new BigNumber(txValue).gt(0)
14
+ ? new BigNumber(txValue).idiv(ARC_NATIVE_SCALE).toFixed(0)
15
+ : undefined;
16
+ // Compare asset identity for refunds only. Do not merge raw native (18) and
17
+ // ERC20 (6) amounts or balances: they share an asset, but use different units.
18
+ // https://docs.arc.io/arc/concepts/stablecoin-native-model#two-interfaces-one-balance
19
+ export const isSameUniAsset = (a, b, chainId, native) => {
20
+ const identity = (token) => chainId === ARC_CHAIN_ID && isSameAddress(token, ARC_ERC20_USDC)
21
+ ? native
22
+ : token;
23
+ return isSameAddress(identity(a), identity(b));
24
+ };
25
+ // Arc exposes one USDC balance as native (18 decimals) and ERC20 (6 decimals).
26
+ // Normalize both the asset and its amount together, after route aggregation.
27
+ // https://docs.arc.io/arc/concepts/stablecoin-native-model#two-interfaces-one-balance
28
+ // Tempo pathUSD is a real 6-decimal TIP20; it must retain its contract address.
29
+ export const normalizeUniAssets = (result, chainId, native) => {
30
+ if (chainId !== ARC_CHAIN_ID || isSameAddress(native, ARC_ERC20_USDC))
31
+ return result;
32
+ const nativeIn = isSameAddress(result.fromToken, ARC_ERC20_USDC);
33
+ const nativeOut = isSameAddress(result.toToken, ARC_ERC20_USDC);
34
+ return Object.assign(Object.assign(Object.assign({}, result), (nativeIn
35
+ ? {
36
+ fromToken: native,
37
+ fromTokenAmount: new BigNumber(result.fromTokenAmount)
38
+ .times(ARC_NATIVE_SCALE)
39
+ .toFixed(),
40
+ }
41
+ : {})), (nativeOut
42
+ ? {
43
+ toToken: native,
44
+ minReceiveToTokenAmount: new BigNumber(result.minReceiveToTokenAmount)
45
+ .times(ARC_NATIVE_SCALE)
46
+ .toFixed(),
47
+ }
48
+ : {}));
49
+ };
50
+ export const getComparableUniInputAmount = (requestedAmount, decoded, chainId, native) => {
51
+ // Keep exact native-value inputs intact. Only Arc's native USDC can also
52
+ // travel through its 6-decimal ERC20 interface, which truncates 12 digits.
53
+ // Compare against that exact integer conversion, without a general tolerance.
54
+ // https://www.arc.io/blog/arc-compatibility-guide-for-existing-evm-apps
55
+ if (decoded.fromTokenAmount !== requestedAmount &&
56
+ chainId === ARC_CHAIN_ID &&
57
+ isSameAddress(decoded.fromToken, native) &&
58
+ !isSameAddress(native, ARC_ERC20_USDC)) {
59
+ return new BigNumber(requestedAmount)
60
+ .idiv(ARC_NATIVE_SCALE)
61
+ .times(ARC_NATIVE_SCALE)
62
+ .toFixed(0);
63
+ }
64
+ return requestedAmount;
65
+ };
@@ -0,0 +1,2 @@
1
+ import type { DecodeCalldataOptions, DecodeCalldataResult, TxWithChainId } from "../types";
2
+ export declare const decodeCalldata: (tx: TxWithChainId, options?: DecodeCalldataOptions) => DecodeCalldataResult;
@@ -0,0 +1,40 @@
1
+ import { utils } from "ethers";
2
+ import BigNumber from "bignumber.js";
3
+ import { getNativeTokenAddress } from "../chain";
4
+ import { UniABI } from "./abi";
5
+ import { chainToken, finishSwap } from "./shared";
6
+ import { walkCommands } from "./universalRouter/commands";
7
+ import { getRouterProfile } from "./universalRouter/profiles";
8
+ import { normalizeUniAssets } from "./assets";
9
+ const uniInterface = new utils.Interface(UniABI);
10
+ export const decodeCalldata = (tx, options = {}) => {
11
+ var _a;
12
+ const native = getNativeTokenAddress(tx.chainId, options.nativeTokenAddress);
13
+ const transaction = uniInterface.parseTransaction({ data: tx.data });
14
+ const ctx = {
15
+ chainId: tx.chainId,
16
+ native,
17
+ sender: tx.from,
18
+ // SwapProxy calls execute normally, so the Router's msgSender() is the
19
+ // proxy, while tx.from remains the user whose requested payout we verify.
20
+ // https://github.com/Uniswap/universal-router/blob/main/contracts/SwapProxy.sol
21
+ routerCaller: transaction.args.router ? tx.to : tx.from,
22
+ // SwapProxy passes the actual Universal Router as an explicit argument.
23
+ router: transaction.args.router || tx.to,
24
+ proxyToken: transaction.args.token,
25
+ proxyAmount: (_a = transaction.args.amount) === null || _a === void 0 ? void 0 : _a.toString(),
26
+ txValue: tx.value,
27
+ };
28
+ const profile = getRouterProfile(ctx.chainId, ctx.router);
29
+ const swap = {};
30
+ if (new BigNumber(ctx.txValue).gt(0))
31
+ swap.pendingMinimums = { [ctx.native.toLowerCase()]: ctx.txValue };
32
+ if (ctx.proxyToken && ctx.proxyAmount && ctx.proxyAmount !== "0") {
33
+ swap.fromToken = ctx.proxyToken;
34
+ swap.fromTokenAmount = ctx.proxyAmount;
35
+ swap.inputAmountLocked = true;
36
+ (swap.pendingMinimums || (swap.pendingMinimums = {}))[chainToken(ctx.proxyToken, ctx.native).toLowerCase()] = ctx.proxyAmount;
37
+ }
38
+ walkCommands(transaction.args.commands, transaction.args.inputs, swap, ctx, profile);
39
+ return normalizeUniAssets(finishSwap(swap), tx.chainId, native);
40
+ };
@@ -0,0 +1,45 @@
1
+ import BigNumber from "bignumber.js";
2
+ import type { DecodeCalldataResult } from "../types";
3
+ export declare const MSG_SENDER = "0x0000000000000000000000000000000000000001";
4
+ export declare const ROUTER = "0x0000000000000000000000000000000000000002";
5
+ export declare const CONTRACT_BALANCE: BigNumber;
6
+ export type Minimums = Record<string, string>;
7
+ export interface Swap {
8
+ fromToken?: string;
9
+ fromTokenAmount?: string;
10
+ toToken?: string;
11
+ minReceive?: string;
12
+ receiver?: string;
13
+ inputAmountLocked?: boolean;
14
+ hasSwap?: boolean;
15
+ pendingMinimums?: Minimums;
16
+ payouts?: Record<string, {
17
+ token: string;
18
+ minimum: string;
19
+ receiver: string;
20
+ }>;
21
+ }
22
+ export interface Context {
23
+ chainId: number;
24
+ native: string;
25
+ sender: string;
26
+ routerCaller: string;
27
+ router: string;
28
+ txValue: string;
29
+ proxyToken?: string;
30
+ proxyAmount?: string;
31
+ }
32
+ export declare const chainToken: (token: string, native: string) => string;
33
+ export declare const realAmount: (amount: string, fallback?: string) => string;
34
+ export declare const assertInputAsset: (swap: Swap, ctx: Context, token: string) => void;
35
+ export declare const setSwap: (swap: Swap, ctx: Context, fromToken: string, toToken: string, amountA: string, amountB: string, payment?: {
36
+ recipient?: string;
37
+ payerIsUser?: boolean;
38
+ inputPair?: string;
39
+ minimums?: Minimums;
40
+ }) => void;
41
+ export declare const setOutput: (swap: Swap, ctx: Context, token: string, recipient: string, minimum?: string, exactAmount?: boolean, poolMinimums?: Minimums) => void;
42
+ export declare const payPortion: (swap: Swap, ctx: Context, token: string, recipient: string, portion: string, denominator: string, poolMinimums?: Minimums) => void;
43
+ export declare const wrapNative: (swap: Swap, ctx: Context, amount: string, recipient: string) => void;
44
+ export declare const unwrapNative: (swap: Swap, ctx: Context, recipient: string, minimum: string) => void;
45
+ export declare const finishSwap: (swap: Swap) => DecodeCalldataResult;
@@ -0,0 +1,222 @@
1
+ import BigNumber from "bignumber.js";
2
+ import { isSameAddress, NULL_ADDRESS } from "../../utils";
3
+ import { getArcNativeInputAmount, isSameUniAsset } from "./assets";
4
+ export const MSG_SENDER = "0x0000000000000000000000000000000000000001";
5
+ export const ROUTER = "0x0000000000000000000000000000000000000002";
6
+ const EETH = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
7
+ export const CONTRACT_BALANCE = new BigNumber(2).pow(255);
8
+ export const chainToken = (token, native) => isSameAddress(token, NULL_ADDRESS) || isSameAddress(token, EETH)
9
+ ? native
10
+ : token;
11
+ export const realAmount = (amount, fallback) => CONTRACT_BALANCE.eq(amount) && fallback ? fallback : amount;
12
+ // Dispatcher.map() and BaseActionsRouter._mapRecipient() use the Router's
13
+ // execution caller, which is SwapProxy for proxied swaps, not the outer user.
14
+ // https://github.com/Uniswap/universal-router/blob/main/contracts/base/Dispatcher.sol
15
+ // https://github.com/Uniswap/v4-periphery/blob/main/src/base/BaseActionsRouter.sol
16
+ const resolveRecipient = (recipient, ctx) => isSameAddress(recipient, MSG_SENDER)
17
+ ? ctx.routerCaller
18
+ : isSameAddress(recipient, ROUTER)
19
+ ? ctx.router
20
+ : recipient;
21
+ const isRouter = (recipient, ctx) => isSameAddress(resolveRecipient(recipient, ctx), ctx.router);
22
+ const isInputRefund = (swap, ctx, output) => swap.hasSwap &&
23
+ isSameUniAsset(output, swap.fromToken || "", ctx.chainId, ctx.native) &&
24
+ !isSameUniAsset(output, swap.toToken || "", ctx.chainId, ctx.native);
25
+ const recordPayout = (swap, ctx, token, recipient, minimum) => {
26
+ var _a;
27
+ const receiver = resolveRecipient(recipient, ctx);
28
+ const key = `${token.toLowerCase()}:${receiver.toLowerCase()}`;
29
+ const payouts = (swap.payouts || (swap.payouts = {}));
30
+ payouts[key] = {
31
+ token,
32
+ receiver,
33
+ minimum: new BigNumber(((_a = payouts[key]) === null || _a === void 0 ? void 0 : _a.minimum) || 0).plus(minimum).toFixed(),
34
+ };
35
+ swap.toToken = token;
36
+ swap.minReceive = payouts[key].minimum;
37
+ swap.receiver = receiver;
38
+ };
39
+ export const assertInputAsset = (swap, ctx, token) => {
40
+ if (swap.inputAmountLocked)
41
+ throw new Error("Additional user-funded debit after prepaid input cannot be represented by the locked input amount");
42
+ if (swap.fromToken &&
43
+ !isSameUniAsset(chainToken(token, ctx.native), swap.fromToken, ctx.chainId, ctx.native)) {
44
+ throw new Error("Multiple user-funded input assets cannot be represented as a single swap result");
45
+ }
46
+ };
47
+ export const setSwap = (swap, ctx, fromToken, toToken, amountA, amountB, payment = {}) => {
48
+ var _a;
49
+ const input = chainToken(fromToken, ctx.native);
50
+ const output = chainToken(toToken, ctx.native);
51
+ const pending = payment.minimums || (swap.pendingMinimums || (swap.pendingMinimums = {}));
52
+ const inputKey = input.toLowerCase();
53
+ const poolCredit = payment.minimums && pending[inputKey] !== undefined;
54
+ if (payment.payerIsUser === true)
55
+ assertInputAsset(swap, ctx, input);
56
+ if (payment.inputPair && payment.payerIsUser === false) {
57
+ // V3 / V4 can pay this token directly into the next V2 pair. Remove only
58
+ // that proven handoff, retaining payments to every other token or recipient.
59
+ const key = `${inputKey}:${payment.inputPair.toLowerCase()}`;
60
+ (_a = swap.payouts) === null || _a === void 0 ? true : delete _a[key];
61
+ }
62
+ if (!swap.inputAmountLocked) {
63
+ if (!swap.hasSwap) {
64
+ const nativeInputAmount = payment.payerIsUser === true
65
+ ? undefined
66
+ : isSameAddress(input, ctx.native) && new BigNumber(ctx.txValue).gt(0)
67
+ ? ctx.txValue
68
+ : getArcNativeInputAmount(input, ctx.chainId, ctx.txValue);
69
+ const amount = nativeInputAmount !== null && nativeInputAmount !== void 0 ? nativeInputAmount : realAmount(amountA, swap.fromTokenAmount || ctx.proxyAmount || ctx.txValue);
70
+ if (!new BigNumber(amount).gt(0) || CONTRACT_BALANCE.eq(amount)) {
71
+ throw new Error("Cannot determine the original input amount from a dynamic router balance");
72
+ }
73
+ swap.fromToken = input;
74
+ swap.fromTokenAmount = amount;
75
+ if (nativeInputAmount !== undefined)
76
+ swap.inputAmountLocked = true;
77
+ }
78
+ else if (payment.payerIsUser !== false &&
79
+ !poolCredit &&
80
+ isSameAddress(input, swap.fromToken || "")) {
81
+ const amount = amountA;
82
+ // Only explicit root inputs can be summed. Intermediate OPEN_DELTA (0)
83
+ // and CONTRACT_BALANCE values describe router funds, not another user debit.
84
+ if (!CONTRACT_BALANCE.eq(amount)) {
85
+ swap.fromTokenAmount = new BigNumber(swap.fromTokenAmount)
86
+ .plus(amount)
87
+ .toFixed();
88
+ }
89
+ }
90
+ }
91
+ if (payment.payerIsUser !== true && pending[inputKey] !== undefined) {
92
+ const amount = amountA;
93
+ pending[inputKey] =
94
+ amount === "0" || CONTRACT_BALANCE.eq(amount)
95
+ ? "0"
96
+ : BigNumber.max(0, new BigNumber(pending[inputKey]).minus(amount)).toFixed();
97
+ }
98
+ swap.toToken = output;
99
+ const minimum = amountB;
100
+ swap.hasSwap = true;
101
+ const recipient = payment.recipient || ROUTER;
102
+ if (isRouter(recipient, ctx)) {
103
+ const key = output.toLowerCase();
104
+ pending[key] = new BigNumber(pending[key] || 0).plus(minimum).toFixed();
105
+ swap.minReceive = pending[key];
106
+ }
107
+ else {
108
+ recordPayout(swap, ctx, output, recipient, minimum);
109
+ }
110
+ };
111
+ export const setOutput = (swap, ctx, token, recipient, minimum, exactAmount = false, poolMinimums) => {
112
+ var _a;
113
+ const output = chainToken(token, ctx.native);
114
+ if (isRouter(recipient, ctx) && !poolMinimums)
115
+ return;
116
+ const pending = poolMinimums || (swap.pendingMinimums || (swap.pendingMinimums = {}));
117
+ const key = output.toLowerCase();
118
+ const amount = (_a = minimum !== null && minimum !== void 0 ? minimum : pending[key]) !== null && _a !== void 0 ? _a : "0";
119
+ pending[key] = exactAmount
120
+ ? BigNumber.max(0, new BigNumber(pending[key] || 0).minus(amount)).toFixed()
121
+ : "0";
122
+ if (isRouter(recipient, ctx)) {
123
+ // TAKE moves pool credit into the router; it is not another pool credit
124
+ // or a payout to the user. Keep the two balances separate.
125
+ const routerMinimums = (swap.pendingMinimums || (swap.pendingMinimums = {}));
126
+ routerMinimums[key] = new BigNumber(routerMinimums[key] || 0)
127
+ .plus(amount)
128
+ .toFixed();
129
+ return;
130
+ }
131
+ // A trailing sweep of the original input is a refund. It must not replace
132
+ // an already identified payout in a different asset.
133
+ if (isInputRefund(swap, ctx, output))
134
+ return;
135
+ recordPayout(swap, ctx, output, recipient, amount);
136
+ };
137
+ // Payments.sol and V4Router TAKE_PORTION transfer floor(balance * portion /
138
+ // denominator). Subtract that fee before a later balance-based payout.
139
+ // https://github.com/Uniswap/universal-router/blob/main/contracts/modules/Payments.sol
140
+ // https://github.com/Uniswap/v4-periphery/blob/main/src/V4Router.sol
141
+ export const payPortion = (swap, ctx, token, recipient, portion, denominator, poolMinimums) => {
142
+ const ratio = new BigNumber(portion);
143
+ if (!ratio.isInteger() || ratio.lt(0) || ratio.gt(denominator))
144
+ throw new Error("Invalid payment portion");
145
+ if (isRouter(recipient, ctx) && !poolMinimums)
146
+ return;
147
+ const pending = poolMinimums || (swap.pendingMinimums || (swap.pendingMinimums = {}));
148
+ const output = chainToken(token, ctx.native);
149
+ const key = output.toLowerCase();
150
+ const minimum = new BigNumber(pending[key] || 0);
151
+ // The remaining balance and the fee are monotone functions of the balance;
152
+ // their lower bounds can both be computed from its known minimum.
153
+ const fee = minimum.times(ratio).idiv(denominator).toFixed(0);
154
+ pending[key] = minimum.minus(fee).toFixed(0);
155
+ if (poolMinimums && isRouter(recipient, ctx)) {
156
+ const routerMinimums = (swap.pendingMinimums || (swap.pendingMinimums = {}));
157
+ routerMinimums[key] = new BigNumber(routerMinimums[key] || 0)
158
+ .plus(fee)
159
+ .toFixed();
160
+ }
161
+ else if (isSameAddress(resolveRecipient(recipient, ctx), ctx.sender)) {
162
+ if (!isInputRefund(swap, ctx, output))
163
+ recordPayout(swap, ctx, output, recipient, fee);
164
+ }
165
+ };
166
+ export const wrapNative = (swap, ctx, amount, recipient) => {
167
+ if (!isRouter(recipient, ctx))
168
+ throw new Error("WRAP_ETH to an external recipient is not modeled as a single swap input");
169
+ const pending = (swap.pendingMinimums || (swap.pendingMinimums = {}));
170
+ const key = ctx.native.toLowerCase();
171
+ pending[key] = CONTRACT_BALANCE.eq(amount)
172
+ ? "0"
173
+ : BigNumber.max(0, new BigNumber(pending[key] || 0).minus(amount)).toFixed();
174
+ if (swap.hasSwap || swap.inputAmountLocked)
175
+ return;
176
+ swap.fromToken = ctx.native;
177
+ // msg.value is the complete native budget, including subsequent split wraps
178
+ // and any input eventually refunded, rather than just the first wrapped leg.
179
+ swap.fromTokenAmount = new BigNumber(ctx.txValue).gt(0)
180
+ ? ctx.txValue
181
+ : realAmount(amount, ctx.txValue);
182
+ swap.inputAmountLocked = true;
183
+ };
184
+ export const unwrapNative = (swap, ctx, recipient, minimum) => {
185
+ setOutput(swap, ctx, ctx.native, recipient, minimum);
186
+ };
187
+ export const finishSwap = (swap) => {
188
+ const payouts = Object.values(swap.payouts || {});
189
+ if (payouts.length > 1) {
190
+ throw new Error("Multiple output assets or recipients cannot be represented as a single swap result");
191
+ }
192
+ if (payouts.length === 1) {
193
+ swap.toToken = payouts[0].token;
194
+ swap.minReceive = payouts[0].minimum;
195
+ swap.receiver = payouts[0].receiver;
196
+ }
197
+ const required = [
198
+ "fromToken",
199
+ "fromTokenAmount",
200
+ "toToken",
201
+ "minReceive",
202
+ "receiver",
203
+ ];
204
+ const missing = required.filter((field) => !swap[field]);
205
+ if (missing.length) {
206
+ throw new Error(`Incomplete swap data: missing ${missing.join(", ")}`);
207
+ }
208
+ if (!new BigNumber(swap.fromTokenAmount).gt(0) ||
209
+ CONTRACT_BALANCE.eq(swap.fromTokenAmount)) {
210
+ throw new Error("Original input amount is zero or unresolved");
211
+ }
212
+ if (isSameAddress(swap.fromToken, swap.toToken)) {
213
+ throw new Error("The route does not describe a swap between two distinct assets");
214
+ }
215
+ return {
216
+ fromToken: swap.fromToken,
217
+ fromTokenAmount: swap.fromTokenAmount,
218
+ toToken: swap.toToken,
219
+ minReceiveToTokenAmount: swap.minReceive,
220
+ toTokenReceiver: swap.receiver,
221
+ };
222
+ };
@@ -0,0 +1,3 @@
1
+ import { Context, Swap } from "../shared";
2
+ import type { RouterProfile } from "./profiles";
3
+ export declare const walkCommands: (commands: string, inputs: string[], swap: Swap, ctx: Context, profile: RouterProfile) => void;