From 3114155fbdc6ec98d22f9253ba394334685e780e Mon Sep 17 00:00:00 2001 From: Wixzi Date: Tue, 6 Jun 2023 15:24:15 +0200 Subject: [PATCH 01/37] [SWA-46] LimitOrder base classes --- src/services/LimitOrders/LimitOrder.types.ts | 46 ++++++++++++++++++ src/services/LimitOrders/LimitOrder.utils.ts | 51 ++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 src/services/LimitOrders/LimitOrder.types.ts create mode 100644 src/services/LimitOrders/LimitOrder.utils.ts diff --git a/src/services/LimitOrders/LimitOrder.types.ts b/src/services/LimitOrders/LimitOrder.types.ts new file mode 100644 index 000000000..04b6d9327 --- /dev/null +++ b/src/services/LimitOrders/LimitOrder.types.ts @@ -0,0 +1,46 @@ +import { ChainId } from '@swapr/sdk' + +import { LimitOrderBase } from './LimitOrder.utils' + +export interface TokenBase { + address: string + decimals: number + symbol: string + chainId: number + name?: string +} + +export type NativeToken = TokenBase & { + isToken: false + isNative: true +} + +export type ERC20Token = TokenBase & { + isNative: false + isToken: true +} + +export interface TokenAmount { + token: Token + /** + * Amount in wei + */ + amount: string +} + +export interface LimitOrderBaseConstructor { + userAddress: string + receiverAddress: string + sellToken: ERC20Token | NativeToken + orderType?: 'partial' | 'full' + kind?: 'buy' | 'sell' + provider?: 'CoW' | '1inch' + supportedChains: ChainId[] +} + +export enum LimitOrderIds { + COW = 'CoW', + ONEINCH = '1inch', +} + +export type LimitOrderProviders = { [key in LimitOrderIds]: LimitOrderBase } diff --git a/src/services/LimitOrders/LimitOrder.utils.ts b/src/services/LimitOrders/LimitOrder.utils.ts new file mode 100644 index 000000000..debd7a1a3 --- /dev/null +++ b/src/services/LimitOrders/LimitOrder.utils.ts @@ -0,0 +1,51 @@ +import { ChainId } from '@swapr/sdk' + +import { ERC20Token, LimitOrderBaseConstructor, NativeToken, TokenAmount } from './LimitOrder.types' + +export abstract class LimitOrderBase { + userAddress: string + receiverAddress: string + sellToken: ERC20Token | NativeToken + buyToken?: ERC20Token | NativeToken + sellAmount?: TokenAmount + buyAmount?: TokenAmount + limitPrice?: string + orderType: 'partial' | 'full' + quoteId?: string + kind: 'buy' | 'sell' + expiresAt?: number + createdAt?: number + limitOrderProvider?: 'CoW' | '1inch' + supportedChanins: ChainId[] + + constructor({ + userAddress, + receiverAddress, + sellToken, + orderType = 'partial', + kind = 'buy', + provider, + supportedChains, + }: LimitOrderBaseConstructor) { + this.userAddress = userAddress + this.receiverAddress = receiverAddress + this.sellToken = sellToken + this.orderType = orderType + this.kind = kind + this.limitOrderProvider = provider + this.supportedChanins = supportedChains + } + + #log = (message: string) => `LimitOrder:: ${this.limitOrderProvider} : ${message}` + + #logger = { + log: (message: string) => console.log(this.#log(message)), + error: (message: string) => console.error(this.#log(message)), + } + + abstract getQuote(): Promise + abstract init(): Promise + abstract onSignerChange(): Promise + abstract approve(): Promise + abstract createOrder(): Promise +} From b89e982deff690523a9c28cac96de6892e8b1577 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Thu, 15 Jun 2023 10:07:58 +0200 Subject: [PATCH 02/37] [SWA-46] Limit order configuration --- src/services/LimitOrders/LimitOrder.config.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/services/LimitOrders/LimitOrder.config.ts diff --git a/src/services/LimitOrders/LimitOrder.config.ts b/src/services/LimitOrders/LimitOrder.config.ts new file mode 100644 index 000000000..04d043cda --- /dev/null +++ b/src/services/LimitOrders/LimitOrder.config.ts @@ -0,0 +1,16 @@ +import { ChainId } from '@swapr/sdk' + +import { OneInch } from './1Inch/OneInch' +import { CoW } from './CoW/CoW' +import { LimitOrderBase } from './LimitOrder.utils' + +export const limitOrderConfig: LimitOrderBase[] = [ + new CoW({ + supportedChains: [ChainId.MAINNET, ChainId.GNOSIS], + protocol: 'CoW', + }), + new OneInch({ + supportedChains: [ChainId.POLYGON, ChainId.OPTIMISM_MAINNET, ChainId.ARBITRUM_ONE, ChainId.BSC_MAINNET], + protocol: '1inch', + }), +] From d1a9edde3d3fac0e83afe092ea4a75e4ddba9881 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Thu, 15 Jun 2023 10:08:12 +0200 Subject: [PATCH 03/37] [SWA-46] Limit order basic Provider --- src/services/LimitOrders/LimitOrder.provider.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/services/LimitOrders/LimitOrder.provider.ts diff --git a/src/services/LimitOrders/LimitOrder.provider.ts b/src/services/LimitOrders/LimitOrder.provider.ts new file mode 100644 index 000000000..27dfe10ef --- /dev/null +++ b/src/services/LimitOrders/LimitOrder.provider.ts @@ -0,0 +1,9 @@ +import { ReactNode, createContext } from 'react' + +import { LimitOrder } from './LimitOrder' + +export const LimitOrderContext = createContext(null) + +export function LimitOrderProvider({ children }: { children: ReactNode }) { + return children +} From 8ce58b39dc2fb86b5ce5470a66abc6984ff0bf1c Mon Sep 17 00:00:00 2001 From: Wixzi Date: Thu, 15 Jun 2023 10:09:44 +0200 Subject: [PATCH 04/37] [SWA-46] Limit order initial types --- src/services/LimitOrders/LimitOrder.types.ts | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/services/LimitOrders/LimitOrder.types.ts b/src/services/LimitOrders/LimitOrder.types.ts index 04b6d9327..b90f3dfe7 100644 --- a/src/services/LimitOrders/LimitOrder.types.ts +++ b/src/services/LimitOrders/LimitOrder.types.ts @@ -1,3 +1,4 @@ +import { Web3Provider } from '@ethersproject/providers' import { ChainId } from '@swapr/sdk' import { LimitOrderBase } from './LimitOrder.utils' @@ -20,27 +21,26 @@ export type ERC20Token = TokenBase & { isToken: true } -export interface TokenAmount { - token: Token - /** - * Amount in wei - */ - amount: string -} +export type Token = NativeToken | ERC20Token export interface LimitOrderBaseConstructor { - userAddress: string - receiverAddress: string - sellToken: ERC20Token | NativeToken - orderType?: 'partial' | 'full' - kind?: 'buy' | 'sell' - provider?: 'CoW' | '1inch' + protocol: 'CoW' | '1inch' supportedChains: ChainId[] + kind: 'buy' | 'sell' + expiresAt: number } +export type ProtocolContructor = Omit + export enum LimitOrderIds { COW = 'CoW', ONEINCH = '1inch', } export type LimitOrderProviders = { [key in LimitOrderIds]: LimitOrderBase } + +export interface LimitOrderChangeHandler { + account: string + activeProvider: Web3Provider + activeChainId: ChainId +} From b6c0aa460e605f90a4a29d94b5dbe60bb7c7d2cc Mon Sep 17 00:00:00 2001 From: Wixzi Date: Thu, 15 Jun 2023 10:10:13 +0200 Subject: [PATCH 05/37] [SWA-46] Limit Base abstract setup --- src/services/LimitOrders/LimitOrder.utils.ts | 74 +++++++++++--------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/src/services/LimitOrders/LimitOrder.utils.ts b/src/services/LimitOrders/LimitOrder.utils.ts index debd7a1a3..19709dc27 100644 --- a/src/services/LimitOrders/LimitOrder.utils.ts +++ b/src/services/LimitOrders/LimitOrder.utils.ts @@ -1,51 +1,59 @@ -import { ChainId } from '@swapr/sdk' +import { Web3Provider } from '@ethersproject/providers' +import { ChainId, TokenAmount } from '@swapr/sdk' -import { ERC20Token, LimitOrderBaseConstructor, NativeToken, TokenAmount } from './LimitOrder.types' +import { Token, LimitOrderBaseConstructor, LimitOrderChangeHandler } from './LimitOrder.types' export abstract class LimitOrderBase { - userAddress: string - receiverAddress: string - sellToken: ERC20Token | NativeToken - buyToken?: ERC20Token | NativeToken - sellAmount?: TokenAmount - buyAmount?: TokenAmount - limitPrice?: string - orderType: 'partial' | 'full' - quoteId?: string - kind: 'buy' | 'sell' - expiresAt?: number - createdAt?: number - limitOrderProvider?: 'CoW' | '1inch' + limitOrder: any + quote: any + userAddress: string | undefined + receiverAddres: string | undefined + sellToken: Token | undefined + buyToken: Token | undefined + sellAmount: TokenAmount | undefined + buyAmount: TokenAmount | undefined + limitPrice: string | undefined + orderType?: 'partial' | 'full' + quoteId: string | undefined + kind?: 'buy' | 'sell' + provider: Web3Provider | undefined + expiresAt: number + createdAt: number | undefined + limitOrderProtocol: 'CoW' | '1inch' supportedChanins: ChainId[] + activeChainId: ChainId | undefined - constructor({ - userAddress, - receiverAddress, - sellToken, - orderType = 'partial', - kind = 'buy', - provider, - supportedChains, - }: LimitOrderBaseConstructor) { - this.userAddress = userAddress - this.receiverAddress = receiverAddress - this.sellToken = sellToken - this.orderType = orderType - this.kind = kind - this.limitOrderProvider = provider + constructor({ protocol, supportedChains, kind, expiresAt }: LimitOrderBaseConstructor) { + this.limitOrderProtocol = protocol this.supportedChanins = supportedChains + this.kind = kind + this.expiresAt = expiresAt } - #log = (message: string) => `LimitOrder:: ${this.limitOrderProvider} : ${message}` + #log = (message: string) => `LimitOrder:: ${this.limitOrderProtocol} : ${message}` #logger = { log: (message: string) => console.log(this.#log(message)), error: (message: string) => console.error(this.#log(message)), } + setSignerData = async ({ account, activeChainId, activeProvider }: LimitOrderChangeHandler) => { + this.userAddress = account + this.activeChainId = activeChainId + this.provider = activeProvider + this.#logger.log(`Signer data set for ${this.limitOrderProtocol}`) + } + + abstract onSellTokenChange(sellToken: Token): void + abstract onBuyTokenChange(buyToken: Token): void + abstract onSellAmountChange(sellAmount: TokenAmount): void + abstract onBuyAmountChange(buyAmount: TokenAmount): void + abstract onLimitOrderChange(limitOrder: any): void + abstract onExpireChange(expiresAt: number): void + abstract getQuote(): Promise - abstract init(): Promise - abstract onSignerChange(): Promise + abstract setToMarket(sellPricePercentage: number, buyPricePercentage: number): Promise + abstract onSignerChange({ account, activeChainId, activeProvider }: LimitOrderChangeHandler): Promise abstract approve(): Promise abstract createOrder(): Promise } From 0c0260d13a25e5f7421b0cf0ba0429baf8434487 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Thu, 15 Jun 2023 10:10:45 +0200 Subject: [PATCH 06/37] [SWA-46] Limit CoW and 1Inch Base config --- src/services/LimitOrders/1Inch/OneInch.ts | 47 ++++++++++ src/services/LimitOrders/CoW/CoW.ts | 106 ++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 src/services/LimitOrders/1Inch/OneInch.ts create mode 100644 src/services/LimitOrders/CoW/CoW.ts diff --git a/src/services/LimitOrders/1Inch/OneInch.ts b/src/services/LimitOrders/1Inch/OneInch.ts new file mode 100644 index 000000000..e020900bc --- /dev/null +++ b/src/services/LimitOrders/1Inch/OneInch.ts @@ -0,0 +1,47 @@ +import { TokenAmount } from '@swapr/sdk' + +import { ProtocolContructor, Token } from '../LimitOrder.types' +import { LimitOrderBase } from '../LimitOrder.utils' + +export class OneInch extends LimitOrderBase { + constructor({ supportedChains, protocol }: ProtocolContructor) { + super({ + supportedChains, + protocol, + kind: 'sell', + expiresAt: 20, + }) + } + + onSellTokenChange(sellToken: Token) { + this.sellToken = sellToken + } + + onBuyTokenChange(buyToken: Token) { + this.buyToken = buyToken + } + + onSellAmountChange(sellAmount: TokenAmount) { + this.sellAmount = sellAmount + } + + onBuyAmountChange(buyAmount: TokenAmount) { + this.buyAmount = buyAmount + } + + getQuote(): Promise { + throw new Error('Method not implemented.') + } + init(): Promise { + throw new Error('Method not implemented.') + } + onSignerChange(): Promise { + throw new Error('Method not implemented.') + } + approve(): Promise { + throw new Error('Method not implemented.') + } + createOrder(): Promise { + throw new Error('Method not implemented.') + } +} diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts new file mode 100644 index 000000000..b05a1623d --- /dev/null +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -0,0 +1,106 @@ +import { TokenAmount } from '@swapr/sdk' + +import dayjs from 'dayjs' +import { parseUnits } from 'ethers/lib/utils' + +import { getQuote } from '../../../pages/Swap/LimitOrderBox/api/cow' +import { LimitOrderChangeHandler, ProtocolContructor, Token } from '../LimitOrder.types' +import { LimitOrderBase } from '../LimitOrder.utils' + +export class CoW extends LimitOrderBase { + constructor({ supportedChains, protocol }: ProtocolContructor) { + super({ + supportedChains, + protocol, + kind: 'sell', + expiresAt: 20, + }) + } + + onSellTokenChange(sellToken: Token) { + this.sellToken = sellToken + } + + onBuyTokenChange(buyToken: Token) { + this.buyToken = buyToken + } + + onSellAmountChange(sellAmount: TokenAmount) { + this.sellAmount = sellAmount + } + + onBuyAmountChange(buyAmount: TokenAmount) { + this.buyAmount = buyAmount + } + + onLimitOrderChange(limitOrder: any): void { + this.limitOrder = limitOrder + } + + onExpireChange(expiresAt: number): void { + this.expiresAt = expiresAt + } + + onKindChange(kind: 'buy' | 'sell'): void { + this.kind = kind + } + + async setToMarket(sellPricePercentage: number, buyPricePercentage: number): Promise { + const sellToken = this.sellToken + const buyToken = this.buyToken + if (!sellToken || !buyToken) { + return + } + let tokenAmount = this.kind === 'sell' ? this.sellAmount : this.buyAmount + if (!tokenAmount) { + return + } + const validatedTokenAmount = Number(tokenAmount.toExact()) > 1 ? tokenAmount.toExact() : '1' + const sellAmount = parseUnits(validatedTokenAmount, tokenAmount.currency.decimals).toString() + + const limitOrder = { + ...this.limitOrder, + sellAmount, + } + + await this.getQuote(limitOrder) + if (!this.quote) { + throw new Error('No quote') + } + const { buyAmount: buyAmountQuote, sellAmount: sellAmountQuote } = this.quote + + console.log('buyAmountQuote', buyAmountQuote) + console.log('sellAmountQuote', sellAmountQuote) + } + + async getQuote(limitOrder?: any): Promise { + const signer = this.provider?.getSigner() + const chainId = this.activeChainId + const order = limitOrder ?? this.limitOrder + const expiresAt = dayjs().add(this.expiresAt, 'minutes').unix() + const kind = this.kind + if (!signer || !chainId || !limitOrder || !expiresAt || !kind) { + throw new Error('Missing required params') + } + const cowQuote = await getQuote({ + chainId, + signer, + order: { ...order, expiresAt }, + }) + + this.quote = cowQuote + } + + async onSignerChange({ account, activeChainId, activeProvider }: LimitOrderChangeHandler) { + this.userAddress = account + this.activeChainId = activeChainId + this.provider = activeProvider + } + + approve(): Promise { + throw new Error('Method not implemented.') + } + createOrder(): Promise { + throw new Error('Method not implemented.') + } +} From fd07d82ca6fbe7f45c7e5b5984e9c6180c79736e Mon Sep 17 00:00:00 2001 From: Wixzi Date: Thu, 15 Jun 2023 10:11:06 +0200 Subject: [PATCH 07/37] [SWA-46] Create Limit Order class --- src/services/LimitOrders/LimitOrder.ts | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/services/LimitOrders/LimitOrder.ts diff --git a/src/services/LimitOrders/LimitOrder.ts b/src/services/LimitOrders/LimitOrder.ts new file mode 100644 index 000000000..4650ab88c --- /dev/null +++ b/src/services/LimitOrders/LimitOrder.ts @@ -0,0 +1,31 @@ +import { limitOrderConfig } from './LimitOrder.config' +import { LimitOrderChangeHandler } from './LimitOrder.types' +import { LimitOrderBase } from './LimitOrder.utils' + +export class LimitOrder { + #protocols: LimitOrderBase[] + activeProtocol?: LimitOrderBase + + constructor() { + console.log('LimitOrder constructor') + this.#protocols = limitOrderConfig + } + + updateSigner = async (signerData: LimitOrderChangeHandler) => { + console.log('LimitOrder updateSigner') + await Promise.all( + Object.values(this.#protocols).map(async protocol => { + await protocol.setSignerData(signerData) + await protocol.onSignerChange(signerData) + }) + ) + } + + getactiveProtocol = async () => { + console.log('LimitOrder getactiveProtocols') + this.activeProtocol = this.#protocols.find( + protocol => protocol.activeChainId && protocol.supportedChanins.includes(protocol.activeChainId) + ) + return this.activeProtocol + } +} From 43214ccc87f118b4fac6172454142f52f9476e07 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Fri, 16 Jun 2023 19:58:50 +0200 Subject: [PATCH 08/37] [SWA-46] Limit order tab --- src/pages/Swap/Components/Tabs.tsx | 6 ++++++ src/pages/Swap/LimitOrder/LimitOrder.tsx | 3 +++ src/pages/Swap/LimitOrder/index.ts | 1 + src/pages/Swap/Swap.tsx | 4 +++- src/services/LimitOrders/1Inch/OneInch.ts | 17 +++++++++++++++++ src/state/user/reducer.ts | 1 + 6 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/pages/Swap/LimitOrder/LimitOrder.tsx create mode 100644 src/pages/Swap/LimitOrder/index.ts diff --git a/src/pages/Swap/Components/Tabs.tsx b/src/pages/Swap/Components/Tabs.tsx index 853fc2f61..d62debad6 100644 --- a/src/pages/Swap/Components/Tabs.tsx +++ b/src/pages/Swap/Components/Tabs.tsx @@ -95,6 +95,12 @@ export function Tabs() { Swap + + + - diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx index 2fea60e07..46816887b 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx @@ -1,12 +1,11 @@ import { formatUnits, parseUnits } from '@ethersproject/units' +import { Currency, TokenAmount } from '@swapr/sdk' -import { Dispatch, SetStateAction, useCallback, useContext, useEffect, useState } from 'react' +import { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react' import { RefreshCw } from 'react-feather' import { useTranslation } from 'react-i18next' -import { Kind, LimitOrderContext, MarketPrices } from '../../../../../services/LimitOrders' -// import { LimitOrderFormContext } from '../../contexts/LimitOrderFormContext' -// import { InputFocus, Kind, MarketPrices } from '../../interfaces' +import { Kind, LimitOrderBase, MarketPrices } from '../../../../../services/LimitOrders' import { InputGroup } from '../InputGroup' import { calculateMarketPriceDiffPercentage } from '../utils' @@ -19,46 +18,71 @@ import { SwapTokenWrapper, ToggleCurrencyButton, } from './styles' + const invalidChars = ['-', '+', 'e'] +const getBaseQuoteTokens = ({ + sellAmount, + buyAmount, + kind, + sellToken, + buyToken, +}: { + sellAmount: TokenAmount + buyAmount: TokenAmount + kind: Kind + sellToken: Currency + buyToken: Currency +}) => { + return kind === Kind.Sell + ? { baseTokenAmount: sellAmount, baseToken: sellToken, quoteTokenAmount: buyAmount, quoteToken: buyToken } + : { baseTokenAmount: buyAmount, baseToken: buyToken, quoteTokenAmount: sellAmount, quoteToken: sellToken } +} + export interface OrderLimitPriceFieldProps { - id?: string marketPrices: MarketPrices fetchMarketPrice: boolean setFetchMarketPrice: Dispatch> + protocol: LimitOrderBase + sellAmount: TokenAmount + buyAmount: TokenAmount + kind: Kind + limitOrder?: any + sellToken: Currency + buyToken: Currency } export function OrderLimitPriceField({ - id, marketPrices, fetchMarketPrice, setFetchMarketPrice, + protocol, + sellAmount, + buyAmount, + kind, + limitOrder = {}, + sellToken, + buyToken, }: OrderLimitPriceFieldProps) { const { t } = useTranslation('swap') - // const { - // limitOrder, - // setLimitOrder, - // buyAmount, - // setBuyTokenAmount, - // sellAmount, - // setSellTokenAmount, - // formattedLimitPrice, - // setFormattedLimitPrice, - // setFormattedBuyAmount, - // setFormattedSellAmount, - // inputFocus, - // } = useContext(LimitOrderFormContext) - - const protocol = useContext(LimitOrderContext) - const { sellAmount, buyAmount, kind, limitOrder = {} } = protocol - // TODO: fix this - const formattedLimitPrice = 0 - const inputFocus = 'sell' - const setFormattedLimitPrice = (_t: any) => {} - - const [baseTokenAmount, quoteTokenAmount] = kind === Kind.Sell ? [sellAmount, buyAmount] : [buyAmount, sellAmount] - const inputGroupLabel = `${kind} ${baseTokenAmount?.currency?.symbol} at` - const toggleCurrencyButtonLabel = `${quoteTokenAmount?.currency?.symbol}` + + // const formattedLimitPrice = 0 + // // const inputFocus = 'sell' + // const setFormattedLimitPrice = (_t: any) => {} + + const [formattedLimitPrice, setFormattedLimitPrice] = useState(0) + + const [{ baseToken, baseTokenAmount: _b, quoteToken, quoteTokenAmount: _q }, setBaseQuoteTokens] = useState( + getBaseQuoteTokens({ sellAmount, buyAmount, kind, sellToken, buyToken }) + ) + + useEffect(() => { + setBaseQuoteTokens(getBaseQuoteTokens({ sellAmount, buyAmount, kind, sellToken, buyToken })) + }, [sellToken, buyToken, sellAmount, buyAmount, kind]) + + const inputGroupLabel = `${kind} ${baseToken?.symbol} at` + const toggleCurrencyButtonLabel = `${quoteToken?.symbol}` + const [inputLimitPrice, setInputLimitPrice] = useState(formattedLimitPrice) const { marketPriceDiffPercentage, isDiffPositive } = calculateMarketPriceDiffPercentage( @@ -80,7 +104,9 @@ export function OrderLimitPriceField({ const toggleBaseCurrency = useCallback(() => { // Toggle between buy and sell currency const kindSelected = kind === Kind.Sell ? Kind.Buy : Kind.Sell + if (!sellAmount || !buyAmount) return + const [baseTokenAmount, quoteTokenAmount] = kindSelected === Kind.Sell ? [sellAmount, buyAmount] : [buyAmount, sellAmount] @@ -101,7 +127,7 @@ export function OrderLimitPriceField({ limitPrice: nextLimitPriceWei, }) // update the formatted limit price - // setFormattedLimitPrice(nextLimitPriceFormatted) + setFormattedLimitPrice(nextLimitPriceFormatted) }, [kind, sellAmount, buyAmount, protocol, limitOrder]) /** @@ -138,7 +164,7 @@ export function OrderLimitPriceField({ setFormattedLimitPrice(nextLimitPriceFormatted) - if (inputFocus === Kind.Sell) { + if (kind === Kind.Sell) { // setBuyTokenAmount(newBuyTokenAmount) // setFormattedBuyAmount(amount.toString()) // setLimitOrder({ @@ -166,7 +192,7 @@ export function OrderLimitPriceField({ return ( - + {inputGroupLabel} {showPercentage && ( @@ -184,7 +210,7 @@ export function OrderLimitPriceField({ { if (invalidChars.includes(e.key)) { diff --git a/src/pages/Swap/LimitOrder/LimitOrder.tsx b/src/pages/Swap/LimitOrder/LimitOrder.tsx index a99e22c85..a509dcdce 100644 --- a/src/pages/Swap/LimitOrder/LimitOrder.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrder.tsx @@ -1,18 +1,12 @@ import { useEffect, useState } from 'react' -import { Flex } from 'rebass' -import { AutoColumn } from '../../../components/Column' -import { CurrencyInputPanel } from '../../../components/CurrencyInputPanel' import { PageMetaData } from '../../../components/PageMetaData' import { useActiveWeb3React } from '../../../hooks' -import LimitOrder, { Kind, LimitOrderChangeHandler, MarketPrices } from '../../../services/LimitOrders' +import LimitOrder, { LimitOrderChangeHandler } from '../../../services/LimitOrders' import { LimitOrderProvider } from '../../../services/LimitOrders/LimitOrder.provider' import AppBody from '../../AppBody' -import { AutoRow } from './Components/AutoRow' -import { OrderExpiryField } from './Components/OrderExpiryField' -import { OrderLimitPriceField } from './Components/OrderLimitPriceField' -import SwapTokens from './Components/SwapTokens' +import LimitOrderForm from './LimitOrderForm' const limitSdk = new LimitOrder() @@ -20,32 +14,9 @@ export default function LimitOrderUI() { const { chainId, account, library } = useActiveWeb3React() const [protocol, setProtocol] = useState(limitSdk.getActiveProtocol()) - const [fetchMarketPrice, setFetchMarketPrice] = useState(true) - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [sellAmount, setSellAmount] = useState(protocol?.sellAmount) - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [buyAmount, setBuyAmount] = useState(protocol?.buyAmount) - - const [sellToken, setSellToken] = useState(protocol?.sellToken) - const [buyToken, setBuyToken] = useState(protocol?.buyToken) - - useEffect(() => { - if (sellToken) { - protocol?.onSellTokenChange(sellToken) - } - console.log('sellToken', protocol, sellToken) - }, [protocol, sellToken]) - - useEffect(() => { - if (buyToken) { - protocol?.onBuyTokenChange(buyToken) - } - console.log('buyToken', protocol, buyToken) - }, [protocol, buyToken]) useEffect(() => { + // console.log('chainId change', chainId) async function updateSigner(signerData: LimitOrderChangeHandler) { await limitSdk.updateSigner(signerData) setProtocol(limitSdk.getActiveProtocol()) @@ -55,75 +26,16 @@ export default function LimitOrderUI() { } }, [account, chainId, library]) - const [marketPrices, setMarketPrices] = useState({ buy: 0, sell: 0 }) - - useEffect(() => { - async function getMarketPrices() { - if (!protocol) return - const { kind } = protocol - const amount = await protocol.getMarketPrice() - - if (kind === Kind.Sell) { - setMarketPrices(marketPrice => ({ ...marketPrice, buy: amount })) - } else { - setMarketPrices(marketPrice => ({ ...marketPrice, sell: amount })) - } - } - getMarketPrices() - }, [protocol, protocol?.kind]) - return ( <> {protocol && ( - - - - { - setSellToken(buyToken) - setBuyToken(sellToken) - }} - loading={false} - /> - - - - - - - - - - - + )} + {!protocol && <>Loading....} ) diff --git a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx new file mode 100644 index 000000000..512d71eab --- /dev/null +++ b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx @@ -0,0 +1,152 @@ +import { Currency, Token, TokenAmount } from '@swapr/sdk' + +import { formatUnits, parseUnits } from 'ethers/lib/utils' +import { useContext, useEffect, useState } from 'react' +import { Flex } from 'rebass' + +import { AutoColumn } from '../../../components/Column' +import { CurrencyInputPanel } from '../../../components/CurrencyInputPanel' +import { Kind, MarketPrices } from '../../../services/LimitOrders' +import { LimitOrderContext } from '../../../services/LimitOrders/LimitOrder.provider' + +import { AutoRow } from './Components/AutoRow' +import { OrderExpiryField } from './Components/OrderExpiryField' +import { OrderLimitPriceField } from './Components/OrderLimitPriceField' +import SwapTokens from './Components/SwapTokens' + +export default function LimitOrderForm() { + const protocol = useContext(LimitOrderContext) + + const [fetchMarketPrice, setFetchMarketPrice] = useState(true) + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [sellAmount, setSellAmount] = useState(protocol.sellAmount) + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [buyAmount, setBuyAmount] = useState(protocol.buyAmount) + + const [sellToken, setSellToken] = useState(protocol.sellToken) + const [buyToken, setBuyToken] = useState(protocol.buyToken) + + const [kind, setKind] = useState(protocol?.kind || Kind.Sell) + + useEffect(() => { + setSellToken(protocol.sellToken) + setBuyToken(protocol.buyToken) + setSellAmount(protocol.sellAmount) + setBuyAmount(protocol.buyAmount) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [protocol.activeChainId]) + + useEffect(() => { + protocol?.onKindChange(kind) + }, [protocol, kind]) + + useEffect(() => { + if (sellToken) { + protocol?.onSellTokenChange(sellToken) + } + console.log('sellToken', protocol, sellToken) + }, [protocol, sellToken]) + + useEffect(() => { + if (buyToken) { + protocol?.onBuyTokenChange(buyToken) + } + console.log('buyToken', protocol, buyToken) + }, [protocol, buyToken]) + + useEffect(() => { + if (sellAmount) { + protocol?.onSellAmountChange(sellAmount) + } + console.log('sellAmount', protocol, sellAmount) + }, [protocol, sellAmount]) + + useEffect(() => { + if (buyAmount) { + protocol?.onSellAmountChange(buyAmount) + } + console.log('buyAmount', protocol, buyAmount) + }, [protocol, buyAmount]) + + const [marketPrices, setMarketPrices] = useState({ buy: 0, sell: 0 }) + + useEffect(() => { + async function getMarketPrices() { + if (!protocol) return + const { kind } = protocol + const amount = await protocol.getMarketPrice() + + if (kind === Kind.Sell) { + setMarketPrices(marketPrice => ({ ...marketPrice, buy: amount })) + } else { + setMarketPrices(marketPrice => ({ ...marketPrice, sell: amount })) + } + } + getMarketPrices() + }, [protocol, protocol?.kind]) + + return ( + + + { + setSellToken(protocol.getTokenFromCurrency(currency)) + }} + /> + { + setSellToken(buyToken) + setBuyToken(sellToken) + }} + loading={false} + /> + { + setBuyToken(protocol.getTokenFromCurrency(currency)) + }} + /> + + + + + + + + + + + ) +} diff --git a/src/services/LimitOrders/1Inch/OneInch.ts b/src/services/LimitOrders/1Inch/OneInch.ts index 093a1e005..1b3fa7260 100644 --- a/src/services/LimitOrders/1Inch/OneInch.ts +++ b/src/services/LimitOrders/1Inch/OneInch.ts @@ -1,32 +1,53 @@ -import { TokenAmount } from '@swapr/sdk' +import { Currency, TokenAmount } from '@swapr/sdk' -import { Kind, LimitOrderChangeHandler, OrderExpiresInUnit, ProtocolContructor, Token } from '../LimitOrder.types' +import { parseUnits } from 'ethers/lib/utils' + +import { getDefaultTokens } from '../LimitOrder.config' +import { Kind, LimitOrderChangeHandler, OrderExpiresInUnit, ProtocolContructor } from '../LimitOrder.types' import { LimitOrderBase } from '../LimitOrder.utils' export class OneInch extends LimitOrderBase { - constructor({ supportedChains, protocol }: ProtocolContructor) { + constructor({ supportedChains, protocol, sellToken, buyToken }: ProtocolContructor) { super({ supportedChains, protocol, kind: Kind.Sell, expiresAt: 20, + sellToken, + buyToken, }) } - onSellTokenChange(sellToken: Token) { - this.sellToken = sellToken + onSellTokenChange(sellToken: Currency) { + this.sellToken = this.getTokenFromCurrency(sellToken) + this.limitOrder = { + ...this.limitOrder, + sellToken: this.sellToken, + } } - onBuyTokenChange(buyToken: Token) { - this.buyToken = buyToken + onBuyTokenChange(buyToken: Currency) { + this.buyToken = this.getTokenFromCurrency(buyToken) + this.limitOrder = { + ...this.limitOrder, + buyToken: this.buyToken, + } } onSellAmountChange(sellAmount: TokenAmount) { this.sellAmount = sellAmount + this.limitOrder = { + ...this.limitOrder, + sellAmount: this.sellAmount, + } } onBuyAmountChange(buyAmount: TokenAmount) { this.buyAmount = buyAmount + this.limitOrder = { + ...this.limitOrder, + buyAmount: this.buyAmount, + } } onLimitOrderChange(limitOrder: any): void { @@ -34,6 +55,7 @@ export class OneInch extends LimitOrderBase { } onExpireChange(expiresAt: number): void { + // TODO: Convert expiresAt based on expiresAtUnit this.expiresAt = expiresAt } @@ -56,8 +78,12 @@ export class OneInch extends LimitOrderBase { init(): Promise { throw new Error('Method not implemented.') } - async onSignerChange(_signer: LimitOrderChangeHandler) { - // TODO: update protocol if needed + async onSignerChange({ activeChainId }: LimitOrderChangeHandler) { + const { sellToken, buyToken } = getDefaultTokens(activeChainId) + this.onSellTokenChange(sellToken) + this.onBuyTokenChange(buyToken) + this.onSellAmountChange(new TokenAmount(sellToken, parseUnits('1', sellToken.decimals).toString())) + this.onBuyAmountChange(new TokenAmount(buyToken, parseUnits('1', buyToken.decimals).toString())) } approve(): Promise { throw new Error('Method not implemented.') diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index 1ac93ca63..a2a632252 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -4,32 +4,35 @@ import dayjs from 'dayjs' import { formatUnits, parseUnits } from 'ethers/lib/utils' import { getQuote } from '../../../pages/Swap/LimitOrder/api/cow' +import { getDefaultTokens } from '../LimitOrder.config' import { Kind, LimitOrderChangeHandler, OrderExpiresInUnit, ProtocolContructor } from '../LimitOrder.types' import { LimitOrderBase } from '../LimitOrder.utils' export class CoW extends LimitOrderBase { - constructor({ supportedChains, protocol }: ProtocolContructor) { + constructor({ supportedChains, protocol, sellToken, buyToken }: ProtocolContructor) { super({ supportedChains, protocol, kind: Kind.Sell, expiresAt: 20, + sellToken, + buyToken, }) } onSellTokenChange(sellToken: Currency) { - this.sellToken = sellToken + this.sellToken = this.getTokenFromCurrency(sellToken) this.limitOrder = { ...this.limitOrder, - sellToken, + sellToken: this.sellToken, } } onBuyTokenChange(buyToken: Currency) { - this.buyToken = buyToken + this.buyToken = this.getTokenFromCurrency(buyToken) this.limitOrder = { ...this.limitOrder, - buyToken, + buyToken: this.buyToken, } } @@ -54,6 +57,7 @@ export class CoW extends LimitOrderBase { } onExpireChange(expiresAt: number): void { + // TODO: Convert expiresAt based on expiresAtUnit this.expiresAt = expiresAt this.limitOrder = { ...this.limitOrder, @@ -119,8 +123,12 @@ export class CoW extends LimitOrderBase { this.quote = cowQuote } - async onSignerChange(_signer: LimitOrderChangeHandler) { - // TODO: update protocol if needed + async onSignerChange({ activeChainId }: LimitOrderChangeHandler) { + const { sellToken, buyToken } = getDefaultTokens(activeChainId) + this.onSellTokenChange(sellToken) + this.onBuyTokenChange(buyToken) + this.onSellAmountChange(new TokenAmount(sellToken, parseUnits('1', sellToken.decimals).toString())) + this.onBuyAmountChange(new TokenAmount(buyToken, parseUnits('1', buyToken.decimals).toString())) } approve(): Promise { diff --git a/src/services/LimitOrders/LimitOrder.config.ts b/src/services/LimitOrders/LimitOrder.config.ts index 04d043cda..fc1c830ca 100644 --- a/src/services/LimitOrders/LimitOrder.config.ts +++ b/src/services/LimitOrders/LimitOrder.config.ts @@ -1,16 +1,53 @@ -import { ChainId } from '@swapr/sdk' +import { ChainId, Currency, DAI, Token, USDT, WBNB, WETH, WMATIC, WXDAI } from '@swapr/sdk' import { OneInch } from './1Inch/OneInch' import { CoW } from './CoW/CoW' import { LimitOrderBase } from './LimitOrder.utils' +export const DefaultTokens: Record = { + [ChainId.MAINNET]: { + sellToken: WETH[ChainId.MAINNET], + buyToken: DAI[ChainId.MAINNET], + }, + [ChainId.GNOSIS]: { + sellToken: WXDAI[ChainId.GNOSIS], + buyToken: USDT[ChainId.GNOSIS], + }, + [ChainId.POLYGON]: { + sellToken: WMATIC[ChainId.POLYGON], + buyToken: DAI[ChainId.POLYGON], + }, + [ChainId.OPTIMISM_MAINNET]: { + sellToken: USDT[ChainId.OPTIMISM_MAINNET], + buyToken: DAI[ChainId.OPTIMISM_MAINNET], + }, + [ChainId.ARBITRUM_ONE]: { + sellToken: WETH[ChainId.ARBITRUM_ONE], + buyToken: DAI[ChainId.ARBITRUM_ONE], + }, + [ChainId.BSC_MAINNET]: { + sellToken: WBNB[ChainId.BSC_MAINNET], + buyToken: DAI[ChainId.BSC_MAINNET], + }, +} + +export const getDefaultTokens = (chainId: ChainId) => { + const defaultSellToken = DefaultTokens[chainId].sellToken + const defaultBuyToken = DefaultTokens[chainId].buyToken + const sellToken = new Token(chainId, defaultSellToken.address!, defaultSellToken.decimals, defaultSellToken.symbol) + const buyToken = new Token(chainId, defaultBuyToken.address!, defaultBuyToken.decimals, defaultBuyToken.symbol) + return { sellToken, buyToken } +} + export const limitOrderConfig: LimitOrderBase[] = [ new CoW({ supportedChains: [ChainId.MAINNET, ChainId.GNOSIS], protocol: 'CoW', + ...getDefaultTokens(ChainId.MAINNET), }), new OneInch({ supportedChains: [ChainId.POLYGON, ChainId.OPTIMISM_MAINNET, ChainId.ARBITRUM_ONE, ChainId.BSC_MAINNET], protocol: '1inch', + ...getDefaultTokens(ChainId.POLYGON), }), ] diff --git a/src/services/LimitOrders/LimitOrder.provider.tsx b/src/services/LimitOrders/LimitOrder.provider.tsx index c5378a2fe..09e842899 100644 --- a/src/services/LimitOrders/LimitOrder.provider.tsx +++ b/src/services/LimitOrders/LimitOrder.provider.tsx @@ -1,9 +1,16 @@ -import { ReactNode, createContext } from 'react' +import { ReactNode, createContext, useEffect, useState } from 'react' import { LimitOrderBase } from './LimitOrder.utils' export const LimitOrderContext = createContext({} as LimitOrderBase) export function LimitOrderProvider({ children, protocol }: { children: ReactNode; protocol: LimitOrderBase }) { - return {children} + const [value, setProtocol] = useState(protocol) + + useEffect(() => { + // console.log('protocol change', protocol) + setProtocol(protocol) + }, [protocol, setProtocol]) + + return {children} } diff --git a/src/services/LimitOrders/LimitOrder.types.ts b/src/services/LimitOrders/LimitOrder.types.ts index 5f8d263c4..a08175e68 100644 --- a/src/services/LimitOrders/LimitOrder.types.ts +++ b/src/services/LimitOrders/LimitOrder.types.ts @@ -1,5 +1,5 @@ import { Web3Provider } from '@ethersproject/providers' -import { ChainId } from '@swapr/sdk' +import { ChainId, Currency, Token } from '@swapr/sdk' import { LimitOrderBase } from './LimitOrder.utils' @@ -17,31 +17,33 @@ export enum Kind { Sell = 'sell', } -export interface TokenBase { - address: string - decimals: number - symbol: string - chainId: number - name?: string -} +// export interface TokenBase { +// address: string +// decimals: number +// symbol: string +// chainId: number +// name?: string +// } -export type NativeToken = TokenBase & { - isToken: false - isNative: true -} +// export type NativeToken = TokenBase & { +// isToken: false +// isNative: true +// } -export type ERC20Token = TokenBase & { - isNative: false - isToken: true -} +// export type ERC20Token = TokenBase & { +// isNative: false +// isToken: true +// } -export type Token = NativeToken | ERC20Token +// export type Token = NativeToken | ERC20Token export interface LimitOrderBaseConstructor { protocol: 'CoW' | '1inch' supportedChains: ChainId[] kind: Kind expiresAt: number + sellToken: Token + buyToken: Token } export type ProtocolContructor = Omit diff --git a/src/services/LimitOrders/LimitOrder.utils.ts b/src/services/LimitOrders/LimitOrder.utils.ts index 01fc1156e..ae7c463e9 100644 --- a/src/services/LimitOrders/LimitOrder.utils.ts +++ b/src/services/LimitOrders/LimitOrder.utils.ts @@ -1,5 +1,7 @@ import { Web3Provider } from '@ethersproject/providers' -import { ChainId, Currency, TokenAmount } from '@swapr/sdk' +import { ChainId, Currency, Token, TokenAmount } from '@swapr/sdk' + +import { parseUnits } from 'ethers/lib/utils' import { LimitOrderBaseConstructor, LimitOrderChangeHandler, OrderExpiresInUnit, Kind } from './LimitOrder.types' @@ -8,10 +10,10 @@ export abstract class LimitOrderBase { quote: any userAddress: string | undefined receiverAddres: string | undefined - sellToken: Currency | undefined - buyToken: Currency | undefined - sellAmount: TokenAmount | undefined - buyAmount: TokenAmount | undefined + sellToken: Token + buyToken: Token + sellAmount: TokenAmount + buyAmount: TokenAmount limitPrice: string | undefined orderType?: 'partial' | 'full' quoteId: string | undefined @@ -24,14 +26,22 @@ export abstract class LimitOrderBase { supportedChanins: ChainId[] activeChainId: ChainId | undefined - constructor({ protocol, supportedChains, kind, expiresAt }: LimitOrderBaseConstructor) { + constructor({ protocol, supportedChains, kind, expiresAt, sellToken, buyToken }: LimitOrderBaseConstructor) { this.limitOrderProtocol = protocol this.supportedChanins = supportedChains this.kind = kind this.expiresAt = expiresAt + this.sellToken = sellToken + this.buyToken = buyToken + this.sellAmount = new TokenAmount(sellToken, parseUnits('1', sellToken.decimals).toString()) + this.buyAmount = new TokenAmount(buyToken, parseUnits('1', buyToken.decimals).toString()) this.limitOrder = { kind: kind, expiresAt: expiresAt, + sellToken: sellToken, + buyToken: buyToken, + sellAmount: this.sellAmount, + buyAmount: this.buyAmount, } } @@ -42,6 +52,14 @@ export abstract class LimitOrderBase { error: (message: string) => console.error(this.#log(message)), } + getTokenFromCurrency(currency: Currency): Token { + let token = currency as Token + if (this.activeChainId) { + token = new Token(this.activeChainId, currency.address!, currency.decimals, currency.symbol) + } + return token + } + setSignerData = async ({ account, activeChainId, activeProvider }: LimitOrderChangeHandler) => { this.userAddress = account this.activeChainId = activeChainId @@ -56,6 +74,7 @@ export abstract class LimitOrderBase { abstract onLimitOrderChange(limitOrder: any): void abstract onExpireChange(expiresAt: number): void abstract onExpireUnitChange(unit: OrderExpiresInUnit): void + abstract onKindChange(kind: Kind): void abstract getQuote(): Promise abstract getMarketPrice(): Promise From ff9c428021bcc9f28388488bbd9d2e1383aa9339 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Sun, 18 Jun 2023 17:45:40 +0200 Subject: [PATCH 14/37] [SWA-46] Remove unwanted console logs and fix buyTokenChange --- src/pages/Swap/LimitOrder/LimitOrderForm.tsx | 6 +----- src/services/LimitOrders/CoW/CoW.ts | 4 ++++ src/services/LimitOrders/LimitOrder.utils.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx index 512d71eab..520cc7bac 100644 --- a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx @@ -46,28 +46,24 @@ export default function LimitOrderForm() { if (sellToken) { protocol?.onSellTokenChange(sellToken) } - console.log('sellToken', protocol, sellToken) }, [protocol, sellToken]) useEffect(() => { if (buyToken) { protocol?.onBuyTokenChange(buyToken) } - console.log('buyToken', protocol, buyToken) }, [protocol, buyToken]) useEffect(() => { if (sellAmount) { protocol?.onSellAmountChange(sellAmount) } - console.log('sellAmount', protocol, sellAmount) }, [protocol, sellAmount]) useEffect(() => { if (buyAmount) { - protocol?.onSellAmountChange(buyAmount) + protocol?.onBuyAmountChange(buyAmount) } - console.log('buyAmount', protocol, buyAmount) }, [protocol, buyAmount]) const [marketPrices, setMarketPrices] = useState({ buy: 0, sell: 0 }) diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index a2a632252..044d7eecf 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -26,6 +26,7 @@ export class CoW extends LimitOrderBase { ...this.limitOrder, sellToken: this.sellToken, } + this.logger.log(`Sell Token Change ${this.sellToken.symbol}`) } onBuyTokenChange(buyToken: Currency) { @@ -34,6 +35,7 @@ export class CoW extends LimitOrderBase { ...this.limitOrder, buyToken: this.buyToken, } + this.logger.log(`Buy Token Change ${this.buyToken.symbol}`) } onSellAmountChange(sellAmount: TokenAmount) { @@ -42,6 +44,7 @@ export class CoW extends LimitOrderBase { ...this.limitOrder, sellAmount, } + this.logger.log(`Sell Amount Change ${this.sellAmount.raw.toString()}`) } onBuyAmountChange(buyAmount: TokenAmount) { @@ -50,6 +53,7 @@ export class CoW extends LimitOrderBase { ...this.limitOrder, buyAmount, } + this.logger.log(`Buy Amount Change ${this.sellAmount.raw.toString()}`) } onLimitOrderChange(limitOrder: any): void { diff --git a/src/services/LimitOrders/LimitOrder.utils.ts b/src/services/LimitOrders/LimitOrder.utils.ts index ae7c463e9..724ca83c4 100644 --- a/src/services/LimitOrders/LimitOrder.utils.ts +++ b/src/services/LimitOrders/LimitOrder.utils.ts @@ -47,7 +47,7 @@ export abstract class LimitOrderBase { #log = (message: string) => `LimitOrder:: ${this.limitOrderProtocol} : ${message}` - #logger = { + logger = { log: (message: string) => console.log(this.#log(message)), error: (message: string) => console.error(this.#log(message)), } @@ -64,7 +64,7 @@ export abstract class LimitOrderBase { this.userAddress = account this.activeChainId = activeChainId this.provider = activeProvider - this.#logger.log(`Signer data set for ${this.limitOrderProtocol}`) + this.logger.log(`Signer data set for ${this.limitOrderProtocol}`) } abstract onSellTokenChange(sellToken: Currency): void From 988502686a9a7a57fef6d0b422dcff07cf1ef567 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Sun, 18 Jun 2023 17:51:15 +0200 Subject: [PATCH 15/37] [SWA-46] Code cleanup --- .../Components/OrderExpiryField.tsx | 1 - .../OrderLimitPriceField.tsx | 4 ---- src/services/LimitOrders/LimitOrder.types.ts | 20 ------------------- 3 files changed, 25 deletions(-) diff --git a/src/pages/Swap/LimitOrder/Components/OrderExpiryField.tsx b/src/pages/Swap/LimitOrder/Components/OrderExpiryField.tsx index 4b9d9b668..9612fa767 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderExpiryField.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderExpiryField.tsx @@ -3,7 +3,6 @@ import { useTranslation } from 'react-i18next' import styled from 'styled-components' import { OrderExpiresInUnit, LimitOrderContext } from '../../../../services/LimitOrders' -// import { LimitOrderContext } from '../../../../services/LimitOrders/LimitOrder.provider' import { ButtonAddonsWrapper, InnerWrapper, Input, InputGroup, Label } from './InputGroup' diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx index 46816887b..425bbab81 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx @@ -66,10 +66,6 @@ export function OrderLimitPriceField({ }: OrderLimitPriceFieldProps) { const { t } = useTranslation('swap') - // const formattedLimitPrice = 0 - // // const inputFocus = 'sell' - // const setFormattedLimitPrice = (_t: any) => {} - const [formattedLimitPrice, setFormattedLimitPrice] = useState(0) const [{ baseToken, baseTokenAmount: _b, quoteToken, quoteTokenAmount: _q }, setBaseQuoteTokens] = useState( diff --git a/src/services/LimitOrders/LimitOrder.types.ts b/src/services/LimitOrders/LimitOrder.types.ts index a08175e68..2a99a14ad 100644 --- a/src/services/LimitOrders/LimitOrder.types.ts +++ b/src/services/LimitOrders/LimitOrder.types.ts @@ -17,26 +17,6 @@ export enum Kind { Sell = 'sell', } -// export interface TokenBase { -// address: string -// decimals: number -// symbol: string -// chainId: number -// name?: string -// } - -// export type NativeToken = TokenBase & { -// isToken: false -// isNative: true -// } - -// export type ERC20Token = TokenBase & { -// isNative: false -// isToken: true -// } - -// export type Token = NativeToken | ERC20Token - export interface LimitOrderBaseConstructor { protocol: 'CoW' | '1inch' supportedChains: ChainId[] From 1509de4f62cb36e61bf6fa1a57522a1b29c7369b Mon Sep 17 00:00:00 2001 From: Wixzi Date: Sun, 18 Jun 2023 18:15:57 +0200 Subject: [PATCH 16/37] [SWA-46] Fix style issues --- .../MarketPriceButton.tsx | 37 ++++++++++--------- .../Components/OrderLimitPriceField/styles.ts | 11 ++++-- src/pages/Swap/LimitOrder/LimitOrder.tsx | 13 +++---- src/services/LimitOrders/1Inch/OneInch.ts | 4 +- src/services/LimitOrders/CoW/CoW.ts | 10 +++-- src/services/LimitOrders/LimitOrder.ts | 13 +++++-- src/services/LimitOrders/LimitOrder.types.ts | 6 +-- src/services/LimitOrders/LimitOrder.utils.ts | 12 +++--- 8 files changed, 59 insertions(+), 47 deletions(-) diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/MarketPriceButton.tsx b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/MarketPriceButton.tsx index 528b0baa5..290e54b41 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/MarketPriceButton.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/MarketPriceButton.tsx @@ -8,6 +8,25 @@ import { ReactComponent as ProgressCircle } from '../../../../../assets/images/p import { MarketPrice } from './styles' +const StyledProgressCircle = styled(ProgressCircle)` + width: 12px; + height: 12px; + margin-left: 4px; + transform: rotate(-90deg); + + .move { + stroke-dasharray: 100; + stroke-dashoffset: 100; + animation: dash 15s 0s infinite linear forwards; + + @keyframes dash { + to { + stroke-dashoffset: 0; + } + } + } +` + export const MarketPriceButton = memo( ({ buyTokenAmountCurrency: _, @@ -18,24 +37,6 @@ export const MarketPriceButton = memo( }) => { const { t } = useTranslation('swap') - const StyledProgressCircle = styled(ProgressCircle)` - width: 12px; - height: 12px; - margin-left: 4px; - transform: rotate(-90deg); - - .move { - stroke-dasharray: 100; - stroke-dashoffset: 100; - animation: dash 15s 0s infinite linear forwards; - - @keyframes dash { - to { - stroke-dashoffset: 0; - } - } - } - ` return ( {t('limitOrder.marketPrice')} diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/styles.ts b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/styles.ts index 3996b5f84..ba7825c3c 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/styles.ts +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/styles.ts @@ -1,9 +1,14 @@ import { Flex } from 'rebass' import styled from 'styled-components' -import { InputGroup } from '../InputGroup' - -export const LimitLabel = styled(InputGroup.Label)` +export const LimitLabel = styled.label` + font-weight: 600; + font-size: 10px; + line-height: 12px; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #8c83c0; + margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center; diff --git a/src/pages/Swap/LimitOrder/LimitOrder.tsx b/src/pages/Swap/LimitOrder/LimitOrder.tsx index a509dcdce..83fda81c3 100644 --- a/src/pages/Swap/LimitOrder/LimitOrder.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrder.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react' import { PageMetaData } from '../../../components/PageMetaData' import { useActiveWeb3React } from '../../../hooks' -import LimitOrder, { LimitOrderChangeHandler } from '../../../services/LimitOrders' +import LimitOrder, { WalletData } from '../../../services/LimitOrders' import { LimitOrderProvider } from '../../../services/LimitOrders/LimitOrder.provider' import AppBody from '../../AppBody' @@ -11,20 +11,19 @@ import LimitOrderForm from './LimitOrderForm' const limitSdk = new LimitOrder() export default function LimitOrderUI() { - const { chainId, account, library } = useActiveWeb3React() + const { chainId, account, library: provider } = useActiveWeb3React() const [protocol, setProtocol] = useState(limitSdk.getActiveProtocol()) useEffect(() => { - // console.log('chainId change', chainId) - async function updateSigner(signerData: LimitOrderChangeHandler) { + async function updateSigner(signerData: WalletData) { await limitSdk.updateSigner(signerData) setProtocol(limitSdk.getActiveProtocol()) } - if (chainId && account && library) { - updateSigner({ activeChainId: chainId, account, activeProvider: library }) + if (chainId && account && provider) { + updateSigner({ activeChainId: chainId, account, provider }) } - }, [account, chainId, library]) + }, [account, chainId, provider]) return ( <> diff --git a/src/services/LimitOrders/1Inch/OneInch.ts b/src/services/LimitOrders/1Inch/OneInch.ts index 1b3fa7260..cb6cd826f 100644 --- a/src/services/LimitOrders/1Inch/OneInch.ts +++ b/src/services/LimitOrders/1Inch/OneInch.ts @@ -3,7 +3,7 @@ import { Currency, TokenAmount } from '@swapr/sdk' import { parseUnits } from 'ethers/lib/utils' import { getDefaultTokens } from '../LimitOrder.config' -import { Kind, LimitOrderChangeHandler, OrderExpiresInUnit, ProtocolContructor } from '../LimitOrder.types' +import { Kind, WalletData, OrderExpiresInUnit, ProtocolContructor } from '../LimitOrder.types' import { LimitOrderBase } from '../LimitOrder.utils' export class OneInch extends LimitOrderBase { @@ -78,7 +78,7 @@ export class OneInch extends LimitOrderBase { init(): Promise { throw new Error('Method not implemented.') } - async onSignerChange({ activeChainId }: LimitOrderChangeHandler) { + async onSignerChange({ activeChainId }: WalletData) { const { sellToken, buyToken } = getDefaultTokens(activeChainId) this.onSellTokenChange(sellToken) this.onBuyTokenChange(buyToken) diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index 044d7eecf..94a9d7d10 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -5,7 +5,7 @@ import { formatUnits, parseUnits } from 'ethers/lib/utils' import { getQuote } from '../../../pages/Swap/LimitOrder/api/cow' import { getDefaultTokens } from '../LimitOrder.config' -import { Kind, LimitOrderChangeHandler, OrderExpiresInUnit, ProtocolContructor } from '../LimitOrder.types' +import { Kind, WalletData, OrderExpiresInUnit, ProtocolContructor } from '../LimitOrder.types' import { LimitOrderBase } from '../LimitOrder.utils' export class CoW extends LimitOrderBase { @@ -105,8 +105,8 @@ export class CoW extends LimitOrderBase { } const { buyAmount: buyAmountQuote, sellAmount: sellAmountQuote } = this.quote - console.log('buyAmountQuote', buyAmountQuote) - console.log('sellAmountQuote', sellAmountQuote) + this.logger.log('buyAmountQuote', buyAmountQuote) + this.logger.log('sellAmountQuote', sellAmountQuote) } async getQuote(limitOrder?: any): Promise { @@ -127,10 +127,12 @@ export class CoW extends LimitOrderBase { this.quote = cowQuote } - async onSignerChange({ activeChainId }: LimitOrderChangeHandler) { + async onSignerChange({ activeChainId }: WalletData) { const { sellToken, buyToken } = getDefaultTokens(activeChainId) + // Setting default tokens for ChainId's this.onSellTokenChange(sellToken) this.onBuyTokenChange(buyToken) + // Setting default amounts for Tokens this.onSellAmountChange(new TokenAmount(sellToken, parseUnits('1', sellToken.decimals).toString())) this.onBuyAmountChange(new TokenAmount(buyToken, parseUnits('1', buyToken.decimals).toString())) } diff --git a/src/services/LimitOrders/LimitOrder.ts b/src/services/LimitOrders/LimitOrder.ts index 7e188c2ae..5a4bc5776 100644 --- a/src/services/LimitOrders/LimitOrder.ts +++ b/src/services/LimitOrders/LimitOrder.ts @@ -1,5 +1,5 @@ import { limitOrderConfig } from './LimitOrder.config' -import { LimitOrderChangeHandler } from './LimitOrder.types' +import { WalletData } from './LimitOrder.types' import { LimitOrderBase } from './LimitOrder.utils' export default class LimitOrder { @@ -11,7 +11,7 @@ export default class LimitOrder { this.#protocols = limitOrderConfig } - updateSigner = async (signerData: LimitOrderChangeHandler) => { + updateSigner = async (signerData: WalletData) => { console.log('LimitOrder updateSigner') await Promise.all( Object.values(this.#protocols).map(async protocol => { @@ -19,13 +19,18 @@ export default class LimitOrder { await protocol.onSignerChange(signerData) }) ) + this.#setActiveProtocol() } - getActiveProtocol = () => { - console.log('LimitOrder getactiveProtocols') + #setActiveProtocol() { + console.log('LimitOrder set Active Protocol') this.activeProtocol = this.#protocols.find( protocol => protocol.activeChainId && protocol.supportedChanins.includes(protocol.activeChainId) ) + } + + getActiveProtocol = () => { + console.log('LimitOrder get Active Protocol') return this.activeProtocol } } diff --git a/src/services/LimitOrders/LimitOrder.types.ts b/src/services/LimitOrders/LimitOrder.types.ts index 2a99a14ad..1a5f702e9 100644 --- a/src/services/LimitOrders/LimitOrder.types.ts +++ b/src/services/LimitOrders/LimitOrder.types.ts @@ -1,5 +1,5 @@ import { Web3Provider } from '@ethersproject/providers' -import { ChainId, Currency, Token } from '@swapr/sdk' +import { ChainId, Token } from '@swapr/sdk' import { LimitOrderBase } from './LimitOrder.utils' @@ -35,8 +35,8 @@ export enum LimitOrderIds { export type LimitOrderProviders = { [key in LimitOrderIds]: LimitOrderBase } -export interface LimitOrderChangeHandler { +export interface WalletData { account: string - activeProvider: Web3Provider + provider: Web3Provider activeChainId: ChainId } diff --git a/src/services/LimitOrders/LimitOrder.utils.ts b/src/services/LimitOrders/LimitOrder.utils.ts index 724ca83c4..0648d3fd2 100644 --- a/src/services/LimitOrders/LimitOrder.utils.ts +++ b/src/services/LimitOrders/LimitOrder.utils.ts @@ -3,7 +3,7 @@ import { ChainId, Currency, Token, TokenAmount } from '@swapr/sdk' import { parseUnits } from 'ethers/lib/utils' -import { LimitOrderBaseConstructor, LimitOrderChangeHandler, OrderExpiresInUnit, Kind } from './LimitOrder.types' +import { LimitOrderBaseConstructor, WalletData, OrderExpiresInUnit, Kind } from './LimitOrder.types' export abstract class LimitOrderBase { limitOrder: any @@ -48,8 +48,8 @@ export abstract class LimitOrderBase { #log = (message: string) => `LimitOrder:: ${this.limitOrderProtocol} : ${message}` logger = { - log: (message: string) => console.log(this.#log(message)), - error: (message: string) => console.error(this.#log(message)), + log: (message: string, ...props: any[]) => console.log(this.#log(message), ...props), + error: (message: string, ...props: any[]) => console.error(this.#log(message), ...props), } getTokenFromCurrency(currency: Currency): Token { @@ -60,10 +60,10 @@ export abstract class LimitOrderBase { return token } - setSignerData = async ({ account, activeChainId, activeProvider }: LimitOrderChangeHandler) => { + setSignerData = async ({ account, activeChainId, provider }: WalletData) => { this.userAddress = account this.activeChainId = activeChainId - this.provider = activeProvider + this.provider = provider this.logger.log(`Signer data set for ${this.limitOrderProtocol}`) } @@ -79,7 +79,7 @@ export abstract class LimitOrderBase { abstract getQuote(): Promise abstract getMarketPrice(): Promise abstract setToMarket(sellPricePercentage: number, buyPricePercentage: number): Promise - abstract onSignerChange({ account, activeChainId, activeProvider }: LimitOrderChangeHandler): Promise + abstract onSignerChange({ account, activeChainId, provider }: WalletData): Promise abstract approve(): Promise abstract createOrder(): Promise } From f8266761041e9c11b4150a201b14bd1b295237fc Mon Sep 17 00:00:00 2001 From: Wixzi Date: Mon, 19 Jun 2023 09:51:26 +0200 Subject: [PATCH 17/37] [SWA-46] Optimize MarketPriceButton --- .../MarketPriceButton.tsx | 53 ++++--------------- .../OrderLimitPriceField.tsx | 5 +- 2 files changed, 11 insertions(+), 47 deletions(-) diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/MarketPriceButton.tsx b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/MarketPriceButton.tsx index 290e54b41..d6eaed39a 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/MarketPriceButton.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/MarketPriceButton.tsx @@ -1,47 +1,14 @@ -import { Currency } from '@swapr/sdk' - -import { memo } from 'react' import { useTranslation } from 'react-i18next' -import styled from 'styled-components' - -import { ReactComponent as ProgressCircle } from '../../../../../assets/images/progress-circle.svg' - -import { MarketPrice } from './styles' - -const StyledProgressCircle = styled(ProgressCircle)` - width: 12px; - height: 12px; - margin-left: 4px; - transform: rotate(-90deg); - - .move { - stroke-dasharray: 100; - stroke-dashoffset: 100; - animation: dash 15s 0s infinite linear forwards; - @keyframes dash { - to { - stroke-dashoffset: 0; - } - } - } -` +import { MarketPrice, StyledProgressCircle } from './styles' -export const MarketPriceButton = memo( - ({ - buyTokenAmountCurrency: _, - sellTokenAmountCurrency: __, - }: { - buyTokenAmountCurrency: Currency - sellTokenAmountCurrency: Currency - }) => { - const { t } = useTranslation('swap') +export const MarketPriceButton = () => { + const { t } = useTranslation('swap') - return ( - - {t('limitOrder.marketPrice')} - - - ) - } -) + return ( + + {t('limitOrder.marketPrice')} + + + ) +} diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx index 425bbab81..df1d09f27 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx @@ -196,10 +196,7 @@ export function OrderLimitPriceField({ )} {fetchMarketPrice && buyAmount?.currency && sellAmount?.currency ? ( - + ) : ( {t('limitOrder.getMarketPrice')} )} From 00c7954d3de75655884d573f21884242d15d08c6 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Mon, 19 Jun 2023 09:52:24 +0200 Subject: [PATCH 18/37] [SWA-46] Fix MarketPrice Styles --- .../Components/OrderLimitPriceField/styles.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/styles.ts b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/styles.ts index ba7825c3c..324e9e43d 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/styles.ts +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/styles.ts @@ -1,6 +1,8 @@ import { Flex } from 'rebass' import styled from 'styled-components' +import { ReactComponent as ProgressCircle } from '../../../../../assets/images/progress-circle.svg' + export const LimitLabel = styled.label` font-weight: 600; font-size: 10px; @@ -77,3 +79,22 @@ export const ToggleCurrencyButton = styled.span` export const MarketPriceDiff = styled.span<{ isPositive: boolean }>` color: ${({ isPositive, theme }) => (isPositive ? theme.green2 : theme.red1)}; ` + +export const StyledProgressCircle = styled(ProgressCircle)` + width: 12px; + height: 12px; + margin-left: 4px; + transform: rotate(-90deg); + + .move { + stroke-dasharray: 100; + stroke-dashoffset: 100; + animation: dash 15s 0s infinite linear forwards; + + @keyframes dash { + to { + stroke-dashoffset: 0; + } + } + } +` From 6f91adc107e204d97de699e218f029718f780b2a Mon Sep 17 00:00:00 2001 From: Wixzi Date: Mon, 19 Jun 2023 16:12:10 +0200 Subject: [PATCH 19/37] [SWA-46] Add LimitOder type --- src/services/LimitOrders/CoW/CoW.ts | 5 +- src/services/LimitOrders/LimitOrder.types.ts | 57 +++++++++++++++++++- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index 94a9d7d10..d7e809dd6 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -53,6 +53,7 @@ export class CoW extends LimitOrderBase { ...this.limitOrder, buyAmount, } + this.logger.log(`Buy Amount Change ${this.sellAmount.raw.toString()}`) } @@ -87,7 +88,7 @@ export class CoW extends LimitOrderBase { if (!sellToken || !buyToken) { return } - let tokenAmount = this.kind === 'sell' ? this.sellAmount : this.buyAmount + let tokenAmount = this.kind === Kind.Sell ? this.sellAmount : this.buyAmount if (!tokenAmount) { return } @@ -113,7 +114,7 @@ export class CoW extends LimitOrderBase { const signer = this.provider?.getSigner() const chainId = this.activeChainId const order = limitOrder ?? this.limitOrder - const expiresAt = dayjs().add(this.expiresAt, 'minutes').unix() + const expiresAt = dayjs().add(this.expiresAt, this.expiresAtUnit).unix() const kind = this.kind if (!signer || !chainId || !limitOrder || !expiresAt || !kind) { throw new Error('Missing required params') diff --git a/src/services/LimitOrders/LimitOrder.types.ts b/src/services/LimitOrders/LimitOrder.types.ts index 1a5f702e9..288eb22b4 100644 --- a/src/services/LimitOrders/LimitOrder.types.ts +++ b/src/services/LimitOrders/LimitOrder.types.ts @@ -13,8 +13,8 @@ export enum OrderExpiresInUnit { Days = 'days', } export enum Kind { - Buy = 'buy', - Sell = 'sell', + Buy = 'Buy', + Sell = 'Sell', } export interface LimitOrderBaseConstructor { @@ -40,3 +40,56 @@ export interface WalletData { provider: Web3Provider activeChainId: ChainId } + +type EVMAddress = string + +export interface LimitOrder { + /** + * The user Address. + */ + userAddress: EVMAddress + /** + * receiver Address. + */ + receiverAddress: EVMAddress + /** + * The sell token Address. The sellToken cannot be native token of the network. + */ + sellToken: EVMAddress + /** + * The buy token address. The native token of the network is represented by `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` + */ + buyToken: EVMAddress + /** + * The sell amount. + */ + sellAmount: string + /** + * The buy amount. + */ + buyAmount: string + /** + * Fee amount. + */ + feeAmount: string + /** + * The buy amount. + */ + limitPrice: string + /** + * Order timestamp as epoh seconds. + */ + createdAt: number + /** + * Order expiration time in seconds. + */ + expiresAt: number + /** + * Order kind + */ + kind: Kind + /** + * Quote Id + */ + quoteId?: number | null +} From 42f7424df16b4218fb47d53977dd8b436cc29db2 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Fri, 23 Jun 2023 00:16:33 +0200 Subject: [PATCH 20/37] [SWA-46] Fix quote price for buy and sell token --- src/pages/Swap/LimitOrder/LimitOrderForm.tsx | 53 ++++- src/pages/Swap/LimitOrder/api/cow.ts | 17 +- src/services/LimitOrders/1Inch/OneInch.ts | 17 -- src/services/LimitOrders/CoW/CoW.constants.ts | 12 + src/services/LimitOrders/CoW/CoW.ts | 207 +++++++++++++----- src/services/LimitOrders/CoW/CoW.types.ts | 11 + .../LimitOrders/LimitOrder.provider.tsx | 7 +- src/services/LimitOrders/LimitOrder.ts | 10 +- src/services/LimitOrders/LimitOrder.utils.ts | 35 +-- 9 files changed, 257 insertions(+), 112 deletions(-) create mode 100644 src/services/LimitOrders/CoW/CoW.constants.ts create mode 100644 src/services/LimitOrders/CoW/CoW.types.ts diff --git a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx index 520cc7bac..0947b6525 100644 --- a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx @@ -1,6 +1,6 @@ import { Currency, Token, TokenAmount } from '@swapr/sdk' -import { formatUnits, parseUnits } from 'ethers/lib/utils' +import { parseUnits } from 'ethers/lib/utils' import { useContext, useEffect, useState } from 'react' import { Flex } from 'rebass' @@ -16,7 +16,7 @@ import SwapTokens from './Components/SwapTokens' export default function LimitOrderForm() { const protocol = useContext(LimitOrderContext) - + console.log('protocol', protocol) const [fetchMarketPrice, setFetchMarketPrice] = useState(true) // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -27,6 +27,7 @@ export default function LimitOrderForm() { const [sellToken, setSellToken] = useState(protocol.sellToken) const [buyToken, setBuyToken] = useState(protocol.buyToken) + const [loading, setLoading] = useState(protocol.loading) const [kind, setKind] = useState(protocol?.kind || Kind.Sell) @@ -35,9 +36,14 @@ export default function LimitOrderForm() { setBuyToken(protocol.buyToken) setSellAmount(protocol.sellAmount) setBuyAmount(protocol.buyAmount) + protocol.getMarketPrice() // eslint-disable-next-line react-hooks/exhaustive-deps }, [protocol.activeChainId]) + useEffect(() => { + setLoading(protocol.loading) + }, [protocol.loading]) + useEffect(() => { protocol?.onKindChange(kind) }, [protocol, kind]) @@ -55,32 +61,57 @@ export default function LimitOrderForm() { }, [protocol, buyToken]) useEffect(() => { + async function getMarketPrices() { + if (!protocol || !sellAmount) return + if (kind === Kind.Sell) { + setLoading(true) + await protocol.getMarketPrice() + setBuyAmount(protocol.buyAmount) + } + } + if (sellAmount) { protocol?.onSellAmountChange(sellAmount) + getMarketPrices() } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [protocol, sellAmount]) useEffect(() => { + async function getMarketPrices() { + if (!protocol || !buyAmount) return + if (kind === Kind.Buy) { + setLoading(true) + await protocol.getMarketPrice() + setSellAmount(protocol.sellAmount) + } + } + if (buyAmount) { protocol?.onBuyAmountChange(buyAmount) + getMarketPrices() } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [protocol, buyAmount]) const [marketPrices, setMarketPrices] = useState({ buy: 0, sell: 0 }) + // TODO: REMOVE THIS USEEFFECT useEffect(() => { - async function getMarketPrices() { + async function _getMarketPrices() { if (!protocol) return const { kind } = protocol + setLoading(true) const amount = await protocol.getMarketPrice() if (kind === Kind.Sell) { + // TODO: MOVE THIS LOGIC setMarketPrices(marketPrice => ({ ...marketPrice, buy: amount })) } else { setMarketPrices(marketPrice => ({ ...marketPrice, sell: amount })) } } - getMarketPrices() + // getMarketPrices() }, [protocol, protocol?.kind]) return ( @@ -88,9 +119,8 @@ export default function LimitOrderForm() { { setSellToken(protocol.getTokenFromCurrency(currency)) }} + currencyOmitList={[buyToken.address]} /> { setSellToken(buyToken) setBuyToken(sellToken) }} - loading={false} + loading={loading} /> { setBuyToken(protocol.getTokenFromCurrency(currency)) diff --git a/src/pages/Swap/LimitOrder/api/cow.ts b/src/pages/Swap/LimitOrder/api/cow.ts index 684b3eaf2..c0c056f89 100644 --- a/src/pages/Swap/LimitOrder/api/cow.ts +++ b/src/pages/Swap/LimitOrder/api/cow.ts @@ -5,7 +5,8 @@ import contractNetworks from '@cowprotocol/contracts/networks.json' import { OrderKind as CoWOrderKind } from '@cowprotocol/cow-sdk' import type { UnsignedOrder } from '@cowprotocol/cow-sdk/dist/utils/sign' -import { LimitOrderKind, SerializableLimitOrder, SerializableSignedLimitOrder } from '../../LimitOrderBox/interfaces' +import { Kind, LimitOrder } from '../../../../services/LimitOrders' +import { SignedLimitOrder } from '../../../../services/LimitOrders/CoW/CoW.types' import cowAppData from '../generated/cow-app-data/app-data.json' // import { LimitOrderKind, SerializableLimitOrder, SerializableSignedLimitOrder } from '../interfaces' @@ -22,7 +23,7 @@ export function getAppDataIPFSHash(chainId: number): string { } export interface SignLimitOrderParams { - order: SerializableLimitOrder + order: LimitOrder signer: Signer chainId: number } @@ -44,7 +45,7 @@ export async function getQuote({ order, signer, chainId }: GetLimitOrderQuotePar let cowQuote: Awaited> - if (kind === LimitOrderKind.BUY) { + if (kind === Kind.Buy) { cowQuote = await cowSdk.cowApi.getQuote({ appData: getAppDataIPFSHash(chainId), buyAmountAfterFee: buyAmount, @@ -76,11 +77,7 @@ export async function getQuote({ order, signer, chainId }: GetLimitOrderQuotePar /** * Signs a limit order to produce a EIP712-compliant signature */ -export async function signLimitOrder({ - order, - signer, - chainId, -}: SignLimitOrderParams): Promise { +export async function signLimitOrder({ order, signer, chainId }: SignLimitOrderParams): Promise { const cowSdk = CoWTrade.getCowSdk(chainId, { signer, appDataHash: getAppDataIPFSHash(chainId), @@ -97,7 +94,7 @@ export async function signLimitOrder({ feeAmount, // from CoW APIs receiver: receiverAddress, // the account that will receive the order validTo: expiresAt, - kind: kind === LimitOrderKind.BUY ? CoWOrderKind.BUY : CoWOrderKind.SELL, + kind: kind === Kind.Buy ? CoWOrderKind.BUY : CoWOrderKind.SELL, partiallyFillable: false, }) @@ -127,7 +124,7 @@ export async function createCoWLimitOrder({ order, signer, chainId }: GetLimitOr feeAmount: '0', // from CoW APIs receiver: order.receiverAddress, // the account that will receive the order validTo: order.expiresAt, - kind: order.kind === LimitOrderKind.BUY ? CoWOrderKind.BUY : CoWOrderKind.SELL, + kind: order.kind === Kind.Buy ? CoWOrderKind.BUY : CoWOrderKind.SELL, partiallyFillable: false, } diff --git a/src/services/LimitOrders/1Inch/OneInch.ts b/src/services/LimitOrders/1Inch/OneInch.ts index cb6cd826f..4b73acb9a 100644 --- a/src/services/LimitOrders/1Inch/OneInch.ts +++ b/src/services/LimitOrders/1Inch/OneInch.ts @@ -20,34 +20,18 @@ export class OneInch extends LimitOrderBase { onSellTokenChange(sellToken: Currency) { this.sellToken = this.getTokenFromCurrency(sellToken) - this.limitOrder = { - ...this.limitOrder, - sellToken: this.sellToken, - } } onBuyTokenChange(buyToken: Currency) { this.buyToken = this.getTokenFromCurrency(buyToken) - this.limitOrder = { - ...this.limitOrder, - buyToken: this.buyToken, - } } onSellAmountChange(sellAmount: TokenAmount) { this.sellAmount = sellAmount - this.limitOrder = { - ...this.limitOrder, - sellAmount: this.sellAmount, - } } onBuyAmountChange(buyAmount: TokenAmount) { this.buyAmount = buyAmount - this.limitOrder = { - ...this.limitOrder, - buyAmount: this.buyAmount, - } } onLimitOrderChange(limitOrder: any): void { @@ -55,7 +39,6 @@ export class OneInch extends LimitOrderBase { } onExpireChange(expiresAt: number): void { - // TODO: Convert expiresAt based on expiresAtUnit this.expiresAt = expiresAt } diff --git a/src/services/LimitOrders/CoW/CoW.constants.ts b/src/services/LimitOrders/CoW/CoW.constants.ts new file mode 100644 index 000000000..95a25a98b --- /dev/null +++ b/src/services/LimitOrders/CoW/CoW.constants.ts @@ -0,0 +1,12 @@ +import { ChainId, DAI, USDT, WETH, WXDAI } from '@swapr/sdk' + +export const DefaultTokens = { + [ChainId.MAINNET]: { + sellToken: WETH[ChainId.MAINNET], + buyToken: DAI[ChainId.MAINNET], + }, + [ChainId.GNOSIS]: { + sellToken: WXDAI[ChainId.GNOSIS], + buy: USDT[ChainId.GNOSIS], + }, +} diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index d7e809dd6..7151bc403 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -5,9 +5,11 @@ import { formatUnits, parseUnits } from 'ethers/lib/utils' import { getQuote } from '../../../pages/Swap/LimitOrder/api/cow' import { getDefaultTokens } from '../LimitOrder.config' -import { Kind, WalletData, OrderExpiresInUnit, ProtocolContructor } from '../LimitOrder.types' +import { Kind, WalletData, OrderExpiresInUnit, ProtocolContructor, LimitOrder } from '../LimitOrder.types' import { LimitOrderBase } from '../LimitOrder.utils' +import { type CoWQuote } from './CoW.types' + export class CoW extends LimitOrderBase { constructor({ supportedChains, protocol, sellToken, buyToken }: ProtocolContructor) { super({ @@ -22,52 +24,48 @@ export class CoW extends LimitOrderBase { onSellTokenChange(sellToken: Currency) { this.sellToken = this.getTokenFromCurrency(sellToken) - this.limitOrder = { - ...this.limitOrder, - sellToken: this.sellToken, - } + this.onLimitOrderChange({ + sellToken: this.sellToken.address, + }) this.logger.log(`Sell Token Change ${this.sellToken.symbol}`) } onBuyTokenChange(buyToken: Currency) { this.buyToken = this.getTokenFromCurrency(buyToken) - this.limitOrder = { - ...this.limitOrder, - buyToken: this.buyToken, - } + + this.onLimitOrderChange({ + buyToken: this.buyToken.address, + }) this.logger.log(`Buy Token Change ${this.buyToken.symbol}`) } onSellAmountChange(sellAmount: TokenAmount) { this.sellAmount = sellAmount - this.limitOrder = { - ...this.limitOrder, - sellAmount, - } + this.onLimitOrderChange({ + sellAmount: sellAmount.raw.toString(), + }) this.logger.log(`Sell Amount Change ${this.sellAmount.raw.toString()}`) } onBuyAmountChange(buyAmount: TokenAmount) { this.buyAmount = buyAmount - this.limitOrder = { - ...this.limitOrder, - buyAmount, - } + this.onLimitOrderChange({ + buyAmount: buyAmount.raw.toString(), + }) this.logger.log(`Buy Amount Change ${this.sellAmount.raw.toString()}`) } - onLimitOrderChange(limitOrder: any): void { - this.limitOrder = limitOrder + onLimitOrderChange(limitOrder: Partial): void { + const fullLimitOrder = this.#getLimitOrder(limitOrder) + this.limitOrder = fullLimitOrder } onExpireChange(expiresAt: number): void { - // TODO: Convert expiresAt based on expiresAtUnit this.expiresAt = expiresAt - this.limitOrder = { - ...this.limitOrder, - expiresAt, - } + this.onLimitOrderChange({ + expiresAt: dayjs().add(expiresAt, this.expiresAtUnit).unix(), + }) } onExpireUnitChange(unit: OrderExpiresInUnit): void { @@ -76,10 +74,9 @@ export class CoW extends LimitOrderBase { onKindChange(kind: Kind): void { this.kind = kind - this.limitOrder = { - ...this.limitOrder, + this.onLimitOrderChange({ kind, - } + }) } async setToMarket(_sellPricePercentage: number, _buyPricePercentage: number): Promise { @@ -95,40 +92,48 @@ export class CoW extends LimitOrderBase { const validatedTokenAmount = Number(tokenAmount.toExact()) > 1 ? tokenAmount.toExact() : '1' const sellAmount = parseUnits(validatedTokenAmount, tokenAmount.currency.decimals).toString() - const limitOrder = { - ...this.limitOrder, - sellAmount, - } + if (this.limitOrder !== undefined) { + const limitOrder = { + ...this.limitOrder, + sellAmount, + } - await this.getQuote(limitOrder) - if (!this.quote) { - throw new Error('No quote') - } - const { buyAmount: buyAmountQuote, sellAmount: sellAmountQuote } = this.quote + await this.getQuote(limitOrder) + if (!this.quote) { + throw new Error('No quote') + } + const { + quote: { buyAmount: buyAmountQuote, sellAmount: sellAmountQuote }, + } = this.quote as CoWQuote - this.logger.log('buyAmountQuote', buyAmountQuote) - this.logger.log('sellAmountQuote', sellAmountQuote) + this.logger.log('buyAmountQuote', buyAmountQuote) + this.logger.log('sellAmountQuote', sellAmountQuote) + } } - async getQuote(limitOrder?: any): Promise { + async getQuote(limitOrder?: LimitOrder): Promise { const signer = this.provider?.getSigner() const chainId = this.activeChainId const order = limitOrder ?? this.limitOrder - const expiresAt = dayjs().add(this.expiresAt, this.expiresAtUnit).unix() const kind = this.kind - if (!signer || !chainId || !limitOrder || !expiresAt || !kind) { + if (!signer || !chainId || !order || !kind) { throw new Error('Missing required params') } - const cowQuote = await getQuote({ - chainId, - signer, - order: { ...order, expiresAt }, - }) - - this.quote = cowQuote + try { + const cowQuote = await getQuote({ + chainId, + signer, + order: { ...order }, + }) + this.quote = cowQuote as CoWQuote + } catch (error: any) { + this.logger.error(error.message) + } finally { + this.loading = false + } } - async onSignerChange({ activeChainId }: WalletData) { + async onSignerChange({ activeChainId, account }: WalletData) { const { sellToken, buyToken } = getDefaultTokens(activeChainId) // Setting default tokens for ChainId's this.onSellTokenChange(sellToken) @@ -136,6 +141,16 @@ export class CoW extends LimitOrderBase { // Setting default amounts for Tokens this.onSellAmountChange(new TokenAmount(sellToken, parseUnits('1', sellToken.decimals).toString())) this.onBuyAmountChange(new TokenAmount(buyToken, parseUnits('1', buyToken.decimals).toString())) + this.onLimitOrderChange({ + kind: this.kind, + expiresAt: this.expiresAt, + sellToken: this.sellToken.address, + buyToken: this.buyToken.address, + sellAmount: this.sellAmount.raw.toString(), + buyAmount: this.buyAmount.raw.toString(), + userAddress: account, + receiverAddress: account, + }) } approve(): Promise { @@ -149,6 +164,7 @@ export class CoW extends LimitOrderBase { const { buyToken, sellToken, provider, limitOrder, kind, activeChainId } = this if (buyToken && sellToken && provider && limitOrder && activeChainId) { + this.loading = true const order = structuredClone(limitOrder) const tokenAmountSelected = kind === Kind.Sell ? this.sellAmount : this.buyAmount const tokenSelected = kind === Kind.Sell ? sellToken : buyToken @@ -158,18 +174,107 @@ export class CoW extends LimitOrderBase { order.sellAmount = parseUnits(tokenAmount, tokenSelected.decimals).toString() - await this.getQuote({ ...order, expiresAt: dayjs().add(20, OrderExpiresInUnit.Minutes).unix() }) + await this.getQuote({ ...order, expiresAt: dayjs().add(this.expiresAt, OrderExpiresInUnit.Minutes).unix() }) - const { buyAmount, sellAmount } = this.quote + const { + quote: { buyAmount, sellAmount }, + } = this.quote as CoWQuote if (kind === Kind.Sell) { + this.onBuyAmountChange(new TokenAmount(buyToken, buyAmount)) return this.#formatMarketPrice(buyAmount, buyToken.decimals, tokenAmount) } else { + this.onSellAmountChange(new TokenAmount(sellToken, sellAmount)) return this.#formatMarketPrice(sellAmount, sellToken.decimals, tokenAmount) } } return 0 } + #getLimitOrder(props: Partial): LimitOrder { + const order = { + ...this.limitOrder, + } + if (props.sellToken !== undefined) { + order.sellToken = props.sellToken + } + if (props.buyToken !== undefined) { + order.buyToken = props.buyToken + } + if (props.sellAmount !== undefined) { + order.sellAmount = props.sellAmount + } + if (props.buyAmount !== undefined) { + order.buyAmount = props.buyAmount + } + + if (props.userAddress !== undefined) { + order.userAddress = props.userAddress + } + if (props.receiverAddress !== undefined) { + order.receiverAddress = props.receiverAddress + } + if (props.kind !== undefined) { + order.kind = props.kind + } + if (props.expiresAt !== undefined) { + order.expiresAt = props.expiresAt + } + if (props.limitPrice !== undefined) { + order.limitPrice = props.limitPrice + } + + if (props.feeAmount !== undefined) { + order.feeAmount = props.feeAmount + } + if (props.createdAt !== undefined) { + order.createdAt = props.createdAt + } + + const { + sellToken = this.sellToken.address, + buyToken = this.buyToken.address, + sellAmount = this.sellAmount.raw.toString() ?? '1000000000000000000', + buyAmount = this.buyAmount.raw.toString() ?? '1000000000000000000', + userAddress = this.userAddress, + receiverAddress = this.userAddress, + kind = this.kind, + expiresAt = dayjs().add(this.expiresAt, OrderExpiresInUnit.Minutes).unix(), + limitPrice = '1', + feeAmount = '0', + createdAt = dayjs().unix(), + } = order + + if ( + !sellToken || + !buyToken || + !sellAmount || + !buyAmount || + !userAddress || + !receiverAddress || + !kind || + !expiresAt || + !limitPrice || + !feeAmount || + !createdAt + ) { + throw new Error('Missing properties in Limit Order params') + } + + return { + sellToken, + buyToken, + sellAmount, + buyAmount, + userAddress, + receiverAddress, + kind, + expiresAt, + limitPrice, + feeAmount, + createdAt, + } as LimitOrder + } + #formatMarketPrice(amount: string, decimals: number, tokenAmount: string) { return parseFloat(formatUnits(amount, decimals) ?? 0) / Number(tokenAmount) } diff --git a/src/services/LimitOrders/CoW/CoW.types.ts b/src/services/LimitOrders/CoW/CoW.types.ts new file mode 100644 index 000000000..39a469ad6 --- /dev/null +++ b/src/services/LimitOrders/CoW/CoW.types.ts @@ -0,0 +1,11 @@ +import { SigningResult } from '@cowprotocol/cow-sdk/dist/utils/sign' + +import { getQuote } from '../../../pages/Swap/LimitOrder/api/cow' +import { LimitOrder } from '../LimitOrder.types' + +export interface SignedLimitOrder extends LimitOrder { + signature: string + signingScheme: SigningResult['signingScheme'] +} + +export type CoWQuote = Awaited> diff --git a/src/services/LimitOrders/LimitOrder.provider.tsx b/src/services/LimitOrders/LimitOrder.provider.tsx index 09e842899..d8e2d3291 100644 --- a/src/services/LimitOrders/LimitOrder.provider.tsx +++ b/src/services/LimitOrders/LimitOrder.provider.tsx @@ -1,16 +1,17 @@ import { ReactNode, createContext, useEffect, useState } from 'react' -import { LimitOrderBase } from './LimitOrder.utils' +import { LimitOrderBase, logger } from './LimitOrder.utils' export const LimitOrderContext = createContext({} as LimitOrderBase) +// TODO: May be don't need a provider. Need to verify at end and remove if not needed export function LimitOrderProvider({ children, protocol }: { children: ReactNode; protocol: LimitOrderBase }) { const [value, setProtocol] = useState(protocol) useEffect(() => { - // console.log('protocol change', protocol) + logger('Limit Order Protocol Changed') setProtocol(protocol) - }, [protocol, setProtocol]) + }, [protocol]) return {children} } diff --git a/src/services/LimitOrders/LimitOrder.ts b/src/services/LimitOrders/LimitOrder.ts index 5a4bc5776..633639631 100644 --- a/src/services/LimitOrders/LimitOrder.ts +++ b/src/services/LimitOrders/LimitOrder.ts @@ -1,18 +1,18 @@ import { limitOrderConfig } from './LimitOrder.config' import { WalletData } from './LimitOrder.types' -import { LimitOrderBase } from './LimitOrder.utils' +import { LimitOrderBase, logger } from './LimitOrder.utils' export default class LimitOrder { #protocols: LimitOrderBase[] activeProtocol?: LimitOrderBase constructor() { - console.log('LimitOrder constructor') + logger('LimitOrder constructor') this.#protocols = limitOrderConfig } updateSigner = async (signerData: WalletData) => { - console.log('LimitOrder updateSigner') + logger('LimitOrder updateSigner') await Promise.all( Object.values(this.#protocols).map(async protocol => { await protocol.setSignerData(signerData) @@ -23,14 +23,14 @@ export default class LimitOrder { } #setActiveProtocol() { - console.log('LimitOrder set Active Protocol') + logger('LimitOrder set Active Protocol') this.activeProtocol = this.#protocols.find( protocol => protocol.activeChainId && protocol.supportedChanins.includes(protocol.activeChainId) ) } getActiveProtocol = () => { - console.log('LimitOrder get Active Protocol') + logger('LimitOrder get Active Protocol') return this.activeProtocol } } diff --git a/src/services/LimitOrders/LimitOrder.utils.ts b/src/services/LimitOrders/LimitOrder.utils.ts index 0648d3fd2..0dade6c44 100644 --- a/src/services/LimitOrders/LimitOrder.utils.ts +++ b/src/services/LimitOrders/LimitOrder.utils.ts @@ -1,13 +1,18 @@ import { Web3Provider } from '@ethersproject/providers' import { ChainId, Currency, Token, TokenAmount } from '@swapr/sdk' -import { parseUnits } from 'ethers/lib/utils' +import { formatUnits, parseUnits } from 'ethers/lib/utils' -import { LimitOrderBaseConstructor, WalletData, OrderExpiresInUnit, Kind } from './LimitOrder.types' +import { CoWQuote } from './CoW/CoW.types' +import { LimitOrderBaseConstructor, WalletData, OrderExpiresInUnit, Kind, LimitOrder } from './LimitOrder.types' + +export const logger = (message?: any, ...optionalParams: any[]) => { + process.env.NODE_ENV === 'development' && console.log(message, ...optionalParams) +} export abstract class LimitOrderBase { - limitOrder: any - quote: any + limitOrder: LimitOrder | undefined + quote: CoWQuote | unknown userAddress: string | undefined receiverAddres: string | undefined sellToken: Token @@ -25,6 +30,7 @@ export abstract class LimitOrderBase { limitOrderProtocol: 'CoW' | '1inch' supportedChanins: ChainId[] activeChainId: ChainId | undefined + loading: boolean = false constructor({ protocol, supportedChains, kind, expiresAt, sellToken, buyToken }: LimitOrderBaseConstructor) { this.limitOrderProtocol = protocol @@ -35,21 +41,13 @@ export abstract class LimitOrderBase { this.buyToken = buyToken this.sellAmount = new TokenAmount(sellToken, parseUnits('1', sellToken.decimals).toString()) this.buyAmount = new TokenAmount(buyToken, parseUnits('1', buyToken.decimals).toString()) - this.limitOrder = { - kind: kind, - expiresAt: expiresAt, - sellToken: sellToken, - buyToken: buyToken, - sellAmount: this.sellAmount, - buyAmount: this.buyAmount, - } } - #log = (message: string) => `LimitOrder:: ${this.limitOrderProtocol} : ${message}` + #logFormat = (message: string) => `LimitOrder:: ${this.limitOrderProtocol} : ${message}` logger = { - log: (message: string, ...props: any[]) => console.log(this.#log(message), ...props), - error: (message: string, ...props: any[]) => console.error(this.#log(message), ...props), + log: (message: string, ...props: any[]) => logger(this.#logFormat(message), ...props), + error: (message: string, ...props: any[]) => logger(this.#logFormat(message), ...props), } getTokenFromCurrency(currency: Currency): Token { @@ -60,6 +58,13 @@ export abstract class LimitOrderBase { return token } + getFormattedAmount = (amount: TokenAmount) => { + const rawAmount = formatUnits(amount.raw.toString(), amount.currency.decimals) || '0' + const formattedAmount = parseFloat(rawAmount).toFixed(6) + if (Number(formattedAmount) === 0) return Number(formattedAmount).toString() + return formattedAmount.replace(/\.?0+$/, '') + } + setSignerData = async ({ account, activeChainId, provider }: WalletData) => { this.userAddress = account this.activeChainId = activeChainId From f87db3895e9e0f539a156ff221e142cce7c5fd75 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Fri, 23 Jun 2023 18:15:46 +0200 Subject: [PATCH 21/37] [SWA-46] Fix quote price and fix Swap Tokens --- .../OrderLimitPriceField.tsx | 67 ++++--- .../Components/OrderLimitPriceField/utils.ts | 60 ++++++ src/pages/Swap/LimitOrder/LimitOrderForm.tsx | 180 ++++++++++-------- src/pages/Swap/LimitOrder/api/cow.ts | 53 +++--- src/services/LimitOrders/1Inch/OneInch.ts | 8 +- src/services/LimitOrders/CoW/CoW.ts | 114 +++++++---- src/services/LimitOrders/LimitOrder.utils.ts | 3 +- 7 files changed, 313 insertions(+), 172 deletions(-) create mode 100644 src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/utils.ts diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx index df1d09f27..f3866f1da 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx @@ -18,6 +18,7 @@ import { SwapTokenWrapper, ToggleCurrencyButton, } from './styles' +import { computeNewAmount } from './utils' const invalidChars = ['-', '+', 'e'] @@ -50,6 +51,9 @@ export interface OrderLimitPriceFieldProps { limitOrder?: any sellToken: Currency buyToken: Currency + setSellAmount(t: TokenAmount): void + setBuyAmount(t: TokenAmount): void + setKind(t: Kind): void } export function OrderLimitPriceField({ @@ -63,10 +67,13 @@ export function OrderLimitPriceField({ limitOrder = {}, sellToken, buyToken, + setSellAmount, + setBuyAmount, + setKind, }: OrderLimitPriceFieldProps) { const { t } = useTranslation('swap') - const [formattedLimitPrice, setFormattedLimitPrice] = useState(0) + const [formattedLimitPrice, setFormattedLimitPrice] = useState(marketPrices.buy.toFixed(6)) const [{ baseToken, baseTokenAmount: _b, quoteToken, quoteTokenAmount: _q }, setBaseQuoteTokens] = useState( getBaseQuoteTokens({ sellAmount, buyAmount, kind, sellToken, buyToken }) @@ -115,21 +122,25 @@ export function OrderLimitPriceField({ const nextLimitPriceFloat = quoteTokenAmountAsFloat / baseTokenAmountAsFloat const nextLimitPriceFormatted = nextLimitPriceFloat.toFixed(6) // 6 is the lowest precision we support due to tokens like USDC - const nextLimitPriceWei = parseUnits(nextLimitPriceFormatted, quoteTokenAmount?.currency?.decimals).toString() - - protocol.onLimitOrderChange({ - ...limitOrder, - kind, - limitPrice: nextLimitPriceWei, - }) + //const nextLimitPriceWei = parseUnits(nextLimitPriceFormatted, quoteTokenAmount?.currency?.decimals).toString() + + setKind(kindSelected) + protocol.onLimitPriceChange(nextLimitPriceFormatted) + // protocol.onLimitOrderChange({ + // ...limitOrder, + // kind, + // limitPrice: nextLimitPriceFormatted, + // }) // update the formatted limit price setFormattedLimitPrice(nextLimitPriceFormatted) - }, [kind, sellAmount, buyAmount, protocol, limitOrder]) + setInputLimitPrice(nextLimitPriceFormatted) + }, [kind, sellAmount, buyAmount, setKind, protocol]) /** * Handle the limit price input change. Compute the buy amount and update the state. */ const onChangeHandler: React.ChangeEventHandler = event => { + const [_baseTokenAmount, quoteTokenAmount] = kind === Kind.Sell ? [sellAmount, buyAmount] : [buyAmount, sellAmount] // Parse the limit price const nextLimitPriceFormatted = event.target.value // When the limit price is empty, set the limit price to 0 if (nextLimitPriceFormatted.split('.').length > 2) { @@ -150,32 +161,32 @@ export function OrderLimitPriceField({ setInputLimitPrice(nextLimitPriceFormatted) // When price is below or equal to 0, set the limit price to 0, but don't update the state - // const { amount, buyAmountWei, sellAmountWei, newBuyTokenAmount, newSellTokenAmount } = computeNewAmount( - // buyAmount, - // sellAmount, - // nextLimitPriceFloat, - // limitOrder.kind, - // inputFocus - // ) + const { buyAmountWei, sellAmountWei, newBuyTokenAmount, newSellTokenAmount } = computeNewAmount( + buyAmount, + sellAmount, + nextLimitPriceFloat, + limitOrder.kind + // inputFocus + ) setFormattedLimitPrice(nextLimitPriceFormatted) if (kind === Kind.Sell) { - // setBuyTokenAmount(newBuyTokenAmount) + setBuyAmount(newBuyTokenAmount) // setFormattedBuyAmount(amount.toString()) - // setLimitOrder({ - // ...limitOrder, - // limitPrice: parseUnits(nextLimitPriceFormatted, quoteTokenAmount?.currency?.decimals).toString(), - // buyAmount: buyAmountWei, - // }) + protocol.onLimitOrderChange({ + ...limitOrder, + limitPrice: parseUnits(nextLimitPriceFormatted, quoteTokenAmount?.currency?.decimals).toString(), + buyAmount: buyAmountWei, + }) } else { - // setSellTokenAmount(newSellTokenAmount) + setSellAmount(newSellTokenAmount) // setFormattedSellAmount(amount.toString()) - // setLimitOrder({ - // ...limitOrder, - // limitPrice: parseUnits(nextLimitPriceFormatted, quoteTokenAmount?.currency?.decimals).toString(), - // sellAmount: sellAmountWei, - // }) + protocol.onLimitOrderChange({ + ...limitOrder, + limitPrice: parseUnits(nextLimitPriceFormatted, quoteTokenAmount?.currency?.decimals).toString(), + sellAmount: sellAmountWei, + }) } setFetchMarketPrice(false) diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/utils.ts b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/utils.ts new file mode 100644 index 000000000..1646658ed --- /dev/null +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/utils.ts @@ -0,0 +1,60 @@ +import { Token, TokenAmount } from '@swapr/sdk' + +import { formatUnits, parseUnits } from 'ethers/lib/utils' + +import { Kind } from '../../../../../services/LimitOrders' + +export interface IComputeNewAmount { + amount: number + buyAmountWei: string + sellAmountWei: string + newBuyTokenAmount: TokenAmount + newSellTokenAmount: TokenAmount +} + +const multiplyPrice = (tokenAmount: number, limitPrice: number) => tokenAmount * limitPrice +const dividePrice = (tokenAmount: number, limitPrice: number) => (limitPrice === 0 ? 0 : tokenAmount / limitPrice) + +const newAmountCalculationSellLimitOrder: Record = { + [Kind.Sell]: multiplyPrice, + [Kind.Buy]: dividePrice, +} +const newAmountCalculationBuyLimitOrder: Record = { + [Kind.Sell]: dividePrice, + [Kind.Buy]: multiplyPrice, +} + +export const computeNewAmount = ( + buyTokenAmount: TokenAmount, + sellTokenAmount: TokenAmount, + limitPrice: number, + limitOrderKind: Kind + // inputFocus: InputFocus +): IComputeNewAmount => { + const buyAmountFloat = parseFloat(formatUnits(buyTokenAmount.raw.toString(), buyTokenAmount.currency.decimals)) + const sellAmountFloat = parseFloat(formatUnits(sellTokenAmount.raw.toString(), sellTokenAmount.currency.decimals)) + + let amount = 0 + let newBuyTokenAmount = buyTokenAmount + let newSellTokenAmount = sellTokenAmount + let buyAmountWei = '0' + let sellAmountWei = '0' + + if (limitOrderKind === Kind.Sell) { + amount = newAmountCalculationSellLimitOrder[limitOrderKind](sellAmountFloat, limitPrice) + buyAmountWei = parseUnits(amount.toFixed(6), buyTokenAmount?.currency?.decimals).toString() + newBuyTokenAmount = new TokenAmount(buyTokenAmount.currency as Token, buyAmountWei) + } else { + amount = newAmountCalculationBuyLimitOrder[limitOrderKind](buyAmountFloat, limitPrice) + sellAmountWei = parseUnits(amount.toFixed(6), sellTokenAmount?.currency?.decimals).toString() + newSellTokenAmount = new TokenAmount(sellTokenAmount.currency as Token, sellAmountWei) + } + + return { + amount, + buyAmountWei, + sellAmountWei, + newBuyTokenAmount, + newSellTokenAmount, + } +} diff --git a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx index 0947b6525..170a45d9d 100644 --- a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx @@ -1,7 +1,7 @@ import { Currency, Token, TokenAmount } from '@swapr/sdk' import { parseUnits } from 'ethers/lib/utils' -import { useContext, useEffect, useState } from 'react' +import { useCallback, useContext, useEffect, useState } from 'react' import { Flex } from 'rebass' import { AutoColumn } from '../../../components/Column' @@ -16,27 +16,30 @@ import SwapTokens from './Components/SwapTokens' export default function LimitOrderForm() { const protocol = useContext(LimitOrderContext) - console.log('protocol', protocol) const [fetchMarketPrice, setFetchMarketPrice] = useState(true) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [sellAmount, setSellAmount] = useState(protocol.sellAmount) - - // eslint-disable-next-line @typescript-eslint/no-unused-vars const [buyAmount, setBuyAmount] = useState(protocol.buyAmount) + const [sellAmount, setSellAmount] = useState(protocol.sellAmount) const [sellToken, setSellToken] = useState(protocol.sellToken) const [buyToken, setBuyToken] = useState(protocol.buyToken) const [loading, setLoading] = useState(protocol.loading) const [kind, setKind] = useState(protocol?.kind || Kind.Sell) + const [marketPrices, setMarketPrices] = useState({ buy: 0, sell: 0 }) useEffect(() => { + async function getMarketPrice() { + const amount = await protocol.getMarketPrice() + setBuyAmount(protocol.buyAmount) + setMarketPrices(marketPrice => ({ ...marketPrice, buy: amount })) + setLoading(false) + } setSellToken(protocol.sellToken) setBuyToken(protocol.buyToken) setSellAmount(protocol.sellAmount) setBuyAmount(protocol.buyAmount) - protocol.getMarketPrice() + getMarketPrice() // eslint-disable-next-line react-hooks/exhaustive-deps }, [protocol.activeChainId]) @@ -48,71 +51,96 @@ export default function LimitOrderForm() { protocol?.onKindChange(kind) }, [protocol, kind]) - useEffect(() => { - if (sellToken) { - protocol?.onSellTokenChange(sellToken) - } - }, [protocol, sellToken]) + const handleSellTokenChange = useCallback(async (currency: Currency, _isMaxAmount?: boolean) => { + setLoading(true) + const newSellToken = protocol.getTokenFromCurrency(currency) + setSellToken(newSellToken) + protocol?.onSellTokenChange(newSellToken) + protocol?.onKindChange(Kind.Sell) + setKind(Kind.Sell) - useEffect(() => { - if (buyToken) { - protocol?.onBuyTokenChange(buyToken) - } - }, [protocol, buyToken]) + await protocol.getQuote() - useEffect(() => { - async function getMarketPrices() { - if (!protocol || !sellAmount) return - if (kind === Kind.Sell) { - setLoading(true) - await protocol.getMarketPrice() - setBuyAmount(protocol.buyAmount) - } - } + setBuyAmount(protocol.buyAmount) + setLoading(false) + // setMarketPrices(marketPrice => ({ ...marketPrice, buy: amount })) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + const handleBuyTokenChange = useCallback(async (currency: Currency, _isMaxAmount?: boolean) => { + setLoading(true) + const newBuyToken = protocol.getTokenFromCurrency(currency) + setBuyToken(newBuyToken) + protocol?.onBuyTokenChange(newBuyToken) + protocol?.onKindChange(Kind.Buy) + setKind(Kind.Buy) + + await protocol.getQuote() + + setSellAmount(protocol.sellAmount) + setLoading(false) + // setMarketPrices(marketPrice => ({ ...marketPrice, sell: amount })) - if (sellAmount) { - protocol?.onSellAmountChange(sellAmount) - getMarketPrices() - } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [protocol, sellAmount]) + }, []) - useEffect(() => { - async function getMarketPrices() { - if (!protocol || !buyAmount) return - if (kind === Kind.Buy) { - setLoading(true) - await protocol.getMarketPrice() - setSellAmount(protocol.sellAmount) - } - } + const handleSellAmountChange = useCallback(async (value: string) => { + if (value.trim() !== '' && value !== '0') { + const amountWei = parseUnits(value, sellToken.decimals).toString() + const newSellAmount = new TokenAmount(sellToken, amountWei) + + setLoading(true) + + protocol?.onSellAmountChange(newSellAmount) + setSellAmount(newSellAmount) + + protocol?.onKindChange(Kind.Sell) + setKind(Kind.Sell) - if (buyAmount) { - protocol?.onBuyAmountChange(buyAmount) - getMarketPrices() + await protocol.getQuote() + + setBuyAmount(protocol.buyAmount) + setLoading(false) + // setMarketPrices(marketPrice => ({ ...marketPrice, buy: amount })) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [protocol, buyAmount]) + }, []) - const [marketPrices, setMarketPrices] = useState({ buy: 0, sell: 0 }) + const handleBuyAmountChange = useCallback(async (value: string) => { + if (value.trim() !== '' && value !== '0') { + const amountWei = parseUnits(value, buyToken.decimals).toString() + const newBuyAmount = new TokenAmount(buyToken, amountWei) - // TODO: REMOVE THIS USEEFFECT - useEffect(() => { - async function _getMarketPrices() { - if (!protocol) return - const { kind } = protocol setLoading(true) - const amount = await protocol.getMarketPrice() - if (kind === Kind.Sell) { - // TODO: MOVE THIS LOGIC - setMarketPrices(marketPrice => ({ ...marketPrice, buy: amount })) - } else { - setMarketPrices(marketPrice => ({ ...marketPrice, sell: amount })) - } + protocol?.onBuyAmountChange(newBuyAmount) + setBuyAmount(newBuyAmount) + + protocol?.onKindChange(Kind.Buy) + setKind(Kind.Buy) + + await protocol.getQuote() + + setSellAmount(protocol.sellAmount) + setLoading(false) + // setMarketPrices(marketPrice => ({ ...marketPrice, sell: amount })) } - // getMarketPrices() - }, [protocol, protocol?.kind]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + const handleSwap = useCallback(async () => { + setLoading(true) + protocol.onSellTokenChange(buyToken) + protocol.onBuyTokenChange(sellToken) + setSellToken(buyToken) + setBuyToken(sellToken) + setKind(Kind.Sell) + + await protocol.getQuote() + + setBuyAmount(protocol.buyAmount) + setLoading(false) + }, [sellToken, buyToken, protocol]) return ( @@ -120,39 +148,24 @@ export default function LimitOrderForm() { { - setSellToken(protocol.getTokenFromCurrency(currency)) - }} + onCurrencySelect={handleSellTokenChange} currencyOmitList={[buyToken.address]} /> - { - setSellToken(buyToken) - setBuyToken(sellToken) - }} - loading={loading} - /> + { + console.log('onMax') }} - currencyOmitList={[sellToken.address]} showNativeCurrency={false} - onCurrencySelect={(currency: Currency, _isMaxAmount?: boolean) => { - setBuyToken(protocol.getTokenFromCurrency(currency)) - }} + onCurrencySelect={handleBuyTokenChange} + currencyOmitList={[sellToken.address]} /> @@ -168,6 +181,9 @@ export default function LimitOrderForm() { buyAmount={buyAmount} kind={kind} limitOrder={protocol.limitOrder} + setSellAmount={setSellAmount} + setBuyAmount={setBuyAmount} + setKind={setKind} /> diff --git a/src/pages/Swap/LimitOrder/api/cow.ts b/src/pages/Swap/LimitOrder/api/cow.ts index c0c056f89..799e00ce4 100644 --- a/src/pages/Swap/LimitOrder/api/cow.ts +++ b/src/pages/Swap/LimitOrder/api/cow.ts @@ -26,6 +26,7 @@ export interface SignLimitOrderParams { order: LimitOrder signer: Signer chainId: number + signal: AbortSignal } type GetLimitOrderQuoteParams = SignLimitOrderParams @@ -34,7 +35,7 @@ type GetLimitOrderQuoteParams = SignLimitOrderParams * Fetches a quote from the CoW API * @returns */ -export async function getQuote({ order, signer, chainId }: GetLimitOrderQuoteParams) { +export async function getQuote({ order, signer, chainId, signal }: GetLimitOrderQuoteParams) { const { buyToken, receiverAddress, userAddress, expiresAt, sellAmount, sellToken, kind, buyAmount } = order // const cowSdk = getCoWSdk(chainId, signer) @@ -46,29 +47,35 @@ export async function getQuote({ order, signer, chainId }: GetLimitOrderQuotePar let cowQuote: Awaited> if (kind === Kind.Buy) { - cowQuote = await cowSdk.cowApi.getQuote({ - appData: getAppDataIPFSHash(chainId), - buyAmountAfterFee: buyAmount, - buyToken, - from: userAddress, - kind: CoWOrderKind.BUY, - partiallyFillable: false, - receiver: receiverAddress, - sellToken, - validTo: expiresAt, - }) + cowQuote = await cowSdk.cowApi.getQuote( + { + appData: getAppDataIPFSHash(chainId), + buyAmountAfterFee: buyAmount, + buyToken, + from: userAddress, + kind: CoWOrderKind.BUY, + partiallyFillable: false, + receiver: receiverAddress, + sellToken, + validTo: expiresAt, + }, + { requestOptions: { signal } } + ) } else { - cowQuote = await cowSdk.cowApi.getQuote({ - appData: getAppDataIPFSHash(chainId), - buyToken, - sellAmountBeforeFee: sellAmount, - from: userAddress, - kind: CoWOrderKind.SELL, - partiallyFillable: false, - receiver: receiverAddress, - sellToken, - validTo: expiresAt, - }) + cowQuote = await cowSdk.cowApi.getQuote( + { + appData: getAppDataIPFSHash(chainId), + buyToken, + sellAmountBeforeFee: sellAmount, + from: userAddress, + kind: CoWOrderKind.SELL, + partiallyFillable: false, + receiver: receiverAddress, + sellToken, + validTo: expiresAt, + }, + { requestOptions: { signal } } + ) } return cowQuote diff --git a/src/services/LimitOrders/1Inch/OneInch.ts b/src/services/LimitOrders/1Inch/OneInch.ts index 4b73acb9a..56f5437ca 100644 --- a/src/services/LimitOrders/1Inch/OneInch.ts +++ b/src/services/LimitOrders/1Inch/OneInch.ts @@ -50,10 +50,14 @@ export class OneInch extends LimitOrderBase { this.kind = kind } - setToMarket(): Promise { - throw new Error('Method not implemented.') + onLimitPriceChange(limitPrice: string): void { + this.limitPrice = limitPrice } + // setToMarket(): Promise { + // throw new Error('Method not implemented.') + // } + getQuote(): Promise { throw new Error('Method not implemented.') } diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index 7151bc403..694aa3c41 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -22,8 +22,26 @@ export class CoW extends LimitOrderBase { }) } + #abortControllers: { [id: string]: AbortController } = {} + + #renewAbortController = (key: string) => { + console.log(this.#abortControllers[key]) + if (this.#abortControllers[key]) { + this.#abortControllers[key].abort() + } + + this.#abortControllers[key] = new AbortController() + + return this.#abortControllers[key].signal + } + onSellTokenChange(sellToken: Currency) { this.sellToken = this.getTokenFromCurrency(sellToken) + let exactAmount = this.sellAmount.toExact() + const sellAmountinWei = parseUnits(Number(exactAmount).toFixed(6), this.sellToken.decimals).toString() + const sellAmount = new TokenAmount(this.sellToken, sellAmountinWei) + this.onSellAmountChange(sellAmount) + this.onLimitOrderChange({ sellToken: this.sellToken.address, }) @@ -32,6 +50,10 @@ export class CoW extends LimitOrderBase { onBuyTokenChange(buyToken: Currency) { this.buyToken = this.getTokenFromCurrency(buyToken) + let exactAmount = this.buyAmount.toExact() + const buyAmountinWei = parseUnits(Number(exactAmount).toFixed(6), this.buyToken.decimals).toString() + const buyAmount = new TokenAmount(this.buyToken, buyAmountinWei) + this.onBuyAmountChange(buyAmount) this.onLimitOrderChange({ buyToken: this.buyToken.address, @@ -79,38 +101,42 @@ export class CoW extends LimitOrderBase { }) } - async setToMarket(_sellPricePercentage: number, _buyPricePercentage: number): Promise { - const sellToken = this.sellToken - const buyToken = this.buyToken - if (!sellToken || !buyToken) { - return - } - let tokenAmount = this.kind === Kind.Sell ? this.sellAmount : this.buyAmount - if (!tokenAmount) { - return - } - const validatedTokenAmount = Number(tokenAmount.toExact()) > 1 ? tokenAmount.toExact() : '1' - const sellAmount = parseUnits(validatedTokenAmount, tokenAmount.currency.decimals).toString() - - if (this.limitOrder !== undefined) { - const limitOrder = { - ...this.limitOrder, - sellAmount, - } - - await this.getQuote(limitOrder) - if (!this.quote) { - throw new Error('No quote') - } - const { - quote: { buyAmount: buyAmountQuote, sellAmount: sellAmountQuote }, - } = this.quote as CoWQuote - - this.logger.log('buyAmountQuote', buyAmountQuote) - this.logger.log('sellAmountQuote', sellAmountQuote) - } + onLimitPriceChange(limitPrice: string): void { + this.limitPrice = limitPrice + this.onLimitOrderChange({ + limitPrice, + }) } + // async setToMarket(_sellPricePercentage: number, _buyPricePercentage: number): Promise { + // const sellToken = this.sellToken + // const buyToken = this.buyToken + // if (!sellToken || !buyToken) { + // return + // } + // let tokenAmount = this.kind === Kind.Sell ? this.sellAmount : this.buyAmount + // if (!tokenAmount) { + // return + // } + // const validatedTokenAmount = Number(tokenAmount.toExact()) > 1 ? tokenAmount.toExact() : '1' + // const sellAmount = parseUnits(validatedTokenAmount, tokenAmount.currency.decimals).toString() + // if (this.limitOrder !== undefined) { + // const limitOrder = { + // ...this.limitOrder, + // sellAmount, + // } + // await this.getQuote(limitOrder) + // if (!this.quote) { + // throw new Error('No quote') + // } + // const { + // quote: { buyAmount: buyAmountQuote, sellAmount: sellAmountQuote }, + // } = this.quote as CoWQuote + // this.logger.log('buyAmountQuote', buyAmountQuote) + // this.logger.log('sellAmountQuote', sellAmountQuote) + // } + // } + async getQuote(limitOrder?: LimitOrder): Promise { const signer = this.provider?.getSigner() const chainId = this.activeChainId @@ -119,17 +145,35 @@ export class CoW extends LimitOrderBase { if (!signer || !chainId || !order || !kind) { throw new Error('Missing required params') } + if (order.sellAmount !== this.sellAmount.raw.toString()) { + order.sellAmount = this.sellAmount.raw.toString() + } + if (order.buyAmount !== this.buyAmount.raw.toString()) { + order.buyAmount = this.buyAmount.raw.toString() + } + if (order.kind !== this.kind && this.kind) { + order.kind = this.kind + } + try { const cowQuote = await getQuote({ chainId, signer, - order: { ...order }, + order: { ...order, expiresAt: dayjs().add(this.expiresAt, OrderExpiresInUnit.Minutes).unix() }, + signal: this.#renewAbortController('getQuote'), }) this.quote = cowQuote as CoWQuote + const { + quote: { buyAmount, sellAmount }, + } = cowQuote + if (kind === Kind.Sell) { + this.onBuyAmountChange(new TokenAmount(this.buyToken, buyAmount)) + } else { + this.onSellAmountChange(new TokenAmount(this.sellToken, sellAmount)) + } } catch (error: any) { + // TODO: SHOW ERROR in UI this.logger.error(error.message) - } finally { - this.loading = false } } @@ -174,16 +218,14 @@ export class CoW extends LimitOrderBase { order.sellAmount = parseUnits(tokenAmount, tokenSelected.decimals).toString() - await this.getQuote({ ...order, expiresAt: dayjs().add(this.expiresAt, OrderExpiresInUnit.Minutes).unix() }) + await this.getQuote(order) const { quote: { buyAmount, sellAmount }, } = this.quote as CoWQuote if (kind === Kind.Sell) { - this.onBuyAmountChange(new TokenAmount(buyToken, buyAmount)) return this.#formatMarketPrice(buyAmount, buyToken.decimals, tokenAmount) } else { - this.onSellAmountChange(new TokenAmount(sellToken, sellAmount)) return this.#formatMarketPrice(sellAmount, sellToken.decimals, tokenAmount) } } diff --git a/src/services/LimitOrders/LimitOrder.utils.ts b/src/services/LimitOrders/LimitOrder.utils.ts index 0dade6c44..38199d3c1 100644 --- a/src/services/LimitOrders/LimitOrder.utils.ts +++ b/src/services/LimitOrders/LimitOrder.utils.ts @@ -80,10 +80,11 @@ export abstract class LimitOrderBase { abstract onExpireChange(expiresAt: number): void abstract onExpireUnitChange(unit: OrderExpiresInUnit): void abstract onKindChange(kind: Kind): void + abstract onLimitPriceChange(limitPrice: string): void abstract getQuote(): Promise abstract getMarketPrice(): Promise - abstract setToMarket(sellPricePercentage: number, buyPricePercentage: number): Promise + // abstract setToMarket(sellPricePercentage: number, buyPricePercentage: number): Promise abstract onSignerChange({ account, activeChainId, provider }: WalletData): Promise abstract approve(): Promise abstract createOrder(): Promise From ed8674854083e86234bc439b2c05486b8539f7fd Mon Sep 17 00:00:00 2001 From: Wixzi Date: Fri, 23 Jun 2023 18:33:53 +0200 Subject: [PATCH 22/37] [SWA-46] Add confirmation Modal --- .../ConfirmationFooter.tsx | 59 +++++++++ .../ConfirmationHeader.tsx | 105 ++++++++++++++++ .../ConfirmLimitOrderModal/index.tsx | 103 ++++++++++++++++ src/pages/Swap/LimitOrder/LimitOrderForm.tsx | 116 +++++++++++------- 4 files changed, 336 insertions(+), 47 deletions(-) create mode 100644 src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/ConfirmationFooter.tsx create mode 100644 src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/ConfirmationHeader.tsx create mode 100644 src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/index.tsx diff --git a/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/ConfirmationFooter.tsx b/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/ConfirmationFooter.tsx new file mode 100644 index 000000000..a4338d45b --- /dev/null +++ b/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/ConfirmationFooter.tsx @@ -0,0 +1,59 @@ +import styled, { useTheme } from 'styled-components' + +import { ButtonPrimary } from '../../../../../components/Button' +import { StyledKey, StyledValue } from '../../../Components/SwapModalFooter' + +export type FooterData = { + askPrice: string + marketPriceDifference: string + isDiffPositive: boolean + expiresIn: string + market: string + onConfirm: () => void +} + +export const ConfirmationFooter = ({ + askPrice, + onConfirm, + expiresIn, + marketPriceDifference, + isDiffPositive, + market, +}: FooterData) => { + const theme = useTheme() + const priceDiffColor = isDiffPositive ? theme.green1 : theme.red1 + + return ( + + + Ask price + {askPrice} + + + Diff. market price + {marketPriceDifference}% + + + Expires in + {expiresIn} + + + Market + {market} + + + PLACE LIMIT ORDER + + + ) +} + +const Wrapper = styled.div` + display: flex; + gap: 7px; + flex-direction: column; +` +const SingleRow = styled.div` + display: flex; + justify-content: space-between; +` diff --git a/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/ConfirmationHeader.tsx b/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/ConfirmationHeader.tsx new file mode 100644 index 000000000..e3e3d30a0 --- /dev/null +++ b/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/ConfirmationHeader.tsx @@ -0,0 +1,105 @@ +import { CurrencyAmount, TokenAmount } from '@swapr/sdk' + +import { ArrowDown } from 'react-feather' +import styled from 'styled-components' + +import { CurrencyLogo } from '../../../../../components/CurrencyLogo' +import { toFixedSix } from '../utils' + +export type HeaderData = { + fiatValueInput: CurrencyAmount | null + fiatValueOutput: CurrencyAmount | null + buyToken: TokenAmount + sellToken: TokenAmount +} + +export const ConfirmationHeader = ({ fiatValueInput, fiatValueOutput, buyToken, sellToken }: HeaderData) => { + const fiatInput = fiatValueInput && fiatValueInput.toFixed(2, { groupSeparator: ',' }) + const fiatOutput = fiatValueOutput && fiatValueOutput.toFixed(2, { groupSeparator: ',' }) + + return ( + + + + YOU SWAP + + {sellToken.currency.symbol}{' '} + + + + + {toFixedSix(Number(sellToken.toExact()))} + {fiatInput && ${fiatInput}} + + + + + + YOU RECIEVE + + {buyToken.currency.symbol}{' '} + + + + + {toFixedSix(Number(buyToken.toExact()))} + {fiatOutput && ${fiatOutput}} + + + + ) +} + +const Wrapper = styled.div` + display: flex; + flex-direction: column; + margin-top: 20px; +` +const CurrencyAmountContainer = styled.div` + display: flex; + padding: 0 19px; + align-items: center; + justify-content: space-between; + height: 82px; + border: 1px solid ${({ theme }) => theme.bg3}; + border-radius: 8.72381px; +` +const CurrencySymbol = styled.div` + font-weight: 600; + font-size: 20px; + line-height: 24px; +` +const LogoWithText = styled.div` + display: flex; + align-items: center; + gap: 6px; +` +const CurrencyLogoInfo = styled.div` + display: flex; + flex-direction: column; + gap: 8px; +` + +const AmountWithUsd = styled.div` + display: flex; + flex-direction: column; + align-items: end; + gap: 4px; +` +const Amount = styled.div` + font-weight: 600; + font-size: 20px; + line-height: 28px; +` +const PurpleText = styled.div` + font-weight: 600; + font-size: 10px; + line-height: 12px; + letter-spacing: 0.08em; + color: ${({ theme }) => theme.purple3}; +` +const StyledArrow = styled(ArrowDown)` + width: 100%; + margin: 4px 0; + height: 16px; +` diff --git a/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/index.tsx b/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/index.tsx new file mode 100644 index 000000000..79373b2cc --- /dev/null +++ b/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/index.tsx @@ -0,0 +1,103 @@ +import { CurrencyAmount } from '@swapr/sdk' + +import { useCallback, useContext } from 'react' + +import TransactionConfirmationModal, { + ConfirmationModalContent, + TransactionErrorContent, +} from '../../../../../components/TransactionConfirmationModal' +import { Kind, LimitOrderContext, MarketPrices } from '../../../../../services/LimitOrders' +import { calculateMarketPriceDiffPercentage } from '../utils' + +import { ConfirmationFooter } from './ConfirmationFooter' +import { ConfirmationHeader } from './ConfirmationHeader' + +interface ConfirmLimitOrderModalProps { + isOpen: boolean + attemptingTxn: boolean + errorMessage: string | undefined + onDismiss: () => void + onConfirm: () => void + marketPrices: MarketPrices + fiatValueInput: CurrencyAmount | null + fiatValueOutput: CurrencyAmount | null +} + +export default function ConfirmLimitOrderModal({ + onConfirm, + onDismiss, + errorMessage, + isOpen, + attemptingTxn, + marketPrices, + fiatValueInput, + fiatValueOutput, +}: ConfirmLimitOrderModalProps) { + const { limitOrder, buyAmount, sellAmount, limitPrice, expiresAt, expiresAtUnit, kind } = + useContext(LimitOrderContext) + + //hardcoded for now + const market = 'CoW Protocol' + + const modalHeader = useCallback(() => { + return ( + + ) + }, [fiatValueInput, fiatValueOutput, buyAmount, sellAmount]) + + const [baseTokenAmount, quoteTokenAmount] = kind === Kind.Sell ? [sellAmount, buyAmount] : [buyAmount, sellAmount] + const askPrice = `${kind} ${baseTokenAmount?.currency?.symbol} at ${limitPrice} ${quoteTokenAmount?.currency?.symbol}` + + let { marketPriceDiffPercentage, isDiffPositive } = calculateMarketPriceDiffPercentage( + limitOrder!.kind, + marketPrices, + limitPrice! + ) + const expiresInFormatted = `${expiresAt} ${expiresAtUnit}` + + const modalBottom = useCallback(() => { + return onConfirm ? ( + + ) : null + }, [marketPriceDiffPercentage, isDiffPositive, onConfirm, askPrice, expiresInFormatted]) + + // text to show while loading + const pendingText = 'Confirm Signature' + + const confirmationContent = useCallback( + () => + errorMessage ? ( + + ) : ( + + ), + [onDismiss, modalBottom, modalHeader, errorMessage] + ) + + return ( + + ) +} diff --git a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx index 170a45d9d..c11178c3a 100644 --- a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx @@ -4,12 +4,14 @@ import { parseUnits } from 'ethers/lib/utils' import { useCallback, useContext, useEffect, useState } from 'react' import { Flex } from 'rebass' +import { ButtonPrimary } from '../../../components/Button' import { AutoColumn } from '../../../components/Column' import { CurrencyInputPanel } from '../../../components/CurrencyInputPanel' import { Kind, MarketPrices } from '../../../services/LimitOrders' import { LimitOrderContext } from '../../../services/LimitOrders/LimitOrder.provider' import { AutoRow } from './Components/AutoRow' +import ConfirmLimitOrderModal from './Components/ConfirmLimitOrderModal' import { OrderExpiryField } from './Components/OrderExpiryField' import { OrderLimitPriceField } from './Components/OrderLimitPriceField' import SwapTokens from './Components/SwapTokens' @@ -28,6 +30,13 @@ export default function LimitOrderForm() { const [kind, setKind] = useState(protocol?.kind || Kind.Sell) const [marketPrices, setMarketPrices] = useState({ buy: 0, sell: 0 }) + const [isModalOpen, setIsModalOpen] = useState(false) + + const onModalDismiss = () => { + setIsModalOpen(false) + // setErrorMessage('') + } + useEffect(() => { async function getMarketPrice() { const amount = await protocol.getMarketPrice() @@ -143,53 +152,66 @@ export default function LimitOrderForm() { }, [sellToken, buyToken, protocol]) return ( - - - - - { - console.log('onMax') - }} - showNativeCurrency={false} - onCurrencySelect={handleBuyTokenChange} - currencyOmitList={[sellToken.address]} - /> - - - - + {}} + onDismiss={onModalDismiss} + isOpen={isModalOpen} + errorMessage={''} + attemptingTxn={loading} + marketPrices={marketPrices} + fiatValueInput={sellAmount} + fiatValueOutput={buyAmount} + /> + + + - - - - - - + + { + console.log('onMax') + }} + showNativeCurrency={false} + onCurrencySelect={handleBuyTokenChange} + currencyOmitList={[sellToken.address]} + /> + + + + + + + + + + setIsModalOpen(true)}>Place Limit Order + + ) } From 3d625e5e91b3d530ba87b0ac7e3a044d1d123eb5 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Fri, 23 Jun 2023 18:48:02 +0200 Subject: [PATCH 23/37] [SWA-46] Move CoW api to Services --- src/services/LimitOrders/CoW/CoW.ts | 2 +- src/services/LimitOrders/CoW/CoW.types.ts | 3 ++- .../Swap/LimitOrder => services/LimitOrders/CoW}/api/cow.ts | 6 +++--- .../LimitOrder => services/LimitOrders/CoW}/api/index.ts | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) rename src/{pages/Swap/LimitOrder => services/LimitOrders/CoW}/api/cow.ts (95%) rename src/{pages/Swap/LimitOrder => services/LimitOrders/CoW}/api/index.ts (98%) diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index 694aa3c41..c87017ba5 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -3,11 +3,11 @@ import { Currency, TokenAmount } from '@swapr/sdk' import dayjs from 'dayjs' import { formatUnits, parseUnits } from 'ethers/lib/utils' -import { getQuote } from '../../../pages/Swap/LimitOrder/api/cow' import { getDefaultTokens } from '../LimitOrder.config' import { Kind, WalletData, OrderExpiresInUnit, ProtocolContructor, LimitOrder } from '../LimitOrder.types' import { LimitOrderBase } from '../LimitOrder.utils' +import { getQuote } from './api/cow' import { type CoWQuote } from './CoW.types' export class CoW extends LimitOrderBase { diff --git a/src/services/LimitOrders/CoW/CoW.types.ts b/src/services/LimitOrders/CoW/CoW.types.ts index 39a469ad6..2cb90988f 100644 --- a/src/services/LimitOrders/CoW/CoW.types.ts +++ b/src/services/LimitOrders/CoW/CoW.types.ts @@ -1,8 +1,9 @@ import { SigningResult } from '@cowprotocol/cow-sdk/dist/utils/sign' -import { getQuote } from '../../../pages/Swap/LimitOrder/api/cow' import { LimitOrder } from '../LimitOrder.types' +import { getQuote } from './api/cow' + export interface SignedLimitOrder extends LimitOrder { signature: string signingScheme: SigningResult['signingScheme'] diff --git a/src/pages/Swap/LimitOrder/api/cow.ts b/src/services/LimitOrders/CoW/api/cow.ts similarity index 95% rename from src/pages/Swap/LimitOrder/api/cow.ts rename to src/services/LimitOrders/CoW/api/cow.ts index 799e00ce4..0413c34de 100644 --- a/src/pages/Swap/LimitOrder/api/cow.ts +++ b/src/services/LimitOrders/CoW/api/cow.ts @@ -5,9 +5,9 @@ import contractNetworks from '@cowprotocol/contracts/networks.json' import { OrderKind as CoWOrderKind } from '@cowprotocol/cow-sdk' import type { UnsignedOrder } from '@cowprotocol/cow-sdk/dist/utils/sign' -import { Kind, LimitOrder } from '../../../../services/LimitOrders' -import { SignedLimitOrder } from '../../../../services/LimitOrders/CoW/CoW.types' -import cowAppData from '../generated/cow-app-data/app-data.json' +import { Kind, LimitOrder } from '../..' +import cowAppData from '../../../../pages/Swap/LimitOrder/generated/cow-app-data/app-data.json' +import { SignedLimitOrder } from '../CoW.types' // import { LimitOrderKind, SerializableLimitOrder, SerializableSignedLimitOrder } from '../interfaces' export const COW_APP_DATA = cowAppData diff --git a/src/pages/Swap/LimitOrder/api/index.ts b/src/services/LimitOrders/CoW/api/index.ts similarity index 98% rename from src/pages/Swap/LimitOrder/api/index.ts rename to src/services/LimitOrders/CoW/api/index.ts index 4073053e4..f5c82707c 100644 --- a/src/pages/Swap/LimitOrder/api/index.ts +++ b/src/services/LimitOrders/CoW/api/index.ts @@ -11,7 +11,7 @@ import { ChainId, CoWTrade } from '@swapr/sdk' import { SigningScheme } from '@cowprotocol/contracts' import { OrderKind as CoWOrderKind } from '@cowprotocol/cow-sdk' -import { LimitOrderKind, SerializableSignedLimitOrder } from '../../LimitOrderBox/interfaces' +import { LimitOrderKind, SerializableSignedLimitOrder } from '../../../../pages/Swap/LimitOrderBox/interfaces' // import { LimitOrderKind, SerializableSignedLimitOrder } from '../interfaces' From c076de5675c5aea743c1d230de187a7cc347a403 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Sat, 24 Jun 2023 15:30:17 +0200 Subject: [PATCH 24/37] [SWA-72] Set default limit price and toggle it --- .../ConfirmLimitOrderModal/index.tsx | 5 +- .../OrderLimitPriceField.tsx | 102 ++++++++---------- src/services/LimitOrders/1Inch/OneInch.ts | 3 + src/services/LimitOrders/CoW/CoW.ts | 19 ++++ src/services/LimitOrders/LimitOrder.utils.ts | 2 +- 5 files changed, 69 insertions(+), 62 deletions(-) diff --git a/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/index.tsx b/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/index.tsx index 79373b2cc..5813870a2 100644 --- a/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/index.tsx +++ b/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/index.tsx @@ -33,8 +33,7 @@ export default function ConfirmLimitOrderModal({ fiatValueInput, fiatValueOutput, }: ConfirmLimitOrderModalProps) { - const { limitOrder, buyAmount, sellAmount, limitPrice, expiresAt, expiresAtUnit, kind } = - useContext(LimitOrderContext) + const { buyAmount, sellAmount, limitPrice, expiresAt, expiresAtUnit, kind } = useContext(LimitOrderContext) //hardcoded for now const market = 'CoW Protocol' @@ -54,7 +53,7 @@ export default function ConfirmLimitOrderModal({ const askPrice = `${kind} ${baseTokenAmount?.currency?.symbol} at ${limitPrice} ${quoteTokenAmount?.currency?.symbol}` let { marketPriceDiffPercentage, isDiffPositive } = calculateMarketPriceDiffPercentage( - limitOrder!.kind, + kind ?? Kind.Sell, marketPrices, limitPrice! ) diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx index f3866f1da..e9b0e10aa 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx @@ -1,13 +1,13 @@ -import { formatUnits, parseUnits } from '@ethersproject/units' +import { parseUnits } from '@ethersproject/units' import { Currency, TokenAmount } from '@swapr/sdk' -import { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react' +import { Dispatch, SetStateAction, useEffect, useState } from 'react' import { RefreshCw } from 'react-feather' import { useTranslation } from 'react-i18next' import { Kind, LimitOrderBase, MarketPrices } from '../../../../../services/LimitOrders' import { InputGroup } from '../InputGroup' -import { calculateMarketPriceDiffPercentage } from '../utils' +// eslint-disable-next-line @typescript-eslint/no-unused-vars import { MarketPriceButton } from './MarketPriceButton' import { @@ -57,6 +57,7 @@ export interface OrderLimitPriceFieldProps { } export function OrderLimitPriceField({ + // eslint-disable-next-line @typescript-eslint/no-unused-vars marketPrices, fetchMarketPrice, setFetchMarketPrice, @@ -64,18 +65,19 @@ export function OrderLimitPriceField({ sellAmount, buyAmount, kind, + // eslint-disable-next-line @typescript-eslint/no-unused-vars limitOrder = {}, sellToken, buyToken, + // eslint-disable-next-line @typescript-eslint/no-unused-vars setSellAmount, + // eslint-disable-next-line @typescript-eslint/no-unused-vars setBuyAmount, setKind, }: OrderLimitPriceFieldProps) { const { t } = useTranslation('swap') - const [formattedLimitPrice, setFormattedLimitPrice] = useState(marketPrices.buy.toFixed(6)) - - const [{ baseToken, baseTokenAmount: _b, quoteToken, quoteTokenAmount: _q }, setBaseQuoteTokens] = useState( + const [{ baseToken, quoteToken }, setBaseQuoteTokens] = useState( getBaseQuoteTokens({ sellAmount, buyAmount, kind, sellToken, buyToken }) ) @@ -83,64 +85,40 @@ export function OrderLimitPriceField({ setBaseQuoteTokens(getBaseQuoteTokens({ sellAmount, buyAmount, kind, sellToken, buyToken })) }, [sellToken, buyToken, sellAmount, buyAmount, kind]) - const inputGroupLabel = `${kind} ${baseToken?.symbol} at` + const inputGroupLabel = `${kind} 1 ${baseToken?.symbol} at` const toggleCurrencyButtonLabel = `${quoteToken?.symbol}` + const [formattedLimitPrice, setFormattedLimitPrice] = useState(protocol.getTokenLimitPrices()) const [inputLimitPrice, setInputLimitPrice] = useState(formattedLimitPrice) + const [inputFocus, setInputFocus] = useState(false) - const { marketPriceDiffPercentage, isDiffPositive } = calculateMarketPriceDiffPercentage( - kind ?? Kind.Sell, - marketPrices, - formattedLimitPrice.toString() - ) + // const { marketPriceDiffPercentage, isDiffPositive } = calculateMarketPriceDiffPercentage( + // kind ?? Kind.Sell, + // marketPrices, + // formattedLimitPrice.toString() + // ) - const showPercentage = - Number(marketPriceDiffPercentage.toFixed(1)) !== 0 && Number(marketPriceDiffPercentage) !== -100 + // const showPercentage = + // Number(marketPriceDiffPercentage.toFixed(1)) !== 0 && Number(marketPriceDiffPercentage) !== -100 + // useEffect(() => { + // setInputLimitPrice(formattedLimitPrice) + // }, [formattedLimitPrice]) + const limitPrice = protocol.getTokenLimitPrices() useEffect(() => { - setInputLimitPrice(formattedLimitPrice) - }, [formattedLimitPrice]) - - /** - * Handle the base currency change. - */ - const toggleBaseCurrency = useCallback(() => { - // Toggle between buy and sell currency - const kindSelected = kind === Kind.Sell ? Kind.Buy : Kind.Sell - - if (!sellAmount || !buyAmount) return - - const [baseTokenAmount, quoteTokenAmount] = - kindSelected === Kind.Sell ? [sellAmount, buyAmount] : [buyAmount, sellAmount] - - const baseTokenAmountAsFloat = parseFloat( - formatUnits(baseTokenAmount.raw.toString(), baseTokenAmount.currency.decimals) - ) - const quoteTokenAmountAsFloat = parseFloat( - formatUnits(quoteTokenAmount.raw.toString(), quoteTokenAmount.currency.decimals) - ) - - const nextLimitPriceFloat = quoteTokenAmountAsFloat / baseTokenAmountAsFloat - const nextLimitPriceFormatted = nextLimitPriceFloat.toFixed(6) // 6 is the lowest precision we support due to tokens like USDC - //const nextLimitPriceWei = parseUnits(nextLimitPriceFormatted, quoteTokenAmount?.currency?.decimals).toString() - - setKind(kindSelected) - protocol.onLimitPriceChange(nextLimitPriceFormatted) - // protocol.onLimitOrderChange({ - // ...limitOrder, - // kind, - // limitPrice: nextLimitPriceFormatted, - // }) - // update the formatted limit price - setFormattedLimitPrice(nextLimitPriceFormatted) - setInputLimitPrice(nextLimitPriceFormatted) - }, [kind, sellAmount, buyAmount, setKind, protocol]) + setFormattedLimitPrice(limitPrice) + if (!inputFocus) { + setInputLimitPrice(limitPrice) + } + }, [inputFocus, limitPrice]) /** * Handle the limit price input change. Compute the buy amount and update the state. */ const onChangeHandler: React.ChangeEventHandler = event => { - const [_baseTokenAmount, quoteTokenAmount] = kind === Kind.Sell ? [sellAmount, buyAmount] : [buyAmount, sellAmount] + setInputFocus(true) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [baseTokenAmount, quoteTokenAmount] = kind === Kind.Sell ? [sellAmount, buyAmount] : [buyAmount, sellAmount] // Parse the limit price const nextLimitPriceFormatted = event.target.value // When the limit price is empty, set the limit price to 0 if (nextLimitPriceFormatted.split('.').length > 2) { @@ -159,7 +137,9 @@ export function OrderLimitPriceField({ return setFormattedLimitPrice('0') } setInputLimitPrice(nextLimitPriceFormatted) - // When price is below or equal to 0, set the limit price to 0, but don't update the state + + // const + // // When price is below or equal to 0, set the limit price to 0, but don't update the state const { buyAmountWei, sellAmountWei, newBuyTokenAmount, newSellTokenAmount } = computeNewAmount( buyAmount, @@ -169,7 +149,7 @@ export function OrderLimitPriceField({ // inputFocus ) - setFormattedLimitPrice(nextLimitPriceFormatted) + // setFormattedLimitPrice(nextLimitPriceFormatted) if (kind === Kind.Sell) { setBuyAmount(newBuyTokenAmount) @@ -184,19 +164,25 @@ export function OrderLimitPriceField({ // setFormattedSellAmount(amount.toString()) protocol.onLimitOrderChange({ ...limitOrder, - limitPrice: parseUnits(nextLimitPriceFormatted, quoteTokenAmount?.currency?.decimals).toString(), + limitPrice: parseUnits(nextLimitPriceFormatted, baseTokenAmount?.currency?.decimals).toString(), sellAmount: sellAmountWei, }) } - - setFetchMarketPrice(false) } } const onClickGetMarketPrice = () => { + setInputFocus(false) setFetchMarketPrice(true) } + // const onChangeHandler = () => {} + + // TODO: fix it + const showPercentage = false + const marketPriceDiffPercentage = 0 + const isDiffPositive = false + return ( @@ -231,7 +217,7 @@ export function OrderLimitPriceField({ onChange={onChangeHandler} /> - + setKind(kind === Kind.Sell ? Kind.Buy : Kind.Sell)}> {toggleCurrencyButtonLabel} diff --git a/src/services/LimitOrders/1Inch/OneInch.ts b/src/services/LimitOrders/1Inch/OneInch.ts index 56f5437ca..94927fd92 100644 --- a/src/services/LimitOrders/1Inch/OneInch.ts +++ b/src/services/LimitOrders/1Inch/OneInch.ts @@ -82,4 +82,7 @@ export class OneInch extends LimitOrderBase { async getMarketPrice() { return 0 } + getTokenLimitPrices() { + return '0' + } } diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index c87017ba5..86129a827 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -317,6 +317,25 @@ export class CoW extends LimitOrderBase { } as LimitOrder } + getTokenLimitPrices() { + const { buyAmount, sellAmount, kind } = this + if (buyAmount && sellAmount && kind) { + const [baseAmount, quoteAmount] = kind === Kind.Sell ? [sellAmount, buyAmount] : [buyAmount, sellAmount] + const quoteAmountInUnits = parseFloat(quoteAmount.toExact()) + const baseAmountInUnits = parseFloat(baseAmount.toExact()) + if ( + !Number.isNaN(quoteAmountInUnits) && + quoteAmountInUnits > 0 && + !Number.isNaN(baseAmountInUnits) && + baseAmountInUnits > 0 + ) { + const limitPrice = parseFloat(quoteAmount.toExact()) / parseFloat(baseAmount.toExact()) + return limitPrice.toFixed(6) + } + } + return '1' + } + #formatMarketPrice(amount: string, decimals: number, tokenAmount: string) { return parseFloat(formatUnits(amount, decimals) ?? 0) / Number(tokenAmount) } diff --git a/src/services/LimitOrders/LimitOrder.utils.ts b/src/services/LimitOrders/LimitOrder.utils.ts index 38199d3c1..2853061f0 100644 --- a/src/services/LimitOrders/LimitOrder.utils.ts +++ b/src/services/LimitOrders/LimitOrder.utils.ts @@ -84,7 +84,7 @@ export abstract class LimitOrderBase { abstract getQuote(): Promise abstract getMarketPrice(): Promise - // abstract setToMarket(sellPricePercentage: number, buyPricePercentage: number): Promise + abstract getTokenLimitPrices(): string abstract onSignerChange({ account, activeChainId, provider }: WalletData): Promise abstract approve(): Promise abstract createOrder(): Promise From f714dec6eee2755f6b581545c774e55fff62821f Mon Sep 17 00:00:00 2001 From: Wixzi Date: Sun, 23 Jul 2023 16:30:14 +0200 Subject: [PATCH 25/37] [SWA-46] Input value change trigger --- .../OrderLimitPriceField.tsx | 77 +++++----- src/pages/Swap/LimitOrder/LimitOrderForm.tsx | 50 +++--- src/services/LimitOrders/1Inch/OneInch.ts | 9 +- src/services/LimitOrders/CoW/CoW.ts | 143 +++++++++++++----- src/services/LimitOrders/LimitOrder.utils.ts | 5 +- 5 files changed, 179 insertions(+), 105 deletions(-) diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx index e9b0e10aa..13b38b0d8 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx @@ -1,11 +1,11 @@ import { parseUnits } from '@ethersproject/units' import { Currency, TokenAmount } from '@swapr/sdk' -import { Dispatch, SetStateAction, useEffect, useState } from 'react' +import { useEffect, useState } from 'react' import { RefreshCw } from 'react-feather' import { useTranslation } from 'react-i18next' -import { Kind, LimitOrderBase, MarketPrices } from '../../../../../services/LimitOrders' +import { Kind, LimitOrderBase } from '../../../../../services/LimitOrders' import { InputGroup } from '../InputGroup' // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -41,9 +41,6 @@ const getBaseQuoteTokens = ({ } export interface OrderLimitPriceFieldProps { - marketPrices: MarketPrices - fetchMarketPrice: boolean - setFetchMarketPrice: Dispatch> protocol: LimitOrderBase sellAmount: TokenAmount buyAmount: TokenAmount @@ -57,21 +54,14 @@ export interface OrderLimitPriceFieldProps { } export function OrderLimitPriceField({ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - marketPrices, - fetchMarketPrice, - setFetchMarketPrice, protocol, sellAmount, buyAmount, kind, - // eslint-disable-next-line @typescript-eslint/no-unused-vars limitOrder = {}, sellToken, buyToken, - // eslint-disable-next-line @typescript-eslint/no-unused-vars setSellAmount, - // eslint-disable-next-line @typescript-eslint/no-unused-vars setBuyAmount, setKind, }: OrderLimitPriceFieldProps) { @@ -88,10 +78,13 @@ export function OrderLimitPriceField({ const inputGroupLabel = `${kind} 1 ${baseToken?.symbol} at` const toggleCurrencyButtonLabel = `${quoteToken?.symbol}` - const [formattedLimitPrice, setFormattedLimitPrice] = useState(protocol.getTokenLimitPrices()) - const [inputLimitPrice, setInputLimitPrice] = useState(formattedLimitPrice) - const [inputFocus, setInputFocus] = useState(false) + const [formattedLimitPrice, setFormattedLimitPrice] = useState(protocol.limitPrice) + const [inputLimitPrice, setInputLimitPrice] = useState(formattedLimitPrice) + // const [inputFocus, setInputFocus] = useState(false) + useEffect(() => { + setInputLimitPrice(protocol.limitPrice) + }, [protocol.limitPrice]) // const { marketPriceDiffPercentage, isDiffPositive } = calculateMarketPriceDiffPercentage( // kind ?? Kind.Sell, // marketPrices, @@ -104,20 +97,20 @@ export function OrderLimitPriceField({ // useEffect(() => { // setInputLimitPrice(formattedLimitPrice) // }, [formattedLimitPrice]) - const limitPrice = protocol.getTokenLimitPrices() - useEffect(() => { - setFormattedLimitPrice(limitPrice) - if (!inputFocus) { - setInputLimitPrice(limitPrice) - } - }, [inputFocus, limitPrice]) + // const limitPrice = protocol.getTokenLimitPrices() + // useEffect(() => { + // setFormattedLimitPrice(limitPrice) + // if (!inputFocus) { + // setInputLimitPrice(limitPrice) + // } + // }, [inputFocus, limitPrice]) /** * Handle the limit price input change. Compute the buy amount and update the state. */ const onChangeHandler: React.ChangeEventHandler = event => { - setInputFocus(true) - // eslint-disable-next-line @typescript-eslint/no-unused-vars + protocol.onUserUpadtedLimitPrice(true) + const [baseTokenAmount, quoteTokenAmount] = kind === Kind.Sell ? [sellAmount, buyAmount] : [buyAmount, sellAmount] // Parse the limit price const nextLimitPriceFormatted = event.target.value // When the limit price is empty, set the limit price to 0 @@ -129,16 +122,20 @@ export function OrderLimitPriceField({ setInputLimitPrice(nextLimitPriceFormatted) } else if (nextLimitPriceFormatted === '.') { setInputLimitPrice('0.') - return setFormattedLimitPrice('0') + setFormattedLimitPrice('0') + return } else { const nextLimitPriceFloat = parseFloat(nextLimitPriceFormatted ?? 0) if (nextLimitPriceFloat < 0 || isNaN(nextLimitPriceFloat)) { - setInputLimitPrice(0) - return setFormattedLimitPrice('0') + setInputLimitPrice('0') + setFormattedLimitPrice('0') + return } setInputLimitPrice(nextLimitPriceFormatted) + setFormattedLimitPrice(nextLimitPriceFormatted) + protocol.onLimitPriceChange(nextLimitPriceFormatted) + protocol.onUserUpadtedLimitPrice(true) - // const // // When price is below or equal to 0, set the limit price to 0, but don't update the state const { buyAmountWei, sellAmountWei, newBuyTokenAmount, newSellTokenAmount } = computeNewAmount( @@ -149,10 +146,9 @@ export function OrderLimitPriceField({ // inputFocus ) - // setFormattedLimitPrice(nextLimitPriceFormatted) - if (kind === Kind.Sell) { setBuyAmount(newBuyTokenAmount) + protocol.onBuyAmountChange(newBuyTokenAmount) // setFormattedBuyAmount(amount.toString()) protocol.onLimitOrderChange({ ...limitOrder, @@ -161,6 +157,7 @@ export function OrderLimitPriceField({ }) } else { setSellAmount(newSellTokenAmount) + protocol.onSellAmountChange(newSellTokenAmount) // setFormattedSellAmount(amount.toString()) protocol.onLimitOrderChange({ ...limitOrder, @@ -171,12 +168,20 @@ export function OrderLimitPriceField({ } } - const onClickGetMarketPrice = () => { - setInputFocus(false) - setFetchMarketPrice(true) - } + const onClickGetMarketPrice = async () => { + // setInputFocus(false) + // setFetchMarketPrice(true) + protocol.onUserUpadtedLimitPrice(false) - // const onChangeHandler = () => {} + await protocol.getQuote() + if (kind === Kind.Sell) { + setBuyAmount(protocol.buyAmount) + } else { + setSellAmount(protocol.sellAmount) + } + const limitPrice = protocol.getTokenLimitPrices() + setInputLimitPrice(limitPrice) + } // TODO: fix it const showPercentage = false @@ -192,7 +197,7 @@ export function OrderLimitPriceField({ ({marketPriceDiffPercentage.toFixed(2)}%) )} - {fetchMarketPrice && buyAmount?.currency && sellAmount?.currency ? ( + {!protocol.userUpdatedLimitPrice && buyAmount?.currency && sellAmount?.currency ? ( ) : ( {t('limitOrder.getMarketPrice')} diff --git a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx index c11178c3a..4c6b446a0 100644 --- a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx @@ -18,7 +18,7 @@ import SwapTokens from './Components/SwapTokens' export default function LimitOrderForm() { const protocol = useContext(LimitOrderContext) - const [fetchMarketPrice, setFetchMarketPrice] = useState(true) + // const [fetchMarketPrice, setFetchMarketPrice] = useState(true) const [buyAmount, setBuyAmount] = useState(protocol.buyAmount) const [sellAmount, setSellAmount] = useState(protocol.sellAmount) @@ -28,7 +28,8 @@ export default function LimitOrderForm() { const [loading, setLoading] = useState(protocol.loading) const [kind, setKind] = useState(protocol?.kind || Kind.Sell) - const [marketPrices, setMarketPrices] = useState({ buy: 0, sell: 0 }) + // TODO: Check the usage of marketPrices + const [marketPrices] = useState({ buy: 0, sell: 0 }) const [isModalOpen, setIsModalOpen] = useState(false) @@ -39,15 +40,16 @@ export default function LimitOrderForm() { useEffect(() => { async function getMarketPrice() { - const amount = await protocol.getMarketPrice() + await protocol.getMarketPrice() setBuyAmount(protocol.buyAmount) - setMarketPrices(marketPrice => ({ ...marketPrice, buy: amount })) + setLoading(false) } + setLoading(true) setSellToken(protocol.sellToken) setBuyToken(protocol.buyToken) setSellAmount(protocol.sellAmount) - setBuyAmount(protocol.buyAmount) + getMarketPrice() // eslint-disable-next-line react-hooks/exhaustive-deps }, [protocol.activeChainId]) @@ -64,53 +66,43 @@ export default function LimitOrderForm() { setLoading(true) const newSellToken = protocol.getTokenFromCurrency(currency) setSellToken(newSellToken) - protocol?.onSellTokenChange(newSellToken) - protocol?.onKindChange(Kind.Sell) - setKind(Kind.Sell) - await protocol.getQuote() + await protocol?.onSellTokenChange(newSellToken) setBuyAmount(protocol.buyAmount) setLoading(false) - // setMarketPrices(marketPrice => ({ ...marketPrice, buy: amount })) + // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const handleBuyTokenChange = useCallback(async (currency: Currency, _isMaxAmount?: boolean) => { setLoading(true) const newBuyToken = protocol.getTokenFromCurrency(currency) + setBuyToken(newBuyToken) - protocol?.onBuyTokenChange(newBuyToken) - protocol?.onKindChange(Kind.Buy) - setKind(Kind.Buy) - await protocol.getQuote() + await protocol?.onBuyTokenChange(newBuyToken) setSellAmount(protocol.sellAmount) setLoading(false) - // setMarketPrices(marketPrice => ({ ...marketPrice, sell: amount })) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const handleSellAmountChange = useCallback(async (value: string) => { - if (value.trim() !== '' && value !== '0') { + if (value.trim() !== '' && value.trim() !== '0') { const amountWei = parseUnits(value, sellToken.decimals).toString() const newSellAmount = new TokenAmount(sellToken, amountWei) setLoading(true) - - protocol?.onSellAmountChange(newSellAmount) setSellAmount(newSellAmount) - - protocol?.onKindChange(Kind.Sell) setKind(Kind.Sell) - await protocol.getQuote() + protocol.onKindChange(Kind.Sell) + await protocol?.onSellAmountChange(newSellAmount) setBuyAmount(protocol.buyAmount) setLoading(false) - // setMarketPrices(marketPrice => ({ ...marketPrice, buy: amount })) } // eslint-disable-next-line react-hooks/exhaustive-deps }, []) @@ -121,18 +113,14 @@ export default function LimitOrderForm() { const newBuyAmount = new TokenAmount(buyToken, amountWei) setLoading(true) - - protocol?.onBuyAmountChange(newBuyAmount) setBuyAmount(newBuyAmount) - - protocol?.onKindChange(Kind.Buy) setKind(Kind.Buy) - await protocol.getQuote() + protocol.onKindChange(Kind.Buy) + await protocol?.onBuyAmountChange(newBuyAmount) setSellAmount(protocol.sellAmount) setLoading(false) - // setMarketPrices(marketPrice => ({ ...marketPrice, sell: amount })) } // eslint-disable-next-line react-hooks/exhaustive-deps }, []) @@ -192,9 +180,9 @@ export default function LimitOrderForm() { { throw new Error('Method not implemented.') } + + onUserUpadtedLimitPrice(status: boolean) { + this.userUpdatedLimitPrice = status + } + async onSignerChange({ activeChainId }: WalletData) { const { sellToken, buyToken } = getDefaultTokens(activeChainId) this.onSellTokenChange(sellToken) @@ -79,9 +84,7 @@ export class OneInch extends LimitOrderBase { throw new Error('Method not implemented.') } - async getMarketPrice() { - return 0 - } + async getMarketPrice() {} getTokenLimitPrices() { return '0' } diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index 86129a827..d86053f97 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -1,7 +1,7 @@ import { Currency, TokenAmount } from '@swapr/sdk' import dayjs from 'dayjs' -import { formatUnits, parseUnits } from 'ethers/lib/utils' +import { parseUnits } from 'ethers/lib/utils' import { getDefaultTokens } from '../LimitOrder.config' import { Kind, WalletData, OrderExpiresInUnit, ProtocolContructor, LimitOrder } from '../LimitOrder.types' @@ -35,45 +35,98 @@ export class CoW extends LimitOrderBase { return this.#abortControllers[key].signal } - onSellTokenChange(sellToken: Currency) { + #validateAmount(num: string | number): string { + const numberValue = parseFloat(num.toString()) + return !isNaN(numberValue) && numberValue > 0 ? `${numberValue}` : '1' + } + + async onSellTokenChange(sellToken: Currency) { this.sellToken = this.getTokenFromCurrency(sellToken) - let exactAmount = this.sellAmount.toExact() + const exactAmount = this.#validateAmount(this.sellAmount.toExact()) const sellAmountinWei = parseUnits(Number(exactAmount).toFixed(6), this.sellToken.decimals).toString() const sellAmount = new TokenAmount(this.sellToken, sellAmountinWei) - this.onSellAmountChange(sellAmount) + this.#setSellAmount(sellAmount) this.onLimitOrderChange({ sellToken: this.sellToken.address, }) + this.onKindChange(Kind.Sell) + + await this.getQuote() + this.logger.log(`Sell Token Change ${this.sellToken.symbol}`) } - onBuyTokenChange(buyToken: Currency) { + async onBuyTokenChange(buyToken: Currency) { this.buyToken = this.getTokenFromCurrency(buyToken) - let exactAmount = this.buyAmount.toExact() + const exactAmount = this.#validateAmount(this.buyAmount.toExact()) const buyAmountinWei = parseUnits(Number(exactAmount).toFixed(6), this.buyToken.decimals).toString() const buyAmount = new TokenAmount(this.buyToken, buyAmountinWei) - this.onBuyAmountChange(buyAmount) + this.#setBuyAmount(buyAmount) this.onLimitOrderChange({ buyToken: this.buyToken.address, }) + + await this.getQuote() + this.logger.log(`Buy Token Change ${this.buyToken.symbol}`) } - onSellAmountChange(sellAmount: TokenAmount) { + async #onAmountChange() { + if (this.buyToken === undefined || this.sellToken === undefined) return + + if (Number(this.limitPrice?.toString().trim()) > 0) { + return + } + if ( + (this.kind === Kind.Sell && Number(this.sellAmount.toExact()) > 0) || + (this.kind === Kind.Buy && Number(this.buyAmount.toExact()) > 0) + ) { + await this.getQuote() + } + } + + #setSellAmount(sellAmount: TokenAmount) { this.sellAmount = sellAmount + this.onLimitOrderChange({ sellAmount: sellAmount.raw.toString(), }) + } + + async onSellAmountChange(sellAmount: TokenAmount) { + this.#setSellAmount(sellAmount) + + await this.#onAmountChange() + + if (this.limitPrice && Number(this.limitPrice) > 0) { + const buyAmount = parseFloat(this.sellAmount.toExact()) * parseFloat(this.limitPrice) + this.#setBuyAmount( + new TokenAmount(this.buyToken, parseUnits(buyAmount.toFixed(6), this.buyToken.decimals).toString()) + ) + } + this.logger.log(`Sell Amount Change ${this.sellAmount.raw.toString()}`) } - onBuyAmountChange(buyAmount: TokenAmount) { + #setBuyAmount(buyAmount: TokenAmount) { this.buyAmount = buyAmount this.onLimitOrderChange({ buyAmount: buyAmount.raw.toString(), }) + } + async onBuyAmountChange(buyAmount: TokenAmount) { + this.#setBuyAmount(buyAmount) + + await this.#onAmountChange() + + if (this.limitPrice && Number(this.limitPrice) > 0) { + const sellAmount = parseFloat(this.buyAmount.toExact()) * parseFloat(this.limitPrice) + this.#setSellAmount( + new TokenAmount(this.sellToken, parseUnits(sellAmount.toFixed(6), this.sellToken.decimals).toString()) + ) + } this.logger.log(`Buy Amount Change ${this.sellAmount.raw.toString()}`) } @@ -108,6 +161,10 @@ export class CoW extends LimitOrderBase { }) } + onUserUpadtedLimitPrice(status: boolean) { + this.userUpdatedLimitPrice = status + } + // async setToMarket(_sellPricePercentage: number, _buyPricePercentage: number): Promise { // const sellToken = this.sellToken // const buyToken = this.buyToken @@ -151,10 +208,10 @@ export class CoW extends LimitOrderBase { if (order.buyAmount !== this.buyAmount.raw.toString()) { order.buyAmount = this.buyAmount.raw.toString() } - if (order.kind !== this.kind && this.kind) { + if (this.kind && order.kind !== this.kind) { order.kind = this.kind } - + console.log('limit price', order.limitPrice) try { const cowQuote = await getQuote({ chainId, @@ -162,14 +219,26 @@ export class CoW extends LimitOrderBase { order: { ...order, expiresAt: dayjs().add(this.expiresAt, OrderExpiresInUnit.Minutes).unix() }, signal: this.#renewAbortController('getQuote'), }) + this.quote = cowQuote as CoWQuote + const { quote: { buyAmount, sellAmount }, } = cowQuote + + const buyTokenAmount = new TokenAmount(this.buyToken, buyAmount) + const sellTokenAmount = new TokenAmount(this.sellToken, sellAmount) + this.limitPrice = this.#calculateLimitPrice(buyTokenAmount, sellTokenAmount) if (kind === Kind.Sell) { - this.onBuyAmountChange(new TokenAmount(this.buyToken, buyAmount)) + this.buyAmount = buyTokenAmount + this.onLimitOrderChange({ + buyAmount: buyTokenAmount.raw.toString(), + }) } else { - this.onSellAmountChange(new TokenAmount(this.sellToken, sellAmount)) + this.sellAmount = sellTokenAmount + this.onLimitOrderChange({ + sellAmount: sellTokenAmount.raw.toString(), + }) } } catch (error: any) { // TODO: SHOW ERROR in UI @@ -183,8 +252,8 @@ export class CoW extends LimitOrderBase { this.onSellTokenChange(sellToken) this.onBuyTokenChange(buyToken) // Setting default amounts for Tokens - this.onSellAmountChange(new TokenAmount(sellToken, parseUnits('1', sellToken.decimals).toString())) - this.onBuyAmountChange(new TokenAmount(buyToken, parseUnits('1', buyToken.decimals).toString())) + this.#setSellAmount(new TokenAmount(sellToken, parseUnits('1', sellToken.decimals).toString())) + this.#setBuyAmount(new TokenAmount(buyToken, parseUnits('1', buyToken.decimals).toString())) this.onLimitOrderChange({ kind: this.kind, expiresAt: this.expiresAt, @@ -204,32 +273,32 @@ export class CoW extends LimitOrderBase { throw new Error('Method not implemented.') } + // TODO: Remove this async getMarketPrice() { const { buyToken, sellToken, provider, limitOrder, kind, activeChainId } = this if (buyToken && sellToken && provider && limitOrder && activeChainId) { this.loading = true - const order = structuredClone(limitOrder) - const tokenAmountSelected = kind === Kind.Sell ? this.sellAmount : this.buyAmount - const tokenSelected = kind === Kind.Sell ? sellToken : buyToken - const tokenAmount = - tokenAmountSelected && Number(tokenAmountSelected.toExact()) > 1 ? tokenAmountSelected.toExact() : '1' + const tokenAmountSelected = kind === Kind.Sell ? this.sellAmount : this.buyAmount - order.sellAmount = parseUnits(tokenAmount, tokenSelected.decimals).toString() + if (!(Number(tokenAmountSelected.toExact()) > 0)) { + if (kind === Kind.Sell) { + this.onSellAmountChange(new TokenAmount(sellToken, parseUnits('1', sellToken.decimals).toString())) + } else { + this.onBuyAmountChange(new TokenAmount(buyToken, parseUnits('1', buyToken.decimals).toString())) + } + } else { + const order = structuredClone(limitOrder) + const tokenSelected = kind === Kind.Sell ? sellToken : buyToken - await this.getQuote(order) + const tokenAmount = + tokenAmountSelected && Number(tokenAmountSelected.toExact()) > 0 ? tokenAmountSelected.toExact() : '1' + order.sellAmount = parseUnits(tokenAmount, tokenSelected.decimals).toString() - const { - quote: { buyAmount, sellAmount }, - } = this.quote as CoWQuote - if (kind === Kind.Sell) { - return this.#formatMarketPrice(buyAmount, buyToken.decimals, tokenAmount) - } else { - return this.#formatMarketPrice(sellAmount, sellToken.decimals, tokenAmount) + await this.getQuote(order) } } - return 0 } #getLimitOrder(props: Partial): LimitOrder { @@ -317,8 +386,14 @@ export class CoW extends LimitOrderBase { } as LimitOrder } + //TODO: Remove it later getTokenLimitPrices() { - const { buyAmount, sellAmount, kind } = this + const { buyAmount, sellAmount } = this + return this.#calculateLimitPrice(buyAmount, sellAmount) + } + + #calculateLimitPrice(buyAmount: TokenAmount, sellAmount: TokenAmount) { + const { kind } = this if (buyAmount && sellAmount && kind) { const [baseAmount, quoteAmount] = kind === Kind.Sell ? [sellAmount, buyAmount] : [buyAmount, sellAmount] const quoteAmountInUnits = parseFloat(quoteAmount.toExact()) @@ -336,7 +411,7 @@ export class CoW extends LimitOrderBase { return '1' } - #formatMarketPrice(amount: string, decimals: number, tokenAmount: string) { - return parseFloat(formatUnits(amount, decimals) ?? 0) / Number(tokenAmount) - } + // #formatMarketPrice(amount: string, decimals: number, tokenAmount: string) { + // return parseFloat(formatUnits(amount, decimals) ?? 0) / Number(tokenAmount) + // } } diff --git a/src/services/LimitOrders/LimitOrder.utils.ts b/src/services/LimitOrders/LimitOrder.utils.ts index 2853061f0..62e7edb50 100644 --- a/src/services/LimitOrders/LimitOrder.utils.ts +++ b/src/services/LimitOrders/LimitOrder.utils.ts @@ -31,6 +31,7 @@ export abstract class LimitOrderBase { supportedChanins: ChainId[] activeChainId: ChainId | undefined loading: boolean = false + userUpdatedLimitPrice: boolean constructor({ protocol, supportedChains, kind, expiresAt, sellToken, buyToken }: LimitOrderBaseConstructor) { this.limitOrderProtocol = protocol @@ -41,6 +42,7 @@ export abstract class LimitOrderBase { this.buyToken = buyToken this.sellAmount = new TokenAmount(sellToken, parseUnits('1', sellToken.decimals).toString()) this.buyAmount = new TokenAmount(buyToken, parseUnits('1', buyToken.decimals).toString()) + this.userUpdatedLimitPrice = false } #logFormat = (message: string) => `LimitOrder:: ${this.limitOrderProtocol} : ${message}` @@ -81,9 +83,10 @@ export abstract class LimitOrderBase { abstract onExpireUnitChange(unit: OrderExpiresInUnit): void abstract onKindChange(kind: Kind): void abstract onLimitPriceChange(limitPrice: string): void + abstract onUserUpadtedLimitPrice(status: boolean): void abstract getQuote(): Promise - abstract getMarketPrice(): Promise + abstract getMarketPrice(): Promise abstract getTokenLimitPrices(): string abstract onSignerChange({ account, activeChainId, provider }: WalletData): Promise abstract approve(): Promise From 14256b466d8341659443bfdf75025b468e487b95 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Tue, 25 Jul 2023 19:38:11 +0200 Subject: [PATCH 26/37] [SWA-72] On user input override the default limit price and update the limit price on the protocol --- .../Swap/LimitOrder/Components/SwapTokens.tsx | 4 +- src/pages/Swap/LimitOrder/LimitOrderForm.tsx | 66 ++++++++++++++++--- src/services/LimitOrders/CoW/CoW.ts | 32 +++++---- 3 files changed, 76 insertions(+), 26 deletions(-) diff --git a/src/pages/Swap/LimitOrder/Components/SwapTokens.tsx b/src/pages/Swap/LimitOrder/Components/SwapTokens.tsx index 8b59aac47..63630532a 100644 --- a/src/pages/Swap/LimitOrder/Components/SwapTokens.tsx +++ b/src/pages/Swap/LimitOrder/Components/SwapTokens.tsx @@ -5,7 +5,9 @@ export default function SwapTokens({ swapTokens, loading }: { swapTokens: any; l return ( { - swapTokens() + if (!loading) { + swapTokens() + } }} > diff --git a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx index 4c6b446a0..b491e66ea 100644 --- a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx @@ -7,8 +7,11 @@ import { Flex } from 'rebass' import { ButtonPrimary } from '../../../components/Button' import { AutoColumn } from '../../../components/Column' import { CurrencyInputPanel } from '../../../components/CurrencyInputPanel' +import { useHigherUSDValue } from '../../../hooks/useUSDValue' import { Kind, MarketPrices } from '../../../services/LimitOrders' import { LimitOrderContext } from '../../../services/LimitOrders/LimitOrder.provider' +import { useCurrencyBalances } from '../../../state/wallet/hooks' +import { maxAmountSpend } from '../../../utils/maxAmountSpend' import { AutoRow } from './Components/AutoRow' import ConfirmLimitOrderModal from './Components/ConfirmLimitOrderModal' @@ -27,6 +30,23 @@ export default function LimitOrderForm() { const [buyToken, setBuyToken] = useState(protocol.buyToken) const [loading, setLoading] = useState(protocol.loading) + const [sellCurrencyBalance, buyCurrencyBalance] = useCurrencyBalances(protocol.userAddress, [ + sellAmount.currency, + buyAmount?.currency, + ]) + + const sellCurrencyMaxAmount = maxAmountSpend(sellCurrencyBalance, protocol.activeChainId) + const buyCurrencyMaxAmount = maxAmountSpend(buyCurrencyBalance, protocol.activeChainId, false) + + const { fiatValueInput, fiatValueOutput, isFallbackFiatValueInput, isFallbackFiatValueOutput } = useHigherUSDValue({ + inputCurrencyAmount: sellAmount, + outputCurrencyAmount: buyAmount, + }) + + console.log([sellAmount, buyAmount]) + console.log([protocol.sellAmount, protocol.buyAmount]) + console.log([sellCurrencyMaxAmount?.toExact(), buyCurrencyMaxAmount?.toExact()]) + const [kind, setKind] = useState(protocol?.kind || Kind.Sell) // TODO: Check the usage of marketPrices const [marketPrices] = useState({ buy: 0, sell: 0 }) @@ -45,6 +65,7 @@ export default function LimitOrderForm() { setLoading(false) } + setLoading(true) setSellToken(protocol.sellToken) setBuyToken(protocol.buyToken) @@ -65,25 +86,36 @@ export default function LimitOrderForm() { const handleSellTokenChange = useCallback(async (currency: Currency, _isMaxAmount?: boolean) => { setLoading(true) const newSellToken = protocol.getTokenFromCurrency(currency) + setSellToken(newSellToken) + setKind(Kind.Sell) + + protocol.onKindChange(Kind.Sell) + await protocol?.onSellTokenChange(newSellToken) + setSellAmount(protocol.sellAmount) setBuyAmount(protocol.buyAmount) setLoading(false) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) - const handleBuyTokenChange = useCallback(async (currency: Currency, _isMaxAmount?: boolean) => { + const handleBuyTokenChange = useCallback(async (currency: Currency) => { setLoading(true) const newBuyToken = protocol.getTokenFromCurrency(currency) setBuyToken(newBuyToken) + setKind(Kind.Buy) + + protocol.onKindChange(Kind.Buy) + await protocol?.onBuyTokenChange(newBuyToken) setSellAmount(protocol.sellAmount) + setBuyAmount(protocol.buyAmount) setLoading(false) // eslint-disable-next-line react-hooks/exhaustive-deps @@ -91,8 +123,8 @@ export default function LimitOrderForm() { const handleSellAmountChange = useCallback(async (value: string) => { if (value.trim() !== '' && value.trim() !== '0') { - const amountWei = parseUnits(value, sellToken.decimals).toString() - const newSellAmount = new TokenAmount(sellToken, amountWei) + const amountWei = parseUnits(value, protocol.sellToken.decimals).toString() + const newSellAmount = new TokenAmount(protocol.sellToken, amountWei) setLoading(true) setSellAmount(newSellAmount) @@ -109,8 +141,8 @@ export default function LimitOrderForm() { const handleBuyAmountChange = useCallback(async (value: string) => { if (value.trim() !== '' && value !== '0') { - const amountWei = parseUnits(value, buyToken.decimals).toString() - const newBuyAmount = new TokenAmount(buyToken, amountWei) + const amountWei = parseUnits(value, protocol.buyToken.decimals).toString() + const newBuyAmount = new TokenAmount(protocol.buyToken, amountWei) setLoading(true) setBuyAmount(newBuyAmount) @@ -125,10 +157,14 @@ export default function LimitOrderForm() { // eslint-disable-next-line react-hooks/exhaustive-deps }, []) - const handleSwap = useCallback(async () => { + const handleSwap = async () => { setLoading(true) + const buyToken = protocol.buyToken + const sellToken = protocol.sellToken + protocol.onSellTokenChange(buyToken) protocol.onBuyTokenChange(sellToken) + setSellToken(buyToken) setBuyToken(sellToken) setKind(Kind.Sell) @@ -136,8 +172,9 @@ export default function LimitOrderForm() { await protocol.getQuote() setBuyAmount(protocol.buyAmount) + setSellAmount(protocol.sellAmount) setLoading(false) - }, [sellToken, buyToken, protocol]) + } return ( <> @@ -161,6 +198,14 @@ export default function LimitOrderForm() { currency={sellToken} onCurrencySelect={handleSellTokenChange} currencyOmitList={[buyToken.address]} + disabled={loading} + onMax={() => { + if (sellCurrencyMaxAmount !== undefined && parseFloat(sellCurrencyMaxAmount.toExact() ?? '0') > 0) { + handleSellAmountChange(parseFloat(sellCurrencyMaxAmount?.toExact()).toFixed(6)) + } + }} + fiatValue={fiatValueInput} + isFallbackFiatValue={isFallbackFiatValueInput} /> { - console.log('onMax') + if (buyCurrencyMaxAmount !== undefined && parseFloat(buyCurrencyMaxAmount.toExact() ?? '0') > 0) { + handleBuyAmountChange(parseFloat(buyCurrencyMaxAmount.toExact()).toFixed(6)) + } }} showNativeCurrency={false} onCurrencySelect={handleBuyTokenChange} currencyOmitList={[sellToken.address]} + disabled={loading} + fiatValue={fiatValueOutput} + isFallbackFiatValue={isFallbackFiatValueOutput} /> diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index d86053f97..cb554fb4c 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -41,6 +41,7 @@ export class CoW extends LimitOrderBase { } async onSellTokenChange(sellToken: Currency) { + this.loading = true this.sellToken = this.getTokenFromCurrency(sellToken) const exactAmount = this.#validateAmount(this.sellAmount.toExact()) const sellAmountinWei = parseUnits(Number(exactAmount).toFixed(6), this.sellToken.decimals).toString() @@ -50,14 +51,16 @@ export class CoW extends LimitOrderBase { this.onLimitOrderChange({ sellToken: this.sellToken.address, }) - this.onKindChange(Kind.Sell) - await this.getQuote() + // this.onKindChange(Kind.Sell) + await this.getQuote() + this.loading = false this.logger.log(`Sell Token Change ${this.sellToken.symbol}`) } async onBuyTokenChange(buyToken: Currency) { + this.loading = true this.buyToken = this.getTokenFromCurrency(buyToken) const exactAmount = this.#validateAmount(this.buyAmount.toExact()) const buyAmountinWei = parseUnits(Number(exactAmount).toFixed(6), this.buyToken.decimals).toString() @@ -68,8 +71,10 @@ export class CoW extends LimitOrderBase { buyToken: this.buyToken.address, }) - await this.getQuote() + // this.onKindChange(Kind.Buy) + await this.getQuote() + this.loading = false this.logger.log(`Buy Token Change ${this.buyToken.symbol}`) } @@ -229,6 +234,7 @@ export class CoW extends LimitOrderBase { const buyTokenAmount = new TokenAmount(this.buyToken, buyAmount) const sellTokenAmount = new TokenAmount(this.sellToken, sellAmount) this.limitPrice = this.#calculateLimitPrice(buyTokenAmount, sellTokenAmount) + if (kind === Kind.Sell) { this.buyAmount = buyTokenAmount this.onLimitOrderChange({ @@ -282,22 +288,14 @@ export class CoW extends LimitOrderBase { const tokenAmountSelected = kind === Kind.Sell ? this.sellAmount : this.buyAmount - if (!(Number(tokenAmountSelected.toExact()) > 0)) { - if (kind === Kind.Sell) { - this.onSellAmountChange(new TokenAmount(sellToken, parseUnits('1', sellToken.decimals).toString())) - } else { - this.onBuyAmountChange(new TokenAmount(buyToken, parseUnits('1', buyToken.decimals).toString())) - } - } else { - const order = structuredClone(limitOrder) - const tokenSelected = kind === Kind.Sell ? sellToken : buyToken + const order = structuredClone(limitOrder) + const tokenSelected = kind === Kind.Sell ? sellToken : buyToken - const tokenAmount = - tokenAmountSelected && Number(tokenAmountSelected.toExact()) > 0 ? tokenAmountSelected.toExact() : '1' - order.sellAmount = parseUnits(tokenAmount, tokenSelected.decimals).toString() + const tokenAmount = + tokenAmountSelected && Number(tokenAmountSelected.toExact()) > 0 ? tokenAmountSelected.toExact() : '1' + order.sellAmount = parseUnits(tokenAmount, tokenSelected.decimals).toString() - await this.getQuote(order) - } + await this.getQuote(order) } } From a96afe5a7844694f0edf6e4a5e0a36aed028a762 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Wed, 26 Jul 2023 16:56:26 +0200 Subject: [PATCH 27/37] feat: [SWA-95] Fallback connect wallet component --- .../LimitOrderFallback/LimitFallback.tsx | 14 ++++++++++++++ .../Components/LimitOrderFallback/index.ts | 1 + .../Components/LimitOrderFallback/styled.ts | 7 +++++++ src/pages/Swap/LimitOrder/LimitOrder.tsx | 3 ++- 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 src/pages/Swap/LimitOrder/Components/LimitOrderFallback/LimitFallback.tsx create mode 100644 src/pages/Swap/LimitOrder/Components/LimitOrderFallback/index.ts create mode 100644 src/pages/Swap/LimitOrder/Components/LimitOrderFallback/styled.ts diff --git a/src/pages/Swap/LimitOrder/Components/LimitOrderFallback/LimitFallback.tsx b/src/pages/Swap/LimitOrder/Components/LimitOrderFallback/LimitFallback.tsx new file mode 100644 index 000000000..e49442176 --- /dev/null +++ b/src/pages/Swap/LimitOrder/Components/LimitOrderFallback/LimitFallback.tsx @@ -0,0 +1,14 @@ +import { ButtonPrimary } from '../../../../../components/Button' +import { useWalletSwitcherPopoverToggle } from '../../../../../state/application/hooks' + +import { Text } from './styled' + +export default function LimitOrderFallback() { + const toggleWalletModal = useWalletSwitcherPopoverToggle() + return ( + <> + Please connect wallet to access Limit Order + Connect Wallet + + ) +} diff --git a/src/pages/Swap/LimitOrder/Components/LimitOrderFallback/index.ts b/src/pages/Swap/LimitOrder/Components/LimitOrderFallback/index.ts new file mode 100644 index 000000000..d5da28f3c --- /dev/null +++ b/src/pages/Swap/LimitOrder/Components/LimitOrderFallback/index.ts @@ -0,0 +1 @@ +export { default } from './LimitFallback' diff --git a/src/pages/Swap/LimitOrder/Components/LimitOrderFallback/styled.ts b/src/pages/Swap/LimitOrder/Components/LimitOrderFallback/styled.ts new file mode 100644 index 000000000..d687312b8 --- /dev/null +++ b/src/pages/Swap/LimitOrder/Components/LimitOrderFallback/styled.ts @@ -0,0 +1,7 @@ +import styled from 'styled-components' + +export const Text = styled.p` + display: flex; + justify-content: center; + margin: 20px 0px 30px 0px; +` diff --git a/src/pages/Swap/LimitOrder/LimitOrder.tsx b/src/pages/Swap/LimitOrder/LimitOrder.tsx index 83fda81c3..3caf4b932 100644 --- a/src/pages/Swap/LimitOrder/LimitOrder.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrder.tsx @@ -6,6 +6,7 @@ import LimitOrder, { WalletData } from '../../../services/LimitOrders' import { LimitOrderProvider } from '../../../services/LimitOrders/LimitOrder.provider' import AppBody from '../../AppBody' +import LimitOrderFallback from './Components/LimitOrderFallback' import LimitOrderForm from './LimitOrderForm' const limitSdk = new LimitOrder() @@ -34,7 +35,7 @@ export default function LimitOrderUI() { )} - {!protocol && <>Loading....} + {!protocol && } ) From 534f7da3b2893d2399d69916f32f39f89a24bea5 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Wed, 26 Jul 2023 17:22:31 +0200 Subject: [PATCH 28/37] feat: [SWA-85] Update amounts based on limit price --- .../OrderLimitPriceField.tsx | 24 ++----------------- .../Components/OrderLimitPriceField/utils.ts | 1 - src/pages/Swap/LimitOrder/LimitOrderForm.tsx | 4 ---- src/services/LimitOrders/CoW/CoW.ts | 5 ++-- 4 files changed, 4 insertions(+), 30 deletions(-) diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx index 13b38b0d8..6f3628fd6 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx @@ -1,4 +1,3 @@ -import { parseUnits } from '@ethersproject/units' import { Currency, TokenAmount } from '@swapr/sdk' import { useEffect, useState } from 'react' @@ -7,7 +6,6 @@ import { useTranslation } from 'react-i18next' import { Kind, LimitOrderBase } from '../../../../../services/LimitOrders' import { InputGroup } from '../InputGroup' -// eslint-disable-next-line @typescript-eslint/no-unused-vars import { MarketPriceButton } from './MarketPriceButton' import { @@ -80,7 +78,6 @@ export function OrderLimitPriceField({ const [formattedLimitPrice, setFormattedLimitPrice] = useState(protocol.limitPrice) const [inputLimitPrice, setInputLimitPrice] = useState(formattedLimitPrice) - // const [inputFocus, setInputFocus] = useState(false) useEffect(() => { setInputLimitPrice(protocol.limitPrice) @@ -111,7 +108,6 @@ export function OrderLimitPriceField({ const onChangeHandler: React.ChangeEventHandler = event => { protocol.onUserUpadtedLimitPrice(true) - const [baseTokenAmount, quoteTokenAmount] = kind === Kind.Sell ? [sellAmount, buyAmount] : [buyAmount, sellAmount] // Parse the limit price const nextLimitPriceFormatted = event.target.value // When the limit price is empty, set the limit price to 0 if (nextLimitPriceFormatted.split('.').length > 2) { @@ -136,41 +132,24 @@ export function OrderLimitPriceField({ protocol.onLimitPriceChange(nextLimitPriceFormatted) protocol.onUserUpadtedLimitPrice(true) - // // When price is below or equal to 0, set the limit price to 0, but don't update the state - - const { buyAmountWei, sellAmountWei, newBuyTokenAmount, newSellTokenAmount } = computeNewAmount( + const { newBuyTokenAmount, newSellTokenAmount } = computeNewAmount( buyAmount, sellAmount, nextLimitPriceFloat, limitOrder.kind - // inputFocus ) if (kind === Kind.Sell) { setBuyAmount(newBuyTokenAmount) protocol.onBuyAmountChange(newBuyTokenAmount) - // setFormattedBuyAmount(amount.toString()) - protocol.onLimitOrderChange({ - ...limitOrder, - limitPrice: parseUnits(nextLimitPriceFormatted, quoteTokenAmount?.currency?.decimals).toString(), - buyAmount: buyAmountWei, - }) } else { setSellAmount(newSellTokenAmount) protocol.onSellAmountChange(newSellTokenAmount) - // setFormattedSellAmount(amount.toString()) - protocol.onLimitOrderChange({ - ...limitOrder, - limitPrice: parseUnits(nextLimitPriceFormatted, baseTokenAmount?.currency?.decimals).toString(), - sellAmount: sellAmountWei, - }) } } } const onClickGetMarketPrice = async () => { - // setInputFocus(false) - // setFetchMarketPrice(true) protocol.onUserUpadtedLimitPrice(false) await protocol.getQuote() @@ -180,6 +159,7 @@ export function OrderLimitPriceField({ setSellAmount(protocol.sellAmount) } const limitPrice = protocol.getTokenLimitPrices() + protocol.onLimitPriceChange(limitPrice) setInputLimitPrice(limitPrice) } diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/utils.ts b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/utils.ts index 1646658ed..10db4bc55 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/utils.ts +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/utils.ts @@ -29,7 +29,6 @@ export const computeNewAmount = ( sellTokenAmount: TokenAmount, limitPrice: number, limitOrderKind: Kind - // inputFocus: InputFocus ): IComputeNewAmount => { const buyAmountFloat = parseFloat(formatUnits(buyTokenAmount.raw.toString(), buyTokenAmount.currency.decimals)) const sellAmountFloat = parseFloat(formatUnits(sellTokenAmount.raw.toString(), sellTokenAmount.currency.decimals)) diff --git a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx index b491e66ea..9af2223bb 100644 --- a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx @@ -43,10 +43,6 @@ export default function LimitOrderForm() { outputCurrencyAmount: buyAmount, }) - console.log([sellAmount, buyAmount]) - console.log([protocol.sellAmount, protocol.buyAmount]) - console.log([sellCurrencyMaxAmount?.toExact(), buyCurrencyMaxAmount?.toExact()]) - const [kind, setKind] = useState(protocol?.kind || Kind.Sell) // TODO: Check the usage of marketPrices const [marketPrices] = useState({ buy: 0, sell: 0 }) diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index cb554fb4c..cded84d63 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -105,7 +105,7 @@ export class CoW extends LimitOrderBase { await this.#onAmountChange() - if (this.limitPrice && Number(this.limitPrice) > 0) { + if (this.limitPrice && Number(this.limitPrice) > 0 && this.kind === Kind.Sell) { const buyAmount = parseFloat(this.sellAmount.toExact()) * parseFloat(this.limitPrice) this.#setBuyAmount( new TokenAmount(this.buyToken, parseUnits(buyAmount.toFixed(6), this.buyToken.decimals).toString()) @@ -126,7 +126,7 @@ export class CoW extends LimitOrderBase { await this.#onAmountChange() - if (this.limitPrice && Number(this.limitPrice) > 0) { + if (this.limitPrice && Number(this.limitPrice) > 0 && this.kind === Kind.Buy) { const sellAmount = parseFloat(this.buyAmount.toExact()) * parseFloat(this.limitPrice) this.#setSellAmount( new TokenAmount(this.sellToken, parseUnits(sellAmount.toFixed(6), this.sellToken.decimals).toString()) @@ -279,7 +279,6 @@ export class CoW extends LimitOrderBase { throw new Error('Method not implemented.') } - // TODO: Remove this async getMarketPrice() { const { buyToken, sellToken, provider, limitOrder, kind, activeChainId } = this From e5afcd613c8427202cac91695dd2634ab5320c3c Mon Sep 17 00:00:00 2001 From: Wixzi Date: Wed, 26 Jul 2023 19:52:40 +0200 Subject: [PATCH 29/37] feat: [SWA-85] Fix limit price calculations --- .../OrderLimitPriceField.tsx | 77 +++++++++++++++--- src/pages/Swap/LimitOrder/LimitOrderForm.tsx | 35 +++++++- src/services/LimitOrders/1Inch/OneInch.ts | 3 +- src/services/LimitOrders/CoW/CoW.ts | 79 +++++-------------- src/services/LimitOrders/LimitOrder.utils.ts | 13 ++- 5 files changed, 130 insertions(+), 77 deletions(-) diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx index 6f3628fd6..0984ff985 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx @@ -1,5 +1,6 @@ import { Currency, TokenAmount } from '@swapr/sdk' +import { parseUnits } from 'ethers/lib/utils' import { useEffect, useState } from 'react' import { RefreshCw } from 'react-feather' import { useTranslation } from 'react-i18next' @@ -16,7 +17,6 @@ import { SwapTokenWrapper, ToggleCurrencyButton, } from './styles' -import { computeNewAmount } from './utils' const invalidChars = ['-', '+', 'e'] @@ -43,7 +43,6 @@ export interface OrderLimitPriceFieldProps { sellAmount: TokenAmount buyAmount: TokenAmount kind: Kind - limitOrder?: any sellToken: Currency buyToken: Currency setSellAmount(t: TokenAmount): void @@ -56,7 +55,6 @@ export function OrderLimitPriceField({ sellAmount, buyAmount, kind, - limitOrder = {}, sellToken, buyToken, setSellAmount, @@ -71,6 +69,10 @@ export function OrderLimitPriceField({ useEffect(() => { setBaseQuoteTokens(getBaseQuoteTokens({ sellAmount, buyAmount, kind, sellToken, buyToken })) + const limitPrice = protocol.getLimitPrice() + setInputLimitPrice(limitPrice) + setFormattedLimitPrice(limitPrice) + // eslint-disable-next-line react-hooks/exhaustive-deps }, [sellToken, buyToken, sellAmount, buyAmount, kind]) const inputGroupLabel = `${kind} 1 ${baseToken?.symbol} at` @@ -81,7 +83,7 @@ export function OrderLimitPriceField({ useEffect(() => { setInputLimitPrice(protocol.limitPrice) - }, [protocol.limitPrice]) + }, [protocol.limitPrice, sellToken, buyToken]) // const { marketPriceDiffPercentage, isDiffPositive } = calculateMarketPriceDiffPercentage( // kind ?? Kind.Sell, // marketPrices, @@ -132,17 +134,45 @@ export function OrderLimitPriceField({ protocol.onLimitPriceChange(nextLimitPriceFormatted) protocol.onUserUpadtedLimitPrice(true) - const { newBuyTokenAmount, newSellTokenAmount } = computeNewAmount( - buyAmount, - sellAmount, - nextLimitPriceFloat, - limitOrder.kind - ) + if (protocol.kind === Kind.Sell) { + protocol.quoteBuyAmount = new TokenAmount( + protocol.buyToken, + parseUnits(nextLimitPriceFormatted, protocol.buyToken.decimals).toString() + ) + protocol.quoteSellAmount = new TokenAmount( + protocol.sellToken, + parseUnits('1', protocol.sellToken.decimals).toString() + ) + } else { + protocol.quoteBuyAmount = new TokenAmount( + protocol.buyToken, + parseUnits('1', protocol.buyToken.decimals).toString() + ) + protocol.quoteSellAmount = new TokenAmount( + protocol.sellToken, + parseUnits(nextLimitPriceFormatted, protocol.sellToken.decimals).toString() + ) + } + const limitPrice = protocol.getLimitPrice() + protocol.onLimitPriceChange(limitPrice) if (kind === Kind.Sell) { + const buyAmount = parseFloat(protocol.sellAmount.toExact()) * parseFloat(limitPrice) + + const newBuyTokenAmount = new TokenAmount( + protocol.buyToken, + parseUnits(buyAmount.toFixed(6), protocol.buyToken.decimals).toString() + ) setBuyAmount(newBuyTokenAmount) protocol.onBuyAmountChange(newBuyTokenAmount) } else { + const sellAmount = parseFloat(protocol.buyAmount.toExact()) * parseFloat(limitPrice) + + const newSellTokenAmount = new TokenAmount( + protocol.sellToken, + parseUnits(sellAmount.toFixed(6), protocol.sellToken.decimals).toString() + ) + setSellAmount(newSellTokenAmount) protocol.onSellAmountChange(newSellTokenAmount) } @@ -158,7 +188,7 @@ export function OrderLimitPriceField({ } else { setSellAmount(protocol.sellAmount) } - const limitPrice = protocol.getTokenLimitPrices() + const limitPrice = protocol.getLimitPrice() protocol.onLimitPriceChange(limitPrice) setInputLimitPrice(limitPrice) } @@ -202,7 +232,30 @@ export function OrderLimitPriceField({ onChange={onChangeHandler} /> - setKind(kind === Kind.Sell ? Kind.Buy : Kind.Sell)}> + { + const newKind = kind === Kind.Sell ? Kind.Buy : Kind.Sell + setKind(newKind) + protocol.onKindChange(newKind) + + // const [baseAmount, quoteAmount] = + // newKind === Kind.Sell + // ? [protocol.quoteSellAmount, protocol.quoteBuyAmount] + // : [protocol.quoteBuyAmount, protocol.quoteSellAmount] + // const quoteAmountInUnits = parseFloat(quoteAmount.toExact()) + // const baseAmountInUnits = parseFloat(baseAmount.toExact()) + // if ( + // !Number.isNaN(quoteAmountInUnits) && + // quoteAmountInUnits > 0 && + // !Number.isNaN(baseAmountInUnits) && + // baseAmountInUnits > 0 + // ) { + const limitPrice = protocol.getLimitPrice() + setInputLimitPrice(limitPrice) + setFormattedLimitPrice(limitPrice) + // } + }} + > {toggleCurrencyButtonLabel} diff --git a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx index 9af2223bb..8021726d6 100644 --- a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx @@ -91,8 +91,11 @@ export default function LimitOrderForm() { await protocol?.onSellTokenChange(newSellToken) + protocol.onLimitPriceChange(protocol.getLimitPrice()) + setSellAmount(protocol.sellAmount) setBuyAmount(protocol.buyAmount) + setLoading(false) // eslint-disable-next-line react-hooks/exhaustive-deps @@ -110,6 +113,8 @@ export default function LimitOrderForm() { await protocol?.onBuyTokenChange(newBuyToken) + protocol.onLimitPriceChange(protocol.getLimitPrice()) + setSellAmount(protocol.sellAmount) setBuyAmount(protocol.buyAmount) setLoading(false) @@ -117,6 +122,23 @@ export default function LimitOrderForm() { // eslint-disable-next-line react-hooks/exhaustive-deps }, []) + // const updateLimitPrice = (kind: Kind) => { + // const [baseAmount, quoteAmount] = kind === Kind.Sell ? [sellAmount, buyAmount] : [buyAmount, sellAmount] + // const quoteAmountInUnits = parseFloat(quoteAmount.toExact()) + // const baseAmountInUnits = parseFloat(baseAmount.toExact()) + // if ( + // !Number.isNaN(quoteAmountInUnits) && + // quoteAmountInUnits > 0 && + // !Number.isNaN(baseAmountInUnits) && + // baseAmountInUnits > 0 + // ) { + // const limitPrice = parseFloat(quoteAmount.toExact()) / parseFloat(baseAmount.toExact()) + // protocol.onLimitPriceChange(limitPrice.toString()) + // // setInputLimitPrice(limitPrice.toFixed(6)) + // // setFormattedLimitPrice(limitPrice.toFixed(6)) + // } + // } + const handleSellAmountChange = useCallback(async (value: string) => { if (value.trim() !== '' && value.trim() !== '0') { const amountWei = parseUnits(value, protocol.sellToken.decimals).toString() @@ -124,9 +146,13 @@ export default function LimitOrderForm() { setLoading(true) setSellAmount(newSellAmount) + // if (kind !== Kind.Sell) { + protocol.onKindChange(Kind.Sell) + const limitPrice = protocol.getLimitPrice() + protocol.onLimitPriceChange(limitPrice) + // } setKind(Kind.Sell) - protocol.onKindChange(Kind.Sell) await protocol?.onSellAmountChange(newSellAmount) setBuyAmount(protocol.buyAmount) @@ -142,9 +168,13 @@ export default function LimitOrderForm() { setLoading(true) setBuyAmount(newBuyAmount) + // if (kind !== Kind.Buy) { + protocol.onKindChange(Kind.Buy) + const limitPrice = protocol.getLimitPrice() + protocol.onLimitPriceChange(limitPrice) + // } setKind(Kind.Buy) - protocol.onKindChange(Kind.Buy) await protocol?.onBuyAmountChange(newBuyAmount) setSellAmount(protocol.sellAmount) @@ -234,7 +264,6 @@ export default function LimitOrderForm() { sellAmount={sellAmount} buyAmount={buyAmount} kind={kind} - limitOrder={protocol.limitOrder} setSellAmount={setSellAmount} setBuyAmount={setBuyAmount} setKind={setKind} diff --git a/src/services/LimitOrders/1Inch/OneInch.ts b/src/services/LimitOrders/1Inch/OneInch.ts index a174746ad..edc74e546 100644 --- a/src/services/LimitOrders/1Inch/OneInch.ts +++ b/src/services/LimitOrders/1Inch/OneInch.ts @@ -85,7 +85,8 @@ export class OneInch extends LimitOrderBase { } async getMarketPrice() {} - getTokenLimitPrices() { + + getLimitPrice() { return '0' } } diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index cded84d63..c3db217fa 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -170,35 +170,6 @@ export class CoW extends LimitOrderBase { this.userUpdatedLimitPrice = status } - // async setToMarket(_sellPricePercentage: number, _buyPricePercentage: number): Promise { - // const sellToken = this.sellToken - // const buyToken = this.buyToken - // if (!sellToken || !buyToken) { - // return - // } - // let tokenAmount = this.kind === Kind.Sell ? this.sellAmount : this.buyAmount - // if (!tokenAmount) { - // return - // } - // const validatedTokenAmount = Number(tokenAmount.toExact()) > 1 ? tokenAmount.toExact() : '1' - // const sellAmount = parseUnits(validatedTokenAmount, tokenAmount.currency.decimals).toString() - // if (this.limitOrder !== undefined) { - // const limitOrder = { - // ...this.limitOrder, - // sellAmount, - // } - // await this.getQuote(limitOrder) - // if (!this.quote) { - // throw new Error('No quote') - // } - // const { - // quote: { buyAmount: buyAmountQuote, sellAmount: sellAmountQuote }, - // } = this.quote as CoWQuote - // this.logger.log('buyAmountQuote', buyAmountQuote) - // this.logger.log('sellAmountQuote', sellAmountQuote) - // } - // } - async getQuote(limitOrder?: LimitOrder): Promise { const signer = this.provider?.getSigner() const chainId = this.activeChainId @@ -213,9 +184,9 @@ export class CoW extends LimitOrderBase { if (order.buyAmount !== this.buyAmount.raw.toString()) { order.buyAmount = this.buyAmount.raw.toString() } - if (this.kind && order.kind !== this.kind) { - order.kind = this.kind - } + + order.kind = kind + console.log('limit price', order.limitPrice) try { const cowQuote = await getQuote({ @@ -233,7 +204,9 @@ export class CoW extends LimitOrderBase { const buyTokenAmount = new TokenAmount(this.buyToken, buyAmount) const sellTokenAmount = new TokenAmount(this.sellToken, sellAmount) - this.limitPrice = this.#calculateLimitPrice(buyTokenAmount, sellTokenAmount) + this.quoteBuyAmount = buyTokenAmount + this.quoteSellAmount = sellTokenAmount + this.limitPrice = this.getLimitPrice() if (kind === Kind.Sell) { this.buyAmount = buyTokenAmount @@ -383,32 +356,22 @@ export class CoW extends LimitOrderBase { } as LimitOrder } - //TODO: Remove it later - getTokenLimitPrices() { - const { buyAmount, sellAmount } = this - return this.#calculateLimitPrice(buyAmount, sellAmount) - } - - #calculateLimitPrice(buyAmount: TokenAmount, sellAmount: TokenAmount) { - const { kind } = this - if (buyAmount && sellAmount && kind) { - const [baseAmount, quoteAmount] = kind === Kind.Sell ? [sellAmount, buyAmount] : [buyAmount, sellAmount] - const quoteAmountInUnits = parseFloat(quoteAmount.toExact()) - const baseAmountInUnits = parseFloat(baseAmount.toExact()) - if ( - !Number.isNaN(quoteAmountInUnits) && - quoteAmountInUnits > 0 && - !Number.isNaN(baseAmountInUnits) && - baseAmountInUnits > 0 - ) { - const limitPrice = parseFloat(quoteAmount.toExact()) / parseFloat(baseAmount.toExact()) - return limitPrice.toFixed(6) - } + getLimitPrice() { + const [baseAmount, quoteAmount] = + this.kind === Kind.Sell + ? [this.quoteSellAmount, this.quoteBuyAmount] + : [this.quoteBuyAmount, this.quoteSellAmount] + const quoteAmountInUnits = parseFloat(quoteAmount.toExact()) + const baseAmountInUnits = parseFloat(baseAmount.toExact()) + if ( + !Number.isNaN(quoteAmountInUnits) && + quoteAmountInUnits > 0 && + !Number.isNaN(baseAmountInUnits) && + baseAmountInUnits > 0 + ) { + return (parseFloat(quoteAmount.toExact()) / parseFloat(baseAmount.toExact())).toFixed(6) } + this.getQuote() return '1' } - - // #formatMarketPrice(amount: string, decimals: number, tokenAmount: string) { - // return parseFloat(formatUnits(amount, decimals) ?? 0) / Number(tokenAmount) - // } } diff --git a/src/services/LimitOrders/LimitOrder.utils.ts b/src/services/LimitOrders/LimitOrder.utils.ts index 62e7edb50..4dfbf224d 100644 --- a/src/services/LimitOrders/LimitOrder.utils.ts +++ b/src/services/LimitOrders/LimitOrder.utils.ts @@ -23,6 +23,9 @@ export abstract class LimitOrderBase { orderType?: 'partial' | 'full' quoteId: string | undefined kind?: Kind = Kind.Sell + quoteSellAmount: TokenAmount + quoteBuyAmount: TokenAmount + provider: Web3Provider | undefined expiresAt: number expiresAtUnit: OrderExpiresInUnit = OrderExpiresInUnit.Minutes @@ -42,9 +45,13 @@ export abstract class LimitOrderBase { this.buyToken = buyToken this.sellAmount = new TokenAmount(sellToken, parseUnits('1', sellToken.decimals).toString()) this.buyAmount = new TokenAmount(buyToken, parseUnits('1', buyToken.decimals).toString()) + this.quoteSellAmount = new TokenAmount(sellToken, parseUnits('1', sellToken.decimals).toString()) + this.quoteBuyAmount = new TokenAmount(buyToken, parseUnits('1', buyToken.decimals).toString()) this.userUpdatedLimitPrice = false } + // Shared methods + #logFormat = (message: string) => `LimitOrder:: ${this.limitOrderProtocol} : ${message}` logger = { @@ -60,14 +67,14 @@ export abstract class LimitOrderBase { return token } - getFormattedAmount = (amount: TokenAmount) => { + getFormattedAmount(amount: TokenAmount) { const rawAmount = formatUnits(amount.raw.toString(), amount.currency.decimals) || '0' const formattedAmount = parseFloat(rawAmount).toFixed(6) if (Number(formattedAmount) === 0) return Number(formattedAmount).toString() return formattedAmount.replace(/\.?0+$/, '') } - setSignerData = async ({ account, activeChainId, provider }: WalletData) => { + async setSignerData({ account, activeChainId, provider }: WalletData) { this.userAddress = account this.activeChainId = activeChainId this.provider = provider @@ -87,7 +94,7 @@ export abstract class LimitOrderBase { abstract getQuote(): Promise abstract getMarketPrice(): Promise - abstract getTokenLimitPrices(): string + abstract getLimitPrice(): string abstract onSignerChange({ account, activeChainId, provider }: WalletData): Promise abstract approve(): Promise abstract createOrder(): Promise From ba4a0938f863c4dfe377ac7f216bbd4b5d8d0a11 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Wed, 26 Jul 2023 19:57:27 +0200 Subject: [PATCH 30/37] feat: [SWA-85] Cleanup commented code --- .../OrderLimitPriceField.tsx | 27 ------------------- src/pages/Swap/LimitOrder/LimitOrderForm.tsx | 26 +++--------------- src/services/LimitOrders/LimitOrder.utils.ts | 2 -- 3 files changed, 4 insertions(+), 51 deletions(-) diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx index 0984ff985..432aabd65 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx @@ -93,24 +93,10 @@ export function OrderLimitPriceField({ // const showPercentage = // Number(marketPriceDiffPercentage.toFixed(1)) !== 0 && Number(marketPriceDiffPercentage) !== -100 - // useEffect(() => { - // setInputLimitPrice(formattedLimitPrice) - // }, [formattedLimitPrice]) - // const limitPrice = protocol.getTokenLimitPrices() - // useEffect(() => { - // setFormattedLimitPrice(limitPrice) - // if (!inputFocus) { - // setInputLimitPrice(limitPrice) - // } - // }, [inputFocus, limitPrice]) - /** * Handle the limit price input change. Compute the buy amount and update the state. */ const onChangeHandler: React.ChangeEventHandler = event => { - protocol.onUserUpadtedLimitPrice(true) - - // Parse the limit price const nextLimitPriceFormatted = event.target.value // When the limit price is empty, set the limit price to 0 if (nextLimitPriceFormatted.split('.').length > 2) { event.preventDefault() @@ -238,22 +224,9 @@ export function OrderLimitPriceField({ setKind(newKind) protocol.onKindChange(newKind) - // const [baseAmount, quoteAmount] = - // newKind === Kind.Sell - // ? [protocol.quoteSellAmount, protocol.quoteBuyAmount] - // : [protocol.quoteBuyAmount, protocol.quoteSellAmount] - // const quoteAmountInUnits = parseFloat(quoteAmount.toExact()) - // const baseAmountInUnits = parseFloat(baseAmount.toExact()) - // if ( - // !Number.isNaN(quoteAmountInUnits) && - // quoteAmountInUnits > 0 && - // !Number.isNaN(baseAmountInUnits) && - // baseAmountInUnits > 0 - // ) { const limitPrice = protocol.getLimitPrice() setInputLimitPrice(limitPrice) setFormattedLimitPrice(limitPrice) - // } }} > diff --git a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx index 8021726d6..19c6997e9 100644 --- a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx @@ -21,7 +21,6 @@ import SwapTokens from './Components/SwapTokens' export default function LimitOrderForm() { const protocol = useContext(LimitOrderContext) - // const [fetchMarketPrice, setFetchMarketPrice] = useState(true) const [buyAmount, setBuyAmount] = useState(protocol.buyAmount) const [sellAmount, setSellAmount] = useState(protocol.sellAmount) @@ -122,23 +121,6 @@ export default function LimitOrderForm() { // eslint-disable-next-line react-hooks/exhaustive-deps }, []) - // const updateLimitPrice = (kind: Kind) => { - // const [baseAmount, quoteAmount] = kind === Kind.Sell ? [sellAmount, buyAmount] : [buyAmount, sellAmount] - // const quoteAmountInUnits = parseFloat(quoteAmount.toExact()) - // const baseAmountInUnits = parseFloat(baseAmount.toExact()) - // if ( - // !Number.isNaN(quoteAmountInUnits) && - // quoteAmountInUnits > 0 && - // !Number.isNaN(baseAmountInUnits) && - // baseAmountInUnits > 0 - // ) { - // const limitPrice = parseFloat(quoteAmount.toExact()) / parseFloat(baseAmount.toExact()) - // protocol.onLimitPriceChange(limitPrice.toString()) - // // setInputLimitPrice(limitPrice.toFixed(6)) - // // setFormattedLimitPrice(limitPrice.toFixed(6)) - // } - // } - const handleSellAmountChange = useCallback(async (value: string) => { if (value.trim() !== '' && value.trim() !== '0') { const amountWei = parseUnits(value, protocol.sellToken.decimals).toString() @@ -146,11 +128,11 @@ export default function LimitOrderForm() { setLoading(true) setSellAmount(newSellAmount) - // if (kind !== Kind.Sell) { + protocol.onKindChange(Kind.Sell) const limitPrice = protocol.getLimitPrice() protocol.onLimitPriceChange(limitPrice) - // } + setKind(Kind.Sell) await protocol?.onSellAmountChange(newSellAmount) @@ -168,11 +150,11 @@ export default function LimitOrderForm() { setLoading(true) setBuyAmount(newBuyAmount) - // if (kind !== Kind.Buy) { + protocol.onKindChange(Kind.Buy) const limitPrice = protocol.getLimitPrice() protocol.onLimitPriceChange(limitPrice) - // } + setKind(Kind.Buy) await protocol?.onBuyAmountChange(newBuyAmount) diff --git a/src/services/LimitOrders/LimitOrder.utils.ts b/src/services/LimitOrders/LimitOrder.utils.ts index 4dfbf224d..1bbf87777 100644 --- a/src/services/LimitOrders/LimitOrder.utils.ts +++ b/src/services/LimitOrders/LimitOrder.utils.ts @@ -50,8 +50,6 @@ export abstract class LimitOrderBase { this.userUpdatedLimitPrice = false } - // Shared methods - #logFormat = (message: string) => `LimitOrder:: ${this.limitOrderProtocol} : ${message}` logger = { From b4d33f50027095b9653a16da017828ca42a0476b Mon Sep 17 00:00:00 2001 From: Wixzi Date: Thu, 27 Jul 2023 20:04:25 +0200 Subject: [PATCH 31/37] feat: [SWA-88] Enable user input limit price --- .../OrderLimitPriceField.tsx | 80 ++++++++++--------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx index 432aabd65..11c9e0879 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx @@ -20,23 +20,39 @@ import { const invalidChars = ['-', '+', 'e'] -const getBaseQuoteTokens = ({ - sellAmount, - buyAmount, - kind, - sellToken, - buyToken, -}: { - sellAmount: TokenAmount - buyAmount: TokenAmount - kind: Kind - sellToken: Currency - buyToken: Currency -}) => { +const getBaseQuoteTokens = ({ kind, sellToken, buyToken }: { kind: Kind; sellToken: Currency; buyToken: Currency }) => { return kind === Kind.Sell - ? { baseTokenAmount: sellAmount, baseToken: sellToken, quoteTokenAmount: buyAmount, quoteToken: buyToken } - : { baseTokenAmount: buyAmount, baseToken: buyToken, quoteTokenAmount: sellAmount, quoteToken: sellToken } + ? { baseToken: sellToken, quoteToken: buyToken } + : { baseToken: buyToken, quoteToken: sellToken } } +const regex = /^(\d+\.\d{0,2})0*$/ + +// function formatNumber(number: string | number): string { +// // Check if the input is a string +// if (typeof number === 'string') { +// number = parseFloat(number) +// } + +// // Get the decimal part of the number +// const decimals = number.toFixed(6) + +// // Check if the decimal part is all zeros +// if (decimals.endsWith('0')) { +// if (decimals.includes('.') && Number(decimals) > 0) { +// const match = regex.test(decimals) +// if (match) { +// // @ts-ignore +// return match[1] +// } else { +// return decimals +// } +// return decimals +// } +// return Math.floor(number).toString() +// } else { +// return decimals +// } +// } export interface OrderLimitPriceFieldProps { protocol: LimitOrderBase @@ -63,27 +79,20 @@ export function OrderLimitPriceField({ }: OrderLimitPriceFieldProps) { const { t } = useTranslation('swap') - const [{ baseToken, quoteToken }, setBaseQuoteTokens] = useState( - getBaseQuoteTokens({ sellAmount, buyAmount, kind, sellToken, buyToken }) - ) + const { baseToken, quoteToken } = getBaseQuoteTokens({ kind, sellToken, buyToken }) + + const inputGroupLabel = `${kind} 1 ${baseToken?.symbol} at` + const toggleCurrencyButtonLabel = `${quoteToken?.symbol}` useEffect(() => { - setBaseQuoteTokens(getBaseQuoteTokens({ sellAmount, buyAmount, kind, sellToken, buyToken })) const limitPrice = protocol.getLimitPrice() - setInputLimitPrice(limitPrice) - setFormattedLimitPrice(limitPrice) + setInputLimitPrice(Number(limitPrice).toString()) // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sellToken, buyToken, sellAmount, buyAmount, kind]) - - const inputGroupLabel = `${kind} 1 ${baseToken?.symbol} at` - const toggleCurrencyButtonLabel = `${quoteToken?.symbol}` + }, [protocol.limitPrice]) - const [formattedLimitPrice, setFormattedLimitPrice] = useState(protocol.limitPrice) - const [inputLimitPrice, setInputLimitPrice] = useState(formattedLimitPrice) + // const [formattedLimitPrice, setFormattedLimitPrice] = useState(protocol.limitPrice) + const [inputLimitPrice, setInputLimitPrice] = useState(protocol.limitPrice) - useEffect(() => { - setInputLimitPrice(protocol.limitPrice) - }, [protocol.limitPrice, sellToken, buyToken]) // const { marketPriceDiffPercentage, isDiffPositive } = calculateMarketPriceDiffPercentage( // kind ?? Kind.Sell, // marketPrices, @@ -106,17 +115,18 @@ export function OrderLimitPriceField({ setInputLimitPrice(nextLimitPriceFormatted) } else if (nextLimitPriceFormatted === '.') { setInputLimitPrice('0.') - setFormattedLimitPrice('0') + protocol.onLimitPriceChange('0') return } else { const nextLimitPriceFloat = parseFloat(nextLimitPriceFormatted ?? 0) if (nextLimitPriceFloat < 0 || isNaN(nextLimitPriceFloat)) { setInputLimitPrice('0') - setFormattedLimitPrice('0') + protocol.onLimitPriceChange('0') return } + setInputLimitPrice(nextLimitPriceFormatted) - setFormattedLimitPrice(nextLimitPriceFormatted) + protocol.onLimitPriceChange(nextLimitPriceFormatted) protocol.onUserUpadtedLimitPrice(true) @@ -210,8 +220,7 @@ export function OrderLimitPriceField({ }} onBlur={e => { if (e.target.value.trim() === '' || e.target.value === '0' || e.target.value === '0.') { - setInputLimitPrice(formattedLimitPrice) - setFormattedLimitPrice(formattedLimitPrice) + setInputLimitPrice(protocol.limitPrice) } }} value={inputLimitPrice} @@ -226,7 +235,6 @@ export function OrderLimitPriceField({ const limitPrice = protocol.getLimitPrice() setInputLimitPrice(limitPrice) - setFormattedLimitPrice(limitPrice) }} > From 22fb61cfc88bb007bab0105b5e5bd116212dfb18 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Fri, 28 Jul 2023 17:00:43 +0200 Subject: [PATCH 32/37] [SWA-92] Market prices and update limit order every 15 seconds --- .../LimitFallback.tsx | 0 .../index.ts | 0 .../styled.ts | 0 .../OrderLimitPriceField.tsx | 127 ++++++++++-------- .../Components/OrderLimitPriceField/styles.ts | 22 +-- src/pages/Swap/LimitOrder/Components/utils.ts | 4 +- src/pages/Swap/LimitOrder/LimitOrder.tsx | 2 +- src/pages/Swap/LimitOrder/LimitOrderForm.tsx | 43 ++++-- src/services/LimitOrders/1Inch/OneInch.ts | 4 - src/services/LimitOrders/CoW/CoW.ts | 6 +- src/services/LimitOrders/LimitOrder.config.ts | 5 +- src/services/LimitOrders/LimitOrder.types.ts | 31 ++--- src/services/LimitOrders/LimitOrder.utils.ts | 13 +- 13 files changed, 154 insertions(+), 103 deletions(-) rename src/pages/Swap/LimitOrder/Components/{LimitOrderFallback => LimitFallback}/LimitFallback.tsx (100%) rename src/pages/Swap/LimitOrder/Components/{LimitOrderFallback => LimitFallback}/index.ts (100%) rename src/pages/Swap/LimitOrder/Components/{LimitOrderFallback => LimitFallback}/styled.ts (100%) diff --git a/src/pages/Swap/LimitOrder/Components/LimitOrderFallback/LimitFallback.tsx b/src/pages/Swap/LimitOrder/Components/LimitFallback/LimitFallback.tsx similarity index 100% rename from src/pages/Swap/LimitOrder/Components/LimitOrderFallback/LimitFallback.tsx rename to src/pages/Swap/LimitOrder/Components/LimitFallback/LimitFallback.tsx diff --git a/src/pages/Swap/LimitOrder/Components/LimitOrderFallback/index.ts b/src/pages/Swap/LimitOrder/Components/LimitFallback/index.ts similarity index 100% rename from src/pages/Swap/LimitOrder/Components/LimitOrderFallback/index.ts rename to src/pages/Swap/LimitOrder/Components/LimitFallback/index.ts diff --git a/src/pages/Swap/LimitOrder/Components/LimitOrderFallback/styled.ts b/src/pages/Swap/LimitOrder/Components/LimitFallback/styled.ts similarity index 100% rename from src/pages/Swap/LimitOrder/Components/LimitOrderFallback/styled.ts rename to src/pages/Swap/LimitOrder/Components/LimitFallback/styled.ts diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx index 11c9e0879..f64869cf1 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx @@ -1,12 +1,14 @@ import { Currency, TokenAmount } from '@swapr/sdk' import { parseUnits } from 'ethers/lib/utils' -import { useEffect, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { RefreshCw } from 'react-feather' import { useTranslation } from 'react-i18next' +import { Flex } from 'rebass' -import { Kind, LimitOrderBase } from '../../../../../services/LimitOrders' +import { Kind, LimitOrderBase, MarketPrices } from '../../../../../services/LimitOrders' import { InputGroup } from '../InputGroup' +import { calculateMarketPriceDiffPercentage } from '../utils' import { MarketPriceButton } from './MarketPriceButton' import { @@ -16,6 +18,7 @@ import { SwapTokenIconWrapper, SwapTokenWrapper, ToggleCurrencyButton, + LimitLabelGroup, } from './styles' const invalidChars = ['-', '+', 'e'] @@ -25,45 +28,19 @@ const getBaseQuoteTokens = ({ kind, sellToken, buyToken }: { kind: Kind; sellTok ? { baseToken: sellToken, quoteToken: buyToken } : { baseToken: buyToken, quoteToken: sellToken } } -const regex = /^(\d+\.\d{0,2})0*$/ - -// function formatNumber(number: string | number): string { -// // Check if the input is a string -// if (typeof number === 'string') { -// number = parseFloat(number) -// } - -// // Get the decimal part of the number -// const decimals = number.toFixed(6) - -// // Check if the decimal part is all zeros -// if (decimals.endsWith('0')) { -// if (decimals.includes('.') && Number(decimals) > 0) { -// const match = regex.test(decimals) -// if (match) { -// // @ts-ignore -// return match[1] -// } else { -// return decimals -// } -// return decimals -// } -// return Math.floor(number).toString() -// } else { -// return decimals -// } -// } - -export interface OrderLimitPriceFieldProps { + +interface OrderLimitPriceFieldProps { protocol: LimitOrderBase sellAmount: TokenAmount buyAmount: TokenAmount kind: Kind + marketPrices: MarketPrices sellToken: Currency buyToken: Currency setSellAmount(t: TokenAmount): void setBuyAmount(t: TokenAmount): void setKind(t: Kind): void + setLoading(t: boolean): void } export function OrderLimitPriceField({ @@ -71,14 +48,18 @@ export function OrderLimitPriceField({ sellAmount, buyAmount, kind, + marketPrices, sellToken, buyToken, setSellAmount, setBuyAmount, setKind, + setLoading, }: OrderLimitPriceFieldProps) { const { t } = useTranslation('swap') + const quoteRef = useRef() + const { baseToken, quoteToken } = getBaseQuoteTokens({ kind, sellToken, buyToken }) const inputGroupLabel = `${kind} 1 ${baseToken?.symbol} at` @@ -90,17 +71,41 @@ export function OrderLimitPriceField({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [protocol.limitPrice]) - // const [formattedLimitPrice, setFormattedLimitPrice] = useState(protocol.limitPrice) + useEffect(() => { + quoteRef.current = setInterval(async () => { + setLoading(true) + try { + if (!protocol.userUpdatedLimitPrice) { + console.log('Hello') + await protocol.getQuote() + } + } finally { + setLoading(false) + } + }, 15000) + + if (protocol.userUpdatedLimitPrice) { + setLoading(false) + clearInterval(quoteRef.current) + } + + return () => { + clearInterval(quoteRef.current) + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [protocol.userUpdatedLimitPrice]) + const [inputLimitPrice, setInputLimitPrice] = useState(protocol.limitPrice) - // const { marketPriceDiffPercentage, isDiffPositive } = calculateMarketPriceDiffPercentage( - // kind ?? Kind.Sell, - // marketPrices, - // formattedLimitPrice.toString() - // ) + const { marketPriceDiffPercentage, isDiffPositive } = calculateMarketPriceDiffPercentage( + kind ?? Kind.Sell, + marketPrices, + protocol.limitPrice + ) - // const showPercentage = - // Number(marketPriceDiffPercentage.toFixed(1)) !== 0 && Number(marketPriceDiffPercentage) !== -100 + const showPercentage = + Number(marketPriceDiffPercentage.toFixed(1)) !== 0 && Number(marketPriceDiffPercentage) !== -100 /** * Handle the limit price input change. Compute the buy amount and update the state. @@ -176,9 +181,13 @@ export function OrderLimitPriceField({ } const onClickGetMarketPrice = async () => { - protocol.onUserUpadtedLimitPrice(false) + protocol.loading = true + setLoading(true) + protocol.onUserUpadtedLimitPrice(false) await protocol.getQuote() + protocol.loading = false + setLoading(false) if (kind === Kind.Sell) { setBuyAmount(protocol.buyAmount) } else { @@ -189,25 +198,29 @@ export function OrderLimitPriceField({ setInputLimitPrice(limitPrice) } - // TODO: fix it - const showPercentage = false - const marketPriceDiffPercentage = 0 - const isDiffPositive = false - return ( - - {inputGroupLabel} - {showPercentage && ( - ({marketPriceDiffPercentage.toFixed(2)}%) - )} - - {!protocol.userUpdatedLimitPrice && buyAmount?.currency && sellAmount?.currency ? ( - - ) : ( - {t('limitOrder.getMarketPrice')} - )} + + +

+ {inputGroupLabel} + {showPercentage && ( + + {' '} + ({marketPriceDiffPercentage.toFixed(2)}%) + + )} +

+
+ + {!protocol.userUpdatedLimitPrice && buyAmount?.currency && sellAmount?.currency ? ( + + ) : ( + {t('limitOrder.getMarketPrice')} + )} + +
theme.green2}; border-radius: 5px; text-transform: uppercase; - padding: 3px 8px; + padding: 3px 5px; &:hover { - color: #736f96; + background-color: #03d48d; } ` @@ -37,15 +44,10 @@ export const MarketPrice = styled.button` font-size: 11px; color: ${({ theme }) => theme.green2}; border: 1px solid ${({ theme }) => theme.green2}; - cursor: pointer; background-color: transparent; border-radius: 5px; text-transform: uppercase; - padding: 3px 8px; - &:hover { - border: 1px solid ${({ theme }) => theme.green2}; - color: ${({ theme }) => theme.green1}; - } + padding: 3px 4px; ` export const SwapTokenIconWrapper = styled.div` diff --git a/src/pages/Swap/LimitOrder/Components/utils.ts b/src/pages/Swap/LimitOrder/Components/utils.ts index fbda7f880..5cea49512 100644 --- a/src/pages/Swap/LimitOrder/Components/utils.ts +++ b/src/pages/Swap/LimitOrder/Components/utils.ts @@ -78,13 +78,13 @@ export const toFixedSix = (price: number): string => { export function calculateMarketPriceDiffPercentage( limitOrderKind: Kind, marketPrices: MarketPrices, - formattedLimitPrice: string + formattedLimitPrice?: string ) { const nextLimitPriceFloat = limitOrderKind === Kind.Sell ? marketPrices.buy : marketPrices.sell let marketPriceDiffPercentage = 0 let isDiffPositive = false - if (Boolean(Number(nextLimitPriceFloat))) { + if (Boolean(Number(nextLimitPriceFloat)) && formattedLimitPrice) { if (limitOrderKind === Kind.Sell) { marketPriceDiffPercentage = (Number(formattedLimitPrice) / Number(nextLimitPriceFloat.toFixed(6)) - 1) * 100 isDiffPositive = Math.sign(Number(marketPriceDiffPercentage)) > 0 diff --git a/src/pages/Swap/LimitOrder/LimitOrder.tsx b/src/pages/Swap/LimitOrder/LimitOrder.tsx index 3caf4b932..cdf9e2e16 100644 --- a/src/pages/Swap/LimitOrder/LimitOrder.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrder.tsx @@ -6,7 +6,7 @@ import LimitOrder, { WalletData } from '../../../services/LimitOrders' import { LimitOrderProvider } from '../../../services/LimitOrders/LimitOrder.provider' import AppBody from '../../AppBody' -import LimitOrderFallback from './Components/LimitOrderFallback' +import LimitOrderFallback from './Components/LimitFallback' import LimitOrderForm from './LimitOrderForm' const limitSdk = new LimitOrder() diff --git a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx index 19c6997e9..125588eef 100644 --- a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx @@ -18,6 +18,7 @@ import ConfirmLimitOrderModal from './Components/ConfirmLimitOrderModal' import { OrderExpiryField } from './Components/OrderExpiryField' import { OrderLimitPriceField } from './Components/OrderLimitPriceField' import SwapTokens from './Components/SwapTokens' +import { formatMarketPrice } from './Components/utils' export default function LimitOrderForm() { const protocol = useContext(LimitOrderContext) @@ -44,7 +45,7 @@ export default function LimitOrderForm() { const [kind, setKind] = useState(protocol?.kind || Kind.Sell) // TODO: Check the usage of marketPrices - const [marketPrices] = useState({ buy: 0, sell: 0 }) + // const [marketPrices] = useState({ buy: 0, sell: 0 }) const [isModalOpen, setIsModalOpen] = useState(false) @@ -184,6 +185,33 @@ export default function LimitOrderForm() { setLoading(false) } + const [marketPrices, setMarketPrices] = useState({ buy: 0, sell: 0 }) + + const getMarketPrices = useCallback(async () => { + const token = kind === Kind.Sell ? sellAmount : buyAmount + const tokenAmount = Number(token.toExact()) > 1 ? token.toExact() : '1' + debugger + if (protocol.kind === Kind.Sell) { + setMarketPrices(marketPrice => ({ + ...marketPrice, + buy: formatMarketPrice(protocol.quoteBuyAmount.raw.toString(), buyAmount.currency.decimals, tokenAmount), + })) + } else { + setMarketPrices(marketPrice => ({ + ...marketPrice, + sell: formatMarketPrice(protocol.quoteSellAmount.raw.toString(), sellAmount.currency.decimals, tokenAmount), + })) + } + }, [buyAmount, kind, protocol, sellAmount]) + + useEffect(() => { + getMarketPrices() + }, [getMarketPrices, kind]) + + useEffect(() => { + setMarketPrices({ buy: 0, sell: 0 }) + }, [sellToken, buyToken]) + return ( <> @@ -235,12 +263,10 @@ export default function LimitOrderForm() { /> - + - + diff --git a/src/services/LimitOrders/1Inch/OneInch.ts b/src/services/LimitOrders/1Inch/OneInch.ts index edc74e546..668de37c8 100644 --- a/src/services/LimitOrders/1Inch/OneInch.ts +++ b/src/services/LimitOrders/1Inch/OneInch.ts @@ -54,10 +54,6 @@ export class OneInch extends LimitOrderBase { this.limitPrice = limitPrice } - // setToMarket(): Promise { - // throw new Error('Method not implemented.') - // } - getQuote(): Promise { throw new Error('Method not implemented.') } diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index c3db217fa..7dd60afef 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -170,7 +170,7 @@ export class CoW extends LimitOrderBase { this.userUpdatedLimitPrice = status } - async getQuote(limitOrder?: LimitOrder): Promise { + async getQuote(limitOrder?: LimitOrder) { const signer = this.provider?.getSigner() const chainId = this.activeChainId const order = limitOrder ?? this.limitOrder @@ -202,6 +202,10 @@ export class CoW extends LimitOrderBase { quote: { buyAmount, sellAmount }, } = cowQuote + if (this.userUpdatedLimitPrice) { + return + } + const buyTokenAmount = new TokenAmount(this.buyToken, buyAmount) const sellTokenAmount = new TokenAmount(this.sellToken, sellAmount) this.quoteBuyAmount = buyTokenAmount diff --git a/src/services/LimitOrders/LimitOrder.config.ts b/src/services/LimitOrders/LimitOrder.config.ts index fc1c830ca..04233817f 100644 --- a/src/services/LimitOrders/LimitOrder.config.ts +++ b/src/services/LimitOrders/LimitOrder.config.ts @@ -2,6 +2,7 @@ import { ChainId, Currency, DAI, Token, USDT, WBNB, WETH, WMATIC, WXDAI } from ' import { OneInch } from './1Inch/OneInch' import { CoW } from './CoW/CoW' +import { Providers } from './LimitOrder.types' import { LimitOrderBase } from './LimitOrder.utils' export const DefaultTokens: Record = { @@ -42,12 +43,12 @@ export const getDefaultTokens = (chainId: ChainId) => { export const limitOrderConfig: LimitOrderBase[] = [ new CoW({ supportedChains: [ChainId.MAINNET, ChainId.GNOSIS], - protocol: 'CoW', + protocol: Providers.COW, ...getDefaultTokens(ChainId.MAINNET), }), new OneInch({ supportedChains: [ChainId.POLYGON, ChainId.OPTIMISM_MAINNET, ChainId.ARBITRUM_ONE, ChainId.BSC_MAINNET], - protocol: '1inch', + protocol: Providers.ONEINCH, ...getDefaultTokens(ChainId.POLYGON), }), ] diff --git a/src/services/LimitOrders/LimitOrder.types.ts b/src/services/LimitOrders/LimitOrder.types.ts index 288eb22b4..41af68dfb 100644 --- a/src/services/LimitOrders/LimitOrder.types.ts +++ b/src/services/LimitOrders/LimitOrder.types.ts @@ -3,22 +3,28 @@ import { ChainId, Token } from '@swapr/sdk' import { LimitOrderBase } from './LimitOrder.utils' -export interface MarketPrices { - buy: number - sell: number -} - export enum OrderExpiresInUnit { Minutes = 'minutes', Days = 'days', } + export enum Kind { Buy = 'Buy', Sell = 'Sell', } -export interface LimitOrderBaseConstructor { - protocol: 'CoW' | '1inch' +export enum Providers { + COW = 'CoW', + ONEINCH = '1Inch', +} + +export type MarketPrices = { + buy: number + sell: number +} + +export type LimitOrderBaseConstructor = { + protocol: Providers supportedChains: ChainId[] kind: Kind expiresAt: number @@ -28,14 +34,9 @@ export interface LimitOrderBaseConstructor { export type ProtocolContructor = Omit -export enum LimitOrderIds { - COW = 'CoW', - ONEINCH = '1inch', -} - -export type LimitOrderProviders = { [key in LimitOrderIds]: LimitOrderBase } +export type LimitOrderProviders = { [key in Providers]: LimitOrderBase } -export interface WalletData { +export type WalletData = { account: string provider: Web3Provider activeChainId: ChainId @@ -43,7 +44,7 @@ export interface WalletData { type EVMAddress = string -export interface LimitOrder { +export type LimitOrder = { /** * The user Address. */ diff --git a/src/services/LimitOrders/LimitOrder.utils.ts b/src/services/LimitOrders/LimitOrder.utils.ts index 1bbf87777..5b8c7fca4 100644 --- a/src/services/LimitOrders/LimitOrder.utils.ts +++ b/src/services/LimitOrders/LimitOrder.utils.ts @@ -4,7 +4,14 @@ import { ChainId, Currency, Token, TokenAmount } from '@swapr/sdk' import { formatUnits, parseUnits } from 'ethers/lib/utils' import { CoWQuote } from './CoW/CoW.types' -import { LimitOrderBaseConstructor, WalletData, OrderExpiresInUnit, Kind, LimitOrder } from './LimitOrder.types' +import { + LimitOrderBaseConstructor, + WalletData, + OrderExpiresInUnit, + Kind, + LimitOrder, + Providers, +} from './LimitOrder.types' export const logger = (message?: any, ...optionalParams: any[]) => { process.env.NODE_ENV === 'development' && console.log(message, ...optionalParams) @@ -30,7 +37,7 @@ export abstract class LimitOrderBase { expiresAt: number expiresAtUnit: OrderExpiresInUnit = OrderExpiresInUnit.Minutes createdAt: number | undefined - limitOrderProtocol: 'CoW' | '1inch' + limitOrderProtocol: Providers supportedChanins: ChainId[] activeChainId: ChainId | undefined loading: boolean = false @@ -90,7 +97,7 @@ export abstract class LimitOrderBase { abstract onLimitPriceChange(limitPrice: string): void abstract onUserUpadtedLimitPrice(status: boolean): void - abstract getQuote(): Promise + abstract getQuote(limitOrder?: LimitOrder): Promise abstract getMarketPrice(): Promise abstract getLimitPrice(): string abstract onSignerChange({ account, activeChainId, provider }: WalletData): Promise From 0345d921a11af2dd1ab73cab17ad71f60e96f19f Mon Sep 17 00:00:00 2001 From: Wixzi Date: Fri, 28 Jul 2023 19:31:39 +0200 Subject: [PATCH 33/37] [SWA-92] Place limit order --- .../LimitOrder/Components/ApprovalFlow.tsx | 33 +++++++ .../OrderLimitPriceField.tsx | 5 +- src/pages/Swap/LimitOrder/LimitOrderForm.tsx | 87 +++++++++++++++---- src/services/LimitOrders/CoW/CoW.ts | 45 ++++++++-- src/services/LimitOrders/CoW/api/cow.ts | 2 +- src/services/LimitOrders/LimitOrder.types.ts | 2 + src/services/LimitOrders/LimitOrder.utils.ts | 3 +- 7 files changed, 145 insertions(+), 32 deletions(-) create mode 100644 src/pages/Swap/LimitOrder/Components/ApprovalFlow.tsx diff --git a/src/pages/Swap/LimitOrder/Components/ApprovalFlow.tsx b/src/pages/Swap/LimitOrder/Components/ApprovalFlow.tsx new file mode 100644 index 000000000..ef94eb17e --- /dev/null +++ b/src/pages/Swap/LimitOrder/Components/ApprovalFlow.tsx @@ -0,0 +1,33 @@ +import { ButtonPrimary } from '../../../../components/Button' +import { Loader } from '../../../../components/Loader' +import ProgressSteps from '../../../../components/ProgressSteps' +import { ApprovalState } from '../../../../hooks/useApproveCallback' + +import { AutoRow } from './AutoRow' + +interface ApprovalFlowProps { + approval: ApprovalState + approveCallback: () => Promise + tokenInSymbol: string +} + +export const ApprovalFlow = ({ approval, approveCallback, tokenInSymbol }: ApprovalFlowProps) => ( + <> + + {approval === ApprovalState.PENDING ? ( + + Approving + + ) : ( + 'Approve ' + tokenInSymbol + )} + +
+ +
+ +) diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx index f64869cf1..2e308fa63 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx @@ -206,10 +206,7 @@ export function OrderLimitPriceField({

{inputGroupLabel} {showPercentage && ( - - {' '} - ({marketPriceDiffPercentage.toFixed(2)}%) - + ({marketPriceDiffPercentage.toFixed(2)}%) )}

diff --git a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx index 125588eef..61bb4235e 100644 --- a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx @@ -2,17 +2,22 @@ import { Currency, Token, TokenAmount } from '@swapr/sdk' import { parseUnits } from 'ethers/lib/utils' import { useCallback, useContext, useEffect, useState } from 'react' +import { Link } from 'react-router-dom' import { Flex } from 'rebass' import { ButtonPrimary } from '../../../components/Button' import { AutoColumn } from '../../../components/Column' import { CurrencyInputPanel } from '../../../components/CurrencyInputPanel' +import { ApprovalState, useApproveCallback } from '../../../hooks/useApproveCallback' import { useHigherUSDValue } from '../../../hooks/useUSDValue' import { Kind, MarketPrices } from '../../../services/LimitOrders' +import { getVaultRelayerAddress } from '../../../services/LimitOrders/CoW/api/cow' import { LimitOrderContext } from '../../../services/LimitOrders/LimitOrder.provider' +import { useNotificationPopup } from '../../../state/application/hooks' import { useCurrencyBalances } from '../../../state/wallet/hooks' import { maxAmountSpend } from '../../../utils/maxAmountSpend' +import { ApprovalFlow } from './Components/ApprovalFlow' import { AutoRow } from './Components/AutoRow' import ConfirmLimitOrderModal from './Components/ConfirmLimitOrderModal' import { OrderExpiryField } from './Components/OrderExpiryField' @@ -23,6 +28,8 @@ import { formatMarketPrice } from './Components/utils' export default function LimitOrderForm() { const protocol = useContext(LimitOrderContext) + const notify = useNotificationPopup() + const [buyAmount, setBuyAmount] = useState(protocol.buyAmount) const [sellAmount, setSellAmount] = useState(protocol.sellAmount) @@ -30,6 +37,10 @@ export default function LimitOrderForm() { const [buyToken, setBuyToken] = useState(protocol.buyToken) const [loading, setLoading] = useState(protocol.loading) + // TODO: Error Message handling + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [errorMessage, setErrorMessage] = useState('') + const [sellCurrencyBalance, buyCurrencyBalance] = useCurrencyBalances(protocol.userAddress, [ sellAmount.currency, buyAmount?.currency, @@ -43,6 +54,14 @@ export default function LimitOrderForm() { outputCurrencyAmount: buyAmount, }) + const [tokenInApproval, tokenInApprovalCallback] = useApproveCallback( + sellAmount, + getVaultRelayerAddress(protocol.activeChainId!) + ) + + // Determine if the token has to be approved first + const showApproveFlow = tokenInApproval === ApprovalState.NOT_APPROVED || tokenInApproval === ApprovalState.PENDING + const [kind, setKind] = useState(protocol?.kind || Kind.Sell) // TODO: Check the usage of marketPrices // const [marketPrices] = useState({ buy: 0, sell: 0 }) @@ -188,21 +207,22 @@ export default function LimitOrderForm() { const [marketPrices, setMarketPrices] = useState({ buy: 0, sell: 0 }) const getMarketPrices = useCallback(async () => { - const token = kind === Kind.Sell ? sellAmount : buyAmount - const tokenAmount = Number(token.toExact()) > 1 ? token.toExact() : '1' - debugger - if (protocol.kind === Kind.Sell) { - setMarketPrices(marketPrice => ({ - ...marketPrice, - buy: formatMarketPrice(protocol.quoteBuyAmount.raw.toString(), buyAmount.currency.decimals, tokenAmount), - })) - } else { - setMarketPrices(marketPrice => ({ - ...marketPrice, - sell: formatMarketPrice(protocol.quoteSellAmount.raw.toString(), sellAmount.currency.decimals, tokenAmount), - })) - } - }, [buyAmount, kind, protocol, sellAmount]) + // const token = kind === Kind.Sell ? sellAmount : buyAmount + const tokenSellAmount = Number(sellAmount.toExact()) > 1 ? sellAmount.toExact() : '1' + const tokenBuyAmount = Number(buyAmount.toExact()) > 1 ? buyAmount.toExact() : '1' + + // if (protocol.kind === Kind.Sell) { + setMarketPrices(() => ({ + sell: formatMarketPrice(protocol.quoteSellAmount.raw.toString(), sellAmount.currency.decimals, tokenSellAmount), + buy: formatMarketPrice(protocol.quoteBuyAmount.raw.toString(), buyAmount.currency.decimals, tokenBuyAmount), + })) + // } else { + // setMarketPrices(marketPrice => ({ + // ...marketPrice, + // sell: formatMarketPrice(protocol.quoteSellAmount.raw.toString(), sellAmount.currency.decimals, tokenAmount), + // })) + // } + }, [buyAmount, protocol, sellAmount]) useEffect(() => { getMarketPrices() @@ -212,10 +232,35 @@ export default function LimitOrderForm() { setMarketPrices({ buy: 0, sell: 0 }) }, [sellToken, buyToken]) + // Form submission handler + const createLimitOrder = async () => { + setLoading(true) + + const successCallback = () => { + notify( + <> + Successfully created limit order. Please check user account for details + + ) + setLoading(false) + } + + const errorCallback = (error: Error) => { + console.error(error) + setErrorMessage('Failed to place limit order. Try again.') + notify('Failed to place limit order. Try again.', false) + } + + const final = () => setLoading(false) + + const response = await protocol.createOrder(successCallback, errorCallback, final) + console.dir(response) + } + return ( <> {}} + onConfirm={createLimitOrder} onDismiss={onModalDismiss} isOpen={isModalOpen} errorMessage={''} @@ -282,7 +327,15 @@ export default function LimitOrderForm() { - setIsModalOpen(true)}>Place Limit Order + {showApproveFlow ? ( + + ) : ( + setIsModalOpen(true)}>Place Limit Order + )} ) diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index 7dd60afef..4b3895452 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -4,10 +4,10 @@ import dayjs from 'dayjs' import { parseUnits } from 'ethers/lib/utils' import { getDefaultTokens } from '../LimitOrder.config' -import { Kind, WalletData, OrderExpiresInUnit, ProtocolContructor, LimitOrder } from '../LimitOrder.types' +import { Kind, WalletData, OrderExpiresInUnit, ProtocolContructor, LimitOrder, Callback } from '../LimitOrder.types' import { LimitOrderBase } from '../LimitOrder.utils' -import { getQuote } from './api/cow' +import { createCoWLimitOrder, getQuote } from './api/cow' import { type CoWQuote } from './CoW.types' export class CoW extends LimitOrderBase { @@ -171,6 +171,9 @@ export class CoW extends LimitOrderBase { } async getQuote(limitOrder?: LimitOrder) { + if (this.userUpdatedLimitPrice) { + return + } const signer = this.provider?.getSigner() const chainId = this.activeChainId const order = limitOrder ?? this.limitOrder @@ -187,7 +190,6 @@ export class CoW extends LimitOrderBase { order.kind = kind - console.log('limit price', order.limitPrice) try { const cowQuote = await getQuote({ chainId, @@ -199,13 +201,11 @@ export class CoW extends LimitOrderBase { this.quote = cowQuote as CoWQuote const { + id, quote: { buyAmount, sellAmount }, } = cowQuote - if (this.userUpdatedLimitPrice) { - return - } - + this.onLimitOrderChange({ quoteId: id }) const buyTokenAmount = new TokenAmount(this.buyToken, buyAmount) const sellTokenAmount = new TokenAmount(this.sellToken, sellAmount) this.quoteBuyAmount = buyTokenAmount @@ -252,8 +252,35 @@ export class CoW extends LimitOrderBase { approve(): Promise { throw new Error('Method not implemented.') } - createOrder(): Promise { - throw new Error('Method not implemented.') + + async createOrder(successCallback: Callback, errorCallback: Callback, final: Callback) { + try { + const signer = this.provider?.getSigner() + const chainId = this.activeChainId + if (signer && chainId && this.limitOrder) { + const finalizedLimitOrder = { + ...this.limitOrder, + expiresAt: dayjs().add(this.expiresAt, this.expiresAtUnit).unix(), + } + + const response = await createCoWLimitOrder({ + chainId, + signer, + order: finalizedLimitOrder, + }) + + if (response) { + successCallback?.() + return response + } else { + throw new Error(response) + } + } + } catch (error) { + errorCallback?.(error) + } finally { + final?.() + } } async getMarketPrice() { diff --git a/src/services/LimitOrders/CoW/api/cow.ts b/src/services/LimitOrders/CoW/api/cow.ts index 0413c34de..5fb011392 100644 --- a/src/services/LimitOrders/CoW/api/cow.ts +++ b/src/services/LimitOrders/CoW/api/cow.ts @@ -26,7 +26,7 @@ export interface SignLimitOrderParams { order: LimitOrder signer: Signer chainId: number - signal: AbortSignal + signal?: AbortSignal } type GetLimitOrderQuoteParams = SignLimitOrderParams diff --git a/src/services/LimitOrders/LimitOrder.types.ts b/src/services/LimitOrders/LimitOrder.types.ts index 41af68dfb..8ddc48e35 100644 --- a/src/services/LimitOrders/LimitOrder.types.ts +++ b/src/services/LimitOrders/LimitOrder.types.ts @@ -23,6 +23,8 @@ export type MarketPrices = { sell: number } +export type Callback = (val?: any) => void + export type LimitOrderBaseConstructor = { protocol: Providers supportedChains: ChainId[] diff --git a/src/services/LimitOrders/LimitOrder.utils.ts b/src/services/LimitOrders/LimitOrder.utils.ts index 5b8c7fca4..318a18beb 100644 --- a/src/services/LimitOrders/LimitOrder.utils.ts +++ b/src/services/LimitOrders/LimitOrder.utils.ts @@ -11,6 +11,7 @@ import { Kind, LimitOrder, Providers, + Callback, } from './LimitOrder.types' export const logger = (message?: any, ...optionalParams: any[]) => { @@ -102,5 +103,5 @@ export abstract class LimitOrderBase { abstract getLimitPrice(): string abstract onSignerChange({ account, activeChainId, provider }: WalletData): Promise abstract approve(): Promise - abstract createOrder(): Promise + abstract createOrder(successCallback?: Callback, errorCallback?: Callback, final?: Callback): Promise } From 090ded6c4e64e48ade1ff116fc8f20da19599958 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Sun, 30 Jul 2023 13:40:14 +0200 Subject: [PATCH 34/37] [SWA-94] Disable limit order and handle common errors --- .../Swap/LimitOrder/Components/MaxAlert.tsx | 12 +++ .../OrderLimitPriceField.tsx | 34 ++++--- src/pages/Swap/LimitOrder/LimitOrder.tsx | 6 +- src/pages/Swap/LimitOrder/LimitOrderForm.tsx | 97 +++++++++++++++++-- src/services/LimitOrders/CoW/CoW.constants.ts | 7 ++ src/services/LimitOrders/CoW/CoW.ts | 21 +++- src/services/LimitOrders/CoW/CoW.types.ts | 12 +++ src/services/LimitOrders/LimitOrder.config.ts | 6 +- src/services/LimitOrders/LimitOrder.ts | 4 +- src/services/LimitOrders/LimitOrder.utils.ts | 2 + 10 files changed, 169 insertions(+), 32 deletions(-) create mode 100644 src/pages/Swap/LimitOrder/Components/MaxAlert.tsx diff --git a/src/pages/Swap/LimitOrder/Components/MaxAlert.tsx b/src/pages/Swap/LimitOrder/Components/MaxAlert.tsx new file mode 100644 index 000000000..535868316 --- /dev/null +++ b/src/pages/Swap/LimitOrder/Components/MaxAlert.tsx @@ -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; +` diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx index 2e308fa63..b727091cb 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx @@ -41,6 +41,7 @@ interface OrderLimitPriceFieldProps { setBuyAmount(t: TokenAmount): void setKind(t: Kind): void setLoading(t: boolean): void + handleGetMarketPrice(): Promise } export function OrderLimitPriceField({ @@ -55,6 +56,7 @@ export function OrderLimitPriceField({ setBuyAmount, setKind, setLoading, + handleGetMarketPrice, }: OrderLimitPriceFieldProps) { const { t } = useTranslation('swap') @@ -76,7 +78,6 @@ export function OrderLimitPriceField({ setLoading(true) try { if (!protocol.userUpdatedLimitPrice) { - console.log('Hello') await protocol.getQuote() } } finally { @@ -181,20 +182,21 @@ 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) } @@ -202,7 +204,7 @@ export function OrderLimitPriceField({ - +

{inputGroupLabel} {showPercentage && ( @@ -210,7 +212,7 @@ export function OrderLimitPriceField({ )}

- + {!protocol.userUpdatedLimitPrice && buyAmount?.currency && sellAmount?.currency ? ( ) : ( diff --git a/src/pages/Swap/LimitOrder/LimitOrder.tsx b/src/pages/Swap/LimitOrder/LimitOrder.tsx index cdf9e2e16..6fce842ed 100644 --- a/src/pages/Swap/LimitOrder/LimitOrder.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrder.tsx @@ -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) diff --git a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx index 61bb4235e..7b6545c3c 100644 --- a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx @@ -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) @@ -37,9 +39,10 @@ export default function LimitOrderForm() { const [buyToken, setBuyToken] = useState(protocol.buyToken) const [loading, setLoading] = useState(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, @@ -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(protocol?.kind || Kind.Sell) - // TODO: Check the usage of marketPrices - // const [marketPrices] = useState({ buy: 0, sell: 0 }) const [isModalOpen, setIsModalOpen] = useState(false) @@ -114,6 +147,10 @@ export default function LimitOrderForm() { setSellAmount(protocol.sellAmount) setBuyAmount(protocol.buyAmount) + setIsPossibleToOrder({ + status: false, + value: 0, + }) setLoading(false) @@ -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 }, []) @@ -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) } @@ -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 ( <> @@ -334,7 +393,29 @@ export default function LimitOrderForm() { approveCallback={tokenInApprovalCallback} /> ) : ( - setIsModalOpen(true)}>Place Limit Order + <> + {protocol.quoteErrorMessage && ( + {`${protocol.quoteErrorMessage}. Please try again. ${( + + )}`} + )} + {isPossibleToOrder.status && ( + + {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. ${( + + )}`} + + )} + setIsModalOpen(true)} disabled={isPossibleToOrder.status}> + Place Limit Order + + )} diff --git a/src/services/LimitOrders/CoW/CoW.constants.ts b/src/services/LimitOrders/CoW/CoW.constants.ts index 95a25a98b..bc0788b7e 100644 --- a/src/services/LimitOrders/CoW/CoW.constants.ts +++ b/src/services/LimitOrders/CoW/CoW.constants.ts @@ -10,3 +10,10 @@ export const DefaultTokens = { buy: USDT[ChainId.GNOSIS], }, } + +export const ErrorCodes = [ + 'InsufficientLiquidity', + 'UnsupportedToken', + 'NoLiquidity', + 'SellAmountDoesNotCoverFee', +] as const diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index 4b3895452..d8c3a299e 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -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) { @@ -171,6 +174,7 @@ export class CoW extends LimitOrderBase { } async getQuote(limitOrder?: LimitOrder) { + this.quoteErrorMessage = undefined if (this.userUpdatedLimitPrice) { return } @@ -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) } } @@ -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 + } } diff --git a/src/services/LimitOrders/CoW/CoW.types.ts b/src/services/LimitOrders/CoW/CoW.types.ts index 2cb90988f..ae3d3f749 100644 --- a/src/services/LimitOrders/CoW/CoW.types.ts +++ b/src/services/LimitOrders/CoW/CoW.types.ts @@ -10,3 +10,15 @@ export interface SignedLimitOrder extends LimitOrder { } export type CoWQuote = Awaited> + +enum CoWErrorCodes { + InsufficientLiquidity = 'InsufficientLiquidity', + UnsupportedToken = 'UnsupportedToken', + NoLiquidity = 'NoLiquidity', + SellAmountDoesNotCoverFee = 'SellAmountDoesNotCoverFee', +} + +export type CoWError = { + error_code: CoWErrorCodes + description: string +} & Error diff --git a/src/services/LimitOrders/LimitOrder.config.ts b/src/services/LimitOrders/LimitOrder.config.ts index 04233817f..2538cedf3 100644 --- a/src/services/LimitOrders/LimitOrder.config.ts +++ b/src/services/LimitOrders/LimitOrder.config.ts @@ -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' @@ -12,7 +12,7 @@ export const DefaultTokens: Record { return { sellToken, buyToken } } -export const limitOrderConfig: LimitOrderBase[] = [ +export const getLimitOrderCofig = (): LimitOrderBase[] => [ new CoW({ supportedChains: [ChainId.MAINNET, ChainId.GNOSIS], protocol: Providers.COW, diff --git a/src/services/LimitOrders/LimitOrder.ts b/src/services/LimitOrders/LimitOrder.ts index 633639631..2f44904c0 100644 --- a/src/services/LimitOrders/LimitOrder.ts +++ b/src/services/LimitOrders/LimitOrder.ts @@ -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' @@ -8,7 +8,7 @@ export default class LimitOrder { constructor() { logger('LimitOrder constructor') - this.#protocols = limitOrderConfig + this.#protocols = getLimitOrderCofig() } updateSigner = async (signerData: WalletData) => { diff --git a/src/services/LimitOrders/LimitOrder.utils.ts b/src/services/LimitOrders/LimitOrder.utils.ts index 318a18beb..ddc4cd82f 100644 --- a/src/services/LimitOrders/LimitOrder.utils.ts +++ b/src/services/LimitOrders/LimitOrder.utils.ts @@ -43,6 +43,7 @@ export abstract class LimitOrderBase { activeChainId: ChainId | undefined loading: boolean = false userUpdatedLimitPrice: boolean + quoteErrorMessage: string | undefined constructor({ protocol, supportedChains, kind, expiresAt, sellToken, buyToken }: LimitOrderBaseConstructor) { this.limitOrderProtocol = protocol @@ -56,6 +57,7 @@ export abstract class LimitOrderBase { this.quoteSellAmount = new TokenAmount(sellToken, parseUnits('1', sellToken.decimals).toString()) this.quoteBuyAmount = new TokenAmount(buyToken, parseUnits('1', buyToken.decimals).toString()) this.userUpdatedLimitPrice = false + this.quoteErrorMessage = undefined } #logFormat = (message: string) => `LimitOrder:: ${this.limitOrderProtocol} : ${message}` From b3a35fbaf17ca84bec32e43039008cf0cf58f39f Mon Sep 17 00:00:00 2001 From: Wixzi Date: Sun, 30 Jul 2023 13:54:42 +0200 Subject: [PATCH 35/37] [SWA-94] Code cleanup and fallbacks --- .../ConfirmLimitOrderModal/index.tsx | 10 ++-- src/pages/Swap/LimitOrder/Components/utils.ts | 56 ------------------- src/pages/Swap/LimitOrder/LimitOrderForm.tsx | 31 ++++++---- src/services/LimitOrders/CoW/CoW.ts | 8 +++ 4 files changed, 32 insertions(+), 73 deletions(-) diff --git a/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/index.tsx b/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/index.tsx index 5813870a2..10cd6b2e1 100644 --- a/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/index.tsx +++ b/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/index.tsx @@ -6,7 +6,7 @@ import TransactionConfirmationModal, { ConfirmationModalContent, TransactionErrorContent, } from '../../../../../components/TransactionConfirmationModal' -import { Kind, LimitOrderContext, MarketPrices } from '../../../../../services/LimitOrders' +import { Kind, LimitOrderContext, MarketPrices, Providers } from '../../../../../services/LimitOrders' import { calculateMarketPriceDiffPercentage } from '../utils' import { ConfirmationFooter } from './ConfirmationFooter' @@ -21,6 +21,7 @@ interface ConfirmLimitOrderModalProps { marketPrices: MarketPrices fiatValueInput: CurrencyAmount | null fiatValueOutput: CurrencyAmount | null + market: Providers } export default function ConfirmLimitOrderModal({ @@ -32,12 +33,10 @@ export default function ConfirmLimitOrderModal({ marketPrices, fiatValueInput, fiatValueOutput, + market = Providers.COW, }: ConfirmLimitOrderModalProps) { const { buyAmount, sellAmount, limitPrice, expiresAt, expiresAtUnit, kind } = useContext(LimitOrderContext) - //hardcoded for now - const market = 'CoW Protocol' - const modalHeader = useCallback(() => { return ( ) : null + // eslint-disable-next-line react-hooks/exhaustive-deps }, [marketPriceDiffPercentage, isDiffPositive, onConfirm, askPrice, expiresInFormatted]) // text to show while loading diff --git a/src/pages/Swap/LimitOrder/Components/utils.ts b/src/pages/Swap/LimitOrder/Components/utils.ts index 5cea49512..de289a794 100644 --- a/src/pages/Swap/LimitOrder/Components/utils.ts +++ b/src/pages/Swap/LimitOrder/Components/utils.ts @@ -2,62 +2,6 @@ import { formatUnits } from '@ethersproject/units' import { Kind, MarketPrices } from '../../../../services/LimitOrders' -// import { Dispatch, SetStateAction } from 'react' - -// import { getQuote } from '../api/cow' -// import { SerializableLimitOrder } from '../../interfaces' - -// export const checkMaxOrderAmount = async ( -// limitOrder: SerializableLimitOrder, -// setIsPossibleToOrder: (value: SetStateAction<{ status: boolean; value: number }>) => void, -// setLimitOrder: Dispatch>, -// amountWei: string, -// expiresAt: number, -// sellTokenAmount: TokenAmount, -// sellCurrencyMaxAmount: CurrencyAmount | undefined, -// chainId: number, -// provider: Web3Provider -// ) => { -// const signer = provider.getSigner() - -// if (limitOrder.sellToken === limitOrder.buyToken) { -// return -// } - -// const { quote } = await getQuote({ -// chainId, -// signer, -// order: { ...limitOrder, sellAmount: amountWei, expiresAt: expiresAt }, -// }) - -// if (!quote || !sellCurrencyMaxAmount?.raw) { -// setIsPossibleToOrder({ -// status: true, -// value: 0, -// }) -// return -// } - -// const { sellAmount } = quote -// const totalSellAmount = Number(formatUnits(sellAmount, sellTokenAmount.currency.decimals) ?? 0) -// const maxAmountAvailable = Number( -// formatUnits(sellCurrencyMaxAmount.raw.toString(), sellTokenAmount.currency.decimals) ?? 0 -// ) -// // Since fee amount is recalculated again before order -// if (totalSellAmount > maxAmountAvailable) { -// const maxSellAmountPossible = maxAmountAvailable < 0 ? 0 : maxAmountAvailable -// setIsPossibleToOrder({ -// status: true, -// value: maxSellAmountPossible, -// }) -// } else { -// setIsPossibleToOrder({ -// status: false, -// value: 0, -// }) -// } -// } - export const formatMaxValue = (value: number) => { if (value === 0) return 0 else if (value < 10) return value.toFixed(5) diff --git a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx index 7b6545c3c..6d5202424 100644 --- a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx @@ -326,6 +326,7 @@ export default function LimitOrderForm() { marketPrices={marketPrices} fiatValueInput={fiatValueInput} fiatValueOutput={fiatValueOutput} + market={protocol.limitOrderProtocol} /> @@ -395,21 +396,27 @@ export default function LimitOrderForm() { ) : ( <> {protocol.quoteErrorMessage && ( - {`${protocol.quoteErrorMessage}. Please try again. ${( - - )}`} + + + {protocol.quoteErrorMessage} + {' '} + Please try again + + )} {isPossibleToOrder.status && ( - {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. ${( - - )}`} + {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 + + )} )} setIsModalOpen(true)} disabled={isPossibleToOrder.status}> diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index d8c3a299e..8aa0c4b85 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -178,6 +178,7 @@ export class CoW extends LimitOrderBase { if (this.userUpdatedLimitPrice) { return } + const signer = this.provider?.getSigner() const chainId = this.activeChainId const order = limitOrder ?? this.limitOrder @@ -191,6 +192,13 @@ export class CoW extends LimitOrderBase { if (order.buyAmount !== this.buyAmount.raw.toString()) { order.buyAmount = this.buyAmount.raw.toString() } + // If token is xDAI for CoW ignore it. It happens if we switch from Swap to Limit order and XDai was already selected + if ( + order.sellToken.toLowerCase() === '0x6b175474e89094c44da98b954eedeac495271d0f' || + order.buyToken.toLowerCase() === '0x6b175474e89094c44da98b954eedeac495271d0f' + ) { + return + } order.kind = kind From 21cc8fe9c59d166aef7265154c03e2053f5d8a00 Mon Sep 17 00:00:00 2001 From: Wixzi Date: Mon, 31 Jul 2023 10:15:04 +0200 Subject: [PATCH 36/37] [SWA-94] Code cleanup and remove context --- .../ConfirmLimitOrderModal/index.tsx | 21 ++- .../Components/OrderExpiryField.tsx | 7 +- .../OrderLimitPriceField.tsx | 14 -- src/pages/Swap/LimitOrder/LimitOrder.tsx | 7 +- src/pages/Swap/LimitOrder/LimitOrderForm.tsx | 131 +++++++++--------- src/services/LimitOrders/1Inch/OneInch.ts | 4 + src/services/LimitOrders/CoW/CoW.ts | 45 +++--- .../LimitOrders/LimitOrder.provider.tsx | 17 --- src/services/LimitOrders/LimitOrder.utils.ts | 8 ++ src/services/LimitOrders/index.ts | 1 - 10 files changed, 128 insertions(+), 127 deletions(-) delete mode 100644 src/services/LimitOrders/LimitOrder.provider.tsx diff --git a/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/index.tsx b/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/index.tsx index 10cd6b2e1..4c8db6b97 100644 --- a/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/index.tsx +++ b/src/pages/Swap/LimitOrder/Components/ConfirmLimitOrderModal/index.tsx @@ -1,12 +1,12 @@ import { CurrencyAmount } from '@swapr/sdk' -import { useCallback, useContext } from 'react' +import { useCallback } from 'react' import TransactionConfirmationModal, { ConfirmationModalContent, TransactionErrorContent, } from '../../../../../components/TransactionConfirmationModal' -import { Kind, LimitOrderContext, MarketPrices, Providers } from '../../../../../services/LimitOrders' +import { Kind, LimitOrderBase, MarketPrices, Providers } from '../../../../../services/LimitOrders' import { calculateMarketPriceDiffPercentage } from '../utils' import { ConfirmationFooter } from './ConfirmationFooter' @@ -21,7 +21,7 @@ interface ConfirmLimitOrderModalProps { marketPrices: MarketPrices fiatValueInput: CurrencyAmount | null fiatValueOutput: CurrencyAmount | null - market: Providers + protocol: LimitOrderBase } export default function ConfirmLimitOrderModal({ @@ -33,9 +33,18 @@ export default function ConfirmLimitOrderModal({ marketPrices, fiatValueInput, fiatValueOutput, - market = Providers.COW, + + protocol, }: ConfirmLimitOrderModalProps) { - const { buyAmount, sellAmount, limitPrice, expiresAt, expiresAtUnit, kind } = useContext(LimitOrderContext) + const { + buyAmount, + sellAmount, + limitPrice, + expiresAt, + expiresAtUnit, + kind, + limitOrderProtocol = Providers.COW, + } = protocol const modalHeader = useCallback(() => { return ( @@ -65,7 +74,7 @@ export default function ConfirmLimitOrderModal({ askPrice={askPrice} expiresIn={expiresInFormatted} marketPriceDifference={marketPriceDiffPercentage.toFixed(2)} - market={`${market} Protocol`} + market={`${limitOrderProtocol} Protocol`} isDiffPositive={isDiffPositive} /> ) : null diff --git a/src/pages/Swap/LimitOrder/Components/OrderExpiryField.tsx b/src/pages/Swap/LimitOrder/Components/OrderExpiryField.tsx index 9612fa767..b9f1d2a5b 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderExpiryField.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderExpiryField.tsx @@ -1,8 +1,8 @@ -import { useContext, useEffect, useState } from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' -import { OrderExpiresInUnit, LimitOrderContext } from '../../../../services/LimitOrders' +import { OrderExpiresInUnit, LimitOrderBase } from '../../../../services/LimitOrders' import { ButtonAddonsWrapper, InnerWrapper, Input, InputGroup, Label } from './InputGroup' @@ -38,8 +38,7 @@ export const MaxExpiryTime = styled.button` } ` -export function OrderExpiryField() { - const protocol = useContext(LimitOrderContext) +export function OrderExpiryField({ protocol }: { protocol: LimitOrderBase }) { const { expiresAt, expiresAtUnit } = protocol const [inputExpiresIn, setInputExpiresIn] = useState(expiresAt) diff --git a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx index b727091cb..4d50b9d1d 100644 --- a/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx +++ b/src/pages/Swap/LimitOrder/Components/OrderLimitPriceField/OrderLimitPriceField.tsx @@ -182,20 +182,6 @@ export function OrderLimitPriceField({ } const onClickGetMarketPrice = 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) const limitPrice = await handleGetMarketPrice() setInputLimitPrice(limitPrice) } diff --git a/src/pages/Swap/LimitOrder/LimitOrder.tsx b/src/pages/Swap/LimitOrder/LimitOrder.tsx index 6fce842ed..ec365a354 100644 --- a/src/pages/Swap/LimitOrder/LimitOrder.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrder.tsx @@ -3,7 +3,6 @@ import { useEffect, useState } from 'react' import { PageMetaData } from '../../../components/PageMetaData' import { useActiveWeb3React } from '../../../hooks' import LimitOrder, { WalletData } from '../../../services/LimitOrders' -import { LimitOrderProvider } from '../../../services/LimitOrders/LimitOrder.provider' import AppBody from '../../AppBody' import LimitOrderFallback from './Components/LimitFallback' @@ -34,11 +33,7 @@ export default function LimitOrderUI() { <> - {protocol && ( - - - - )} + {protocol && } {!protocol && } diff --git a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx index 6d5202424..402ecf0a7 100644 --- a/src/pages/Swap/LimitOrder/LimitOrderForm.tsx +++ b/src/pages/Swap/LimitOrder/LimitOrderForm.tsx @@ -1,7 +1,7 @@ import { Currency, Token, TokenAmount } from '@swapr/sdk' import { parseUnits } from 'ethers/lib/utils' -import { useCallback, useContext, useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { Link } from 'react-router-dom' import { Flex } from 'rebass' @@ -10,9 +10,8 @@ import { AutoColumn } from '../../../components/Column' import { CurrencyInputPanel } from '../../../components/CurrencyInputPanel' import { ApprovalState, useApproveCallback } from '../../../hooks/useApproveCallback' import { useHigherUSDValue } from '../../../hooks/useUSDValue' -import { Kind, MarketPrices } from '../../../services/LimitOrders' +import { Kind, LimitOrderBase, MarketPrices } from '../../../services/LimitOrders' import { getVaultRelayerAddress } from '../../../services/LimitOrders/CoW/api/cow' -import { LimitOrderContext } from '../../../services/LimitOrders/LimitOrder.provider' import { useNotificationPopup } from '../../../state/application/hooks' import { useCurrencyBalances } from '../../../state/wallet/hooks' import { maxAmountSpend } from '../../../utils/maxAmountSpend' @@ -27,9 +26,7 @@ import { SetToMarket } from './Components/OrderLimitPriceField/styles' import SwapTokens from './Components/SwapTokens' import { formatMarketPrice, formatMaxValue } from './Components/utils' -export default function LimitOrderForm() { - const protocol = useContext(LimitOrderContext) - +export default function LimitOrderForm({ protocol }: { protocol: LimitOrderBase }) { const notify = useNotificationPopup() const [buyAmount, setBuyAmount] = useState(protocol.buyAmount) @@ -67,12 +64,12 @@ export default function LimitOrderForm() { 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 (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 @@ -92,7 +89,7 @@ export default function LimitOrderForm() { } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sellAmount, sellCurrencyMaxAmount, sellToken]) + }, [sellAmount, buyAmount, sellCurrencyMaxAmount, sellToken, buyToken]) // Determine if the token has to be approved first const showApproveFlow = tokenInApproval === ApprovalState.NOT_APPROVED || tokenInApproval === ApprovalState.PENDING @@ -103,15 +100,16 @@ export default function LimitOrderForm() { const onModalDismiss = () => { setIsModalOpen(false) - // setErrorMessage('') } useEffect(() => { async function getMarketPrice() { - await protocol.getMarketPrice() - setBuyAmount(protocol.buyAmount) - - setLoading(false) + try { + await protocol.getMarketPrice() + setBuyAmount(protocol.buyAmount) + } finally { + setLoading(false) + } } setLoading(true) @@ -140,20 +138,20 @@ export default function LimitOrderForm() { setKind(Kind.Sell) protocol.onKindChange(Kind.Sell) + try { + await protocol?.onSellTokenChange(newSellToken) - await protocol?.onSellTokenChange(newSellToken) - - protocol.onLimitPriceChange(protocol.getLimitPrice()) - - setSellAmount(protocol.sellAmount) - setBuyAmount(protocol.buyAmount) - setIsPossibleToOrder({ - status: false, - value: 0, - }) - - setLoading(false) + protocol.onLimitPriceChange(protocol.getLimitPrice()) + setSellAmount(protocol.sellAmount) + setBuyAmount(protocol.buyAmount) + } finally { + setIsPossibleToOrder({ + status: false, + value: 0, + }) + setLoading(false) + } // eslint-disable-next-line react-hooks/exhaustive-deps }, []) @@ -166,19 +164,20 @@ export default function LimitOrderForm() { setKind(Kind.Buy) protocol.onKindChange(Kind.Buy) + try { + await protocol?.onBuyTokenChange(newBuyToken) - await protocol?.onBuyTokenChange(newBuyToken) - - protocol.onLimitPriceChange(protocol.getLimitPrice()) - - setSellAmount(protocol.sellAmount) - setBuyAmount(protocol.buyAmount) - setLoading(false) - setIsPossibleToOrder({ - status: false, - value: 0, - }) + protocol.onLimitPriceChange(protocol.getLimitPrice()) + setSellAmount(protocol.sellAmount) + setBuyAmount(protocol.buyAmount) + } finally { + setLoading(false) + setIsPossibleToOrder({ + status: false, + value: 0, + }) + } // eslint-disable-next-line react-hooks/exhaustive-deps }, []) @@ -199,8 +198,8 @@ export default function LimitOrderForm() { await protocol?.onSellAmountChange(newSellAmount) setBuyAmount(protocol.buyAmount) - setLoading(false) } + setLoading(false) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) @@ -221,8 +220,8 @@ export default function LimitOrderForm() { await protocol?.onBuyAmountChange(newBuyAmount) setSellAmount(protocol.sellAmount) - setLoading(false) } + setLoading(false) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) @@ -237,32 +236,38 @@ export default function LimitOrderForm() { setSellToken(buyToken) setBuyToken(sellToken) setKind(Kind.Sell) - - await protocol.getQuote() - - setBuyAmount(protocol.buyAmount) - setSellAmount(protocol.sellAmount) - setLoading(false) + try { + await protocol.getQuote() + setBuyAmount(protocol.buyAmount) + setSellAmount(protocol.sellAmount) + } finally { + setLoading(false) + } } const [marketPrices, setMarketPrices] = useState({ buy: 0, sell: 0 }) const getMarketPrices = useCallback(async () => { - // const token = kind === Kind.Sell ? sellAmount : buyAmount const tokenSellAmount = Number(sellAmount.toExact()) > 1 ? sellAmount.toExact() : '1' const tokenBuyAmount = Number(buyAmount.toExact()) > 1 ? buyAmount.toExact() : '1' - // if (protocol.kind === Kind.Sell) { - setMarketPrices(() => ({ - sell: formatMarketPrice(protocol.quoteSellAmount.raw.toString(), sellAmount.currency.decimals, tokenSellAmount), - buy: formatMarketPrice(protocol.quoteBuyAmount.raw.toString(), buyAmount.currency.decimals, tokenBuyAmount), - })) - // } else { - // setMarketPrices(marketPrice => ({ - // ...marketPrice, - // sell: formatMarketPrice(protocol.quoteSellAmount.raw.toString(), sellAmount.currency.decimals, tokenAmount), - // })) - // } + const cowQuote = await protocol.getRawQuote() + + if (cowQuote) { + const { buyAmount: quoteBuyAmount, sellAmount: quoteSellAmount } = cowQuote + + if (protocol.kind === Kind.Sell) { + setMarketPrices(marketPrice => ({ + ...marketPrice, + buy: formatMarketPrice(quoteBuyAmount, buyAmount.currency.decimals, tokenSellAmount), + })) + } else { + setMarketPrices(marketPrice => ({ + ...marketPrice, + sell: formatMarketPrice(quoteSellAmount, sellAmount.currency.decimals, tokenBuyAmount), + })) + } + } }, [buyAmount, protocol, sellAmount]) useEffect(() => { @@ -326,7 +331,7 @@ export default function LimitOrderForm() { marketPrices={marketPrices} fiatValueInput={fiatValueInput} fiatValueOutput={fiatValueOutput} - market={protocol.limitOrderProtocol} + protocol={protocol} /> @@ -384,7 +389,7 @@ export default function LimitOrderForm() { /> - + {showApproveFlow ? ( diff --git a/src/services/LimitOrders/1Inch/OneInch.ts b/src/services/LimitOrders/1Inch/OneInch.ts index 668de37c8..26d7df071 100644 --- a/src/services/LimitOrders/1Inch/OneInch.ts +++ b/src/services/LimitOrders/1Inch/OneInch.ts @@ -58,6 +58,10 @@ export class OneInch extends LimitOrderBase { throw new Error('Method not implemented.') } + getRawQuote(): Promise { + throw new Error('Method not implemented.') + } + init(): Promise { throw new Error('Method not implemented.') } diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index 8aa0c4b85..85b06e796 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -173,12 +173,7 @@ export class CoW extends LimitOrderBase { this.userUpdatedLimitPrice = status } - async getQuote(limitOrder?: LimitOrder) { - this.quoteErrorMessage = undefined - if (this.userUpdatedLimitPrice) { - return - } - + async getRawQuote(limitOrder?: LimitOrder) { const signer = this.provider?.getSigner() const chainId = this.activeChainId const order = limitOrder ?? this.limitOrder @@ -212,11 +207,37 @@ export class CoW extends LimitOrderBase { this.quote = cowQuote as CoWQuote + const response = cowQuote + if (!response) { + throw new Error('Quote error') + } + const { id, quote: { buyAmount, sellAmount }, - } = cowQuote + } = response + return { id, buyAmount, sellAmount } + } catch (error: any) { + quoteCounter = ++quoteCounter + if (quoteCounter < 3) { + if (this.#validateError(error)) { + this.getQuote() + } + } + this.logger.error(error.message) + } + } + + async getQuote(limitOrder?: LimitOrder) { + this.quoteErrorMessage = undefined + if (this.userUpdatedLimitPrice) { + return + } + + const response = await this.getRawQuote(limitOrder) + if (response) { + const { id, sellAmount, buyAmount } = response this.onLimitOrderChange({ quoteId: id }) const buyTokenAmount = new TokenAmount(this.buyToken, buyAmount) const sellTokenAmount = new TokenAmount(this.sellToken, sellAmount) @@ -224,7 +245,7 @@ export class CoW extends LimitOrderBase { this.quoteSellAmount = sellTokenAmount this.limitPrice = this.getLimitPrice() - if (kind === Kind.Sell) { + if (this.kind === Kind.Sell) { this.buyAmount = buyTokenAmount this.onLimitOrderChange({ buyAmount: buyTokenAmount.raw.toString(), @@ -235,14 +256,6 @@ export class CoW extends LimitOrderBase { sellAmount: sellTokenAmount.raw.toString(), }) } - } catch (error: any) { - quoteCounter = ++quoteCounter - if (quoteCounter < 3) { - if (this.#validateError(error)) { - this.getQuote() - } - } - this.logger.error(error.message) } } diff --git a/src/services/LimitOrders/LimitOrder.provider.tsx b/src/services/LimitOrders/LimitOrder.provider.tsx deleted file mode 100644 index d8e2d3291..000000000 --- a/src/services/LimitOrders/LimitOrder.provider.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { ReactNode, createContext, useEffect, useState } from 'react' - -import { LimitOrderBase, logger } from './LimitOrder.utils' - -export const LimitOrderContext = createContext({} as LimitOrderBase) - -// TODO: May be don't need a provider. Need to verify at end and remove if not needed -export function LimitOrderProvider({ children, protocol }: { children: ReactNode; protocol: LimitOrderBase }) { - const [value, setProtocol] = useState(protocol) - - useEffect(() => { - logger('Limit Order Protocol Changed') - setProtocol(protocol) - }, [protocol]) - - return {children} -} diff --git a/src/services/LimitOrders/LimitOrder.utils.ts b/src/services/LimitOrders/LimitOrder.utils.ts index ddc4cd82f..504aeed27 100644 --- a/src/services/LimitOrders/LimitOrder.utils.ts +++ b/src/services/LimitOrders/LimitOrder.utils.ts @@ -101,6 +101,14 @@ export abstract class LimitOrderBase { abstract onUserUpadtedLimitPrice(status: boolean): void abstract getQuote(limitOrder?: LimitOrder): Promise + abstract getRawQuote(limitOrder?: LimitOrder): Promise< + | { + id: number | null + buyAmount: string + sellAmount: string + } + | undefined + > abstract getMarketPrice(): Promise abstract getLimitPrice(): string abstract onSignerChange({ account, activeChainId, provider }: WalletData): Promise diff --git a/src/services/LimitOrders/index.ts b/src/services/LimitOrders/index.ts index 7504a9f2e..57e314e25 100644 --- a/src/services/LimitOrders/index.ts +++ b/src/services/LimitOrders/index.ts @@ -2,4 +2,3 @@ export { default } from './LimitOrder' export * from './LimitOrder.types' export * from './LimitOrder.utils' export * from './LimitOrder.config' -export * from './LimitOrder.provider' From 7fb1c68f003762229472b2bde8ac710ab4830efa Mon Sep 17 00:00:00 2001 From: Wixzi Date: Mon, 31 Jul 2023 16:19:49 +0200 Subject: [PATCH 37/37] [SWA-94] Bug Fix --- src/services/LimitOrders/CoW/CoW.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/services/LimitOrders/CoW/CoW.ts b/src/services/LimitOrders/CoW/CoW.ts index 85b06e796..952f0f964 100644 --- a/src/services/LimitOrders/CoW/CoW.ts +++ b/src/services/LimitOrders/CoW/CoW.ts @@ -1,4 +1,4 @@ -import { Currency, TokenAmount } from '@swapr/sdk' +import { ChainId, Currency, TokenAmount } from '@swapr/sdk' import dayjs from 'dayjs' import { parseUnits } from 'ethers/lib/utils' @@ -187,10 +187,11 @@ export class CoW extends LimitOrderBase { if (order.buyAmount !== this.buyAmount.raw.toString()) { order.buyAmount = this.buyAmount.raw.toString() } - // If token is xDAI for CoW ignore it. It happens if we switch from Swap to Limit order and XDai was already selected + // If token is xDAI for CoW on GNOSIS ignore it. It happens if we switch from Swap to Limit order and XDai was already selected if ( - order.sellToken.toLowerCase() === '0x6b175474e89094c44da98b954eedeac495271d0f' || - order.buyToken.toLowerCase() === '0x6b175474e89094c44da98b954eedeac495271d0f' + chainId === ChainId.GNOSIS && + (order.sellToken.toLowerCase() === '0x6b175474e89094c44da98b954eedeac495271d0f' || + order.buyToken.toLowerCase() === '0x6b175474e89094c44da98b954eedeac495271d0f') ) { return }