diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/InputTokenPanel.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/InputTokenPanel.tsx index c62b9c0ac68a..a4657f6a01b4 100644 --- a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/InputTokenPanel.tsx +++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/InputTokenPanel.tsx @@ -184,7 +184,7 @@ export const InputTokenPanel = memo( ), }} - inputProps={{ className: classes.input }} + inputProps={{ className: classes.input, autoComplete: 'off' }} /> ) }, diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/Trader.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/Trader.tsx index 6c23e42a6a3f..15a07d78e346 100644 --- a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/Trader.tsx +++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/Trader.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { makeStyles, useStylesExtends } from '@masknet/theme' import { ChainId, @@ -6,46 +6,37 @@ import { createNativeToken, EthereumTokenType, formatBalance, - formatGweiToWei, FungibleTokenDetailed, - GasOptionConfig, - getNetworkTypeFromChainId, isSameAddress, TransactionStateType, useChainId, useChainIdValid, useFungibleTokenBalance, - useProviderType, useTokenConstants, useWallet, - useAccount, } from '@masknet/web3-shared-evm' -import { isGreaterThan, isLessThan, multipliedBy } from '@masknet/web3-shared-base' import { useRemoteControlledDialog } from '@masknet/shared' import { delay } from '@masknet/shared-base' +import { useGasConfig } from './hooks/useGasConfig' import type { Coin } from '../../types' import { TokenPanelType, TradeInfo } from '../../types' import { useI18N } from '../../../../utils' import { TradeForm } from './TradeForm' import { AllProviderTradeActionType, AllProviderTradeContext } from '../../trader/useAllProviderTradeContext' -import { MINIMUM_AMOUNT, UST } from '../../constants' +import { UST } from '../../constants' import { SelectTokenDialogEvent, WalletMessages } from '@masknet/plugin-wallet' -import { useAsync, useUnmount, useUpdateEffect } from 'react-use' +import { useUnmount, useUpdateEffect } from 'react-use' import { isTwitter } from '../../../../social-network-adaptor/twitter.com/base' import { activatedSocialNetworkUI } from '../../../../social-network' import { isFacebook } from '../../../../social-network-adaptor/facebook.com/base' import { useTradeCallback } from '../../trader/useTradeCallback' import { isNativeTokenWrapper } from '../../helpers' import { ConfirmDialog } from './ConfirmDialog' -import Services from '../../../../extension/service' -import { currentBalancesSettings } from '../../../Wallet/settings' import { TargetChainIdContext } from '../../trader/useTargetChainIdContext' -import { WalletRPC } from '../../../Wallet/messages' import { PluginTraderMessages } from '../../messages' -import { NetworkType } from '@masknet/public-api' -import BigNumber from 'bignumber.js' -import { useNativeTokenPrice, useTokenPrice } from '../../../Wallet/hooks/useTokenPrice' import { SettingsDialog } from './SettingsDialog' +import { useSortedTrades } from './hooks/useSortedTrades' +import { useUpdateBalance } from './hooks/useUpdateBalance' const useStyles = makeStyles()(() => { return { @@ -65,14 +56,11 @@ export function Trader(props: TraderProps) { const { coin, tokenDetailed, chainId: targetChainId } = props const { decimals } = tokenDetailed ?? coin ?? {} const [focusedTrade, setFocusTrade] = useState() - const [gasConfig, setGasConfig] = useState() const wallet = useWallet() const currentChainId = useChainId() const chainId = targetChainId ?? currentChainId const chainIdValid = useChainIdValid() const { NATIVE_TOKEN_ADDRESS } = useTokenConstants() - const currentAccount = useAccount() - const currentProvider = useProviderType() const classes = useStylesExtends(useStyles(), props) const { t } = useI18N() const { setTargetChainId } = TargetChainIdContext.useContainer() @@ -85,7 +73,11 @@ export function Trader(props: TraderProps) { ], allTradeComputed, } = AllProviderTradeContext.useContainer() - //endregion + //#endregion + + //#region gas config and gas price + const { gasPrice, gasConfig, setGasConfig } = useGasConfig(chainId) + //#endregion //#region if chain id be changed, update input token be native token useEffect(() => { @@ -178,56 +170,6 @@ export function Trader(props: TraderProps) { NATIVE_TOKEN_ADDRESS, ]) - // Query the balance of native tokens on target chain - useAsync(async () => { - if (!currentAccount) { - dispatchTradeStore({ - type: AllProviderTradeActionType.UPDATE_INPUT_TOKEN_BALANCE, - balance: '0', - }) - - dispatchTradeStore({ - type: AllProviderTradeActionType.UPDATE_OUTPUT_TOKEN_BALANCE, - balance: '0', - }) - return - } - - if (chainId && currentProvider && currentAccount) { - const cacheBalance = currentBalancesSettings.value[currentProvider]?.[chainId] - - let balance: string - - if (cacheBalance) balance = cacheBalance - else { - balance = await Services.Ethereum.getBalance(currentAccount, { - chainId: chainId, - providerType: currentProvider, - }) - await WalletRPC.updateBalances({ - [currentProvider]: { - [chainId]: balance, - }, - }) - } - - dispatchTradeStore({ - type: AllProviderTradeActionType.UPDATE_INPUT_TOKEN_BALANCE, - balance: inputToken?.type === EthereumTokenType.Native ? balance : '0', - }) - - dispatchTradeStore({ - type: AllProviderTradeActionType.UPDATE_OUTPUT_TOKEN_BALANCE, - balance: - isSameAddress(outputToken?.address, NATIVE_TOKEN_ADDRESS) || - outputToken?.type === EthereumTokenType.Native - ? balance - : '0', - }) - } - }, [inputToken, outputToken, currentAccount, currentProvider, chainId, currentChainId]) - // #endregion - // #region select token const excludeTokens = [inputToken, outputToken].filter(Boolean).map((x) => x?.address) as string[] const [focusedTokenPanelType, setFocusedTokenPanelType] = useState(TokenPanelType.Input) @@ -354,43 +296,6 @@ export function Trader(props: TraderProps) { ) //#endregion - //#region get gas price - const { value: gasPrice } = useAsync(async () => { - try { - const network = getNetworkTypeFromChainId(chainId) - if (gasConfig) { - return new BigNumber( - (network === NetworkType.Ethereum ? gasConfig.maxFeePerGas : gasConfig.gasPrice) ?? 0, - ).toString() - } else { - if (network === NetworkType.Ethereum) { - const response = await WalletRPC.getEstimateGasFees(chainId) - const maxFeePerGas = formatGweiToWei(response?.medium?.suggestedMaxFeePerGas ?? 0).toString() - const maxPriorityFeePerGas = formatGweiToWei( - response?.medium?.suggestedMaxPriorityFeePerGas ?? 0, - ).toString() - setGasConfig({ - maxFeePerGas, - maxPriorityFeePerGas, - }) - - return maxFeePerGas - } else { - const response = await WalletRPC.getGasPriceDictFromDeBank(chainId) - const gasPrice = new BigNumber(response?.data.normal.price ?? 0).toString() - setGasConfig({ - gasPrice, - }) - - return gasPrice - } - } - } catch { - return '0' - } - }, [chainId, gasConfig]) - //#endregion - //#region open the transaction dialog useEffect(() => { if (tradeState?.type === TransactionStateType.UNKNOWN) return @@ -411,54 +316,13 @@ export function Trader(props: TraderProps) { //#endregion //#region The trades sort by best price (Estimate received * price - Gas fee * native token price) - const nativeTokenPrice = useNativeTokenPrice(chainId) - const outputTokenPrice = useTokenPrice(chainId, outputToken?.address.toLowerCase()) - const sortedAllTradeComputed = useMemo(() => { - if (outputToken && (outputTokenPrice || nativeTokenPrice)) { - return allTradeComputed - .map((trade) => { - if ( - gasPrice && - trade.value && - isGreaterThan(trade.value.outputAmount, MINIMUM_AMOUNT) && - trade.gas.value - ) { - const gasFee = multipliedBy(gasPrice, trade.gas.value).integerValue().toFixed() - - const gasFeeUSD = new BigNumber(formatBalance(gasFee ?? 0, outputToken.decimals)).times( - nativeTokenPrice, - ) - - const finalPrice = new BigNumber( - formatBalance(trade.value.outputAmount, outputToken.decimals, 2), - ) - .times(outputToken.type !== EthereumTokenType.Native ? outputTokenPrice : nativeTokenPrice) - .minus(gasFeeUSD) - - return { - ...trade, - finalPrice, - } - } - return trade - }) - .filter(({ value }) => !!value && !value.outputAmount.isZero()) - .sort(({ finalPrice: a }, { finalPrice: b }) => { - if (a && b && isGreaterThan(a, b)) return -1 - if (a && b && isLessThan(a, b)) return 1 - return 0 - }) - } - return allTradeComputed - .filter(({ value }) => !!value && !value.outputAmount.isZero()) - .sort(({ value: a }, { value: b }) => { - if (a?.outputAmount.isGreaterThan(b?.outputAmount ?? 0)) return -1 - if (a?.outputAmount.isLessThan(b?.outputAmount ?? 0)) return 1 - return 0 - }) - }, [allTradeComputed, outputToken, gasPrice, outputTokenPrice, nativeTokenPrice]) + const sortedAllTradeComputed = useSortedTrades(allTradeComputed, chainId, gasPrice) //#endregion + // Query the balance of native tokens on target chain + useUpdateBalance(chainId, currentChainId) + // #endregion + //#region reset focused trade when chainId, inputToken, outputToken, inputAmount be changed useUpdateEffect(() => { setFocusTrade(undefined) diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/hooks/useGasConfig.ts b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/hooks/useGasConfig.ts new file mode 100644 index 000000000000..b32f1ecc864f --- /dev/null +++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/hooks/useGasConfig.ts @@ -0,0 +1,47 @@ +import type { ChainId, GasOptionConfig } from '@masknet/web3-shared-evm' +import { useAsync } from 'react-use' +import { formatGweiToWei, getNetworkTypeFromChainId } from '@masknet/web3-shared-evm' +import BigNumber from 'bignumber.js' +import { NetworkType } from '@masknet/public-api' +import { WalletRPC } from '../../../../Wallet/messages' +import { useState } from 'react' + +export function useGasConfig(chainId: ChainId) { + const [gasConfig, setGasConfig] = useState() + const { value: gasPrice } = useAsync(async () => { + try { + const network = getNetworkTypeFromChainId(chainId) + if (gasConfig) { + return new BigNumber( + (network === NetworkType.Ethereum ? gasConfig.maxFeePerGas : gasConfig.gasPrice) ?? 0, + ).toString() + } else { + if (network === NetworkType.Ethereum) { + const response = await WalletRPC.getEstimateGasFees(chainId) + const maxFeePerGas = formatGweiToWei(response?.medium?.suggestedMaxFeePerGas ?? 0).toFixed() + const maxPriorityFeePerGas = formatGweiToWei( + response?.medium?.suggestedMaxPriorityFeePerGas ?? 0, + ).toFixed() + setGasConfig({ + maxFeePerGas, + maxPriorityFeePerGas, + }) + + return maxFeePerGas + } else { + const response = await WalletRPC.getGasPriceDictFromDeBank(chainId) + const gasPrice = new BigNumber(response?.data.normal.price ?? 0).toString() + setGasConfig({ + gasPrice, + }) + + return gasPrice + } + } + } catch { + return '0' + } + }, [chainId, gasConfig]) + + return { gasPrice, gasConfig, setGasConfig } +} diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/hooks/useSortedTrades.ts b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/hooks/useSortedTrades.ts new file mode 100644 index 000000000000..7c9de8b7597e --- /dev/null +++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/hooks/useSortedTrades.ts @@ -0,0 +1,80 @@ +import type { TradeInfo } from '../../../types' +import { useMemo } from 'react' +import { isGreaterThan, isLessThan, multipliedBy } from '@masknet/web3-shared-base' +import { MINIMUM_AMOUNT } from '../../../constants' +import BigNumber from 'bignumber.js' +import { useNativeTokenPrice, useTokenPrice } from '../../../../Wallet/hooks/useTokenPrice' +import { ChainId, EthereumTokenType, formatBalance, useNativeTokenDetailed } from '@masknet/web3-shared-evm' +import { AllProviderTradeContext } from '../../../trader/useAllProviderTradeContext' + +export function useSortedTrades(traders: TradeInfo[], chainId: ChainId, gasPrice?: string) { + const { value: nativeToken } = useNativeTokenDetailed(chainId) + const nativeTokenPrice = useNativeTokenPrice(chainId) + + const { + tradeState: [{ outputToken }], + } = AllProviderTradeContext.useContainer() + + const outputTokenPrice = useTokenPrice(chainId, outputToken?.address.toLowerCase()) + + return useMemo(() => { + if (outputToken && nativeToken && (outputTokenPrice || nativeTokenPrice)) { + return traders + .map((trade) => { + if ( + gasPrice && + trade.value && + isGreaterThan(trade.value.outputAmount, MINIMUM_AMOUNT) && + trade.gas.value + ) { + const gasFee = multipliedBy(gasPrice, trade.gas.value).integerValue().toFixed() + + const gasFeeUSD = new BigNumber(formatBalance(gasFee ?? 0, nativeToken?.decimals)).times( + nativeTokenPrice, + ) + + const finalPrice = new BigNumber( + formatBalance(trade.value.outputAmount, outputToken.decimals, 2), + ) + .times(outputToken.type !== EthereumTokenType.Native ? outputTokenPrice : nativeTokenPrice) + .minus(gasFeeUSD) + + return { + ...trade, + finalPrice, + } + } + return trade + }) + .filter(({ value }) => !!value && !value.outputAmount.isZero()) + .sort( + ( + { finalPrice: finalPriceA, gas: { value: gasA }, value: valueA }, + { finalPrice: finalPriceB, gas: { value: gasB }, value: valueB }, + ) => { + let a = finalPriceA + let b = finalPriceB + + if (!gasA && gasB) { + return 1 // B goes first + } else if (gasA && !gasB) { + return -1 // A goes first + } else if (!gasA && !gasB) { + a = valueA?.outputAmount + b = valueB?.outputAmount + } + if (isGreaterThan(a ?? 0, b ?? 0)) return -1 + if (isLessThan(a ?? 0, b ?? 0)) return 1 + return 0 + }, + ) + } + return traders + .filter(({ value }) => !!value && !value.outputAmount.isZero()) + .sort(({ value: a }, { value: b }) => { + if (a?.outputAmount.isGreaterThan(b?.outputAmount ?? 0)) return -1 + if (a?.outputAmount.isLessThan(b?.outputAmount ?? 0)) return 1 + return 0 + }) + }, [traders, outputToken, gasPrice, outputTokenPrice, nativeTokenPrice, nativeToken]) +} diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/hooks/useUpdateBalance.ts b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/hooks/useUpdateBalance.ts new file mode 100644 index 000000000000..9c227fd30862 --- /dev/null +++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/hooks/useUpdateBalance.ts @@ -0,0 +1,71 @@ +import type { ChainId } from '@masknet/web3-shared-evm' +import { AllProviderTradeActionType, AllProviderTradeContext } from '../../../trader/useAllProviderTradeContext' +import { + EthereumTokenType, + isSameAddress, + useAccount, + useProviderType, + useTokenConstants, +} from '@masknet/web3-shared-evm' +import { useAsync } from 'react-use' +import { currentBalancesSettings } from '../../../../Wallet/settings' +import Services from '../../../../../extension/service' +import { WalletRPC } from '../../../../Wallet/messages' + +export function useUpdateBalance(chainId: ChainId, currentChainId: ChainId) { + const currentAccount = useAccount() + const currentProvider = useProviderType() + const { NATIVE_TOKEN_ADDRESS } = useTokenConstants() + + const { + tradeState: [{ inputToken, outputToken }, dispatchTradeStore], + } = AllProviderTradeContext.useContainer() + + return useAsync(async () => { + if (!currentAccount) { + dispatchTradeStore({ + type: AllProviderTradeActionType.UPDATE_INPUT_TOKEN_BALANCE, + balance: '0', + }) + + dispatchTradeStore({ + type: AllProviderTradeActionType.UPDATE_OUTPUT_TOKEN_BALANCE, + balance: '0', + }) + return + } + + if (chainId && currentProvider && currentAccount) { + const cacheBalance = currentBalancesSettings.value[currentProvider]?.[chainId] + + let balance: string + + if (cacheBalance) balance = cacheBalance + else { + balance = await Services.Ethereum.getBalance(currentAccount, { + chainId: chainId, + providerType: currentProvider, + }) + await WalletRPC.updateBalances({ + [currentProvider]: { + [chainId]: balance, + }, + }) + } + + dispatchTradeStore({ + type: AllProviderTradeActionType.UPDATE_INPUT_TOKEN_BALANCE, + balance: inputToken?.type === EthereumTokenType.Native ? balance : '0', + }) + + dispatchTradeStore({ + type: AllProviderTradeActionType.UPDATE_OUTPUT_TOKEN_BALANCE, + balance: + isSameAddress(outputToken?.address, NATIVE_TOKEN_ADDRESS) || + outputToken?.type === EthereumTokenType.Native + ? balance + : '0', + }) + } + }, [inputToken, outputToken, currentAccount, currentProvider, chainId, currentChainId, NATIVE_TOKEN_ADDRESS]) +}