diff --git a/.changeset/lazy-files-cry.md b/.changeset/lazy-files-cry.md new file mode 100644 index 0000000000000..40d9b0c6692d7 --- /dev/null +++ b/.changeset/lazy-files-cry.md @@ -0,0 +1,10 @@ +--- +'@pancakeswap/wagmi': major +'@pancakeswap/universal-router-sdk': patch +'@pancakeswap/smart-router': patch +'@pancakeswap/permit2-sdk': patch +'@pancakeswap/v3-sdk': patch +--- + +Remove binance extension wallet connector @pancakeswap/wagmi +release new pkg @pancakeswap/universal-router-sdk & @pancakeswap/permit2-sdk \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0cd1d7d0e0e5b..1f9b22de108bc 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ npm-debug.log* yarn-debug.log* yarn-error.log* debug.log* +**/vite.config.ts.timestamp*.mjs **/apps/web/config/abi/types **/packages/smart-router/evm/abis/types diff --git a/.vscode/settings.json b/.vscode/settings.json index 9ace958600946..62443b5b42bb5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,6 +23,7 @@ }, "editor.formatOnSave": true, "cSpell.words": [ + "bips", "Aptos", "ARBITRUM", "awgmi", diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index 593432c97d2d2..e8633b253090f 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -62,6 +62,8 @@ const config = { '@pancakeswap/hooks', '@pancakeswap/utils', '@pancakeswap/widgets-internal', + '@pancakeswap/universal-router-sdk', + '@pancakeswap/permit2-sdk', '@pancakeswap/ifos', ], reactStrictMode: true, diff --git a/apps/web/package.json b/apps/web/package.json index fb91421fadf56..d2674a5f5a779 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -52,6 +52,7 @@ "@pancakeswap/ifos": "workspace:*", "@pancakeswap/localization": "workspace:*", "@pancakeswap/multicall": "workspace:*", + "@pancakeswap/permit2-sdk": "workspace:*", "@pancakeswap/pools": "workspace:*", "@pancakeswap/position-managers": "workspace:*", "@pancakeswap/prediction": "workspace:*", @@ -62,6 +63,7 @@ "@pancakeswap/tokens": "workspace:*", "@pancakeswap/ui-wallets": "workspace:*", "@pancakeswap/uikit": "workspace:*", + "@pancakeswap/universal-router-sdk": "workspace:*", "@pancakeswap/utils": "workspace:*", "@pancakeswap/v3-sdk": "workspace:*", "@pancakeswap/wagmi": "workspace:*", @@ -124,7 +126,7 @@ "tiny-invariant": "^1.3.0", "uuid": "^8.0.0", "viem": "1.19.11", - "wagmi": "1.4.7", + "wagmi": "1.4.13", "zod": "^3.22.3" }, "devDependencies": { diff --git a/apps/web/src/config/abi/pancakeSwapSmartRouter.json b/apps/web/src/config/abi/pancakeSwapSmartRouter.json deleted file mode 100644 index cef4edc55e609..0000000000000 --- a/apps/web/src/config/abi/pancakeSwapSmartRouter.json +++ /dev/null @@ -1,130 +0,0 @@ -[ - { - "inputs": [ - { "internalType": "address", "name": "_WETHAddress", "type": "address" }, - { "internalType": "address", "name": "_pancakeswapV2", "type": "address" }, - { "internalType": "address", "name": "_stableswapFactory", "type": "address" } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { "inputs": [], "name": "ApproveCalledOnETH", "type": "error" }, - { "inputs": [], "name": "ETHTransferFailed", "type": "error" }, - { "inputs": [], "name": "ForceApproveFailed", "type": "error" }, - { "inputs": [], "name": "FromIsNotSender", "type": "error" }, - { "inputs": [], "name": "InsufficientBalance", "type": "error" }, - { "inputs": [], "name": "NotEnoughValue", "type": "error" }, - { "inputs": [], "name": "SafeTransferFailed", "type": "error" }, - { "inputs": [], "name": "SafeTransferFromFailed", "type": "error" }, - { "inputs": [], "name": "ToIsNotThis", "type": "error" }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "sender", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "factory", "type": "address" } - ], - "name": "NewStableSwapFactory", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "sender", "type": "address" }, - { "indexed": false, "internalType": "address", "name": "srcTokenAddr", "type": "address" }, - { "indexed": false, "internalType": "address", "name": "dstTokenAddr", "type": "address" }, - { "indexed": false, "internalType": "uint256", "name": "srcAmount", "type": "uint256" } - ], - "name": "Swap", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "sender", "type": "address" }, - { "indexed": false, "internalType": "address", "name": "srcTokenAddr", "type": "address" }, - { "indexed": false, "internalType": "address", "name": "dstTokenAddr", "type": "address" }, - { "indexed": false, "internalType": "uint256", "name": "srcAmount", "type": "uint256" } - ], - "name": "SwapMulti", - "type": "event" - }, - { "stateMutability": "nonpayable", "type": "fallback" }, - { - "inputs": [], - "name": "owner", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pancakeswapV2", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { "inputs": [], "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [{ "internalType": "address", "name": "_factory", "type": "address" }], - "name": "setStableSwapFactory", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "stableswapFactory", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "contract IERC20", "name": "srcToken", "type": "address" }, - { "internalType": "contract IERC20", "name": "dstToken", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" }, - { "internalType": "uint256", "name": "minReturn", "type": "uint256" }, - { "internalType": "enum PancakeSwapSmartRouter.FLAG", "name": "flag", "type": "uint8" } - ], - "name": "swap", - "outputs": [{ "internalType": "uint256", "name": "returnAmount", "type": "uint256" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "contract IERC20[]", "name": "tokens", "type": "address[]" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" }, - { "internalType": "uint256", "name": "minReturn", "type": "uint256" }, - { "internalType": "enum PancakeSwapSmartRouter.FLAG[]", "name": "flags", "type": "uint8[]" } - ], - "name": "swapMulti", - "outputs": [{ "internalType": "uint256", "name": "returnAmount", "type": "uint256" }], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "weth", - "outputs": [{ "internalType": "contract IWETH02", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" - }, - { "stateMutability": "payable", "type": "receive" } -] diff --git a/apps/web/src/config/experimentalFeatures.ts b/apps/web/src/config/experimentalFeatures.ts index 9692dfc4974ad..d898b11add104 100644 --- a/apps/web/src/config/experimentalFeatures.ts +++ b/apps/web/src/config/experimentalFeatures.ts @@ -1,6 +1,7 @@ export enum EXPERIMENTAL_FEATURES { WebNotifications = 'web-notifications', SpeedQuote = 'routing-speed-quote', + UniversalRouter = 'universal-router', } export type EnumValues = T extends { [key: string]: infer U } ? U : never @@ -28,4 +29,9 @@ export const EXPERIMENTAL_FEATURE_CONFIGS: ExperimentalFeatureConfigs = [ percentage: 0.1, whitelist: [], }, + { + feature: EXPERIMENTAL_FEATURES.UniversalRouter, + percentage: 0, + whitelist: [], + }, ] diff --git a/apps/web/src/config/wallet.ts b/apps/web/src/config/wallet.ts index 01a15131d69ce..5f0d243cbe4db 100644 --- a/apps/web/src/config/wallet.ts +++ b/apps/web/src/config/wallet.ts @@ -3,7 +3,6 @@ import { WalletConfigV2 } from '@pancakeswap/ui-wallets' import { WalletFilledIcon } from '@pancakeswap/uikit' import { getTrustWalletProvider } from '@pancakeswap/wagmi/connectors/trustWallet' import type { ExtendEthereum } from 'global' -import { isFirefox } from 'react-device-detect' import { walletConnectNoQrCodeConnector } from '../utils/wagmi' import { ASSET_CDN } from './constants/endpoints' @@ -12,11 +11,11 @@ export enum ConnectorNames { Injected = 'injected', WalletConnect = 'walletConnect', WalletConnectV1 = 'walletConnectLegacy', - BSC = 'bsc', + // BSC = 'bsc', BinanceW3W = 'BinanceW3W', Blocto = 'blocto', WalletLink = 'coinbaseWallet', - Ledger = 'ledger', + // Ledger = 'ledger', TrustWallet = 'trustWallet', CyberWallet = 'cyberwallet', } @@ -87,23 +86,23 @@ const walletsConfig = ({ return undefined }, }, - { - id: 'binance', - title: 'Binance Wallet', - icon: `${ASSET_CDN}/web/wallets/binance.png`, - get installed() { - return typeof window !== 'undefined' && Boolean(window.BinanceChain) - }, - connectorId: ConnectorNames.BSC, - guide: { - desktop: 'https://www.bnbchain.org/en/binance-wallet', - }, - downloadLink: { - desktop: isFirefox - ? 'https://addons.mozilla.org/en-US/firefox/addon/binance-chain/?src=search' - : 'https://chrome.google.com/webstore/detail/binance-wallet/fhbohimaelbohpjbbldcngcnapndodjp', - }, - }, + // { + // id: 'binance', + // title: 'Binance Wallet', + // icon: `${ASSET_CDN}/web/wallets/binance.png`, + // get installed() { + // return typeof window !== 'undefined' && Boolean(window.BinanceChain) + // }, + // connectorId: ConnectorNames.BSC, + // guide: { + // desktop: 'https://www.bnbchain.org/en/binance-wallet', + // }, + // downloadLink: { + // desktop: isFirefox + // ? 'https://addons.mozilla.org/en-US/firefox/addon/binance-chain/?src=search' + // : 'https://chrome.google.com/webstore/detail/binance-wallet/fhbohimaelbohpjbbldcngcnapndodjp', + // }, + // }, { id: 'coinbase', title: 'Coinbase Wallet', @@ -236,12 +235,12 @@ const walletsConfig = ({ desktop: 'https://docs.cyber.co/sdk/cyber-account#supported-chains', }, }, - { - id: 'ledger', - title: 'Ledger', - icon: `${ASSET_CDN}/web/wallets/ledger.png`, - connectorId: ConnectorNames.Ledger, - }, + // { + // id: 'ledger', + // title: 'Ledger', + // icon: `${ASSET_CDN}/web/wallets/ledger.png`, + // connectorId: ConnectorNames.Ledger, + // }, ] } diff --git a/apps/web/src/hooks/useApproveCallback.ts b/apps/web/src/hooks/useApproveCallback.ts index e2258476e3c16..7468246ae0d97 100644 --- a/apps/web/src/hooks/useApproveCallback.ts +++ b/apps/web/src/hooks/useApproveCallback.ts @@ -143,18 +143,11 @@ export function useApproveCallback( }) if (!estimatedGas) return undefined - - return callWithGasPrice( - tokenContract, - 'approve' as const, - [ - spender as Address, - overrideAmountApprove ?? (useExact ? amountToApprove?.quotient ?? targetAmount ?? MaxUint256 : MaxUint256), - ], - { - gas: calculateGasMargin(estimatedGas), - }, - ) + const finalAmount = + overrideAmountApprove ?? (useExact ? amountToApprove?.quotient ?? targetAmount ?? MaxUint256 : MaxUint256) + return callWithGasPrice(tokenContract, 'approve' as const, [spender as Address, finalAmount], { + gas: calculateGasMargin(estimatedGas), + }) .then((response) => { if (addToTransaction && token) { addTransaction(response, { @@ -163,7 +156,7 @@ export function useApproveCallback( text: 'Approve %symbol%', data: { symbol: overrideAmountApprove?.toString() ?? amountToApprove?.currency?.symbol }, }, - approval: { tokenAddress: token.address, spender }, + approval: { tokenAddress: token.address, spender, amount: finalAmount.toString() }, type: 'approve', }) } diff --git a/apps/web/src/hooks/useCallWithGasPrice.ts b/apps/web/src/hooks/useCallWithGasPrice.ts index a245b0f7a86f2..c1c0aefb38752 100644 --- a/apps/web/src/hooks/useCallWithGasPrice.ts +++ b/apps/web/src/hooks/useCallWithGasPrice.ts @@ -1,6 +1,8 @@ import { useCallback } from 'react' import { useGasPrice } from 'state/user/hooks' +import { calculateGasMargin } from 'utils' import { publicClient } from 'utils/wagmi' +import type { EstimateContractGasParameters } from 'viem' import { Abi, Account, @@ -11,10 +13,8 @@ import { InferFunctionName, WriteContractParameters, } from 'viem' -import type { EstimateContractGasParameters } from 'viem' import { useWalletClient } from 'wagmi' import { SendTransactionResult } from 'wagmi/actions' -import { calculateGasMargin } from 'utils' import { useActiveChainId } from './useActiveChainId' export function useCallWithGasPrice() { @@ -100,6 +100,7 @@ export function useCallWithGasPrice() { functionName: methodName, args: methodArgs, gasPrice, + // for some reason gas price is insamely high when using maxuint approval, so commenting out for now gas: calculateGasMargin(gas), value: 0n, ...overrides_, diff --git a/apps/web/src/hooks/useGetENSAddressByName.ts b/apps/web/src/hooks/useGetENSAddressByName.ts index 9645c96be5f9b..958d36891e13a 100644 --- a/apps/web/src/hooks/useGetENSAddressByName.ts +++ b/apps/web/src/hooks/useGetENSAddressByName.ts @@ -12,7 +12,7 @@ const ENS_NAME_REGEX = /^[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a- const ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/ -export const useGetENSAddressByName = (ensNameOrAddress: string) => { +export const useGetENSAddressByName = (ensNameOrAddress?: string) => { const { chainId } = useActiveChainId() const ensSupported = useMemo( () => Boolean(chainId && ENS_SUPPORT_CHAIN_IDS.includes(chainId as (typeof ENS_SUPPORT_CHAIN_IDS)[number])), @@ -22,6 +22,7 @@ export const useGetENSAddressByName = (ensNameOrAddress: string) => { name: ensNameOrAddress, chainId, enabled: + typeof ensNameOrAddress !== 'undefined' && (ENS_NAME_REGEX.test(ensNameOrAddress) || ADDRESS_REGEX.test(ensNameOrAddress)) && chainId !== ChainId.BSC && chainId !== ChainId.BSC_TESTNET && diff --git a/apps/web/src/hooks/usePermit2.ts b/apps/web/src/hooks/usePermit2.ts new file mode 100644 index 0000000000000..92caf35323df7 --- /dev/null +++ b/apps/web/src/hooks/usePermit2.ts @@ -0,0 +1,108 @@ +import { getPermit2Address } from '@pancakeswap/permit2-sdk' +import { Currency, CurrencyAmount, Token } from '@pancakeswap/swap-sdk-core' +import { Permit2Signature } from '@pancakeswap/universal-router-sdk' +import { QueryObserverResult } from '@tanstack/react-query' +import { useCallback, useMemo, useState } from 'react' +import { Address } from 'viem' +import { SendTransactionResult } from 'wagmi/actions' +import useAccountActiveChain from './useAccountActiveChain' +import { useApproveCallback } from './useApproveCallback' +import { Permit2Details, usePermit2Details } from './usePermit2Details' +import { usePermit2Requires } from './usePermit2Requires' +import { useWritePermit } from './useWritePermit' + +type Permit2HookState = { + permit2Allowance: CurrencyAmount | undefined + permit2Details: Permit2Details | undefined + + isPermitting: boolean + isApproving: boolean + isRevoking: boolean + + requirePermit: boolean + requireApprove: boolean + requireRevoke: boolean +} + +type Permit2HookCallback = { + permit: () => Promise + approve: () => Promise + revoke: () => Promise + + refetch: () => Promise> +} + +type UsePermit2ReturnType = Permit2HookState & Permit2HookCallback + +export const usePermit2 = ( + amount: CurrencyAmount | undefined, + spender: Address | undefined, +): UsePermit2ReturnType => { + const { account, chainId } = useAccountActiveChain() + const approveTarget = useMemo(() => getPermit2Address(chainId), [chainId]) + + const { data: permit2Details } = usePermit2Details(account, amount?.currency, spender) + const { + requireApprove, + requirePermit, + requireRevoke, + refetch, + allowance: permit2Allowance, + } = usePermit2Requires(amount, spender) + + const [isPermitting, setIsPermitting] = useState(false) + const [isRevoking, setIsRevoking] = useState(false) + const [isApproving, setIsApproving] = useState(false) + + const writePermit = useWritePermit(amount?.currency, spender, permit2Details?.nonce) + const { approveCallback, revokeCallback } = useApproveCallback(amount, approveTarget) + + const permit = useCallback(async () => { + setIsPermitting(true) + + const signature = await writePermit() + + setIsPermitting(false) + + return signature + }, [writePermit]) + + const approve = useCallback(async () => { + setIsApproving(true) + try { + const result = await approveCallback() + return result + } finally { + setIsApproving(false) + } + }, [approveCallback]) + + const revoke = useCallback(async () => { + setIsRevoking(true) + try { + const result = await revokeCallback() + return result + } finally { + setIsRevoking(false) + } + }, [revokeCallback]) + + return { + permit2Allowance, + permit2Details, + + isPermitting, + isApproving, + isRevoking, + + requireApprove, + requirePermit, + requireRevoke, + + refetch, + + approve, + revoke, + permit, + } +} diff --git a/apps/web/src/hooks/usePermit2Allowance.ts b/apps/web/src/hooks/usePermit2Allowance.ts new file mode 100644 index 0000000000000..74616139054b8 --- /dev/null +++ b/apps/web/src/hooks/usePermit2Allowance.ts @@ -0,0 +1,15 @@ +import { getPermit2Address } from '@pancakeswap/permit2-sdk' +import { Currency, Token } from '@pancakeswap/swap-sdk-core' +import { Address } from 'viem' +import { useActiveChainId } from './useActiveChainId' +import useTokenAllowance from './useTokenAllowance' + +export const usePermit2Allowance = (owner?: Address, token?: Currency) => { + const { chainId } = useActiveChainId() + const { allowance, refetch } = useTokenAllowance( + token?.isNative ? undefined : (token as Token), + owner, + getPermit2Address(chainId), + ) + return { allowance, refetch } +} diff --git a/apps/web/src/hooks/usePermit2Details.ts b/apps/web/src/hooks/usePermit2Details.ts new file mode 100644 index 0000000000000..37c9c49d761fd --- /dev/null +++ b/apps/web/src/hooks/usePermit2Details.ts @@ -0,0 +1,58 @@ +import { Permit2ABI, getPermit2Address } from '@pancakeswap/permit2-sdk' +import { CurrencyAmount, Token } from '@pancakeswap/swap-sdk-core' +import { useQuery } from '@tanstack/react-query' +import { FAST_INTERVAL } from 'config/constants' +import { useMemo } from 'react' +import { publicClient } from 'utils/client' +import { Address, zeroAddress } from 'viem' +import { useActiveChainId } from './useActiveChainId' + +export type Permit2Details = { + // the maximum amount allowed to spend + amount: CurrencyAmount | undefined + // timestamp at which a spender's token allowances become invalid + expiration: number + // an incrementing value indexed per owner,token,and spender for each signature + nonce: number +} + +export const usePermit2Details = ( + owner: Address | undefined, + token: Token | undefined, + spender: Address | undefined, +) => { + const { chainId } = useActiveChainId() + const inputs = useMemo<[Address, Address, Address]>( + () => [owner ?? zeroAddress, token?.address ?? zeroAddress, spender ?? zeroAddress], + [owner, spender, token?.address], + ) + + const placeholderData = useMemo(() => { + return [0n, 0, 0] as const + }, []) + + return useQuery({ + queryKey: ['/token-permit/', chainId, token?.address, owner, spender], + queryFn: async () => + publicClient({ chainId }).readContract({ + abi: Permit2ABI, + address: getPermit2Address(chainId), + functionName: 'allowance', + args: inputs, + }), + placeholderData, + refetchInterval: FAST_INTERVAL, + retry: true, + refetchOnWindowFocus: false, + enabled: Boolean(chainId && token && !token.isNative && spender && owner), + select: (data): Permit2Details | undefined => { + if (!data || token?.isNative) return undefined + const [amount, expiration, nonce] = data + return { + amount: CurrencyAmount.fromRawAmount(token!, amount), + expiration: Number(expiration), + nonce: Number(nonce), + } + }, + }) +} diff --git a/apps/web/src/hooks/usePermit2Requires.ts b/apps/web/src/hooks/usePermit2Requires.ts new file mode 100644 index 0000000000000..18b0a765df110 --- /dev/null +++ b/apps/web/src/hooks/usePermit2Requires.ts @@ -0,0 +1,56 @@ +import { CurrencyAmount, Token } from '@pancakeswap/swap-sdk-core' +import { bscTestnetTokens, ethereumTokens, goerliTestnetTokens } from '@pancakeswap/tokens' +import { useMemo } from 'react' +import { Address, isAddressEqual } from 'viem' +import useAccountActiveChain from './useAccountActiveChain' +import useCurrentBlockTimestamp from './useCurrentBlockTimestamp' +import { usePermit2Allowance } from './usePermit2Allowance' +import { usePermit2Details } from './usePermit2Details' + +const EXPIRES_BUFFER = 60n * 15n // 15 minutes in seconds + +export const usePermit2Requires = (amount: CurrencyAmount | undefined, spender?: Address) => { + const { account } = useAccountActiveChain() + const { allowance, refetch } = usePermit2Allowance(account, amount?.currency) + const { data } = usePermit2Details(account, amount?.currency, spender) + const { amount: permitAmount, expiration = 0n } = data ?? {} + const now = useCurrentBlockTimestamp() ?? 0n + + const requireRevoke = useMemo((): boolean => { + const isMainnetUSDT = + amount?.currency?.chainId === ethereumTokens.usdt.chainId && + isAddressEqual(amount.currency.address, ethereumTokens.usdt.address) + + const isBSCTestNetBUSD = + amount?.currency?.chainId === bscTestnetTokens.busd.chainId && + isAddressEqual(amount.currency.address, bscTestnetTokens.busd.address) + + const isGoerliUSDC = + amount?.currency?.chainId === goerliTestnetTokens.usdc.chainId && + isAddressEqual(amount.currency.address, goerliTestnetTokens.usdc.address) + + if (!isMainnetUSDT && !isBSCTestNetBUSD && !isGoerliUSDC) return false + + return !!allowance && allowance.greaterThan(0) && allowance.lessThan(amount) + }, [allowance, amount]) + + const requireApprove = useMemo((): boolean => { + return !!amount && !!allowance && allowance.lessThan(amount) + }, [allowance, amount]) + + const requirePermit = useMemo((): boolean => { + return ( + (amount && permitAmount?.lessThan(amount)) || (Boolean(expiration) && BigInt(expiration) - now < EXPIRES_BUFFER) + ) + }, [amount, permitAmount, expiration, now]) + + return { + requireApprove, + requireRevoke, + requirePermit, + + allowance, + + refetch, + } +} diff --git a/apps/web/src/hooks/usePublicNodeWaitForTransaction.ts b/apps/web/src/hooks/usePublicNodeWaitForTransaction.ts index 2115e73f822fb..f5a8103949a5f 100644 --- a/apps/web/src/hooks/usePublicNodeWaitForTransaction.ts +++ b/apps/web/src/hooks/usePublicNodeWaitForTransaction.ts @@ -1,24 +1,24 @@ import { ChainId } from '@gelatonetwork/limit-orders-lib' +import { BSC_BLOCK_TIME } from 'config' import { CHAINS } from 'config/chains' +import { AVERAGE_CHAIN_BLOCK_TIMES } from 'config/constants/averageChainBlockTimes' +import { useCallback } from 'react' +import { RetryableError, retry } from 'state/multicall/retry' import { - TransactionReceipt, + BlockNotFoundError, GetTransactionReceiptParameters, - createPublicClient, - http, PublicClient, TransactionNotFoundError, + TransactionReceipt, TransactionReceiptNotFoundError, - BlockNotFoundError, WaitForTransactionReceiptTimeoutError, + createPublicClient, + http, } from 'viem' -import { useCallback } from 'react' -import { retry, RetryableError } from 'state/multicall/retry' import { usePublicClient } from 'wagmi' -import { AVERAGE_CHAIN_BLOCK_TIMES } from 'config/constants/averageChainBlockTimes' -import { BSC_BLOCK_TIME } from 'config' import { useActiveChainId } from './useActiveChainId' -const viemClientsPublicNodes = CHAINS.reduce((prev, cur) => { +export const viemClientsPublicNodes = CHAINS.reduce((prev, cur) => { return { ...prev, [cur.id]: createPublicClient({ diff --git a/apps/web/src/hooks/useTokenAllowance.ts b/apps/web/src/hooks/useTokenAllowance.ts index 95d54b72f95ff..99300af7612f9 100644 --- a/apps/web/src/hooks/useTokenAllowance.ts +++ b/apps/web/src/hooks/useTokenAllowance.ts @@ -1,11 +1,11 @@ -import { Token, CurrencyAmount } from '@pancakeswap/sdk' -import { erc20ABI } from 'wagmi' +import { CurrencyAmount, Token } from '@pancakeswap/sdk' import { useMemo } from 'react' +import { erc20ABI } from 'wagmi' -import { useQuery } from '@tanstack/react-query' +import { QueryObserverResult, useQuery } from '@tanstack/react-query' +import { FAST_INTERVAL } from 'config/constants' import { useActiveChainId } from 'hooks/useActiveChainId' import { publicClient } from 'utils/wagmi' -import { FAST_INTERVAL } from 'config/constants' function useTokenAllowance( token?: Token, @@ -13,7 +13,7 @@ function useTokenAllowance( spender?: string, ): { allowance: CurrencyAmount | undefined - refetch: () => Promise + refetch: () => Promise> } { const { chainId } = useActiveChainId() diff --git a/apps/web/src/hooks/useWritePermit.ts b/apps/web/src/hooks/useWritePermit.ts new file mode 100644 index 0000000000000..8863c123973c1 --- /dev/null +++ b/apps/web/src/hooks/useWritePermit.ts @@ -0,0 +1,43 @@ +import { AllowanceTransfer, Permit, generatePermitTypedData, getPermit2Address } from '@pancakeswap/permit2-sdk' +import { Token } from '@pancakeswap/swap-sdk-core' +import { Permit2Signature } from '@pancakeswap/universal-router-sdk' +import { useCallback } from 'react' +import { Address, isHex } from 'viem' +import { useSignTypedData } from 'wagmi' +import useAccountActiveChain from './useAccountActiveChain' + +export const useWritePermit = (token?: Token, spender?: Address, nonce?: number) => { + const { account, chainId } = useAccountActiveChain() + const { signTypedDataAsync } = useSignTypedData() + + return useCallback(async (): Promise => { + if (!chainId) throw new Error('PERMIT: missing chainId') + if (!token) throw new Error('PERMIT: missing token') + if (!spender) throw new Error('PERMIT: missing spender') + if (!account) throw new Error('PERMIT: missing owner') + if (nonce === undefined) throw new Error('PERMIT: missing nonce') + + const permit: Permit = generatePermitTypedData(token, nonce, spender) + const { + domain, + types, + values: message, + } = AllowanceTransfer.getPermitData(permit, getPermit2Address(chainId), chainId) + + let signature = await signTypedDataAsync({ + account, + domain, + primaryType: 'PermitSingle', + types, + message, + }) + + // @hack: trust extension wallet doesn't prefix the signature with 0x + signature = isHex(signature) ? signature : `0x${signature}` + + return { + ...permit, + signature, + } + }, [account, chainId, nonce, signTypedDataAsync, spender, token]) +} diff --git a/apps/web/src/state/multicall/retry.ts b/apps/web/src/state/multicall/retry.ts index cc273a20f9f24..104bfba5f5e0a 100644 --- a/apps/web/src/state/multicall/retry.ts +++ b/apps/web/src/state/multicall/retry.ts @@ -1,6 +1,6 @@ /* eslint-disable */ -function wait(ms: number): Promise { +export function wait(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)) } diff --git a/apps/web/src/state/transactions/hooks.tsx b/apps/web/src/state/transactions/hooks.tsx index 8ee9c27ad108c..93a16a020016f 100644 --- a/apps/web/src/state/transactions/hooks.tsx +++ b/apps/web/src/state/transactions/hooks.tsx @@ -1,28 +1,29 @@ -import { useCallback, useMemo } from 'react' -import { useSelector } from 'react-redux' import { Order } from '@gelatonetwork/limit-orders-lib' -import { AppState, useAppDispatch } from 'state' -import pickBy from 'lodash/pickBy' -import mapValues from 'lodash/mapValues' +import isEmpty from 'lodash/isEmpty' import keyBy from 'lodash/keyBy' -import orderBy from 'lodash/orderBy' +import mapValues from 'lodash/mapValues' import omitBy from 'lodash/omitBy' -import isEmpty from 'lodash/isEmpty' +import orderBy from 'lodash/orderBy' +import pickBy from 'lodash/pickBy' +import { useCallback, useMemo } from 'react' +import { useSelector } from 'react-redux' +import { AppState, useAppDispatch } from 'state' import { useAccount } from 'wagmi' -import { useActiveChainId } from 'hooks/useActiveChainId' import { FeeAmount } from '@pancakeswap/v3-sdk' +import { useActiveChainId } from 'hooks/useActiveChainId' import { Hash } from 'viem' +import { Token } from '@pancakeswap/swap-sdk-core' import useAccountActiveChain from 'hooks/useAccountActiveChain' -import { TransactionDetails } from './reducer' import { - addTransaction, - TransactionType, - NonBscFarmTransactionType, FarmTransactionStatus, NonBscFarmStepType, + NonBscFarmTransactionType, + TransactionType, + addTransaction, } from './actions' +import { TransactionDetails } from './reducer' // helper that can take a ethers library transaction response and add it to the list of transactions export function useTransactionAdder(): ( @@ -30,7 +31,7 @@ export function useTransactionAdder(): ( customData?: { summary?: string translatableSummary?: { text: string; data?: Record } - approval?: { tokenAddress: string; spender: string } + approval?: { tokenAddress: string; spender: string; amount: string } claim?: { recipient: string } type?: TransactionType order?: Order @@ -203,6 +204,25 @@ export function useHasPendingApproval(tokenAddress: string | undefined, spender: ) } +export function useHasPendingRevocation(token?: Token, spender?: string) { + const allTransactions = useAllActiveChainTransactions() + const pendingApprovals = useMemo(() => { + if (typeof token?.address !== 'string' || typeof spender !== 'string') { + return undefined + } + // eslint-disable-next-line guard-for-in + for (const txHash in allTransactions) { + const tx = allTransactions[txHash] + if (!tx || tx.receipt || tx.type === 'approve' || !tx.approval) continue + if (tx.approval.spender === spender && tx.approval.tokenAddress === token.address && isTransactionRecent(tx)) { + return BigInt(tx.approval.amount ?? 0) + } + } + return undefined + }, [allTransactions, spender, token?.address]) + return pendingApprovals === 0n ?? false +} + // we want the latest one to come first, so return negative if a is after b function newTransactionsFirst(a: TransactionDetails, b: TransactionDetails) { return b.addedTime - a.addedTime diff --git a/apps/web/src/state/transactions/reducer.ts b/apps/web/src/state/transactions/reducer.ts index 69b56741263f4..f3262b5f22942 100644 --- a/apps/web/src/state/transactions/reducer.ts +++ b/apps/web/src/state/transactions/reducer.ts @@ -20,7 +20,7 @@ const now = () => Date.now() export interface TransactionDetails { hash: Hash - approval?: { tokenAddress: string; spender: string } + approval?: { tokenAddress: string; spender: string; amount?: string } type?: TransactionType order?: Order summary?: string @@ -101,14 +101,14 @@ export default createReducer(initialState, (builder) => } else if (tx.type === 'limit-order-cancellation') { confirmOrderCancellation(chainId, receipt.from, hash, receipt.status !== 0) } else if (tx.type === 'non-bsc-farm') { - if (tx.nonBscFarm && tx.nonBscFarm.steps[0].status === FarmTransactionStatus.PENDING) { + if (tx.nonBscFarm?.steps[0].status === FarmTransactionStatus.PENDING) { if (receipt.status === FarmTransactionStatus.FAIL) { tx.nonBscFarm = { ...tx.nonBscFarm, status: receipt.status } } tx.nonBscFarm.steps[0] = { ...tx.nonBscFarm.steps[0], - status: receipt.status || FarmTransactionStatus.PENDING, + status: receipt.status ?? FarmTransactionStatus.PENDING, } } else { tx.nonBscFarm = nonBscFarm diff --git a/apps/web/src/utils/errors.ts b/apps/web/src/utils/errors.ts index c92b591581ebe..b8b3bf9739f8c 100644 --- a/apps/web/src/utils/errors.ts +++ b/apps/web/src/utils/errors.ts @@ -20,3 +20,18 @@ export function getViemErrorMessage(err: any) { } return String(err) } + +export class UserUnexpectedTxError extends BaseError { + override name = 'UserUnexpectedTxError' + + constructor({ expectedData, actualData }: { expectedData: unknown; actualData: unknown }) { + super('User initiated transaction with unexpected data', { + metaMessages: [ + `User initiated transaction with unexpected data`, + ``, + ` Expected data: ${expectedData}`, + ` Actual data: ${actualData}`, + ], + }) + } +} diff --git a/apps/web/src/utils/log.ts b/apps/web/src/utils/log.ts index 4746a742b2d57..227f6e79fac26 100644 --- a/apps/web/src/utils/log.ts +++ b/apps/web/src/utils/log.ts @@ -23,7 +23,7 @@ export const logSwap = ({ chainId: number account: `0x${string}` hash: `0x${string}` - type: 'V2Swap' | 'SmartSwap' | 'StableSwap' | 'MarketMakerSwap' | 'V3SmartSwap' + type: 'V2Swap' | 'SmartSwap' | 'StableSwap' | 'MarketMakerSwap' | 'V3SmartSwap' | 'UniversalRouter' }) => { try { logger.info(type, { diff --git a/apps/web/src/utils/wagmi.ts b/apps/web/src/utils/wagmi.ts index 5bb7b35a76f64..8b7b39e326e6c 100644 --- a/apps/web/src/utils/wagmi.ts +++ b/apps/web/src/utils/wagmi.ts @@ -1,14 +1,13 @@ import { getWagmiConnector } from '@binance/w3w-wagmi-connector' import { CyberWalletConnector, isCyberWallet } from '@cyberlab/cyber-app-sdk' import { ChainId } from '@pancakeswap/chains' -import { BinanceWalletConnector } from '@pancakeswap/wagmi/connectors/binanceWallet' import { BloctoConnector } from '@pancakeswap/wagmi/connectors/blocto' import { TrustWalletConnector } from '@pancakeswap/wagmi/connectors/trustWallet' import memoize from 'lodash/memoize' import { createConfig, createStorage } from 'wagmi' import { CoinbaseWalletConnector } from 'wagmi/connectors/coinbaseWallet' import { InjectedConnector } from 'wagmi/connectors/injected' -import { LedgerConnector } from 'wagmi/connectors/ledger' +// import { LedgerConnector } from 'wagmi/connectors/ledger' import { MetaMaskConnector } from 'wagmi/connectors/metaMask' import { WalletConnectConnector } from 'wagmi/connectors/walletConnect' import { chains, publicClient } from './client' @@ -63,14 +62,12 @@ const bloctoConnector = new BloctoConnector({ }, }) -const ledgerConnector = new LedgerConnector({ - chains, - options: { - projectId: 'e542ff314e26ff34de2d4fba98db70bb', - }, -}) - -export const bscConnector = new BinanceWalletConnector({ chains }) +// const ledgerConnector = new LedgerConnector({ +// chains, +// options: { +// projectId: 'e542ff314e26ff34de2d4fba98db70bb', +// }, +// }) export const trustWalletConnector = new TrustWalletConnector({ chains, @@ -116,10 +113,9 @@ export const wagmiConfig = createConfig({ injectedConnector, coinbaseConnector, walletConnectConnector, - bscConnector, // @ts-ignore FIXME: wagmi bloctoConnector, - ledgerConnector, + // ledgerConnector, trustWalletConnector, binanceWeb3WalletConnector, ...(cyberWalletConnector ? [cyberWalletConnector as any] : []), diff --git a/apps/web/src/views/Swap/MMLinkPools/components/MMCommitButton.tsx b/apps/web/src/views/Swap/MMLinkPools/components/MMCommitButton.tsx index 5ec1119ccb0c8..2ded7e40ed7b2 100644 --- a/apps/web/src/views/Swap/MMLinkPools/components/MMCommitButton.tsx +++ b/apps/web/src/views/Swap/MMLinkPools/components/MMCommitButton.tsx @@ -14,9 +14,9 @@ import { useCallback, useEffect, useState } from 'react' import { Field } from 'state/swap/actions' import { logGTMClickSwapEvent } from 'utils/customGTMEventTracking' import { parseMMError } from 'views/Swap/MMLinkPools/utils/exchange' +import { ConfirmSwapModal } from 'views/Swap/V3Swap/containers/ConfirmSwapModal' import { useConfirmModalState } from 'views/Swap/V3Swap/hooks/useConfirmModalState' import { SendTransactionResult } from 'wagmi/actions' -import { ConfirmSwapModal } from '../../V3Swap/containers/ConfirmSwapModal' import { useSwapCallArguments } from '../hooks/useSwapCallArguments' import { useSwapCallback } from '../hooks/useSwapCallback' import { MMRfqTrade } from '../types' @@ -25,14 +25,14 @@ const SettingsModalWithCustomDismiss = withCustomOnDismiss(SettingsModal) interface SwapCommitButtonPropsType { swapIsUnsupported: boolean - account: string + account: string | undefined showWrap: boolean - wrapInputError: string - onWrap: () => Promise + wrapInputError: string | undefined + onWrap?: () => Promise wrapType: WrapType approval: ApprovalState - approveCallback: () => Promise - revokeCallback: () => Promise + approveCallback: () => Promise + revokeCallback: () => Promise approvalSubmitted: boolean currencies: { INPUT?: Currency @@ -45,11 +45,11 @@ interface SwapCommitButtonPropsType { INPUT?: CurrencyAmount OUTPUT?: CurrencyAmount } - recipient: string + recipient: string | null onUserInput: (field: Field, typedValue: string) => void mmQuoteExpiryRemainingSec?: number | null isPendingError: boolean - currentAllowance: CurrencyAmount + currentAllowance: CurrencyAmount | undefined } export function MMSwapCommitButton({ @@ -106,10 +106,12 @@ export function MMSwapCommitButton({ } setSwapState({ attemptingTxn: true, tradeToConfirm, swapErrorMessage: undefined, txHash: undefined }) return swapCallback() - .then((hash) => { - setSwapState({ attemptingTxn: false, tradeToConfirm, swapErrorMessage: undefined, txHash: hash }) + .then((result) => { + setSwapState({ attemptingTxn: false, tradeToConfirm, swapErrorMessage: undefined, txHash: result.hash }) }) .catch((error) => { + console.error('handleSwap error', error) + setSwapState({ attemptingTxn: false, tradeToConfirm, diff --git a/apps/web/src/views/Swap/MMLinkPools/components/MMCommitButtonV2.tsx b/apps/web/src/views/Swap/MMLinkPools/components/MMCommitButtonV2.tsx new file mode 100644 index 0000000000000..92753fb17a0f6 --- /dev/null +++ b/apps/web/src/views/Swap/MMLinkPools/components/MMCommitButtonV2.tsx @@ -0,0 +1,193 @@ +import { useTranslation } from '@pancakeswap/localization' +import { Currency, CurrencyAmount, Token, TradeType } from '@pancakeswap/sdk' +import { SmartRouterTrade } from '@pancakeswap/smart-router/evm' +import { Button, useModal } from '@pancakeswap/uikit' +import { useExpertMode } from '@pancakeswap/utils/user' +import { CommitButton } from 'components/CommitButton' +import ConnectWalletButton from 'components/ConnectWalletButton' +import SettingsModal, { withCustomOnDismiss } from 'components/Menu/GlobalSettings/SettingsModal' +import { SettingsMode } from 'components/Menu/GlobalSettings/types' +import { WrapType } from 'hooks/useWrapCallback' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { Field } from 'state/swap/actions' +import { logGTMClickSwapEvent } from 'utils/customGTMEventTracking' +import { Address } from 'viem' +import { parseMMError } from 'views/Swap/MMLinkPools/utils/exchange' +import { ConfirmSwapModalV2 } from 'views/Swap/V3Swap/containers/ConfirmSwapModalV2' +import { MMTradeInfo } from '../hooks' +import { useMMConfirmModalState } from '../hooks/useMMConfirmModalState' +import { useSwapCallArguments } from '../hooks/useSwapCallArguments' +import { MMRfqTrade } from '../types' + +const SettingsModalWithCustomDismiss = withCustomOnDismiss(SettingsModal) + +interface SwapCommitButtonPropsType { + mmTradeInfo: MMTradeInfo + swapIsUnsupported: boolean + account: Address | undefined + showWrap: boolean + wrapInputError?: string + onWrap?: () => Promise + wrapType: WrapType + // approval: ApprovalState + // allowance: Allowance + // approvalSubmitted: boolean + currencies: { + INPUT?: Currency + OUTPUT?: Currency + } + isExpertMode: boolean + rfqTrade: MMRfqTrade + swapInputError: string + currencyBalances: { + INPUT?: CurrencyAmount + OUTPUT?: CurrencyAmount + } + recipient: string | null + onUserInput: (field: Field, typedValue: string) => void + mmQuoteExpiryRemainingSec?: number | null + // isPendingError: boolean + // currentAllowance?: CurrencyAmount +} + +export function MMSwapCommitButtonV2({ + mmTradeInfo, + swapIsUnsupported, + account, + showWrap, + wrapInputError, + onWrap, + wrapType, + rfqTrade, + swapInputError, + currencyBalances, + recipient, + onUserInput, +}: // isPendingError, +SwapCommitButtonPropsType) { + const [isExpertMode] = useExpertMode() + + const { t } = useTranslation() + const [tradeToConfirm, setTradeToConfirm] = useState | undefined>(undefined) + const amountToApprove = useMemo(() => { + return mmTradeInfo?.slippageAdjustedAmounts[Field.INPUT] + }, [mmTradeInfo]) + const mmSpender = useMemo(() => { + return mmTradeInfo?.routerAddress as Address | undefined + }, [mmTradeInfo]) + const swapCalls = useSwapCallArguments(rfqTrade.trade, rfqTrade.rfq ?? undefined, recipient ?? undefined) + const { resetState, txHash, confirmState, confirmSteps, callToAction, errorMessage } = useMMConfirmModalState( + isExpertMode ? rfqTrade.trade : tradeToConfirm, + swapCalls, + (recipient as Address) ?? null, + amountToApprove?.currency.isToken ? (amountToApprove as CurrencyAmount) : undefined, + mmSpender, + ) + + // Handlers + + const handleAcceptChanges = useCallback(() => { + setTradeToConfirm(rfqTrade.trade) + resetState() + }, [rfqTrade.trade, resetState]) + + const handleConfirmDismiss = useCallback(() => { + resetState() + // if there was a tx hash, we want to clear the input + if (txHash) { + onUserInput(Field.INPUT, '') + } + }, [resetState, txHash, onUserInput]) + + // Modals + const [indirectlyOpenConfirmModalState, setIndirectlyOpenConfirmModalState] = useState(false) + + const [onPresentSettingsModal] = useModal( + setIndirectlyOpenConfirmModalState(true)} + mode={SettingsMode.SWAP_LIQUIDITY} + />, + ) + + const [onPresentConfirmModal] = useModal( + , + true, + true, + 'MMconfirmSwapModal', + ) + // End Modals + + const onSwapHandler = useCallback(() => { + setTradeToConfirm(rfqTrade.trade) + resetState() + + if (isExpertMode) { + callToAction() + } + onPresentConfirmModal() + logGTMClickSwapEvent() + }, [rfqTrade.trade, resetState, isExpertMode, onPresentConfirmModal, callToAction]) + + // useEffect + useEffect(() => { + if (indirectlyOpenConfirmModalState) { + setIndirectlyOpenConfirmModalState(false) + resetState() + onPresentConfirmModal() + } + }, [indirectlyOpenConfirmModalState, onPresentConfirmModal, resetState]) + + if (swapIsUnsupported) { + return ( + + ) + } + + if (!account) { + return + } + + if (showWrap) { + return ( + + {wrapInputError ?? (wrapType === WrapType.WRAP ? t('Wrap') : wrapType === WrapType.UNWRAP ? t('Unwrap') : null)} + + ) + } + + const isValid = !swapInputError + + return ( + + {parseMMError(swapInputError) || t('Swap')} + + ) +} diff --git a/apps/web/src/views/Swap/MMLinkPools/hooks/useApprove.ts b/apps/web/src/views/Swap/MMLinkPools/hooks/useApprove.ts new file mode 100644 index 0000000000000..5af3d572b88cb --- /dev/null +++ b/apps/web/src/views/Swap/MMLinkPools/hooks/useApprove.ts @@ -0,0 +1,102 @@ +import { CurrencyAmount, Token } from '@pancakeswap/swap-sdk-core' +import { bscTestnetTokens, bscTokens } from '@pancakeswap/tokens' +import { QueryObserverResult } from '@tanstack/react-query' +import useAccountActiveChain from 'hooks/useAccountActiveChain' +import { useApproveCallback } from 'hooks/useApproveCallback' +import useTokenAllowance from 'hooks/useTokenAllowance' +import { useMemo, useState } from 'react' +import { Address, isAddressEqual } from 'viem' +import { SendTransactionResult } from 'wagmi/actions' + +type UseApproveReturnType = { + allowance: CurrencyAmount | undefined + + requireApprove: boolean + requireRevoke: boolean + + isApproving: boolean + isRevoking: boolean + + approve: () => Promise + revoke: () => Promise + refetch: () => Promise> +} + +export const useApproveRequires = (amount: CurrencyAmount | undefined, spender?: Address) => { + const { account } = useAccountActiveChain() + const { allowance, refetch } = useTokenAllowance(amount?.currency, account, spender) + + const requireRevoke = useMemo((): boolean => { + const isMainnetUSDT = + amount?.currency?.chainId === bscTokens.usdt.chainId && + isAddressEqual(amount?.currency.address, bscTokens.usdt.address) + + const isBSCTestNetBUSD = + amount?.currency?.chainId === bscTestnetTokens.busd.chainId && + isAddressEqual(amount?.currency.address, bscTestnetTokens.busd.address) + + if (!isMainnetUSDT || isBSCTestNetBUSD) return false + + return !!allowance && allowance.greaterThan(0) && allowance.lessThan(amount) + }, [allowance, amount]) + + const requireApprove = useMemo((): boolean => { + return !!amount && !!allowance && allowance.lessThan(amount) + }, [allowance, amount]) + + return { + requireApprove, + requireRevoke, + allowance, + refetch, + } +} + +export const useApprove = ( + amount: CurrencyAmount | undefined, + spender: Address | undefined, +): UseApproveReturnType => { + const { requireApprove, requireRevoke, allowance, refetch } = useApproveRequires(amount, spender) + const { revokeCallback, approveCallback } = useApproveCallback(amount, spender) + + const [isRevoking, setIsRevoking] = useState(false) + const [isApproving, setIsApproving] = useState(false) + + const approve = async () => { + setIsApproving(true) + try { + const result = await approveCallback() + setIsApproving(false) + return result + } catch (error) { + setIsApproving(false) + throw error + } + } + + const revoke = async () => { + setIsRevoking(true) + try { + const result = await revokeCallback() + setIsRevoking(false) + return result + } catch (error) { + setIsRevoking(false) + throw error + } + } + + return { + allowance, + + requireApprove, + requireRevoke, + + isApproving, + isRevoking, + + refetch, + approve, + revoke, + } +} diff --git a/apps/web/src/views/Swap/MMLinkPools/hooks/useMMConfirmModalState.ts b/apps/web/src/views/Swap/MMLinkPools/hooks/useMMConfirmModalState.ts new file mode 100644 index 0000000000000..5970228ee343b --- /dev/null +++ b/apps/web/src/views/Swap/MMLinkPools/hooks/useMMConfirmModalState.ts @@ -0,0 +1,239 @@ +import { usePreviousValue } from '@pancakeswap/hooks' +import { SmartRouterTrade } from '@pancakeswap/smart-router' +import { Currency, CurrencyAmount, Token, TradeType } from '@pancakeswap/swap-sdk-core' +import { ConfirmModalState } from '@pancakeswap/widgets-internal' +import { useActiveChainId } from 'hooks/useActiveChainId' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { publicClient } from 'utils/client' +import { UserUnexpectedTxError } from 'utils/errors' +import { Address, Hex } from 'viem' +import { ConfirmAction } from 'views/Swap/V3Swap/hooks/useConfirmModalStateV2' +import { userRejectedError } from 'views/Swap/V3Swap/hooks/useSendSwapTransaction' +import { useApprove, useApproveRequires } from './useApprove' +import { MMSwapCall } from './useSwapCallArguments' +import { useSwapCallback } from './useSwapCallback' + +const useConfirmActions = ( + trade: SmartRouterTrade | undefined, + swapCalls: MMSwapCall[], + recipient: Address | null, + amountToApprove: CurrencyAmount | undefined, + spender: Address | undefined, +) => { + const { chainId } = useActiveChainId() + const { revoke, approve, allowance, refetch } = useApprove(amountToApprove, spender) + const { callback: swap, error: swapCallbackError } = useSwapCallback(trade, recipient, swapCalls) + + const [confirmState, setConfirmState] = useState(ConfirmModalState.REVIEWING) + const [txHash, setTxHash] = useState(undefined) + const [errorMessage, setErrorMessage] = useState(undefined) + + const resetState = useCallback(() => { + setConfirmState(ConfirmModalState.REVIEWING) + setTxHash(undefined) + setErrorMessage(undefined) + }, []) + + const revokeStep = useMemo(() => { + const action = async (nextState?: ConfirmModalState) => { + setTxHash(undefined) + setConfirmState(ConfirmModalState.RESETTING_APPROVAL) + try { + const result = await revoke() + if (result?.hash) { + setTxHash(result.hash) + await publicClient({ chainId }).waitForTransactionReceipt({ hash: result.hash }) + } + + // check if user really reset the approval to 0 + const { data } = await refetch() + const newAllowance = CurrencyAmount.fromRawAmount(amountToApprove?.currency as Currency, data ?? 0n) + if (!newAllowance.equalTo(0)) { + throw new UserUnexpectedTxError({ + expectedData: 0, + actualData: allowance?.toExact(), + }) + } + + setConfirmState(nextState ?? ConfirmModalState.APPROVING_TOKEN) + } catch (error) { + console.error('revoke error', error) + if (userRejectedError(error) || error instanceof UserUnexpectedTxError) { + resetState() + } + } + } + return { + step: ConfirmModalState.RESETTING_APPROVAL, + action, + showIndicator: true, + } + }, [allowance, amountToApprove?.currency, chainId, refetch, resetState, revoke]) + + const approveStep = useMemo(() => { + const action = async (nextState?: ConfirmModalState) => { + setTxHash(undefined) + setConfirmState(ConfirmModalState.APPROVING_TOKEN) + try { + const result = await approve() + if (result?.hash) { + setTxHash(result.hash) + await publicClient({ chainId }).waitForTransactionReceipt({ hash: result.hash }) + } + // check if user really approved the amount trade needs + const { data } = await refetch() + const newAllowance = CurrencyAmount.fromRawAmount(amountToApprove?.currency as Currency, data ?? 0n) + if (amountToApprove && newAllowance && newAllowance.lessThan(amountToApprove)) { + throw new UserUnexpectedTxError({ + expectedData: amountToApprove.toExact(), + actualData: allowance?.toExact(), + }) + } + + setConfirmState(nextState ?? ConfirmModalState.PENDING_CONFIRMATION) + } catch (error) { + console.error('approve error', error) + if (userRejectedError(error) || error instanceof UserUnexpectedTxError) { + resetState() + } + } + } + return { + step: ConfirmModalState.APPROVING_TOKEN, + action, + showIndicator: true, + } + }, [allowance, amountToApprove, approve, chainId, refetch, resetState]) + + const swapStep = useMemo(() => { + const action = async () => { + setTxHash(undefined) + setConfirmState(ConfirmModalState.PENDING_CONFIRMATION) + + if (!swap) { + resetState() + return + } + + if (swapCallbackError) { + setErrorMessage(swapCallbackError) + return + } + try { + const result = await swap() + if (result?.hash) { + setTxHash(result.hash) + await publicClient({ chainId }).waitForTransactionReceipt({ hash: result.hash }) + } + setConfirmState(ConfirmModalState.COMPLETED) + } catch (error: any) { + console.error('swap error', error) + if (userRejectedError(error)) { + resetState() + } else { + setErrorMessage(typeof error === 'string' ? error : error?.message) + } + } + } + return { + step: ConfirmModalState.PENDING_CONFIRMATION, + action, + showIndicator: false, + } + }, [chainId, resetState, swap, swapCallbackError]) + + return { + txHash, + actions: { + [ConfirmModalState.RESETTING_APPROVAL]: revokeStep, + [ConfirmModalState.APPROVING_TOKEN]: approveStep, + [ConfirmModalState.PENDING_CONFIRMATION]: swapStep, + } as Record, + + confirmState, + resetState, + errorMessage, + } +} + +const useCreateConfirmSteps = ( + amountToApprove: CurrencyAmount | undefined, + spender: Address | undefined, + actions: Record, +) => { + const { requireApprove, requireRevoke } = useApproveRequires(amountToApprove, spender) + + return useCallback(() => { + const steps: ConfirmAction[] = [] + if (requireRevoke) { + steps.push(actions[ConfirmModalState.RESETTING_APPROVAL]) + } + if (requireApprove) { + steps.push(actions[ConfirmModalState.APPROVING_TOKEN]) + } + steps.push(actions[ConfirmModalState.PENDING_CONFIRMATION]) + return steps + }, [actions, requireApprove, requireRevoke]) +} + +export const useMMConfirmModalState = ( + trade: SmartRouterTrade | undefined, + swapCalls: MMSwapCall[], + recipient: Address | null, + amountToApprove: CurrencyAmount | undefined, + spender: Address | undefined, +) => { + const { actions, confirmState, resetState, errorMessage, txHash } = useConfirmActions( + trade, + swapCalls, + recipient, + amountToApprove, + spender, + ) + const preConfirmState = usePreviousValue(confirmState) + const confirmSteps = useRef() + + const createSteps = useCreateConfirmSteps(amountToApprove, spender, actions) + + const performStep = useCallback( + async (nextStep?: ConfirmModalState) => { + if (!confirmSteps.current) { + return + } + + const steps = confirmSteps.current + const step = steps.find((s) => s.step === confirmState) ?? steps[0] + + await step.action(nextStep) + }, + [confirmState], + ) + + const callToAction = useCallback(() => { + const steps = createSteps() + confirmSteps.current = steps + const nextStep = steps[1] ? steps[1].step : undefined + + performStep(nextStep) + }, [createSteps, performStep]) + + // auto perform the next step + useEffect(() => { + if ( + preConfirmState !== confirmState && + preConfirmState !== ConfirmModalState.REVIEWING && + confirmSteps.current?.some((step) => step.step === confirmState) + ) { + performStep(confirmState) + } + }, [confirmState, performStep, preConfirmState]) + + return { + callToAction, + errorMessage, + confirmState, + resetState, + txHash, + confirmSteps: confirmSteps.current, + } +} diff --git a/apps/web/src/views/Swap/MMLinkPools/hooks/useSwapCallArguments.ts b/apps/web/src/views/Swap/MMLinkPools/hooks/useSwapCallArguments.ts index 93381144d9245..1ba37bfb9b787 100644 --- a/apps/web/src/views/Swap/MMLinkPools/hooks/useSwapCallArguments.ts +++ b/apps/web/src/views/Swap/MMLinkPools/hooks/useSwapCallArguments.ts @@ -1,10 +1,14 @@ import { SwapParameters, TradeType } from '@pancakeswap/sdk' +import { SmartRouterTrade } from '@pancakeswap/smart-router/evm' import { toHex } from '@pancakeswap/v3-sdk' +import { mmLinkedPoolABI } from 'config/abi/mmLinkedPool' +import useAccountActiveChain from 'hooks/useAccountActiveChain' +import { useGetENSAddressByName } from 'hooks/useGetENSAddressByName' import { useTransactionDeadline } from 'hooks/useTransactionDeadline' import { useMemo } from 'react' import invariant from 'tiny-invariant' -import useAccountActiveChain from 'hooks/useAccountActiveChain' -import { SmartRouterTrade } from '@pancakeswap/smart-router/evm' +import { safeGetAddress } from 'utils' +import { Address, Hex, encodeFunctionData } from 'viem' import { MM_SIGNER, NATIVE_CURRENCY_ADDRESS } from '../constants' import { RFQResponse } from '../types' import { useMMSwapContract } from '../utils/exchange' @@ -14,44 +18,54 @@ export interface SwapCall { parameters: SwapParameters } +export type MMSwapCall = { + address: Address + calldata: Hex + value: Hex +} + /** * Returns the swap calls that can be used to make the trade * @param trade trade to execute * @param allowedSlippage user allowed slippage * @param recipientAddressOrName */ -export function useSwapCallArguments( +export const useSwapCallArguments = ( trade: SmartRouterTrade | null | undefined, // trade to execute, required - rfq: RFQResponse['message'], - recipientAddress: string, // the address of the recipient of the trade, or null if swap should be returned to sender -): SwapCall[] { + rfq: RFQResponse['message'] | undefined, + recipientAddressOrName: string | undefined, // the address of the recipient of the trade, or null if swap should be returned to sender +): MMSwapCall[] => { const { account, chainId } = useAccountActiveChain() - const recipient = recipientAddress ?? account + const recipientENSAddress = useGetENSAddressByName(recipientAddressOrName ?? undefined) + const recipient: Address | undefined = useMemo(() => { + if (!recipientAddressOrName || !recipientENSAddress) return account + return safeGetAddress(recipientAddressOrName) ?? safeGetAddress(recipientENSAddress) ?? undefined + }, [account, recipientAddressOrName, recipientENSAddress]) const [deadline] = useTransactionDeadline() const contract = useMMSwapContract() const mmSigner = (chainId && rfq?.mmId && MM_SIGNER?.[chainId]?.[rfq.mmId]) ?? '' return useMemo(() => { - if (!trade || !recipient || !account || !chainId || !deadline || !mmSigner || !rfq) return [] - - if (!contract) { - return [] - } - const swapMethods: any[] = [] + if (!trade || !recipient || !account || !chainId || !rfq || !contract || !deadline || !mmSigner) return [] - swapMethods.push(swapCallParameters(mmSigner, trade, rfq, recipient)) - - return swapMethods.map((parameters) => ({ parameters, contract })) - }, [account, chainId, contract, deadline, recipient, trade, rfq, mmSigner]) + const { calldata, value } = swapCallParameters(mmSigner, trade, rfq, recipient) + return [ + { + address: contract.address, + calldata, + value, + }, + ] + }, [account, chainId, contract, deadline, mmSigner, recipient, rfq, trade]) } -function swapCallParameters( - mmSigner: string, +const swapCallParameters = ( + mmSigner: Address, trade: SmartRouterTrade, rfq: RFQResponse['message'], - recipient: string, -): SwapParameters { + recipient: Address, +) => { const etherIn = trade.inputAmount.currency.isNative const etherOut = trade.outputAmount.currency.isNative // the router does not support both ether in and out @@ -59,30 +73,28 @@ function swapCallParameters( invariant(rfq, 'RFQ_REQUIRED') invariant(rfq.trader !== NATIVE_CURRENCY_ADDRESS, 'RFQ_REQUIRED') - const methodName = 'swap' - const args = [ - mmSigner, - { - nonce: rfq?.nonce, - user: recipient, - baseToken: rfq?.takerSideToken, - quoteToken: rfq?.makerSideToken, - baseTokenAmount: rfq?.takerSideTokenAmount, - quoteTokenAmount: rfq?.makerSideTokenAmount, - expiryTimestamp: rfq?.quoteExpiry.toString(), - }, - rfq.signature, - ] - let value: string | undefined + const calldata = encodeFunctionData({ + abi: mmLinkedPoolABI, + functionName: 'swap', + args: [ + mmSigner, + { + nonce: BigInt(rfq.nonce), + user: recipient, + baseToken: rfq.takerSideToken as Address, + quoteToken: rfq.makerSideToken as Address, + baseTokenAmount: BigInt(rfq.takerSideTokenAmount), + quoteTokenAmount: BigInt(rfq.makerSideTokenAmount), + expiryTimestamp: BigInt(rfq.quoteExpiry), + }, + rfq.signature as Hex, + ], + }) - if (etherIn) { - value = rfq.takerSideTokenAmount - } + const value = etherIn ? toHex(rfq.takerSideTokenAmount) : toHex(0) return { - methodName, - // @ts-ignore - args, - value: value ? toHex(value) : '0x', + calldata, + value, } } diff --git a/apps/web/src/views/Swap/MMLinkPools/hooks/useSwapCallback.ts b/apps/web/src/views/Swap/MMLinkPools/hooks/useSwapCallback.ts index bafa6c02da1e4..b33e198c478c5 100644 --- a/apps/web/src/views/Swap/MMLinkPools/hooks/useSwapCallback.ts +++ b/apps/web/src/views/Swap/MMLinkPools/hooks/useSwapCallback.ts @@ -1,18 +1,20 @@ import { useTranslation } from '@pancakeswap/localization' -import { SwapParameters, TradeType } from '@pancakeswap/sdk' -import isZero from '@pancakeswap/utils/isZero' +import { ChainId, TradeType } from '@pancakeswap/sdk' +import { SmartRouterTrade } from '@pancakeswap/smart-router/evm' import truncateHash from '@pancakeswap/utils/truncateHash' +import useAccountActiveChain from 'hooks/useAccountActiveChain' import { useMemo } from 'react' +import { useSwapState } from 'state/swap/hooks' import { useTransactionAdder } from 'state/transactions/hooks' -import { useGasPrice } from 'state/user/hooks' -import { Hash, hexToBigInt } from 'viem' import { calculateGasMargin, safeGetAddress } from 'utils' import { logSwap, logTx } from 'utils/log' import { isUserRejected } from 'utils/sentry' import { transactionErrorToUserReadableMessage } from 'utils/transactionErrorToUserReadableMessage' -import useAccountActiveChain from 'hooks/useAccountActiveChain' -import { SmartRouterTrade } from '@pancakeswap/smart-router/evm' -import { useMMSwapContract } from '../utils/exchange' +import { viemClients } from 'utils/viem' +import { Address, Hex, hexToBigInt } from 'viem' +import { useSendTransaction } from 'wagmi' +import { SendTransactionResult } from 'wagmi/actions' +import { MMSwapCall } from './useSwapCallArguments' export enum SwapCallbackState { INVALID, @@ -21,8 +23,9 @@ export enum SwapCallbackState { } interface SwapCall { - contract: ReturnType - parameters: SwapParameters + address: Address + calldata: Hex + value: Hex } interface SuccessfulCall extends SwapCallEstimate { @@ -40,16 +43,12 @@ interface SwapCallEstimate { // returns a function that will execute a swap, if the parameters are all valid // and the user has approved the slippage adjusted input amount for the trade export function useSwapCallback( - trade: SmartRouterTrade, // trade to execute, required + trade: SmartRouterTrade | undefined, // trade to execute, required recipientAddress: string | null, // the address of the recipient of the trade, or null if swap should be returned to sender - swapCalls: SwapCall[], -): { state: SwapCallbackState; callback: null | (() => Promise); error: string | null } { + swapCalls: MMSwapCall[], +): { state: SwapCallbackState; callback: null | (() => Promise); error: string | null } { const { account, chainId } = useAccountActiveChain() - const gasPrice = useGasPrice() - - const { t } = useTranslation() - - const addTransaction = useTransactionAdder() + const { callback } = useSendMMTransaction(account, chainId, trade, swapCalls) const recipient = recipientAddress === null ? account : recipientAddress @@ -66,16 +65,44 @@ export function useSwapCallback( return { state: SwapCallbackState.VALID, - callback: async function onSwap(): Promise { + callback, + error: null, + } + }, [trade, account, chainId, recipient, callback, recipientAddress]) +} + +const useSendMMTransaction = ( + account?: Address, + chainId?: number, + trade?: SmartRouterTrade, + swapCalls: MMSwapCall[] = [], +): { callback: null | (() => Promise) } => { + const { t } = useTranslation() + const addTransaction = useTransactionAdder() + const { sendTransactionAsync } = useSendTransaction() + const publicClient = viemClients[chainId as ChainId] + const { recipient } = useSwapState() + const recipientAddress = recipient === null ? account : recipient + + return useMemo(() => { + if (!trade || !account || !chainId || !publicClient || !sendTransactionAsync) { + return { callback: null } + } + + return { + callback: async function callback(): Promise { const estimatedCalls: SwapCallEstimate[] = await Promise.all( swapCalls.map((call) => { - const { - parameters: { methodName, args, value }, - contract, - } = call - const options = - !value || isZero(value) ? { value: undefined, account } : { value: hexToBigInt(value), account } - return contract.estimateGas[methodName](args, options) + const { address, calldata, value } = call + const tx = { + account, + to: address, + data: calldata, + value: hexToBigInt(value), + } + + return publicClient + .estimateGas(tx) .then((gasEstimate) => { return { call, @@ -83,18 +110,8 @@ export function useSwapCallback( } }) .catch((gasError) => { - console.error('Gas estimate failed, trying eth_call to extract error', call) - - return contract.simulate[methodName](args, options) - .then((result) => { - console.error('Unexpected successful call after failed estimate gas', call, gasError, result) - return { call, error: t('Unexpected issue with estimating the gas. Please try again.') } - }) - .catch((callError) => { - console.error('Call threw error', call, callError) - - return { call, error: transactionErrorToUserReadableMessage(callError, t) } - }) + console.debug('Gas estimate failed, trying to extract error', call, gasError) + return { call, error: transactionErrorToUserReadableMessage(gasError, t) } }) }), ) @@ -112,19 +129,20 @@ export function useSwapCallback( } const { - call: { - contract, - parameters: { methodName, args, value }, - }, + call: { address, calldata, value }, gasEstimate, } = successfulEstimation - return contract.write[methodName](args, { + return sendTransactionAsync({ + account, + chainId, + to: address, + data: calldata, + value: hexToBigInt(value), gas: calculateGasMargin(gasEstimate), - gasPrice, - ...(value && !isZero(value) ? { value, account } : { account }), }) - .then((response: Hash) => { + .then((response) => { + const { hash } = response const inputSymbol = trade.inputAmount.currency.symbol const outputSymbol = trade.outputAmount.currency.symbol // const pct = basisPointsToPercent(allowedSlippage) @@ -153,7 +171,7 @@ export function useSwapCallback( : 'Swap %inputAmount% %inputSymbol% for min. %outputAmount% %outputSymbol% to %recipientAddress%' addTransaction( - { hash: response }, + { hash }, { summary: withRecipient, translatableSummary: { @@ -171,7 +189,7 @@ export function useSwapCallback( ) logSwap({ account, - hash: response, + hash, chainId, inputAmount, outputAmount, @@ -179,7 +197,7 @@ export function useSwapCallback( output: trade.outputAmount.currency, type: 'MarketMakerSwap', }) - logTx({ account, chainId, hash: response }) + logTx({ account, chainId, hash }) return response }) @@ -189,12 +207,22 @@ export function useSwapCallback( throw new Error('Transaction rejected.') } else { // otherwise, the error was unexpected and we need to convey that - console.error(`Swap failed`, error, methodName, args, value) + console.error(`Swap failed`, error) throw new Error(t('Swap failed: %message%', { message: transactionErrorToUserReadableMessage(error, t) })) } }) }, - error: null, } - }, [trade, account, chainId, recipient, recipientAddress, swapCalls, t, addTransaction, gasPrice]) + }, [ + account, + addTransaction, + chainId, + publicClient, + recipient, + recipientAddress, + sendTransactionAsync, + swapCalls, + t, + trade, + ]) } diff --git a/apps/web/src/views/Swap/V3Swap/components/SwapModalFooter.tsx b/apps/web/src/views/Swap/V3Swap/components/SwapModalFooter.tsx index df072d665bc97..ce9afa52d9e99 100644 --- a/apps/web/src/views/Swap/V3Swap/components/SwapModalFooter.tsx +++ b/apps/web/src/views/Swap/V3Swap/components/SwapModalFooter.tsx @@ -1,20 +1,20 @@ import { useTranslation } from '@pancakeswap/localization' -import { SmartRouterTrade, SmartRouter } from '@pancakeswap/smart-router/evm' import { Currency, CurrencyAmount, Percent, TradeType } from '@pancakeswap/sdk' -import { BackForwardIcon, Button, QuestionHelper, Text, Link, AutoColumn, Dots, Flex } from '@pancakeswap/uikit' +import { SmartRouter, SmartRouterTrade } from '@pancakeswap/smart-router/evm' +import { AutoColumn, BackForwardIcon, Button, Dots, Flex, Link, QuestionHelper, Text } from '@pancakeswap/uikit' import { formatAmount } from '@pancakeswap/utils/formatFractions' import { AutoRow, RowBetween, RowFixed } from 'components/Layout/Row' -import { useState, memo, useMemo } from 'react' +import { CurrencyLogo } from 'components/Logo' +import { BUYBACK_FEE, LP_HOLDERS_FEE, TOTAL_FEE, TREASURY_FEE } from 'config/constants/info' +import { memo, useMemo, useState } from 'react' import { Field } from 'state/swap/actions' import { styled } from 'styled-components' -import { CurrencyLogo } from 'components/Logo' import { warningSeverity } from 'utils/exchange' -import { BUYBACK_FEE, LP_HOLDERS_FEE, TOTAL_FEE, TREASURY_FEE } from 'config/constants/info' import { formatExecutionPrice as mmFormatExecutionPrice } from 'views/Swap/MMLinkPools/utils/exchange' import FormattedPriceImpact from '../../components/FormattedPriceImpact' import { StyledBalanceMaxMini, SwapCallbackError } from '../../components/styleds' -import { formatExecutionPrice } from '../utils/exchange' +import { SlippageAdjustedAmounts, formatExecutionPrice } from '../utils/exchange' const SwapModalFooterContainer = styled(AutoColumn)` margin-top: 24px; @@ -46,7 +46,7 @@ export const SwapModalFooter = memo(function SwapModalFooter({ inputAmount: CurrencyAmount outputAmount: CurrencyAmount priceImpact?: Percent - slippageAdjustedAmounts: { [field in Field]?: CurrencyAmount } + slippageAdjustedAmounts: SlippageAdjustedAmounts | undefined | null isEnoughInputBalance?: boolean swapErrorMessage?: string | undefined disabledConfirm: boolean @@ -114,8 +114,8 @@ export const SwapModalFooter = memo(function SwapModalFooter({ {tradeType === TradeType.EXACT_INPUT - ? formatAmount(slippageAdjustedAmounts[Field.OUTPUT], 4) ?? '-' - : formatAmount(slippageAdjustedAmounts[Field.INPUT], 4) ?? '-'} + ? formatAmount(slippageAdjustedAmounts?.[Field.OUTPUT], 4) ?? '-' + : formatAmount(slippageAdjustedAmounts?.[Field.INPUT], 4) ?? '-'} {tradeType === TradeType.EXACT_INPUT ? outputAmount.currency.symbol : inputAmount.currency.symbol} diff --git a/apps/web/src/views/Swap/V3Swap/components/TransactionConfirmSwapContent.tsx b/apps/web/src/views/Swap/V3Swap/components/TransactionConfirmSwapContent.tsx index f8c1603fda42d..348dd7f08cf0d 100644 --- a/apps/web/src/views/Swap/V3Swap/components/TransactionConfirmSwapContent.tsx +++ b/apps/web/src/views/Swap/V3Swap/components/TransactionConfirmSwapContent.tsx @@ -4,17 +4,17 @@ import { ConfirmationModalContent } from '@pancakeswap/widgets-internal' import { memo, useCallback, useMemo } from 'react' import { Field } from 'state/swap/actions' import { maxAmountSpend } from 'utils/maxAmountSpend' +import { MMSlippageTolerance } from 'views/Swap/MMLinkPools/components/MMSlippageTolerance' import { computeSlippageAdjustedAmounts as mmComputeSlippageAdjustedAmountsWithSmartRouter, computeTradePriceBreakdown as mmComputeTradePriceBreakdownWithSmartRouter, } from 'views/Swap/MMLinkPools/utils/exchange' -import { MMSlippageTolerance } from 'views/Swap/MMLinkPools/components/MMSlippageTolerance' -import { SwapModalFooter } from './SwapModalFooter' +import SwapModalHeader from '../../components/SwapModalHeader' import { computeSlippageAdjustedAmounts as computeSlippageAdjustedAmountsWithSmartRouter, computeTradePriceBreakdown as computeTradePriceBreakdownWithSmartRouter, } from '../utils/exchange' -import SwapModalHeader from '../../components/SwapModalHeader' +import { SwapModalFooter } from './SwapModalFooter' /** * Returns true if the trade requires a confirmation of details before we can submit it @@ -101,7 +101,7 @@ export const TransactionConfirmSwapContent = memo : allowedSlippage} - slippageAdjustedAmounts={slippageAdjustedAmounts} + slippageAdjustedAmounts={slippageAdjustedAmounts ?? undefined} isEnoughInputBalance={isEnoughInputBalance ?? undefined} recipient={recipient ?? undefined} showAcceptChanges={showAcceptChanges} @@ -134,7 +134,7 @@ export const TransactionConfirmSwapContent = memo diff --git a/apps/web/src/views/Swap/V3Swap/containers/ApproveStepFlow.tsx b/apps/web/src/views/Swap/V3Swap/containers/ApproveStepFlow.tsx index bc09eddbb2cbe..aa4ded41bd14e 100644 --- a/apps/web/src/views/Swap/V3Swap/containers/ApproveStepFlow.tsx +++ b/apps/web/src/views/Swap/V3Swap/containers/ApproveStepFlow.tsx @@ -1,8 +1,8 @@ -import { useMemo } from 'react' import { useTranslation } from '@pancakeswap/localization' +import { Box, Flex, LinkExternal, Text, useTooltip } from '@pancakeswap/uikit' +import { useMemo } from 'react' import { styled } from 'styled-components' -import { Flex, Text, Box, LinkExternal, useTooltip } from '@pancakeswap/uikit' -import { ConfirmModalState, PendingConfirmModalState } from '../types' +import { ConfirmModalStateV1, PendingConfirmModalStateV1 } from '../types' const StyledLinkExternal = styled(LinkExternal)` &:hover { @@ -26,8 +26,8 @@ const Step = styled('div')<{ active: boolean; width: string }>` ` interface ApproveStepFlowProps { - confirmModalState: ConfirmModalState - pendingModalSteps: PendingConfirmModalState[] + confirmModalState: ConfirmModalStateV1 + pendingModalSteps: PendingConfirmModalStateV1[] } export const ApproveStepFlow: React.FC> = ({ @@ -56,13 +56,13 @@ export const ApproveStepFlow: React.FC {pendingModalSteps.length !== 3 && ( - + )} - - - + + + - {confirmModalState === ConfirmModalState.RESETTING_APPROVAL && ( + {confirmModalState === ConfirmModalStateV1.RESETTING_APPROVAL && ( {t('Why resetting approval')} )} - {confirmModalState === ConfirmModalState.APPROVING_TOKEN && ( + {confirmModalState === ConfirmModalStateV1.APPROVING_TOKEN && ( theme.colors.input}; +` + +const Step = styled('div')<{ active: boolean; width: string }>` + height: 100%; + width: ${({ width }) => width}; + background: ${({ theme, active }) => (active ? theme.colors.secondary : theme.colors.input)}; +` + +interface ApproveStepFlowProps { + confirmModalState: ConfirmModalState + pendingModalSteps: PendingConfirmModalState[] +} + +const ApprovalSteps = ({ pendingModalSteps, confirmModalState }: ApproveStepFlowProps) => { + const stepWidth = useMemo(() => `${100 / pendingModalSteps.length}%`, [pendingModalSteps]) + return ( + <> + {pendingModalSteps.map((pendingStep: PendingConfirmModalState) => { + return ( + + ) + })} + + ) +} + +export const ApproveStepFlowV2: React.FC> = ({ + confirmModalState, + pendingModalSteps, +}) => { + const { t } = useTranslation() + const { targetRef, tooltip, tooltipVisible } = useTooltip( + + {t( + 'If wallet require you to enter the number of tokens you want to approve, you could enter a number that is greater than or equal to the amount of tokens you are swapping.', + )} + , + { placement: 'top' }, + ) + + const hideStepIndicators = useMemo(() => pendingModalSteps.length === 1, [pendingModalSteps]) + + return ( + + + {t('Proceed in your wallet')} + + {!hideStepIndicators && ( + <> + + + + {confirmModalState === ConfirmModalState.RESETTING_APPROVAL && ( + + {t('Why resetting approval')} + + )} + {confirmModalState === ConfirmModalState.APPROVING_TOKEN && ( + + {t('Why')} + + {t('approving')} + + {tooltipVisible && tooltip} + {t('this?')} + + )} + + )} + + ) +} diff --git a/apps/web/src/views/Swap/V3Swap/containers/ConfirmSwapModal.tsx b/apps/web/src/views/Swap/V3Swap/containers/ConfirmSwapModal.tsx index b07125c63c4a8..2887564363958 100644 --- a/apps/web/src/views/Swap/V3Swap/containers/ConfirmSwapModal.tsx +++ b/apps/web/src/views/Swap/V3Swap/containers/ConfirmSwapModal.tsx @@ -9,9 +9,9 @@ import { Box, BscScanIcon, Flex, InjectedModalProps, Link } from '@pancakeswap/u import { formatAmount } from '@pancakeswap/utils/formatFractions' import truncateHash from '@pancakeswap/utils/truncateHash' import { - ApproveModalContent, - SwapPendingModalContent, - SwapTransactionReceiptModalContent, + ApproveModalContentV1, + SwapPendingModalContentV1, + SwapTransactionReceiptModalContentV1, } from '@pancakeswap/widgets-internal' import { getBlockExploreLink, getBlockExploreName } from 'utils' import { wrappedCurrency } from 'utils/wrappedCurrency' @@ -23,7 +23,7 @@ import { useActiveChainId } from 'hooks/useActiveChainId' import { ApprovalState } from 'hooks/useApproveCallback' import { Field } from 'state/swap/actions' import { useSwapState } from 'state/swap/hooks' -import { ConfirmModalState, PendingConfirmModalState } from '../types' +import { ConfirmModalStateV1, PendingConfirmModalStateV1 } from '../types' import ConfirmSwapModalContainer from '../../components/ConfirmSwapModalContainer' import { SwapTransactionErrorContent } from '../../components/SwapTransactionErrorContent' @@ -42,9 +42,9 @@ interface ConfirmSwapModalProps { approval: ApprovalState swapErrorMessage?: string | boolean showApproveFlow: boolean - confirmModalState: ConfirmModalState + confirmModalState: ConfirmModalStateV1 startSwapFlow: () => void - pendingModalSteps: PendingConfirmModalState[] + pendingModalSteps: PendingConfirmModalStateV1[] currentAllowance?: CurrencyAmount onAcceptChanges: () => void customOnDismiss?: () => void @@ -91,17 +91,17 @@ export const ConfirmSwapModal = memo const amountA = formatAmount(trade?.inputAmount, 6) ?? '' const amountB = formatAmount(trade?.outputAmount, 6) ?? '' - if (confirmModalState === ConfirmModalState.RESETTING_APPROVAL) { - return + if (confirmModalState === ConfirmModalStateV1.RESETTING_APPROVAL) { + return } if ( showApproveFlow && - (confirmModalState === ConfirmModalState.APPROVING_TOKEN || - confirmModalState === ConfirmModalState.APPROVE_PENDING) + (confirmModalState === ConfirmModalStateV1.APPROVING_TOKEN || + confirmModalState === ConfirmModalStateV1.APPROVE_PENDING) ) { return ( - if (attemptingTxn) { return ( - ) } - if (confirmModalState === ConfirmModalState.PENDING_CONFIRMATION) { + if (confirmModalState === ConfirmModalStateV1.PENDING_CONFIRMATION) { return ( - tokenDecimals={token?.decimals} tokenLogo={token instanceof WrappedTokenInfo ? token?.logoURI : undefined} /> - + ) } - if (confirmModalState === ConfirmModalState.COMPLETED && txHash) { + if (confirmModalState === ConfirmModalStateV1.COMPLETED && txHash) { return ( - + {chainId && ( {t('View on %site%', { site: getBlockExploreName(chainId) })}: {truncateHash(txHash, 8, 0)} @@ -182,7 +182,7 @@ export const ConfirmSwapModal = memo tokenDecimals={token?.decimals} tokenLogo={token instanceof WrappedTokenInfo ? token?.logoURI : undefined} /> - + ) } @@ -224,9 +224,9 @@ export const ConfirmSwapModal = memo const isShowingLoadingAnimation = useMemo( () => - confirmModalState === ConfirmModalState.RESETTING_APPROVAL || - confirmModalState === ConfirmModalState.APPROVING_TOKEN || - confirmModalState === ConfirmModalState.APPROVE_PENDING || + confirmModalState === ConfirmModalStateV1.RESETTING_APPROVAL || + confirmModalState === ConfirmModalStateV1.APPROVING_TOKEN || + confirmModalState === ConfirmModalStateV1.APPROVE_PENDING || attemptingTxn, [confirmModalState, attemptingTxn], ) @@ -237,7 +237,7 @@ export const ConfirmSwapModal = memo | undefined }) => { + [step in AllowedAllowanceState]: string +} = ({ trade }: { trade: SmartRouterTrade | undefined }) => { + const { t } = useTranslation() + return useMemo(() => { + return { + [ConfirmModalState.RESETTING_APPROVAL]: t('Reset approval on USDT.'), + [ConfirmModalState.APPROVING_TOKEN]: t('Approve %symbol%', { + symbol: trade ? trade.inputAmount.currency.symbol : '', + }), + [ConfirmModalState.PERMITTING]: t('Permit %symbol%', { symbol: trade ? trade.inputAmount.currency.symbol : '' }), + } + }, [t, trade]) +} + +type ConfirmSwapModalProps = InjectedModalProps & { + customOnDismiss?: () => void + onDismiss?: () => void + confirmModalState: ConfirmModalState + pendingModalSteps: ConfirmAction[] + isMM?: boolean + isRFQReady?: boolean + trade?: SmartRouterTrade + originalTrade?: SmartRouterTrade + currencyBalances: { [field in Field]?: CurrencyAmount } + txHash?: string + swapErrorMessage?: string + onAcceptChanges: () => void + onConfirm: (setConfirmModalState?: () => void) => void + openSettingModal?: () => void +} + +export const ConfirmSwapModalV2: React.FC = ({ + confirmModalState, + pendingModalSteps, + customOnDismiss, + swapErrorMessage, + onDismiss, + isMM, + isRFQReady, + trade, + originalTrade, + txHash, + currencyBalances, + openSettingModal, + onAcceptChanges, + onConfirm, +}) => { + const { t } = useTranslation() + const { chainId } = useActiveChainId() + const [allowedSlippage] = useUserSlippage() + const { recipient } = useSwapState() + const loadingAnimationVisible = useMemo(() => { + return [ + ConfirmModalState.RESETTING_APPROVAL, + ConfirmModalState.APPROVING_TOKEN, + ConfirmModalState.PERMITTING, + ].includes(confirmModalState) + }, [confirmModalState]) + const hasError = useMemo(() => swapErrorMessage !== undefined, [swapErrorMessage]) + const stepsVisible = useMemo(() => { + if (swapErrorMessage) return false + if (confirmModalState === ConfirmModalState.REVIEWING || confirmModalState === ConfirmModalState.COMPLETED) + return false + if (confirmModalState === ConfirmModalState.PENDING_CONFIRMATION && txHash) return false + return pendingModalSteps.length > 0 && pendingModalSteps.some((step) => step.showIndicator) + }, [confirmModalState, pendingModalSteps, swapErrorMessage, txHash]) + + const stepContents = useApprovalPhaseStepTitles({ trade }) + const token: Token | undefined = useMemo( + () => wrappedCurrency(trade?.outputAmount?.currency, chainId), + [chainId, trade?.outputAmount?.currency], + ) + + const handleDismiss = useCallback(() => { + if (typeof customOnDismiss === 'function') { + customOnDismiss() + } + + onDismiss?.() + }, [customOnDismiss, onDismiss]) + + const modalContent = useMemo(() => { + const currencyA = currencyBalances.INPUT?.currency ?? trade?.inputAmount?.currency + const currencyB = currencyBalances.OUTPUT?.currency ?? trade?.outputAmount?.currency + const amountA = formatAmount(trade?.inputAmount, 6) ?? '' + const amountB = formatAmount(trade?.outputAmount, 6) ?? '' + + if (swapErrorMessage) { + return ( + + + + ) + } + if ( + confirmModalState === ConfirmModalState.APPROVING_TOKEN || + confirmModalState === ConfirmModalState.PERMITTING || + confirmModalState === ConfirmModalState.RESETTING_APPROVAL + ) { + return ( + step.step) as any} + /> + ) + } + + if (confirmModalState === ConfirmModalState.PENDING_CONFIRMATION) { + return ( + + {txHash ? ( + + ) : null} + + ) + } + + if (confirmModalState === ConfirmModalState.COMPLETED && txHash) { + return ( + + {t('View on %site%', { site: getBlockExploreName(chainId) })}: {truncateHash(txHash, 8, 0)} + {chainId === ChainId.BSC && } + + ) : ( + <> + ) + } + > + + + ) + } + + return ( + + ) + }, [ + allowedSlippage, + chainId, + confirmModalState, + currencyBalances, + handleDismiss, + isMM, + isRFQReady, + onAcceptChanges, + onConfirm, + openSettingModal, + originalTrade, + pendingModalSteps, + recipient, + stepContents, + swapErrorMessage, + t, + token, + trade, + txHash, + ]) + + if (!chainId) return null + + return ( + + {modalContent} + {stepsVisible ? ( + step.step) as any} + /> + ) : null} + + ) +} diff --git a/apps/web/src/views/Swap/V3Swap/containers/MMCommitButton.tsx b/apps/web/src/views/Swap/V3Swap/containers/MMCommitButton.tsx index 50f25b789a3d7..79f415feed4cf 100644 --- a/apps/web/src/views/Swap/V3Swap/containers/MMCommitButton.tsx +++ b/apps/web/src/views/Swap/V3Swap/containers/MMCommitButton.tsx @@ -4,14 +4,14 @@ import { useCurrency } from 'hooks/Tokens' import { useIsTransactionUnsupported } from 'hooks/Trades' import { ApprovalState, useApproveCallback } from 'hooks/useApproveCallback' import useWrapCallback, { WrapType } from 'hooks/useWrapCallback' -import { useEffect, useMemo, useState } from 'react' +import { memo, useEffect, useMemo, useState } from 'react' import { Field } from 'state/swap/actions' import { useSwapState } from 'state/swap/hooks' import { useSwapActionHandlers } from 'state/swap/useSwapActionHandlers' import { MMSwapCommitButton } from 'views/Swap/MMLinkPools/components/MMCommitButton' import { useAccount } from 'wagmi' -export function MMCommitButton({ mmOrderBookTrade, mmRFQTrade, mmQuoteExpiryRemainingSec, mmTradeInfo }) { +function MMCommitButtonComp({ mmOrderBookTrade, mmRFQTrade, mmQuoteExpiryRemainingSec, mmTradeInfo }) { const { typedValue, recipient, @@ -86,3 +86,5 @@ export function MMCommitButton({ mmOrderBookTrade, mmRFQTrade, mmQuoteExpiryRema /> ) } + +export const MMCommitButton = memo(MMCommitButtonComp) diff --git a/apps/web/src/views/Swap/V3Swap/containers/MMCommitButtonV2.tsx b/apps/web/src/views/Swap/V3Swap/containers/MMCommitButtonV2.tsx new file mode 100644 index 0000000000000..8ee1c0930668d --- /dev/null +++ b/apps/web/src/views/Swap/V3Swap/containers/MMCommitButtonV2.tsx @@ -0,0 +1,64 @@ +import { Currency } from '@pancakeswap/sdk' +import { useExpertMode } from '@pancakeswap/utils/user' +import { useCurrency } from 'hooks/Tokens' +import { useIsTransactionUnsupported } from 'hooks/Trades' +import useWrapCallback, { WrapType } from 'hooks/useWrapCallback' +import { memo, useMemo } from 'react' +import { Field } from 'state/swap/actions' +import { useSwapState } from 'state/swap/hooks' +import { useSwapActionHandlers } from 'state/swap/useSwapActionHandlers' +import { MMSwapCommitButtonV2 } from 'views/Swap/MMLinkPools/components/MMCommitButtonV2' +import { useAccount } from 'wagmi' + +function MMCommitButtonCompV2({ mmOrderBookTrade, mmRFQTrade, mmQuoteExpiryRemainingSec, mmTradeInfo }) { + const { + typedValue, + recipient, + [Field.INPUT]: { currencyId: inputCurrencyId }, + [Field.OUTPUT]: { currencyId: outputCurrencyId }, + } = useSwapState() + + const inputCurrency = useCurrency(inputCurrencyId) ?? undefined + const outputCurrency = useCurrency(outputCurrencyId) ?? undefined + const { address: account } = useAccount() + const [isExpertMode] = useExpertMode() + const currencies: { [field in Field]?: Currency } = useMemo( + () => ({ + [Field.INPUT]: inputCurrency, + [Field.OUTPUT]: outputCurrency, + }), + [inputCurrency, outputCurrency], + ) + const swapIsUnsupported = useIsTransactionUnsupported(currencies?.INPUT, currencies?.OUTPUT) + + const { + wrapType, + execute: onWrap, + inputError: wrapInputError, + } = useWrapCallback(inputCurrency, outputCurrency, typedValue) + const showWrap = wrapType !== WrapType.NOT_APPLICABLE + const { onUserInput } = useSwapActionHandlers() + + return ( + + ) +} + +export const MMCommitButtonV2 = memo(MMCommitButtonCompV2) diff --git a/apps/web/src/views/Swap/V3Swap/containers/SwapCommitButton.tsx b/apps/web/src/views/Swap/V3Swap/containers/SwapCommitButton.tsx index 8d5beb054d500..531088c8b87ba 100644 --- a/apps/web/src/views/Swap/V3Swap/containers/SwapCommitButton.tsx +++ b/apps/web/src/views/Swap/V3Swap/containers/SwapCommitButton.tsx @@ -31,9 +31,9 @@ import { useCurrencyBalances } from 'state/wallet/hooks' import { warningSeverity } from 'utils/exchange' import { useActiveChainId } from 'hooks/useActiveChainId' -import { useConfirmModalState } from 'views/Swap/V3Swap/hooks/useConfirmModalState' import { useAccount } from 'wagmi' import { useParsedAmounts, useSlippageAdjustedAmounts, useSwapCallback, useSwapInputError } from '../hooks' +import { useConfirmModalState } from '../hooks/useConfirmModalState' import { TransactionRejectedError } from '../hooks/useSendSwapTransaction' import { useWallchainApi } from '../hooks/useWallchain' import { computeTradePriceBreakdown } from '../utils/exchange' @@ -177,6 +177,7 @@ export const SwapCommitButton = memo(function SwapCommitButton({ setSwapState((s) => ({ ...s, txHash: undefined, + swapErrorMessage: t('Transaction rejected'), attemptingTxn: false, })) // throw reject error to reset the flow diff --git a/apps/web/src/views/Swap/V3Swap/containers/SwapCommitButtonV2.tsx b/apps/web/src/views/Swap/V3Swap/containers/SwapCommitButtonV2.tsx new file mode 100644 index 0000000000000..e8f19e90f51c7 --- /dev/null +++ b/apps/web/src/views/Swap/V3Swap/containers/SwapCommitButtonV2.tsx @@ -0,0 +1,289 @@ +import { TradeType } from '@pancakeswap/sdk' +import { SmartRouterTrade } from '@pancakeswap/smart-router/evm' +import { Currency, CurrencyAmount, Token } from '@pancakeswap/swap-sdk-core' +import { AutoColumn, Box, Button, Dots, Message, MessageText, Text, useModal } from '@pancakeswap/uikit' +import React, { memo, useCallback, useEffect, useMemo, useState } from 'react' + +import { useTranslation } from '@pancakeswap/localization' +import { getUniversalRouterAddress } from '@pancakeswap/universal-router-sdk' +import { ConfirmModalState } from '@pancakeswap/widgets-internal' +import { GreyCard } from 'components/Card' +import { CommitButton } from 'components/CommitButton' +import ConnectWalletButton from 'components/ConnectWalletButton' +import { AutoRow } from 'components/Layout/Row' +import SettingsModal, { RoutingSettingsButton, withCustomOnDismiss } from 'components/Menu/GlobalSettings/SettingsModal' +import { SettingsMode } from 'components/Menu/GlobalSettings/types' +import { BIG_INT_ZERO } from 'config/constants/exchange' +import { useCurrency } from 'hooks/Tokens' +import { useIsTransactionUnsupported } from 'hooks/Trades' +import useWrapCallback, { WrapType } from 'hooks/useWrapCallback' +import { Field } from 'state/swap/actions' +import { useSwapState } from 'state/swap/hooks' +import { useSwapActionHandlers } from 'state/swap/useSwapActionHandlers' +import { useRoutingSettingChanged } from 'state/user/smartRouter' +import { useCurrencyBalances } from 'state/wallet/hooks' +import { logGTMClickSwapEvent } from 'utils/customGTMEventTracking' +import { warningSeverity } from 'utils/exchange' +import { useAccount, useChainId } from 'wagmi' +import { useParsedAmounts, useSlippageAdjustedAmounts, useSwapInputError } from '../hooks' +import { useConfirmModalStateV2 } from '../hooks/useConfirmModalStateV2' +import { useSwapConfig } from '../hooks/useSwapConfig' +import { useSwapCurrency } from '../hooks/useSwapCurrency' +import { computeTradePriceBreakdown } from '../utils/exchange' +import { ConfirmSwapModalV2 } from './ConfirmSwapModalV2' + +const SettingsModalWithCustomDismiss = withCustomOnDismiss(SettingsModal) + +interface SwapCommitButtonPropsType { + trade?: SmartRouterTrade + tradeError?: Error + tradeLoading?: boolean +} + +const useSettingModal = (onDismiss) => { + const [openSettingsModal] = useModal( + , + ) + return openSettingsModal +} + +const useSwapCurrencies = () => { + const { + [Field.INPUT]: { currencyId: inputCurrencyId }, + [Field.OUTPUT]: { currencyId: outputCurrencyId }, + } = useSwapState() + const inputCurrency = useCurrency(inputCurrencyId) as Currency + const outputCurrency = useCurrency(outputCurrencyId) as Currency + return { inputCurrency, outputCurrency } +} + +const WrapCommitButtonReplace: React.FC = ({ children }) => { + const { t } = useTranslation() + const { inputCurrency, outputCurrency } = useSwapCurrencies() + const { typedValue } = useSwapState() + const { + wrapType, + execute: onWrap, + inputError: wrapInputError, + } = useWrapCallback(inputCurrency, outputCurrency, typedValue) + const showWrap = wrapType !== WrapType.NOT_APPLICABLE + + if (!showWrap) return children + + return ( + + {wrapInputError ?? (wrapType === WrapType.WRAP ? t('Wrap') : wrapType === WrapType.UNWRAP ? t('Unwrap') : null)} + + ) +} + +const ConnectButtonReplace = ({ children }) => { + const { address: account } = useAccount() + + if (!account) { + return + } + return children +} + +const UnsupportedSwapButtonReplace = ({ children }) => { + const { t } = useTranslation() + const { inputCurrency, outputCurrency } = useSwapCurrencies() + const swapIsUnsupported = useIsTransactionUnsupported(inputCurrency, outputCurrency) + + if (swapIsUnsupported) { + return ( + + ) + } + return children +} + +const SwapCommitButtonCompV2: React.FC = (props) => { + return ( + + + + + + + + ) +} + +export const SwapCommitButtonV2 = memo(SwapCommitButtonCompV2) + +const SwapCommitButtonInner = memo(function SwapCommitButtonInner({ + trade, + tradeError, + tradeLoading, +}: SwapCommitButtonPropsType) { + const { address: account } = useAccount() + const { t } = useTranslation() + const chainId = useChainId() + // form data + const { independentField } = useSwapState() + const [inputCurrency, outputCurrency] = useSwapCurrency() + const { isExpertMode } = useSwapConfig() + + const slippageAdjustedAmounts = useSlippageAdjustedAmounts(trade) + const amountToApprove = useMemo( + () => (inputCurrency?.isNative ? undefined : slippageAdjustedAmounts[Field.INPUT]), + [inputCurrency?.isNative, slippageAdjustedAmounts], + ) + + const tradePriceBreakdown = useMemo(() => computeTradePriceBreakdown(trade), [trade]) + // warnings on slippage + const priceImpactSeverity = warningSeverity( + tradePriceBreakdown ? tradePriceBreakdown.priceImpactWithoutFee : undefined, + ) + + const relevantTokenBalances = useCurrencyBalances(account ?? undefined, [ + inputCurrency ?? undefined, + outputCurrency ?? undefined, + ]) + const currencyBalances = { + [Field.INPUT]: relevantTokenBalances[0], + [Field.OUTPUT]: relevantTokenBalances[1], + } + const parsedAmounts = useParsedAmounts(trade, currencyBalances, false) + const parsedIndependentFieldAmount = parsedAmounts[independentField] + const swapInputError = useSwapInputError(trade, currencyBalances) + const [tradeToConfirm, setTradeToConfirm] = useState | undefined>(undefined) + const [indirectlyOpenConfirmModalState, setIndirectlyOpenConfirmModalState] = useState(false) + + const { callToAction, confirmState, txHash, confirmActions, errorMessage, resetState } = useConfirmModalStateV2( + isExpertMode ? trade : tradeToConfirm, + amountToApprove?.currency.isToken ? (amountToApprove as CurrencyAmount) : undefined, + getUniversalRouterAddress(chainId), + ) + + const { onUserInput } = useSwapActionHandlers() + const reset = useCallback(() => { + if (confirmState === ConfirmModalState.COMPLETED) { + onUserInput(Field.INPUT, '') + } + resetState() + }, [confirmState, onUserInput, resetState]) + + const handleAcceptChanges = useCallback(() => { + setTradeToConfirm(trade) + }, [trade]) + + const noRoute = useMemo(() => !((trade?.routes?.length ?? 0) > 0) || tradeError, [trade?.routes?.length, tradeError]) + const isValid = useMemo(() => !swapInputError && !tradeLoading, [swapInputError, tradeLoading]) + const disabled = useMemo( + () => !isValid || (priceImpactSeverity > 3 && !isExpertMode), + [isExpertMode, isValid, priceImpactSeverity], + ) + + const userHasSpecifiedInputOutput = Boolean( + inputCurrency && outputCurrency && parsedIndependentFieldAmount?.greaterThan(BIG_INT_ZERO), + ) + + // modals + const onSettingModalDismiss = useCallback(() => { + setIndirectlyOpenConfirmModalState(true) + }, []) + const openSettingModal = useSettingModal(onSettingModalDismiss) + const [openConfirmSwapModal] = useModal( + , + true, + true, + 'confirmSwapModal', + ) + + const handleSwap = useCallback(() => { + setTradeToConfirm(trade) + resetState() + + // if expert mode turn-on, will not show preview modal + // start swap directly + if (isExpertMode) { + callToAction() + } + + openConfirmSwapModal() + logGTMClickSwapEvent() + }, [callToAction, isExpertMode, openConfirmSwapModal, resetState, trade]) + + useEffect(() => { + if (indirectlyOpenConfirmModalState) { + setIndirectlyOpenConfirmModalState(false) + openConfirmSwapModal() + } + }, [indirectlyOpenConfirmModalState, openConfirmSwapModal]) + + if (noRoute && userHasSpecifiedInputOutput && !tradeLoading) { + return + } + + return ( + + 2 && !errorMessage ? 'danger' : 'primary'} + disabled={disabled} + onClick={handleSwap} + > + {swapInputError || + (tradeLoading && {t('Searching For The Best Price')}) || + (priceImpactSeverity > 3 && !isExpertMode + ? t('Price Impact Too High') + : priceImpactSeverity > 2 + ? t('Swap Anyway') + : t('Swap'))} + + + ) +}) + +const ResetRoutesButton = () => { + const { t } = useTranslation() + const [isRoutingSettingChange, resetRoutingSetting] = useRoutingSettingChanged() + return ( + + + {t('Insufficient liquidity for this trade.')} + + {isRoutingSettingChange && ( + }> + + {t('Unable to establish trading route due to customized routing.')} + + + {t('Check your settings')} + + {t('or')} + + + + + )} + + ) +} diff --git a/apps/web/src/views/Swap/V3Swap/containers/index.ts b/apps/web/src/views/Swap/V3Swap/containers/index.ts index e67fc62b36614..8e0146cbfe000 100644 --- a/apps/web/src/views/Swap/V3Swap/containers/index.ts +++ b/apps/web/src/views/Swap/V3Swap/containers/index.ts @@ -1,6 +1,6 @@ +export * from './BuyCryptoLink' export * from './FormHeader' export * from './FormMain' export * from './PricingAndSlippage' +export * from './SwapCommitButtonV2' export * from './TradeDetails' -export * from './SwapCommitButton' -export * from './BuyCryptoLink' diff --git a/apps/web/src/views/Swap/V3Swap/hooks/index.ts b/apps/web/src/views/Swap/V3Swap/hooks/index.ts index 466b36055fe75..322880520bfda 100644 --- a/apps/web/src/views/Swap/V3Swap/hooks/index.ts +++ b/apps/web/src/views/Swap/V3Swap/hooks/index.ts @@ -1,8 +1,9 @@ -export * from './useIsWrapping' export * from './useAllowRecipient' -export * from './useSwapBestTrade' -export * from './useSlippageAdjustedAmounts' -export * from './useRouterAddress' -export * from './useSwapInputError' +export * from './useIsWrapping' export * from './useParsedAmounts' +export * from './useRouterAddress' +export * from './useSlippageAdjustedAmounts' +export * from './useSwapBestTrade' export * from './useSwapCallback' +export * from './useSwapCallbackV2' +export * from './useSwapInputError' diff --git a/apps/web/src/views/Swap/V3Swap/hooks/useConfirmModalState.tsx b/apps/web/src/views/Swap/V3Swap/hooks/useConfirmModalState.ts similarity index 71% rename from apps/web/src/views/Swap/V3Swap/hooks/useConfirmModalState.tsx rename to apps/web/src/views/Swap/V3Swap/hooks/useConfirmModalState.ts index 745af58bb8e35..0f6dee0f91a8f 100644 --- a/apps/web/src/views/Swap/V3Swap/hooks/useConfirmModalState.tsx +++ b/apps/web/src/views/Swap/V3Swap/hooks/useConfirmModalState.ts @@ -2,10 +2,11 @@ import { ChainId } from '@pancakeswap/chains' import { Currency, CurrencyAmount } from '@pancakeswap/swap-sdk-core' import { ethereumTokens } from '@pancakeswap/tokens' import { ApprovalState } from 'hooks/useApproveCallback' +import { usePublicNodeWaitForTransaction } from 'hooks/usePublicNodeWaitForTransaction' import { useCallback, useEffect, useState } from 'react' -import { ConfirmModalState, PendingConfirmModalState } from 'views/Swap/V3Swap/types' +import { Hex } from 'viem' +import { ConfirmModalStateV1, PendingConfirmModalStateV1 } from 'views/Swap/V3Swap/types' import { SendTransactionResult } from 'wagmi/actions' -import { usePublicNodeWaitForTransaction } from 'hooks/usePublicNodeWaitForTransaction' import { TransactionRejectedError } from './useSendSwapTransaction' interface UseConfirmModalStateProps { @@ -21,9 +22,10 @@ interface UseConfirmModalStateProps { revokeCallback: () => Promise } -function isInApprovalPhase(confirmModalState: ConfirmModalState) { +function isInApprovalPhase(confirmModalState: ConfirmModalStateV1) { return ( - confirmModalState === ConfirmModalState.APPROVING_TOKEN || confirmModalState === ConfirmModalState.APPROVE_PENDING + confirmModalState === ConfirmModalStateV1.APPROVING_TOKEN || + confirmModalState === ConfirmModalStateV1.APPROVE_PENDING ) } export const useConfirmModalState = ({ @@ -38,13 +40,13 @@ export const useConfirmModalState = ({ revokeCallback, }: UseConfirmModalStateProps) => { const { waitForTransaction } = usePublicNodeWaitForTransaction() - const [confirmModalState, setConfirmModalState] = useState(ConfirmModalState.REVIEWING) - const [pendingModalSteps, setPendingModalSteps] = useState([]) + const [confirmModalState, setConfirmModalState] = useState(ConfirmModalStateV1.REVIEWING) + const [pendingModalSteps, setPendingModalSteps] = useState([]) const [previouslyPending, setPreviouslyPending] = useState(false) const [resettingApproval, setResettingApproval] = useState(false) const generateRequiredSteps = useCallback(() => { - const steps: PendingConfirmModalState[] = [] + const steps: PendingConfirmModalStateV1[] = [] // Any existing USDT allowance needs to be reset before we can approve the new amount (mainnet only). // See the `approve` function here: https://etherscan.io/address/0xdAC17F958D2ee523a2206206994597C13D831ec7#code @@ -55,47 +57,47 @@ export const useConfirmModalState = ({ approvalToken.chainId === ethereumTokens.usdt.chainId && approvalToken.wrapped.address.toLowerCase() === ethereumTokens.usdt.address.toLowerCase() ) { - steps.push(ConfirmModalState.RESETTING_APPROVAL) + steps.push(ConfirmModalStateV1.RESETTING_APPROVAL) } if (approval === ApprovalState.NOT_APPROVED) { setPreviouslyPending(false) - steps.push(ConfirmModalState.APPROVING_TOKEN, ConfirmModalState.APPROVE_PENDING) + steps.push(ConfirmModalStateV1.APPROVING_TOKEN, ConfirmModalStateV1.APPROVE_PENDING) } - steps.push(ConfirmModalState.PENDING_CONFIRMATION) + steps.push(ConfirmModalStateV1.PENDING_CONFIRMATION) return steps }, [approval, approvalToken, currentAllowance]) const onCancel = useCallback(() => { - setConfirmModalState(ConfirmModalState.REVIEWING) + setConfirmModalState(ConfirmModalStateV1.REVIEWING) setPreviouslyPending(false) }, []) const resetSwapFlow = useCallback(() => { - setConfirmModalState(ConfirmModalState.REVIEWING) + setConfirmModalState(ConfirmModalStateV1.REVIEWING) setPendingModalSteps([]) setPreviouslyPending(false) setResettingApproval(false) }, []) const performStep = useCallback( - async (step: ConfirmModalState) => { + async (step: ConfirmModalStateV1) => { switch (step) { - case ConfirmModalState.RESETTING_APPROVAL: - setConfirmModalState(ConfirmModalState.RESETTING_APPROVAL) + case ConfirmModalStateV1.RESETTING_APPROVAL: + setConfirmModalState(ConfirmModalStateV1.RESETTING_APPROVAL) revokeCallback() .then(() => setResettingApproval(true)) .catch(() => onCancel()) break - case ConfirmModalState.APPROVING_TOKEN: - setConfirmModalState(ConfirmModalState.APPROVING_TOKEN) + case ConfirmModalStateV1.APPROVING_TOKEN: + setConfirmModalState(ConfirmModalStateV1.APPROVING_TOKEN) approveCallback() - .then(() => setConfirmModalState(ConfirmModalState.APPROVE_PENDING)) + .then(() => setConfirmModalState(ConfirmModalStateV1.APPROVE_PENDING)) .catch(() => onCancel()) break - case ConfirmModalState.PENDING_CONFIRMATION: - setConfirmModalState(ConfirmModalState.PENDING_CONFIRMATION) + case ConfirmModalStateV1.PENDING_CONFIRMATION: + setConfirmModalState(ConfirmModalStateV1.PENDING_CONFIRMATION) try { await onConfirm() } catch (error) { @@ -104,11 +106,11 @@ export const useConfirmModalState = ({ } } break - case ConfirmModalState.COMPLETED: - setConfirmModalState(ConfirmModalState.COMPLETED) + case ConfirmModalStateV1.COMPLETED: + setConfirmModalState(ConfirmModalStateV1.COMPLETED) break default: - setConfirmModalState(ConfirmModalState.REVIEWING) + setConfirmModalState(ConfirmModalStateV1.REVIEWING) break } }, @@ -125,7 +127,7 @@ export const useConfirmModalState = ({ async (hash: `0x${string}`) => { const receipt: any = await waitForTransaction({ hash, chainId }) if (receipt.status === 'success') { - performStep(ConfirmModalState.COMPLETED) + performStep(ConfirmModalStateV1.COMPLETED) } }, [performStep, waitForTransaction, chainId], @@ -139,7 +141,7 @@ export const useConfirmModalState = ({ }, [approval, resettingApproval, performStep, startSwapFlow]) useEffect(() => { - if (approval === ApprovalState.PENDING && confirmModalState === ConfirmModalState.APPROVE_PENDING) { + if (approval === ApprovalState.PENDING && confirmModalState === ConfirmModalStateV1.APPROVE_PENDING) { setPreviouslyPending(true) } }, [approval, confirmModalState]) @@ -149,7 +151,7 @@ export const useConfirmModalState = ({ if ( previouslyPending && approval === ApprovalState.NOT_APPROVED && - confirmModalState === ConfirmModalState.APPROVE_PENDING + confirmModalState === ConfirmModalStateV1.APPROVE_PENDING ) { onCancel() } @@ -157,20 +159,24 @@ export const useConfirmModalState = ({ // Submit Approve, get error when submit approve. useEffect(() => { - if (isPendingError && confirmModalState === ConfirmModalState.APPROVE_PENDING) { + if (isPendingError && confirmModalState === ConfirmModalStateV1.APPROVE_PENDING) { onCancel() } }, [isPendingError, confirmModalState, previouslyPending, onCancel]) useEffect(() => { if (isInApprovalPhase(confirmModalState) && approval === ApprovalState.APPROVED) { - performStep(ConfirmModalState.PENDING_CONFIRMATION) + performStep(ConfirmModalStateV1.PENDING_CONFIRMATION) } }, [approval, confirmModalState, performStep]) useEffect(() => { - if (txHash && confirmModalState === ConfirmModalState.PENDING_CONFIRMATION && approval === ApprovalState.APPROVED) { - checkHashIsReceipted(txHash as `0x${string}`) + if ( + txHash && + confirmModalState === ConfirmModalStateV1.PENDING_CONFIRMATION && + approval === ApprovalState.APPROVED + ) { + checkHashIsReceipted(txHash as Hex) } }, [approval, txHash, confirmModalState, checkHashIsReceipted, performStep]) diff --git a/apps/web/src/views/Swap/V3Swap/hooks/useConfirmModalStateV2.ts b/apps/web/src/views/Swap/V3Swap/hooks/useConfirmModalStateV2.ts new file mode 100644 index 0000000000000..6e99452cf2c8e --- /dev/null +++ b/apps/web/src/views/Swap/V3Swap/hooks/useConfirmModalStateV2.ts @@ -0,0 +1,353 @@ +import { usePreviousValue } from '@pancakeswap/hooks' +import { useTranslation } from '@pancakeswap/localization' +import { BSC_BLOCK_TIME } from '@pancakeswap/pools' +import { SmartRouterTrade } from '@pancakeswap/smart-router' +import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@pancakeswap/swap-sdk-core' +import { Permit2Signature } from '@pancakeswap/universal-router-sdk' +import { ConfirmModalState, confirmPriceImpactWithoutFee } from '@pancakeswap/widgets-internal' +import { AVERAGE_CHAIN_BLOCK_TIMES } from 'config/constants/averageChainBlockTimes' +import { ALLOWED_PRICE_IMPACT_HIGH, PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN } from 'config/constants/exchange' +import { useActiveChainId } from 'hooks/useActiveChainId' +import { usePermit2 } from 'hooks/usePermit2' +import { usePermit2Requires } from 'hooks/usePermit2Requires' +import { useTransactionDeadline } from 'hooks/useTransactionDeadline' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { wait } from 'state/multicall/retry' +import { publicClient } from 'utils/client' +import { UserUnexpectedTxError } from 'utils/errors' +import { Address, Hex } from 'viem' +import { mainnet } from 'wagmi' +import { computeTradePriceBreakdown } from '../utils/exchange' +import { userRejectedError } from './useSendSwapTransaction' +import { useSwapCallbackV2 } from './useSwapCallbackV2' + +export type ConfirmAction = { + step: ConfirmModalState + action: (nextStep?: ConfirmModalState) => Promise + showIndicator: boolean +} + +const useCreateConfirmSteps = (amountToApprove: CurrencyAmount | undefined, spender: Address | undefined) => { + const { requireApprove, requirePermit, requireRevoke } = usePermit2Requires(amountToApprove, spender) + + return useCallback(() => { + const steps: ConfirmModalState[] = [] + if (requireRevoke) { + steps.push(ConfirmModalState.RESETTING_APPROVAL) + } + if (requireApprove) { + steps.push(ConfirmModalState.APPROVING_TOKEN) + } + if (requirePermit) { + steps.push(ConfirmModalState.PERMITTING) + } + steps.push(ConfirmModalState.PENDING_CONFIRMATION) + return steps + }, [requireRevoke, requireApprove, requirePermit]) +} + +// define the actions of each step +const useConfirmActions = ( + trade: SmartRouterTrade | undefined, + amountToApprove: CurrencyAmount | undefined, + spender: Address | undefined, +) => { + const { t } = useTranslation() + const { chainId } = useActiveChainId() + const [deadline] = useTransactionDeadline() + const { revoke, permit, approve, refetch } = usePermit2(amountToApprove, spender) + const [permit2Signature, setPermit2Signature] = useState(undefined) + const { callback: swap, error: swapError } = useSwapCallbackV2({ + trade, + deadline, + permitSignature: permit2Signature, + }) + const [confirmState, setConfirmState] = useState(ConfirmModalState.REVIEWING) + const [txHash, setTxHash] = useState(undefined) + const [errorMessage, setErrorMessage] = useState(undefined) + + const resetState = useCallback(() => { + setConfirmState(ConfirmModalState.REVIEWING) + setTxHash(undefined) + setErrorMessage(undefined) + setPermit2Signature(undefined) + }, []) + + const showError = useCallback((error: string) => { + setErrorMessage(error) + setTxHash(undefined) + setPermit2Signature(undefined) + }, []) + + // define the action of each step + const revokeStep = useMemo(() => { + const action = async (nextState?: ConfirmModalState) => { + setTxHash(undefined) + setConfirmState(ConfirmModalState.RESETTING_APPROVAL) + try { + const result = await revoke() + if (result?.hash && chainId) { + setTxHash(result.hash) + // sync to same with updater /apps/web/src/state/transactions/updater.tsx#L101 + await wait((AVERAGE_CHAIN_BLOCK_TIMES[chainId] ?? BSC_BLOCK_TIME) * 1000 + 2000) + await publicClient({ chainId }).waitForTransactionReceipt({ hash: result.hash }) + } + + let newAllowanceRaw: bigint = 0n + + try { + // check if user really reset the approval to 0 + const { data } = await refetch() + newAllowanceRaw = data ?? 0n + } catch (error) { + // assume the approval reset is successful, if we can't check the allowance + console.error('check allowance after revoke failed: ', error) + } + + const newAllowance = CurrencyAmount.fromRawAmount(amountToApprove?.currency as Currency, newAllowanceRaw ?? 0n) + if (!newAllowance.equalTo(0)) { + throw new UserUnexpectedTxError({ + expectedData: 0, + actualData: newAllowanceRaw.toString(), + }) + } + + setConfirmState(nextState ?? ConfirmModalState.APPROVING_TOKEN) + } catch (error) { + console.error('revoke error', error) + if (userRejectedError(error)) { + showError(t('Transaction rejected')) + } else if (error instanceof UserUnexpectedTxError) { + showError(t('Revert transaction filled, but Approval not reset to 0. Please try again.')) + } else { + showError(typeof error === 'string' ? error : (error as any)?.message) + } + } + } + return { + step: ConfirmModalState.RESETTING_APPROVAL, + action, + showIndicator: true, + } + }, [amountToApprove?.currency, chainId, refetch, revoke, showError, t]) + + const permitStep = useMemo(() => { + return { + step: ConfirmModalState.PERMITTING, + action: async (nextState?: ConfirmModalState) => { + setConfirmState(ConfirmModalState.PERMITTING) + try { + const result = await permit() + setPermit2Signature(result) + setConfirmState(nextState ?? ConfirmModalState.PENDING_CONFIRMATION) + } catch (error) { + if (userRejectedError(error)) { + showError('Transaction rejected') + } else { + showError(typeof error === 'string' ? error : (error as any)?.message) + } + } + }, + showIndicator: true, + } + }, [permit, showError]) + + const approveStep = useMemo(() => { + return { + step: ConfirmModalState.APPROVING_TOKEN, + action: async (nextState?: ConfirmModalState) => { + setTxHash(undefined) + setConfirmState(ConfirmModalState.APPROVING_TOKEN) + try { + const result = await approve() + if (result?.hash && chainId) { + setTxHash(result.hash) + await publicClient({ chainId }).waitForTransactionReceipt({ + hash: result.hash, + confirmations: chainId === mainnet.id ? 2 : 1, + }) + } + let newAllowanceRaw: bigint = amountToApprove?.quotient ?? 0n + // check if user really approved the amount trade needs + try { + const { data } = await refetch() + newAllowanceRaw = data ?? 0n + } catch (error) { + // assume the approval is successful, if we can't check the allowance + console.error('check allowance after approve failed: ', error) + } + const newAllowance = CurrencyAmount.fromRawAmount( + amountToApprove?.currency as Currency, + newAllowanceRaw ?? 0n, + ) + if (amountToApprove && newAllowance && newAllowance.lessThan(amountToApprove)) { + throw new UserUnexpectedTxError({ + expectedData: amountToApprove.toExact(), + actualData: newAllowanceRaw.toString(), + }) + } + + setConfirmState(nextState ?? ConfirmModalState.PERMITTING) + } catch (error) { + console.error('approve error', error) + if (userRejectedError(error)) { + showError(t('Transaction rejected')) + } else if (error instanceof UserUnexpectedTxError) { + showError( + t('Approve transaction filled, but Approval still not enough to fill current trade. Please try again.'), + ) + } else { + showError(typeof error === 'string' ? error : (error as any)?.message) + } + } + }, + showIndicator: true, + } + }, [amountToApprove, approve, chainId, refetch, showError, t]) + + const tradePriceBreakdown = useMemo(() => computeTradePriceBreakdown(trade), [trade]) + const swapPreflightCheck = useCallback(() => { + if ( + tradePriceBreakdown && + !confirmPriceImpactWithoutFee( + tradePriceBreakdown.priceImpactWithoutFee as Percent, + PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN, + ALLOWED_PRICE_IMPACT_HIGH, + t, + ) + ) { + return false + } + return true + }, [t, tradePriceBreakdown]) + + const swapStep = useMemo(() => { + return { + step: ConfirmModalState.PENDING_CONFIRMATION, + action: async () => { + setTxHash(undefined) + setConfirmState(ConfirmModalState.PENDING_CONFIRMATION) + + if (!swap || !swapPreflightCheck()) { + resetState() + return + } + + if (swapError) { + showError(swapError) + return + } + + try { + const result = await swap() + if (result?.hash) { + setTxHash(result.hash) + + await publicClient({ chainId }).waitForTransactionReceipt({ + hash: result.hash, + confirmations: chainId === mainnet.id ? 2 : 1, + }) + } + setConfirmState(ConfirmModalState.COMPLETED) + } catch (error: any) { + console.error('swap error', error) + if (userRejectedError(error)) { + showError('Transaction rejected') + } else { + showError(typeof error === 'string' ? error : (error as any)?.message) + } + } + }, + showIndicator: false, + } + }, [chainId, resetState, showError, swap, swapError, swapPreflightCheck]) + + const actions = useMemo(() => { + return { + [ConfirmModalState.RESETTING_APPROVAL]: revokeStep, + [ConfirmModalState.PERMITTING]: permitStep, + [ConfirmModalState.APPROVING_TOKEN]: approveStep, + [ConfirmModalState.PENDING_CONFIRMATION]: swapStep, + } as { [k in ConfirmModalState]: ConfirmAction } + }, [approveStep, permitStep, revokeStep, swapStep]) + + return { + txHash, + actions, + + confirmState, + resetState, + errorMessage, + } +} + +export const useConfirmModalStateV2 = ( + trade: SmartRouterTrade | undefined, + amountToApprove: CurrencyAmount | undefined, + spender: Address | undefined, +) => { + const { actions, confirmState, txHash, errorMessage, resetState } = useConfirmActions(trade, amountToApprove, spender) + const preConfirmState = usePreviousValue(confirmState) + const [confirmSteps, setConfirmSteps] = useState() + + const createSteps = useCreateConfirmSteps(amountToApprove, spender) + const confirmActions = useMemo(() => { + return confirmSteps?.map((step) => actions[step]) + }, [confirmSteps, actions]) + + const performStep = useCallback( + async ({ + nextStep, + stepActions, + state, + }: { + nextStep?: ConfirmModalState + stepActions: ConfirmAction[] + state: ConfirmModalState + }) => { + if (!stepActions) { + return + } + + const step = stepActions.find((s) => s.step === state) ?? stepActions[0] + + await step.action(nextStep) + }, + [], + ) + + const callToAction = useCallback(() => { + const steps = createSteps() + setConfirmSteps(steps) + const stepActions = steps.map((step) => actions[step]) + const nextStep = steps[1] ?? undefined + + performStep({ + nextStep, + stepActions, + state: steps[0], + }) + }, [actions, createSteps, performStep]) + + // auto perform the next step + useEffect(() => { + if ( + preConfirmState !== confirmState && + preConfirmState !== ConfirmModalState.REVIEWING && + confirmActions?.some((step) => step.step === confirmState) + ) { + const nextStep = confirmActions.findIndex((step) => step.step === confirmState) + const nextStepState = confirmActions[nextStep + 1]?.step ?? ConfirmModalState.PENDING_CONFIRMATION + performStep({ nextStep: nextStepState, stepActions: confirmActions, state: confirmState }) + } + }, [confirmActions, confirmState, performStep, preConfirmState]) + + return { + callToAction, + errorMessage, + confirmState, + resetState, + txHash, + confirmActions, + } +} diff --git a/apps/web/src/views/Swap/V3Swap/hooks/useSendSwapTransaction.ts b/apps/web/src/views/Swap/V3Swap/hooks/useSendSwapTransaction.ts index 51bedfa82da40..6384c5ef4fb19 100644 --- a/apps/web/src/views/Swap/V3Swap/hooks/useSendSwapTransaction.ts +++ b/apps/web/src/views/Swap/V3Swap/hooks/useSendSwapTransaction.ts @@ -14,12 +14,12 @@ import { basisPointsToPercent } from 'utils/exchange' import { logSwap, logTx } from 'utils/log' import { isUserRejected } from 'utils/sentry' import { transactionErrorToUserReadableMessage } from 'utils/transactionErrorToUserReadableMessage' -import { viemClients } from 'utils/viem' -import { Address, Hex, TransactionExecutionError, hexToBigInt } from 'viem' +import { Address, Hex, TransactionExecutionError, UserRejectedRequestError, hexToBigInt } from 'viem' import { useSendTransaction } from 'wagmi' import { SendTransactionResult } from 'wagmi/actions' import { logger } from 'utils/datadog' +import { viemClients } from 'utils/viem' import { isZero } from '../utils/isZero' interface SwapCall { @@ -54,6 +54,7 @@ export default function useSendSwapTransaction( chainId?: number, trade?: SmartRouterTrade | null, // trade to execute, required swapCalls: SwapCall[] | WallchainSwapCall[] = [], + type: 'V3SmartSwap' | 'UniversalRouter' = 'V3SmartSwap', ): { callback: null | (() => Promise) } { const { t } = useTranslation() const addTransaction = useTransactionAdder() @@ -88,7 +89,6 @@ export default function useSendSwapTransaction( data: calldata, value: hexToBigInt(value), } - return publicClient .estimateGas(tx) .then((gasEstimate) => { @@ -136,7 +136,7 @@ export default function useSendSwapTransaction( } else { call.gas = 'gasEstimate' in bestCallOption && bestCallOption.gasEstimate - ? calculateGasMargin(bestCallOption.gasEstimate) + ? calculateGasMargin(bestCallOption.gasEstimate, 2000n) : undefined } @@ -202,7 +202,7 @@ export default function useSendSwapTransaction( outputAmount, input: trade.inputAmount.currency, output: trade.outputAmount.currency, - type: 'V3SmartSwap', + type, }) logTx({ account, chainId, hash: response.hash }) return response @@ -221,6 +221,7 @@ export default function useSendSwapTransaction( output: trade.outputAmount.currency, address: call.address, value: call.value, + type, cause: error instanceof TransactionExecutionError ? error.cause : undefined, }, error, @@ -243,5 +244,14 @@ export default function useSendSwapTransaction( recipientAddress, recipient, addTransaction, + type, ]) } + +export const userRejectedError = (error: unknown): boolean => { + return ( + error instanceof UserRejectedRequestError || + error instanceof TransactionRejectedError || + (typeof error !== 'string' && isUserRejected(error)) + ) +} diff --git a/apps/web/src/views/Swap/V3Swap/hooks/useSwapBestTrade.ts b/apps/web/src/views/Swap/V3Swap/hooks/useSwapBestTrade.ts index 221a32eb6004e..d1e43eb51758f 100644 --- a/apps/web/src/views/Swap/V3Swap/hooks/useSwapBestTrade.ts +++ b/apps/web/src/views/Swap/V3Swap/hooks/useSwapBestTrade.ts @@ -4,7 +4,7 @@ import { useUserSingleHopOnly } from '@pancakeswap/utils/user' import { useCurrency } from 'hooks/Tokens' import { useBestAMMTrade } from 'hooks/useBestAMMTrade' -import { useDeferredValue } from 'react' +import { useDeferredValue, useMemo } from 'react' import { Field } from 'state/swap/actions' import { useSwapState } from 'state/swap/hooks' import { @@ -38,6 +38,10 @@ export function useSwapBestTrade({ maxHops }: Options = {}) { const [v2Swap] = useUserV2SwapEnable() const [v3Swap] = useUserV3SwapEnable() const [stableSwap] = useUserStableSwapEnable() + // stable swap only support exact in + const stableSwapEnable = useMemo(() => { + return stableSwap && isExactIn + }, [stableSwap, isExactIn]) const { isLoading, trade, refresh, syncing, isStale, error } = useBestAMMTrade({ amount, @@ -48,7 +52,7 @@ export function useSwapBestTrade({ maxHops }: Options = {}) { maxSplits: split ? undefined : 0, v2Swap, v3Swap, - stableSwap, + stableSwap: stableSwapEnable, type: 'auto', trackPerf: true, }) diff --git a/apps/web/src/views/Swap/V3Swap/hooks/useSwapCallArguments.ts b/apps/web/src/views/Swap/V3Swap/hooks/useSwapCallArguments.ts index 639cf32a62b96..76307649e063f 100644 --- a/apps/web/src/views/Swap/V3Swap/hooks/useSwapCallArguments.ts +++ b/apps/web/src/views/Swap/V3Swap/hooks/useSwapCallArguments.ts @@ -3,7 +3,6 @@ import { Percent, TradeType } from '@pancakeswap/sdk' import { SMART_ROUTER_ADDRESSES, SmartRouterTrade, SwapRouter } from '@pancakeswap/smart-router/evm' import { FeeOptions } from '@pancakeswap/v3-sdk' import { useMemo } from 'react' -import { safeGetAddress } from 'utils' import { useGetENSAddressByName } from 'hooks/useGetENSAddressByName' @@ -32,13 +31,13 @@ export function useSwapCallArguments( feeOptions: FeeOptions | undefined, ): SwapCall[] { const { account, chainId } = useAccountActiveChain() - const recipientENSAddress = useGetENSAddressByName(recipientAddressOrName) + const recipientENSAddress = useGetENSAddressByName(recipientAddressOrName ?? undefined) const recipient = ( recipientAddressOrName === null || recipientAddressOrName === undefined ? account : isAddress(recipientAddressOrName) ? recipientAddressOrName - : isAddress(recipientENSAddress) + : isAddress(recipientENSAddress ?? '') ? recipientENSAddress : null ) as Address | null diff --git a/apps/web/src/views/Swap/V3Swap/hooks/useSwapCallArgumentsV2.ts b/apps/web/src/views/Swap/V3Swap/hooks/useSwapCallArgumentsV2.ts new file mode 100644 index 0000000000000..9afc854be06a3 --- /dev/null +++ b/apps/web/src/views/Swap/V3Swap/hooks/useSwapCallArgumentsV2.ts @@ -0,0 +1,70 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { Percent, TradeType } from '@pancakeswap/sdk' +import { SmartRouterTrade } from '@pancakeswap/smart-router/evm' +import { + PancakeSwapUniversalRouter, + Permit2Signature, + getUniversalRouterAddress, +} from '@pancakeswap/universal-router-sdk' +import { FeeOptions } from '@pancakeswap/v3-sdk' +import useAccountActiveChain from 'hooks/useAccountActiveChain' +import { useGetENSAddressByName } from 'hooks/useGetENSAddressByName' +import { useMemo } from 'react' +import { safeGetAddress } from 'utils' +import { Address, Hex } from 'viem' + +interface SwapCall { + address: Address + calldata: Hex + value: Hex +} + +/** + * Returns the swap calls that can be used to make the trade + * @param trade trade to execute + * @param allowedSlippage user allowed slippage + * @param recipientAddressOrName the ENS name or address of the recipient of the swap output + * @param deadline the deadline for executing the trade + * @param feeOptions the fee options to be applied to the trade. + */ +export function useSwapCallArgumentsV2( + trade: SmartRouterTrade | undefined | null, + allowedSlippage: Percent, + recipientAddressOrName: string | null | undefined, + permitSignature: Permit2Signature | undefined, + deadline: bigint | undefined, + feeOptions: FeeOptions | undefined, +): SwapCall[] { + const { account, chainId } = useAccountActiveChain() + const recipientENSAddress = useGetENSAddressByName(recipientAddressOrName ?? undefined) + const recipient = ( + recipientAddressOrName === null || recipientAddressOrName === undefined + ? account + : safeGetAddress(recipientAddressOrName) + ? recipientAddressOrName + : safeGetAddress(recipientENSAddress) + ? recipientENSAddress + : null + ) as Address | null + + return useMemo(() => { + if (!trade || !recipient || !account || !chainId) return [] + + const methodParameters = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, { + fee: feeOptions, + recipient, + inputTokenPermit: permitSignature, + slippageTolerance: allowedSlippage, + deadlineOrPreviousBlockhash: deadline?.toString(), + }) + const swapRouterAddress = getUniversalRouterAddress(chainId) + if (!swapRouterAddress) return [] + return [ + { + address: swapRouterAddress, + calldata: methodParameters.calldata as `0x${string}`, + value: methodParameters.value as `0x${string}`, + }, + ] + }, [account, allowedSlippage, chainId, deadline, feeOptions, recipient, permitSignature, trade]) +} diff --git a/apps/web/src/views/Swap/V3Swap/hooks/useSwapCallback.ts b/apps/web/src/views/Swap/V3Swap/hooks/useSwapCallback.ts index 18888ce78ea12..d896d82e2cda4 100644 --- a/apps/web/src/views/Swap/V3Swap/hooks/useSwapCallback.ts +++ b/apps/web/src/views/Swap/V3Swap/hooks/useSwapCallback.ts @@ -13,12 +13,11 @@ import { basisPointsToPercent } from 'utils/exchange' import useAccountActiveChain from 'hooks/useAccountActiveChain' import { SendTransactionResult } from 'wagmi/actions' import useSendSwapTransaction from './useSendSwapTransaction' -import { useSwapCallArguments } from './useSwapCallArguments' -// import { useWallchainSwapCallArguments } from './useWallchain' +import { useSwapCallArguments } from './useSwapCallArguments' import type { TWallchainMasterInput } from './useWallchain' -export enum SwapCallbackState { +enum SwapCallbackState { INVALID, LOADING, VALID, @@ -75,7 +74,7 @@ UseSwapCallbackArgs): UseSwapCallbackReturns { // wallchainMasterInput, // ) - const { callback } = useSendSwapTransaction(account, chainId, trade, swapCalls) + const { callback } = useSendSwapTransaction(account, chainId, trade, swapCalls, 'V3SmartSwap') return useMemo(() => { if (!trade || !account || !chainId || !callback) { diff --git a/apps/web/src/views/Swap/V3Swap/hooks/useSwapCallbackV2.ts b/apps/web/src/views/Swap/V3Swap/hooks/useSwapCallbackV2.ts new file mode 100644 index 0000000000000..ab0d76253053e --- /dev/null +++ b/apps/web/src/views/Swap/V3Swap/hooks/useSwapCallbackV2.ts @@ -0,0 +1,96 @@ +// eslint-disable-next-line no-restricted-imports +import { useTranslation } from '@pancakeswap/localization' +import { TradeType } from '@pancakeswap/sdk' +import { SmartRouterTrade } from '@pancakeswap/smart-router/evm' +import { Permit2Signature } from '@pancakeswap/universal-router-sdk' +import { FeeOptions } from '@pancakeswap/v3-sdk' +import { useMemo } from 'react' + +import { useUserSlippage } from '@pancakeswap/utils/user' +import { INITIAL_ALLOWED_SLIPPAGE } from 'config/constants' +import { useSwapState } from 'state/swap/hooks' +import { basisPointsToPercent } from 'utils/exchange' + +import useAccountActiveChain from 'hooks/useAccountActiveChain' +import { SendTransactionResult } from 'wagmi/actions' +import useSendSwapTransaction from './useSendSwapTransaction' +import { useSwapCallArgumentsV2 } from './useSwapCallArgumentsV2' +import type { TWallchainMasterInput, WallchainStatus } from './useWallchain' + +export enum SwapCallbackState { + INVALID, + LOADING, + VALID, + REVERTED, +} + +interface UseSwapCallbackReturns { + state: SwapCallbackState + callback?: () => Promise + error?: string + reason?: string +} +interface UseSwapCallbackArgs { + trade: SmartRouterTrade | undefined | null // trade to execute, required + // allowedSlippage: Percent // in bips + // recipientAddressOrName: string | null | undefined // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender + deadline?: bigint + permitSignature: Permit2Signature | undefined + feeOptions?: FeeOptions + onWallchainDrop?: () => void + statusWallchain?: WallchainStatus + wallchainMasterInput?: TWallchainMasterInput +} + +// returns a function that will execute a swap, if the parameters are all valid +// and the user has approved the slippage adjusted input amount for the trade +export function useSwapCallbackV2({ + trade, + deadline, + permitSignature, + feeOptions, +}: UseSwapCallbackArgs): UseSwapCallbackReturns { + const { t } = useTranslation() + const { account, chainId } = useAccountActiveChain() + const [allowedSlippageRaw] = useUserSlippage() || [INITIAL_ALLOWED_SLIPPAGE] + const allowedSlippage = useMemo(() => basisPointsToPercent(allowedSlippageRaw), [allowedSlippageRaw]) + const { recipient: recipientAddress } = useSwapState() + const recipient = recipientAddress === null ? account : recipientAddress + + const swapCalls = useSwapCallArgumentsV2( + trade, + allowedSlippage, + recipientAddress, + permitSignature, + deadline, + feeOptions, + ) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + // const wallchainSwapCalls = useWallchainSwapCallArguments( + // trade, + // swapCalls, + // account, + // onWallchainDrop, + // wallchainMasterInput, + // ) + // const wallchainSwapCalls = [] + + const { callback } = useSendSwapTransaction(account, chainId, trade ?? undefined, swapCalls, 'UniversalRouter') + + return useMemo(() => { + if (!trade || !account || !chainId || !callback) { + return { state: SwapCallbackState.INVALID, error: t('Missing dependencies') } + } + if (!recipient) { + if (recipientAddress !== null) { + return { state: SwapCallbackState.INVALID, error: t('Invalid recipient') } + } + return { state: SwapCallbackState.LOADING } + } + + return { + state: SwapCallbackState.VALID, + callback: async () => callback(), + } + }, [trade, account, chainId, callback, recipient, recipientAddress, t]) +} diff --git a/apps/web/src/views/Swap/V3Swap/hooks/useSwapConfig.ts b/apps/web/src/views/Swap/V3Swap/hooks/useSwapConfig.ts new file mode 100644 index 0000000000000..ddd011af4d149 --- /dev/null +++ b/apps/web/src/views/Swap/V3Swap/hooks/useSwapConfig.ts @@ -0,0 +1,16 @@ +import { useExpertMode } from '@pancakeswap/utils/user' +import { useTransactionDeadline } from 'hooks/useTransactionDeadline' + +export type SwapConfig = { + isExpertMode: boolean + deadline: bigint | undefined +} +export const useSwapConfig = (): SwapConfig => { + const [isExpertMode] = useExpertMode() + const [deadline] = useTransactionDeadline() + + return { + isExpertMode, + deadline, + } +} diff --git a/apps/web/src/views/Swap/V3Swap/hooks/useSwapCurrency.ts b/apps/web/src/views/Swap/V3Swap/hooks/useSwapCurrency.ts new file mode 100644 index 0000000000000..d8d4811768ff2 --- /dev/null +++ b/apps/web/src/views/Swap/V3Swap/hooks/useSwapCurrency.ts @@ -0,0 +1,22 @@ +import { Currency } from '@pancakeswap/swap-sdk-core' +import { useCurrency } from 'hooks/Tokens' +import { Field } from 'state/swap/actions' +import { useSwapState } from 'state/swap/hooks' + +export const useSwapCurrencyIds = (): [string | undefined, string | undefined] => { + const { + [Field.INPUT]: { currencyId: inputCurrencyId }, + [Field.OUTPUT]: { currencyId: outputCurrencyId }, + } = useSwapState() + + return [inputCurrencyId, outputCurrencyId] +} + +export const useSwapCurrency = (): [Currency | undefined, Currency | undefined] => { + const [inputCurrencyId, outputCurrencyId] = useSwapCurrencyIds() + + const inputCurrency = useCurrency(inputCurrencyId) as Currency + const outputCurrency = useCurrency(outputCurrencyId) as Currency + + return [inputCurrency, outputCurrency] +} diff --git a/apps/web/src/views/Swap/V3Swap/hooks/useWallchain.ts b/apps/web/src/views/Swap/V3Swap/hooks/useWallchain.ts index 3a3201f4d8697..2380f59d6480d 100644 --- a/apps/web/src/views/Swap/V3Swap/hooks/useWallchain.ts +++ b/apps/web/src/views/Swap/V3Swap/hooks/useWallchain.ts @@ -19,7 +19,7 @@ import { useWalletClient } from 'wagmi' import Bottleneck from 'bottleneck' import { WALLCHAIN_ENABLED, WallchainKeys, WallchainTokens } from 'config/wallchain' import { Address, Hex } from 'viem' -import { useSwapCallArguments } from './useSwapCallArguments' +import { useSwapCallArgumentsV2 } from './useSwapCallArgumentsV2' interface SwapCall { address: Address @@ -133,13 +133,14 @@ export function useWallchainApi( const [allowedSlippageRaw] = useUserSlippage() || [INITIAL_ALLOWED_SLIPPAGE] const allowedSlippage = useMemo(() => basisPointsToPercent(allowedSlippageRaw), [allowedSlippageRaw]) const [lastUpdate, setLastUpdate] = useState(0) + const useUniversalRouter = true const sdk = useWallchainSDK() - const swapCalls = useSwapCallArguments(trade, allowedSlippage, account, deadline, feeOptions) + const swapCalls = useSwapCallArgumentsV2(trade, allowedSlippage, account, undefined, deadline, feeOptions) useEffect(() => { - if (!sdk || !walletClient || !trade || !account) { + if (!sdk || !walletClient || !trade || !account || useUniversalRouter) { setStatus('not-found') return } @@ -188,7 +189,7 @@ export function useWallchainSwapCallArguments( previousSwapCalls: { address: `0x${string}`; calldata: `0x${string}`; value: `0x${string}` }[] | undefined | null, account: string | undefined | null, onWallchainDrop: () => void, - masterInput?: [TMEVFoundResponse['searcherRequest'], string], + masterInput?: [TMEVFoundResponse['searcherRequest'], string | undefined], ): SwapCall[] | WallchainSwapCall[] { const [swapCalls, setSwapCalls] = useState([]) const { data: walletClient } = useWalletClient() diff --git a/apps/web/src/views/Swap/V3Swap/index.tsx b/apps/web/src/views/Swap/V3Swap/index.tsx index 5edbc71dc7ef9..fc2eafe9447a4 100644 --- a/apps/web/src/views/Swap/V3Swap/index.tsx +++ b/apps/web/src/views/Swap/V3Swap/index.tsx @@ -1,24 +1,28 @@ import { SmartRouter } from '@pancakeswap/smart-router/evm' +import { Box } from '@pancakeswap/uikit' import throttle from 'lodash/throttle' import { useMemo } from 'react' -import { Box } from '@pancakeswap/uikit' -import { shouldShowMMLiquidityError } from 'views/Swap/MMLinkPools/utils/exchange' import { MMLiquidityWarning } from 'views/Swap/MMLinkPools/components/MMLiquidityWarning' +import { shouldShowMMLiquidityError } from 'views/Swap/MMLinkPools/utils/exchange' +import { EXPERIMENTAL_FEATURES } from 'config/experimentalFeatures' +import { useExperimentalFeatureEnabled } from 'hooks/useExperimentalFeatureEnabled' import { useDerivedBestTradeWithMM } from '../MMLinkPools/hooks/useDerivedSwapInfoWithMM' -import { useCheckInsufficientError } from './hooks/useCheckSufficient' import { + BuyCryptoLink, FormHeader, FormMain, MMTradeDetail, PricingAndSlippage, - SwapCommitButton, + SwapCommitButtonV2, TradeDetails, - BuyCryptoLink, } from './containers' import { MMCommitButton } from './containers/MMCommitButton' +import { MMCommitButtonV2 } from './containers/MMCommitButtonV2' +import { SwapCommitButton } from './containers/SwapCommitButton' import { useSwapBestTrade } from './hooks' +import { useCheckInsufficientError } from './hooks/useCheckSufficient' export function V3SwapForm() { const { isLoading, trade, refresh, syncing, isStale, error } = useSwapBestTrade() @@ -33,26 +37,44 @@ export function V3SwapForm() { const finalTrade = mm.isMMBetter ? mm?.mmTradeInfo?.trade : trade + // console.debug('debug trade', { + // trade, + // mm, + // finalTrade, + // }) + const tradeLoaded = !isLoading const price = useMemo(() => trade && SmartRouter.getExecutionPrice(trade), [trade]) const insufficientFundCurrency = useCheckInsufficientError(trade) + const featureEnabled = useExperimentalFeatureEnabled(EXPERIMENTAL_FEATURES.UniversalRouter) + const commitButton = useMemo(() => { + if (featureEnabled) { + return mm?.isMMBetter ? ( + + ) : ( + + ) + } + return mm?.isMMBetter ? ( + + ) : ( + + ) + }, [error, featureEnabled, mm, trade, tradeLoaded]) + return ( <> } + pricingAndSlippage={ + + } inputAmount={finalTrade?.inputAmount} outputAmount={finalTrade?.outputAmount} - swapCommitButton={ - mm?.isMMBetter ? ( - - ) : ( - - ) - } + swapCommitButton={commitButton} /> diff --git a/apps/web/src/views/Swap/V3Swap/types.ts b/apps/web/src/views/Swap/V3Swap/types.ts index 730f574591386..6a6c37b643a7c 100644 --- a/apps/web/src/views/Swap/V3Swap/types.ts +++ b/apps/web/src/views/Swap/V3Swap/types.ts @@ -1,4 +1,6 @@ -export enum ConfirmModalState { +import { ConfirmModalState } from '@pancakeswap/widgets-internal' + +export enum ConfirmModalStateV1 { REVIEWING, RESETTING_APPROVAL, APPROVING_TOKEN, @@ -7,10 +9,24 @@ export enum ConfirmModalState { COMPLETED, } +export type PendingConfirmModalStateV1 = Extract< + ConfirmModalStateV1, + | ConfirmModalStateV1.RESETTING_APPROVAL + | ConfirmModalStateV1.APPROVING_TOKEN + | ConfirmModalStateV1.APPROVE_PENDING + | ConfirmModalStateV1.PENDING_CONFIRMATION +> + export type PendingConfirmModalState = Extract< ConfirmModalState, - | ConfirmModalState.RESETTING_APPROVAL | ConfirmModalState.APPROVING_TOKEN - | ConfirmModalState.APPROVE_PENDING + | ConfirmModalState.PERMITTING | ConfirmModalState.PENDING_CONFIRMATION + | ConfirmModalState.WRAPPING + | ConfirmModalState.RESETTING_APPROVAL > + +export type AllowedAllowanceState = + | ConfirmModalState.RESETTING_APPROVAL + | ConfirmModalState.APPROVING_TOKEN + | ConfirmModalState.PERMITTING diff --git a/apps/web/src/views/Swap/components/Chart/PriceChartContainer.tsx b/apps/web/src/views/Swap/components/Chart/PriceChartContainer.tsx index 5ec75d97f4961..8f8a141f4585a 100644 --- a/apps/web/src/views/Swap/components/Chart/PriceChartContainer.tsx +++ b/apps/web/src/views/Swap/components/Chart/PriceChartContainer.tsx @@ -5,9 +5,9 @@ import BnbWbnbNotice from './BnbWbnbNotice' import PriceChart from './PriceChart' type PriceChartContainerProps = { - inputCurrencyId: string + inputCurrencyId: string | undefined inputCurrency?: Currency - outputCurrencyId: string + outputCurrencyId: string | undefined outputCurrency?: Currency isChartExpanded: boolean setIsChartExpanded: React.Dispatch> | null diff --git a/apps/web/src/views/Swap/components/SwapModalHeader.tsx b/apps/web/src/views/Swap/components/SwapModalHeader.tsx index a5c8f22e70d1e..88638e04fbad5 100644 --- a/apps/web/src/views/Swap/components/SwapModalHeader.tsx +++ b/apps/web/src/views/Swap/components/SwapModalHeader.tsx @@ -1,14 +1,15 @@ -import { ReactElement, useMemo } from 'react' -import { TradeType, CurrencyAmount, Currency, Percent } from '@pancakeswap/sdk' -import { Button, Text, ErrorIcon, ArrowDownIcon, AutoColumn } from '@pancakeswap/uikit' -import { Field } from 'state/swap/actions' import { useTranslation } from '@pancakeswap/localization' +import { Currency, CurrencyAmount, Percent, TradeType } from '@pancakeswap/sdk' +import { ArrowDownIcon, AutoColumn, Button, ErrorIcon, Text } from '@pancakeswap/uikit' import { formatAmount } from '@pancakeswap/utils/formatFractions' -import { warningSeverity, basisPointsToPercent } from 'utils/exchange' -import { CurrencyLogo } from 'components/Logo' -import { RowBetween, RowFixed } from 'components/Layout/Row' import truncateHash from '@pancakeswap/utils/truncateHash' -import { TruncatedText, SwapShowAcceptChanges } from './styleds' +import { RowBetween, RowFixed } from 'components/Layout/Row' +import { CurrencyLogo } from 'components/Logo' +import { ReactElement, useMemo } from 'react' +import { Field } from 'state/swap/actions' +import { basisPointsToPercent, warningSeverity } from 'utils/exchange' +import { SlippageAdjustedAmounts } from '../V3Swap/utils/exchange' +import { SwapShowAcceptChanges, TruncatedText } from './styleds' export default function SwapModalHeader({ inputAmount, @@ -31,7 +32,7 @@ export default function SwapModalHeader({ } tradeType: TradeType priceImpactWithoutFee?: Percent - slippageAdjustedAmounts: { [field in Field]?: CurrencyAmount } + slippageAdjustedAmounts: SlippageAdjustedAmounts | undefined | null isEnoughInputBalance?: boolean recipient?: string showAcceptChanges: boolean @@ -51,8 +52,8 @@ export default function SwapModalHeader({ const amount = tradeType === TradeType.EXACT_INPUT - ? formatAmount(slippageAdjustedAmounts[Field.OUTPUT], 6) - : formatAmount(slippageAdjustedAmounts[Field.INPUT], 6) + ? formatAmount(slippageAdjustedAmounts?.[Field.OUTPUT], 6) + : formatAmount(slippageAdjustedAmounts?.[Field.INPUT], 6) const symbol = tradeType === TradeType.EXACT_INPUT ? outputAmount.currency.symbol : inputAmount.currency.symbol const tradeInfoText = useMemo(() => { diff --git a/apps/web/src/views/Swap/index.tsx b/apps/web/src/views/Swap/index.tsx index 27231ea3662e5..8e9b2459a34c3 100644 --- a/apps/web/src/views/Swap/index.tsx +++ b/apps/web/src/views/Swap/index.tsx @@ -117,7 +117,7 @@ export default function Swap() { /> } isOpen={isChartDisplayed} - setIsOpen={setIsChartDisplayed} + setIsOpen={(isOpen) => setIsChartDisplayed?.(isOpen)} /> )} {isDesktop && isSwapHotTokenDisplay && isHotTokenSupported && ( diff --git a/apps/web/vitest.config.ts b/apps/web/vitest.config.ts index 4489ce07324dc..e7b70b7b4c6cd 100644 --- a/apps/web/vitest.config.ts +++ b/apps/web/vitest.config.ts @@ -11,7 +11,6 @@ export default defineConfig({ plugins: [tsconfigPaths({ projects: ['tsconfig.test.json'] }), react(), vanillaExtractPlugin()], resolve: { alias: { - '@pancakeswap/wagmi/connectors/binanceWallet': r('../../packages/wagmi/connectors/binanceWallet/index.ts'), '@pancakeswap/wagmi/connectors/blocto': r('../../packages/wagmi/connectors/blocto/index.ts'), '@pancakeswap/wagmi/connectors/miniProgram': r('../../packages/wagmi/connectors/miniProgram/index.ts'), '@pancakeswap/wagmi/connectors/trustWallet': r('../../packages/wagmi/connectors/trustWallet/index.ts'), diff --git a/package.json b/package.json index 8affcb2198f11..d49fd84f68931 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "packageManager": "pnpm@8.15.4", "scripts": { "preinstall": "npx only-allow pnpm", - "dev": "turbo run dev --filter=web... --concurrency=26", + "dev": "turbo run dev --filter=web... --concurrency=28", "dev:aptos": "pnpm turbo run dev --filter=aptos-web... --concurrency=20", "dev:blog": "pnpm turbo run dev --filter=blog... --concurrency=12", "dev:bridge": "pnpm turbo run dev --filter=bridge... --concurrency=16", @@ -75,7 +75,8 @@ }, "dependencies": { "encoding": "^0.1.13", - "ws": "^8.13.0" + "ws": "^8.13.0", + "zod": "^3.22.3" }, "pnpm": { "patchedDependencies": { diff --git a/packages/ifos/package.json b/packages/ifos/package.json index 0489e2edaa85e..2fa46c8f65136 100644 --- a/packages/ifos/package.json +++ b/packages/ifos/package.json @@ -12,7 +12,7 @@ "@pancakeswap/tokens": "workspace:*", "@pancakeswap/utils": "workspace:*", "viem": "1.19.11", - "wagmi": "1.4.7" + "wagmi": "1.4.13" }, "dependencies": { "@layerzerolabs/scan-client": "^0.0.6", @@ -25,6 +25,6 @@ "@pancakeswap/tsconfig": "workspace:*", "@pancakeswap/utils": "workspace:*", "viem": "1.19.11", - "wagmi": "1.4.7" + "wagmi": "1.4.13" } } diff --git a/packages/localization/src/config/translations.json b/packages/localization/src/config/translations.json index 70c485a5e782a..d3c226992a67d 100644 --- a/packages/localization/src/config/translations.json +++ b/packages/localization/src/config/translations.json @@ -2652,7 +2652,6 @@ "When staking on zkSync Era, unstaking your CAKE shortly after staking could result in no rewards being earned.": "When staking on zkSync Era, unstaking your CAKE shortly after staking could result in no rewards being earned.", "%chainId% supports limited fiat currencies. USD are not supported": "%chainId% supports limited fiat currencies. USD are not supported", "Proceed in your wallet": "Proceed in your wallet", - "Enable spending %symbol%": "Enable spending %symbol%", "If wallet require you to enter the number of tokens you want to approve, you could enter a number that is greater than or equal to the amount of tokens you are swapping.": "If wallet require you to enter the number of tokens you want to approve, you could enter a number that is greater than or equal to the amount of tokens you are swapping.", "Why": "Why", "approving": "approving", @@ -3047,7 +3046,6 @@ "To earn Farm rewards, continue seeding liquidity on PancakeSwap and stake your LP token in the Farm.": "To earn Farm rewards, continue seeding liquidity on PancakeSwap and stake your LP token in the Farm.", "To earn Merkl rewards, continue seeding liquidity on PancakeSwap but DO NOT stake your LP token in the Farm. Claim your rewards directly on ": "To earn Merkl rewards, continue seeding liquidity on PancakeSwap but DO NOT stake your LP token in the Farm. Claim your rewards directly on ", "An active veCAKE staking position is required for activating farm yield boosters.": "An active veCAKE staking position is required for activating farm yield boosters.", - "Go to CAKE Staking": "Go to CAKE Staking", "Yield booster active": "Yield booster active", "Boost unlimited number of positions on all V3 Farms. Boost will be applied when staking. Lock more CAKE or extend your lock to receive a higher boost.": "Boost unlimited number of positions on all V3 Farms. Boost will be applied when staking. Lock more CAKE or extend your lock to receive a higher boost.", "Go to Lock": "Go to Lock", @@ -3198,5 +3196,11 @@ "Preview of your veCAKE⌛ at snapshot time:": "Preview of your veCAKE⌛ at snapshot time:", "Snapshot at / Campaign Ends:": "Snapshot at / Campaign Ends:", "Increase your veCAKE to continue earning": "Increase your veCAKE to continue earning", - "Your CAKE staking position is expired. Unlock your position and set up a new one to start earning.": "Your CAKE staking position is expired. Unlock your position and set up a new one to start earning." + "Your CAKE staking position is expired. Unlock your position and set up a new one to start earning.": "Your CAKE staking position is expired. Unlock your position and set up a new one to start earning.", + "Permit %symbol%": "Permit %symbol%", + "Enable spending %symbol%": "Enable spending %symbol%", + "Reset approval on USDT.": "Reset approval on USDT.", + "Go to CAKE Staking": "Go to CAKE Staking", + "Revert transaction filled, but Approval not reset to 0. Please try again.": "Revert transaction filled, but Approval not reset to 0. Please try again.", + "Approve transaction filled, but Approval still not enough to fill current trade. Please try again.": "Approve transaction filled, but Approval still not enough to fill current trade. Please try again." } diff --git a/packages/permit2-sdk/package.json b/packages/permit2-sdk/package.json new file mode 100644 index 0000000000000..b5a381a249354 --- /dev/null +++ b/packages/permit2-sdk/package.json @@ -0,0 +1,45 @@ +{ + "name": "@pancakeswap/permit2-sdk", + "version": "1.0.0", + "description": "An sdk for interacting with permit2.", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "module": "dist/index.mjs", + "files": [ + "dist" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./package.json": "./package.json" + }, + "repository": { + "type": "git", + "url": "https://github.com/pancakeswap/pancake-frontend.git", + "directory": "packages/permit2-sdk" + }, + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx,json}\"", + "format:write": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json}\"", + "prepublishOnly": "pnpm run build" + }, + "keywords": [ + "pancakeswap", + "ethereum" + ], + "dependencies": { + "@pancakeswap/chains": "workspace:*", + "@pancakeswap/sdk": "workspace:*", + "tiny-invariant": "^1.3.1", + "viem": "1.19.11" + }, + "devDependencies": { + "tslib": "^2.6.2", + "tsup": "^6.7.0" + } +} diff --git a/packages/permit2-sdk/src/abis/Permit2.ts b/packages/permit2-sdk/src/abis/Permit2.ts new file mode 100644 index 0000000000000..da2556d0e8c9d --- /dev/null +++ b/packages/permit2-sdk/src/abis/Permit2.ts @@ -0,0 +1,901 @@ +export const Permit2ABI = [ + { + inputs: [ + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'AllowanceExpired', + type: 'error', + }, + { + inputs: [], + name: 'ExcessiveInvalidation', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'InsufficientAllowance', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'maxAmount', + type: 'uint256', + }, + ], + name: 'InvalidAmount', + type: 'error', + }, + { + inputs: [], + name: 'InvalidContractSignature', + type: 'error', + }, + { + inputs: [], + name: 'InvalidNonce', + type: 'error', + }, + { + inputs: [], + name: 'InvalidSignature', + type: 'error', + }, + { + inputs: [], + name: 'InvalidSignatureLength', + type: 'error', + }, + { + inputs: [], + name: 'InvalidSigner', + type: 'error', + }, + { + inputs: [], + name: 'LengthMismatch', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'signatureDeadline', + type: 'uint256', + }, + ], + name: 'SignatureExpired', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'token', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint160', + name: 'amount', + type: 'uint160', + }, + { + indexed: false, + internalType: 'uint48', + name: 'expiration', + type: 'uint48', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'token', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'spender', + type: 'address', + }, + ], + name: 'Lockdown', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'token', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint48', + name: 'newNonce', + type: 'uint48', + }, + { + indexed: false, + internalType: 'uint48', + name: 'oldNonce', + type: 'uint48', + }, + ], + name: 'NonceInvalidation', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'token', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint160', + name: 'amount', + type: 'uint160', + }, + { + indexed: false, + internalType: 'uint48', + name: 'expiration', + type: 'uint48', + }, + { + indexed: false, + internalType: 'uint48', + name: 'nonce', + type: 'uint48', + }, + ], + name: 'Permit', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'word', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'mask', + type: 'uint256', + }, + ], + name: 'UnorderedNonceInvalidation', + type: 'event', + }, + { + inputs: [], + name: 'DOMAIN_SEPARATOR', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'allowance', + outputs: [ + { + internalType: 'uint160', + name: 'amount', + type: 'uint160', + }, + { + internalType: 'uint48', + name: 'expiration', + type: 'uint48', + }, + { + internalType: 'uint48', + name: 'nonce', + type: 'uint48', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint160', + name: 'amount', + type: 'uint160', + }, + { + internalType: 'uint48', + name: 'expiration', + type: 'uint48', + }, + ], + name: 'approve', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint48', + name: 'newNonce', + type: 'uint48', + }, + ], + name: 'invalidateNonces', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'wordPos', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'mask', + type: 'uint256', + }, + ], + name: 'invalidateUnorderedNonces', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + ], + internalType: 'struct IAllowanceTransfer.TokenSpenderPair[]', + name: 'approvals', + type: 'tuple[]', + }, + ], + name: 'lockdown', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + name: 'nonceBitmap', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint160', + name: 'amount', + type: 'uint160', + }, + { + internalType: 'uint48', + name: 'expiration', + type: 'uint48', + }, + { + internalType: 'uint48', + name: 'nonce', + type: 'uint48', + }, + ], + internalType: 'struct IAllowanceTransfer.PermitDetails[]', + name: 'details', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'sigDeadline', + type: 'uint256', + }, + ], + internalType: 'struct IAllowanceTransfer.PermitBatch', + name: 'permitBatch', + type: 'tuple', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + ], + name: 'permit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint160', + name: 'amount', + type: 'uint160', + }, + { + internalType: 'uint48', + name: 'expiration', + type: 'uint48', + }, + { + internalType: 'uint48', + name: 'nonce', + type: 'uint48', + }, + ], + internalType: 'struct IAllowanceTransfer.PermitDetails', + name: 'details', + type: 'tuple', + }, + { + internalType: 'address', + name: 'spender', + type: 'address', + }, + { + internalType: 'uint256', + name: 'sigDeadline', + type: 'uint256', + }, + ], + internalType: 'struct IAllowanceTransfer.PermitSingle', + name: 'permitSingle', + type: 'tuple', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + ], + name: 'permit', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + internalType: 'struct ISignatureTransfer.TokenPermissions', + name: 'permitted', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + internalType: 'struct ISignatureTransfer.PermitTransferFrom', + name: 'permit', + type: 'tuple', + }, + { + components: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'requestedAmount', + type: 'uint256', + }, + ], + internalType: 'struct ISignatureTransfer.SignatureTransferDetails', + name: 'transferDetails', + type: 'tuple', + }, + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + ], + name: 'permitTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + internalType: 'struct ISignatureTransfer.TokenPermissions[]', + name: 'permitted', + type: 'tuple[]', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + internalType: 'struct ISignatureTransfer.PermitBatchTransferFrom', + name: 'permit', + type: 'tuple', + }, + { + components: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'requestedAmount', + type: 'uint256', + }, + ], + internalType: 'struct ISignatureTransfer.SignatureTransferDetails[]', + name: 'transferDetails', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + ], + name: 'permitTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + internalType: 'struct ISignatureTransfer.TokenPermissions', + name: 'permitted', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + internalType: 'struct ISignatureTransfer.PermitTransferFrom', + name: 'permit', + type: 'tuple', + }, + { + components: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'requestedAmount', + type: 'uint256', + }, + ], + internalType: 'struct ISignatureTransfer.SignatureTransferDetails', + name: 'transferDetails', + type: 'tuple', + }, + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'witness', + type: 'bytes32', + }, + { + internalType: 'string', + name: 'witnessTypeString', + type: 'string', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + ], + name: 'permitWitnessTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + internalType: 'struct ISignatureTransfer.TokenPermissions[]', + name: 'permitted', + type: 'tuple[]', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + internalType: 'struct ISignatureTransfer.PermitBatchTransferFrom', + name: 'permit', + type: 'tuple', + }, + { + components: [ + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'requestedAmount', + type: 'uint256', + }, + ], + internalType: 'struct ISignatureTransfer.SignatureTransferDetails[]', + name: 'transferDetails', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'witness', + type: 'bytes32', + }, + { + internalType: 'string', + name: 'witnessTypeString', + type: 'string', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + ], + name: 'permitWitnessTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint160', + name: 'amount', + type: 'uint160', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + ], + internalType: 'struct IAllowanceTransfer.AllowanceTransferDetails[]', + name: 'transferDetails', + type: 'tuple[]', + }, + ], + name: 'transferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint160', + name: 'amount', + type: 'uint160', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + ], + name: 'transferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const diff --git a/packages/permit2-sdk/src/allowanceTransfer.ts b/packages/permit2-sdk/src/allowanceTransfer.ts new file mode 100644 index 0000000000000..7bb2c8f69f814 --- /dev/null +++ b/packages/permit2-sdk/src/allowanceTransfer.ts @@ -0,0 +1,105 @@ +import invariant from 'tiny-invariant' +import { BigintIsh } from '@pancakeswap/sdk' +import { Address, TypedData } from 'viem' +import { MaxSigDeadline, MaxOrderedNonce, MaxAllowanceTransferAmount, MaxAllowanceExpiration } from './constants' +import { permit2Domain } from './domain' +import { TypedDataDomain } from './utils/types' + +export interface PermitDetails { + token: string + amount: BigintIsh + expiration: BigintIsh + nonce: BigintIsh +} + +export interface PermitSingle { + details: PermitDetails + spender: string + sigDeadline: BigintIsh + [key: string]: unknown +} + +export interface PermitBatch { + details: PermitDetails[] + spender: string + sigDeadline: BigintIsh + [key: string]: unknown +} + +type TypedStructData = { + domain: TypedDataDomain + types: TypedData + values: TValue +} + +export type PermitSingleData = TypedStructData +export type PermitBatchData = TypedStructData + +const PERMIT_DETAILS = [ + { name: 'token', type: 'address' }, + { name: 'amount', type: 'uint160' }, + { name: 'expiration', type: 'uint48' }, + { name: 'nonce', type: 'uint48' }, +] + +const PERMIT_TYPES = { + PermitSingle: [ + { name: 'details', type: 'PermitDetails' }, + { name: 'spender', type: 'address' }, + { name: 'sigDeadline', type: 'uint256' }, + ], + PermitDetails: PERMIT_DETAILS, +} + +const PERMIT_BATCH_TYPES = { + PermitBatch: [ + { name: 'details', type: 'PermitDetails[]' }, + { name: 'spender', type: 'address' }, + { name: 'sigDeadline', type: 'uint256' }, + ], + PermitDetails: PERMIT_DETAILS, +} + +function isPermit(permit: PermitSingle | PermitBatch): permit is PermitSingle { + return !Array.isArray(permit.details) +} + +export abstract class AllowanceTransfer { + /** + * Cannot be constructed. + */ + // eslint-disable-next-line no-useless-constructor + private constructor() {} + + // return the data to be sent in a eth_signTypedData RPC call + // for signing the given permit data + public static getPermitData( + permit: PermitSingle | PermitBatch, + permit2Address: Address, + chainId: number, + ): PermitSingleData | PermitBatchData { + invariant(MaxSigDeadline >= BigInt(permit.sigDeadline), 'SIG_DEADLINE_OUT_OF_RANGE') + + const domain = permit2Domain(permit2Address, chainId) + if (isPermit(permit)) { + validatePermitDetails(permit.details) + return { + domain, + types: PERMIT_TYPES, + values: permit, + } + } + permit.details.forEach(validatePermitDetails) + return { + domain, + types: PERMIT_BATCH_TYPES, + values: permit, + } + } +} + +function validatePermitDetails(details: PermitDetails) { + invariant(MaxOrderedNonce >= BigInt(details.nonce), 'NONCE_OUT_OF_RANGE') + invariant(MaxAllowanceTransferAmount >= BigInt(details.amount), 'AMOUNT_OUT_OF_RANGE') + invariant(MaxAllowanceExpiration >= BigInt(details.expiration), 'EXPIRATION_OUT_OF_RANGE') +} diff --git a/packages/permit2-sdk/src/constants.ts b/packages/permit2-sdk/src/constants.ts new file mode 100644 index 0000000000000..d6e5a42f6d534 --- /dev/null +++ b/packages/permit2-sdk/src/constants.ts @@ -0,0 +1,61 @@ +import { ChainId } from '@pancakeswap/chains' +import { Address } from 'viem' + +// @fixme convert to ChainId after all chains are updated +const PERMIT2_ADDRESSES: Record = { + [ChainId.ETHEREUM]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + [ChainId.GOERLI]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + [ChainId.SEPOLIA]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + + [ChainId.BSC]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + [ChainId.BSC_TESTNET]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + + // [ChainId.SCROLL]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + [ChainId.SCROLL_SEPOLIA]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + + [ChainId.ARBITRUM_ONE]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + [ChainId.ARBITRUM_GOERLI]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + [ChainId.ARBITRUM_SEPOLIA]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + + [ChainId.BASE]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + [ChainId.BASE_TESTNET]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + [ChainId.BASE_SEPOLIA]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + + [ChainId.POLYGON_ZKEVM]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + [ChainId.POLYGON_ZKEVM_TESTNET]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + + [ChainId.LINEA]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + [ChainId.LINEA_TESTNET]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + + [ChainId.ZKSYNC]: '0x686FD50007EaA636F01154d660b96110B6bFe351', + [ChainId.ZKSYNC_TESTNET]: '0xaf321b731E65715DdbFDa73A066E00BB28345709', + + [ChainId.OPBNB]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', + [ChainId.OPBNB_TESTNET]: '0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768', +} + +export const getPermit2Address = (chainId: ChainId | undefined): Address => { + if (chainId === undefined) return PERMIT2_ADDRESSES[ChainId.BSC] + if (!(chainId in PERMIT2_ADDRESSES)) throw new Error(`Permit2 Contract not deployed on chain ${chainId}`) + return PERMIT2_ADDRESSES[chainId] +} + +export const MaxUint48 = BigInt('0xffffffffffff') +export const MaxUint160 = BigInt('0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF') +export const MaxUint256 = BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff') + +// alias max types for their usages +// allowance transfer types +export const MaxAllowanceTransferAmount = MaxUint160 +export const MaxAllowanceExpiration = MaxUint48 +export const MaxOrderedNonce = MaxUint48 + +// signature transfer types +export const MaxSignatureTransferAmount = MaxUint256 +export const MaxUnorderedNonce = MaxUint256 +export const MaxSigDeadline = MaxUint256 + +export const PERMIT_EXPIRATION = 2592000000 // 30 day +export const PERMIT_SIG_EXPIRATION = 1800000 // 30 min + +export const InstantExpiration = BigInt('0') diff --git a/packages/permit2-sdk/src/domain.ts b/packages/permit2-sdk/src/domain.ts new file mode 100644 index 0000000000000..f939c54269dc9 --- /dev/null +++ b/packages/permit2-sdk/src/domain.ts @@ -0,0 +1,18 @@ +import type { Address } from 'viem' +import { TypedDataDomain, TypedDataParameter } from './utils/types' + +const PERMIT2_DOMAIN_NAME = 'Permit2' + +export function permit2Domain(permit2Address: Address, chainId: number): TypedDataDomain { + return { + name: PERMIT2_DOMAIN_NAME, + chainId, + verifyingContract: permit2Address, + } +} + +export type PermitData = { + domain: TypedDataDomain + types: Record + values: any +} diff --git a/packages/permit2-sdk/src/index.ts b/packages/permit2-sdk/src/index.ts new file mode 100644 index 0000000000000..8dc16ae42b2db --- /dev/null +++ b/packages/permit2-sdk/src/index.ts @@ -0,0 +1,5 @@ +export * from './allowanceTransfer' +export * from './signatureTransfer' +export * from './constants' +export * from './utils/utils' +export * from './abis/Permit2' diff --git a/packages/permit2-sdk/src/signatureTransfer.ts b/packages/permit2-sdk/src/signatureTransfer.ts new file mode 100644 index 0000000000000..a28f44b83e12c --- /dev/null +++ b/packages/permit2-sdk/src/signatureTransfer.ts @@ -0,0 +1,144 @@ +import invariant from 'tiny-invariant' +import { BigintIsh } from '@pancakeswap/sdk' +import { Address } from 'viem' +import { permit2Domain } from './domain' +import { MaxSigDeadline, MaxUnorderedNonce, MaxSignatureTransferAmount } from './constants' +import { TypedDataDomain, TypedDataParameter } from './utils/types' + +export interface Witness { + witness: any + witnessTypeName: string + witnessType: Record +} + +export interface TokenPermissions { + token: string + amount: BigintIsh +} + +export interface PermitTransferFrom { + permitted: TokenPermissions + spender: string + nonce: BigintIsh + deadline: BigintIsh +} + +export interface PermitBatchTransferFrom { + permitted: TokenPermissions[] + spender: string + nonce: BigintIsh + deadline: BigintIsh +} + +export type PermitTransferFromData = { + domain: TypedDataDomain + types: Record + values: PermitTransferFrom +} + +export type PermitBatchTransferFromData = { + domain: TypedDataDomain + types: Record + values: PermitBatchTransferFrom +} + +const TOKEN_PERMISSIONS = [ + { name: 'token', type: 'address' }, + { name: 'amount', type: 'uint256' }, +] + +const PERMIT_TRANSFER_FROM_TYPES = { + PermitTransferFrom: [ + { name: 'permitted', type: 'TokenPermissions' }, + { name: 'spender', type: 'address' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + ], + TokenPermissions: TOKEN_PERMISSIONS, +} + +const PERMIT_BATCH_TRANSFER_FROM_TYPES = { + PermitBatchTransferFrom: [ + { name: 'permitted', type: 'TokenPermissions[]' }, + { name: 'spender', type: 'address' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + ], + TokenPermissions: TOKEN_PERMISSIONS, +} + +function permitTransferFromWithWitnessType(witness: Witness): Record { + return { + PermitWitnessTransferFrom: [ + { name: 'permitted', type: 'TokenPermissions' }, + { name: 'spender', type: 'address' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + { name: 'witness', type: witness.witnessTypeName }, + ], + TokenPermissions: TOKEN_PERMISSIONS, + ...witness.witnessType, + } +} + +function permitBatchTransferFromWithWitnessType(witness: Witness): Record { + return { + PermitBatchWitnessTransferFrom: [ + { name: 'permitted', type: 'TokenPermissions[]' }, + { name: 'spender', type: 'address' }, + { name: 'nonce', type: 'uint256' }, + { name: 'deadline', type: 'uint256' }, + { name: 'witness', type: witness.witnessTypeName }, + ], + TokenPermissions: TOKEN_PERMISSIONS, + ...witness.witnessType, + } +} + +function isPermitTransferFrom(permit: PermitTransferFrom | PermitBatchTransferFrom): permit is PermitTransferFrom { + return !Array.isArray(permit.permitted) +} + +export abstract class SignatureTransfer { + /** + * Cannot be constructed. + */ + // eslint-disable-next-line no-useless-constructor + private constructor() {} + + // return the data to be sent in a eth_signTypedData RPC call + // for signing the given permit data + public static getPermitData( + permit: PermitTransferFrom | PermitBatchTransferFrom, + permit2Address: Address, + chainId: number, + witness?: Witness, + ): PermitTransferFromData | PermitBatchTransferFromData { + invariant(MaxSigDeadline >= BigInt(permit.deadline), 'SIG_DEADLINE_OUT_OF_RANGE') + invariant(MaxUnorderedNonce >= BigInt(permit.nonce), 'NONCE_OUT_OF_RANGE') + + const domain = permit2Domain(permit2Address, chainId) + if (isPermitTransferFrom(permit)) { + validateTokenPermissions(permit.permitted) + const types = witness ? permitTransferFromWithWitnessType(witness) : PERMIT_TRANSFER_FROM_TYPES + const values = witness ? Object.assign(permit, { witness: witness.witness }) : permit + return { + domain, + types, + values, + } + } + permit.permitted.forEach(validateTokenPermissions) + const types = witness ? permitBatchTransferFromWithWitnessType(witness) : PERMIT_BATCH_TRANSFER_FROM_TYPES + const values = witness ? Object.assign(permit, { witness: witness.witness }) : permit + return { + domain, + types, + values, + } + } +} + +function validateTokenPermissions(permissions: TokenPermissions) { + invariant(MaxSignatureTransferAmount >= BigInt(permissions.amount), 'AMOUNT_OUT_OF_RANGE') +} diff --git a/packages/permit2-sdk/src/utils/types.ts b/packages/permit2-sdk/src/utils/types.ts new file mode 100644 index 0000000000000..1c047d4cd9559 --- /dev/null +++ b/packages/permit2-sdk/src/utils/types.ts @@ -0,0 +1,8 @@ +export type Bytes = ArrayLike + +export type { TypedDataDomain, TypedDataParameter } from 'viem' + +export interface TypedDataField { + name: string + type: string +} diff --git a/packages/permit2-sdk/src/utils/utils.ts b/packages/permit2-sdk/src/utils/utils.ts new file mode 100644 index 0000000000000..82884fd7cfc32 --- /dev/null +++ b/packages/permit2-sdk/src/utils/utils.ts @@ -0,0 +1,25 @@ +import { BigintIsh, Token } from '@pancakeswap/sdk' +import { PermitSingle } from '../allowanceTransfer' +import { MaxAllowanceTransferAmount, PERMIT_EXPIRATION, PERMIT_SIG_EXPIRATION } from '../constants' + +// @TODO: remove this type, use `PermitSingle` only +export interface Permit extends PermitSingle { + sigDeadline: string +} + +export const toDeadline = (expiration: number): number => { + return Math.floor((Date.now() + expiration) / 1000) +} + +export const generatePermitTypedData = (token: Token, nonce: BigintIsh, spender: string): Permit => { + return { + details: { + token: token.address, + amount: MaxAllowanceTransferAmount.toString(), + expiration: toDeadline(PERMIT_EXPIRATION).toString(), + nonce: nonce.toString(), + }, + spender, + sigDeadline: toDeadline(PERMIT_SIG_EXPIRATION).toString(), + } +} diff --git a/packages/permit2-sdk/tsconfig.json b/packages/permit2-sdk/tsconfig.json new file mode 100644 index 0000000000000..c7b3968a97fb1 --- /dev/null +++ b/packages/permit2-sdk/tsconfig.json @@ -0,0 +1,28 @@ +{ + "extends": "@pancakeswap/tsconfig/base", + "exclude": ["**/*.test.ts", "./dist/**"], + "include": ["src"], + "compilerOptions": { + "outDir": "./dist", + "lib": ["ESNext"], + "target": "es2020", + "module": "esnext", + "importHelpers": true, + "declaration": true, + "sourceMap": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "moduleResolution": "node", + "esModuleInterop": true, + "resolveJsonModule": true + } +} diff --git a/packages/permit2-sdk/tsup.config.ts b/packages/permit2-sdk/tsup.config.ts new file mode 100644 index 0000000000000..0a5ef0ddd8028 --- /dev/null +++ b/packages/permit2-sdk/tsup.config.ts @@ -0,0 +1,23 @@ +import { exec } from 'child_process' +import { defineConfig } from 'tsup' + +export default defineConfig((options) => ({ + entry: { + index: './src/index.ts', + }, + format: ['esm', 'cjs'], + dts: false, + clean: !options.watch, + treeshake: true, + splitting: true, + onSuccess: async () => { + exec('tsc --emitDeclarationOnly --declaration', (err, stdout) => { + if (err) { + console.error(stdout) + if (!options.watch) { + process.exit(1) + } + } + }) + }, +})) diff --git a/packages/pools/package.json b/packages/pools/package.json index bd28802030276..5ec4a7b695c60 100644 --- a/packages/pools/package.json +++ b/packages/pools/package.json @@ -16,13 +16,13 @@ "types": "dist/index.d.ts", "module": "dist/index.mjs", "dependencies": { + "@pancakeswap/chains": "workspace:*", "@pancakeswap/token-lists": "workspace:*", "@pancakeswap/tokens": "workspace:*", - "@pancakeswap/chains": "workspace:*", "bignumber.js": "^9.0.0", "lodash": "^4.17.21", "viem": "1.19.11", - "wagmi": "1.4.7" + "wagmi": "1.4.13" }, "devDependencies": { "@pancakeswap/tsconfig": "workspace:*", diff --git a/packages/position-managers/package.json b/packages/position-managers/package.json index eb1269d890101..9f66d4b763c7d 100644 --- a/packages/position-managers/package.json +++ b/packages/position-managers/package.json @@ -8,19 +8,19 @@ }, "type": "module", "dependencies": { + "@pancakeswap/chains": "workspace:*", "@pancakeswap/sdk": "workspace:*", - "@pancakeswap/v3-sdk": "workspace:*", "@pancakeswap/token-lists": "workspace:*", "@pancakeswap/tokens": "workspace:*", - "@pancakeswap/chains": "workspace:*", + "@pancakeswap/v3-sdk": "workspace:*", "bignumber.js": "^9.0.0", "lodash": "^4.17.21", "viem": "1.19.11", - "wagmi": "1.4.7" + "wagmi": "1.4.13" }, "devDependencies": { - "@types/lodash": "^4.14.168", "@pancakeswap/tsconfig": "workspace:*", - "@pancakeswap/utils": "workspace:*" + "@pancakeswap/utils": "workspace:*", + "@types/lodash": "^4.14.168" } } diff --git a/packages/prediction/package.json b/packages/prediction/package.json index f2a0d87e9edb9..eca21a25c966d 100644 --- a/packages/prediction/package.json +++ b/packages/prediction/package.json @@ -12,10 +12,10 @@ }, "dependencies": { "@pancakeswap/chains": "workspace:*", - "@pancakeswap/tokens": "workspace:*", "@pancakeswap/pools": "workspace:*", + "@pancakeswap/tokens": "workspace:*", "viem": "1.19.11", - "wagmi": "1.4.7" + "wagmi": "1.4.13" }, "devDependencies": { "@pancakeswap/sdk": "workspace:*", diff --git a/packages/smart-router/evm/index.test.ts b/packages/smart-router/evm/index.test.ts index 8565ca1941403..60465bff2d519 100644 --- a/packages/smart-router/evm/index.test.ts +++ b/packages/smart-router/evm/index.test.ts @@ -35,6 +35,7 @@ test('exports', () => { "StableSwap", "SmartRouter", "SwapRouter", + "getPoolAddress", "Transformer", "PoolType", "RouteType", @@ -103,14 +104,19 @@ test('SmartRouter exports', () => { "APISchema", "Transformer", "getExecutionPrice", - "getMidPrice", "getPoolAddress", - "involvesCurrency", - "isStablePool", "isV2Pool", "isV3Pool", + "isStablePool", + "getMidPrice", + "involvesCurrency", + "encodeMixedRouteToPath", + "buildBaseRoute", + "getOutputOfPools", + "partitionMixedRouteByProtocol", "log", "logger", + "getPriceImpact", "maximumAmountIn", "metric", "minimumAmountOut", diff --git a/packages/smart-router/evm/v3-router/index.ts b/packages/smart-router/evm/v3-router/index.ts index 93ad1505e9d48..e21eae24ebfc0 100644 --- a/packages/smart-router/evm/v3-router/index.ts +++ b/packages/smart-router/evm/v3-router/index.ts @@ -1,3 +1,5 @@ export * as SmartRouter from './smartRouter' export { SwapRouter } from './utils/swapRouter' +export type { SwapOptions } from './utils/swapRouter' +export { getPoolAddress } from './utils/pool' export { Transformer } from './utils' diff --git a/packages/smart-router/evm/v3-router/smartRouter.ts b/packages/smart-router/evm/v3-router/smartRouter.ts index d1bdc6c1d31c3..1a7d3f7960084 100644 --- a/packages/smart-router/evm/v3-router/smartRouter.ts +++ b/packages/smart-router/evm/v3-router/smartRouter.ts @@ -7,14 +7,19 @@ export type { V2PoolWithTvl as SubgraphV2Pool, V3PoolWithTvl as SubgraphV3Pool } export { Transformer, getExecutionPrice, - getMidPrice, getPoolAddress, - involvesCurrency, - isStablePool, isV2Pool, isV3Pool, + isStablePool, + getMidPrice, + involvesCurrency, + encodeMixedRouteToPath, + buildBaseRoute, + getOutputOfPools, + partitionMixedRouteByProtocol, log, logger, + getPriceImpact, maximumAmountIn, metric, minimumAmountOut, diff --git a/packages/smart-router/evm/v3-router/utils/index.ts b/packages/smart-router/evm/v3-router/utils/index.ts index e8c47438ff933..46cea1a53fd86 100644 --- a/packages/smart-router/evm/v3-router/utils/index.ts +++ b/packages/smart-router/evm/v3-router/utils/index.ts @@ -1,10 +1,14 @@ export * from './encodeMixedRouteToPath' export * from './getExecutionPrice' export * from './getNativeWrappedToken' +export * from './getOutputOfPools' +export * from './getPriceImpact' export * from './getUsdGasToken' export * from './isCurrenciesSameChain' export * from './logger' export * from './maximumAmount' +export * from './partitionMixedRouteByProtocol' export * from './pool' export * from './route' +export * from './swapRouter' export * as Transformer from './transformer' diff --git a/packages/smart-router/evm/v3-router/utils/swapRouter.test.ts b/packages/smart-router/evm/v3-router/utils/swapRouter.test.ts index 9ff282528bcff..f705b7ce88d43 100644 --- a/packages/smart-router/evm/v3-router/utils/swapRouter.test.ts +++ b/packages/smart-router/evm/v3-router/utils/swapRouter.test.ts @@ -426,7 +426,7 @@ describe('SwapRouter', () => { } it('generates the same calldata', async () => { - const trades = [v2Trade, v3Trade] + const trades = [mixedRouteTrade1, mixedRouteTrade2] const { calldata, value } = SwapRouter.swapCallParameters(trades, { slippageTolerance, recipient, diff --git a/packages/smart-router/evm/v3-router/utils/swapRouter.ts b/packages/smart-router/evm/v3-router/utils/swapRouter.ts index f1d405a867270..ce82acc192991 100644 --- a/packages/smart-router/evm/v3-router/utils/swapRouter.ts +++ b/packages/smart-router/evm/v3-router/utils/swapRouter.ts @@ -768,7 +768,7 @@ export abstract class SwapRouter { } // if price impact is very high, there's a chance of hitting max/min prices resulting in a partial fill of the swap - private static riskOfPartialFill(trades: AnyTradeType): boolean { + public static riskOfPartialFill(trades: AnyTradeType): boolean { if (Array.isArray(trades)) { return trades.some((trade) => { return SwapRouter.v3TradeWithHighPriceImpact(trade) diff --git a/packages/swap-sdk/src/router.ts b/packages/swap-sdk/src/router.ts index 637bc7d110cdb..211193b6bc364 100644 --- a/packages/swap-sdk/src/router.ts +++ b/packages/swap-sdk/src/router.ts @@ -1,4 +1,4 @@ -import { TradeType, Token, CurrencyAmount, Currency, Percent } from '@pancakeswap/swap-sdk-core' +import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@pancakeswap/swap-sdk-core' import invariant from 'tiny-invariant' import { Address, Hex } from 'viem' import { Trade } from './entities' @@ -48,7 +48,7 @@ export interface SwapParameters { /** * The arguments to pass to the method, all hex encoded. */ - args: (Hex | Hex[])[] + args: Array /** * The amount of wei to send in hex. */ diff --git a/packages/uikit/package.json b/packages/uikit/package.json index 2d147570e214f..806a341683629 100644 --- a/packages/uikit/package.json +++ b/packages/uikit/package.json @@ -141,7 +141,7 @@ "sonner": "^1.2.4", "styled-system": "^5.1.5", "tslib": "^2.2.0", - "wagmi": "1.4.7" + "wagmi": "1.4.13" }, "publishConfig": { "access": "public" diff --git a/packages/universal-router-sdk/README.md b/packages/universal-router-sdk/README.md new file mode 100644 index 0000000000000..c028a825c54c1 --- /dev/null +++ b/packages/universal-router-sdk/README.md @@ -0,0 +1,13 @@ +# universal-router-sdk +This SDK facilitates interactions with Universal Router. This repo is the pancakeswap variation of the original Universal-router-sdk by UniswapLabs. The PancakeSwap implementation makes the necessary changes in order to be compatible with the Pancakeswap smart-router and other existing SDK's. + +## Usage +Install latest version of universal-router-sdk. Then import the corresponding Trade class and Data object for each protocol you'd like to interact with. + +```sh +npm add @pancakeswap/universal-router-sdk +# or +yarn add @pancakeswap/universal-router-sdk +# or +pnpm add @pancakeswap/universal-router-sdk +``` diff --git a/packages/universal-router-sdk/package.json b/packages/universal-router-sdk/package.json new file mode 100644 index 0000000000000..adc31a2b78803 --- /dev/null +++ b/packages/universal-router-sdk/package.json @@ -0,0 +1,54 @@ +{ + "name": "@pancakeswap/universal-router-sdk", + "version": "1.0.0", + "description": "🛠 An SDK for building applications on top of Pancakeswap.", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "module": "dist/index.mjs", + "files": [ + "dist" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + }, + "./package.json": "./package.json" + }, + "repository": { + "type": "git", + "url": "https://github.com/pancakeswap/pancake-frontend.git", + "directory": "packages/universal-router-sdk" + }, + "keywords": [ + "pancakeswap" + ], + "scripts": { + "build": "tsup", + "dev": "tsup --watch", + "coverage": "vitest run --coverage", + "test": "vitest --run", + "prepublishOnly": "pnpm run build", + "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist", + "format:check": "prettier --check --loglevel error \"{src,test}/**/*.{js,jsx,ts,tsx,json}\"", + "format:write": "prettier --write \"{src,test}/**/*.{js,jsx,ts,tsx,json}\"", + "lint": "eslint '{src,test}/**/*.{js,jsx,ts,json}'" + }, + "devDependencies": { + "@pancakeswap/tokens": "workspace:*", + "@types/node": "^15.12.2", + "tslib": "^2.3.0", + "tsup": "^6.7.0" + }, + "dependencies": { + "@pancakeswap/chains": "workspace:*", + "@pancakeswap/permit2-sdk": "workspace:*", + "@pancakeswap/sdk": "workspace:*", + "@pancakeswap/smart-router": "workspace:*", + "@pancakeswap/v3-sdk": "workspace:*", + "abitype": "^0.9.8", + "tiny-invariant": "^1.1.0", + "viem": "1.19.11" + } +} diff --git a/packages/universal-router-sdk/src/abis/ERC20.ts b/packages/universal-router-sdk/src/abis/ERC20.ts new file mode 100644 index 0000000000000..0a64636bdea41 --- /dev/null +++ b/packages/universal-router-sdk/src/abis/ERC20.ts @@ -0,0 +1,222 @@ +export const erc20Abi = [ + { + constant: true, + inputs: {}, + name: 'name', + outputs: [ + { + name: '', + type: 'string', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_spender', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + name: 'approve', + outputs: [ + { + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: {}, + name: 'totalSupply', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_from', + type: 'address', + }, + { + name: '_to', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + name: 'transferFrom', + outputs: [ + { + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: {}, + name: 'decimals', + outputs: [ + { + name: '', + type: 'uint8', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address', + }, + ], + name: 'balanceOf', + outputs: [ + { + name: 'balance', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: {}, + name: 'symbol', + outputs: [ + { + name: '', + type: 'string', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_to', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + name: 'transfer', + outputs: [ + { + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address', + }, + { + name: '_spender', + type: 'address', + }, + ], + name: 'allowance', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + payable: true, + stateMutability: 'payable', + type: 'fallback', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'owner', + type: 'address', + }, + { + indexed: true, + name: 'spender', + type: 'address', + }, + { + indexed: false, + name: 'value', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'from', + type: 'address', + }, + { + indexed: true, + name: 'to', + type: 'address', + }, + { + indexed: false, + name: 'value', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, +] as const diff --git a/packages/universal-router-sdk/src/abis/Element.ts b/packages/universal-router-sdk/src/abis/Element.ts new file mode 100644 index 0000000000000..55ea42edab17e --- /dev/null +++ b/packages/universal-router-sdk/src/abis/Element.ts @@ -0,0 +1,2530 @@ +export const elementAbi = [ + { + inputs: [ + { + internalType: 'contract IEtherToken', + name: 'weth', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bytes32', + name: 'orderHash', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + indexed: false, + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + indexed: false, + internalType: 'struct INFTOrdersFeature.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + indexed: false, + internalType: 'address', + name: 'erc721Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'erc721TokenId', + type: 'uint256', + }, + ], + name: 'ERC721BuyOrderFilled', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + indexed: false, + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + indexed: false, + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + indexed: false, + internalType: 'address', + name: 'erc721Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'erc721TokenId', + type: 'uint256', + }, + { + components: [ + { + internalType: 'contract IPropertyValidator', + name: 'propertyValidator', + type: 'address', + }, + { + internalType: 'bytes', + name: 'propertyData', + type: 'bytes', + }, + ], + indexed: false, + internalType: 'struct LibNFTOrder.Property[]', + name: 'nftProperties', + type: 'tuple[]', + }, + ], + name: 'ERC721BuyOrderPreSigned', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + ], + name: 'ERC721OrderCancelled', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bytes32', + name: 'orderHash', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + indexed: false, + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + indexed: false, + internalType: 'struct INFTOrdersFeature.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + indexed: false, + internalType: 'address', + name: 'erc721Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'erc721TokenId', + type: 'uint256', + }, + ], + name: 'ERC721SellOrderFilled', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + indexed: false, + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + indexed: false, + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + indexed: false, + internalType: 'address', + name: 'erc721Token', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'erc721TokenId', + type: 'uint256', + }, + ], + name: 'ERC721SellOrderPreSigned', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'newHashNonce', + type: 'uint256', + }, + ], + name: 'HashNonceIncremented', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bytes32', + name: 'orderHash', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'bytes', + name: 'takerData', + type: 'bytes', + }, + ], + name: 'TakerDataEmitted', + type: 'event', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + ], + internalType: 'struct LibNFTOrder.NFTSellOrder[]', + name: 'sellOrders', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'enum LibSignature.SignatureType', + name: 'signatureType', + type: 'uint8', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + internalType: 'struct LibSignature.Signature[]', + name: 'signatures', + type: 'tuple[]', + }, + { + internalType: 'bool', + name: 'revertIfIncomplete', + type: 'bool', + }, + ], + name: 'batchBuyERC721s', + outputs: [ + { + internalType: 'bool[]', + name: 'successes', + type: 'bool[]', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + ], + internalType: 'struct LibNFTOrder.NFTSellOrder[]', + name: 'sellOrders', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'enum LibSignature.SignatureType', + name: 'signatureType', + type: 'uint8', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + internalType: 'struct LibSignature.Signature[]', + name: 'signatures', + type: 'tuple[]', + }, + { + internalType: 'address[]', + name: 'takers', + type: 'address[]', + }, + { + internalType: 'bytes[]', + name: 'takerDatas', + type: 'bytes[]', + }, + { + internalType: 'bool', + name: 'revertIfIncomplete', + type: 'bool', + }, + ], + name: 'batchBuyERC721sEx', + outputs: [ + { + internalType: 'bool[]', + name: 'successes', + type: 'bool[]', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256[]', + name: 'orderNonces', + type: 'uint256[]', + }, + ], + name: 'batchCancelERC721Orders', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + ], + internalType: 'struct LibNFTOrder.NFTSellOrder[]', + name: 'sellOrders', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + { + components: [ + { + internalType: 'contract IPropertyValidator', + name: 'propertyValidator', + type: 'address', + }, + { + internalType: 'bytes', + name: 'propertyData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Property[]', + name: 'nftProperties', + type: 'tuple[]', + }, + ], + internalType: 'struct LibNFTOrder.NFTBuyOrder[]', + name: 'buyOrders', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'enum LibSignature.SignatureType', + name: 'signatureType', + type: 'uint8', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + internalType: 'struct LibSignature.Signature[]', + name: 'sellOrderSignatures', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'enum LibSignature.SignatureType', + name: 'signatureType', + type: 'uint8', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + internalType: 'struct LibSignature.Signature[]', + name: 'buyOrderSignatures', + type: 'tuple[]', + }, + ], + name: 'batchMatchERC721Orders', + outputs: [ + { + internalType: 'uint256[]', + name: 'profits', + type: 'uint256[]', + }, + { + internalType: 'bool[]', + name: 'successes', + type: 'bool[]', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + ], + internalType: 'struct LibNFTOrder.NFTSellOrder', + name: 'sellOrder', + type: 'tuple', + }, + { + components: [ + { + internalType: 'enum LibSignature.SignatureType', + name: 'signatureType', + type: 'uint8', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + internalType: 'struct LibSignature.Signature', + name: 'signature', + type: 'tuple', + }, + ], + name: 'buyERC721', + outputs: {}, + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + ], + internalType: 'struct LibNFTOrder.NFTSellOrder', + name: 'sellOrder', + type: 'tuple', + }, + { + components: [ + { + internalType: 'enum LibSignature.SignatureType', + name: 'signatureType', + type: 'uint8', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + internalType: 'struct LibSignature.Signature', + name: 'signature', + type: 'tuple', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'bytes', + name: 'takerData', + type: 'bytes', + }, + ], + name: 'buyERC721Ex', + outputs: {}, + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + ], + internalType: 'struct LibNFTOrder.NFTSellOrder', + name: 'sellOrder', + type: 'tuple', + }, + { + components: [ + { + internalType: 'enum LibSignature.SignatureType', + name: 'signatureType', + type: 'uint8', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + internalType: 'struct LibSignature.Signature', + name: 'signature', + type: 'tuple', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'ethAvailable', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'takerData', + type: 'bytes', + }, + ], + name: 'buyERC721ExFromProxy', + outputs: {}, + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + ], + internalType: 'struct LibNFTOrder.NFTSellOrder', + name: 'sellOrder', + type: 'tuple', + }, + { + components: [ + { + internalType: 'enum LibSignature.SignatureType', + name: 'signatureType', + type: 'uint8', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + internalType: 'struct LibSignature.Signature', + name: 'signature', + type: 'tuple', + }, + ], + name: 'buyERC721FromProxy', + outputs: {}, + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'orderNonce', + type: 'uint256', + }, + ], + name: 'cancelERC721Order', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + { + components: [ + { + internalType: 'contract IPropertyValidator', + name: 'propertyValidator', + type: 'address', + }, + { + internalType: 'bytes', + name: 'propertyData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Property[]', + name: 'nftProperties', + type: 'tuple[]', + }, + ], + internalType: 'struct LibNFTOrder.NFTBuyOrder', + name: 'order', + type: 'tuple', + }, + ], + name: 'getERC721BuyOrderHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + { + components: [ + { + internalType: 'contract IPropertyValidator', + name: 'propertyValidator', + type: 'address', + }, + { + internalType: 'bytes', + name: 'propertyData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Property[]', + name: 'nftProperties', + type: 'tuple[]', + }, + ], + internalType: 'struct LibNFTOrder.NFTBuyOrder', + name: 'order', + type: 'tuple', + }, + ], + name: 'getERC721BuyOrderStatus', + outputs: [ + { + internalType: 'enum LibNFTOrder.OrderStatus', + name: '', + type: 'uint8', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'uint248', + name: 'nonceRange', + type: 'uint248', + }, + ], + name: 'getERC721OrderStatusBitVector', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + ], + internalType: 'struct LibNFTOrder.NFTSellOrder', + name: 'order', + type: 'tuple', + }, + ], + name: 'getERC721SellOrderHash', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + ], + internalType: 'struct LibNFTOrder.NFTSellOrder', + name: 'order', + type: 'tuple', + }, + ], + name: 'getERC721SellOrderStatus', + outputs: [ + { + internalType: 'enum LibNFTOrder.OrderStatus', + name: '', + type: 'uint8', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + ], + name: 'getHashNonce', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'incrementHashNonce', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + ], + internalType: 'struct LibNFTOrder.NFTSellOrder', + name: 'sellOrder', + type: 'tuple', + }, + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + { + components: [ + { + internalType: 'contract IPropertyValidator', + name: 'propertyValidator', + type: 'address', + }, + { + internalType: 'bytes', + name: 'propertyData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Property[]', + name: 'nftProperties', + type: 'tuple[]', + }, + ], + internalType: 'struct LibNFTOrder.NFTBuyOrder', + name: 'buyOrder', + type: 'tuple', + }, + { + components: [ + { + internalType: 'enum LibSignature.SignatureType', + name: 'signatureType', + type: 'uint8', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + internalType: 'struct LibSignature.Signature', + name: 'sellOrderSignature', + type: 'tuple', + }, + { + components: [ + { + internalType: 'enum LibSignature.SignatureType', + name: 'signatureType', + type: 'uint8', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + internalType: 'struct LibSignature.Signature', + name: 'buyOrderSignature', + type: 'tuple', + }, + ], + name: 'matchERC721Orders', + outputs: [ + { + internalType: 'uint256', + name: 'profit', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + name: 'onERC721Received', + outputs: [ + { + internalType: 'bytes4', + name: 'success', + type: 'bytes4', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + { + components: [ + { + internalType: 'contract IPropertyValidator', + name: 'propertyValidator', + type: 'address', + }, + { + internalType: 'bytes', + name: 'propertyData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Property[]', + name: 'nftProperties', + type: 'tuple[]', + }, + ], + internalType: 'struct LibNFTOrder.NFTBuyOrder', + name: 'order', + type: 'tuple', + }, + ], + name: 'preSignERC721BuyOrder', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + ], + internalType: 'struct LibNFTOrder.NFTSellOrder', + name: 'order', + type: 'tuple', + }, + ], + name: 'preSignERC721SellOrder', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + { + components: [ + { + internalType: 'contract IPropertyValidator', + name: 'propertyValidator', + type: 'address', + }, + { + internalType: 'bytes', + name: 'propertyData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Property[]', + name: 'nftProperties', + type: 'tuple[]', + }, + ], + internalType: 'struct LibNFTOrder.NFTBuyOrder', + name: 'buyOrder', + type: 'tuple', + }, + { + components: [ + { + internalType: 'enum LibSignature.SignatureType', + name: 'signatureType', + type: 'uint8', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + internalType: 'struct LibSignature.Signature', + name: 'signature', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'erc721TokenId', + type: 'uint256', + }, + { + internalType: 'bool', + name: 'unwrapNativeToken', + type: 'bool', + }, + { + internalType: 'bytes', + name: 'takerData', + type: 'bytes', + }, + ], + name: 'sellERC721', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + { + components: [ + { + internalType: 'contract IPropertyValidator', + name: 'propertyValidator', + type: 'address', + }, + { + internalType: 'bytes', + name: 'propertyData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Property[]', + name: 'nftProperties', + type: 'tuple[]', + }, + ], + internalType: 'struct LibNFTOrder.NFTBuyOrder', + name: 'order', + type: 'tuple', + }, + { + components: [ + { + internalType: 'enum LibSignature.SignatureType', + name: 'signatureType', + type: 'uint8', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + internalType: 'struct LibSignature.Signature', + name: 'signature', + type: 'tuple', + }, + ], + name: 'validateERC721BuyOrderSignature', + outputs: {}, + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'nonce', + type: 'uint256', + }, + { + internalType: 'contract IERC20', + name: 'erc20Token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'erc20TokenAmount', + type: 'uint256', + }, + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'feeData', + type: 'bytes', + }, + ], + internalType: 'struct LibNFTOrder.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'nft', + type: 'address', + }, + { + internalType: 'uint256', + name: 'nftId', + type: 'uint256', + }, + ], + internalType: 'struct LibNFTOrder.NFTSellOrder', + name: 'order', + type: 'tuple', + }, + { + components: [ + { + internalType: 'enum LibSignature.SignatureType', + name: 'signatureType', + type: 'uint8', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + internalType: 'struct LibSignature.Signature', + name: 'signature', + type: 'tuple', + }, + ], + name: 'validateERC721SellOrderSignature', + outputs: {}, + stateMutability: 'view', + type: 'function', + }, +] as const diff --git a/packages/universal-router-sdk/src/abis/Foundation.ts b/packages/universal-router-sdk/src/abis/Foundation.ts new file mode 100644 index 0000000000000..d4d7ecbd1c5c4 --- /dev/null +++ b/packages/universal-router-sdk/src/abis/Foundation.ts @@ -0,0 +1,1581 @@ +export const foundationAb = [ + { + inputs: [ + { + internalType: 'address payable', + name: 'treasury', + type: 'address', + }, + { + internalType: 'address', + name: 'feth', + type: 'address', + }, + { + internalType: 'address', + name: 'royaltyRegistry', + type: 'address', + }, + { + internalType: 'uint256', + name: 'duration', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: {}, + name: 'FoundationTreasuryNode_Address_Is_Not_A_Contract', + type: 'error', + }, + { + inputs: {}, + name: 'FoundationTreasuryNode_Caller_Not_Admin', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'buyPrice', + type: 'uint256', + }, + ], + name: 'NFTMarketBuyPrice_Cannot_Buy_At_Lower_Price', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketBuyPrice_Cannot_Buy_Unset_Price', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketBuyPrice_Cannot_Cancel_Unset_Price', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + ], + name: 'NFTMarketBuyPrice_Only_Owner_Can_Cancel_Price', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + ], + name: 'NFTMarketBuyPrice_Only_Owner_Can_Set_Price', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketBuyPrice_Price_Already_Set', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketBuyPrice_Price_Too_High', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'seller', + type: 'address', + }, + ], + name: 'NFTMarketBuyPrice_Seller_Mismatch', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketCore_FETH_Address_Is_Not_A_Contract', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketCore_Only_FETH_Can_Transfer_ETH', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketCore_Seller_Not_Found', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketFees_Address_Does_Not_Support_IRoyaltyRegistry', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketOffer_Cannot_Be_Made_While_In_Auction', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'currentOfferAmount', + type: 'uint256', + }, + ], + name: 'NFTMarketOffer_Offer_Below_Min_Amount', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'expiry', + type: 'uint256', + }, + ], + name: 'NFTMarketOffer_Offer_Expired', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'currentOfferFrom', + type: 'address', + }, + ], + name: 'NFTMarketOffer_Offer_From_Does_Not_Match', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'minOfferAmount', + type: 'uint256', + }, + ], + name: 'NFTMarketOffer_Offer_Must_Be_At_Least_Min_Amount', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketOffer_Provided_Contract_And_TokenId_Count_Must_Match', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketOffer_Reason_Required', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + ], + name: 'NFTMarketReserveAuction_Already_Listed', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'minAmount', + type: 'uint256', + }, + ], + name: 'NFTMarketReserveAuction_Bid_Must_Be_At_Least_Min_Amount', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketReserveAuction_Cannot_Admin_Cancel_Without_Reason', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'reservePrice', + type: 'uint256', + }, + ], + name: 'NFTMarketReserveAuction_Cannot_Bid_Lower_Than_Reserve_Price', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + ], + name: 'NFTMarketReserveAuction_Cannot_Bid_On_Ended_Auction', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketReserveAuction_Cannot_Bid_On_Nonexistent_Auction', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketReserveAuction_Cannot_Cancel_Nonexistent_Auction', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketReserveAuction_Cannot_Finalize_Already_Settled_Auction', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + ], + name: 'NFTMarketReserveAuction_Cannot_Finalize_Auction_In_Progress', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketReserveAuction_Cannot_Rebid_Over_Outstanding_Bid', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketReserveAuction_Cannot_Update_Auction_In_Progress', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'maxDuration', + type: 'uint256', + }, + ], + name: 'NFTMarketReserveAuction_Exceeds_Max_Duration', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'extensionDuration', + type: 'uint256', + }, + ], + name: 'NFTMarketReserveAuction_Less_Than_Extension_Duration', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketReserveAuction_Must_Set_Non_Zero_Reserve_Price', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'seller', + type: 'address', + }, + ], + name: 'NFTMarketReserveAuction_Not_Matching_Seller', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'owner', + type: 'address', + }, + ], + name: 'NFTMarketReserveAuction_Only_Owner_Can_Update_Auction', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketReserveAuction_Price_Already_Set', + type: 'error', + }, + { + inputs: {}, + name: 'NFTMarketReserveAuction_Too_Much_Value_Provided', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'seller', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'buyer', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'protocolFee', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'creatorFee', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'sellerRev', + type: 'uint256', + }, + ], + name: 'BuyPriceAccepted', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'BuyPriceCanceled', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'BuyPriceInvalidated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'seller', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'price', + type: 'uint256', + }, + ], + name: 'BuyPriceSet', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'address', + name: 'buyReferrer', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'buyReferrerProtocolFee', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'buyReferrerSellerFee', + type: 'uint256', + }, + ], + name: 'BuyReferralPaid', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint8', + name: 'version', + type: 'uint8', + }, + ], + name: 'Initialized', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'buyer', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'seller', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'protocolFee', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'creatorFee', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'sellerRev', + type: 'uint256', + }, + ], + name: 'OfferAccepted', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'string', + name: 'reason', + type: 'string', + }, + ], + name: 'OfferCanceledByAdmin', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'OfferInvalidated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'buyer', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'expiration', + type: 'uint256', + }, + ], + name: 'OfferMade', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'bidder', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + ], + name: 'ReserveAuctionBidPlaced', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + ], + name: 'ReserveAuctionCanceled', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'string', + name: 'reason', + type: 'string', + }, + ], + name: 'ReserveAuctionCanceledByAdmin', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'seller', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'duration', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'extensionDuration', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'reservePrice', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + ], + name: 'ReserveAuctionCreated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'seller', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'bidder', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'protocolFee', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'creatorFee', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'sellerRev', + type: 'uint256', + }, + ], + name: 'ReserveAuctionFinalized', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + ], + name: 'ReserveAuctionInvalidated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'reservePrice', + type: 'uint256', + }, + ], + name: 'ReserveAuctionUpdated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'user', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'WithdrawalToFETH', + type: 'event', + }, + { + inputs: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'address', + name: 'offerFrom', + type: 'address', + }, + { + internalType: 'uint256', + name: 'minAmount', + type: 'uint256', + }, + ], + name: 'acceptOffer', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address[]', + name: 'nftContracts', + type: 'address[]', + }, + { + internalType: 'uint256[]', + name: 'tokenIds', + type: 'uint256[]', + }, + { + internalType: 'string', + name: 'reason', + type: 'string', + }, + ], + name: 'adminCancelOffers', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + { + internalType: 'string', + name: 'reason', + type: 'string', + }, + ], + name: 'adminCancelReserveAuction', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'maxPrice', + type: 'uint256', + }, + ], + name: 'buy', + outputs: {}, + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'maxPrice', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'referrer', + type: 'address', + }, + ], + name: 'buyV2', + outputs: {}, + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'cancelBuyPrice', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + ], + name: 'cancelReserveAuction', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'reservePrice', + type: 'uint256', + }, + ], + name: 'createReserveAuction', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + ], + name: 'finalizeReserveAuction', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'getBuyPrice', + outputs: [ + { + internalType: 'address', + name: 'seller', + type: 'address', + }, + { + internalType: 'uint256', + name: 'price', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'price', + type: 'uint256', + }, + ], + name: 'getFeesAndRecipients', + outputs: [ + { + internalType: 'uint256', + name: 'protocolFee', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'creatorRev', + type: 'uint256', + }, + { + internalType: 'address payable[]', + name: 'creatorRecipients', + type: 'address[]', + }, + { + internalType: 'uint256[]', + name: 'creatorShares', + type: 'uint256[]', + }, + { + internalType: 'uint256', + name: 'sellerRev', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'owner', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'getFethAddress', + outputs: [ + { + internalType: 'address', + name: 'fethAddress', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'getFoundationTreasury', + outputs: [ + { + internalType: 'address payable', + name: 'treasuryAddress', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'getImmutableRoyalties', + outputs: [ + { + internalType: 'address payable[]', + name: 'recipients', + type: 'address[]', + }, + { + internalType: 'uint256[]', + name: 'splitPerRecipientInBasisPoints', + type: 'uint256[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + ], + name: 'getMinBidAmount', + outputs: [ + { + internalType: 'uint256', + name: 'minimum', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'getMinOfferAmount', + outputs: [ + { + internalType: 'uint256', + name: 'minimum', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'creator', + type: 'address', + }, + ], + name: 'getMutableRoyalties', + outputs: [ + { + internalType: 'address payable[]', + name: 'recipients', + type: 'address[]', + }, + { + internalType: 'uint256[]', + name: 'splitPerRecipientInBasisPoints', + type: 'uint256[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'getOffer', + outputs: [ + { + internalType: 'address', + name: 'buyer', + type: 'address', + }, + { + internalType: 'uint256', + name: 'expiration', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'getOfferReferrer', + outputs: [ + { + internalType: 'address payable', + name: 'referrer', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + ], + name: 'getReserveAuction', + outputs: [ + { + components: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'seller', + type: 'address', + }, + { + internalType: 'uint256', + name: 'duration', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'extensionDuration', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'bidder', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + internalType: 'struct NFTMarketReserveAuction.ReserveAuction', + name: 'auction', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + ], + name: 'getReserveAuctionBidReferrer', + outputs: [ + { + internalType: 'address payable', + name: 'referrer', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'getReserveAuctionIdFor', + outputs: [ + { + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'getRoyaltyRegistry', + outputs: [ + { + internalType: 'address', + name: 'registry', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'getTokenCreator', + outputs: [ + { + internalType: 'address payable', + name: 'creator', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'initialize', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'makeOffer', + outputs: [ + { + internalType: 'uint256', + name: 'expiration', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'referrer', + type: 'address', + }, + ], + name: 'makeOfferV2', + outputs: [ + { + internalType: 'uint256', + name: 'expiration', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + ], + name: 'placeBid', + outputs: {}, + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'referrer', + type: 'address', + }, + ], + name: 'placeBidV2', + outputs: {}, + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'nftContract', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'price', + type: 'uint256', + }, + ], + name: 'setBuyPrice', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'auctionId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'reservePrice', + type: 'uint256', + }, + ], + name: 'updateReserveAuction', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + stateMutability: 'payable', + type: 'receive', + }, +] as const diff --git a/packages/universal-router-sdk/src/abis/IUniswapV3Pool.ts b/packages/universal-router-sdk/src/abis/IUniswapV3Pool.ts new file mode 100644 index 0000000000000..802009f341b7c --- /dev/null +++ b/packages/universal-router-sdk/src/abis/IUniswapV3Pool.ts @@ -0,0 +1,988 @@ +export const uniV3PoolAbi = [ + { + inputs: {}, + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'int24', + name: 'tickLower', + type: 'int24', + }, + { + indexed: true, + internalType: 'int24', + name: 'tickUpper', + type: 'int24', + }, + { + indexed: false, + internalType: 'uint128', + name: 'amount', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + name: 'Burn', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: true, + internalType: 'int24', + name: 'tickLower', + type: 'int24', + }, + { + indexed: true, + internalType: 'int24', + name: 'tickUpper', + type: 'int24', + }, + { + indexed: false, + internalType: 'uint128', + name: 'amount0', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint128', + name: 'amount1', + type: 'uint128', + }, + ], + name: 'Collect', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint128', + name: 'amount0', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint128', + name: 'amount1', + type: 'uint128', + }, + ], + name: 'CollectProtocol', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'paid0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'paid1', + type: 'uint256', + }, + ], + name: 'Flash', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint16', + name: 'observationCardinalityNextOld', + type: 'uint16', + }, + { + indexed: false, + internalType: 'uint16', + name: 'observationCardinalityNextNew', + type: 'uint16', + }, + ], + name: 'IncreaseObservationCardinalityNext', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint160', + name: 'sqrtPriceX96', + type: 'uint160', + }, + { + indexed: false, + internalType: 'int24', + name: 'tick', + type: 'int24', + }, + ], + name: 'Initialize', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'owner', + type: 'address', + }, + { + indexed: true, + internalType: 'int24', + name: 'tickLower', + type: 'int24', + }, + { + indexed: true, + internalType: 'int24', + name: 'tickUpper', + type: 'int24', + }, + { + indexed: false, + internalType: 'uint128', + name: 'amount', + type: 'uint128', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + name: 'Mint', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint8', + name: 'feeProtocol0Old', + type: 'uint8', + }, + { + indexed: false, + internalType: 'uint8', + name: 'feeProtocol1Old', + type: 'uint8', + }, + { + indexed: false, + internalType: 'uint8', + name: 'feeProtocol0New', + type: 'uint8', + }, + { + indexed: false, + internalType: 'uint8', + name: 'feeProtocol1New', + type: 'uint8', + }, + ], + name: 'SetFeeProtocol', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'int256', + name: 'amount0', + type: 'int256', + }, + { + indexed: false, + internalType: 'int256', + name: 'amount1', + type: 'int256', + }, + { + indexed: false, + internalType: 'uint160', + name: 'sqrtPriceX96', + type: 'uint160', + }, + { + indexed: false, + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + indexed: false, + internalType: 'int24', + name: 'tick', + type: 'int24', + }, + ], + name: 'Swap', + type: 'event', + }, + { + inputs: [ + { + internalType: 'int24', + name: 'tickLower', + type: 'int24', + }, + { + internalType: 'int24', + name: 'tickUpper', + type: 'int24', + }, + { + internalType: 'uint128', + name: 'amount', + type: 'uint128', + }, + ], + name: 'burn', + outputs: [ + { + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'int24', + name: 'tickLower', + type: 'int24', + }, + { + internalType: 'int24', + name: 'tickUpper', + type: 'int24', + }, + { + internalType: 'uint128', + name: 'amount0Requested', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'amount1Requested', + type: 'uint128', + }, + ], + name: 'collect', + outputs: [ + { + internalType: 'uint128', + name: 'amount0', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'amount1', + type: 'uint128', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint128', + name: 'amount0Requested', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'amount1Requested', + type: 'uint128', + }, + ], + name: 'collectProtocol', + outputs: [ + { + internalType: 'uint128', + name: 'amount0', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'amount1', + type: 'uint128', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'factory', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'fee', + outputs: [ + { + internalType: 'uint24', + name: '', + type: 'uint24', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'feeGrowthGlobal0X128', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'feeGrowthGlobal1X128', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + name: 'flash', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint16', + name: 'observationCardinalityNext', + type: 'uint16', + }, + ], + name: 'increaseObservationCardinalityNext', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint160', + name: 'sqrtPriceX96', + type: 'uint160', + }, + ], + name: 'initialize', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'liquidity', + outputs: [ + { + internalType: 'uint128', + name: '', + type: 'uint128', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'maxLiquidityPerTick', + outputs: [ + { + internalType: 'uint128', + name: '', + type: 'uint128', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'int24', + name: 'tickLower', + type: 'int24', + }, + { + internalType: 'int24', + name: 'tickUpper', + type: 'int24', + }, + { + internalType: 'uint128', + name: 'amount', + type: 'uint128', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + name: 'mint', + outputs: [ + { + internalType: 'uint256', + name: 'amount0', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount1', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + name: 'observations', + outputs: [ + { + internalType: 'uint32', + name: 'blockTimestamp', + type: 'uint32', + }, + { + internalType: 'int56', + name: 'tickCumulative', + type: 'int56', + }, + { + internalType: 'uint160', + name: 'secondsPerLiquidityCumulativeX128', + type: 'uint160', + }, + { + internalType: 'bool', + name: 'initialized', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint32[]', + name: 'secondsAgos', + type: 'uint32[]', + }, + ], + name: 'observe', + outputs: [ + { + internalType: 'int56[]', + name: 'tickCumulatives', + type: 'int56[]', + }, + { + internalType: 'uint160[]', + name: 'secondsPerLiquidityCumulativeX128s', + type: 'uint160[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + name: 'positions', + outputs: [ + { + internalType: 'uint128', + name: 'liquidity', + type: 'uint128', + }, + { + internalType: 'uint256', + name: 'feeGrowthInside0LastX128', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'feeGrowthInside1LastX128', + type: 'uint256', + }, + { + internalType: 'uint128', + name: 'tokensOwed0', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'tokensOwed1', + type: 'uint128', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'protocolFees', + outputs: [ + { + internalType: 'uint128', + name: 'token0', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'token1', + type: 'uint128', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint8', + name: 'feeProtocol0', + type: 'uint8', + }, + { + internalType: 'uint8', + name: 'feeProtocol1', + type: 'uint8', + }, + ], + name: 'setFeeProtocol', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'slot0', + outputs: [ + { + internalType: 'uint160', + name: 'sqrtPriceX96', + type: 'uint160', + }, + { + internalType: 'int24', + name: 'tick', + type: 'int24', + }, + { + internalType: 'uint16', + name: 'observationIndex', + type: 'uint16', + }, + { + internalType: 'uint16', + name: 'observationCardinality', + type: 'uint16', + }, + { + internalType: 'uint16', + name: 'observationCardinalityNext', + type: 'uint16', + }, + { + internalType: 'uint8', + name: 'feeProtocol', + type: 'uint8', + }, + { + internalType: 'bool', + name: 'unlocked', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'int24', + name: 'tickLower', + type: 'int24', + }, + { + internalType: 'int24', + name: 'tickUpper', + type: 'int24', + }, + ], + name: 'snapshotCumulativesInside', + outputs: [ + { + internalType: 'int56', + name: 'tickCumulativeInside', + type: 'int56', + }, + { + internalType: 'uint160', + name: 'secondsPerLiquidityInsideX128', + type: 'uint160', + }, + { + internalType: 'uint32', + name: 'secondsInside', + type: 'uint32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'bool', + name: 'zeroForOne', + type: 'bool', + }, + { + internalType: 'int256', + name: 'amountSpecified', + type: 'int256', + }, + { + internalType: 'uint160', + name: 'sqrtPriceLimitX96', + type: 'uint160', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + name: 'swap', + outputs: [ + { + internalType: 'int256', + name: 'amount0', + type: 'int256', + }, + { + internalType: 'int256', + name: 'amount1', + type: 'int256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'int16', + name: '', + type: 'int16', + }, + ], + name: 'tickBitmap', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'tickSpacing', + outputs: [ + { + internalType: 'int24', + name: '', + type: 'int24', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'int24', + name: '', + type: 'int24', + }, + ], + name: 'ticks', + outputs: [ + { + internalType: 'uint128', + name: 'liquidityGross', + type: 'uint128', + }, + { + internalType: 'int128', + name: 'liquidityNet', + type: 'int128', + }, + { + internalType: 'uint256', + name: 'feeGrowthOutside0X128', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'feeGrowthOutside1X128', + type: 'uint256', + }, + { + internalType: 'int56', + name: 'tickCumulativeOutside', + type: 'int56', + }, + { + internalType: 'uint160', + name: 'secondsPerLiquidityOutsideX128', + type: 'uint160', + }, + { + internalType: 'uint32', + name: 'secondsOutside', + type: 'uint32', + }, + { + internalType: 'bool', + name: 'initialized', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'token0', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'token1', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, +] as const diff --git a/packages/universal-router-sdk/src/abis/LooksRareV2.ts b/packages/universal-router-sdk/src/abis/LooksRareV2.ts new file mode 100644 index 0000000000000..b1467ddbe41bd --- /dev/null +++ b/packages/universal-router-sdk/src/abis/LooksRareV2.ts @@ -0,0 +1,1924 @@ +export const looksRareV2Abi = [ + { + inputs: [ + { + internalType: 'address', + name: '_owner', + type: 'address', + }, + { + internalType: 'address', + name: '_protocolFeeRecipient', + type: 'address', + }, + { + internalType: 'address', + name: '_transferManager', + type: 'address', + }, + { + internalType: 'address', + name: '_weth', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: {}, + name: 'CallerInvalid', + type: 'error', + }, + { + inputs: {}, + name: 'ChainIdInvalid', + type: 'error', + }, + { + inputs: {}, + name: 'CreatorFeeBpTooHigh', + type: 'error', + }, + { + inputs: {}, + name: 'CurrencyInvalid', + type: 'error', + }, + { + inputs: {}, + name: 'ERC20TransferFromFail', + type: 'error', + }, + { + inputs: {}, + name: 'LengthsInvalid', + type: 'error', + }, + { + inputs: {}, + name: 'MerkleProofInvalid', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'length', + type: 'uint256', + }, + ], + name: 'MerkleProofTooLarge', + type: 'error', + }, + { + inputs: {}, + name: 'NewGasLimitETHTransferTooLow', + type: 'error', + }, + { + inputs: {}, + name: 'NewProtocolFeeRecipientCannotBeNullAddress', + type: 'error', + }, + { + inputs: {}, + name: 'NoOngoingTransferInProgress', + type: 'error', + }, + { + inputs: {}, + name: 'NoSelectorForStrategy', + type: 'error', + }, + { + inputs: {}, + name: 'NoncesInvalid', + type: 'error', + }, + { + inputs: {}, + name: 'NotAContract', + type: 'error', + }, + { + inputs: {}, + name: 'NotAffiliateController', + type: 'error', + }, + { + inputs: {}, + name: 'NotOwner', + type: 'error', + }, + { + inputs: {}, + name: 'NotV2Strategy', + type: 'error', + }, + { + inputs: {}, + name: 'NullSignerAddress', + type: 'error', + }, + { + inputs: {}, + name: 'OutsideOfTimeRange', + type: 'error', + }, + { + inputs: {}, + name: 'PercentageTooHigh', + type: 'error', + }, + { + inputs: {}, + name: 'QuoteTypeInvalid', + type: 'error', + }, + { + inputs: {}, + name: 'ReentrancyFail', + type: 'error', + }, + { + inputs: {}, + name: 'RenouncementNotInProgress', + type: 'error', + }, + { + inputs: {}, + name: 'SameDomainSeparator', + type: 'error', + }, + { + inputs: {}, + name: 'SignatureEOAInvalid', + type: 'error', + }, + { + inputs: {}, + name: 'SignatureERC1271Invalid', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'length', + type: 'uint256', + }, + ], + name: 'SignatureLengthInvalid', + type: 'error', + }, + { + inputs: {}, + name: 'SignatureParameterSInvalid', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + ], + name: 'SignatureParameterVInvalid', + type: 'error', + }, + { + inputs: {}, + name: 'StrategyHasNoSelector', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'strategyId', + type: 'uint256', + }, + ], + name: 'StrategyNotAvailable', + type: 'error', + }, + { + inputs: {}, + name: 'StrategyNotUsed', + type: 'error', + }, + { + inputs: {}, + name: 'StrategyProtocolFeeTooHigh', + type: 'error', + }, + { + inputs: {}, + name: 'TransferAlreadyInProgress', + type: 'error', + }, + { + inputs: {}, + name: 'TransferNotInProgress', + type: 'error', + }, + { + inputs: {}, + name: 'WrongPotentialOwner', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'affiliate', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'currency', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'affiliateFee', + type: 'uint256', + }, + ], + name: 'AffiliatePayment', + type: 'event', + }, + { + anonymous: false, + inputs: {}, + name: 'CancelOwnershipTransfer', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'currency', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'isAllowed', + type: 'bool', + }, + ], + name: 'CurrencyStatusUpdated', + type: 'event', + }, + { + anonymous: false, + inputs: {}, + name: 'InitiateOwnershipRenouncement', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'potentialOwner', + type: 'address', + }, + ], + name: 'InitiateOwnershipTransfer', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'affiliateController', + type: 'address', + }, + ], + name: 'NewAffiliateController', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bool', + name: 'isActive', + type: 'bool', + }, + ], + name: 'NewAffiliateProgramStatus', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'affiliate', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'rate', + type: 'uint256', + }, + ], + name: 'NewAffiliateRate', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'user', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'bidNonce', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'askNonce', + type: 'uint256', + }, + ], + name: 'NewBidAskNonces', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'creatorFeeManager', + type: 'address', + }, + ], + name: 'NewCreatorFeeManager', + type: 'event', + }, + { + anonymous: false, + inputs: {}, + name: 'NewDomainSeparator', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'gasLimitETHTransfer', + type: 'uint256', + }, + ], + name: 'NewGasLimitETHTransfer', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'maxCreatorFeeBp', + type: 'uint256', + }, + ], + name: 'NewMaxCreatorFeeBp', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'NewOwner', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'protocolFeeRecipient', + type: 'address', + }, + ], + name: 'NewProtocolFeeRecipient', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'strategyId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint16', + name: 'standardProtocolFeeBp', + type: 'uint16', + }, + { + indexed: false, + internalType: 'uint16', + name: 'minTotalFeeBp', + type: 'uint16', + }, + { + indexed: false, + internalType: 'uint16', + name: 'maxProtocolFeeBp', + type: 'uint16', + }, + { + indexed: false, + internalType: 'bytes4', + name: 'selector', + type: 'bytes4', + }, + { + indexed: false, + internalType: 'bool', + name: 'isMakerBid', + type: 'bool', + }, + { + indexed: false, + internalType: 'address', + name: 'implementation', + type: 'address', + }, + ], + name: 'NewStrategy', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'user', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256[]', + name: 'orderNonces', + type: 'uint256[]', + }, + ], + name: 'OrderNoncesCancelled', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'strategyId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'bool', + name: 'isActive', + type: 'bool', + }, + { + indexed: false, + internalType: 'uint16', + name: 'standardProtocolFeeBp', + type: 'uint16', + }, + { + indexed: false, + internalType: 'uint16', + name: 'minTotalFeeBp', + type: 'uint16', + }, + ], + name: 'StrategyUpdated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'user', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256[]', + name: 'subsetNonces', + type: 'uint256[]', + }, + ], + name: 'SubsetNoncesCancelled', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'orderHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'orderNonce', + type: 'uint256', + }, + { + internalType: 'bool', + name: 'isNonceInvalidated', + type: 'bool', + }, + ], + indexed: false, + internalType: 'struct ILooksRareProtocol.NonceInvalidationParameters', + name: 'nonceInvalidationParameters', + type: 'tuple', + }, + { + indexed: false, + internalType: 'address', + name: 'askUser', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'bidUser', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'strategyId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'address', + name: 'currency', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'collection', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256[]', + name: 'itemIds', + type: 'uint256[]', + }, + { + indexed: false, + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + { + indexed: false, + internalType: 'address[2]', + name: 'feeRecipients', + type: 'address[2]', + }, + { + indexed: false, + internalType: 'uint256[3]', + name: 'feeAmounts', + type: 'uint256[3]', + }, + ], + name: 'TakerAsk', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + components: [ + { + internalType: 'bytes32', + name: 'orderHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'orderNonce', + type: 'uint256', + }, + { + internalType: 'bool', + name: 'isNonceInvalidated', + type: 'bool', + }, + ], + indexed: false, + internalType: 'struct ILooksRareProtocol.NonceInvalidationParameters', + name: 'nonceInvalidationParameters', + type: 'tuple', + }, + { + indexed: false, + internalType: 'address', + name: 'bidUser', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'bidRecipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'strategyId', + type: 'uint256', + }, + { + indexed: false, + internalType: 'address', + name: 'currency', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'collection', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256[]', + name: 'itemIds', + type: 'uint256[]', + }, + { + indexed: false, + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + { + indexed: false, + internalType: 'address[2]', + name: 'feeRecipients', + type: 'address[2]', + }, + { + indexed: false, + internalType: 'uint256[3]', + name: 'feeAmounts', + type: 'uint256[3]', + }, + ], + name: 'TakerBid', + type: 'event', + }, + { + inputs: {}, + name: 'MAGIC_VALUE_ORDER_NONCE_EXECUTED', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'WETH', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint16', + name: 'standardProtocolFeeBp', + type: 'uint16', + }, + { + internalType: 'uint16', + name: 'minTotalFeeBp', + type: 'uint16', + }, + { + internalType: 'uint16', + name: 'maxProtocolFeeBp', + type: 'uint16', + }, + { + internalType: 'bytes4', + name: 'selector', + type: 'bytes4', + }, + { + internalType: 'bool', + name: 'isMakerBid', + type: 'bool', + }, + { + internalType: 'address', + name: 'implementation', + type: 'address', + }, + ], + name: 'addStrategy', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'affiliateController', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'affiliateRates', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256[]', + name: 'orderNonces', + type: 'uint256[]', + }, + ], + name: 'cancelOrderNonces', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'cancelOwnershipTransfer', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256[]', + name: 'subsetNonces', + type: 'uint256[]', + }, + ], + name: 'cancelSubsetNonces', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'chainId', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'confirmOwnershipRenouncement', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'confirmOwnershipTransfer', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'creatorFeeManager', + outputs: [ + { + internalType: 'contract ICreatorFeeManager', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'domainSeparator', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'bytes', + name: 'additionalParameters', + type: 'bytes', + }, + ], + internalType: 'struct OrderStructs.Taker[]', + name: 'takerBids', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'enum QuoteType', + name: 'quoteType', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'globalNonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'subsetNonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'orderNonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'strategyId', + type: 'uint256', + }, + { + internalType: 'enum CollectionType', + name: 'collectionType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'collection', + type: 'address', + }, + { + internalType: 'address', + name: 'currency', + type: 'address', + }, + { + internalType: 'address', + name: 'signer', + type: 'address', + }, + { + internalType: 'uint256', + name: 'startTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'price', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'itemIds', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: 'additionalParameters', + type: 'bytes', + }, + ], + internalType: 'struct OrderStructs.Maker[]', + name: 'makerAsks', + type: 'tuple[]', + }, + { + internalType: 'bytes[]', + name: 'makerSignatures', + type: 'bytes[]', + }, + { + components: [ + { + internalType: 'bytes32', + name: 'root', + type: 'bytes32', + }, + { + components: [ + { + internalType: 'bytes32', + name: 'value', + type: 'bytes32', + }, + { + internalType: 'enum OrderStructs.MerkleTreeNodePosition', + name: 'position', + type: 'uint8', + }, + ], + internalType: 'struct OrderStructs.MerkleTreeNode[]', + name: 'proof', + type: 'tuple[]', + }, + ], + internalType: 'struct OrderStructs.MerkleTree[]', + name: 'merkleTrees', + type: 'tuple[]', + }, + { + internalType: 'address', + name: 'affiliate', + type: 'address', + }, + { + internalType: 'bool', + name: 'isAtomic', + type: 'bool', + }, + ], + name: 'executeMultipleTakerBids', + outputs: {}, + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'bytes', + name: 'additionalParameters', + type: 'bytes', + }, + ], + internalType: 'struct OrderStructs.Taker', + name: 'takerAsk', + type: 'tuple', + }, + { + components: [ + { + internalType: 'enum QuoteType', + name: 'quoteType', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'globalNonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'subsetNonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'orderNonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'strategyId', + type: 'uint256', + }, + { + internalType: 'enum CollectionType', + name: 'collectionType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'collection', + type: 'address', + }, + { + internalType: 'address', + name: 'currency', + type: 'address', + }, + { + internalType: 'address', + name: 'signer', + type: 'address', + }, + { + internalType: 'uint256', + name: 'startTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'price', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'itemIds', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: 'additionalParameters', + type: 'bytes', + }, + ], + internalType: 'struct OrderStructs.Maker', + name: 'makerBid', + type: 'tuple', + }, + { + internalType: 'bytes', + name: 'makerSignature', + type: 'bytes', + }, + { + components: [ + { + internalType: 'bytes32', + name: 'root', + type: 'bytes32', + }, + { + components: [ + { + internalType: 'bytes32', + name: 'value', + type: 'bytes32', + }, + { + internalType: 'enum OrderStructs.MerkleTreeNodePosition', + name: 'position', + type: 'uint8', + }, + ], + internalType: 'struct OrderStructs.MerkleTreeNode[]', + name: 'proof', + type: 'tuple[]', + }, + ], + internalType: 'struct OrderStructs.MerkleTree', + name: 'merkleTree', + type: 'tuple', + }, + { + internalType: 'address', + name: 'affiliate', + type: 'address', + }, + ], + name: 'executeTakerAsk', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'bytes', + name: 'additionalParameters', + type: 'bytes', + }, + ], + internalType: 'struct OrderStructs.Taker', + name: 'takerBid', + type: 'tuple', + }, + { + components: [ + { + internalType: 'enum QuoteType', + name: 'quoteType', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'globalNonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'subsetNonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'orderNonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'strategyId', + type: 'uint256', + }, + { + internalType: 'enum CollectionType', + name: 'collectionType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'collection', + type: 'address', + }, + { + internalType: 'address', + name: 'currency', + type: 'address', + }, + { + internalType: 'address', + name: 'signer', + type: 'address', + }, + { + internalType: 'uint256', + name: 'startTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'price', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'itemIds', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: 'additionalParameters', + type: 'bytes', + }, + ], + internalType: 'struct OrderStructs.Maker', + name: 'makerAsk', + type: 'tuple', + }, + { + internalType: 'bytes', + name: 'makerSignature', + type: 'bytes', + }, + { + components: [ + { + internalType: 'bytes32', + name: 'root', + type: 'bytes32', + }, + { + components: [ + { + internalType: 'bytes32', + name: 'value', + type: 'bytes32', + }, + { + internalType: 'enum OrderStructs.MerkleTreeNodePosition', + name: 'position', + type: 'uint8', + }, + ], + internalType: 'struct OrderStructs.MerkleTreeNode[]', + name: 'proof', + type: 'tuple[]', + }, + ], + internalType: 'struct OrderStructs.MerkleTree', + name: 'merkleTree', + type: 'tuple', + }, + { + internalType: 'address', + name: 'affiliate', + type: 'address', + }, + ], + name: 'executeTakerBid', + outputs: {}, + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'root', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'proofLength', + type: 'uint256', + }, + ], + name: 'hashBatchOrder', + outputs: [ + { + internalType: 'bytes32', + name: 'batchOrderHash', + type: 'bytes32', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bool', + name: 'bid', + type: 'bool', + }, + { + internalType: 'bool', + name: 'ask', + type: 'bool', + }, + ], + name: 'incrementBidAskNonces', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'initiateOwnershipRenouncement', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'newPotentialOwner', + type: 'address', + }, + ], + name: 'initiateOwnershipTransfer', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'isAffiliateProgramActive', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'isCurrencyAllowed', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'maxCreatorFeeBp', + outputs: [ + { + internalType: 'uint16', + name: '', + type: 'uint16', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'ownershipStatus', + outputs: [ + { + internalType: 'enum IOwnableTwoSteps.Status', + name: '', + type: 'uint8', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'potentialOwner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'protocolFeeRecipient', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'bytes', + name: 'additionalParameters', + type: 'bytes', + }, + ], + internalType: 'struct OrderStructs.Taker', + name: 'takerBid', + type: 'tuple', + }, + { + components: [ + { + internalType: 'enum QuoteType', + name: 'quoteType', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'globalNonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'subsetNonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'orderNonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'strategyId', + type: 'uint256', + }, + { + internalType: 'enum CollectionType', + name: 'collectionType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'collection', + type: 'address', + }, + { + internalType: 'address', + name: 'currency', + type: 'address', + }, + { + internalType: 'address', + name: 'signer', + type: 'address', + }, + { + internalType: 'uint256', + name: 'startTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'price', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'itemIds', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: 'additionalParameters', + type: 'bytes', + }, + ], + internalType: 'struct OrderStructs.Maker', + name: 'makerAsk', + type: 'tuple', + }, + { + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'orderHash', + type: 'bytes32', + }, + ], + name: 'restrictedExecuteTakerBid', + outputs: [ + { + internalType: 'uint256', + name: 'protocolFeeAmount', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + name: 'strategyInfo', + outputs: [ + { + internalType: 'bool', + name: 'isActive', + type: 'bool', + }, + { + internalType: 'uint16', + name: 'standardProtocolFeeBp', + type: 'uint16', + }, + { + internalType: 'uint16', + name: 'minTotalFeeBp', + type: 'uint16', + }, + { + internalType: 'uint16', + name: 'maxProtocolFeeBp', + type: 'uint16', + }, + { + internalType: 'bytes4', + name: 'selector', + type: 'bytes4', + }, + { + internalType: 'bool', + name: 'isMakerBid', + type: 'bool', + }, + { + internalType: 'address', + name: 'implementation', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'transferManager', + outputs: [ + { + internalType: 'contract TransferManager', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'newAffiliateController', + type: 'address', + }, + ], + name: 'updateAffiliateController', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bool', + name: 'isActive', + type: 'bool', + }, + ], + name: 'updateAffiliateProgramStatus', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'affiliate', + type: 'address', + }, + { + internalType: 'uint256', + name: 'bp', + type: 'uint256', + }, + ], + name: 'updateAffiliateRate', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'newCreatorFeeManager', + type: 'address', + }, + ], + name: 'updateCreatorFeeManager', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'currency', + type: 'address', + }, + { + internalType: 'bool', + name: 'isAllowed', + type: 'bool', + }, + ], + name: 'updateCurrencyStatus', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'updateDomainSeparator', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'newGasLimitETHTransfer', + type: 'uint256', + }, + ], + name: 'updateETHGasLimitForTransfer', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint16', + name: 'newMaxCreatorFeeBp', + type: 'uint16', + }, + ], + name: 'updateMaxCreatorFeeBp', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'newProtocolFeeRecipient', + type: 'address', + }, + ], + name: 'updateProtocolFeeRecipient', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'strategyId', + type: 'uint256', + }, + { + internalType: 'bool', + name: 'isActive', + type: 'bool', + }, + { + internalType: 'uint16', + name: 'newStandardProtocolFee', + type: 'uint16', + }, + { + internalType: 'uint16', + name: 'newMinTotalFee', + type: 'uint16', + }, + ], + name: 'updateStrategy', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'userBidAskNonces', + outputs: [ + { + internalType: 'uint256', + name: 'bidNonce', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'askNonce', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + name: 'userOrderNonce', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + name: 'userSubsetNonce', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, +] as const diff --git a/packages/universal-router-sdk/src/abis/NFT20.ts b/packages/universal-router-sdk/src/abis/NFT20.ts new file mode 100644 index 0000000000000..e01f83463abd2 --- /dev/null +++ b/packages/universal-router-sdk/src/abis/NFT20.ts @@ -0,0 +1,242 @@ +export const nft20Abi = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + inputs: {}, + name: 'ETH', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'NFT20', + outputs: [ + { + internalType: 'contract INFT20Factory', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'UNIV2', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'UNIV3', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'WETH', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_nft', + type: 'address', + }, + { + internalType: 'uint256[]', + name: '_toIds', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: '_toAmounts', + type: 'uint256[]', + }, + { + internalType: 'address', + name: '_receipient', + type: 'address', + }, + { + internalType: 'uint24', + name: '_fee', + type: 'uint24', + }, + { + internalType: 'bool', + name: 'isV3', + type: 'bool', + }, + ], + name: 'ethForNft', + outputs: {}, + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_nft', + type: 'address', + }, + { + internalType: 'uint256[]', + name: '_ids', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: '_amounts', + type: 'uint256[]', + }, + { + internalType: 'bool', + name: 'isErc721', + type: 'bool', + }, + { + internalType: 'uint24', + name: '_fee', + type: 'uint24', + }, + { + internalType: 'bool', + name: 'isV3', + type: 'bool', + }, + ], + name: 'nftForEth', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'tokenAddress', + type: 'address', + }, + { + internalType: 'uint256', + name: 'tokenAmount', + type: 'uint256', + }, + { + internalType: 'address', + name: 'sendTo', + type: 'address', + }, + ], + name: 'recoverERC20', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'renounceOwnership', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_registry', + type: 'address', + }, + ], + name: 'setNFT20', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'transferOwnership', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'withdrawEth', + outputs: {}, + stateMutability: 'payable', + type: 'function', + }, + { + stateMutability: 'payable', + type: 'receive', + }, +] as const diff --git a/packages/universal-router-sdk/src/abis/NFTXZap.ts b/packages/universal-router-sdk/src/abis/NFTXZap.ts new file mode 100644 index 0000000000000..53a54456c1c38 --- /dev/null +++ b/packages/universal-router-sdk/src/abis/NFTXZap.ts @@ -0,0 +1,603 @@ +export const nftxZapAbi = [ + { + inputs: [ + { + internalType: 'address', + name: '_nftxFactory', + type: 'address', + }, + { + internalType: 'address', + name: '_WETH', + type: 'address', + }, + { + internalType: 'address payable', + name: '_swapTarget', + type: 'address', + }, + { + internalType: 'uint256', + name: '_dustThreshold', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'count', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'ethSpent', + type: 'uint256', + }, + { + indexed: false, + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'Buy', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'ethAmount', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'vTokenAmount', + type: 'uint256', + }, + { + indexed: false, + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'DustReturned', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'count', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'ethReceived', + type: 'uint256', + }, + { + indexed: false, + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'Sell', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'count', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'ethSpent', + type: 'uint256', + }, + { + indexed: false, + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'Swap', + type: 'event', + }, + { + inputs: {}, + name: 'WETH', + outputs: [ + { + internalType: 'contract IWETH', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'vaultId', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'specificIds', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: 'swapCallData', + type: 'bytes', + }, + { + internalType: 'address payable', + name: 'to', + type: 'address', + }, + ], + name: 'buyAndRedeem', + outputs: {}, + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'vaultId', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'idsIn', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'specificIds', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: 'swapCallData', + type: 'bytes', + }, + { + internalType: 'address payable', + name: 'to', + type: 'address', + }, + ], + name: 'buyAndSwap1155', + outputs: {}, + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'vaultId', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'idsIn', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'specificIds', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: 'swapCallData', + type: 'bytes', + }, + { + internalType: 'address payable', + name: 'to', + type: 'address', + }, + ], + name: 'buyAndSwap721', + outputs: {}, + stateMutability: 'payable', + type: 'function', + }, + { + inputs: {}, + name: 'dustThreshold', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'feeDistributor', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'vaultId', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'ids', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: 'swapCallData', + type: 'bytes', + }, + { + internalType: 'address payable', + name: 'to', + type: 'address', + }, + ], + name: 'mintAndSell1155', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'vaultId', + type: 'uint256', + }, + { + internalType: 'uint256[]', + name: 'ids', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: 'swapCallData', + type: 'bytes', + }, + { + internalType: 'address payable', + name: 'to', + type: 'address', + }, + ], + name: 'mintAndSell721', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'nftxFactory', + outputs: [ + { + internalType: 'contract INFTXVaultFactory', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + name: 'nftxVaultAddresses', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: '', + type: 'bytes', + }, + ], + name: 'onERC1155BatchReceived', + outputs: [ + { + internalType: 'bytes4', + name: '', + type: 'bytes4', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '', + type: 'bytes', + }, + ], + name: 'onERC1155Received', + outputs: [ + { + internalType: 'bytes4', + name: '', + type: 'bytes4', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '', + type: 'bytes', + }, + ], + name: 'onERC721Received', + outputs: [ + { + internalType: 'bytes4', + name: '', + type: 'bytes4', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bool', + name: '_paused', + type: 'bool', + }, + ], + name: 'pause', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'paused', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'renounceOwnership', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + ], + name: 'rescue', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '_dustThreshold', + type: 'uint256', + }, + ], + name: 'setDustThreshold', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: 'interfaceId', + type: 'bytes4', + }, + ], + name: 'supportsInterface', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'transferOwnership', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + stateMutability: 'payable', + type: 'receive', + }, +] as const diff --git a/packages/universal-router-sdk/src/abis/Seaport.ts b/packages/universal-router-sdk/src/abis/Seaport.ts new file mode 100644 index 0000000000000..0a00461ec5a33 --- /dev/null +++ b/packages/universal-router-sdk/src/abis/Seaport.ts @@ -0,0 +1,2595 @@ +export const seaportAbi = [ + { + inputs: [ + { + internalType: 'address', + name: 'conduitController', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: {}, + name: 'BadContractSignature', + type: 'error', + }, + { + inputs: {}, + name: 'BadFraction', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'BadReturnValueFromERC20OnTransfer', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + ], + name: 'BadSignatureV', + type: 'error', + }, + { + inputs: {}, + name: 'ConsiderationCriteriaResolverOutOfRange', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'orderIndex', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'considerationIndex', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'shortfallAmount', + type: 'uint256', + }, + ], + name: 'ConsiderationNotMet', + type: 'error', + }, + { + inputs: {}, + name: 'CriteriaNotEnabledForItem', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'identifiers', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + ], + name: 'ERC1155BatchTransferGenericFailure', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'EtherTransferGenericFailure', + type: 'error', + }, + { + inputs: {}, + name: 'InexactFraction', + type: 'error', + }, + { + inputs: {}, + name: 'InsufficientEtherSupplied', + type: 'error', + }, + { + inputs: {}, + name: 'Invalid1155BatchTransferEncoding', + type: 'error', + }, + { + inputs: {}, + name: 'InvalidBasicOrderParameterEncoding', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'conduit', + type: 'address', + }, + ], + name: 'InvalidCallToConduit', + type: 'error', + }, + { + inputs: {}, + name: 'InvalidCanceller', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'conduitKey', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'conduit', + type: 'address', + }, + ], + name: 'InvalidConduit', + type: 'error', + }, + { + inputs: {}, + name: 'InvalidERC721TransferAmount', + type: 'error', + }, + { + inputs: {}, + name: 'InvalidFulfillmentComponentData', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'InvalidMsgValue', + type: 'error', + }, + { + inputs: {}, + name: 'InvalidNativeOfferItem', + type: 'error', + }, + { + inputs: {}, + name: 'InvalidProof', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'orderHash', + type: 'bytes32', + }, + ], + name: 'InvalidRestrictedOrder', + type: 'error', + }, + { + inputs: {}, + name: 'InvalidSignature', + type: 'error', + }, + { + inputs: {}, + name: 'InvalidSigner', + type: 'error', + }, + { + inputs: {}, + name: 'InvalidTime', + type: 'error', + }, + { + inputs: {}, + name: 'MismatchedFulfillmentOfferAndConsiderationComponents', + type: 'error', + }, + { + inputs: [ + { + internalType: 'enum Side', + name: 'side', + type: 'uint8', + }, + ], + name: 'MissingFulfillmentComponentOnAggregation', + type: 'error', + }, + { + inputs: {}, + name: 'MissingItemAmount', + type: 'error', + }, + { + inputs: {}, + name: 'MissingOriginalConsiderationItems', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address', + }, + ], + name: 'NoContract', + type: 'error', + }, + { + inputs: {}, + name: 'NoReentrantCalls', + type: 'error', + }, + { + inputs: {}, + name: 'NoSpecifiedOrdersAvailable', + type: 'error', + }, + { + inputs: {}, + name: 'OfferAndConsiderationRequiredOnFulfillment', + type: 'error', + }, + { + inputs: {}, + name: 'OfferCriteriaResolverOutOfRange', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'orderHash', + type: 'bytes32', + }, + ], + name: 'OrderAlreadyFilled', + type: 'error', + }, + { + inputs: {}, + name: 'OrderCriteriaResolverOutOfRange', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'orderHash', + type: 'bytes32', + }, + ], + name: 'OrderIsCancelled', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'orderHash', + type: 'bytes32', + }, + ], + name: 'OrderPartiallyFilled', + type: 'error', + }, + { + inputs: {}, + name: 'PartialFillsNotEnabledForOrder', + type: 'error', + }, + { + inputs: [ + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifier', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'TokenTransferGenericFailure', + type: 'error', + }, + { + inputs: {}, + name: 'UnresolvedConsiderationCriteria', + type: 'error', + }, + { + inputs: {}, + name: 'UnresolvedOfferCriteria', + type: 'error', + }, + { + inputs: {}, + name: 'UnusedItemParameters', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'newCounter', + type: 'uint256', + }, + { + indexed: true, + internalType: 'address', + name: 'offerer', + type: 'address', + }, + ], + name: 'CounterIncremented', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bytes32', + name: 'orderHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'offerer', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'zone', + type: 'address', + }, + ], + name: 'OrderCancelled', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bytes32', + name: 'orderHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'offerer', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'zone', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifier', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + indexed: false, + internalType: 'struct SpentItem[]', + name: 'offer', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifier', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'recipient', + type: 'address', + }, + ], + indexed: false, + internalType: 'struct ReceivedItem[]', + name: 'consideration', + type: 'tuple[]', + }, + ], + name: 'OrderFulfilled', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bytes32', + name: 'orderHash', + type: 'bytes32', + }, + { + indexed: true, + internalType: 'address', + name: 'offerer', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'zone', + type: 'address', + }, + ], + name: 'OrderValidated', + type: 'event', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'offerer', + type: 'address', + }, + { + internalType: 'address', + name: 'zone', + type: 'address', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + ], + internalType: 'struct OfferItem[]', + name: 'offer', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'recipient', + type: 'address', + }, + ], + internalType: 'struct ConsiderationItem[]', + name: 'consideration', + type: 'tuple[]', + }, + { + internalType: 'enum OrderType', + name: 'orderType', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'startTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'zoneHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'salt', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'conduitKey', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'counter', + type: 'uint256', + }, + ], + internalType: 'struct OrderComponents[]', + name: 'orders', + type: 'tuple[]', + }, + ], + name: 'cancel', + outputs: [ + { + internalType: 'bool', + name: 'cancelled', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'offerer', + type: 'address', + }, + { + internalType: 'address', + name: 'zone', + type: 'address', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + ], + internalType: 'struct OfferItem[]', + name: 'offer', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'recipient', + type: 'address', + }, + ], + internalType: 'struct ConsiderationItem[]', + name: 'consideration', + type: 'tuple[]', + }, + { + internalType: 'enum OrderType', + name: 'orderType', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'startTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'zoneHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'salt', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'conduitKey', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'totalOriginalConsiderationItems', + type: 'uint256', + }, + ], + internalType: 'struct OrderParameters', + name: 'parameters', + type: 'tuple', + }, + { + internalType: 'uint120', + name: 'numerator', + type: 'uint120', + }, + { + internalType: 'uint120', + name: 'denominator', + type: 'uint120', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'extraData', + type: 'bytes', + }, + ], + internalType: 'struct AdvancedOrder', + name: 'advancedOrder', + type: 'tuple', + }, + { + components: [ + { + internalType: 'uint256', + name: 'orderIndex', + type: 'uint256', + }, + { + internalType: 'enum Side', + name: 'side', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'index', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'identifier', + type: 'uint256', + }, + { + internalType: 'bytes32[]', + name: 'criteriaProof', + type: 'bytes32[]', + }, + ], + internalType: 'struct CriteriaResolver[]', + name: 'criteriaResolvers', + type: 'tuple[]', + }, + { + internalType: 'bytes32', + name: 'fulfillerConduitKey', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + ], + name: 'fulfillAdvancedOrder', + outputs: [ + { + internalType: 'bool', + name: 'fulfilled', + type: 'bool', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'offerer', + type: 'address', + }, + { + internalType: 'address', + name: 'zone', + type: 'address', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + ], + internalType: 'struct OfferItem[]', + name: 'offer', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'recipient', + type: 'address', + }, + ], + internalType: 'struct ConsiderationItem[]', + name: 'consideration', + type: 'tuple[]', + }, + { + internalType: 'enum OrderType', + name: 'orderType', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'startTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'zoneHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'salt', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'conduitKey', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'totalOriginalConsiderationItems', + type: 'uint256', + }, + ], + internalType: 'struct OrderParameters', + name: 'parameters', + type: 'tuple', + }, + { + internalType: 'uint120', + name: 'numerator', + type: 'uint120', + }, + { + internalType: 'uint120', + name: 'denominator', + type: 'uint120', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'extraData', + type: 'bytes', + }, + ], + internalType: 'struct AdvancedOrder[]', + name: 'advancedOrders', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'uint256', + name: 'orderIndex', + type: 'uint256', + }, + { + internalType: 'enum Side', + name: 'side', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'index', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'identifier', + type: 'uint256', + }, + { + internalType: 'bytes32[]', + name: 'criteriaProof', + type: 'bytes32[]', + }, + ], + internalType: 'struct CriteriaResolver[]', + name: 'criteriaResolvers', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'uint256', + name: 'orderIndex', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'itemIndex', + type: 'uint256', + }, + ], + internalType: 'struct FulfillmentComponent[][]', + name: 'offerFulfillments', + type: 'tuple[][]', + }, + { + components: [ + { + internalType: 'uint256', + name: 'orderIndex', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'itemIndex', + type: 'uint256', + }, + ], + internalType: 'struct FulfillmentComponent[][]', + name: 'considerationFulfillments', + type: 'tuple[][]', + }, + { + internalType: 'bytes32', + name: 'fulfillerConduitKey', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'maximumFulfilled', + type: 'uint256', + }, + ], + name: 'fulfillAvailableAdvancedOrders', + outputs: [ + { + internalType: 'bool[]', + name: 'availableOrders', + type: 'bool[]', + }, + { + components: [ + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifier', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'recipient', + type: 'address', + }, + ], + internalType: 'struct ReceivedItem', + name: 'item', + type: 'tuple', + }, + { + internalType: 'address', + name: 'offerer', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'conduitKey', + type: 'bytes32', + }, + ], + internalType: 'struct Execution[]', + name: 'executions', + type: 'tuple[]', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'offerer', + type: 'address', + }, + { + internalType: 'address', + name: 'zone', + type: 'address', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + ], + internalType: 'struct OfferItem[]', + name: 'offer', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'recipient', + type: 'address', + }, + ], + internalType: 'struct ConsiderationItem[]', + name: 'consideration', + type: 'tuple[]', + }, + { + internalType: 'enum OrderType', + name: 'orderType', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'startTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'zoneHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'salt', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'conduitKey', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'totalOriginalConsiderationItems', + type: 'uint256', + }, + ], + internalType: 'struct OrderParameters', + name: 'parameters', + type: 'tuple', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + ], + internalType: 'struct Order[]', + name: 'orders', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'uint256', + name: 'orderIndex', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'itemIndex', + type: 'uint256', + }, + ], + internalType: 'struct FulfillmentComponent[][]', + name: 'offerFulfillments', + type: 'tuple[][]', + }, + { + components: [ + { + internalType: 'uint256', + name: 'orderIndex', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'itemIndex', + type: 'uint256', + }, + ], + internalType: 'struct FulfillmentComponent[][]', + name: 'considerationFulfillments', + type: 'tuple[][]', + }, + { + internalType: 'bytes32', + name: 'fulfillerConduitKey', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'maximumFulfilled', + type: 'uint256', + }, + ], + name: 'fulfillAvailableOrders', + outputs: [ + { + internalType: 'bool[]', + name: 'availableOrders', + type: 'bool[]', + }, + { + components: [ + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifier', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'recipient', + type: 'address', + }, + ], + internalType: 'struct ReceivedItem', + name: 'item', + type: 'tuple', + }, + { + internalType: 'address', + name: 'offerer', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'conduitKey', + type: 'bytes32', + }, + ], + internalType: 'struct Execution[]', + name: 'executions', + type: 'tuple[]', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'considerationToken', + type: 'address', + }, + { + internalType: 'uint256', + name: 'considerationIdentifier', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'considerationAmount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'offerer', + type: 'address', + }, + { + internalType: 'address', + name: 'zone', + type: 'address', + }, + { + internalType: 'address', + name: 'offerToken', + type: 'address', + }, + { + internalType: 'uint256', + name: 'offerIdentifier', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'offerAmount', + type: 'uint256', + }, + { + internalType: 'enum BasicOrderType', + name: 'basicOrderType', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'startTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'zoneHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'salt', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'offererConduitKey', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'fulfillerConduitKey', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'totalOriginalAdditionalRecipients', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'recipient', + type: 'address', + }, + ], + internalType: 'struct AdditionalRecipient[]', + name: 'additionalRecipients', + type: 'tuple[]', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + ], + internalType: 'struct BasicOrderParameters', + name: 'parameters', + type: 'tuple', + }, + ], + name: 'fulfillBasicOrder', + outputs: [ + { + internalType: 'bool', + name: 'fulfilled', + type: 'bool', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'offerer', + type: 'address', + }, + { + internalType: 'address', + name: 'zone', + type: 'address', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + ], + internalType: 'struct OfferItem[]', + name: 'offer', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'recipient', + type: 'address', + }, + ], + internalType: 'struct ConsiderationItem[]', + name: 'consideration', + type: 'tuple[]', + }, + { + internalType: 'enum OrderType', + name: 'orderType', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'startTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'zoneHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'salt', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'conduitKey', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'totalOriginalConsiderationItems', + type: 'uint256', + }, + ], + internalType: 'struct OrderParameters', + name: 'parameters', + type: 'tuple', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + ], + internalType: 'struct Order', + name: 'order', + type: 'tuple', + }, + { + internalType: 'bytes32', + name: 'fulfillerConduitKey', + type: 'bytes32', + }, + ], + name: 'fulfillOrder', + outputs: [ + { + internalType: 'bool', + name: 'fulfilled', + type: 'bool', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'offerer', + type: 'address', + }, + ], + name: 'getCounter', + outputs: [ + { + internalType: 'uint256', + name: 'counter', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'offerer', + type: 'address', + }, + { + internalType: 'address', + name: 'zone', + type: 'address', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + ], + internalType: 'struct OfferItem[]', + name: 'offer', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'recipient', + type: 'address', + }, + ], + internalType: 'struct ConsiderationItem[]', + name: 'consideration', + type: 'tuple[]', + }, + { + internalType: 'enum OrderType', + name: 'orderType', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'startTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'zoneHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'salt', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'conduitKey', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'counter', + type: 'uint256', + }, + ], + internalType: 'struct OrderComponents', + name: 'order', + type: 'tuple', + }, + ], + name: 'getOrderHash', + outputs: [ + { + internalType: 'bytes32', + name: 'orderHash', + type: 'bytes32', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: 'orderHash', + type: 'bytes32', + }, + ], + name: 'getOrderStatus', + outputs: [ + { + internalType: 'bool', + name: 'isValidated', + type: 'bool', + }, + { + internalType: 'bool', + name: 'isCancelled', + type: 'bool', + }, + { + internalType: 'uint256', + name: 'totalFilled', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'totalSize', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'incrementCounter', + outputs: [ + { + internalType: 'uint256', + name: 'newCounter', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'information', + outputs: [ + { + internalType: 'string', + name: 'version', + type: 'string', + }, + { + internalType: 'bytes32', + name: 'domainSeparator', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'conduitController', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'offerer', + type: 'address', + }, + { + internalType: 'address', + name: 'zone', + type: 'address', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + ], + internalType: 'struct OfferItem[]', + name: 'offer', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'recipient', + type: 'address', + }, + ], + internalType: 'struct ConsiderationItem[]', + name: 'consideration', + type: 'tuple[]', + }, + { + internalType: 'enum OrderType', + name: 'orderType', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'startTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'zoneHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'salt', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'conduitKey', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'totalOriginalConsiderationItems', + type: 'uint256', + }, + ], + internalType: 'struct OrderParameters', + name: 'parameters', + type: 'tuple', + }, + { + internalType: 'uint120', + name: 'numerator', + type: 'uint120', + }, + { + internalType: 'uint120', + name: 'denominator', + type: 'uint120', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'extraData', + type: 'bytes', + }, + ], + internalType: 'struct AdvancedOrder[]', + name: 'advancedOrders', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'uint256', + name: 'orderIndex', + type: 'uint256', + }, + { + internalType: 'enum Side', + name: 'side', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'index', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'identifier', + type: 'uint256', + }, + { + internalType: 'bytes32[]', + name: 'criteriaProof', + type: 'bytes32[]', + }, + ], + internalType: 'struct CriteriaResolver[]', + name: 'criteriaResolvers', + type: 'tuple[]', + }, + { + components: [ + { + components: [ + { + internalType: 'uint256', + name: 'orderIndex', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'itemIndex', + type: 'uint256', + }, + ], + internalType: 'struct FulfillmentComponent[]', + name: 'offerComponents', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'uint256', + name: 'orderIndex', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'itemIndex', + type: 'uint256', + }, + ], + internalType: 'struct FulfillmentComponent[]', + name: 'considerationComponents', + type: 'tuple[]', + }, + ], + internalType: 'struct Fulfillment[]', + name: 'fulfillments', + type: 'tuple[]', + }, + ], + name: 'matchAdvancedOrders', + outputs: [ + { + components: [ + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifier', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'recipient', + type: 'address', + }, + ], + internalType: 'struct ReceivedItem', + name: 'item', + type: 'tuple', + }, + { + internalType: 'address', + name: 'offerer', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'conduitKey', + type: 'bytes32', + }, + ], + internalType: 'struct Execution[]', + name: 'executions', + type: 'tuple[]', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'offerer', + type: 'address', + }, + { + internalType: 'address', + name: 'zone', + type: 'address', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + ], + internalType: 'struct OfferItem[]', + name: 'offer', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'recipient', + type: 'address', + }, + ], + internalType: 'struct ConsiderationItem[]', + name: 'consideration', + type: 'tuple[]', + }, + { + internalType: 'enum OrderType', + name: 'orderType', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'startTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'zoneHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'salt', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'conduitKey', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'totalOriginalConsiderationItems', + type: 'uint256', + }, + ], + internalType: 'struct OrderParameters', + name: 'parameters', + type: 'tuple', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + ], + internalType: 'struct Order[]', + name: 'orders', + type: 'tuple[]', + }, + { + components: [ + { + components: [ + { + internalType: 'uint256', + name: 'orderIndex', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'itemIndex', + type: 'uint256', + }, + ], + internalType: 'struct FulfillmentComponent[]', + name: 'offerComponents', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'uint256', + name: 'orderIndex', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'itemIndex', + type: 'uint256', + }, + ], + internalType: 'struct FulfillmentComponent[]', + name: 'considerationComponents', + type: 'tuple[]', + }, + ], + internalType: 'struct Fulfillment[]', + name: 'fulfillments', + type: 'tuple[]', + }, + ], + name: 'matchOrders', + outputs: [ + { + components: [ + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifier', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'recipient', + type: 'address', + }, + ], + internalType: 'struct ReceivedItem', + name: 'item', + type: 'tuple', + }, + { + internalType: 'address', + name: 'offerer', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'conduitKey', + type: 'bytes32', + }, + ], + internalType: 'struct Execution[]', + name: 'executions', + type: 'tuple[]', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: {}, + name: 'name', + outputs: [ + { + internalType: 'string', + name: 'contractName', + type: 'string', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'address', + name: 'offerer', + type: 'address', + }, + { + internalType: 'address', + name: 'zone', + type: 'address', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + ], + internalType: 'struct OfferItem[]', + name: 'offer', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'enum ItemType', + name: 'itemType', + type: 'uint8', + }, + { + internalType: 'address', + name: 'token', + type: 'address', + }, + { + internalType: 'uint256', + name: 'identifierOrCriteria', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'startAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAmount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'recipient', + type: 'address', + }, + ], + internalType: 'struct ConsiderationItem[]', + name: 'consideration', + type: 'tuple[]', + }, + { + internalType: 'enum OrderType', + name: 'orderType', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'startTime', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endTime', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'zoneHash', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'salt', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'conduitKey', + type: 'bytes32', + }, + { + internalType: 'uint256', + name: 'totalOriginalConsiderationItems', + type: 'uint256', + }, + ], + internalType: 'struct OrderParameters', + name: 'parameters', + type: 'tuple', + }, + { + internalType: 'bytes', + name: 'signature', + type: 'bytes', + }, + ], + internalType: 'struct Order[]', + name: 'orders', + type: 'tuple[]', + }, + ], + name: 'validate', + outputs: [ + { + internalType: 'bool', + name: 'validated', + type: 'bool', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const diff --git a/packages/universal-router-sdk/src/abis/Sudoswap.ts b/packages/universal-router-sdk/src/abis/Sudoswap.ts new file mode 100644 index 0000000000000..367425eea4ee0 --- /dev/null +++ b/packages/universal-router-sdk/src/abis/Sudoswap.ts @@ -0,0 +1,1109 @@ +export const sudoSwapAbi = [ + { + inputs: [ + { + internalType: 'contract ILSSVMPairFactoryLike', + name: '_factory', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: {}, + name: 'factory', + outputs: [ + { + internalType: 'contract ILSSVMPairFactoryLike', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract ERC20', + name: 'token', + type: 'address', + }, + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'enum ILSSVMPairFactoryLike.PairVariant', + name: 'variant', + type: 'uint8', + }, + ], + name: 'pairTransferERC20From', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'contract IERC721', + name: 'nft', + type: 'address', + }, + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + { + internalType: 'enum ILSSVMPairFactoryLike.PairVariant', + name: 'variant', + type: 'uint8', + }, + ], + name: 'pairTransferNFTFrom', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256', + name: 'numItems', + type: 'uint256', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapAny', + name: 'swapInfo', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'maxCost', + type: 'uint256', + }, + ], + internalType: 'struct LSSVMRouter.RobustPairSwapAny[]', + name: 'swapList', + type: 'tuple[]', + }, + { + internalType: 'uint256', + name: 'inputAmount', + type: 'uint256', + }, + { + internalType: 'address', + name: 'nftRecipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'robustSwapERC20ForAnyNFTs', + outputs: [ + { + internalType: 'uint256', + name: 'remainingValue', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'nftIds', + type: 'uint256[]', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapSpecific', + name: 'swapInfo', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'maxCost', + type: 'uint256', + }, + ], + internalType: 'struct LSSVMRouter.RobustPairSwapSpecific[]', + name: 'swapList', + type: 'tuple[]', + }, + { + internalType: 'uint256', + name: 'inputAmount', + type: 'uint256', + }, + { + internalType: 'address', + name: 'nftRecipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'robustSwapERC20ForSpecificNFTs', + outputs: [ + { + internalType: 'uint256', + name: 'remainingValue', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'nftIds', + type: 'uint256[]', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapSpecific', + name: 'swapInfo', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'maxCost', + type: 'uint256', + }, + ], + internalType: 'struct LSSVMRouter.RobustPairSwapSpecific[]', + name: 'tokenToNFTTrades', + type: 'tuple[]', + }, + { + components: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'nftIds', + type: 'uint256[]', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapSpecific', + name: 'swapInfo', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'minOutput', + type: 'uint256', + }, + ], + internalType: 'struct LSSVMRouter.RobustPairSwapSpecificForToken[]', + name: 'nftToTokenTrades', + type: 'tuple[]', + }, + { + internalType: 'uint256', + name: 'inputAmount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'tokenRecipient', + type: 'address', + }, + { + internalType: 'address', + name: 'nftRecipient', + type: 'address', + }, + ], + internalType: 'struct LSSVMRouter.RobustPairNFTsFoTokenAndTokenforNFTsTrade', + name: 'params', + type: 'tuple', + }, + ], + name: 'robustSwapERC20ForSpecificNFTsAndNFTsToToken', + outputs: [ + { + internalType: 'uint256', + name: 'remainingValue', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'outputAmount', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256', + name: 'numItems', + type: 'uint256', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapAny', + name: 'swapInfo', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'maxCost', + type: 'uint256', + }, + ], + internalType: 'struct LSSVMRouter.RobustPairSwapAny[]', + name: 'swapList', + type: 'tuple[]', + }, + { + internalType: 'address payable', + name: 'ethRecipient', + type: 'address', + }, + { + internalType: 'address', + name: 'nftRecipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'robustSwapETHForAnyNFTs', + outputs: [ + { + internalType: 'uint256', + name: 'remainingValue', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'nftIds', + type: 'uint256[]', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapSpecific', + name: 'swapInfo', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'maxCost', + type: 'uint256', + }, + ], + internalType: 'struct LSSVMRouter.RobustPairSwapSpecific[]', + name: 'swapList', + type: 'tuple[]', + }, + { + internalType: 'address payable', + name: 'ethRecipient', + type: 'address', + }, + { + internalType: 'address', + name: 'nftRecipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'robustSwapETHForSpecificNFTs', + outputs: [ + { + internalType: 'uint256', + name: 'remainingValue', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'nftIds', + type: 'uint256[]', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapSpecific', + name: 'swapInfo', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'maxCost', + type: 'uint256', + }, + ], + internalType: 'struct LSSVMRouter.RobustPairSwapSpecific[]', + name: 'tokenToNFTTrades', + type: 'tuple[]', + }, + { + components: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'nftIds', + type: 'uint256[]', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapSpecific', + name: 'swapInfo', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'minOutput', + type: 'uint256', + }, + ], + internalType: 'struct LSSVMRouter.RobustPairSwapSpecificForToken[]', + name: 'nftToTokenTrades', + type: 'tuple[]', + }, + { + internalType: 'uint256', + name: 'inputAmount', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'tokenRecipient', + type: 'address', + }, + { + internalType: 'address', + name: 'nftRecipient', + type: 'address', + }, + ], + internalType: 'struct LSSVMRouter.RobustPairNFTsFoTokenAndTokenforNFTsTrade', + name: 'params', + type: 'tuple', + }, + ], + name: 'robustSwapETHForSpecificNFTsAndNFTsToToken', + outputs: [ + { + internalType: 'uint256', + name: 'remainingValue', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'outputAmount', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'nftIds', + type: 'uint256[]', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapSpecific', + name: 'swapInfo', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'minOutput', + type: 'uint256', + }, + ], + internalType: 'struct LSSVMRouter.RobustPairSwapSpecificForToken[]', + name: 'swapList', + type: 'tuple[]', + }, + { + internalType: 'address payable', + name: 'tokenRecipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'robustSwapNFTsForToken', + outputs: [ + { + internalType: 'uint256', + name: 'outputAmount', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256', + name: 'numItems', + type: 'uint256', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapAny[]', + name: 'swapList', + type: 'tuple[]', + }, + { + internalType: 'uint256', + name: 'inputAmount', + type: 'uint256', + }, + { + internalType: 'address', + name: 'nftRecipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapERC20ForAnyNFTs', + outputs: [ + { + internalType: 'uint256', + name: 'remainingValue', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'nftIds', + type: 'uint256[]', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapSpecific[]', + name: 'swapList', + type: 'tuple[]', + }, + { + internalType: 'uint256', + name: 'inputAmount', + type: 'uint256', + }, + { + internalType: 'address', + name: 'nftRecipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapERC20ForSpecificNFTs', + outputs: [ + { + internalType: 'uint256', + name: 'remainingValue', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256', + name: 'numItems', + type: 'uint256', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapAny[]', + name: 'swapList', + type: 'tuple[]', + }, + { + internalType: 'address payable', + name: 'ethRecipient', + type: 'address', + }, + { + internalType: 'address', + name: 'nftRecipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapETHForAnyNFTs', + outputs: [ + { + internalType: 'uint256', + name: 'remainingValue', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'nftIds', + type: 'uint256[]', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapSpecific[]', + name: 'swapList', + type: 'tuple[]', + }, + { + internalType: 'address payable', + name: 'ethRecipient', + type: 'address', + }, + { + internalType: 'address', + name: 'nftRecipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapETHForSpecificNFTs', + outputs: [ + { + internalType: 'uint256', + name: 'remainingValue', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'nftIds', + type: 'uint256[]', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapSpecific[]', + name: 'nftToTokenTrades', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256', + name: 'numItems', + type: 'uint256', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapAny[]', + name: 'tokenToNFTTrades', + type: 'tuple[]', + }, + ], + internalType: 'struct LSSVMRouter.NFTsForAnyNFTsTrade', + name: 'trade', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'inputAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'minOutput', + type: 'uint256', + }, + { + internalType: 'address', + name: 'nftRecipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapNFTsForAnyNFTsThroughERC20', + outputs: [ + { + internalType: 'uint256', + name: 'outputAmount', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'nftIds', + type: 'uint256[]', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapSpecific[]', + name: 'nftToTokenTrades', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256', + name: 'numItems', + type: 'uint256', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapAny[]', + name: 'tokenToNFTTrades', + type: 'tuple[]', + }, + ], + internalType: 'struct LSSVMRouter.NFTsForAnyNFTsTrade', + name: 'trade', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'minOutput', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'ethRecipient', + type: 'address', + }, + { + internalType: 'address', + name: 'nftRecipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapNFTsForAnyNFTsThroughETH', + outputs: [ + { + internalType: 'uint256', + name: 'outputAmount', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'nftIds', + type: 'uint256[]', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapSpecific[]', + name: 'nftToTokenTrades', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'nftIds', + type: 'uint256[]', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapSpecific[]', + name: 'tokenToNFTTrades', + type: 'tuple[]', + }, + ], + internalType: 'struct LSSVMRouter.NFTsForSpecificNFTsTrade', + name: 'trade', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'inputAmount', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'minOutput', + type: 'uint256', + }, + { + internalType: 'address', + name: 'nftRecipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapNFTsForSpecificNFTsThroughERC20', + outputs: [ + { + internalType: 'uint256', + name: 'outputAmount', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'nftIds', + type: 'uint256[]', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapSpecific[]', + name: 'nftToTokenTrades', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'nftIds', + type: 'uint256[]', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapSpecific[]', + name: 'tokenToNFTTrades', + type: 'tuple[]', + }, + ], + internalType: 'struct LSSVMRouter.NFTsForSpecificNFTsTrade', + name: 'trade', + type: 'tuple', + }, + { + internalType: 'uint256', + name: 'minOutput', + type: 'uint256', + }, + { + internalType: 'address payable', + name: 'ethRecipient', + type: 'address', + }, + { + internalType: 'address', + name: 'nftRecipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapNFTsForSpecificNFTsThroughETH', + outputs: [ + { + internalType: 'uint256', + name: 'outputAmount', + type: 'uint256', + }, + ], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'contract LSSVMPair', + name: 'pair', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'nftIds', + type: 'uint256[]', + }, + ], + internalType: 'struct LSSVMRouter.PairSwapSpecific[]', + name: 'swapList', + type: 'tuple[]', + }, + { + internalType: 'uint256', + name: 'minOutput', + type: 'uint256', + }, + { + internalType: 'address', + name: 'tokenRecipient', + type: 'address', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'swapNFTsForToken', + outputs: [ + { + internalType: 'uint256', + name: 'outputAmount', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + stateMutability: 'payable', + type: 'receive', + }, +] as const diff --git a/packages/universal-router-sdk/src/abis/UniversalRouter.ts b/packages/universal-router-sdk/src/abis/UniversalRouter.ts new file mode 100644 index 0000000000000..efbbca64bc0be --- /dev/null +++ b/packages/universal-router-sdk/src/abis/UniversalRouter.ts @@ -0,0 +1,685 @@ +export const UniversalRouterABI = [ + { + inputs: [ + { + components: [ + { + internalType: 'address', + name: 'permit2', + type: 'address', + }, + { + internalType: 'address', + name: 'weth9', + type: 'address', + }, + { + internalType: 'address', + name: 'seaportV1_5', + type: 'address', + }, + { + internalType: 'address', + name: 'seaportV1_4', + type: 'address', + }, + { + internalType: 'address', + name: 'openseaConduit', + type: 'address', + }, + { + internalType: 'address', + name: 'x2y2', + type: 'address', + }, + { + internalType: 'address', + name: 'looksRareV2', + type: 'address', + }, + { + internalType: 'address', + name: 'routerRewardsDistributor', + type: 'address', + }, + { + internalType: 'address', + name: 'looksRareRewardsDistributor', + type: 'address', + }, + { + internalType: 'address', + name: 'looksRareToken', + type: 'address', + }, + { + internalType: 'address', + name: 'v2Factory', + type: 'address', + }, + { + internalType: 'address', + name: 'v3Factory', + type: 'address', + }, + { + internalType: 'address', + name: 'v3Deployer', + type: 'address', + }, + { + internalType: 'bytes32', + name: 'v2InitCodeHash', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 'v3InitCodeHash', + type: 'bytes32', + }, + { + internalType: 'address', + name: 'stableFactory', + type: 'address', + }, + { + internalType: 'address', + name: 'stableInfo', + type: 'address', + }, + { + internalType: 'address', + name: 'pancakeNFTMarket', + type: 'address', + }, + ], + internalType: 'struct RouterParameters', + name: 'params', + type: 'tuple', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [], + name: 'BalanceTooLow', + type: 'error', + }, + { + inputs: [], + name: 'BuyPancakeNFTFailed', + type: 'error', + }, + { + inputs: [], + name: 'BuyPunkFailed', + type: 'error', + }, + { + inputs: [], + name: 'ContractLocked', + type: 'error', + }, + { + inputs: [], + name: 'ETHNotAccepted', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'commandIndex', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'message', + type: 'bytes', + }, + ], + name: 'ExecutionFailed', + type: 'error', + }, + { + inputs: [], + name: 'FromAddressIsNotOwner', + type: 'error', + }, + { + inputs: [], + name: 'InsufficientETH', + type: 'error', + }, + { + inputs: [], + name: 'InsufficientToken', + type: 'error', + }, + { + inputs: [], + name: 'InvalidBips', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'commandType', + type: 'uint256', + }, + ], + name: 'InvalidCommandType', + type: 'error', + }, + { + inputs: [], + name: 'InvalidOwnerERC1155', + type: 'error', + }, + { + inputs: [], + name: 'InvalidOwnerERC721', + type: 'error', + }, + { + inputs: [], + name: 'InvalidPath', + type: 'error', + }, + { + inputs: [], + name: 'InvalidPoolAddress', + type: 'error', + }, + { + inputs: [], + name: 'InvalidPoolLength', + type: 'error', + }, + { + inputs: [], + name: 'InvalidReserves', + type: 'error', + }, + { + inputs: [], + name: 'InvalidSpender', + type: 'error', + }, + { + inputs: [], + name: 'LengthMismatch', + type: 'error', + }, + { + inputs: [], + name: 'SliceOutOfBounds', + type: 'error', + }, + { + inputs: [], + name: 'StableInvalidPath', + type: 'error', + }, + { + inputs: [], + name: 'StableTooLittleReceived', + type: 'error', + }, + { + inputs: [], + name: 'StableTooMuchRequested', + type: 'error', + }, + { + inputs: [], + name: 'TransactionDeadlinePassed', + type: 'error', + }, + { + inputs: [], + name: 'UnableToClaim', + type: 'error', + }, + { + inputs: [], + name: 'UnsafeCast', + type: 'error', + }, + { + inputs: [], + name: 'V2InvalidPath', + type: 'error', + }, + { + inputs: [], + name: 'V2TooLittleReceived', + type: 'error', + }, + { + inputs: [], + name: 'V2TooMuchRequested', + type: 'error', + }, + { + inputs: [], + name: 'V3InvalidAmountOut', + type: 'error', + }, + { + inputs: [], + name: 'V3InvalidCaller', + type: 'error', + }, + { + inputs: [], + name: 'V3InvalidSwap', + type: 'error', + }, + { + inputs: [], + name: 'V3TooLittleReceived', + type: 'error', + }, + { + inputs: [], + name: 'V3TooMuchRequested', + type: 'error', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'account', + type: 'address', + }, + ], + name: 'Paused', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'RewardsSent', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'factory', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'info', + type: 'address', + }, + ], + name: 'SetStableSwap', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'account', + type: 'address', + }, + ], + name: 'Unpaused', + type: 'event', + }, + { + inputs: [ + { + internalType: 'bytes', + name: 'looksRareClaim', + type: 'bytes', + }, + ], + name: 'collectRewards', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes', + name: 'commands', + type: 'bytes', + }, + { + internalType: 'bytes[]', + name: 'inputs', + type: 'bytes[]', + }, + ], + name: 'execute', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes', + name: 'commands', + type: 'bytes', + }, + { + internalType: 'bytes[]', + name: 'inputs', + type: 'bytes[]', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + ], + name: 'execute', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: '', + type: 'bytes', + }, + ], + name: 'onERC1155BatchReceived', + outputs: [ + { + internalType: 'bytes4', + name: '', + type: 'bytes4', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '', + type: 'bytes', + }, + ], + name: 'onERC1155Received', + outputs: [ + { + internalType: 'bytes4', + name: '', + type: 'bytes4', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'address', + name: '', + type: 'address', + }, + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + { + internalType: 'bytes', + name: '', + type: 'bytes', + }, + ], + name: 'onERC721Received', + outputs: [ + { + internalType: 'bytes4', + name: '', + type: 'bytes4', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'int256', + name: 'amount0Delta', + type: 'int256', + }, + { + internalType: 'int256', + name: 'amount1Delta', + type: 'int256', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + name: 'pancakeV3SwapCallback', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'pause', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'paused', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'renounceOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_factory', + type: 'address', + }, + { + internalType: 'address', + name: '_info', + type: 'address', + }, + ], + name: 'setStableSwap', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'stableSwapFactory', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'stableSwapInfo', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: 'interfaceId', + type: 'bytes4', + }, + ], + name: 'supportsInterface', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'unpause', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + stateMutability: 'payable', + type: 'receive', + }, +] as const diff --git a/packages/universal-router-sdk/src/abis/X2Y2.ts b/packages/universal-router-sdk/src/abis/X2Y2.ts new file mode 100644 index 0000000000000..43263834f2bb1 --- /dev/null +++ b/packages/universal-router-sdk/src/abis/X2Y2.ts @@ -0,0 +1,1086 @@ +export const x2y2Abi = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'itemHash', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'address', + name: 'currency', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'incentive', + type: 'uint256', + }, + ], + name: 'EvAuctionRefund', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'itemHash', + type: 'bytes32', + }, + ], + name: 'EvCancel', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'delegate', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'isRemoval', + type: 'bool', + }, + ], + name: 'EvDelegate', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'index', + type: 'uint256', + }, + { + indexed: false, + internalType: 'bytes', + name: 'error', + type: 'bytes', + }, + ], + name: 'EvFailure', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'newValue', + type: 'uint256', + }, + ], + name: 'EvFeeCapUpdate', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'bytes32', + name: 'itemHash', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'address', + name: 'maker', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'taker', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'orderSalt', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'settleSalt', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'intent', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'delegateType', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + indexed: false, + internalType: 'contract IERC20Upgradeable', + name: 'currency', + type: 'address', + }, + { + indexed: false, + internalType: 'bytes', + name: 'dataMask', + type: 'bytes', + }, + { + components: [ + { + internalType: 'uint256', + name: 'price', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + indexed: false, + internalType: 'struct Market.OrderItem', + name: 'item', + type: 'tuple', + }, + { + components: [ + { + internalType: 'enum Market.Op', + name: 'op', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'orderIdx', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'itemIdx', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'price', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'itemHash', + type: 'bytes32', + }, + { + internalType: 'contract IDelegate', + name: 'executionDelegate', + type: 'address', + }, + { + internalType: 'bytes', + name: 'dataReplacement', + type: 'bytes', + }, + { + internalType: 'uint256', + name: 'bidIncentivePct', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'aucMinIncrementPct', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'aucIncDurationSecs', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint256', + name: 'percentage', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + internalType: 'struct Market.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + ], + indexed: false, + internalType: 'struct Market.SettleDetail', + name: 'detail', + type: 'tuple', + }, + ], + name: 'EvInventory', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'bytes32', + name: 'itemHash', + type: 'bytes32', + }, + { + indexed: false, + internalType: 'address', + name: 'currency', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'EvProfit', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'signer', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'isRemoval', + type: 'bool', + }, + ], + name: 'EvSigner', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'previousOwner', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'account', + type: 'address', + }, + ], + name: 'Paused', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'account', + type: 'address', + }, + ], + name: 'Unpaused', + type: 'event', + }, + { + inputs: {}, + name: 'RATE_BASE', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32[]', + name: 'itemHashes', + type: 'bytes32[]', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + ], + name: 'cancel', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'delegates', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'feeCapPct', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'feeCapPct_', + type: 'uint256', + }, + { + internalType: 'address', + name: 'weth_', + type: 'address', + }, + ], + name: 'initialize', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + name: 'inventoryStatus', + outputs: [ + { + internalType: 'enum Market.InvStatus', + name: '', + type: 'uint8', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32', + }, + ], + name: 'ongoingAuctions', + outputs: [ + { + internalType: 'uint256', + name: 'price', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'netPrice', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'endAt', + type: 'uint256', + }, + { + internalType: 'address', + name: 'bidder', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'owner', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'pause', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'paused', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: {}, + name: 'renounceOwnership', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: 'uint256', + name: 'salt', + type: 'uint256', + }, + { + internalType: 'address', + name: 'user', + type: 'address', + }, + { + internalType: 'uint256', + name: 'network', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'intent', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'delegateType', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'contract IERC20Upgradeable', + name: 'currency', + type: 'address', + }, + { + internalType: 'bytes', + name: 'dataMask', + type: 'bytes', + }, + { + components: [ + { + internalType: 'uint256', + name: 'price', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + internalType: 'struct Market.OrderItem[]', + name: 'items', + type: 'tuple[]', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'uint8', + name: 'signVersion', + type: 'uint8', + }, + ], + internalType: 'struct Market.Order[]', + name: 'orders', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'enum Market.Op', + name: 'op', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'orderIdx', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'itemIdx', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'price', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'itemHash', + type: 'bytes32', + }, + { + internalType: 'contract IDelegate', + name: 'executionDelegate', + type: 'address', + }, + { + internalType: 'bytes', + name: 'dataReplacement', + type: 'bytes', + }, + { + internalType: 'uint256', + name: 'bidIncentivePct', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'aucMinIncrementPct', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'aucIncDurationSecs', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint256', + name: 'percentage', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + internalType: 'struct Market.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + ], + internalType: 'struct Market.SettleDetail[]', + name: 'details', + type: 'tuple[]', + }, + { + components: [ + { + internalType: 'uint256', + name: 'salt', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountToEth', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountToWeth', + type: 'uint256', + }, + { + internalType: 'address', + name: 'user', + type: 'address', + }, + { + internalType: 'bool', + name: 'canFail', + type: 'bool', + }, + ], + internalType: 'struct Market.SettleShared', + name: 'shared', + type: 'tuple', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + ], + internalType: 'struct Market.RunInput', + name: 'input', + type: 'tuple', + }, + ], + name: 'run', + outputs: {}, + stateMutability: 'payable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'uint256', + name: 'salt', + type: 'uint256', + }, + { + internalType: 'address', + name: 'user', + type: 'address', + }, + { + internalType: 'uint256', + name: 'network', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'intent', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'delegateType', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'contract IERC20Upgradeable', + name: 'currency', + type: 'address', + }, + { + internalType: 'bytes', + name: 'dataMask', + type: 'bytes', + }, + { + components: [ + { + internalType: 'uint256', + name: 'price', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + internalType: 'struct Market.OrderItem[]', + name: 'items', + type: 'tuple[]', + }, + { + internalType: 'bytes32', + name: 'r', + type: 'bytes32', + }, + { + internalType: 'bytes32', + name: 's', + type: 'bytes32', + }, + { + internalType: 'uint8', + name: 'v', + type: 'uint8', + }, + { + internalType: 'uint8', + name: 'signVersion', + type: 'uint8', + }, + ], + internalType: 'struct Market.Order', + name: 'order', + type: 'tuple', + }, + { + components: [ + { + internalType: 'uint256', + name: 'salt', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'deadline', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountToEth', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amountToWeth', + type: 'uint256', + }, + { + internalType: 'address', + name: 'user', + type: 'address', + }, + { + internalType: 'bool', + name: 'canFail', + type: 'bool', + }, + ], + internalType: 'struct Market.SettleShared', + name: 'shared', + type: 'tuple', + }, + { + components: [ + { + internalType: 'enum Market.Op', + name: 'op', + type: 'uint8', + }, + { + internalType: 'uint256', + name: 'orderIdx', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'itemIdx', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'price', + type: 'uint256', + }, + { + internalType: 'bytes32', + name: 'itemHash', + type: 'bytes32', + }, + { + internalType: 'contract IDelegate', + name: 'executionDelegate', + type: 'address', + }, + { + internalType: 'bytes', + name: 'dataReplacement', + type: 'bytes', + }, + { + internalType: 'uint256', + name: 'bidIncentivePct', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'aucMinIncrementPct', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'aucIncDurationSecs', + type: 'uint256', + }, + { + components: [ + { + internalType: 'uint256', + name: 'percentage', + type: 'uint256', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + internalType: 'struct Market.Fee[]', + name: 'fees', + type: 'tuple[]', + }, + ], + internalType: 'struct Market.SettleDetail', + name: 'detail', + type: 'tuple', + }, + ], + name: 'run1', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '', + type: 'address', + }, + ], + name: 'signers', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'newOwner', + type: 'address', + }, + ], + name: 'transferOwnership', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'unpause', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address[]', + name: 'toAdd', + type: 'address[]', + }, + { + internalType: 'address[]', + name: 'toRemove', + type: 'address[]', + }, + ], + name: 'updateDelegates', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'val', + type: 'uint256', + }, + ], + name: 'updateFeeCap', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address[]', + name: 'toAdd', + type: 'address[]', + }, + { + internalType: 'address[]', + name: 'toRemove', + type: 'address[]', + }, + ], + name: 'updateSigners', + outputs: {}, + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: {}, + name: 'weth', + outputs: [ + { + internalType: 'contract IWETHUpgradable', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + stateMutability: 'payable', + type: 'receive', + }, +] as const diff --git a/packages/universal-router-sdk/src/constants.ts b/packages/universal-router-sdk/src/constants.ts new file mode 100644 index 0000000000000..32f83acbcb33b --- /dev/null +++ b/packages/universal-router-sdk/src/constants.ts @@ -0,0 +1,49 @@ +import { ChainId } from '@pancakeswap/chains' +import { Address } from 'viem' + +const UNIVERSAL_ROUTER_ADDRESSES: Record = { + [ChainId.ETHEREUM]: '0x65b382653f7C31bC0Af67f188122035461ec9C76', + [ChainId.GOERLI]: '0xC46abF8B66Df4B9Eb0cC0cf6eba24226AC6E6285', + [ChainId.SEPOLIA]: '0x55D32fa7Da7290838347bc97cb7fAD4992672255', + + [ChainId.BSC]: '0x1A0A18AC4BECDDbd6389559687d1A73d8927E416', + [ChainId.BSC_TESTNET]: '0x9A082015c919AD0E47861e5Db9A1c7070E81A2C7', + + // [ChainId.SCROLL]: '0xB89a6778D1efE7a5b7096757A21b810CC2886fa1', + [ChainId.SCROLL_SEPOLIA]: '0xB89a6778D1efE7a5b7096757A21b810CC2886fa1', + + [ChainId.ARBITRUM_ONE]: '0xFE6508f0015C778Bdcc1fB5465bA5ebE224C9912', + [ChainId.ARBITRUM_GOERLI]: '0xa8EEA7aa6620712524d18D742821848e55E773B5', + [ChainId.ARBITRUM_SEPOLIA]: '0xFE6508f0015C778Bdcc1fB5465bA5ebE224C9912', + + [ChainId.BASE]: '0xFE6508f0015C778Bdcc1fB5465bA5ebE224C9912', + [ChainId.BASE_TESTNET]: '0xa8EEA7aa6620712524d18D742821848e55E773B5', + [ChainId.BASE_SEPOLIA]: '0xFE6508f0015C778Bdcc1fB5465bA5ebE224C9912', + + [ChainId.POLYGON_ZKEVM]: '0xB89a6778D1efE7a5b7096757A21b810CC2886fa1', + [ChainId.POLYGON_ZKEVM_TESTNET]: '0xa8EEA7aa6620712524d18D742821848e55E773B5', + + [ChainId.LINEA]: '0xFE6508f0015C778Bdcc1fB5465bA5ebE224C9912', + [ChainId.LINEA_TESTNET]: '0x9f3Cb8251492a069dBF0634C24e9De305d6946B8', + + [ChainId.ZKSYNC]: '0xdAee41E335322C85ff2c5a6745c98e1351806e98', + [ChainId.ZKSYNC_TESTNET]: '0xCa97D1FAFCEa54EFc68d66eA914AB2F8Fbfe93c5', + + [ChainId.OPBNB]: '0xB89a6778D1efE7a5b7096757A21b810CC2886fa1', + [ChainId.OPBNB_TESTNET]: '0xa8EEA7aa6620712524d18D742821848e55E773B5', +} + +export const getUniversalRouterAddress = (chainId: ChainId): Address => { + if (!(chainId in UNIVERSAL_ROUTER_ADDRESSES)) throw new Error(`Universal Router not deployed on chain ${chainId}`) + return UNIVERSAL_ROUTER_ADDRESSES[chainId] +} + +export const CONTRACT_BALANCE = 2n ** 255n +export const SENDER_AS_RECIPIENT = '0x0000000000000000000000000000000000000001' +export const ROUTER_AS_RECIPIENT = '0x0000000000000000000000000000000000000002' + +// export const OPENSEA_CONDUIT_SPENDER_ID = 0 +// export const SUDOSWAP_SPENDER_ID = 1 + +export const SIGNATURE_LENGTH = 65 +export const EIP_2098_SIGNATURE_LENGTH = 64 diff --git a/packages/universal-router-sdk/src/entities/Command.ts b/packages/universal-router-sdk/src/entities/Command.ts new file mode 100644 index 0000000000000..572c589fdecb6 --- /dev/null +++ b/packages/universal-router-sdk/src/entities/Command.ts @@ -0,0 +1,17 @@ +import { RoutePlanner } from '../utils/routerCommands' + +export type TradeConfig = { + allowRevert: boolean +} + +export enum RouterTradeType { + PancakeSwapTrade = 'PancakeSwapTrade', + // NFTTrade = 'NFTTrade', + UnwrapWETH = 'UnwrapWETH', +} + +// interface for entities that can be encoded as a Universal Router command +export interface Command { + tradeType: RouterTradeType + encode(planner: RoutePlanner, config: TradeConfig): void +} diff --git a/packages/universal-router-sdk/src/entities/index.ts b/packages/universal-router-sdk/src/entities/index.ts new file mode 100644 index 0000000000000..90a884bba2478 --- /dev/null +++ b/packages/universal-router-sdk/src/entities/index.ts @@ -0,0 +1,2 @@ +export * from './protocols' +export * from './Command' diff --git a/packages/universal-router-sdk/src/entities/protocols/index.ts b/packages/universal-router-sdk/src/entities/protocols/index.ts new file mode 100644 index 0000000000000..67794ece8b7d6 --- /dev/null +++ b/packages/universal-router-sdk/src/entities/protocols/index.ts @@ -0,0 +1,2 @@ +export * from './pancakeswap' +export * from './unwrapWETH' diff --git a/packages/universal-router-sdk/src/entities/protocols/pancakeswap.ts b/packages/universal-router-sdk/src/entities/protocols/pancakeswap.ts new file mode 100644 index 0000000000000..e8b16b5d5b93a --- /dev/null +++ b/packages/universal-router-sdk/src/entities/protocols/pancakeswap.ts @@ -0,0 +1,437 @@ +import { Currency, CurrencyAmount, Percent, TradeType, validateAndParseAddress } from '@pancakeswap/sdk' +import { + BaseRoute, + RouteType, + SmartRouter, + SmartRouterTrade, + StablePool, + getPoolAddress, +} from '@pancakeswap/smart-router/evm' +import invariant from 'tiny-invariant' +import { Address } from 'viem' + +import { CONTRACT_BALANCE, ROUTER_AS_RECIPIENT, SENDER_AS_RECIPIENT } from '../../constants' +import { encodeFeeBips } from '../../utils/numbers' +import { ABIParametersType, CommandType, RoutePlanner } from '../../utils/routerCommands' +import { Command, RouterTradeType } from '../Command' +import { PancakeSwapOptions } from '../types' + +// Wrapper for pancakeswap router-sdk trade entity to encode swaps for Universal Router +export class PancakeSwapTrade implements Command { + readonly tradeType: RouterTradeType = RouterTradeType.PancakeSwapTrade + + readonly type: TradeType + + constructor(public trade: SmartRouterTrade, public options: PancakeSwapOptions) { + this.type = this.trade.tradeType + if (options.fee && options.flatFee) { + throw new Error('Cannot specify both fee and flatFee') + } + } + + encode(planner: RoutePlanner): void { + let payerIsUser = true + const { trade } = this + const numberOfTrades = trade.routes.length + + // If the input currency is the native currency, we need to wrap it with the router as the recipient + if (trade.inputAmount.currency.isNative) { + // TODO: optimize if only one v2 pool we can directly send this to the pool + planner.addCommand(CommandType.WRAP_ETH, [ + ROUTER_AS_RECIPIENT, + BigInt( + SmartRouter.maximumAmountIn(trade, this.options.slippageTolerance, trade.inputAmount).quotient.toString(), + ), + ]) + // since WETH is now owned by the router, the router pays for inputs + payerIsUser = false + } + // The overall recipient at the end of the trade, SENDER_AS_RECIPIENT uses the msg.sender + this.options.recipient = this.options.recipient ?? SENDER_AS_RECIPIENT + + const inputIsNative = trade.inputAmount.currency.isNative + const outputIsNative = trade.outputAmount.currency.isNative + const performAggregatedSlippageCheck = trade.tradeType === TradeType.EXACT_INPUT && numberOfTrades > 2 + const routerMustCustody = outputIsNative || !!this.options.fee || performAggregatedSlippageCheck + + for (const route of trade.routes) { + const singleRouteTrade: SmartRouterTrade = { + ...trade, + routes: [route], + inputAmount: route.inputAmount, + outputAmount: route.outputAmount, + } + if (route.type === RouteType.V2) { + addV2Swap(planner, singleRouteTrade, this.options, routerMustCustody, payerIsUser) + continue + } + if (route.type === RouteType.V3) { + addV3Swap(planner, singleRouteTrade, this.options, routerMustCustody, payerIsUser) + continue + } + if (route.type === RouteType.STABLE) { + addStableSwap(planner, singleRouteTrade, this.options, routerMustCustody, payerIsUser) + continue + } + addMixedSwap(planner, singleRouteTrade, this.options, payerIsUser, routerMustCustody) + } + + let minAmountOut = SmartRouter.minimumAmountOut(trade, this.options.slippageTolerance, trade.outputAmount) + // The router custodies for 3 reasons: to unwrap, to take a fee, and/or to do a slippage check + if (routerMustCustody) { + // If there is a fee, that percentage is sent to the fee recipient + // In the case where ETH is the output currency, the fee is taken in WETH (for gas reasons) + if (this.options.fee) { + const feeBips = BigInt(encodeFeeBips(this.options.fee.fee)) + planner.addCommand(CommandType.PAY_PORTION, [ + trade.outputAmount.currency.wrapped.address, + this.options.fee.recipient, + feeBips, + ]) + + // If the trade is exact output, and a fee was taken, we must adjust the amount out to be the amount after the fee + // Otherwise we continue as expected with the trade's normal expected output + if (this.type === TradeType.EXACT_OUTPUT) { + minAmountOut = minAmountOut.subtract(minAmountOut.multiply(feeBips).divide(10000)) + } + } + + // TODO: missing flatFee + if (this.options.flatFee) { + const fee = BigInt(this.options.flatFee.amount.toString()) + if (fee < minAmountOut.quotient) throw new Error("Flat fee can't be greater than minimum amount out") + + planner.addCommand(CommandType.TRANSFER, [ + trade.outputAmount.currency.wrapped.address, + this.options.flatFee.recipient, + fee, + ]) + + // If the trade is exact output, and a fee was taken, we must adjust the amount out to be the amount after the fee + // Otherwise we continue as expected with the trade's normal expected output + if (this.type === TradeType.EXACT_OUTPUT) { + minAmountOut = CurrencyAmount.fromRawAmount(trade.outputAmount.currency, minAmountOut.quotient - fee) + } + } + + // The remaining tokens that need to be sent to the user after the fee is taken will be caught + // by this if-else clause. + if (outputIsNative) { + planner.addCommand(CommandType.UNWRAP_WETH, [this.options.recipient, minAmountOut.quotient]) + } else { + planner.addCommand(CommandType.SWEEP, [ + trade.outputAmount.currency.wrapped.address, + this.options.recipient, + minAmountOut.quotient, + ]) + } + } + + if (inputIsNative && (this.type === TradeType.EXACT_OUTPUT || riskOfPartialFill(trade))) { + // for exactOutput swaps that take native currency as input + // we need to send back the change to the user + planner.addCommand(CommandType.UNWRAP_WETH, [this.options.recipient, 0n]) + } + } +} + +// encode a v2 swap +function addV2Swap( + planner: RoutePlanner, + trade: SmartRouterTrade, + options: PancakeSwapOptions, + routerMustCustody: boolean, + payerIsUser: boolean, +): void { + const amountIn = BigInt(SmartRouter.maximumAmountIn(trade, options.slippageTolerance).quotient.toString()) + const amountOut = BigInt(SmartRouter.minimumAmountOut(trade, options.slippageTolerance).quotient.toString()) + + invariant(trade.routes.length === 1 && trade.routes[0].type === RouteType.V2, 'Only allow single route v2 trade') + + // V2 trade should have only one route + const [route] = trade.routes + const path = route.path.map((token) => token.wrapped.address) + + const recipient = routerMustCustody + ? ROUTER_AS_RECIPIENT + : validateAndParseAddress(options.recipient ?? SENDER_AS_RECIPIENT) + + // same as encodeV2Swap only we dont return calldatas instead we push to the command planner + if (trade.tradeType === TradeType.EXACT_INPUT) { + planner.addCommand(CommandType.V2_SWAP_EXACT_IN, [ + // if native, we have to unwrap so keep in the router for now + recipient, + amountIn, + amountOut, + path, + payerIsUser, + ]) + return + } + + planner.addCommand(CommandType.V2_SWAP_EXACT_OUT, [recipient, amountOut, amountIn, path, payerIsUser]) +} + +// encode a v3 swap +function addV3Swap( + planner: RoutePlanner, + trade: SmartRouterTrade, + options: PancakeSwapOptions, + routerMustCustody: boolean, + payerIsUser: boolean, +): void { + invariant(trade.routes.length === 1 && trade.routes[0].type === RouteType.V3, 'Only allow single route v3 trade') + const [route] = trade.routes + + const { inputAmount, outputAmount } = route + + // we need to generaate v3 path as a hash string. we can still use encodeMixedRoute + // as a v3 swap is essentially a for of mixedRoute + const path = SmartRouter.encodeMixedRouteToPath( + { ...route, input: inputAmount.currency, output: outputAmount.currency }, + trade.tradeType === TradeType.EXACT_OUTPUT, + ) + const amountIn: bigint = SmartRouter.maximumAmountIn(trade, options.slippageTolerance, inputAmount).quotient + const amountOut: bigint = SmartRouter.minimumAmountOut(trade, options.slippageTolerance, outputAmount).quotient + + const recipient = routerMustCustody + ? ROUTER_AS_RECIPIENT + : validateAndParseAddress(options.recipient ?? SENDER_AS_RECIPIENT) + + if (trade.tradeType === TradeType.EXACT_INPUT) { + const exactInputSingleParams: ABIParametersType = [ + recipient, + amountIn, + amountOut, + path, + payerIsUser, + ] + planner.addCommand(CommandType.V3_SWAP_EXACT_IN, exactInputSingleParams) + return + } + + const exactOutputSingleParams: ABIParametersType = [ + recipient, + amountOut, + amountIn, + path, + payerIsUser, + ] + planner.addCommand(CommandType.V3_SWAP_EXACT_OUT, exactOutputSingleParams) +} + +function addStableSwap( + planner: RoutePlanner, + trade: SmartRouterTrade, + options: PancakeSwapOptions, + routerMustCustody: boolean, + // @notice: stable swap inputToken will never be nativeToken + payerIsUser = false, +): void { + invariant( + trade.routes.length === 1 && trade.routes[0].type === RouteType.STABLE, + 'Only allow single route stable trade', + ) + + const amountIn = SmartRouter.maximumAmountIn(trade, options.slippageTolerance).quotient + const amountOut = SmartRouter.minimumAmountOut(trade, options.slippageTolerance).quotient + + const [route] = trade.routes + const path = route.path.map((token) => token.wrapped.address) + const flags = route.pools.map((p) => BigInt((p as StablePool).balances.length)) + const recipient = routerMustCustody + ? ROUTER_AS_RECIPIENT + : validateAndParseAddress(options.recipient ?? SENDER_AS_RECIPIENT) + + if (trade.tradeType === TradeType.EXACT_INPUT) { + const exactInputParams: ABIParametersType = [ + recipient, + amountIn, + amountOut, + path, + flags, + payerIsUser, + ] + planner.addCommand(CommandType.STABLE_SWAP_EXACT_IN, exactInputParams) + return + } + + const exactOutputParams: ABIParametersType = [ + recipient, + amountOut, + amountIn, + path, + flags, + payerIsUser, + ] + + planner.addCommand(CommandType.STABLE_SWAP_EXACT_OUT, exactOutputParams) +} + +// encode a mixed route swap, i.e. including both v2 and v3 pools +function addMixedSwap( + planner: RoutePlanner, + trade: SmartRouterTrade, + options: PancakeSwapOptions, + payerIsUser: boolean, + routerMustCustody: boolean, +): void { + invariant( + trade.routes.length === 1 && trade.routes[0].type === RouteType.MIXED, + 'Only allow single route mixed trade', + ) + + const [route] = trade.routes + const { inputAmount, outputAmount } = route + const amountIn: bigint = SmartRouter.maximumAmountIn(trade, options.slippageTolerance, inputAmount).quotient + const amountOut: bigint = SmartRouter.minimumAmountOut(trade, options.slippageTolerance, outputAmount).quotient + + const recipient = routerMustCustody + ? ROUTER_AS_RECIPIENT + : validateAndParseAddress(options.recipient ?? SENDER_AS_RECIPIENT) + + const mixedRouteIsAllV3 = (r: Omit) => { + return r.pools.every(SmartRouter.isV3Pool) + } + const mixedRouteIsAllV2 = (r: Omit) => { + return r.pools.every(SmartRouter.isV2Pool) + } + const mixedRouteIsAllStable = (r: Omit) => { + return r.pools.every(SmartRouter.isStablePool) + } + + // Check if a mixed route is actually pure v2, v3 or stable route. + // If that's the case then let the corresponding command adder to handle the encode + if (mixedRouteIsAllV3(route)) { + addV3Swap( + planner, + { + ...trade, + routes: [ + { + ...route, + type: RouteType.V3, + }, + ], + inputAmount, + outputAmount, + }, + options, + routerMustCustody, + payerIsUser, + ) + return + } + if (mixedRouteIsAllV2(route)) { + addV2Swap( + planner, + { + ...trade, + routes: [ + { + ...route, + type: RouteType.V2, + }, + ], + inputAmount, + outputAmount, + }, + options, + routerMustCustody, + payerIsUser, + ) + return + } + if (mixedRouteIsAllStable(route)) { + addStableSwap( + planner, + { + ...trade, + routes: [ + { + ...route, + type: RouteType.STABLE, + }, + ], + inputAmount, + outputAmount, + }, + options, + routerMustCustody, + payerIsUser, + ) + return + } + + invariant(trade.tradeType === TradeType.EXACT_INPUT, 'Exact output is not supported for mixed route trade') + + const sections = SmartRouter.partitionMixedRouteByProtocol(route) + + let outputToken: Currency | undefined + let inputToken = inputAmount.currency.wrapped + + for (let i = 0; i < sections.length; i++) { + const section = sections[i] + const nextSection = sections[i + 1] ?? [] + const isFirstSection = i === 0 + const isLastSection = i === sections.length - 1 + + const nextIsV2 = nextSection.length && nextSection.every(SmartRouter.isV2Pool) + + const getRecipient = (): Address => { + if (isLastSection) return recipient + if (nextIsV2) { + const address = getPoolAddress(nextSection[0]) + if (!address) throw new Error('unknown v2 pool address') + return address + } + return ROUTER_AS_RECIPIENT + } + const currentRecipient = getRecipient() + + // Now, we get output of this section + outputToken = SmartRouter.getOutputOfPools(section, inputToken) + const newRoute = SmartRouter.buildBaseRoute([...section], inputToken, outputToken) + inputToken = outputToken.wrapped + + const payByUser = payerIsUser && isFirstSection + + const inAmount = isFirstSection ? amountIn : CONTRACT_BALANCE + const outAmount = isLastSection ? amountOut : 0n + + switch (newRoute.type) { + case RouteType.V3: { + const path = SmartRouter.encodeMixedRouteToPath(newRoute, false) + planner.addCommand(CommandType.V3_SWAP_EXACT_IN, [currentRecipient, inAmount, outAmount, path, payByUser]) + break + } + case RouteType.V2: { + const path = newRoute.path.map((token) => token.wrapped.address) + planner.addCommand(CommandType.V2_SWAP_EXACT_IN, [currentRecipient, inAmount, outAmount, path, payByUser]) + break + } + case RouteType.STABLE: { + const path = newRoute.path.map((token) => token.wrapped.address) + const flags = (newRoute.pools as StablePool[]).map((pool) => BigInt(pool.balances.length)) + planner.addCommand(CommandType.STABLE_SWAP_EXACT_IN, [ + currentRecipient, + inAmount, + outAmount, + path, + flags, + payByUser, + ]) + break + } + default: + throw new RangeError('Unexpected route type') + } + } +} + +const REFUND_ETH_PRICE_IMPACT_THRESHOLD = new Percent(50, 100) + +// if price impact is very high, there's a chance of hitting max/min prices resulting in a partial fill of the swap +function riskOfPartialFill(trade: SmartRouterTrade): boolean { + return SmartRouter.getPriceImpact(trade).greaterThan(REFUND_ETH_PRICE_IMPACT_THRESHOLD) +} diff --git a/packages/universal-router-sdk/src/entities/protocols/unwrapWETH.ts b/packages/universal-router-sdk/src/entities/protocols/unwrapWETH.ts new file mode 100644 index 0000000000000..c0cf8b8d3421c --- /dev/null +++ b/packages/universal-router-sdk/src/entities/protocols/unwrapWETH.ts @@ -0,0 +1,43 @@ +import { ChainId } from '@pancakeswap/chains' +import { BigintIsh, WETH9 } from '@pancakeswap/sdk' +import invariant from 'tiny-invariant' +import { ROUTER_AS_RECIPIENT } from '../../constants' +import { encodeInputTokenOptions } from '../../utils/inputTokens' +import { CommandType, RoutePlanner } from '../../utils/routerCommands' +import { Command, RouterTradeType, TradeConfig } from '../Command' +import { Permit2Signature } from '../types' + +export class UnwrapWETH implements Command { + readonly tradeType: RouterTradeType = RouterTradeType.UnwrapWETH + + readonly permit2Data: Permit2Signature | undefined + + readonly wethAddress: string + + readonly amount: BigintIsh + + constructor(amount: BigintIsh, chainId: ChainId, permit2?: Permit2Signature) { + this.wethAddress = WETH9[chainId].address + this.amount = amount + + if (permit2) { + invariant( + permit2.details.token.toLowerCase() === this.wethAddress.toLowerCase(), + `must be permitting WETH address: ${this.wethAddress}`, + ) + invariant(permit2.details.amount >= amount, `Did not permit enough WETH for unwrapWETH transaction`) + this.permit2Data = permit2 + } + } + + encode(planner: RoutePlanner, _: TradeConfig): void { + encodeInputTokenOptions(planner, { + permit2Permit: this.permit2Data, + permit2TransferFrom: { + token: this.wethAddress, + amount: this.amount.toString(), + }, + }) + planner.addCommand(CommandType.UNWRAP_WETH, [ROUTER_AS_RECIPIENT, BigInt(this.amount.toString())]) + } +} diff --git a/packages/universal-router-sdk/src/entities/types.ts b/packages/universal-router-sdk/src/entities/types.ts new file mode 100644 index 0000000000000..bec5b2d126947 --- /dev/null +++ b/packages/universal-router-sdk/src/entities/types.ts @@ -0,0 +1,23 @@ +import { PermitSingle } from '@pancakeswap/permit2-sdk' +import { BigintIsh } from '@pancakeswap/sdk' +import { SwapOptions } from '@pancakeswap/smart-router/evm' +import { Address } from 'viem' + +export interface Permit2Signature extends PermitSingle { + signature: `0x${string}` +} + +export type SwapRouterConfig = { + sender?: Address // address + deadline?: BigintIsh | undefined +} + +export type FlatFeeOptions = { + amount: BigintIsh + recipient: Address +} + +export type PancakeSwapOptions = Omit & { + inputTokenPermit?: Permit2Signature + flatFee?: FlatFeeOptions +} diff --git a/packages/universal-router-sdk/src/index.ts b/packages/universal-router-sdk/src/index.ts new file mode 100644 index 0000000000000..69bcc0a994de2 --- /dev/null +++ b/packages/universal-router-sdk/src/index.ts @@ -0,0 +1,4 @@ +export { ROUTER_AS_RECIPIENT, getUniversalRouterAddress } from './constants' +export * from './entities' +export * from './entities/types' +export { PancakeSwapUniversalRouter } from './swapRouter' diff --git a/packages/universal-router-sdk/src/swapRouter.ts b/packages/universal-router-sdk/src/swapRouter.ts new file mode 100644 index 0000000000000..3b3c78ea5427b --- /dev/null +++ b/packages/universal-router-sdk/src/swapRouter.ts @@ -0,0 +1,68 @@ +import { TradeType } from '@pancakeswap/sdk' +import { SmartRouter, SmartRouterTrade } from '@pancakeswap/smart-router/evm' +import { MethodParameters } from '@pancakeswap/v3-sdk' +import invariant from 'tiny-invariant' +import { encodeFunctionData, toHex } from 'viem' +import { PancakeSwapTrade } from './entities/protocols/pancakeswap' +import { encodePermit } from './utils/inputTokens' +import { RoutePlanner } from './utils/routerCommands' +import { PancakeSwapOptions, SwapRouterConfig } from './entities/types' +import { UniversalRouterABI } from './abis/UniversalRouter' + +export abstract class PancakeSwapUniversalRouter { + /** + * Produces the on-chain method name to call and the hex encoded parameters to pass as arguments for a given trade. + * @param trades to produce call parameters for + * @param options options for the call parameters + */ + public static swapERC20CallParameters( + trade: SmartRouterTrade, + options: PancakeSwapOptions, + ): MethodParameters { + // TODO: use permit if signature included in swapOptions + const planner = new RoutePlanner() + + const tradeCommand: PancakeSwapTrade = new PancakeSwapTrade(trade, options) + + const inputCurrency = tradeCommand.trade.inputAmount.currency + invariant(!(inputCurrency.isNative && !!options.inputTokenPermit), 'NATIVE_INPUT_PERMIT') + + if (options.inputTokenPermit && typeof options.inputTokenPermit === 'object') { + encodePermit(planner, options.inputTokenPermit) + } + + const nativeCurrencyValue = inputCurrency.isNative + ? SmartRouter.maximumAmountIn(tradeCommand.trade, options.slippageTolerance, tradeCommand.trade.inputAmount) + .quotient + : 0n + + tradeCommand.encode(planner) + return PancakeSwapUniversalRouter.encodePlan(planner, nativeCurrencyValue, { + deadline: options.deadlineOrPreviousBlockhash + ? BigInt(options.deadlineOrPreviousBlockhash.toString()) + : undefined, + }) + } + + /** + * Encodes a planned route into a method name and parameters for the Router contract. + * @param planner the planned route + * @param nativeCurrencyValue the native currency value of the planned route + * @param config the router config + */ + private static encodePlan( + planner: RoutePlanner, + nativeCurrencyValue: bigint, + config: SwapRouterConfig = {}, + ): MethodParameters { + const { commands, inputs } = planner + const calldata = config.deadline + ? encodeFunctionData({ + abi: UniversalRouterABI, + args: [commands, inputs, BigInt(config.deadline)], + functionName: 'execute', + }) + : encodeFunctionData({ abi: UniversalRouterABI, args: [commands, inputs], functionName: 'execute' }) + return { calldata, value: toHex(nativeCurrencyValue) } + } +} diff --git a/packages/universal-router-sdk/src/utils/inputTokens.ts b/packages/universal-router-sdk/src/utils/inputTokens.ts new file mode 100644 index 0000000000000..0ca5597f9a9f7 --- /dev/null +++ b/packages/universal-router-sdk/src/utils/inputTokens.ts @@ -0,0 +1,75 @@ +import invariant from 'tiny-invariant' +import type { Address } from 'viem' +import { ROUTER_AS_RECIPIENT } from '../constants' +import { Permit2Signature } from '../entities/types' +import { CommandType, RoutePlanner } from './routerCommands' + +export type ApproveProtocol = { + token: string + protocol: string +} + +export type Permit2TransferFrom = { + token: string + amount: string + recipient?: string +} + +export type InputTokenOptions = { + approval?: ApproveProtocol + permit2Permit?: Permit2Signature + permit2TransferFrom?: Permit2TransferFrom +} + +export function encodePermit(planner: RoutePlanner, permit2: Permit2Signature): void { + planner.addCommand(CommandType.PERMIT2_PERMIT, [permit2, permit2.signature as `0x${string}`]) +} + +// Handles the encoding of commands needed to gather input tokens for a trade +// Approval: The router approving another address to take tokens. +// note: Only seaport and sudoswap support this action. Approvals are left open. +// Permit: A Permit2 signature-based Permit to allow the router to access a user's tokens +// Transfer: A Permit2 TransferFrom of tokens from a user to either the router or another address +export function encodeInputTokenOptions(planner: RoutePlanner, options: InputTokenOptions) { + // first ensure that all tokens provided for encoding are the same + if (!!options.approval && !!options.permit2Permit) + invariant(options.approval.token === options.permit2Permit.details.token, `inconsistent token`) + if (!!options.approval && !!options.permit2TransferFrom) + invariant(options.approval.token === options.permit2TransferFrom.token, `inconsistent token`) + if (!!options.permit2TransferFrom && !!options.permit2Permit) + invariant(options.permit2TransferFrom.token === options.permit2Permit.details.token, `inconsistent token`) + + // if an options.approval is required, add it + // if (options.approval) { + // planner.addCommand(CommandType.APPROVE_ERC20, [ + // options.approval.token as Address, + // BigInt(mapApprovalProtocol(options.approval.protocol)), + // ]) + // } + + // if this order has a options.permit2Permit, encode it + if (options.permit2Permit) { + encodePermit(planner, options.permit2Permit) + } + + if (options.permit2TransferFrom) { + planner.addCommand(CommandType.PERMIT2_TRANSFER_FROM, [ + options.permit2TransferFrom.token as Address, + (options.permit2TransferFrom.recipient ? options.permit2TransferFrom.recipient : ROUTER_AS_RECIPIENT) as Address, + BigInt(options.permit2TransferFrom.amount), + ]) + } +} + +// function mapApprovalProtocol(protocolAddress: string): number { +// switch (protocolAddress.toLowerCase()) { +// case '0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC': // Seaport v1.5 +// return OPENSEA_CONDUIT_SPENDER_ID +// case '0x00000000000001ad428e4906aE43D8F9852d0dD6': // Seaport v1.4 +// return OPENSEA_CONDUIT_SPENDER_ID +// case '0x2B2e8cDA09bBA9660dCA5cB6233787738Ad68329': // Sudoswap +// return SUDOSWAP_SPENDER_ID +// default: +// throw new Error('unsupported protocol address') +// } +// } diff --git a/packages/universal-router-sdk/src/utils/numbers.ts b/packages/universal-router-sdk/src/utils/numbers.ts new file mode 100644 index 0000000000000..94e5e93adfdb6 --- /dev/null +++ b/packages/universal-router-sdk/src/utils/numbers.ts @@ -0,0 +1,5 @@ +import { Percent as PancakePercent } from '@pancakeswap/sdk' + +export function encodeFeeBips(fee: PancakePercent): string { + return fee.multiply(10_000).quotient.toString() +} diff --git a/packages/universal-router-sdk/src/utils/routerCommands.ts b/packages/universal-router-sdk/src/utils/routerCommands.ts new file mode 100644 index 0000000000000..011d0975fe94d --- /dev/null +++ b/packages/universal-router-sdk/src/utils/routerCommands.ts @@ -0,0 +1,247 @@ +import { AbiParametersToPrimitiveTypes } from 'abitype' +import { Hex, encodeAbiParameters, parseAbiParameters } from 'viem' + +export type ABIType = typeof ABI_PARAMETER +export type ABIParametersType = AbiParametersToPrimitiveTypes + +/** + * CommandTypes + * @description Flags that modify a command's execution + * @enum {number} + */ +export enum CommandType { + // Masks to extract certain bits of commands + ALLOW_REVERT_FLAG = 0x80, + COMMAND_TYPE_MASK = 0x3f, + + // Command Types. Maximum supported command at this moment is 0x3f. + + // Command Types where value<0x08, executed in the first nested-if block + V3_SWAP_EXACT_IN = 0x00, + V3_SWAP_EXACT_OUT = 0x01, + PERMIT2_TRANSFER_FROM = 0x02, + PERMIT2_PERMIT_BATCH = 0x03, + SWEEP = 0x04, + TRANSFER = 0x05, + PAY_PORTION = 0x06, + // COMMAND_PLACEHOLDER = 0x07; + + // The commands are executed in nested if blocks to minimise gas consumption + // The following constant defines one of the boundaries where the if blocks split commands + // FIRST_IF_BOUNDARY = 0x08, + + // Command Types where 0x08<=value<0x10, executed in the second nested-if block + V2_SWAP_EXACT_IN = 0x08, + V2_SWAP_EXACT_OUT = 0x09, + PERMIT2_PERMIT = 0x0a, + WRAP_ETH = 0x0b, + UNWRAP_WETH = 0x0c, + PERMIT2_TRANSFER_FROM_BATCH = 0x0d, + BALANCE_CHECK_ERC20 = 0x0e, + // COMMAND_PLACEHOLDER = 0x0f; + + // The commands are executed in nested if blocks to minimise gas consumption + // The following constant defines one of the boundaries where the if blocks split commands + // SECOND_IF_BOUNDARY = 0x10, + + // Command Types where 0x10<=value<0x18, executed in the third nested-if block + // OWNER_CHECK_721 = 0x10, + // OWNER_CHECK_1155 = 0x11, + // SWEEP_ERC721 = 0x12, + // SWEEP_ERC1155 = 0x13, + // COMMAND_PLACEHOLDER for 0x14-0x17 (all unused) + + // The commands are executed in nested if blocks to minimise gas consumption + // The following constant defines one of the boundaries where the if blocks split commands + // THIRD_IF_BOUNDARY = 0x18, + + // Command Types where 0x18<=value<=0x1f, executed in the final nested-if block + // SEAPORT_V1_5 = 0x18, + // SEAPORT_V1_4 = 0x19, + // LOOKS_RARE_V2 = 0x1a, + // X2Y2_721 = 0x1b, + // X2Y2_1155 = 0x1c, + // COMMAND_PLACEHOLDER for 0x1d-0x1f (all unused) + + // The commands are executed in nested if blocks to minimise gas consumption + // The following constant defines one of the boundaries where the if blocks split commands + // FOURTH_IF_BOUNDARY = 0x20, + + // Command Types where 0x20<=value + EXECUTE_SUB_PLAN = 0x20, + // APPROVE_ERC20 = 0x21, + STABLE_SWAP_EXACT_IN = 0x22, + STABLE_SWAP_EXACT_OUT = 0x23, + // PANCAKE_NFT_BNB = 0x24, + // PANCAKE_NFT_WBNB = 0x25, +} + +const ALLOW_REVERT_FLAG = 0x80 + +const REVERTIBLE_COMMANDS = new Set([ + // CommandType.SEAPORT_V1_5, + // CommandType.SEAPORT_V1_4, + // CommandType.LOOKS_RARE_V2, + // CommandType.X2Y2_721, + // CommandType.X2Y2_1155, + CommandType.EXECUTE_SUB_PLAN, +]) + +const ABI_STRUCT_PERMIT_DETAILS = ` +struct PermitDetails { + address token; + uint160 amount; + uint48 expiration; + uint48 nonce; +}`.replaceAll('\n', '') + +const ABI_STRUCT_PERMIT_SINGLE = ` +struct PermitSingle { + PermitDetails details; + address spender; + uint256 sigDeadline; +} +`.replaceAll('\n', '') + +const ABI_STRUCT_PERMIT_BATCH = ` +struct PermitBatch { + PermitSingle[] details; + address spender; + uint256 sigDeadline; +} +`.replaceAll('\n', '') + +const ABI_STRUCT_ALLOWANCE_TRANSFER_DETAILS = ` +struct AllowanceTransferDetails { + address from; + address to; + uint160 amount; + address token; +} +`.replaceAll('\n', '') + +export const ABI_PARAMETER = { + // Batch Reverts + [CommandType.EXECUTE_SUB_PLAN]: parseAbiParameters('bytes _commands, bytes[] _inputs'), + + // Permit2 Actions + [CommandType.PERMIT2_PERMIT]: parseAbiParameters([ + 'PermitSingle permitSingle, bytes data', + ABI_STRUCT_PERMIT_SINGLE, + ABI_STRUCT_PERMIT_DETAILS, + ]), + [CommandType.PERMIT2_PERMIT_BATCH]: parseAbiParameters([ + 'PermitBatch permitBatch, bytes data', + ABI_STRUCT_PERMIT_BATCH, + ABI_STRUCT_PERMIT_SINGLE, + ABI_STRUCT_PERMIT_DETAILS, + ]), + [CommandType.PERMIT2_TRANSFER_FROM]: parseAbiParameters('address token, address recipient, uint160 amount'), + [CommandType.PERMIT2_TRANSFER_FROM_BATCH]: parseAbiParameters([ + 'AllowanceTransferDetails[] batchDetails', + ABI_STRUCT_ALLOWANCE_TRANSFER_DETAILS, + ]), + + // swap actions + [CommandType.V3_SWAP_EXACT_IN]: parseAbiParameters( + 'address recipient, uint256 amountIn, uint256 amountOutMin, bytes path, bool payerIsUser', + ), + [CommandType.V3_SWAP_EXACT_OUT]: parseAbiParameters( + 'address recipient, uint256 amountOut, uint256 amountInMax, bytes path, bool payerIsUser', + ), + [CommandType.V2_SWAP_EXACT_IN]: parseAbiParameters( + 'address recipient, uint256 amountIn, uint256 amountOutMin, address[] path, bool payerIsUser', + ), + [CommandType.V2_SWAP_EXACT_OUT]: parseAbiParameters( + 'address recipient, uint256 amountOut, uint256 amountInMax, address[] path, bool payerIsUser', + ), + [CommandType.STABLE_SWAP_EXACT_IN]: parseAbiParameters( + 'address recipient, uint256 amountIn, uint256 amountOutMin, address[] path, uint256[] flag, bool payerIsUser', + ), + [CommandType.STABLE_SWAP_EXACT_OUT]: parseAbiParameters( + 'address recipient, uint256 amountOut, uint256 amountInMax, address[] path, uint256[] flag, bool payerIsUser', + ), + + // Token Actions and Checks + [CommandType.WRAP_ETH]: parseAbiParameters('address recipient, uint256 amountMin'), + [CommandType.UNWRAP_WETH]: parseAbiParameters('address recipient, uint256 amountMin'), + [CommandType.SWEEP]: parseAbiParameters('address token, address recipient, uint256 amountMin'), + // [CommandType.SWEEP_ERC721]: parseAbiParameters('address token, address recipient, uint256 id'), + // [CommandType.SWEEP_ERC1155]: parseAbiParameters('address token, address recipient, uint256 id, uint256 amount'), + [CommandType.TRANSFER]: parseAbiParameters('address token, address recipient, uint256 value'), + [CommandType.PAY_PORTION]: parseAbiParameters('address token, address recipient, uint256 bips'), + [CommandType.BALANCE_CHECK_ERC20]: parseAbiParameters('address owner, address token, uint256 minBalance'), + // [CommandType.OWNER_CHECK_721]: parseAbiParameters('address owner, address token, uint256 id'), + // [CommandType.OWNER_CHECK_1155]: parseAbiParameters('address owner, address token, uint256 id, uint256 minBalance'), + // [CommandType.APPROVE_ERC20]: parseAbiParameters('address token, uint256 spender'), + + // NFT Markets + // [CommandType.SEAPORT_V1_5]: parseAbiParameters('uint256 value, bytes data'), + // [CommandType.SEAPORT_V1_4]: parseAbiParameters('uint256 value, bytes data'), + // @fixme: contract not implemented + // [CommandType.LOOKS_RARE_V2]: parseAbiParameters('uint256 value, bytes data'), + // [CommandType.X2Y2_721]: parseAbiParameters('uint256 value, bytes data, address recipient, address token, uint256 id'), + // [CommandType.X2Y2_1155]: parseAbiParameters( + // 'uint256 value, bytes data, address recipient, address token, uint256 id, uint256 amount', + // ), + // [CommandType.PANCAKE_NFT_WBNB]: parseAbiParameters('address collection, uint256 tokenId, uint256 price'), + // [CommandType.PANCAKE_NFT_BNB]: parseAbiParameters('address collection, uint256 tokenId, uint256 price'), + // @notice: following marketplace not supported now + // [CommandType.NFTX]: parseAbiParameters('uint256 value, bytes data'), + // [CommandType.FOUNDATION]: parseAbiParameters( + // 'uint256 value, bytes data, address recipient, address token, uint256 id' + // ), + // [CommandType.SUDOSWAP]: parseAbiParameters('uint256 value, bytes data'), + // [CommandType.NFT20]: parseAbiParameters('uint256 value, bytes data'), + // [CommandType.CRYPTOPUNKS]: parseAbiParameters('uint256 punkId, address recipient, uint256 value'), + // [CommandType.ELEMENT_MARKET]: parseAbiParameters('uint256 value, bytes data'), +} + +export type CommandUsed = keyof typeof ABI_PARAMETER + +export class RoutePlanner { + commands: Hex + + inputs: Hex[] + + constructor() { + this.commands = '0x' + this.inputs = [] + } + + addSubPlan(subplan: RoutePlanner): void { + this.addCommand(CommandType.EXECUTE_SUB_PLAN, [subplan.commands, subplan.inputs], true) + } + + addCommand( + type: TCommandType, + parameters: ABIParametersType, + allowRevert = false, + ): void { + const command = createCommand(type, parameters) + this.inputs.push(command.encodedInput) + if (allowRevert) { + if (!REVERTIBLE_COMMANDS.has(command.type)) { + throw new Error(`command type: ${command.type} cannot be allowed to revert`) + } + // eslint-disable-next-line no-bitwise + command.type |= ALLOW_REVERT_FLAG + } + + this.commands = this.commands.concat(command.type.toString(16).padStart(2, '0')) as Hex + } +} + +export type RouterCommand = { + type: CommandUsed + encodedInput: Hex +} + +export function createCommand( + type: TCommandType, + parameters: ABIParametersType, +): RouterCommand { + // const params = parameters.filter((param) => param !== null) + const encodedInput = encodeAbiParameters(ABI_PARAMETER[type], parameters as any) + return { type, encodedInput } +} diff --git a/packages/universal-router-sdk/test/__snapshots__/trade.test.ts.snap b/packages/universal-router-sdk/test/__snapshots__/trade.test.ts.snap new file mode 100644 index 0000000000000..c592f326c62a7 --- /dev/null +++ b/packages/universal-router-sdk/test/__snapshots__/trade.test.ts.snap @@ -0,0 +1,3309 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`PancakeSwap StableSwap Through Universal Router, BSC Network Only > mixed > should encodes a mixed exactInput BNB-v2->USDC-stable->USDT swap 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + }, + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "0" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0x2170Ed0880ac9A755fd29B2688956BD959F933F8", + "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "STABLE_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "57896044618658097711785492504343953926634992332820282019728792003956564819968" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "948957469284015852" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", + "0x55d398326f99059fF775485246999027B3197955" + ] + }, + { + "type": "uint256[]", + "name": "flag", + "value": [ + "2" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + } +] +`; + +exports[`PancakeSwap StableSwap Through Universal Router, BSC Network Only > mixed > should encodes a mixed exactInput ETH-v3->USDC-stable->USDT swap 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + }, + { + "command": "V3_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "0" + }, + { + "type": "bytes", + "name": "path", + "value": "0x2170ed0880ac9a755fd29b2688956bd959f933f80009c48ac76a51cc950d9822d68b83fe1ad97b32cd580d" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "STABLE_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "57896044618658097711785492504343953926634992332820282019728792003956564819968" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "948957469284015852" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", + "0x55d398326f99059fF775485246999027B3197955" + ] + }, + { + "type": "uint256[]", + "name": "flag", + "value": [ + "2" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + } +] +`; + +exports[`PancakeSwap StableSwap Through Universal Router, BSC Network Only > mixed > should encodes a mixed exactInput USDT-stable->USDC-v2->BNB swap 1`] = ` +[ + { + "command": "STABLE_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0xd99c7F6C65857AC913a8f880A4cb84032AB2FC5b" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "0" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0x55d398326f99059fF775485246999027B3197955", + "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d" + ] + }, + { + "type": "uint256[]", + "name": "flag", + "value": [ + "2" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "57896044618658097711785492504343953926634992332820282019728792003956564819968" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "949904999" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", + "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "949904999" + } + ] + } +] +`; + +exports[`PancakeSwap StableSwap Through Universal Router, BSC Network Only > mixed > should encodes a mixed exactInput USDT-stable->USDC-v3->BNB swap 1`] = ` +[ + { + "command": "STABLE_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "0" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0x55d398326f99059fF775485246999027B3197955", + "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d" + ] + }, + { + "type": "uint256[]", + "name": "flag", + "value": [ + "2" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "V3_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "57896044618658097711785492504343953926634992332820282019728792003956564819968" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "949904999" + }, + { + "type": "bytes", + "name": "path", + "value": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d0009c4bb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "949904999" + } + ] + } +] +`; + +exports[`PancakeSwap StableSwap Through Universal Router, BSC Network Only > multi-route > should encode a multi-route exactInput v2 USDT-USDC & v3 USDT-USDC & stable USDT-USDC 1`] = ` +[ + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "949999999" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0x55d398326f99059fF775485246999027B3197955", + "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "V3_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "951904760" + }, + { + "type": "bytes", + "name": "path", + "value": "0x55d398326f99059ff775485246999027b31979550001f48ac76a51cc950d9822d68b83fe1ad97b32cd580d" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "STABLE_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "952380952" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0x55d398326f99059fF775485246999027B3197955", + "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d" + ] + }, + { + "type": "uint256[]", + "name": "flag", + "value": [ + "2" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "SWEEP", + "args": [ + { + "type": "address", + "name": "token", + "value": "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d" + }, + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "2854285712" + } + ] + } +] +`; + +exports[`PancakeSwap StableSwap Through Universal Router, BSC Network Only > should encode a exactInput USDT->USDC->BUSD swap 1`] = ` +[ + { + "command": "STABLE_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "952380952" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0x55d398326f99059fF775485246999027B3197955", + "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", + "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56" + ] + }, + { + "type": "uint256[]", + "name": "flag", + "value": [ + "2", + "2" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + } +] +`; + +exports[`PancakeSwap StableSwap Through Universal Router, BSC Network Only > should encode a single exactInput USDT->USDC swap 1`] = ` +[ + { + "command": "STABLE_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "952380952" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0x55d398326f99059fF775485246999027B3197955", + "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d" + ] + }, + { + "type": "uint256[]", + "name": "flag", + "value": [ + "2" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + } +] +`; + +exports[`PancakeSwap StableSwap Through Universal Router, BSC Network Only > should encode a single exactInput USDT->USDC swap, with fee 1`] = ` +[ + { + "command": "STABLE_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "909090909" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0x55d398326f99059fF775485246999027B3197955", + "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d" + ] + }, + { + "type": "uint256[]", + "name": "flag", + "value": [ + "2" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "PAY_PORTION", + "args": [ + { + "type": "address", + "name": "token", + "value": "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d" + }, + { + "type": "address", + "name": "recipient", + "value": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + { + "type": "uint256", + "name": "bips", + "value": "500" + } + ] + }, + { + "command": "SWEEP", + "args": [ + { + "type": "address", + "name": "token", + "value": "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d" + }, + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "909090909" + } + ] + } +] +`; + +exports[`PancakeSwap StableSwap Through Universal Router, BSC Network Only > should encode a single exactInput USDT->USDC swap, with permit2 1`] = ` +[ + { + "command": "PERMIT2_PERMIT", + "args": [ + { + "type": "tuple", + "name": "permitSingle", + "value": { + "details": { + "token": "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", + "amount": "1461501637330902918203684832716283019655932542975", + "expiration": 281474976710655, + "nonce": 0 + }, + "spender": "0x1A0A18AC4BECDDbd6389559687d1A73d8927E416", + "sigDeadline": "281474976710655" + } + }, + { + "type": "bytes", + "name": "data", + "value": "0x777406b366e3539754d5ea1056d11c8f7482c7f095b48cc3520051169c6e0e1f49116cbe67f45bfe7c1af7599e7c6c5c93565294bcc0fb2f42699d2845b11da41b" + } + ] + }, + { + "command": "STABLE_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "952380952" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0x55d398326f99059fF775485246999027B3197955", + "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d" + ] + }, + { + "type": "uint256[]", + "name": "flag", + "value": [ + "2" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > mixed > should encodes a mixed exactInput ETH-v2->USDC-v2->USDT swap 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + }, + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "945740605931802096" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xdAC17F958D2ee523a2206206994597C13D831ec7" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > mixed > should encodes a mixed exactInput ETH-v2->USDC-v3->USDT swap 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + }, + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "0" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "V3_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "57896044618658097711785492504343953926634992332820282019728792003956564819968" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "947634940925779854" + }, + { + "type": "bytes", + "name": "path", + "value": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f4dac17f958d2ee523a2206206994597c13d831ec7" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > mixed > should encodes a mixed exactInput ETH-v3->USDC-v2->USDT swap 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + }, + { + "command": "V3_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0xaD6B7d0beE456e89B04064f0F750775Cde0F4c9b" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "0" + }, + { + "type": "bytes", + "name": "path", + "value": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20009c4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "57896044618658097711785492504343953926634992332820282019728792003956564819968" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "945740605931802096" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xdAC17F958D2ee523a2206206994597C13D831ec7" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > mixed > should encodes a mixed exactInput ETH-v3->USDC-v3->USDT swap 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + }, + { + "command": "V3_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "947634940925779854" + }, + { + "type": "bytes", + "name": "path", + "value": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20009c4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f4dac17f958d2ee523a2206206994597c13d831ec7" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > mixed > should encodes a mixed exactInput USDT-v2->USDC-v3->ETH swap 1`] = ` +[ + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "0" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "V3_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "57896044618658097711785492504343953926634992332820282019728792003956564819968" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "947624998" + }, + { + "type": "bytes", + "name": "path", + "value": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb480009c4c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "947624998" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > multi-route > should encode a split exactInput with 2 routes: v2 ETH-USDC & v3 ETH-USDC 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "2000000000000000000" + } + ] + }, + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "949053319313984300" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "V3_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "949053319313984300" + }, + { + "type": "bytes", + "name": "path", + "value": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20009c4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > multi-route > should encode a split exactInput with 3 routes: v2 ETH-USDC & v3 ETH-USDC & v3 ETH-USDC 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "2000000000000000000" + } + ] + }, + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "949053319313984300" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "V3_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "500000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "474763211848090763" + }, + { + "type": "bytes", + "name": "path", + "value": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20009c4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "V3_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "500000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "475714642559761711" + }, + { + "type": "bytes", + "name": "path", + "value": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "SWEEP", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + }, + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1899531173721836775" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode a exactInput ETH->USDC->USDT Swap, with fee, with recipient 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + }, + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "902752396571265637" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xdAC17F958D2ee523a2206206994597C13D831ec7" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "PAY_PORTION", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xdAC17F958D2ee523a2206206994597C13D831ec7" + }, + { + "type": "address", + "name": "recipient", + "value": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + { + "type": "uint256", + "name": "bips", + "value": "500" + } + ] + }, + { + "command": "SWEEP", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xdAC17F958D2ee523a2206206994597C13D831ec7" + }, + { + "type": "address", + "name": "recipient", + "value": "0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "902752396571265637" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode a exactInput ETH->USDC->USDT Swap, with fee, without recipient 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + }, + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "902752396571265637" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xdAC17F958D2ee523a2206206994597C13D831ec7" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "PAY_PORTION", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xdAC17F958D2ee523a2206206994597C13D831ec7" + }, + { + "type": "address", + "name": "recipient", + "value": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + { + "type": "uint256", + "name": "bips", + "value": "500" + } + ] + }, + { + "command": "SWEEP", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xdAC17F958D2ee523a2206206994597C13D831ec7" + }, + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "902752396571265637" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode a exactInput ETH->USDC->USDT Swap, without fee, with recipient 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + }, + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "945740605931802096" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xdAC17F958D2ee523a2206206994597C13D831ec7" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode a exactInput ETH->USDC->USDT Swap, without fee, without recipient 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + }, + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "945740605931802096" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xdAC17F958D2ee523a2206206994597C13D831ec7" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode a single exactInput ETH->USDC Swap, with fee, with recipient 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + }, + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "905914532072439559" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "PAY_PORTION", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + }, + { + "type": "address", + "name": "recipient", + "value": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + { + "type": "uint256", + "name": "bips", + "value": "500" + } + ] + }, + { + "command": "SWEEP", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + }, + { + "type": "address", + "name": "recipient", + "value": "0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "905914532072439559" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode a single exactInput ETH->USDC Swap, with fee, without recipient 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + }, + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "905914532072439559" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "PAY_PORTION", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + }, + { + "type": "address", + "name": "recipient", + "value": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + { + "type": "uint256", + "name": "bips", + "value": "500" + } + ] + }, + { + "command": "SWEEP", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + }, + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "905914532072439559" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode a single exactInput ETH->USDC Swap, without fee, with recipient 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + }, + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "949053319313984300" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode a single exactInput ETH->USDC Swap, without fee, without recipient 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + }, + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "949053319313984300" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode a single exactInput USDC->ETH Swap 1`] = ` +[ + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "949999999" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "949999999" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode a single exactInput USDC->ETH Swap, with fee 1`] = ` +[ + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "906818180" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "PAY_PORTION", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + }, + { + "type": "address", + "name": "recipient", + "value": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + { + "type": "uint256", + "name": "bips", + "value": "500" + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "906818180" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode a single exactInput USDC->ETH Swap, with fee, with recipient 1`] = ` +[ + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "906818180" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "PAY_PORTION", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + }, + { + "type": "address", + "name": "recipient", + "value": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + { + "type": "uint256", + "name": "bips", + "value": "500" + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "906818180" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode a single exactInput USDC->ETH Swap, with recipient 1`] = ` +[ + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "949999999" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "949999999" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode a single exactInput USDC->ETH swap, with EIP-2098 permit 1`] = ` +[ + { + "command": "PERMIT2_PERMIT", + "args": [ + { + "type": "tuple", + "name": "permitSingle", + "value": { + "details": { + "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "amount": "1461501637330902918203684832716283019655932542975", + "expiration": 281474976710655, + "nonce": 0 + }, + "spender": "0x65b382653f7C31bC0Af67f188122035461ec9C76", + "sigDeadline": "281474976710655" + } + }, + { + "type": "bytes", + "name": "data", + "value": "0x92ff2b035c8005213bd910849532da0f4adde9e35393c8ed4872db90eef2c153492dec30a70476e98f21d28398396669fbef6f9785c903ef83810673ec96fc8d" + } + ] + }, + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "949999999" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "949999999" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode a single exactInput USDC->ETH swap, with permit2 1`] = ` +[ + { + "command": "PERMIT2_PERMIT", + "args": [ + { + "type": "tuple", + "name": "permitSingle", + "value": { + "details": { + "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "amount": "1461501637330902918203684832716283019655932542975", + "expiration": 281474976710655, + "nonce": 0 + }, + "spender": "0x65b382653f7C31bC0Af67f188122035461ec9C76", + "sigDeadline": "281474976710655" + } + }, + { + "type": "bytes", + "name": "data", + "value": "0x92ff2b035c8005213bd910849532da0f4adde9e35393c8ed4872db90eef2c153492dec30a70476e98f21d28398396669fbef6f9785c903ef83810673ec96fc8d1b" + } + ] + }, + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "949999999" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "949999999" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode exactInput USDT->USDC->ETH swap 1`] = ` +[ + { + "command": "V2_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "947624999" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xdAC17F958D2ee523a2206206994597C13D831ec7", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "947624999" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode exactOutput ETH->USDC swap 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1053685264211580002" + } + ] + }, + { + "command": "V2_SWAP_EXACT_OUT", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountOut", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountInMax", + "value": "1053685264211580002" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "0" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode exactOutput ETH->USDC swap, with fee 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1103860752983560002" + } + ] + }, + { + "command": "V2_SWAP_EXACT_OUT", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountOut", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountInMax", + "value": "1103860752983560002" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "PAY_PORTION", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + }, + { + "type": "address", + "name": "recipient", + "value": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + { + "type": "uint256", + "name": "bips", + "value": "500" + } + ] + }, + { + "command": "SWEEP", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + }, + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "950000000000000000" + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "0" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode exactOutput ETH->USDC swap, with fee, with recipient 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1103860752983560002" + } + ] + }, + { + "command": "V2_SWAP_EXACT_OUT", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountOut", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountInMax", + "value": "1103860752983560002" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "PAY_PORTION", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + }, + { + "type": "address", + "name": "recipient", + "value": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + { + "type": "uint256", + "name": "bips", + "value": "500" + } + ] + }, + { + "command": "SWEEP", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + }, + { + "type": "address", + "name": "recipient", + "value": "0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "950000000000000000" + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "0" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v2 > should encode exactOutput USDC-ETH swap 1`] = ` +[ + { + "command": "V2_SWAP_EXACT_OUT", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountOut", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountInMax", + "value": "1053685264211580002" + }, + { + "type": "address[]", + "name": "path", + "value": [ + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + ] + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v3 > should encode a exactInput ETH->USDC->USDT swap 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + }, + { + "command": "V3_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "947634940925779854" + }, + { + "type": "bytes", + "name": "path", + "value": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20009c4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f4dac17f958d2ee523a2206206994597c13d831ec7" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v3 > should encode a exactOutput ETH->USDC->USDT swap 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1053158161" + } + ] + }, + { + "command": "V3_SWAP_EXACT_OUT", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountOut", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountInMax", + "value": "1053158161" + }, + { + "type": "bytes", + "name": "path", + "value": "0xdac17f958d2ee523a2206206994597c13d831ec70001f4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480009c4c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "0" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v3 > should encode a exactOutput USDT->USDC->ETH swap 1`] = ` +[ + { + "command": "V3_SWAP_EXACT_OUT", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountOut", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountInMax", + "value": "1055271345507736707" + }, + { + "type": "bytes", + "name": "path", + "value": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20009c4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f4dac17f958d2ee523a2206206994597c13d831ec7" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v3 > should encode a single exactInput ETH->USDC swap 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + }, + { + "command": "V3_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "949053319313984300" + }, + { + "type": "bytes", + "name": "path", + "value": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20009c4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v3 > should encode a single exactInput ETH->USDC swap, with a fee 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + }, + { + "command": "V3_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "905914532072439559" + }, + { + "type": "bytes", + "name": "path", + "value": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20009c4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "PAY_PORTION", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + }, + { + "type": "address", + "name": "recipient", + "value": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + { + "type": "uint256", + "name": "bips", + "value": "500" + } + ] + }, + { + "command": "SWEEP", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + }, + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "905914532072439559" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v3 > should encode a single exactInput USDC->ETH swap 1`] = ` +[ + { + "command": "V3_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "949999999" + }, + { + "type": "bytes", + "name": "path", + "value": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb480009c4c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "949999999" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v3 > should encode a single exactInput USDC->ETH swap, with a fee 1`] = ` +[ + { + "command": "V3_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "906818180" + }, + { + "type": "bytes", + "name": "path", + "value": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb480009c4c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "PAY_PORTION", + "args": [ + { + "type": "address", + "name": "token", + "value": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" + }, + { + "type": "address", + "name": "recipient", + "value": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + { + "type": "uint256", + "name": "bips", + "value": "500" + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "906818180" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v3 > should encode a single exactInput USDC->ETH swap, with permit2 1`] = ` +[ + { + "command": "PERMIT2_PERMIT", + "args": [ + { + "type": "tuple", + "name": "permitSingle", + "value": { + "details": { + "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "amount": "1461501637330902918203684832716283019655932542975", + "expiration": 281474976710655, + "nonce": 0 + }, + "spender": "0x65b382653f7C31bC0Af67f188122035461ec9C76", + "sigDeadline": "281474976710655" + } + }, + { + "type": "bytes", + "name": "data", + "value": "0x92ff2b035c8005213bd910849532da0f4adde9e35393c8ed4872db90eef2c153492dec30a70476e98f21d28398396669fbef6f9785c903ef83810673ec96fc8d1b" + } + ] + }, + { + "command": "V3_SWAP_EXACT_IN", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountIn", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountOutMin", + "value": "949999999" + }, + { + "type": "bytes", + "name": "path", + "value": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb480009c4c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "949999999" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v3 > should encode a single exactOutput ETH->USDC swap 1`] = ` +[ + { + "command": "WRAP_ETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1052631580" + } + ] + }, + { + "command": "V3_SWAP_EXACT_OUT", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountOut", + "value": "1000000000" + }, + { + "type": "uint256", + "name": "amountInMax", + "value": "1052631580" + }, + { + "type": "bytes", + "name": "path", + "value": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb480009c4c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": false + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "0" + } + ] + } +] +`; + +exports[`PancakeSwap Universal Router Trade > v3 > should encode a single exactOutput USDC->ETH swap 1`] = ` +[ + { + "command": "V3_SWAP_EXACT_OUT", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000002" + }, + { + "type": "uint256", + "name": "amountOut", + "value": "1000000000000000000" + }, + { + "type": "uint256", + "name": "amountInMax", + "value": "1053685264211580003" + }, + { + "type": "bytes", + "name": "path", + "value": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20009c4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" + }, + { + "type": "bool", + "name": "payerIsUser", + "value": true + } + ] + }, + { + "command": "UNWRAP_WETH", + "args": [ + { + "type": "address", + "name": "recipient", + "value": "0x0000000000000000000000000000000000000001" + }, + { + "type": "uint256", + "name": "amountMin", + "value": "1000000000000000000" + } + ] + } +] +`; diff --git a/packages/universal-router-sdk/test/fixtures/address.ts b/packages/universal-router-sdk/test/fixtures/address.ts new file mode 100644 index 0000000000000..e1678497c2196 --- /dev/null +++ b/packages/universal-router-sdk/test/fixtures/address.ts @@ -0,0 +1,185 @@ +import { ChainId } from '@pancakeswap/chains' +import { getPermit2Address } from '@pancakeswap/permit2-sdk' +import { CurrencyAmount, ERC20Token, Pair, Percent } from '@pancakeswap/sdk' +import { PoolType, SmartRouter, StablePool, V2Pool, V3Pool } from '@pancakeswap/smart-router/evm' +import { + DEPLOYER_ADDRESSES, + FeeAmount, + Pool, + TICK_SPACINGS, + TickMath, + computePoolAddress, + encodeSqrtRatioX96, + nearestUsableTick, +} from '@pancakeswap/v3-sdk' +import { getUniversalRouterAddress } from '../../src' +import { Provider } from './clients' +import { BUSD, CAKE, ETHER, USDC, USDT, WBNB, WETH9 } from './constants/tokens' + +const fixtureTokensAddresses = (chainId: ChainId) => { + return { + ETHER: ETHER.on(chainId), + USDC: USDC[chainId], + USDT: USDT[chainId], + CAKE: CAKE[chainId], + WETH: WETH9[chainId], + BUSD: BUSD[chainId], + WBNB: WBNB[chainId], + } +} + +export const getStablePool = async ( + tokenA: ERC20Token, + tokenB: ERC20Token, + provider: Provider, + liquidity?: bigint, +): Promise => { + const pools = await SmartRouter.getStableCandidatePools({ + currencyA: tokenA, + currencyB: tokenB, + onChainProvider: provider, + }) + + if (!pools.length) throw new ReferenceError(`No Stable Pool found with token ${tokenA.symbol}/${tokenB.symbol}`) + const stablePool = pools[0] as StablePool + + if (liquidity) { + stablePool.balances[0] = CurrencyAmount.fromRawAmount(stablePool.balances[0].currency, liquidity) + stablePool.balances[1] = CurrencyAmount.fromRawAmount(stablePool.balances[1].currency, liquidity) + } + + return stablePool +} + +const getPair = (tokenA: ERC20Token, tokenB: ERC20Token, liquidity: bigint) => { + // eslint-disable-next-line no-console + console.assert(tokenA.chainId === tokenB.chainId, 'Invalid token pair') + + // @notice: to match off-chain testing ,we can use fixed liquid to match snapshots + const reserve0 = liquidity + const reserve1 = liquidity + + const [token0, token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA] + + return new Pair(CurrencyAmount.fromRawAmount(token0, reserve0), CurrencyAmount.fromRawAmount(token1, reserve1)) +} + +export const convertPoolToV3Pool = (pool: Pool): V3Pool => { + return { + type: PoolType.V3, + token0: pool.token0, + token1: pool.token1, + fee: pool.fee, + sqrtRatioX96: pool.sqrtRatioX96, + liquidity: pool.liquidity, + tick: pool.tickCurrent, + address: Pool.getAddress(pool.token0, pool.token1, pool.fee), + token0ProtocolFee: new Percent(0, 100), + token1ProtocolFee: new Percent(0, 100), + } +} +export const convertPairToV2Pool = (pair: Pair): V2Pool => ({ + type: PoolType.V2, + reserve0: pair.reserve0, + reserve1: pair.reserve1, +}) + +export const convertV3PoolToSDKPool = ({ token0, token1, fee, sqrtRatioX96, liquidity, tick, ticks }: V3Pool) => + new Pool(token0.wrapped, token1.wrapped, fee, sqrtRatioX96, liquidity, tick, ticks) +export const convertV2PoolToSDKPool = ({ reserve1, reserve0 }: V2Pool) => new Pair(reserve0.wrapped, reserve1.wrapped) + +const fixturePool = ({ + tokenA, + tokenB, + feeAmount = FeeAmount.MEDIUM, + reserve, +}: { + tokenA: ERC20Token + tokenB: ERC20Token + feeAmount: FeeAmount + reserve: + | { + reserve0: bigint + reserve1: bigint + } + | bigint +}) => { + // eslint-disable-next-line no-console + console.assert(tokenA.chainId === tokenB.chainId, 'Invalid token pair') + + const reserve0 = typeof reserve === 'bigint' ? reserve : reserve.reserve0 + const reserve1 = typeof reserve === 'bigint' ? reserve : reserve.reserve1 + const sqrtPriceX96 = encodeSqrtRatioX96(reserve0, reserve1) + // fixture to full range liquidity + const liquidity = BigInt(Math.floor(Math.sqrt(Number(reserve0 * reserve1)))) + + return new Pool(tokenA, tokenB, feeAmount, sqrtPriceX96, liquidity, TickMath.getTickAtSqrtRatio(sqrtPriceX96), [ + { + index: nearestUsableTick(TickMath.MIN_TICK, TICK_SPACINGS[feeAmount]), + liquidityNet: liquidity, + liquidityGross: liquidity, + }, + { + index: nearestUsableTick(TickMath.MAX_TICK, TICK_SPACINGS[feeAmount]), + liquidityNet: -liquidity, + liquidityGross: liquidity, + }, + ]) +} + +export const fixtureAddresses = async (chainId: ChainId, liquidity: bigint) => { + const tokens = fixtureTokensAddresses(chainId) + // eslint-disable-next-line @typescript-eslint/no-shadow + const { USDC, USDT, WETH, WBNB } = tokens + + const v2Pairs = { + WETH_USDC_V2: getPair(WETH, USDC, liquidity), + WBNB_USDC_V2: getPair(WBNB, USDC, liquidity), + USDC_USDT_V2: getPair(USDT, USDC, liquidity), + } + + const v3Pools = { + WETH_USDC_V3_MEDIUM: fixturePool({ + tokenA: WETH, + tokenB: USDC, + feeAmount: FeeAmount.MEDIUM, + reserve: liquidity, + }), + WETH_USDC_V3_MEDIUM_ADDRESS: computePoolAddress({ + deployerAddress: DEPLOYER_ADDRESSES[chainId], + tokenA: WETH, + tokenB: USDC, + fee: FeeAmount.MEDIUM, + }), + WETH_USDC_V3_LOW: fixturePool({ + tokenA: WETH, + tokenB: USDC, + reserve: liquidity, + feeAmount: FeeAmount.LOW, + }), + WBNB_USDC_V3_MEDIUM: fixturePool({ + tokenA: WBNB, + tokenB: USDC, + feeAmount: FeeAmount.MEDIUM, + reserve: liquidity, + }), + USDC_USDT_V3_LOW: fixturePool({ tokenA: USDC, tokenB: USDT, feeAmount: FeeAmount.LOW, reserve: liquidity }), + USDC_USDT_V3_LOW_ADDRESS: computePoolAddress({ + deployerAddress: DEPLOYER_ADDRESSES[chainId], + tokenA: USDC, + tokenB: USDT, + fee: FeeAmount.LOW, + }), + } + + const UNIVERSAL_ROUTER = getUniversalRouterAddress(chainId) + const PERMIT2 = getPermit2Address(chainId) + + return { + ...tokens, + ...v2Pairs, + ...v3Pools, + UNIVERSAL_ROUTER, + PERMIT2, + } +} diff --git a/packages/universal-router-sdk/test/fixtures/clients.ts b/packages/universal-router-sdk/test/fixtures/clients.ts new file mode 100644 index 0000000000000..41f5f8085cf54 --- /dev/null +++ b/packages/universal-router-sdk/test/fixtures/clients.ts @@ -0,0 +1,33 @@ +import { ChainId } from '@pancakeswap/chains' +import { PublicClient, createPublicClient, http, Chain, Client, WalletClient, createWalletClient } from 'viem' +import { mnemonicToAccount } from 'viem/accounts' +import { CHAINS } from './constants/chains' + +const account = mnemonicToAccount('test test test test test test test test test test test junk') + +const createClients = (chains: Chain[]) => { + return (type: 'Wallet' | 'Public'): Record => { + return chains.reduce((prev, cur) => { + const clientConfig = { chain: cur, transport: http() } + const client = + type === 'Wallet' ? createWalletClient({ ...clientConfig, account }) : createPublicClient(clientConfig) + return { + ...prev, + [cur.id]: client, + } + }, {} as Record) + } +} + +const publicClients = createClients(CHAINS)('Public') +const walletClients = createClients(CHAINS)('Wallet') + +export const getPublicClient = ({ chainId }: { chainId?: ChainId }) => { + return publicClients[chainId!] +} + +export type Provider = ({ chainId }: { chainId?: ChainId }) => PublicClient + +export const getWalletClient = ({ chainId }: { chainId?: ChainId }) => { + return walletClients[chainId!] +} diff --git a/packages/universal-router-sdk/test/fixtures/constants/addresses.ts b/packages/universal-router-sdk/test/fixtures/constants/addresses.ts new file mode 100644 index 0000000000000..021088dcb04b4 --- /dev/null +++ b/packages/universal-router-sdk/test/fixtures/constants/addresses.ts @@ -0,0 +1,2 @@ +export { FACTORY_ADDRESS_MAP as V2_FACTORY_ADDRESSES } from '@pancakeswap/sdk' +export { FACTORY_ADDRESSES as V3_FACTORY_ADDRESSES } from '@pancakeswap/v3-sdk' diff --git a/packages/universal-router-sdk/test/fixtures/constants/chains.ts b/packages/universal-router-sdk/test/fixtures/constants/chains.ts new file mode 100644 index 0000000000000..2b22c193d8b3c --- /dev/null +++ b/packages/universal-router-sdk/test/fixtures/constants/chains.ts @@ -0,0 +1,133 @@ +import { ChainId } from '@pancakeswap/chains' +import { + bsc, + bscTestnet, + goerli, + mainnet, + zkSync, + zkSyncTestnet, + polygonZkEvmTestnet, + polygonZkEvm, + lineaTestnet, + arbitrum, + arbitrumGoerli, + base, + baseGoerli, + scrollSepolia as scrollSepolia_, + Chain, +} from 'viem/chains' + +const scrollSepolia = { + ...scrollSepolia_, + contracts: { + multicall3: { + address: '0xcA11bde05977b3631167028862bE2a173976CA11', + blockCreated: 9473, + }, + }, +} as const satisfies Chain + +export const opbnbTestnet = { + id: 5_611, + name: 'opBNB Testnet', + network: 'opbnb-testnet', + nativeCurrency: bscTestnet.nativeCurrency, + rpcUrls: { + default: { + http: ['https://opbnb-testnet-rpc.bnbchain.org'], + }, + public: { + http: ['https://opbnb-testnet-rpc.bnbchain.org'], + }, + }, + blockExplorers: { + default: { + name: 'opBNBScan', + url: 'https://opbnbscan.com', + }, + }, + contracts: { + multicall3: { + address: '0xcA11bde05977b3631167028862bE2a173976CA11', + blockCreated: 3705108, + }, + }, + testnet: true, +} as const satisfies Chain + +export const linea = { + id: 59_144, + name: 'Linea Mainnet', + network: 'linea-mainnet', + nativeCurrency: { name: 'Linea Ether', symbol: 'ETH', decimals: 18 }, + rpcUrls: { + infura: { + http: ['https://linea-mainnet.infura.io/v3'], + webSocket: ['wss://linea-mainnet.infura.io/ws/v3'], + }, + default: { + http: ['https://rpc.linea.build'], + webSocket: ['wss://rpc.linea.build'], + }, + public: { + http: ['https://rpc.linea.build'], + webSocket: ['wss://rpc.linea.build'], + }, + }, + blockExplorers: { + default: { + name: 'Etherscan', + url: 'https://lineascan.build', + }, + etherscan: { + name: 'Etherscan', + url: 'https://lineascan.build', + }, + }, + contracts: { + multicall3: { + address: '0xcA11bde05977b3631167028862bE2a173976CA11', + blockCreated: 42, + }, + }, + testnet: false, +} as const satisfies Chain + +/** + * Controls some L2 specific behavior, e.g. slippage tolerance, special UI behavior. + * The expectation is that all of these networks have immediate transaction confirmation. + */ +export const L2_CHAIN_IDS: ChainId[] = [ + ChainId.ARBITRUM_ONE, + ChainId.ARBITRUM_GOERLI, + ChainId.POLYGON_ZKEVM, + ChainId.POLYGON_ZKEVM_TESTNET, + ChainId.ZKSYNC, + ChainId.ZKSYNC_TESTNET, + ChainId.LINEA_TESTNET, + ChainId.LINEA, + ChainId.BASE, + ChainId.BASE_TESTNET, + ChainId.OPBNB_TESTNET, +] + +export const CHAINS = [ + bsc, + mainnet, + bscTestnet, + goerli, + polygonZkEvm, + polygonZkEvmTestnet, + zkSync, + zkSyncTestnet, + arbitrum, + arbitrumGoerli, + linea, + lineaTestnet, + arbitrumGoerli, + arbitrum, + base, + baseGoerli, + opbnbTestnet, + scrollSepolia, +] diff --git a/packages/universal-router-sdk/test/fixtures/constants/tokens.ts b/packages/universal-router-sdk/test/fixtures/constants/tokens.ts new file mode 100644 index 0000000000000..63c1bd4a12a55 --- /dev/null +++ b/packages/universal-router-sdk/test/fixtures/constants/tokens.ts @@ -0,0 +1,65 @@ +import { ERC20Token, Ether } from '@pancakeswap/sdk' +import { ChainId } from '@pancakeswap/chains' +import * as Tokens from '@pancakeswap/tokens' +import { zeroAddress } from 'viem' + +export { WETH9 } from '@pancakeswap/sdk' + +const MockToken: Record = (() => { + const tokens: Record = {} as Record + + for (const chainId in ChainId) { + if (!Number.isNaN(Number(chainId))) { + const id = Number(chainId) as unknown as ChainId + tokens[id] = new ERC20Token(id, zeroAddress, 18, 'MockToken') + } + } + + return tokens +})() + +export const ETHER = { + on(chainId: ChainId): Ether { + return Ether.onChain(chainId) + }, +} + +export const CAKE = { + ...MockToken, + ...Tokens.CAKE, + + // @notice: temporary ignore missed testnet address + [ChainId.OPBNB_TESTNET]: new ERC20Token(ChainId.OPBNB_TESTNET, zeroAddress, 0, 'CAKE'), + [ChainId.SCROLL_SEPOLIA]: new ERC20Token(ChainId.SCROLL_SEPOLIA, zeroAddress, 0, 'CAKE'), +} + +export const USDT = { + ...Tokens.USDT, + [ChainId.BSC_TESTNET]: new ERC20Token(ChainId.BSC_TESTNET, '0x337610d27c682E347C9cD60BD4b3b107C9d34dDd', 6, 'USDT'), + // @notice: use USDC in goerli instead of + [ChainId.GOERLI]: new ERC20Token(ChainId.GOERLI, zeroAddress, 6, 'USDT'), + + // @notice: temporary ignore missed testnet address + [ChainId.ZKSYNC_TESTNET]: new ERC20Token(ChainId.ZKSYNC_TESTNET, zeroAddress, 6, 'USDT'), + [ChainId.ARBITRUM_GOERLI]: new ERC20Token(ChainId.ARBITRUM_GOERLI, zeroAddress, 6, 'USDT'), + [ChainId.SCROLL_SEPOLIA]: new ERC20Token(ChainId.SCROLL_SEPOLIA, zeroAddress, 6, 'USDT'), + [ChainId.LINEA_TESTNET]: new ERC20Token(ChainId.LINEA_TESTNET, zeroAddress, 6, 'USDT'), + [ChainId.BASE]: new ERC20Token(ChainId.BASE, zeroAddress, 6, 'USDT'), + [ChainId.BASE_TESTNET]: new ERC20Token(ChainId.BASE_TESTNET, zeroAddress, 6, 'USDT'), +} +export const USDC = { + ...MockToken, + ...Tokens.USDC, + // @notice: temporary ignore missed testnet address + [ChainId.POLYGON_ZKEVM_TESTNET]: new ERC20Token(ChainId.POLYGON_ZKEVM_TESTNET, zeroAddress, 6, '_USDC'), +} + +export const BUSD = { + ...MockToken, + ...Tokens.BUSD, +} + +export const WBNB = { + ...MockToken, + [ChainId.BSC]: Tokens.bscTokens.wbnb, +} diff --git a/packages/universal-router-sdk/test/trade.test.ts b/packages/universal-router-sdk/test/trade.test.ts new file mode 100644 index 0000000000000..ddb9582d86a77 --- /dev/null +++ b/packages/universal-router-sdk/test/trade.test.ts @@ -0,0 +1,2313 @@ +import { ChainId } from '@pancakeswap/chains' +import { + CurrencyAmount, + ERC20Token, + Ether, + Pair, + Percent, + TradeType, + Route as V2Route, + Trade as V2Trade, +} from '@pancakeswap/sdk' +import { PoolType, SmartRouter, SmartRouterTrade, V2Pool, V3Pool } from '@pancakeswap/smart-router/evm' +import { Pool, Route as V3Route, Trade as V3Trade } from '@pancakeswap/v3-sdk' +import { Address, WalletClient, isHex, parseEther, parseUnits, stringify } from 'viem' +import { beforeEach, describe, expect, it } from 'vitest' +import { PancakeSwapUniversalRouter, ROUTER_AS_RECIPIENT } from '../src' +import { CONTRACT_BALANCE, SENDER_AS_RECIPIENT } from '../src/constants' +import { PancakeSwapOptions, Permit2Signature } from '../src/entities/types' +import { CommandType } from '../src/utils/routerCommands' +import { convertPoolToV3Pool, fixtureAddresses, getStablePool } from './fixtures/address' +import { getPublicClient, getWalletClient } from './fixtures/clients' +import { buildMixedRouteTrade, buildStableTrade, buildV2Trade, buildV3Trade } from './utils/buildTrade' +import { decodeUniversalCalldata } from './utils/calldataDecode' +import { makePermit } from './utils/permit' + +const TEST_RECIPIENT_ADDRESS = '0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa' as const +const TEST_FEE_RECIPIENT_ADDRESS = '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB' as const +const PERMIT2_SIG = + '0x92ff2b035c8005213bd910849532da0f4adde9e35393c8ed4872db90eef2c153492dec30a70476e98f21d28398396669fbef6f9785c903ef83810673ec96fc8d1b' + +const swapOptions = (options: Partial): PancakeSwapOptions => { + let slippageTolerance = new Percent(5, 100) + if (options.fee) slippageTolerance = slippageTolerance.add(options.fee.fee) + return { + slippageTolerance, + ...options, + } +} + +const TEST_FEE = 500n + +const feeOptions = { + recipient: TEST_FEE_RECIPIENT_ADDRESS, + fee: new Percent(TEST_FEE, 10000n), +} + +describe('PancakeSwap Universal Router Trade', () => { + const chainId = ChainId.ETHEREUM + const liquidity = parseEther('1000') + + let wallet: WalletClient + + let ETHER: Ether + let USDC: ERC20Token + let USDT: ERC20Token + let WETH_USDC_V2: Pair + let USDC_USDT_V2: Pair + let WETH_USDC_V3_MEDIUM: Pool + let WETH_USDC_V3_LOW: Pool + let USDC_USDT_V3_LOW: Pool + let UNIVERSAL_ROUTER: Address + let PERMIT2: Address + + expect.addSnapshotSerializer({ + serialize(val) { + return stringify(decodeUniversalCalldata(val), null, 2) + }, + test(val) { + return val && isHex(val) + }, + }) + + beforeEach(async () => { + ;({ + UNIVERSAL_ROUTER, + PERMIT2, + ETHER, + USDC, + USDT, + WETH_USDC_V2, + USDC_USDT_V2, + USDC_USDT_V3_LOW, + WETH_USDC_V3_LOW, + WETH_USDC_V3_MEDIUM, + } = await fixtureAddresses(chainId, liquidity)) + wallet = getWalletClient({ chainId }) + }) + + describe('v2', () => { + it('should encode a single exactInput ETH->USDC Swap, without fee, without recipient', async () => { + const amountIn = parseEther('1') + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2], ETHER, USDC), + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + ) + const v2Pool: V2Pool = { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + } + const trade = buildV2Trade(v2Trade, [v2Pool]) + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(2) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(v2Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + }) + it('should encode a single exactInput ETH->USDC Swap, without fee, with recipient', async () => { + const amountIn = parseEther('1') + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2], ETHER, USDC), + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + ) + const v2Pool: V2Pool = { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + } + const trade = buildV2Trade(v2Trade, [v2Pool]) + const options = swapOptions({ + recipient: TEST_RECIPIENT_ADDRESS, + }) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(TEST_RECIPIENT_ADDRESS) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(v2Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + }) + it('should encode a single exactInput ETH->USDC Swap, with fee, without recipient', async () => { + const amountIn = parseEther('1') + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2], ETHER, USDC), + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + ) + const v2Pool: V2Pool = { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + } + const trade = buildV2Trade(v2Trade, [v2Pool]) + + const options = swapOptions({ + fee: feeOptions, + }) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(4) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(v2Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual('PAY_PORTION') + expect(decodedCommands[2].args[0].name).toEqual('token') + expect(decodedCommands[2].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[2].args[1].name).toEqual('recipient') + expect(decodedCommands[2].args[1].value).toEqual(TEST_FEE_RECIPIENT_ADDRESS) + expect(decodedCommands[2].args[2].name).toEqual('bips') + expect(decodedCommands[2].args[2].value).toEqual(TEST_FEE) + + expect(decodedCommands[3].command).toEqual('SWEEP') + expect(decodedCommands[3].args[0].name).toEqual('token') + expect(decodedCommands[3].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[3].args[1].name).toEqual('recipient') + expect(decodedCommands[3].args[1].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[3].args[2].name).toEqual('amountMin') + expect(decodedCommands[3].args[2].value).toBeGreaterThan(0n) + }) + it('should encode a single exactInput ETH->USDC Swap, with fee, with recipient', async () => { + const amountIn = parseEther('1') + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2], ETHER, USDC), + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + ) + const v2Pool: V2Pool = { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + } + const trade = buildV2Trade(v2Trade, [v2Pool]) + const options = swapOptions({ + fee: feeOptions, + recipient: TEST_RECIPIENT_ADDRESS, + }) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(4) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(v2Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.PAY_PORTION]) + expect(decodedCommands[2].args[0].name).toEqual('token') + expect(decodedCommands[2].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[2].args[1].name).toEqual('recipient') + expect(decodedCommands[2].args[1].value).toEqual(TEST_FEE_RECIPIENT_ADDRESS) + expect(decodedCommands[2].args[2].name).toEqual('bips') + expect(decodedCommands[2].args[2].value).toEqual(TEST_FEE) + + expect(decodedCommands[3].command).toEqual(CommandType[CommandType.SWEEP]) + expect(decodedCommands[3].args[0].name).toEqual('token') + expect(decodedCommands[3].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[3].args[1].name).toEqual('recipient') + expect(decodedCommands[3].args[1].value).toEqual(TEST_RECIPIENT_ADDRESS) + expect(decodedCommands[3].args[2].name).toEqual('amountMin') + expect(decodedCommands[3].args[2].value).toBeGreaterThan(0n) + }) + + it('should encode a exactInput ETH->USDC->USDT Swap, without fee, without recipient', async () => { + const amountIn = parseEther('1') + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2, USDC_USDT_V2], ETHER, USDT), + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + ) + const v2Pools: V2Pool[] = [ + { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + }, + { + type: PoolType.V2, + reserve0: USDC_USDT_V2.reserve0, + reserve1: USDC_USDT_V2.reserve1, + }, + ] + const trade = buildV2Trade(v2Trade, v2Pools) + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(2) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(v2Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + }) + it('should encode a exactInput ETH->USDC->USDT Swap, with fee, without recipient', async () => { + const amountIn = parseEther('1') + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2, USDC_USDT_V2], ETHER, USDT), + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + ) + const v2Pools: V2Pool[] = [ + { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + }, + { + type: PoolType.V2, + reserve0: USDC_USDT_V2.reserve0, + reserve1: USDC_USDT_V2.reserve1, + }, + ] + const trade = buildV2Trade(v2Trade, v2Pools) + const options = swapOptions({ fee: feeOptions }) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(4) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(v2Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.PAY_PORTION]) + expect(decodedCommands[2].args[0].name).toEqual('token') + expect(decodedCommands[2].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[2].args[1].name).toEqual('recipient') + expect(decodedCommands[2].args[1].value).toEqual(TEST_FEE_RECIPIENT_ADDRESS) + expect(decodedCommands[2].args[2].name).toEqual('bips') + expect(decodedCommands[2].args[2].value).toEqual(TEST_FEE) + + expect(decodedCommands[3].command).toEqual(CommandType[CommandType.SWEEP]) + expect(decodedCommands[3].args[0].name).toEqual('token') + expect(decodedCommands[3].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[3].args[1].name).toEqual('recipient') + expect(decodedCommands[3].args[1].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[3].args[2].name).toEqual('amountMin') + expect(decodedCommands[3].args[2].value).toBeGreaterThan(0n) + }) + it('should encode a exactInput ETH->USDC->USDT Swap, without fee, with recipient', async () => { + const amountIn = parseEther('1') + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2, USDC_USDT_V2], ETHER, USDT), + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + ) + const v2Pools: V2Pool[] = [ + { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + }, + { + type: PoolType.V2, + reserve0: USDC_USDT_V2.reserve0, + reserve1: USDC_USDT_V2.reserve1, + }, + ] + const trade = buildV2Trade(v2Trade, v2Pools) + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(2) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(v2Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + }) + it('should encode a exactInput ETH->USDC->USDT Swap, with fee, with recipient', async () => { + const amountIn = parseEther('1') + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2, USDC_USDT_V2], ETHER, USDT), + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + ) + const v2Pools: V2Pool[] = [ + { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + }, + { + type: PoolType.V2, + reserve0: USDC_USDT_V2.reserve0, + reserve1: USDC_USDT_V2.reserve1, + }, + ] + const trade = buildV2Trade(v2Trade, v2Pools) + const options = swapOptions({ fee: feeOptions, recipient: TEST_RECIPIENT_ADDRESS }) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(4) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(v2Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.PAY_PORTION]) + expect(decodedCommands[2].args[0].name).toEqual('token') + expect(decodedCommands[2].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[2].args[1].name).toEqual('recipient') + expect(decodedCommands[2].args[1].value).toEqual(TEST_FEE_RECIPIENT_ADDRESS) + expect(decodedCommands[2].args[2].name).toEqual('bips') + expect(decodedCommands[2].args[2].value).toEqual(TEST_FEE) + + expect(decodedCommands[3].command).toEqual(CommandType[CommandType.SWEEP]) + expect(decodedCommands[3].args[0].name).toEqual('token') + expect(decodedCommands[3].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[3].args[1].name).toEqual('recipient') + expect(decodedCommands[3].args[1].value).toEqual(TEST_RECIPIENT_ADDRESS) + expect(decodedCommands[3].args[2].name).toEqual('amountMin') + expect(decodedCommands[3].args[2].value).toBeGreaterThan(0n) + }) + + it('should encode a single exactInput USDC->ETH Swap', async () => { + const amountIn = parseUnits('1000', 6) + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2], USDC, ETHER), + CurrencyAmount.fromRawAmount(USDC, amountIn), + TradeType.EXACT_INPUT, + ) + const v2Pool: V2Pool = { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + } + const trade = buildV2Trade(v2Trade, [v2Pool]) + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(2) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[0].args[1].name).toEqual('amountIn') + expect(decodedCommands[0].args[1].value).toEqual(amountIn) + expect(decodedCommands[0].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[0].args[2].value).toEqual(v2Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[0].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[0].args[4].value).toEqual(true) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + }) + it('should encode a single exactInput USDC->ETH Swap, with fee', async () => { + const amountIn = parseUnits('1000', 6) + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2], USDC, ETHER), + CurrencyAmount.fromRawAmount(USDC, amountIn), + TradeType.EXACT_INPUT, + ) + const v2Pool: V2Pool = { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + } + const trade = buildV2Trade(v2Trade, [v2Pool]) + const options = swapOptions({ + fee: feeOptions, + }) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[0].args[1].name).toEqual('amountIn') + expect(decodedCommands[0].args[1].value).toEqual(amountIn) + expect(decodedCommands[0].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[0].args[2].value).toEqual(v2Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[0].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[0].args[4].value).toEqual(true) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.PAY_PORTION]) + expect(decodedCommands[1].args[0].name).toEqual('token') + expect(decodedCommands[1].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[1].args[1].name).toEqual('recipient') + expect(decodedCommands[1].args[1].value).toEqual(TEST_FEE_RECIPIENT_ADDRESS) + expect(decodedCommands[1].args[2].name).toEqual('bips') + expect(decodedCommands[1].args[2].value).toEqual(TEST_FEE) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(SENDER_AS_RECIPIENT) + }) + it('should encode a single exactInput USDC->ETH Swap, with recipient', async () => { + const amountIn = parseUnits('1000', 6) + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2], USDC, ETHER), + CurrencyAmount.fromRawAmount(USDC, amountIn), + TradeType.EXACT_INPUT, + ) + const v2Pool: V2Pool = { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + } + const trade = buildV2Trade(v2Trade, [v2Pool]) + const options = swapOptions({ + recipient: TEST_RECIPIENT_ADDRESS, + }) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(2) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[0].args[1].name).toEqual('amountIn') + expect(decodedCommands[0].args[1].value).toEqual(amountIn) + expect(decodedCommands[0].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[0].args[2].value).toEqual(v2Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[0].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[0].args[4].value).toEqual(true) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(TEST_RECIPIENT_ADDRESS) + }) + it('should encode a single exactInput USDC->ETH Swap, with fee, with recipient', async () => { + const amountIn = parseUnits('1000', 6) + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2], USDC, ETHER), + CurrencyAmount.fromRawAmount(USDC, amountIn), + TradeType.EXACT_INPUT, + ) + const v2Pool: V2Pool = { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + } + const trade = buildV2Trade(v2Trade, [v2Pool]) + const options = swapOptions({ + recipient: TEST_RECIPIENT_ADDRESS, + fee: feeOptions, + }) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[0].args[1].name).toEqual('amountIn') + expect(decodedCommands[0].args[1].value).toEqual(amountIn) + expect(decodedCommands[0].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[0].args[2].value).toEqual(v2Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[0].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[0].args[4].value).toEqual(true) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.PAY_PORTION]) + expect(decodedCommands[1].args[0].name).toEqual('token') + expect(decodedCommands[1].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[1].args[1].name).toEqual('recipient') + expect(decodedCommands[1].args[1].value).toEqual(TEST_FEE_RECIPIENT_ADDRESS) + expect(decodedCommands[1].args[2].name).toEqual('bips') + expect(decodedCommands[1].args[2].value).toEqual(TEST_FEE) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(TEST_RECIPIENT_ADDRESS) + }) + + it('should encode a single exactInput USDC->ETH swap, with permit2', async () => { + const amountIn = parseUnits('1000', 6) + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2], USDC, ETHER), + CurrencyAmount.fromRawAmount(USDC, amountIn), + TradeType.EXACT_INPUT, + ) + const v2Pool: V2Pool = { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + } + const trade = buildV2Trade(v2Trade, [v2Pool]) + + const permit = makePermit(USDC.address, UNIVERSAL_ROUTER) + + const permit2Permit: Permit2Signature = { + ...permit, + signature: PERMIT2_SIG, + } + + const options = swapOptions({ + inputTokenPermit: permit2Permit, + }) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.PERMIT2_PERMIT]) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(v2Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(true) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(SENDER_AS_RECIPIENT) + }) + + it('should encode a single exactInput USDC->ETH swap, with EIP-2098 permit', async () => { + const amountIn = parseUnits('1000', 6) + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2], USDC, ETHER), + CurrencyAmount.fromRawAmount(USDC, amountIn), + TradeType.EXACT_INPUT, + ) + const v2Pool: V2Pool = { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + } + const trade = buildV2Trade(v2Trade, [v2Pool]) + + const permit = makePermit(USDC.address, UNIVERSAL_ROUTER) + + const permit2Permit: Permit2Signature = { + ...permit, + signature: + '0x92ff2b035c8005213bd910849532da0f4adde9e35393c8ed4872db90eef2c153492dec30a70476e98f21d28398396669fbef6f9785c903ef83810673ec96fc8d', + } + + const options = swapOptions({ + inputTokenPermit: permit2Permit, + }) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.PERMIT2_PERMIT]) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(v2Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(true) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(SENDER_AS_RECIPIENT) + }) + + it('should encode exactInput USDT->USDC->ETH swap', async () => { + const amountIn = parseUnits('1000', 6) + const v2Trade = new V2Trade( + new V2Route([USDC_USDT_V2, WETH_USDC_V2], USDT, ETHER), + CurrencyAmount.fromRawAmount(USDT, amountIn), + TradeType.EXACT_INPUT, + ) + const v2Pools: V2Pool[] = [ + { + type: PoolType.V2, + reserve0: USDC_USDT_V2.reserve0, + reserve1: USDC_USDT_V2.reserve1, + }, + { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + }, + ] + const trade = buildV2Trade(v2Trade, v2Pools) + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(2) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[0].args[1].name).toEqual('amountIn') + expect(decodedCommands[0].args[1].value).toEqual(amountIn) + expect(decodedCommands[0].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[0].args[2].value).toEqual(v2Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[0].args[3].name).toEqual('path') + expect((decodedCommands[0].args[3].value as string[]).length).toEqual(3) + expect(decodedCommands[0].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[0].args[4].value).toEqual(true) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + }) + + it('should encode exactOutput ETH->USDC swap', async () => { + const amountOut = parseEther('1') + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2], ETHER, USDC), + CurrencyAmount.fromRawAmount(USDC, amountOut), + TradeType.EXACT_OUTPUT, + ) + const v2Pool: V2Pool = { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + } + const trade = buildV2Trade(v2Trade, [v2Pool]) + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).not.toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_OUT]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountOut') + expect(decodedCommands[1].args[1].value).toEqual(amountOut) + expect(decodedCommands[1].args[2].name).toEqual('amountInMax') + expect(decodedCommands[1].args[2].value).toEqual(v2Trade.maximumAmountIn(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[2].args[1].name).toEqual('amountMin') + expect(decodedCommands[2].args[1].value).toEqual(0n) + }) + + it('should encode exactOutput ETH->USDC swap, with fee', async () => { + const amountOut = parseEther('1') + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2], ETHER, USDC), + CurrencyAmount.fromRawAmount(USDC, amountOut), + TradeType.EXACT_OUTPUT, + ) + const v2Pool: V2Pool = { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + } + const trade = buildV2Trade(v2Trade, [v2Pool]) + const options = swapOptions({ + fee: feeOptions, + }) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).not.toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(5) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_OUT]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountOut') + expect(decodedCommands[1].args[1].value).toEqual(amountOut) + expect(decodedCommands[1].args[2].name).toEqual('amountInMax') + expect(decodedCommands[1].args[2].value).toEqual(v2Trade.maximumAmountIn(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual('PAY_PORTION') + expect(decodedCommands[2].args[0].name).toEqual('token') + expect(decodedCommands[2].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[2].args[1].name).toEqual('recipient') + expect(decodedCommands[2].args[1].value).toEqual(TEST_FEE_RECIPIENT_ADDRESS) + expect(decodedCommands[2].args[2].name).toEqual('bips') + expect(decodedCommands[2].args[2].value).toEqual(TEST_FEE) + + expect(decodedCommands[3].command).toEqual('SWEEP') + expect(decodedCommands[3].args[0].name).toEqual('token') + expect(decodedCommands[3].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[3].args[1].name).toEqual('recipient') + expect(decodedCommands[3].args[1].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[3].args[2].name).toEqual('amountMin') + expect(decodedCommands[3].args[2].value).toBeGreaterThan(0n) + + expect(decodedCommands[4].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[4].args[0].name).toEqual('recipient') + expect(decodedCommands[4].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[4].args[1].name).toEqual('amountMin') + expect(decodedCommands[4].args[1].value).toEqual(0n) + }) + it('should encode exactOutput ETH->USDC swap, with fee, with recipient', async () => { + const amountOut = parseEther('1') + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2], ETHER, USDC), + CurrencyAmount.fromRawAmount(USDC, amountOut), + TradeType.EXACT_OUTPUT, + ) + const v2Pool: V2Pool = { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + } + const trade = buildV2Trade(v2Trade, [v2Pool]) + const options = swapOptions({ + fee: feeOptions, + recipient: TEST_RECIPIENT_ADDRESS, + }) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).not.toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(5) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_OUT]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountOut') + expect(decodedCommands[1].args[1].value).toEqual(amountOut) + expect(decodedCommands[1].args[2].name).toEqual('amountInMax') + expect(decodedCommands[1].args[2].value).toEqual(v2Trade.maximumAmountIn(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual('PAY_PORTION') + expect(decodedCommands[2].args[0].name).toEqual('token') + expect(decodedCommands[2].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[2].args[1].name).toEqual('recipient') + expect(decodedCommands[2].args[1].value).toEqual(TEST_FEE_RECIPIENT_ADDRESS) + expect(decodedCommands[2].args[2].name).toEqual('bips') + expect(decodedCommands[2].args[2].value).toEqual(TEST_FEE) + + expect(decodedCommands[3].command).toEqual('SWEEP') + expect(decodedCommands[3].args[0].name).toEqual('token') + expect(decodedCommands[3].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[3].args[1].name).toEqual('recipient') + expect(decodedCommands[3].args[1].value).toEqual(TEST_RECIPIENT_ADDRESS) + expect(decodedCommands[3].args[2].name).toEqual('amountMin') + expect(decodedCommands[3].args[2].value).toBeGreaterThan(0n) + + expect(decodedCommands[4].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[4].args[0].name).toEqual('recipient') + expect(decodedCommands[4].args[0].value).toEqual(TEST_RECIPIENT_ADDRESS) + expect(decodedCommands[4].args[1].name).toEqual('amountMin') + expect(decodedCommands[4].args[1].value).toEqual(0n) + }) + + it('should encode exactOutput USDC-ETH swap', async () => { + const amountOut = parseEther('1') + const v2Trade = new V2Trade( + new V2Route([WETH_USDC_V2], USDC, ETHER), + CurrencyAmount.fromRawAmount(ETHER, amountOut), + TradeType.EXACT_OUTPUT, + ) + const v2Pool: V2Pool = { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + } + const trade = buildV2Trade(v2Trade, [v2Pool]) + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(2) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_OUT]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[0].args[1].name).toEqual('amountOut') + expect(decodedCommands[0].args[1].value).toEqual(amountOut) + expect(decodedCommands[0].args[2].name).toEqual('amountInMax') + expect(decodedCommands[0].args[2].value).toEqual(v2Trade.maximumAmountIn(options.slippageTolerance).quotient) + expect(decodedCommands[0].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[0].args[4].value).toEqual(true) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + }) + }) + + describe('v3', () => { + it('should encode a single exactInput ETH->USDC swap', async () => { + const amountIn = parseEther('1') + const v3Trade = await V3Trade.fromRoute( + new V3Route([WETH_USDC_V3_MEDIUM], ETHER, USDC), + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + ) + const v3Pool: V3Pool = convertPoolToV3Pool(WETH_USDC_V3_MEDIUM) + + const trade = buildV3Trade(v3Trade, [v3Pool]) + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(2) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(v3Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + }) + + it('should encode a single exactInput ETH->USDC swap, with a fee', async () => { + const amountIn = parseEther('1') + const v3Trade = await V3Trade.fromRoute( + new V3Route([WETH_USDC_V3_MEDIUM], ETHER, USDC), + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + ) + const v3Pool: V3Pool = convertPoolToV3Pool(WETH_USDC_V3_MEDIUM) + + const trade = buildV3Trade(v3Trade, [v3Pool]) + const options = swapOptions({ + fee: feeOptions, + }) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(4) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(v3Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual('PAY_PORTION') + expect(decodedCommands[2].args[0].name).toEqual('token') + expect(decodedCommands[2].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[2].args[1].name).toEqual('recipient') + expect(decodedCommands[2].args[1].value).toEqual(TEST_FEE_RECIPIENT_ADDRESS) + expect(decodedCommands[2].args[2].name).toEqual('bips') + expect(decodedCommands[2].args[2].value).toEqual(TEST_FEE) + + expect(decodedCommands[3].command).toEqual('SWEEP') + expect(decodedCommands[3].args[0].name).toEqual('token') + expect(decodedCommands[3].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[3].args[1].name).toEqual('recipient') + expect(decodedCommands[3].args[1].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[3].args[2].name).toEqual('amountMin') + expect(decodedCommands[3].args[2].value).toBeGreaterThan(0n) + }) + + it('should encode a single exactInput USDC->ETH swap', async () => { + const amountIn = parseUnits('1000', 6) + const v3Trade = await V3Trade.fromRoute( + new V3Route([WETH_USDC_V3_MEDIUM], USDC, ETHER), + CurrencyAmount.fromRawAmount(USDC, amountIn), + TradeType.EXACT_INPUT, + ) + const v3Pool: V3Pool = convertPoolToV3Pool(WETH_USDC_V3_MEDIUM) + + const trade = buildV3Trade(v3Trade, [v3Pool]) + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(2) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_IN]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[0].args[1].name).toEqual('amountIn') + expect(decodedCommands[0].args[1].value).toEqual(amountIn) + expect(decodedCommands[0].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[0].args[2].value).toEqual(v3Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[0].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[0].args[4].value).toEqual(true) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + }) + it('should encode a single exactInput USDC->ETH swap, with a fee', async () => { + const amountIn = parseUnits('1000', 6) + const v3Trade = await V3Trade.fromRoute( + new V3Route([WETH_USDC_V3_MEDIUM], USDC, ETHER), + CurrencyAmount.fromRawAmount(USDC, amountIn), + TradeType.EXACT_INPUT, + ) + const v3Pool: V3Pool = convertPoolToV3Pool(WETH_USDC_V3_MEDIUM) + const trade = buildV3Trade(v3Trade, [v3Pool]) + + const options = swapOptions({ + fee: feeOptions, + }) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_IN]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[0].args[1].name).toEqual('amountIn') + expect(decodedCommands[0].args[1].value).toEqual(amountIn) + expect(decodedCommands[0].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[0].args[2].value).toEqual(v3Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[0].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[0].args[4].value).toEqual(true) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.PAY_PORTION]) + expect(decodedCommands[1].args[0].name).toEqual('token') + expect(decodedCommands[1].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[1].args[1].name).toEqual('recipient') + expect(decodedCommands[1].args[1].value).toEqual(TEST_FEE_RECIPIENT_ADDRESS) + expect(decodedCommands[1].args[2].name).toEqual('bips') + expect(decodedCommands[1].args[2].value).toEqual(TEST_FEE) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(SENDER_AS_RECIPIENT) + }) + it('should encode a single exactInput USDC->ETH swap, with permit2', async () => { + const amountIn = parseUnits('1000', 6) + const v3Trade = await V3Trade.fromRoute( + new V3Route([WETH_USDC_V3_MEDIUM], USDC, ETHER), + CurrencyAmount.fromRawAmount(USDC, amountIn), + TradeType.EXACT_INPUT, + ) + const v3Pool: V3Pool = convertPoolToV3Pool(WETH_USDC_V3_MEDIUM) + + const trade = buildV3Trade(v3Trade, [v3Pool]) + + const permit = makePermit(USDC.address, UNIVERSAL_ROUTER) + + const permit2Permit: Permit2Signature = { + ...permit, + signature: PERMIT2_SIG, + } + const options = swapOptions({ + inputTokenPermit: permit2Permit, + }) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.PERMIT2_PERMIT]) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(v3Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(true) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(SENDER_AS_RECIPIENT) + }) + + it('should encode a exactInput ETH->USDC->USDT swap', async () => { + const amountIn = parseEther('1') + const v3Trade = await V3Trade.fromRoute( + new V3Route([WETH_USDC_V3_MEDIUM, USDC_USDT_V3_LOW], ETHER, USDT), + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + ) + const v3Pool: V3Pool[] = [convertPoolToV3Pool(WETH_USDC_V3_MEDIUM), convertPoolToV3Pool(USDC_USDT_V3_LOW)] + + const trade = buildV3Trade(v3Trade, v3Pool) + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(2) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(v3Trade.minimumAmountOut(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + }) + it('should encode a single exactOutput ETH->USDC swap', async () => { + const amountOut = parseUnits('1000', 6) + const v3Trade = await V3Trade.fromRoute( + new V3Route([WETH_USDC_V3_MEDIUM], ETHER, USDC), + CurrencyAmount.fromRawAmount(USDC, amountOut), + TradeType.EXACT_OUTPUT, + ) + const v3Pool: V3Pool = convertPoolToV3Pool(WETH_USDC_V3_MEDIUM) + + const trade = buildV3Trade(v3Trade, [v3Pool]) + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).not.toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_OUT]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountOut') + expect(decodedCommands[1].args[1].value).toEqual(amountOut) + expect(decodedCommands[1].args[2].name).toEqual('amountInMax') + expect(decodedCommands[1].args[2].value).toEqual(v3Trade.maximumAmountIn(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[2].args[1].name).toEqual('amountMin') + expect(decodedCommands[2].args[1].value).toEqual(0n) + }) + it('should encode a single exactOutput USDC->ETH swap', async () => { + const amountOut = parseEther('1') + const v3Trade = await V3Trade.fromRoute( + new V3Route([WETH_USDC_V3_MEDIUM], USDC, ETHER), + CurrencyAmount.fromRawAmount(ETHER, amountOut), + TradeType.EXACT_OUTPUT, + ) + const v3Pool: V3Pool = convertPoolToV3Pool(WETH_USDC_V3_MEDIUM) + + const trade = buildV3Trade(v3Trade, [v3Pool]) + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(2) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_OUT]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[0].args[1].name).toEqual('amountOut') + expect(decodedCommands[0].args[1].value).toEqual(amountOut) + expect(decodedCommands[0].args[2].name).toEqual('amountInMax') + expect(decodedCommands[0].args[2].value).toEqual(v3Trade.maximumAmountIn(options.slippageTolerance).quotient) + expect(decodedCommands[0].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[0].args[4].value).toEqual(true) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + }) + it('should encode a exactOutput ETH->USDC->USDT swap', async () => { + const amountOut = parseUnits('1000', 6) + const v3Trade = await V3Trade.fromRoute( + new V3Route([WETH_USDC_V3_MEDIUM, USDC_USDT_V3_LOW], ETHER, USDT), + CurrencyAmount.fromRawAmount(USDT, amountOut), + TradeType.EXACT_OUTPUT, + ) + const v3Pool: V3Pool[] = [convertPoolToV3Pool(WETH_USDC_V3_MEDIUM), convertPoolToV3Pool(USDC_USDT_V3_LOW)] + + const trade = buildV3Trade(v3Trade, v3Pool) + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).not.toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_OUT]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountOut') + expect(decodedCommands[1].args[1].value).toEqual(amountOut) + expect(decodedCommands[1].args[2].name).toEqual('amountInMax') + expect(decodedCommands[1].args[2].value).toEqual(v3Trade.maximumAmountIn(options.slippageTolerance).quotient) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[2].args[1].name).toEqual('amountMin') + expect(decodedCommands[2].args[1].value).toEqual(0n) + }) + it('should encode a exactOutput USDT->USDC->ETH swap', async () => { + const amountOut = parseEther('1') + const v3Trade = await V3Trade.fromRoute( + new V3Route([USDC_USDT_V3_LOW, WETH_USDC_V3_MEDIUM], USDT, ETHER), + CurrencyAmount.fromRawAmount(ETHER, amountOut), + TradeType.EXACT_OUTPUT, + ) + const v3Pool: V3Pool[] = [convertPoolToV3Pool(USDC_USDT_V3_LOW), convertPoolToV3Pool(WETH_USDC_V3_MEDIUM)] + + const trade = buildV3Trade(v3Trade, v3Pool) + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(2) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_OUT]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[0].args[1].name).toEqual('amountOut') + expect(decodedCommands[0].args[1].value).toEqual(amountOut) + expect(decodedCommands[0].args[2].name).toEqual('amountInMax') + expect(decodedCommands[0].args[2].value).toEqual(v3Trade.maximumAmountIn(options.slippageTolerance).quotient) + expect(decodedCommands[0].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[0].args[4].value).toEqual(true) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + }) + }) + describe('mixed', () => { + it('should encodes a mixed exactInput ETH-v3->USDC-v2->USDT swap', async () => { + const amountIn = parseEther('1') + + const trade = await buildMixedRouteTrade( + ETHER, + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + [WETH_USDC_V3_MEDIUM, USDC_USDT_V2], + ) + + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(USDC_USDT_V2.liquidityToken.address) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(0n) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[2].args[1].name).toEqual('amountIn') + expect(decodedCommands[2].args[1].value).toEqual(CONTRACT_BALANCE) + expect(decodedCommands[2].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[2].args[2].value).toBeGreaterThanOrEqual( + SmartRouter.minimumAmountOut(trade, options.slippageTolerance).quotient, + ) + expect(decodedCommands[2].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[2].args[4].value).toEqual(false) + }) + + it('should encodes a mixed exactInput ETH-v2->USDC-v3->USDT swap', async () => { + const amountIn = parseEther('1') + + const trade = await buildMixedRouteTrade( + ETHER, + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + [WETH_USDC_V2, USDC_USDT_V3_LOW], + ) + + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + // v2 support address aliases, no need to assigned to v3 pool address + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(0n) + expect(decodedCommands[1].args[3].name).toEqual('path') + expect((decodedCommands[1].args[3].value as string[]).length).toEqual(2) + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_IN]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[2].args[1].name).toEqual('amountIn') + expect(decodedCommands[2].args[1].value).toEqual(CONTRACT_BALANCE) + expect(decodedCommands[2].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[2].args[2].value).toBeGreaterThanOrEqual( + SmartRouter.minimumAmountOut(trade, options.slippageTolerance).quotient, + ) + expect(decodedCommands[2].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[2].args[4].value).toEqual(false) + }) + + it('should encodes a mixed exactInput ETH-v2->USDC-v2->USDT swap', async () => { + const amountIn = parseEther('1') + + const trade = await buildMixedRouteTrade( + ETHER, + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + [WETH_USDC_V2, USDC_USDT_V2], + ) + + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(2) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toBeGreaterThanOrEqual( + SmartRouter.minimumAmountOut(trade, options.slippageTolerance).quotient, + ) + expect(decodedCommands[1].args[3].name).toEqual('path') + expect((decodedCommands[1].args[3].value as string[]).length).toEqual(3) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + }) + + it('should encodes a mixed exactInput ETH-v3->USDC-v3->USDT swap', async () => { + const amountIn = parseEther('1') + + const trade = await buildMixedRouteTrade( + ETHER, + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + [WETH_USDC_V3_MEDIUM, USDC_USDT_V3_LOW], + ) + + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(2) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual( + SmartRouter.minimumAmountOut(trade, options.slippageTolerance).quotient, + ) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + }) + + it('should encodes a mixed exactInput USDT-v2->USDC-v3->ETH swap', async () => { + const amountIn = parseUnits('1000', 6) + + const trade = await buildMixedRouteTrade( + USDT, + CurrencyAmount.fromRawAmount(USDT, amountIn), + TradeType.EXACT_INPUT, + [USDC_USDT_V2, WETH_USDC_V3_MEDIUM], + ) + + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + // v2 support address aliases, no need to assigned to v3 pool address + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[0].args[1].name).toEqual('amountIn') + expect(decodedCommands[0].args[1].value).toEqual(amountIn) + expect(decodedCommands[0].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[0].args[2].value).toEqual(0n) + expect(decodedCommands[0].args[3].name).toEqual('path') + expect((decodedCommands[0].args[3].value as string[]).length).toEqual(2) + expect(decodedCommands[0].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[0].args[4].value).toEqual(true) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(CONTRACT_BALANCE) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toBeGreaterThanOrEqual( + SmartRouter.minimumAmountOut(trade, options.slippageTolerance).quotient, + ) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(SENDER_AS_RECIPIENT) + }) + }) + describe('multi-route', () => { + it('should encode a split exactInput with 2 routes: v2 ETH-USDC & v3 ETH-USDC', async () => { + const amountIn = parseEther('1') + const v2TradeRoute = new V2Trade( + new V2Route([WETH_USDC_V2], ETHER, USDC), + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + ) + const v2Trade = buildV2Trade(v2TradeRoute, [ + { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + }, + ]) + const v3TradeRoute = await V3Trade.fromRoute( + new V3Route([WETH_USDC_V3_MEDIUM], ETHER, USDC), + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + ) + const v3Trade = buildV3Trade(v3TradeRoute, [convertPoolToV3Pool(WETH_USDC_V3_MEDIUM)]) + + v2Trade.routes[0].percent = 50 + v3Trade.routes[0].percent = 50 + + const options = swapOptions({}) + const trade: SmartRouterTrade = { + tradeType: TradeType.EXACT_INPUT, + inputAmount: CurrencyAmount.fromRawAmount(ETHER, amountIn * 2n), + outputAmount: CurrencyAmount.fromRawAmount(USDC, amountIn), + routes: [...v2Trade.routes, ...v3Trade.routes], + gasEstimate: 0n, + gasEstimateInUSD: CurrencyAmount.fromRawAmount(ETHER, amountIn), + } + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + expect(BigInt(value)).toEqual(amountIn * 2n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + // v2 support address aliases, no need to assigned to v3 pool address + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual( + v2TradeRoute.minimumAmountOut(options.slippageTolerance).quotient, + ) + expect(decodedCommands[1].args[3].name).toEqual('path') + expect((decodedCommands[1].args[3].value as string[]).length).toEqual(2) + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_IN]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[2].args[1].name).toEqual('amountIn') + expect(decodedCommands[2].args[1].value).toEqual(amountIn) + expect(decodedCommands[2].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[2].args[2].value).toEqual( + v3TradeRoute.minimumAmountOut(options.slippageTolerance).quotient, + ) + expect(decodedCommands[2].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[2].args[4].value).toEqual(false) + }) + + it('should encode a split exactInput with 3 routes: v2 ETH-USDC & v3 ETH-USDC & v3 ETH-USDC', async () => { + const amountIn = parseEther('2') + const v2TradeRoute = new V2Trade( + new V2Route([WETH_USDC_V2], ETHER, USDC), + CurrencyAmount.fromRawAmount(ETHER, (amountIn * 5000n) / 10000n), + TradeType.EXACT_INPUT, + ) + const v2Trade = buildV2Trade(v2TradeRoute, [ + { + type: PoolType.V2, + reserve0: WETH_USDC_V2.reserve0, + reserve1: WETH_USDC_V2.reserve1, + }, + ]) + const v3TradeRoute = await V3Trade.fromRoute( + new V3Route([WETH_USDC_V3_MEDIUM], ETHER, USDC), + CurrencyAmount.fromRawAmount(ETHER, (amountIn * 2500n) / 10000n), + // CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + ) + const v3Trade = buildV3Trade(v3TradeRoute, [convertPoolToV3Pool(WETH_USDC_V3_MEDIUM)]) + + const v3TradeRoute2 = await V3Trade.fromRoute( + new V3Route([WETH_USDC_V3_LOW], ETHER, USDC), + CurrencyAmount.fromRawAmount(ETHER, (amountIn * 2500n) / 10000n), + // CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + ) + const v3Trade2 = buildV3Trade(v3TradeRoute2, [convertPoolToV3Pool(WETH_USDC_V3_LOW)]) + + v2Trade.routes[0].percent = 50 + v3Trade.routes[0].percent = 25 + v3Trade2.routes[0].percent = 25 + + const options = swapOptions({}) + const routes = [...v2Trade.routes, ...v3Trade.routes, ...v3Trade2.routes] + const trade: SmartRouterTrade = { + tradeType: TradeType.EXACT_INPUT, + inputAmount: CurrencyAmount.fromRawAmount(ETHER, amountIn), + outputAmount: CurrencyAmount.fromRawAmount( + USDC, + routes.reduce((acc, r) => acc + r.outputAmount.quotient, 0n), + ), + routes, + gasEstimate: 0n, + gasEstimateInUSD: CurrencyAmount.fromRawAmount(ETHER, amountIn), + } + + // FIXME: a valid trade should have the correct output amounts + expect(trade.routes.reduce((acc, r) => acc + r.outputAmount.quotient, 0n)).toEqual(trade.outputAmount.quotient) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(5) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + // v2 support address aliases, no need to assigned to v3 pool address + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(v2TradeRoute.inputAmount.quotient) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual( + v2TradeRoute.minimumAmountOut(options.slippageTolerance).quotient, + ) + expect(decodedCommands[1].args[3].name).toEqual('path') + expect((decodedCommands[1].args[3].value as string[]).length).toEqual(2) + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_IN]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[2].args[1].name).toEqual('amountIn') + expect(decodedCommands[2].args[1].value).toEqual(v3TradeRoute.inputAmount.quotient) + expect(decodedCommands[2].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[2].args[2].value).toEqual( + v3TradeRoute.minimumAmountOut(options.slippageTolerance).quotient, + ) + expect(decodedCommands[2].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[2].args[4].value).toEqual(false) + + expect(decodedCommands[3].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_IN]) + expect(decodedCommands[3].args[0].name).toEqual('recipient') + expect(decodedCommands[3].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[3].args[1].name).toEqual('amountIn') + expect(decodedCommands[3].args[1].value).toEqual(v3TradeRoute2.inputAmount.quotient) + expect(decodedCommands[3].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[3].args[2].value).toEqual( + v3TradeRoute2.minimumAmountOut(options.slippageTolerance).quotient, + ) + expect(decodedCommands[3].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[3].args[4].value).toEqual(false) + + expect(decodedCommands[4].command).toEqual('SWEEP') + expect(decodedCommands[4].args[0].name).toEqual('token') + expect(decodedCommands[4].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[4].args[1].name).toEqual('recipient') + expect(decodedCommands[4].args[1].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[4].args[2].name).toEqual('amountMin') + expect(decodedCommands[4].args[2].value).toBeGreaterThan(0n) + }) + }) +}) + +describe('PancakeSwap StableSwap Through Universal Router, BSC Network Only', () => { + const chainId = ChainId.BSC + const liquidity = parseEther('1000') + + let wallet: WalletClient + + let ETHER: Ether + let USDC: ERC20Token + let USDT: ERC20Token + let BUSD: ERC20Token + let USDC_USDT_V2: Pair + let WBNB_USDC_V2: Pair + let USDC_USDT_V3_LOW: Pool + let WETH_USDC_V2: Pair + let WETH_USDC_V3_MEDIUM: Pool + let WBNB_USDC_V3_MEDIUM: Pool + let UNIVERSAL_ROUTER: Address + let PERMIT2: Address + + beforeEach(async () => { + ;({ + UNIVERSAL_ROUTER, + PERMIT2, + USDC, + USDT, + BUSD, + ETHER, + WETH_USDC_V2, + WBNB_USDC_V2, + WETH_USDC_V3_MEDIUM, + WBNB_USDC_V3_MEDIUM, + USDC_USDT_V2, + USDC_USDT_V3_LOW, + } = await fixtureAddresses(chainId, liquidity)) + wallet = getWalletClient({ chainId }) + }) + + describe('mixed', () => { + it('should encodes a mixed exactInput USDT-stable->USDC-v3->BNB swap', async () => { + const amountIn = parseUnits('1000', 6) + + const stablePool = await getStablePool(USDT, USDC, getPublicClient, liquidity) + + const trade = await buildMixedRouteTrade( + USDT, + CurrencyAmount.fromRawAmount(USDT, amountIn), + TradeType.EXACT_INPUT, + [stablePool, WBNB_USDC_V3_MEDIUM], + ) + + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.STABLE_SWAP_EXACT_IN]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[0].args[1].name).toEqual('amountIn') + expect(decodedCommands[0].args[1].value).toEqual(amountIn) + expect(decodedCommands[0].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[0].args[2].value).toEqual(0n) + expect(decodedCommands[0].args[3].name).toEqual('path') + expect((decodedCommands[0].args[3].value as string[]).length).toEqual(2) + expect(decodedCommands[0].args[5].name).toEqual('payerIsUser') + expect(decodedCommands[0].args[5].value).toEqual(true) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(CONTRACT_BALANCE) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toBeGreaterThanOrEqual( + SmartRouter.minimumAmountOut(trade, options.slippageTolerance).quotient, + ) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(SENDER_AS_RECIPIENT) + }) + it('should encodes a mixed exactInput USDT-stable->USDC-v2->BNB swap', async () => { + const amountIn = parseUnits('1000', 6) + + const stablePool = await getStablePool(USDT, USDC, getPublicClient, liquidity) + + const trade = await buildMixedRouteTrade( + USDT, + CurrencyAmount.fromRawAmount(USDT, amountIn), + TradeType.EXACT_INPUT, + [stablePool, WBNB_USDC_V2], + ) + + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.STABLE_SWAP_EXACT_IN]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(WBNB_USDC_V2.liquidityToken.address) + expect(decodedCommands[0].args[1].name).toEqual('amountIn') + expect(decodedCommands[0].args[1].value).toEqual(amountIn) + expect(decodedCommands[0].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[0].args[2].value).toEqual(0n) + expect(decodedCommands[0].args[3].name).toEqual('path') + expect((decodedCommands[0].args[3].value as string[]).length).toEqual(2) + expect(decodedCommands[0].args[5].name).toEqual('payerIsUser') + expect(decodedCommands[0].args[5].value).toEqual(true) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(CONTRACT_BALANCE) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual( + SmartRouter.minimumAmountOut(trade, options.slippageTolerance).quotient, + ) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.UNWRAP_WETH]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(SENDER_AS_RECIPIENT) + }) + it('should encodes a mixed exactInput BNB-v2->USDC-stable->USDT swap', async () => { + const amountIn = parseEther('1') + + const stablePool = await getStablePool(USDT, USDC, getPublicClient, liquidity) + + const trade = await buildMixedRouteTrade( + ETHER, + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + [WETH_USDC_V2, stablePool], + ) + + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(0n) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.STABLE_SWAP_EXACT_IN]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[2].args[1].name).toEqual('amountIn') + expect(decodedCommands[2].args[1].value).toEqual(CONTRACT_BALANCE) + expect(decodedCommands[2].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[2].args[2].value).toEqual( + SmartRouter.minimumAmountOut(trade, options.slippageTolerance).quotient, + ) + expect(decodedCommands[2].args[3].name).toEqual('path') + expect((decodedCommands[2].args[3].value as string[]).length).toEqual(2) + expect(decodedCommands[2].args[5].name).toEqual('payerIsUser') + expect(decodedCommands[2].args[5].value).toEqual(false) + }) + it('should encodes a mixed exactInput ETH-v3->USDC-stable->USDT swap', async () => { + const amountIn = parseEther('1') + + const stablePool = await getStablePool(USDT, USDC, getPublicClient, liquidity) + + const trade = await buildMixedRouteTrade( + ETHER, + CurrencyAmount.fromRawAmount(ETHER, amountIn), + TradeType.EXACT_INPUT, + [WETH_USDC_V3_MEDIUM, stablePool], + ) + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(amountIn) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.WRAP_ETH]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual(0n) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(false) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.STABLE_SWAP_EXACT_IN]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[2].args[1].name).toEqual('amountIn') + expect(decodedCommands[2].args[1].value).toEqual(CONTRACT_BALANCE) + expect(decodedCommands[2].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[2].args[2].value).toEqual( + SmartRouter.minimumAmountOut(trade, options.slippageTolerance).quotient, + ) + expect(decodedCommands[2].args[3].name).toEqual('path') + expect((decodedCommands[2].args[3].value as string[]).length).toEqual(2) + expect(decodedCommands[2].args[5].name).toEqual('payerIsUser') + expect(decodedCommands[2].args[5].value).toEqual(false) + }) + }) + + describe('multi-route', () => { + it('should encode a multi-route exactInput v2 USDT-USDC & v3 USDT-USDC & stable USDT-USDC', async () => { + const amountIn = parseUnits('1000', 6) + const v2TradeRoute = new V2Trade( + new V2Route([USDC_USDT_V2], USDT, USDC), + CurrencyAmount.fromRawAmount(USDT, amountIn), + TradeType.EXACT_INPUT, + ) + const v2Trade = buildV2Trade(v2TradeRoute, [ + { + type: PoolType.V2, + reserve0: USDC_USDT_V2.reserve0, + reserve1: USDC_USDT_V2.reserve1, + }, + ]) + const v3TradeRoute = await V3Trade.fromRoute( + new V3Route([USDC_USDT_V3_LOW], USDT, USDC), + CurrencyAmount.fromRawAmount(USDT, amountIn), + TradeType.EXACT_INPUT, + ) + const v3Trade = buildV3Trade(v3TradeRoute, [convertPoolToV3Pool(USDC_USDT_V3_LOW)]) + + const stableTrade = buildStableTrade(USDT, USDC, CurrencyAmount.fromRawAmount(USDT, amountIn), [ + await getStablePool(USDT, USDC, getPublicClient, liquidity), + ]) + + v2Trade.routes[0].percent = 33 + v3Trade.routes[0].percent = 33 + stableTrade.routes[0].percent = 33 + + const routes = [...v2Trade.routes, ...v3Trade.routes, ...stableTrade.routes] + const options = swapOptions({}) + const trade: SmartRouterTrade = { + tradeType: TradeType.EXACT_INPUT, + inputAmount: CurrencyAmount.fromRawAmount(USDT, amountIn * 3n), + outputAmount: CurrencyAmount.fromRawAmount( + USDC, + routes.reduce((acc, r) => acc + r.outputAmount.quotient, 0n), + ), + routes, + gasEstimate: 0n, + gasEstimateInUSD: CurrencyAmount.fromRawAmount(USDT, amountIn), + } + + expect(trade.routes.reduce((acc, r) => acc + r.outputAmount.quotient, 0n)).toEqual(trade.outputAmount.quotient) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(4) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.V2_SWAP_EXACT_IN]) + // v2 support address aliases, no need to assigned to v3 pool address + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[0].args[1].name).toEqual('amountIn') + expect(decodedCommands[0].args[1].value).toEqual(amountIn) + expect(decodedCommands[0].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[0].args[2].value).toEqual( + v2TradeRoute.minimumAmountOut(options.slippageTolerance).quotient, + ) + expect(decodedCommands[0].args[3].name).toEqual('path') + expect((decodedCommands[0].args[3].value as string[]).length).toEqual(2) + expect(decodedCommands[0].args[4].value).toEqual(true) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.V3_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual( + v3TradeRoute.minimumAmountOut(options.slippageTolerance).quotient, + ) + expect(decodedCommands[1].args[4].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[4].value).toEqual(true) + + expect(decodedCommands[2].command).toEqual(CommandType[CommandType.STABLE_SWAP_EXACT_IN]) + expect(decodedCommands[2].args[0].name).toEqual('recipient') + expect(decodedCommands[2].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[2].args[1].name).toEqual('amountIn') + expect(decodedCommands[2].args[1].value).toEqual(amountIn) + expect(decodedCommands[2].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[2].args[2].value).toEqual( + SmartRouter.minimumAmountOut(stableTrade, options.slippageTolerance).quotient, + ) + expect(decodedCommands[2].args[3].name).toEqual('path') + expect((decodedCommands[2].args[3].value as string[]).length).toEqual(2) + expect(decodedCommands[2].args[5].name).toEqual('payerIsUser') + expect(decodedCommands[2].args[5].value).toEqual(true) + + expect(decodedCommands[3].command).toEqual('SWEEP') + expect(decodedCommands[3].args[0].name).toEqual('token') + expect(decodedCommands[3].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[3].args[1].name).toEqual('recipient') + expect(decodedCommands[3].args[1].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[3].args[2].name).toEqual('amountMin') + expect(decodedCommands[3].args[2].value).toEqual( + SmartRouter.minimumAmountOut(trade, options.slippageTolerance).quotient, + ) + }) + }) + + it('should encode a single exactInput USDT->USDC swap', async () => { + const amountIn = parseUnits('1000', 6) + + const stablePool = await getStablePool(USDT, USDC, getPublicClient, liquidity) + const trade = buildStableTrade(USDT, USDC, CurrencyAmount.fromRawAmount(USDT, amountIn), [stablePool]) + + const options = swapOptions({}) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(1) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.STABLE_SWAP_EXACT_IN]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[0].args[1].name).toEqual('amountIn') + expect(decodedCommands[0].args[1].value).toEqual(amountIn) + expect(decodedCommands[0].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[0].args[2].value).toEqual( + SmartRouter.minimumAmountOut(trade, options.slippageTolerance).quotient, + ) + expect(decodedCommands[0].args[3].name).toEqual('path') + expect((decodedCommands[0].args[3].value as string[]).length).toEqual(2) + expect(decodedCommands[0].args[5].name).toEqual('payerIsUser') + expect(decodedCommands[0].args[5].value).toEqual(true) + }) + it('should encode a single exactInput USDT->USDC swap, with fee', async () => { + const amountIn = parseUnits('1000', 6) + + const stablePool = await getStablePool(USDT, USDC, getPublicClient, liquidity) + const trade = buildStableTrade(USDT, USDC, CurrencyAmount.fromRawAmount(USDT, amountIn), [stablePool]) + + const options = swapOptions({ fee: feeOptions }) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(3) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.STABLE_SWAP_EXACT_IN]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(ROUTER_AS_RECIPIENT) + expect(decodedCommands[0].args[1].name).toEqual('amountIn') + expect(decodedCommands[0].args[1].value).toEqual(amountIn) + expect(decodedCommands[0].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[0].args[2].value).toEqual( + SmartRouter.minimumAmountOut(trade, options.slippageTolerance).quotient, + ) + expect(decodedCommands[0].args[3].name).toEqual('path') + expect((decodedCommands[0].args[3].value as string[]).length).toEqual(2) + expect(decodedCommands[0].args[5].name).toEqual('payerIsUser') + expect(decodedCommands[0].args[5].value).toEqual(true) + + expect(decodedCommands[1].command).toEqual('PAY_PORTION') + expect(decodedCommands[1].args[0].name).toEqual('token') + expect(decodedCommands[1].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[1].args[1].name).toEqual('recipient') + expect(decodedCommands[1].args[1].value).toEqual(TEST_FEE_RECIPIENT_ADDRESS) + expect(decodedCommands[1].args[2].name).toEqual('bips') + expect(decodedCommands[1].args[2].value).toEqual(TEST_FEE) + + expect(decodedCommands[2].command).toEqual('SWEEP') + expect(decodedCommands[2].args[0].name).toEqual('token') + expect(decodedCommands[2].args[0].value).toEqual(trade.outputAmount.currency.wrapped.address) + expect(decodedCommands[2].args[1].name).toEqual('recipient') + expect(decodedCommands[2].args[1].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[2].args[2].name).toEqual('amountMin') + expect(decodedCommands[2].args[2].value).toEqual( + SmartRouter.minimumAmountOut(trade, options.slippageTolerance).quotient, + ) + }) + it('should encode a single exactInput USDT->USDC swap, with permit2', async () => { + const amountIn = parseUnits('1000', 6) + + const stablePool = await getStablePool(USDT, USDC, getPublicClient, liquidity) + const trade = buildStableTrade(USDT, USDC, CurrencyAmount.fromRawAmount(USDT, amountIn), [stablePool]) + + const permit = makePermit(USDC.address, UNIVERSAL_ROUTER) + const permit2Permit: Permit2Signature = { + ...permit, + signature: + '0x777406b366e3539754d5ea1056d11c8f7482c7f095b48cc3520051169c6e0e1f49116cbe67f45bfe7c1af7599e7c6c5c93565294bcc0fb2f42699d2845b11da41b', + } + const options = swapOptions({ + inputTokenPermit: permit2Permit, + }) + + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(2) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.PERMIT2_PERMIT]) + + expect(decodedCommands[1].command).toEqual(CommandType[CommandType.STABLE_SWAP_EXACT_IN]) + expect(decodedCommands[1].args[0].name).toEqual('recipient') + expect(decodedCommands[1].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[1].args[1].name).toEqual('amountIn') + expect(decodedCommands[1].args[1].value).toEqual(amountIn) + expect(decodedCommands[1].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[1].args[2].value).toEqual( + SmartRouter.minimumAmountOut(trade, options.slippageTolerance).quotient, + ) + expect(decodedCommands[1].args[3].name).toEqual('path') + expect((decodedCommands[1].args[3].value as string[]).length).toEqual(2) + expect(decodedCommands[1].args[5].name).toEqual('payerIsUser') + expect(decodedCommands[1].args[5].value).toEqual(true) + }) + it('should encode a exactInput USDT->USDC->BUSD swap', async () => { + const amountIn = parseUnits('1000', 6) + + const USDT_USDC_POOL = await getStablePool(USDT, USDC, getPublicClient, liquidity) + const USDC_BUSD_POOL = await getStablePool(USDC, BUSD, getPublicClient) + + const trade = buildStableTrade(USDT, BUSD, CurrencyAmount.fromRawAmount(USDT, amountIn), [ + USDT_USDC_POOL, + USDC_BUSD_POOL, + ]) + + const options = swapOptions({}) + const { calldata, value } = PancakeSwapUniversalRouter.swapERC20CallParameters(trade, options) + expect(BigInt(value)).toEqual(0n) + expect(calldata).toMatchSnapshot() + + const decodedCommands = decodeUniversalCalldata(calldata) + expect(decodedCommands.length).toEqual(1) + + expect(decodedCommands[0].command).toEqual(CommandType[CommandType.STABLE_SWAP_EXACT_IN]) + expect(decodedCommands[0].args[0].name).toEqual('recipient') + expect(decodedCommands[0].args[0].value).toEqual(SENDER_AS_RECIPIENT) + expect(decodedCommands[0].args[1].name).toEqual('amountIn') + expect(decodedCommands[0].args[1].value).toEqual(amountIn) + expect(decodedCommands[0].args[2].name).toEqual('amountOutMin') + expect(decodedCommands[0].args[2].value).toEqual( + SmartRouter.minimumAmountOut(trade, options.slippageTolerance).quotient, + ) + expect(decodedCommands[0].args[3].name).toEqual('path') + expect((decodedCommands[0].args[3].value as string[]).length).toEqual(3) + expect(decodedCommands[0].args[5].name).toEqual('payerIsUser') + expect(decodedCommands[0].args[5].value).toEqual(true) + }) +}) diff --git a/packages/universal-router-sdk/test/utils/buildTrade.ts b/packages/universal-router-sdk/test/utils/buildTrade.ts new file mode 100644 index 0000000000000..599dbd1ce8726 --- /dev/null +++ b/packages/universal-router-sdk/test/utils/buildTrade.ts @@ -0,0 +1,166 @@ +import { Currency, CurrencyAmount, ERC20Token, Native, Pair, TradeType, Trade as V2Trade } from '@pancakeswap/sdk' +import { + RouteType, + SmartRouter, + SmartRouterTrade, + StablePool, + StableSwap, + V2Pool, + V3Pool, +} from '@pancakeswap/smart-router/evm' +import { Pool, Trade as V3Trade } from '@pancakeswap/v3-sdk' +import { convertPairToV2Pool, convertPoolToV3Pool } from '../fixtures/address' + +export const buildV2Trade = ( + v2Trade: V2Trade, + v2Pools: V2Pool[], +): SmartRouterTrade => { + return { + tradeType: v2Trade.tradeType, + inputAmount: v2Trade.inputAmount, + outputAmount: v2Trade.outputAmount, + routes: [ + { + type: RouteType.V2, + path: v2Trade.route.path, + inputAmount: v2Trade.inputAmount, + outputAmount: v2Trade.outputAmount, + percent: 100, + pools: v2Pools, + }, + ], + gasEstimate: 0n, + gasEstimateInUSD: CurrencyAmount.fromRawAmount(v2Trade.route.input, 0), + } +} + +export const buildStableTrade = ( + input: Currency, + output: Currency, + amountIn: CurrencyAmount, + stablePools: StablePool[], +): SmartRouterTrade => { + // @notice: just set same amountOut quantity as amountIn, for easy fixture + const amountOut = CurrencyAmount.fromFractionalAmount(output, amountIn.numerator, amountIn.denominator) + const path: Currency[] = [input] + let current = input + + for (const pool of stablePools) { + const { balances } = pool + current = balances[0].currency.equals(current) ? balances[1].currency : balances[0].currency + path.push(current) + } + + return { + tradeType: TradeType.EXACT_INPUT, + inputAmount: amountIn, + outputAmount: amountOut, + routes: [ + { + type: RouteType.STABLE, + path, + inputAmount: amountIn, + outputAmount: amountOut, + percent: 100, + pools: stablePools, + }, + ], + gasEstimate: 0n, + gasEstimateInUSD: CurrencyAmount.fromRawAmount(input, 0), + } +} + +export const buildV3Trade = ( + trade: V3Trade, + pools: V3Pool[], +): SmartRouterTrade => { + return { + tradeType: trade.tradeType, + inputAmount: trade.inputAmount, + outputAmount: trade.outputAmount, + routes: [ + { + type: RouteType.V3, + path: trade.swaps[0].route.tokenPath, + inputAmount: trade.inputAmount, + outputAmount: trade.outputAmount, + percent: 100, + pools, + }, + ], + gasEstimate: 0n, + gasEstimateInUSD: CurrencyAmount.fromRawAmount(trade.route.input, 0), + } +} + +export const buildMixedRouteTrade = async < + TInput extends Currency, + TOutput extends Currency, + TTradeType extends TradeType, +>( + tokenIn: TInput, + amount: CurrencyAmount, + tradeType: TTradeType, + pools: Array, + isOutputNative = true, +): Promise> => { + const path: Currency[] = [tokenIn.wrapped] + const outputPools = pools.map((pool) => { + if (pool instanceof Pair) return convertPairToV2Pool(pool) + if (pool instanceof Pool) return convertPoolToV3Pool(pool) + + return pool + }) + + const amounts: CurrencyAmount[] = [] + + amounts.push(amount.wrapped) + + for (const pool of pools) { + const input = amounts[amounts.length - 1] + let outputAmount: CurrencyAmount + if (pool instanceof Pair || pool instanceof Pool) { + // eslint-disable-next-line no-await-in-loop + ;[outputAmount] = await pool.getOutputAmount(input as CurrencyAmount) + path.push(outputAmount.currency) + amounts.push(outputAmount) + } else if (SmartRouter.isStablePool(pool)) { + const { amplifier, balances, fee } = pool + outputAmount = StableSwap.getSwapOutput({ + amplifier, + amount: input, + balances, + fee, + outputCurrency: balances[0].currency.equals(input.currency) ? balances[1].currency : balances[0].currency, + }).wrapped + path.push(outputAmount.currency) + amounts.push(outputAmount) + } + } + + // mixed Router support exactIn only + const inputAmount = amount + let outputAmount = amounts[amounts.length - 1] + const nativeCurrency = Native.onChain(outputAmount.currency.chainId) + // is wrapped token + if (outputAmount.currency.wrapped.equals(nativeCurrency.wrapped) && isOutputNative) { + outputAmount = CurrencyAmount.fromFractionalAmount(nativeCurrency, outputAmount.numerator, outputAmount.denominator) + } + return { + tradeType, + inputAmount: amount, + outputAmount: isOutputNative ? outputAmount : outputAmount.wrapped, + routes: [ + { + type: RouteType.MIXED, + path, + pools: outputPools, + inputAmount, + outputAmount, + percent: 100, + }, + ], + gasEstimate: 0n, + gasEstimateInUSD: CurrencyAmount.fromRawAmount(tokenIn, 0), + } +} diff --git a/packages/universal-router-sdk/test/utils/calldataDecode.ts b/packages/universal-router-sdk/test/utils/calldataDecode.ts new file mode 100644 index 0000000000000..cf9b613660fa2 --- /dev/null +++ b/packages/universal-router-sdk/test/utils/calldataDecode.ts @@ -0,0 +1,56 @@ +import { decodeFunctionData, Hex, decodeAbiParameters, ParseAbiParameters } from 'viem' +import { UniversalRouterABI } from '../../src/abis/UniversalRouter' +import { ABI_PARAMETER, CommandType } from '../../src/utils/routerCommands' + +export type DecodedCommand = { + command: string + args: { + type: string + name: string + value: unknown + }[] +} + +export function decodeUniversalCalldata(calldata: Hex): DecodedCommand[] { + const { functionName, args } = decodeFunctionData({ + abi: UniversalRouterABI, + data: calldata, + }) + if (functionName !== 'execute') throw RangeError(`Invalid function called: ${functionName}, support 'execute' only`) + + const commands = + args[0] + .toString() + .match(/../g) + ?.splice(1) + .map((str) => BigInt(`0x${str}`).toString()) ?? [] + + const decoded: DecodedCommand[] = [] + + for (const [index, command] of Object.entries(commands)) { + // @ts-expect-error do not check this + const abi: ParseAbiParameters = ABI_PARAMETER[command] + + // @ts-expect-error do not check this + const commandName = CommandType[command] + + const parameters = decodeAbiParameters(abi, args[1][Number(index)]) + + const formatedArgs: DecodedCommand['args'] = [] + + for (const [i, p] of Object.entries(abi)) { + formatedArgs.push({ + type: p.type, + name: p.name!, + value: parameters[Number(i)], + }) + } + + decoded.push({ + command: commandName, + args: formatedArgs, + }) + } + + return decoded +} diff --git a/packages/universal-router-sdk/test/utils/permit.ts b/packages/universal-router-sdk/test/utils/permit.ts new file mode 100644 index 0000000000000..e0fe06b7948e4 --- /dev/null +++ b/packages/universal-router-sdk/test/utils/permit.ts @@ -0,0 +1,23 @@ +import { MaxAllowanceExpiration, MaxAllowanceTransferAmount, PermitSingle } from '@pancakeswap/permit2-sdk' +import { type Address } from 'viem' + +const TEST_DEADLINE = MaxAllowanceExpiration + +export const makePermit = ( + token: Address, + // as spender + routerAddress: Address, + amount: string = MaxAllowanceTransferAmount.toString(), + nonce = 0, +): PermitSingle => { + return { + details: { + token, + amount, + expiration: TEST_DEADLINE, + nonce, + }, + spender: routerAddress, + sigDeadline: TEST_DEADLINE, + } +} diff --git a/packages/universal-router-sdk/tsconfig.json b/packages/universal-router-sdk/tsconfig.json new file mode 100644 index 0000000000000..9eff499f70410 --- /dev/null +++ b/packages/universal-router-sdk/tsconfig.json @@ -0,0 +1,28 @@ +{ + "extends": "@pancakeswap/tsconfig/base", + "include": ["./src"], + "exclude": ["**/*.test.ts", "./dist/**"], + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "target": "es2020", + "lib": ["ES2021"], + "importHelpers": true, + "declaration": true, + "sourceMap": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "moduleResolution": "node", + "esModuleInterop": true, + "resolveJsonModule": true + } +} diff --git a/packages/universal-router-sdk/tsup.config.ts b/packages/universal-router-sdk/tsup.config.ts new file mode 100644 index 0000000000000..18baef7bb615b --- /dev/null +++ b/packages/universal-router-sdk/tsup.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from 'tsup' +import { exec } from 'child_process' + +export default defineConfig((options) => ({ + entry: { + index: './src/index.ts', + }, + format: ['esm', 'cjs'], + dts: false, + clean: !options.watch, + treeshake: true, + splitting: true, + onSuccess: async () => { + exec('tsc --emitDeclarationOnly --declaration', (err, stdout) => { + if (err) { + console.error(stdout) + if (!options.watch) { + process.exit(1) + } + } + }) + }, +})) diff --git a/packages/universal-router-sdk/vitest.config.ts b/packages/universal-router-sdk/vitest.config.ts new file mode 100644 index 0000000000000..dd380f935bce5 --- /dev/null +++ b/packages/universal-router-sdk/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + testTimeout: 100_000, + globals: true, + }, +}) diff --git a/packages/v3-sdk/src/abi/V3PoolAbi.ts b/packages/v3-sdk/src/abi/V3PoolAbi.ts new file mode 100644 index 0000000000000..bd3ef837db235 --- /dev/null +++ b/packages/v3-sdk/src/abi/V3PoolAbi.ts @@ -0,0 +1,52 @@ +export const v3PoolAbi = [ + { + inputs: [], + name: 'liquidity', + outputs: [{ internalType: 'uint128', name: '', type: 'uint128' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'slot0', + outputs: [ + { + internalType: 'uint160', + name: 'sqrtPriceX96', + type: 'uint160', + }, + { + internalType: 'int24', + name: 'tick', + type: 'int24', + }, + { + internalType: 'uint16', + name: 'observationIndex', + type: 'uint16', + }, + { + internalType: 'uint16', + name: 'observationCardinality', + type: 'uint16', + }, + { + internalType: 'uint16', + name: 'observationCardinalityNext', + type: 'uint16', + }, + { + internalType: 'uint32', + name: 'feeProtocol', + type: 'uint32', + }, + { + internalType: 'bool', + name: 'unlocked', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, +] as const diff --git a/packages/v3-sdk/src/index.test.ts b/packages/v3-sdk/src/index.test.ts index b7a6553df7ea0..56398ff699ce6 100644 --- a/packages/v3-sdk/src/index.test.ts +++ b/packages/v3-sdk/src/index.test.ts @@ -73,6 +73,7 @@ test('exports', () => { "quoterV2ABI", "selfPermitABI", "swapRouterABI", + "v3PoolAbi", "v3StakerABI", ] `) diff --git a/packages/v3-sdk/src/index.ts b/packages/v3-sdk/src/index.ts index 7cfa200062365..c0221813d3043 100644 --- a/packages/v3-sdk/src/index.ts +++ b/packages/v3-sdk/src/index.ts @@ -20,4 +20,5 @@ export * from './abi/Quoter' export * from './abi/QuoterV2' export * from './abi/SelfPermit' export * from './abi/SwapRouter' +export * from './abi/V3PoolAbi' export * from './abi/V3Staker' diff --git a/packages/wagmi/connectors/binanceWallet/binanceWallet.ts b/packages/wagmi/connectors/binanceWallet/binanceWallet.ts deleted file mode 100644 index 5235ef13cbbd1..0000000000000 --- a/packages/wagmi/connectors/binanceWallet/binanceWallet.ts +++ /dev/null @@ -1,133 +0,0 @@ -/* eslint-disable prefer-destructuring */ -/* eslint-disable consistent-return */ -/* eslint-disable class-methods-use-this */ -import { Chain, ConnectorNotFoundError, SwitchChainNotSupportedError, WindowProvider } from 'wagmi' -import { UserRejectedRequestError, ResourceUnavailableRpcError, ProviderRpcError, toHex } from 'viem' -import { InjectedConnector } from 'wagmi/connectors/injected' - -declare global { - interface Window { - BinanceChain?: { - bnbSign?: (address: string, message: string) => Promise<{ publicKey: string; signature: string }> - switchNetwork?: (networkId: string) => Promise - } & WindowProvider - } -} - -const mappingNetwork: Record = { - 1: 'eth-mainnet', - 56: 'bsc-mainnet', - 97: 'bsc-testnet', -} - -const _binanceChainListener = async () => - new Promise((resolve) => - Object.defineProperty(window, 'BinanceChain', { - get() { - return this.bsc - }, - set(bsc) { - this.bsc = bsc - - resolve() - }, - }), - ) - -export class BinanceWalletConnector extends InjectedConnector { - readonly id = 'bsc' - - readonly ready = typeof window !== 'undefined' - - provider?: Window['BinanceChain'] - - constructor({ - chains: _chains, - }: { - chains?: Chain[] - } = {}) { - const options = { - name: 'Binance', - shimDisconnect: false, - shimChainChangedDisconnect: true, - } - const chains = _chains?.filter((c) => !!mappingNetwork[c.id]) - super({ - chains, - options, - }) - } - - async connect({ chainId }: { chainId?: number } = {}) { - try { - const provider = await this.getProvider() - if (!provider) throw new ConnectorNotFoundError() - - if (provider.on) { - provider.on('accountsChanged', this.onAccountsChanged) - provider.on('chainChanged', this.onChainChanged) - provider.on('disconnect', this.onDisconnect) - } - - this.emit('message', { type: 'connecting' }) - - const account = await this.getAccount() - // Switch to chain if provided - let id = await this.getChainId() - let unsupported = this.isChainUnsupported(id) - if (chainId && id !== chainId) { - const chain = await this.switchChain(chainId) - id = chain.id - unsupported = this.isChainUnsupported(id) - } - - return { account, chain: { id, unsupported }, provider } - } catch (error) { - if (this.isUserRejectedRequestError(error)) throw new UserRejectedRequestError(error as Error) - if ((error).code === -32002) throw new ResourceUnavailableRpcError(error as ProviderRpcError) - throw error - } - } - - async getProvider() { - if (typeof window !== 'undefined') { - // TODO: Fallback to `ethereum#initialized` event for async injection - // https://github.com/MetaMask/detect-provider#synchronous-and-asynchronous-injection= - if (window.BinanceChain) { - this.provider = window.BinanceChain - } else { - await _binanceChainListener() - this.provider = window.BinanceChain - } - } - return this.provider - } - - async switchChain(chainId: number): Promise { - const provider = await this.getProvider() - if (!provider) throw new ConnectorNotFoundError() - - const id = toHex(chainId) - - if (mappingNetwork[chainId]) { - try { - await provider.switchNetwork?.(mappingNetwork[chainId]) - - return ( - this.chains.find((x) => x.id === chainId) || { - id: chainId, - name: `Chain ${id}`, - network: `${id}`, - nativeCurrency: { decimals: 18, name: 'BNB', symbol: 'BNB' }, - rpcUrls: { default: { http: [''] }, public: { http: [''] } }, - } - ) - } catch (error) { - if ((error as any).error === 'user rejected') { - throw new UserRejectedRequestError(error as Error) - } - } - } - throw new SwitchChainNotSupportedError({ connector: this }) - } -} diff --git a/packages/wagmi/connectors/binanceWallet/index.ts b/packages/wagmi/connectors/binanceWallet/index.ts deleted file mode 100644 index dc8e0c1113ddf..0000000000000 --- a/packages/wagmi/connectors/binanceWallet/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './binanceWallet' diff --git a/packages/wagmi/connectors/binanceWallet/package.json b/packages/wagmi/connectors/binanceWallet/package.json deleted file mode 100644 index 9e011670e91cf..0000000000000 --- a/packages/wagmi/connectors/binanceWallet/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "main": "../../dist/connectors/binanceWallet.js", - "module": "../../dist/connectors/binanceWallet.mjs", - "types": "../../dist/connectors/binanceWallet.d.ts" -} diff --git a/packages/wagmi/package.json b/packages/wagmi/package.json index c1b4b6208d59c..1a48778ae0033 100644 --- a/packages/wagmi/package.json +++ b/packages/wagmi/package.json @@ -22,7 +22,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "viem": "1.19.11", - "wagmi": "1.4.7" + "wagmi": "1.4.13" }, "devDependencies": { "@blocto/sdk": "^0.5.4", @@ -32,7 +32,7 @@ "react-dom": "^18.0.0", "tsup": "^6.7.0", "viem": "1.19.11", - "wagmi": "1.4.7" + "wagmi": "1.4.13" }, "exports": { "./package.json": "./package.json", @@ -46,11 +46,6 @@ "import": "./dist/connectors/miniProgram.mjs", "require": "./dist/connectors/miniProgram.js" }, - "./connectors/binanceWallet": { - "types": "./dist/connectors/binanceWallet.d.ts", - "import": "./dist/connectors/binanceWallet.mjs", - "require": "./dist/connectors/binanceWallet.js" - }, "./connectors/blocto": { "types": "./dist/connectors/blocto.d.ts", "import": "./dist/connectors/blocto.mjs", diff --git a/packages/wagmi/tsup.config.ts b/packages/wagmi/tsup.config.ts index 3d6d37734eedb..8deb7bc0eb812 100644 --- a/packages/wagmi/tsup.config.ts +++ b/packages/wagmi/tsup.config.ts @@ -4,7 +4,6 @@ export default defineConfig({ entry: { index: 'src/index.ts', 'connectors/miniProgram': 'connectors/miniProgram/index.ts', - 'connectors/binanceWallet': 'connectors/binanceWallet/index.ts', 'connectors/blocto': 'connectors/blocto/index.ts', 'connectors/trustWallet': 'connectors/trustWallet/index.ts', }, diff --git a/packages/widgets-internal/swap/ApproveModalContent.tsx b/packages/widgets-internal/swap/ApproveModalContent.tsx index 1f381d0be0f55..817d36dbdfc0c 100644 --- a/packages/widgets-internal/swap/ApproveModalContent.tsx +++ b/packages/widgets-internal/swap/ApproveModalContent.tsx @@ -1,50 +1,144 @@ import { useTranslation } from "@pancakeswap/localization"; -import { Spinner, Text, Box, Flex, TooltipText, AutoColumn, ColumnCenter, useTooltip } from "@pancakeswap/uikit"; +import { Currency } from "@pancakeswap/swap-sdk-core"; +import { AutoColumn, Box, Column, ColumnCenter, Flex, Text, TooltipText, useTooltip } from "@pancakeswap/uikit"; +import { FC, ReactNode, Ref, useMemo, useRef } from "react"; +import styled, { css } from "styled-components"; +import { ApprovalPhaseIcon } from "./Logos"; +import { AnimationType, slideInAnimation, slideOutAnimation } from "./styles"; +import { useUnmountingAnimation } from "./useUnmountingAnimation"; + +export enum ConfirmModalState { + REVIEWING, + WRAPPING, + RESETTING_APPROVAL, + APPROVING_TOKEN, + PERMITTING, + PENDING_CONFIRMATION, + COMPLETED, +} + +export type PendingApproveModalState = Extract< + ConfirmModalState, + ConfirmModalState.APPROVING_TOKEN | ConfirmModalState.PERMITTING | ConfirmModalState.RESETTING_APPROVAL +>; + +type AllowedAllowanceState = + | ConfirmModalState.RESETTING_APPROVAL + | ConfirmModalState.APPROVING_TOKEN + | ConfirmModalState.PERMITTING; interface ApproveModalContentProps { - title: string; - isMM?: boolean; + title: { + [step in AllowedAllowanceState]: string; + }; + isMM: boolean | undefined; isBonus: boolean; + currencyA: Currency; + asBadge: boolean; + currentStep: ConfirmModalState; + approvalModalSteps: PendingApproveModalState[]; +} + +interface StepTitleAnimationContainerProps { + disableEntranceAnimation?: boolean; + children: ReactNode; + ref?: Ref; } -export const ApproveModalContent: React.FC = ({ title, isMM, isBonus }) => { +export const StepTitleAnimationContainer: FC = styled( + Column +)` + align-items: center; + transition: display 300ms ease-in-out; + ${({ disableEntranceAnimation }) => + !disableEntranceAnimation && + css` + ${slideInAnimation} + `} + + &.${AnimationType.EXITING} { + ${slideOutAnimation} + } +`; + +export const ApproveModalContent: React.FC = ({ + title, + isMM, + isBonus, + currencyA, + asBadge, + currentStep, + approvalModalSteps, +}) => { const { t } = useTranslation(); const { targetRef, tooltip, tooltipVisible } = useTooltip( {t("Pancakeswap AMM includes V3, V2 and stable swap.")}, { placement: "top" } ); - return ( - - - - - - - - - {title} - - - {t("Swapping thru:")} - {isMM ? ( - - {t("Pancakeswap MM")} - - ) : isBonus ? ( - - {t("Bonus Route")} - - ) : ( - <> - - {t("Pancakeswap AMM")} - - {tooltipVisible && tooltip} - - )} + const currentStepContainerRef = useRef(null); + useUnmountingAnimation(currentStepContainerRef, () => AnimationType.EXITING); + const disableEntranceAnimation = approvalModalSteps[0] === currentStep; + + return useMemo( + () => ( + + + + + - - + + {approvalModalSteps.map((step: PendingApproveModalState) => { + return ( + Boolean(step === currentStep) && ( + + + {title[step]} + + + {t("Swapping thru:")} + {isMM ? ( + + {t("Pancakeswap MM")} + + ) : isBonus ? ( + + {t("Bonus Route")} + + ) : ( + <> + + {t("Pancakeswap AMM")} + + {tooltipVisible && tooltip} + + )} + + + ) + ); + })} + + + ), + [ + currencyA, + isBonus, + isMM, + t, + targetRef, + title, + tooltip, + tooltipVisible, + asBadge, + approvalModalSteps, + currentStep, + disableEntranceAnimation, + ] ); }; diff --git a/packages/widgets-internal/swap/ApproveModalContentV1.tsx b/packages/widgets-internal/swap/ApproveModalContentV1.tsx new file mode 100644 index 0000000000000..fba4048a2508f --- /dev/null +++ b/packages/widgets-internal/swap/ApproveModalContentV1.tsx @@ -0,0 +1,50 @@ +import { useTranslation } from "@pancakeswap/localization"; +import { AutoColumn, Box, ColumnCenter, Flex, Spinner, Text, TooltipText, useTooltip } from "@pancakeswap/uikit"; + +interface ApproveModalContentProps { + title: string; + isMM?: boolean; + isBonus: boolean; +} + +export const ApproveModalContentV1: React.FC = ({ title, isMM, isBonus }) => { + const { t } = useTranslation(); + const { targetRef, tooltip, tooltipVisible } = useTooltip( + {t("Pancakeswap AMM includes V3, V2 and stable swap.")}, + { placement: "top" } + ); + + return ( + + + + + + + + + {title} + + + {t("Swapping thru:")} + {isMM ? ( + + {t("Pancakeswap MM")} + + ) : isBonus ? ( + + {t("Bonus Route")} + + ) : ( + <> + + {t("Pancakeswap AMM")} + + {tooltipVisible && tooltip} + + )} + + + + ); +}; diff --git a/packages/widgets-internal/swap/Logos.tsx b/packages/widgets-internal/swap/Logos.tsx new file mode 100644 index 0000000000000..8f5cdccc776f3 --- /dev/null +++ b/packages/widgets-internal/swap/Logos.tsx @@ -0,0 +1,157 @@ +import { Currency } from "@pancakeswap/swap-sdk-core"; +import { Box, Spinner as Sp, SpinnerProps, Svg } from "@pancakeswap/uikit"; +import { useRef } from "react"; +import { styled, useTheme } from "styled-components"; +import { CurrencyLogo } from "../components/CurrencyLogo"; +import { AnimationType, FadeWrapper, RotationStyle, StyledSVG } from "./styles"; +import { useUnmountingAnimation } from "./useUnmountingAnimation"; + +const LoadingIndicator = styled(LoaderV3)` + stroke: grey; + fill: grey; + width: calc(80px + 16px); + height: calc(80px + 16px); + top: -7px; + left: -7px; + position: absolute; + z-index: -100; +`; + +const CurrencyLoaderContainer = styled(FadePresence)<{ asBadge: boolean }>` + z-index: 2; + border-radius: 50%; + position: absolute; + transition: all 250ms ease-in-out; + height: ${({ asBadge }) => (asBadge ? "25px" : "80px")}; + width: ${({ asBadge }) => (asBadge ? "25px" : "80px")}; + bottom: ${({ asBadge }) => (asBadge ? "-4px" : 0)}; + right: ${({ asBadge }) => (asBadge ? "-4px" : 0)}; + outline: ${({ theme, asBadge }) => (asBadge ? `2px solid ${theme.background}` : "")}; +`; + +const RaisedCurrencyLogo = styled(CurrencyLogo)` + z-index: 1; +`; +const AllowanceIconCircle = styled(FadePresence)<{ width: number; height: number; showSpinner: boolean }>` + display: flex; + position: relative; + height: ${({ height }) => `${height}px`}; + width: ${({ width }) => `${width}px`}; + border-radius: 50%; + align-items: center; + justify-content: center; + background-color: ${({ showSpinner, theme }) => (showSpinner ? "transparent" : theme.colors.primary)}; + z-index: 5; +`; +const PermitIcon = () => { + return ( + + + + ); +}; + +export function LoaderV3({ size = "4px", ...rest }: { size?: string; [k: string]: any }) { + const theme = useTheme(); + return ( + + + + + ); +} + +export const StyledRotatingSVG = styled(StyledSVG)` + ${RotationStyle} +`; + +export function FadePresence({ + children, + className, + $scale = false, + ...rest +}: { + children: React.ReactNode; + className?: string; + $scale?: boolean; +}) { + const ref = useRef(null); + useUnmountingAnimation(ref, () => AnimationType.EXITING); + return ( + + {children} + + ); +} + +export const Spinner: React.FC> = () => { + const ref = useRef(null); + useUnmountingAnimation(ref, () => AnimationType.EXITING); + return ( + + + + ); +}; + +export function CurrencyLoader({ currency, asBadge = false }: { currency?: Currency; asBadge?: boolean }) { + return ( + + + + ); +} + +export const ApprovalPhaseIcon = ({ + size = 80, + currency, + asBadge, +}: { + size?: number; + currency: Currency; + asBadge: boolean; +}) => { + return ( + + + + + + ); +}; + +export const PendingSwapConfirmationIcon = ({ size = 128 }: { size?: number }) => { + return ( + + + + + + ); +}; + +export function LoadingIndicatorOverlay() { + return ( + + + + ); +} diff --git a/packages/widgets-internal/swap/SwapPendingModalContent.tsx b/packages/widgets-internal/swap/SwapPendingModalContent.tsx index 2fb9421d2199b..1f513e43f53bf 100644 --- a/packages/widgets-internal/swap/SwapPendingModalContent.tsx +++ b/packages/widgets-internal/swap/SwapPendingModalContent.tsx @@ -1,15 +1,20 @@ -import { ReactNode } from "react"; import { Currency } from "@pancakeswap/sdk"; -import { Spinner, Text, Box, ArrowUpIcon, ColumnCenter, AutoColumn } from "@pancakeswap/uikit"; +import { ArrowUpIcon, AutoColumn, Box, ColumnCenter, Text } from "@pancakeswap/uikit"; +import { ReactNode, useRef } from "react"; +import { ConfirmModalState, StepTitleAnimationContainer } from "./ApproveModalContent"; +import { FadePresence, PendingSwapConfirmationIcon } from "./Logos"; import TokenTransferInfo from "./TokenTransferInfo"; +import { AnimationType } from "./styles"; +import { useUnmountingAnimation } from "./useUnmountingAnimation"; interface SwapPendingModalContentProps { title: string; showIcon?: boolean; - currencyA?: Currency; - currencyB?: Currency; + currencyA: Currency | undefined; + currencyB: Currency | undefined; amountA: string; amountB: string; + currentStep: ConfirmModalState; children?: ReactNode; } @@ -20,37 +25,47 @@ export const SwapPendingModalContent: React.FC = ( currencyB, amountA, amountB, + currentStep, children, }) => { const symbolA = currencyA?.symbol; const symbolB = currencyB?.symbol; + const currentStepContainerRef = useRef(null); + useUnmountingAnimation(currentStepContainerRef, () => AnimationType.EXITING); + return ( {showIcon ? ( - - - + + + + + ) : ( - + )} - - {title} - - - {children} + + + {title} + + + {children} + ); diff --git a/packages/widgets-internal/swap/SwapPendingModalContentV1.tsx b/packages/widgets-internal/swap/SwapPendingModalContentV1.tsx new file mode 100644 index 0000000000000..0bb57ff37108a --- /dev/null +++ b/packages/widgets-internal/swap/SwapPendingModalContentV1.tsx @@ -0,0 +1,57 @@ +import { Currency } from "@pancakeswap/sdk"; +import { ArrowUpIcon, AutoColumn, Box, ColumnCenter, Spinner, Text } from "@pancakeswap/uikit"; +import { ReactNode } from "react"; +import TokenTransferInfo from "./TokenTransferInfo"; + +interface SwapPendingModalContentProps { + title: string; + showIcon?: boolean; + currencyA?: Currency; + currencyB?: Currency; + amountA: string; + amountB: string; + children?: ReactNode; +} + +export const SwapPendingModalContentV1: React.FC = ({ + title, + showIcon, + currencyA, + currencyB, + amountA, + amountB, + children, +}) => { + const symbolA = currencyA?.symbol; + const symbolB = currencyB?.symbol; + + return ( + + {showIcon ? ( + + + + ) : ( + + + + + + )} + + + {title} + + + {children} + + + ); +}; diff --git a/packages/widgets-internal/swap/SwapTransactionReceiptModalContent.tsx b/packages/widgets-internal/swap/SwapTransactionReceiptModalContent.tsx index 80e81cd5c768c..b2f6c276dad2e 100644 --- a/packages/widgets-internal/swap/SwapTransactionReceiptModalContent.tsx +++ b/packages/widgets-internal/swap/SwapTransactionReceiptModalContent.tsx @@ -1,19 +1,30 @@ import { useTranslation } from "@pancakeswap/localization"; -import { PropsWithChildren } from "react"; -import { Text, Box, CheckmarkCircleIcon, AutoColumn } from "@pancakeswap/uikit"; +import { AutoColumn, Box, CheckmarkCircleIcon, Text } from "@pancakeswap/uikit"; +import { ReactNode } from "react"; +import { StepTitleAnimationContainer } from "./ApproveModalContent"; +import { FadePresence } from "./Logos"; -export const SwapTransactionReceiptModalContent: React.FC = ({ children }) => { +interface SwaReceiptContents { + explorerLink: ReactNode; + children: ReactNode; +} +export const SwapTransactionReceiptModalContent = ({ explorerLink, children }: SwaReceiptContents) => { const { t } = useTranslation(); return ( - - - - - - {t("Transaction receipt")} - + + + + + + + + + {t("Transaction receipt")} + + {explorerLink} + {children} diff --git a/packages/widgets-internal/swap/SwapTransactionReceiptModalContentV1.tsx b/packages/widgets-internal/swap/SwapTransactionReceiptModalContentV1.tsx new file mode 100644 index 0000000000000..0e4d99b86c5d6 --- /dev/null +++ b/packages/widgets-internal/swap/SwapTransactionReceiptModalContentV1.tsx @@ -0,0 +1,21 @@ +import { useTranslation } from "@pancakeswap/localization"; +import { AutoColumn, Box, CheckmarkCircleIcon, Text } from "@pancakeswap/uikit"; +import { PropsWithChildren } from "react"; + +export const SwapTransactionReceiptModalContentV1: React.FC = ({ children }) => { + const { t } = useTranslation(); + + return ( + + + + + + + {t("Transaction receipt")} + + {children} + + + ); +}; diff --git a/packages/widgets-internal/swap/TransactionErrorContent.tsx b/packages/widgets-internal/swap/TransactionErrorContent.tsx index 09e7233cd4d22..da37a36c42f0e 100644 --- a/packages/widgets-internal/swap/TransactionErrorContent.tsx +++ b/packages/widgets-internal/swap/TransactionErrorContent.tsx @@ -2,6 +2,8 @@ import { ReactElement } from "react"; import { useTranslation } from "@pancakeswap/localization"; import { styled } from "styled-components"; import { AutoColumn, ErrorIcon, Text, Flex, Button } from "@pancakeswap/uikit"; +import { StepTitleAnimationContainer } from "./ApproveModalContent"; +import { FadePresence } from "./Logos"; const Wrapper = styled.div` width: 100%; @@ -18,16 +20,22 @@ export function TransactionErrorContent({ return ( - - - {message} - + + + + + + {message} + + {onDismiss ? ( - - - + + + + + ) : null} ); diff --git a/packages/widgets-internal/swap/index.ts b/packages/widgets-internal/swap/index.ts index 066a605ec2c0f..59ad2cb7fb089 100644 --- a/packages/widgets-internal/swap/index.ts +++ b/packages/widgets-internal/swap/index.ts @@ -1,16 +1,20 @@ -export * from "./confirmPriceImpactWithoutFee"; +export * from "./ApproveModalContent"; +export * from "./ApproveModalContentV1"; +export * from "./ConfirmationModalContent"; +export * from "./ConfirmationPendingContent"; export * from "./ExpertModal"; export * from "./ImportList"; +export * from "./LiquidityChartRangeInput/InfoBox"; export * from "./ListLogo"; +export * from "./Logos"; export * from "./NumericalInput"; export * from "./SwapCallbackError"; +export * from "./SwapPendingModalContent"; +export * from "./SwapPendingModalContentV1"; +export * from "./SwapTransactionReceiptModalContent"; +export * from "./SwapTransactionReceiptModalContentV1"; export * as Swap from "./SwapWidget"; +export * from "./TokenRowButton"; export * from "./TransactionErrorContent"; -export * from "./ConfirmationModalContent"; -export * from "./ConfirmationPendingContent"; +export * from "./confirmPriceImpactWithoutFee"; export * from "./withCurrencyLogo"; -export * from "./TokenRowButton"; -export * from "./ApproveModalContent"; -export * from "./SwapPendingModalContent"; -export * from "./SwapTransactionReceiptModalContent"; -export * from "./LiquidityChartRangeInput/InfoBox"; diff --git a/packages/widgets-internal/swap/styles.tsx b/packages/widgets-internal/swap/styles.tsx new file mode 100644 index 0000000000000..220eb514df5c9 --- /dev/null +++ b/packages/widgets-internal/swap/styles.tsx @@ -0,0 +1,92 @@ +import styled, { keyframes, css } from "styled-components"; + +export enum AnimationType { + EXITING = "exiting", +} + +export const LogoContainer = styled.div` + height: 48px; + width: 48px; + position: relative; + display: flex; + border-radius: 50%; + overflow: visible; +`; + +export const slideIn = keyframes` + from { opacity: 0; transform: translateX(20px) } + to { opacity: 1; transform: translateX(0px) } +`; +export const slideInAnimation = css` + animation: ${slideIn} 300ms ease-in-out; +`; +export const slideOut = keyframes` + from { opacity: 1; transform: translateX(0px) } + to { opacity: 0; transform: translateX(-40px) } +`; +export const slideOutAnimation = css` + animation: ${slideOut} 300ms ease-in-out; +`; + +export const fadeIn = keyframes` + from { opacity: 0;} + to { opacity: 1;} +`; +export const fadeAndScaleIn = keyframes` + from { opacity: 0; transform: scale(0); } + to { opacity: 1; transform: scale(1); } +`; +export const fadeInAnimation = css` + animation: ${fadeIn} 250ms ease-in-out; +`; +export const fadeAndScaleInAnimation = css` + animation: ${fadeAndScaleIn} 250ms ease-in-out; +`; + +export const fadeOut = keyframes` + from { opacity: 1; } + to { opacity: 0; } +`; +export const fadeAndScaleOut = keyframes` + from { opacity: 1; transform: scale(1); } + to { opacity: 0; transform: scale(0); } +`; +export const fadeOutAnimation = css` + animation: ${fadeOut} 250ms ease-in-out; +`; +export const fadeAndScaleOutAnimation = css` + animation: ${fadeAndScaleOut} 250ms ease-in-out; +`; + +export const FadeWrapper = styled.div<{ $scale: boolean }>` + transition: display 400ms ease-in-out; + transform 250ms ease-in-out; + ${({ $scale }) => ($scale ? fadeAndScaleInAnimation : fadeInAnimation)} + + &.${AnimationType.EXITING} { + ${({ $scale }) => ($scale ? fadeAndScaleOutAnimation : fadeOutAnimation)} + } +`; + +const rotateAnimation = keyframes` + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +`; + +export const RotationStyle = css` + animation: 2s ${rotateAnimation} linear infinite; +`; + +export const StyledSVG = styled.svg<{ size: string; stroke?: string; fill?: string }>` + height: ${({ size }) => size}; + width: ${({ size }) => size}; + path { + stroke: ${({ stroke }) => stroke}; + background: grey; + fill: ${({ fill }) => fill}; + } +`; diff --git a/packages/widgets-internal/swap/useUnmountingAnimation.ts b/packages/widgets-internal/swap/useUnmountingAnimation.ts new file mode 100644 index 0000000000000..734b51a210db1 --- /dev/null +++ b/packages/widgets-internal/swap/useUnmountingAnimation.ts @@ -0,0 +1,80 @@ +import { RefObject, useEffect } from "react"; + +/** + * Checks whether a given node is currently animating. + * + * @param node - The node to check for ongoing animations. + * @returns - true if the node is animating; false otherwise. + */ +function isAnimating(node?: Animatable | Document): boolean { + return (node?.getAnimations?.().length ?? 0) > 0; +} + +/** + * This hook runs an unmounting animation on a specified node. + * + * The hook will also run the animation on any additional elements specified in + * the `animatedElements` parameter. If no additional elements are specified, + * the animation will only run on the provided node. + * + * After any of the animated elements have completed their animation, `node` is removed from its parent. + * + * @param node - The node to animate and remove. + * @param getAnimatingClass - A function that returns the CSS class to add to the animating elements. + * @param animatedElements - Additional elements to animate. + * @param skip - Whether to skip the animation and remove the node immediately. + */ +export function useUnmountingAnimation( + node: RefObject, + getAnimatingClass: () => string, + animatedElements?: RefObject[], + skip = false +) { + useEffect(() => { + const { current } = node; + + // Gather all elements to animate, defaulting to the current node if none are specified. + const animated = animatedElements?.map((element) => element.current) ?? [current]; + const parent = current?.parentElement; + const removeChild = parent?.removeChild; + + // If we can't remove the child or skipping is requested, stop here. + if (!(parent && removeChild) || skip) return; + + // Override the parent's removeChild function to add our animation logic + // eslint-disable-next-line func-names + parent.removeChild = function (child: T) { + // If the current child is the one being removed and it's supposed to animate + if ((child as Node) === current && animated) { + // Add animation class to all elements + animated.forEach((element) => element?.classList.add(getAnimatingClass())); + + // Check if any of the animated elements is animating + const animating = animated.find((element) => isAnimating(element ?? undefined)); + if (animating) { + // If an element is animating, we wait for the animation to end before removing the child + animating?.addEventListener("animationend", (x) => { + // This check is needed because the animationend event will fire for all animations on the + // element or its children. + if (x.target === animating) { + removeChild.call(parent, child); + } + }); + } else { + // If no element is animating, we remove the child immediately + removeChild.call(parent, child); + } + // We've handled the removal, so we return the child + return child; + } + // If the child isn't the one we're supposed to animate, remove it normally + return removeChild.call(parent, child) as T; + }; + + // Reset the removeChild function to its original value when the component is unmounted + // eslint-disable-next-line consistent-return + return () => { + parent.removeChild = removeChild; + }; + }, [animatedElements, getAnimatingClass, node, skip]); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6cb7246fd700..cf69cd4c52a50 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,9 @@ importers: ws: specifier: ^8.13.0 version: 8.14.2(bufferutil@4.0.8)(utf-8-validate@5.0.10) + zod: + specifier: ^3.22.3 + version: 3.22.4 devDependencies: '@changesets/cli': specifier: ^2.27.1 @@ -447,7 +450,7 @@ importers: dependencies: '@0xsquid/widget': specifier: ^1.6.0 - version: 1.6.16(@types/react@18.2.37)(@wagmi/core@0.10.17)(encoding@0.1.13)(ethers@5.7.2)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2) + version: 1.6.16(@types/react@18.2.37)(@wagmi/core@0.10.17)(encoding@0.1.13)(ethers@5.7.2)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(zod@3.22.4) '@material-ui/core': specifier: ^4.12.4 version: 4.12.4(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) @@ -474,7 +477,7 @@ importers: version: 2.3.2(@types/node@18.0.4)(next@13.5.6)(webpack@5.89.0) '@wagmi/core': specifier: ^0.10.11 - version: 0.10.17(@types/react@18.2.37)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2) + version: 0.10.17(@types/react@18.2.37)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2)(zod@3.22.4) '@wormhole-foundation/wormhole-connect': specifier: 0.1.7 version: 0.1.7(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) @@ -654,7 +657,7 @@ importers: version: 1.1.4 '@binance/w3w-wagmi-connector': specifier: 1.1.4 - version: 1.1.4(encoding@0.1.13)(viem@1.19.11)(wagmi@1.4.7) + version: 1.1.4(encoding@0.1.13)(viem@1.19.11)(wagmi@1.4.13) '@cyberlab/cyber-app-sdk': specifier: 2.4.4 version: 2.4.4(@types/node@13.13.52)(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(zod@3.22.4) @@ -697,6 +700,9 @@ importers: '@pancakeswap/multicall': specifier: workspace:* version: link:../../packages/multicall + '@pancakeswap/permit2-sdk': + specifier: workspace:* + version: link:../../packages/permit2-sdk '@pancakeswap/pools': specifier: workspace:* version: link:../../packages/pools @@ -727,6 +733,9 @@ importers: '@pancakeswap/uikit': specifier: workspace:* version: link:../../packages/uikit + '@pancakeswap/universal-router-sdk': + specifier: workspace:* + version: link:../../packages/universal-router-sdk '@pancakeswap/utils': specifier: workspace:* version: link:../../packages/utils @@ -914,8 +923,8 @@ importers: specifier: 1.19.11 version: 1.19.11(typescript@5.2.2)(zod@3.22.4) wagmi: - specifier: 1.4.7 - version: 1.4.7(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) + specifier: 1.4.13 + version: 1.4.13(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) zod: specifier: ^3.22.3 version: 3.22.4 @@ -1361,8 +1370,8 @@ importers: specifier: 1.19.11 version: 1.19.11(typescript@5.2.2)(zod@3.22.4) wagmi: - specifier: 1.4.7 - version: 1.4.7(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) + specifier: 1.4.13 + version: 1.4.13(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) packages/localization: dependencies: @@ -1417,6 +1426,28 @@ importers: specifier: 13.5.6 version: 13.5.6(@babel/core@7.23.3)(react-dom@18.2.0)(react@18.2.0) + packages/permit2-sdk: + dependencies: + '@pancakeswap/chains': + specifier: workspace:* + version: link:../chains + '@pancakeswap/sdk': + specifier: workspace:* + version: link:../swap-sdk + tiny-invariant: + specifier: ^1.3.1 + version: 1.3.1 + viem: + specifier: 1.19.11 + version: 1.19.11(typescript@5.2.2)(zod@3.22.4) + devDependencies: + tslib: + specifier: ^2.6.2 + version: 2.6.2 + tsup: + specifier: ^6.7.0 + version: 6.7.0(postcss@8.4.33)(typescript@5.2.2) + packages/pools: dependencies: '@pancakeswap/chains': @@ -1438,8 +1469,8 @@ importers: specifier: 1.19.11 version: 1.19.11(typescript@5.2.2)(zod@3.22.4) wagmi: - specifier: 1.4.7 - version: 1.4.7(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) + specifier: 1.4.13 + version: 1.4.13(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) devDependencies: '@pancakeswap/tsconfig': specifier: workspace:* @@ -1481,8 +1512,8 @@ importers: specifier: 1.19.11 version: 1.19.11(typescript@5.2.2)(zod@3.22.4) wagmi: - specifier: 1.4.7 - version: 1.4.7(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) + specifier: 1.4.13 + version: 1.4.13(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) devDependencies: '@pancakeswap/tsconfig': specifier: workspace:* @@ -1509,8 +1540,8 @@ importers: specifier: 1.19.11 version: 1.19.11(typescript@5.2.2)(zod@3.22.4) wagmi: - specifier: 1.4.7 - version: 1.4.7(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) + specifier: 1.4.13 + version: 1.4.13(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) devDependencies: '@pancakeswap/sdk': specifier: workspace:* @@ -1817,8 +1848,8 @@ importers: specifier: ^2.2.0 version: 2.6.2 wagmi: - specifier: 1.4.7 - version: 1.4.7(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) + specifier: 1.4.13 + version: 1.4.13(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) devDependencies: '@babel/core': specifier: ^7.20.12 @@ -1977,6 +2008,46 @@ importers: specifier: ^4.0.3 version: 4.2.1(typescript@5.2.2)(vite@5.0.12) + packages/universal-router-sdk: + dependencies: + '@pancakeswap/chains': + specifier: workspace:* + version: link:../chains + '@pancakeswap/permit2-sdk': + specifier: workspace:* + version: link:../permit2-sdk + '@pancakeswap/sdk': + specifier: workspace:* + version: link:../swap-sdk + '@pancakeswap/smart-router': + specifier: workspace:* + version: link:../smart-router + '@pancakeswap/v3-sdk': + specifier: workspace:* + version: link:../v3-sdk + abitype: + specifier: ^0.9.8 + version: 0.9.8(typescript@5.2.2)(zod@3.22.4) + tiny-invariant: + specifier: ^1.1.0 + version: 1.3.1 + viem: + specifier: 1.19.11 + version: 1.19.11(typescript@5.2.2)(zod@3.22.4) + devDependencies: + '@pancakeswap/tokens': + specifier: workspace:* + version: link:../tokens + '@types/node': + specifier: ^15.12.2 + version: 15.12.2 + tslib: + specifier: ^2.3.0 + version: 2.6.2 + tsup: + specifier: ^6.7.0 + version: 6.7.0(postcss@8.4.33)(typescript@5.2.2) + packages/utils: dependencies: '@pancakeswap/chains': @@ -2078,8 +2149,8 @@ importers: specifier: 1.19.11 version: 1.19.11(typescript@5.2.2)(zod@3.22.4) wagmi: - specifier: 1.4.7 - version: 1.4.7(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) + specifier: 1.4.13 + version: 1.4.13(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) packages/widgets-internal: dependencies: @@ -2140,7 +2211,7 @@ importers: devDependencies: '@sentry/nextjs': specifier: ^7.0.0 - version: 7.80.0(encoding@0.1.13)(next@13.5.6)(react@18.2.0)(webpack@5.89.0) + version: 7.80.0(encoding@0.1.13)(next@13.5.6)(react@18.2.0) '@types/lodash': specifier: ^4.14.168 version: 4.14.201 @@ -2222,7 +2293,7 @@ packages: - utf-8-validate dev: false - /@0xsquid/widget@1.6.16(@types/react@18.2.37)(@wagmi/core@0.10.17)(encoding@0.1.13)(ethers@5.7.2)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2): + /@0xsquid/widget@1.6.16(@types/react@18.2.37)(@wagmi/core@0.10.17)(encoding@0.1.13)(ethers@5.7.2)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(zod@3.22.4): resolution: {integrity: sha512-k1LTEwVkLf9KAV7POU6v/doRBU0PEs3nEFQuYY8IC6IiKgIh6ypydEOufadgzhrbjC5xqhV0aQ4JYOnvgKfjbw==} peerDependencies: ethers: ^5.7.2 || ^4.0.49 @@ -2241,7 +2312,7 @@ packages: '@tanstack/react-query-devtools': 4.36.1(@tanstack/react-query@4.36.1)(react-dom@18.2.0)(react@18.2.0) '@types/color': 3.0.6 '@types/lodash': 4.14.201 - '@wagmi/connectors': 0.3.16(@wagmi/core@0.10.17)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2) + '@wagmi/connectors': 0.3.16(@wagmi/core@0.10.17)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2)(zod@3.22.4) axios: 0.27.2(debug@4.3.4) bignumber.js: 9.1.2 clsx: 1.2.1 @@ -2256,7 +2327,7 @@ packages: react-dom: 18.2.0(react@18.2.0) react-icons: 4.11.0(react@18.2.0) secretjs: 1.12.1(encoding@0.1.13) - wagmi: 0.12.19(@types/react@18.2.37)(encoding@0.1.13)(ethers@5.7.2)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2) + wagmi: 0.12.19(@types/react@18.2.37)(encoding@0.1.13)(ethers@5.7.2)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(zod@3.22.4) zustand: 4.4.6(@types/react@18.2.37)(react@18.2.0) transitivePeerDependencies: - '@azure/app-configuration' @@ -3882,7 +3953,7 @@ packages: js-base64: 3.7.5 dev: false - /@binance/w3w-wagmi-connector@1.1.4(encoding@0.1.13)(viem@1.19.11)(wagmi@1.4.7): + /@binance/w3w-wagmi-connector@1.1.4(encoding@0.1.13)(viem@1.19.11)(wagmi@1.4.13): resolution: {integrity: sha512-wMC6XjRU1IRb/CYWcaM0KbIhdzHBhx9ArrhZUkg2G11K1jwYgM0WdwSdOauRHHotIP958tFfimmwL+pmn2LQdA==} peerDependencies: viem: ^1.19.3 @@ -3891,7 +3962,7 @@ packages: '@binance/w3w-ethereum-provider': 1.1.4(encoding@0.1.13) '@binance/w3w-utils': 1.1.4 viem: 1.19.11(typescript@5.2.2)(zod@3.22.4) - wagmi: 1.4.7(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) + wagmi: 1.4.13(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) transitivePeerDependencies: - bufferutil - debug @@ -4152,7 +4223,7 @@ packages: /@confio/ics23@0.6.8: resolution: {integrity: sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w==} dependencies: - '@noble/hashes': 1.3.2 + '@noble/hashes': 1.3.3 protobufjs: 6.11.4 dev: false @@ -4191,7 +4262,7 @@ packages: '@cosmjs/encoding': 0.31.3 '@cosmjs/math': 0.31.3 '@cosmjs/utils': 0.31.3 - '@noble/hashes': 1.3.2 + '@noble/hashes': 1.3.3 bn.js: 5.2.1 elliptic: 6.5.4 libsodium-wrappers-sumo: 0.7.13 @@ -4337,7 +4408,7 @@ packages: '@wagmi/connectors': 3.1.5(@types/react@18.2.37)(encoding@0.1.13)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) ts-node: 10.9.1(@types/node@13.13.52)(typescript@5.2.2) viem: 1.19.11(typescript@5.2.2)(zod@3.22.4) - wagmi: 1.4.7(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) + wagmi: 1.4.13(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -5983,7 +6054,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 13.13.52 + '@types/node': 18.18.9 '@types/yargs': 15.0.18 chalk: 4.1.2 dev: true @@ -5995,7 +6066,7 @@ packages: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.5.7 + '@types/node': 18.18.9 '@types/yargs': 17.0.31 chalk: 4.1.2 dev: true @@ -6112,6 +6183,7 @@ packages: /@ledgerhq/connect-kit-loader@1.1.2(patch_hash=xlsebypdiwsukm3c2ow7voujra): resolution: {integrity: sha512-mscwGroSJQrCTjtNGBu+18FQbZYA4+q6Tyx6K7CXHl6AwgZKbWfZYdgP2F+fyZcRUdGRsMX8QtvU61VcGGtO1A==} + dev: false patched: true /@ledgerhq/iframe-provider@0.4.3: @@ -7948,6 +8020,18 @@ packages: - typescript - utf-8-validate - zod + dev: false + + /@safe-global/safe-apps-provider@0.18.2(typescript@5.2.2)(zod@3.22.4): + resolution: {integrity: sha512-yHHAcppwE7aIUWEeZiYAClQzZCdP5l0Kbd0CBlhKAsTcqZnx4Gh3G3G3frY5LlWcGzp9qmQ5jv+J1GBpaZLDgw==} + dependencies: + '@safe-global/safe-apps-sdk': 9.0.0(typescript@5.2.2)(zod@3.22.4) + events: 3.3.0 + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + - zod /@safe-global/safe-apps-sdk@7.11.0: resolution: {integrity: sha512-RDamzPM1Lhhiiz0O+Dn6FkFqIh47jmZX+HCV/BBnBBOSKfBJE//IGD3+02zMgojXHTikQAburdPes9qmH1SA1A==} @@ -7979,6 +8063,7 @@ packages: - typescript - utf-8-validate - zod + dev: false /@safe-global/safe-apps-sdk@8.1.0(typescript@5.2.2)(zod@3.22.4): resolution: {integrity: sha512-XJbEPuaVc7b9n23MqlF6c+ToYIS3f7P2Sel8f3cSBQ9WORE4xrSuvhMpK9fDSFqJ7by/brc+rmJR/5HViRr0/w==} @@ -7991,6 +8076,17 @@ packages: - utf-8-validate - zod + /@safe-global/safe-apps-sdk@9.0.0(typescript@5.2.2)(zod@3.22.4): + resolution: {integrity: sha512-fEqmQBU3JqTjORSl3XYrcaxdxkUqeeM39qsQjqCzzTHioN8DEfg3JCLq6EBoXzcKTVOYi8SPzLV7KJccdDw+4w==} + dependencies: + '@safe-global/safe-gateway-typescript-sdk': 3.13.2 + viem: 1.19.11(typescript@5.2.2)(zod@3.22.4) + transitivePeerDependencies: + - bufferutil + - typescript + - utf-8-validate + - zod + /@safe-global/safe-gateway-typescript-sdk@3.13.2: resolution: {integrity: sha512-kGlJecJHBzGrGTq/yhLANh56t+Zur6Ubpt+/w03ARX1poDb4TM8vKU3iV8tuYpk359PPWp+Qvjnqb9oW2YQcYw==} engines: {node: '>=16'} @@ -8002,7 +8098,7 @@ packages: resolution: {integrity: sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==} dependencies: '@noble/curves': 1.1.0 - '@noble/hashes': 1.3.2 + '@noble/hashes': 1.3.3 '@scure/base': 1.1.3 dev: true @@ -8010,7 +8106,7 @@ packages: resolution: {integrity: sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA==} dependencies: '@noble/curves': 1.2.0 - '@noble/hashes': 1.3.2 + '@noble/hashes': 1.3.3 '@scure/base': 1.1.3 /@scure/bip39@1.2.1: @@ -8069,6 +8165,37 @@ packages: '@sentry/utils': 7.80.0 localforage: 1.10.0 + /@sentry/nextjs@7.80.0(encoding@0.1.13)(next@13.5.6)(react@18.2.0): + resolution: {integrity: sha512-4KZVZV1U/1ldmVKS85n31MSKIWJElcy9gZW+PTyQnrlCxbQnnhXBde95+TXmvO1DKTNhVphv/2DXq7bmxBW9bA==} + engines: {node: '>=8'} + peerDependencies: + next: ^10.0.8 || ^11.0 || ^12.0 || ^13.0 || ^14.0 + react: 16.x || 17.x || 18.x + webpack: '>= 4.0.0' + peerDependenciesMeta: + webpack: + optional: true + dependencies: + '@rollup/plugin-commonjs': 24.0.0(rollup@2.78.0) + '@sentry/core': 7.80.0 + '@sentry/integrations': 7.80.0 + '@sentry/node': 7.80.0 + '@sentry/react': 7.80.0(react@18.2.0) + '@sentry/types': 7.80.0 + '@sentry/utils': 7.80.0 + '@sentry/vercel-edge': 7.80.0 + '@sentry/webpack-plugin': 1.20.0(encoding@0.1.13) + chalk: 3.0.0 + next: 13.5.6(@babel/core@7.23.3)(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + resolve: 1.22.8 + rollup: 2.78.0 + stacktrace-parser: 0.1.10 + transitivePeerDependencies: + - encoding + - supports-color + dev: true + /@sentry/nextjs@7.80.0(encoding@0.1.13)(next@13.5.6)(react@18.2.0)(webpack@5.89.0): resolution: {integrity: sha512-4KZVZV1U/1ldmVKS85n31MSKIWJElcy9gZW+PTyQnrlCxbQnnhXBde95+TXmvO1DKTNhVphv/2DXq7bmxBW9bA==} engines: {node: '>=8'} @@ -8099,6 +8226,7 @@ packages: transitivePeerDependencies: - encoding - supports-color + dev: false /@sentry/node@7.80.0: resolution: {integrity: sha512-J35fqe8J5ac/17ZXT0ML3opYGTOclqYNE9Sybs1y9n6BqacHyzH8By72YrdI03F7JJDHwrcGw+/H8hGpkCwi0Q==} @@ -8218,7 +8346,7 @@ packages: dependencies: '@babel/runtime': 7.23.9 '@noble/curves': 1.2.0 - '@noble/hashes': 1.3.2 + '@noble/hashes': 1.3.3 '@solana/buffer-layout': 4.0.1 agentkeepalive: 4.5.0 bigint-buffer: 1.1.5 @@ -9550,7 +9678,7 @@ packages: /@types/better-sqlite3@7.6.7: resolution: {integrity: sha512-+c2YGPWY5831v3uj2/X0HRTK94u1GXU3sCnLqu7AKlxlSfawswnAiJR//TFzSL5azWsLQkG/uS+YnnqHtuZxPw==} dependencies: - '@types/node': 20.5.7 + '@types/node': 18.18.9 dev: true /@types/big.js@4.0.5: @@ -9561,7 +9689,7 @@ packages: resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} dependencies: '@types/connect': 3.4.38 - '@types/node': 20.5.7 + '@types/node': 18.18.9 dev: true /@types/cacheable-request@6.0.3: @@ -9601,7 +9729,7 @@ packages: /@types/connect@3.4.38: resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: - '@types/node': 20.5.7 + '@types/node': 18.18.9 /@types/cookie@0.4.1: resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} @@ -9610,7 +9738,7 @@ packages: /@types/cross-spawn@6.0.5: resolution: {integrity: sha512-wsIMP68FvGXk+RaWhraz6Xp4v7sl4qwzHAmtPaJEN2NRTXXI9LtFawUpeTsBNL/pd6QoLStdytCaAyiK7AEd/Q==} dependencies: - '@types/node': 20.5.7 + '@types/node': 18.18.9 dev: true /@types/d3-array@3.2.1: @@ -9877,7 +10005,7 @@ packages: /@types/express-serve-static-core@4.17.41: resolution: {integrity: sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==} dependencies: - '@types/node': 20.5.7 + '@types/node': 18.18.9 '@types/qs': 6.9.10 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -9904,7 +10032,7 @@ packages: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.5.7 + '@types/node': 18.18.9 dev: true /@types/graceful-fs@4.1.9: @@ -10039,7 +10167,7 @@ packages: /@types/node-forge@1.3.9: resolution: {integrity: sha512-meK88cx/sTalPSLSoCzkiUB4VPIFHmxtXm5FaaqRDqBX2i/Sy8bJ4odsan0b20RBjPh06dAQ+OTTdnyQyhJZyQ==} dependencies: - '@types/node': 20.5.7 + '@types/node': 18.18.9 dev: true /@types/node@10.12.18: @@ -10060,6 +10188,10 @@ packages: resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} dev: true + /@types/node@15.12.2: + resolution: {integrity: sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==} + dev: true + /@types/node@16.18.61: resolution: {integrity: sha512-k0N7BqGhJoJzdh6MuQg1V1ragJiXTh8VUBAZTWjJ9cUq23SG0F0xavOwZbhiP4J3y20xd6jxKx+xNUhkMAi76Q==} dev: false @@ -10074,6 +10206,7 @@ packages: /@types/node@20.5.7: resolution: {integrity: sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==} + dev: false /@types/normalize-package-data@2.4.4: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -10100,7 +10233,7 @@ packages: /@types/randombytes@2.0.3: resolution: {integrity: sha512-+NRgihTfuURllWCiIAhm1wsJqzsocnqXM77V/CalsdJIYSRGEHMnritxh+6EsBklshC+clo1KgnN14qgSGeQdw==} dependencies: - '@types/node': 13.13.52 + '@types/node': 18.18.9 dev: false /@types/range-parser@1.2.7: @@ -10209,7 +10342,7 @@ packages: resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} dependencies: '@types/mime': 1.3.5 - '@types/node': 20.5.7 + '@types/node': 18.18.9 dev: true /@types/serve-static@1.15.5: @@ -10217,7 +10350,7 @@ packages: dependencies: '@types/http-errors': 2.0.4 '@types/mime': 3.0.4 - '@types/node': 20.5.7 + '@types/node': 18.18.9 dev: true /@types/simplemde@1.11.11: @@ -10905,7 +11038,7 @@ packages: typescript: 5.2.2 dev: false - /@wagmi/connectors@0.3.16(@wagmi/core@0.10.17)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2): + /@wagmi/connectors@0.3.16(@wagmi/core@0.10.17)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2)(zod@3.22.4): resolution: {integrity: sha512-WtiFyvai6IWbV7DhujjmtJF0m+FFQCiIDrtHsNf1xio0gBfpnO8rT9PZQQf0uxuLn0nLxqXqYMMwzPipUNaIcg==} peerDependencies: '@wagmi/core': '>=0.9.x' @@ -10921,11 +11054,11 @@ packages: '@ledgerhq/connect-kit-loader': 1.1.2(patch_hash=xlsebypdiwsukm3c2ow7voujra) '@safe-global/safe-apps-provider': 0.15.2 '@safe-global/safe-apps-sdk': 7.11.0 - '@wagmi/core': 0.10.17(@types/react@18.2.37)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2) + '@wagmi/core': 0.10.17(@types/react@18.2.37)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2)(zod@3.22.4) '@walletconnect/ethereum-provider': 2.7.0(@web3modal/standalone@2.4.3)(encoding@0.1.13) '@walletconnect/legacy-provider': 2.0.0(encoding@0.1.13) '@web3modal/standalone': 2.4.3(react@18.2.0) - abitype: 0.3.0(typescript@5.2.2) + abitype: 0.3.0(typescript@5.2.2)(zod@3.22.4) ethers: 5.7.2 eventemitter3: 4.0.7 typescript: 5.2.2 @@ -10950,7 +11083,7 @@ packages: - zod dev: false - /@wagmi/connectors@0.3.24(@types/react@18.2.37)(@wagmi/core@0.10.17)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2): + /@wagmi/connectors@0.3.24(@types/react@18.2.37)(@wagmi/core@0.10.17)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2)(zod@3.22.4): resolution: {integrity: sha512-1pI0G9HRblc651dCz9LXuEu/zWQk23XwOUYqJEINb/c2TTLtw5TnTRIcefxxK6RnxeJvcKfnmK0rdZp/4ujFAA==} peerDependencies: '@wagmi/core': '>=0.9.x' @@ -10966,11 +11099,11 @@ packages: '@ledgerhq/connect-kit-loader': 1.1.2(patch_hash=xlsebypdiwsukm3c2ow7voujra) '@safe-global/safe-apps-provider': 0.15.2 '@safe-global/safe-apps-sdk': 7.11.0 - '@wagmi/core': 0.10.17(@types/react@18.2.37)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2) + '@wagmi/core': 0.10.17(@types/react@18.2.37)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2)(zod@3.22.4) '@walletconnect/ethereum-provider': 2.9.0(@walletconnect/modal@2.6.2)(encoding@0.1.13) '@walletconnect/legacy-provider': 2.0.0(encoding@0.1.13) '@walletconnect/modal': 2.6.2(@types/react@18.2.37)(react@18.2.0) - abitype: 0.3.0(typescript@5.2.2) + abitype: 0.3.0(typescript@5.2.2)(zod@3.22.4) ethers: 5.7.2 eventemitter3: 4.0.7 typescript: 5.2.2 @@ -10995,6 +11128,46 @@ packages: - zod dev: false + /@wagmi/connectors@3.1.11(@types/react@18.2.37)(encoding@0.1.13)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4): + resolution: {integrity: sha512-wzxp9f9PtSUFjDUP/QDjc1t7HON4D8wrVKsw35ejdO8hToDpx1gU9lwH/47Zo/1zExGezQc392sjoHSszYd7OA==} + peerDependencies: + typescript: '>=5.0.4' + viem: '>=0.3.35' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@coinbase/wallet-sdk': 3.7.2(encoding@0.1.13) + '@safe-global/safe-apps-provider': 0.18.2(typescript@5.2.2)(zod@3.22.4) + '@safe-global/safe-apps-sdk': 8.1.0(typescript@5.2.2)(zod@3.22.4) + '@walletconnect/ethereum-provider': 2.11.0(@types/react@18.2.37)(encoding@0.1.13)(react@18.2.0) + '@walletconnect/legacy-provider': 2.0.0(encoding@0.1.13) + '@walletconnect/modal': 2.6.2(@types/react@18.2.37)(react@18.2.0) + '@walletconnect/utils': 2.11.0 + abitype: 0.8.7(typescript@5.2.2)(zod@3.22.4) + eventemitter3: 4.0.7 + typescript: 5.2.2 + viem: 1.19.11(typescript@5.2.2)(zod@3.22.4) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@types/react' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - react + - supports-color + - utf-8-validate + - zod + /@wagmi/connectors@3.1.5(@types/react@18.2.37)(encoding@0.1.13)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4): resolution: {integrity: sha512-aE4rWZbivqWa9HqjiLDPtwROH2b1Az+lBVMeZ3o/aFxGNGNEkdrSAMOUG15/UFy3VnN6HqGOtTobOBZ10JhfNQ==} peerDependencies: @@ -11035,8 +11208,9 @@ packages: - supports-color - utf-8-validate - zod + dev: false - /@wagmi/core@0.10.17(@types/react@18.2.37)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2): + /@wagmi/core@0.10.17(@types/react@18.2.37)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2)(zod@3.22.4): resolution: {integrity: sha512-qud45y3IlHp7gYWzoFeyysmhyokRie59Xa5tcx5F1E/v4moD5BY0kzD26mZW/ZQ3WZuVK/lZwiiPRqpqWH52Gw==} peerDependencies: ethers: '>=5.5.1 <6' @@ -11046,8 +11220,8 @@ packages: optional: true dependencies: '@wagmi/chains': 0.2.22(typescript@5.2.2) - '@wagmi/connectors': 0.3.24(@types/react@18.2.37)(@wagmi/core@0.10.17)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2) - abitype: 0.3.0(typescript@5.2.2) + '@wagmi/connectors': 0.3.24(@types/react@18.2.37)(@wagmi/core@0.10.17)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2)(zod@3.22.4) + abitype: 0.3.0(typescript@5.2.2)(zod@3.22.4) ethers: 5.7.2 eventemitter3: 4.0.7 typescript: 5.2.2 @@ -11097,6 +11271,42 @@ packages: - typescript dev: false + /@wagmi/core@1.4.13(@types/react@18.2.37)(encoding@0.1.13)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4): + resolution: {integrity: sha512-ytMCvXbBOgfDu9Qw67279wq/jNEe7EZLjLyekX7ROnvHRADqFr3lwZI6ih41UmtRZAmXAx8Ghyuqy154EjB5mQ==} + peerDependencies: + typescript: '>=5.0.4' + viem: '>=0.3.35' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@wagmi/connectors': 3.1.11(@types/react@18.2.37)(encoding@0.1.13)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) + abitype: 0.8.7(typescript@5.2.2)(zod@3.22.4) + eventemitter3: 4.0.7 + typescript: 5.2.2 + viem: 1.19.11(typescript@5.2.2)(zod@3.22.4) + zustand: 4.4.6(@types/react@18.2.37)(react@18.2.0) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@types/react' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - immer + - react + - supports-color + - utf-8-validate + - zod + /@wagmi/core@1.4.7(@types/react@18.2.37)(encoding@0.1.13)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4): resolution: {integrity: sha512-PiOIGni8ArQoPmuDylHX38zMt2nPnTYRIluIqiduKyGCM61X/tf10a0rafUMOOphDPudZu1TacNDhCSeoh/LEA==} peerDependencies: @@ -11132,6 +11342,7 @@ packages: - supports-color - utf-8-validate - zod + dev: false /@wallchain/sdk@0.6.8: resolution: {integrity: sha512-k1RnyXMOi1Ajv12f8TDTP6zRkpw1zl6kl0J/Ul3DT+pIeMhUx0gEsAUDXFaBZtZlN1oNj966RVxPjuHQkpIRgA==} @@ -11192,6 +11403,7 @@ packages: - bufferutil - supports-color - utf-8-validate + dev: false /@walletconnect/core@2.10.5: resolution: {integrity: sha512-QnGHkA05KzJrtqExPqXm/TsstM1uTDI8tQT0x86/DuR6LdiYEntzSpVjnv7kKK6Mo9UxlXfud431dNRfOW5uJg==} @@ -11228,6 +11440,43 @@ packages: - supports-color - utf-8-validate + /@walletconnect/core@2.11.0(encoding@0.1.13): + resolution: {integrity: sha512-2Tjp5BCevI7dbmqo/OrCjX4tqgMqwJNQLlQAlphqPfvwlF9+tIu6pGcVbSN3U9zyXzWIZCeleqEaWUeSeET4Ew==} + dependencies: + '@walletconnect/heartbeat': 1.2.1 + '@walletconnect/jsonrpc-provider': 1.0.13 + '@walletconnect/jsonrpc-types': 1.0.3 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/jsonrpc-ws-connection': 1.0.14 + '@walletconnect/keyvaluestorage': 1.1.1 + '@walletconnect/logger': 2.0.1 + '@walletconnect/relay-api': 1.0.9 + '@walletconnect/relay-auth': 1.0.4 + '@walletconnect/safe-json': 1.0.2 + '@walletconnect/time': 1.0.2 + '@walletconnect/types': 2.11.0 + '@walletconnect/utils': 2.11.0 + events: 3.3.0 + isomorphic-unfetch: 3.1.0(encoding@0.1.13) + lodash.isequal: 4.5.0 + uint8arrays: 3.1.1 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - supports-color + - utf-8-validate + /@walletconnect/core@2.7.0: resolution: {integrity: sha512-xUeFPpElybgn1a+lknqtHleei4VyuV/4qWgB1nP8qQUAO6a5pNsioODrnB2VAPdUHJYBdx2dCt2maRk6g53IPQ==} dependencies: @@ -11369,6 +11618,39 @@ packages: - encoding - supports-color - utf-8-validate + dev: false + + /@walletconnect/ethereum-provider@2.11.0(@types/react@18.2.37)(encoding@0.1.13)(react@18.2.0): + resolution: {integrity: sha512-YrTeHVjuSuhlUw7SQ6xBJXDuJ6iAC+RwINm9nVhoKYJSHAy3EVSJZOofMKrnecL0iRMtD29nj57mxAInIBRuZA==} + dependencies: + '@walletconnect/jsonrpc-http-connection': 1.0.7(encoding@0.1.13) + '@walletconnect/jsonrpc-provider': 1.0.13 + '@walletconnect/jsonrpc-types': 1.0.3 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/modal': 2.6.2(@types/react@18.2.37)(react@18.2.0) + '@walletconnect/sign-client': 2.11.0(encoding@0.1.13) + '@walletconnect/types': 2.11.0 + '@walletconnect/universal-provider': 2.11.0(encoding@0.1.13) + '@walletconnect/utils': 2.11.0 + events: 3.3.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@types/react' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - react + - supports-color + - utf-8-validate /@walletconnect/ethereum-provider@2.7.0(@web3modal/standalone@2.4.3)(encoding@0.1.13): resolution: {integrity: sha512-6TwQ05zi6DP1TP1XNgSvLbmCmLf/sz7kLTfMaVk45YYHNgYTTBlXqkyjUpQZI9lpq+uXLBbHn/jx2OGhOPUP0Q==} @@ -11545,6 +11827,7 @@ packages: transitivePeerDependencies: - bufferutil - utf-8-validate + dev: false /@walletconnect/jsonrpc-ws-connection@1.0.14: resolution: {integrity: sha512-Jsl6fC55AYcbkNVkwNM6Jo+ufsuCQRqViOQ8ZBPH9pRREHH9welbBiszuTLqEJiQcO/6XfFDl6bzCJIkrEi8XA==} @@ -11754,6 +12037,36 @@ packages: - bufferutil - supports-color - utf-8-validate + dev: false + + /@walletconnect/sign-client@2.11.0(encoding@0.1.13): + resolution: {integrity: sha512-H2ukscibBS+6WrzQWh+WyVBqO5z4F5et12JcwobdwgHnJSlqIoZxqnUYYWNCI5rUR5UKsKWaUyto4AE9N5dw4Q==} + dependencies: + '@walletconnect/core': 2.11.0(encoding@0.1.13) + '@walletconnect/events': 1.0.1 + '@walletconnect/heartbeat': 1.2.1 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/logger': 2.0.1 + '@walletconnect/time': 1.0.2 + '@walletconnect/types': 2.11.0 + '@walletconnect/utils': 2.11.0 + events: 3.3.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - supports-color + - utf-8-validate /@walletconnect/sign-client@2.7.0: resolution: {integrity: sha512-K99xa6GSFS04U+140yrIEi/VJJJ0Q1ov4jCaiqa9euILDKxlBsM7m5GR+9sq6oYyj18SluJY4CJTdeOXUJlarA==} @@ -11840,6 +12153,7 @@ packages: - '@upstash/redis' - '@vercel/kv' - supports-color + dev: false /@walletconnect/types@2.10.5: resolution: {integrity: sha512-N8xaN7/Kob93rKxKDaT6oy6symgIkAwyLqq0/dLJEhXfv7S/gyNvDka4SosjVVTc4oTvE1+OmxNIR8pB1DuwJw==} @@ -11864,6 +12178,29 @@ packages: - '@vercel/kv' - supports-color + /@walletconnect/types@2.11.0: + resolution: {integrity: sha512-AB5b1lrEbCGHxqS2vqfCkIoODieH+ZAUp9rA1O2ftrhnqDJiJK983Df87JhYhECsQUBHHfALphA8ydER0q+9sw==} + dependencies: + '@walletconnect/events': 1.0.1 + '@walletconnect/heartbeat': 1.2.1 + '@walletconnect/jsonrpc-types': 1.0.3 + '@walletconnect/keyvaluestorage': 1.1.1 + '@walletconnect/logger': 2.0.1 + events: 3.3.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/kv' + - supports-color + /@walletconnect/types@2.7.0: resolution: {integrity: sha512-aMUDUtO79WSBtC/bDetE6aFwdgwJr0tJ8nC8gnAl5ELsrjygEKCn6M8Q+v6nP9svG9yf5Rds4cImxCT6BWwTyw==} dependencies: @@ -11940,6 +12277,36 @@ packages: - encoding - supports-color - utf-8-validate + dev: false + + /@walletconnect/universal-provider@2.11.0(encoding@0.1.13): + resolution: {integrity: sha512-zgJv8jDvIMP4Qse/D9oIRXGdfoNqonsrjPZanQ/CHNe7oXGOBiQND2IIeX+tS0H7uNA0TPvctljCLiIN9nw4eA==} + dependencies: + '@walletconnect/jsonrpc-http-connection': 1.0.7(encoding@0.1.13) + '@walletconnect/jsonrpc-provider': 1.0.13 + '@walletconnect/jsonrpc-types': 1.0.3 + '@walletconnect/jsonrpc-utils': 1.0.8 + '@walletconnect/logger': 2.0.1 + '@walletconnect/sign-client': 2.11.0(encoding@0.1.13) + '@walletconnect/types': 2.11.0 + '@walletconnect/utils': 2.11.0 + events: 3.3.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - supports-color + - utf-8-validate /@walletconnect/universal-provider@2.7.0(encoding@0.1.13): resolution: {integrity: sha512-aAIudO3ZlKD16X36VnXChpxBB6/JLK1SCJBfidk7E0GE2S4xr1xW5jXGSGS4Z+wIkNZXK0n7ULSK3PZ7mPBdog==} @@ -12033,6 +12400,7 @@ packages: - '@upstash/redis' - '@vercel/kv' - supports-color + dev: false /@walletconnect/utils@2.10.5: resolution: {integrity: sha512-3yeclD9/AlPEIHBqBVzrHUO/KRAEIXVK0ViIQ5oUH+zT3TpdsDGDiW1Z0TsAQ1EiYoiiz8dOQzd80a3eZVwnrg==} @@ -12065,6 +12433,37 @@ packages: - '@vercel/kv' - supports-color + /@walletconnect/utils@2.11.0: + resolution: {integrity: sha512-hxkHPlTlDQILHfIKXlmzgNJau/YcSBC3XHUSuZuKZbNEw3duFT6h6pm3HT/1+j1a22IG05WDsNBuTCRkwss+BQ==} + dependencies: + '@stablelib/chacha20poly1305': 1.0.1 + '@stablelib/hkdf': 1.0.1 + '@stablelib/random': 1.0.2 + '@stablelib/sha256': 1.0.1 + '@stablelib/x25519': 1.0.3 + '@walletconnect/relay-api': 1.0.9 + '@walletconnect/safe-json': 1.0.2 + '@walletconnect/time': 1.0.2 + '@walletconnect/types': 2.11.0 + '@walletconnect/window-getters': 1.0.1 + '@walletconnect/window-metadata': 1.0.1 + detect-browser: 5.3.0 + query-string: 7.1.3 + uint8arrays: 3.1.1 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@upstash/redis' + - '@vercel/kv' + - supports-color + /@walletconnect/utils@2.7.0: resolution: {integrity: sha512-k32jrQeyJsNZPdmtmg85Y3QgaS5YfzYSPrAxRC2uUD1ts7rrI6P5GG2iXNs3AvWKOuCgsp/PqU8s7AC7CRUscw==} dependencies: @@ -12382,7 +12781,7 @@ packages: typescript: 5.2.2 dev: false - /abitype@0.3.0(typescript@5.2.2): + /abitype@0.3.0(typescript@5.2.2)(zod@3.22.4): resolution: {integrity: sha512-0YokyAV4hKMcy97Pl+6QgZBlBdZJN2llslOs7kiFY+cu7kMlVXDBpxMExfv0krzBCQt2t7hNovpQ3y/zvEm18A==} engines: {pnpm: '>=7'} peerDependencies: @@ -12393,6 +12792,7 @@ packages: optional: true dependencies: typescript: 5.2.2 + zod: 3.22.4 dev: false /abitype@0.8.7(typescript@5.2.2)(zod@3.22.4): @@ -12462,14 +12862,9 @@ packages: engines: {node: '>=0.4.0'} dev: true - /acorn-walk@8.3.0: - resolution: {integrity: sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==} - engines: {node: '>=0.4.0'} - /acorn-walk@8.3.2: resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} engines: {node: '>=0.4.0'} - dev: true /acorn@7.4.1: resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} @@ -13883,7 +14278,7 @@ packages: dev: true /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} /concat-stream@1.6.2: resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} @@ -15738,7 +16133,7 @@ packages: resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==} engines: {node: '>= 0.8'} dependencies: - '@types/node': 20.5.7 + '@types/node': 18.18.9 require-like: 0.1.2 /event-stream@3.3.4: @@ -17540,7 +17935,7 @@ packages: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.5.7 + '@types/node': 18.18.9 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -17573,7 +17968,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.5.7 + '@types/node': 18.18.9 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -18099,7 +18494,7 @@ packages: mlly: 1.4.2 node-forge: 1.3.1 pathe: 1.1.1 - std-env: 3.4.3 + std-env: 3.7.0 ufo: 1.3.1 untun: 0.1.2 uqr: 0.1.2 @@ -20029,7 +20424,7 @@ packages: '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 '@types/long': 4.0.2 - '@types/node': 20.5.7 + '@types/node': 18.18.9 long: 4.0.0 dev: false @@ -21844,12 +22239,8 @@ packages: engines: {node: '>= 0.8'} dev: true - /std-env@3.4.3: - resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==} - /std-env@3.7.0: resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} - dev: true /stop-iteration-iterator@1.0.0: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} @@ -22540,6 +22931,7 @@ packages: serialize-javascript: 6.0.1 terser: 5.24.0 webpack: 5.89.0(esbuild@0.19.8) + dev: false /terser@5.24.0: resolution: {integrity: sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==} @@ -22759,7 +23151,7 @@ packages: '@tsconfig/node16': 1.0.4 '@types/node': 13.13.52 acorn: 8.11.2 - acorn-walk: 8.3.0 + acorn-walk: 8.3.2 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 @@ -23724,7 +24116,7 @@ packages: dependencies: '@types/node': 13.13.52 esbuild: 0.17.19 - postcss: 8.4.31 + postcss: 8.4.33 rollup: 3.29.4 optionalDependencies: fsevents: 2.3.3 @@ -23757,7 +24149,7 @@ packages: dependencies: '@types/node': 18.0.4 esbuild: 0.17.19 - postcss: 8.4.31 + postcss: 8.4.33 rollup: 3.29.4 optionalDependencies: fsevents: 2.3.3 @@ -23789,7 +24181,7 @@ packages: dependencies: '@types/node': 18.18.9 esbuild: 0.17.19 - postcss: 8.4.31 + postcss: 8.4.33 rollup: 3.29.4 optionalDependencies: fsevents: 2.3.3 @@ -23943,7 +24335,7 @@ packages: typescript: 5.2.2 dev: true - /wagmi@0.12.19(@types/react@18.2.37)(encoding@0.1.13)(ethers@5.7.2)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2): + /wagmi@0.12.19(@types/react@18.2.37)(encoding@0.1.13)(ethers@5.7.2)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(zod@3.22.4): resolution: {integrity: sha512-S/el9BDb/HNeQWh1v8TvntMPX/CgKLDAoJqDb8i7jifLfWPqFL7gor3vnI1Vs6ZlB8uh7m+K1Qyg+mKhbITuDQ==} peerDependencies: ethers: '>=5.5.1 <6' @@ -23956,8 +24348,8 @@ packages: '@tanstack/query-sync-storage-persister': 4.36.1 '@tanstack/react-query': 4.36.1(react-dom@18.2.0)(react@18.2.0) '@tanstack/react-query-persist-client': 4.36.1(@tanstack/react-query@4.36.1) - '@wagmi/core': 0.10.17(@types/react@18.2.37)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2) - abitype: 0.3.0(typescript@5.2.2) + '@wagmi/core': 0.10.17(@types/react@18.2.37)(encoding@0.1.13)(ethers@5.7.2)(react@18.2.0)(typescript@5.2.2)(zod@3.22.4) + abitype: 0.3.0(typescript@5.2.2)(zod@3.22.4) ethers: 5.7.2 react: 18.2.0 typescript: 5.2.2 @@ -23985,6 +24377,47 @@ packages: - zod dev: false + /wagmi@1.4.13(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4): + resolution: {integrity: sha512-AScVYFjqNt1wMgL99Bob7MLdhoTZ3XKiOZL5HVBdy4W1sh7QodA3gQ8IsmTuUrQ7oQaTxjiXEhwg7sWNrPBvJA==} + peerDependencies: + react: '>=17.0.0' + typescript: '>=5.0.4' + viem: '>=0.3.35' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@tanstack/query-sync-storage-persister': 4.36.1 + '@tanstack/react-query': 4.36.1(react-dom@18.2.0)(react@18.2.0) + '@tanstack/react-query-persist-client': 4.36.1(@tanstack/react-query@4.36.1) + '@wagmi/core': 1.4.13(@types/react@18.2.37)(encoding@0.1.13)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4) + abitype: 0.8.7(typescript@5.2.2)(zod@3.22.4) + react: 18.2.0 + typescript: 5.2.2 + use-sync-external-store: 1.2.0(react@18.2.0) + viem: 1.19.11(typescript@5.2.2)(zod@3.22.4) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@planetscale/database' + - '@react-native-async-storage/async-storage' + - '@types/react' + - '@upstash/redis' + - '@vercel/kv' + - bufferutil + - encoding + - immer + - react-dom + - react-native + - supports-color + - utf-8-validate + - zod + /wagmi@1.4.7(@types/react@18.2.37)(encoding@0.1.13)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(viem@1.19.11)(zod@3.22.4): resolution: {integrity: sha512-/k8gA9S6RnwU6Qroxs630jAFvRIx+DSKpCP1owgAEGWc7D2bAJHljwRSCRTGENz48HyJ4V3R7KYV1yImxPvM3A==} peerDependencies: @@ -24025,6 +24458,7 @@ packages: - supports-color - utf-8-validate - zod + dev: false /wait-on@7.0.1(debug@4.3.4): resolution: {integrity: sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog==} @@ -24082,7 +24516,7 @@ packages: hasBin: true dependencies: acorn: 8.11.2 - acorn-walk: 8.3.0 + acorn-walk: 8.3.2 chalk: 4.1.2 commander: 7.2.0 gzip-size: 6.0.0 @@ -24220,6 +24654,7 @@ packages: - '@swc/core' - esbuild - uglify-js + dev: false /whatwg-mimetype@3.0.0: resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}