Skip to content
2 changes: 2 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,8 @@
"crwdns",
"dodoapi",
"getdodoroute",
"openocean",
"OPENOCEAN",
"getx",
"hookform",
"idiv",
Expand Down
13 changes: 11 additions & 2 deletions packages/dashboard/src/pages/Labs/components/SwapSettingDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,34 @@ export default function SwapSettingDialog({ open, onClose }: SettingDialogProps)
{ label: 'Balancer', value: TradeProvider.BALANCER },
{ label: 'DODO', value: TradeProvider.DODO },
{ label: 'Bancor', value: TradeProvider.BANCOR },
{ label: 'OpenOcean', value: TradeProvider.OPENOCEAN },
]

const polygonOptions = [
{ label: 'QuickSwap', value: TradeProvider.QUICKSWAP },
{ label: 'SushiSwap', value: TradeProvider.SUSHISWAP },
{ label: '0x', value: TradeProvider.ZRX },
{ label: 'DODO', value: TradeProvider.DODO },
{ label: 'OpenOcean', value: TradeProvider.OPENOCEAN },
]

const bscOptions = [
{ label: 'PancakeSwap', value: TradeProvider.PANCAKESWAP },
{ label: 'SushiSwap', value: TradeProvider.SUSHISWAP },
{ label: '0x', value: TradeProvider.ZRX },
{ label: 'DODO', value: TradeProvider.DODO },
{ label: 'OpenOcean', value: TradeProvider.OPENOCEAN },
]

const arbitrumOptions = [{ label: 'UniSwap V3', value: TradeProvider.UNISWAP_V3 }]
const arbitrumOptions = [
{ label: 'UniSwap V3', value: TradeProvider.UNISWAP_V3 },
{ label: 'OpenOcean', value: TradeProvider.OPENOCEAN },
]

const xDaiOptions = [{ label: 'SushiSwap', value: TradeProvider.SUSHISWAP }]
const xDaiOptions = [
{ label: 'SushiSwap', value: TradeProvider.SUSHISWAP },
{ label: 'OpenOcean', value: TradeProvider.OPENOCEAN },
]
const fantomOptions = xDaiOptions

const celoOptions = [{ label: 'SushiSwap', value: TradeProvider.SUSHISWAP }]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SushiSwapIcon } from '../../../../resources/SushiSwapIcon'
import { UniswapIcon } from '../../../../resources/UniswapIcon'
import { ZRXIcon } from '../../../../resources/ZRXIcon'
import { DODOIcon } from '../../../../resources/DODOIcon'
import { OpenOceanIcon } from '../../../../resources/OpenOceanIcon'
import { resolveTradeProviderName } from '../../pipes'

const quickswapIcon = new URL('../../../../resources/quickswap.png', import.meta.url).toString()
Expand Down Expand Up @@ -59,6 +60,8 @@ export function TradeProviderIcon(props: TradeProviderIconProps) {
return <DODOIcon classes={{ root: classes.icon }} />
case TradeProvider.BANCOR:
return <BancorIcon classes={{ root: classes.icon }} />
case TradeProvider.OPENOCEAN:
return <OpenOceanIcon classes={{ root: classes.icon }} />
default:
unreachable(props.provider)
}
Expand Down
1 change: 1 addition & 0 deletions packages/mask/src/plugins/Trader/apis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './blocks'
export * from './LBP'
export * from './dodo'
export * from './bancor'
export * from './openocean'
39 changes: 39 additions & 0 deletions packages/mask/src/plugins/Trader/apis/openocean/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { SwapOOData, SwapOORequest } from '../../types/openocean'
import { OPENOCEAN_BASE_URL } from '../../constants/openocean'
import { leftShift } from '@masknet/web3-shared-base'
import urlcat from 'urlcat'

export async function swapOO(request: SwapOORequest): Promise<SwapOOData> {
const response = await fetch(
urlcat(OPENOCEAN_BASE_URL, `/${request.chainId}/swap`, {
inTokenSymbol: request.toToken?.symbol,
inTokenAddress: request.fromToken?.address,
outTokenSymbol: request.toToken?.symbol,
outTokenAddress: request.toToken?.address,
amount: request.fromAmount,
gasPrice: 5000000000,
slippage: request.slippage,
disabledDexIds: '',
account: request.userAddr,
}),
)
const payload = await response.json()
const { data, outAmount, minOutAmount, to, value } = payload
const _resAmount = leftShift(outAmount, request.toToken.decimals).toNumber()
const _fromAmount = leftShift(request.fromAmount, request.fromToken.decimals).toNumber()
return {
data,
targetApproveAddr: request.fromToken.address,
targetDecimals: request.fromToken.decimals,
resAmount: _resAmount,
fromAmount: _fromAmount,
resPricePerFromToken: +(_fromAmount / _resAmount).toFixed(8),
resPricePerToToken: +(_resAmount / _fromAmount).toFixed(8),
to,
value,
slippage: request.slippage,
fromTokenSymbol: request.fromToken.symbol,
toTokenSymbol: request.toToken.symbol,
minOutAmount,
} as SwapOOData
Comment thread
cformcn marked this conversation as resolved.
}
14 changes: 11 additions & 3 deletions packages/mask/src/plugins/Trader/apis/trader/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export async function getAvailableTraderProviders(chainId: ChainId) {
TradeProvider.BALANCER,
TradeProvider.DODO,
TradeProvider.BANCOR,
TradeProvider.OPENOCEAN,
]
case NetworkType.Polygon:
return [
Expand All @@ -24,13 +25,20 @@ export async function getAvailableTraderProviders(chainId: ChainId) {
TradeProvider.SUSHISWAP,
TradeProvider.DODO,
TradeProvider.ZRX,
TradeProvider.OPENOCEAN,
]
case NetworkType.Binance:
return [TradeProvider.PANCAKESWAP, TradeProvider.SUSHISWAP, TradeProvider.DODO, TradeProvider.ZRX]
return [
TradeProvider.PANCAKESWAP,
TradeProvider.SUSHISWAP,
TradeProvider.DODO,
TradeProvider.ZRX,
TradeProvider.OPENOCEAN,
]
case NetworkType.Arbitrum:
return [TradeProvider.UNISWAP_V3]
return [TradeProvider.UNISWAP_V3, TradeProvider.OPENOCEAN]
case NetworkType.xDai:
return [TradeProvider.SUSHISWAP]
return [TradeProvider.SUSHISWAP, TradeProvider.OPENOCEAN]
case NetworkType.Celo:
return [TradeProvider.SUSHISWAP]
case NetworkType.Fantom:
Expand Down
13 changes: 13 additions & 0 deletions packages/mask/src/plugins/Trader/constants/openocean.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { NetworkType } from '@masknet/web3-shared-evm'

export const OPENOCEAN_BASE_URL = 'https://ethapi.openocean.finance/v2/'

export const networkNames: Record<NetworkType, string> = {
Comment thread
guanbinrui marked this conversation as resolved.
[NetworkType.Ethereum]: 'eth',
[NetworkType.Binance]: 'bsc',
[NetworkType.Polygon]: 'polygon',
[NetworkType.Arbitrum]: 'arbitrum',
[NetworkType.xDai]: 'xdai',
[NetworkType.Celo]: 'celo',
[NetworkType.Fantom]: 'fantom',
}
6 changes: 6 additions & 0 deletions packages/mask/src/plugins/Trader/pipes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const resolveTradeProviderName = createLookupTableResolver<TradeProvider,
[TradeProvider.PANCAKESWAP]: 'PancakeSwap',
[TradeProvider.DODO]: 'DODO',
[TradeProvider.BANCOR]: 'Bancor',
[TradeProvider.OPENOCEAN]: 'OpenOcean',
},
(tradeProvider) => {
throw new Error(`Unknown provider type: ${tradeProvider}`)
Expand Down Expand Up @@ -102,6 +103,8 @@ export function resolveTradeProviderLink(tradeProvider: TradeProvider, networkTy
return 'https://app.dodoex.io'
case TradeProvider.BANCOR:
return 'https://app.bancor.network/eth/swap'
case TradeProvider.OPENOCEAN:
return 'https://openocean.finance/classic'
default:
unreachable(tradeProvider)
}
Expand Down Expand Up @@ -157,6 +160,9 @@ export function resolveTradePairLink(tradeProvider: TradeProvider, address: stri
case TradeProvider.BANCOR:
// TODO - Bancor analytics should be available with V3
return ``
case TradeProvider.OPENOCEAN:
// TODO - OpenOcean
return ``
default:
unreachable(tradeProvider)
}
Expand Down
3 changes: 3 additions & 0 deletions packages/mask/src/plugins/Trader/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const pancakeswapSettings = createInternalSettings<string>(`${PLUGIN_ID}+tradePr
const balancerSettings = createInternalSettings<string>(`${PLUGIN_ID}+tradeProvider+balancer`, '')
const dodoSettings = createInternalSettings<string>(`${PLUGIN_ID}+tradeProvider+dodo`, '')
const bancorSettings = createInternalSettings<string>(`${PLUGIN_ID}+tradeProvider+bancor`, '')
const openoceanSettings = createInternalSettings<string>(`${PLUGIN_ID}+tradeProvider+openocean`, '')

/**
* The general settings of specific tarde provider
Expand All @@ -129,6 +130,8 @@ export function getCurrentTradeProviderGeneralSettings(tradeProvider: TradeProvi
return dodoSettings
case TradeProvider.BANCOR:
return bancorSettings
case TradeProvider.OPENOCEAN:
return openoceanSettings
default:
unreachable(tradeProvider)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import type { AsyncState } from 'react-use/lib/useAsyncFn'

export function useTradeGasLimit(tradeComputed: TradeComputed<SwapRouteData> | null): AsyncState<number> {
const { targetChainId } = TargetChainIdContext.useContainer()

const web3 = useWeb3(false, targetChainId)
const web3 = useWeb3(true, targetChainId)
const account = useAccount()
const config = useMemo(() => {
if (!account || !tradeComputed?.trade_) return null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { clamp } from 'lodash-unified'
import { useValueRef } from '@masknet/shared'
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)
}
65 changes: 65 additions & 0 deletions packages/mask/src/plugins/Trader/trader/openocean/useTrade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
FungibleTokenDetailed,
isNativeTokenAddress,
useAccount,
useBlockNumber,
useRPCConstants,
useTokenConstants,
useTraderConstants,
} from '@masknet/web3-shared-evm'
import { useAsyncRetry } from 'react-use'
import { PluginTraderRPC } from '../../messages'
import type { TradeStrategy } from '../../types'
import { TargetChainIdContext } from '../useTargetChainIdContext'
import { useSlippageTolerance } from './useSlippageTolerance'
import { first } from 'lodash-unified'

export function useTrade(
strategy: TradeStrategy,
inputAmount: string,
outputAmount: string,
inputToken?: FungibleTokenDetailed,
outputToken?: FungibleTokenDetailed,
) {
const { NATIVE_TOKEN_ADDRESS } = useTokenConstants()
const blockNumber = useBlockNumber()
const slippage = useSlippageTolerance()
const { targetChainId } = TargetChainIdContext.useContainer()
const { RPC } = useRPCConstants(targetChainId)
const providerURL = first(RPC)
const { OPENOCEAN_ETH_ADDRESS } = useTraderConstants(targetChainId)
const account = useAccount()

return useAsyncRetry(async () => {
if (!inputToken || !outputToken) return null
if (inputAmount === '0') return null
const sellToken = isNativeTokenAddress(inputToken.address)
? { ...inputToken, address: OPENOCEAN_ETH_ADDRESS ?? '' }
: inputToken
const buyToken = isNativeTokenAddress(outputToken.address)
? { ...outputToken, address: OPENOCEAN_ETH_ADDRESS ?? '' }
: outputToken
return PluginTraderRPC.swapOO({
isNativeSellToken: isNativeTokenAddress(inputToken.address),
fromToken: sellToken,
toToken: buyToken,
fromAmount: inputAmount,
slippage,
userAddr: account,
rpc: providerURL,
chainId: targetChainId,
})
}, [
NATIVE_TOKEN_ADDRESS,
strategy,
inputAmount,
outputAmount,
inputToken?.address,
outputToken?.address,
slippage,
blockNumber, // refresh api each block
account,
providerURL,
targetChainId,
])
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { useCallback, useMemo, useState } from 'react'
import stringify from 'json-stable-stringify'
import { pick } from 'lodash-unified'
import type { TransactionConfig } from 'web3-core'
import {
TransactionState,
TransactionStateType,
useAccount,
useChainId,
useWeb3,
GasOptionConfig,
} from '@masknet/web3-shared-evm'
import type { SwapOOSuccessResponse, TradeComputed } from '../../types'

export function useTradeCallback(
tradeComputed: TradeComputed<SwapOOSuccessResponse> | null,
gasConfig?: GasOptionConfig,
) {
const web3 = useWeb3()
const account = useAccount()
const chainId = useChainId()
const [tradeState, setTradeState] = useState<TransactionState>({
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 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<string>((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)])

const resetCallback = useCallback(() => {
setTradeState({
type: TransactionStateType.UNKNOWN,
})
}, [])

return [tradeState, tradeCallback, resetCallback] as const
}
Loading