From fc4d3d010e90c51ddf8b9dbf84298693d37f3967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BA=A7n=20Trung=20T=C3=ADn?= Date: Mon, 18 Apr 2022 23:31:17 +0700 Subject: [PATCH 1/4] feat: integrate WooFi DEX with Mask --- .../mask/src/plugins/Trader/apis/index.ts | 1 + .../src/plugins/Trader/apis/trader/index.ts | 11 ++- .../src/plugins/Trader/apis/woofi/index.ts | 36 ++++++++ .../src/plugins/Trader/constants/index.ts | 1 + .../src/plugins/Trader/constants/woofi.ts | 46 ++++++++++ packages/mask/src/plugins/Trader/pipes.ts | 1 + .../Trader/trader/useAllTradeComputed.ts | 16 ++++ .../Trader/trader/useGetTradeContext.ts | 14 ++++ .../plugins/Trader/trader/useTradeCallback.ts | 8 ++ .../plugins/Trader/trader/useTradeContext.ts | 13 +++ .../trader/woofi/useSlippageTolerance.ts | 9 ++ .../plugins/Trader/trader/woofi/useTrade.ts | 70 ++++++++++++++++ .../Trader/trader/woofi/useTradeCallback.ts | 84 +++++++++++++++++++ .../Trader/trader/woofi/useTradeComputed.ts | 35 ++++++++ .../Trader/trader/woofi/useTradeGasLimit.ts | 26 ++++++ .../mask/src/plugins/Trader/types/index.ts | 1 + .../mask/src/plugins/Trader/types/trader.ts | 1 + .../mask/src/plugins/Trader/types/woofi.ts | 49 +++++++++++ packages/public-api/src/web.ts | 1 + packages/web3-constants/evm/token.json | 23 ++++- packages/web3-constants/evm/trader.json | 84 +++++++++++++++++++ packages/web3-shared/evm/constants/tokens.ts | 1 + 22 files changed, 528 insertions(+), 3 deletions(-) create mode 100644 packages/mask/src/plugins/Trader/apis/woofi/index.ts create mode 100644 packages/mask/src/plugins/Trader/constants/woofi.ts create mode 100644 packages/mask/src/plugins/Trader/trader/woofi/useSlippageTolerance.ts create mode 100644 packages/mask/src/plugins/Trader/trader/woofi/useTrade.ts create mode 100644 packages/mask/src/plugins/Trader/trader/woofi/useTradeCallback.ts create mode 100644 packages/mask/src/plugins/Trader/trader/woofi/useTradeComputed.ts create mode 100644 packages/mask/src/plugins/Trader/trader/woofi/useTradeGasLimit.ts create mode 100644 packages/mask/src/plugins/Trader/types/woofi.ts diff --git a/packages/mask/src/plugins/Trader/apis/index.ts b/packages/mask/src/plugins/Trader/apis/index.ts index ed4fa7e0fd63..e6ea7e6a1539 100644 --- a/packages/mask/src/plugins/Trader/apis/index.ts +++ b/packages/mask/src/plugins/Trader/apis/index.ts @@ -7,3 +7,4 @@ export * from './LBP' export * from './dodo' export * from './bancor' export * from './openocean' +export * from './woofi' diff --git a/packages/mask/src/plugins/Trader/apis/trader/index.ts b/packages/mask/src/plugins/Trader/apis/trader/index.ts index 8b1c44229c17..0a6c70df843b 100644 --- a/packages/mask/src/plugins/Trader/apis/trader/index.ts +++ b/packages/mask/src/plugins/Trader/apis/trader/index.ts @@ -37,19 +37,26 @@ export async function getAvailableTraderProviders(chainId: ChainId) { TradeProvider.ZRX, TradeProvider.OPENOCEAN, TradeProvider.MDEX, + TradeProvider.WOOFI, ] case NetworkType.Arbitrum: return [TradeProvider.UNISWAP_V3, TradeProvider.OPENOCEAN, TradeProvider.DODO] case NetworkType.xDai: return [TradeProvider.SUSHISWAP, TradeProvider.OPENOCEAN] case NetworkType.Avalanche: - return [TradeProvider.SUSHISWAP, TradeProvider.OPENOCEAN, TradeProvider.TRADERJOE, TradeProvider.PANGOLIN] + return [ + TradeProvider.SUSHISWAP, + TradeProvider.OPENOCEAN, + TradeProvider.TRADERJOE, + TradeProvider.PANGOLIN, + TradeProvider.WOOFI, + ] case NetworkType.xDai: return [TradeProvider.SUSHISWAP, TradeProvider.OPENOCEAN] case NetworkType.Celo: return [TradeProvider.SUSHISWAP] case NetworkType.Fantom: - return [TradeProvider.SUSHISWAP] + return [TradeProvider.SUSHISWAP, TradeProvider.WOOFI] case NetworkType.Aurora: return [TradeProvider.DODO, TradeProvider.WANNASWAP, TradeProvider.TRISOLARIS] case NetworkType.Boba: diff --git a/packages/mask/src/plugins/Trader/apis/woofi/index.ts b/packages/mask/src/plugins/Trader/apis/woofi/index.ts new file mode 100644 index 000000000000..dcce704a7b54 --- /dev/null +++ b/packages/mask/src/plugins/Trader/apis/woofi/index.ts @@ -0,0 +1,36 @@ +import type { WoofiSwapResponse, WoofiSwapRequest } from '../../types/woofi' +import { woofiNetworkNames, WOOFI_BASE_URL } from '../../constants' +import urlcat from 'urlcat' +import { leftShift } from '@masknet/web3-shared-base' +import type { NetworkType } from '@masknet/web3-shared-evm' + +export async function woofiSwap(request: WoofiSwapRequest, networkType: NetworkType) { + const response = await fetch( + // cspell:disable-next-line + urlcat(WOOFI_BASE_URL, '/woofi_swap', { + from_token: request.fromToken?.address, + to_token: request.toToken?.address, + from_amount: request.fromAmount, + network: woofiNetworkNames[networkType], + }), + ) + const payload: WoofiSwapResponse = await response.json() + + if (payload.status !== 'ok') { + throw new Error(payload.error?.message ?? 'Unknown Error') + } + + return { + toAmount: leftShift(payload.data.to_amount, request.toToken.decimals).toNumber(), + path: payload.data.path, + price: payload.data.price, + tradingFee: payload.data.trading_fee, + route: payload.data.route, + singleFeePercentage: payload.data.single_fee_percentage, + fromAmount: leftShift(request.fromAmount, request.fromToken.decimals).toNumber(), + value: request.isNativeSellToken ? request.fromAmount : '0', + slippage: request.slippage, + fromTokenSymbol: request.fromToken.symbol, + toTokenSymbol: request.toToken.symbol, + } +} diff --git a/packages/mask/src/plugins/Trader/constants/index.ts b/packages/mask/src/plugins/Trader/constants/index.ts index 9f89799a8a2d..f78c3dacaff3 100644 --- a/packages/mask/src/plugins/Trader/constants/index.ts +++ b/packages/mask/src/plugins/Trader/constants/index.ts @@ -14,4 +14,5 @@ export * from './pangolindex' export * from './wannaswap' export * from './trisolaris' export * from './mdex' +export * from './woofi' export type { ERC20TokenCustomizedBase, ERC20AgainstToken } from './types' diff --git a/packages/mask/src/plugins/Trader/constants/woofi.ts b/packages/mask/src/plugins/Trader/constants/woofi.ts new file mode 100644 index 000000000000..06f5ebb61941 --- /dev/null +++ b/packages/mask/src/plugins/Trader/constants/woofi.ts @@ -0,0 +1,46 @@ +import { + ChainId, + USDC, + USDT, + WBTC, + WNATIVE, + WNATIVE_ONLY, + BTCB, + ETHER, + NetworkType, + WOO, +} from '@masknet/web3-shared-evm' + +import type { ERC20AgainstToken, ERC20TokenCustomizedBase } from './types' + +export const woofiNetworkNames: Record = { + [NetworkType.Ethereum]: '', + [NetworkType.Binance]: 'bsc', + [NetworkType.Polygon]: '', + [NetworkType.Arbitrum]: '', + [NetworkType.xDai]: '', + [NetworkType.Celo]: '', + [NetworkType.Fantom]: 'fantom', + [NetworkType.Avalanche]: 'avax', + [NetworkType.Aurora]: '', + [NetworkType.Boba]: '', + [NetworkType.Fuse]: '', + [NetworkType.Metis]: '', + [NetworkType.Optimistic]: '', + [NetworkType.Conflux]: '', +} + +export const WOOFI_BASE_URL = 'https://fi-api.woo.org/' + +/** + * Some tokens can only be swapped via certain pairs, + * so we override the list of bases that are considered for these tokens. + */ +export const WOOFI_CUSTOM_BASES: ERC20TokenCustomizedBase = {} + +export const WOOFI_BASE_AGAINST_TOKENS: ERC20AgainstToken = { + ...WNATIVE_ONLY, + [ChainId.BSC]: [WNATIVE, BTCB, ETHER, WOO, USDT].map((x) => x[ChainId.BSC]), + [ChainId.Avalanche]: [WNATIVE, WBTC, ETHER, WOO, USDT].map((x) => x[ChainId.Avalanche]), + [ChainId.Fantom]: [WNATIVE, WBTC, ETHER, WOO, USDC].map((x) => x[ChainId.Fantom]), +} diff --git a/packages/mask/src/plugins/Trader/pipes.ts b/packages/mask/src/plugins/Trader/pipes.ts index d04f1b973f57..fe6d409134c0 100644 --- a/packages/mask/src/plugins/Trader/pipes.ts +++ b/packages/mask/src/plugins/Trader/pipes.ts @@ -41,6 +41,7 @@ export const resolveTradeProviderName = createLookupTableResolver { throw new Error(`Unknown provider type: ${tradeProvider}`) diff --git a/packages/mask/src/plugins/Trader/trader/useAllTradeComputed.ts b/packages/mask/src/plugins/Trader/trader/useAllTradeComputed.ts index e8f998637286..cb5e85bb8560 100644 --- a/packages/mask/src/plugins/Trader/trader/useAllTradeComputed.ts +++ b/packages/mask/src/plugins/Trader/trader/useAllTradeComputed.ts @@ -23,6 +23,9 @@ import { useTradeGasLimit as useBancorTradeGasLimit } from './bancor/useTradeGas import { useTradeComputed as useOpenOceanTradeComputed } from './openocean/useTradeComputed' import { useTrade as useOpenOceanTrade } from './openocean/useTrade' import { useTradeGasLimit as useOpenOceanTradeGasLimit } from './openocean/useTradeGasLimit' +import { useTrade as useWoofiTrade } from './woofi/useTrade' +import { useTradeComputed as useWoofiTradeComputed } from './woofi/useTradeComputed' +import { useTradeGasLimit as useWoofiTradeGasLimit } from './woofi/useTradeGasLimit' import { TradeProvider } from '@masknet/public-api' import { useAvailableTraderProviders } from '../trending/useAvailableTraderProviders' import { useNativeTradeGasLimit } from './useNativeTradeGasLimit' @@ -206,6 +209,18 @@ export function useAllTradeComputed( traderEstimateGas: mdexEstimateGas, } = useUniswapV2Like(tradeProviders, TradeProvider.MDEX, inputAmount_, inputToken, outputToken) + // woofi + const woofi_ = useWoofiTrade( + TradeStrategy.ExactIn, + inputAmount_, + '0', + tradeProviders.some((x) => x === TradeProvider.WOOFI) ? inputToken : undefined, + tradeProviders.some((x) => x === TradeProvider.WOOFI) ? outputToken : undefined, + temporarySlippage, + ) + const woofi = useWoofiTradeComputed(woofi_.value ?? null, TradeStrategy.ExactIn, inputToken, outputToken) + const woofiEstimateGas = useWoofiTradeGasLimit(woofi) + const allTradeResult = [ { provider: TradeProvider.UNISWAP_V2, ...uniswapV2_, value: uniswapV2, gas: uniswapV2EstimateGas }, { provider: TradeProvider.SUSHISWAP, ...sushiSwap_, value: sushiSwap, gas: sushiSwapEstimateGas }, @@ -223,6 +238,7 @@ export function useAllTradeComputed( { provider: TradeProvider.WANNASWAP, ...wannaswap_, value: wannaswap, gas: wannaSwapEstimateGas }, { provider: TradeProvider.TRISOLARIS, ...trisolaris_, value: trisolaris, gas: trisolarisEstimateGas }, { provider: TradeProvider.MDEX, ...mdex_, value: mdex, gas: mdexEstimateGas }, + { provider: TradeProvider.WOOFI, ...woofi_, value: woofi, gas: woofiEstimateGas }, ] return nativeToken_.value diff --git a/packages/mask/src/plugins/Trader/trader/useGetTradeContext.ts b/packages/mask/src/plugins/Trader/trader/useGetTradeContext.ts index 122a42570f09..fb5ea6f43d18 100644 --- a/packages/mask/src/plugins/Trader/trader/useGetTradeContext.ts +++ b/packages/mask/src/plugins/Trader/trader/useGetTradeContext.ts @@ -23,6 +23,8 @@ import { TRISOLARIS_CUSTOM_BASES, MDEX_BASE_AGAINST_TOKENS, MDEX_CUSTOM_BASES, + WOOFI_BASE_AGAINST_TOKENS, + WOOFI_CUSTOM_BASES, } from '../constants' import { unreachable } from '@dimensiondev/kit' import { TargetChainIdContext } from './useTargetChainIdContext' @@ -188,6 +190,18 @@ export function useGetTradeContext(tradeProvider?: TradeProvider) { TYPE: tradeProvider, ROUTER_CONTRACT_ADDRESS: DEX_TRADE.BANCOR_EXCHANGE_PROXY_ADDRESS, } + case TradeProvider.WOOFI: + return { + TYPE: tradeProvider, + IS_UNISWAP_V2_LIKE: true, + GRAPH_API: DEX_TRADE.WOOFI_THEGRAPH, + INIT_CODE_HASH: DEX_TRADE.WOOFI_INIT_CODE_HASH, + ROUTER_CONTRACT_ADDRESS: DEX_TRADE.WOOFI_ROUTER_ADDRESS, + FACTORY_CONTRACT_ADDRESS: DEX_TRADE.WOOFI_FACTORY_ADDRESS, + AGAINST_TOKENS: WOOFI_BASE_AGAINST_TOKENS, + ADDITIONAL_TOKENS: {}, + CUSTOM_TOKENS: WOOFI_CUSTOM_BASES, + } default: if (tradeProvider) unreachable(tradeProvider) return null diff --git a/packages/mask/src/plugins/Trader/trader/useTradeCallback.ts b/packages/mask/src/plugins/Trader/trader/useTradeCallback.ts index 1f45f7b22380..6ff2e7afbd4c 100644 --- a/packages/mask/src/plugins/Trader/trader/useTradeCallback.ts +++ b/packages/mask/src/plugins/Trader/trader/useTradeCallback.ts @@ -10,6 +10,7 @@ import type { SwapRouteSuccessResponse, TradeComputed, SwapOOSuccessResponse, + WoofiSwapSuccessResponse, } from '../types' import { useTradeCallback as useNativeTokenWrapperCallback } from './native/useTradeCallback' import { useTradeCallback as useZrxCallback } from './0x/useTradeCallback' @@ -18,6 +19,7 @@ import { useTradeCallback as useBalancerCallback } from './balancer/useTradeCall import { useTradeCallback as useDODOCallback } from './dodo/useTradeCallback' import { useTradeCallback as useBancorCallback } from './bancor/useTradeCallback' import { useTradeCallback as useOpenOceanCallback } from './openocean/useTradeCallback' +import { useTradeCallback as useWoofiCallback } from './woofi/useTradeCallback' import { useExchangeProxyContract } from '../contracts/balancer/useExchangeProxyContract' import type { NativeTokenWrapper } from './native/useTradeComputed' import { isNativeTokenWrapper } from '../helpers' @@ -53,6 +55,9 @@ export function useTradeCallback( const tradeComputedForOpenOcean = !isNativeTokenWrapper_ ? (tradeComputed as TradeComputed) : null + const tradeComputedForWoofi = !isNativeTokenWrapper_ + ? (tradeComputed as TradeComputed) + : null // uniswap like providers const uniswapV2Like = useUniswapCallback(tradeComputedForUniswapV2Like, provider, gasConfig, allowedSlippage) const uniswapV3Like = useUniswapCallback(tradeComputedForUniswapV3Like, provider, gasConfig, allowedSlippage) @@ -74,6 +79,7 @@ export function useTradeCallback( provider === TradeProvider.OPENOCEAN ? tradeComputedForOpenOcean : null, gasConfig, ) + const woofi = useWoofiCallback(provider === TradeProvider.WOOFI ? tradeComputedForWoofi : null, gasConfig) // the trade is an ETH-WETH pair const nativeTokenWrapper = useNativeTokenWrapperCallback( @@ -116,6 +122,8 @@ export function useTradeCallback( return uniswapV2Like case TradeProvider.OPENOCEAN: return openocean + case TradeProvider.WOOFI: + return woofi default: if (provider) unreachable(provider) return [] diff --git a/packages/mask/src/plugins/Trader/trader/useTradeContext.ts b/packages/mask/src/plugins/Trader/trader/useTradeContext.ts index f7c178b17548..8d46a6e2908c 100644 --- a/packages/mask/src/plugins/Trader/trader/useTradeContext.ts +++ b/packages/mask/src/plugins/Trader/trader/useTradeContext.ts @@ -23,6 +23,8 @@ import { TRISOLARIS_BASE_AGAINST_TOKENS, MDEX_CUSTOM_BASES, MDEX_BASE_AGAINST_TOKENS, + WOOFI_CUSTOM_BASES, + WOOFI_BASE_AGAINST_TOKENS, } from '../constants' import type { TradeContext as TradeContext_ } from '../types' import { TargetChainIdContext } from './useTargetChainIdContext' @@ -190,6 +192,17 @@ export function useTradeContext(tradeProvider: TradeProvider) { TYPE: tradeProvider, ROUTER_CONTRACT_ADDRESS: DEX_TRADE.OPENOCEAN_EXCHANGE_PROXY_ADDRESS, } + case TradeProvider.WOOFI: + return { + TYPE: tradeProvider, + GRAPH_API: DEX_TRADE.WOOFI_THEGRAPH, + INIT_CODE_HASH: DEX_TRADE.WOOFI_INIT_CODE_HASH, + ROUTER_CONTRACT_ADDRESS: DEX_TRADE.WOOFI_ROUTER_ADDRESS, + FACTORY_CONTRACT_ADDRESS: DEX_TRADE.WOOFI_FACTORY_ADDRESS, + AGAINST_TOKENS: WOOFI_BASE_AGAINST_TOKENS, + ADDITIONAL_TOKENS: {}, + CUSTOM_TOKENS: WOOFI_CUSTOM_BASES, + } default: unreachable(tradeProvider) } diff --git a/packages/mask/src/plugins/Trader/trader/woofi/useSlippageTolerance.ts b/packages/mask/src/plugins/Trader/trader/woofi/useSlippageTolerance.ts new file mode 100644 index 000000000000..2a6f0dd0f4c5 --- /dev/null +++ b/packages/mask/src/plugins/Trader/trader/woofi/useSlippageTolerance.ts @@ -0,0 +1,9 @@ +import { clamp } from 'lodash-unified' +import { useValueRef } from '@masknet/shared-base-ui' +import { SLIPPAGE_MAX, SLIPPAGE_MIN } from '../../constants' +import { currentSlippageSettings } from '../../settings' + +export function useSlippageTolerance() { + const slippage_ = useValueRef(currentSlippageSettings) + return clamp(slippage_, SLIPPAGE_MIN, SLIPPAGE_MAX) +} diff --git a/packages/mask/src/plugins/Trader/trader/woofi/useTrade.ts b/packages/mask/src/plugins/Trader/trader/woofi/useTrade.ts new file mode 100644 index 000000000000..13e2a6ab4042 --- /dev/null +++ b/packages/mask/src/plugins/Trader/trader/woofi/useTrade.ts @@ -0,0 +1,70 @@ +import { + FungibleTokenDetailed, + getNetworkTypeFromChainId, + isNativeTokenAddress, + useAccount, + useRPCConstants, + useTokenConstants, +} from '@masknet/web3-shared-evm' +import { PluginTraderRPC } from '../../messages' +import type { WoofiSwapData, TradeStrategy } from '../../types' +import { useSlippageTolerance } from './useSlippageTolerance' +import { first } from 'lodash-unified' +import { TargetChainIdContext } from '../useTargetChainIdContext' +import { useDoubleBlockBeatRetry } from '@masknet/plugin-infra/web3' +import type { AsyncStateRetry } from 'react-use/lib/useAsyncRetry' +import { currentNetworkSettings } from '../../../Wallet/settings' + +export function useTrade( + strategy: TradeStrategy, + inputAmount: string, + outputAmount: string, + inputToken?: FungibleTokenDetailed, + outputToken?: FungibleTokenDetailed, + temporarySlippage?: number, +): AsyncStateRetry { + const { targetChainId } = TargetChainIdContext.useContainer() + + const slippageSetting = useSlippageTolerance() + const slippage = temporarySlippage || slippageSetting + const { targetChainId: chainId } = TargetChainIdContext.useContainer() + const { WNATIVE_ADDRESS } = useTokenConstants(chainId) + const { RPC } = useRPCConstants(chainId) + const providerURL = first(RPC) + const account = useAccount() + + return useDoubleBlockBeatRetry(async () => { + if (!inputToken || !outputToken) return null + if (inputAmount === '0') return null + const sellToken = isNativeTokenAddress(inputToken) + ? { ...inputToken, address: WNATIVE_ADDRESS ?? '' } + : inputToken + const buyToken = isNativeTokenAddress(outputToken) + ? { ...outputToken, address: WNATIVE_ADDRESS ?? '' } + : outputToken + return PluginTraderRPC.woofiSwap( + { + isNativeSellToken: isNativeTokenAddress(inputToken), + fromToken: sellToken, + toToken: buyToken, + fromAmount: inputAmount, + slippage: slippage / 100, + userAddr: account, + rpc: providerURL, + chainId, + }, + getNetworkTypeFromChainId(targetChainId) ?? currentNetworkSettings.value, + ) + }, [ + WNATIVE_ADDRESS, + strategy, + inputAmount, + outputAmount, + inputToken?.address, + outputToken?.address, + slippage, + account, + providerURL, + chainId, + ]) +} diff --git a/packages/mask/src/plugins/Trader/trader/woofi/useTradeCallback.ts b/packages/mask/src/plugins/Trader/trader/woofi/useTradeCallback.ts new file mode 100644 index 000000000000..c05b2d9ccce5 --- /dev/null +++ b/packages/mask/src/plugins/Trader/trader/woofi/useTradeCallback.ts @@ -0,0 +1,84 @@ +import { useCallback, useMemo, useState } from 'react' +import stringify from 'json-stable-stringify' +import { pick } from 'lodash-unified' +import type { TransactionConfig } from 'web3-core' +import { GasOptionConfig, TransactionState, TransactionStateType, useAccount, useWeb3 } from '@masknet/web3-shared-evm' +import type { WoofiSwapSuccessResponse, TradeComputed } from '../../types' +import { TargetChainIdContext } from '../useTargetChainIdContext' + +export function useTradeCallback( + tradeComputed: TradeComputed | null, + gasConfig?: GasOptionConfig, +) { + const account = useAccount() + const { targetChainId: chainId } = TargetChainIdContext.useContainer() + const web3 = useWeb3({ chainId }) + + const [tradeState, setTradeState] = useState({ + type: TransactionStateType.UNKNOWN, + }) + + // compose transaction config + const config = useMemo(() => { + if (!account || !tradeComputed?.trade_) return null + return { + from: account, + ...pick(tradeComputed.trade_, ['to', 'data', 'value']), + } as unknown as TransactionConfig + }, [account, tradeComputed]) + + const tradeCallback = useCallback(async () => { + // validate config + if (!account || !config) { + setTradeState({ + type: TransactionStateType.UNKNOWN, + }) + return + } + + // start waiting for provider to confirm tx + setTradeState({ + type: TransactionStateType.WAIT_FOR_CONFIRMING, + }) + + // compose transaction config + const config_ = { + ...config, + gas: await web3.eth.estimateGas(config).catch((error) => { + setTradeState({ + type: TransactionStateType.FAILED, + error, + }) + throw error + }), + ...gasConfig, + } + + // send transaction and wait for hash + return new Promise((resolve, reject) => { + web3.eth.sendTransaction(config_, (error, hash) => { + if (error) { + setTradeState({ + type: TransactionStateType.FAILED, + error, + }) + reject(error) + } else { + setTradeState({ + type: TransactionStateType.HASH, + hash, + }) + resolve(hash) + } + }) + }) + }, [web3, account, chainId, stringify(config), gasConfig]) + + const resetCallback = useCallback(() => { + setTradeState({ + type: TransactionStateType.UNKNOWN, + }) + }, []) + + return [tradeState, tradeCallback, resetCallback] as const +} diff --git a/packages/mask/src/plugins/Trader/trader/woofi/useTradeComputed.ts b/packages/mask/src/plugins/Trader/trader/woofi/useTradeComputed.ts new file mode 100644 index 000000000000..a44b94f59e0b --- /dev/null +++ b/packages/mask/src/plugins/Trader/trader/woofi/useTradeComputed.ts @@ -0,0 +1,35 @@ +import BigNumber from 'bignumber.js' +import { useMemo } from 'react' +import type { FungibleTokenDetailed } from '@masknet/web3-shared-evm' +import { rightShift, ZERO } from '@masknet/web3-shared-base' +import { WoofiSwapData, TradeComputed, TradeStrategy } from '../../types' + +export function useTradeComputed( + trade: WoofiSwapData | null, + strategy: TradeStrategy, + inputToken?: FungibleTokenDetailed, + outputToken?: FungibleTokenDetailed, +) { + return useMemo(() => { + if (!trade) return null + if (!inputToken || !outputToken) return null + const isExactIn = strategy === TradeStrategy.ExactIn + if (!isExactIn) return null + const inputAmount = rightShift(trade.fromAmount, inputToken.decimals).integerValue() + const executionPrice = new BigNumber(trade.price) + const outputAmount = rightShift(trade.toAmount, outputToken.decimals).integerValue() + + return { + strategy, + inputToken, + outputToken, + inputAmount, + outputAmount, + executionPrice, + fee: ZERO, + maximumSold: inputAmount, + + trade_: { ...trade }, + } as TradeComputed + }, [trade, strategy, inputToken, outputToken]) +} diff --git a/packages/mask/src/plugins/Trader/trader/woofi/useTradeGasLimit.ts b/packages/mask/src/plugins/Trader/trader/woofi/useTradeGasLimit.ts new file mode 100644 index 000000000000..6e5d2af11bc5 --- /dev/null +++ b/packages/mask/src/plugins/Trader/trader/woofi/useTradeGasLimit.ts @@ -0,0 +1,26 @@ +import type { WoofiSwapData, TradeComputed } from '../../types' +import { useMemo } from 'react' +import { useAccount, useWeb3 } from '@masknet/web3-shared-evm' +import { TargetChainIdContext } from '../useTargetChainIdContext' +import { pick } from 'lodash-unified' +import type { TransactionConfig } from 'web3-core' +import { useAsync } from 'react-use' +import type { AsyncState } from 'react-use/lib/useAsyncFn' + +export function useTradeGasLimit(tradeComputed: TradeComputed | null): AsyncState { + const { targetChainId } = TargetChainIdContext.useContainer() + const web3 = useWeb3({ chainId: targetChainId }) + const account = useAccount() + const config = useMemo(() => { + if (!account || !tradeComputed?.trade_) return null + return { + from: account, + ...pick(tradeComputed.trade_, ['to', 'data', 'value']), + } as TransactionConfig + }, [account, tradeComputed]) + + return useAsync(async () => { + if (!config) return 0 + return web3.eth.estimateGas(config) + }, [config, web3]) +} diff --git a/packages/mask/src/plugins/Trader/types/index.ts b/packages/mask/src/plugins/Trader/types/index.ts index bcb516e55046..b35eca7761a3 100644 --- a/packages/mask/src/plugins/Trader/types/index.ts +++ b/packages/mask/src/plugins/Trader/types/index.ts @@ -6,3 +6,4 @@ export * from './trader' export * from './trending' export * from './bancor' export * from './openocean' +export * from './woofi' diff --git a/packages/mask/src/plugins/Trader/types/trader.ts b/packages/mask/src/plugins/Trader/types/trader.ts index 7b93d13ef58d..32ca92f5dca4 100644 --- a/packages/mask/src/plugins/Trader/types/trader.ts +++ b/packages/mask/src/plugins/Trader/types/trader.ts @@ -76,6 +76,7 @@ export enum ZrxTradePool { Trisolaris = 'Trisolaris', WannaSwap = 'WannaSwap', Mdex = 'Mdex', + Woofi = 'Woofi', } export interface TradeComputed { diff --git a/packages/mask/src/plugins/Trader/types/woofi.ts b/packages/mask/src/plugins/Trader/types/woofi.ts new file mode 100644 index 000000000000..9d83822ea0e2 --- /dev/null +++ b/packages/mask/src/plugins/Trader/types/woofi.ts @@ -0,0 +1,49 @@ +import type { FungibleTokenDetailed } from '@masknet/web3-shared-evm' + +export interface WoofiSwapRequest { + isNativeSellToken: boolean + fromToken: FungibleTokenDetailed + toToken: FungibleTokenDetailed + fromAmount: string + slippage: number + userAddr: string + rpc?: string + chainId: number +} + +export type WoofiSwapResponse = WoofiSwapSuccessResponse | WoofiSwapErrorResponse + +export interface WoofiSwapSuccessResponse { + status: 'ok' + data: WoofiSwapResponseData +} + +interface WoofiSwapResponseData { + to_amount: string + trading_fee: string + single_fee_percentage: number + price: number + path: string[] + route: string[] +} + +interface WoofiSwapErrorResponse { + status: 'fail' + error?: { + message: string + } +} + +export interface WoofiSwapData { + toAmount: number + path: string[] + price: number + tradingFee: string + route: string[] + singleFeePercentage: number + fromAmount: number + value: string + slippage: number + fromTokenSymbol: string | undefined + toTokenSymbol: string | undefined +} diff --git a/packages/public-api/src/web.ts b/packages/public-api/src/web.ts index eaebe68c57c3..1f79d9edd723 100644 --- a/packages/public-api/src/web.ts +++ b/packages/public-api/src/web.ts @@ -200,6 +200,7 @@ export enum TradeProvider { TRADERJOE = 13, PANGOLIN = 14, MDEX = 15, + WOOFI = 16, } /** Supported language settings */ diff --git a/packages/web3-constants/evm/token.json b/packages/web3-constants/evm/token.json index e6cdc468f1a2..e2fbc8b06b6d 100644 --- a/packages/web3-constants/evm/token.json +++ b/packages/web3-constants/evm/token.json @@ -1002,7 +1002,7 @@ "Avalanche": "0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB", "Avalanche_Fuji": "", "Celo": "", - "Fantom": "", + "Fantom": "0x74b23882a30290451A17c44f4F05243b6b58C76d", "Aurora": "", "Aurora_Testnet": "", "Conflux": "" @@ -2140,5 +2140,26 @@ "Aurora": "", "Aurora_Testnet": "", "Conflux": "0x0000000000000000000000000000000000000000" + }, + "WOO_ADDRESS": { + "Mainnet": "", + "Ropsten": "", + "Rinkeby": "", + "Kovan": "", + "Gorli": "", + "BSC": "0x4691937a7508860f876c9c0a2a617e7d9e945d4b", + "BSCT": "", + "Matic": "", + "Mumbai": "", + "Arbitrum": "", + "Arbitrum_Rinkeby": "", + "xDai": "", + "Avalanche": "0xaBC9547B534519fF73921b1FBA6E672b5f58D083", + "Avalanche_Fuji": "", + "Celo": "", + "Fantom": "0x6626c47c00F1D87902fc13EECfaC3ed06D5E8D8a", + "Aurora": "", + "Aurora_Testnet": "", + "Conflux": "" } } diff --git a/packages/web3-constants/evm/trader.json b/packages/web3-constants/evm/trader.json index e8cac04eca46..9f1cfc6e2d43 100644 --- a/packages/web3-constants/evm/trader.json +++ b/packages/web3-constants/evm/trader.json @@ -1153,5 +1153,89 @@ "Aurora": "", "Aurora_Testnet": "", "Conflux": "" + }, + "WOOFI_ROUTER_ADDRESS": { + "Mainnet": "", + "Ropsten": "", + "Rinkeby": "", + "Kovan": "", + "Gorli": "", + "BSC": "", + "BSCT": "", + "Matic": "", + "Mumbai": "", + "Arbitrum": "", + "Arbitrum_Rinkeby": "", + "xDai": "", + "Avalanche": "", + "Avalanche_Fuji": "", + "Celo": "", + "Fantom": "", + "Aurora": "", + "Aurora_Testnet": "", + "Conflux": "" + }, + "WOOFI_FACTORY_ADDRESS": { + "Mainnet": "", + "Ropsten": "", + "Rinkeby": "", + "Kovan": "", + "Gorli": "", + "BSC": "", + "BSCT": "", + "Matic": "", + "Mumbai": "", + "Arbitrum": "", + "Arbitrum_Rinkeby": "", + "xDai": "", + "Avalanche": "", + "Avalanche_Fuji": "", + "Celo": "", + "Fantom": "", + "Aurora": "", + "Aurora_Testnet": "", + "Conflux": "" + }, + "WOOFI_THEGRAPH": { + "Mainnet": "", + "Ropsten": "", + "Rinkeby": "", + "Kovan": "", + "Gorli": "", + "BSC": "", + "BSCT": "", + "Matic": "", + "Mumbai": "", + "Arbitrum": "", + "Arbitrum_Rinkeby": "", + "xDai": "", + "Avalanche": "", + "Avalanche_Fuji": "", + "Celo": "", + "Fantom": "", + "Aurora": "", + "Aurora_Testnet": "", + "Conflux": "" + }, + "WOOFI_INIT_CODE_HASH": { + "Mainnet": "", + "Ropsten": "", + "Rinkeby": "", + "Kovan": "", + "Gorli": "", + "BSC": "", + "BSCT": "", + "Matic": "", + "Mumbai": "", + "Arbitrum": "", + "Arbitrum_Rinkeby": "", + "xDai": "", + "Avalanche": "", + "Avalanche_Fuji": "", + "Celo": "", + "Fantom": "", + "Aurora": "", + "Aurora_Testnet": "", + "Conflux": "" } } diff --git a/packages/web3-shared/evm/constants/tokens.ts b/packages/web3-shared/evm/constants/tokens.ts index 69b687e60645..6849af50c668 100644 --- a/packages/web3-shared/evm/constants/tokens.ts +++ b/packages/web3-shared/evm/constants/tokens.ts @@ -45,6 +45,7 @@ export const CUSD = createERC20Tokens('cUSD_ADDRESS', 'Celo Dollar', 'cUSD', 18) export const CEUR = createERC20Tokens('cEUR_ADDRESS', 'Celo Euro', 'cEUR', 18) export const JOE = createERC20Tokens('JOE_ADDRESS', 'JoeToken', 'JOE', 18) export const PNG = createERC20Tokens('PNG_ADDRESS', 'Pangolin', 'PNG', 18) +export const WOO = createERC20Tokens('WOO_ADDRESS', 'Woo', 'WOO', 18) export const WNATIVE = createERC20Tokens( 'WNATIVE_ADDRESS', From 25f58119e45c7e5e3b2a9e0dbfa4006baac7dd25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BA=A7n=20Trung=20T=C3=ADn?= Date: Thu, 21 Apr 2022 09:36:13 +0700 Subject: [PATCH 2/4] feat: use WooRouterV2 for estimateGas and swap --- .../src/plugins/Trader/constants/woofi.ts | 1 + .../contracts/woofi/useWooRouterV2Contract.ts | 8 + .../mask/src/plugins/Trader/helpers/trader.ts | 16 + .../Trader/trader/useAllTradeComputed.ts | 8 +- .../Trader/trader/useGetTradeContext.ts | 4 - .../plugins/Trader/trader/useTradeCallback.ts | 8 +- .../plugins/Trader/trader/useTradeContext.ts | 3 - .../Trader/trader/woofi/useTradeCallback.ts | 97 ++++-- .../Trader/trader/woofi/useTradeComputed.ts | 9 +- .../Trader/trader/woofi/useTradeGasLimit.ts | 20 +- packages/web3-constants/evm/trader.json | 69 +---- packages/web3-contracts/abis/WooRouterV2.json | 278 ++++++++++++++++++ .../web3-contracts/types/WooRouterV2.d.ts | 146 +++++++++ 13 files changed, 555 insertions(+), 112 deletions(-) create mode 100644 packages/mask/src/plugins/Trader/contracts/woofi/useWooRouterV2Contract.ts create mode 100644 packages/web3-contracts/abis/WooRouterV2.json create mode 100644 packages/web3-contracts/types/WooRouterV2.d.ts diff --git a/packages/mask/src/plugins/Trader/constants/woofi.ts b/packages/mask/src/plugins/Trader/constants/woofi.ts index 06f5ebb61941..14d2babdd82f 100644 --- a/packages/mask/src/plugins/Trader/constants/woofi.ts +++ b/packages/mask/src/plugins/Trader/constants/woofi.ts @@ -13,6 +13,7 @@ import { import type { ERC20AgainstToken, ERC20TokenCustomizedBase } from './types' +export const WOOFI_NATIVE_TOKEN_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' export const woofiNetworkNames: Record = { [NetworkType.Ethereum]: '', [NetworkType.Binance]: 'bsc', diff --git a/packages/mask/src/plugins/Trader/contracts/woofi/useWooRouterV2Contract.ts b/packages/mask/src/plugins/Trader/contracts/woofi/useWooRouterV2Contract.ts new file mode 100644 index 000000000000..6a07c1142d50 --- /dev/null +++ b/packages/mask/src/plugins/Trader/contracts/woofi/useWooRouterV2Contract.ts @@ -0,0 +1,8 @@ +import type { AbiItem } from 'web3-utils' +import WooRouterV2ABI from '@masknet/web3-contracts/abis/WooRouterV2.json' +import type { WooRouterV2 } from '@masknet/web3-contracts/types/WooRouterV2' +import { ChainId, useContract } from '@masknet/web3-shared-evm' + +export function useWooRouterV2Contract(address?: string, chainId?: ChainId) { + return useContract(address, WooRouterV2ABI as AbiItem[], chainId) +} diff --git a/packages/mask/src/plugins/Trader/helpers/trader.ts b/packages/mask/src/plugins/Trader/helpers/trader.ts index e61cb6489f90..0afe1281c338 100644 --- a/packages/mask/src/plugins/Trader/helpers/trader.ts +++ b/packages/mask/src/plugins/Trader/helpers/trader.ts @@ -1,3 +1,5 @@ +import type { FungibleTokenDetailed } from '@masknet/web3-shared-evm' +import { ONE } from '@masknet/web3-shared-base' import BigNumber from 'bignumber.js' import { BIPS_BASE } from '../constants' import type { NativeTokenWrapper } from '../trader/native/useTradeComputed' @@ -11,3 +13,17 @@ export function isNativeTokenWrapper(trade: TradeComputed | null): trad const trade_ = trade as TradeComputed | null return trade_?.trade_?.isNativeTokenWrapper ?? false } + +export const calculateMinimumReceived = ({ + toToken, + toAmount, + slippage, +}: { + toToken: FungibleTokenDetailed + toAmount: BigNumber + slippage: number +}): BigNumber => { + const slippageWei = new BigNumber(slippage).dividedBy(BIPS_BASE) + const minReturnWei = toAmount.times(ONE.minus(slippageWei)) + return minReturnWei +} diff --git a/packages/mask/src/plugins/Trader/trader/useAllTradeComputed.ts b/packages/mask/src/plugins/Trader/trader/useAllTradeComputed.ts index cb5e85bb8560..eb48e40692b1 100644 --- a/packages/mask/src/plugins/Trader/trader/useAllTradeComputed.ts +++ b/packages/mask/src/plugins/Trader/trader/useAllTradeComputed.ts @@ -218,7 +218,13 @@ export function useAllTradeComputed( tradeProviders.some((x) => x === TradeProvider.WOOFI) ? outputToken : undefined, temporarySlippage, ) - const woofi = useWoofiTradeComputed(woofi_.value ?? null, TradeStrategy.ExactIn, inputToken, outputToken) + const woofi = useWoofiTradeComputed( + woofi_.value ?? null, + TradeStrategy.ExactIn, + inputToken, + outputToken, + temporarySlippage, + ) const woofiEstimateGas = useWoofiTradeGasLimit(woofi) const allTradeResult = [ diff --git a/packages/mask/src/plugins/Trader/trader/useGetTradeContext.ts b/packages/mask/src/plugins/Trader/trader/useGetTradeContext.ts index fb5ea6f43d18..16dbb42a54a0 100644 --- a/packages/mask/src/plugins/Trader/trader/useGetTradeContext.ts +++ b/packages/mask/src/plugins/Trader/trader/useGetTradeContext.ts @@ -193,11 +193,7 @@ export function useGetTradeContext(tradeProvider?: TradeProvider) { case TradeProvider.WOOFI: return { TYPE: tradeProvider, - IS_UNISWAP_V2_LIKE: true, - GRAPH_API: DEX_TRADE.WOOFI_THEGRAPH, - INIT_CODE_HASH: DEX_TRADE.WOOFI_INIT_CODE_HASH, ROUTER_CONTRACT_ADDRESS: DEX_TRADE.WOOFI_ROUTER_ADDRESS, - FACTORY_CONTRACT_ADDRESS: DEX_TRADE.WOOFI_FACTORY_ADDRESS, AGAINST_TOKENS: WOOFI_BASE_AGAINST_TOKENS, ADDITIONAL_TOKENS: {}, CUSTOM_TOKENS: WOOFI_CUSTOM_BASES, diff --git a/packages/mask/src/plugins/Trader/trader/useTradeCallback.ts b/packages/mask/src/plugins/Trader/trader/useTradeCallback.ts index 6ff2e7afbd4c..2abbd1389387 100644 --- a/packages/mask/src/plugins/Trader/trader/useTradeCallback.ts +++ b/packages/mask/src/plugins/Trader/trader/useTradeCallback.ts @@ -10,7 +10,7 @@ import type { SwapRouteSuccessResponse, TradeComputed, SwapOOSuccessResponse, - WoofiSwapSuccessResponse, + WoofiSwapData, } from '../types' import { useTradeCallback as useNativeTokenWrapperCallback } from './native/useTradeCallback' import { useTradeCallback as useZrxCallback } from './0x/useTradeCallback' @@ -55,9 +55,7 @@ export function useTradeCallback( const tradeComputedForOpenOcean = !isNativeTokenWrapper_ ? (tradeComputed as TradeComputed) : null - const tradeComputedForWoofi = !isNativeTokenWrapper_ - ? (tradeComputed as TradeComputed) - : null + const tradeComputedForWoofi = !isNativeTokenWrapper_ ? (tradeComputed as TradeComputed) : null // uniswap like providers const uniswapV2Like = useUniswapCallback(tradeComputedForUniswapV2Like, provider, gasConfig, allowedSlippage) const uniswapV3Like = useUniswapCallback(tradeComputedForUniswapV3Like, provider, gasConfig, allowedSlippage) @@ -79,7 +77,7 @@ export function useTradeCallback( provider === TradeProvider.OPENOCEAN ? tradeComputedForOpenOcean : null, gasConfig, ) - const woofi = useWoofiCallback(provider === TradeProvider.WOOFI ? tradeComputedForWoofi : null, gasConfig) + const woofi = useWoofiCallback(provider === TradeProvider.WOOFI ? tradeComputedForWoofi : null, provider, gasConfig) // the trade is an ETH-WETH pair const nativeTokenWrapper = useNativeTokenWrapperCallback( diff --git a/packages/mask/src/plugins/Trader/trader/useTradeContext.ts b/packages/mask/src/plugins/Trader/trader/useTradeContext.ts index 8d46a6e2908c..1f6e48d0af05 100644 --- a/packages/mask/src/plugins/Trader/trader/useTradeContext.ts +++ b/packages/mask/src/plugins/Trader/trader/useTradeContext.ts @@ -195,10 +195,7 @@ export function useTradeContext(tradeProvider: TradeProvider) { case TradeProvider.WOOFI: return { TYPE: tradeProvider, - GRAPH_API: DEX_TRADE.WOOFI_THEGRAPH, - INIT_CODE_HASH: DEX_TRADE.WOOFI_INIT_CODE_HASH, ROUTER_CONTRACT_ADDRESS: DEX_TRADE.WOOFI_ROUTER_ADDRESS, - FACTORY_CONTRACT_ADDRESS: DEX_TRADE.WOOFI_FACTORY_ADDRESS, AGAINST_TOKENS: WOOFI_BASE_AGAINST_TOKENS, ADDITIONAL_TOKENS: {}, CUSTOM_TOKENS: WOOFI_CUSTOM_BASES, diff --git a/packages/mask/src/plugins/Trader/trader/woofi/useTradeCallback.ts b/packages/mask/src/plugins/Trader/trader/woofi/useTradeCallback.ts index c05b2d9ccce5..ddda38201b09 100644 --- a/packages/mask/src/plugins/Trader/trader/woofi/useTradeCallback.ts +++ b/packages/mask/src/plugins/Trader/trader/woofi/useTradeCallback.ts @@ -1,16 +1,30 @@ import { useCallback, useMemo, useState } from 'react' import stringify from 'json-stable-stringify' import { pick } from 'lodash-unified' -import type { TransactionConfig } from 'web3-core' -import { GasOptionConfig, TransactionState, TransactionStateType, useAccount, useWeb3 } from '@masknet/web3-shared-evm' -import type { WoofiSwapSuccessResponse, TradeComputed } from '../../types' +import { + GasOptionConfig, + isNativeTokenAddress, + TransactionEventType, + TransactionState, + TransactionStateType, + useAccount, + useWeb3, +} from '@masknet/web3-shared-evm' +import type { TradeComputed, WoofiSwapData } from '../../types' import { TargetChainIdContext } from '../useTargetChainIdContext' +import type { TradeProvider } from '@masknet/public-api' +import { useWooRouterV2Contract } from '../../contracts/woofi/useWooRouterV2Contract' +import { WOOFI_NATIVE_TOKEN_ADDRESS } from '../../constants' +import { useGetTradeContext } from '../useGetTradeContext' +import type { TransactionReceipt } from 'web3-core' export function useTradeCallback( - tradeComputed: TradeComputed | null, + trade: TradeComputed | null, + tradeProvider?: TradeProvider, gasConfig?: GasOptionConfig, ) { const account = useAccount() + const context = useGetTradeContext(tradeProvider) const { targetChainId: chainId } = TargetChainIdContext.useContainer() const web3 = useWeb3({ chainId }) @@ -18,59 +32,90 @@ export function useTradeCallback( type: TransactionStateType.UNKNOWN, }) + const swapRouterContract = useWooRouterV2Contract(context?.ROUTER_CONTRACT_ADDRESS, chainId) + // compose transaction config const config = useMemo(() => { - if (!account || !tradeComputed?.trade_) return null + if (!account || !trade?.trade_) return null return { from: account, - ...pick(tradeComputed.trade_, ['to', 'data', 'value']), - } as unknown as TransactionConfig - }, [account, tradeComputed]) + ...pick(trade.trade_, ['to', 'data', 'value']), + } + }, [account, trade]) const tradeCallback = useCallback(async () => { // validate config - if (!account || !config) { + if (!account || !config || !trade?.inputToken || !trade?.outputToken) { setTradeState({ type: TransactionStateType.UNKNOWN, }) return } + const { inputToken, outputToken } = trade + // start waiting for provider to confirm tx setTradeState({ type: TransactionStateType.WAIT_FOR_CONFIRMING, }) + const sellToken = isNativeTokenAddress(inputToken) ? WOOFI_NATIVE_TOKEN_ADDRESS : inputToken.address + const buyToken = isNativeTokenAddress(outputToken) ? WOOFI_NATIVE_TOKEN_ADDRESS : outputToken.address + + const swap = swapRouterContract?.methods.swap( + sellToken, + buyToken, + trade.inputAmount.toString(), + trade.minimumReceived.toString(), + account, + account, + ) // compose transaction config const config_ = { ...config, - gas: await web3.eth.estimateGas(config).catch((error) => { - setTradeState({ - type: TransactionStateType.FAILED, - error, - }) - throw error - }), + gas: undefined as number | undefined, ...gasConfig, } + config_.gas = await swap?.estimateGas(config_).catch((error) => { + setTradeState({ + type: TransactionStateType.FAILED, + error, + }) + throw error + }) + // send transaction and wait for hash return new Promise((resolve, reject) => { - web3.eth.sendTransaction(config_, (error, hash) => { - if (error) { - setTradeState({ - type: TransactionStateType.FAILED, - error, - }) - reject(error) - } else { + swap?.send(config_) + .on(TransactionEventType.TRANSACTION_HASH, (hash: string) => { setTradeState({ type: TransactionStateType.HASH, hash, }) resolve(hash) - } - }) + }) + .on(TransactionEventType.RECEIPT, (receipt: TransactionReceipt) => { + setTradeState({ + type: TransactionStateType.CONFIRMED, + no: 0, + receipt, + }) + }) + .on(TransactionEventType.CONFIRMATION, (no: number, receipt: TransactionReceipt) => { + setTradeState({ + type: TransactionStateType.CONFIRMED, + no, + receipt, + }) + }) + .on(TransactionEventType.ERROR, (error: Error) => { + setTradeState({ + type: TransactionStateType.FAILED, + error, + }) + reject(error) + }) }) }, [web3, account, chainId, stringify(config), gasConfig]) diff --git a/packages/mask/src/plugins/Trader/trader/woofi/useTradeComputed.ts b/packages/mask/src/plugins/Trader/trader/woofi/useTradeComputed.ts index a44b94f59e0b..f968c5ba1407 100644 --- a/packages/mask/src/plugins/Trader/trader/woofi/useTradeComputed.ts +++ b/packages/mask/src/plugins/Trader/trader/woofi/useTradeComputed.ts @@ -3,13 +3,16 @@ import { useMemo } from 'react' import type { FungibleTokenDetailed } from '@masknet/web3-shared-evm' import { rightShift, ZERO } from '@masknet/web3-shared-base' import { WoofiSwapData, TradeComputed, TradeStrategy } from '../../types' +import { SLIPPAGE_DEFAULT } from '../../constants' +import { calculateMinimumReceived } from '../../helpers' export function useTradeComputed( trade: WoofiSwapData | null, strategy: TradeStrategy, inputToken?: FungibleTokenDetailed, outputToken?: FungibleTokenDetailed, -) { + slippage: number = SLIPPAGE_DEFAULT, +): TradeComputed | null { return useMemo(() => { if (!trade) return null if (!inputToken || !outputToken) return null @@ -28,8 +31,10 @@ export function useTradeComputed( executionPrice, fee: ZERO, maximumSold: inputAmount, + priceImpact: ZERO, + minimumReceived: calculateMinimumReceived({ toAmount: outputAmount, toToken: outputToken, slippage }), trade_: { ...trade }, - } as TradeComputed + } }, [trade, strategy, inputToken, outputToken]) } diff --git a/packages/mask/src/plugins/Trader/trader/woofi/useTradeGasLimit.ts b/packages/mask/src/plugins/Trader/trader/woofi/useTradeGasLimit.ts index 6e5d2af11bc5..5a3e42a167ab 100644 --- a/packages/mask/src/plugins/Trader/trader/woofi/useTradeGasLimit.ts +++ b/packages/mask/src/plugins/Trader/trader/woofi/useTradeGasLimit.ts @@ -1,26 +1,36 @@ import type { WoofiSwapData, TradeComputed } from '../../types' import { useMemo } from 'react' -import { useAccount, useWeb3 } from '@masknet/web3-shared-evm' +import { isNativeTokenAddress, useAccount, useWeb3 } from '@masknet/web3-shared-evm' import { TargetChainIdContext } from '../useTargetChainIdContext' import { pick } from 'lodash-unified' -import type { TransactionConfig } from 'web3-core' import { useAsync } from 'react-use' import type { AsyncState } from 'react-use/lib/useAsyncFn' +import { useWooRouterV2Contract } from '../../contracts/woofi/useWooRouterV2Contract' +import { useGetTradeContext } from '../useGetTradeContext' +import { TradeProvider } from '@masknet/public-api' +import { WOOFI_NATIVE_TOKEN_ADDRESS } from '../../constants' export function useTradeGasLimit(tradeComputed: TradeComputed | null): AsyncState { + const context = useGetTradeContext(TradeProvider.WOOFI) const { targetChainId } = TargetChainIdContext.useContainer() const web3 = useWeb3({ chainId: targetChainId }) const account = useAccount() + const swapRouterContract = useWooRouterV2Contract(context?.ROUTER_CONTRACT_ADDRESS, targetChainId) const config = useMemo(() => { if (!account || !tradeComputed?.trade_) return null return { from: account, ...pick(tradeComputed.trade_, ['to', 'data', 'value']), - } as TransactionConfig + } }, [account, tradeComputed]) return useAsync(async () => { - if (!config) return 0 - return web3.eth.estimateGas(config) + if (!config || !tradeComputed?.inputToken || !tradeComputed?.outputToken) return 0 + const { inputToken, outputToken, inputAmount, minimumReceived } = tradeComputed! + const sellToken = isNativeTokenAddress(inputToken) ? WOOFI_NATIVE_TOKEN_ADDRESS : inputToken!.address + const buyToken = isNativeTokenAddress(outputToken) ? WOOFI_NATIVE_TOKEN_ADDRESS : outputToken!.address + return swapRouterContract?.methods + .swap(sellToken, buyToken, inputAmount.toString(), minimumReceived.toString(), account, account) + .estimateGas(config) }, [config, web3]) } diff --git a/packages/web3-constants/evm/trader.json b/packages/web3-constants/evm/trader.json index 9f1cfc6e2d43..19440144c823 100644 --- a/packages/web3-constants/evm/trader.json +++ b/packages/web3-constants/evm/trader.json @@ -1160,80 +1160,17 @@ "Rinkeby": "", "Kovan": "", "Gorli": "", - "BSC": "", - "BSCT": "", - "Matic": "", - "Mumbai": "", - "Arbitrum": "", - "Arbitrum_Rinkeby": "", - "xDai": "", - "Avalanche": "", - "Avalanche_Fuji": "", - "Celo": "", - "Fantom": "", - "Aurora": "", - "Aurora_Testnet": "", - "Conflux": "" - }, - "WOOFI_FACTORY_ADDRESS": { - "Mainnet": "", - "Ropsten": "", - "Rinkeby": "", - "Kovan": "", - "Gorli": "", - "BSC": "", + "BSC": "0xcef5be73ae943b77f9bc08859367d923c030a269", "BSCT": "", "Matic": "", "Mumbai": "", "Arbitrum": "", "Arbitrum_Rinkeby": "", "xDai": "", - "Avalanche": "", + "Avalanche": "0x3e0da0a9e4139b32b37710784b8dca643c152001", "Avalanche_Fuji": "", "Celo": "", - "Fantom": "", - "Aurora": "", - "Aurora_Testnet": "", - "Conflux": "" - }, - "WOOFI_THEGRAPH": { - "Mainnet": "", - "Ropsten": "", - "Rinkeby": "", - "Kovan": "", - "Gorli": "", - "BSC": "", - "BSCT": "", - "Matic": "", - "Mumbai": "", - "Arbitrum": "", - "Arbitrum_Rinkeby": "", - "xDai": "", - "Avalanche": "", - "Avalanche_Fuji": "", - "Celo": "", - "Fantom": "", - "Aurora": "", - "Aurora_Testnet": "", - "Conflux": "" - }, - "WOOFI_INIT_CODE_HASH": { - "Mainnet": "", - "Ropsten": "", - "Rinkeby": "", - "Kovan": "", - "Gorli": "", - "BSC": "", - "BSCT": "", - "Matic": "", - "Mumbai": "", - "Arbitrum": "", - "Arbitrum_Rinkeby": "", - "xDai": "", - "Avalanche": "", - "Avalanche_Fuji": "", - "Celo": "", - "Fantom": "", + "Fantom": "0x37b5a5a730dad670874f26cc5507bb1b9705e447", "Aurora": "", "Aurora_Testnet": "", "Conflux": "" diff --git a/packages/web3-contracts/abis/WooRouterV2.json b/packages/web3-contracts/abis/WooRouterV2.json new file mode 100644 index 000000000000..468fb57de1e3 --- /dev/null +++ b/packages/web3-contracts/abis/WooRouterV2.json @@ -0,0 +1,278 @@ +[ + { + "inputs": [ + { "internalType": "address", "name": "_weth", "type": "address" }, + { "internalType": "address", "name": "_pool", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newPool", + "type": "address" + } + ], + "name": "WooPoolChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "enum IWooRouterV2.SwapType", + "name": "swapType", + "type": "uint8" + }, + { + "indexed": true, + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fromAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "toAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "rebateTo", + "type": "address" + } + ], + "name": "WooRouterSwap", + "type": "event" + }, + { + "inputs": [], + "name": "WETH", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "approveTarget", "type": "address" }, + { "internalType": "address", "name": "swapTarget", "type": "address" }, + { "internalType": "address", "name": "fromToken", "type": "address" }, + { "internalType": "address", "name": "toToken", "type": "address" }, + { "internalType": "uint256", "name": "fromAmount", "type": "uint256" }, + { "internalType": "uint256", "name": "minToAmount", "type": "uint256" }, + { "internalType": "address payable", "name": "to", "type": "address" }, + { "internalType": "bytes", "name": "data", "type": "bytes" } + ], + "name": "externalSwap", + "outputs": [{ "internalType": "uint256", "name": "realToAmount", "type": "uint256" }], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "isWhitelisted", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "baseToken", "type": "address" }, + { "internalType": "uint256", "name": "baseAmount", "type": "uint256" } + ], + "name": "querySellBase", + "outputs": [{ "internalType": "uint256", "name": "quoteAmount", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "baseToken", "type": "address" }, + { "internalType": "uint256", "name": "quoteAmount", "type": "uint256" } + ], + "name": "querySellQuote", + "outputs": [{ "internalType": "uint256", "name": "baseAmount", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "fromToken", "type": "address" }, + { "internalType": "address", "name": "toToken", "type": "address" }, + { "internalType": "uint256", "name": "fromAmount", "type": "uint256" } + ], + "name": "querySwap", + "outputs": [{ "internalType": "uint256", "name": "toAmount", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "quoteToken", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "rescueFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "rescueNativeFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "baseToken", "type": "address" }, + { "internalType": "uint256", "name": "baseAmount", "type": "uint256" }, + { + "internalType": "uint256", + "name": "minQuoteAmount", + "type": "uint256" + }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "address", "name": "rebateTo", "type": "address" } + ], + "name": "sellBase", + "outputs": [ + { + "internalType": "uint256", + "name": "realQuoteAmount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "baseToken", "type": "address" }, + { "internalType": "uint256", "name": "quoteAmount", "type": "uint256" }, + { "internalType": "uint256", "name": "minBaseAmount", "type": "uint256" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "address", "name": "rebateTo", "type": "address" } + ], + "name": "sellQuote", + "outputs": [{ "internalType": "uint256", "name": "realBaseAmount", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "newPool", "type": "address" }], + "name": "setPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "bool", "name": "whitelisted", "type": "bool" } + ], + "name": "setWhitelisted", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "fromToken", "type": "address" }, + { "internalType": "address", "name": "toToken", "type": "address" }, + { "internalType": "uint256", "name": "fromAmount", "type": "uint256" }, + { "internalType": "uint256", "name": "minToAmount", "type": "uint256" }, + { "internalType": "address payable", "name": "to", "type": "address" }, + { "internalType": "address", "name": "rebateTo", "type": "address" } + ], + "name": "swap", + "outputs": [{ "internalType": "uint256", "name": "realToAmount", "type": "uint256" }], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "wooPool", + "outputs": [{ "internalType": "contract IWooPP", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { "stateMutability": "payable", "type": "receive" } +] diff --git a/packages/web3-contracts/types/WooRouterV2.d.ts b/packages/web3-contracts/types/WooRouterV2.d.ts new file mode 100644 index 000000000000..322c923b3543 --- /dev/null +++ b/packages/web3-contracts/types/WooRouterV2.d.ts @@ -0,0 +1,146 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import BN from 'bn.js' +import { ContractOptions } from 'web3-eth-contract' +import { EventLog } from 'web3-core' +import { EventEmitter } from 'events' +import { + Callback, + PayableTransactionObject, + NonPayableTransactionObject, + BlockType, + ContractEventLog, + BaseContract, +} from './types' + +interface EventOptions { + filter?: object + fromBlock?: BlockType + topics?: string[] +} + +export type OwnershipTransferred = ContractEventLog<{ + previousOwner: string + newOwner: string + 0: string + 1: string +}> +export type WooPoolChanged = ContractEventLog<{ + newPool: string + 0: string +}> +export type WooRouterSwap = ContractEventLog<{ + swapType: string + fromToken: string + toToken: string + fromAmount: string + toAmount: string + from: string + to: string + rebateTo: string + 0: string + 1: string + 2: string + 3: string + 4: string + 5: string + 6: string + 7: string +}> + +export interface WooRouterV2 extends BaseContract { + constructor(jsonInterface: any[], address?: string, options?: ContractOptions): WooRouterV2 + clone(): WooRouterV2 + methods: { + WETH(): NonPayableTransactionObject + + externalSwap( + approveTarget: string, + swapTarget: string, + fromToken: string, + toToken: string, + fromAmount: number | string | BN, + minToAmount: number | string | BN, + to: string, + data: string | number[], + ): PayableTransactionObject + + isWhitelisted(arg0: string): NonPayableTransactionObject + + owner(): NonPayableTransactionObject + + querySellBase(baseToken: string, baseAmount: number | string | BN): NonPayableTransactionObject + + querySellQuote(baseToken: string, quoteAmount: number | string | BN): NonPayableTransactionObject + + querySwap( + fromToken: string, + toToken: string, + fromAmount: number | string | BN, + ): NonPayableTransactionObject + + quoteToken(): NonPayableTransactionObject + + renounceOwnership(): NonPayableTransactionObject + + rescueFunds(token: string, amount: number | string | BN): NonPayableTransactionObject + + rescueNativeFunds(): NonPayableTransactionObject + + sellBase( + baseToken: string, + baseAmount: number | string | BN, + minQuoteAmount: number | string | BN, + to: string, + rebateTo: string, + ): NonPayableTransactionObject + + sellQuote( + baseToken: string, + quoteAmount: number | string | BN, + minBaseAmount: number | string | BN, + to: string, + rebateTo: string, + ): NonPayableTransactionObject + + setPool(newPool: string): NonPayableTransactionObject + + setWhitelisted(target: string, whitelisted: boolean): NonPayableTransactionObject + + swap( + fromToken: string, + toToken: string, + fromAmount: number | string | BN, + minToAmount: number | string | BN, + to: string, + rebateTo: string, + ): PayableTransactionObject + + transferOwnership(newOwner: string): NonPayableTransactionObject + + wooPool(): NonPayableTransactionObject + } + events: { + OwnershipTransferred(cb?: Callback): EventEmitter + OwnershipTransferred(options?: EventOptions, cb?: Callback): EventEmitter + + WooPoolChanged(cb?: Callback): EventEmitter + WooPoolChanged(options?: EventOptions, cb?: Callback): EventEmitter + + WooRouterSwap(cb?: Callback): EventEmitter + WooRouterSwap(options?: EventOptions, cb?: Callback): EventEmitter + + allEvents(options?: EventOptions, cb?: Callback): EventEmitter + } + + once(event: 'OwnershipTransferred', cb: Callback): void + once(event: 'OwnershipTransferred', options: EventOptions, cb: Callback): void + + once(event: 'WooPoolChanged', cb: Callback): void + once(event: 'WooPoolChanged', options: EventOptions, cb: Callback): void + + once(event: 'WooRouterSwap', cb: Callback): void + once(event: 'WooRouterSwap', options: EventOptions, cb: Callback): void +} From bfc2c486c37fe135194fffd8f5af82e947585a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BA=A7n=20Trung=20T=C3=ADn?= Date: Thu, 21 Apr 2022 11:16:50 +0700 Subject: [PATCH 3/4] chore: fix WooFi case --- packages/mask/src/plugins/Trader/pipes.ts | 2 +- packages/mask/src/plugins/Trader/types/trader.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/mask/src/plugins/Trader/pipes.ts b/packages/mask/src/plugins/Trader/pipes.ts index fe6d409134c0..e41a3e47b837 100644 --- a/packages/mask/src/plugins/Trader/pipes.ts +++ b/packages/mask/src/plugins/Trader/pipes.ts @@ -41,7 +41,7 @@ export const resolveTradeProviderName = createLookupTableResolver { throw new Error(`Unknown provider type: ${tradeProvider}`) diff --git a/packages/mask/src/plugins/Trader/types/trader.ts b/packages/mask/src/plugins/Trader/types/trader.ts index 32ca92f5dca4..c054c72be756 100644 --- a/packages/mask/src/plugins/Trader/types/trader.ts +++ b/packages/mask/src/plugins/Trader/types/trader.ts @@ -76,7 +76,7 @@ export enum ZrxTradePool { Trisolaris = 'Trisolaris', WannaSwap = 'WannaSwap', Mdex = 'Mdex', - Woofi = 'Woofi', + Woofi = 'WooFi', } export interface TradeComputed { From 988341dde90ca95fd8ee65f4a0e8a4c5f41c84e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BA=A7n=20Trung=20T=C3=ADn?= Date: Tue, 26 Apr 2022 12:44:23 +0700 Subject: [PATCH 4/4] chore: add cspell word --- cspell.json | 1 + 1 file changed, 1 insertion(+) diff --git a/cspell.json b/cspell.json index 19852b5cee60..f067ada59ce7 100644 --- a/cspell.json +++ b/cspell.json @@ -268,6 +268,7 @@ "withdrawed", "wmatic", "wnative", + "woofi", "xdai", "xlarge", "xlink",