diff --git a/package.json b/package.json index c7f14951..006a38b7 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@types/styled-components": "^5.1.9", "@types/wcag-contrast": "^3.0.0", "@typescript-eslint/eslint-plugin": "^4.33.0", - "@typescript-eslint/parser": "^4.28.0", + "@typescript-eslint/parser": "^4.33.0", "@ubeswap/injected-connector": "^6.1.0", "@uniswap/token-lists": "^1.0.0-beta.24", "@web3-react/core": "^6.1.9", @@ -134,7 +134,7 @@ "@ubeswap/core": "^1.0.2", "@ubeswap/default-token-list": "^4.1.39", "@ubeswap/moola": "^0.2.0", - "@ubeswap/sdk": "^2.1.1", + "@ubeswap/sdk": "^2.1.2", "@uniswap/default-token-list": "^2.0.0", "ajv-formats": "^2.1.1", "eventemitter3": "^4.0.7", diff --git a/public/locales/en.json b/public/locales/en.json index 36384b2c..2c46b3d4 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -229,5 +229,8 @@ "farmUBE": "Farm UBE", "youHaveUnclaimedRewards": "You have {{count}} farms with unclaimed rewards", "claimAllRewards": "Claim all rewards", - "featuredPools": "Featured Pools" + "featuredPools": "Featured Pools", + "limitOrder": "Limit Order", + "price": "Price", + "placeOrder": "Place Order" } diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 97e0c07c..b0fde538 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -337,3 +337,20 @@ export function ButtonRadio({ active, ...rest }: { active?: boolean } & ButtonPr return } } + +export const TabButton = styled(ButtonLight)<{ + active?: boolean +}>` + background-color: initial; + width: 35%; + ${({ active }) => + active && + ` + box-shadow: 0 0 0 1pt #6D619A70; + background-color: #6D619A70; +`} + font-size: 12px; + display: inline-block; + padding: 0.75rem; + margin: 0.5rem 0.05rem 0.5rem 0.5rem; +` diff --git a/src/components/Column/index.tsx b/src/components/Column/index.tsx index bb046e9a..f5813d8f 100644 --- a/src/components/Column/index.tsx +++ b/src/components/Column/index.tsx @@ -25,5 +25,8 @@ export const TopSection = styled(AutoColumn)` width: 100%; margin-bottom: 24px; ` +export const TopSectionLimitOrder = styled(TopSection)` + max-width: 420px; +` export default Column diff --git a/src/components/Header/BridgeMenuGroup.tsx b/src/components/Header/BridgeMenuGroup.tsx index 3e2d18f2..e1551e91 100644 --- a/src/components/Header/BridgeMenuGroup.tsx +++ b/src/components/Header/BridgeMenuGroup.tsx @@ -1,4 +1,3 @@ -import { darken } from 'polished' import React, { useRef } from 'react' import styled from 'styled-components' @@ -6,33 +5,7 @@ import { useOnClickOutside } from '../../hooks/useOnClickOutside' import { ApplicationModal } from '../../state/application/actions' import { useModalOpen, useToggleModal } from '../../state/application/hooks' import { ExternalLink } from '../../theme' - -const StyledNavMenu = styled('div')` - ${({ theme }) => theme.flexRowNoWrap} - align-items: left; - border-radius: 3rem; - outline: none; - cursor: pointer; - text-decoration: none; - color: ${({ theme }) => theme.text2}; - font-size: 1rem; - width: fit-content; - margin: 0 12px; - font-weight: 500; - - :hover, - :focus { - color: ${({ theme }) => darken(0.1, theme.text1)}; - } - - @media (max-width: 320px) { - margin: 0 8px; - } - - ${({ theme }) => theme.mediaWidth.upToExtraSmall` - display: none; - `} -` +import { StyledNavMenu } from './NavMenu' const StyledMenu = styled.div` display: flex; @@ -93,6 +66,9 @@ export default function BridgeMenuGroup() { Optics + + Orbit + )} diff --git a/src/components/Header/ChartsMenuGroup.tsx b/src/components/Header/ChartsMenuGroup.tsx new file mode 100644 index 00000000..df679185 --- /dev/null +++ b/src/components/Header/ChartsMenuGroup.tsx @@ -0,0 +1,70 @@ +import React, { useRef } from 'react' +import styled from 'styled-components' + +import { useOnClickOutside } from '../../hooks/useOnClickOutside' +import { ApplicationModal } from '../../state/application/actions' +import { useModalOpen, useToggleModal } from '../../state/application/hooks' +import { ExternalLink } from '../../theme' +import { StyledNavMenu } from './NavMenu' + +const StyledMenu = styled.div` + display: flex; + justify-content: center; + align-items: center; + position: relative; +` + +const MenuFlyout = styled.span` + min-width: 8.125rem; + background-color: ${({ theme }) => theme.bg3}; + box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), + 0px 24px 32px rgba(0, 0, 0, 0.01); + border-radius: 12px; + padding: 0.5rem; + display: flex; + flex-direction: column; + font-size: 1rem; + position: absolute; + top: 3rem; + right: 0rem; + z-index: 100; +` + +const MenuItem = styled(ExternalLink)` + flex: 1; + padding: 0.5rem 0.5rem; + color: ${({ theme }) => theme.text2}; + :hover { + color: ${({ theme }) => theme.text1}; + cursor: pointer; + text-decoration: none; + } + > svg { + margin-right: 8px; + } +` + +export default function ChartsMenuGroup() { + const node = useRef() + const open = useModalOpen(ApplicationModal.CHARTS) + const toggle = useToggleModal(ApplicationModal.CHARTS) + useOnClickOutside(node, open ? toggle : undefined) + + return ( + // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451 + + Charts + + {open && ( + + + Analytics + + + Celo Tracker + + + )} + + ) +} diff --git a/src/components/Header/NavMenu.tsx b/src/components/Header/NavMenu.tsx new file mode 100644 index 00000000..5786c6c2 --- /dev/null +++ b/src/components/Header/NavMenu.tsx @@ -0,0 +1,29 @@ +import { darken } from 'polished' +import styled from 'styled-components' + +export const StyledNavMenu = styled('div')` + ${({ theme }) => theme.flexRowNoWrap} + align-items: left; + border-radius: 3rem; + outline: none; + cursor: pointer; + text-decoration: none; + color: ${({ theme }) => theme.text2}; + font-size: 1rem; + width: fit-content; + margin: 0 12px; + font-weight: 500; + + :hover, + :focus { + color: ${({ theme }) => darken(0.1, theme.text1)}; + } + + @media (max-width: 320px) { + margin: 0 8px; + } + + ${({ theme }) => theme.mediaWidth.upToExtraSmall` + display: none; + `} +` diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index 7c39c22c..8da99ffa 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -29,6 +29,7 @@ import Menu from '../Menu' import Row, { RowFixed } from '../Row' import Web3Status from '../Web3Status' import BridgeMenuGroup from './BridgeMenuGroup' +import ChartsMenuGroup from './ChartsMenuGroup' import UbeBalanceContent from './UbeBalanceContent' const HeaderFrame = styled.div` @@ -382,6 +383,9 @@ export default function Header() { {t('swap')} + + {t('limitOrder')} + {t('stake')} - - {t('charts')} - + @@ -417,6 +419,16 @@ export default function Header() { onClose={onDrawerClose} > + + + {t('stake')} + + + + + Limit Orders + + Bridge @@ -437,16 +449,26 @@ export default function Header() { Optics + + + Orbit + + - - {t('stake')} + + Charts - - - {t('charts')} + + + Analytics - + + + + Celo Tracker + + diff --git a/src/components/LimitOrderHistory/LimitOrderHistoryBody.tsx b/src/components/LimitOrderHistory/LimitOrderHistoryBody.tsx new file mode 100644 index 00000000..ee42df19 --- /dev/null +++ b/src/components/LimitOrderHistory/LimitOrderHistoryBody.tsx @@ -0,0 +1,11 @@ +import { BodyWrapper } from 'pages/AppBody' +import React from 'react' +import styled from 'styled-components' + +const LimitOrderHistoryBodyWrapper = styled(BodyWrapper)` + margin-top: 2rem; +` + +export default function LimitOrderHistoryBody({ children }: { children: React.ReactNode }) { + return {children} +} diff --git a/src/components/LimitOrderHistory/LimitOrderHistoryItem.tsx b/src/components/LimitOrderHistory/LimitOrderHistoryItem.tsx new file mode 100644 index 00000000..02d905ff --- /dev/null +++ b/src/components/LimitOrderHistory/LimitOrderHistoryItem.tsx @@ -0,0 +1,197 @@ +import { useContractKit } from '@celo-tools/use-contractkit' +import { ChainId as UbeswapChainId, JSBI, Token, TokenAmount } from '@ubeswap/sdk' +import { BigNumber } from 'ethers' +import { useToken } from 'hooks/Tokens' +import { useOrderBookContract, useOrderBookRewardDistributorContract } from 'hooks/useContract' +import { BPS_DENOMINATOR } from 'pages/LimitOrder' +import React from 'react' +import { useSingleCallResult } from 'state/multicall/hooks' +import styled from 'styled-components' + +import { ORDER_BOOK_ADDRESS, ORDER_BOOK_REWARD_DISTRIBUTOR_ADDRESS } from '../../constants' +import useTheme from '../../hooks/useTheme' +import { useCancelOrderCallback } from '../../pages/LimitOrder/useCancelOrderCallback' +import { ExternalLink, LinkIcon, TYPE } from '../../theme' +import { RowFlat } from '../Row' + +const Container = styled.div<{ + lastDisplayItem?: boolean +}>` + background-color: ${({ theme }) => theme.bg1}; + margin-bottom: 2rem; + padding-bottom: 1rem; + padding-left: 0.5rem; + border-bottom: 2px solid ${({ theme }) => theme.primary5}; + ${({ lastDisplayItem }) => + lastDisplayItem && + ` +border-bottom-style: none; +`} +` + +const SymbolContainer = styled.div` + width: 75%; +` + +const AssetSymbol = styled.div` + border-radius: 12px; + border: 1px solid ${({ theme }) => theme.primary5}; + padding: 0.5rem; +` + +const AssetRow = styled(RowFlat)` + margin-bottom: 0.5rem; +` +const SellText = styled.div` + font-weight: 700; + margin-top: 0.25rem; +` + +const OrderToFill = styled.div` + font-weight: 300; + font-size: 14px; + margin-top: 0.25rem; +` + +const StyledControlButton = styled.button` + height: 24px; + background-color: ${({ theme }) => theme.red1}; + border: 1px solid ${({ theme }) => theme.red2}; + border-radius: 0.5rem; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + margin-left: 7rem; + margin-right: 2rem; + color: white; + :hover { + border: 1px solid ${({ theme }) => theme.red3}; + } + :focus { + border: 1px solid ${({ theme }) => theme.red3}; + outline: none; + } + + ${({ theme }) => theme.mediaWidth.upToExtraSmall` + margin-left: 0.4rem; + margin-right: 0.1rem; + `}; +` + +const AddressLink = styled(ExternalLink)` + font-size: 0.825rem; + color: ${({ theme }) => theme.text3}; + border-radius: 12px; + width: 45%; + padding: 0.25rem; + margin-top: 0.5rem; + border: 1px solid ${({ theme }) => theme.primary5}; + font-size: 0.825rem; + display: flex; + :hover { + color: ${({ theme }) => theme.text2}; + } +` + +const BaselineRow = styled(AssetRow)` + align-items: baseline; +` + +interface LimitOrderHistoryItemProps { + item: { + orderHash: string + makingAmount: BigNumber + takingAmount: BigNumber + makerAsset: string + takerAsset: string + remaining: BigNumber + isOrderOpen: boolean + transactionHash: string + } + rewardCurrency: Token | undefined + lastDisplayItem: boolean +} + +export default function LimitOrderHistoryItem({ item, rewardCurrency, lastDisplayItem }: LimitOrderHistoryItemProps) { + const { network } = useContractKit() + const chainId = network.chainId as unknown as UbeswapChainId + const { callback: cancelOrder } = useCancelOrderCallback(item.orderHash) + const theme = useTheme() + const makerToken = useToken(item.makerAsset) + const takerToken = useToken(item.takerAsset) + + const transactionLink = `${network.explorer}/tx/${item.transactionHash}` + + const orderBookContract = useOrderBookContract(ORDER_BOOK_ADDRESS[chainId as unknown as UbeswapChainId]) + const orderBookFee = useSingleCallResult(orderBookContract, 'fee', []).result?.[0] + const rewardDistributorContract = useOrderBookRewardDistributorContract( + ORDER_BOOK_REWARD_DISTRIBUTOR_ADDRESS[chainId] + ) + // TODO: This should really be based on the latest rewardRate change event from the logs + const rewardRate = useSingleCallResult(rewardDistributorContract, 'rewardRate', [makerToken?.address]).result?.[0] + + if (!makerToken || !takerToken) { + return null + } + + const makingAmount = new TokenAmount(makerToken, item.makingAmount.toString()) + const reward = + rewardCurrency && rewardRate + ? new TokenAmount( + rewardCurrency, + JSBI.divide(JSBI.multiply(makingAmount.raw, JSBI.BigInt(rewardRate.toString())), BPS_DENOMINATOR) + ) + : undefined + const takingAmount = new TokenAmount(takerToken, item.takingAmount.toString()) + const remaining = new TokenAmount(makerToken, item.remaining.toString()) + + return ( + + + + + {makerToken.symbol} + + ➜ + + {takerToken.symbol} + + + {item.isOrderOpen && ( + cancelOrder && cancelOrder()}>Cancel + )} + + + {makingAmount.toSignificant(4)} {makerToken.symbol} for {takingAmount.toSignificant(4)} {takerToken.symbol} + + {item.isOrderOpen && ( + + Remaining Order to Fill: {remaining.toSignificant(4)} {makerToken.symbol} + + )} + + Order Placement Fee:{' '} + {orderBookFee + ? makingAmount.multiply(orderBookFee.toString()).divide(BPS_DENOMINATOR.toString()).toSignificant(4) + : '-'}{' '} + {makerToken.symbol} + + {reward?.greaterThan('0') ? ( + + Order Reward: {reward.toSignificant(4)} {reward.currency.symbol} + + ) : ( + Order Reward: - + )} + {item.isOrderOpen && ( + + + View Transaction + + )} + + ) +} diff --git a/src/components/PriceInputPanel/index.tsx b/src/components/PriceInputPanel/index.tsx new file mode 100644 index 00000000..44e3c83d --- /dev/null +++ b/src/components/PriceInputPanel/index.tsx @@ -0,0 +1,68 @@ +import React from 'react' +import { useTranslation } from 'react-i18next' +import styled from 'styled-components' + +import { TYPE } from '../../theme' +import { Input as NumericalInput } from '../NumericalInput' + +const InputRow = styled.div<{ selected: boolean }>` + ${({ theme }) => theme.flexRowNoWrap} + align-items: center; + padding: ${({ selected }) => (selected ? '0.75rem 0.5rem 0.75rem 1rem' : '0.75rem 0.75rem 0.75rem 1rem')}; +` + +const InputPanel = styled.div<{ hideInput?: boolean }>` + ${({ theme }) => theme.flexColumnNoWrap} + position: relative; + border-radius: ${({ hideInput }) => (hideInput ? '8px' : '20px')}; + background-color: ${({ theme }) => theme.bg2}; + z-index: 1; +` + +const Container = styled.div<{ hideInput: boolean }>` + border-radius: ${({ hideInput }) => (hideInput ? '8px' : '20px')}; + border: 1px solid ${({ theme }) => theme.bg2}; + background-color: ${({ theme }) => theme.bg1}; +` + +interface PriceInputPanelProps { + value: string + placeholder?: string + onUserInput: (value: string) => void + disableCurrencySelect?: boolean + hideInput?: boolean + id: string +} + +export default function PriceInputPanel({ + value, + placeholder, + onUserInput, + disableCurrencySelect = false, + hideInput = false, + id, +}: PriceInputPanelProps) { + const { t } = useTranslation() + + return ( + + + + {!hideInput && ( + <> + {t('price')} + { + onUserInput(val) + }} + placeholder={placeholder} + /> + + )} + + + + ) +} diff --git a/src/components/Stake/StakeInputField.tsx b/src/components/Stake/StakeInputField.tsx index 3c828737..825a1bb7 100644 --- a/src/components/Stake/StakeInputField.tsx +++ b/src/components/Stake/StakeInputField.tsx @@ -4,11 +4,8 @@ import { darken } from 'polished' import React from 'react' import styled from 'styled-components' -import useTheme from '../../hooks/useTheme' import { useCurrencyBalance } from '../../state/wallet/hooks' -import { TYPE } from '../../theme' import { Input as NumericalInput } from '../NumericalInput' -import { RowBetween } from '../Row' const InputRow = styled.div<{ selected: boolean }>` ${({ theme }) => theme.flexRowNoWrap} @@ -68,6 +65,27 @@ const StyledControlButton = styled.button` margin-right: 0.1rem; `}; ` + +const AmountWrapper = styled.div` + display: flex; + flex-wrap: wrap; + justify-content: space-between; + width: 100%; +` + +const AmountDescriptionWrapper = styled.div` + display: flex; + justify-content: space-between; + width: unset; + cursor: pointer; + font-weight: 500; + font-size: 14px; + margin-bottom: 0.5rem; + color: ${({ theme }) => theme.text2}; + ${({ theme }) => theme.mediaWidth.upToExtraSmall` + width: 100%; +`}; +` const ButtonGroup = styled.div`` interface StakeInputFieldProps { @@ -78,7 +96,6 @@ interface StakeInputFieldProps { hideBalance?: boolean hideInput?: boolean id: string - customBalanceText?: string chainId?: ChainId stakeBalance?: TokenAmount walletBalance?: TokenAmount @@ -92,7 +109,6 @@ export default function StakeInputField({ hideBalance = false, hideInput = false, id, - customBalanceText, stakeBalance, walletBalance, }: StakeInputFieldProps) { @@ -100,41 +116,33 @@ export default function StakeInputField({ const userBalance = useCurrencyBalance(account ?? undefined, currency ?? undefined) const selectedCurrencyBalance = walletBalance ?? userBalance - const theme = useTheme() return ( {!hideInput && ( - + {account && ( <> - - {'Current Stake: ' + (stakeBalance ? stakeBalance.toFixed(2, { groupSeparator: ',' }) : '--')} - - - {!hideBalance && !!currency && selectedCurrencyBalance - ? (customBalanceText ?? 'Wallet Balance: ') + selectedCurrencyBalance?.toSignificant(4) - : ' -'} - + + Current Stake:  + {stakeBalance ? stakeBalance.toFixed(2, { groupSeparator: ',' }) : '--'} + + + Wallet Balance:  + + {!hideBalance && !!currency && selectedCurrencyBalance + ? selectedCurrencyBalance?.toSignificant(4) + : '--'} + + )} - + )} - + {!hideInput && ( <> void + onConfirm: () => void + swapErrorMessage: string | undefined + onDismiss: () => void +}) { + const { t } = useTranslation() + const showAcceptChanges = useMemo( + () => Boolean(trade && originalTrade && tradeMeaningfullyDiffers(trade, originalTrade)), + [originalTrade, trade] + ) + const modalHeader = useCallback(() => { + return trade ? ( + + ) : null + }, [allowedSlippage, onAcceptChanges, recipient, showAcceptChanges, trade]) + + const modalBottom = useCallback(() => { + return trade ? ( + + ) : null + }, [allowedSlippage, onConfirm, showAcceptChanges, swapErrorMessage, trade]) + + // text to show while loading + const pendingText = `Swapping ${trade?.inputAmount?.toSignificant(6)} ${ + trade?.inputAmount?.currency?.symbol + } for ${trade?.outputAmount?.toSignificant(6)} ${trade?.outputAmount?.currency?.symbol}` + + const confirmationContent = useCallback( + () => + swapErrorMessage ? ( + + ) : ( + + ), + [swapErrorMessage, onDismiss, t, modalHeader, modalBottom] + ) + + return ( + + ) +} diff --git a/src/components/swap/LimitOrderModalHeader.tsx b/src/components/swap/LimitOrderModalHeader.tsx new file mode 100644 index 00000000..49eb73f1 --- /dev/null +++ b/src/components/swap/LimitOrderModalHeader.tsx @@ -0,0 +1,132 @@ +import { Trade, TradeType } from '@ubeswap/sdk' +import React, { useContext, useMemo } from 'react' +import { AlertTriangle, ArrowDown } from 'react-feather' +import { Text } from 'rebass' +import { ThemeContext } from 'styled-components' + +import { Field } from '../../state/swap/actions' +import { TYPE } from '../../theme' +import { isAddress, shortenAddress } from '../../utils' +import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices' +import { ButtonPrimary } from '../Button' +import { AutoColumn } from '../Column' +import CurrencyLogo from '../CurrencyLogo' +import { RowBetween, RowFixed } from '../Row' +import { MoolaDirectTrade } from './routing/moola/MoolaDirectTrade' +import { SwapShowAcceptChanges, TruncatedText } from './styleds' + +export default function LimitOrderModalHeader({ + trade, + allowedSlippage, + recipient, + showAcceptChanges, + onAcceptChanges, +}: { + trade: Trade + allowedSlippage: number + recipient: string | null + showAcceptChanges: boolean + onAcceptChanges: () => void +}) { + const slippageAdjustedAmounts = useMemo( + () => computeSlippageAdjustedAmounts(trade, allowedSlippage), + [trade, allowedSlippage] + ) + const { priceImpactWithoutFee } = useMemo(() => computeTradePriceBreakdown(trade), [trade]) + const priceImpactSeverity = warningSeverity(priceImpactWithoutFee) + + const theme = useContext(ThemeContext) + + return ( + + + + + + {trade.inputAmount.toSignificant(6)} + + + + + {trade.inputAmount.currency.symbol} + + + + + + + + + + 2 + ? theme.red1 + : showAcceptChanges && trade.tradeType === TradeType.EXACT_INPUT + ? theme.primary1 + : '' + } + > + {trade.outputAmount.toSignificant(6)} + + + + + {trade.outputAmount.currency.symbol} + + + + {showAcceptChanges ? ( + + + + + Price Updated + + + Accept + + + + ) : null} + {!(trade instanceof MoolaDirectTrade) && ( + + {trade.tradeType === TradeType.EXACT_INPUT ? ( + + {`Output is estimated. You will receive at least `} + + {slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {trade.outputAmount.currency.symbol} + + {' or the transaction will revert.'} + + ) : ( + + {`Input is estimated. You will sell at most `} + + {slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {trade.inputAmount.currency.symbol} + + {' or the transaction will revert.'} + + )} + + )} + {recipient !== null ? ( + + + Output will be sent to{' '} + {isAddress(recipient) ? shortenAddress(recipient) : recipient} + + + ) : null} + + ) +} diff --git a/src/components/swap/SwapHeader.tsx b/src/components/swap/SwapHeader.tsx index 6423eed8..f418ac53 100644 --- a/src/components/swap/SwapHeader.tsx +++ b/src/components/swap/SwapHeader.tsx @@ -23,7 +23,9 @@ export default function SwapHeader({ return ( - {title} + + {title} + {hideSettings || } diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index fd291d84..86e7b9fe 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -61,6 +61,33 @@ export default function SwapModalFooter({ ) + } else if (routingMethod === RoutingMethod.LIMIT) { + info = ( + + + + Price + + + {formatExecutionPrice(trade, showInverted)} + setShowInverted(!showInverted)}> + + + + + + ) } else { info = ( diff --git a/src/components/swap/TradePrice.tsx b/src/components/swap/TradePrice.tsx index 1d5a768b..fd37a29d 100644 --- a/src/components/swap/TradePrice.tsx +++ b/src/components/swap/TradePrice.tsx @@ -1,4 +1,4 @@ -import { Price } from '@ubeswap/sdk' +import { JSBI, Price } from '@ubeswap/sdk' import React, { useContext } from 'react' import { Repeat } from 'react-feather' import { Text } from 'rebass' @@ -15,7 +15,18 @@ interface TradePriceProps { export default function TradePrice({ price, showInverted, setShowInverted }: TradePriceProps) { const theme = useContext(ThemeContext) - const formattedPrice = showInverted ? price?.toSignificant(6) : price?.invert()?.toSignificant(6) + let formattedPrice + if (price) { + if (showInverted) { + if (!JSBI.equal(price.denominator, JSBI.BigInt(0))) { + formattedPrice = price.toSignificant(6) + } + } else { + if (!JSBI.equal(price.numerator, JSBI.BigInt(0))) { + formattedPrice = price.invert().toSignificant(6) + } + } + } const show = Boolean(price?.baseCurrency && price?.quoteCurrency) const label = showInverted diff --git a/src/components/swap/routing/describeTrade.ts b/src/components/swap/routing/describeTrade.ts index 288ab0ae..82145d58 100644 --- a/src/components/swap/routing/describeTrade.ts +++ b/src/components/swap/routing/describeTrade.ts @@ -7,6 +7,7 @@ export enum RoutingMethod { UBESWAP = 0, MOOLA = 1, MOOLA_ROUTER = 2, + LIMIT = 3, } export const describeTrade = ( diff --git a/src/components/swap/routing/index.ts b/src/components/swap/routing/index.ts index be9ec6b6..ecad9888 100644 --- a/src/components/swap/routing/index.ts +++ b/src/components/swap/routing/index.ts @@ -1,5 +1,5 @@ import { useContractKit, useGetConnectedSigner } from '@celo-tools/use-contractkit' -import { Signer } from '@ethersproject/abstract-signer' +import { JsonRpcSigner } from '@ethersproject/providers' import { ChainId, Trade } from '@ubeswap/sdk' import { BigNumber, BigNumberish, CallOverrides, Contract, ContractTransaction, PayableOverrides } from 'ethers' import { useCallback } from 'react' @@ -10,7 +10,7 @@ type Head = Required extends [...infer H, any] ? H : never type Last> = Required extends [...unknown[], infer L] ? L : never type MethodArgs = Head> -type DoTransactionFn = < +export type DoTransactionFn = < C extends Contract, M extends string & keyof C['estimateGas'], O extends Last> & (PayableOverrides | CallOverrides) @@ -29,7 +29,18 @@ type DoTransactionFn = < export interface TradeExecutor { (args: { trade: T - signer: Signer + signer: JsonRpcSigner + chainId: ChainId.MAINNET | ChainId.ALFAJORES + doTransaction: DoTransactionFn + }): Promise<{ + hash: string + }> +} + +export interface CancelLimitOrderExecutor { + (args: { + orderHash: string + signer: JsonRpcSigner chainId: ChainId.MAINNET | ChainId.ALFAJORES doTransaction: DoTransactionFn }): Promise<{ diff --git a/src/components/swap/routing/limit/queueLimitOrderTrade.ts b/src/components/swap/routing/limit/queueLimitOrderTrade.ts new file mode 100644 index 00000000..0179f3f5 --- /dev/null +++ b/src/components/swap/routing/limit/queueLimitOrderTrade.ts @@ -0,0 +1,91 @@ +import { useGetConnectedSigner } from '@celo-tools/use-contractkit' +import { ChainId, TokenAmount } from '@ubeswap/sdk' +import { LimitOrderProtocol__factory } from 'generated/factories/LimitOrderProtocol__factory' +import { OrderBook__factory } from 'generated/factories/OrderBook__factory' +import { useCallback, useState } from 'react' +import { buildOrderData } from 'utils/limitOrder' + +import { + LIMIT_ORDER_ADDRESS, + ORDER_BOOK_ADDRESS, + ORDER_BOOK_REWARD_DISTRIBUTOR_ADDRESS, + ZERO_ADDRESS, +} from '../../../../constants' +import { useDoTransaction } from '..' + +function cutLastArg(data: string, padding = 0) { + return data.substr(0, data.length - 64 - padding) +} + +/** + * Queues a limit order trade. + * @returns + */ +export const useQueueLimitOrderTrade = () => { + const getConnectedSigner = useGetConnectedSigner() + const doTransaction = useDoTransaction() + const [loading, setLoading] = useState(false) + const queueLimitOrderCallback = useCallback( + async ({ + inputAmount, + outputAmount, + chainId, + }: { + inputAmount: TokenAmount + outputAmount: TokenAmount + chainId: ChainId + }) => { + const signer = await getConnectedSigner() + const limitOrderAddr = LIMIT_ORDER_ADDRESS[chainId] + const orderBookAddr = ORDER_BOOK_ADDRESS[chainId] + const rewardDistributorAddr = ORDER_BOOK_REWARD_DISTRIBUTOR_ADDRESS[chainId] + + const limitOrderProtocolIface = LimitOrderProtocol__factory.createInterface() + const orderBook = OrderBook__factory.connect(orderBookAddr, signer) + + const makingAmount = inputAmount.raw.toString() + const takingAmount = outputAmount.raw.toString() + + const limitOrder = { + salt: Math.floor(Math.random() * 1_000_000_000), // Reasonably random + makerAsset: inputAmount.currency.address, + takerAsset: outputAmount.currency.address, + maker: await signer.getAddress(), + receiver: ZERO_ADDRESS, + allowedSender: ZERO_ADDRESS, + makingAmount, + takingAmount, + makerAssetData: '0x', + takerAssetData: '0x', + getMakerAmount: cutLastArg( + limitOrderProtocolIface.encodeFunctionData('getMakerAmount', [makingAmount, takingAmount, 0]) + ), + getTakerAmount: cutLastArg( + limitOrderProtocolIface.encodeFunctionData('getTakerAmount', [makingAmount, takingAmount, 0]) + ), + predicate: '0x', + permit: '0x', + interaction: '0x', + } + try { + setLoading(true) + const limitOrderTypedData = buildOrderData(chainId.toString(), limitOrderAddr, limitOrder) + const limitOrderSignature = await signer._signTypedData( + limitOrderTypedData.domain, + limitOrderTypedData.types, + limitOrder + ) + await doTransaction(orderBook, 'broadcastOrder', { + args: [limitOrder, limitOrderSignature, rewardDistributorAddr], + summary: `Place limit order for ${outputAmount.toSignificant(2)} ${outputAmount.currency.symbol}`, + }) + } catch (e) { + console.error(e) + } finally { + setLoading(false) + } + }, + [doTransaction, getConnectedSigner] + ) + return { queueLimitOrderCallback, loading } +} diff --git a/src/components/swap/routing/moola/useMoola.ts b/src/components/swap/routing/moola/useMoola.ts index 3976506f..3ef832c9 100644 --- a/src/components/swap/routing/moola/useMoola.ts +++ b/src/components/swap/routing/moola/useMoola.ts @@ -1,7 +1,7 @@ import { CeloContract } from '@celo/contractkit' import { useContractKit, useProvider } from '@celo-tools/use-contractkit' -import { CELO, ChainId, currencyEquals, cUSD, Token } from '@ubeswap/sdk' -import { CEUR, MCELO, MCEUR, MCUSD } from 'constants/index' +import { CELO, ChainId, cREAL, currencyEquals, cUSD, Token } from '@ubeswap/sdk' +import { CEUR, MCELO, MCEUR, MCREAL, MCUSD } from 'constants/index' import { useMemo } from 'react' import { LendingPool, LendingPool__factory } from '../../../../generated' @@ -15,6 +15,7 @@ export const moolaLendingPools = { [CeloContract.GoldToken]: CELO[ChainId.ALFAJORES], [CeloContract.StableToken]: cUSD[ChainId.ALFAJORES], mcUSD: MCUSD[ChainId.ALFAJORES], + mCREAL: MCREAL[ChainId.ALFAJORES], mCELO: MCELO[ChainId.ALFAJORES], }, [ChainId.MAINNET]: { @@ -24,12 +25,14 @@ export const moolaLendingPools = { [CeloContract.GoldToken]: CELO[ChainId.MAINNET], [CeloContract.StableToken]: cUSD[ChainId.MAINNET], mcUSD: MCUSD[ChainId.MAINNET], + mCREAL: MCREAL[ChainId.MAINNET], mCELO: MCELO[ChainId.MAINNET], }, } export const moolaDuals = ( [ + [MCREAL, cREAL], [MCUSD, cUSD], [MCELO, CELO], [MCEUR, CEUR], diff --git a/src/constants/abis/limit/LimitOrderProtocol.json b/src/constants/abis/limit/LimitOrderProtocol.json new file mode 100644 index 00000000..8675dc22 --- /dev/null +++ b/src/constants/abis/limit/LimitOrderProtocol.json @@ -0,0 +1,1726 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newNonce", + "type": "uint256" + } + ], + "name": "NonceIncreased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "orderHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "remainingRaw", + "type": "uint256" + } + ], + "name": "OrderCanceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "orderHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "remaining", + "type": "uint256" + } + ], + "name": "OrderFilled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "orderHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + } + ], + "name": "OrderFilledRFQ", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIMIT_ORDER_RFQ_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIMIT_ORDER_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "amount", + "type": "uint8" + } + ], + "name": "advanceNonce", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "and", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "arbitraryStaticCall", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "salt", + "type": "uint256" + }, + { + "internalType": "address", + "name": "makerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "takerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "allowedSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "makerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "takerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getMakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getTakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "predicate", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "permit", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "interaction", + "type": "bytes" + } + ], + "internalType": "struct OrderMixin.Order", + "name": "order", + "type": "tuple" + } + ], + "name": "cancelOrder", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "orderInfo", + "type": "uint256" + } + ], + "name": "cancelOrderRFQ", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "salt", + "type": "uint256" + }, + { + "internalType": "address", + "name": "makerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "takerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "allowedSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "makerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "takerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getMakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getTakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "predicate", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "permit", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "interaction", + "type": "bytes" + } + ], + "internalType": "struct OrderMixin.Order", + "name": "order", + "type": "tuple" + } + ], + "name": "checkPredicate", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract AggregatorV3Interface", + "name": "oracle1", + "type": "address" + }, + { + "internalType": "contract AggregatorV3Interface", + "name": "oracle2", + "type": "address" + }, + { + "internalType": "uint256", + "name": "spread", + "type": "uint256" + }, + { + "internalType": "int256", + "name": "decimalsScale", + "type": "int256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "doublePrice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "eq", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "salt", + "type": "uint256" + }, + { + "internalType": "address", + "name": "makerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "takerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "allowedSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "makerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "takerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getMakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getTakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "predicate", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "permit", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "interaction", + "type": "bytes" + } + ], + "internalType": "struct OrderMixin.Order", + "name": "order", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "thresholdAmount", + "type": "uint256" + } + ], + "internalType": "struct OrderMixin.OrderAmounts", + "name": "orderAmounts", + "type": "tuple" + } + ], + "name": "fillOrder", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "info", + "type": "uint256" + }, + { + "internalType": "contract IERC20", + "name": "makerAsset", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "takerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "allowedSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + } + ], + "internalType": "struct OrderRFQMixin.OrderRFQ", + "name": "order", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + } + ], + "name": "fillOrderRFQ", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "info", + "type": "uint256" + }, + { + "internalType": "contract IERC20", + "name": "makerAsset", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "takerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "allowedSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + } + ], + "internalType": "struct OrderRFQMixin.OrderRFQ", + "name": "order", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "fillOrderRFQTo", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "info", + "type": "uint256" + }, + { + "internalType": "contract IERC20", + "name": "makerAsset", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "takerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "allowedSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + } + ], + "internalType": "struct OrderRFQMixin.OrderRFQ", + "name": "order", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "permit", + "type": "bytes" + } + ], + "name": "fillOrderRFQToWithPermit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "salt", + "type": "uint256" + }, + { + "internalType": "address", + "name": "makerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "takerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "allowedSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "makerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "takerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getMakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getTakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "predicate", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "permit", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "interaction", + "type": "bytes" + } + ], + "internalType": "struct OrderMixin.Order", + "name": "order", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "thresholdAmount", + "type": "uint256" + } + ], + "internalType": "struct OrderMixin.OrderAmounts", + "name": "orderAmounts", + "type": "tuple" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "fillOrderTo", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "salt", + "type": "uint256" + }, + { + "internalType": "address", + "name": "makerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "takerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "allowedSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "makerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "takerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getMakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getTakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "predicate", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "permit", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "interaction", + "type": "bytes" + } + ], + "internalType": "struct OrderMixin.Order", + "name": "order", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "thresholdAmount", + "type": "uint256" + } + ], + "internalType": "struct OrderMixin.OrderAmounts", + "name": "orderAmounts", + "type": "tuple" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "extraInteraction", + "type": "bytes" + } + ], + "name": "fillOrderToWithExtraInteraction", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "salt", + "type": "uint256" + }, + { + "internalType": "address", + "name": "makerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "takerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "allowedSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "makerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "takerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getMakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getTakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "predicate", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "permit", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "interaction", + "type": "bytes" + } + ], + "internalType": "struct OrderMixin.Order", + "name": "order", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "thresholdAmount", + "type": "uint256" + } + ], + "internalType": "struct OrderMixin.OrderAmounts", + "name": "orderAmounts", + "type": "tuple" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "permit", + "type": "bytes" + } + ], + "name": "fillOrderToWithPermit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "orderMakerAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "orderTakerAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "swapTakerAmount", + "type": "uint256" + } + ], + "name": "getMakerAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "orderMakerAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "orderTakerAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "swapMakerAmount", + "type": "uint256" + } + ], + "name": "getTakerAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "gt", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "salt", + "type": "uint256" + }, + { + "internalType": "address", + "name": "makerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "takerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "allowedSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "makerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "takerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getMakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getTakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "predicate", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "permit", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "interaction", + "type": "bytes" + } + ], + "internalType": "struct OrderMixin.Order", + "name": "order", + "type": "tuple" + } + ], + "name": "hashOrder", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "info", + "type": "uint256" + }, + { + "internalType": "contract IERC20", + "name": "makerAsset", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "takerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "allowedSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + } + ], + "internalType": "struct OrderRFQMixin.OrderRFQ", + "name": "order", + "type": "tuple" + } + ], + "name": "hashOrderRFQ", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "increaseNonce", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "uint256", + "name": "slot", + "type": "uint256" + } + ], + "name": "invalidatorForOrderRFQ", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "lt", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "nonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "makerAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "makerNonce", + "type": "uint256" + } + ], + "name": "nonceEquals", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "or", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "orderHash", + "type": "bytes32" + } + ], + "name": "remaining", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "orderHash", + "type": "bytes32" + } + ], + "name": "remainingRaw", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "orderHashes", + "type": "bytes32[]" + } + ], + "name": "remainingsRaw", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "simulateCalls", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract AggregatorV3Interface", + "name": "oracle", + "type": "address" + }, + { + "internalType": "uint256", + "name": "inverseAndSpread", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "singlePrice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "timestampBelow", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/constants/abis/limit/OrderBook.json b/src/constants/abis/limit/OrderBook.json new file mode 100644 index 00000000..46e70520 --- /dev/null +++ b/src/constants/abis/limit/OrderBook.json @@ -0,0 +1,412 @@ +[ + { + "inputs": [ + { + "internalType": "contract LimitOrderProtocol", + "name": "_limitOrderProtocol", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_fee", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_feeRecipient", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldFee", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newFee", + "type": "uint256" + } + ], + "name": "FeeChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldFeeRecipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newFeeRecipient", + "type": "address" + } + ], + "name": "FeeRecipientChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "orderHash", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "salt", + "type": "uint256" + }, + { + "internalType": "address", + "name": "makerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "takerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "allowedSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "makerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "takerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getMakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getTakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "predicate", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "permit", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "interaction", + "type": "bytes" + } + ], + "indexed": false, + "internalType": "struct OrderMixin.Order", + "name": "order", + "type": "tuple" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "OrderBroadcasted", + "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" + }, + { + "inputs": [], + "name": "MAX_FEE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PCT_DENOMINATOR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "salt", + "type": "uint256" + }, + { + "internalType": "address", + "name": "makerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "takerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "allowedSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "makerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "takerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getMakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getTakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "predicate", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "permit", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "interaction", + "type": "bytes" + } + ], + "internalType": "struct OrderMixin.Order", + "name": "_order", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "_signature", + "type": "bytes" + }, + { + "internalType": "address", + "name": "_notificationTarget", + "type": "address" + } + ], + "name": "broadcastOrder", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_fee", + "type": "uint256" + } + ], + "name": "changeFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_feeRecipient", + "type": "address" + } + ], + "name": "changeFeeRecipient", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "fee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeRecipient", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "limitOrderProtocol", + "outputs": [ + { + "internalType": "contract LimitOrderProtocol", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/constants/abis/limit/OrderBookRewardDistributor.json b/src/constants/abis/limit/OrderBookRewardDistributor.json new file mode 100644 index 00000000..ea25bcdc --- /dev/null +++ b/src/constants/abis/limit/OrderBookRewardDistributor.json @@ -0,0 +1,381 @@ +[ + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "_rewardCurrency", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ERC20Rescued", + "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": "oldRewardCurrency", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newRewardCurrency", + "type": "address" + } + ], + "name": "RewardCurrencyChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "oldRewardRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newRewardRate", + "type": "uint256" + } + ], + "name": "RewardRateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "whitelisted", + "type": "bool" + } + ], + "name": "WhitelistChanged", + "type": "event" + }, + { + "inputs": [], + "name": "PCT_DENOMINATOR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_user", + "type": "address" + } + ], + "name": "addToWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "_rewardCurrency", + "type": "address" + } + ], + "name": "changeRewardCurrency", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_rewardRate", + "type": "uint256" + } + ], + "name": "changeRewardRate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "salt", + "type": "uint256" + }, + { + "internalType": "address", + "name": "makerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "takerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "address", + "name": "allowedSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "makingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takingAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "makerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "takerAssetData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getMakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "getTakerAmount", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "predicate", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "permit", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "interaction", + "type": "bytes" + } + ], + "internalType": "struct OrderMixin.Order", + "name": "_order", + "type": "tuple" + }, + { + "internalType": "address", + "name": "_rewardRecipient", + "type": "address" + } + ], + "name": "notifyOrderBroadcasted", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_user", + "type": "address" + } + ], + "name": "removeFromWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "rescueERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rewardCurrency", + "outputs": [ + { + "internalType": "contract IERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "rewardRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "whitelist", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/constants/abis/moola/moola-staking-rewards.ts b/src/constants/abis/moola/moola-staking-rewards.ts new file mode 100644 index 00000000..28f7252b --- /dev/null +++ b/src/constants/abis/moola/moola-staking-rewards.ts @@ -0,0 +1,5 @@ +import { Interface } from '@ethersproject/abi' + +import MOOLA_STAKING_REWARDS_ABI from './MoolaStakingRewards.json' + +export const MOOLA_STAKING_REWARDS_INTERFACE = new Interface(MOOLA_STAKING_REWARDS_ABI) diff --git a/src/constants/abis/multicall/Multicall.json b/src/constants/abis/multicall/Multicall.json new file mode 100644 index 00000000..76cadf4c --- /dev/null +++ b/src/constants/abis/multicall/Multicall.json @@ -0,0 +1,165 @@ +[ + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall2.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "aggregate", + "outputs": [ + { "internalType": "uint256", "name": "blockNumber", "type": "uint256" }, + { "internalType": "bytes[]", "name": "returnData", "type": "bytes[]" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall2.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "blockAndAggregate", + "outputs": [ + { "internalType": "uint256", "name": "blockNumber", "type": "uint256" }, + { "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }, + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall2.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" }], + "name": "getBlockHash", + "outputs": [{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getBlockNumber", + "outputs": [{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockCoinbase", + "outputs": [{ "internalType": "address", "name": "coinbase", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockDifficulty", + "outputs": [{ "internalType": "uint256", "name": "difficulty", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockGasLimit", + "outputs": [{ "internalType": "uint256", "name": "gaslimit", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentBlockTimestamp", + "outputs": [{ "internalType": "uint256", "name": "timestamp", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "addr", "type": "address" }], + "name": "getEthBalance", + "outputs": [{ "internalType": "uint256", "name": "balance", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastBlockHash", + "outputs": [{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "requireSuccess", "type": "bool" }, + { + "components": [ + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall2.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryAggregate", + "outputs": [ + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall2.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bool", "name": "requireSuccess", "type": "bool" }, + { + "components": [ + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "bytes", "name": "callData", "type": "bytes" } + ], + "internalType": "struct Multicall2.Call[]", + "name": "calls", + "type": "tuple[]" + } + ], + "name": "tryBlockAndAggregate", + "outputs": [ + { "internalType": "uint256", "name": "blockNumber", "type": "uint256" }, + { "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }, + { + "components": [ + { "internalType": "bool", "name": "success", "type": "bool" }, + { "internalType": "bytes", "name": "returnData", "type": "bytes" } + ], + "internalType": "struct Multicall2.Result[]", + "name": "returnData", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/constants/index.ts b/src/constants/index.ts index f99e29a4..4b1deaed 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -21,11 +21,52 @@ export const AVERAGE_BLOCK_TIME_IN_SECS = 13 export const PROPOSAL_LENGTH_IN_BLOCKS = 40_320 export const PROPOSAL_LENGTH_IN_SECS = AVERAGE_BLOCK_TIME_IN_SECS * PROPOSAL_LENGTH_IN_BLOCKS +export const LIMIT_ORDER_ADDRESS = { + [ChainId.MAINNET]: '0x83013dCE53676F523dB8175832f2f3AD5B1fBb1f', + [ChainId.ALFAJORES]: '0xb5911e904EEf100803D5d4bDb22ff1177324e7F3', + [ChainId.BAKLAVA]: '', +} + +export const ORDER_BOOK_ADDRESS = { + [ChainId.MAINNET]: '0xF3acED685402F5Ece86A0672512a95Ab20a8f1bC', + [ChainId.ALFAJORES]: '0x6F15Ed13b9a5ce8C3B19861331458556374628Ad', + [ChainId.BAKLAVA]: '', +} + +export const ORDER_BOOK_REWARD_DISTRIBUTOR_ADDRESS = { + [ChainId.MAINNET]: '0xEEc60eB73DfC052682351E0D4309797B3C9eB6d8', + [ChainId.ALFAJORES]: '0x08AD74C7D04c1e54E96A4142598BcCe56A4a4119', + [ChainId.BAKLAVA]: '', +} + +export const MULTICALL_ADDRESS = { + [ChainId.MAINNET]: '0x75f59534dd892c1f8a7b172d639fa854d529ada3', + [ChainId.ALFAJORES]: '0x387ce7960b5DA5381De08Ea4967b13a7c8cAB3f6', + [ChainId.BAKLAVA]: '', +} + export const POOF = { [ChainId.MAINNET]: new Token(ChainId.MAINNET, '0x00400FcbF0816bebB94654259de7273f4A05c762', 18, 'POOF', 'POOF'), [ChainId.ALFAJORES]: new Token(ChainId.ALFAJORES, '0x00400FcbF0816bebB94654259de7273f4A05c762', 18, 'POOF', 'POOF'), } +export const MCREAL = { + [ChainId.MAINNET]: new Token( + ChainId.MAINNET, + '0x9802d866fdE4563d088a6619F7CeF82C0B991A55', + 18, + 'mCREAL', + 'Moola cREAL' + ), + [ChainId.ALFAJORES]: new Token( + ChainId.ALFAJORES, + '0x3D0EDA535ca4b15c739D46761d24E42e37664Ad7', + 18, + 'mCREAL', + 'Moola cREAL' + ), +} + export const MCUSD = { [ChainId.MAINNET]: new Token( ChainId.MAINNET, diff --git a/src/hooks/useContract.ts b/src/hooks/useContract.ts index 703abc02..6c7fa9e5 100644 --- a/src/hooks/useContract.ts +++ b/src/hooks/useContract.ts @@ -7,13 +7,24 @@ import { StakingInfo } from 'state/stake/hooks' import ENS_PUBLIC_RESOLVER_ABI from '../constants/abis/ens-public-resolver.json' import ERC20_ABI, { ERC20_BYTES32_ABI } from '../constants/abis/erc20' +import LIMIT_ORDER_PROTOCOL_ABI from '../constants/abis/limit/LimitOrderProtocol.json' +import ORDER_BOOK_ABI from '../constants/abis/limit/OrderBook.json' +import ORDER_BOOK_REWARD_DISTRUBUTOR_ABI from '../constants/abis/limit/OrderBookRewardDistributor.json' import DUAL_REWARDS_ABI from '../constants/abis/moola/MoolaStakingRewards.json' import POOL_MANAGER_ABI from '../constants/abis/pool-manager.json' import RELEASE_UBE_ABI from '../constants/abis/ReleaseUbe.json' import STAKING_REWARDS_ABI from '../constants/abis/StakingRewards.json' import VOTABLE_STAKING_REWARDS_ABI from '../constants/abis/VotableStakingRewards.json' import { MULTICALL_ABI, MULTICALL_NETWORKS } from '../constants/multicall' -import { Erc20, MoolaStakingRewards, PoolManager, StakingRewards } from '../generated' +import { + Erc20, + LimitOrderProtocol, + MoolaStakingRewards, + OrderBook, + OrderBookRewardDistributor, + PoolManager, + StakingRewards, +} from '../generated' import { getContract } from '../utils' // returns null on errors @@ -119,3 +130,25 @@ export function useMultiStakingContract( ): MoolaStakingRewards | null { return useContract(stakingAddress, DUAL_REWARDS_ABI, withSignerIfPossible) as MoolaStakingRewards | null } + +export function useOrderBookContract(address?: string, withSignerIfPossible?: boolean): OrderBook | null { + return useContract(address, ORDER_BOOK_ABI, withSignerIfPossible) as OrderBook | null +} + +export function useOrderBookRewardDistributorContract( + address?: string, + withSignerIfPossible?: boolean +): OrderBookRewardDistributor | null { + return useContract( + address, + ORDER_BOOK_REWARD_DISTRUBUTOR_ABI, + withSignerIfPossible + ) as OrderBookRewardDistributor | null +} + +export function useLimitOrderProtocolContract( + address?: string, + withSignerIfPossible?: boolean +): LimitOrderProtocol | null { + return useContract(address, LIMIT_ORDER_PROTOCOL_ABI, withSignerIfPossible) as LimitOrderProtocol | null +} diff --git a/src/index.tsx b/src/index.tsx index ca89c790..282e7d1e 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -113,6 +113,7 @@ function Updaters() { ReactDOM.render( + {/* TODO: Mainnet, not alfajores */} An unexpected error occured on this part of the page. Please reload.

}> + diff --git a/src/pages/Earn/useFarmRegistry.ts b/src/pages/Earn/useFarmRegistry.ts index 375b09a8..fa7b245b 100644 --- a/src/pages/Earn/useFarmRegistry.ts +++ b/src/pages/Earn/useFarmRegistry.ts @@ -26,9 +26,9 @@ const blacklist: Record = { } const featuredPoolWhitelist: Record = { - '0x6F11B6eA70DEe4f167b1A4ED1F01C903f6781960': true, // PACT + '0x6F11B6eA70DEe4f167b1A4ED1F01C903f6781960': false, // PACT '0xEfe2f9d62E45815837b4f20c1F44F0A83605B540': false, // ARI - '0x155DA6F164D925E3a91F510B50DEC08aA03B4071': true, // IMMO + '0x155DA6F164D925E3a91F510B50DEC08aA03B4071': false, // IMMO } const CREATION_BLOCK = 9840049 diff --git a/src/pages/LimitOrder/LimitOrderHistory.tsx b/src/pages/LimitOrder/LimitOrderHistory.tsx new file mode 100644 index 00000000..06cb6183 --- /dev/null +++ b/src/pages/LimitOrder/LimitOrderHistory.tsx @@ -0,0 +1,61 @@ +import { useContractKit } from '@celo-tools/use-contractkit' +import { ChainId as UbeswapChainId } from '@ubeswap/sdk' +import { TabButton } from 'components/Button' +import LimitOrderHistoryBody from 'components/LimitOrderHistory/LimitOrderHistoryBody' +import LimitOrderHistoryItem from 'components/LimitOrderHistory/LimitOrderHistoryItem' +import { Wrapper } from 'components/swap/styleds' +import { useToken } from 'hooks/Tokens' +import { useOrderBookRewardDistributorContract } from 'hooks/useContract' +import React, { useState } from 'react' +import { useSingleCallResult } from 'state/multicall/hooks' + +import { ORDER_BOOK_REWARD_DISTRIBUTOR_ADDRESS } from '../../constants' +import { useLimitOrdersHistory } from './useOrderBroadcasted' + +export const LimitOrderHistory: React.FC = () => { + const { network } = useContractKit() + const chainId = network.chainId as unknown as UbeswapChainId + const limitOrderHistory = useLimitOrdersHistory() + + const [openOrdersTabActive, setOpenOrdersTabActive] = useState(true) + + const rewardDistributorContract = useOrderBookRewardDistributorContract( + ORDER_BOOK_REWARD_DISTRIBUTOR_ADDRESS[chainId] + ) + const rewardCurrencyAddress = useSingleCallResult(rewardDistributorContract, 'rewardCurrency', []).result?.[0] + const rewardCurrency = useToken(rewardCurrencyAddress) + + return ( + +
+ setOpenOrdersTabActive(true)}> + Open ({limitOrderHistory.filter((limitOrderHist) => limitOrderHist.isOrderOpen).length}) + + setOpenOrdersTabActive(false)}> + Completed ({limitOrderHistory.filter((limitOrderHist) => !limitOrderHist.isOrderOpen).length}) + +
+ + + {limitOrderHistory + .filter((limitOrderHist) => { + if (openOrdersTabActive) { + return limitOrderHist.isOrderOpen + } + return !limitOrderHist.isOrderOpen + }) + .reverse() + .map((limitOrderHist, idx, arr) => { + return ( + + ) + })} + +
+ ) +} diff --git a/src/pages/LimitOrder/executCancelOrder.tsx b/src/pages/LimitOrder/executCancelOrder.tsx new file mode 100644 index 00000000..e1d7daf1 --- /dev/null +++ b/src/pages/LimitOrder/executCancelOrder.tsx @@ -0,0 +1,31 @@ +import { CancelLimitOrderExecutor } from 'components/swap/routing' +import { ContractTransaction } from 'ethers' +import { LimitOrderProtocol__factory, OrderBook__factory } from 'generated' + +import { LIMIT_ORDER_ADDRESS, ORDER_BOOK_ADDRESS } from '../../constants' + +export const executeCancelOrder: CancelLimitOrderExecutor = async ({ signer, chainId, orderHash, doTransaction }) => { + const orderBookAddr = ORDER_BOOK_ADDRESS[chainId] + const limitOrderAddr = LIMIT_ORDER_ADDRESS[chainId] + + const orderBook = OrderBook__factory.connect(orderBookAddr, signer) + const limitOrderProtocolFactory = LimitOrderProtocol__factory.connect(limitOrderAddr, signer) + + const cancel = async (): Promise => { + const orders = await orderBook.queryFilter(orderBook.filters['OrderBroadcasted'](undefined, orderHash), 0, 'latest') + if (orders.length === 0) { + throw new Error('Error finding the order') + } else if (orders.length > 0) { + console.warn('More than one order was found with the same hash') + } + + const { order } = orders[0].args + + return await doTransaction(limitOrderProtocolFactory, 'cancelOrder', { + args: [order], + summary: `Cancel Order`, + }) + } + + return { hash: (await cancel()).hash } +} diff --git a/src/pages/LimitOrder/index.tsx b/src/pages/LimitOrder/index.tsx new file mode 100644 index 00000000..4be1b762 --- /dev/null +++ b/src/pages/LimitOrder/index.tsx @@ -0,0 +1,479 @@ +import { useContractKit, WalletTypes } from '@celo-tools/use-contractkit' +import { RampInstantSDK } from '@ramp-network/ramp-instant-sdk' +import { ChainId as UbeswapChainId, cUSD, JSBI, TokenAmount, Trade } from '@ubeswap/sdk' +import { CardNoise, CardSection, DataCard } from 'components/earn/styled' +import { useQueueLimitOrderTrade } from 'components/swap/routing/limit/queueLimitOrderTrade' +import { useTradeCallback } from 'components/swap/routing/useTradeCallback' +import { useIsTransactionUnsupported } from 'hooks/Trades' +import { useOrderBookContract, useOrderBookRewardDistributorContract } from 'hooks/useContract' +import useENS from 'hooks/useENS' +import React, { useCallback, useContext, useEffect, useState } from 'react' +import ReactGA from 'react-ga' +import { useTranslation } from 'react-i18next' +import { Text } from 'rebass' +import { + useDerivedLimitOrderInfo, + useLimitOrderActionHandlers, + useLimitOrderState, + useMarketPriceDiff, +} from 'state/limit/hooks' +import { useSingleCallResult } from 'state/multicall/hooks' +import { ThemeContext } from 'styled-components' + +import { ButtonConfirmed, ButtonLight, ButtonPrimary, TabButton } from '../../components/Button' +import Card from '../../components/Card' +import Column, { AutoColumn, TopSectionLimitOrder } from '../../components/Column' +import CurrencyInputPanel from '../../components/CurrencyInputPanel' +import Loader from '../../components/Loader' +import ProgressSteps from '../../components/ProgressSteps' +import { AutoRow, RowBetween } from '../../components/Row' +import ConfirmSwapModal from '../../components/swap/ConfirmSwapModal' +import { BottomGrouping, Wrapper } from '../../components/swap/styleds' +import SwapHeader from '../../components/swap/SwapHeader' +import TradePrice from '../../components/swap/TradePrice' +import { LIMIT_ORDER_ADDRESS, ORDER_BOOK_ADDRESS, ORDER_BOOK_REWARD_DISTRIBUTOR_ADDRESS } from '../../constants' +import { useCurrency, useToken } from '../../hooks/Tokens' +import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback' +import { useWalletModalToggle } from '../../state/application/hooks' +import { Field } from '../../state/limit/actions' +import { useUserSlippageTolerance } from '../../state/user/hooks' +import { TYPE } from '../../theme' +import AppBody from '../AppBody' +import { LimitOrderHistory } from './LimitOrderHistory' + +export const BPS_DENOMINATOR = JSBI.BigInt(1_000_000) + +export default function LimitOrder() { + const { address: account, network, walletType } = useContractKit() + const chainId = network.chainId as unknown as UbeswapChainId + const { queueLimitOrderCallback, loading: queueOrderLoading } = useQueueLimitOrderTrade() + + const { t } = useTranslation() + + const theme = useContext(ThemeContext) + + // toggle wallet when disconnected + const toggleWalletModal = useWalletModalToggle() + + // get custom setting values for user + const [allowedSlippage] = useUserSlippageTolerance() + + const orderBookContract = useOrderBookContract(ORDER_BOOK_ADDRESS[chainId]) + const orderBookFee = useSingleCallResult(orderBookContract, 'fee', []).result?.[0] + + // swap state + const { tokenTypedValue, priceTypedValue, recipient } = useLimitOrderState() + const { + v2Trade: trade, + parsedInputTotal, + parsedOutputTotal, + currencies, + inputError: limitOrderInputError, + showRamp, + buying, + } = useDerivedLimitOrderInfo() + const { address: recipientAddress } = useENS(recipient) + + const rewardDistributorContract = useOrderBookRewardDistributorContract( + ORDER_BOOK_REWARD_DISTRIBUTOR_ADDRESS[chainId] + ) + const rewardCurrencyAddress = useSingleCallResult(rewardDistributorContract, 'rewardCurrency', []).result?.[0] + const rewardCurrency = useToken(rewardCurrencyAddress) + const rewardRate = useSingleCallResult(rewardDistributorContract, 'rewardRate', [ + buying ? currencies?.PRICE?.address : currencies?.TOKEN?.address, + ]).result?.[0] + + const { onCurrencySelection, onUserInput, setBuying } = useLimitOrderActionHandlers() + const defaultPriceCurrency = useCurrency(cUSD[chainId].address) + useEffect(() => { + defaultPriceCurrency && onCurrencySelection(Field.PRICE, defaultPriceCurrency) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + const isValid = !limitOrderInputError + + const handleTypeTokenAmount = useCallback( + (value: string) => { + onUserInput(Field.TOKEN, value) + }, + [onUserInput] + ) + const handleTypePrice = useCallback( + (value: string) => { + onUserInput(Field.PRICE, value) + }, + [onUserInput] + ) + + // modal and loading + const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{ + showConfirm: boolean + tradeToConfirm: Trade | undefined + attemptingTxn: boolean + swapErrorMessage: string | undefined + txHash: string | undefined + }>({ + showConfirm: false, + tradeToConfirm: undefined, + attemptingTxn: false, + swapErrorMessage: undefined, + txHash: undefined, + }) + + const formattedAmounts = { + [Field.PRICE]: priceTypedValue, + [Field.TOKEN]: tokenTypedValue, + } + + // check whether the user has approved the router on the input token + // TODO: inputAmount may not work if it is the dependent field + const [limitOrderApproval, limitOrderApprovalCallback] = useApproveCallback( + parsedInputTotal, + LIMIT_ORDER_ADDRESS[chainId] + ) + const orderFee = + parsedInputTotal && orderBookFee + ? new TokenAmount( + parsedInputTotal.currency, + JSBI.divide(JSBI.multiply(parsedInputTotal.raw, JSBI.BigInt(orderBookFee.toString())), BPS_DENOMINATOR) + ) + : undefined + + const { aboveMarketPrice, marketPriceDiffIndicator } = useMarketPriceDiff() + + const reward = + parsedInputTotal && rewardCurrency && rewardRate + ? new TokenAmount( + rewardCurrency, + JSBI.divide(JSBI.multiply(parsedInputTotal.raw, JSBI.BigInt(rewardRate.toString())), BPS_DENOMINATOR) + ) + : undefined + const [orderBookApproval, orderBookApprovalCallback] = useApproveCallback(orderFee, ORDER_BOOK_ADDRESS[chainId]) + const approvalCallback = useCallback(async () => { + if (limitOrderApproval === ApprovalState.NOT_APPROVED) { + await limitOrderApprovalCallback() + } + if (orderBookApproval === ApprovalState.NOT_APPROVED) { + await orderBookApprovalCallback() + } + }, [limitOrderApproval, orderBookApproval, limitOrderApprovalCallback, orderBookApprovalCallback]) + + // check if user has gone through orderBookApproval process, used to show two step buttons, reset on token change + const [approvalSubmitted, setApprovalSubmitted] = useState(false) + + // mark when a user has submitted an orderBookApproval, reset onTokenSelection for input field + useEffect(() => { + if (limitOrderApproval === ApprovalState.PENDING || orderBookApproval === ApprovalState.PENDING) { + setApprovalSubmitted(true) + } + }, [limitOrderApproval, orderBookApproval, approvalSubmitted]) + + const getColor = () => { + return buying ? (aboveMarketPrice ? theme.green1 : theme.red1) : aboveMarketPrice ? theme.red1 : theme.green1 + } + + // the callback to execute the swap + const { callback: swapCallback } = useTradeCallback(tradeToConfirm, allowedSlippage, recipient) + + const handlePlaceOrder = useCallback(() => { + if (!swapCallback) { + return + } + setSwapState({ attemptingTxn: true, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: undefined }) + swapCallback() + .then((hash) => { + setSwapState({ attemptingTxn: false, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: hash }) + + ReactGA.event({ + category: 'Limit Order', + action: + recipient === null + ? 'Limit Order w/o Send' + : (recipientAddress ?? recipient) === account + ? 'Limit Order w/o Send + recipient' + : 'Limit Order w/ Send', + label: [trade?.inputAmount?.currency?.symbol, trade?.outputAmount?.currency?.symbol].join('/'), + }) + }) + .catch((error) => { + setSwapState({ + attemptingTxn: false, + tradeToConfirm, + showConfirm, + swapErrorMessage: error.message, + txHash: undefined, + }) + }) + }, [swapCallback, tradeToConfirm, showConfirm, recipient, recipientAddress, account, trade]) + + // errors + const [showInverted, setShowInverted] = useState(false) + + // show approve flow when: no error on inputs, not approved or pending, or approved in current session + // never show if price impact is above threshold in non expert mode + const showApproveFlow = + (!limitOrderInputError && + (orderBookApproval === ApprovalState.NOT_APPROVED || + orderBookApproval === ApprovalState.PENDING || + (approvalSubmitted && orderBookApproval === ApprovalState.APPROVED))) || + limitOrderApproval === ApprovalState.NOT_APPROVED || + limitOrderApproval === ApprovalState.PENDING || + (approvalSubmitted && limitOrderApproval === ApprovalState.APPROVED) + + const handleConfirmDismiss = useCallback(() => { + setSwapState({ showConfirm: false, tradeToConfirm, attemptingTxn, swapErrorMessage, txHash }) + // if there was a tx hash, we want to clear the input + if (txHash) { + onUserInput(Field.TOKEN, '') + onUserInput(Field.PRICE, '') + } + }, [attemptingTxn, onUserInput, swapErrorMessage, tradeToConfirm, txHash]) + + const handleAcceptChanges = useCallback(() => { + setSwapState({ tradeToConfirm: trade, swapErrorMessage, txHash, attemptingTxn, showConfirm }) + }, [attemptingTxn, showConfirm, swapErrorMessage, trade, txHash]) + + const handlePriceSelect = useCallback( + (inputCurrency) => { + buying && setApprovalSubmitted(false) // reset 2 step UI for approvals + onCurrencySelection(Field.PRICE, inputCurrency) + }, + [buying, onCurrencySelection] + ) + + const handleTokenSelect = useCallback( + (outputCurrency) => { + !buying && setApprovalSubmitted(false) // reset 2 step UI for approvals + onCurrencySelection(Field.TOKEN, outputCurrency) + }, + [buying, onCurrencySelection] + ) + + const swapIsUnsupported = useIsTransactionUnsupported(currencies?.PRICE, currencies?.TOKEN) + + const walletIsSupported = + walletType === WalletTypes.MetaMask || + walletType === WalletTypes.CeloExtensionWallet || + walletType === WalletTypes.PrivateKey || + walletType === WalletTypes.Injected + + return ( + <> + {!walletIsSupported && ( + + + + + + + Notice + + + + You must be connected to a Metamask wallet to place limit orders + + {' '} + + + + + + )} + + +
+ setBuying(true)}> + Buy + + setBuying(false)}> + Sell + +
+ + + + + + {buying ? 'Buy' : 'Sell'} Amount + + + + Limit Price + + + + + + <> + + {marketPriceDiffIndicator && ( +
+ + {marketPriceDiffIndicator.toSignificant(4)}% {aboveMarketPrice ? 'below' : 'above'}   + + + market price + +
+ )} +
+ + + Market Price + + {trade ? ( + + ) : ( + - + )} + + + + Order Reward + + + {reward?.toSignificant(2) ?? '-'} {reward?.currency.symbol} + + + + + Order Fee + + + {orderFee?.toSignificant(2) ?? '-'} {orderFee?.currency.symbol} + + + + + Total + + + {parsedInputTotal && orderFee ? parsedInputTotal.add(orderFee).toSignificant(6) : '-'}{' '} + {parsedInputTotal?.currency.symbol} + + + +
+
+
+ + {swapIsUnsupported ? ( + + Unsupported Asset + + ) : !account ? ( + {t('connectWallet')} + ) : showRamp ? ( + { + new RampInstantSDK({ + hostAppName: 'Ubeswap', + hostLogoUrl: 'https://info.ubeswap.org/favicon.png', + userAddress: account, + swapAsset: parsedInputTotal?.currency.symbol, + hostApiKey: process.env.REACT_APP_RAMP_KEY, + }).show() + }} + > + Get more {parsedInputTotal?.currency.symbol} via Ramp + + ) : ( + + + {limitOrderApproval === ApprovalState.PENDING || orderBookApproval === ApprovalState.PENDING ? ( + + Approving + + ) : approvalSubmitted && orderBookApproval === ApprovalState.APPROVED ? ( + 'Approved' + ) : ( + 'Approve ' + (currencies[buying ? Field.PRICE : Field.TOKEN]?.symbol ?? '') + )} + + { + if (parsedInputTotal && parsedOutputTotal) { + queueLimitOrderCallback({ + inputAmount: parsedInputTotal, + outputAmount: parsedOutputTotal, + chainId: chainId, + }) + } + }} + width="48%" + id="swap-button" + disabled={ + !isValid || + limitOrderApproval !== ApprovalState.APPROVED || + orderBookApproval !== ApprovalState.APPROVED + } + altDisabledStyle={queueOrderLoading} // show solid button while waiting + paddingY="14px" + > + + + {t('placeOrder')} + + {queueOrderLoading && } + + + + )} + {showApproveFlow && ( + + + + )} + +
+
+ + + ) +} diff --git a/src/pages/LimitOrder/redirects.tsx b/src/pages/LimitOrder/redirects.tsx new file mode 100644 index 00000000..48680c38 --- /dev/null +++ b/src/pages/LimitOrder/redirects.tsx @@ -0,0 +1,42 @@ +import React, { useEffect } from 'react' +import { useDispatch } from 'react-redux' +import { Redirect, RouteComponentProps } from 'react-router-dom' + +import { AppDispatch } from '../../state' +import { ApplicationModal, setOpenModal } from '../../state/application/actions' + +// Redirects to swap but only replace the pathname +export function RedirectPathToSwapOnly({ location }: RouteComponentProps) { + return +} + +// Redirects from the /swap/:outputCurrency path to the /swap?outputCurrency=:outputCurrency format +export function RedirectToSwap(props: RouteComponentProps<{ outputCurrency: string }>) { + const { + location: { search }, + match: { + params: { outputCurrency }, + }, + } = props + + return ( + 1 + ? `${search}&outputCurrency=${outputCurrency}` + : `?outputCurrency=${outputCurrency}`, + }} + /> + ) +} + +export function OpenClaimAddressModalAndRedirectToSwap(props: RouteComponentProps) { + const dispatch = useDispatch() + useEffect(() => { + dispatch(setOpenModal(ApplicationModal.ADDRESS_CLAIM)) + }, [dispatch]) + return +} diff --git a/src/pages/LimitOrder/useCancelOrderCallback.tsx b/src/pages/LimitOrder/useCancelOrderCallback.tsx new file mode 100644 index 00000000..6a475807 --- /dev/null +++ b/src/pages/LimitOrder/useCancelOrderCallback.tsx @@ -0,0 +1,38 @@ +import { useContractKit, useProvider } from '@celo-tools/use-contractkit' +import { ChainId } from '@ubeswap/sdk' +import { useDoTransaction } from 'components/swap/routing' +import { useMemo } from 'react' + +import { executeCancelOrder } from './executCancelOrder' + +/** + * Use callback to cancel open limit order + * @param orderHash the hash of the order to cancel + * @returns + */ +export const useCancelOrderCallback = ( + orderHash: string | undefined // orderHash of order to cancel +): { callback: null | (() => Promise); error: string | null } => { + const { address: account, network } = useContractKit() + const library = useProvider() + const chainId = network.chainId as unknown as ChainId + const doTransaction = useDoTransaction() + + return useMemo(() => { + if (!library || !orderHash || !account) { + return { callback: null, error: 'Missing dependencies' } + } + + if (chainId === ChainId.BAKLAVA) { + return { callback: null, error: 'Baklava is not supported' } + } + + const signer = library.getSigner(account) + const env = { signer, chainId, doTransaction } + + return { + callback: async () => (await executeCancelOrder({ ...env, orderHash })).hash, + error: null, + } + }, [library, chainId, doTransaction, orderHash, account]) +} diff --git a/src/pages/LimitOrder/useOrderBroadcasted.tsx b/src/pages/LimitOrder/useOrderBroadcasted.tsx new file mode 100644 index 00000000..3ad5c32a --- /dev/null +++ b/src/pages/LimitOrder/useOrderBroadcasted.tsx @@ -0,0 +1,125 @@ +import { useContractKit, useProvider } from '@celo-tools/use-contractkit' +import { ChainId } from '@ubeswap/sdk' +import { BigNumber } from 'ethers' +import { OrderBook__factory } from 'generated' +import { useLimitOrderProtocolContract } from 'hooks/useContract' +import React, { useEffect } from 'react' +import { useSingleContractMultipleData } from 'state/multicall/hooks' + +import { LIMIT_ORDER_ADDRESS, ORDER_BOOK_ADDRESS } from '../../constants' + +// TODO: Just do batch fetching in the future +const CREATION_BLOCK = 10_000_000 + +export interface LimitOrdersHistory { + orderHash: string + isOrderOpen: boolean + makingAmount: BigNumber + takingAmount: BigNumber + makerAsset: string + takerAsset: string + remaining: BigNumber + transactionHash: string +} + +export interface LimitOrderRewards { + rewardRate: BigNumber + makerCurrencyAddress: string +} + +type OrderBookEvent = { + maker: string + orderHash: string + order: { + salt: BigNumber + makerAsset: string + takerAsset: string + maker: string + receiver: string + allowedSender: string + makingAmount: BigNumber + takingAmount: BigNumber + makerAssetData: string + takerAssetData: string + getMakerAmount: string + getTakerAmount: string + predicate: string + permit: string + interaction: string + } + signature: string + transactionHash: string +} + +export const useOrderBroadcasted = () => { + const { account, network } = useContractKit() + const provider = useProvider() + const chainId = network.chainId as unknown as ChainId + const orderBookAddr = ORDER_BOOK_ADDRESS[chainId] + + const [orderBroadcasts, setOrderBroadcasts] = React.useState([]) + const call = React.useCallback(async () => { + if (!account) { + return + } + const orderBook = OrderBook__factory.connect(orderBookAddr, provider) + const orderBookEvents = await orderBook.queryFilter( + orderBook.filters['OrderBroadcasted'](account), + CREATION_BLOCK, + 'latest' + ) + const orderBroadcasts = orderBookEvents.map((orderBookEvent) => { + return { + ...orderBookEvent.args, + transactionHash: orderBookEvent.transactionHash, + } + }) + setOrderBroadcasts(orderBroadcasts) + }, [account, orderBookAddr, provider]) + + useEffect(() => { + const timer = setInterval(call, 5000) + return () => { + clearInterval(timer) + } + }, [call]) + + return orderBroadcasts +} + +export const useLimitOrdersHistory = (): LimitOrdersHistory[] => { + const orderEvents = useOrderBroadcasted() + const { network } = useContractKit() + const chainId = network.chainId as unknown as ChainId + const limitOrderAddr = LIMIT_ORDER_ADDRESS[chainId] + + const limitOrderProtocol = useLimitOrderProtocolContract(limitOrderAddr) + const remainingsRaw = useSingleContractMultipleData( + limitOrderProtocol, + 'remainingRaw', + orderEvents.map(({ orderHash }) => [orderHash]) + ) + const remainings = remainingsRaw?.find((result) => !result.result) + ? null + : (remainingsRaw.map((r) => r.result?.[0] ?? BigNumber.from(0)) as readonly BigNumber[]) + + return React.useMemo(() => { + if (!remainings) return [] + + return orderEvents.map(({ orderHash, order, transactionHash }, idx) => { + const { makerAsset, takerAsset, makingAmount, takingAmount } = order + const remaining = remainings[idx].eq(0) ? makingAmount : remainings[idx].sub(1) + + return { + orderHash, + makerAsset, + takerAsset, + isOrderOpen: remaining.gt(0), + remaining, + makingAmount, + takingAmount, + transactionHash, + } + }) + }, [orderEvents, remainings]) +} diff --git a/src/pages/Stake/index.tsx b/src/pages/Stake/index.tsx index ad735340..c5de6c57 100644 --- a/src/pages/Stake/index.tsx +++ b/src/pages/Stake/index.tsx @@ -1,7 +1,7 @@ import { useContractKit, useGetConnectedSigner } from '@celo-tools/use-contractkit' import { TokenAmount } from '@ubeswap/sdk' import { ButtonEmpty, ButtonLight, ButtonPrimary, ButtonRadio } from 'components/Button' -import { GreyCard, YellowCard } from 'components/Card' +import { GreyCard, LightCard } from 'components/Card' import { AutoColumn } from 'components/Column' import CurrencyLogo from 'components/CurrencyLogo' import { CardNoise, CardSection, DataCard } from 'components/earn/styled' @@ -215,9 +215,9 @@ export const Stake: React.FC = () => { {/*

Your UBE stake: {stakeBalance ? stakeBalance.toFixed(2, { groupSeparator: ',' }) : '--'} UBE

*/} {userRewardRate?.greaterThan('0') ? ( <> - + -

Your weekly rewards

+

Your Weekly Rewards

{userRewardRate ? userRewardRate.multiply(BIG_INT_SECONDS_IN_WEEK).toFixed(2, { groupSeparator: ',' }) @@ -226,7 +226,7 @@ export const Stake: React.FC = () => {

-

Annual stake APR

+

Annual Stake APR

{apy?.multiply('100').toFixed(2, { groupSeparator: ',' }) ?? '--'}%{' '}

@@ -234,15 +234,15 @@ export const Stake: React.FC = () => {

Unclaimed Rewards

-

- {userRewardRate ? earned.toFixed(4, { groupSeparator: ',' }) : '--'} -

{t('claim')} +

+ {userRewardRate ? earned.toFixed(4, { groupSeparator: ',' }) : '--'} +

-
+

Community UBE Stake

@@ -254,14 +254,14 @@ export const Stake: React.FC = () => {

{stakeBalance?.toSignificant(4)}

View UBE Contract @@ -304,10 +304,10 @@ export const Stake: React.FC = () => { -

Your UBE stake

-
+

Your UBE Stake

+
setStaking(true)}> Stake diff --git a/src/state/application/actions.ts b/src/state/application/actions.ts index cd8f456c..acbdea27 100644 --- a/src/state/application/actions.ts +++ b/src/state/application/actions.ts @@ -28,6 +28,7 @@ export enum ApplicationModal { MENU, DELEGATE, VOTE, + CHARTS, } export const updateBlockNumber = createAction<{ chainId: number; blockNumber: number }>('application/updateBlockNumber') diff --git a/src/state/index.ts b/src/state/index.ts index cbd4c394..755e23f9 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -4,6 +4,7 @@ import { load, save } from 'redux-localstorage-simple' import application from './application/reducer' import burn from './burn/reducer' import { updateVersion } from './global/actions' +import limit from './limit/reducer' import lists from './lists/reducer' import mint from './mint/reducer' import multicall from './multicall/reducer' @@ -19,6 +20,7 @@ const store = configureStore({ user, transactions, swap, + limit, mint, burn, multicall, diff --git a/src/state/limit/actions.ts b/src/state/limit/actions.ts new file mode 100644 index 00000000..ef46103d --- /dev/null +++ b/src/state/limit/actions.ts @@ -0,0 +1,21 @@ +import { createAction } from '@reduxjs/toolkit' + +export enum Field { + TOKEN = 'TOKEN', + PRICE = 'PRICE', +} + +export const selectCurrency = createAction<{ field: Field; currencyId: string }>('limit/selectCurrency') +export const switchCurrencies = createAction('limit/switchCurrencies') +export const typeInput = createAction<{ field: Field; typedValue: string }>('limit/typeInput') +export const setBuying = createAction<{ buying: boolean }>('limit/setBuying') +export const replaceLimitState = createAction<{ + field: Field + priceTypedValue: string + tokenTypedValue: string + priceCurrencyId?: string + tokenCurrencyId?: string + recipient: string | null + buying: boolean +}>('limit/replaceLimitState') +export const setRecipient = createAction<{ recipient: string | null }>('limit/setRecipient') diff --git a/src/state/limit/hooks.ts b/src/state/limit/hooks.ts new file mode 100644 index 00000000..3f39ad76 --- /dev/null +++ b/src/state/limit/hooks.ts @@ -0,0 +1,245 @@ +import { useContractKit } from '@celo-tools/use-contractkit' +import { parseUnits } from '@ethersproject/units' +import { CELO, cEUR, ChainId as UbeswapChainId, cUSD, Fraction, Token, TokenAmount } from '@ubeswap/sdk' +import { useUbeswapTradeExactIn, useUbeswapTradeExactOut } from 'components/swap/routing/hooks/useTrade' +import { UbeswapTrade } from 'components/swap/routing/trade' +import { useCallback } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { tryParseAmount } from 'state/swap/hooks' +import { useCurrencyBalances } from 'state/wallet/hooks' + +import { ROUTER_ADDRESS } from '../../constants' +import { useCurrency } from '../../hooks/Tokens' +import useENS from '../../hooks/useENS' +import { isAddress } from '../../utils' +import { AppDispatch, AppState } from '../index' +import { + Field, + selectCurrency, + setBuying as setBuyingAction, + setRecipient, + switchCurrencies, + typeInput, +} from './actions' + +const BAD_RECIPIENT_ADDRESSES: string[] = [ + '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f', // v2 factory + '0xf164fC0Ec4E93095b804a4795bBe1e041497b92a', // v2 router 01 + '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', // v2 router 02 + ROUTER_ADDRESS, +] + +export function useLimitOrderState(): AppState['limit'] { + return useSelector((state) => state.limit) +} + +// from the current limit order inputs, compute the trade +export function useDerivedLimitOrderInfo(): { + currencies: { [field in Field]?: Token } + currencyBalances: { [field in Field]?: TokenAmount } + parsedInputTotal: TokenAmount | undefined + parsedOutputTotal: TokenAmount | undefined + v2Trade: UbeswapTrade | undefined + inputError?: string + showRamp: boolean + buying: boolean +} { + const { address: account, network } = useContractKit() + + const { + priceTypedValue, + tokenTypedValue, + [Field.PRICE]: { currencyId: priceCurrencyId }, + [Field.TOKEN]: { currencyId: tokenCurrencyId }, + recipient, + buying, + } = useLimitOrderState() + + const priceCurrency = useCurrency(priceCurrencyId) + const tokenCurrency = useCurrency(tokenCurrencyId) + const recipientLookup = useENS(recipient ?? undefined) + const to: string | null = (recipient === null ? account : recipientLookup.address) ?? null + + const relevantTokenBalances = useCurrencyBalances(account ?? undefined, [ + priceCurrency ?? undefined, + tokenCurrency ?? undefined, + ]) + + // When buying, the buy asset + const parsedTokenAmount = tryParseAmount(tokenTypedValue, tokenCurrency ?? undefined) + const parsedPrice = tryParseAmount(priceTypedValue, priceCurrency ?? undefined) + // When buying, the sell asset + const parsedTokenOutput = parsedPrice + ? tryParseAmount( + parsedTokenAmount?.multiply(parsedPrice.numerator).divide(parsedPrice.denominator).toFixed(10), + priceCurrency ?? undefined + ) + : undefined + + const parsedInputTotal = buying ? parsedTokenOutput : parsedTokenAmount + const parsedOutputTotal = buying ? parsedTokenAmount : parsedTokenOutput + + // Determine price as if we were trading 1 of the asset + const buyTrade = useUbeswapTradeExactOut( + priceCurrency ?? undefined, + parsedTokenAmount + ? new TokenAmount(parsedTokenAmount.currency, parseUnits('1', parsedTokenAmount.currency.decimals).toString()) + : undefined + ) + const sellTrade = useUbeswapTradeExactIn( + parsedTokenAmount + ? new TokenAmount(parsedTokenAmount.currency, parseUnits('1', parsedTokenAmount.currency.decimals).toString()) + : undefined, + priceCurrency ?? undefined + ) + const v2Trade = buying ? buyTrade : sellTrade + + const currencyBalances = { + [Field.PRICE]: relevantTokenBalances[0], + [Field.TOKEN]: relevantTokenBalances[1], + } + + const currencies: { [field in Field]?: Token } = { + [Field.PRICE]: priceCurrency ?? undefined, + [Field.TOKEN]: tokenCurrency ?? undefined, + } + + let inputError: string | undefined + if (!account) { + inputError = 'Connect Wallet' + } + + if (!parsedTokenAmount) { + inputError = inputError ?? 'Enter a token amount' + } else if (!parsedPrice) { + inputError = inputError ?? 'Enter a limit order price' + } + + if (!currencies[Field.TOKEN] || !currencies[Field.PRICE]) { + inputError = inputError ?? 'Select a token' + } + + const formattedTo = isAddress(to) + if (!to || !formattedTo) { + inputError = inputError ?? 'Enter a recipient' + } else { + if (BAD_RECIPIENT_ADDRESSES.indexOf(formattedTo) !== -1) { + inputError = inputError ?? 'Invalid recipient' + } + } + + // compare input balance to max input based on version + const [balanceIn, amountIn] = [ + buying ? currencyBalances[Field.PRICE] : currencyBalances[Field.TOKEN], + parsedInputTotal, + ] + + let showRamp = false + if (balanceIn && amountIn && balanceIn.lessThan(amountIn)) { + if ( + balanceIn.currency.address === cUSD[network.chainId as unknown as UbeswapChainId].address || + balanceIn.currency.address === CELO[network.chainId as unknown as UbeswapChainId].address || + balanceIn.currency.address === cEUR[network.chainId as unknown as UbeswapChainId].address + ) { + showRamp = true + } + inputError = 'Insufficient ' + amountIn.currency.symbol + ' balance' + } + + return { + currencies, + currencyBalances, + parsedInputTotal, + parsedOutputTotal, + v2Trade: v2Trade ?? undefined, + showRamp, + inputError, + buying, + } +} + +export function useMarketPriceDiff(): { + marketPriceDiffIndicator: Fraction | undefined + aboveMarketPrice: boolean | undefined +} { + const { + priceTypedValue, + [Field.PRICE]: { currencyId: priceCurrencyId }, + } = useLimitOrderState() + const { v2Trade: trade, buying } = useDerivedLimitOrderInfo() + + const priceCurrency = useCurrency(priceCurrencyId) + const parsedPrice = tryParseAmount(priceTypedValue, priceCurrency ?? undefined) + const marketDiffFraction = + trade && parsedPrice + ? new Fraction( + parsedPrice?.numerator, + buying ? trade.executionPrice.invert().numerator : trade.executionPrice.invert().denominator + ) + : undefined + const aboveMarketPrice = marketDiffFraction && marketDiffFraction.lessThan('1') + const marketPriceDiffIndicator = marketDiffFraction + ? aboveMarketPrice + ? new Fraction('1', '1').subtract(marketDiffFraction).multiply(new Fraction('1000', '10')) + : marketDiffFraction.subtract(new Fraction('1', '1')).multiply(new Fraction('1000', '10')) + : undefined + + return { + marketPriceDiffIndicator, + aboveMarketPrice, + } +} + +export function useLimitOrderActionHandlers(): { + onCurrencySelection: (field: Field, currency: Token) => void + onSwitchTokens: () => void + onUserInput: (field: Field, typedValue: string) => void + onChangeRecipient: (recipient: string | null) => void + setBuying: (buying: boolean) => void +} { + const dispatch = useDispatch() + const onCurrencySelection = useCallback( + (field: Field, currency: Token) => { + dispatch( + selectCurrency({ + field, + currencyId: currency instanceof Token ? currency.address : '', + }) + ) + }, + [dispatch] + ) + + const onSwitchTokens = useCallback(() => { + dispatch(switchCurrencies()) + }, [dispatch]) + + const onUserInput = useCallback( + (field: Field, typedValue: string) => { + dispatch(typeInput({ field, typedValue })) + }, + [dispatch] + ) + + const onChangeRecipient = useCallback( + (recipient: string | null) => { + dispatch(setRecipient({ recipient })) + }, + [dispatch] + ) + + const setBuying = useCallback( + (buying: boolean) => { + dispatch(setBuyingAction({ buying })) + }, + [dispatch] + ) + + return { + onSwitchTokens, + onCurrencySelection, + onUserInput, + onChangeRecipient, + setBuying, + } +} diff --git a/src/state/limit/reducer.ts b/src/state/limit/reducer.ts new file mode 100644 index 00000000..b46c60fa --- /dev/null +++ b/src/state/limit/reducer.ts @@ -0,0 +1,107 @@ +import { createReducer } from '@reduxjs/toolkit' + +import { + Field, + replaceLimitState, + selectCurrency, + setBuying, + setRecipient, + switchCurrencies, + typeInput, +} from './actions' + +export interface LimitState { + readonly priceTypedValue: string + readonly tokenTypedValue: string + readonly [Field.TOKEN]: { + readonly currencyId: string | undefined + } + readonly [Field.PRICE]: { + readonly currencyId: string | undefined + } + // the typed recipient address or ENS name, or null if Limit should go to sender + readonly recipient: string | null + readonly buying: boolean +} + +const initialState: LimitState = { + priceTypedValue: '', + tokenTypedValue: '', + [Field.TOKEN]: { + currencyId: '', + }, + [Field.PRICE]: { + currencyId: '', + }, + recipient: null, + buying: true, +} + +export default createReducer(initialState, (builder) => + builder + .addCase( + replaceLimitState, + ( + state, + { payload: { tokenTypedValue, priceTypedValue, recipient, field, tokenCurrencyId, priceCurrencyId, buying } } + ) => { + return { + [Field.TOKEN]: { + currencyId: tokenCurrencyId, + }, + [Field.PRICE]: { + currencyId: priceCurrencyId, + }, + independentField: field, + tokenTypedValue, + priceTypedValue, + recipient, + buying, + } + } + ) + .addCase(selectCurrency, (state, { payload: { currencyId, field } }) => { + const otherField = field === Field.TOKEN ? Field.PRICE : Field.TOKEN + if (currencyId === state[otherField].currencyId) { + // the case where we have to Limit the order + return { + ...state, + [field]: { currencyId: currencyId }, + [otherField]: { currencyId: state[field].currencyId }, + } + } else { + // the normal case + return { + ...state, + [field]: { currencyId: currencyId }, + } + } + }) + .addCase(switchCurrencies, (state) => { + return { + ...state, + [Field.TOKEN]: { currencyId: state[Field.PRICE].currencyId }, + [Field.PRICE]: { currencyId: state[Field.TOKEN].currencyId }, + } + }) + .addCase(typeInput, (state, { payload: { field, typedValue } }) => { + if (field === Field.PRICE) { + return { + ...state, + priceTypedValue: typedValue, + } + } else if (field === Field.TOKEN) { + return { + ...state, + tokenTypedValue: typedValue, + } + } + return state + }) + .addCase(setRecipient, (state, { payload: { recipient } }) => { + state.recipient = recipient + }) + .addCase(setBuying, (state, { payload: { buying } }) => { + state.buying = buying + }) +) diff --git a/src/state/stake/farms.ts b/src/state/stake/farms.ts deleted file mode 100644 index 89c38f48..00000000 --- a/src/state/stake/farms.ts +++ /dev/null @@ -1,295 +0,0 @@ -export type MultiRewardPool = { - address: string - underlyingPool: string - basePool: string - numRewards: number - active: boolean -} - -export const multiRewardPools: MultiRewardPool[] = [ - // ** Friends ** // - // CELO-MOBI - { - address: '0xb450940c5297e9b5e7167FAC5903fD1e90b439b8', - underlyingPool: '0xd930501A0848DC0AA3E301c7B9b8AFE8134D7f5F', - basePool: '0x19F1A692C77B481C23e9916E3E83Af919eD49765', - numRewards: 3, - active: true, - }, - // mCUSD-mcEUR - { - address: '0x2Ca16986bEA18D562D26354b4Ff4C504F14fB01c', - underlyingPool: '0xF554690b1A996893c4DEBadc57b759350dc10b29', - basePool: '0xaAA7bf214367572cadbF17f17d8E035742b55ab9', - numRewards: 3, - active: true, - }, - // MOO-mCELO - { - address: '0xE76525610652fFC3aF751Ab0dcC3448B345051F6', - underlyingPool: '0xBA7dCc70c68e11633d7DACBAFa493Af61D0c5B1d', - basePool: '0x54097E406DFC00B9179167F9E20B26406Ad42f0F', - numRewards: 3, - active: true, - }, - // POOF-UBE - { - address: '0x4274AA72B12221D32ca77cB37057A9692E0b59Eb', - underlyingPool: '0xC88B8d622c0322fb59ae4473D7A1798DE60785dD', - basePool: '0xC88B8d622c0322fb59ae4473D7A1798DE60785dD', - numRewards: 2, - active: true, - }, - // pCELO-POOF - { - address: '0x999DF57c86EcC98E0e55f5f1dEdD102A89F25e8e', - underlyingPool: '0x66B6e41AC12d51918775410D3331B21F7851a8Bc', - basePool: '0x66B6e41AC12d51918775410D3331B21F7851a8Bc', - numRewards: 2, - active: true, - }, - // pCELO-POOF - { - address: '0x7B7F08164036abEbafD1bf75c1464c6F0d01653C', - underlyingPool: '0xd60E0034D4B27DE226EFf13f68249F69d4D6Cb38', - basePool: '0xd60E0034D4B27DE226EFf13f68249F69d4D6Cb38', - numRewards: 2, - active: false, - }, - // KNX-CELO - { - address: '0x1f1678Cc7358F4ed808B53733Bc49c4CFFe8A075', - underlyingPool: '0x7313fDf9D8Cab87E54efc8905B9D7d4BA3Fe7c8D', - basePool: '0x7313fDf9D8Cab87E54efc8905B9D7d4BA3Fe7c8D', - numRewards: 2, - active: true, - }, - // TFBX-UBE - { - address: '0x501ba7c59BA8afC1427F75D310A862BA0D2adcD2', - underlyingPool: '0x3DAc201Ec1b3a037bC9124906A2ae0A6a09ACC1d', - basePool: '0x3DAc201Ec1b3a037bC9124906A2ae0A6a09ACC1d', - numRewards: 2, - active: true, - }, - // SOURCE-mcUSD - { - address: '0xF4662e4E254006939c2198cb6F61635b03fd14Eb', - underlyingPool: '0x5F5c3eEa2b9e65f667E34C70Db68f62bbbFC9188', - basePool: '0x9cAF0Cd20C8eF7622EEb8dB50e5bB4d407e38AE2', - numRewards: 3, - active: true, - }, - // ARI-CELO - { - address: '0xEfe2f9d62E45815837b4f20c1F44F0A83605B540', - underlyingPool: '0xFEB0df4542E5394aAc89383c135E2Fc829812C6c', - basePool: '0xFEB0df4542E5394aAc89383c135E2Fc829812C6c', - numRewards: 2, - active: true, - }, - // PACT-CELO - { - address: '0x6F11B6eA70DEe4f167b1A4ED1F01C903f6781960', - underlyingPool: '0xFc26229c90E6236fC85c492b738c6E496c177cd0', - basePool: '0x833Febc01260D8f3DCc98393c216A025E90b405d', - numRewards: 3, - active: true, - }, - // ABR-mcUSD - { - address: '0x3904056570Ca95Cc3371E349A7733b5BfaeD64e5', - underlyingPool: '0xD94E14358f66A3C0D13ae76ec45Fe1c92Dd7Fb23', - basePool: '0xD94E14358f66A3C0D13ae76ec45Fe1c92Dd7Fb23', - numRewards: 2, - active: true, - }, - - // ** D4P ** // - // UBE-CELO - { - address: '0x9D87c01672A7D02b2Dc0D0eB7A145C7e13793c3B', - underlyingPool: '0x295D6f96081fEB1569d9Ce005F7f2710042ec6a1', - basePool: '0x295D6f96081fEB1569d9Ce005F7f2710042ec6a1', - numRewards: 2, - active: true, - }, - // rCELO-CELO - { - address: '0x194478Aa91e4D7762c3E51EeE57376ea9ac72761', - underlyingPool: '0xD7D6b5213b9B9DFffbb7ef008b3cF3c677eb2468', - basePool: '0xD7D6b5213b9B9DFffbb7ef008b3cF3c677eb2468', - numRewards: 2, - active: true, - }, - // CELO-mcUSD - { - address: '0x161c77b4919271B7ED59AdB2151FdaDe3F907a1F', - underlyingPool: '0xcca933D2ffEDCa69495435049a878C4DC34B079d', - basePool: '0xcca933D2ffEDCa69495435049a878C4DC34B079d', - numRewards: 2, - active: true, - }, - // CELO-mcEUR - { - address: '0x728C650D1Fb4da2D18ccF4DF45Af70c5AEb09f81', - underlyingPool: '0x32779E096bF913093933Ea94d31956AF8a763CE9', - basePool: '0x32779E096bF913093933Ea94d31956AF8a763CE9', - numRewards: 2, - active: true, - }, - // WBTC-mcUSD - { - address: '0xf3D9E027B131Af5162451601038EddBF456d824B', - underlyingPool: '0x0079418D54F887e7859c7A3Ecc16cE96A416527b', - basePool: '0x0079418D54F887e7859c7A3Ecc16cE96A416527b', - numRewards: 2, - active: false, - }, - // WETH-mcUSD - { - address: '0xD6E28720Fcd1C1aB6da2d1043a6763FDBb67b3aA', - underlyingPool: '0x666C59E75271f1fF5a52b58D4563afdc76a53b4e', - basePool: '0x666C59E75271f1fF5a52b58D4563afdc76a53b4e', - numRewards: 2, - active: false, - }, - // WETH-mcUSD - { - address: '0x81DDaFE15c01aDfda3dd8Fe9Bb984E64Cba606eB', - underlyingPool: '0x1e41a9fd5a94def942ed46aa8bdb4a7f248efad3', - basePool: '0x1e41a9fd5a94def942ed46aa8bdb4a7f248efad3', - numRewards: 2, - active: true, - }, - // WBTC-mcUSD - { - address: '0xE6AD921bDa9F4971aBc8FA78cBD07AeB5c1A61ea', - underlyingPool: '0xc6910dB4156B535966E4a7e8CcA7D39579b99A81', - basePool: '0xc6910dB4156B535966E4a7e8CcA7D39579b99A81', - numRewards: 2, - active: true, - }, - // SUSHI-mcUSD - { - address: '0x0E83662A17B8A3a0585DcA34E5BE81ea6bd59556', - underlyingPool: '0xA2674f69B2BEf4ca3E75589aD4f4d36F061048a9', - basePool: '0xA2674f69B2BEf4ca3E75589aD4f4d36F061048a9', - numRewards: 2, - active: true, - }, - // CRV-mcUSD - { - address: '0x85B21208C0058019bc8004D85eFEa881E7598D17', - underlyingPool: '0xA92Bb4D6399Be5403d6c8DF3cce4dd991ca8EaFc', - basePool: '0xA92Bb4D6399Be5403d6c8DF3cce4dd991ca8EaFc', - numRewards: 2, - active: true, - }, - // AAVE-mcUSD - { - address: '0x09c1cF8669f9A026c59EDd4792944a9aCd2d2a2E', - underlyingPool: '0xF20448aaF8CC60432FC2E774F9ED965D4bf77cDc', - basePool: '0xF20448aaF8CC60432FC2E774F9ED965D4bf77cDc', - numRewards: 2, - active: true, - }, - // FTM-mcUSD - { - address: '0x3C29593674c5c760172d354acE88Da4D9d3EB64f', - underlyingPool: '0x5704F21cF5C7e6556cBD1ceEbbD23752B68e4845', - basePool: '0x5704F21cF5C7e6556cBD1ceEbbD23752B68e4845', - numRewards: 2, - active: true, - }, - // AVAX-mcUSD - { - address: '0x750bB68Fa18F06d9696af85Ecc312f178E75fCfD', - underlyingPool: '0x9584870281DD0d764748a2a234e2218AE544C614', - basePool: '0x9584870281DD0d764748a2a234e2218AE544C614', - numRewards: 2, - active: true, - }, - // BNB-mcUSD - { - address: '0xCD2d4024A42109593301fF11967c16eA180DD381', - underlyingPool: '0x522be12487d0640337abCfC7201066eC8F787AC5', - basePool: '0x522be12487d0640337abCfC7201066eC8F787AC5', - numRewards: 2, - active: true, - }, - // WMATIC-mcUSD - { - address: '0x00C4aCee9eB84B1a6Cdc741AeEd19BF84CbE7bF5', - underlyingPool: '0x80ED8Da2d3cd269B0ccbc6ddF8DA2807BF583307', - basePool: '0x80ED8Da2d3cd269B0ccbc6ddF8DA2807BF583307', - numRewards: 2, - active: true, - }, - // SOL-CELO - { - address: '0x83470506ba97dB33Df0EBe01E876C6718C762Df6', - underlyingPool: '0x33cD870547DD6F30db86e7EE7707DC78e7825289', - basePool: '0x33cD870547DD6F30db86e7EE7707DC78e7825289', - numRewards: 2, - active: true, - }, - - // ** Inactive ** // - // CELO-MOBI - { - address: '0xd930501A0848DC0AA3E301c7B9b8AFE8134D7f5F', - underlyingPool: '0x19F1A692C77B481C23e9916E3E83Af919eD49765', - basePool: '0x19F1A692C77B481C23e9916E3E83Af919eD49765', - numRewards: 2, - active: false, - }, - // CELO-mcUSDxOLD - { - address: '0xbbC8C824c638fd238178a71F5b1E5Ce7e4Ce586B', - underlyingPool: '0x66bD2eF224318cA5e3A93E165e77fAb6DD986E89', - basePool: '0x66bD2eF224318cA5e3A93E165e77fAb6DD986E89', - numRewards: 2, - active: false, - }, - // CELO-mcEURxOLD - { - address: '0x0F3d01aea89dA0b6AD81712Edb96FA7AF1c17E9B', - underlyingPool: '0x08252f2E68826950d31D268DfAE5E691EE8a2426', - basePool: '0x08252f2E68826950d31D268DfAE5E691EE8a2426', - numRewards: 2, - active: false, - }, - // mCUSDxOLD-mcEURxOLD - { - address: '0x2f0ddEAa9DD2A0FB78d41e58AD35373d6A81EbB0', - underlyingPool: '0xaf13437122cd537C5D8942f17787cbDBd787fE94', - basePool: '0xaf13437122cd537C5D8942f17787cbDBd787fE94', - numRewards: 2, - active: false, - }, - // MOO-mCELOxOLD - { - address: '0x84Bb1795b699Bf7a798C0d63e9Aad4c96B0830f4', - underlyingPool: '0xC087aEcAC0a4991f9b0e931Ce2aC77a826DDdaf3', - basePool: '0xC087aEcAC0a4991f9b0e931Ce2aC77a826DDdaf3', - numRewards: 2, - active: false, - }, - // mCUSDxOLD-mcEURxOLD - { - address: '0x3d823f7979bB3af846D8F1a7d98922514eA203fC', - underlyingPool: '0xb030882bfc44e223fd5e20d8645c961be9b30bb3', - basePool: '0xaf13437122cd537C5D8942f17787cbDBd787fE94', - numRewards: 3, - active: false, - }, - // MOO-mCELOxOLD - { - address: '0x3c7beeA32A49D96d72ce45C7DeFb5b287479C2ba', - underlyingPool: '0x8f309df7527f16dff49065d3338ea3f3c12b5d09', - basePool: '0xC087aEcAC0a4991f9b0e931Ce2aC77a826DDdaf3', - numRewards: 3, - active: false, - }, -] diff --git a/src/state/stake/hooks.ts b/src/state/stake/hooks.ts index c96af048..d3222ce5 100644 --- a/src/state/stake/hooks.ts +++ b/src/state/stake/hooks.ts @@ -1,20 +1,21 @@ -import { ChainId, useContractKit } from '@celo-tools/use-contractkit' +import { ChainId, useContractKit, useProvider } from '@celo-tools/use-contractkit' import { BigNumber } from '@ethersproject/bignumber' import { ChainId as UbeswapChainId, JSBI, Pair, Token, TokenAmount } from '@ubeswap/sdk' import { POOL_MANAGER } from 'constants/poolManager' import { UBE } from 'constants/tokens' -import { PoolManager } from 'generated/' +import { MoolaStakingRewards__factory, PoolManager } from 'generated/' import { useAllTokens } from 'hooks/Tokens' import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp' import zip from 'lodash/zip' // Hooks -import { useMemo } from 'react' +import React, { useEffect, useMemo } from 'react' import ERC_20_INTERFACE from '../../constants/abis/erc20' import { STAKING_REWARDS_INTERFACE } from '../../constants/abis/staking-rewards' // Interfaces import { UNISWAP_V2_PAIR_INTERFACE } from '../../constants/abis/uniswap-v2-pair' import { usePoolManagerContract, useTokenContract } from '../../hooks/useContract' +import { useFarmRegistry } from '../../pages/Earn/useFarmRegistry' import { NEVER_RELOAD, useMultipleContractSingleData, @@ -22,11 +23,12 @@ import { useSingleContractMultipleData, } from '../multicall/hooks' import { tryParseAmount } from '../swap/hooks' -import { multiRewardPools } from './farms' import { useMultiStakeRewards } from './useDualStakeRewards' import useStakingInfo from './useStakingInfo' export const STAKING_GENESIS = 1619100000 +const ACTIVE_CONTRACT_UPDATED_THRESHOLD = 5259492 +const UNPREDICTABLE_GAS_LIMIT_ERROR_CODE = 'UNPREDICTABLE_GAS_LIMIT' export interface StakingInfo { // the address of the reward contract @@ -61,13 +63,95 @@ export interface StakingInfo { readonly rewardTokens: Token[] } +type MultiRewardPool = { + address: string + underlyingPool: string + basePool: string + numRewards: number + active: boolean +} + +export const useMultiRewardPools = (): MultiRewardPool[] => { + const library = useProvider() + const farmSummaries = useFarmRegistry() + + const [multiRewardPools, setMultiRewardPools] = React.useState([]) + + const call = React.useCallback(async () => { + const multiRwdPools: MultiRewardPool[] = [] + + await Promise.all( + farmSummaries.map(async (fs) => { + let poolContract = MoolaStakingRewards__factory.connect(fs.stakingAddress, library) + const rewardsTokens = [] + const externalStakingRwdAddresses = [] + + // the first reward token at the top level + rewardsTokens.push(await poolContract.rewardsToken()) + + // last time the contract was updated - set isActive to false if it has been longer than 2 months + let periodFinish = await poolContract.periodFinish() + let isActive = Math.floor(Date.now() / 1000) - periodFinish.toNumber() < ACTIVE_CONTRACT_UPDATED_THRESHOLD + + let baseContractFound = false + // recursivley find underlying and base pool contracts + while (!baseContractFound) { + try { + // find the underlying contract if one exists + const externalStakingRewardAddr = await poolContract.externalStakingRewards() + externalStakingRwdAddresses.push(externalStakingRewardAddr) + + // capture the contract's reward token + poolContract = MoolaStakingRewards__factory.connect(externalStakingRewardAddr, library) + rewardsTokens.push(await poolContract.rewardsToken()) + + // determine if the underlying contract is active or not + periodFinish = await poolContract.periodFinish() + isActive = + Math.floor(Date.now() / 1000) - periodFinish.toNumber() < ACTIVE_CONTRACT_UPDATED_THRESHOLD || isActive + } catch (e: any) { + //if the error is not what is expected - log it + if (e.code !== UNPREDICTABLE_GAS_LIMIT_ERROR_CODE) { + console.log(e) + } + + //set true when externalStakingRewards() throws an error + baseContractFound = true + } + } + + if (externalStakingRwdAddresses.length) { + multiRwdPools.push({ + address: fs.stakingAddress, + underlyingPool: externalStakingRwdAddresses[0], + basePool: externalStakingRwdAddresses[externalStakingRwdAddresses.length - 1], + numRewards: rewardsTokens.length, + active: isActive, + }) + } + }) + ) + setMultiRewardPools(multiRwdPools) + }, [farmSummaries, library]) + + useEffect(() => { + call() + }, [call]) + + return multiRewardPools +} + export const usePairMultiStakingInfo = ( stakingInfo: StakingInfo | undefined, stakingAddress: string ): StakingInfo | null => { - const multiRewardPool = multiRewardPools - .filter((x) => x.address.toLowerCase() === stakingAddress.toLowerCase()) - .find((x) => x.basePool.toLowerCase() === stakingInfo?.poolInfo.poolAddress.toLowerCase()) + const multiRewardPools = useMultiRewardPools() + + const multiRewardPool = useMemo(() => { + return multiRewardPools + .filter((x) => x.address.toLowerCase() === stakingAddress.toLowerCase()) + .find((x) => x.basePool.toLowerCase() === stakingInfo?.poolInfo.poolAddress.toLowerCase()) + }, [multiRewardPools, stakingAddress, stakingInfo?.poolInfo.poolAddress]) const isTriple = multiRewardPool?.numRewards === 3 diff --git a/src/utils/limitOrder.ts b/src/utils/limitOrder.ts new file mode 100644 index 00000000..d8d96772 --- /dev/null +++ b/src/utils/limitOrder.ts @@ -0,0 +1,29 @@ +const Order = [ + { name: 'salt', type: 'uint256' }, + { name: 'makerAsset', type: 'address' }, + { name: 'takerAsset', type: 'address' }, + { name: 'maker', type: 'address' }, + { name: 'receiver', type: 'address' }, + { name: 'allowedSender', type: 'address' }, + { name: 'makingAmount', type: 'uint256' }, + { name: 'takingAmount', type: 'uint256' }, + { name: 'makerAssetData', type: 'bytes' }, + { name: 'takerAssetData', type: 'bytes' }, + { name: 'getMakerAmount', type: 'bytes' }, + { name: 'getTakerAmount', type: 'bytes' }, + { name: 'predicate', type: 'bytes' }, + { name: 'permit', type: 'bytes' }, + { name: 'interaction', type: 'bytes' }, +] + +const name = 'Limit Order Protocol' +const version = '2' + +export function buildOrderData(chainId: string, verifyingContract: string, order: any) { + return { + primaryType: 'Order', + types: { Order }, + domain: { name, version, chainId, verifyingContract }, + message: order, + } +} diff --git a/src/utils/multicall.ts b/src/utils/multicall.ts new file mode 100644 index 00000000..f11b80e0 --- /dev/null +++ b/src/utils/multicall.ts @@ -0,0 +1,18 @@ +import { BytesLike } from 'ethers' +import { Multicall } from 'generated' + +const BUCKET_SIZE = 500 + +export const multicallBatch = async ( + multicall: Multicall, + calls: { target: string; callData: BytesLike }[], + bucketSize = BUCKET_SIZE +): Promise => { + const results = [] + let i = 0 + while (i < calls.length) { + results.push(...(await multicall.callStatic.aggregate(calls.slice(i, i + bucketSize)).then((r) => r.returnData))) + i += bucketSize + } + return results +} diff --git a/yarn.lock b/yarn.lock index 0158970e..cf230c5a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3968,23 +3968,15 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^4.28.0", "@typescript-eslint/parser@^4.5.0": - version "4.32.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.32.0.tgz#751ecca0e2fecd3d44484a9b3049ffc1871616e5" - integrity sha512-lhtYqQ2iEPV5JqV7K+uOVlPePjClj4dOw7K4/Z1F2yvjIUvyr13yJnDzkK6uon4BjHYuHy3EG0c2Z9jEhFk56w== - dependencies: - "@typescript-eslint/scope-manager" "4.32.0" - "@typescript-eslint/types" "4.32.0" - "@typescript-eslint/typescript-estree" "4.32.0" - debug "^4.3.1" - -"@typescript-eslint/scope-manager@4.32.0": - version "4.32.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.32.0.tgz#e03c8668f8b954072b3f944d5b799c0c9225a7d5" - integrity sha512-DK+fMSHdM216C0OM/KR1lHXjP1CNtVIhJ54kQxfOE6x8UGFAjha8cXgDMBEIYS2XCYjjCtvTkjQYwL3uvGOo0w== +"@typescript-eslint/parser@^4.33.0", "@typescript-eslint/parser@^4.5.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.33.0.tgz#dfe797570d9694e560528d18eecad86c8c744899" + integrity sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA== dependencies: - "@typescript-eslint/types" "4.32.0" - "@typescript-eslint/visitor-keys" "4.32.0" + "@typescript-eslint/scope-manager" "4.33.0" + "@typescript-eslint/types" "4.33.0" + "@typescript-eslint/typescript-estree" "4.33.0" + debug "^4.3.1" "@typescript-eslint/scope-manager@4.33.0": version "4.33.0" @@ -3999,11 +3991,6 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727" integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ== -"@typescript-eslint/types@4.32.0": - version "4.32.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.32.0.tgz#52c633c18da47aee09449144bf59565ab36df00d" - integrity sha512-LE7Z7BAv0E2UvqzogssGf1x7GPpUalgG07nGCBYb1oK4mFsOiFC/VrSMKbZQzFJdN2JL5XYmsx7C7FX9p9ns0w== - "@typescript-eslint/types@4.33.0": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" @@ -4023,19 +4010,6 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@4.32.0": - version "4.32.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.32.0.tgz#db00ccc41ccedc8d7367ea3f50c6994b8efa9f3b" - integrity sha512-tRYCgJ3g1UjMw1cGG8Yn1KzOzNlQ6u1h9AmEtPhb5V5a1TmiHWcRyF/Ic+91M4f43QeChyYlVTcf3DvDTZR9vw== - dependencies: - "@typescript-eslint/types" "4.32.0" - "@typescript-eslint/visitor-keys" "4.32.0" - debug "^4.3.1" - globby "^11.0.3" - is-glob "^4.0.1" - semver "^7.3.5" - tsutils "^3.21.0" - "@typescript-eslint/typescript-estree@4.33.0": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" @@ -4056,14 +4030,6 @@ dependencies: eslint-visitor-keys "^1.1.0" -"@typescript-eslint/visitor-keys@4.32.0": - version "4.32.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.32.0.tgz#455ba8b51242f2722a497ffae29313f33b14cb7f" - integrity sha512-e7NE0qz8W+atzv3Cy9qaQ7BTLwWsm084Z0c4nIO2l3Bp6u9WIgdqCgyPyV5oSPDMIW3b20H59OOCmVk3jw3Ptw== - dependencies: - "@typescript-eslint/types" "4.32.0" - eslint-visitor-keys "^2.0.0" - "@typescript-eslint/visitor-keys@4.33.0": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd" @@ -4098,10 +4064,10 @@ dependencies: openzeppelin-solidity "^4.0.0" -"@ubeswap/sdk@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@ubeswap/sdk/-/sdk-2.1.1.tgz#b1622f66087678a17bdc184256618418dc508732" - integrity sha512-UBcqcUv2q1IA/5RqYSRHGQiHkm55nN+dZ+b98Il6wsi+FcUS2qyZFPgoNtGD8jpGfauERFJs4OIaRJWJRF1NYg== +"@ubeswap/sdk@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@ubeswap/sdk/-/sdk-2.1.2.tgz#e0dd4e44c1b9071668f493e8b35486aba694b198" + integrity sha512-3K98FW33lGH1K2qAy4KAze7rt5aJ1p+kRkIPNCXsnuRGjRlM38igFcDRggk1pBu3QJel42qyydTQrSXf/gkkIQ== dependencies: "@uniswap/v2-core" "^1.0.0" big.js "^6.0.3"