From c0cc401cc49fd4f7f087f9b6196976effbdae42c Mon Sep 17 00:00:00 2001 From: ridel1e Date: Tue, 21 Dec 2021 12:07:22 +0300 Subject: [PATCH 01/17] fix hooks performance --- src/hooks/useObservable.ts | 45 +++++++++++++++++++++---------------- src/services/new/balance.ts | 2 ++ 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/hooks/useObservable.ts b/src/hooks/useObservable.ts index 87531092f..07d094d32 100644 --- a/src/hooks/useObservable.ts +++ b/src/hooks/useObservable.ts @@ -17,20 +17,25 @@ export function useObservable( observable: Observable, config?: { defaultValue?: T; deps?: any[] }, ): [T | undefined, boolean, Error | undefined] { - const [data, setData] = useState(config?.defaultValue); - const [error, setError] = useState(); - const [loading, setLoading] = useState(true); + const [{ data, error, loading }, setParams] = useState<{ + data: T | undefined; + error: Error | undefined; + loading: boolean; + }>({ + data: config?.defaultValue, + error: undefined, + loading: false, + }); useEffect(() => { - setLoading(true); + setParams((params) => ({ ...params, loading: true })); + const subscription = observable.subscribe({ next: (value: T) => { - setData(() => value); - setLoading(false); + setParams((params) => ({ ...params, data: value, loading: false })); }, error: (error: Error) => { - setError(error); - setLoading(false); + setParams((params) => ({ ...params, error, loading: false })); }, }); @@ -67,12 +72,16 @@ export function useSubject Observable>( boolean, Error | undefined, ] { - const [data, setData] = useState> | undefined>( - config?.defaultValue, - ); - const [error, setError] = useState(); - const [loading, setLoading] = useState(true); - const [nextData, setNextData] = useState<{ + const [{ data, error, loading }, setParams] = useState<{ + data: any | undefined; + error: Error | undefined; + loading: boolean; + }>({ + data: config?.defaultValue, + error: undefined, + loading: false, + }); + const [nextData] = useState<{ subject: Subject>; next: (...args: Parameters) => void; //@ts-ignore @@ -86,17 +95,15 @@ export function useSubject Observable>( }); useEffect(() => { - setLoading(true); + setParams((params) => ({ ...params, loading: true })); const subscription = nextData.subject .pipe(switchMap((args) => observableAction(...args))) .subscribe({ next: (value: Unpacked>) => { - setData(() => value); - setLoading(false); + setParams((params) => ({ ...params, loading: false, data: value })); }, error: (error: Error) => { - setError(error); - setLoading(false); + setParams((params) => ({ ...params, error, loading: false })); }, }); diff --git a/src/services/new/balance.ts b/src/services/new/balance.ts index 1c580c05b..5c1805fce 100644 --- a/src/services/new/balance.ts +++ b/src/services/new/balance.ts @@ -1,6 +1,7 @@ import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; import { combineLatest, + debounceTime, map, Observable, publishReplay, @@ -55,6 +56,7 @@ export const walletBalance$ = combineLatest([ utxos$.pipe(switchMap(() => assets$)), utxos$.pipe(map((utxos) => Object.values(getListAvailableTokens(utxos)))), ]).pipe( + debounceTime(200), map(([nativeTokenBalance, assets, boxAssets]) => boxAssets .map<[bigint, AssetInfo]>((ba) => [ From d2fc2d2cca89013d59697e60d9b7799c25a011d8 Mon Sep 17 00:00:00 2001 From: ridel1e Date: Wed, 22 Dec 2021 01:08:21 +0300 Subject: [PATCH 02/17] add currency structure --- src/services/new/currency.ts | 151 +++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 src/services/new/currency.ts diff --git a/src/services/new/currency.ts b/src/services/new/currency.ts new file mode 100644 index 000000000..749bc9811 --- /dev/null +++ b/src/services/new/currency.ts @@ -0,0 +1,151 @@ +import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; + +import { parseUserInputToFractions, renderFractions } from '../../utils/math'; + +const createUnknownAsset = (decimals = 0) => ({ + id: '-1', + name: 'unknown', + decimals, +}); + +const isUnknownAsset = (asset: AssetInfo): boolean => asset.name === 'unknown'; + +export class Currency { + private _amount = 0n; + + private _asset: AssetInfo = createUnknownAsset(0); + + constructor(amount?: bigint | string, asset?: AssetInfo) { + if (!!asset) { + this._asset = asset; + } + if (typeof amount === 'bigint') { + this._amount = amount; + } + if (typeof amount === 'string') { + this.checkAmountErrors(amount, this._asset); + this._amount = parseUserInputToFractions(amount, this._asset.decimals); + } + } + + get amount(): bigint { + return this._amount; + } + + get asset(): AssetInfo { + return this._asset; + } + + changeAmount(amount: bigint | string): Currency { + return new Currency(amount, this.asset); + } + + changeAsset(asset: AssetInfo): Currency { + return new Currency( + this.normalizeAmount(this.amount, this.asset, asset), + asset, + ); + } + + gt(currency: Currency): boolean { + this.checkComparisonErrors(currency); + return this.amount > currency.amount; + } + + lt(currency: Currency) { + this.checkComparisonErrors(currency); + return this.amount < currency.amount; + } + + gte(currency: Currency) { + this.checkComparisonErrors(currency); + return this.amount >= currency.amount; + } + + lte(currency: Currency) { + this.checkComparisonErrors(currency); + return this.amount <= currency.amount; + } + + plus(currency: Currency): Currency { + if (isUnknownAsset(this.asset)) { + throw new Error("can't sum unknown asset"); + } + if (this.asset.id !== currency.asset.id) { + throw new Error("can't sum currencies with different assets"); + } + + return new Currency(this.amount + currency.amount, this.asset); + } + + minus(currency: Currency) { + if (isUnknownAsset(this.asset)) { + throw new Error("can't subtract unknown asset"); + } + if (this.asset.id !== currency.asset.id) { + throw new Error("can't subtract currencies with different assets"); + } + + return new Currency(this.amount - currency.amount, this.asset); + } + + toString(config?: { prefix: boolean }): string { + if ((!config || !!config?.prefix) && !isUnknownAsset(this.asset)) { + return `${renderFractions(this.amount, this.asset.decimals)} ${ + this.asset.name + }`; + } + + return `${renderFractions(this.amount, this.asset.decimals)}`; + } + + toUsd() {} + + private checkComparisonErrors(currency: Currency): void { + if (isUnknownAsset(this.asset)) { + throw new Error("can't compare unknown asset"); + } + if (this.asset.id !== currency.asset.id) { + throw new Error("can't compare currencies with different assets"); + } + } + + private checkAmountErrors(amount: string, asset: AssetInfo): void { + const decimalsCount = this.getDecimalsCount(amount); + + if (isUnknownAsset(asset)) { + this._asset = createUnknownAsset(decimalsCount); + return; + } + if (decimalsCount > (asset?.decimals || 0)) { + throw new Error('amount has to many fractions'); + } + } + + private getDecimalsCount(amount: string) { + const decimals = amount.split('.')[1]; + + if (decimals) { + return decimals.length; + } + return 0; + } + + private normalizeAmount( + amount: bigint, + currentAsset: AssetInfo, + newAsset: AssetInfo, + ): string { + const amountString = renderFractions(amount, currentAsset.decimals); + const currentDecimalsCount = this.getDecimalsCount(amountString); + + if (currentDecimalsCount <= (newAsset.decimals || 0)) { + return amountString; + } + + return amountString.slice( + 0, + amountString.length - currentDecimalsCount + (newAsset.decimals || 0), + ); + } +} From b8f91debf7449b548e514a05335744fbd8c6cdef Mon Sep 17 00:00:00 2001 From: ridel1e Date: Sat, 25 Dec 2021 19:41:05 +0300 Subject: [PATCH 03/17] update all files with currency stricture --- src/@types/asset.d.ts | 3 + .../ConfirmationModal/ConfirmationModal.tsx | 43 ++-- .../TokensTab/TokenListItem/TokenListItem.tsx | 7 +- .../WalletModal/TokensTab/TokensTab.tsx | 7 +- .../common/PoolSelect/PoolSelect.tsx | 12 +- .../TokenAmountInput/TokenAmountInput.tsx | 68 ++++-- .../common/TokenControl/TokenControl.tsx | 135 +---------- src/ergodex-cdk/components/Form/NewForm.tsx | 2 +- src/hooks/usePair.ts | 3 + src/pages/Pool/AddLiquidity/AddLiquidity.tsx | 220 ++++++------------ .../AddLiquidityConfirmationModal.tsx | 52 ++--- src/pages/Pool/AddLiquidity/FormModel.ts | 16 +- .../ConfirmRemoveModal/ConfirmRemoveModal.tsx | 105 ++++----- src/pages/Remove/PairSpace/PairSpace.tsx | 28 ++- src/pages/Remove/Remove.tsx | 67 +++--- src/pages/Swap/Ratio/Ratio.tsx | 61 ++--- src/pages/Swap/Swap.tsx | 160 +++++-------- .../SwapConfirmationModal.tsx | 33 +-- src/pages/Swap/SwapModel.ts | 8 +- src/pages/Swap/SwapTooltip/SwapTooltip.tsx | 14 +- src/services/new/assets.ts | 8 +- src/services/new/balance.ts | 29 +-- src/services/new/core.ts | 50 +++- src/services/new/currency.ts | 20 +- src/services/new/pools.ts | 72 +++++- src/utils/math.ts | 2 +- 26 files changed, 530 insertions(+), 695 deletions(-) diff --git a/src/@types/asset.d.ts b/src/@types/asset.d.ts index 1f3335bd8..fd8feca50 100644 --- a/src/@types/asset.d.ts +++ b/src/@types/asset.d.ts @@ -1,7 +1,10 @@ +import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; + type Asset = { name?: string; amount?: number; earnedFees?: number; + asset?: AssetInfo; }; type AssetPair = { diff --git a/src/components/ConfirmationModal/ConfirmationModal.tsx b/src/components/ConfirmationModal/ConfirmationModal.tsx index 039d91a9c..7bdbc72a7 100644 --- a/src/components/ConfirmationModal/ConfirmationModal.tsx +++ b/src/components/ConfirmationModal/ConfirmationModal.tsx @@ -1,9 +1,9 @@ import { TxId } from '@ergolabs/ergo-sdk'; -import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; import React, { ReactNode } from 'react'; import { Flex, Modal, Typography } from '../../ergodex-cdk'; import { RequestProps } from '../../ergodex-cdk/components/Modal/presets/Request'; +import { Currency } from '../../services/new/currency'; import { renderFractions } from '../../utils/math'; import { exploreTx } from '../../utils/redirect'; @@ -14,44 +14,27 @@ export enum Operation { REFUND, } -export interface ConfirmationAssetAmount { - readonly amount: number; - readonly asset: AssetInfo; -} - const getDescriptionByData = ( operation: Operation, - xAsset: ConfirmationAssetAmount, - yAsset: ConfirmationAssetAmount, + xAsset: Currency, + yAsset: Currency, ): ReactNode => { switch (operation) { case Operation.ADD_LIQUIDITY: - return `Adding liquidity ${xAsset.amount} ${xAsset.asset.name} and ${yAsset.amount} ${yAsset.asset.name}`; + return `Adding liquidity ${xAsset.toString()} and ${yAsset.toString()}`; case Operation.REFUND: - return `Refunding ${renderFractions( - xAsset.amount, - xAsset.asset.decimals, - )} ${xAsset.asset.name} and ${renderFractions( - yAsset.amount, - yAsset.asset.decimals, - )} ${yAsset.asset.name}`; + return `Refunding ${xAsset.toString()} and ${yAsset.toString()}`; case Operation.REMOVE_LIQUIDITY: - return `Removing liquidity ${renderFractions( - xAsset.amount, - xAsset.asset.decimals, - )} ${xAsset.asset.name} and ${renderFractions( - yAsset.amount, - yAsset.asset.decimals, - )} ${yAsset.asset.name}`; + return `Removing liquidity ${xAsset.toString()} and ${yAsset.toString()}`; case Operation.SWAP: - return `Swapping ${xAsset.amount} ${xAsset.asset.name} for ${yAsset.amount} ${yAsset.asset.name}`; + return `Swapping ${xAsset.toString()} for ${yAsset.toString()}`; } }; const ProgressModalContent = ( operation: Operation, - xAsset: ConfirmationAssetAmount, - yAsset: ConfirmationAssetAmount, + xAsset: Currency, + yAsset: Currency, ) => { return ( @@ -74,8 +57,8 @@ const ProgressModalContent = ( const ErrorModalContent = ( operation: Operation, - xAsset: ConfirmationAssetAmount, - yAsset: ConfirmationAssetAmount, + xAsset: Currency, + yAsset: Currency, ) => ( @@ -114,8 +97,8 @@ const SuccessModalContent = (txId: TxId) => ( export const openConfirmationModal = ( actionContent: RequestProps['actionContent'], operation: Operation, - xAsset: ConfirmationAssetAmount, - yAsset: ConfirmationAssetAmount, + xAsset: Currency, + yAsset: Currency, ) => { return Modal.request({ actionContent, diff --git a/src/components/WalletModal/TokensTab/TokenListItem/TokenListItem.tsx b/src/components/WalletModal/TokensTab/TokenListItem/TokenListItem.tsx index ee9dbcb4c..8f3894e30 100644 --- a/src/components/WalletModal/TokensTab/TokenListItem/TokenListItem.tsx +++ b/src/components/WalletModal/TokensTab/TokenListItem/TokenListItem.tsx @@ -2,11 +2,12 @@ import { AssetInfo } from '@ergolabs/ergo-sdk'; import React, { useEffect } from 'react'; import { Box, Flex, Typography } from '../../../../ergodex-cdk'; +import { Currency } from '../../../../services/new/currency'; import { TokenIcon } from '../../../TokenIcon/TokenIcon'; interface TokenListItemProps { readonly asset: AssetInfo; - readonly balance: number; + readonly balance: Currency; } export const TokenListItem: React.FC = ({ @@ -26,9 +27,7 @@ export const TokenListItem: React.FC = ({ - - {balance} {asset.name} - + {balance.toString()} ); diff --git a/src/components/WalletModal/TokensTab/TokensTab.tsx b/src/components/WalletModal/TokensTab/TokensTab.tsx index c4bf9956a..9f1c5762f 100644 --- a/src/components/WalletModal/TokensTab/TokensTab.tsx +++ b/src/components/WalletModal/TokensTab/TokensTab.tsx @@ -6,14 +6,15 @@ import { Flex, List } from '../../../ergodex-cdk'; import { useObservable } from '../../../hooks/useObservable'; import { assets$ } from '../../../services/new/assets'; import { Balance, walletBalance$ } from '../../../services/new/balance'; +import { Currency } from '../../../services/new/currency'; import { TokenListItem } from './TokenListItem/TokenListItem'; const userAssets$ = combineLatest([assets$, walletBalance$]).pipe( - map<[AssetInfo[], Balance], { asset: AssetInfo; balance: number }[]>( + map<[AssetInfo[], Balance], { asset: AssetInfo; balance: Currency }[]>( ([assets, balance]) => assets - .filter((a) => balance.get(a.id) > 0) - .map((a) => ({ asset: a, balance: balance.get(a.id) })), + .filter((a) => balance.get(a)?.isPositive()) + .map((a) => ({ asset: a, balance: balance.get(a) })), ), ); diff --git a/src/components/common/PoolSelect/PoolSelect.tsx b/src/components/common/PoolSelect/PoolSelect.tsx index 3f14ab16a..c2a3db5f0 100644 --- a/src/components/common/PoolSelect/PoolSelect.tsx +++ b/src/components/common/PoolSelect/PoolSelect.tsx @@ -1,6 +1,5 @@ import './PoolSelect.less'; -import { AmmPool } from '@ergolabs/ergo-dex-sdk'; import { maxBy } from 'lodash'; import React, { useEffect } from 'react'; @@ -12,11 +11,12 @@ import { Menu, Typography, } from '../../../ergodex-cdk'; +import { Pool } from '../../../services/new/pools'; import { TokenIconPair } from '../../TokenIconPair/TokenIconPair'; import { FeeTag } from '../FeeTag/FeeTag'; interface PoolOptionProps { - position: AmmPool; + position: Pool; } const PoolOption: React.FC = ({ position }) => { @@ -49,9 +49,9 @@ const PoolOption: React.FC = ({ position }) => { }; interface PoolSelectProps { - positions?: AmmPool[]; - value?: AmmPool; - onChange?: (pool: AmmPool) => void; + positions?: Pool[]; + value?: Pool; + onChange?: (pool: Pool) => void; } const PoolSelect: React.FC = ({ @@ -59,7 +59,7 @@ const PoolSelect: React.FC = ({ value, onChange, }) => { - const handleChange = (position: AmmPool) => { + const handleChange = (position: Pool) => { if (onChange) { onChange(position); } diff --git a/src/components/common/TokenControl/TokenAmountInput/TokenAmountInput.tsx b/src/components/common/TokenControl/TokenAmountInput/TokenAmountInput.tsx index a89034a58..a39e102e1 100644 --- a/src/components/common/TokenControl/TokenAmountInput/TokenAmountInput.tsx +++ b/src/components/common/TokenControl/TokenAmountInput/TokenAmountInput.tsx @@ -1,8 +1,10 @@ import './TokenAmountInput.less'; -import React from 'react'; +import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; +import React, { useEffect, useState } from 'react'; import { Box, Input } from '../../../../ergodex-cdk'; +import { Currency } from '../../../../services/new/currency'; import { toFloat } from '../../../../utils/string/string'; import { escapeRegExp } from './format'; @@ -14,40 +16,70 @@ export interface TokenAmountInputValue { } export interface TokenAmountInputProps { - value?: TokenAmountInputValue | number; - onChange?: (data: TokenAmountInputValue) => void; + value?: Currency; + onChange?: (data: Currency | undefined) => void; disabled?: boolean; readonly?: boolean; - decimals?: number; + asset?: AssetInfo; } +const isValidAmount = ( + value: string, + asset: AssetInfo | undefined, +): boolean => { + if (!asset) { + return true; + } + if (!asset.decimals && value.indexOf('.') !== -1) { + return false; + } + return (value.split('.')[1]?.length || 0) <= (asset?.decimals || 0); +}; + const TokenAmountInput: React.FC = ({ value, onChange, disabled, readonly, - decimals, + asset, }) => { - const normalizeViewValue = ( - value: TokenAmountInputValue | number | undefined, - ): string | undefined => { - if (typeof value === 'number') { - return toFloat(value.toString(), decimals); + const [userInput, setUserInput] = useState(undefined); + + useEffect(() => { + if (Number(value?.toString({ suffix: false })) !== Number(userInput)) { + setUserInput(value?.toString({ suffix: false })); } + }, [value]); - return toFloat(value?.viewValue || '', decimals); - }; + useEffect(() => { + if (value && asset) { + const newValue = value?.changeAsset(asset); + + setUserInput(newValue.toString({ suffix: false })); + + if (onChange) { + onChange(newValue); + } + } + }, [asset?.id]); const enforcer = (nextUserInput: string) => { if (nextUserInput.startsWith('.')) { nextUserInput = nextUserInput.replace('.', '0.'); } - if (nextUserInput === '' || inputRegex.test(escapeRegExp(nextUserInput))) { + if (nextUserInput === '' && onChange) { + setUserInput(''); + onChange(undefined); + return; + } + if ( + inputRegex.test(escapeRegExp(nextUserInput)) && onChange && - onChange({ - viewValue: nextUserInput, - value: nextUserInput !== undefined ? +nextUserInput : undefined, - }); + isValidAmount(nextUserInput, asset) + ) { + setUserInput(nextUserInput); + onChange(new Currency(nextUserInput, asset)); + return; } }; @@ -55,7 +87,7 @@ const TokenAmountInput: React.FC = ({ { enforcer(event.target.value.replace(/,/g, '.')); }} diff --git a/src/components/common/TokenControl/TokenControl.tsx b/src/components/common/TokenControl/TokenControl.tsx index 8d6091843..2b39861a3 100644 --- a/src/components/common/TokenControl/TokenControl.tsx +++ b/src/components/common/TokenControl/TokenControl.tsx @@ -2,7 +2,7 @@ import './TokenControl.less'; import { AssetInfo } from '@ergolabs/ergo-sdk'; import cn from 'classnames'; -import React, { FC, ReactNode, useEffect } from 'react'; +import React, { FC, ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; import { Observable, of } from 'rxjs'; @@ -11,11 +11,9 @@ import { Form, useFormContext, } from '../../../ergodex-cdk/components/Form/NewForm'; -import { useObservable, useSubject } from '../../../hooks/useObservable'; -import { - getBalanceByTokenId, - useWalletBalance, -} from '../../../services/new/balance'; +import { useObservable } from '../../../hooks/useObservable'; +import { useWalletBalance } from '../../../services/new/balance'; +import { Currency } from '../../../services/new/currency'; import { TokenAmountInput, TokenAmountInputValue, @@ -40,121 +38,6 @@ export interface TokenControlProps { readonly bordered?: boolean; } -const getTokenBalanceByTokenName = (tokenName: string | undefined) => - tokenName ? getBalanceByTokenId(tokenName) : of(undefined); - -export const TokenControl: FC = ({ - label, - value, - onChange, - maxButton, - assets, - hasBorder, - disabled, - readonly, - noBottomInfo, - bordered, -}) => { - const { t } = useTranslation(); - const [balance, updateBalance] = useSubject(getTokenBalanceByTokenName); - - useEffect(() => { - if (value?.asset) { - updateBalance(value?.asset?.id); - } else { - updateBalance(undefined); - } - }, [value, updateBalance]); - - const onAmountChange = (amount: TokenAmountInputValue) => { - if (onChange) { - onChange({ ...value, amount }); - } - }; - - const onTokenChange = (asset: AssetInfo) => { - if (onChange) { - onChange({ ...value, asset }); - } - }; - - const onMaxButtonClick = () => { - if (onChange) { - onChange({ - asset: value?.asset, - amount: { value: +(balance as any), viewValue: balance?.toString() }, - }); - } - }; - - return ( - - - - {label} - - - - - - - - - - - - - {!noBottomInfo && ( - - {balance !== undefined && ( - - - {t`common.tokenControl.balanceLabel`} {balance}{' '} - {value?.asset?.name} - - - )} - {balance !== undefined && maxButton && ( - - )} - - )} - - - ); -}; - export interface TokenControlFormItemProps { readonly name: string; readonly label?: ReactNode; @@ -203,12 +86,9 @@ export const TokenControlFormItem: FC = ({ : of(undefined), ); - const handleMaxButtonClick = (maxBalance: number) => { + const handleMaxButtonClick = (maxBalance: Currency) => { if (amountName) { - form.controls[amountName].patchValue({ - value: maxBalance, - viewValue: maxBalance.toString(), - }); + form.controls[amountName].patchValue(maxBalance); } }; @@ -250,6 +130,7 @@ export const TokenControlFormItem: FC = ({ @@ -285,7 +166,7 @@ export const TokenControlFormItem: FC = ({ {t`common.tokenControl.balanceLabel`}{' '} - {balance.get(selectedAsset)} {selectedAsset?.name} + {balance.get(selectedAsset).toString()} )} diff --git a/src/ergodex-cdk/components/Form/NewForm.tsx b/src/ergodex-cdk/components/Form/NewForm.tsx index f605ad254..df9dbd401 100644 --- a/src/ergodex-cdk/components/Form/NewForm.tsx +++ b/src/ergodex-cdk/components/Form/NewForm.tsx @@ -155,11 +155,11 @@ export class FormControl implements AbstractFormItem { this.withWarnings = !!this.currentWarning; this.withoutWarnings = !this.withWarnings; this.emitEvent(config); + this.parent.emitEvent(); } onChange(value: T): void { this.patchValue(value); - this.parent.emitEvent(); } reset(value: T, config?: EventConfig): void { diff --git a/src/hooks/usePair.ts b/src/hooks/usePair.ts index 9b151271b..4bf9e46d8 100644 --- a/src/hooks/usePair.ts +++ b/src/hooks/usePair.ts @@ -2,6 +2,7 @@ import { AmmPool } from '@ergolabs/ergo-dex-sdk'; import { AssetAmount } from '@ergolabs/ergo-sdk'; import { useEffect, useState } from 'react'; +import { AssetPair } from '../@types/asset'; import { parseUserInputToFractions, renderFractions } from '../utils/math'; interface Pair { @@ -33,12 +34,14 @@ const usePair = (pool: AmmPool | undefined): Pair => { amount: Number( renderFractions(sharedPair[0].amount, sharedPair[0].asset.decimals), ), + asset: sharedPair[0].asset, }, assetY: { name: sharedPair[1].asset.name || '', amount: Number( renderFractions(sharedPair[1].amount, sharedPair[1].asset.decimals), ), + asset: sharedPair[1].asset, }, }; diff --git a/src/pages/Pool/AddLiquidity/AddLiquidity.tsx b/src/pages/Pool/AddLiquidity/AddLiquidity.tsx index 6c08b1bc1..576457056 100644 --- a/src/pages/Pool/AddLiquidity/AddLiquidity.tsx +++ b/src/pages/Pool/AddLiquidity/AddLiquidity.tsx @@ -2,10 +2,9 @@ import './AddLiquidity.less'; import { PoolId } from '@ergolabs/ergo-dex-sdk'; -import { AssetAmount } from '@ergolabs/ergo-sdk'; import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; import { Skeleton } from 'antd'; -import React, { useCallback, useMemo } from 'react'; +import React, { useMemo } from 'react'; import { useParams } from 'react-router'; import { BehaviorSubject, @@ -27,14 +26,6 @@ import { Operation, } from '../../../components/ConfirmationModal/ConfirmationModal'; import { FormPageWrapper } from '../../../components/FormPageWrapper/FormPageWrapper'; -import { - ERG_DECIMALS, - ERG_TOKEN_ID, - ERG_TOKEN_NAME, - UI_FEE, -} from '../../../constants/erg'; -import { defaultExFee } from '../../../constants/settings'; -import { useSettings } from '../../../context'; import { Flex, Typography } from '../../../ergodex-cdk'; import { Form, useForm } from '../../../ergodex-cdk/components/Form/NewForm'; import { @@ -44,12 +35,8 @@ import { } from '../../../hooks/useObservable'; import { assets$, getAvailableAssetFor } from '../../../services/new/assets'; import { useWalletBalance } from '../../../services/new/balance'; +import { useNativeToken, useTotalFees } from '../../../services/new/core'; import { getPoolById, getPoolByPair } from '../../../services/new/pools'; -import { - parseUserInputToFractions, - renderFractions, -} from '../../../utils/math'; -import { calculateTotalFee } from '../../../utils/transactions'; import { AddLiquidityConfirmationModal } from './AddLiquidityConfirmationModal/AddLiquidityConfirmationModal'; import { AddLiquidityFormModel } from './FormModel'; @@ -62,16 +49,13 @@ const getAvailablePools = (xId?: string, yId?: string) => const AddLiquidity = (): JSX.Element => { const [balance] = useWalletBalance(); - const [{ minerFee }] = useSettings(); + const totalFees = useTotalFees(); + const nativeToken = useNativeToken(); const { poolId } = useParams<{ poolId?: PoolId }>(); const form = useForm({ - x: { - name: 'ERG', - id: '0000000000000000000000000000000000000000000000000000000000000000', - decimals: ERG_DECIMALS, - }, + x: undefined, y: undefined, - activePool: undefined, + pool: undefined, xAmount: undefined, yAmount: undefined, }); @@ -95,87 +79,23 @@ const AddLiquidity = (): JSX.Element => { [], ); - const getInsufficientTokenNameForFee = useCallback( - (value: AddLiquidityFormModel): string | undefined => { - const { xAmount, x } = value; - - let totalFees = +calculateTotalFee( - [minerFee, UI_FEE, defaultExFee], - ERG_DECIMALS, - ); - - totalFees = - x?.id === ERG_TOKEN_ID ? totalFees + xAmount?.value! : totalFees; - - return +totalFees > balance.get(ERG_TOKEN_ID) - ? ERG_TOKEN_NAME - : undefined; - }, - [balance, minerFee], - ); - - const getInsufficientTokenNameForTx = useCallback( - (value: AddLiquidityFormModel): string | undefined => { - const { x, y, xAmount, yAmount } = value; - const xAmountValue = xAmount?.value; - const yAmountValue = yAmount?.value; - - if (x && xAmount && xAmountValue! > balance.get(x?.id)) { - return x?.name; - } - - if (y && yAmount && yAmountValue! > balance.get(y?.id)) { - return y?.name; - } - - return undefined; - }, - [balance], - ); - - const isAmountNotEntered = useCallback( - (value: AddLiquidityFormModel): boolean => { - return !value.xAmount?.value || !value.yAmount?.value; - }, - [], + useSubscription( + form.controls.x.valueChanges$, + (token: AssetInfo | undefined) => updateYAssets$.next(token?.id), ); - const isTokensNotSelected = useCallback( - (value: AddLiquidityFormModel): boolean => { - return !value.activePool; - }, - [], + useSubscription(form.controls.x.valueChanges$, () => + form.patchValue({ y: undefined, pool: undefined }), ); - const addLiquidityAction = useCallback((value: AddLiquidityFormModel) => { - openConfirmationModal( - (next) => { - return ( - - ); - }, - Operation.ADD_LIQUIDITY, - { asset: value.x!, amount: value?.xAmount?.value! }, - { asset: value.y!, amount: value?.yAmount?.value! }, - ); - }, []); - useSubscription( - form.controls.x.valueChanges$, - (token: AssetInfo | undefined) => updateYAssets$.next(token?.id), + combineLatest([ + form.controls.x.valueChangesWithSystem$, + form.controls.y.valueChangesWithSystem$, + ]).pipe(debounceTime(100)), + ([x, y]) => { + updatePools(x?.id, y?.id); + }, ); useSubscription( @@ -194,71 +114,75 @@ const AddLiquidity = (): JSX.Element => { }, ); - useSubscription( - combineLatest([ - form.controls.x.valueChangesWithSystem$, - form.controls.y.valueChangesWithSystem$, - ]).pipe(debounceTime(100)), - ([x, y]) => { - updatePools(x?.id, y?.id); - }, - ); - - useSubscription(form.controls.x.valueChanges$, () => - form.patchValue({ y: undefined, activePool: undefined }), - ); - useSubscription( combineLatest([ form.controls.xAmount.valueChanges$.pipe(skip(1)), - form.controls.activePool.valueChanges$, + form.controls.pool.valueChanges$, ]).pipe(debounceTime(100)), - ([amount]) => { - const newYAmount = form.value.activePool!.depositAmount( - new AssetAmount( - form.value.x!, - parseUserInputToFractions(amount?.value ?? 0, form.value.x!.decimals), - ), - ); - - const value = Number( - renderFractions(newYAmount.amount, newYAmount.asset.decimals), - ); - + ([amount]) => form.controls.yAmount.patchValue( - { - value, - viewValue: value.toString(), - }, - { emitEvent: 'system' }, - ); - }, + amount ? form.value.pool!.calculateDepositAmount(amount) : undefined, + { emitEvent: 'silent' }, + ), ); useSubscription( form.controls.yAmount.valueChanges$.pipe(skip(1)), (amount) => { - const newXAmount = form.value.activePool!.depositAmount( - new AssetAmount( - form.value.y!, - parseUserInputToFractions(amount?.value ?? 0, form.value.y!.decimals), - ), - ); - - const value = Number( - renderFractions(newXAmount.amount, newXAmount.asset.decimals), - ); - form.controls.xAmount.patchValue( - { - value, - viewValue: value.toString(), - }, + amount ? form.value.pool!.calculateDepositAmount(amount) : undefined, { emitEvent: 'system' }, ); }, + [], ); + const getInsufficientTokenNameForFee = ({ + xAmount, + }: Required): string | undefined => { + const totalFeesWithAmount = xAmount.isAssetEquals(nativeToken) + ? xAmount.plus(totalFees) + : totalFees; + + return totalFeesWithAmount.gt(balance.get(nativeToken)) + ? nativeToken.name + : undefined; + }; + + const getInsufficientTokenNameForTx = ({ + xAmount, + yAmount, + }: Required): string | undefined => { + if (xAmount.gt(balance.get(xAmount.asset))) { + return xAmount.asset.name; + } + + if (yAmount.gt(balance.get(yAmount.asset))) { + return yAmount.asset.name; + } + + return undefined; + }; + + const isAmountNotEntered = (value: AddLiquidityFormModel): boolean => { + return !value.xAmount?.isPositive() || !value.yAmount?.isPositive(); + }; + + const isTokensNotSelected = (value: AddLiquidityFormModel): boolean => { + return !value.pool; + }; + + const addLiquidityAction = (value: Required) => { + openConfirmationModal( + (next) => { + return ; + }, + Operation.ADD_LIQUIDITY, + value.xAmount!, + value.yAmount!, + ); + }; + return ( { style={{ opacity: isPairSelected ? '' : '0.3' }} > Select Pool - + {({ value, onChange }) => ( ; onClose: (r: Promise) => void; } -const AddLiquidityConfirmationModal: React.FC = ({ - position, - pair, +const AddLiquidityConfirmationModal: FC = ({ + value, onClose, }) => { const [{ minerFee, address, pk }] = useSettings(); const [utxos] = useObservable(utxos$); + const totalFees = useTotalFees(); const uiFeeNErg = parseUserInputToFractions(UI_FEE, ERG_DECIMALS); const exFeeNErg = parseUserInputToFractions(defaultExFee, ERG_DECIMALS); const minerFeeNErgs = parseUserInputToFractions(minerFee, ERG_DECIMALS); - const totalFees = calculateTotalFee( - [minerFee, UI_FEE, defaultExFee], - ERG_DECIMALS, - ); - const addLiquidityOperation = async () => { - if (position && pk && address && utxos) { - const poolId = position.id; + const { pool, yAmount, xAmount } = value; - const actions = poolActions(position); + if (pool && pk && address && utxos) { + const poolId = pool.id; - const inputX = position.x.withAmount( - parseUserInputToFractions( - String(pair.assetX.amount), - position.x.asset.decimals, - ), - ); - const inputY = position.y.withAmount( - parseUserInputToFractions( - String(pair.assetY.amount), - position.y.asset.decimals, - ), - ); + const actions = poolActions(pool['pool']); + + const inputX = pool['pool'].x.withAmount(xAmount.amount); + const inputY = pool['pool'].y.withAmount(yAmount.amount); const target = makeTarget( [inputX, inputY], @@ -99,7 +85,11 @@ const AddLiquidityConfirmationModal: React.FC = ({ - + diff --git a/src/pages/Pool/AddLiquidity/FormModel.ts b/src/pages/Pool/AddLiquidity/FormModel.ts index 749314c6a..2f2d34caf 100644 --- a/src/pages/Pool/AddLiquidity/FormModel.ts +++ b/src/pages/Pool/AddLiquidity/FormModel.ts @@ -1,12 +1,12 @@ -import { AmmPool } from '@ergolabs/ergo-dex-sdk'; +import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; -import { TokenAmountInputValue } from '../../../components/common/TokenControl/TokenAmountInput/TokenAmountInput'; -import { TokenControlValue } from '../../../components/common/TokenControl/TokenControl'; +import { Currency } from '../../../services/new/currency'; +import { Pool } from '../../../services/new/pools'; export interface AddLiquidityFormModel { - readonly x?: TokenControlValue['asset']; - readonly y?: TokenControlValue['asset']; - readonly xAmount?: TokenAmountInputValue; - readonly yAmount?: TokenAmountInputValue; - readonly activePool?: AmmPool; + readonly x?: AssetInfo; + readonly y?: AssetInfo; + readonly xAmount?: Currency; + readonly yAmount?: Currency; + readonly pool?: Pool; } diff --git a/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx b/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx index 3001e46d1..8fcebbef3 100644 --- a/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx +++ b/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx @@ -16,6 +16,8 @@ import { } from '../../../ergodex-cdk'; import { useUTXOs } from '../../../hooks/useUTXOs'; import { explorer } from '../../../services/explorer'; +import { Currency } from '../../../services/new/currency'; +import { Pool } from '../../../services/new/pools'; import { poolActions } from '../../../services/poolActions'; import { submitTx } from '../../../services/yoroi'; import { makeTarget } from '../../../utils/ammMath'; @@ -26,15 +28,17 @@ import { RemoveFormSpaceWrapper } from '../RemoveFormSpaceWrapper/RemoveFormSpac interface ConfirmRemoveModalProps { onClose: (p: Promise) => void; - position: AmmPool; + pool: Pool; lpToRemove: number; - pair: AssetPair; + xAmount: Currency; + yAmount: Currency; } const ConfirmRemoveModal: React.FC = ({ - position, + pool, lpToRemove, - pair, + xAmount, + yAmount, onClose, }) => { const UTXOs = useUTXOs(); @@ -49,61 +53,46 @@ const ConfirmRemoveModal: React.FC = ({ ERG_DECIMALS, ); - const removeOperation = useCallback( - async (position: AmmPool) => { - const actions = poolActions(position); - const lp = position.lp.withAmount(BigInt(lpToRemove.toFixed(0))); + const removeOperation = async (pool: Pool) => { + const actions = poolActions(pool['pool']); + const lp = pool['pool'].lp.withAmount(BigInt(lpToRemove.toFixed(0))); - const poolId = position.id; + const poolId = pool.id; - try { - const network = await explorer.getNetworkContext(); + try { + const network = await explorer.getNetworkContext(); - const inputs = DefaultBoxSelector.select( - UTXOs, - makeTarget( - [lp], - minValueForOrder(minerFeeNErgs, uiFeeNErg, exFeeNErg), - ), - ) as BoxSelection; + const inputs = DefaultBoxSelector.select( + UTXOs, + makeTarget([lp], minValueForOrder(minerFeeNErgs, uiFeeNErg, exFeeNErg)), + ) as BoxSelection; - if (address && pk) { - onClose( - actions - .redeem( - { - poolId, - pk, - lp, - exFee: exFeeNErg, - uiFee: uiFeeNErg, - }, - { - inputs, - changeAddress: address, - selfAddress: address, - feeNErgs: minerFeeNErgs, - network, - }, - ) - .then((tx) => submitTx(tx)), - ); - } - } catch (err) { - message.error('Network connection issue'); + if (address && pk) { + onClose( + actions + .redeem( + { + poolId, + pk, + lp, + exFee: exFeeNErg, + uiFee: uiFeeNErg, + }, + { + inputs, + changeAddress: address, + selfAddress: address, + feeNErgs: minerFeeNErgs, + network, + }, + ) + .then((tx) => submitTx(tx)), + ); } - }, - [ - UTXOs, - address, - exFeeNErg, - lpToRemove, - minerFeeNErgs, - onClose, - pk, - uiFeeNErg, - ], - ); + } catch (err) { + message.error('Network connection issue'); + } + }; return ( <> @@ -112,7 +101,11 @@ const ConfirmRemoveModal: React.FC = ({ - + @@ -158,7 +151,7 @@ const ConfirmRemoveModal: React.FC = ({ block type="primary" size="large" - onClick={() => removeOperation(position)} + onClick={() => removeOperation(pool)} > Remove liquidity diff --git a/src/pages/Remove/PairSpace/PairSpace.tsx b/src/pages/Remove/PairSpace/PairSpace.tsx index 39f1428ed..427f16153 100644 --- a/src/pages/Remove/PairSpace/PairSpace.tsx +++ b/src/pages/Remove/PairSpace/PairSpace.tsx @@ -2,19 +2,23 @@ import React from 'react'; import { TokenIcon } from '../../../components/TokenIcon/TokenIcon'; import { Box, Flex, Typography } from '../../../ergodex-cdk'; +import { Currency } from '../../../services/new/currency'; import { RemoveFormSpaceWrapper } from '../RemoveFormSpaceWrapper/RemoveFormSpaceWrapper'; interface PairSpaceProps { - title: string; - pair: AssetPair; - fees?: boolean; + readonly title: string; + readonly amountX: Currency; + readonly amountY: Currency; + readonly fees?: boolean; } const PairSpace: React.FC = ({ title, - pair, + amountX, + amountY, fees, }): JSX.Element => { + console.log(amountX, amountY); return ( @@ -24,17 +28,19 @@ const PairSpace: React.FC = ({ - + - {pair.assetX.name} + + {amountX.asset.name} + - {fees ? pair.assetX.earnedFees : pair.assetX.amount} + {fees ? undefined : amountX.toString({ suffix: false })} @@ -45,17 +51,19 @@ const PairSpace: React.FC = ({ - + - {pair.assetY.name} + + {amountY.asset.name} + - {fees ? pair.assetY.earnedFees : pair.assetY.amount} + {fees ? undefined : amountY.toString({ suffix: false })} diff --git a/src/pages/Remove/Remove.tsx b/src/pages/Remove/Remove.tsx index 3816a4610..830f5e0c5 100644 --- a/src/pages/Remove/Remove.tsx +++ b/src/pages/Remove/Remove.tsx @@ -4,6 +4,7 @@ import { evaluate } from 'mathjs'; import React, { useCallback, useEffect, useState } from 'react'; import { useParams } from 'react-router'; +import { AssetPair } from '../../@types/asset'; import { openConfirmationModal, Operation, @@ -14,6 +15,8 @@ import { TokenIconPair } from '../../components/TokenIconPair/TokenIconPair'; import { Flex, Skeleton, Typography } from '../../ergodex-cdk'; import { usePair } from '../../hooks/usePair'; import { usePosition } from '../../hooks/usePosition'; +import { Currency } from '../../services/new/currency'; +import { Pool } from '../../services/new/pools'; import { parseUserInputToFractions } from '../../utils/math'; import { ConfirmRemoveModal } from './ConfirmRemoveModal/ConfirmRemoveModal'; import { PairSpace } from './PairSpace/PairSpace'; @@ -23,7 +26,7 @@ import { RemovePositionSlider } from './RemovePositionSlider/RemovePositionSlide const getPercent = (val: number | undefined, percent: string): number => Number(evaluate(`${val} * ${percent}%`)); -const Remove = (): JSX.Element => { +export const Remove = (): JSX.Element => { const { poolId } = useParams<{ poolId: PoolId }>(); const DEFAULT_SLIDER_PERCENTAGE = '100'; @@ -52,12 +55,18 @@ const Remove = (): JSX.Element => { setPair({ assetX: { name: pair.assetX.name, - amount: getPercent(initialPair.assetX.amount, percentage), + asset: pair.assetX.asset, + amount: +getPercent(initialPair.assetX.amount, percentage).toFixed( + pair.assetX.asset?.decimals, + ), earnedFees: pair.assetX?.earnedFees, }, assetY: { name: pair.assetY.name, - amount: getPercent(initialPair.assetY.amount, percentage), + asset: pair.assetY.asset, + amount: +getPercent(initialPair.assetY.amount, percentage).toFixed( + pair.assetY.asset?.decimals, + ), earnedFees: pair.assetY?.earnedFees, }, }); @@ -68,36 +77,30 @@ const Remove = (): JSX.Element => { const handleRemove = () => { if (pair && position && lpToRemove) { + const xAmount = new Currency( + pair.assetX.amount?.toString(), + position.x.asset, + ); + const yAmount = new Currency( + pair.assetY.amount?.toString(), + position.y.asset, + ); + openConfirmationModal( (next) => { return ( ); }, Operation.REMOVE_LIQUIDITY, - { - asset: position?.x.asset, - amount: Number( - parseUserInputToFractions( - pair.assetX.amount!, - position?.x.asset.decimals, - ), - ), - }, - { - asset: position?.y.asset, - amount: Number( - parseUserInputToFractions( - pair.assetY.amount!, - position?.y.asset.decimals, - ), - ), - }, + xAmount, + yAmount, ); } }; @@ -138,7 +141,21 @@ const Remove = (): JSX.Element => { - + {/*TODO: ADD_FEES_DISPLAY_AFTER_SDK_UPDATE[EDEX-468]*/} @@ -157,5 +174,3 @@ const Remove = (): JSX.Element => { ); }; - -export { Remove }; diff --git a/src/pages/Swap/Ratio/Ratio.tsx b/src/pages/Swap/Ratio/Ratio.tsx index f1b1cb6d2..656e336b2 100644 --- a/src/pages/Swap/Ratio/Ratio.tsx +++ b/src/pages/Swap/Ratio/Ratio.tsx @@ -1,69 +1,34 @@ /* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */ -import { AmmPool } from '@ergolabs/ergo-dex-sdk'; -import { AssetAmount } from '@ergolabs/ergo-sdk'; -import React, { useEffect, useMemo } from 'react'; -import { - distinctUntilChanged, - filter, - interval, - map, - mapTo, - of, - startWith, - tap, -} from 'rxjs'; -import { TokenControlValue } from '../../../components/common/TokenControl/TokenControl'; -import { FormInstance, Typography } from '../../../ergodex-cdk'; +import React from 'react'; +import { map } from 'rxjs'; + +import { Typography } from '../../../ergodex-cdk'; import { FormGroup } from '../../../ergodex-cdk/components/Form/NewForm'; -import { useObservable, useSubject } from '../../../hooks/useObservable'; -import { - math, - parseUserInputToFractions, - renderFractions, -} from '../../../utils/math'; +import { useObservable } from '../../../hooks/useObservable'; +import { Currency } from '../../../services/new/currency'; +import { math, renderFractions } from '../../../utils/math'; import { SwapFormModel } from '../SwapModel'; -export function renderPrice(x: AssetAmount, y: AssetAmount): string { - const nameX = x.asset.name ?? x.asset.id.slice(0, 8); - const nameY = y.asset.name ?? y.asset.id.slice(0, 8); +export function renderPrice(x: Currency, y: Currency): string { + const nameX = x.asset.name; + const nameY = y.asset.name; const fmtX = renderFractions(x.amount, x.asset.decimals); const fmtY = renderFractions(y.amount, y.asset.decimals); const p = math.evaluate!(`${fmtY} / ${fmtX}`).toFixed(y.asset.decimals ?? 0); return `1 ${nameX} - ${p} ${nameY}`; } -const calculateRatio = (value: SwapFormModel) => { - return renderPrice( - new AssetAmount( - value?.fromAsset!, - parseUserInputToFractions( - value?.fromAmount?.value!, - value?.fromAsset?.decimals!, - ), - ), - new AssetAmount( - value?.toAsset!, - parseUserInputToFractions( - value?.toAmount?.value!, - value?.toAsset?.decimals!, - ), - ), - ); -}; - export const Ratio = ({ form }: { form: FormGroup }) => { const [ratio] = useObservable( form.valueChangesWithSilent$.pipe( map((value) => { if ( - value.fromAmount?.value && - value.fromAsset && - value.toAmount?.value && - value.toAsset && + value.fromAmount?.isPositive() && + value.toAmount?.isPositive() && value.pool ) { - return calculateRatio(value); + return renderPrice(value.fromAmount, value.toAmount); } else { return undefined; } diff --git a/src/pages/Swap/Swap.tsx b/src/pages/Swap/Swap.tsx index d7c758529..f922da8c1 100644 --- a/src/pages/Swap/Swap.tsx +++ b/src/pages/Swap/Swap.tsx @@ -39,7 +39,8 @@ import { useForm } from '../../ergodex-cdk/components/Form/NewForm'; import { useSubscription } from '../../hooks/useObservable'; import { assets$, getAvailableAssetFor } from '../../services/new/assets'; import { useWalletBalance } from '../../services/new/balance'; -import { getPoolByPair } from '../../services/new/pools'; +import { Currency } from '../../services/new/currency'; +import { getPoolByPair, Pool } from '../../services/new/pools'; import { fractionsToNum, parseUserInputToFractions } from '../../utils/math'; import { calculateTotalFee } from '../../utils/transactions'; import { Ratio } from './Ratio/Ratio'; @@ -48,53 +49,13 @@ import { SwapFormModel } from './SwapModel'; import { SwapTooltip } from './SwapTooltip/SwapTooltip'; import { TransactionSettings } from './TransactionSettings/TransactionSettings'; -const convertToTo = ( - fromAmount: TokenAmountInputValue | undefined, - fromAsset: AssetInfo, - pool: AmmPool, -): number | undefined => { - if (!fromAmount) { - return undefined; - } - - const toAmount = pool.outputAmount( - new AssetAmount( - fromAsset, - parseUserInputToFractions(fromAmount.value!, fromAsset.decimals), - ), - ); - - return fractionsToNum(toAmount.amount, toAmount.asset?.decimals); -}; - -const convertToFrom = ( - toAmount: TokenAmountInputValue | undefined, - toAsset: AssetInfo, - pool: AmmPool, -): number | undefined => { - if (!toAmount) { - return undefined; - } - - const fromAmount = pool.inputAmount( - new AssetAmount( - toAsset, - parseUserInputToFractions(toAmount.value!, toAsset.decimals), - ), - ); - - return fromAmount - ? fractionsToNum(fromAmount.amount, fromAmount.asset?.decimals) - : undefined; -}; - const getToAssets = (fromAsset?: string) => fromAsset ? getAvailableAssetFor(fromAsset) : assets$; const getSelectedPool = ( xId?: string, yId?: string, -): Observable => +): Observable => xId && yId ? getPoolByPair(xId, yId).pipe( map((pools) => maxBy(pools, (p) => p.lp.amount)), @@ -124,73 +85,68 @@ export const Swap = (): JSX.Element => { [], ); - const getInsufficientTokenNameForFee = useCallback( - (value: SwapFormModel) => { - const { fromAmount, fromAsset } = value; - let totalFees = +calculateTotalFee( - [minerFee, UI_FEE, defaultExFee], - ERG_DECIMALS, - ); - totalFees = - fromAsset?.id === ERG_TOKEN_ID - ? totalFees + fromAmount?.value! - : totalFees; + const getInsufficientTokenNameForFee = (value: SwapFormModel) => { + const { fromAmount, fromAsset } = value; + let totalFees = new Currency( + calculateTotalFee([minerFee, UI_FEE, defaultExFee], ERG_DECIMALS), + { + name: 'ERG', + id: '0000000000000000000000000000000000000000000000000000000000000000', + decimals: ERG_DECIMALS, + }, + ); + totalFees = + fromAsset?.id === ERG_TOKEN_ID ? totalFees.plus(fromAmount!) : totalFees; - return +totalFees > balance.get(ERG_TOKEN_ID) - ? ERG_TOKEN_NAME - : undefined; - }, - [minerFee, balance], - ); + return totalFees.gt( + balance.get({ + name: 'ERG', + id: '0000000000000000000000000000000000000000000000000000000000000000', + decimals: ERG_DECIMALS, + }), + ) + ? ERG_TOKEN_NAME + : undefined; + }; - const getInsufficientTokenNameForTx = useCallback( - (value: SwapFormModel) => { - const { fromAmount, fromAsset } = value; - const asset = fromAsset; - const amount = fromAmount?.value; + const getInsufficientTokenNameForTx = (value: SwapFormModel) => { + const { fromAmount, fromAsset } = value; + const asset = fromAsset; + const amount = fromAmount; - if (asset && amount && amount > balance.get(asset)) { - return asset.name; - } + if (asset && amount && amount.gt(balance.get(asset))) { + return asset.name; + } - return undefined; - }, - [balance], - ); + return undefined; + }; - const isAmountNotEntered = useCallback( - (value: SwapFormModel) => - !value.fromAmount?.value || !value.toAmount?.value, - [], - ); + const isAmountNotEntered = (value: SwapFormModel) => + !value.fromAmount?.isPositive() || !value.toAmount?.isPositive(); - const isTokensNotSelected = useCallback( - (value: SwapFormModel) => !value.toAsset || !value.fromAsset, - [], - ); + const isTokensNotSelected = (value: SwapFormModel) => + !value.toAsset || !value.fromAsset; - const submitSwap = useCallback((value: SwapFormModel) => { + const submitSwap = (value: Required) => { openConfirmationModal( (next) => { return ; }, Operation.SWAP, - { asset: value.fromAsset!, amount: value?.fromAmount?.value! }, - { asset: value.toAsset!, amount: value?.toAmount?.value! }, + value.fromAmount!, + value.toAmount!, ); - }, []); + }; - const isLiquidityInsufficient = useCallback((value: SwapFormModel) => { + const isLiquidityInsufficient = (value: SwapFormModel) => { const { toAmount, pool } = value; - if (!toAmount?.value || !pool) { + if (!toAmount?.isPositive() || !pool) { return false; } - return ( - toAmount.value > fractionsToNum(pool?.y.amount, pool?.y.asset.decimals) - ); - }, []); + return toAmount?.gt(pool.y); + }; useSubscription( form.controls.fromAsset.valueChangesWithSilent$, @@ -224,36 +180,26 @@ export const Swap = (): JSX.Element => { form.controls.pool.valueChanges$, ]).pipe( debounceTime(100), - filter(([amount, pool]) => !!amount && !!form.value.fromAsset && !!pool), + filter(([_, pool]) => !!form.value.fromAsset && !!pool), ), ([amount, pool]) => { - const toAmount = convertToTo(amount!, form.value.fromAsset!, pool!); form.patchValue( - { - toAmount: toAmount - ? { value: toAmount, viewValue: toAmount.toString() } - : undefined, - }, + { toAmount: amount ? pool!.calculateOutputAmount(amount) : undefined }, { emitEvent: 'system' }, ); }, ); useSubscription( - combineLatest([ - form.controls.toAmount.valueChanges$, - form.controls.pool.valueChanges$, - ]).pipe( + combineLatest([form.controls.toAmount.valueChanges$]).pipe( debounceTime(100), - filter(([amount, pool]) => !!amount && !!form.value.toAsset && !!pool), + filter(() => !!form.value.toAsset && !!form.value.pool), ), - ([amount, pool]) => { - const fromAmount = convertToFrom(amount!, form.value.toAsset!, pool!); - + ([amount]) => { form.patchValue( { - fromAmount: fromAmount - ? { value: fromAmount, viewValue: fromAmount.toString() } + fromAmount: amount + ? form.value.pool!.calculateInputAmount(amount!) : undefined, }, { emitEvent: 'system' }, diff --git a/src/pages/Swap/SwapConfirmationModal/SwapConfirmationModal.tsx b/src/pages/Swap/SwapConfirmationModal/SwapConfirmationModal.tsx index 9043cc78e..af1361deb 100644 --- a/src/pages/Swap/SwapConfirmationModal/SwapConfirmationModal.tsx +++ b/src/pages/Swap/SwapConfirmationModal/SwapConfirmationModal.tsx @@ -33,7 +33,7 @@ import { import { SwapFormModel } from '../SwapModel'; export interface SwapConfirmationModalProps { - value: SwapFormModel; + value: Required; onClose: (p: Promise) => void; } @@ -63,20 +63,17 @@ export const SwapConfirmationModal: FC = ({ const poolId = value.pool?.id; const poolFeeNum = value.pool?.poolFeeNum; - const inputAmount = value.fromAmount?.viewValue; - const inputAsset = value.fromAsset; - useEffect(() => { - if (value.pool && inputAmount && inputAsset) { + if (value.pool && value.fromAsset && value.fromAmount) { setBaseParams( - getBaseInputParameters(value.pool, { - inputAmount, - inputAsset, + getBaseInputParameters(value.pool['pool'], { + inputAmount: value.fromAmount.toString({ suffix: false }), + inputAsset: value.fromAsset, slippage, }), ); } - }, [inputAmount, inputAsset, slippage, value.pool]); + }, [value.fromAmount, value.fromAsset, slippage, value.pool]); useEffect(() => { if (baseParams?.minOutput) { @@ -106,18 +103,6 @@ export const SwapConfirmationModal: FC = ({ } }, [baseParams, exFeeNErg, nitro, minerFee]); - // const { baseInput, baseInputAmount, minOutput } = getBaseInputParameters( - // value.pool, - // { - // inputAmount, - // inputAsset, - // slippage, - // }, - // ); - - // const exFeePerToken = vars[0]; - // const { maxExFee, minOutput } = vars[1]; - const swapOperation = async () => { if ( poolFeeNum && @@ -127,12 +112,12 @@ export const SwapConfirmationModal: FC = ({ poolId && operationVars && value.pool && - value.fromAmount?.viewValue && + value.fromAmount && value.fromAsset && value.toAsset?.id ) { const pk = publicKeyFromAddress(address)!; - const actions = poolActions(value.pool); + const actions = poolActions(value.pool['pool']); const quoteAsset = value.toAsset?.id; const minNErgs = minValueForOrder( @@ -142,7 +127,7 @@ export const SwapConfirmationModal: FC = ({ ); const target = makeTarget( - [new AssetAmount(inputAsset!, baseParams.baseInputAmount)], + [new AssetAmount(value.fromAsset!, baseParams.baseInputAmount)], minNErgs, ); diff --git a/src/pages/Swap/SwapModel.ts b/src/pages/Swap/SwapModel.ts index 298b5baa1..943adc18e 100644 --- a/src/pages/Swap/SwapModel.ts +++ b/src/pages/Swap/SwapModel.ts @@ -2,11 +2,13 @@ import { AmmPool } from '@ergolabs/ergo-dex-sdk'; import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; import { TokenAmountInputValue } from '../../components/common/TokenControl/TokenAmountInput/TokenAmountInput'; +import { Currency } from '../../services/new/currency'; +import { Pool } from '../../services/new/pools'; export interface SwapFormModel { - readonly fromAmount?: TokenAmountInputValue; - readonly toAmount?: TokenAmountInputValue; + readonly fromAmount?: Currency; + readonly toAmount?: Currency; readonly fromAsset?: AssetInfo; readonly toAsset?: AssetInfo; - readonly pool?: AmmPool; + readonly pool?: Pool; } diff --git a/src/pages/Swap/SwapTooltip/SwapTooltip.tsx b/src/pages/Swap/SwapTooltip/SwapTooltip.tsx index 0b3c2cecd..da0122264 100644 --- a/src/pages/Swap/SwapTooltip/SwapTooltip.tsx +++ b/src/pages/Swap/SwapTooltip/SwapTooltip.tsx @@ -18,16 +18,12 @@ const TxInfoTooltipContent: FC<{ value: SwapFormModel }> = ({ value }) => { const [{ slippage, minerFee, nitro }] = useSettings(); const swapExtremums = - value.fromAmount?.value && - value.fromAsset && - value.toAmount?.value && - value.toAsset && - value.pool + value.fromAmount?.isPositive() && value.toAmount?.isPositive() && value.pool ? swapVars( MIN_EX_FEE, nitro, - getBaseInputParameters(value.pool!, { - inputAmount: value.fromAmount?.value?.toString()!, + getBaseInputParameters(value.pool['pool']!, { + inputAmount: value.fromAmount?.toString({ suffix: false })!, inputAsset: value.fromAsset!, slippage, }).minOutput, @@ -85,9 +81,9 @@ export const SwapTooltip = ({ return value.pool && value.toAsset && - value.toAmount?.value && + value.toAmount?.isPositive() && value.fromAsset && - value.fromAmount?.value ? ( + value.fromAmount?.isPositive() ? ( } diff --git a/src/services/new/assets.ts b/src/services/new/assets.ts index 0fbb39b57..b289fd506 100644 --- a/src/services/new/assets.ts +++ b/src/services/new/assets.ts @@ -5,7 +5,7 @@ import { map, Observable, publishReplay, refCount } from 'rxjs'; import { pools$ } from './pools'; export const assets$ = pools$.pipe( - map((pools) => pools.flatMap((p) => [p.assetX, p.assetY])), + map((pools) => pools.flatMap((p) => [p.x.asset, p.y.asset])), map((assets) => uniqBy(assets, 'id')), publishReplay(1), refCount(), @@ -17,13 +17,13 @@ export const getAssetById = (id: string): Observable => export const getAvailableAssetFor = (assetId: string) => pools$.pipe( map((pools) => - pools.filter((p) => p.assetX.id === assetId || p.assetY.id === assetId), + pools.filter((p) => p.x.asset.id === assetId || p.y.asset.id === assetId), ), map((pools) => pools .flatMap((p) => [ - p.assetX.id !== assetId ? p.assetX : undefined, - p.assetY.id !== assetId ? p.assetY : undefined, + p.x.asset.id !== assetId ? p.x.asset : undefined, + p.y.asset.id !== assetId ? p.y.asset : undefined, ]) .filter(Boolean as any), ), diff --git a/src/services/new/balance.ts b/src/services/new/balance.ts index 5c1805fce..dec8f5e55 100644 --- a/src/services/new/balance.ts +++ b/src/services/new/balance.ts @@ -18,34 +18,29 @@ import { import { fractionsToNum, parseUserInputToFractions } from '../../utils/math'; import { assets$ } from './assets'; import { nativeTokenBalance$, utxos$ } from './core'; +import { Currency } from './currency'; const ERGO_ID = '0000000000000000000000000000000000000000000000000000000000000000'; export class Balance { - private mapTokenIdToBalance = new Map(); + private mapAssetIdToBalance = new Map(); - constructor(tokens: [bigint, AssetInfo][]) { - this.mapTokenIdToBalance = new Map( - tokens.map(([amount, info]) => [ + constructor(assetAmount: [bigint, AssetInfo][]) { + this.mapAssetIdToBalance = new Map( + assetAmount.map(([amount, info]) => [ info.id, - fractionsToNum(amount, info.decimals), + new Currency(amount, info), ]), ); } - get(token: string | AssetInfo) { - if (typeof token === 'string') { - return this.mapTokenIdToBalance.get(token) || 0; - } - if (isAsset(token)) { - return this.mapTokenIdToBalance.get(token.tokenId) || 0; - } - return this.mapTokenIdToBalance.get(token.id) || 0; + get(asset: AssetInfo): Currency { + return this.mapAssetIdToBalance.get(asset.id) || new Currency(0n, asset); } toArray() { - return this.mapTokenIdToBalance.entries(); + return this.mapAssetIdToBalance.entries(); } } @@ -77,11 +72,9 @@ export const useWalletBalance = () => defaultValue: new Balance([]), }); -export const getBalanceByTokenId = ( - token: string | AssetInfo, -): Observable => +export const getBalanceByAsset = (asset: AssetInfo): Observable => walletBalance$.pipe( - map((balance) => balance.get(token)), + map((balance) => balance.get(asset)), publishReplay(1), refCount(), ); diff --git a/src/services/new/core.ts b/src/services/new/core.ts index ba896f4e4..01e3dea0b 100644 --- a/src/services/new/core.ts +++ b/src/services/new/core.ts @@ -1,10 +1,10 @@ import { ergoBoxFromProxy } from '@ergolabs/ergo-sdk'; +import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; import { combineLatest, distinctUntilChanged, filter, from, - iif, interval, map, Observable, @@ -14,12 +14,16 @@ import { startWith, Subject, switchMap, - tap, } from 'rxjs'; -import { ERG_DECIMALS, ERG_TOKEN_NAME } from '../../constants/erg'; +import { ERG_DECIMALS, ERG_TOKEN_NAME, UI_FEE } from '../../constants/erg'; +import { defaultExFee } from '../../constants/settings'; +import { useSettings } from '../../context'; +import { useObservable } from '../../hooks/useObservable'; import { walletCookies } from '../../utils/cookies'; import { renderFractions } from '../../utils/math'; +import { calculateTotalFee } from '../../utils/transactions'; +import { Currency } from './currency'; const UPDATE_TIME = 5 * 1000; const ERGO_ID = @@ -107,3 +111,43 @@ export const getTokenBalance = (tokenId: string): Observable => ), map((amount) => +renderFractions(amount, ERG_DECIMALS)), ); + +export const nativeToken = { + name: 'ERG', + id: ERGO_ID, + decimals: ERG_DECIMALS, +}; + +export const nativeToken$: Observable = of(nativeToken).pipe( + publishReplay(1), + refCount(), +); + +export const uiFee$: Observable = nativeToken$.pipe( + map((nativeToken) => new Currency(UI_FEE.toString(), nativeToken)), + publishReplay(1), + refCount(), +); + +export const defaultExFee$: Observable = nativeToken$.pipe( + map((nativeToken) => new Currency(defaultExFee.toString(), nativeToken)), + publishReplay(1), + refCount(), +); + +export const useNativeToken = (): AssetInfo => { + const [_nativeToken] = useObservable(nativeToken$, { + defaultValue: nativeToken, + }); + + return _nativeToken; +}; + +export const useTotalFees = (): Currency => { + const [{ minerFee }] = useSettings(); + + return new Currency( + calculateTotalFee([minerFee, UI_FEE, defaultExFee], ERG_DECIMALS), + nativeToken, + ); +}; diff --git a/src/services/new/currency.ts b/src/services/new/currency.ts index 749bc9811..aa5b8af45 100644 --- a/src/services/new/currency.ts +++ b/src/services/new/currency.ts @@ -36,6 +36,22 @@ export class Currency { return this._asset; } + fromAmount(amount: bigint | string): Currency { + return this.changeAmount(amount); + } + + isUnknownAsset(): boolean { + return isUnknownAsset(this.asset); + } + + isAssetEquals(a: AssetInfo): boolean { + return a.id === this.asset.id; + } + + isPositive() { + return this.amount > 0n; + } + changeAmount(amount: bigint | string): Currency { return new Currency(amount, this.asset); } @@ -89,8 +105,8 @@ export class Currency { return new Currency(this.amount - currency.amount, this.asset); } - toString(config?: { prefix: boolean }): string { - if ((!config || !!config?.prefix) && !isUnknownAsset(this.asset)) { + toString(config?: { suffix: boolean }): string { + if ((!config || !!config?.suffix) && !isUnknownAsset(this.asset)) { return `${renderFractions(this.amount, this.asset.decimals)} ${ this.asset.name }`; diff --git a/src/services/new/pools.ts b/src/services/new/pools.ts index 118d5fbcb..7098846ac 100644 --- a/src/services/new/pools.ts +++ b/src/services/new/pools.ts @@ -5,7 +5,7 @@ import { NetworkPools, PoolId, } from '@ergolabs/ergo-dex-sdk'; -import { ErgoBox } from '@ergolabs/ergo-sdk'; +import { AssetAmount, ErgoBox } from '@ergolabs/ergo-sdk'; import { combineLatest, defer, @@ -20,8 +20,14 @@ import { } from 'rxjs'; import { getListAvailableTokens } from '../../utils/getListAvailableTokens'; +import { + math, + parseUserInputToFractions, + renderFractions, +} from '../../utils/math'; import { explorer } from '../explorer'; import { utxos$ } from './core'; +import { Currency } from './currency'; export const networkPools = (): NetworkPools => makePools(explorer); export const nativeNetworkPools = (): NetworkPools => makeNativePools(explorer); @@ -59,6 +65,7 @@ export const pools$ = combineLatest([nativeNetworkPools$, networkPools$]).pipe( .concat(networkPools) .filter((p) => p.id != BlacklistedPoolId), ), + map((pools) => pools.map((p) => new Pool(p))), publishReplay(1), refCount(), ); @@ -106,16 +113,65 @@ export const getPoolById = (poolId: PoolId): Observable => map((pools) => pools.find((position) => position.id === poolId)), ); -const byPair = (xId: string, yId: string) => (p: AmmPool) => - (p.assetX.id === xId || p.assetY.id === xId) && - (p.assetX.id === yId || p.assetY.id === yId); +const byPair = (xId: string, yId: string) => (p: Pool) => + (p.x.asset.id === xId || p.y.asset.id === xId) && + (p.x.asset.id === yId || p.y.asset.id === yId); -export const getPoolByPair = ( - xId: string, - yId: string, -): Observable => +export const getPoolByPair = (xId: string, yId: string): Observable => pools$.pipe( map((pools) => pools.filter(byPair(xId, yId))), publishReplay(1), refCount(), ); + +export class Pool { + constructor(private pool: AmmPool) {} + + get id(): PoolId { + return this.pool.id; + } + + get poolFeeNum(): number { + return this.pool.poolFeeNum; + } + + get feeNum(): bigint { + return this.pool.feeNum; + } + + get lp(): Currency { + return new Currency(this.pool.lp.amount, this.pool.lp.asset); + } + + get y(): Currency { + return new Currency(this.pool.y.amount, this.pool.y.asset); + } + + get x(): Currency { + return new Currency(this.pool.x.amount, this.pool.x.asset); + } + + calculateDepositAmount(currency: Currency): Currency { + const depositAmount = this.pool.depositAmount( + new AssetAmount(currency.asset, currency.amount), + ); + + return new Currency(depositAmount?.amount || 0n, depositAmount?.asset); + } + + calculateInputAmount(currency: Currency): Currency { + const inputAmount = this.pool.inputAmount( + new AssetAmount(currency.asset, currency.amount), + ); + + return new Currency(inputAmount?.amount || 0n, inputAmount?.asset); + } + + calculateOutputAmount(currency: Currency): Currency { + const outputAmount = this.pool.outputAmount( + new AssetAmount(currency.asset, currency.amount), + ); + + return new Currency(outputAmount.amount || 0n, outputAmount?.asset); + } +} diff --git a/src/utils/math.ts b/src/utils/math.ts index 86ee2c373..d5016caf4 100644 --- a/src/utils/math.ts +++ b/src/utils/math.ts @@ -37,7 +37,7 @@ export function parseUserInputToFractions( formatOptions, ); // TODO: CHECK_FUNCTION_LAST_CHANGE[] - return BigInt(Number(input).toFixed(0)); + return BigInt(input); } export function renderFractions( From ea333f608fb416f7ba23f1e6f381fa18f5013c69 Mon Sep 17 00:00:00 2001 From: ridel1e Date: Wed, 29 Dec 2021 13:49:35 +0300 Subject: [PATCH 04/17] fix: fix mr remarks --- .../TokensTab/TokenListItem/TokenListItem.tsx | 14 ++--- .../WalletModal/TokensTab/TokensTab.tsx | 30 ++++------ .../common/PoolSelect/PoolSelect.tsx | 12 ++-- src/constants/erg.ts | 2 - src/pages/Pool/AddLiquidity/AddLiquidity.tsx | 18 ++++-- .../AddLiquidityConfirmationModal.tsx | 4 +- src/pages/Pool/AddLiquidity/FormModel.ts | 4 +- .../ConfirmRemoveModal/ConfirmRemoveModal.tsx | 12 ++-- src/pages/Remove/PairSpace/PairSpace.tsx | 21 ++++--- src/pages/Remove/Remove.tsx | 8 +-- src/pages/Swap/Swap.tsx | 57 +++++++------------ src/pages/Swap/SwapModel.ts | 6 +- src/services/new/balance.ts | 15 ++--- src/services/new/core.ts | 22 +++---- src/services/new/pools.ts | 25 ++++---- 15 files changed, 109 insertions(+), 141 deletions(-) diff --git a/src/components/WalletModal/TokensTab/TokenListItem/TokenListItem.tsx b/src/components/WalletModal/TokensTab/TokenListItem/TokenListItem.tsx index 8f3894e30..70b0cf617 100644 --- a/src/components/WalletModal/TokensTab/TokenListItem/TokenListItem.tsx +++ b/src/components/WalletModal/TokensTab/TokenListItem/TokenListItem.tsx @@ -6,28 +6,24 @@ import { Currency } from '../../../../services/new/currency'; import { TokenIcon } from '../../../TokenIcon/TokenIcon'; interface TokenListItemProps { - readonly asset: AssetInfo; - readonly balance: Currency; + readonly currency: Currency; } -export const TokenListItem: React.FC = ({ - asset, - balance, -}) => ( +export const TokenListItem: React.FC = ({ currency }) => ( - + - {asset.name} + {currency.asset.name} {/*{asset.name}*/} - {balance.toString()} + {currency.toString({ suffix: false })} ); diff --git a/src/components/WalletModal/TokensTab/TokensTab.tsx b/src/components/WalletModal/TokensTab/TokensTab.tsx index 9f1c5762f..548ae4b17 100644 --- a/src/components/WalletModal/TokensTab/TokensTab.tsx +++ b/src/components/WalletModal/TokensTab/TokensTab.tsx @@ -1,33 +1,23 @@ -import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; import React from 'react'; -import { combineLatest, map } from 'rxjs'; import { Flex, List } from '../../../ergodex-cdk'; -import { useObservable } from '../../../hooks/useObservable'; -import { assets$ } from '../../../services/new/assets'; -import { Balance, walletBalance$ } from '../../../services/new/balance'; -import { Currency } from '../../../services/new/currency'; +import { useWalletBalance } from '../../../services/new/balance'; import { TokenListItem } from './TokenListItem/TokenListItem'; -const userAssets$ = combineLatest([assets$, walletBalance$]).pipe( - map<[AssetInfo[], Balance], { asset: AssetInfo; balance: Currency }[]>( - ([assets, balance]) => - assets - .filter((a) => balance.get(a)?.isPositive()) - .map((a) => ({ asset: a, balance: balance.get(a) })), - ), -); - export const TokensTab: React.FC = () => { - const [assets] = useObservable(userAssets$); + const [balance] = useWalletBalance(); return ( - - {(item) => ( - - )} + + {(item) => } diff --git a/src/components/common/PoolSelect/PoolSelect.tsx b/src/components/common/PoolSelect/PoolSelect.tsx index c2a3db5f0..3e5c01e6e 100644 --- a/src/components/common/PoolSelect/PoolSelect.tsx +++ b/src/components/common/PoolSelect/PoolSelect.tsx @@ -11,12 +11,12 @@ import { Menu, Typography, } from '../../../ergodex-cdk'; -import { Pool } from '../../../services/new/pools'; +import { AmmPool } from '../../../services/new/pools'; import { TokenIconPair } from '../../TokenIconPair/TokenIconPair'; import { FeeTag } from '../FeeTag/FeeTag'; interface PoolOptionProps { - position: Pool; + position: AmmPool; } const PoolOption: React.FC = ({ position }) => { @@ -49,9 +49,9 @@ const PoolOption: React.FC = ({ position }) => { }; interface PoolSelectProps { - positions?: Pool[]; - value?: Pool; - onChange?: (pool: Pool) => void; + positions?: AmmPool[]; + value?: AmmPool; + onChange?: (pool: AmmPool) => void; } const PoolSelect: React.FC = ({ @@ -59,7 +59,7 @@ const PoolSelect: React.FC = ({ value, onChange, }) => { - const handleChange = (position: Pool) => { + const handleChange = (position: AmmPool) => { if (onChange) { onChange(position); } diff --git a/src/constants/erg.ts b/src/constants/erg.ts index 6df552d89..2a19b9ea6 100644 --- a/src/constants/erg.ts +++ b/src/constants/erg.ts @@ -1,6 +1,4 @@ export const ERG_TOKEN_NAME = 'ERG'; -export const ERG_TOKEN_ID = - '0000000000000000000000000000000000000000000000000000000000000000'; export const DEFAULT_MINER_FEE = BigInt(2_000_000); export const EXECUTION_MINER_FEE = BigInt(2_000_000); export const ERG_DECIMALS = 9; diff --git a/src/pages/Pool/AddLiquidity/AddLiquidity.tsx b/src/pages/Pool/AddLiquidity/AddLiquidity.tsx index 576457056..0a313576b 100644 --- a/src/pages/Pool/AddLiquidity/AddLiquidity.tsx +++ b/src/pages/Pool/AddLiquidity/AddLiquidity.tsx @@ -4,7 +4,7 @@ import './AddLiquidity.less'; import { PoolId } from '@ergolabs/ergo-dex-sdk'; import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; import { Skeleton } from 'antd'; -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { useParams } from 'react-router'; import { BehaviorSubject, @@ -35,7 +35,7 @@ import { } from '../../../hooks/useObservable'; import { assets$, getAvailableAssetFor } from '../../../services/new/assets'; import { useWalletBalance } from '../../../services/new/balance'; -import { useNativeToken, useTotalFees } from '../../../services/new/core'; +import { useNetworkAsset, useTotalFees } from '../../../services/new/core'; import { getPoolById, getPoolByPair } from '../../../services/new/pools'; import { AddLiquidityConfirmationModal } from './AddLiquidityConfirmationModal/AddLiquidityConfirmationModal'; import { AddLiquidityFormModel } from './FormModel'; @@ -50,7 +50,7 @@ const getAvailablePools = (xId?: string, yId?: string) => const AddLiquidity = (): JSX.Element => { const [balance] = useWalletBalance(); const totalFees = useTotalFees(); - const nativeToken = useNativeToken(); + const networkAsset = useNetworkAsset(); const { poolId } = useParams<{ poolId?: PoolId }>(); const form = useForm({ x: undefined, @@ -70,6 +70,12 @@ const AddLiquidity = (): JSX.Element => { ), ); + useEffect(() => { + if (!poolId) { + form.patchValue({ x: networkAsset }); + } + }, [networkAsset]); + const updateYAssets$ = useMemo( () => new BehaviorSubject(undefined), [], @@ -140,12 +146,12 @@ const AddLiquidity = (): JSX.Element => { const getInsufficientTokenNameForFee = ({ xAmount, }: Required): string | undefined => { - const totalFeesWithAmount = xAmount.isAssetEquals(nativeToken) + const totalFeesWithAmount = xAmount.isAssetEquals(networkAsset) ? xAmount.plus(totalFees) : totalFees; - return totalFeesWithAmount.gt(balance.get(nativeToken)) - ? nativeToken.name + return totalFeesWithAmount.gt(balance.get(networkAsset)) + ? networkAsset.name : undefined; }; diff --git a/src/pages/Pool/AddLiquidity/AddLiquidityConfirmationModal/AddLiquidityConfirmationModal.tsx b/src/pages/Pool/AddLiquidity/AddLiquidityConfirmationModal/AddLiquidityConfirmationModal.tsx index ab9d7a115..a87b4bdfe 100644 --- a/src/pages/Pool/AddLiquidity/AddLiquidityConfirmationModal/AddLiquidityConfirmationModal.tsx +++ b/src/pages/Pool/AddLiquidity/AddLiquidityConfirmationModal/AddLiquidityConfirmationModal.tsx @@ -87,8 +87,8 @@ const AddLiquidityConfirmationModal: FC = ({ diff --git a/src/pages/Pool/AddLiquidity/FormModel.ts b/src/pages/Pool/AddLiquidity/FormModel.ts index 2f2d34caf..5895cbdaa 100644 --- a/src/pages/Pool/AddLiquidity/FormModel.ts +++ b/src/pages/Pool/AddLiquidity/FormModel.ts @@ -1,12 +1,12 @@ import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; import { Currency } from '../../../services/new/currency'; -import { Pool } from '../../../services/new/pools'; +import { AmmPool } from '../../../services/new/pools'; export interface AddLiquidityFormModel { readonly x?: AssetInfo; readonly y?: AssetInfo; readonly xAmount?: Currency; readonly yAmount?: Currency; - readonly pool?: Pool; + readonly pool?: AmmPool; } diff --git a/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx b/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx index 8fcebbef3..ee07691ea 100644 --- a/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx +++ b/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx @@ -1,4 +1,4 @@ -import { AmmPool, minValueForOrder } from '@ergolabs/ergo-dex-sdk'; +import { minValueForOrder } from '@ergolabs/ergo-dex-sdk'; import { BoxSelection, DefaultBoxSelector } from '@ergolabs/ergo-sdk'; import React, { useCallback } from 'react'; @@ -17,7 +17,7 @@ import { import { useUTXOs } from '../../../hooks/useUTXOs'; import { explorer } from '../../../services/explorer'; import { Currency } from '../../../services/new/currency'; -import { Pool } from '../../../services/new/pools'; +import { AmmPool } from '../../../services/new/pools'; import { poolActions } from '../../../services/poolActions'; import { submitTx } from '../../../services/yoroi'; import { makeTarget } from '../../../utils/ammMath'; @@ -28,7 +28,7 @@ import { RemoveFormSpaceWrapper } from '../RemoveFormSpaceWrapper/RemoveFormSpac interface ConfirmRemoveModalProps { onClose: (p: Promise) => void; - pool: Pool; + pool: AmmPool; lpToRemove: number; xAmount: Currency; yAmount: Currency; @@ -53,7 +53,7 @@ const ConfirmRemoveModal: React.FC = ({ ERG_DECIMALS, ); - const removeOperation = async (pool: Pool) => { + const removeOperation = async (pool: AmmPool) => { const actions = poolActions(pool['pool']); const lp = pool['pool'].lp.withAmount(BigInt(lpToRemove.toFixed(0))); @@ -103,8 +103,8 @@ const ConfirmRemoveModal: React.FC = ({ diff --git a/src/pages/Remove/PairSpace/PairSpace.tsx b/src/pages/Remove/PairSpace/PairSpace.tsx index 427f16153..15068f0b4 100644 --- a/src/pages/Remove/PairSpace/PairSpace.tsx +++ b/src/pages/Remove/PairSpace/PairSpace.tsx @@ -7,18 +7,17 @@ import { RemoveFormSpaceWrapper } from '../RemoveFormSpaceWrapper/RemoveFormSpac interface PairSpaceProps { readonly title: string; - readonly amountX: Currency; - readonly amountY: Currency; + readonly xAmount: Currency; + readonly yAmount: Currency; readonly fees?: boolean; } const PairSpace: React.FC = ({ title, - amountX, - amountY, + xAmount, + yAmount, fees, }): JSX.Element => { - console.log(amountX, amountY); return ( @@ -28,11 +27,11 @@ const PairSpace: React.FC = ({ - + - {amountX.asset.name} + {xAmount.asset.name} @@ -40,7 +39,7 @@ const PairSpace: React.FC = ({ - {fees ? undefined : amountX.toString({ suffix: false })} + {fees ? undefined : xAmount.toString({ suffix: false })} @@ -51,11 +50,11 @@ const PairSpace: React.FC = ({ - + - {amountY.asset.name} + {yAmount.asset.name} @@ -63,7 +62,7 @@ const PairSpace: React.FC = ({ - {fees ? undefined : amountY.toString({ suffix: false })} + {fees ? undefined : yAmount.toString({ suffix: false })} diff --git a/src/pages/Remove/Remove.tsx b/src/pages/Remove/Remove.tsx index 830f5e0c5..31a937b60 100644 --- a/src/pages/Remove/Remove.tsx +++ b/src/pages/Remove/Remove.tsx @@ -16,7 +16,7 @@ import { Flex, Skeleton, Typography } from '../../ergodex-cdk'; import { usePair } from '../../hooks/usePair'; import { usePosition } from '../../hooks/usePosition'; import { Currency } from '../../services/new/currency'; -import { Pool } from '../../services/new/pools'; +import { AmmPool } from '../../services/new/pools'; import { parseUserInputToFractions } from '../../utils/math'; import { ConfirmRemoveModal } from './ConfirmRemoveModal/ConfirmRemoveModal'; import { PairSpace } from './PairSpace/PairSpace'; @@ -93,7 +93,7 @@ export const Remove = (): JSX.Element => { onClose={next} xAmount={xAmount} yAmount={yAmount} - pool={new Pool(position)} + pool={new AmmPool(position)} lpToRemove={lpToRemove} /> ); @@ -143,13 +143,13 @@ export const Remove = (): JSX.Element => { const getSelectedPool = ( xId?: string, yId?: string, -): Observable => +): Observable => xId && yId ? getPoolByPair(xId, yId).pipe( map((pools) => maxBy(pools, (p) => p.lp.amount)), @@ -66,16 +58,14 @@ export const Swap = (): JSX.Element => { const form = useForm({ fromAmount: undefined, toAmount: undefined, - fromAsset: { - name: 'ERG', - id: '0000000000000000000000000000000000000000000000000000000000000000', - decimals: ERG_DECIMALS, - }, + fromAsset: undefined, toAsset: undefined, pool: undefined, }); + const networkAsset = useNetworkAsset(); const [balance] = useWalletBalance(); const [{ minerFee }] = useSettings(); + const totalFees = useTotalFees(); const updateToAssets$ = useMemo( () => new BehaviorSubject(undefined), [], @@ -85,27 +75,18 @@ export const Swap = (): JSX.Element => { [], ); - const getInsufficientTokenNameForFee = (value: SwapFormModel) => { - const { fromAmount, fromAsset } = value; - let totalFees = new Currency( - calculateTotalFee([minerFee, UI_FEE, defaultExFee], ERG_DECIMALS), - { - name: 'ERG', - id: '0000000000000000000000000000000000000000000000000000000000000000', - decimals: ERG_DECIMALS, - }, - ); - totalFees = - fromAsset?.id === ERG_TOKEN_ID ? totalFees.plus(fromAmount!) : totalFees; + useEffect(() => { + form.patchValue({ fromAsset: networkAsset }); + }, [networkAsset]); + + const getInsufficientTokenNameForFee = (value: Required) => { + const { fromAmount } = value; + const totalFeesWithAmount = fromAmount.isAssetEquals(networkAsset) + ? fromAmount.plus(totalFees) + : totalFees; - return totalFees.gt( - balance.get({ - name: 'ERG', - id: '0000000000000000000000000000000000000000000000000000000000000000', - decimals: ERG_DECIMALS, - }), - ) - ? ERG_TOKEN_NAME + return totalFeesWithAmount.gt(balance.get(networkAsset)) + ? networkAsset.name : undefined; }; diff --git a/src/pages/Swap/SwapModel.ts b/src/pages/Swap/SwapModel.ts index 943adc18e..03e8d3d45 100644 --- a/src/pages/Swap/SwapModel.ts +++ b/src/pages/Swap/SwapModel.ts @@ -1,14 +1,12 @@ -import { AmmPool } from '@ergolabs/ergo-dex-sdk'; import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; -import { TokenAmountInputValue } from '../../components/common/TokenControl/TokenAmountInput/TokenAmountInput'; import { Currency } from '../../services/new/currency'; -import { Pool } from '../../services/new/pools'; +import { AmmPool } from '../../services/new/pools'; export interface SwapFormModel { readonly fromAmount?: Currency; readonly toAmount?: Currency; readonly fromAsset?: AssetInfo; readonly toAsset?: AssetInfo; - readonly pool?: Pool; + readonly pool?: AmmPool; } diff --git a/src/services/new/balance.ts b/src/services/new/balance.ts index dec8f5e55..a2b8c64fb 100644 --- a/src/services/new/balance.ts +++ b/src/services/new/balance.ts @@ -11,11 +11,8 @@ import { import { ERG_DECIMALS } from '../../constants/erg'; import { useObservable } from '../../hooks/useObservable'; -import { - getListAvailableTokens, - isAsset, -} from '../../utils/getListAvailableTokens'; -import { fractionsToNum, parseUserInputToFractions } from '../../utils/math'; +import { getListAvailableTokens } from '../../utils/getListAvailableTokens'; +import { parseUserInputToFractions } from '../../utils/math'; import { assets$ } from './assets'; import { nativeTokenBalance$, utxos$ } from './core'; import { Currency } from './currency'; @@ -39,8 +36,12 @@ export class Balance { return this.mapAssetIdToBalance.get(asset.id) || new Currency(0n, asset); } - toArray() { - return this.mapAssetIdToBalance.entries(); + entries() { + return Array.from(this.mapAssetIdToBalance.entries()); + } + + values() { + return Array.from(this.mapAssetIdToBalance.values()); } } diff --git a/src/services/new/core.ts b/src/services/new/core.ts index 01e3dea0b..ac16c55f6 100644 --- a/src/services/new/core.ts +++ b/src/services/new/core.ts @@ -112,32 +112,26 @@ export const getTokenBalance = (tokenId: string): Observable => map((amount) => +renderFractions(amount, ERG_DECIMALS)), ); -export const nativeToken = { +export const networkAsset = { name: 'ERG', id: ERGO_ID, decimals: ERG_DECIMALS, }; -export const nativeToken$: Observable = of(nativeToken).pipe( +export const networkAsset$: Observable = of(networkAsset).pipe( publishReplay(1), refCount(), ); -export const uiFee$: Observable = nativeToken$.pipe( - map((nativeToken) => new Currency(UI_FEE.toString(), nativeToken)), - publishReplay(1), - refCount(), -); - -export const defaultExFee$: Observable = nativeToken$.pipe( +export const defaultExFee$: Observable = networkAsset$.pipe( map((nativeToken) => new Currency(defaultExFee.toString(), nativeToken)), publishReplay(1), refCount(), ); -export const useNativeToken = (): AssetInfo => { - const [_nativeToken] = useObservable(nativeToken$, { - defaultValue: nativeToken, +export const useNetworkAsset = (): AssetInfo => { + const [_nativeToken] = useObservable(networkAsset$, { + defaultValue: networkAsset, }); return _nativeToken; @@ -147,7 +141,7 @@ export const useTotalFees = (): Currency => { const [{ minerFee }] = useSettings(); return new Currency( - calculateTotalFee([minerFee, UI_FEE, defaultExFee], ERG_DECIMALS), - nativeToken, + calculateTotalFee([minerFee, defaultExFee], ERG_DECIMALS), + networkAsset, ); }; diff --git a/src/services/new/pools.ts b/src/services/new/pools.ts index 7098846ac..57e0624bf 100644 --- a/src/services/new/pools.ts +++ b/src/services/new/pools.ts @@ -1,5 +1,5 @@ import { - AmmPool, + AmmPool as BaseAmmPool, makeNativePools, makePools, NetworkPools, @@ -39,9 +39,9 @@ const utxosToTokenIds = (utxos: ErgoBox[]): string[] => Object.values(getListAvailableTokens(utxos)).map((token) => token.tokenId); const filterPoolsByTokenIds = ( - pools: AmmPool[], + pools: BaseAmmPool[], tokenIds: string[], -): AmmPool[] => pools.filter((p) => tokenIds.includes(p.lp.asset.id)); +): BaseAmmPool[] => pools.filter((p) => tokenIds.includes(p.lp.asset.id)); const nativeNetworkPools$ = defer(() => from(nativeNetworkPools().getAll({ limit: 100, offset: 0 })), @@ -65,7 +65,7 @@ export const pools$ = combineLatest([nativeNetworkPools$, networkPools$]).pipe( .concat(networkPools) .filter((p) => p.id != BlacklistedPoolId), ), - map((pools) => pools.map((p) => new Pool(p))), + map((pools) => pools.map((p) => new AmmPool(p))), publishReplay(1), refCount(), ); @@ -98,7 +98,7 @@ const availableNetworkPools$ = utxos$.pipe( refCount(), ); -export const availablePools$: Observable = zip([ +export const availablePools$: Observable = zip([ availableNativeNetworkPools$, availableNetworkPools$, ]).pipe( @@ -108,24 +108,29 @@ export const availablePools$: Observable = zip([ refCount(), ); -export const getPoolById = (poolId: PoolId): Observable => +export const getPoolById = ( + poolId: PoolId, +): Observable => availablePools$.pipe( map((pools) => pools.find((position) => position.id === poolId)), ); -const byPair = (xId: string, yId: string) => (p: Pool) => +const byPair = (xId: string, yId: string) => (p: AmmPool) => (p.x.asset.id === xId || p.y.asset.id === xId) && (p.x.asset.id === yId || p.y.asset.id === yId); -export const getPoolByPair = (xId: string, yId: string): Observable => +export const getPoolByPair = ( + xId: string, + yId: string, +): Observable => pools$.pipe( map((pools) => pools.filter(byPair(xId, yId))), publishReplay(1), refCount(), ); -export class Pool { - constructor(private pool: AmmPool) {} +export class AmmPool { + constructor(private pool: BaseAmmPool) {} get id(): PoolId { return this.pool.id; From 396af8baf5ef2ec29fd6232e4b48436d946b659e Mon Sep 17 00:00:00 2001 From: ridel1e Date: Wed, 29 Dec 2021 14:13:05 +0300 Subject: [PATCH 05/17] update swap component --- src/pages/Swap/Swap.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/pages/Swap/Swap.tsx b/src/pages/Swap/Swap.tsx index 693b9a34e..ebaef2f52 100644 --- a/src/pages/Swap/Swap.tsx +++ b/src/pages/Swap/Swap.tsx @@ -23,23 +23,18 @@ import { Operation, } from '../../components/ConfirmationModal/ConfirmationModal'; import { FormPageWrapper } from '../../components/FormPageWrapper/FormPageWrapper'; -import { ERG_DECIMALS, UI_FEE } from '../../constants/erg'; -import { defaultExFee } from '../../constants/settings'; -import { useSettings } from '../../context'; import { Button, Flex, SwapOutlined, Typography } from '../../ergodex-cdk'; import { useForm } from '../../ergodex-cdk/components/Form/NewForm'; import { useSubscription } from '../../hooks/useObservable'; import { assets$, getAvailableAssetFor } from '../../services/new/assets'; import { useWalletBalance } from '../../services/new/balance'; import { useNetworkAsset, useTotalFees } from '../../services/new/core'; -import { Currency } from '../../services/new/currency'; import { AmmPool, getPoolByPair } from '../../services/new/pools'; -import { calculateTotalFee } from '../../utils/transactions'; +import { OperationSettings } from './OperationSettings/OperationSettings'; import { Ratio } from './Ratio/Ratio'; import { SwapConfirmationModal } from './SwapConfirmationModal/SwapConfirmationModal'; import { SwapFormModel } from './SwapModel'; import { SwapTooltip } from './SwapTooltip/SwapTooltip'; -import { TransactionSettings } from './TransactionSettings/TransactionSettings'; const getToAssets = (fromAsset?: string) => fromAsset ? getAvailableAssetFor(fromAsset) : assets$; @@ -64,7 +59,6 @@ export const Swap = (): JSX.Element => { }); const networkAsset = useNetworkAsset(); const [balance] = useWalletBalance(); - const [{ minerFee }] = useSettings(); const totalFees = useTotalFees(); const updateToAssets$ = useMemo( () => new BehaviorSubject(undefined), @@ -219,7 +213,7 @@ export const Swap = (): JSX.Element => { {t`swap.title`} - + {t`swap.subtitle`} From 3c2c588611313d23570272820b077586d1fa2828 Mon Sep 17 00:00:00 2001 From: ridel1e Date: Thu, 30 Dec 2021 21:41:43 +0300 Subject: [PATCH 06/17] refactoring & update ratio component --- src/common/models/AmmPool.ts | 95 +++++++++++++++++++ .../currency.ts => common/models/Currency.ts} | 28 ++---- src/common/utils/amount.ts | 23 +++++ .../ConfirmationModal/ConfirmationModal.tsx | 2 +- .../TokensTab/TokenListItem/TokenListItem.tsx | 2 +- .../common/PoolSelect/PoolSelect.tsx | 2 +- .../TokenAmountInput/TokenAmountInput.tsx | 2 +- .../common/TokenControl/TokenControl.tsx | 2 +- src/pages/Pool/AddLiquidity/FormModel.ts | 4 +- .../ConfirmRemoveModal/ConfirmRemoveModal.tsx | 4 +- src/pages/Remove/PairSpace/PairSpace.tsx | 2 +- src/pages/Remove/Remove.tsx | 4 +- .../Swap/PriceIndicator/PriceIndicator.less | 4 + .../Swap/PriceIndicator/PriceIndicator.tsx | 75 +++++++++++++++ src/pages/Swap/Ratio/Ratio.tsx | 41 -------- src/pages/Swap/Swap.tsx | 9 +- .../SwapConfirmationModal.tsx | 2 +- .../Swap/{SwapModel.ts => SwapFormModel.ts} | 4 +- src/pages/Swap/SwapTooltip/SwapTooltip.tsx | 2 +- src/services/new/balance.ts | 2 +- src/services/new/core.ts | 2 +- src/services/new/pools.ts | 55 +---------- 22 files changed, 230 insertions(+), 136 deletions(-) create mode 100644 src/common/models/AmmPool.ts rename src/{services/new/currency.ts => common/models/Currency.ts} (86%) create mode 100644 src/common/utils/amount.ts create mode 100644 src/pages/Swap/PriceIndicator/PriceIndicator.less create mode 100644 src/pages/Swap/PriceIndicator/PriceIndicator.tsx delete mode 100644 src/pages/Swap/Ratio/Ratio.tsx rename src/pages/Swap/{SwapModel.ts => SwapFormModel.ts} (70%) diff --git a/src/common/models/AmmPool.ts b/src/common/models/AmmPool.ts new file mode 100644 index 000000000..e89225b86 --- /dev/null +++ b/src/common/models/AmmPool.ts @@ -0,0 +1,95 @@ +import { PoolId } from '@ergolabs/ergo-dex-sdk'; +import { AmmPool as BaseAmmPool } from '@ergolabs/ergo-dex-sdk/build/main/amm/entities/ammPool'; +import { AssetAmount } from '@ergolabs/ergo-sdk'; + +import { math, renderFractions } from '../../utils/math'; +import { normalizeAmount } from '../utils/amount'; +import { Currency } from './Currency'; + +export class AmmPool { + constructor(private pool: BaseAmmPool) {} + + get id(): PoolId { + return this.pool.id; + } + + get poolFeeNum(): number { + return this.pool.poolFeeNum; + } + + get feeNum(): bigint { + return this.pool.feeNum; + } + + get lp(): Currency { + return new Currency(this.pool.lp.amount, this.pool.lp.asset); + } + + get y(): Currency { + return new Currency(this.pool.y.amount, this.pool.y.asset); + } + + get x(): Currency { + return new Currency(this.pool.x.amount, this.pool.x.asset); + } + + calculateOutputRatio(inputCurrency: Currency): Currency { + const outputCurrency = this.calculateOutputAmount(inputCurrency); + + if (inputCurrency.amount === 1n) { + return outputCurrency; + } + + const fmtInput = inputCurrency.toString({ suffix: false }); + const fmtOutput = outputCurrency.toString({ suffix: false }); + + const p = math.evaluate!(`${fmtOutput} / ${fmtInput}`).toString(); + + return new Currency( + normalizeAmount(p, outputCurrency.asset), + outputCurrency.asset, + ); + } + + calculateInputRatio(outputCurrency: Currency): Currency { + const inputCurrency = this.calculateInputAmount(outputCurrency); + + if (outputCurrency.amount === 1n) { + return inputCurrency; + } + + const fmtInput = inputCurrency.toString({ suffix: false }); + const fmtOutput = outputCurrency.toString({ suffix: false }); + + const p = math.evaluate!(`${fmtInput} / ${fmtOutput}`).toString(); + + return new Currency( + normalizeAmount(p, inputCurrency.asset), + inputCurrency.asset, + ); + } + + calculateDepositAmount(currency: Currency): Currency { + const depositAmount = this.pool.depositAmount( + new AssetAmount(currency.asset, currency.amount), + ); + + return new Currency(depositAmount?.amount || 0n, depositAmount?.asset); + } + + calculateInputAmount(currency: Currency): Currency { + const inputAmount = this.pool.inputAmount( + new AssetAmount(currency.asset, currency.amount), + ); + + return new Currency(inputAmount?.amount || 0n, inputAmount?.asset); + } + + calculateOutputAmount(currency: Currency): Currency { + const outputAmount = this.pool.outputAmount( + new AssetAmount(currency.asset, currency.amount), + ); + + return new Currency(outputAmount.amount || 0n, outputAmount?.asset); + } +} diff --git a/src/services/new/currency.ts b/src/common/models/Currency.ts similarity index 86% rename from src/services/new/currency.ts rename to src/common/models/Currency.ts index aa5b8af45..088dbbc53 100644 --- a/src/services/new/currency.ts +++ b/src/common/models/Currency.ts @@ -1,6 +1,11 @@ import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; -import { parseUserInputToFractions, renderFractions } from '../../utils/math'; +import { + math, + parseUserInputToFractions, + renderFractions, +} from '../../utils/math'; +import { getDecimalsCount, normalizeAmount } from '../utils/amount'; const createUnknownAsset = (decimals = 0) => ({ id: '-1', @@ -127,7 +132,7 @@ export class Currency { } private checkAmountErrors(amount: string, asset: AssetInfo): void { - const decimalsCount = this.getDecimalsCount(amount); + const decimalsCount = getDecimalsCount(amount); if (isUnknownAsset(asset)) { this._asset = createUnknownAsset(decimalsCount); @@ -138,30 +143,13 @@ export class Currency { } } - private getDecimalsCount(amount: string) { - const decimals = amount.split('.')[1]; - - if (decimals) { - return decimals.length; - } - return 0; - } - private normalizeAmount( amount: bigint, currentAsset: AssetInfo, newAsset: AssetInfo, ): string { const amountString = renderFractions(amount, currentAsset.decimals); - const currentDecimalsCount = this.getDecimalsCount(amountString); - if (currentDecimalsCount <= (newAsset.decimals || 0)) { - return amountString; - } - - return amountString.slice( - 0, - amountString.length - currentDecimalsCount + (newAsset.decimals || 0), - ); + return normalizeAmount(amountString, newAsset); } } diff --git a/src/common/utils/amount.ts b/src/common/utils/amount.ts new file mode 100644 index 000000000..be786959e --- /dev/null +++ b/src/common/utils/amount.ts @@ -0,0 +1,23 @@ +import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; + +export const getDecimalsCount = (amount: string) => { + const decimals = amount.split('.')[1]; + + if (decimals) { + return decimals.length; + } + return 0; +}; + +export const normalizeAmount = (amount: string, asset: AssetInfo) => { + const currentDecimalsCount = getDecimalsCount(amount); + + if (currentDecimalsCount <= (asset.decimals || 0)) { + return amount; + } + + return amount.slice( + 0, + amount.length - currentDecimalsCount + (asset.decimals || 0), + ); +}; diff --git a/src/components/ConfirmationModal/ConfirmationModal.tsx b/src/components/ConfirmationModal/ConfirmationModal.tsx index 7bdbc72a7..a311b2048 100644 --- a/src/components/ConfirmationModal/ConfirmationModal.tsx +++ b/src/components/ConfirmationModal/ConfirmationModal.tsx @@ -1,9 +1,9 @@ import { TxId } from '@ergolabs/ergo-sdk'; import React, { ReactNode } from 'react'; +import { Currency } from '../../common/models/Currency'; import { Flex, Modal, Typography } from '../../ergodex-cdk'; import { RequestProps } from '../../ergodex-cdk/components/Modal/presets/Request'; -import { Currency } from '../../services/new/currency'; import { renderFractions } from '../../utils/math'; import { exploreTx } from '../../utils/redirect'; diff --git a/src/components/WalletModal/TokensTab/TokenListItem/TokenListItem.tsx b/src/components/WalletModal/TokensTab/TokenListItem/TokenListItem.tsx index 70b0cf617..45c3ca75b 100644 --- a/src/components/WalletModal/TokensTab/TokenListItem/TokenListItem.tsx +++ b/src/components/WalletModal/TokensTab/TokenListItem/TokenListItem.tsx @@ -1,8 +1,8 @@ import { AssetInfo } from '@ergolabs/ergo-sdk'; import React, { useEffect } from 'react'; +import { Currency } from '../../../../common/models/Currency'; import { Box, Flex, Typography } from '../../../../ergodex-cdk'; -import { Currency } from '../../../../services/new/currency'; import { TokenIcon } from '../../../TokenIcon/TokenIcon'; interface TokenListItemProps { diff --git a/src/components/common/PoolSelect/PoolSelect.tsx b/src/components/common/PoolSelect/PoolSelect.tsx index 3e5c01e6e..fb332c096 100644 --- a/src/components/common/PoolSelect/PoolSelect.tsx +++ b/src/components/common/PoolSelect/PoolSelect.tsx @@ -3,6 +3,7 @@ import './PoolSelect.less'; import { maxBy } from 'lodash'; import React, { useEffect } from 'react'; +import { AmmPool } from '../../../common/models/AmmPool'; import { Button, DownOutlined, @@ -11,7 +12,6 @@ import { Menu, Typography, } from '../../../ergodex-cdk'; -import { AmmPool } from '../../../services/new/pools'; import { TokenIconPair } from '../../TokenIconPair/TokenIconPair'; import { FeeTag } from '../FeeTag/FeeTag'; diff --git a/src/components/common/TokenControl/TokenAmountInput/TokenAmountInput.tsx b/src/components/common/TokenControl/TokenAmountInput/TokenAmountInput.tsx index a39e102e1..7d53df737 100644 --- a/src/components/common/TokenControl/TokenAmountInput/TokenAmountInput.tsx +++ b/src/components/common/TokenControl/TokenAmountInput/TokenAmountInput.tsx @@ -3,8 +3,8 @@ import './TokenAmountInput.less'; import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; import React, { useEffect, useState } from 'react'; +import { Currency } from '../../../../common/models/Currency'; import { Box, Input } from '../../../../ergodex-cdk'; -import { Currency } from '../../../../services/new/currency'; import { toFloat } from '../../../../utils/string/string'; import { escapeRegExp } from './format'; diff --git a/src/components/common/TokenControl/TokenControl.tsx b/src/components/common/TokenControl/TokenControl.tsx index 2b39861a3..53c21697a 100644 --- a/src/components/common/TokenControl/TokenControl.tsx +++ b/src/components/common/TokenControl/TokenControl.tsx @@ -6,6 +6,7 @@ import React, { FC, ReactNode } from 'react'; import { useTranslation } from 'react-i18next'; import { Observable, of } from 'rxjs'; +import { Currency } from '../../../common/models/Currency'; import { Box, Button, Flex, Typography } from '../../../ergodex-cdk'; import { Form, @@ -13,7 +14,6 @@ import { } from '../../../ergodex-cdk/components/Form/NewForm'; import { useObservable } from '../../../hooks/useObservable'; import { useWalletBalance } from '../../../services/new/balance'; -import { Currency } from '../../../services/new/currency'; import { TokenAmountInput, TokenAmountInputValue, diff --git a/src/pages/Pool/AddLiquidity/FormModel.ts b/src/pages/Pool/AddLiquidity/FormModel.ts index 5895cbdaa..6d52d990d 100644 --- a/src/pages/Pool/AddLiquidity/FormModel.ts +++ b/src/pages/Pool/AddLiquidity/FormModel.ts @@ -1,7 +1,7 @@ import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; -import { Currency } from '../../../services/new/currency'; -import { AmmPool } from '../../../services/new/pools'; +import { AmmPool } from '../../../common/models/AmmPool'; +import { Currency } from '../../../common/models/Currency'; export interface AddLiquidityFormModel { readonly x?: AssetInfo; diff --git a/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx b/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx index befdd2241..6e1a49d4e 100644 --- a/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx +++ b/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx @@ -2,6 +2,8 @@ import { minValueForOrder } from '@ergolabs/ergo-dex-sdk'; import { BoxSelection, DefaultBoxSelector } from '@ergolabs/ergo-sdk'; import React, { useCallback } from 'react'; +import { AmmPool } from '../../../common/models/AmmPool'; +import { Currency } from '../../../common/models/Currency'; import { InfoTooltip } from '../../../components/InfoTooltip/InfoTooltip'; import { ERG_DECIMALS, UI_FEE } from '../../../constants/erg'; import { defaultExFee } from '../../../constants/settings'; @@ -16,8 +18,6 @@ import { } from '../../../ergodex-cdk'; import { useUTXOs } from '../../../hooks/useUTXOs'; import { explorer } from '../../../services/explorer'; -import { Currency } from '../../../services/new/currency'; -import { AmmPool } from '../../../services/new/pools'; import { poolActions } from '../../../services/poolActions'; import { submitTx } from '../../../services/yoroi'; import { makeTarget } from '../../../utils/ammMath'; diff --git a/src/pages/Remove/PairSpace/PairSpace.tsx b/src/pages/Remove/PairSpace/PairSpace.tsx index 15068f0b4..c1dfedfd2 100644 --- a/src/pages/Remove/PairSpace/PairSpace.tsx +++ b/src/pages/Remove/PairSpace/PairSpace.tsx @@ -1,8 +1,8 @@ import React from 'react'; +import { Currency } from '../../../common/models/Currency'; import { TokenIcon } from '../../../components/TokenIcon/TokenIcon'; import { Box, Flex, Typography } from '../../../ergodex-cdk'; -import { Currency } from '../../../services/new/currency'; import { RemoveFormSpaceWrapper } from '../RemoveFormSpaceWrapper/RemoveFormSpaceWrapper'; interface PairSpaceProps { diff --git a/src/pages/Remove/Remove.tsx b/src/pages/Remove/Remove.tsx index 31a937b60..c25b6ef5e 100644 --- a/src/pages/Remove/Remove.tsx +++ b/src/pages/Remove/Remove.tsx @@ -5,6 +5,8 @@ import React, { useCallback, useEffect, useState } from 'react'; import { useParams } from 'react-router'; import { AssetPair } from '../../@types/asset'; +import { AmmPool } from '../../common/models/AmmPool'; +import { Currency } from '../../common/models/Currency'; import { openConfirmationModal, Operation, @@ -15,8 +17,6 @@ import { TokenIconPair } from '../../components/TokenIconPair/TokenIconPair'; import { Flex, Skeleton, Typography } from '../../ergodex-cdk'; import { usePair } from '../../hooks/usePair'; import { usePosition } from '../../hooks/usePosition'; -import { Currency } from '../../services/new/currency'; -import { AmmPool } from '../../services/new/pools'; import { parseUserInputToFractions } from '../../utils/math'; import { ConfirmRemoveModal } from './ConfirmRemoveModal/ConfirmRemoveModal'; import { PairSpace } from './PairSpace/PairSpace'; diff --git a/src/pages/Swap/PriceIndicator/PriceIndicator.less b/src/pages/Swap/PriceIndicator/PriceIndicator.less new file mode 100644 index 000000000..295341938 --- /dev/null +++ b/src/pages/Swap/PriceIndicator/PriceIndicator.less @@ -0,0 +1,4 @@ +.price-indicator { + cursor: pointer; + user-select: none; +} diff --git a/src/pages/Swap/PriceIndicator/PriceIndicator.tsx b/src/pages/Swap/PriceIndicator/PriceIndicator.tsx new file mode 100644 index 000000000..e5d602750 --- /dev/null +++ b/src/pages/Swap/PriceIndicator/PriceIndicator.tsx @@ -0,0 +1,75 @@ +import './PriceIndicator.less'; + +import React, { useState } from 'react'; +import { debounceTime, map, tap } from 'rxjs'; + +import { Currency } from '../../../common/models/Currency'; +import { Typography } from '../../../ergodex-cdk'; +import { FormGroup } from '../../../ergodex-cdk/components/Form/NewForm'; +import { useObservable } from '../../../hooks/useObservable'; +import { SwapFormModel } from '../SwapFormModel'; + +const calculateOutputRatio = ({ + fromAmount, + fromAsset, + pool, +}: Required): Currency => { + if (fromAmount?.isPositive()) { + return pool.calculateOutputRatio(fromAmount); + } else { + return pool.calculateOutputRatio(new Currency('1', fromAsset)); + } +}; + +const calculateInputRatio = ({ + toAmount, + toAsset, + pool, +}: Required): Currency => { + if (toAmount?.isPositive()) { + return pool.calculateInputRatio(toAmount); + } else { + return pool.calculateInputRatio(new Currency('1', toAsset)); + } +}; + +export const PriceIndicator = ({ + form, +}: { + form: FormGroup; +}) => { + const [reversed, setReversed] = useState(false); + const [ratio] = useObservable( + form.valueChangesWithSilent$.pipe( + map((value) => { + if (!value.pool || !value.fromAsset) { + return undefined; + } + if (reversed) { + return calculateInputRatio(value as Required); + } else { + return calculateOutputRatio(value as Required); + } + }), + debounceTime(100), + map((price) => + reversed + ? `1 ${form.value.toAsset?.name} - ${price?.toString()}` + : `1 ${form.value.fromAsset?.name} - ${price?.toString()}`, + ), + ), + { deps: [form, reversed] }, + ); + + const toggleReversed = () => setReversed((reversed) => !reversed); + + return ( + <> + {form.value.pool && ( + + {ratio} + + )} + + ); +}; diff --git a/src/pages/Swap/Ratio/Ratio.tsx b/src/pages/Swap/Ratio/Ratio.tsx deleted file mode 100644 index 656e336b2..000000000 --- a/src/pages/Swap/Ratio/Ratio.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */ - -import React from 'react'; -import { map } from 'rxjs'; - -import { Typography } from '../../../ergodex-cdk'; -import { FormGroup } from '../../../ergodex-cdk/components/Form/NewForm'; -import { useObservable } from '../../../hooks/useObservable'; -import { Currency } from '../../../services/new/currency'; -import { math, renderFractions } from '../../../utils/math'; -import { SwapFormModel } from '../SwapModel'; - -export function renderPrice(x: Currency, y: Currency): string { - const nameX = x.asset.name; - const nameY = y.asset.name; - const fmtX = renderFractions(x.amount, x.asset.decimals); - const fmtY = renderFractions(y.amount, y.asset.decimals); - const p = math.evaluate!(`${fmtY} / ${fmtX}`).toFixed(y.asset.decimals ?? 0); - return `1 ${nameX} - ${p} ${nameY}`; -} - -export const Ratio = ({ form }: { form: FormGroup }) => { - const [ratio] = useObservable( - form.valueChangesWithSilent$.pipe( - map((value) => { - if ( - value.fromAmount?.isPositive() && - value.toAmount?.isPositive() && - value.pool - ) { - return renderPrice(value.fromAmount, value.toAmount); - } else { - return undefined; - } - }), - ), - { deps: [form] }, - ); - - return {ratio}; -}; diff --git a/src/pages/Swap/Swap.tsx b/src/pages/Swap/Swap.tsx index ebaef2f52..8f37f45b5 100644 --- a/src/pages/Swap/Swap.tsx +++ b/src/pages/Swap/Swap.tsx @@ -16,6 +16,7 @@ import { switchMap, } from 'rxjs'; +import { AmmPool } from '../../common/models/AmmPool'; import { ActionForm } from '../../components/common/ActionForm/ActionForm'; import { TokenControlFormItem } from '../../components/common/TokenControl/TokenControl'; import { @@ -29,11 +30,11 @@ import { useSubscription } from '../../hooks/useObservable'; import { assets$, getAvailableAssetFor } from '../../services/new/assets'; import { useWalletBalance } from '../../services/new/balance'; import { useNetworkAsset, useTotalFees } from '../../services/new/core'; -import { AmmPool, getPoolByPair } from '../../services/new/pools'; +import { getPoolByPair } from '../../services/new/pools'; import { OperationSettings } from './OperationSettings/OperationSettings'; -import { Ratio } from './Ratio/Ratio'; +import { PriceIndicator } from './PriceIndicator/PriceIndicator'; import { SwapConfirmationModal } from './SwapConfirmationModal/SwapConfirmationModal'; -import { SwapFormModel } from './SwapModel'; +import { SwapFormModel } from './SwapFormModel'; import { SwapTooltip } from './SwapTooltip/SwapTooltip'; const getToAssets = (fromAsset?: string) => @@ -243,7 +244,7 @@ export const Swap = (): JSX.Element => { - + diff --git a/src/pages/Swap/SwapConfirmationModal/SwapConfirmationModal.tsx b/src/pages/Swap/SwapConfirmationModal/SwapConfirmationModal.tsx index 0040397da..2b4d29bc0 100644 --- a/src/pages/Swap/SwapConfirmationModal/SwapConfirmationModal.tsx +++ b/src/pages/Swap/SwapConfirmationModal/SwapConfirmationModal.tsx @@ -30,7 +30,7 @@ import { BaseInputParameters, getBaseInputParameters, } from '../../../utils/walletMath'; -import { SwapFormModel } from '../SwapModel'; +import { SwapFormModel } from '../SwapFormModel'; export interface SwapConfirmationModalProps { value: Required; diff --git a/src/pages/Swap/SwapModel.ts b/src/pages/Swap/SwapFormModel.ts similarity index 70% rename from src/pages/Swap/SwapModel.ts rename to src/pages/Swap/SwapFormModel.ts index 03e8d3d45..34f8b4881 100644 --- a/src/pages/Swap/SwapModel.ts +++ b/src/pages/Swap/SwapFormModel.ts @@ -1,7 +1,7 @@ import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; -import { Currency } from '../../services/new/currency'; -import { AmmPool } from '../../services/new/pools'; +import { AmmPool } from '../../common/models/AmmPool'; +import { Currency } from '../../common/models/Currency'; export interface SwapFormModel { readonly fromAmount?: Currency; diff --git a/src/pages/Swap/SwapTooltip/SwapTooltip.tsx b/src/pages/Swap/SwapTooltip/SwapTooltip.tsx index 175712e7f..62fb89c8e 100644 --- a/src/pages/Swap/SwapTooltip/SwapTooltip.tsx +++ b/src/pages/Swap/SwapTooltip/SwapTooltip.tsx @@ -12,7 +12,7 @@ import { useObservable } from '../../../hooks/useObservable'; import { renderFractions } from '../../../utils/math'; import { calculateTotalFee } from '../../../utils/transactions'; import { getBaseInputParameters } from '../../../utils/walletMath'; -import { SwapFormModel } from '../SwapModel'; +import { SwapFormModel } from '../SwapFormModel'; const TxInfoTooltipContent: FC<{ value: SwapFormModel }> = ({ value }) => { const [{ slippage, minerFee, nitro }] = useSettings(); diff --git a/src/services/new/balance.ts b/src/services/new/balance.ts index a2b8c64fb..4a8213ed5 100644 --- a/src/services/new/balance.ts +++ b/src/services/new/balance.ts @@ -9,13 +9,13 @@ import { switchMap, } from 'rxjs'; +import { Currency } from '../../common/models/Currency'; import { ERG_DECIMALS } from '../../constants/erg'; import { useObservable } from '../../hooks/useObservable'; import { getListAvailableTokens } from '../../utils/getListAvailableTokens'; import { parseUserInputToFractions } from '../../utils/math'; import { assets$ } from './assets'; import { nativeTokenBalance$, utxos$ } from './core'; -import { Currency } from './currency'; const ERGO_ID = '0000000000000000000000000000000000000000000000000000000000000000'; diff --git a/src/services/new/core.ts b/src/services/new/core.ts index 4a8632e1d..1bf97f47e 100644 --- a/src/services/new/core.ts +++ b/src/services/new/core.ts @@ -17,6 +17,7 @@ import { switchMap, } from 'rxjs'; +import { Currency } from '../../common/models/Currency'; import { ERG_DECIMALS, ERG_TOKEN_NAME, UI_FEE } from '../../constants/erg'; import { defaultExFee } from '../../constants/settings'; import { useSettings } from '../../context'; @@ -24,7 +25,6 @@ import { useObservable } from '../../hooks/useObservable'; import { walletCookies } from '../../utils/cookies'; import { renderFractions } from '../../utils/math'; import { calculateTotalFee } from '../../utils/transactions'; -import { Currency } from './currency'; const UPDATE_TIME = 5 * 1000; const ERGO_ID = diff --git a/src/services/new/pools.ts b/src/services/new/pools.ts index 449d52913..f73ec79e1 100644 --- a/src/services/new/pools.ts +++ b/src/services/new/pools.ts @@ -19,6 +19,8 @@ import { zip, } from 'rxjs'; +import { AmmPool } from '../../common/models/AmmPool'; +import { Currency } from '../../common/models/Currency'; import { getListAvailableTokens } from '../../utils/getListAvailableTokens'; import { math, @@ -27,7 +29,6 @@ import { } from '../../utils/math'; import { explorer } from '../explorer'; import { utxos$ } from './core'; -import { Currency } from './currency'; export const networkPools = (): NetworkPools => makePools(explorer); export const nativeNetworkPools = (): NetworkPools => makeNativePools(explorer); @@ -127,55 +128,3 @@ export const getPoolByPair = ( publishReplay(1), refCount(), ); - -export class AmmPool { - constructor(private pool: BaseAmmPool) {} - - get id(): PoolId { - return this.pool.id; - } - - get poolFeeNum(): number { - return this.pool.poolFeeNum; - } - - get feeNum(): bigint { - return this.pool.feeNum; - } - - get lp(): Currency { - return new Currency(this.pool.lp.amount, this.pool.lp.asset); - } - - get y(): Currency { - return new Currency(this.pool.y.amount, this.pool.y.asset); - } - - get x(): Currency { - return new Currency(this.pool.x.amount, this.pool.x.asset); - } - - calculateDepositAmount(currency: Currency): Currency { - const depositAmount = this.pool.depositAmount( - new AssetAmount(currency.asset, currency.amount), - ); - - return new Currency(depositAmount?.amount || 0n, depositAmount?.asset); - } - - calculateInputAmount(currency: Currency): Currency { - const inputAmount = this.pool.inputAmount( - new AssetAmount(currency.asset, currency.amount), - ); - - return new Currency(inputAmount?.amount || 0n, inputAmount?.asset); - } - - calculateOutputAmount(currency: Currency): Currency { - const outputAmount = this.pool.outputAmount( - new AssetAmount(currency.asset, currency.amount), - ); - - return new Currency(outputAmount.amount || 0n, outputAmount?.asset); - } -} From 55f24b11ff1dd883bbe0c88a7ffd28c03a318000 Mon Sep 17 00:00:00 2001 From: ridel1e Date: Fri, 31 Dec 2021 15:48:53 +0300 Subject: [PATCH 07/17] remove UI_FEE --- src/constants/erg.ts | 2 +- .../AddLiquidityConfirmationModal.tsx | 6 ------ src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx | 6 ------ .../Swap/SwapConfirmationModal/SwapConfirmationModal.tsx | 6 ------ src/pages/Swap/SwapTooltip/SwapTooltip.tsx | 7 ++----- 5 files changed, 3 insertions(+), 24 deletions(-) diff --git a/src/constants/erg.ts b/src/constants/erg.ts index 2a19b9ea6..1efdfdd5b 100644 --- a/src/constants/erg.ts +++ b/src/constants/erg.ts @@ -7,4 +7,4 @@ export const MIN_EX_FEE = BigInt(7_000_000); export const MIN_NITRO = 1.2; export const NITRO_DECIMALS = 2; -export const UI_FEE = 0.01; +export const UI_FEE = 0; diff --git a/src/pages/Pool/AddLiquidity/AddLiquidityConfirmationModal/AddLiquidityConfirmationModal.tsx b/src/pages/Pool/AddLiquidity/AddLiquidityConfirmationModal/AddLiquidityConfirmationModal.tsx index a87b4bdfe..1a5eea627 100644 --- a/src/pages/Pool/AddLiquidity/AddLiquidityConfirmationModal/AddLiquidityConfirmationModal.tsx +++ b/src/pages/Pool/AddLiquidity/AddLiquidityConfirmationModal/AddLiquidityConfirmationModal.tsx @@ -113,12 +113,6 @@ const AddLiquidityConfirmationModal: FC = ({ {defaultExFee} ERG - - - UI Fee: - {UI_FEE} ERG - - } /> diff --git a/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx b/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx index ee07691ea..befdd2241 100644 --- a/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx +++ b/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx @@ -129,12 +129,6 @@ const ConfirmRemoveModal: React.FC = ({ {defaultExFee} ERG - - - UI Fee: - {UI_FEE} ERG - - } /> diff --git a/src/pages/Swap/SwapConfirmationModal/SwapConfirmationModal.tsx b/src/pages/Swap/SwapConfirmationModal/SwapConfirmationModal.tsx index af1361deb..0040397da 100644 --- a/src/pages/Swap/SwapConfirmationModal/SwapConfirmationModal.tsx +++ b/src/pages/Swap/SwapConfirmationModal/SwapConfirmationModal.tsx @@ -244,12 +244,6 @@ export const SwapConfirmationModal: FC = ({ {minerFee} ERG - - - UI Fee: - {UI_FEE} ERG - - Execution Fee: diff --git a/src/pages/Swap/SwapTooltip/SwapTooltip.tsx b/src/pages/Swap/SwapTooltip/SwapTooltip.tsx index da0122264..175712e7f 100644 --- a/src/pages/Swap/SwapTooltip/SwapTooltip.tsx +++ b/src/pages/Swap/SwapTooltip/SwapTooltip.tsx @@ -3,7 +3,7 @@ import { swapVars } from '@ergolabs/ergo-dex-sdk/build/main/amm/math/swap'; import React, { FC } from 'react'; import { InfoTooltip } from '../../../components/InfoTooltip/InfoTooltip'; -import { ERG_DECIMALS, MIN_EX_FEE, UI_FEE } from '../../../constants/erg'; +import { ERG_DECIMALS, MIN_EX_FEE } from '../../../constants/erg'; import { defaultExFee } from '../../../constants/settings'; import { useSettings } from '../../../context'; import { Flex } from '../../../ergodex-cdk'; @@ -40,10 +40,7 @@ const TxInfoTooltipContent: FC<{ value: SwapFormModel }> = ({ value }) => { )} ${swapExtremums[1].minOutput.asset.name}` : undefined; - const totalFees = calculateTotalFee( - [minerFee, UI_FEE, defaultExFee], - ERG_DECIMALS, - ); + const totalFees = calculateTotalFee([minerFee, defaultExFee], ERG_DECIMALS); return ( From c2ca9d9e07450c518e0772543f4ea13ba931a1ee Mon Sep 17 00:00:00 2001 From: ridel1e Date: Tue, 4 Jan 2022 11:38:23 +0300 Subject: [PATCH 08/17] add expand animation component --- src/assets/styles/styles.less | 2 +- .../components/Animation/Expand/Expand.tsx | 36 +++++++++++++++++++ src/ergodex-cdk/components/Animation/index.ts | 5 +++ src/ergodex-cdk/components/index.ts | 1 + .../Swap/PriceIndicator/PriceIndicator.tsx | 14 ++++---- 5 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 src/ergodex-cdk/components/Animation/Expand/Expand.tsx create mode 100644 src/ergodex-cdk/components/Animation/index.ts diff --git a/src/assets/styles/styles.less b/src/assets/styles/styles.less index 6f05ce1d4..8020db5cf 100644 --- a/src/assets/styles/styles.less +++ b/src/assets/styles/styles.less @@ -9,7 +9,7 @@ body { *, *::before, *::after { box-sizing: border-box !important; - transition: none !important; + transition: none; } .ant-click-animating-node { diff --git a/src/ergodex-cdk/components/Animation/Expand/Expand.tsx b/src/ergodex-cdk/components/Animation/Expand/Expand.tsx new file mode 100644 index 000000000..69bdc8837 --- /dev/null +++ b/src/ergodex-cdk/components/Animation/Expand/Expand.tsx @@ -0,0 +1,36 @@ +import React, { FC, ReactNode, useEffect, useRef, useState } from 'react'; + +const calculateHeight = (elt: HTMLDivElement) => + parseFloat(window.getComputedStyle(elt).height); + +export interface ExpandProps { + children?: ReactNode | ReactNode[] | string; + duration?: number; + expanded?: boolean; +} + +export const Expand: FC = ({ duration, children, expanded }) => { + const containerRef = useRef(); + const [height, setHeight] = useState(0); + + useEffect(() => { + if (containerRef.current) { + setHeight(calculateHeight(containerRef.current)); + } else { + setHeight(0); + } + }, [expanded]); + + return ( +
+ {expanded &&
{children}
} +
+ ); +}; diff --git a/src/ergodex-cdk/components/Animation/index.ts b/src/ergodex-cdk/components/Animation/index.ts new file mode 100644 index 000000000..292f79429 --- /dev/null +++ b/src/ergodex-cdk/components/Animation/index.ts @@ -0,0 +1,5 @@ +import { Expand } from './Expand/Expand'; + +export const Animation = { + Expand, +}; diff --git a/src/ergodex-cdk/components/index.ts b/src/ergodex-cdk/components/index.ts index 7f398ddaa..bdd2abc8f 100644 --- a/src/ergodex-cdk/components/index.ts +++ b/src/ergodex-cdk/components/index.ts @@ -1,4 +1,5 @@ export * from './Alert/Alert'; +export * from './Animation'; export * from './Box/Box'; export * from './Button/Button'; export * from './Col/Col'; diff --git a/src/pages/Swap/PriceIndicator/PriceIndicator.tsx b/src/pages/Swap/PriceIndicator/PriceIndicator.tsx index e5d602750..08b01d997 100644 --- a/src/pages/Swap/PriceIndicator/PriceIndicator.tsx +++ b/src/pages/Swap/PriceIndicator/PriceIndicator.tsx @@ -4,7 +4,7 @@ import React, { useState } from 'react'; import { debounceTime, map, tap } from 'rxjs'; import { Currency } from '../../../common/models/Currency'; -import { Typography } from '../../../ergodex-cdk'; +import { Animation, Typography } from '../../../ergodex-cdk'; import { FormGroup } from '../../../ergodex-cdk/components/Form/NewForm'; import { useObservable } from '../../../hooks/useObservable'; import { SwapFormModel } from '../SwapFormModel'; @@ -64,12 +64,10 @@ export const PriceIndicator = ({ const toggleReversed = () => setReversed((reversed) => !reversed); return ( - <> - {form.value.pool && ( - - {ratio} - - )} - + + + {ratio} + + ); }; From a55525110c76b4af3bd234ce25a7d5d72194102b Mon Sep 17 00:00:00 2001 From: ridel1e Date: Tue, 4 Jan 2022 11:44:22 +0300 Subject: [PATCH 09/17] fix naming and update styles --- src/common/models/AmmPool.ts | 4 +-- .../PriceIndicator.less => Ratio/Ratio.less} | 4 +++ .../PriceIndicator.tsx => Ratio/Ratio.tsx} | 26 ++++++++----------- src/pages/Swap/Swap.tsx | 4 +-- 4 files changed, 19 insertions(+), 19 deletions(-) rename src/pages/Swap/{PriceIndicator/PriceIndicator.less => Ratio/Ratio.less} (55%) rename src/pages/Swap/{PriceIndicator/PriceIndicator.tsx => Ratio/Ratio.tsx} (71%) diff --git a/src/common/models/AmmPool.ts b/src/common/models/AmmPool.ts index e89225b86..444693eee 100644 --- a/src/common/models/AmmPool.ts +++ b/src/common/models/AmmPool.ts @@ -33,7 +33,7 @@ export class AmmPool { return new Currency(this.pool.x.amount, this.pool.x.asset); } - calculateOutputRatio(inputCurrency: Currency): Currency { + calculateOutputPrice(inputCurrency: Currency): Currency { const outputCurrency = this.calculateOutputAmount(inputCurrency); if (inputCurrency.amount === 1n) { @@ -51,7 +51,7 @@ export class AmmPool { ); } - calculateInputRatio(outputCurrency: Currency): Currency { + calculateInputPrice(outputCurrency: Currency): Currency { const inputCurrency = this.calculateInputAmount(outputCurrency); if (outputCurrency.amount === 1n) { diff --git a/src/pages/Swap/PriceIndicator/PriceIndicator.less b/src/pages/Swap/Ratio/Ratio.less similarity index 55% rename from src/pages/Swap/PriceIndicator/PriceIndicator.less rename to src/pages/Swap/Ratio/Ratio.less index 295341938..2b4667b86 100644 --- a/src/pages/Swap/PriceIndicator/PriceIndicator.less +++ b/src/pages/Swap/Ratio/Ratio.less @@ -1,4 +1,8 @@ .price-indicator { cursor: pointer; user-select: none; + + &:hover { + text-decoration: underline; + } } diff --git a/src/pages/Swap/PriceIndicator/PriceIndicator.tsx b/src/pages/Swap/Ratio/Ratio.tsx similarity index 71% rename from src/pages/Swap/PriceIndicator/PriceIndicator.tsx rename to src/pages/Swap/Ratio/Ratio.tsx index e5d602750..896187417 100644 --- a/src/pages/Swap/PriceIndicator/PriceIndicator.tsx +++ b/src/pages/Swap/Ratio/Ratio.tsx @@ -1,7 +1,7 @@ -import './PriceIndicator.less'; +import './Ratio.less'; import React, { useState } from 'react'; -import { debounceTime, map, tap } from 'rxjs'; +import { debounceTime, map } from 'rxjs'; import { Currency } from '../../../common/models/Currency'; import { Typography } from '../../../ergodex-cdk'; @@ -9,35 +9,31 @@ import { FormGroup } from '../../../ergodex-cdk/components/Form/NewForm'; import { useObservable } from '../../../hooks/useObservable'; import { SwapFormModel } from '../SwapFormModel'; -const calculateOutputRatio = ({ +const calculateOutputPrice = ({ fromAmount, fromAsset, pool, }: Required): Currency => { if (fromAmount?.isPositive()) { - return pool.calculateOutputRatio(fromAmount); + return pool.calculateOutputPrice(fromAmount); } else { - return pool.calculateOutputRatio(new Currency('1', fromAsset)); + return pool.calculateOutputPrice(new Currency('1', fromAsset)); } }; -const calculateInputRatio = ({ +const calculateInputPrice = ({ toAmount, toAsset, pool, }: Required): Currency => { if (toAmount?.isPositive()) { - return pool.calculateInputRatio(toAmount); + return pool.calculateInputPrice(toAmount); } else { - return pool.calculateInputRatio(new Currency('1', toAsset)); + return pool.calculateInputPrice(new Currency('1', toAsset)); } }; -export const PriceIndicator = ({ - form, -}: { - form: FormGroup; -}) => { +export const Ratio = ({ form }: { form: FormGroup }) => { const [reversed, setReversed] = useState(false); const [ratio] = useObservable( form.valueChangesWithSilent$.pipe( @@ -46,9 +42,9 @@ export const PriceIndicator = ({ return undefined; } if (reversed) { - return calculateInputRatio(value as Required); + return calculateInputPrice(value as Required); } else { - return calculateOutputRatio(value as Required); + return calculateOutputPrice(value as Required); } }), debounceTime(100), diff --git a/src/pages/Swap/Swap.tsx b/src/pages/Swap/Swap.tsx index 8f37f45b5..7695ddf40 100644 --- a/src/pages/Swap/Swap.tsx +++ b/src/pages/Swap/Swap.tsx @@ -32,7 +32,7 @@ import { useWalletBalance } from '../../services/new/balance'; import { useNetworkAsset, useTotalFees } from '../../services/new/core'; import { getPoolByPair } from '../../services/new/pools'; import { OperationSettings } from './OperationSettings/OperationSettings'; -import { PriceIndicator } from './PriceIndicator/PriceIndicator'; +import { Ratio } from './Ratio/Ratio'; import { SwapConfirmationModal } from './SwapConfirmationModal/SwapConfirmationModal'; import { SwapFormModel } from './SwapFormModel'; import { SwapTooltip } from './SwapTooltip/SwapTooltip'; @@ -244,7 +244,7 @@ export const Swap = (): JSX.Element => {
- + From d08c7f39c3477d2882ca1c8963aab97044c3b60e Mon Sep 17 00:00:00 2001 From: ridel1e Date: Tue, 4 Jan 2022 13:42:18 +0300 Subject: [PATCH 10/17] add token control animation --- .../common/TokenControl/TokenControl.less | 4 -- .../common/TokenControl/TokenControl.tsx | 44 ++++++++++--------- .../components/Animation/Expand/Expand.tsx | 12 ++++- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/components/common/TokenControl/TokenControl.less b/src/components/common/TokenControl/TokenControl.less index b0cc2786d..9ee362967 100644 --- a/src/components/common/TokenControl/TokenControl.less +++ b/src/components/common/TokenControl/TokenControl.less @@ -8,10 +8,6 @@ margin-bottom: 0; } -.token-control-bottom-panel { - min-height: 32px; -} - .token-control--bordered { border: 1px solid var(--ergo-box-bg-contrast); } diff --git a/src/components/common/TokenControl/TokenControl.tsx b/src/components/common/TokenControl/TokenControl.tsx index 53c21697a..784de864e 100644 --- a/src/components/common/TokenControl/TokenControl.tsx +++ b/src/components/common/TokenControl/TokenControl.tsx @@ -7,7 +7,7 @@ import { useTranslation } from 'react-i18next'; import { Observable, of } from 'rxjs'; import { Currency } from '../../../common/models/Currency'; -import { Box, Button, Flex, Typography } from '../../../ergodex-cdk'; +import { Animation, Box, Button, Flex, Typography } from '../../../ergodex-cdk'; import { Form, useFormContext, @@ -162,26 +162,30 @@ export const TokenControlFormItem: FC = ({ align="center" className="token-control-bottom-panel" > - {selectedAsset !== undefined && ( - - - {t`common.tokenControl.balanceLabel`}{' '} - {balance.get(selectedAsset).toString()} - - - )} - {selectedAsset !== undefined && - !!balance.get(selectedAsset) && - maxButton && ( - + + {() => ( + <> + + + {t`common.tokenControl.balanceLabel`}{' '} + {balance.get(selectedAsset).toString()} + + + {!!balance.get(selectedAsset) && maxButton && ( + + )} + )} + )}
diff --git a/src/ergodex-cdk/components/Animation/Expand/Expand.tsx b/src/ergodex-cdk/components/Animation/Expand/Expand.tsx index 69bdc8837..d80bba505 100644 --- a/src/ergodex-cdk/components/Animation/Expand/Expand.tsx +++ b/src/ergodex-cdk/components/Animation/Expand/Expand.tsx @@ -4,7 +4,11 @@ const calculateHeight = (elt: HTMLDivElement) => parseFloat(window.getComputedStyle(elt).height); export interface ExpandProps { - children?: ReactNode | ReactNode[] | string; + children?: + | ReactNode + | ReactNode[] + | string + | (() => ReactNode | ReactNode[] | string); duration?: number; expanded?: boolean; } @@ -30,7 +34,11 @@ export const Expand: FC = ({ duration, children, expanded }) => { overflow: expanded ? 'initial' : 'hidden', }} > - {expanded &&
{children}
} + {expanded && ( +
+ {children instanceof Function ? children() : children} +
+ )} ); }; From 80265b949f9345ce4a53516621b1104edcf7971f Mon Sep 17 00:00:00 2001 From: ridel1e Date: Wed, 5 Jan 2022 01:09:29 +0300 Subject: [PATCH 11/17] fix animation styles --- src/assets/styles/styles.less | 8 +- .../common/TokenControl/TokenControl.tsx | 76 +++++++++++-------- src/ergodex-cdk/components/Button/Button.less | 8 ++ src/pages/Swap/Ratio/Ratio.tsx | 2 +- 4 files changed, 58 insertions(+), 36 deletions(-) diff --git a/src/assets/styles/styles.less b/src/assets/styles/styles.less index 8020db5cf..1d952c540 100644 --- a/src/assets/styles/styles.less +++ b/src/assets/styles/styles.less @@ -7,9 +7,13 @@ body { background-color: var(--ergo-body-bg) !important; } -*, *::before, *::after { +* { box-sizing: border-box !important; - transition: none; +} + +*::before, *::after { + box-sizing: border-box !important; + transition: none !important; } .ant-click-animating-node { diff --git a/src/components/common/TokenControl/TokenControl.tsx b/src/components/common/TokenControl/TokenControl.tsx index 784de864e..72a72db14 100644 --- a/src/components/common/TokenControl/TokenControl.tsx +++ b/src/components/common/TokenControl/TokenControl.tsx @@ -14,6 +14,7 @@ import { } from '../../../ergodex-cdk/components/Form/NewForm'; import { useObservable } from '../../../hooks/useObservable'; import { useWalletBalance } from '../../../services/new/balance'; +import { isWalletLoading$ } from '../../../services/new/core'; import { TokenAmountInput, TokenAmountInputValue, @@ -79,12 +80,13 @@ export const TokenControlFormItem: FC = ({ }) => { const { t } = useTranslation(); const { form } = useFormContext(); - const [balance] = useWalletBalance(); + const [balance, balanceLoading] = useWalletBalance(); const [selectedAsset] = useObservable( tokenName ? form.controls[tokenName].valueChangesWithSilent$ : of(undefined), ); + const [isWalletLoading] = useObservable(isWalletLoading$); const handleMaxButtonClick = (maxBalance: Currency) => { if (amountName) { @@ -155,39 +157,47 @@ export const TokenControlFormItem: FC = ({ )} - - {!noBottomInfo && ( - - - {() => ( - <> - - - {t`common.tokenControl.balanceLabel`}{' '} - {balance.get(selectedAsset).toString()} - - - {!!balance.get(selectedAsset) && maxButton && ( - + {!noBottomInfo && ( + + + + {() => ( + <> + + + {t`common.tokenControl.balanceLabel`}{' '} + {balance.get(selectedAsset).toString()} + + + {!!balance.get(selectedAsset) && maxButton && ( + + )} + )} - - )} - - - )} + + + + )} + ); }; diff --git a/src/ergodex-cdk/components/Button/Button.less b/src/ergodex-cdk/components/Button/Button.less index d3a14089a..6756ac1e4 100644 --- a/src/ergodex-cdk/components/Button/Button.less +++ b/src/ergodex-cdk/components/Button/Button.less @@ -6,6 +6,14 @@ } } +.ant-btn { + transition: none !important; + + > * { + transition: none !important; + } +} + .ant-btn.ant-btn-lg { border-radius: var(--ergo-border-radius-md); diff --git a/src/pages/Swap/Ratio/Ratio.tsx b/src/pages/Swap/Ratio/Ratio.tsx index 699ed6af7..a0e30dff4 100644 --- a/src/pages/Swap/Ratio/Ratio.tsx +++ b/src/pages/Swap/Ratio/Ratio.tsx @@ -37,6 +37,7 @@ export const Ratio = ({ form }: { form: FormGroup }) => { const [reversed, setReversed] = useState(false); const [ratio] = useObservable( form.valueChangesWithSilent$.pipe( + debounceTime(100), map((value) => { if (!value.pool || !value.fromAsset) { return undefined; @@ -47,7 +48,6 @@ export const Ratio = ({ form }: { form: FormGroup }) => { return calculateOutputPrice(value as Required); } }), - debounceTime(100), map((price) => reversed ? `1 ${form.value.toAsset?.name} - ${price?.toString()}` From 779595889f91b74d26fe6fc12906e4341bfadad1 Mon Sep 17 00:00:00 2001 From: ridel1e Date: Wed, 5 Jan 2022 12:12:42 +0300 Subject: [PATCH 12/17] add types and remove some warnings --- src/common/models/AmmPool.ts | 8 ++++---- src/common/models/Currency.ts | 20 ++++++++----------- src/common/utils/amount.ts | 4 ++-- .../ConfirmationModal/ConfirmationModal.tsx | 5 ++--- .../TokensTab/TokenListItem/TokenListItem.tsx | 3 +-- .../ActionForm/ActionButton/ActionButton.tsx | 6 ------ .../common/ActionForm/ActionForm.tsx | 2 +- .../TokenAmountInput/TokenAmountInput.tsx | 1 - src/context/ConnectionContext.tsx | 3 ++- src/ergodex-cdk/components/Form/NewForm.tsx | 12 +++++------ src/ergodex-cdk/components/Modal/Modal.tsx | 13 ++++++------ .../components/Modal/presets/Progress.tsx | 1 - .../components/Modal/presets/Warning.tsx | 1 - src/ergodex-cdk/utils/gutter.ts | 2 +- src/hooks/useObservable.ts | 2 +- src/pages/Pool/Pool.tsx | 8 ++------ .../ConfirmRemoveModal/ConfirmRemoveModal.tsx | 2 +- src/pages/Remove/Remove.tsx | 1 - src/pages/Swap/Ratio/Ratio.tsx | 4 ++-- src/services/new/assets.ts | 4 +++- src/services/new/balance.ts | 4 ++-- src/services/new/core.ts | 4 ++-- src/services/new/pools.ts | 9 +-------- 23 files changed, 47 insertions(+), 72 deletions(-) diff --git a/src/common/models/AmmPool.ts b/src/common/models/AmmPool.ts index 444693eee..75196bde4 100644 --- a/src/common/models/AmmPool.ts +++ b/src/common/models/AmmPool.ts @@ -2,7 +2,7 @@ import { PoolId } from '@ergolabs/ergo-dex-sdk'; import { AmmPool as BaseAmmPool } from '@ergolabs/ergo-dex-sdk/build/main/amm/entities/ammPool'; import { AssetAmount } from '@ergolabs/ergo-sdk'; -import { math, renderFractions } from '../../utils/math'; +import { math } from '../../utils/math'; import { normalizeAmount } from '../utils/amount'; import { Currency } from './Currency'; @@ -74,7 +74,7 @@ export class AmmPool { new AssetAmount(currency.asset, currency.amount), ); - return new Currency(depositAmount?.amount || 0n, depositAmount?.asset); + return new Currency(depositAmount?.amount, depositAmount?.asset); } calculateInputAmount(currency: Currency): Currency { @@ -82,7 +82,7 @@ export class AmmPool { new AssetAmount(currency.asset, currency.amount), ); - return new Currency(inputAmount?.amount || 0n, inputAmount?.asset); + return new Currency(inputAmount?.amount, inputAmount?.asset); } calculateOutputAmount(currency: Currency): Currency { @@ -90,6 +90,6 @@ export class AmmPool { new AssetAmount(currency.asset, currency.amount), ); - return new Currency(outputAmount.amount || 0n, outputAmount?.asset); + return new Currency(outputAmount.amount, outputAmount?.asset); } } diff --git a/src/common/models/Currency.ts b/src/common/models/Currency.ts index 088dbbc53..0f0a71677 100644 --- a/src/common/models/Currency.ts +++ b/src/common/models/Currency.ts @@ -1,13 +1,9 @@ import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; -import { - math, - parseUserInputToFractions, - renderFractions, -} from '../../utils/math'; +import { parseUserInputToFractions, renderFractions } from '../../utils/math'; import { getDecimalsCount, normalizeAmount } from '../utils/amount'; -const createUnknownAsset = (decimals = 0) => ({ +const createUnknownAsset = (decimals = 0): AssetInfo => ({ id: '-1', name: 'unknown', decimals, @@ -53,7 +49,7 @@ export class Currency { return a.id === this.asset.id; } - isPositive() { + isPositive(): boolean { return this.amount > 0n; } @@ -73,17 +69,17 @@ export class Currency { return this.amount > currency.amount; } - lt(currency: Currency) { + lt(currency: Currency): boolean { this.checkComparisonErrors(currency); return this.amount < currency.amount; } - gte(currency: Currency) { + gte(currency: Currency): boolean { this.checkComparisonErrors(currency); return this.amount >= currency.amount; } - lte(currency: Currency) { + lte(currency: Currency): boolean { this.checkComparisonErrors(currency); return this.amount <= currency.amount; } @@ -99,7 +95,7 @@ export class Currency { return new Currency(this.amount + currency.amount, this.asset); } - minus(currency: Currency) { + minus(currency: Currency): Currency { if (isUnknownAsset(this.asset)) { throw new Error("can't subtract unknown asset"); } @@ -120,7 +116,7 @@ export class Currency { return `${renderFractions(this.amount, this.asset.decimals)}`; } - toUsd() {} + toUsd(): void {} private checkComparisonErrors(currency: Currency): void { if (isUnknownAsset(this.asset)) { diff --git a/src/common/utils/amount.ts b/src/common/utils/amount.ts index be786959e..ae8e72df2 100644 --- a/src/common/utils/amount.ts +++ b/src/common/utils/amount.ts @@ -1,6 +1,6 @@ import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; -export const getDecimalsCount = (amount: string) => { +export const getDecimalsCount = (amount: string): number => { const decimals = amount.split('.')[1]; if (decimals) { @@ -9,7 +9,7 @@ export const getDecimalsCount = (amount: string) => { return 0; }; -export const normalizeAmount = (amount: string, asset: AssetInfo) => { +export const normalizeAmount = (amount: string, asset: AssetInfo): string => { const currentDecimalsCount = getDecimalsCount(amount); if (currentDecimalsCount <= (asset.decimals || 0)) { diff --git a/src/components/ConfirmationModal/ConfirmationModal.tsx b/src/components/ConfirmationModal/ConfirmationModal.tsx index a311b2048..a78cfd649 100644 --- a/src/components/ConfirmationModal/ConfirmationModal.tsx +++ b/src/components/ConfirmationModal/ConfirmationModal.tsx @@ -2,9 +2,8 @@ import { TxId } from '@ergolabs/ergo-sdk'; import React, { ReactNode } from 'react'; import { Currency } from '../../common/models/Currency'; -import { Flex, Modal, Typography } from '../../ergodex-cdk'; +import { DialogRef, Flex, Modal, Typography } from '../../ergodex-cdk'; import { RequestProps } from '../../ergodex-cdk/components/Modal/presets/Request'; -import { renderFractions } from '../../utils/math'; import { exploreTx } from '../../utils/redirect'; export enum Operation { @@ -99,7 +98,7 @@ export const openConfirmationModal = ( operation: Operation, xAsset: Currency, yAsset: Currency, -) => { +): DialogRef => { return Modal.request({ actionContent, errorContent: ErrorModalContent(operation, xAsset, yAsset), diff --git a/src/components/WalletModal/TokensTab/TokenListItem/TokenListItem.tsx b/src/components/WalletModal/TokensTab/TokenListItem/TokenListItem.tsx index 45c3ca75b..693ef3e2e 100644 --- a/src/components/WalletModal/TokensTab/TokenListItem/TokenListItem.tsx +++ b/src/components/WalletModal/TokensTab/TokenListItem/TokenListItem.tsx @@ -1,5 +1,4 @@ -import { AssetInfo } from '@ergolabs/ergo-sdk'; -import React, { useEffect } from 'react'; +import React from 'react'; import { Currency } from '../../../../common/models/Currency'; import { Box, Flex, Typography } from '../../../../ergodex-cdk'; diff --git a/src/components/common/ActionForm/ActionButton/ActionButton.tsx b/src/components/common/ActionForm/ActionButton/ActionButton.tsx index 669880c70..e97b2e8cc 100644 --- a/src/components/common/ActionForm/ActionButton/ActionButton.tsx +++ b/src/components/common/ActionForm/ActionButton/ActionButton.tsx @@ -107,12 +107,6 @@ export const ActionButton: FC = (props) => { props.children, ); - const handleClick = () => { - if (props.state === ActionButtonState.ACTION && props.onClick) { - props.onClick(); - } - }; - return ( ({ online: true, }); -export const useConnection = () => useContext(ConnectionContext); +export const useConnection = (): ConnectionContextType => + useContext(ConnectionContext); export const ConnectionContextProvider: FC> = ({ children }) => { diff --git a/src/ergodex-cdk/components/Form/NewForm.tsx b/src/ergodex-cdk/components/Form/NewForm.tsx index df9dbd401..eae03f939 100644 --- a/src/ergodex-cdk/components/Form/NewForm.tsx +++ b/src/ergodex-cdk/components/Form/NewForm.tsx @@ -167,7 +167,7 @@ export class FormControl implements AbstractFormItem { this.markAsUntouched(); } - emitEvent(config?: EventConfig) { + emitEvent(config?: EventConfig): void { if ( config?.emitEvent === 'system' || config?.emitEvent === 'default' || @@ -297,29 +297,29 @@ export class FormGroup implements AbstractFormItem { return dictionary; } - markAllAsTouched() { + markAllAsTouched(): void { this.controlsArray.forEach((c) => c.markAsTouched()); } - markAllAsUntouched() { + markAllAsUntouched(): void { this.controlsArray.forEach((c) => c.markAsUntouched()); } - patchValue(value: Partial, config?: EventConfig) { + patchValue(value: Partial, config?: EventConfig): void { Object.entries(value).forEach(([key, value]) => this.controls[key as keyof T].patchValue(value as any, config), ); this.emitEvent(config); } - reset(value: Partial, config?: EventConfig) { + reset(value: Partial, config?: EventConfig): void { Object.entries(value).forEach(([key, value]) => this.controls[key as keyof T].reset(value as any, config), ); this.emitEvent(config); } - private emitEvent(config?: EventConfig) { + private emitEvent(config?: EventConfig): void { if ( config?.emitEvent === 'system' || config?.emitEvent === 'default' || diff --git a/src/ergodex-cdk/components/Modal/Modal.tsx b/src/ergodex-cdk/components/Modal/Modal.tsx index 6fb0c3c82..dab2bd67e 100644 --- a/src/ergodex-cdk/components/Modal/Modal.tsx +++ b/src/ergodex-cdk/components/Modal/Modal.tsx @@ -1,6 +1,6 @@ import './Modal.less'; -import { Modal as BaseModal, Typography } from 'antd'; +import { Modal as BaseModal } from 'antd'; import React, { ReactElement } from 'react'; import { ReactNode } from 'react'; import ReactDOM from 'react-dom'; @@ -9,7 +9,6 @@ import { ModalContent } from './ModalContent/ModalContent'; import { ModalInnerTitle, ModalTitle, - ModalTitleContext, ModalTitleContextProvider, } from './ModalTitle/ModalTitle'; import { Error } from './presets/Error'; @@ -26,7 +25,7 @@ export interface ModalParams { readonly width?: number; } -interface DialogRef { +export interface DialogRef { close: (result?: T) => void; } @@ -192,11 +191,11 @@ export class ContextModalProvider private modals = new Map(); - componentDidMount() { + componentDidMount(): void { Modal.provider = this; } - componentWillUnmount() { + componentWillUnmount(): void { Modal.provider = new BaseModalProvider(); } @@ -267,7 +266,7 @@ export class ContextModalProvider return { close }; } - render() { + render(): ReactNode | ReactNode[] | string { return ( <> {Array.from(this.modals.values()).map((modal) => ( @@ -280,7 +279,7 @@ export class ContextModalProvider ); } - private createDialogId() { + private createDialogId(): number { return dialogId++; } } diff --git a/src/ergodex-cdk/components/Modal/presets/Progress.tsx b/src/ergodex-cdk/components/Modal/presets/Progress.tsx index 9822d571b..a67332db9 100644 --- a/src/ergodex-cdk/components/Modal/presets/Progress.tsx +++ b/src/ergodex-cdk/components/Modal/presets/Progress.tsx @@ -2,7 +2,6 @@ import { LoadingOutlined } from '@ant-design/icons'; import React, { FC, ReactNode } from 'react'; import { Flex } from '../../Flex/Flex'; -import { Row } from '../../Row/Row'; import { Spin } from '../../Spin/Spin'; import { ModalContent } from '../ModalContent/ModalContent'; import { ModalTitle } from '../ModalTitle/ModalTitle'; diff --git a/src/ergodex-cdk/components/Modal/presets/Warning.tsx b/src/ergodex-cdk/components/Modal/presets/Warning.tsx index 29f52428b..fa25fc835 100644 --- a/src/ergodex-cdk/components/Modal/presets/Warning.tsx +++ b/src/ergodex-cdk/components/Modal/presets/Warning.tsx @@ -2,7 +2,6 @@ import { ExclamationCircleOutlined } from '@ant-design/icons'; import React, { FC, ReactNode } from 'react'; import { Flex } from '../../Flex/Flex'; -import { Row } from '../../Row/Row'; import { ModalContent } from '../ModalContent/ModalContent'; import { ModalTitle } from '../ModalTitle/ModalTitle'; import { INFO_DIALOG_WIDTH } from './core'; diff --git a/src/ergodex-cdk/utils/gutter.ts b/src/ergodex-cdk/utils/gutter.ts index 6590e389b..96ee16878 100644 --- a/src/ergodex-cdk/utils/gutter.ts +++ b/src/ergodex-cdk/utils/gutter.ts @@ -5,7 +5,7 @@ export type Gutter = number | GutterTwoNumbers | GutterFourNumbers; export const calcGutter = (n: number): string => `calc(var(--ergo-base-gutter) * ${n})`; -export const getGutter = (p: Gutter) => { +export const getGutter = (p: Gutter): string => { if (p instanceof Array && p.length === 2) { return `${calcGutter(p[0])} ${calcGutter(p[1])}`; } diff --git a/src/hooks/useObservable.ts b/src/hooks/useObservable.ts index 19cb74b3e..1215f8b6d 100644 --- a/src/hooks/useObservable.ts +++ b/src/hooks/useObservable.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { map, Observable, Subject, Subscription, switchMap } from 'rxjs'; +import { Observable, Subject, Subscription, switchMap } from 'rxjs'; import { Unpacked } from '../utils/unpacked'; diff --git a/src/pages/Pool/Pool.tsx b/src/pages/Pool/Pool.tsx index 2afc211ea..c66b73280 100644 --- a/src/pages/Pool/Pool.tsx +++ b/src/pages/Pool/Pool.tsx @@ -1,17 +1,13 @@ -import { isEmpty } from 'lodash'; -import React, { useContext } from 'react'; +import React from 'react'; import { useHistory } from 'react-router-dom'; import { ConnectWalletButton } from '../../components/common/ConnectWalletButton/ConnectWalletButton'; import { FormPageWrapper } from '../../components/FormPageWrapper/FormPageWrapper'; -import { WalletContext } from '../../context'; -import { Button, Flex, PlusOutlined, Typography } from '../../ergodex-cdk'; +import { Button, Flex, PlusOutlined } from '../../ergodex-cdk'; import { useObservable } from '../../hooks/useObservable'; import { isWalletSetuped$ } from '../../services/new/core'; -import { availablePools$ } from '../../services/new/pools'; import { EmptyPositionsWrapper } from './components/EmptyPositionsWrapper/EmptyPositionsWrapper'; import { LiquidityPositionsList } from './components/LiquidityPositionsList/LiquidityPositionsList'; -import { PositionListLoader } from './components/PositionListLoader/PositionListLoader'; // import { LPGuide } from './LPGuide/LPGuide'; diff --git a/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx b/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx index 6e1a49d4e..f7988c2c1 100644 --- a/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx +++ b/src/pages/Remove/ConfirmRemoveModal/ConfirmRemoveModal.tsx @@ -1,6 +1,6 @@ import { minValueForOrder } from '@ergolabs/ergo-dex-sdk'; import { BoxSelection, DefaultBoxSelector } from '@ergolabs/ergo-sdk'; -import React, { useCallback } from 'react'; +import React from 'react'; import { AmmPool } from '../../../common/models/AmmPool'; import { Currency } from '../../../common/models/Currency'; diff --git a/src/pages/Remove/Remove.tsx b/src/pages/Remove/Remove.tsx index c25b6ef5e..ddb7c8d92 100644 --- a/src/pages/Remove/Remove.tsx +++ b/src/pages/Remove/Remove.tsx @@ -17,7 +17,6 @@ import { TokenIconPair } from '../../components/TokenIconPair/TokenIconPair'; import { Flex, Skeleton, Typography } from '../../ergodex-cdk'; import { usePair } from '../../hooks/usePair'; import { usePosition } from '../../hooks/usePosition'; -import { parseUserInputToFractions } from '../../utils/math'; import { ConfirmRemoveModal } from './ConfirmRemoveModal/ConfirmRemoveModal'; import { PairSpace } from './PairSpace/PairSpace'; import { RemoveFormSpaceWrapper } from './RemoveFormSpaceWrapper/RemoveFormSpaceWrapper'; diff --git a/src/pages/Swap/Ratio/Ratio.tsx b/src/pages/Swap/Ratio/Ratio.tsx index 896187417..e69460406 100644 --- a/src/pages/Swap/Ratio/Ratio.tsx +++ b/src/pages/Swap/Ratio/Ratio.tsx @@ -1,6 +1,6 @@ import './Ratio.less'; -import React, { useState } from 'react'; +import React, { FC, useState } from 'react'; import { debounceTime, map } from 'rxjs'; import { Currency } from '../../../common/models/Currency'; @@ -33,7 +33,7 @@ const calculateInputPrice = ({ } }; -export const Ratio = ({ form }: { form: FormGroup }) => { +export const Ratio: FC<{ form: FormGroup }> = ({ form }) => { const [reversed, setReversed] = useState(false); const [ratio] = useObservable( form.valueChangesWithSilent$.pipe( diff --git a/src/services/new/assets.ts b/src/services/new/assets.ts index b289fd506..672178aee 100644 --- a/src/services/new/assets.ts +++ b/src/services/new/assets.ts @@ -14,7 +14,9 @@ export const assets$ = pools$.pipe( export const getAssetById = (id: string): Observable => assets$.pipe(map((assets) => find(assets, ['id', id])!)); -export const getAvailableAssetFor = (assetId: string) => +export const getAvailableAssetFor = ( + assetId: string, +): Observable => pools$.pipe( map((pools) => pools.filter((p) => p.x.asset.id === assetId || p.y.asset.id === assetId), diff --git a/src/services/new/balance.ts b/src/services/new/balance.ts index 4a8213ed5..64ffd26ac 100644 --- a/src/services/new/balance.ts +++ b/src/services/new/balance.ts @@ -36,11 +36,11 @@ export class Balance { return this.mapAssetIdToBalance.get(asset.id) || new Currency(0n, asset); } - entries() { + entries(): [string, Currency][] { return Array.from(this.mapAssetIdToBalance.entries()); } - values() { + values(): Currency[] { return Array.from(this.mapAssetIdToBalance.values()); } } diff --git a/src/services/new/core.ts b/src/services/new/core.ts index 1bf97f47e..32c5716cf 100644 --- a/src/services/new/core.ts +++ b/src/services/new/core.ts @@ -18,7 +18,7 @@ import { } from 'rxjs'; import { Currency } from '../../common/models/Currency'; -import { ERG_DECIMALS, ERG_TOKEN_NAME, UI_FEE } from '../../constants/erg'; +import { ERG_DECIMALS, ERG_TOKEN_NAME } from '../../constants/erg'; import { defaultExFee } from '../../constants/settings'; import { useSettings } from '../../context'; import { useObservable } from '../../hooks/useObservable'; @@ -55,7 +55,7 @@ export const walletState$ = updateWalletState.pipe( refCount(), ); -export const connectWallet = () => { +export const connectWallet = (): void => { updateWalletState.next(undefined); }; diff --git a/src/services/new/pools.ts b/src/services/new/pools.ts index f73ec79e1..44f394479 100644 --- a/src/services/new/pools.ts +++ b/src/services/new/pools.ts @@ -5,7 +5,7 @@ import { NetworkPools, PoolId, } from '@ergolabs/ergo-dex-sdk'; -import { AssetAmount, ErgoBox } from '@ergolabs/ergo-sdk'; +import { ErgoBox } from '@ergolabs/ergo-sdk'; import { combineLatest, defer, @@ -14,19 +14,12 @@ import { Observable, publishReplay, refCount, - startWith, switchMap, zip, } from 'rxjs'; import { AmmPool } from '../../common/models/AmmPool'; -import { Currency } from '../../common/models/Currency'; import { getListAvailableTokens } from '../../utils/getListAvailableTokens'; -import { - math, - parseUserInputToFractions, - renderFractions, -} from '../../utils/math'; import { explorer } from '../explorer'; import { utxos$ } from './core'; From 0304247649f5b4681f9146a6c08eeea608ca5a88 Mon Sep 17 00:00:00 2001 From: ridel1e Date: Wed, 5 Jan 2022 12:19:46 +0300 Subject: [PATCH 13/17] update ratio component --- src/pages/Swap/Ratio/Ratio.less | 2 +- src/pages/Swap/Ratio/Ratio.tsx | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/pages/Swap/Ratio/Ratio.less b/src/pages/Swap/Ratio/Ratio.less index 2b4667b86..c745bb6a3 100644 --- a/src/pages/Swap/Ratio/Ratio.less +++ b/src/pages/Swap/Ratio/Ratio.less @@ -1,4 +1,4 @@ -.price-indicator { +.ratio { cursor: pointer; user-select: none; diff --git a/src/pages/Swap/Ratio/Ratio.tsx b/src/pages/Swap/Ratio/Ratio.tsx index e69460406..b45594d33 100644 --- a/src/pages/Swap/Ratio/Ratio.tsx +++ b/src/pages/Swap/Ratio/Ratio.tsx @@ -34,14 +34,14 @@ const calculateInputPrice = ({ }; export const Ratio: FC<{ form: FormGroup }> = ({ form }) => { - const [reversed, setReversed] = useState(false); + const [reversedRatio, setReversedRatio] = useState(false); const [ratio] = useObservable( form.valueChangesWithSilent$.pipe( map((value) => { if (!value.pool || !value.fromAsset) { return undefined; } - if (reversed) { + if (reversedRatio) { return calculateInputPrice(value as Required); } else { return calculateOutputPrice(value as Required); @@ -49,20 +49,21 @@ export const Ratio: FC<{ form: FormGroup }> = ({ form }) => { }), debounceTime(100), map((price) => - reversed + reversedRatio ? `1 ${form.value.toAsset?.name} - ${price?.toString()}` : `1 ${form.value.fromAsset?.name} - ${price?.toString()}`, ), ), - { deps: [form, reversed] }, + { deps: [form, reversedRatio] }, ); - const toggleReversed = () => setReversed((reversed) => !reversed); + const toggleReversedRatio = () => + setReversedRatio((reversedRatio) => !reversedRatio); return ( <> {form.value.pool && ( - + {ratio} )} From 7ae6634be9d6c6a4b0ee37cbe2bb70ac3aeb52a1 Mon Sep 17 00:00:00 2001 From: ridel1e Date: Fri, 7 Jan 2022 06:58:55 +0300 Subject: [PATCH 14/17] fix lint & add action form delay & fix swap rules & add new AmmPool.ts method --- .eslintrc.json | 2 +- src/common/models/AmmPool.ts | 11 ++++++ .../common/ActionForm/ActionForm.tsx | 14 ++++--- .../TokenAmountInput/TokenAmountInput.tsx | 2 +- src/pages/Swap/Swap.tsx | 39 ++++++++----------- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index c66fd61ca..c23015c07 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -35,7 +35,7 @@ "simple-import-sort/exports": "error", "prettier/prettier": "error", "react-hooks/rules-of-hooks": "error", - "react-hooks/exhaustive-deps": "warn", + "react-hooks/exhaustive-deps": "off", "no-console": ["warn", { "allow": ["warn", "error"] }], "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/indent": "off", diff --git a/src/common/models/AmmPool.ts b/src/common/models/AmmPool.ts index 75196bde4..fc06107b0 100644 --- a/src/common/models/AmmPool.ts +++ b/src/common/models/AmmPool.ts @@ -1,6 +1,7 @@ import { PoolId } from '@ergolabs/ergo-dex-sdk'; import { AmmPool as BaseAmmPool } from '@ergolabs/ergo-dex-sdk/build/main/amm/entities/ammPool'; import { AssetAmount } from '@ergolabs/ergo-sdk'; +import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; import { math } from '../../utils/math'; import { normalizeAmount } from '../utils/amount'; @@ -33,6 +34,16 @@ export class AmmPool { return new Currency(this.pool.x.amount, this.pool.x.asset); } + getAssetAmount(asset: AssetInfo): Currency { + if (this.pool.x.asset.id === asset.id) { + return this.x; + } + if (this.pool.y.asset.id === asset.id) { + return this.y; + } + throw new Error('unknown asset'); + } + calculateOutputPrice(inputCurrency: Currency): Currency { const outputCurrency = this.calculateOutputAmount(inputCurrency); diff --git a/src/components/common/ActionForm/ActionForm.tsx b/src/components/common/ActionForm/ActionForm.tsx index 5c6448b98..2777cebfb 100644 --- a/src/components/common/ActionForm/ActionForm.tsx +++ b/src/components/common/ActionForm/ActionForm.tsx @@ -1,6 +1,6 @@ /* eslint-disable react-hooks/exhaustive-deps */ import React, { FC, ReactNode, useEffect, useState } from 'react'; -import { first, Observable } from 'rxjs'; +import { debounceTime, first, Observable } from 'rxjs'; import { Flex } from '../../../ergodex-cdk'; import { Form, FormGroup } from '../../../ergodex-cdk/components/Form/NewForm'; @@ -34,9 +34,13 @@ export const ActionForm: FC> = ({ }) => { const [isOnline] = useObservable(isOnline$); const [isWalletLoading] = useObservable(isWalletLoading$); - const [value] = useObservable(form.valueChangesWithSilent$, { - deps: [form], - }); + const [value] = useObservable( + form.valueChangesWithSilent$.pipe(debounceTime(100)), + { + deps: [form], + defaultValue: {}, + }, + ); const [buttonData, setButtonData] = useState<{ state: ActionButtonState; data?: any; @@ -70,7 +74,7 @@ export const ActionForm: FC> = ({ setButtonData({ state: ActionButtonState.INSUFFICIENT_FEE_BALANCE, data: { - token: getInsufficientTokenNameForFee(value), + nativeToken: getInsufficientTokenNameForFee(value), }, }); } else if (isLiquidityInsufficient && isLiquidityInsufficient(value)) { diff --git a/src/components/common/TokenControl/TokenAmountInput/TokenAmountInput.tsx b/src/components/common/TokenControl/TokenAmountInput/TokenAmountInput.tsx index 4a7b9e800..1f696a4ff 100644 --- a/src/components/common/TokenControl/TokenAmountInput/TokenAmountInput.tsx +++ b/src/components/common/TokenControl/TokenAmountInput/TokenAmountInput.tsx @@ -56,7 +56,7 @@ const TokenAmountInput: React.FC = ({ setUserInput(newValue.toString({ suffix: false })); - if (onChange) { + if (onChange && value.asset.id !== asset.id) { onChange(newValue); } } diff --git a/src/pages/Swap/Swap.tsx b/src/pages/Swap/Swap.tsx index 7695ddf40..47969906d 100644 --- a/src/pages/Swap/Swap.tsx +++ b/src/pages/Swap/Swap.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */ import './Swap.less'; import { AssetInfo } from '@ergolabs/ergo-sdk/build/main/entities/assetInfo'; @@ -70,12 +69,11 @@ export const Swap = (): JSX.Element => { [], ); - useEffect(() => { - form.patchValue({ fromAsset: networkAsset }); - }, [networkAsset]); + useEffect(() => form.patchValue({ fromAsset: networkAsset }), [networkAsset]); - const getInsufficientTokenNameForFee = (value: Required) => { - const { fromAmount } = value; + const getInsufficientTokenNameForFee = ({ + fromAmount, + }: Required) => { const totalFeesWithAmount = fromAmount.isAssetEquals(networkAsset) ? fromAmount.plus(totalFees) : totalFees; @@ -85,23 +83,21 @@ export const Swap = (): JSX.Element => { : undefined; }; - const getInsufficientTokenNameForTx = (value: SwapFormModel) => { - const { fromAmount, fromAsset } = value; - const asset = fromAsset; - const amount = fromAmount; - - if (asset && amount && amount.gt(balance.get(asset))) { - return asset.name; + const getInsufficientTokenNameForTx = ({ + fromAsset, + fromAmount, + }: SwapFormModel) => { + if (fromAsset && fromAmount && fromAmount.gt(balance.get(fromAsset))) { + return fromAsset.name; } - return undefined; }; - const isAmountNotEntered = (value: SwapFormModel) => - !value.fromAmount?.isPositive() || !value.toAmount?.isPositive(); + const isAmountNotEntered = ({ toAmount, fromAmount }: SwapFormModel) => + !fromAmount?.isPositive() || !toAmount?.isPositive(); - const isTokensNotSelected = (value: SwapFormModel) => - !value.toAsset || !value.fromAsset; + const isTokensNotSelected = ({ toAsset, fromAsset }: SwapFormModel) => + !toAsset || !fromAsset; const submitSwap = (value: Required) => { openConfirmationModal( @@ -114,14 +110,11 @@ export const Swap = (): JSX.Element => { ); }; - const isLiquidityInsufficient = (value: SwapFormModel) => { - const { toAmount, pool } = value; - + const isLiquidityInsufficient = ({ toAmount, pool }: SwapFormModel) => { if (!toAmount?.isPositive() || !pool) { return false; } - - return toAmount?.gt(pool.y); + return toAmount?.gt(pool.getAssetAmount(toAmount?.asset)); }; useSubscription( From 02379ad4dd76aba9608f1f66646890b6705d58e4 Mon Sep 17 00:00:00 2001 From: ridel1e Date: Fri, 7 Jan 2022 10:56:10 +0300 Subject: [PATCH 15/17] change currency type on control to silent --- .../TokenAmountInput/TokenAmountInput.tsx | 5 +++-- src/ergodex-cdk/components/Form/NewForm.tsx | 12 ++++++------ src/pages/Swap/Swap.tsx | 8 ++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/components/common/TokenControl/TokenAmountInput/TokenAmountInput.tsx b/src/components/common/TokenControl/TokenAmountInput/TokenAmountInput.tsx index 1f696a4ff..7112db5f8 100644 --- a/src/components/common/TokenControl/TokenAmountInput/TokenAmountInput.tsx +++ b/src/components/common/TokenControl/TokenAmountInput/TokenAmountInput.tsx @@ -5,6 +5,7 @@ import React, { useEffect, useState } from 'react'; import { Currency } from '../../../../common/models/Currency'; import { Box, Input } from '../../../../ergodex-cdk'; +import { EventConfig } from '../../../../ergodex-cdk/components/Form/NewForm'; import { escapeRegExp } from './format'; const inputRegex = RegExp(`^\\d*(?:\\\\[.])?\\d*$`); // match escaped "." characters via in a non-capturing group @@ -16,7 +17,7 @@ export interface TokenAmountInputValue { export interface TokenAmountInputProps { value?: Currency; - onChange?: (data: Currency | undefined) => void; + onChange?: (data: Currency | undefined, config?: EventConfig) => void; disabled?: boolean; readonly?: boolean; asset?: AssetInfo; @@ -57,7 +58,7 @@ const TokenAmountInput: React.FC = ({ setUserInput(newValue.toString({ suffix: false })); if (onChange && value.asset.id !== asset.id) { - onChange(newValue); + onChange(newValue, { emitEvent: 'silent' }); } } }, [asset?.id]); diff --git a/src/ergodex-cdk/components/Form/NewForm.tsx b/src/ergodex-cdk/components/Form/NewForm.tsx index eae03f939..06ea7e337 100644 --- a/src/ergodex-cdk/components/Form/NewForm.tsx +++ b/src/ergodex-cdk/components/Form/NewForm.tsx @@ -35,7 +35,7 @@ function ctrl( (_useForm as any).ctrl = ctrl; -interface EventConfig { +export interface EventConfig { readonly emitEvent: 'default' | 'system' | 'silent'; } @@ -158,8 +158,8 @@ export class FormControl implements AbstractFormItem { this.parent.emitEvent(); } - onChange(value: T): void { - this.patchValue(value); + onChange(value: T, config?: EventConfig): void { + this.patchValue(value, config); } reset(value: T, config?: EventConfig): void { @@ -384,7 +384,7 @@ class _Form extends React.Component> { interface FormItemFnParams { readonly value: T; - readonly onChange: (value: T) => void; + readonly onChange: (value: T, config?: EventConfig) => void; readonly touched: boolean; readonly untouched: boolean; readonly invalid: boolean; @@ -412,8 +412,8 @@ class _FormItem extends React.Component> { this.subscription?.unsubscribe(); } - onChange(ctrl: FormControl, value: T) { - ctrl.onChange(value); + onChange(ctrl: FormControl, value: T, config?: EventConfig) { + ctrl.onChange(value, config); } render() { diff --git a/src/pages/Swap/Swap.tsx b/src/pages/Swap/Swap.tsx index 47969906d..697dd6f69 100644 --- a/src/pages/Swap/Swap.tsx +++ b/src/pages/Swap/Swap.tsx @@ -153,22 +153,22 @@ export const Swap = (): JSX.Element => { ), ([amount, pool]) => { form.patchValue( - { toAmount: amount ? pool!.calculateOutputAmount(amount) : undefined }, + { toAmount: amount ? pool?.calculateOutputAmount(amount) : undefined }, { emitEvent: 'system' }, ); }, ); useSubscription( - combineLatest([form.controls.toAmount.valueChanges$]).pipe( + form.controls.toAmount.valueChanges$.pipe( debounceTime(100), filter(() => !!form.value.toAsset && !!form.value.pool), ), - ([amount]) => { + (amount) => { form.patchValue( { fromAmount: amount - ? form.value.pool!.calculateInputAmount(amount!) + ? form.value.pool?.calculateInputAmount(amount) : undefined, }, { emitEvent: 'system' }, From 4da5931860ed2e047ebd4597746e3f5517859436 Mon Sep 17 00:00:00 2001 From: ridel1e Date: Fri, 7 Jan 2022 14:40:47 +0300 Subject: [PATCH 16/17] update ratio and swap components --- src/ergodex-cdk/components/Form/NewForm.tsx | 10 +++-- src/pages/Swap/Ratio/Ratio.tsx | 4 +- src/pages/Swap/Swap.tsx | 49 +++++++++++++++------ 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/src/ergodex-cdk/components/Form/NewForm.tsx b/src/ergodex-cdk/components/Form/NewForm.tsx index 06ea7e337..812dbf7dd 100644 --- a/src/ergodex-cdk/components/Form/NewForm.tsx +++ b/src/ergodex-cdk/components/Form/NewForm.tsx @@ -140,7 +140,7 @@ export class FormControl implements AbstractFormItem { this.touched = false; } - patchValue(value: T, config?: EventConfig): void { + internalPatchValue(value: T, config?: EventConfig): void { this.value = value; this.currentError = this.getCurrentCheckName( this.value, @@ -155,7 +155,11 @@ export class FormControl implements AbstractFormItem { this.withWarnings = !!this.currentWarning; this.withoutWarnings = !this.withWarnings; this.emitEvent(config); - this.parent.emitEvent(); + } + + patchValue(value: T, config?: EventConfig): void { + this.internalPatchValue(value, config); + this.parent.emitEvent(config); } onChange(value: T, config?: EventConfig): void { @@ -307,7 +311,7 @@ export class FormGroup implements AbstractFormItem { patchValue(value: Partial, config?: EventConfig): void { Object.entries(value).forEach(([key, value]) => - this.controls[key as keyof T].patchValue(value as any, config), + this.controls[key as keyof T].internalPatchValue(value as any, config), ); this.emitEvent(config); } diff --git a/src/pages/Swap/Ratio/Ratio.tsx b/src/pages/Swap/Ratio/Ratio.tsx index dcbb1e735..3e1976cb0 100644 --- a/src/pages/Swap/Ratio/Ratio.tsx +++ b/src/pages/Swap/Ratio/Ratio.tsx @@ -50,8 +50,8 @@ export const Ratio: FC<{ form: FormGroup }> = ({ form }) => { }), map((price) => reversedRatio - ? `1 ${form.value.toAsset?.name} - ${price?.toString()}` - : `1 ${form.value.fromAsset?.name} - ${price?.toString()}`, + ? `1 ${form.value.toAsset?.name} = ${price?.toString()}` + : `1 ${form.value.fromAsset?.name} = ${price?.toString()}`, ), ), { deps: [form, reversedRatio] }, diff --git a/src/pages/Swap/Swap.tsx b/src/pages/Swap/Swap.tsx index 697dd6f69..a0a8ba5e5 100644 --- a/src/pages/Swap/Swap.tsx +++ b/src/pages/Swap/Swap.tsx @@ -8,11 +8,13 @@ import { BehaviorSubject, combineLatest, debounceTime, + distinctUntilChanged, filter, map, Observable, of, switchMap, + tap, } from 'rxjs'; import { AmmPool } from '../../common/models/AmmPool'; @@ -117,25 +119,41 @@ export const Swap = (): JSX.Element => { return toAmount?.gt(pool.getAssetAmount(toAmount?.asset)); }; + // useSubscription(form.valueChangesWithSilent$, (value) => console.log(value)); + useSubscription( form.controls.fromAsset.valueChangesWithSilent$, (token: AssetInfo | undefined) => updateToAssets$.next(token?.id), ); useSubscription(form.controls.fromAsset.valueChanges$, () => - form.patchValue({ - toAsset: undefined, - fromAmount: undefined, - toAmount: undefined, - }), + form.patchValue( + { + toAsset: undefined, + fromAmount: undefined, + toAmount: undefined, + }, + { emitEvent: 'silent' }, + ), ); useSubscription( combineLatest([ - form.controls.fromAsset.valueChanges$, - form.controls.toAsset.valueChanges$, + form.controls.fromAsset.valueChangesWithSilent$.pipe( + distinctUntilChanged(), + ), + form.controls.toAsset.valueChangesWithSilent$.pipe( + distinctUntilChanged(), + ), ]).pipe( debounceTime(100), + distinctUntilChanged(([prevFrom, prevTo], [nextFrom, nextTo]) => { + return ( + (prevFrom?.id === nextFrom?.id && prevTo?.id === nextTo?.id) || + (prevFrom?.id === nextTo?.id && prevTo?.id === nextFrom?.id) + ); + }), + tap(() => form.patchValue({ pool: undefined })), switchMap(([fromAsset, toAsset]) => getSelectedPool(fromAsset?.id, toAsset?.id), ), @@ -145,7 +163,7 @@ export const Swap = (): JSX.Element => { useSubscription( combineLatest([ - form.controls.fromAmount.valueChanges$, + form.controls.fromAmount.valueChangesWithSystem$, form.controls.pool.valueChanges$, ]).pipe( debounceTime(100), @@ -154,7 +172,7 @@ export const Swap = (): JSX.Element => { ([amount, pool]) => { form.patchValue( { toAmount: amount ? pool?.calculateOutputAmount(amount) : undefined }, - { emitEvent: 'system' }, + { emitEvent: 'silent' }, ); }, ); @@ -171,20 +189,19 @@ export const Swap = (): JSX.Element => { ? form.value.pool?.calculateInputAmount(amount) : undefined, }, - { emitEvent: 'system' }, + { emitEvent: 'silent' }, ); }, ); - const swapTokens = () => { + const switchAssets = () => { form.patchValue( { fromAsset: form.value.toAsset, fromAmount: form.value.toAmount, toAsset: form.value.fromAsset, - toAmount: form.value.fromAmount, }, - { emitEvent: 'silent' }, + { emitEvent: 'system' }, ); }; @@ -222,7 +239,11 @@ export const Swap = (): JSX.Element => { /> -