Commit 5ff2cff8 authored by Noah Zinsmeister's avatar Noah Zinsmeister Committed by GitHub

feat: Add Optimism (#1923)

* preliminary support

* fix multicall abi

add explorer support

* Fix code style issues with ESLint

* fix gas estimation

* fix todo tests

* fix gas estimation comments

* anonymize links to kovan etherscan and poll more frequently on optimistic kovan

* separate pool creation

* remove supported chain id

* remove the blocktag

* do not use the blocktag on optimism only

* give more gas to tokenURI for optimism

* update sdk

* temp fix to the block tag thing (do not update the block number from the fetch block number)

* remove unused import

* gasRequired -> multi-network

* bump quoter gas limit to 6m on optimism

* move the gas required parameter by chain id one level up

* missed a hook

* retry fetching receipts for optimism as many times as arbitrum

* fix duplicate enum, add optimism as well to retry options config

* fix: state not getting updated after a transaction confirmation

* bump sdk version

* update for mainnet optimism (#1998)

* fix for calculateGasMargin on optimism
Co-authored-by: default avatarLint Action <lint-action@samuelmeuli.com>
Co-authored-by: default avatarMoody Salem <moody.salem@gmail.com>
parent 0c096886
......@@ -27,10 +27,10 @@ const NETWORK_URLS: {
[SupportedChainId.ROPSTEN]: `https://ropsten.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.GOERLI]: `https://goerli.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.KOVAN]: `https://kovan.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.OPTIMISM]: `https://optimism-mainnet.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.OPTIMISTIC_KOVAN]: `https://optimism-kovan.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.ARBITRUM_ONE]: `https://arb1.arbitrum.io/rpc`,
[SupportedChainId.ARBITRUM_RINKEBY]: `https://rinkeby.arbitrum.io/rpc`,
[SupportedChainId.OPTIMISM]: `https://mainnet.optimism.io`,
[SupportedChainId.OPTIMISTIC_KOVAN]: `https://kovan.optimism.io`,
}
const SUPPORTED_CHAIN_IDS: SupportedChainId[] = [
......@@ -39,10 +39,10 @@ const SUPPORTED_CHAIN_IDS: SupportedChainId[] = [
SupportedChainId.GOERLI,
SupportedChainId.RINKEBY,
SupportedChainId.ROPSTEN,
SupportedChainId.ARBITRUM_ONE,
SupportedChainId.ARBITRUM_RINKEBY,
SupportedChainId.OPTIMISM,
SupportedChainId.OPTIMISTIC_KOVAN,
SupportedChainId.ARBITRUM_ONE,
SupportedChainId.ARBITRUM_RINKEBY,
]
export const network = new NetworkConnector({
......
......@@ -7,9 +7,12 @@ type AddressMap = { [chainId: number]: string }
export const UNI_ADDRESS: AddressMap = constructSameAddressMap('0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984')
export const MULTICALL_ADDRESS: AddressMap = {
...constructSameAddressMap('0x1F98415757620B543A52E61c46B32eB19261F984'),
[SupportedChainId.ARBITRUM_RINKEBY]: '0xa501c031958F579dB7676fF1CE78AD305794d579',
...constructSameAddressMap('0x1F98415757620B543A52E61c46B32eB19261F984', [
SupportedChainId.OPTIMISM,
SupportedChainId.OPTIMISTIC_KOVAN,
]),
[SupportedChainId.ARBITRUM_ONE]: '0xadF885960B47eA2CD9B55E6DAc6B42b7Cb2806dB',
[SupportedChainId.ARBITRUM_RINKEBY]: '0xa501c031958F579dB7676fF1CE78AD305794d579',
}
export const V2_FACTORY_ADDRESSES: AddressMap = constructSameAddressMap(V2_FACTORY_ADDRESS)
export const V2_ROUTER_ADDRESS: AddressMap = constructSameAddressMap('0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D')
......@@ -36,16 +39,25 @@ export const ARGENT_WALLET_DETECTOR_ADDRESS: AddressMap = {
[SupportedChainId.MAINNET]: '0xeca4B0bDBf7c55E9b7925919d03CbF8Dc82537E8',
}
export const V3_CORE_FACTORY_ADDRESSES: AddressMap = constructSameAddressMap(V3_FACTORY_ADDRESS, [
SupportedChainId.OPTIMISM,
SupportedChainId.OPTIMISTIC_KOVAN,
SupportedChainId.ARBITRUM_ONE,
SupportedChainId.ARBITRUM_RINKEBY,
])
export const QUOTER_ADDRESSES: AddressMap = constructSameAddressMap('0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6', [
SupportedChainId.OPTIMISM,
SupportedChainId.OPTIMISTIC_KOVAN,
SupportedChainId.ARBITRUM_ONE,
SupportedChainId.ARBITRUM_RINKEBY,
])
export const NONFUNGIBLE_POSITION_MANAGER_ADDRESSES: AddressMap = constructSameAddressMap(
'0xC36442b4a4522E871399CD717aBDD847Ab11FE88',
[SupportedChainId.ARBITRUM_ONE, SupportedChainId.ARBITRUM_RINKEBY]
[
SupportedChainId.OPTIMISM,
SupportedChainId.OPTIMISTIC_KOVAN,
SupportedChainId.ARBITRUM_ONE,
SupportedChainId.ARBITRUM_RINKEBY,
]
)
export const ENS_REGISTRAR_ADDRESSES: AddressMap = {
[SupportedChainId.MAINNET]: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
......@@ -57,6 +69,8 @@ export const SOCKS_CONTROLLER_ADDRESSES: AddressMap = {
[SupportedChainId.MAINNET]: '0x65770b5283117639760beA3F867b69b3697a91dd',
}
export const SWAP_ROUTER_ADDRESSES: AddressMap = constructSameAddressMap('0xE592427A0AEce92De3Edee1F18E0157C05861564', [
SupportedChainId.OPTIMISM,
SupportedChainId.OPTIMISTIC_KOVAN,
SupportedChainId.ARBITRUM_ONE,
SupportedChainId.ARBITRUM_RINKEBY,
])
......
......@@ -118,30 +118,30 @@ export const UNI: { [chainId: number]: Token } = {
export const WETH9_EXTENDED: { [chainId: number]: Token } = {
...WETH9,
[SupportedChainId.ARBITRUM_ONE]: new Token(
SupportedChainId.ARBITRUM_ONE,
'0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
[SupportedChainId.OPTIMISM]: new Token(
SupportedChainId.OPTIMISM,
'0x4200000000000000000000000000000000000006',
18,
'WETH',
'Wrapped Ether'
),
[SupportedChainId.ARBITRUM_RINKEBY]: new Token(
SupportedChainId.ARBITRUM_RINKEBY,
'0xB47e6A5f8b33b3F17603C83a0535A9dcD7E32681',
[SupportedChainId.OPTIMISTIC_KOVAN]: new Token(
SupportedChainId.OPTIMISTIC_KOVAN,
'0x4200000000000000000000000000000000000006',
18,
'WETH',
'Wrapped Ether'
),
[SupportedChainId.OPTIMISM]: new Token(
SupportedChainId.OPTIMISM,
'0x4200000000000000000000000000000000000006',
[SupportedChainId.ARBITRUM_ONE]: new Token(
SupportedChainId.ARBITRUM_ONE,
'0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
18,
'WETH',
'Wrapped Ether'
),
[SupportedChainId.OPTIMISTIC_KOVAN]: new Token(
SupportedChainId.OPTIMISTIC_KOVAN,
'0x4200000000000000000000000000000000000006',
[SupportedChainId.ARBITRUM_RINKEBY]: new Token(
SupportedChainId.ARBITRUM_RINKEBY,
'0xB47e6A5f8b33b3F17603C83a0535A9dcD7E32681',
18,
'WETH',
'Wrapped Ether'
......
......@@ -23,7 +23,7 @@ export function useApproveCallback(
amountToApprove?: CurrencyAmount<Currency>,
spender?: string
): [ApprovalState, () => Promise<void>] {
const { account } = useActiveWeb3React()
const { account, chainId } = useActiveWeb3React()
const token = amountToApprove?.currency?.isToken ? amountToApprove.currency : undefined
const currentAllowance = useTokenAllowance(token, account ?? undefined, spender)
const pendingApproval = useHasPendingApproval(token?.address, spender)
......@@ -51,6 +51,11 @@ export function useApproveCallback(
console.error('approve was called unnecessarily')
return
}
if (!chainId) {
console.error('no chainId')
return
}
if (!token) {
console.error('no token')
return
......@@ -80,7 +85,7 @@ export function useApproveCallback(
return tokenContract
.approve(spender, useExact ? amountToApprove.quotient.toString() : MaxUint256, {
gasLimit: calculateGasMargin(estimatedGas),
gasLimit: calculateGasMargin(chainId, estimatedGas),
})
.then((response: TransactionResponse) => {
addTransaction(response, {
......@@ -92,7 +97,7 @@ export function useApproveCallback(
console.debug('Failed to approve token', error)
throw error
})
}, [approvalState, token, tokenContract, amountToApprove, spender, addTransaction])
}, [approvalState, token, tokenContract, amountToApprove, spender, addTransaction, chainId])
return [approvalState, approve]
}
......
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { encodeRouteToPath, Route, Trade } from '@uniswap/v3-sdk'
import { SupportedChainId } from 'constants/chains'
import { BigNumber } from 'ethers'
import { useMemo } from 'react'
import { useSingleContractMultipleData } from '../state/multicall/hooks'
import { useAllV3Routes } from './useAllV3Routes'
import { useV3Quoter } from './useContract'
import { useActiveWeb3React } from './web3'
export enum V3TradeState {
LOADING,
......@@ -14,6 +16,11 @@ export enum V3TradeState {
SYNCING,
}
const QUOTE_GAS_OVERRIDES: { [chainId: number]: number } = {
[SupportedChainId.OPTIMISM]: 6_000_000,
[SupportedChainId.OPTIMISTIC_KOVAN]: 6_000_000,
}
/**
* Returns the best v3 trade for a desired exact input swap
* @param amountIn the amount to swap in
......@@ -23,6 +30,7 @@ export function useBestV3TradeExactIn(
amountIn?: CurrencyAmount<Currency>,
currencyOut?: Currency
): { state: V3TradeState; trade: Trade<Currency, Currency, TradeType.EXACT_INPUT> | null } {
const { chainId } = useActiveWeb3React()
const quoter = useV3Quoter()
const { routes, loading: routesLoading } = useAllV3Routes(amountIn?.currency, currencyOut)
......@@ -33,7 +41,9 @@ export function useBestV3TradeExactIn(
])
}, [amountIn, routes])
const quotesResults = useSingleContractMultipleData(quoter, 'quoteExactInput', quoteExactInInputs)
const quotesResults = useSingleContractMultipleData(quoter, 'quoteExactInput', quoteExactInInputs, {
gasRequired: chainId ? QUOTE_GAS_OVERRIDES[chainId] ?? 1_000_000 : undefined,
})
return useMemo(() => {
if (!amountIn || !currencyOut) {
......@@ -104,6 +114,7 @@ export function useBestV3TradeExactOut(
currencyIn?: Currency,
amountOut?: CurrencyAmount<Currency>
): { state: V3TradeState; trade: Trade<Currency, Currency, TradeType.EXACT_OUTPUT> | null } {
const { chainId } = useActiveWeb3React()
const quoter = useV3Quoter()
const { routes, loading: routesLoading } = useAllV3Routes(currencyIn, amountOut?.currency)
......@@ -114,7 +125,9 @@ export function useBestV3TradeExactOut(
])
}, [amountOut, routes])
const quotesResults = useSingleContractMultipleData(quoter, 'quoteExactOutput', quoteExactOutInputs)
const quotesResults = useSingleContractMultipleData(quoter, 'quoteExactOutput', quoteExactOutInputs, {
gasRequired: chainId ? QUOTE_GAS_OVERRIDES[chainId] ?? 1_000_000 : undefined,
})
return useMemo(() => {
if (!amountOut || !currencyIn || quotesResults.some(({ valid }) => !valid)) {
......
......@@ -33,7 +33,10 @@ export function usePositionTokenURI(tokenId: TokenId | undefined): UsePositionTo
() => [tokenId instanceof BigNumber ? tokenId.toHexString() : tokenId?.toString(16)],
[tokenId]
)
const { result, error, loading, valid } = useSingleCallResult(contract, 'tokenURI', inputs, NEVER_RELOAD, 3_000_000)
const { result, error, loading, valid } = useSingleCallResult(contract, 'tokenURI', inputs, {
...NEVER_RELOAD,
gasRequired: 3_000_000,
})
return useMemo(() => {
if (error || !valid || !tokenId) {
......
......@@ -328,7 +328,9 @@ export function useSwapCallback(
to: address,
data: calldata,
// let the wallet try if we can't estimate the gas
...('gasEstimate' in bestCallOption ? { gasLimit: calculateGasMargin(bestCallOption.gasEstimate) } : {}),
...('gasEstimate' in bestCallOption
? { gasLimit: calculateGasMargin(chainId, bestCallOption.gasEstimate) }
: {}),
...(value && !isZero(value) ? { value } : {}),
})
.then((response) => {
......
......@@ -56,6 +56,7 @@ import { BigNumber } from '@ethersproject/bignumber'
import { AddRemoveTabs } from 'components/NavigationTabs'
import HoverInlineText from 'components/HoverInlineText'
import { SwitchLocaleLink } from 'components/SwitchLocaleLink'
import { SupportedChainId } from 'constants/chains'
const DEFAULT_ADD_IN_RANGE_SLIPPAGE_TOLERANCE = new Percent(50, 10_000)
......@@ -187,6 +188,63 @@ export default function AddLiquidity({
outOfRange ? ZERO_PERCENT : DEFAULT_ADD_IN_RANGE_SLIPPAGE_TOLERANCE
)
// only called on optimism, atm
async function onCreate() {
if (!chainId || !library) return
if (!positionManager || !currencyA || !currencyB) {
return
}
if (position && account && deadline) {
const { calldata, value } = NonfungiblePositionManager.createCallParameters(position.pool)
const txn: { to: string; data: string; value: string } = {
to: NONFUNGIBLE_POSITION_MANAGER_ADDRESSES[chainId],
data: calldata,
value,
}
setAttemptingTxn(true)
library
.getSigner()
.estimateGas(txn)
.then((estimate) => {
const newTxn = {
...txn,
gasLimit: calculateGasMargin(chainId, estimate),
}
return library
.getSigner()
.sendTransaction(newTxn)
.then((response: TransactionResponse) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: t`Create ${currencyA?.symbol}/${currencyB?.symbol} V3 pool`,
})
setTxHash(response.hash)
ReactGA.event({
category: 'Liquidity',
action: 'Create',
label: [currencies[Field.CURRENCY_A]?.symbol, currencies[Field.CURRENCY_B]?.symbol].join('/'),
})
})
})
.catch((error) => {
console.error('Failed to send transaction', error)
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (error?.code !== 4001) {
console.error(error)
}
})
} else {
return
}
}
async function onAdd() {
if (!chainId || !library || !account) return
......@@ -250,7 +308,7 @@ export default function AddLiquidity({
.then((estimate) => {
const newTxn = {
...txn,
gasLimit: calculateGasMargin(estimate),
gasLimit: calculateGasMargin(chainId, estimate),
}
return library
......@@ -385,6 +443,10 @@ export default function AddLiquidity({
const showApprovalB =
!argentWalletContract && approvalB !== ApprovalState.APPROVED && !!parsedAmounts[Field.CURRENCY_B]
// flag for whether pool creation must be a separate tx
const mustCreateSeparately =
noLiquidity && (chainId === SupportedChainId.OPTIMISM || chainId === SupportedChainId.OPTIMISTIC_KOVAN)
return (
<>
<ScrollablePage>
......@@ -755,11 +817,19 @@ export default function AddLiquidity({
)}
</RowBetween>
)}
{mustCreateSeparately && (
<ButtonError onClick={onCreate}>
<Text fontWeight={500}>
<Trans>Create</Trans>
</Text>
</ButtonError>
)}
<ButtonError
onClick={() => {
expertMode ? onAdd() : setShowConfirm(true)
}}
disabled={
mustCreateSeparately ||
!isValid ||
(!argentWalletContract && approvalA !== ApprovalState.APPROVED && !depositADisabled) ||
(!argentWalletContract && approvalB !== ApprovalState.APPROVED && !depositBDisabled)
......
......@@ -184,7 +184,7 @@ export default function AddLiquidity({
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
gasLimit: calculateGasMargin(chainId, estimatedGasLimit),
}).then((response) => {
setAttemptingTxn(false)
......
......@@ -270,7 +270,8 @@ function V2PairMigration({
typeof tickLower !== 'number' ||
typeof tickUpper !== 'number' ||
!v3Amount0Min ||
!v3Amount1Min
!v3Amount1Min ||
!chainId
)
return
......@@ -331,7 +332,7 @@ function V2PairMigration({
.multicall(data)
.then((gasEstimate) => {
return migrator
.multicall(data, { gasLimit: calculateGasMargin(gasEstimate) })
.multicall(data, { gasLimit: calculateGasMargin(chainId, gasEstimate) })
.then((response: TransactionResponse) => {
ReactGA.event({
category: 'Migrate',
......@@ -349,6 +350,7 @@ function V2PairMigration({
setConfirmingMigration(false)
})
}, [
chainId,
isNotUniswap,
migrator,
noLiquidity,
......
......@@ -384,7 +384,7 @@ export function PositionPage({
.then((estimate) => {
const newTxn = {
...txn,
gasLimit: calculateGasMargin(estimate),
gasLimit: calculateGasMargin(chainId, estimate),
}
return library
......
......@@ -136,7 +136,7 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
.then((estimate) => {
const newTxn = {
...txn,
gasLimit: calculateGasMargin(estimate),
gasLimit: calculateGasMargin(chainId, estimate),
}
return library
......
......@@ -240,7 +240,7 @@ export default function RemoveLiquidity({
const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
methodNames.map((methodName) =>
router.estimateGas[methodName](...args)
.then(calculateGasMargin)
.then((estimateGas) => calculateGasMargin(chainId, estimateGas))
.catch((error) => {
console.error(`estimateGas failed`, methodName, args, error)
return undefined
......
......@@ -162,7 +162,7 @@ export function useClaimCallback(account: string | null | undefined): {
return distributorContract.estimateGas['claim'](...args, {}).then((estimatedGasLimit) => {
return distributorContract
.claim(...args, { value: null, gasLimit: calculateGasMargin(estimatedGasLimit) })
.claim(...args, { value: null, gasLimit: calculateGasMargin(chainId, estimatedGasLimit) })
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: `Claimed ${unclaimedAmount?.toSignificant(4)} UNI`,
......
......@@ -240,7 +240,7 @@ export function useDelegateCallback(): (delegatee: string | undefined) => undefi
if (!uniContract) throw new Error('No UNI Contract!')
return uniContract.estimateGas.delegate(...args, {}).then((estimatedGasLimit) => {
return uniContract
.delegate(...args, { value: null, gasLimit: calculateGasMargin(estimatedGasLimit) })
.delegate(...args, { value: null, gasLimit: calculateGasMargin(chainId, estimatedGasLimit) })
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: t`Delegated votes`,
......@@ -256,7 +256,7 @@ export function useDelegateCallback(): (delegatee: string | undefined) => undefi
export function useVoteCallback(): {
voteCallback: (proposalId: string | undefined, support: boolean) => undefined | Promise<string>
} {
const { account } = useActiveWeb3React()
const { account, chainId } = useActiveWeb3React()
const latestGovernanceContract = useLatestGovernanceContract()
......@@ -264,11 +264,11 @@ export function useVoteCallback(): {
const voteCallback = useCallback(
(proposalId: string | undefined, support: boolean) => {
if (!account || !latestGovernanceContract || !proposalId) return
if (!account || !latestGovernanceContract || !proposalId || !chainId) return
const args = [proposalId, support]
return latestGovernanceContract.estimateGas.castVote(...args, {}).then((estimatedGasLimit) => {
return latestGovernanceContract
.castVote(...args, { value: null, gasLimit: calculateGasMargin(estimatedGasLimit) })
.castVote(...args, { value: null, gasLimit: calculateGasMargin(chainId, estimatedGasLimit) })
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: `Voted ${support ? 'for ' : 'against'} proposal ${proposalId}`,
......@@ -277,7 +277,7 @@ export function useVoteCallback(): {
})
})
},
[account, addTransaction, latestGovernanceContract]
[account, addTransaction, latestGovernanceContract, chainId]
)
return { voteCallback }
}
......@@ -285,14 +285,14 @@ export function useVoteCallback(): {
export function useCreateProposalCallback(): (
createProposalData: CreateProposalData | undefined
) => undefined | Promise<string> {
const { account } = useActiveWeb3React()
const { account, chainId } = useActiveWeb3React()
const latestGovernanceContract = useLatestGovernanceContract()
const addTransaction = useTransactionAdder()
return useCallback(
(createProposalData: CreateProposalData | undefined) => {
if (!account || !latestGovernanceContract || !createProposalData) return undefined
if (!account || !latestGovernanceContract || !createProposalData || !chainId) return undefined
const args = [
createProposalData.targets,
......@@ -304,7 +304,7 @@ export function useCreateProposalCallback(): (
return latestGovernanceContract.estimateGas.propose(...args).then((estimatedGasLimit) => {
return latestGovernanceContract
.propose(...args, { gasLimit: calculateGasMargin(estimatedGasLimit) })
.propose(...args, { gasLimit: calculateGasMargin(chainId, estimatedGasLimit) })
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: t`Submitted new proposal`,
......@@ -313,7 +313,7 @@ export function useCreateProposalCallback(): (
})
})
},
[account, addTransaction, latestGovernanceContract]
[account, addTransaction, latestGovernanceContract, chainId]
)
}
......
......@@ -158,11 +158,13 @@ export function useSingleContractMultipleData(
contract: Contract | null | undefined,
methodName: string,
callInputs: OptionalMethodInputs[],
options?: ListenerOptions,
gasRequired?: number
options: Partial<ListenerOptions> & { gasRequired?: number } = {}
): CallState[] {
const fragment = useMemo(() => contract?.interface?.getFunction(methodName), [contract, methodName])
const blocksPerFetch = options?.blocksPerFetch
const gasRequired = options?.gasRequired
const calls = useMemo(
() =>
contract && fragment && callInputs?.length > 0 && callInputs.every((inputs) => isValidMethodArgs(inputs))
......@@ -177,7 +179,7 @@ export function useSingleContractMultipleData(
[contract, fragment, callInputs, gasRequired]
)
const results = useCallsData(calls, options)
const results = useCallsData(calls, blocksPerFetch ? { blocksPerFetch } : undefined)
const latestBlockNumber = useBlockNumber()
......@@ -191,10 +193,13 @@ export function useMultipleContractSingleData(
contractInterface: Interface,
methodName: string,
callInputs?: OptionalMethodInputs,
options?: ListenerOptions,
gasRequired?: number
options?: Partial<ListenerOptions> & { gasRequired?: number }
): CallState[] {
const fragment = useMemo(() => contractInterface.getFunction(methodName), [contractInterface, methodName])
const blocksPerFetch = options?.blocksPerFetch
const gasRequired = options?.gasRequired
const callData: string | undefined = useMemo(
() =>
fragment && isValidMethodArgs(callInputs)
......@@ -219,7 +224,7 @@ export function useMultipleContractSingleData(
[addresses, callData, fragment, gasRequired]
)
const results = useCallsData(calls, options)
const results = useCallsData(calls, blocksPerFetch ? { blocksPerFetch } : undefined)
const latestBlockNumber = useBlockNumber()
......@@ -232,11 +237,13 @@ export function useSingleCallResult(
contract: Contract | null | undefined,
methodName: string,
inputs?: OptionalMethodInputs,
options?: ListenerOptions,
gasRequired?: number
options?: Partial<ListenerOptions> & { gasRequired?: number }
): CallState {
const fragment = useMemo(() => contract?.interface?.getFunction(methodName), [contract, methodName])
const blocksPerFetch = options?.blocksPerFetch
const gasRequired = options?.gasRequired
const calls = useMemo<Call[]>(() => {
return contract && fragment && isValidMethodArgs(inputs)
? [
......@@ -249,7 +256,7 @@ export function useSingleCallResult(
: []
}, [contract, fragment, inputs, gasRequired])
const result = useCallsData(calls, options)[0]
const result = useCallsData(calls, blocksPerFetch ? { blocksPerFetch } : undefined)[0]
const latestBlockNumber = useBlockNumber()
return useMemo(() => {
......
......@@ -4,7 +4,6 @@ import { useMulticall2Contract } from '../../hooks/useContract'
import useDebounce from '../../hooks/useDebounce'
import chunkArray from '../../utils/chunkArray'
import { retry, RetryableError } from '../../utils/retry'
import { updateBlockNumber } from '../application/actions'
import { useBlockNumber } from '../application/hooks'
import { AppState } from '../index'
import { errorFetchingMulticallResults, fetchingMulticallResults, updateMulticallResults } from './actions'
......@@ -26,6 +25,8 @@ async function fetchChunk(
minBlockNumber: number
): Promise<{
results: { success: boolean; returnData: string }[]
// note we are not using this returned block number because layer 2 multicalls do not consistently return the L2 block number
// instead we are relying on the blockTag parameter and assume the returned data is at least as new as that block number
blockNumber: number
}> {
console.debug('Fetching chunk', chunk, minBlockNumber)
......@@ -189,7 +190,7 @@ export default function Updater(): null {
maxWait: 2500,
})
promise
.then(({ results: returnData, blockNumber: fetchBlockNumber }) => {
.then(({ results: returnData }) => {
// accumulates the length of all previous indices
const firstCallKeyIndex = chunkedCalls.slice(0, index).reduce<number>((memo, curr) => memo + curr.length, 0)
const lastCallKeyIndex = firstCallKeyIndex + returnData.length
......@@ -218,7 +219,7 @@ export default function Updater(): null {
updateMulticallResults({
chainId,
results,
blockNumber: fetchBlockNumber,
blockNumber: latestBlockNumber,
})
)
......@@ -229,14 +230,10 @@ export default function Updater(): null {
errorFetchingMulticallResults({
calls: erroredCalls,
chainId,
fetchingBlockNumber: fetchBlockNumber,
fetchingBlockNumber: latestBlockNumber,
})
)
}
if (fetchBlockNumber > latestBlockNumber) {
dispatch(updateBlockNumber({ chainId, blockNumber: fetchBlockNumber }))
}
})
.catch((error: any) => {
if (error.isCancelledError) {
......
......@@ -33,8 +33,11 @@ export function shouldCheck(lastBlockNumber: number, tx: TxInterface): boolean {
const RETRY_OPTIONS_BY_CHAIN_ID: { [chainId: number]: RetryOptions } = {
[SupportedChainId.ARBITRUM_ONE]: { n: 10, minWait: 250, maxWait: 1000 },
[SupportedChainId.ARBITRUM_RINKEBY]: { n: 10, minWait: 250, maxWait: 1000 },
[SupportedChainId.OPTIMISTIC_KOVAN]: { n: 10, minWait: 250, maxWait: 1000 },
[SupportedChainId.OPTIMISM]: { n: 10, minWait: 250, maxWait: 1000 },
}
const DEFAULT_RETRY_OPTIONS: RetryOptions = { n: 3, minWait: 1000, maxWait: 3000 }
const DEFAULT_RETRY_OPTIONS: RetryOptions = { n: 1, minWait: 0, maxWait: 0 }
export default function Updater(): null {
const { chainId, library } = useActiveWeb3React()
......
......@@ -12,6 +12,7 @@ import { useTotalUniEarned } from '../stake/hooks'
import { Interface } from '@ethersproject/abi'
import ERC20ABI from 'abis/erc20.json'
import { Erc20Interface } from 'abis/types/Erc20'
import { SupportedChainId } from 'constants/chains'
/**
* Returns a map of the given addresses to their eventually consistent ETH balances.
*/
......@@ -50,6 +51,11 @@ export function useETHBalances(uncheckedAddresses?: (string | undefined)[]): {
)
}
const TOKEN_BALANCE_GAS_OVERRIDE: { [chainId: number]: number } = {
[SupportedChainId.OPTIMISM]: 250_000,
[SupportedChainId.OPTIMISTIC_KOVAN]: 250_000,
}
/**
* Returns a map of token addresses to their eventually consistent token balances for a single account.
*/
......@@ -62,16 +68,13 @@ export function useTokenBalancesWithLoadingIndicator(
[tokens]
)
const { chainId } = useActiveWeb3React()
const validatedTokenAddresses = useMemo(() => validatedTokens.map((vt) => vt.address), [validatedTokens])
const ERC20Interface = new Interface(ERC20ABI) as Erc20Interface
const balances = useMultipleContractSingleData(
validatedTokenAddresses,
ERC20Interface,
'balanceOf',
[address],
undefined,
100_000
)
const balances = useMultipleContractSingleData(validatedTokenAddresses, ERC20Interface, 'balanceOf', [address], {
gasRequired: (chainId && TOKEN_BALANCE_GAS_OVERRIDE[chainId]) ?? 100_000,
})
const anyLoading: boolean = useMemo(() => balances.some((callState) => callState.loading), [balances])
......
......@@ -4,6 +4,8 @@ const EXPLORER_HOSTNAMES: { [hostname: string]: true } = {
'rinkeby.etherscan.io': true,
'kovan.etherscan.io': true,
'goerli.etherscan.io': true,
'optimistic.etherscan.io': true,
'kovan-optimistic.etherscan.io': true,
'rinkeby-explorer.arbitrum.io': true,
'explorer.arbitrum.io': true,
}
......
......@@ -3,7 +3,12 @@ import { calculateGasMargin } from './calculateGasMargin'
describe('#calculateGasMargin', () => {
it('adds 20%', () => {
expect(calculateGasMargin(BigNumber.from(1000)).toString()).toEqual('1200')
expect(calculateGasMargin(BigNumber.from(50)).toString()).toEqual('60')
expect(calculateGasMargin(1, BigNumber.from(1000)).toString()).toEqual('1200')
expect(calculateGasMargin(1, BigNumber.from(50)).toString()).toEqual('60')
})
it('optimism - returns exact value', () => {
expect(calculateGasMargin(69, BigNumber.from(1000)).toString()).toEqual('1000')
expect(calculateGasMargin(69, BigNumber.from(50)).toString()).toEqual('50')
})
})
import { BigNumber } from '@ethersproject/bignumber'
import { SupportedChainId } from 'constants/chains'
// add 20%
export function calculateGasMargin(value: BigNumber): BigNumber {
return value.mul(BigNumber.from(10000 + 2000)).div(BigNumber.from(10000))
// add 20% (except on optimism)
export function calculateGasMargin(chainId: number, value: BigNumber): BigNumber {
return chainId === SupportedChainId.OPTIMISM || chainId === SupportedChainId.OPTIMISTIC_KOVAN
? value
: value.mul(BigNumber.from(10000 + 2000)).div(BigNumber.from(10000))
}
......@@ -6,6 +6,8 @@ const ETHERSCAN_PREFIXES: { [chainId: number]: string } = {
[SupportedChainId.RINKEBY]: 'rinkeby.',
[SupportedChainId.GOERLI]: 'goerli.',
[SupportedChainId.KOVAN]: 'kovan.',
[SupportedChainId.OPTIMISM]: 'optimistic.',
[SupportedChainId.OPTIMISTIC_KOVAN]: 'kovan-optimistic.',
}
export enum ExplorerDataType {
......
......@@ -4,6 +4,8 @@ import { SupportedChainId } from '../constants/chains'
const NETWORK_POLLING_INTERVALS: { [chainId: number]: number } = {
[SupportedChainId.ARBITRUM_ONE]: 1_000,
[SupportedChainId.ARBITRUM_RINKEBY]: 1_000,
[SupportedChainId.OPTIMISM]: 1_000,
[SupportedChainId.OPTIMISTIC_KOVAN]: 1_000,
}
export default function getLibrary(provider: any): Web3Provider {
......
......@@ -4147,10 +4147,10 @@
base64-sol "1.0.1"
hardhat-watcher "^2.1.1"
"@uniswap/v3-sdk@^3.0.0-alpha.9":
version "3.0.0-alpha.9"
resolved "https://registry.npmjs.org/@uniswap/v3-sdk/-/v3-sdk-3.0.0-alpha.9.tgz"
integrity sha512-mfISrPYixSwpwG2XLzXJXJdmGbO76bycXrpNe7kEpcyPUpByejAwuYOnmAZBwZuvDylXtQZS6DiDDRghzNrc9w==
"@uniswap/v3-sdk@^3.2.1":
version "3.2.1"
resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-3.2.1.tgz#2da1dab8427aa5e79b266536f9a2f8854b52c3ae"
integrity sha512-AqDvYwSguuBwOvZyY2gdV2e15Cfu1/cdxGcjdClZGs4vmYZH/zMCfO99G+efGa+O6K4TtEWJYtgBc77XBQ5lzA==
dependencies:
"@ethersproject/abi" "^5.0.12"
"@ethersproject/solidity" "^5.0.9"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment