Skip to content

Commit

Permalink
[SWA-94] Disable limit order and handle common errors
Browse files Browse the repository at this point in the history
  • Loading branch information
Wixzi committed Jul 30, 2023
1 parent 0345d92 commit 090ded6
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 32 deletions.
12 changes: 12 additions & 0 deletions src/pages/Swap/LimitOrder/Components/MaxAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import styled from 'styled-components'

export const MaxAlert = styled.div`
background-color: ${({ theme }) => theme.orange1};
display: flex;
font-size: 13px;
padding: 10px;
margin: 10px 0px;
color: ${({ theme }) => theme.purpleBase};
border-radius: 5px;
justify-content: center;
`
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ interface OrderLimitPriceFieldProps {
setBuyAmount(t: TokenAmount): void
setKind(t: Kind): void
setLoading(t: boolean): void
handleGetMarketPrice(): Promise<string>
}

export function OrderLimitPriceField({
Expand All @@ -55,6 +56,7 @@ export function OrderLimitPriceField({
setBuyAmount,
setKind,
setLoading,
handleGetMarketPrice,
}: OrderLimitPriceFieldProps) {
const { t } = useTranslation('swap')

Expand All @@ -76,7 +78,6 @@ export function OrderLimitPriceField({
setLoading(true)
try {
if (!protocol.userUpdatedLimitPrice) {
console.log('Hello')
await protocol.getQuote()
}
} finally {
Expand Down Expand Up @@ -181,36 +182,37 @@ export function OrderLimitPriceField({
}

const onClickGetMarketPrice = async () => {
protocol.loading = true
setLoading(true)
// protocol.loading = true
// setLoading(true)

protocol.onUserUpadtedLimitPrice(false)
await protocol.getQuote()
protocol.loading = false
setLoading(false)
if (kind === Kind.Sell) {
setBuyAmount(protocol.buyAmount)
} else {
setSellAmount(protocol.sellAmount)
}
const limitPrice = protocol.getLimitPrice()
protocol.onLimitPriceChange(limitPrice)
// protocol.onUserUpadtedLimitPrice(false)
// await protocol.getQuote()
// protocol.loading = false
// setLoading(false)
// if (kind === Kind.Sell) {
// setBuyAmount(protocol.buyAmount)
// } else {
// setSellAmount(protocol.sellAmount)
// }
// const limitPrice = protocol.getLimitPrice()
// protocol.onLimitPriceChange(limitPrice)
const limitPrice = await handleGetMarketPrice()
setInputLimitPrice(limitPrice)
}

return (
<InputGroup>
<LimitLabel htmlFor="limitPrice">
<LimitLabelGroup>
<Flex flex={60}>
<Flex flex={58}>
<p>
{inputGroupLabel}
{showPercentage && (
<MarketPriceDiff isPositive={isDiffPositive}>({marketPriceDiffPercentage.toFixed(2)}%)</MarketPriceDiff>
)}
</p>
</Flex>
<Flex flex={40} flexDirection="row-reverse">
<Flex flex={42} flexDirection="row-reverse">
{!protocol.userUpdatedLimitPrice && buyAmount?.currency && sellAmount?.currency ? (
<MarketPriceButton key={`${buyToken.symbol}-${sellToken.symbol}-${protocol.limitPrice}`} />
) : (
Expand Down
6 changes: 5 additions & 1 deletion src/pages/Swap/LimitOrder/LimitOrder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@ import AppBody from '../../AppBody'
import LimitOrderFallback from './Components/LimitFallback'
import LimitOrderForm from './LimitOrderForm'

const limitSdk = new LimitOrder()
let limitSdk: LimitOrder = new LimitOrder()

export default function LimitOrderUI() {
const { chainId, account, library: provider } = useActiveWeb3React()

const [protocol, setProtocol] = useState(limitSdk.getActiveProtocol())

useEffect(() => {
limitSdk = new LimitOrder()
}, [])

useEffect(() => {
async function updateSigner(signerData: WalletData) {
await limitSdk.updateSigner(signerData)
Expand Down
97 changes: 89 additions & 8 deletions src/pages/Swap/LimitOrder/LimitOrderForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import { maxAmountSpend } from '../../../utils/maxAmountSpend'
import { ApprovalFlow } from './Components/ApprovalFlow'
import { AutoRow } from './Components/AutoRow'
import ConfirmLimitOrderModal from './Components/ConfirmLimitOrderModal'
import { MaxAlert } from './Components/MaxAlert'
import { OrderExpiryField } from './Components/OrderExpiryField'
import { OrderLimitPriceField } from './Components/OrderLimitPriceField'
import { SetToMarket } from './Components/OrderLimitPriceField/styles'
import SwapTokens from './Components/SwapTokens'
import { formatMarketPrice } from './Components/utils'
import { formatMarketPrice, formatMaxValue } from './Components/utils'

export default function LimitOrderForm() {
const protocol = useContext(LimitOrderContext)
Expand All @@ -37,9 +39,10 @@ export default function LimitOrderForm() {
const [buyToken, setBuyToken] = useState<Token>(protocol.buyToken)
const [loading, setLoading] = useState<boolean>(protocol.loading)

// TODO: Error Message handling
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [errorMessage, setErrorMessage] = useState('')
const [isPossibleToOrder, setIsPossibleToOrder] = useState({
status: false,
value: 0,
})

const [sellCurrencyBalance, buyCurrencyBalance] = useCurrencyBalances(protocol.userAddress, [
sellAmount.currency,
Expand All @@ -59,12 +62,42 @@ export default function LimitOrderForm() {
getVaultRelayerAddress(protocol.activeChainId!)
)

useEffect(() => {
let totalSellAmount = Number(sellAmount.toExact() ?? 0)
const maxAmountAvailable = Number(sellCurrencyMaxAmount?.toExact() ?? 0)

if (totalSellAmount > 0 && maxAmountAvailable >= 0) {
if (protocol.quoteSellAmount && sellAmount.token.address === protocol.quoteSellAmount.token.address) {
const quoteAmount = Number(protocol.quoteSellAmount.toExact() ?? 0)
if (quoteAmount < totalSellAmount) {
totalSellAmount = quoteAmount
}
}

if (totalSellAmount > maxAmountAvailable) {
const maxSellAmountPossible = maxAmountAvailable < 0 ? 0 : maxAmountAvailable
if (isPossibleToOrder.value !== maxSellAmountPossible || !isPossibleToOrder.status) {
setIsPossibleToOrder({
status: true,
value: maxSellAmountPossible,
})
}
} else {
if (isPossibleToOrder.value !== 0 || isPossibleToOrder.status) {
setIsPossibleToOrder({
status: false,
value: 0,
})
}
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sellAmount, sellCurrencyMaxAmount, sellToken])

// Determine if the token has to be approved first
const showApproveFlow = tokenInApproval === ApprovalState.NOT_APPROVED || tokenInApproval === ApprovalState.PENDING

const [kind, setKind] = useState<Kind>(protocol?.kind || Kind.Sell)
// TODO: Check the usage of marketPrices
// const [marketPrices] = useState<MarketPrices>({ buy: 0, sell: 0 })

const [isModalOpen, setIsModalOpen] = useState(false)

Expand Down Expand Up @@ -114,6 +147,10 @@ export default function LimitOrderForm() {

setSellAmount(protocol.sellAmount)
setBuyAmount(protocol.buyAmount)
setIsPossibleToOrder({
status: false,
value: 0,
})

setLoading(false)

Expand All @@ -137,6 +174,10 @@ export default function LimitOrderForm() {
setSellAmount(protocol.sellAmount)
setBuyAmount(protocol.buyAmount)
setLoading(false)
setIsPossibleToOrder({
status: false,
value: 0,
})

// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
Expand Down Expand Up @@ -247,7 +288,6 @@ export default function LimitOrderForm() {

const errorCallback = (error: Error) => {
console.error(error)
setErrorMessage('Failed to place limit order. Try again.')
notify('Failed to place limit order. Try again.', false)
}

Expand All @@ -257,6 +297,24 @@ export default function LimitOrderForm() {
console.dir(response)
}

const handleGetMarketPrice = async () => {
protocol.loading = true
setLoading(true)

protocol.onUserUpadtedLimitPrice(false)
await protocol.getQuote()
protocol.loading = false
setLoading(false)
if (kind === Kind.Sell) {
setBuyAmount(protocol.buyAmount)
} else {
setSellAmount(protocol.sellAmount)
}
const limitPrice = protocol.getLimitPrice()
protocol.onLimitPriceChange(limitPrice)
return limitPrice
}

return (
<>
<ConfirmLimitOrderModal
Expand Down Expand Up @@ -321,6 +379,7 @@ export default function LimitOrderForm() {
setBuyAmount={setBuyAmount}
setKind={setKind}
setLoading={setLoading}
handleGetMarketPrice={handleGetMarketPrice}
/>
</Flex>
<Flex flex={35}>
Expand All @@ -334,7 +393,29 @@ export default function LimitOrderForm() {
approveCallback={tokenInApprovalCallback}
/>
) : (
<ButtonPrimary onClick={() => setIsModalOpen(true)}>Place Limit Order</ButtonPrimary>
<>
{protocol.quoteErrorMessage && (
<MaxAlert>{`${protocol.quoteErrorMessage}. Please try again. ${(
<SetToMarket onClick={handleGetMarketPrice}></SetToMarket>
)}`}</MaxAlert>
)}
{isPossibleToOrder.status && (
<MaxAlert>
{isPossibleToOrder.value > 0
? `Max possible amount with fees for ${sellToken.symbol} is ${formatMaxValue(
isPossibleToOrder.value
)}.`
: isPossibleToOrder.value === 0
? `You dont have a positive balance for ${sellToken.symbol}.`
: `Some error occurred please try again. ${(
<SetToMarket onClick={handleGetMarketPrice}></SetToMarket>
)}`}
</MaxAlert>
)}
<ButtonPrimary onClick={() => setIsModalOpen(true)} disabled={isPossibleToOrder.status}>
Place Limit Order
</ButtonPrimary>
</>
)}
</AutoColumn>
</>
Expand Down
7 changes: 7 additions & 0 deletions src/services/LimitOrders/CoW/CoW.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@ export const DefaultTokens = {
buy: USDT[ChainId.GNOSIS],
},
}

export const ErrorCodes = [
'InsufficientLiquidity',
'UnsupportedToken',
'NoLiquidity',
'SellAmountDoesNotCoverFee',
] as const
21 changes: 19 additions & 2 deletions src/services/LimitOrders/CoW/CoW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import { Kind, WalletData, OrderExpiresInUnit, ProtocolContructor, LimitOrder, C
import { LimitOrderBase } from '../LimitOrder.utils'

import { createCoWLimitOrder, getQuote } from './api/cow'
import { type CoWQuote } from './CoW.types'
import { ErrorCodes } from './CoW.constants'
import { CoWError, type CoWQuote } from './CoW.types'

let quoteCounter = 0

export class CoW extends LimitOrderBase {
constructor({ supportedChains, protocol, sellToken, buyToken }: ProtocolContructor) {
Expand Down Expand Up @@ -171,6 +174,7 @@ export class CoW extends LimitOrderBase {
}

async getQuote(limitOrder?: LimitOrder) {
this.quoteErrorMessage = undefined
if (this.userUpdatedLimitPrice) {
return
}
Expand Down Expand Up @@ -224,7 +228,12 @@ export class CoW extends LimitOrderBase {
})
}
} catch (error: any) {
// TODO: SHOW ERROR in UI
quoteCounter = ++quoteCounter
if (quoteCounter < 3) {
if (this.#validateError(error)) {
this.getQuote()
}
}
this.logger.error(error.message)
}
}
Expand Down Expand Up @@ -405,4 +414,12 @@ export class CoW extends LimitOrderBase {
this.getQuote()
return '1'
}

#validateError(error: CoWError) {
if (ErrorCodes.includes(error.error_code)) {
this.quoteErrorMessage = error.message
return false
}
return true
}
}
12 changes: 12 additions & 0 deletions src/services/LimitOrders/CoW/CoW.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,15 @@ export interface SignedLimitOrder extends LimitOrder {
}

export type CoWQuote = Awaited<ReturnType<typeof getQuote>>

enum CoWErrorCodes {
InsufficientLiquidity = 'InsufficientLiquidity',
UnsupportedToken = 'UnsupportedToken',
NoLiquidity = 'NoLiquidity',
SellAmountDoesNotCoverFee = 'SellAmountDoesNotCoverFee',
}

export type CoWError = {
error_code: CoWErrorCodes
description: string
} & Error
6 changes: 3 additions & 3 deletions src/services/LimitOrders/LimitOrder.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChainId, Currency, DAI, Token, USDT, WBNB, WETH, WMATIC, WXDAI } from '@swapr/sdk'
import { ChainId, Currency, DAI, GNO, Token, USDT, WBNB, WETH, WMATIC, WXDAI } from '@swapr/sdk'

import { OneInch } from './1Inch/OneInch'
import { CoW } from './CoW/CoW'
Expand All @@ -12,7 +12,7 @@ export const DefaultTokens: Record<number, { sellToken: Currency; buyToken: Curr
},
[ChainId.GNOSIS]: {
sellToken: WXDAI[ChainId.GNOSIS],
buyToken: USDT[ChainId.GNOSIS],
buyToken: GNO,
},
[ChainId.POLYGON]: {
sellToken: WMATIC[ChainId.POLYGON],
Expand Down Expand Up @@ -40,7 +40,7 @@ export const getDefaultTokens = (chainId: ChainId) => {
return { sellToken, buyToken }
}

export const limitOrderConfig: LimitOrderBase[] = [
export const getLimitOrderCofig = (): LimitOrderBase[] => [
new CoW({
supportedChains: [ChainId.MAINNET, ChainId.GNOSIS],
protocol: Providers.COW,
Expand Down
4 changes: 2 additions & 2 deletions src/services/LimitOrders/LimitOrder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { limitOrderConfig } from './LimitOrder.config'
import { getLimitOrderCofig } from './LimitOrder.config'
import { WalletData } from './LimitOrder.types'
import { LimitOrderBase, logger } from './LimitOrder.utils'

Expand All @@ -8,7 +8,7 @@ export default class LimitOrder {

constructor() {
logger('LimitOrder constructor')
this.#protocols = limitOrderConfig
this.#protocols = getLimitOrderCofig()
}

updateSigner = async (signerData: WalletData) => {
Expand Down
Loading

0 comments on commit 090ded6

Please sign in to comment.