From 1c69d38521010b539a11e6e86a5315a7f82128b8 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Wed, 29 Jul 2020 19:54:04 -0500 Subject: [PATCH 01/29] move the gas estimation stuff into its own hook and report errors from the gas estimation --- .../swap/AdvancedSwapDetailsDropdown.tsx | 4 +- src/connectors/NetworkConnector.ts | 94 +++++-- src/hooks/useGasEstimates.ts | 163 ++++++++++++ src/hooks/useLast.ts | 30 ++- src/hooks/useSwapCallback.ts | 234 +++++++++++------- src/pages/AddLiquidity/PoolPriceBar.tsx | 12 +- src/pages/Swap/index.tsx | 13 +- src/state/mint/hooks.ts | 14 +- src/utils/index.ts | 2 +- src/utils/isZero.ts | 7 + 10 files changed, 441 insertions(+), 132 deletions(-) create mode 100644 src/hooks/useGasEstimates.ts create mode 100644 src/utils/isZero.ts diff --git a/src/components/swap/AdvancedSwapDetailsDropdown.tsx b/src/components/swap/AdvancedSwapDetailsDropdown.tsx index e3da41b3e87..e80c37bfc18 100644 --- a/src/components/swap/AdvancedSwapDetailsDropdown.tsx +++ b/src/components/swap/AdvancedSwapDetailsDropdown.tsx @@ -1,6 +1,6 @@ import React from 'react' import styled from 'styled-components' -import useLast from '../../hooks/useLast' +import { useLastTruthy } from '../../hooks/useLast' import { AdvancedSwapDetails, AdvancedSwapDetailsProps } from './AdvancedSwapDetails' const AdvancedDetailsFooter = styled.div<{ show: boolean }>` @@ -20,7 +20,7 @@ const AdvancedDetailsFooter = styled.div<{ show: boolean }>` ` export default function AdvancedSwapDetailsDropdown({ trade, ...rest }: AdvancedSwapDetailsProps) { - const lastTrade = useLast(trade) + const lastTrade = useLastTruthy(trade) return ( diff --git a/src/connectors/NetworkConnector.ts b/src/connectors/NetworkConnector.ts index 415affcf59b..8e27145996b 100644 --- a/src/connectors/NetworkConnector.ts +++ b/src/connectors/NetworkConnector.ts @@ -22,19 +22,83 @@ class RequestError extends Error { } } +interface BatchItem { + request: { jsonrpc: '2.0'; id: number; method: string; params: unknown } + resolve: (result: any) => void + reject: (error: Error) => void +} + class MiniRpcProvider implements AsyncSendable { public readonly isMetaMask: false = false public readonly chainId: number public readonly url: string public readonly host: string public readonly path: string + public readonly batchWaitTimeMs: number - constructor(chainId: number, url: string) { + private nextId = 1 + private batchTimeoutId: ReturnType | null = null + private batch: BatchItem[] = [] + + constructor(chainId: number, url: string, batchWaitTimeMs?: number) { this.chainId = chainId this.url = url const parsed = new URL(url) this.host = parsed.host this.path = parsed.pathname + // how long to wait to batch calls + this.batchWaitTimeMs = batchWaitTimeMs ?? 50 + } + + public readonly clearBatch = async () => { + console.debug('Clearing batch', this.batch) + const batch = this.batch + this.batch = [] + this.batchTimeoutId = null + let response: Response + try { + response = await fetch(this.url, { + method: 'POST', + headers: { 'content-type': 'application/json', accept: 'application/json' }, + body: JSON.stringify(batch.map(item => item.request)) + }) + } catch (error) { + batch.forEach(({ reject }) => reject(new Error('Failed to send batch call'))) + return + } + + if (!response.ok) { + batch.forEach(({ reject }) => reject(new RequestError(`${response.status}: ${response.statusText}`, -32000))) + return + } + + let json + try { + json = await response.json() + } catch (error) { + batch.forEach(({ reject }) => reject(new Error('Failed to parse JSON response'))) + return + } + const byKey = batch.reduce<{ [id: number]: BatchItem }>((memo, current) => { + memo[current.request.id] = current + return memo + }, {}) + for (const result of json) { + const { + resolve, + reject, + request: { method } + } = byKey[result.id] + if (resolve && reject) { + if ('error' in result) { + reject(new RequestError(result?.error?.message, result?.error?.code, result?.error?.data)) + } else if ('result' in result) { + resolve(result.result) + } else { + reject(new RequestError(`Received unexpected JSON-RPC response to ${method} request.`, -32000, result)) + } + } + } } public readonly sendAsync = ( @@ -56,24 +120,20 @@ class MiniRpcProvider implements AsyncSendable { if (method === 'eth_chainId') { return `0x${this.chainId.toString(16)}` } - const response = await fetch(this.url, { - method: 'POST', - body: JSON.stringify({ - jsonrpc: '2.0', - id: 1, - method, - params + const promise = new Promise((resolve, reject) => { + this.batch.push({ + request: { + jsonrpc: '2.0', + id: this.nextId++, + method, + params + }, + resolve, + reject }) }) - if (!response.ok) throw new RequestError(`${response.status}: ${response.statusText}`, -32000) - const body = await response.json() - if ('error' in body) { - throw new RequestError(body?.error?.message, body?.error?.code, body?.error?.data) - } else if ('result' in body) { - return body.result - } else { - throw new RequestError(`Received unexpected JSON-RPC response to ${method} request.`, -32000, body) - } + this.batchTimeoutId = this.batchTimeoutId ?? setTimeout(this.clearBatch, this.batchWaitTimeMs) + return promise } } diff --git a/src/hooks/useGasEstimates.ts b/src/hooks/useGasEstimates.ts new file mode 100644 index 00000000000..ef4d3638a20 --- /dev/null +++ b/src/hooks/useGasEstimates.ts @@ -0,0 +1,163 @@ +import { BigNumber } from '@ethersproject/bignumber' +import { Contract } from '@ethersproject/contracts' +import { ChainId } from '@uniswap/sdk' +import { useEffect, useMemo, useState } from 'react' +import { useBlockNumber } from '../state/application/hooks' +import { Call } from '../state/multicall/actions' +import isZero from '../utils/isZero' +import { useActiveWeb3React } from './index' + +export enum GasEstimateState { + INVALID, + LOADING, + VALID +} + +export interface EstimatableContractCall { + contract: Contract // the contract to call + methodName: string // the method to call on the contract + args: (string | string[])[] // args to pass to the call + value: string // hex ether value to send with the transaction +} + +interface SerializedEstimatableCall extends Call { + value: string +} + +function toEstimatableCall(estimatable: EstimatableContractCall): SerializedEstimatableCall { + return { + address: estimatable.contract.address, + callData: estimatable.contract.interface.encodeFunctionData( + estimatable.contract.interface.getFunction(estimatable.methodName), + estimatable.args + ), + value: estimatable.value + } +} + +function toCallKey(chainId: ChainId, call: SerializedEstimatableCall): string { + return `${chainId}:${call.address}:${call.callData}:${isZero(call.value) ? '' : call.value}` +} + +/** + * Return the gas estimate for the given contract methods and arguments + */ +export default function useGasEstimates( + calls: (EstimatableContractCall | undefined)[] | undefined +): [GasEstimateState, BigNumber | undefined][] { + const { chainId, library, account } = useActiveWeb3React() + const lastBlockNumber = useBlockNumber() + + const [state, setState] = useState<{ + [callKey: string]: { + blockNumber: number + estimate: BigNumber | undefined + error: string | undefined + fetchingBlockNumber: number | undefined + } + }>({}) + + // clear estimates on chain change for memory usage + useEffect(() => { + if (!chainId) return + setState({}) + }, [chainId]) + + const serializedEstimatableCalls: (SerializedEstimatableCall | undefined)[] = useMemo(() => { + return ( + calls?.map(call => { + try { + return call ? toEstimatableCall(call) : undefined + } catch (error) { + return undefined + } + }) ?? [] + ) + }, [calls]) + + useEffect(() => { + if (!library || !chainId || !lastBlockNumber) return + serializedEstimatableCalls.forEach(call => { + if (!call) return + const key = toCallKey(chainId, call) + if ((state[key]?.blockNumber ?? 0) >= lastBlockNumber) { + return + } + if ((state[key]?.fetchingBlockNumber ?? 0) >= lastBlockNumber) { + return + } + setState(state => { + return { + ...state, + [key]: { + ...state[key], + fetchingBlockNumber: lastBlockNumber + } + } + }) + try { + library + .estimateGas({ + from: account ?? undefined, + to: call.address, + data: call.callData, + ...(isZero(call.value) ? {} : { value: call.value }) + }) + .then(estimate => { + setState(state => { + return { + ...state, + [key]: { + blockNumber: lastBlockNumber, + error: undefined, + estimate, + fetchingBlockNumber: undefined + } + } + }) + }) + .catch(error => { + setState(state => { + return { + ...state, + [key]: { + blockNumber: lastBlockNumber, + error: error.message, + estimate: undefined, + fetchingBlockNumber: undefined + } + } + }) + }) + } catch (error) { + setState(state => { + return { + ...state, + [key]: { + blockNumber: lastBlockNumber, + estimate: undefined, + error: error.message, + fetchingBlockNumber: undefined + } + } + }) + } + }) + }, [serializedEstimatableCalls, chainId, state, lastBlockNumber, library, account]) + + return useMemo(() => { + return ( + serializedEstimatableCalls?.map(call => { + if (!call || !chainId) return [GasEstimateState.INVALID, undefined] + const result = state[toCallKey(chainId, call)] + if (!result) return [GasEstimateState.LOADING, undefined] + const { estimate, error, blockNumber } = result + const loading = !error && (!estimate || blockNumber !== lastBlockNumber) + return [ + error ? GasEstimateState.INVALID : loading ? GasEstimateState.LOADING : GasEstimateState.VALID, + error ? undefined : estimate + ] + }) ?? [] + ) + }, [chainId, lastBlockNumber, serializedEstimatableCalls, state]) +} diff --git a/src/hooks/useLast.ts b/src/hooks/useLast.ts index 9703044eb39..6260997b831 100644 --- a/src/hooks/useLast.ts +++ b/src/hooks/useLast.ts @@ -1,13 +1,33 @@ import { useEffect, useState } from 'react' /** - * Returns the last truthy value of type T + * Returns the last value of type T that passes a filter function * @param value changing value + * @param filterFn function that determines whether a given value should be considered for the last value */ -export default function useLast(value: T | undefined | null): T | null | undefined { - const [last, setLast] = useState(value) +export default function useLast( + value: T | undefined | null, + filterFn?: (value: T | null | undefined) => boolean +): T | null | undefined { + const [last, setLast] = useState(filterFn && filterFn(value) ? value : undefined) useEffect(() => { - setLast(last => value ?? last) - }, [value]) + setLast(last => { + const shouldUse: boolean = filterFn ? filterFn(value) : true + if (shouldUse) return value + return last + }) + }, [filterFn, value]) return last } + +function isDefined(x: T | null | undefined): x is T { + return x !== null && x !== undefined +} + +/** + * Returns the last truthy value of type T + * @param value changing value + */ +export function useLastTruthy(value: T | undefined | null): T | null | undefined { + return useLast(value, isDefined) +} diff --git a/src/hooks/useSwapCallback.ts b/src/hooks/useSwapCallback.ts index 2a1431329c8..a41adee0354 100644 --- a/src/hooks/useSwapCallback.ts +++ b/src/hooks/useSwapCallback.ts @@ -1,136 +1,184 @@ import { BigNumber } from '@ethersproject/bignumber' import { Contract } from '@ethersproject/contracts' -import { JSBI, Percent, Router, Trade, TradeType } from '@uniswap/sdk' +import { JSBI, Percent, Router, SwapParameters, Trade, TradeType } from '@uniswap/sdk' import { useMemo } from 'react' import { BIPS_BASE, DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE } from '../constants' import { getTradeVersion, useV1TradeExchangeAddress } from '../data/V1' import { useTransactionAdder } from '../state/transactions/hooks' import { calculateGasMargin, getRouterContract, isAddress, shortenAddress } from '../utils' +import isZero from '../utils/isZero' import v1SwapArguments from '../utils/v1SwapArguments' import { useActiveWeb3React } from './index' import { useV1ExchangeContract } from './useContract' import useENS from './useENS' +import useGasEstimates, { EstimatableContractCall, GasEstimateState } from './useGasEstimates' import { Version } from './useToggledVersion' -function isZero(hexNumber: string) { - return /^0x0*$/.test(hexNumber) +export enum SwapCallbackState { + INVALID, + LOADING, + VALID } -// returns a function that will execute a swap, if the parameters are all valid -// and the user has approved the slippage adjusted input amount for the trade -export function useSwapCallback( +interface SwapCall { + contract: Contract + parameters: SwapParameters +} + +/** + * Returns the swap calls that can be used to make the trade + * @param trade trade to execute + * @param allowedSlippage user allowed slippage + * @param deadline the deadline for the trade + * @param recipientAddressOrName + */ +function useSwapCallArguments( trade: Trade | undefined, // trade to execute, required allowedSlippage: number = INITIAL_ALLOWED_SLIPPAGE, // in bips deadline: number = DEFAULT_DEADLINE_FROM_NOW, // in seconds from now recipientAddressOrName: string | null // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender -): null | (() => Promise) { +): SwapCall[] { const { account, chainId, library } = useActiveWeb3React() - const addTransaction = useTransactionAdder() const { address: recipientAddress } = useENS(recipientAddressOrName) const recipient = recipientAddressOrName === null ? account : recipientAddress - const tradeVersion = getTradeVersion(trade) const v1Exchange = useV1ExchangeContract(useV1TradeExchangeAddress(trade), true) return useMemo(() => { - if (!trade || !recipient || !library || !account || !tradeVersion || !chainId) return null + const tradeVersion = getTradeVersion(trade) + if (!trade || !recipient || !library || !account || !tradeVersion || !chainId) return [] - return async function onSwap() { - const contract: Contract | null = - tradeVersion === Version.v2 ? getRouterContract(chainId, library, account) : v1Exchange - if (!contract) { - throw new Error('Failed to get a swap contract') - } + const contract: Contract | null = + tradeVersion === Version.v2 ? getRouterContract(chainId, library, account) : v1Exchange + if (!contract) { + return [] + } - const swapMethods = [] + const swapMethods = [] - switch (tradeVersion) { - case Version.v2: + switch (tradeVersion) { + case Version.v2: + swapMethods.push( + Router.swapCallParameters(trade, { + feeOnTransfer: false, + allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE), + recipient, + ttl: deadline + }) + ) + + if (trade.tradeType === TradeType.EXACT_INPUT) { swapMethods.push( Router.swapCallParameters(trade, { - feeOnTransfer: false, + feeOnTransfer: true, allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE), recipient, ttl: deadline }) ) + } + break + case Version.v1: + swapMethods.push( + v1SwapArguments(trade, { + allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE), + recipient, + ttl: deadline + }) + ) + break + } + return swapMethods.map(parameters => ({ parameters, contract })) + }, [account, allowedSlippage, chainId, deadline, library, recipient, trade, v1Exchange]) +} - if (trade.tradeType === TradeType.EXACT_INPUT) { - swapMethods.push( - Router.swapCallParameters(trade, { - feeOnTransfer: true, - allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE), - recipient, - ttl: deadline - }) - ) - } - break - case Version.v1: - swapMethods.push( - v1SwapArguments(trade, { - allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE), - recipient, - ttl: deadline - }) - ) - break +// returns a function that will execute a swap, if the parameters are all valid +// and the user has approved the slippage adjusted input amount for the trade +export function useSwapCallback( + trade: Trade | undefined, // trade to execute, required + allowedSlippage: number = INITIAL_ALLOWED_SLIPPAGE, // in bips + deadline: number = DEFAULT_DEADLINE_FROM_NOW, // in seconds from now + recipientAddressOrName: string | null // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender +): { state: SwapCallbackState; callback: null | (() => Promise); error: string | null } { + const { account, chainId, library } = useActiveWeb3React() + + const swapCalls = useSwapCallArguments(trade, allowedSlippage, deadline, recipientAddressOrName) + const estimatableSwapCalls: EstimatableContractCall[] = useMemo( + () => + swapCalls.map(({ contract, parameters: { methodName, args, value } }) => ({ + contract, + methodName, + value, + args + })), + [swapCalls] + ) + const gasEstimates = useGasEstimates(estimatableSwapCalls) + const [indexOfSuccessfulEstimation, loadingGasEstimation] = useMemo( + () => [ + gasEstimates.findIndex(([, estimate]) => BigNumber.isBigNumber(estimate)), + gasEstimates.some(([state]) => state === GasEstimateState.LOADING) + ], + [gasEstimates] + ) + + const addTransaction = useTransactionAdder() + + const { address: recipientAddress } = useENS(recipientAddressOrName) + const recipient = recipientAddressOrName === null ? account : recipientAddress + + return useMemo(() => { + if (!trade || !library || !account || !chainId) { + return { state: SwapCallbackState.INVALID, callback: null, error: 'Missing dependencies' } + } + if (!recipient) { + if (recipientAddressOrName !== null) { + return { state: SwapCallbackState.INVALID, callback: null, error: 'Invalid recipient' } + } else { + return { state: SwapCallbackState.LOADING, callback: null, error: null } } + } - const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all( - swapMethods.map(({ args, methodName, value }) => - contract.estimateGas[methodName](...args, value && !isZero(value) ? { value } : {}) - .then(calculateGasMargin) - .catch(error => { - console.error(`estimateGas failed for ${methodName}`, error) - return undefined - }) - ) - ) - - // we expect failures from left to right, so throw if we see failures - // from right to left - for (let i = 0; i < safeGasEstimates.length - 1; i++) { - // if the FoT method fails, but the regular method does not, we should not - // use the regular method. this probably means something is wrong with the fot token. - if (BigNumber.isBigNumber(safeGasEstimates[i]) && !BigNumber.isBigNumber(safeGasEstimates[i + 1])) { - throw new Error( - 'An error occurred. Please try raising your slippage. If that does not work, contact support.' - ) - } + if (loadingGasEstimation) { + return { state: SwapCallbackState.LOADING, callback: null, error: null } + } + + const tradeVersion = getTradeVersion(trade) + + if (indexOfSuccessfulEstimation === -1) { + return { + state: SwapCallbackState.INVALID, + callback: null, + error: 'Could not compute gas estimation. Try increasing your slippage tolerance.' } + } - const indexOfSuccessfulEstimation = safeGasEstimates.findIndex(safeGasEstimate => - BigNumber.isBigNumber(safeGasEstimate) - ) - - // all estimations failed... - if (indexOfSuccessfulEstimation === -1) { - // if only 1 method exists, either: - // a) the token is doing something weird not related to FoT (e.g. enforcing a whitelist) - // b) the token is FoT and the user specified an exact output, which is not allowed - if (swapMethods.length === 1) { - throw Error( - `An error occurred. If either of the tokens you're swapping take a fee on transfer, you must specify an exact input amount.` - ) + // we expect failures from left to right, so throw if we see failures + // from right to left + for (let i = 0; i < gasEstimates.length - 1; i++) { + // if the FoT method fails, but the regular method does not, we should not + // use the regular method. this probably means something is wrong with the fot token. + if (BigNumber.isBigNumber(gasEstimates[i][1]) && !BigNumber.isBigNumber(gasEstimates[i + 1][1])) { + return { + state: SwapCallbackState.INVALID, + callback: null, + error: 'Unexpected error. Try increasing your slippage tolerance. Otherwise, contact support.' } - // if 2 methods exists, either: - // a) the token is doing something weird not related to FoT (e.g. enforcing a whitelist) - // b) the token is FoT and is taking more than the specified slippage - else if (swapMethods.length === 2) { - throw Error( - `An error occurred. If either of the tokens you're swapping take a fee on transfer, you must specify a slippage tolerance higher than the fee.` - ) - } else { - throw Error('This transaction would fail. Please contact support.') - } - } else { - const { methodName, args, value } = swapMethods[indexOfSuccessfulEstimation] - const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation] + } + } + + return { + state: SwapCallbackState.VALID, + callback: async function onSwap() { + const { + contract, + parameters: { methodName, args, value } + } = swapCalls[indexOfSuccessfulEstimation] + const safeGasEstimate = gasEstimates[indexOfSuccessfulEstimation][1] return contract[methodName](...args, { - gasLimit: safeGasEstimate, + gasLimit: safeGasEstimate && calculateGasMargin(safeGasEstimate), ...(value && !isZero(value) ? { value } : {}) }) .then((response: any) => { @@ -169,18 +217,18 @@ export function useSwapCallback( throw Error('An error occurred while swapping. Please contact support.') } }) - } + }, + error: null } }, [ trade, recipient, library, account, - tradeVersion, chainId, - allowedSlippage, - v1Exchange, - deadline, + indexOfSuccessfulEstimation, + swapCalls, + gasEstimates, recipientAddressOrName, addTransaction ]) diff --git a/src/pages/AddLiquidity/PoolPriceBar.tsx b/src/pages/AddLiquidity/PoolPriceBar.tsx index 04593a82da5..9d2f920be3c 100644 --- a/src/pages/AddLiquidity/PoolPriceBar.tsx +++ b/src/pages/AddLiquidity/PoolPriceBar.tsx @@ -1,4 +1,4 @@ -import { Currency, Fraction, Percent } from '@uniswap/sdk' +import { Currency, Percent, Price } from '@uniswap/sdk' import React, { useContext } from 'react' import { Text } from 'rebass' import { ThemeContext } from 'styled-components' @@ -8,7 +8,7 @@ import { ONE_BIPS } from '../../constants' import { Field } from '../../state/mint/actions' import { TYPE } from '../../theme' -export const PoolPriceBar = ({ +export function PoolPriceBar({ currencies, noLiquidity, poolTokenPercentage, @@ -17,20 +17,20 @@ export const PoolPriceBar = ({ currencies: { [field in Field]?: Currency } noLiquidity?: boolean poolTokenPercentage?: Percent - price?: Fraction -}) => { + price?: Price +}) { const theme = useContext(ThemeContext) return ( - {price?.toSignificant(6) ?? '0'} + {price?.toSignificant(6) ?? '-'} {currencies[Field.CURRENCY_B]?.symbol} per {currencies[Field.CURRENCY_A]?.symbol} - {price?.invert().toSignificant(6) ?? '0'} + {price?.invert()?.toSignificant(6) ?? '-'} {currencies[Field.CURRENCY_A]?.symbol} per {currencies[Field.CURRENCY_B]?.symbol} diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index 1b42d86b851..4e41c427463 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -150,7 +150,12 @@ export default function Swap() { const slippageAdjustedAmounts = computeSlippageAdjustedAmounts(trade, allowedSlippage) // the callback to execute the swap - const swapCallback = useSwapCallback(trade, allowedSlippage, deadline, recipient) + const { callback: swapCallback, error: swapCallbackError } = useSwapCallback( + trade, + allowedSlippage, + deadline, + recipient + ) const { priceImpactWithoutFee, realizedLPFee } = computeTradePriceBreakdown(trade) @@ -408,12 +413,14 @@ export default function Swap() { expertMode ? onSwap() : setShowConfirm(true) }} id="swap-button" - disabled={!isValid || (priceImpactSeverity > 3 && !expertMode)} - error={isValid && priceImpactSeverity > 2} + disabled={!isValid || (priceImpactSeverity > 3 && !expertMode) || !!swapCallbackError} + error={isValid && priceImpactSeverity > 2 && !swapCallbackError} > {error ? error + : swapCallbackError + ? swapCallbackError : priceImpactSeverity > 3 && !expertMode ? `Price Impact Too High` : `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`} diff --git a/src/state/mint/hooks.ts b/src/state/mint/hooks.ts index a587ff120bb..aa049160356 100644 --- a/src/state/mint/hooks.ts +++ b/src/state/mint/hooks.ts @@ -95,12 +95,16 @@ export function useDerivedMintInfo( } const price = useMemo(() => { - const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts - if (currencyAAmount && currencyBAmount) { - return new Price(currencyAAmount.currency, currencyBAmount.currency, currencyAAmount.raw, currencyBAmount.raw) + if (noLiquidity) { + const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts + if (currencyAAmount && currencyBAmount) { + return new Price(currencyAAmount.currency, currencyBAmount.currency, currencyAAmount.raw, currencyBAmount.raw) + } + return + } else { + return pair?.token0Price } - return - }, [parsedAmounts]) + }, [noLiquidity, pair?.token0Price, parsedAmounts]) // liquidity minted const totalSupply = useTotalSupply(pair?.liquidityToken) diff --git a/src/utils/index.ts b/src/utils/index.ts index fb04ac52a5b..48d417fac9d 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -91,7 +91,7 @@ export function getContract(address: string, ABI: any, library: Web3Provider, ac } // account is optional -export function getRouterContract(_: number, library: Web3Provider, account?: string) { +export function getRouterContract(_: number, library: Web3Provider, account?: string): Contract { return getContract(ROUTER_ADDRESS, IUniswapV2Router02ABI, library, account) } diff --git a/src/utils/isZero.ts b/src/utils/isZero.ts new file mode 100644 index 00000000000..1b84e675606 --- /dev/null +++ b/src/utils/isZero.ts @@ -0,0 +1,7 @@ +/** + * Returns true if the string value is zero in hex + * @param hexNumberString + */ +export default function isZero(hexNumberString: string) { + return /^0x0*$/.test(hexNumberString) +} From b997e65aaddee16b5c2f2668f2faf8f8c9f1352c Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Wed, 29 Jul 2020 19:55:15 -0500 Subject: [PATCH 02/29] fix linter errors --- src/hooks/useSwapCallback.ts | 7 ++++--- src/state/mint/hooks.ts | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/hooks/useSwapCallback.ts b/src/hooks/useSwapCallback.ts index a41adee0354..f4ba8a9b280 100644 --- a/src/hooks/useSwapCallback.ts +++ b/src/hooks/useSwapCallback.ts @@ -222,14 +222,15 @@ export function useSwapCallback( } }, [ trade, - recipient, library, account, chainId, + recipient, + loadingGasEstimation, indexOfSuccessfulEstimation, - swapCalls, - gasEstimates, recipientAddressOrName, + gasEstimates, + swapCalls, addTransaction ]) } diff --git a/src/state/mint/hooks.ts b/src/state/mint/hooks.ts index aa049160356..853f95c8953 100644 --- a/src/state/mint/hooks.ts +++ b/src/state/mint/hooks.ts @@ -94,6 +94,7 @@ export function useDerivedMintInfo( [Field.CURRENCY_B]: independentField === Field.CURRENCY_A ? dependentAmount : independentAmount } + const token0Price = pair?.token0Price const price = useMemo(() => { if (noLiquidity) { const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts @@ -102,9 +103,9 @@ export function useDerivedMintInfo( } return } else { - return pair?.token0Price + return token0Price } - }, [noLiquidity, pair?.token0Price, parsedAmounts]) + }, [noLiquidity, token0Price, parsedAmounts]) // liquidity minted const totalSupply = useTotalSupply(pair?.liquidityToken) From a5098a61630d990924422ca8295e44663dd900c1 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Wed, 29 Jul 2020 22:40:50 -0500 Subject: [PATCH 03/29] show the swap callback error separately --- src/components/swap/styleds.ts | 31 ++++++++++++------------------- src/hooks/useSwapCallback.ts | 2 +- src/pages/Swap/index.tsx | 11 +++++++---- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/components/swap/styleds.ts b/src/components/swap/styleds.ts index 612dd02f5fc..45b4b8ac9f7 100644 --- a/src/components/swap/styleds.ts +++ b/src/components/swap/styleds.ts @@ -1,9 +1,7 @@ +import { darken } from 'polished' import styled, { css } from 'styled-components' -import { AutoColumn } from '../Column' import { Text } from 'rebass' -import NumericalInput from '../NumericalInput' - export const Wrapper = styled.div` position: relative; ` @@ -30,7 +28,6 @@ export const SectionBreak = styled.div` export const BottomGrouping = styled.div` margin-top: 12px; - position: relative; ` export const ErrorText = styled(Text)<{ severity?: 0 | 1 | 2 | 3 | 4 }>` @@ -44,21 +41,6 @@ export const ErrorText = styled(Text)<{ severity?: 0 | 1 | 2 | 3 | 4 }>` : theme.green1}; ` -export const InputGroup = styled(AutoColumn)` - position: relative; - padding: 40px 0 20px 0; -` - -export const StyledNumerical = styled(NumericalInput)` - text-align: center; - font-size: 48px; - font-weight: 500px; - width: 100%; - - ::placeholder { - color: ${({ theme }) => theme.text4}; - } -` export const StyledBalanceMaxMini = styled.button` height: 22px; width: 22px; @@ -112,3 +94,14 @@ export const Dots = styled.span` } } ` + +export const SwapCallbackError = styled.div` + background-color: ${({ theme }) => theme.red1}; + border-radius: 1rem; + padding: 1rem; + display: flex; + align-items: center; + margin-top: 1rem; + border: 1px solid ${({ theme }) => darken(0.1, theme.red1)}; + color: ${({ theme }) => theme.white}; +` diff --git a/src/hooks/useSwapCallback.ts b/src/hooks/useSwapCallback.ts index f4ba8a9b280..6324bd77857 100644 --- a/src/hooks/useSwapCallback.ts +++ b/src/hooks/useSwapCallback.ts @@ -214,7 +214,7 @@ export function useSwapCallback( // otherwise, the error was unexpected and we need to convey that else { console.error(`Swap failed`, error, methodName, args, value) - throw Error('An error occurred while swapping. Please contact support.') + throw new Error('An error occurred while swapping. Please contact support.') } }) }, diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index 4e41c427463..f7996c490fc 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -1,6 +1,6 @@ import { CurrencyAmount, JSBI } from '@uniswap/sdk' import React, { useCallback, useContext, useEffect, useState } from 'react' -import { ArrowDown } from 'react-feather' +import { AlertTriangle, ArrowDown } from 'react-feather' import ReactGA from 'react-ga' import { Text } from 'rebass' import { ThemeContext } from 'styled-components' @@ -15,7 +15,7 @@ import { AutoRow, RowBetween } from '../../components/Row' import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown' import BetterTradeLink from '../../components/swap/BetterTradeLink' import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee' -import { ArrowWrapper, BottomGrouping, Dots, Wrapper } from '../../components/swap/styleds' +import { ArrowWrapper, BottomGrouping, Dots, SwapCallbackError, Wrapper } from '../../components/swap/styleds' import SwapModalFooter from '../../components/swap/SwapModalFooter' import SwapModalHeader from '../../components/swap/SwapModalHeader' import TradePrice from '../../components/swap/TradePrice' @@ -419,14 +419,17 @@ export default function Swap() { {error ? error - : swapCallbackError - ? swapCallbackError : priceImpactSeverity > 3 && !expertMode ? `Price Impact Too High` : `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`} )} + {!error && swapCallbackError ? ( + + {swapCallbackError} + + ) : null} {betterTradeLinkVersion && } From dbb28b5724a84975c7846faa1da23a4d4a730a07 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Wed, 29 Jul 2020 22:48:03 -0500 Subject: [PATCH 04/29] rename some variables --- src/hooks/useWrapCallback.ts | 6 +++--- src/pages/Swap/index.tsx | 26 +++++++++++++++++--------- src/state/swap/hooks.ts | 16 ++++++++-------- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/hooks/useWrapCallback.ts b/src/hooks/useWrapCallback.ts index 43211c38e47..46a4f8e2d33 100644 --- a/src/hooks/useWrapCallback.ts +++ b/src/hooks/useWrapCallback.ts @@ -23,7 +23,7 @@ export default function useWrapCallback( inputCurrency: Currency | undefined, outputCurrency: Currency | undefined, typedValue: string | undefined -): { wrapType: WrapType; execute?: undefined | (() => Promise); error?: string } { +): { wrapType: WrapType; execute?: undefined | (() => Promise); inputError?: string } { const { chainId, account } = useActiveWeb3React() const wethContract = useWETHContract() const balance = useCurrencyBalance(account ?? undefined, inputCurrency) @@ -50,7 +50,7 @@ export default function useWrapCallback( } } : undefined, - error: sufficientBalance ? undefined : 'Insufficient ETH balance' + inputError: sufficientBalance ? undefined : 'Insufficient ETH balance' } } else if (currencyEquals(WETH[chainId], inputCurrency) && outputCurrency === ETHER) { return { @@ -66,7 +66,7 @@ export default function useWrapCallback( } } : undefined, - error: sufficientBalance ? undefined : 'Insufficient WETH balance' + inputError: sufficientBalance ? undefined : 'Insufficient WETH balance' } } else { return NOT_APPLICABLE diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index f7996c490fc..0918962b8cf 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -63,8 +63,15 @@ export default function Swap() { // swap state const { independentField, typedValue, recipient } = useSwapState() - const { v1Trade, v2Trade, currencyBalances, parsedAmount, currencies, error } = useDerivedSwapInfo() - const { wrapType, execute: onWrap, error: wrapError } = useWrapCallback( + const { + v1Trade, + v2Trade, + currencyBalances, + parsedAmount, + currencies, + inputError: swapInputError + } = useDerivedSwapInfo() + const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback( currencies[Field.INPUT], currencies[Field.OUTPUT], typedValue @@ -97,7 +104,7 @@ export default function Swap() { } const { onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient } = useSwapActionHandlers() - const isValid = !error + const isValid = !swapInputError const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT const handleTypeInput = useCallback( @@ -205,7 +212,7 @@ export default function Swap() { // show approve flow when: no error on inputs, not approved or pending, or approved in current session // never show if price impact is above threshold in non expert mode const showApproveFlow = - !error && + !swapInputError && (approval === ApprovalState.NOT_APPROVED || approval === ApprovalState.PENDING || (approvalSubmitted && approval === ApprovalState.APPROVED)) && @@ -368,8 +375,9 @@ export default function Swap() { {!account ? ( Connect Wallet ) : showWrap ? ( - - {wrapError ?? (wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)} + + {wrapInputError ?? + (wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)} ) : noRoute && userHasSpecifiedInputOutput ? ( @@ -417,15 +425,15 @@ export default function Swap() { error={isValid && priceImpactSeverity > 2 && !swapCallbackError} > - {error - ? error + {swapInputError + ? swapInputError : priceImpactSeverity > 3 && !expertMode ? `Price Impact Too High` : `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`} )} - {!error && swapCallbackError ? ( + {!swapInputError && swapCallbackError ? ( {swapCallbackError} diff --git a/src/state/swap/hooks.ts b/src/state/swap/hooks.ts index e8a5f1ed543..fa4d6dc43ab 100644 --- a/src/state/swap/hooks.ts +++ b/src/state/swap/hooks.ts @@ -94,7 +94,7 @@ export function useDerivedSwapInfo(): { currencyBalances: { [field in Field]?: CurrencyAmount } parsedAmount: CurrencyAmount | undefined v2Trade: Trade | undefined - error?: string + inputError?: string v1Trade: Trade | undefined } { const { account } = useActiveWeb3React() @@ -140,21 +140,21 @@ export function useDerivedSwapInfo(): { // get link to trade on v1, if a better rate exists const v1Trade = useV1Trade(isExactIn, currencies[Field.INPUT], currencies[Field.OUTPUT], parsedAmount) - let error: string | undefined + let inputError: string | undefined if (!account) { - error = 'Connect Wallet' + inputError = 'Connect Wallet' } if (!parsedAmount) { - error = error ?? 'Enter an amount' + inputError = inputError ?? 'Enter an amount' } if (!currencies[Field.INPUT] || !currencies[Field.OUTPUT]) { - error = error ?? 'Select a token' + inputError = inputError ?? 'Select a token' } if (!to) { - error = error ?? 'Enter a recipient' + inputError = inputError ?? 'Enter a recipient' } const [allowedSlippage] = useUserSlippageTolerance() @@ -177,7 +177,7 @@ export function useDerivedSwapInfo(): { ] if (balanceIn && amountIn && balanceIn.lessThan(amountIn)) { - error = 'Insufficient ' + amountIn.currency.symbol + ' balance' + inputError = 'Insufficient ' + amountIn.currency.symbol + ' balance' } return { @@ -185,7 +185,7 @@ export function useDerivedSwapInfo(): { currencyBalances, parsedAmount, v2Trade: v2Trade ?? undefined, - error, + inputError, v1Trade } } From b05e07bc6f6551e11579447ea079bd2681b31df9 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Thu, 30 Jul 2020 18:30:54 -0500 Subject: [PATCH 05/29] use a manually specified key for gas estimates --- src/components/Popups/PopupItem.tsx | 4 ++-- .../{TxnPopup.tsx => TransactionPopup.tsx} | 20 +++++++++++++++---- src/data/V1.ts | 2 +- src/hooks/useGasEstimates.ts | 14 ++++++------- src/hooks/useSwapCallback.ts | 7 ++++--- src/pages/RemoveLiquidity/index.tsx | 5 +++-- src/pages/Swap/index.tsx | 2 +- src/state/mint/hooks.ts | 6 +++--- 8 files changed, 36 insertions(+), 24 deletions(-) rename src/components/Popups/{TxnPopup.tsx => TransactionPopup.tsx} (76%) diff --git a/src/components/Popups/PopupItem.tsx b/src/components/Popups/PopupItem.tsx index 6d6f5137377..f7f0436052a 100644 --- a/src/components/Popups/PopupItem.tsx +++ b/src/components/Popups/PopupItem.tsx @@ -5,7 +5,7 @@ import useInterval from '../../hooks/useInterval' import { PopupContent } from '../../state/application/actions' import { useRemovePopup } from '../../state/application/hooks' import ListUpdatePopup from './ListUpdatePopup' -import TxnPopup from './TxnPopup' +import TransactionPopup from './TransactionPopup' export const StyledClose = styled(X)` position: absolute; @@ -68,7 +68,7 @@ export default function PopupItem({ content, popKey }: { content: PopupContent; const { txn: { hash, success, summary } } = content - popupContent = + popupContent = } else if ('listUpdate' in content) { const { listUpdate: { listUrl, oldList, newList, auto } diff --git a/src/components/Popups/TxnPopup.tsx b/src/components/Popups/TransactionPopup.tsx similarity index 76% rename from src/components/Popups/TxnPopup.tsx rename to src/components/Popups/TransactionPopup.tsx index 4cde7d290a1..53495a8da9b 100644 --- a/src/components/Popups/TxnPopup.tsx +++ b/src/components/Popups/TransactionPopup.tsx @@ -1,6 +1,6 @@ import React, { useContext } from 'react' import { AlertCircle, CheckCircle } from 'react-feather' -import { ThemeContext } from 'styled-components' +import styled, { ThemeContext } from 'styled-components' import { useActiveWeb3React } from '../../hooks' import { TYPE } from '../../theme' import { ExternalLink } from '../../theme/components' @@ -8,13 +8,25 @@ import { getEtherscanLink } from '../../utils' import { AutoColumn } from '../Column' import { AutoRow } from '../Row' -export default function TxnPopup({ hash, success, summary }: { hash: string; success?: boolean; summary?: string }) { +const RowNoFlex = styled(AutoRow)` + flex-wrap: nowrap; +` + +export default function TransactionPopup({ + hash, + success, + summary +}: { + hash: string + success?: boolean + summary?: string +}) { const { chainId } = useActiveWeb3React() const theme = useContext(ThemeContext) return ( - +
{success ? : }
@@ -22,6 +34,6 @@ export default function TxnPopup({ hash, success, summary }: { hash: string; suc {summary ?? 'Hash: ' + hash.slice(0, 8) + '...' + hash.slice(58, 65)} View on Etherscan
-
+ ) } diff --git a/src/data/V1.ts b/src/data/V1.ts index 73ec3931a22..67f7b78ccf8 100644 --- a/src/data/V1.ts +++ b/src/data/V1.ts @@ -131,7 +131,7 @@ export function useV1Trade( ? new Trade(route, exactAmount, isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT) : undefined } catch (error) { - console.error('Failed to create V1 trade', error) + console.debug('Failed to create V1 trade', error) } return v1Trade } diff --git a/src/hooks/useGasEstimates.ts b/src/hooks/useGasEstimates.ts index ef4d3638a20..ecb5341db23 100644 --- a/src/hooks/useGasEstimates.ts +++ b/src/hooks/useGasEstimates.ts @@ -1,6 +1,5 @@ import { BigNumber } from '@ethersproject/bignumber' import { Contract } from '@ethersproject/contracts' -import { ChainId } from '@uniswap/sdk' import { useEffect, useMemo, useState } from 'react' import { useBlockNumber } from '../state/application/hooks' import { Call } from '../state/multicall/actions' @@ -14,6 +13,7 @@ export enum GasEstimateState { } export interface EstimatableContractCall { + key: string contract: Contract // the contract to call methodName: string // the method to call on the contract args: (string | string[])[] // args to pass to the call @@ -22,6 +22,7 @@ export interface EstimatableContractCall { interface SerializedEstimatableCall extends Call { value: string + key: string } function toEstimatableCall(estimatable: EstimatableContractCall): SerializedEstimatableCall { @@ -31,14 +32,11 @@ function toEstimatableCall(estimatable: EstimatableContractCall): SerializedEsti estimatable.contract.interface.getFunction(estimatable.methodName), estimatable.args ), - value: estimatable.value + value: estimatable.value, + key: estimatable.key } } -function toCallKey(chainId: ChainId, call: SerializedEstimatableCall): string { - return `${chainId}:${call.address}:${call.callData}:${isZero(call.value) ? '' : call.value}` -} - /** * Return the gas estimate for the given contract methods and arguments */ @@ -79,7 +77,7 @@ export default function useGasEstimates( if (!library || !chainId || !lastBlockNumber) return serializedEstimatableCalls.forEach(call => { if (!call) return - const key = toCallKey(chainId, call) + const key = call.key if ((state[key]?.blockNumber ?? 0) >= lastBlockNumber) { return } @@ -149,7 +147,7 @@ export default function useGasEstimates( return ( serializedEstimatableCalls?.map(call => { if (!call || !chainId) return [GasEstimateState.INVALID, undefined] - const result = state[toCallKey(chainId, call)] + const result = state[call.key] if (!result) return [GasEstimateState.LOADING, undefined] const { estimate, error, blockNumber } = result const loading = !error && (!estimate || blockNumber !== lastBlockNumber) diff --git a/src/hooks/useSwapCallback.ts b/src/hooks/useSwapCallback.ts index 6324bd77857..72e3a19acdf 100644 --- a/src/hooks/useSwapCallback.ts +++ b/src/hooks/useSwapCallback.ts @@ -110,9 +110,10 @@ export function useSwapCallback( contract, methodName, value, - args + args, + key: `${chainId}:${contract.address}:${methodName}:${allowedSlippage}` })), - [swapCalls] + [allowedSlippage, chainId, swapCalls] ) const gasEstimates = useGasEstimates(estimatableSwapCalls) const [indexOfSuccessfulEstimation, loadingGasEstimation] = useMemo( @@ -140,7 +141,7 @@ export function useSwapCallback( } } - if (loadingGasEstimation) { + if (loadingGasEstimation && indexOfSuccessfulEstimation === -1) { return { state: SwapCallbackState.LOADING, callback: null, error: null } } diff --git a/src/pages/RemoveLiquidity/index.tsx b/src/pages/RemoveLiquidity/index.tsx index abfe3c06b8f..9990d552d22 100644 --- a/src/pages/RemoveLiquidity/index.tsx +++ b/src/pages/RemoveLiquidity/index.tsx @@ -274,12 +274,13 @@ export default function RemoveLiquidity({ throw new Error('Attempting to confirm without approval or a signature. Please contact support.') } - const safeGasEstimates = await Promise.all( + const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all( methodNames.map(methodName => router.estimateGas[methodName](...args) .then(calculateGasMargin) .catch(error => { - console.error(`estimateGas failed for ${methodName}`, error) + console.error(`estimateGas failed`, methodName, args, error) + return undefined }) ) ) diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index 0918962b8cf..8d03bb5af34 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -433,7 +433,7 @@ export default function Swap() { )} - {!swapInputError && swapCallbackError ? ( + {!swapInputError && trade && swapCallbackError ? ( {swapCallbackError} diff --git a/src/state/mint/hooks.ts b/src/state/mint/hooks.ts index 853f95c8953..b1ee6f43ebd 100644 --- a/src/state/mint/hooks.ts +++ b/src/state/mint/hooks.ts @@ -94,7 +94,7 @@ export function useDerivedMintInfo( [Field.CURRENCY_B]: independentField === Field.CURRENCY_A ? dependentAmount : independentAmount } - const token0Price = pair?.token0Price + const token1Price = pair?.token1Price const price = useMemo(() => { if (noLiquidity) { const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts @@ -103,9 +103,9 @@ export function useDerivedMintInfo( } return } else { - return token0Price + return token1Price } - }, [noLiquidity, token0Price, parsedAmounts]) + }, [noLiquidity, token1Price, parsedAmounts]) // liquidity minted const totalSupply = useTotalSupply(pair?.liquidityToken) From e818962f7e5dbfb38a81c1ff19df6ebca6dc5cd5 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Fri, 31 Jul 2020 12:23:17 -0500 Subject: [PATCH 06/29] flip price... thought i did this already --- src/state/mint/hooks.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/state/mint/hooks.ts b/src/state/mint/hooks.ts index b1ee6f43ebd..853f95c8953 100644 --- a/src/state/mint/hooks.ts +++ b/src/state/mint/hooks.ts @@ -94,7 +94,7 @@ export function useDerivedMintInfo( [Field.CURRENCY_B]: independentField === Field.CURRENCY_A ? dependentAmount : independentAmount } - const token1Price = pair?.token1Price + const token0Price = pair?.token0Price const price = useMemo(() => { if (noLiquidity) { const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts @@ -103,9 +103,9 @@ export function useDerivedMintInfo( } return } else { - return token1Price + return token0Price } - }, [noLiquidity, token1Price, parsedAmounts]) + }, [noLiquidity, token0Price, parsedAmounts]) // liquidity minted const totalSupply = useTotalSupply(pair?.liquidityToken) From 532f2d758b04d1aead9d5610c74be65234061a97 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Fri, 31 Jul 2020 14:32:07 -0500 Subject: [PATCH 07/29] only show swap callback error if approval state is approved --- src/pages/Swap/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index 6992b0c117d..ecab41d981c 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -266,7 +266,7 @@ export default function Swap() { return ( <> {showWarning && } - + )} - {!swapInputError && trade && swapCallbackError ? ( + {swapCallbackError && approval === ApprovalState.APPROVED && !swapInputError && trade ? ( {swapCallbackError} From 504f10caae2149ab6d1fc829ebbb1893cfc1d453 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Fri, 31 Jul 2020 16:58:45 -0500 Subject: [PATCH 08/29] some clean up to the swap components --- src/components/ConfirmationModal/index.tsx | 4 +-- src/components/swap/AdvancedSwapDetails.tsx | 30 +++++++++-------- .../swap/AdvancedSwapDetailsDropdown.tsx | 2 +- src/components/swap/FormattedPriceImpact.tsx | 5 ++- src/components/swap/SwapModalFooter.tsx | 32 ++++++++----------- src/components/swap/SwapModalHeader.tsx | 23 +++++++------ .../swap/confirmPriceImpactWithoutFee.ts | 6 +++- src/components/swap/tsconfig.json | 4 +++ src/pages/Swap/index.tsx | 12 +++---- 9 files changed, 62 insertions(+), 56 deletions(-) create mode 100644 src/components/swap/tsconfig.json diff --git a/src/components/ConfirmationModal/index.tsx b/src/components/ConfirmationModal/index.tsx index b17e48398cc..93be0305e21 100644 --- a/src/components/ConfirmationModal/index.tsx +++ b/src/components/ConfirmationModal/index.tsx @@ -39,8 +39,8 @@ interface ConfirmationModalProps { isOpen: boolean onDismiss: () => void hash: string - topContent: () => React.ReactChild - bottomContent: () => React.ReactChild + topContent: () => React.ReactNode + bottomContent: () => React.ReactNode attemptingTxn: boolean pendingText: string title?: string diff --git a/src/components/swap/AdvancedSwapDetails.tsx b/src/components/swap/AdvancedSwapDetails.tsx index f4847c26012..c7a2e8d150a 100644 --- a/src/components/swap/AdvancedSwapDetails.tsx +++ b/src/components/swap/AdvancedSwapDetails.tsx @@ -73,23 +73,27 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) { const [allowedSlippage] = useUserSlippageTolerance() - const showRoute = trade?.route?.path?.length > 2 + const showRoute = Boolean(trade && trade.route.path.length > 2) return ( - {trade && } - {showRoute && ( + {trade && ( <> - - - - - Route - - - - - + + {showRoute && ( + <> + + + + + Route + + + + + + + )} )} diff --git a/src/components/swap/AdvancedSwapDetailsDropdown.tsx b/src/components/swap/AdvancedSwapDetailsDropdown.tsx index e80c37bfc18..4529ba34003 100644 --- a/src/components/swap/AdvancedSwapDetailsDropdown.tsx +++ b/src/components/swap/AdvancedSwapDetailsDropdown.tsx @@ -24,7 +24,7 @@ export default function AdvancedSwapDetailsDropdown({ trade, ...rest }: Advanced return ( - + ) } diff --git a/src/components/swap/FormattedPriceImpact.tsx b/src/components/swap/FormattedPriceImpact.tsx index c79da60a08b..c67727e06e2 100644 --- a/src/components/swap/FormattedPriceImpact.tsx +++ b/src/components/swap/FormattedPriceImpact.tsx @@ -4,10 +4,13 @@ import { ONE_BIPS } from '../../constants' import { warningSeverity } from '../../utils/prices' import { ErrorText } from './styleds' +/** + * Formatted version of price impact text with warning colors + */ export default function FormattedPriceImpact({ priceImpact }: { priceImpact?: Percent }) { return ( - {priceImpact?.lessThan(ONE_BIPS) ? '<0.01%' : `${priceImpact?.toFixed(2)}%` ?? '-'} + {priceImpact ? (priceImpact.lessThan(ONE_BIPS) ? '<0.01%' : `${priceImpact.toFixed(2)}%`) : '-'} ) } diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index 55412f4d644..bfec8898660 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -20,21 +20,17 @@ export default function SwapModalFooter({ severity, slippageAdjustedAmounts, onSwap, - parsedAmounts, realizedLPFee, - priceImpactWithoutFee, - confirmText + priceImpactWithoutFee }: { - trade?: Trade + trade: Trade showInverted: boolean setShowInverted: (inverted: boolean) => void severity: number - slippageAdjustedAmounts?: { [field in Field]?: CurrencyAmount } + slippageAdjustedAmounts: { [field in Field]?: CurrencyAmount } onSwap: () => any - parsedAmounts?: { [field in Field]?: CurrencyAmount } - realizedLPFee?: CurrencyAmount + realizedLPFee: CurrencyAmount priceImpactWithoutFee?: Percent - confirmText: string }) { const theme = useContext(ThemeContext) @@ -71,23 +67,21 @@ export default function SwapModalFooter({ - {trade?.tradeType === TradeType.EXACT_INPUT ? 'Minimum received' : 'Maximum sold'} + {trade.tradeType === TradeType.EXACT_INPUT ? 'Minimum received' : 'Maximum sold'} - {trade?.tradeType === TradeType.EXACT_INPUT + {trade.tradeType === TradeType.EXACT_INPUT ? slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(4) ?? '-' : slippageAdjustedAmounts[Field.INPUT]?.toSignificant(4) ?? '-'} - {parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.INPUT] && ( - - {trade?.tradeType === TradeType.EXACT_INPUT - ? parsedAmounts[Field.OUTPUT]?.currency?.symbol - : parsedAmounts[Field.INPUT]?.currency?.symbol} - - )} + + {trade.tradeType === TradeType.EXACT_INPUT + ? trade.outputAmount.currency.symbol + : trade.inputAmount.currency.symbol} + @@ -107,7 +101,7 @@ export default function SwapModalFooter({ - {realizedLPFee ? realizedLPFee?.toSignificant(6) + ' ' + trade?.inputAmount?.currency?.symbol : '-'} + {realizedLPFee ? realizedLPFee?.toSignificant(6) + ' ' + trade.inputAmount.currency.symbol : '-'}
@@ -115,7 +109,7 @@ export default function SwapModalFooter({ 2} style={{ margin: '10px 0 0 0' }} id="confirm-swap-or-send"> - {confirmText} + {severity > 2 ? 'Swap Anyway' : 'Confirm Swap'} diff --git a/src/components/swap/SwapModalHeader.tsx b/src/components/swap/SwapModalHeader.tsx index 57c52941364..87b2db734f9 100644 --- a/src/components/swap/SwapModalHeader.tsx +++ b/src/components/swap/SwapModalHeader.tsx @@ -1,4 +1,4 @@ -import { Currency, CurrencyAmount } from '@uniswap/sdk' +import { CurrencyAmount, Trade, TradeType } from '@uniswap/sdk' import React, { useContext } from 'react' import { ArrowDown } from 'react-feather' import { Text } from 'rebass' @@ -7,19 +7,18 @@ import { Field } from '../../state/swap/actions' import { TYPE } from '../../theme' import { isAddress, shortenAddress } from '../../utils' import { AutoColumn } from '../Column' -import { RowBetween, RowFixed } from '../Row' import CurrencyLogo from '../CurrencyLogo' +import { RowBetween, RowFixed } from '../Row' import { TruncatedText } from './styleds' export default function SwapModalHeader({ - currencies, + trade, formattedAmounts, slippageAdjustedAmounts, priceImpactSeverity, - independentField, recipient }: { - currencies: { [field in Field]?: Currency } + trade: Trade formattedAmounts: { [field in Field]?: string } slippageAdjustedAmounts: { [field in Field]?: CurrencyAmount } priceImpactSeverity: number @@ -35,9 +34,9 @@ export default function SwapModalHeader({ {formattedAmounts[Field.INPUT]} - + - {currencies[Field.INPUT]?.symbol} + {trade.inputAmount.currency.symbol} @@ -49,18 +48,18 @@ export default function SwapModalHeader({ {formattedAmounts[Field.OUTPUT]} - + - {currencies[Field.OUTPUT]?.symbol} + {trade.outputAmount.currency.symbol} - {independentField === Field.INPUT ? ( + {trade.tradeType === TradeType.EXACT_INPUT ? ( {`Output is estimated. You will receive at least `} - {slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {currencies[Field.OUTPUT]?.symbol} + {slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {trade.outputAmount.currency.symbol} {' or the transaction will revert.'} @@ -68,7 +67,7 @@ export default function SwapModalHeader({ {`Input is estimated. You will sell at most `} - {slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {currencies[Field.INPUT]?.symbol} + {slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {trade.inputAmount.currency.symbol} {' or the transaction will revert.'} diff --git a/src/components/swap/confirmPriceImpactWithoutFee.ts b/src/components/swap/confirmPriceImpactWithoutFee.ts index 982e19eec97..1b955c33862 100644 --- a/src/components/swap/confirmPriceImpactWithoutFee.ts +++ b/src/components/swap/confirmPriceImpactWithoutFee.ts @@ -1,7 +1,11 @@ -// gathers additional user consent for a high price impact import { Percent } from '@uniswap/sdk' import { ALLOWED_PRICE_IMPACT_HIGH, PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN } from '../../constants' +/** + * Given the price impact, get user confirmation. + * + * @param priceImpactWithoutFee price impact of the trade without the fee. + */ export default function confirmPriceImpactWithoutFee(priceImpactWithoutFee: Percent): boolean { if (!priceImpactWithoutFee.lessThan(PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN)) { return ( diff --git a/src/components/swap/tsconfig.json b/src/components/swap/tsconfig.json new file mode 100644 index 00000000000..638227fff6d --- /dev/null +++ b/src/components/swap/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../../tsconfig.strict.json", + "include": ["**/*"] +} \ No newline at end of file diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index ecab41d981c..0504355828f 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -224,33 +224,31 @@ export default function Swap() { !(priceImpactSeverity > 3 && !expertMode) function modalHeader() { - return ( + return trade ? ( - ) + ) : null } function modalBottom() { - return ( + return trade && realizedLPFee ? ( 2 ? 'Swap Anyway' : 'Confirm Swap'} showInverted={showInverted} severity={priceImpactSeverity} setShowInverted={setShowInverted} onSwap={onSwap} realizedLPFee={realizedLPFee} - parsedAmounts={parsedAmounts} priceImpactWithoutFee={priceImpactWithoutFee} slippageAdjustedAmounts={slippageAdjustedAmounts} trade={trade} /> - ) + ) : null } // text to show while loading From 9934e872aca7dfbae607b738d1deafbe2fccd85d Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Mon, 3 Aug 2020 13:02:49 -0500 Subject: [PATCH 09/29] stop proactively looking for gas estimates --- package.json | 4 +- src/components/Popups/index.tsx | 25 +++-- src/hooks/useGasEstimates.ts | 161 -------------------------------- src/hooks/useSwapCallback.ts | 143 +++++++++++++++------------- src/pages/Swap/index.tsx | 4 +- yarn.lock | 40 +++----- 6 files changed, 104 insertions(+), 273 deletions(-) delete mode 100644 src/hooks/useGasEstimates.ts diff --git a/package.json b/package.json index 1691aa0c06a..550cf927d75 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "lodash.flatmap": "^4.5.0", "polished": "^3.3.2", "prettier": "^1.17.0", - "qrcode.react": "^0.9.3", "qs": "^6.9.4", "react": "^16.13.1", "react-device-detect": "^1.6.2", @@ -80,8 +79,7 @@ "serve": "^11.3.0", "start-server-and-test": "^1.11.0", "styled-components": "^4.2.0", - "typescript": "^3.8.3", - "use-media": "^1.4.0" + "typescript": "^3.8.3" }, "scripts": { "start": "react-scripts start", diff --git a/src/components/Popups/index.tsx b/src/components/Popups/index.tsx index 58f68f82607..25528ccfde7 100644 --- a/src/components/Popups/index.tsx +++ b/src/components/Popups/index.tsx @@ -1,6 +1,5 @@ import React from 'react' import styled from 'styled-components' -import { useMediaLayout } from 'use-media' import { useActivePopups } from '../../state/application/hooks' import { AutoColumn } from '../Column' import PopupItem from './PopupItem' @@ -11,6 +10,11 @@ const MobilePopupWrapper = styled.div<{ height: string | number }>` height: ${({ height }) => height}; margin: ${({ height }) => (height ? '0 auto;' : 0)}; margin-bottom: ${({ height }) => (height ? '20px' : 0)}}; + + display: none; + ${({ theme }) => theme.mediaWidth.upToSmall` + display: block; + `}; ` const MobilePopupInner = styled.div` @@ -26,8 +30,8 @@ const MobilePopupInner = styled.div` ` const FixedPopupColumn = styled(AutoColumn)` - position: absolute; - top: 112px; + position: fixed; + top: 64px; right: 1rem; max-width: 355px !important; width: 100%; @@ -41,21 +45,13 @@ export default function Popups() { // get all popups const activePopups = useActivePopups() - // switch view settings on mobile - const isMobile = useMediaLayout({ maxWidth: '600px' }) - - if (!isMobile) { - return ( + return ( + <> {activePopups.map(item => ( ))} - ) - } - //mobile - else - return ( 0 ? 'fit-content' : 0}> {activePopups // reverse so new items up front @@ -66,5 +62,6 @@ export default function Popups() { ))} - ) + + ) } diff --git a/src/hooks/useGasEstimates.ts b/src/hooks/useGasEstimates.ts deleted file mode 100644 index ecb5341db23..00000000000 --- a/src/hooks/useGasEstimates.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { BigNumber } from '@ethersproject/bignumber' -import { Contract } from '@ethersproject/contracts' -import { useEffect, useMemo, useState } from 'react' -import { useBlockNumber } from '../state/application/hooks' -import { Call } from '../state/multicall/actions' -import isZero from '../utils/isZero' -import { useActiveWeb3React } from './index' - -export enum GasEstimateState { - INVALID, - LOADING, - VALID -} - -export interface EstimatableContractCall { - key: string - contract: Contract // the contract to call - methodName: string // the method to call on the contract - args: (string | string[])[] // args to pass to the call - value: string // hex ether value to send with the transaction -} - -interface SerializedEstimatableCall extends Call { - value: string - key: string -} - -function toEstimatableCall(estimatable: EstimatableContractCall): SerializedEstimatableCall { - return { - address: estimatable.contract.address, - callData: estimatable.contract.interface.encodeFunctionData( - estimatable.contract.interface.getFunction(estimatable.methodName), - estimatable.args - ), - value: estimatable.value, - key: estimatable.key - } -} - -/** - * Return the gas estimate for the given contract methods and arguments - */ -export default function useGasEstimates( - calls: (EstimatableContractCall | undefined)[] | undefined -): [GasEstimateState, BigNumber | undefined][] { - const { chainId, library, account } = useActiveWeb3React() - const lastBlockNumber = useBlockNumber() - - const [state, setState] = useState<{ - [callKey: string]: { - blockNumber: number - estimate: BigNumber | undefined - error: string | undefined - fetchingBlockNumber: number | undefined - } - }>({}) - - // clear estimates on chain change for memory usage - useEffect(() => { - if (!chainId) return - setState({}) - }, [chainId]) - - const serializedEstimatableCalls: (SerializedEstimatableCall | undefined)[] = useMemo(() => { - return ( - calls?.map(call => { - try { - return call ? toEstimatableCall(call) : undefined - } catch (error) { - return undefined - } - }) ?? [] - ) - }, [calls]) - - useEffect(() => { - if (!library || !chainId || !lastBlockNumber) return - serializedEstimatableCalls.forEach(call => { - if (!call) return - const key = call.key - if ((state[key]?.blockNumber ?? 0) >= lastBlockNumber) { - return - } - if ((state[key]?.fetchingBlockNumber ?? 0) >= lastBlockNumber) { - return - } - setState(state => { - return { - ...state, - [key]: { - ...state[key], - fetchingBlockNumber: lastBlockNumber - } - } - }) - try { - library - .estimateGas({ - from: account ?? undefined, - to: call.address, - data: call.callData, - ...(isZero(call.value) ? {} : { value: call.value }) - }) - .then(estimate => { - setState(state => { - return { - ...state, - [key]: { - blockNumber: lastBlockNumber, - error: undefined, - estimate, - fetchingBlockNumber: undefined - } - } - }) - }) - .catch(error => { - setState(state => { - return { - ...state, - [key]: { - blockNumber: lastBlockNumber, - error: error.message, - estimate: undefined, - fetchingBlockNumber: undefined - } - } - }) - }) - } catch (error) { - setState(state => { - return { - ...state, - [key]: { - blockNumber: lastBlockNumber, - estimate: undefined, - error: error.message, - fetchingBlockNumber: undefined - } - } - }) - } - }) - }, [serializedEstimatableCalls, chainId, state, lastBlockNumber, library, account]) - - return useMemo(() => { - return ( - serializedEstimatableCalls?.map(call => { - if (!call || !chainId) return [GasEstimateState.INVALID, undefined] - const result = state[call.key] - if (!result) return [GasEstimateState.LOADING, undefined] - const { estimate, error, blockNumber } = result - const loading = !error && (!estimate || blockNumber !== lastBlockNumber) - return [ - error ? GasEstimateState.INVALID : loading ? GasEstimateState.LOADING : GasEstimateState.VALID, - error ? undefined : estimate - ] - }) ?? [] - ) - }, [chainId, lastBlockNumber, serializedEstimatableCalls, state]) -} diff --git a/src/hooks/useSwapCallback.ts b/src/hooks/useSwapCallback.ts index 72e3a19acdf..7e8235f10e3 100644 --- a/src/hooks/useSwapCallback.ts +++ b/src/hooks/useSwapCallback.ts @@ -11,7 +11,6 @@ import v1SwapArguments from '../utils/v1SwapArguments' import { useActiveWeb3React } from './index' import { useV1ExchangeContract } from './useContract' import useENS from './useENS' -import useGasEstimates, { EstimatableContractCall, GasEstimateState } from './useGasEstimates' import { Version } from './useToggledVersion' export enum SwapCallbackState { @@ -25,6 +24,18 @@ interface SwapCall { parameters: SwapParameters } +interface SuccessfulCall { + call: SwapCall + gasEstimate: BigNumber +} + +interface FailedCall { + call: SwapCall + error: Error +} + +type EstimatedSwapCall = SuccessfulCall | FailedCall + /** * Returns the swap calls that can be used to make the trade * @param trade trade to execute @@ -104,25 +115,6 @@ export function useSwapCallback( const { account, chainId, library } = useActiveWeb3React() const swapCalls = useSwapCallArguments(trade, allowedSlippage, deadline, recipientAddressOrName) - const estimatableSwapCalls: EstimatableContractCall[] = useMemo( - () => - swapCalls.map(({ contract, parameters: { methodName, args, value } }) => ({ - contract, - methodName, - value, - args, - key: `${chainId}:${contract.address}:${methodName}:${allowedSlippage}` - })), - [allowedSlippage, chainId, swapCalls] - ) - const gasEstimates = useGasEstimates(estimatableSwapCalls) - const [indexOfSuccessfulEstimation, loadingGasEstimation] = useMemo( - () => [ - gasEstimates.findIndex(([, estimate]) => BigNumber.isBigNumber(estimate)), - gasEstimates.some(([state]) => state === GasEstimateState.LOADING) - ], - [gasEstimates] - ) const addTransaction = useTransactionAdder() @@ -141,45 +133,80 @@ export function useSwapCallback( } } - if (loadingGasEstimation && indexOfSuccessfulEstimation === -1) { - return { state: SwapCallbackState.LOADING, callback: null, error: null } - } - const tradeVersion = getTradeVersion(trade) - if (indexOfSuccessfulEstimation === -1) { - return { - state: SwapCallbackState.INVALID, - callback: null, - error: 'Could not compute gas estimation. Try increasing your slippage tolerance.' - } - } + return { + state: SwapCallbackState.VALID, + callback: async function onSwap(): Promise { + const estimatedCalls: EstimatedSwapCall[] = await Promise.all( + swapCalls.map(call => { + const { + parameters: { methodName, args, value }, + contract + } = call + const options = !value || isZero(value) ? {} : { value } + + return contract.estimateGas[methodName](...args, options) + .then(gasEstimate => { + return { + call, + gasEstimate + } + }) + .catch(gasError => { + console.debug('Gas estimate failed, trying eth_call to extract error', call) + + return contract.callStatic[methodName](...args, { ...options, from: account }) + .then(result => { + console.debug('Unexpected successful call after failed estimate gas', call, gasError, result) + return { call, error: new Error('Unexpected error. Please contact support.') } + }) + .catch(callError => { + console.debug('Call threw error', call, callError) + // error.reason - The Revert reason; this is what you probably care about. :) + // Additionally: + // - error.address - the contract address + // - error.args - [ BigNumber(1), BigNumber(2), BigNumber(3) ] in this case + // - error.method - "someMethod()" in this case + // - error.errorSignature - "Error(string)" (the EIP 838 sighash; supports future custom errors) + // - error.errorArgs - The arguments passed into the error (more relevant post EIP 838 custom errors) + // - error.transaction - The call transaction used + console.log( + callError.reason, + callError.address, + callError.args, + callError.method, + callError.errorSignature, + callError.errorArgs, + callError.transaction + ) + return { call, error: callError } + }) + }) + }) + ) + + // a successful estimation is a bignumber gas estimate and the next call is also a bignumber gas estimate + const indexOfSuccessfulEstimation = estimatedCalls.findIndex( + (el, ix, list): boolean => 'gasEstimate' in el && (ix === list.length - 1 || 'gasEstimate' in list[ix + 1]) + ) - // we expect failures from left to right, so throw if we see failures - // from right to left - for (let i = 0; i < gasEstimates.length - 1; i++) { - // if the FoT method fails, but the regular method does not, we should not - // use the regular method. this probably means something is wrong with the fot token. - if (BigNumber.isBigNumber(gasEstimates[i][1]) && !BigNumber.isBigNumber(gasEstimates[i + 1][1])) { - return { - state: SwapCallbackState.INVALID, - callback: null, - error: 'Unexpected error. Try increasing your slippage tolerance. Otherwise, contact support.' + if (indexOfSuccessfulEstimation === -1) { + const lastCall = estimatedCalls[estimatedCalls.length - 1] + if ('error' in lastCall) throw lastCall.error + throw new Error('Unexpected error. Please contact support.') } - } - } - return { - state: SwapCallbackState.VALID, - callback: async function onSwap() { const { - contract, - parameters: { methodName, args, value } - } = swapCalls[indexOfSuccessfulEstimation] - const safeGasEstimate = gasEstimates[indexOfSuccessfulEstimation][1] + call: { + contract, + parameters: { methodName, args, value } + }, + gasEstimate + } = estimatedCalls[indexOfSuccessfulEstimation] as SuccessfulCall return contract[methodName](...args, { - gasLimit: safeGasEstimate && calculateGasMargin(safeGasEstimate), + gasLimit: calculateGasMargin(gasEstimate), ...(value && !isZero(value) ? { value } : {}) }) .then((response: any) => { @@ -221,17 +248,5 @@ export function useSwapCallback( }, error: null } - }, [ - trade, - library, - account, - chainId, - recipient, - loadingGasEstimation, - indexOfSuccessfulEstimation, - recipientAddressOrName, - gasEstimates, - swapCalls, - addTransaction - ]) + }, [trade, library, account, chainId, recipient, recipientAddressOrName, swapCalls, addTransaction]) } diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index 0504355828f..b20e17f2b19 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -39,9 +39,9 @@ import { } from '../../state/swap/hooks' import { useExpertModeManager, + useTokenWarningDismissal, useUserDeadline, - useUserSlippageTolerance, - useTokenWarningDismissal + useUserSlippageTolerance } from '../../state/user/hooks' import { CursorPointer, LinkStyledButton, TYPE } from '../../theme' import { maxAmountSpend } from '../../utils/maxAmountSpend' diff --git a/yarn.lock b/yarn.lock index b9fcaaab681..aa34b629d26 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1335,7 +1335,7 @@ "@ethersproject/logger" "^5.0.0" "@ethersproject/properties" "^5.0.0" -"@ethersproject/address@5.0.0-beta.134": +"@ethersproject/address@5.0.0-beta.134", "@ethersproject/address@>=5.0.0-beta.128", "@ethersproject/address@>=5.0.0-beta.134": version "5.0.0-beta.134" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.0.0-beta.134.tgz#9c1790c87b763dc547ac12e2dbc9fa78d0799a71" integrity sha512-FHhUVJTUIg2pXvOOhIt8sB1cQbcwrzZKzf9CPV7JM1auli20nGoYhyMFYGK7u++GXzTMJduIkU1OwlIBupewDw== @@ -1347,7 +1347,7 @@ "@ethersproject/rlp" ">=5.0.0-beta.126" bn.js "^4.4.0" -"@ethersproject/address@>=5.0.0-beta.128", "@ethersproject/address@>=5.0.0-beta.134", "@ethersproject/address@^5.0.0": +"@ethersproject/address@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.0.2.tgz#80d0ddfb7d4bd0d32657747fa4bdd2defef2e00a" integrity sha512-+rz26RKj7ujGfQynys4V9VJRbR+wpC6eL8F22q3raWMH3152Ha31GwJPWzxE/bEA+43M/zTNVwY0R53gn53L2Q== @@ -1374,7 +1374,7 @@ "@ethersproject/bytes" "^5.0.0" "@ethersproject/properties" "^5.0.0" -"@ethersproject/bignumber@5.0.0-beta.138": +"@ethersproject/bignumber@5.0.0-beta.138", "@ethersproject/bignumber@>=5.0.0-beta.130", "@ethersproject/bignumber@>=5.0.0-beta.138": version "5.0.0-beta.138" resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.0.0-beta.138.tgz#a635f2f9a6f1b262cc38e1c7ee561fb13d79fda4" integrity sha512-DTlOEJw6jAFz7/qkY8p4mPGGHVwgYUUC5rk1Pbg2/gR/gHPFDim+uBY+XGavh0QSWd1i3hXKafVPre92j4fs5g== @@ -1384,7 +1384,7 @@ "@ethersproject/properties" ">=5.0.0-beta.131" bn.js "^4.4.0" -"@ethersproject/bignumber@>=5.0.0-beta.130", "@ethersproject/bignumber@>=5.0.0-beta.138", "@ethersproject/bignumber@^5.0.0": +"@ethersproject/bignumber@^5.0.0": version "5.0.5" resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.0.5.tgz#31bd7e75aad46ace345fae69b1f5bb120906af1b" integrity sha512-24ln7PV0g8ZzjcVZiLW9Wod0i+XCmK6zKkAaxw5enraTIT1p7gVOcSXFSzNQ9WYAwtiFQPvvA+TIO2oEITZNJA== @@ -1400,14 +1400,14 @@ dependencies: "@ethersproject/logger" "^5.0.0" -"@ethersproject/constants@5.0.0-beta.133": +"@ethersproject/constants@5.0.0-beta.133", "@ethersproject/constants@>=5.0.0-beta.128": version "5.0.0-beta.133" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.0.0-beta.133.tgz#af4ccd7232f3ed73aebe066a695ede32c497a394" integrity sha512-VCTpk3AF00mlWQw1vg+fI6qCo0qO5EVWK574t4HNBKW6X748jc9UJPryKUz9JgZ64ZQupyLM92wHilsG/YTpNQ== dependencies: "@ethersproject/bignumber" ">=5.0.0-beta.130" -"@ethersproject/constants@>=5.0.0-beta.128", "@ethersproject/constants@^5.0.0": +"@ethersproject/constants@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.0.2.tgz#f7ac0b320e2bbec1a5950da075015f8bc4e8fed1" integrity sha512-nNoVlNP6bgpog7pQ2EyD1xjlaXcy1Cl4kK5v1KoskHj58EtB6TK8M8AFGi3GgHTdMldfT4eN3OsoQ/CdOTVNFA== @@ -1515,14 +1515,14 @@ resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.0.4.tgz#09fa4765b5691233e3afb6617cb38a700f9dd2e4" integrity sha512-alA2LiAy1LdQ/L1SA9ajUC7MvGAEQLsICEfKK4erX5qhkXE1LwLSPIzobtOWFsMHf2yrXGKBLnnpuVHprI3sAw== -"@ethersproject/networks@5.0.0-beta.136": +"@ethersproject/networks@5.0.0-beta.136", "@ethersproject/networks@>=5.0.0-beta.129": version "5.0.0-beta.136" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.0.0-beta.136.tgz#8d6fdae297c0ce7ebe1893e601c4a57f7e38dc7a" integrity sha512-skMDix0LVOhpfCItbg6Z1fXLK6vAtUkzAKaslDxVczEPUvjQ0kiJ5ceurmL+ROOO1owURGxUac5BrIarbO7Zgw== dependencies: "@ethersproject/logger" ">=5.0.0-beta.129" -"@ethersproject/networks@>=5.0.0-beta.129", "@ethersproject/networks@^5.0.0": +"@ethersproject/networks@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.0.2.tgz#a49e82cf071e3618e87e3c5d69fdbcf54dc6766c" integrity sha512-T7HVd62D4izNU2tDHf6xUDo7k4JOGX4Lk7vDmVcDKrepSWwL2OmGWrqSlkRe2a1Dnz4+1VPE6fb6+KsmSRe82g== @@ -1634,7 +1634,7 @@ "@ethersproject/sha2" "^5.0.0" "@ethersproject/strings" "^5.0.0" -"@ethersproject/strings@5.0.0-beta.136": +"@ethersproject/strings@5.0.0-beta.136", "@ethersproject/strings@>=5.0.0-beta.130": version "5.0.0-beta.136" resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.0-beta.136.tgz#053cbf4f9f96a7537cbc50300597f2d707907f51" integrity sha512-Hb9RvTrgGcOavHvtQZz+AuijB79BO3g1cfF2MeMfCU9ID4j3mbZv/olzDMS2pK9r4aERJpAS94AmlWzCgoY2LQ== @@ -1643,7 +1643,7 @@ "@ethersproject/constants" ">=5.0.0-beta.128" "@ethersproject/logger" ">=5.0.0-beta.129" -"@ethersproject/strings@>=5.0.0-beta.130", "@ethersproject/strings@^5.0.0": +"@ethersproject/strings@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.2.tgz#1753408c3c889813fd0992abd76393e3e47a2619" integrity sha512-oNa+xvSqsFU96ndzog0IBTtsRFGOqGpzrXJ7shXLBT7juVeSEyZA/sYs0DMZB5mJ9FEjHdZKxR/rTyBY91vuXg== @@ -12063,7 +12063,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.4" -prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -12159,19 +12159,6 @@ q@^1.1.2: resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= -qr.js@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f" - integrity sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8= - -qrcode.react@^0.9.3: - version "0.9.3" - resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-0.9.3.tgz#91de1287912bdc5ccfb3b091737b828d6ced60c5" - integrity sha512-gGd30Ez7cmrKxyN2M3nueaNLk/f9J7NDRgaD5fVgxGpPLsYGWMn9UQ+XnDpv95cfszTQTdaf4QGLNMf3xU0hmw== - dependencies: - prop-types "^15.6.0" - qr.js "0.0.0" - qrcode@1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.4.4.tgz#f0c43568a7e7510a55efc3b88d9602f71963ea83" @@ -14647,11 +14634,6 @@ use-callback-ref@^1.2.1, use-callback-ref@^1.2.3: resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.4.tgz#d86d1577bfd0b955b6e04aaf5971025f406bea3c" integrity sha512-rXpsyvOnqdScyied4Uglsp14qzag1JIemLeTWGKbwpotWht57hbP78aNT+Q4wdFKQfQibbUX4fb6Qb4y11aVOQ== -use-media@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/use-media/-/use-media-1.4.0.tgz#e777bf1f382a7aacabbd1f9ce3da2b62e58b2a98" - integrity sha512-XsgyUAf3nhzZmEfhc5MqLHwyaPjs78bgytpVJ/xDl0TF4Bptf3vEpBNBBT/EIKOmsOc8UbuECq3mrP3mt1QANA== - use-sidecar@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.2.tgz#e72f582a75842f7de4ef8becd6235a4720ad8af6" From b67d5aa8b6b48335d710be018edba5e3b45dde93 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Mon, 3 Aug 2020 15:18:29 -0500 Subject: [PATCH 10/29] improve some retry stuff, show errors inline --- src/components/ConfirmationModal/index.tsx | 12 +-- src/components/swap/SwapModalFooter.tsx | 8 +- .../swap/{styleds.ts => styleds.tsx} | 11 ++- src/hooks/useSwapCallback.ts | 46 ++++----- src/pages/Swap/index.tsx | 92 +++++++++++------- src/state/multicall/updater.tsx | 97 ++++++++++++------- src/utils/retry.test.ts | 14 ++- src/utils/retry.ts | 47 +++++++-- 8 files changed, 216 insertions(+), 111 deletions(-) rename src/components/swap/{styleds.ts => styleds.tsx} (85%) diff --git a/src/components/ConfirmationModal/index.tsx b/src/components/ConfirmationModal/index.tsx index 93be0305e21..dda0c230a73 100644 --- a/src/components/ConfirmationModal/index.tsx +++ b/src/components/ConfirmationModal/index.tsx @@ -38,7 +38,7 @@ const CustomLightSpinner = styled(Spinner)<{ size: string }>` interface ConfirmationModalProps { isOpen: boolean onDismiss: () => void - hash: string + hash: string | undefined topContent: () => React.ReactNode bottomContent: () => React.ReactNode attemptingTxn: boolean @@ -59,10 +59,10 @@ export default function ConfirmationModal({ const { chainId } = useActiveWeb3React() const theme = useContext(ThemeContext) - const transactionBroadcast = !!hash + const isTransactionBroadcast = !!hash // waiting for user to confirm/reject tx _or_ showing info on a tx that has been broadcast - if (attemptingTxn || transactionBroadcast) { + if (attemptingTxn || isTransactionBroadcast) { return ( @@ -72,7 +72,7 @@ export default function ConfirmationModal({ - {transactionBroadcast ? ( + {isTransactionBroadcast ? ( ) : ( @@ -80,7 +80,7 @@ export default function ConfirmationModal({ - {transactionBroadcast ? 'Transaction Submitted' : 'Waiting For Confirmation'} + {isTransactionBroadcast ? 'Transaction Submitted' : 'Waiting For Confirmation'} @@ -88,7 +88,7 @@ export default function ConfirmationModal({ - {transactionBroadcast ? ( + {isTransactionBroadcast ? ( <> diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index bfec8898660..8b7ba78107e 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -11,7 +11,7 @@ import { AutoColumn } from '../Column' import QuestionHelper from '../QuestionHelper' import { AutoRow, RowBetween, RowFixed } from '../Row' import FormattedPriceImpact from './FormattedPriceImpact' -import { StyledBalanceMaxMini } from './styleds' +import { StyledBalanceMaxMini, SwapCallbackError } from './styleds' export default function SwapModalFooter({ trade, @@ -21,7 +21,8 @@ export default function SwapModalFooter({ slippageAdjustedAmounts, onSwap, realizedLPFee, - priceImpactWithoutFee + priceImpactWithoutFee, + swapErrorMessage }: { trade: Trade showInverted: boolean @@ -31,6 +32,7 @@ export default function SwapModalFooter({ onSwap: () => any realizedLPFee: CurrencyAmount priceImpactWithoutFee?: Percent + swapErrorMessage: string | undefined }) { const theme = useContext(ThemeContext) @@ -112,6 +114,8 @@ export default function SwapModalFooter({ {severity > 2 ? 'Swap Anyway' : 'Confirm Swap'} + + {swapErrorMessage ? : null} ) diff --git a/src/components/swap/styleds.ts b/src/components/swap/styleds.tsx similarity index 85% rename from src/components/swap/styleds.ts rename to src/components/swap/styleds.tsx index 45b4b8ac9f7..9ee105a3edc 100644 --- a/src/components/swap/styleds.ts +++ b/src/components/swap/styleds.tsx @@ -1,4 +1,6 @@ import { darken } from 'polished' +import React from 'react' +import { AlertTriangle } from 'react-feather' import styled, { css } from 'styled-components' import { Text } from 'rebass' @@ -95,7 +97,7 @@ export const Dots = styled.span` } ` -export const SwapCallbackError = styled.div` +const SwapCallbackErrorInner = styled.div` background-color: ${({ theme }) => theme.red1}; border-radius: 1rem; padding: 1rem; @@ -105,3 +107,10 @@ export const SwapCallbackError = styled.div` border: 1px solid ${({ theme }) => darken(0.1, theme.red1)}; color: ${({ theme }) => theme.white}; ` +export function SwapCallbackError({ error }: { error: string }) { + return ( + + {error} + + ) +} diff --git a/src/hooks/useSwapCallback.ts b/src/hooks/useSwapCallback.ts index 7e8235f10e3..14d5b321ff6 100644 --- a/src/hooks/useSwapCallback.ts +++ b/src/hooks/useSwapCallback.ts @@ -104,6 +104,8 @@ function useSwapCallArguments( }, [account, allowedSlippage, chainId, deadline, library, recipient, trade, v1Exchange]) } +const DEFAULT_FAILED_SWAP_ERROR = 'Unexpected error. Please try again or contact support.' + // returns a function that will execute a swap, if the parameters are all valid // and the user has approved the slippage adjusted input amount for the trade export function useSwapCallback( @@ -156,10 +158,10 @@ export function useSwapCallback( .catch(gasError => { console.debug('Gas estimate failed, trying eth_call to extract error', call) - return contract.callStatic[methodName](...args, { ...options, from: account }) + return contract.callStatic[methodName](...args, options) .then(result => { console.debug('Unexpected successful call after failed estimate gas', call, gasError, result) - return { call, error: new Error('Unexpected error. Please contact support.') } + return { call, error: new Error(DEFAULT_FAILED_SWAP_ERROR) } }) .catch(callError => { console.debug('Call threw error', call, callError) @@ -171,30 +173,29 @@ export function useSwapCallback( // - error.errorSignature - "Error(string)" (the EIP 838 sighash; supports future custom errors) // - error.errorArgs - The arguments passed into the error (more relevant post EIP 838 custom errors) // - error.transaction - The call transaction used - console.log( - callError.reason, - callError.address, - callError.args, - callError.method, - callError.errorSignature, - callError.errorArgs, - callError.transaction - ) - return { call, error: callError } + let errorMessage: string = DEFAULT_FAILED_SWAP_ERROR + switch (callError.reason) { + case 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT': + errorMessage = + 'The transaction will not succeed either due to price movement or fee on transfer. Try increasing your slippage tolerance.' + break + } + return { call, error: new Error(errorMessage) } }) }) }) ) // a successful estimation is a bignumber gas estimate and the next call is also a bignumber gas estimate - const indexOfSuccessfulEstimation = estimatedCalls.findIndex( - (el, ix, list): boolean => 'gasEstimate' in el && (ix === list.length - 1 || 'gasEstimate' in list[ix + 1]) + const successfulEstimation = estimatedCalls.find( + (el, ix, list): el is SuccessfulCall => + 'gasEstimate' in el && (ix === list.length - 1 || 'gasEstimate' in list[ix + 1]) ) - if (indexOfSuccessfulEstimation === -1) { - const lastCall = estimatedCalls[estimatedCalls.length - 1] - if ('error' in lastCall) throw lastCall.error - throw new Error('Unexpected error. Please contact support.') + if (!successfulEstimation) { + const errorCalls = estimatedCalls.filter((call): call is FailedCall => 'error' in call) + if (errorCalls.length > 0) throw errorCalls[errorCalls.length - 1].error + throw new Error(DEFAULT_FAILED_SWAP_ERROR) } const { @@ -203,7 +204,7 @@ export function useSwapCallback( parameters: { methodName, args, value } }, gasEstimate - } = estimatedCalls[indexOfSuccessfulEstimation] as SuccessfulCall + } = successfulEstimation return contract[methodName](...args, { gasLimit: calculateGasMargin(gasEstimate), @@ -238,11 +239,10 @@ export function useSwapCallback( // if the user rejected the tx, pass this along if (error?.code === 4001) { throw error - } - // otherwise, the error was unexpected and we need to convey that - else { + } else { + // otherwise, the error was unexpected and we need to convey that console.error(`Swap failed`, error, methodName, args, value) - throw new Error('An error occurred while swapping. Please contact support.') + throw new Error(DEFAULT_FAILED_SWAP_ERROR) } }) }, diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index b20e17f2b19..0c5a8b743c0 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -1,6 +1,6 @@ import { CurrencyAmount, JSBI } from '@uniswap/sdk' import React, { useCallback, useContext, useEffect, useState } from 'react' -import { AlertTriangle, ArrowDown } from 'react-feather' +import { ArrowDown } from 'react-feather' import ReactGA from 'react-ga' import { Text } from 'rebass' import { ThemeContext } from 'styled-components' @@ -60,7 +60,7 @@ export default function Swap() { // for expert mode const toggleSettings = useToggleSettingsMenu() - const [expertMode] = useExpertModeManager() + const [isExpertMode] = useExpertModeManager() // get custom setting values for user const [deadline] = useUserDeadline() @@ -126,9 +126,17 @@ export default function Swap() { ) // modal and loading - const [showConfirm, setShowConfirm] = useState(false) // show confirmation modal - const [attemptingTxn, setAttemptingTxn] = useState(false) // waiting for user confirmaion/rejection - const [txHash, setTxHash] = useState('') + const [{ showConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{ + showConfirm: boolean + attemptingTxn: boolean + swapErrorMessage: string | undefined + txHash: string | undefined + }>({ + showConfirm: false, + attemptingTxn: false, + swapErrorMessage: undefined, + txHash: undefined + }) const formattedAmounts = { [independentField]: typedValue, @@ -178,11 +186,10 @@ export default function Swap() { if (!swapCallback) { return } - setAttemptingTxn(true) + setSwapState({ attemptingTxn: true, showConfirm, swapErrorMessage: undefined, txHash: undefined }) swapCallback() .then(hash => { - setAttemptingTxn(false) - setTxHash(hash) + setSwapState({ attemptingTxn: false, showConfirm, swapErrorMessage: undefined, txHash: hash }) ReactGA.event({ category: 'Swap', @@ -200,11 +207,7 @@ export default function Swap() { }) }) .catch(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) - } + setSwapState({ attemptingTxn: false, showConfirm, swapErrorMessage: error.message, txHash: undefined }) }) } @@ -221,7 +224,7 @@ export default function Swap() { (approval === ApprovalState.NOT_APPROVED || approval === ApprovalState.PENDING || (approvalSubmitted && approval === ApprovalState.APPROVED)) && - !(priceImpactSeverity > 3 && !expertMode) + !(priceImpactSeverity > 3 && !isExpertMode) function modalHeader() { return trade ? ( @@ -247,6 +250,7 @@ export default function Swap() { priceImpactWithoutFee={priceImpactWithoutFee} slippageAdjustedAmounts={slippageAdjustedAmounts} trade={trade} + swapErrorMessage={swapErrorMessage} /> ) : null } @@ -261,6 +265,19 @@ export default function Swap() { const showWarning = (!dismissedToken0 && !!currencies[Field.INPUT]) || (!dismissedToken1 && !!currencies[Field.OUTPUT]) + const onConfirmDismiss = useCallback(() => { + setSwapState({ + showConfirm: false, + attemptingTxn: false, + txHash: undefined, + swapErrorMessage: undefined + }) + // if there was a tx hash, we want to clear the input + if (txHash) { + onUserInput(Field.INPUT, '') + } + }, [onUserInput, txHash]) + return ( <> {showWarning && } @@ -270,14 +287,7 @@ export default function Swap() { { - setShowConfirm(false) - // if there was a tx hash, we want to clear the input - if (txHash) { - onUserInput(Field.INPUT, '') - } - setTxHash('') - }} + onDismiss={onConfirmDismiss} attemptingTxn={attemptingTxn} hash={txHash} topContent={modalHeader} @@ -409,15 +419,26 @@ export default function Swap() { { - expertMode ? onSwap() : setShowConfirm(true) + if (isExpertMode) { + onSwap() + } else { + setSwapState({ + attemptingTxn: false, + swapErrorMessage: undefined, + showConfirm: true, + txHash: undefined + }) + } }} width="48%" id="swap-button" - disabled={!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !expertMode)} + disabled={ + !isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !isExpertMode) + } error={isValid && priceImpactSeverity > 2} > - {priceImpactSeverity > 3 && !expertMode + {priceImpactSeverity > 3 && !isExpertMode ? `Price Impact High` : `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`} @@ -426,26 +447,31 @@ export default function Swap() { ) : ( { - expertMode ? onSwap() : setShowConfirm(true) + if (isExpertMode) { + onSwap() + } else { + setSwapState({ + attemptingTxn: false, + swapErrorMessage: undefined, + showConfirm: true, + txHash: undefined + }) + } }} id="swap-button" - disabled={!isValid || (priceImpactSeverity > 3 && !expertMode) || !!swapCallbackError} + disabled={!isValid || (priceImpactSeverity > 3 && !isExpertMode) || !!swapCallbackError} error={isValid && priceImpactSeverity > 2 && !swapCallbackError} > {swapInputError ? swapInputError - : priceImpactSeverity > 3 && !expertMode + : priceImpactSeverity > 3 && !isExpertMode ? `Price Impact Too High` : `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`} )} - {swapCallbackError && approval === ApprovalState.APPROVED && !swapInputError && trade ? ( - - {swapCallbackError} - - ) : null} + {isExpertMode && swapErrorMessage ? : null} {betterTradeLinkVersion && } diff --git a/src/state/multicall/updater.tsx b/src/state/multicall/updater.tsx index e0a51571dda..a2b52c341b7 100644 --- a/src/state/multicall/updater.tsx +++ b/src/state/multicall/updater.tsx @@ -1,11 +1,11 @@ import { Contract } from '@ethersproject/contracts' -import { useEffect, useMemo } from 'react' +import { useEffect, useMemo, useRef } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useActiveWeb3React } from '../../hooks' import { useMulticallContract } from '../../hooks/useContract' import useDebounce from '../../hooks/useDebounce' import chunkArray from '../../utils/chunkArray' -import { retry } from '../../utils/retry' +import { CancelledError, retry } from '../../utils/retry' import { useBlockNumber } from '../application/hooks' import { AppDispatch, AppState } from '../index' import { @@ -30,10 +30,16 @@ async function fetchChunk( chunk: Call[], minBlockNumber: number ): Promise<{ results: string[]; blockNumber: number }> { - const [resultsBlockNumber, returnData] = await multicallContract.aggregate( - chunk.map(obj => [obj.address, obj.callData]) - ) + console.debug('Fetching chunk', multicallContract, chunk, minBlockNumber) + let resultsBlockNumber, returnData + try { + ;[resultsBlockNumber, returnData] = await multicallContract.aggregate(chunk.map(obj => [obj.address, obj.callData])) + } catch (error) { + console.debug('Failed to fetch chunk inside retry', error) + throw error + } if (resultsBlockNumber.toNumber() < minBlockNumber) { + console.debug(`Fetched results for old block number: ${resultsBlockNumber.toString()} vs. ${minBlockNumber}`) throw new Error('Fetched for old block number') } return { results: returnData, blockNumber: resultsBlockNumber.toNumber() } @@ -112,6 +118,7 @@ export default function Updater() { const latestBlockNumber = useBlockNumber() const { chainId } = useActiveWeb3React() const multicallContract = useMulticallContract() + const cancellations = useRef<{ blockNumber: number; cancellations: (() => void)[] }>() const listeningKeys: { [callKey: string]: number } = useMemo(() => { return activeListeningKeys(debouncedListeners, chainId) @@ -134,6 +141,10 @@ export default function Updater() { const chunkedCalls = chunkArray(calls, CALL_CHUNK_SIZE) + if (cancellations.current?.blockNumber !== latestBlockNumber) { + cancellations.current?.cancellations?.forEach(c => c()) + } + dispatch( fetchingMulticallResults({ calls, @@ -142,38 +153,52 @@ export default function Updater() { }) ) - chunkedCalls.forEach((chunk, index) => - // todo: cancel retries when the block number updates - retry(() => fetchChunk(multicallContract, chunk, latestBlockNumber), { n: 10, minWait: 2500, maxWait: 5000 }) - .then(({ results: returnData, blockNumber: fetchBlockNumber }) => { - // accumulates the length of all previous indices - const firstCallKeyIndex = chunkedCalls.slice(0, index).reduce((memo, curr) => memo + curr.length, 0) - const lastCallKeyIndex = firstCallKeyIndex + returnData.length - - dispatch( - updateMulticallResults({ - chainId, - results: outdatedCallKeys - .slice(firstCallKeyIndex, lastCallKeyIndex) - .reduce<{ [callKey: string]: string | null }>((memo, callKey, i) => { - memo[callKey] = returnData[i] ?? null - return memo - }, {}), - blockNumber: fetchBlockNumber - }) - ) - }) - .catch((error: any) => { - console.error('Failed to fetch multicall chunk', chunk, chainId, error) - dispatch( - errorFetchingMulticallResults({ - calls: chunk, - chainId, - fetchingBlockNumber: latestBlockNumber - }) - ) + cancellations.current = { + blockNumber: latestBlockNumber, + cancellations: chunkedCalls.map((chunk, index) => { + const { cancel, promise } = retry(() => fetchChunk(multicallContract, chunk, latestBlockNumber), { + n: 10, + minWait: 1000, + maxWait: 2000 }) - ) + promise + .then(({ results: returnData, blockNumber: fetchBlockNumber }) => { + cancellations.current = { cancellations: [], blockNumber: latestBlockNumber } + + // accumulates the length of all previous indices + const firstCallKeyIndex = chunkedCalls.slice(0, index).reduce((memo, curr) => memo + curr.length, 0) + const lastCallKeyIndex = firstCallKeyIndex + returnData.length + + dispatch( + updateMulticallResults({ + chainId, + results: outdatedCallKeys + .slice(firstCallKeyIndex, lastCallKeyIndex) + .reduce<{ [callKey: string]: string | null }>((memo, callKey, i) => { + memo[callKey] = returnData[i] ?? null + return memo + }, {}), + blockNumber: fetchBlockNumber + }) + ) + }) + .catch((error: any) => { + if (error instanceof CancelledError) { + console.debug('Cancelled fetch for blockNumber', latestBlockNumber) + return + } + console.error('Failed to fetch multicall chunk', chunk, chainId, error) + dispatch( + errorFetchingMulticallResults({ + calls: chunk, + chainId, + fetchingBlockNumber: latestBlockNumber + }) + ) + }) + return cancel + }) + } }, [chainId, multicallContract, dispatch, serializedOutdatedCallKeys, latestBlockNumber]) return null diff --git a/src/utils/retry.test.ts b/src/utils/retry.test.ts index 788dc55bd57..a049935ea88 100644 --- a/src/utils/retry.test.ts +++ b/src/utils/retry.test.ts @@ -12,15 +12,21 @@ describe('retry', () => { } it('works after one fail', async () => { - await expect(retry(makeFn(1, 'abc'), { n: 3, maxWait: 0, minWait: 0 })).resolves.toEqual('abc') + await expect(retry(makeFn(1, 'abc'), { n: 3, maxWait: 0, minWait: 0 }).promise).resolves.toEqual('abc') }) it('works after two fails', async () => { - await expect(retry(makeFn(2, 'abc'), { n: 3, maxWait: 0, minWait: 0 })).resolves.toEqual('abc') + await expect(retry(makeFn(2, 'abc'), { n: 3, maxWait: 0, minWait: 0 }).promise).resolves.toEqual('abc') }) it('throws if too many fails', async () => { - await expect(retry(makeFn(4, 'abc'), { n: 3, maxWait: 0, minWait: 0 })).rejects.toThrow('failure') + await expect(retry(makeFn(4, 'abc'), { n: 3, maxWait: 0, minWait: 0 }).promise).rejects.toThrow('failure') + }) + + it('cancel causes promise to reject', async () => { + const { promise, cancel } = retry(makeFn(2, 'abc'), { n: 3, minWait: 100, maxWait: 100 }) + cancel() + await expect(promise).rejects.toThrow('Cancelled') }) async function checkTime(fn: () => Promise, min: number, max: number) { @@ -36,7 +42,7 @@ describe('retry', () => { for (let i = 0; i < 10; i++) { promises.push( checkTime( - () => expect(retry(makeFn(4, 'abc'), { n: 3, maxWait: 100, minWait: 50 })).rejects.toThrow('failure'), + () => expect(retry(makeFn(4, 'abc'), { n: 3, maxWait: 100, minWait: 50 }).promise).rejects.toThrow('failure'), 150, 305 ) diff --git a/src/utils/retry.ts b/src/utils/retry.ts index 9a92cb5eb07..1a0fb54f605 100644 --- a/src/utils/retry.ts +++ b/src/utils/retry.ts @@ -6,6 +6,12 @@ function waitRandom(min: number, max: number): Promise { return wait(min + Math.round(Math.random() * Math.max(0, max - min))) } +export class CancelledError extends Error { + constructor() { + super('Cancelled') + } +} + /** * Retries the function that returns the promise until the promise successfully resolves up to n retries * @param fn function to retry @@ -13,13 +19,42 @@ function waitRandom(min: number, max: number): Promise { * @param minWait min wait between retries in ms * @param maxWait max wait between retries in ms */ -// todo: support cancelling the retry export function retry( fn: () => Promise, - { n = 3, minWait = 500, maxWait = 1000 }: { n?: number; minWait?: number; maxWait?: number } = {} -): Promise { - return fn().catch(error => { - if (n === 0) throw error - return waitRandom(minWait, maxWait).then(() => retry(fn, { n: n - 1, minWait, maxWait })) + { n, minWait, maxWait }: { n: number; minWait: number; maxWait: number } +): { promise: Promise; cancel: () => void } { + let cancelled = false + let completed = false + let rejectCancelled: (error: Error) => void + const promise = new Promise(async (resolve, reject) => { + rejectCancelled = reject + while (true) { + let result: T + try { + result = await fn() + if (!cancelled) { + resolve(result) + completed = true + } + break + } catch (error) { + if (!cancelled && n <= 0) { + reject(error) + completed = true + break + } + n-- + } + await waitRandom(minWait, maxWait) + } }) + return { + promise, + cancel: () => { + if (completed) return + cancelled = true + completed = true + rejectCancelled(new CancelledError()) + } + } } From 7d83a2e7c35f7f9be15fe932d20769ee6b857efb Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Mon, 3 Aug 2020 15:25:04 -0500 Subject: [PATCH 11/29] add another retry test --- src/utils/retry.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/utils/retry.test.ts b/src/utils/retry.test.ts index a049935ea88..a0c30ee2c44 100644 --- a/src/utils/retry.test.ts +++ b/src/utils/retry.test.ts @@ -29,6 +29,13 @@ describe('retry', () => { await expect(promise).rejects.toThrow('Cancelled') }) + it('cancel no-op after complete', async () => { + const { promise, cancel } = retry(makeFn(0, 'abc'), { n: 3, minWait: 100, maxWait: 100 }) + // defer + setTimeout(cancel, 0) + await expect(promise).resolves.toEqual('abc') + }) + async function checkTime(fn: () => Promise, min: number, max: number) { const time = new Date().getTime() await fn() From f7ce0f116372b4e391eafc90c368a1287d41380f Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Mon, 3 Aug 2020 18:58:52 -0500 Subject: [PATCH 12/29] latest ethers --- package.json | 12 +- src/components/swap/styleds.tsx | 1 + src/hooks/useSwapCallback.ts | 4 +- src/state/multicall/updater.tsx | 10 +- src/utils/retry.test.ts | 12 +- src/utils/retry.ts | 17 ++- yarn.lock | 227 ++++---------------------------- 7 files changed, 59 insertions(+), 224 deletions(-) diff --git a/package.json b/package.json index 550cf927d75..b47fa3f7a9b 100644 --- a/package.json +++ b/package.json @@ -4,17 +4,6 @@ "homepage": ".", "private": true, "devDependencies": { - "@ethersproject/address": "5.0.0-beta.134", - "@ethersproject/bignumber": "5.0.0-beta.138", - "@ethersproject/constants": "5.0.0-beta.133", - "@ethersproject/contracts": "5.0.0-beta.151", - "@ethersproject/experimental": "5.0.0-beta.141", - "@ethersproject/networks": "5.0.0-beta.136", - "@ethersproject/providers": "5.0.0-beta.162", - "@ethersproject/solidity": "5.0.2", - "@ethersproject/strings": "5.0.0-beta.136", - "@ethersproject/units": "5.0.0-beta.132", - "@ethersproject/wallet": "5.0.0-beta.141", "@popperjs/core": "^2.4.4", "@reach/dialog": "^0.10.3", "@reach/portal": "^0.10.3", @@ -52,6 +41,7 @@ "eslint-plugin-prettier": "^3.1.3", "eslint-plugin-react": "^7.19.0", "eslint-plugin-react-hooks": "^4.0.0", + "ethers": "^5.0.7", "i18next": "^15.0.9", "i18next-browser-languagedetector": "^3.0.1", "i18next-xhr-backend": "^2.0.1", diff --git a/src/components/swap/styleds.tsx b/src/components/swap/styleds.tsx index 9ee105a3edc..40acef40782 100644 --- a/src/components/swap/styleds.tsx +++ b/src/components/swap/styleds.tsx @@ -104,6 +104,7 @@ const SwapCallbackErrorInner = styled.div` display: flex; align-items: center; margin-top: 1rem; + width: 100%; border: 1px solid ${({ theme }) => darken(0.1, theme.red1)}; color: ${({ theme }) => theme.white}; ` diff --git a/src/hooks/useSwapCallback.ts b/src/hooks/useSwapCallback.ts index 14d5b321ff6..1160383ab61 100644 --- a/src/hooks/useSwapCallback.ts +++ b/src/hooks/useSwapCallback.ts @@ -208,7 +208,7 @@ export function useSwapCallback( return contract[methodName](...args, { gasLimit: calculateGasMargin(gasEstimate), - ...(value && !isZero(value) ? { value } : {}) + ...(value && !isZero(value) ? { value, from: account } : { from: account }) }) .then((response: any) => { const inputSymbol = trade.inputAmount.currency.symbol @@ -238,7 +238,7 @@ export function useSwapCallback( .catch((error: any) => { // if the user rejected the tx, pass this along if (error?.code === 4001) { - throw error + throw new Error('Transaction rejected.') } else { // otherwise, the error was unexpected and we need to convey that console.error(`Swap failed`, error, methodName, args, value) diff --git a/src/state/multicall/updater.tsx b/src/state/multicall/updater.tsx index a2b52c341b7..a360f7652b6 100644 --- a/src/state/multicall/updater.tsx +++ b/src/state/multicall/updater.tsx @@ -5,7 +5,7 @@ import { useActiveWeb3React } from '../../hooks' import { useMulticallContract } from '../../hooks/useContract' import useDebounce from '../../hooks/useDebounce' import chunkArray from '../../utils/chunkArray' -import { CancelledError, retry } from '../../utils/retry' +import { CancelledError, retry, RetryableError } from '../../utils/retry' import { useBlockNumber } from '../application/hooks' import { AppDispatch, AppState } from '../index' import { @@ -40,7 +40,7 @@ async function fetchChunk( } if (resultsBlockNumber.toNumber() < minBlockNumber) { console.debug(`Fetched results for old block number: ${resultsBlockNumber.toString()} vs. ${minBlockNumber}`) - throw new Error('Fetched for old block number') + throw new RetryableError('Fetched for old block number') } return { results: returnData, blockNumber: resultsBlockNumber.toNumber() } } @@ -157,9 +157,9 @@ export default function Updater() { blockNumber: latestBlockNumber, cancellations: chunkedCalls.map((chunk, index) => { const { cancel, promise } = retry(() => fetchChunk(multicallContract, chunk, latestBlockNumber), { - n: 10, - minWait: 1000, - maxWait: 2000 + n: Infinity, + minWait: 2500, + maxWait: 3500 }) promise .then(({ results: returnData, blockNumber: fetchBlockNumber }) => { diff --git a/src/utils/retry.test.ts b/src/utils/retry.test.ts index a0c30ee2c44..2ea7ca39304 100644 --- a/src/utils/retry.test.ts +++ b/src/utils/retry.test.ts @@ -1,16 +1,22 @@ -import { retry } from './retry' +import { retry, RetryableError } from './retry' describe('retry', () => { - function makeFn(fails: number, result: T): () => Promise { + function makeFn(fails: number, result: T, retryable = true): () => Promise { return async () => { if (fails > 0) { fails-- - throw new Error('failure') + throw retryable ? new RetryableError('failure') : new Error('bad failure') } return result } } + it('fails for non-retryable error', async () => { + await expect(retry(makeFn(1, 'abc', false), { n: 3, maxWait: 0, minWait: 0 }).promise).rejects.toThrow( + 'bad failure' + ) + }) + it('works after one fail', async () => { await expect(retry(makeFn(1, 'abc'), { n: 3, maxWait: 0, minWait: 0 }).promise).resolves.toEqual('abc') }) diff --git a/src/utils/retry.ts b/src/utils/retry.ts index 1a0fb54f605..e2d85302876 100644 --- a/src/utils/retry.ts +++ b/src/utils/retry.ts @@ -6,12 +6,20 @@ function waitRandom(min: number, max: number): Promise { return wait(min + Math.round(Math.random() * Math.max(0, max - min))) } +/** + * This error is thrown if the function is cancelled before completing + */ export class CancelledError extends Error { constructor() { super('Cancelled') } } +/** + * Throw this error if the function should retry + */ +export class RetryableError extends Error {} + /** * Retries the function that returns the promise until the promise successfully resolves up to n retries * @param fn function to retry @@ -23,7 +31,6 @@ export function retry( fn: () => Promise, { n, minWait, maxWait }: { n: number; minWait: number; maxWait: number } ): { promise: Promise; cancel: () => void } { - let cancelled = false let completed = false let rejectCancelled: (error: Error) => void const promise = new Promise(async (resolve, reject) => { @@ -32,13 +39,16 @@ export function retry( let result: T try { result = await fn() - if (!cancelled) { + if (!completed) { resolve(result) completed = true } break } catch (error) { - if (!cancelled && n <= 0) { + if (completed) { + break + } + if (n <= 0 || !(error instanceof RetryableError)) { reject(error) completed = true break @@ -52,7 +62,6 @@ export function retry( promise, cancel: () => { if (completed) return - cancelled = true completed = true rejectCancelled(new CancelledError()) } diff --git a/yarn.lock b/yarn.lock index aa34b629d26..ebcec635ffb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1288,15 +1288,7 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== -"@ensdomains/address-encoder@^0.1.2": - version "0.1.7" - resolved "https://registry.yarnpkg.com/@ensdomains/address-encoder/-/address-encoder-0.1.7.tgz#4fd60468c741bea15fbd30394db410d2adc15997" - integrity sha512-p43NCgeyF3Q5ZEhEa24SN/UYUTKZIvFHwQ3xT8xqv4/iBbf7+efOP8Pl6kHbu6HI6pdWhTMvHOUR4UEDu4MN1g== - dependencies: - bech32 "^1.1.3" - crypto-addr-codec "^0.1.7" - -"@ethersproject/abi@>=5.0.0-beta.137", "@ethersproject/abi@^5.0.0": +"@ethersproject/abi@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.0.2.tgz#7fe8f080aa1483fe32cd27bb5b8f2019266af1e2" integrity sha512-Z+5f7xOgtRLu/W2l9Ry5xF7ehh9QVQ0m1vhynmTcS7DMfHgqTd1/PDFC62aw91ZPRCRZsYdZJu8ymokC5e1JSw== @@ -1311,7 +1303,7 @@ "@ethersproject/properties" "^5.0.0" "@ethersproject/strings" "^5.0.0" -"@ethersproject/abstract-provider@>=5.0.0-beta.131", "@ethersproject/abstract-provider@>=5.0.0-beta.139", "@ethersproject/abstract-provider@^5.0.0": +"@ethersproject/abstract-provider@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.0.2.tgz#9b4e8f4870f0691463e8d5b092c95dd5275c635d" integrity sha512-U1s60+nG02x8FKNMoVNI6MG8SguWCoG9HJtwOqWZ38LBRMsDV4c0w4izKx98kcsN3wXw4U2/YAyJ9LlH7+/hkg== @@ -1324,7 +1316,7 @@ "@ethersproject/transactions" "^5.0.0" "@ethersproject/web" "^5.0.0" -"@ethersproject/abstract-signer@>=5.0.0-beta.132", "@ethersproject/abstract-signer@>=5.0.0-beta.142", "@ethersproject/abstract-signer@^5.0.0": +"@ethersproject/abstract-signer@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.0.2.tgz#5776f888fda816de1d08ddb0e74778ecb9590f69" integrity sha512-CzzXbeqKlgayE4YTnvvreGBG3n+HxakGXrxaGM6LjBZnOOIVSYi6HMFG8ZXls7UspRY4hvMrtnKEJKDCOngSBw== @@ -1335,18 +1327,6 @@ "@ethersproject/logger" "^5.0.0" "@ethersproject/properties" "^5.0.0" -"@ethersproject/address@5.0.0-beta.134", "@ethersproject/address@>=5.0.0-beta.128", "@ethersproject/address@>=5.0.0-beta.134": - version "5.0.0-beta.134" - resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.0.0-beta.134.tgz#9c1790c87b763dc547ac12e2dbc9fa78d0799a71" - integrity sha512-FHhUVJTUIg2pXvOOhIt8sB1cQbcwrzZKzf9CPV7JM1auli20nGoYhyMFYGK7u++GXzTMJduIkU1OwlIBupewDw== - dependencies: - "@ethersproject/bignumber" ">=5.0.0-beta.130" - "@ethersproject/bytes" ">=5.0.0-beta.129" - "@ethersproject/keccak256" ">=5.0.0-beta.127" - "@ethersproject/logger" ">=5.0.0-beta.129" - "@ethersproject/rlp" ">=5.0.0-beta.126" - bn.js "^4.4.0" - "@ethersproject/address@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.0.2.tgz#80d0ddfb7d4bd0d32657747fa4bdd2defef2e00a" @@ -1374,16 +1354,6 @@ "@ethersproject/bytes" "^5.0.0" "@ethersproject/properties" "^5.0.0" -"@ethersproject/bignumber@5.0.0-beta.138", "@ethersproject/bignumber@>=5.0.0-beta.130", "@ethersproject/bignumber@>=5.0.0-beta.138": - version "5.0.0-beta.138" - resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.0.0-beta.138.tgz#a635f2f9a6f1b262cc38e1c7ee561fb13d79fda4" - integrity sha512-DTlOEJw6jAFz7/qkY8p4mPGGHVwgYUUC5rk1Pbg2/gR/gHPFDim+uBY+XGavh0QSWd1i3hXKafVPre92j4fs5g== - dependencies: - "@ethersproject/bytes" ">=5.0.0-beta.129" - "@ethersproject/logger" ">=5.0.0-beta.129" - "@ethersproject/properties" ">=5.0.0-beta.131" - bn.js "^4.4.0" - "@ethersproject/bignumber@^5.0.0": version "5.0.5" resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.0.5.tgz#31bd7e75aad46ace345fae69b1f5bb120906af1b" @@ -1393,20 +1363,13 @@ "@ethersproject/logger" "^5.0.0" bn.js "^4.4.0" -"@ethersproject/bytes@>=5.0.0-beta.129", "@ethersproject/bytes@>=5.0.0-beta.137", "@ethersproject/bytes@^5.0.0": +"@ethersproject/bytes@^5.0.0": version "5.0.3" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.0.3.tgz#b3769963ae0188a35713d343890a903bda20af9c" integrity sha512-AyPMAlY+Amaw4Zfp8OAivm1xYPI8mqiUYmEnSUk1CnS2NrQGHEMmFJFiOJdS3gDDpgSOFhWIjZwxKq2VZpqNTA== dependencies: "@ethersproject/logger" "^5.0.0" -"@ethersproject/constants@5.0.0-beta.133", "@ethersproject/constants@>=5.0.0-beta.128": - version "5.0.0-beta.133" - resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.0.0-beta.133.tgz#af4ccd7232f3ed73aebe066a695ede32c497a394" - integrity sha512-VCTpk3AF00mlWQw1vg+fI6qCo0qO5EVWK574t4HNBKW6X748jc9UJPryKUz9JgZ64ZQupyLM92wHilsG/YTpNQ== - dependencies: - "@ethersproject/bignumber" ">=5.0.0-beta.130" - "@ethersproject/constants@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.0.2.tgz#f7ac0b320e2bbec1a5950da075015f8bc4e8fed1" @@ -1414,22 +1377,6 @@ dependencies: "@ethersproject/bignumber" "^5.0.0" -"@ethersproject/contracts@5.0.0-beta.151": - version "5.0.0-beta.151" - resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.0.0-beta.151.tgz#4cee195c01b6865e8e7d8849777427864819e931" - integrity sha512-ELmsmZ/vE/rz5ydJNlU04aXsh7sw22tzmy7vM5JXCgMm5nEFhGoRF+dRIrUFCuUV2Mxe0bALN11qGkRqFKlXRQ== - dependencies: - "@ethersproject/abi" ">=5.0.0-beta.137" - "@ethersproject/abstract-provider" ">=5.0.0-beta.131" - "@ethersproject/abstract-signer" ">=5.0.0-beta.132" - "@ethersproject/address" ">=5.0.0-beta.128" - "@ethersproject/bignumber" ">=5.0.0-beta.130" - "@ethersproject/bytes" ">=5.0.0-beta.129" - "@ethersproject/constants" ">=5.0.0-beta.128" - "@ethersproject/logger" ">=5.0.0-beta.129" - "@ethersproject/properties" ">=5.0.0-beta.131" - "@ethersproject/transactions" ">=5.0.0-beta.128" - "@ethersproject/contracts@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.0.2.tgz#f19ed8335ceeb6abb60f5d45641f0a2a62b6fbc5" @@ -1445,17 +1392,7 @@ "@ethersproject/logger" "^5.0.0" "@ethersproject/properties" "^5.0.0" -"@ethersproject/experimental@5.0.0-beta.141": - version "5.0.0-beta.141" - resolved "https://registry.yarnpkg.com/@ethersproject/experimental/-/experimental-5.0.0-beta.141.tgz#2dc7e1f1c33f818cda1799b63b2ecb9e226f46bb" - integrity sha512-SFUfN5c6Wcpq18ZZBQdpf6ie50aIkz3jco/8PPv5PFkRSIrGTP4HfobAu6A3eORd/tnvlgm1H2XWOLuRJ3WujA== - dependencies: - "@ensdomains/address-encoder" "^0.1.2" - "@ethersproject/web" ">=5.0.0-beta.138" - ethers ">=5.0.0-beta.186" - scrypt-js "3.0.0" - -"@ethersproject/hash@>=5.0.0-beta.128", "@ethersproject/hash@>=5.0.0-beta.133", "@ethersproject/hash@^5.0.0": +"@ethersproject/hash@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.0.2.tgz#6d69558786961836d530b8b4a8714eac5388aec7" integrity sha512-dWGvNwmVRX2bxoQQ3ciMw46Vzl1nqfL+5R8+2ZxsRXD3Cjgw1dL2mdjJF7xMMWPvPdrlhKXWSK0gb8VLwHZ8Cw== @@ -1465,7 +1402,7 @@ "@ethersproject/logger" "^5.0.0" "@ethersproject/strings" "^5.0.0" -"@ethersproject/hdnode@>=5.0.0-beta.139", "@ethersproject/hdnode@^5.0.0": +"@ethersproject/hdnode@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.0.2.tgz#c4f2152590a64822d0c0feb90f09cc247af657e0" integrity sha512-QAUI5tfseTFqv00Vnbwzofqse81wN9TaL+x5GufTHIHJXgVdguxU+l39E3VYDCmO+eVAA6RCn5dJgeyra+PU2g== @@ -1483,7 +1420,7 @@ "@ethersproject/transactions" "^5.0.0" "@ethersproject/wordlists" "^5.0.0" -"@ethersproject/json-wallets@>=5.0.0-beta.138", "@ethersproject/json-wallets@^5.0.0": +"@ethersproject/json-wallets@^5.0.0": version "5.0.3" resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.0.3.tgz#072021fe79f69c9ca1300f780abd9b9d0c8ea42e" integrity sha512-VfDXn5ylugkfiM6SrvQfhX9oAHVU5dsNpRw8PjjTCn4k5E2JuVRO5A8sibkYXDhcBmRISZIWqclIxka6FI/chg== @@ -1502,7 +1439,7 @@ aes-js "3.0.0" scrypt-js "3.0.1" -"@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@>=5.0.0-beta.131", "@ethersproject/keccak256@^5.0.0", "@ethersproject/keccak256@^5.0.0-beta.130": +"@ethersproject/keccak256@^5.0.0", "@ethersproject/keccak256@^5.0.0-beta.130": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.2.tgz#7ed4a95bb45ee502cf4532223833740a83602797" integrity sha512-MbroXutc0gPNYIrUjS4Aw0lDuXabdzI7+l7elRWr1G6G+W0v00e/3gbikWkCReGtt2Jnt4lQSgnflhDwQGcIhA== @@ -1510,18 +1447,11 @@ "@ethersproject/bytes" "^5.0.0" js-sha3 "0.5.7" -"@ethersproject/logger@>=5.0.0-beta.129", "@ethersproject/logger@>=5.0.0-beta.137", "@ethersproject/logger@^5.0.0": +"@ethersproject/logger@^5.0.0": version "5.0.4" resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.0.4.tgz#09fa4765b5691233e3afb6617cb38a700f9dd2e4" integrity sha512-alA2LiAy1LdQ/L1SA9ajUC7MvGAEQLsICEfKK4erX5qhkXE1LwLSPIzobtOWFsMHf2yrXGKBLnnpuVHprI3sAw== -"@ethersproject/networks@5.0.0-beta.136", "@ethersproject/networks@>=5.0.0-beta.129": - version "5.0.0-beta.136" - resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.0.0-beta.136.tgz#8d6fdae297c0ce7ebe1893e601c4a57f7e38dc7a" - integrity sha512-skMDix0LVOhpfCItbg6Z1fXLK6vAtUkzAKaslDxVczEPUvjQ0kiJ5ceurmL+ROOO1owURGxUac5BrIarbO7Zgw== - dependencies: - "@ethersproject/logger" ">=5.0.0-beta.129" - "@ethersproject/networks@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.0.2.tgz#a49e82cf071e3618e87e3c5d69fdbcf54dc6766c" @@ -1537,35 +1467,13 @@ "@ethersproject/bytes" "^5.0.0" "@ethersproject/sha2" "^5.0.0" -"@ethersproject/properties@>=5.0.0-beta.131", "@ethersproject/properties@>=5.0.0-beta.140", "@ethersproject/properties@^5.0.0": +"@ethersproject/properties@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.0.2.tgz#2facb62d2f2d968c7b3d0befa5bcc884cc565d3b" integrity sha512-FxAisPGAOACQjMJzewl9OJG6lsGCPTm5vpUMtfeoxzAlAb2lv+kHzQPUh9h4jfAILzE8AR1jgXMzRmlhwyra1Q== dependencies: "@ethersproject/logger" "^5.0.0" -"@ethersproject/providers@5.0.0-beta.162": - version "5.0.0-beta.162" - resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.0.0-beta.162.tgz#cb4efbeea2c776d0ce97712e05ffaa3e0a8df215" - integrity sha512-mXT5pQLOmRkXP5pza6TuV9RitaI50b1O2r0og8VzUIHcjO9bq4yppVbWs0Zcxn4KQAiIrAd2xXbYE3q2KdfUYQ== - dependencies: - "@ethersproject/abstract-provider" ">=5.0.0-beta.131" - "@ethersproject/abstract-signer" ">=5.0.0-beta.132" - "@ethersproject/address" ">=5.0.0-beta.128" - "@ethersproject/bignumber" ">=5.0.0-beta.130" - "@ethersproject/bytes" ">=5.0.0-beta.129" - "@ethersproject/constants" ">=5.0.0-beta.128" - "@ethersproject/hash" ">=5.0.0-beta.128" - "@ethersproject/logger" ">=5.0.0-beta.129" - "@ethersproject/networks" ">=5.0.0-beta.129" - "@ethersproject/properties" ">=5.0.0-beta.131" - "@ethersproject/random" ">=5.0.0-beta.128" - "@ethersproject/rlp" ">=5.0.0-beta.126" - "@ethersproject/strings" ">=5.0.0-beta.130" - "@ethersproject/transactions" ">=5.0.0-beta.128" - "@ethersproject/web" ">=5.0.0-beta.129" - ws "7.2.3" - "@ethersproject/providers@^5.0.0": version "5.0.5" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.0.5.tgz#fa28498ce9683d1d99f6cb11e1a7fe8d4886e0ce" @@ -1588,7 +1496,7 @@ "@ethersproject/web" "^5.0.0" ws "7.2.3" -"@ethersproject/random@>=5.0.0-beta.128", "@ethersproject/random@>=5.0.0-beta.135", "@ethersproject/random@^5.0.0": +"@ethersproject/random@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.0.2.tgz#bb58aca69a85e8de506686117f050d03dac69023" integrity sha512-kLeS+6bwz37WR2zbe69gudyoGVoUzljQO0LhifnATsZ7rW0JZ9Zgt0h5aXY7tqFDo9TvdqeCwUFdp1t3T5Fkhg== @@ -1596,7 +1504,7 @@ "@ethersproject/bytes" "^5.0.0" "@ethersproject/logger" "^5.0.0" -"@ethersproject/rlp@>=5.0.0-beta.126", "@ethersproject/rlp@^5.0.0": +"@ethersproject/rlp@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.0.2.tgz#d6b550a2ac5e484f15f0f63337e522004d2e78cd" integrity sha512-oE0M5jqQ67fi2SuMcrpoewOpEuoXaD8M9JeR9md1bXRMvDYgKXUtDHs22oevpEOdnO2DPIRabp6MVHa4aDuWmw== @@ -1613,7 +1521,7 @@ "@ethersproject/logger" "^5.0.0" hash.js "1.1.3" -"@ethersproject/signing-key@>=5.0.0-beta.135", "@ethersproject/signing-key@^5.0.0": +"@ethersproject/signing-key@^5.0.0": version "5.0.3" resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.0.3.tgz#adb84360e147bfd336cb2fe114100120732dc10a" integrity sha512-5QPZaBRGCLzfVMbFb3LcVjNR0UbTXnwDHASnQYfbzwUOnFYHKxHsrcbl/5ONGoppgi8yXgOocKqlPCFycJJVWQ== @@ -1623,7 +1531,7 @@ "@ethersproject/properties" "^5.0.0" elliptic "6.5.3" -"@ethersproject/solidity@5.0.2", "@ethersproject/solidity@^5.0.0": +"@ethersproject/solidity@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.0.2.tgz#431cee341ec51e022bd897b93fef04521f414756" integrity sha512-RygurUe1hPW1LDYAPXy4471AklGWNnxgFWc3YUE6H11gzkit26jr6AyZH4Yyjw38eBBL6j0AOfQzMWm+NhxZ9g== @@ -1634,15 +1542,6 @@ "@ethersproject/sha2" "^5.0.0" "@ethersproject/strings" "^5.0.0" -"@ethersproject/strings@5.0.0-beta.136", "@ethersproject/strings@>=5.0.0-beta.130": - version "5.0.0-beta.136" - resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.0-beta.136.tgz#053cbf4f9f96a7537cbc50300597f2d707907f51" - integrity sha512-Hb9RvTrgGcOavHvtQZz+AuijB79BO3g1cfF2MeMfCU9ID4j3mbZv/olzDMS2pK9r4aERJpAS94AmlWzCgoY2LQ== - dependencies: - "@ethersproject/bytes" ">=5.0.0-beta.129" - "@ethersproject/constants" ">=5.0.0-beta.128" - "@ethersproject/logger" ">=5.0.0-beta.129" - "@ethersproject/strings@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.2.tgz#1753408c3c889813fd0992abd76393e3e47a2619" @@ -1652,7 +1551,7 @@ "@ethersproject/constants" "^5.0.0" "@ethersproject/logger" "^5.0.0" -"@ethersproject/transactions@>=5.0.0-beta.128", "@ethersproject/transactions@>=5.0.0-beta.135", "@ethersproject/transactions@^5.0.0": +"@ethersproject/transactions@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.0.2.tgz#590ede71fc87b45be7bd46002e18ae52246a2347" integrity sha512-jZp0ZbbJlq4JLZY6qoMzNtp2HQsX6USQposi3ns0MPUdn3OdZJBDtrcO15r/2VS5t/K1e1GE5MI1HmMKlcTbbQ== @@ -1667,15 +1566,6 @@ "@ethersproject/rlp" "^5.0.0" "@ethersproject/signing-key" "^5.0.0" -"@ethersproject/units@5.0.0-beta.132": - version "5.0.0-beta.132" - resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.0.0-beta.132.tgz#54c03c821e515a09ef79a22704ad57994ee66c45" - integrity sha512-3GZDup1uTydvqaP5wpwoRF36irp6kx/gd3buPG+aoGWLPCoPjyk76OiGoxNQKfEaynOdZ7zG2lM8WevlBDJ57g== - dependencies: - "@ethersproject/bignumber" ">=5.0.0-beta.130" - "@ethersproject/constants" ">=5.0.0-beta.128" - "@ethersproject/logger" ">=5.0.0-beta.129" - "@ethersproject/units@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.0.2.tgz#de1461ff3ad2587e57bf367d056b6b72cfceda78" @@ -1685,27 +1575,6 @@ "@ethersproject/constants" "^5.0.0" "@ethersproject/logger" "^5.0.0" -"@ethersproject/wallet@5.0.0-beta.141": - version "5.0.0-beta.141" - resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.0.0-beta.141.tgz#2a4a72cf2423c6ac08c38b5faa28e72f8e9a4f03" - integrity sha512-N/69EgBOhRXYmDj91ZUrDK7V38Eb4mrC8OvUdmGEwjHVO3VIz0sH+Li1IDVRdyGSWYhoxfVRP650ObMzL9a7dQ== - dependencies: - "@ethersproject/abstract-provider" ">=5.0.0-beta.139" - "@ethersproject/abstract-signer" ">=5.0.0-beta.142" - "@ethersproject/address" ">=5.0.0-beta.134" - "@ethersproject/bignumber" ">=5.0.0-beta.138" - "@ethersproject/bytes" ">=5.0.0-beta.137" - "@ethersproject/hash" ">=5.0.0-beta.133" - "@ethersproject/hdnode" ">=5.0.0-beta.139" - "@ethersproject/json-wallets" ">=5.0.0-beta.138" - "@ethersproject/keccak256" ">=5.0.0-beta.131" - "@ethersproject/logger" ">=5.0.0-beta.137" - "@ethersproject/properties" ">=5.0.0-beta.140" - "@ethersproject/random" ">=5.0.0-beta.135" - "@ethersproject/signing-key" ">=5.0.0-beta.135" - "@ethersproject/transactions" ">=5.0.0-beta.135" - "@ethersproject/wordlists" ">=5.0.0-beta.136" - "@ethersproject/wallet@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.0.2.tgz#714ca8324c1b3b66e51b9b4e0358c882e88caf1d" @@ -1727,7 +1596,7 @@ "@ethersproject/transactions" "^5.0.0" "@ethersproject/wordlists" "^5.0.0" -"@ethersproject/web@>=5.0.0-beta.129", "@ethersproject/web@>=5.0.0-beta.138", "@ethersproject/web@^5.0.0": +"@ethersproject/web@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.0.2.tgz#6565b4c4fe2f56de9556d0e9a966c4ccc1b7b7da" integrity sha512-uAlcxdrAWB9PXZlb5NPzbOOt5/m9EJP2c6eLw15/PXPkNNohEIKvdXXOWdcQgTjZ0pcAaD/9mnJ6HXg7NbqXiw== @@ -1737,7 +1606,7 @@ "@ethersproject/properties" "^5.0.0" "@ethersproject/strings" "^5.0.0" -"@ethersproject/wordlists@>=5.0.0-beta.136", "@ethersproject/wordlists@^5.0.0": +"@ethersproject/wordlists@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.0.2.tgz#eded47314509c8608373fc2b22879ee2b71b7c7c" integrity sha512-6vKDQcjjpnfdSCr0+jNxpFH3ieKxUPkm29tQX2US7a3zT/sJU/BGlKBR7D8oOpwdE0hpkHhJyMlypRBK+A2avA== @@ -4202,7 +4071,7 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base-x@^3.0.2, base-x@^3.0.8: +base-x@^3.0.2: version "3.0.8" resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d" integrity sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA== @@ -4239,16 +4108,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -bech32@^1.1.3: - version "1.1.4" - resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" - integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== - -big-integer@1.6.36: - version "1.6.36" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.36.tgz#78631076265d4ae3555c04f85e7d9d2f3a071a36" - integrity sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg== - big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -4499,7 +4358,7 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.6.2, browserslist@^4. escalade "^3.0.1" node-releases "^1.1.58" -bs58@^4.0.0, bs58@^4.0.1: +bs58@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo= @@ -4565,14 +4424,6 @@ buffer-xor@^1.0.3: resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= -buffer@5.6.0, buffer@^5.4.3: - version "5.6.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" - integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - buffer@^4.3.0: version "4.9.2" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" @@ -4582,6 +4433,14 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" +buffer@^5.4.3: + version "5.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" + integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" @@ -5420,19 +5279,6 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1: shebang-command "^2.0.0" which "^2.0.1" -crypto-addr-codec@^0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/crypto-addr-codec/-/crypto-addr-codec-0.1.7.tgz#e16cea892730178fe25a38f6d15b680cab3124ae" - integrity sha512-X4hzfBzNhy4mAc3UpiXEC/L0jo5E8wAa9unsnA8nNXYzXjCcGk83hfC5avJWCSGT8V91xMnAS9AKMHmjw5+XCg== - dependencies: - base-x "^3.0.8" - big-integer "1.6.36" - blakejs "^1.1.0" - bs58 "^4.0.1" - ripemd160-min "0.0.6" - safe-buffer "^5.2.0" - sha3 "^2.1.1" - crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -6937,7 +6783,7 @@ ethereumjs-vm@^2.1.0, ethereumjs-vm@^2.3.4, ethereumjs-vm@^2.6.0: rustbn.js "~0.2.0" safe-buffer "^5.1.1" -ethers@>=5.0.0-beta.186: +ethers@^5.0.7: version "5.0.7" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.0.7.tgz#41c3d774e0a57bfde12b0198885789fb41a14976" integrity sha512-1Zu9s+z4BgsDAZcGIYACJdWBB6mVtCCmUonj68Njul7STcSdgwOyj0sCAxCUr2Nsmsamckr4E12q3ecvZPGAUw== @@ -13083,11 +12929,6 @@ rimraf@^2.5.4, rimraf@^2.6.3, rimraf@^2.7.1: dependencies: glob "^7.1.3" -ripemd160-min@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/ripemd160-min/-/ripemd160-min-0.0.6.tgz#a904b77658114474d02503e819dcc55853b67e62" - integrity sha512-+GcJgQivhs6S9qvLogusiTcS9kQUfgR75whKuy5jIhuiOfQuJ8fjqxV6EGD5duH1Y/FawFUMtMhyeq3Fbnib8A== - ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -13230,11 +13071,6 @@ schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.1, schema-utils@^2.6 ajv "^6.12.2" ajv-keywords "^3.4.1" -scrypt-js@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.0.tgz#52361c1f272eeaab09ec1f806ea82078bca58b15" - integrity sha512-7CC7aufwukEvqdmllR0ny0QaSg0+S22xKXrXz3ZahaV6J+fgD2YAtrjtImuoDWog17/Ty9Q4HBmnXEXJ3JkfQA== - scrypt-js@3.0.1, scrypt-js@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" @@ -13438,13 +13274,6 @@ sha3@^1.2.2: dependencies: nan "2.13.2" -sha3@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.1.3.tgz#ab05b841b2bce347765db31f57fe2a3134b9fb48" - integrity sha512-Io53D4o9qOmf3Ow9p/DoGLQiQHhtuR0ulbyambvRSG+OX5yXExk2yYfvjHtb7AtOyk6K6+sPeK/qaowWc/E/GA== - dependencies: - buffer "5.6.0" - shallow-clone@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-0.1.2.tgz#5909e874ba77106d73ac414cfec1ffca87d97060" From f84aa07b1de9f12091b9af6d6c3ad33b8e203c9d Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Mon, 3 Aug 2020 19:06:08 -0500 Subject: [PATCH 13/29] fix integration tests --- package.json | 1 + yarn.lock | 75 ++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index b47fa3f7a9b..55eeba1b182 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "homepage": ".", "private": true, "devDependencies": { + "@ethersproject/experimental": "^5.0.1", "@popperjs/core": "^2.4.4", "@reach/dialog": "^0.10.3", "@reach/portal": "^0.10.3", diff --git a/yarn.lock b/yarn.lock index ebcec635ffb..605ea0b30c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1288,6 +1288,14 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== +"@ensdomains/address-encoder@^0.1.2": + version "0.1.7" + resolved "https://registry.yarnpkg.com/@ensdomains/address-encoder/-/address-encoder-0.1.7.tgz#4fd60468c741bea15fbd30394db410d2adc15997" + integrity sha512-p43NCgeyF3Q5ZEhEa24SN/UYUTKZIvFHwQ3xT8xqv4/iBbf7+efOP8Pl6kHbu6HI6pdWhTMvHOUR4UEDu4MN1g== + dependencies: + bech32 "^1.1.3" + crypto-addr-codec "^0.1.7" + "@ethersproject/abi@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.0.2.tgz#7fe8f080aa1483fe32cd27bb5b8f2019266af1e2" @@ -1392,6 +1400,16 @@ "@ethersproject/logger" "^5.0.0" "@ethersproject/properties" "^5.0.0" +"@ethersproject/experimental@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@ethersproject/experimental/-/experimental-5.0.1.tgz#c488d43092543c49e4cb70fbaeafad0956c826e0" + integrity sha512-PAVv/i4PwO2L4E2PWgPgEGP9FOt/5qaTv7W9YDTSL7Tq2zfp41jolRBI1o7X0UdnPWUe54TiibOp4xJR65Dwpw== + dependencies: + "@ensdomains/address-encoder" "^0.1.2" + "@ethersproject/web" "^5.0.0" + ethers "^5.0.0" + scrypt-js "3.0.1" + "@ethersproject/hash@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.0.2.tgz#6d69558786961836d530b8b4a8714eac5388aec7" @@ -4071,7 +4089,7 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base-x@^3.0.2: +base-x@^3.0.2, base-x@^3.0.8: version "3.0.8" resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz#1e1106c2537f0162e8b52474a557ebb09000018d" integrity sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA== @@ -4108,6 +4126,16 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +bech32@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" + integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== + +big-integer@1.6.36: + version "1.6.36" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.36.tgz#78631076265d4ae3555c04f85e7d9d2f3a071a36" + integrity sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -4358,7 +4386,7 @@ browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.6.2, browserslist@^4. escalade "^3.0.1" node-releases "^1.1.58" -bs58@^4.0.0: +bs58@^4.0.0, bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo= @@ -4424,6 +4452,14 @@ buffer-xor@^1.0.3: resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= +buffer@5.6.0, buffer@^5.4.3: + version "5.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" + integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + buffer@^4.3.0: version "4.9.2" resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" @@ -4433,14 +4469,6 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.4.3: - version "5.6.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" - integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" @@ -5279,6 +5307,19 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1: shebang-command "^2.0.0" which "^2.0.1" +crypto-addr-codec@^0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/crypto-addr-codec/-/crypto-addr-codec-0.1.7.tgz#e16cea892730178fe25a38f6d15b680cab3124ae" + integrity sha512-X4hzfBzNhy4mAc3UpiXEC/L0jo5E8wAa9unsnA8nNXYzXjCcGk83hfC5avJWCSGT8V91xMnAS9AKMHmjw5+XCg== + dependencies: + base-x "^3.0.8" + big-integer "1.6.36" + blakejs "^1.1.0" + bs58 "^4.0.1" + ripemd160-min "0.0.6" + safe-buffer "^5.2.0" + sha3 "^2.1.1" + crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -6783,7 +6824,7 @@ ethereumjs-vm@^2.1.0, ethereumjs-vm@^2.3.4, ethereumjs-vm@^2.6.0: rustbn.js "~0.2.0" safe-buffer "^5.1.1" -ethers@^5.0.7: +ethers@^5.0.0, ethers@^5.0.7: version "5.0.7" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.0.7.tgz#41c3d774e0a57bfde12b0198885789fb41a14976" integrity sha512-1Zu9s+z4BgsDAZcGIYACJdWBB6mVtCCmUonj68Njul7STcSdgwOyj0sCAxCUr2Nsmsamckr4E12q3ecvZPGAUw== @@ -12929,6 +12970,11 @@ rimraf@^2.5.4, rimraf@^2.6.3, rimraf@^2.7.1: dependencies: glob "^7.1.3" +ripemd160-min@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/ripemd160-min/-/ripemd160-min-0.0.6.tgz#a904b77658114474d02503e819dcc55853b67e62" + integrity sha512-+GcJgQivhs6S9qvLogusiTcS9kQUfgR75whKuy5jIhuiOfQuJ8fjqxV6EGD5duH1Y/FawFUMtMhyeq3Fbnib8A== + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -13274,6 +13320,13 @@ sha3@^1.2.2: dependencies: nan "2.13.2" +sha3@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.1.3.tgz#ab05b841b2bce347765db31f57fe2a3134b9fb48" + integrity sha512-Io53D4o9qOmf3Ow9p/DoGLQiQHhtuR0ulbyambvRSG+OX5yXExk2yYfvjHtb7AtOyk6K6+sPeK/qaowWc/E/GA== + dependencies: + buffer "5.6.0" + shallow-clone@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-0.1.2.tgz#5909e874ba77106d73ac414cfec1ffca87d97060" From e798d826974ce677c86f36f94a8269e211a9f1bd Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Tue, 4 Aug 2020 08:51:49 -0500 Subject: [PATCH 14/29] simplify modal and fix jitter on open in mobile --- src/components/Modal/index.tsx | 129 +++++++++------------------------ src/theme/index.tsx | 2 +- 2 files changed, 37 insertions(+), 94 deletions(-) diff --git a/src/components/Modal/index.tsx b/src/components/Modal/index.tsx index b76ac1b508d..bf214756607 100644 --- a/src/components/Modal/index.tsx +++ b/src/components/Modal/index.tsx @@ -1,8 +1,6 @@ import React from 'react' import styled, { css } from 'styled-components' import { animated, useTransition, useSpring } from 'react-spring' -import { Spring } from 'react-spring/renderprops' - import { DialogOverlay, DialogContent } from '@reach/dialog' import { isMobile } from 'react-device-detect' import '@reach/dialog/styles.css' @@ -11,39 +9,25 @@ import { useGesture } from 'react-use-gesture' const AnimatedDialogOverlay = animated(DialogOverlay) // eslint-disable-next-line @typescript-eslint/no-unused-vars -const StyledDialogOverlay = styled(({ mobile, ...rest }) => )<{ mobile: boolean }>` +const StyledDialogOverlay = styled(AnimatedDialogOverlay)` &[data-reach-dialog-overlay] { z-index: 2; - display: flex; - align-items: center; - justify-content: center; background-color: transparent; overflow: hidden; - ${({ mobile }) => - mobile && - css` - align-items: flex-end; - `} + display: flex; + align-items: center; + justify-content: center; - &::after { - content: ''; - background-color: ${({ theme }) => theme.modalBG}; - opacity: 0.5; - top: 0; - left: 0; - bottom: 0; - right: 0; - position: fixed; - z-index: -1; - } + background-color: ${({ theme }) => theme.modalBG}; } ` +const AnimatedDialogContent = animated(DialogContent) // destructure to not pass custom props to Dialog DOM element // eslint-disable-next-line @typescript-eslint/no-unused-vars const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...rest }) => ( - + )).attrs({ 'aria-label': 'dialog' })` @@ -55,6 +39,8 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r padding: 0px; width: 50vw; + align-self: ${({ mobile }) => (mobile ? 'flex-end' : 'center')}; + max-width: 420px; ${({ maxHeight }) => maxHeight && @@ -102,7 +88,7 @@ export default function Modal({ initialFocusRef = null, children }: ModalProps) { - const transitions = useTransition(isOpen, null, { + const fadeTransition = useTransition(isOpen, null, { config: { duration: 200 }, from: { opacity: 0 }, enter: { opacity: 1 }, @@ -115,80 +101,37 @@ export default function Modal({ set({ y: state.down ? state.movement[1] : 0 }) - if (state.velocity > 3 && state.direction[1] > 0) { + if (state.movement[1] > 300 || (state.velocity > 3 && state.direction[1] > 0)) { onDismiss() } } }) - if (isMobile) { - return ( - <> - {transitions.map( - ({ item, key, props }) => - item && ( - + {fadeTransition.map( + ({ item, key, props }) => + item && ( + + `translateY(${y > 0 ? y : 0}px)`) } + } + : {})} + aria-label="dialog content" + minHeight={minHeight} + maxHeight={maxHeight} + mobile={isMobile} > {/* prevents the automatic focusing of inputs on mobile by the reach dialog */} - {initialFocusRef ? null :
} - - {props => ( - `translateY(${y > 0 ? y : 0}px)`) - }} - > - - - )} - - - ) - )} - - ) - } else { - return ( - <> - {transitions.map( - ({ item, key, props }) => - item && ( - - - - ) - )} - - ) - } + {!initialFocusRef && isMobile ?
: null} + {children} + + + ) + )} + + ) } diff --git a/src/theme/index.tsx b/src/theme/index.tsx index 19c9acc2fa1..b46511a1da7 100644 --- a/src/theme/index.tsx +++ b/src/theme/index.tsx @@ -55,7 +55,7 @@ export function colors(darkMode: boolean): Colors { bg5: darkMode ? '#565A69' : '#888D9B', //specialty colors - modalBG: darkMode ? 'rgba(0,0,0,0.85)' : 'rgba(0,0,0,0.6)', + modalBG: darkMode ? 'rgba(0,0,0,42.5)' : 'rgba(0,0,0,0.3)', advancedBG: darkMode ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.6)', //primary colors From 69a2ceedab50e412384cbd6a96f0eaaef9a27c17 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Tue, 4 Aug 2020 14:29:40 -0500 Subject: [PATCH 15/29] refactor confirmation modal into pieces before creating the error content --- src/components/ConfirmationModal/index.tsx | 133 ------------- .../TransactionConfirmationModal/index.tsx | 179 ++++++++++++++++++ .../tsconfig.json | 4 + src/hooks/useSwapCallback.ts | 9 +- src/pages/AddLiquidity/index.tsx | 4 +- src/pages/RemoveLiquidity/index.tsx | 4 +- src/pages/Swap/index.tsx | 4 +- 7 files changed, 190 insertions(+), 147 deletions(-) delete mode 100644 src/components/ConfirmationModal/index.tsx create mode 100644 src/components/TransactionConfirmationModal/index.tsx create mode 100644 src/components/TransactionConfirmationModal/tsconfig.json diff --git a/src/components/ConfirmationModal/index.tsx b/src/components/ConfirmationModal/index.tsx deleted file mode 100644 index dda0c230a73..00000000000 --- a/src/components/ConfirmationModal/index.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import React, { useContext } from 'react' -import styled, { ThemeContext } from 'styled-components' -import Modal from '../Modal' -import { ExternalLink } from '../../theme' -import { Text } from 'rebass' -import { CloseIcon, Spinner } from '../../theme/components' -import { RowBetween } from '../Row' -import { ArrowUpCircle } from 'react-feather' -import { ButtonPrimary } from '../Button' -import { AutoColumn, ColumnCenter } from '../Column' -import Circle from '../../assets/images/blue-loader.svg' - -import { getEtherscanLink } from '../../utils' -import { useActiveWeb3React } from '../../hooks' - -const Wrapper = styled.div` - width: 100%; -` -const Section = styled(AutoColumn)` - padding: 24px; -` - -const BottomSection = styled(Section)` - background-color: ${({ theme }) => theme.bg2}; - border-bottom-left-radius: 20px; - border-bottom-right-radius: 20px; -` - -const ConfirmedIcon = styled(ColumnCenter)` - padding: 60px 0; -` - -const CustomLightSpinner = styled(Spinner)<{ size: string }>` - height: ${({ size }) => size}; - width: ${({ size }) => size}; -` - -interface ConfirmationModalProps { - isOpen: boolean - onDismiss: () => void - hash: string | undefined - topContent: () => React.ReactNode - bottomContent: () => React.ReactNode - attemptingTxn: boolean - pendingText: string - title?: string -} - -export default function ConfirmationModal({ - isOpen, - onDismiss, - topContent, - bottomContent, - attemptingTxn, - hash, - pendingText, - title = '' -}: ConfirmationModalProps) { - const { chainId } = useActiveWeb3React() - const theme = useContext(ThemeContext) - - const isTransactionBroadcast = !!hash - - // waiting for user to confirm/reject tx _or_ showing info on a tx that has been broadcast - if (attemptingTxn || isTransactionBroadcast) { - return ( - - -
- -
- - - - {isTransactionBroadcast ? ( - - ) : ( - - )} - - - - {isTransactionBroadcast ? 'Transaction Submitted' : 'Waiting For Confirmation'} - - - - {pendingText} - - - - {isTransactionBroadcast ? ( - <> - - - View on Etherscan - - - - - Close - - - - ) : ( - - Confirm this transaction in your wallet - - )} - -
-
-
- ) - } - - // confirmation screen - return ( - - -
- - - {title} - - - - {topContent()} -
- {bottomContent()} -
-
- ) -} diff --git a/src/components/TransactionConfirmationModal/index.tsx b/src/components/TransactionConfirmationModal/index.tsx new file mode 100644 index 00000000000..c82e6277fd4 --- /dev/null +++ b/src/components/TransactionConfirmationModal/index.tsx @@ -0,0 +1,179 @@ +import { ChainId } from '@uniswap/sdk' +import React, { useContext } from 'react' +import styled, { ThemeContext } from 'styled-components' +import Modal from '../Modal' +import { ExternalLink } from '../../theme' +import { Text } from 'rebass' +import { CloseIcon, Spinner } from '../../theme/components' +import { RowBetween } from '../Row' +import { ArrowUpCircle } from 'react-feather' +import { ButtonPrimary } from '../Button' +import { AutoColumn, ColumnCenter } from '../Column' +import Circle from '../../assets/images/blue-loader.svg' + +import { getEtherscanLink } from '../../utils' +import { useActiveWeb3React } from '../../hooks' + +const Wrapper = styled.div` + width: 100%; +` +const Section = styled(AutoColumn)` + padding: 24px; +` + +const BottomSection = styled(Section)` + background-color: ${({ theme }) => theme.bg2}; + border-bottom-left-radius: 20px; + border-bottom-right-radius: 20px; +` + +const ConfirmedIcon = styled(ColumnCenter)` + padding: 60px 0; +` + +const CustomLightSpinner = styled(Spinner)<{ size: string }>` + height: ${({ size }) => size}; + width: ${({ size }) => size}; +` + +function ConfirmationPendingContent({ onDismiss, pendingText }: { onDismiss: () => void; pendingText: string }) { + return ( + +
+ +
+ + + + + + + + Waiting For Confirmation + + + + {pendingText} + + + + Confirm this transaction in your wallet + + +
+
+ ) +} + +function TransactionSubmittedContent({ + onDismiss, + chainId, + hash +}: { + onDismiss: () => void + hash: string | undefined + chainId: ChainId +}) { + const theme = useContext(ThemeContext) + + return ( + +
+ +
+ + + + + + + + Transaction Submitted + + + + + View on Etherscan + + + + + Close + + + +
+
+ ) +} + +export function ConfirmationModalContent({ + title, + bottomContent, + onDismiss, + topContent +}: { + title: string + onDismiss: () => void + topContent: () => React.ReactNode + bottomContent: () => React.ReactNode +}) { + return ( + +
+ + + {title} + + + + {topContent()} +
+ {bottomContent()} +
+ ) +} + +interface ConfirmationModalProps { + isOpen: boolean + onDismiss: () => void + hash: string | undefined + topContent: () => React.ReactNode + bottomContent: () => React.ReactNode + attemptingTxn: boolean + pendingText: string + title: string +} + +export default function TransactionConfirmationModal({ + isOpen, + onDismiss, + topContent, + bottomContent, + attemptingTxn, + hash, + pendingText, + title +}: ConfirmationModalProps) { + const { chainId } = useActiveWeb3React() + + if (!chainId) return null + + // confirmation screen + return ( + + {attemptingTxn ? ( + + ) : hash ? ( + + ) : ( + + )} + + ) +} diff --git a/src/components/TransactionConfirmationModal/tsconfig.json b/src/components/TransactionConfirmationModal/tsconfig.json new file mode 100644 index 00000000000..638227fff6d --- /dev/null +++ b/src/components/TransactionConfirmationModal/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../../tsconfig.strict.json", + "include": ["**/*"] +} \ No newline at end of file diff --git a/src/hooks/useSwapCallback.ts b/src/hooks/useSwapCallback.ts index 1160383ab61..067e24843d8 100644 --- a/src/hooks/useSwapCallback.ts +++ b/src/hooks/useSwapCallback.ts @@ -165,17 +165,10 @@ export function useSwapCallback( }) .catch(callError => { console.debug('Call threw error', call, callError) - // error.reason - The Revert reason; this is what you probably care about. :) - // Additionally: - // - error.address - the contract address - // - error.args - [ BigNumber(1), BigNumber(2), BigNumber(3) ] in this case - // - error.method - "someMethod()" in this case - // - error.errorSignature - "Error(string)" (the EIP 838 sighash; supports future custom errors) - // - error.errorArgs - The arguments passed into the error (more relevant post EIP 838 custom errors) - // - error.transaction - The call transaction used let errorMessage: string = DEFAULT_FAILED_SWAP_ERROR switch (callError.reason) { case 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT': + case 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT': errorMessage = 'The transaction will not succeed either due to price movement or fee on transfer. Try increasing your slippage tolerance.' break diff --git a/src/pages/AddLiquidity/index.tsx b/src/pages/AddLiquidity/index.tsx index c481b2b3b4f..b1872d9e6b0 100644 --- a/src/pages/AddLiquidity/index.tsx +++ b/src/pages/AddLiquidity/index.tsx @@ -10,7 +10,7 @@ import { ThemeContext } from 'styled-components' import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button' import { BlueCard, GreyCard, LightCard } from '../../components/Card' import { AutoColumn, ColumnCenter } from '../../components/Column' -import ConfirmationModal from '../../components/ConfirmationModal' +import TransactionConfirmationModal from '../../components/TransactionConfirmationModal' import CurrencyInputPanel from '../../components/CurrencyInputPanel' import DoubleCurrencyLogo from '../../components/DoubleLogo' import { AddRemoveTabs } from '../../components/NavigationTabs' @@ -299,7 +299,7 @@ export default function AddLiquidity({ - { setShowConfirm(false) diff --git a/src/pages/RemoveLiquidity/index.tsx b/src/pages/RemoveLiquidity/index.tsx index 9990d552d22..b716977c8cc 100644 --- a/src/pages/RemoveLiquidity/index.tsx +++ b/src/pages/RemoveLiquidity/index.tsx @@ -11,7 +11,7 @@ import { ThemeContext } from 'styled-components' import { ButtonPrimary, ButtonLight, ButtonError, ButtonConfirmed } from '../../components/Button' import { LightCard } from '../../components/Card' import { AutoColumn, ColumnCenter } from '../../components/Column' -import ConfirmationModal from '../../components/ConfirmationModal' +import TransactionConfirmationModal from '../../components/TransactionConfirmationModal' import CurrencyInputPanel from '../../components/CurrencyInputPanel' import DoubleCurrencyLogo from '../../components/DoubleLogo' import { AddRemoveTabs } from '../../components/NavigationTabs' @@ -453,7 +453,7 @@ export default function RemoveLiquidity({ - { setShowConfirm(false) diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index 0c5a8b743c0..5ecf5f57dc2 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -8,7 +8,7 @@ import AddressInputPanel from '../../components/AddressInputPanel' import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button' import Card, { GreyCard } from '../../components/Card' import { AutoColumn } from '../../components/Column' -import ConfirmationModal from '../../components/ConfirmationModal' +import TransactionConfirmationModal from '../../components/TransactionConfirmationModal' import CurrencyInputPanel from '../../components/CurrencyInputPanel' import { SwapPoolTabs } from '../../components/NavigationTabs' import { AutoRow, RowBetween } from '../../components/Row' @@ -284,7 +284,7 @@ export default function Swap() { - Date: Tue, 4 Aug 2020 14:37:39 -0500 Subject: [PATCH 16/29] finish refactoring of transaction confirmation modal --- .../TransactionConfirmationModal/index.tsx | 15 ++------- src/pages/AddLiquidity/index.tsx | 31 ++++++++++------- src/pages/RemoveLiquidity/index.tsx | 33 +++++++++++-------- src/pages/Swap/index.tsx | 26 +++++++-------- 4 files changed, 55 insertions(+), 50 deletions(-) diff --git a/src/components/TransactionConfirmationModal/index.tsx b/src/components/TransactionConfirmationModal/index.tsx index c82e6277fd4..f30212def79 100644 --- a/src/components/TransactionConfirmationModal/index.tsx +++ b/src/components/TransactionConfirmationModal/index.tsx @@ -138,22 +138,18 @@ interface ConfirmationModalProps { isOpen: boolean onDismiss: () => void hash: string | undefined - topContent: () => React.ReactNode - bottomContent: () => React.ReactNode + content: () => React.ReactNode attemptingTxn: boolean pendingText: string - title: string } export default function TransactionConfirmationModal({ isOpen, onDismiss, - topContent, - bottomContent, attemptingTxn, hash, pendingText, - title + content }: ConfirmationModalProps) { const { chainId } = useActiveWeb3React() @@ -167,12 +163,7 @@ export default function TransactionConfirmationModal({ ) : hash ? ( ) : ( - + content() )} ) diff --git a/src/pages/AddLiquidity/index.tsx b/src/pages/AddLiquidity/index.tsx index b1872d9e6b0..bc53c5092e8 100644 --- a/src/pages/AddLiquidity/index.tsx +++ b/src/pages/AddLiquidity/index.tsx @@ -10,7 +10,7 @@ import { ThemeContext } from 'styled-components' import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button' import { BlueCard, GreyCard, LightCard } from '../../components/Card' import { AutoColumn, ColumnCenter } from '../../components/Column' -import TransactionConfirmationModal from '../../components/TransactionConfirmationModal' +import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal' import CurrencyInputPanel from '../../components/CurrencyInputPanel' import DoubleCurrencyLogo from '../../components/DoubleLogo' import { AddRemoveTabs } from '../../components/NavigationTabs' @@ -294,6 +294,15 @@ export default function AddLiquidity({ [currencyIdA, history, currencyIdB] ) + const handleDismissConfirmation = useCallback(() => { + setShowConfirm(false) + // if there was a tx hash, we want to clear the input + if (txHash) { + onFieldAInput('') + } + setTxHash('') + }, [onFieldAInput, txHash]) + return ( <> @@ -301,20 +310,18 @@ export default function AddLiquidity({ { - setShowConfirm(false) - // if there was a tx hash, we want to clear the input - if (txHash) { - onFieldAInput('') - } - setTxHash('') - }} + onDismiss={handleDismissConfirmation} attemptingTxn={attemptingTxn} hash={txHash} - topContent={modalHeader} - bottomContent={modalBottom} + content={() => ( + + )} pendingText={pendingText} - title={noLiquidity ? 'You are creating a pool' : 'You will receive'} /> {noLiquidity && ( diff --git a/src/pages/RemoveLiquidity/index.tsx b/src/pages/RemoveLiquidity/index.tsx index b716977c8cc..ef4729967ee 100644 --- a/src/pages/RemoveLiquidity/index.tsx +++ b/src/pages/RemoveLiquidity/index.tsx @@ -11,7 +11,7 @@ import { ThemeContext } from 'styled-components' import { ButtonPrimary, ButtonLight, ButtonError, ButtonConfirmed } from '../../components/Button' import { LightCard } from '../../components/Card' import { AutoColumn, ColumnCenter } from '../../components/Column' -import TransactionConfirmationModal from '../../components/TransactionConfirmationModal' +import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal' import CurrencyInputPanel from '../../components/CurrencyInputPanel' import DoubleCurrencyLogo from '../../components/DoubleLogo' import { AddRemoveTabs } from '../../components/NavigationTabs' @@ -448,6 +448,16 @@ export default function RemoveLiquidity({ [currencyIdA, currencyIdB, history] ) + const handleDismissConfirmation = useCallback(() => { + setShowConfirm(false) + setSignatureData(null) // important that we clear signature data to avoid bad sigs + // if there was a tx hash, we want to clear the input + if (txHash) { + onUserInput(Field.LIQUIDITY_PERCENT, '0') + } + setTxHash('') + }, [onUserInput, txHash]) + return ( <> @@ -455,21 +465,18 @@ export default function RemoveLiquidity({ { - setShowConfirm(false) - setSignatureData(null) // important that we clear signature data to avoid bad sigs - // if there was a tx hash, we want to clear the input - if (txHash) { - onUserInput(Field.LIQUIDITY_PERCENT, '0') - } - setTxHash('') - }} + onDismiss={handleDismissConfirmation} attemptingTxn={attemptingTxn} hash={txHash ? txHash : ''} - topContent={modalHeader} - bottomContent={modalBottom} + content={() => ( + + )} pendingText={pendingText} - title="You will receive" /> diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index 5ecf5f57dc2..fb3b0cfb69f 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -8,7 +8,7 @@ import AddressInputPanel from '../../components/AddressInputPanel' import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button' import Card, { GreyCard } from '../../components/Card' import { AutoColumn } from '../../components/Column' -import TransactionConfirmationModal from '../../components/TransactionConfirmationModal' +import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal' import CurrencyInputPanel from '../../components/CurrencyInputPanel' import { SwapPoolTabs } from '../../components/NavigationTabs' import { AutoRow, RowBetween } from '../../components/Row' @@ -265,18 +265,13 @@ export default function Swap() { const showWarning = (!dismissedToken0 && !!currencies[Field.INPUT]) || (!dismissedToken1 && !!currencies[Field.OUTPUT]) - const onConfirmDismiss = useCallback(() => { - setSwapState({ - showConfirm: false, - attemptingTxn: false, - txHash: undefined, - swapErrorMessage: undefined - }) + const handleConfirmDismiss = useCallback(() => { // if there was a tx hash, we want to clear the input if (txHash) { + setSwapState({ showConfirm: false, attemptingTxn, swapErrorMessage, txHash }) onUserInput(Field.INPUT, '') } - }, [onUserInput, txHash]) + }, [attemptingTxn, onUserInput, swapErrorMessage, txHash]) return ( <> @@ -286,12 +281,17 @@ export default function Swap() { ( + + )} pendingText={pendingText} /> From 368c78218a1f5ab39cd4fcd0b525b4b6c0ce5186 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Tue, 4 Aug 2020 15:17:00 -0500 Subject: [PATCH 17/29] show error state in the transaction confirmation modal --- .../TransactionConfirmationModal/index.tsx | 34 ++++++++++++++++--- src/pages/Swap/index.tsx | 27 +++++++++------ 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/components/TransactionConfirmationModal/index.tsx b/src/components/TransactionConfirmationModal/index.tsx index f30212def79..decb5205409 100644 --- a/src/components/TransactionConfirmationModal/index.tsx +++ b/src/components/TransactionConfirmationModal/index.tsx @@ -2,13 +2,13 @@ import { ChainId } from '@uniswap/sdk' import React, { useContext } from 'react' import styled, { ThemeContext } from 'styled-components' import Modal from '../Modal' -import { ExternalLink } from '../../theme' +import { ExternalLink, TYPE } from '../../theme' import { Text } from 'rebass' import { CloseIcon, Spinner } from '../../theme/components' import { RowBetween } from '../Row' -import { ArrowUpCircle } from 'react-feather' -import { ButtonPrimary } from '../Button' -import { AutoColumn, ColumnCenter } from '../Column' +import { AlertTriangle, ArrowUpCircle } from 'react-feather' +import { ButtonConfirmed, ButtonPrimary } from '../Button' +import Column, { AutoColumn, ColumnCenter } from '../Column' import Circle from '../../assets/images/blue-loader.svg' import { getEtherscanLink } from '../../utils' @@ -134,6 +134,32 @@ export function ConfirmationModalContent({ ) } +export function TransactionErrorContent({ message, onDismiss }: { message: string; onDismiss: () => void }) { + const theme = useContext(ThemeContext) + return ( + +
+ + + Error + + + + + + + {message} + + Slippage can be controlled in the settings panel + +
+ + Dismiss + +
+ ) +} + interface ConfirmationModalProps { isOpen: boolean onDismiss: () => void diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index fb3b0cfb69f..d25a2181dc7 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -8,7 +8,10 @@ import AddressInputPanel from '../../components/AddressInputPanel' import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button' import Card, { GreyCard } from '../../components/Card' import { AutoColumn } from '../../components/Column' -import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal' +import TransactionConfirmationModal, { + ConfirmationModalContent, + TransactionErrorContent +} from '../../components/TransactionConfirmationModal' import CurrencyInputPanel from '../../components/CurrencyInputPanel' import { SwapPoolTabs } from '../../components/NavigationTabs' import { AutoRow, RowBetween } from '../../components/Row' @@ -266,9 +269,9 @@ export default function Swap() { (!dismissedToken0 && !!currencies[Field.INPUT]) || (!dismissedToken1 && !!currencies[Field.OUTPUT]) const handleConfirmDismiss = useCallback(() => { + setSwapState({ showConfirm: false, attemptingTxn, swapErrorMessage, txHash }) // if there was a tx hash, we want to clear the input if (txHash) { - setSwapState({ showConfirm: false, attemptingTxn, swapErrorMessage, txHash }) onUserInput(Field.INPUT, '') } }, [attemptingTxn, onUserInput, swapErrorMessage, txHash]) @@ -284,14 +287,18 @@ export default function Swap() { onDismiss={handleConfirmDismiss} attemptingTxn={attemptingTxn} hash={txHash} - content={() => ( - - )} + content={() => + swapErrorMessage ? ( + + ) : ( + + ) + } pendingText={pendingText} /> From 1c5ba1d36edc6b5257404511f408bc9d1d562bda Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Tue, 4 Aug 2020 15:17:22 -0500 Subject: [PATCH 18/29] fix lint errors --- src/components/TransactionConfirmationModal/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/TransactionConfirmationModal/index.tsx b/src/components/TransactionConfirmationModal/index.tsx index decb5205409..48397db88c6 100644 --- a/src/components/TransactionConfirmationModal/index.tsx +++ b/src/components/TransactionConfirmationModal/index.tsx @@ -7,8 +7,8 @@ import { Text } from 'rebass' import { CloseIcon, Spinner } from '../../theme/components' import { RowBetween } from '../Row' import { AlertTriangle, ArrowUpCircle } from 'react-feather' -import { ButtonConfirmed, ButtonPrimary } from '../Button' -import Column, { AutoColumn, ColumnCenter } from '../Column' +import { ButtonPrimary } from '../Button' +import { AutoColumn, ColumnCenter } from '../Column' import Circle from '../../assets/images/blue-loader.svg' import { getEtherscanLink } from '../../utils' From 74b6639bdeae79049d6b89d6a1f29f2d61ee3ca4 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Tue, 4 Aug 2020 15:18:58 -0500 Subject: [PATCH 19/29] error not always relevant --- src/components/TransactionConfirmationModal/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/TransactionConfirmationModal/index.tsx b/src/components/TransactionConfirmationModal/index.tsx index 48397db88c6..72163e470da 100644 --- a/src/components/TransactionConfirmationModal/index.tsx +++ b/src/components/TransactionConfirmationModal/index.tsx @@ -150,7 +150,6 @@ export function TransactionErrorContent({ message, onDismiss }: { message: strin {message} - Slippage can be controlled in the settings panel
From 69e6f17a5028ff4c21af4f6edb87b43ea2e01b28 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Tue, 4 Aug 2020 15:34:39 -0500 Subject: [PATCH 20/29] fix lint errors, remove action item --- .../TransactionConfirmationModal/index.tsx | 6 +- src/pages/Swap/index.tsx | 55 ++++++++++++------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/components/TransactionConfirmationModal/index.tsx b/src/components/TransactionConfirmationModal/index.tsx index 72163e470da..e690d4ca9c3 100644 --- a/src/components/TransactionConfirmationModal/index.tsx +++ b/src/components/TransactionConfirmationModal/index.tsx @@ -2,7 +2,7 @@ import { ChainId } from '@uniswap/sdk' import React, { useContext } from 'react' import styled, { ThemeContext } from 'styled-components' import Modal from '../Modal' -import { ExternalLink, TYPE } from '../../theme' +import { ExternalLink } from '../../theme' import { Text } from 'rebass' import { CloseIcon, Spinner } from '../../theme/components' import { RowBetween } from '../Row' @@ -145,9 +145,9 @@ export function TransactionErrorContent({ message, onDismiss }: { message: strin - + - + {message} diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index d25a2181dc7..740251ce43e 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -182,7 +182,7 @@ export default function Swap() { const { priceImpactWithoutFee, realizedLPFee } = computeTradePriceBreakdown(trade) - function onSwap() { + const handleSwap = useCallback(() => { if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) { return } @@ -212,7 +212,7 @@ export default function Swap() { .catch(error => { setSwapState({ attemptingTxn: false, showConfirm, swapErrorMessage: error.message, txHash: undefined }) }) - } + }, [account, priceImpactWithoutFee, recipient, recipientAddress, showConfirm, swapCallback, trade]) // errors const [showInverted, setShowInverted] = useState(false) @@ -229,7 +229,7 @@ export default function Swap() { (approvalSubmitted && approval === ApprovalState.APPROVED)) && !(priceImpactSeverity > 3 && !isExpertMode) - function modalHeader() { + const modalHeader = useCallback(() => { return trade ? ( ) : null - } + }, [formattedAmounts, independentField, priceImpactSeverity, recipient, slippageAdjustedAmounts, trade]) - function modalBottom() { + const modalBottom = useCallback(() => { return trade && realizedLPFee ? ( ) : null - } + }, [ + handleSwap, + priceImpactSeverity, + priceImpactWithoutFee, + realizedLPFee, + showInverted, + slippageAdjustedAmounts, + swapErrorMessage, + trade + ]) // text to show while loading const pendingText = `Swapping ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${ @@ -276,6 +285,21 @@ export default function Swap() { } }, [attemptingTxn, onUserInput, swapErrorMessage, txHash]) + const confirmationContent = useCallback( + () => + swapErrorMessage ? ( + + ) : ( + + ), + [handleConfirmDismiss, modalBottom, modalHeader, swapErrorMessage] + ) + return ( <> {showWarning && } @@ -287,18 +311,7 @@ export default function Swap() { onDismiss={handleConfirmDismiss} attemptingTxn={attemptingTxn} hash={txHash} - content={() => - swapErrorMessage ? ( - - ) : ( - - ) - } + content={confirmationContent} pendingText={pendingText} /> @@ -427,7 +440,7 @@ export default function Swap() { { if (isExpertMode) { - onSwap() + handleSwap() } else { setSwapState({ attemptingTxn: false, @@ -455,7 +468,7 @@ export default function Swap() { { if (isExpertMode) { - onSwap() + handleSwap() } else { setSwapState({ attemptingTxn: false, From a3ebbb190639c687afaa0d1487e4ff642665f251 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Tue, 4 Aug 2020 18:37:25 -0500 Subject: [PATCH 21/29] move a lot of code into ConfirmSwapModal.tsx --- src/components/AccountDetails/index.tsx | 9 +- src/components/swap/ConfirmSwapModal.tsx | 76 +++++++++++++++ src/components/swap/SwapModalFooter.tsx | 47 +++++----- src/components/swap/SwapModalHeader.tsx | 25 ++--- src/constants/index.ts | 112 ++++++++++++----------- src/pages/Swap/index.tsx | 110 ++++++---------------- 6 files changed, 206 insertions(+), 173 deletions(-) create mode 100644 src/components/swap/ConfirmSwapModal.tsx diff --git a/src/components/AccountDetails/index.tsx b/src/components/AccountDetails/index.tsx index d64df155333..82caecdd2ff 100644 --- a/src/components/AccountDetails/index.tsx +++ b/src/components/AccountDetails/index.tsx @@ -251,26 +251,26 @@ export default function AccountDetails({ } else if (connector === walletconnect) { return ( - {''} + {'wallet ) } else if (connector === walletlink) { return ( - {''} + {'coinbase ) } else if (connector === fortmatic) { return ( - {''} + {'fortmatic ) } else if (connector === portis) { return ( <> - {''} + {'portis { portis.portis.showPortis() @@ -382,7 +382,6 @@ export default function AccountDetails({ )} - {/* {formatConnectorName()} */} diff --git a/src/components/swap/ConfirmSwapModal.tsx b/src/components/swap/ConfirmSwapModal.tsx new file mode 100644 index 00000000000..af6ccc9b0f3 --- /dev/null +++ b/src/components/swap/ConfirmSwapModal.tsx @@ -0,0 +1,76 @@ +import { Trade } from '@uniswap/sdk' +import React, { useCallback } from 'react' +import TransactionConfirmationModal, { + ConfirmationModalContent, + TransactionErrorContent +} from '../TransactionConfirmationModal' +import SwapModalFooter from './SwapModalFooter' +import SwapModalHeader from './SwapModalHeader' + +export default function ConfirmSwapModal({ + trade, + allowedSlippage, + onConfirm, + onDismiss, + recipient, + swapErrorMessage, + isOpen, + attemptingTxn, + txHash +}: { + isOpen: boolean + trade: Trade | undefined + attemptingTxn: boolean + txHash: string | undefined + recipient: string | null + allowedSlippage: number + onConfirm: () => void + swapErrorMessage: string | undefined + onDismiss: () => void +}) { + const modalHeader = useCallback(() => { + return trade ? : null + }, [allowedSlippage, recipient, trade]) + + const modalBottom = useCallback(() => { + return trade ? ( + + ) : null + }, [allowedSlippage, onConfirm, swapErrorMessage, trade]) + + // text to show while loading + const pendingText = `Swapping ${trade?.inputAmount?.toSignificant(6)} ${ + trade?.inputAmount?.currency?.symbol + } for ${trade?.outputAmount?.toSignificant(6)} ${trade?.outputAmount?.currency?.symbol}` + + const confirmationContent = useCallback( + () => + swapErrorMessage ? ( + + ) : ( + + ), + [onDismiss, modalBottom, modalHeader, swapErrorMessage] + ) + + return ( + + ) +} diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index 8b7ba78107e..23566968ca3 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -1,11 +1,16 @@ -import { CurrencyAmount, Percent, Trade, TradeType } from '@uniswap/sdk' -import React, { useContext } from 'react' +import { Trade, TradeType } from '@uniswap/sdk' +import React, { useContext, useMemo, useState } from 'react' import { Repeat } from 'react-feather' import { Text } from 'rebass' import { ThemeContext } from 'styled-components' import { Field } from '../../state/swap/actions' import { TYPE } from '../../theme' -import { formatExecutionPrice } from '../../utils/prices' +import { + computeSlippageAdjustedAmounts, + computeTradePriceBreakdown, + formatExecutionPrice, + warningSeverity +} from '../../utils/prices' import { ButtonError } from '../Button' import { AutoColumn } from '../Column' import QuestionHelper from '../QuestionHelper' @@ -15,30 +20,23 @@ import { StyledBalanceMaxMini, SwapCallbackError } from './styleds' export default function SwapModalFooter({ trade, - showInverted, - setShowInverted, - severity, - slippageAdjustedAmounts, - onSwap, - realizedLPFee, - priceImpactWithoutFee, + onConfirm, + allowedSlippage, swapErrorMessage }: { trade: Trade - showInverted: boolean - setShowInverted: (inverted: boolean) => void - severity: number - slippageAdjustedAmounts: { [field in Field]?: CurrencyAmount } - onSwap: () => any - realizedLPFee: CurrencyAmount - priceImpactWithoutFee?: Percent + allowedSlippage: number + onConfirm: () => void swapErrorMessage: string | undefined }) { + const [showInverted, setShowInverted] = useState(false) const theme = useContext(ThemeContext) - - if (!trade) { - return null - } + const slippageAdjustedAmounts = useMemo(() => computeSlippageAdjustedAmounts(trade, allowedSlippage), [ + allowedSlippage, + trade + ]) + const { priceImpactWithoutFee, realizedLPFee } = useMemo(() => computeTradePriceBreakdown(trade), [trade]) + const severity = warningSeverity(priceImpactWithoutFee) return ( <> @@ -109,7 +107,12 @@ export default function SwapModalFooter({ - 2} style={{ margin: '10px 0 0 0' }} id="confirm-swap-or-send"> + 2} + style={{ margin: '10px 0 0 0' }} + id="confirm-swap-or-send" + > {severity > 2 ? 'Swap Anyway' : 'Confirm Swap'} diff --git a/src/components/swap/SwapModalHeader.tsx b/src/components/swap/SwapModalHeader.tsx index 87b2db734f9..69847eb767c 100644 --- a/src/components/swap/SwapModalHeader.tsx +++ b/src/components/swap/SwapModalHeader.tsx @@ -1,11 +1,12 @@ -import { CurrencyAmount, Trade, TradeType } from '@uniswap/sdk' -import React, { useContext } from 'react' +import { Trade, TradeType } from '@uniswap/sdk' +import React, { useContext, useMemo } from 'react' import { ArrowDown } from 'react-feather' import { Text } from 'rebass' import { ThemeContext } from 'styled-components' import { Field } from '../../state/swap/actions' import { TYPE } from '../../theme' import { isAddress, shortenAddress } from '../../utils' +import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices' import { AutoColumn } from '../Column' import CurrencyLogo from '../CurrencyLogo' import { RowBetween, RowFixed } from '../Row' @@ -13,25 +14,27 @@ import { TruncatedText } from './styleds' export default function SwapModalHeader({ trade, - formattedAmounts, - slippageAdjustedAmounts, - priceImpactSeverity, + allowedSlippage, recipient }: { trade: Trade - formattedAmounts: { [field in Field]?: string } - slippageAdjustedAmounts: { [field in Field]?: CurrencyAmount } - priceImpactSeverity: number - independentField: Field + allowedSlippage: number recipient: string | null }) { + const slippageAdjustedAmounts = useMemo(() => computeSlippageAdjustedAmounts(trade, allowedSlippage), [ + trade, + allowedSlippage + ]) + const { priceImpactWithoutFee } = useMemo(() => computeTradePriceBreakdown(trade), [trade]) + const priceImpactSeverity = warningSeverity(priceImpactWithoutFee) + const theme = useContext(ThemeContext) return ( - {formattedAmounts[Field.INPUT]} + {trade.inputAmount.toSignificant(6)} @@ -45,7 +48,7 @@ export default function SwapModalHeader({ 2 ? theme.red1 : ''}> - {formattedAmounts[Field.OUTPUT]} + {trade.outputAmount.toSignificant(6)} diff --git a/src/constants/index.ts b/src/constants/index.ts index 0420b4f1aa3..aa5470c37cc 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,3 +1,4 @@ +import { AbstractConnector } from '@web3-react/abstract-connector' import { ChainId, JSBI, Percent, Token, WETH } from '@uniswap/sdk' import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors' @@ -52,7 +53,19 @@ export const PINNED_PAIRS: { readonly [chainId in ChainId]?: [Token, Token][] } ] } -const TESTNET_CAPABLE_WALLETS = { +export interface WalletInfo { + connector?: AbstractConnector + name: string + iconName: string + description: string + href: string | null + color: string + primary?: true + mobile?: true + mobileOnly?: true +} + +export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = { INJECTED: { connector: injected, name: 'Injected', @@ -69,62 +82,53 @@ const TESTNET_CAPABLE_WALLETS = { description: 'Easy-to-use browser extension.', href: null, color: '#E8831D' + }, + WALLET_CONNECT: { + connector: walletconnect, + name: 'WalletConnect', + iconName: 'walletConnectIcon.svg', + description: 'Connect to Trust Wallet, Rainbow Wallet and more...', + href: null, + color: '#4196FC', + mobile: true + }, + WALLET_LINK: { + connector: walletlink, + name: 'Coinbase Wallet', + iconName: 'coinbaseWalletIcon.svg', + description: 'Use Coinbase Wallet app on mobile device', + href: null, + color: '#315CF5' + }, + COINBASE_LINK: { + name: 'Open in Coinbase Wallet', + iconName: 'coinbaseWalletIcon.svg', + description: 'Open in Coinbase Wallet app.', + href: 'https://go.cb-w.com/mtUDhEZPy1', + color: '#315CF5', + mobile: true, + mobileOnly: true + }, + FORTMATIC: { + connector: fortmatic, + name: 'Fortmatic', + iconName: 'fortmaticIcon.png', + description: 'Login using Fortmatic hosted wallet', + href: null, + color: '#6748FF', + mobile: true + }, + Portis: { + connector: portis, + name: 'Portis', + iconName: 'portisIcon.png', + description: 'Login using Portis hosted wallet', + href: null, + color: '#4A6C9B', + mobile: true } } -export const SUPPORTED_WALLETS = - process.env.REACT_APP_CHAIN_ID !== '1' - ? TESTNET_CAPABLE_WALLETS - : { - ...TESTNET_CAPABLE_WALLETS, - ...{ - WALLET_CONNECT: { - connector: walletconnect, - name: 'WalletConnect', - iconName: 'walletConnectIcon.svg', - description: 'Connect to Trust Wallet, Rainbow Wallet and more...', - href: null, - color: '#4196FC', - mobile: true - }, - WALLET_LINK: { - connector: walletlink, - name: 'Coinbase Wallet', - iconName: 'coinbaseWalletIcon.svg', - description: 'Use Coinbase Wallet app on mobile device', - href: null, - color: '#315CF5' - }, - COINBASE_LINK: { - name: 'Open in Coinbase Wallet', - iconName: 'coinbaseWalletIcon.svg', - description: 'Open in Coinbase Wallet app.', - href: 'https://go.cb-w.com/mtUDhEZPy1', - color: '#315CF5', - mobile: true, - mobileOnly: true - }, - FORTMATIC: { - connector: fortmatic, - name: 'Fortmatic', - iconName: 'fortmaticIcon.png', - description: 'Login using Fortmatic hosted wallet', - href: null, - color: '#6748FF', - mobile: true - }, - Portis: { - connector: portis, - name: 'Portis', - iconName: 'portisIcon.png', - description: 'Login using Portis hosted wallet', - href: null, - color: '#4A6C9B', - mobile: true - } - } - } - export const NetworkContextName = 'NETWORK' // default allowed slippage, in bips diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index 740251ce43e..445da53031c 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -1,4 +1,4 @@ -import { CurrencyAmount, JSBI } from '@uniswap/sdk' +import { CurrencyAmount, JSBI, Trade } from '@uniswap/sdk' import React, { useCallback, useContext, useEffect, useState } from 'react' import { ArrowDown } from 'react-feather' import ReactGA from 'react-ga' @@ -8,10 +8,7 @@ import AddressInputPanel from '../../components/AddressInputPanel' import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button' import Card, { GreyCard } from '../../components/Card' import { AutoColumn } from '../../components/Column' -import TransactionConfirmationModal, { - ConfirmationModalContent, - TransactionErrorContent -} from '../../components/TransactionConfirmationModal' +import ConfirmSwapModal from '../../components/swap/ConfirmSwapModal' import CurrencyInputPanel from '../../components/CurrencyInputPanel' import { SwapPoolTabs } from '../../components/NavigationTabs' import { AutoRow, RowBetween } from '../../components/Row' @@ -19,8 +16,6 @@ import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetai import BetterTradeLink from '../../components/swap/BetterTradeLink' import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee' import { ArrowWrapper, BottomGrouping, Dots, SwapCallbackError, Wrapper } from '../../components/swap/styleds' -import SwapModalFooter from '../../components/swap/SwapModalFooter' -import SwapModalHeader from '../../components/swap/SwapModalHeader' import TradePrice from '../../components/swap/TradePrice' import { TokenWarningCards } from '../../components/TokenWarningCard' @@ -48,7 +43,7 @@ import { } from '../../state/user/hooks' import { CursorPointer, LinkStyledButton, TYPE } from '../../theme' import { maxAmountSpend } from '../../utils/maxAmountSpend' -import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices' +import { computeTradePriceBreakdown, warningSeverity } from '../../utils/prices' import AppBody from '../AppBody' import { ClickableText } from '../Pool/styleds' @@ -129,13 +124,15 @@ export default function Swap() { ) // modal and loading - const [{ showConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{ + const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{ showConfirm: boolean + tradeToConfirm: Trade | null attemptingTxn: boolean swapErrorMessage: string | undefined txHash: string | undefined }>({ showConfirm: false, + tradeToConfirm: null, attemptingTxn: false, swapErrorMessage: undefined, txHash: undefined @@ -170,8 +167,6 @@ export default function Swap() { const maxAmountInput: CurrencyAmount | undefined = maxAmountSpend(currencyBalances[Field.INPUT]) const atMaxAmountInput = Boolean(maxAmountInput && parsedAmounts[Field.INPUT]?.equalTo(maxAmountInput)) - const slippageAdjustedAmounts = computeSlippageAdjustedAmounts(trade, allowedSlippage) - // the callback to execute the swap const { callback: swapCallback, error: swapCallbackError } = useSwapCallback( trade, @@ -180,7 +175,7 @@ export default function Swap() { recipient ) - const { priceImpactWithoutFee, realizedLPFee } = computeTradePriceBreakdown(trade) + const { priceImpactWithoutFee } = computeTradePriceBreakdown(trade) const handleSwap = useCallback(() => { if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) { @@ -189,10 +184,10 @@ export default function Swap() { if (!swapCallback) { return } - setSwapState({ attemptingTxn: true, showConfirm, swapErrorMessage: undefined, txHash: undefined }) + setSwapState({ attemptingTxn: true, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: undefined }) swapCallback() .then(hash => { - setSwapState({ attemptingTxn: false, showConfirm, swapErrorMessage: undefined, txHash: hash }) + setSwapState({ attemptingTxn: false, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: hash }) ReactGA.event({ category: 'Swap', @@ -210,9 +205,15 @@ export default function Swap() { }) }) .catch(error => { - setSwapState({ attemptingTxn: false, showConfirm, swapErrorMessage: error.message, txHash: undefined }) + setSwapState({ + attemptingTxn: false, + tradeToConfirm, + showConfirm, + swapErrorMessage: error.message, + txHash: undefined + }) }) - }, [account, priceImpactWithoutFee, recipient, recipientAddress, showConfirm, swapCallback, trade]) + }, [tradeToConfirm, account, priceImpactWithoutFee, recipient, recipientAddress, showConfirm, swapCallback, trade]) // errors const [showInverted, setShowInverted] = useState(false) @@ -229,76 +230,18 @@ export default function Swap() { (approvalSubmitted && approval === ApprovalState.APPROVED)) && !(priceImpactSeverity > 3 && !isExpertMode) - const modalHeader = useCallback(() => { - return trade ? ( - - ) : null - }, [formattedAmounts, independentField, priceImpactSeverity, recipient, slippageAdjustedAmounts, trade]) - - const modalBottom = useCallback(() => { - return trade && realizedLPFee ? ( - - ) : null - }, [ - handleSwap, - priceImpactSeverity, - priceImpactWithoutFee, - realizedLPFee, - showInverted, - slippageAdjustedAmounts, - swapErrorMessage, - trade - ]) - - // text to show while loading - const pendingText = `Swapping ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${ - currencies[Field.INPUT]?.symbol - } for ${parsedAmounts[Field.OUTPUT]?.toSignificant(6)} ${currencies[Field.OUTPUT]?.symbol}` - const [dismissedToken0] = useTokenWarningDismissal(chainId, currencies[Field.INPUT]) const [dismissedToken1] = useTokenWarningDismissal(chainId, currencies[Field.OUTPUT]) const showWarning = (!dismissedToken0 && !!currencies[Field.INPUT]) || (!dismissedToken1 && !!currencies[Field.OUTPUT]) const handleConfirmDismiss = useCallback(() => { - setSwapState({ showConfirm: false, attemptingTxn, swapErrorMessage, txHash }) + setSwapState({ showConfirm: false, tradeToConfirm, attemptingTxn, swapErrorMessage, txHash }) // if there was a tx hash, we want to clear the input if (txHash) { onUserInput(Field.INPUT, '') } - }, [attemptingTxn, onUserInput, swapErrorMessage, txHash]) - - const confirmationContent = useCallback( - () => - swapErrorMessage ? ( - - ) : ( - - ), - [handleConfirmDismiss, modalBottom, modalHeader, swapErrorMessage] - ) + }, [attemptingTxn, onUserInput, swapErrorMessage, tradeToConfirm, txHash]) return ( <> @@ -306,13 +249,16 @@ export default function Swap() { - @@ -443,6 +389,7 @@ export default function Swap() { handleSwap() } else { setSwapState({ + tradeToConfirm: trade ?? null, attemptingTxn: false, swapErrorMessage: undefined, showConfirm: true, @@ -471,6 +418,7 @@ export default function Swap() { handleSwap() } else { setSwapState({ + tradeToConfirm: trade ?? null, attemptingTxn: false, swapErrorMessage: undefined, showConfirm: true, From 00d2bf66b6d3b765310e975d3288551ad6acf656 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Tue, 4 Aug 2020 18:53:04 -0500 Subject: [PATCH 22/29] show accept changes flow, not styled --- src/components/swap/ConfirmSwapModal.tsx | 43 +++++++++++++++++++++--- src/components/swap/SwapModalFooter.tsx | 5 ++- src/components/swap/SwapModalHeader.tsx | 16 +++++++-- src/pages/Swap/index.tsx | 14 +++++--- 4 files changed, 66 insertions(+), 12 deletions(-) diff --git a/src/components/swap/ConfirmSwapModal.tsx b/src/components/swap/ConfirmSwapModal.tsx index af6ccc9b0f3..24ed3f3d664 100644 --- a/src/components/swap/ConfirmSwapModal.tsx +++ b/src/components/swap/ConfirmSwapModal.tsx @@ -1,5 +1,5 @@ -import { Trade } from '@uniswap/sdk' -import React, { useCallback } from 'react' +import { currencyEquals, Trade } from '@uniswap/sdk' +import React, { useCallback, useMemo } from 'react' import TransactionConfirmationModal, { ConfirmationModalContent, TransactionErrorContent @@ -7,8 +7,25 @@ import TransactionConfirmationModal, { import SwapModalFooter from './SwapModalFooter' import SwapModalHeader from './SwapModalHeader' +/** + * Returns true if the trade requires a confirmation of details before we can submit it + * @param tradeA trade A + * @param tradeB trade B + */ +function tradeMeaningfullyDiffers(tradeA: Trade, tradeB: Trade): boolean { + return ( + tradeA.tradeType !== tradeB.tradeType || + !currencyEquals(tradeA.inputAmount.currency, tradeB.inputAmount.currency) || + !tradeA.inputAmount.equalTo(tradeB.inputAmount) || + !currencyEquals(tradeA.outputAmount.currency, tradeB.outputAmount.currency) || + !tradeA.outputAmount.equalTo(tradeB.outputAmount) + ) +} + export default function ConfirmSwapModal({ trade, + originalTrade, + onAcceptChanges, allowedSlippage, onConfirm, onDismiss, @@ -20,28 +37,44 @@ export default function ConfirmSwapModal({ }: { isOpen: boolean trade: Trade | undefined + originalTrade: Trade | undefined attemptingTxn: boolean txHash: string | undefined recipient: string | null allowedSlippage: number + onAcceptChanges: () => void onConfirm: () => void swapErrorMessage: string | undefined onDismiss: () => void }) { + const showAcceptChanges = useMemo( + () => Boolean(trade && originalTrade && tradeMeaningfullyDiffers(trade, originalTrade)), + [originalTrade, trade] + ) + const modalHeader = useCallback(() => { - return trade ? : null - }, [allowedSlippage, recipient, trade]) + return trade ? ( + + ) : null + }, [allowedSlippage, onAcceptChanges, recipient, showAcceptChanges, trade]) const modalBottom = useCallback(() => { return trade ? ( ) : null - }, [allowedSlippage, onConfirm, swapErrorMessage, trade]) + }, [allowedSlippage, onConfirm, showAcceptChanges, swapErrorMessage, trade]) // text to show while loading const pendingText = `Swapping ${trade?.inputAmount?.toSignificant(6)} ${ diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx index 23566968ca3..369ff88b03c 100644 --- a/src/components/swap/SwapModalFooter.tsx +++ b/src/components/swap/SwapModalFooter.tsx @@ -22,12 +22,14 @@ export default function SwapModalFooter({ trade, onConfirm, allowedSlippage, - swapErrorMessage + swapErrorMessage, + disabledConfirm }: { trade: Trade allowedSlippage: number onConfirm: () => void swapErrorMessage: string | undefined + disabledConfirm: boolean }) { const [showInverted, setShowInverted] = useState(false) const theme = useContext(ThemeContext) @@ -109,6 +111,7 @@ export default function SwapModalFooter({ 2} style={{ margin: '10px 0 0 0' }} id="confirm-swap-or-send" diff --git a/src/components/swap/SwapModalHeader.tsx b/src/components/swap/SwapModalHeader.tsx index 69847eb767c..1b88b9d578b 100644 --- a/src/components/swap/SwapModalHeader.tsx +++ b/src/components/swap/SwapModalHeader.tsx @@ -4,7 +4,7 @@ import { ArrowDown } from 'react-feather' import { Text } from 'rebass' import { ThemeContext } from 'styled-components' import { Field } from '../../state/swap/actions' -import { TYPE } from '../../theme' +import { LinkStyledButton, TYPE } from '../../theme' import { isAddress, shortenAddress } from '../../utils' import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices' import { AutoColumn } from '../Column' @@ -15,11 +15,15 @@ import { TruncatedText } from './styleds' export default function SwapModalHeader({ trade, allowedSlippage, - recipient + recipient, + showAcceptChanges, + onAcceptChanges }: { trade: Trade allowedSlippage: number recipient: string | null + showAcceptChanges: boolean + onAcceptChanges: () => void }) { const slippageAdjustedAmounts = useMemo(() => computeSlippageAdjustedAmounts(trade, allowedSlippage), [ trade, @@ -84,6 +88,14 @@ export default function SwapModalHeader({ ) : null} + {showAcceptChanges ? ( + + + This trade has changed. Please review and{' '} + accept changes to continue. + + + ) : null} ) } diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index 445da53031c..8068fbc251f 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -126,13 +126,13 @@ export default function Swap() { // modal and loading const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{ showConfirm: boolean - tradeToConfirm: Trade | null + tradeToConfirm: Trade | undefined attemptingTxn: boolean swapErrorMessage: string | undefined txHash: string | undefined }>({ showConfirm: false, - tradeToConfirm: null, + tradeToConfirm: undefined, attemptingTxn: false, swapErrorMessage: undefined, txHash: undefined @@ -243,6 +243,10 @@ export default function Swap() { } }, [attemptingTxn, onUserInput, swapErrorMessage, tradeToConfirm, txHash]) + const handleAcceptChanges = useCallback(() => { + setSwapState({ tradeToConfirm: trade, swapErrorMessage, txHash, attemptingTxn, showConfirm }) + }, [attemptingTxn, showConfirm, swapErrorMessage, trade, txHash]) + return ( <> {showWarning && } @@ -252,6 +256,8 @@ export default function Swap() { Date: Wed, 5 Aug 2020 11:00:47 -0400 Subject: [PATCH 23/29] Adjust styles for slippage error states --- src/components/Button/index.tsx | 2 ++ .../TransactionConfirmationModal/index.tsx | 6 ++-- src/components/swap/styleds.tsx | 35 +++++++++++++++---- src/hooks/useSwapCallback.ts | 2 +- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 2a14966df1d..6d1e8ce3556 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -27,6 +27,8 @@ const Base = styled(RebassButton)<{ flex-wrap: nowrap; align-items: center; cursor: pointer; + position: relative; + z-index: 1; &:disabled { cursor: auto; } diff --git a/src/components/TransactionConfirmationModal/index.tsx b/src/components/TransactionConfirmationModal/index.tsx index e690d4ca9c3..dcfb77f6f0f 100644 --- a/src/components/TransactionConfirmationModal/index.tsx +++ b/src/components/TransactionConfirmationModal/index.tsx @@ -145,9 +145,9 @@ export function TransactionErrorContent({ message, onDismiss }: { message: strin - - - + + + {message} diff --git a/src/components/swap/styleds.tsx b/src/components/swap/styleds.tsx index 40acef40782..8bd0159b090 100644 --- a/src/components/swap/styleds.tsx +++ b/src/components/swap/styleds.tsx @@ -1,4 +1,4 @@ -import { darken } from 'polished' +import { darken, transparentize } from 'polished' import React from 'react' import { AlertTriangle } from 'react-feather' import styled, { css } from 'styled-components' @@ -98,20 +98,41 @@ export const Dots = styled.span` ` const SwapCallbackErrorInner = styled.div` - background-color: ${({ theme }) => theme.red1}; + background-color: ${({ theme }) => transparentize(0.9, theme.red1)}; border-radius: 1rem; - padding: 1rem; display: flex; align-items: center; - margin-top: 1rem; + font-size: 0.825rem; width: 100%; - border: 1px solid ${({ theme }) => darken(0.1, theme.red1)}; - color: ${({ theme }) => theme.white}; + padding: 3rem 1.5rem 1rem 1rem; + margin-top: -2rem; + color: ${({ theme }) => theme.red1}; + z-index: -1; + p { + padding: 0; + margin: 0; + font-weight: 500; + } ` + +const SwapCallbackErrorInnerAlertTriangle = styled.div` + background-color: ${({ theme }) => transparentize(0.9, theme.red1)}; + display: flex; + align-items: center; + justify-content: center; + margin-right: 12px; + border-radius: 12px; + min-width: 48px; + height: 48px; +` + export function SwapCallbackError({ error }: { error: string }) { return ( - {error} + + + +

{error}

) } diff --git a/src/hooks/useSwapCallback.ts b/src/hooks/useSwapCallback.ts index 067e24843d8..e8b39cf082f 100644 --- a/src/hooks/useSwapCallback.ts +++ b/src/hooks/useSwapCallback.ts @@ -170,7 +170,7 @@ export function useSwapCallback( case 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT': case 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT': errorMessage = - 'The transaction will not succeed either due to price movement or fee on transfer. Try increasing your slippage tolerance.' + 'This transaction will not succeed either due to price movement or fee on transfer. Try increasing your slippage tolerance.' break } return { call, error: new Error(errorMessage) } From 9d47801846f1d1a81f3d1f144aab6514992e4319 Mon Sep 17 00:00:00 2001 From: Callil Capuozzo Date: Wed, 5 Aug 2020 12:33:49 -0400 Subject: [PATCH 24/29] Add styles for updated price prompt --- src/components/swap/SwapModalHeader.tsx | 59 ++++++++++++++++--------- src/components/swap/styleds.tsx | 13 +++++- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/components/swap/SwapModalHeader.tsx b/src/components/swap/SwapModalHeader.tsx index 1b88b9d578b..f433fd088af 100644 --- a/src/components/swap/SwapModalHeader.tsx +++ b/src/components/swap/SwapModalHeader.tsx @@ -1,16 +1,17 @@ import { Trade, TradeType } from '@uniswap/sdk' import React, { useContext, useMemo } from 'react' -import { ArrowDown } from 'react-feather' +import { ArrowDown, AlertTriangle } from 'react-feather' import { Text } from 'rebass' import { ThemeContext } from 'styled-components' import { Field } from '../../state/swap/actions' import { LinkStyledButton, TYPE } from '../../theme' +import { ButtonPrimary } from '../Button' import { isAddress, shortenAddress } from '../../utils' import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices' import { AutoColumn } from '../Column' import CurrencyLogo from '../CurrencyLogo' import { RowBetween, RowFixed } from '../Row' -import { TruncatedText } from './styleds' +import { TruncatedText, SwapShowAcceptChanges } from './styleds' export default function SwapModalHeader({ trade, @@ -37,30 +38,54 @@ export default function SwapModalHeader({ return ( - - {trade.inputAmount.toSignificant(6)} - - - + + + + {trade.inputAmount.toSignificant(6)} + + + {trade.inputAmount.currency.symbol} - + - 2 ? theme.red1 : ''}> - {trade.outputAmount.toSignificant(6)} - - - + + + 2 ? theme.red1 : showAcceptChanges ? theme.primary1 : ''} + > + {trade.outputAmount.toSignificant(6)} + + + {trade.outputAmount.currency.symbol} + {showAcceptChanges ? ( + + + + + Price Updated + + + Continue + + + + ) : null} {trade.tradeType === TradeType.EXACT_INPUT ? ( @@ -88,14 +113,6 @@ export default function SwapModalHeader({ ) : null} - {showAcceptChanges ? ( - - - This trade has changed. Please review and{' '} - accept changes to continue. - - - ) : null} ) } diff --git a/src/components/swap/styleds.tsx b/src/components/swap/styleds.tsx index 8bd0159b090..0655eb25250 100644 --- a/src/components/swap/styleds.tsx +++ b/src/components/swap/styleds.tsx @@ -3,6 +3,7 @@ import React from 'react' import { AlertTriangle } from 'react-feather' import styled, { css } from 'styled-components' import { Text } from 'rebass' +import { AutoColumn } from '../Column' export const Wrapper = styled.div` position: relative; @@ -104,7 +105,7 @@ const SwapCallbackErrorInner = styled.div` align-items: center; font-size: 0.825rem; width: 100%; - padding: 3rem 1.5rem 1rem 1rem; + padding: 3rem 1.25rem 1rem 1rem; margin-top: -2rem; color: ${({ theme }) => theme.red1}; z-index: -1; @@ -130,9 +131,17 @@ export function SwapCallbackError({ error }: { error: string }) { return ( - +

{error}

) } + +export const SwapShowAcceptChanges = styled(AutoColumn)` + background-color: ${({ theme }) => transparentize(0.9, theme.primary1)}; + color: ${({ theme }) => theme.primary1}; + padding: 0.5rem; + border-radius: 12px; + margin-top: 8px; +` From b368a62043fe2fbd2a4e03f073f5857fc753a828 Mon Sep 17 00:00:00 2001 From: Callil Capuozzo Date: Wed, 5 Aug 2020 12:49:35 -0400 Subject: [PATCH 25/29] Add input/output highlighting --- src/components/swap/SwapModalHeader.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/components/swap/SwapModalHeader.tsx b/src/components/swap/SwapModalHeader.tsx index f433fd088af..30b207bae8a 100644 --- a/src/components/swap/SwapModalHeader.tsx +++ b/src/components/swap/SwapModalHeader.tsx @@ -40,7 +40,11 @@ export default function SwapModalHeader({ - + {trade.inputAmount.toSignificant(6)} @@ -59,7 +63,13 @@ export default function SwapModalHeader({ 2 ? theme.red1 : showAcceptChanges ? theme.primary1 : ''} + color={ + priceImpactSeverity > 2 + ? theme.red1 + : showAcceptChanges && trade.tradeType === TradeType.EXACT_INPUT + ? theme.primary1 + : '' + } > {trade.outputAmount.toSignificant(6)} @@ -81,7 +91,7 @@ export default function SwapModalHeader({ style={{ padding: '.5rem', width: 'fit-content', fontSize: '0.825rem', borderRadius: '12px' }} onClick={onAcceptChanges} > - Continue + Accept From 2aa81caa39cd743fa316e3d80b0f73bcddb87aee Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Wed, 5 Aug 2020 12:00:31 -0500 Subject: [PATCH 26/29] lint errors --- src/components/swap/SwapModalHeader.tsx | 2 +- src/components/swap/styleds.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/swap/SwapModalHeader.tsx b/src/components/swap/SwapModalHeader.tsx index 30b207bae8a..e82e51ad08e 100644 --- a/src/components/swap/SwapModalHeader.tsx +++ b/src/components/swap/SwapModalHeader.tsx @@ -4,7 +4,7 @@ import { ArrowDown, AlertTriangle } from 'react-feather' import { Text } from 'rebass' import { ThemeContext } from 'styled-components' import { Field } from '../../state/swap/actions' -import { LinkStyledButton, TYPE } from '../../theme' +import { TYPE } from '../../theme' import { ButtonPrimary } from '../Button' import { isAddress, shortenAddress } from '../../utils' import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices' diff --git a/src/components/swap/styleds.tsx b/src/components/swap/styleds.tsx index 0655eb25250..bfc98be6898 100644 --- a/src/components/swap/styleds.tsx +++ b/src/components/swap/styleds.tsx @@ -1,4 +1,4 @@ -import { darken, transparentize } from 'polished' +import { transparentize } from 'polished' import React from 'react' import { AlertTriangle } from 'react-feather' import styled, { css } from 'styled-components' From dc02608775b8ac57fdc36aa17d677825b57115af Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Wed, 5 Aug 2020 12:08:04 -0500 Subject: [PATCH 27/29] fix link to wallets in modal --- src/components/WalletModal/index.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/WalletModal/index.tsx b/src/components/WalletModal/index.tsx index 6b21dee3ebd..b6b2356bba2 100644 --- a/src/components/WalletModal/index.tsx +++ b/src/components/WalletModal/index.tsx @@ -349,9 +349,7 @@ export default function WalletModal({ {walletView !== WALLET_VIEWS.PENDING && ( New to Ethereum?  {' '} - - Learn more about wallets - + Learn more about wallets )} From 09468913b41ed5e5c524fe182d116376be88a04a Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Wed, 5 Aug 2020 15:48:08 -0500 Subject: [PATCH 28/29] use total supply instead of reserves for `noLiquidity` (fixes #701) --- src/state/mint/hooks.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/state/mint/hooks.ts b/src/state/mint/hooks.ts index 853f95c8953..c7d5fbf121e 100644 --- a/src/state/mint/hooks.ts +++ b/src/state/mint/hooks.ts @@ -50,9 +50,10 @@ export function useDerivedMintInfo( // pair const [pairState, pair] = usePair(currencies[Field.CURRENCY_A], currencies[Field.CURRENCY_B]) + const totalSupply = useTotalSupply(pair?.liquidityToken) + const noLiquidity: boolean = - pairState === PairState.NOT_EXISTS || - Boolean(pair && JSBI.equal(pair.reserve0.raw, ZERO) && JSBI.equal(pair.reserve1.raw, ZERO)) + pairState === PairState.NOT_EXISTS || Boolean(totalSupply && JSBI.equal(totalSupply.raw, ZERO)) // balances const balances = useCurrencyBalances(account ?? undefined, [ @@ -108,7 +109,6 @@ export function useDerivedMintInfo( }, [noLiquidity, token0Price, parsedAmounts]) // liquidity minted - const totalSupply = useTotalSupply(pair?.liquidityToken) const liquidityMinted = useMemo(() => { const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts const [tokenAmountA, tokenAmountB] = [ From 120c766a93d3ed8127837a992f5d20da6f6c7e59 Mon Sep 17 00:00:00 2001 From: Moody Salem Date: Thu, 6 Aug 2020 17:22:43 -0500 Subject: [PATCH 29/29] bump the walletconnect version to the fixed alpha --- package.json | 3 ++ yarn.lock | 134 +++++++++++++++++++++++++-------------------------- 2 files changed, 70 insertions(+), 67 deletions(-) diff --git a/package.json b/package.json index 55eeba1b182..c09a9a86063 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,9 @@ "styled-components": "^4.2.0", "typescript": "^3.8.3" }, + "resolutions": { + "@walletconnect/web3-provider": "1.1.1-alpha.0" + }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", diff --git a/yarn.lock b/yarn.lock index 605ea0b30c6..67b7f1bb4b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2591,92 +2591,92 @@ "@uniswap/lib" "1.1.1" "@uniswap/v2-core" "1.0.0" -"@walletconnect/client@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@walletconnect/client/-/client-1.1.0.tgz#f2454cba82da3d8c7375b2a5d9d47f34ed7348ec" - integrity sha512-pHxvUDCkD4oP3AFxYLU7yeE+qDZtcHF20b2K8/HNvyuyu3eWFX4jpHgx6FdvcIcFcAXGs5nk24zBUEO8p+axWg== - dependencies: - "@walletconnect/core" "^1.1.0" - "@walletconnect/iso-crypto" "^1.1.0" - "@walletconnect/types" "^1.1.0" - "@walletconnect/utils" "^1.1.0" - -"@walletconnect/core@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-1.1.0.tgz#053f08b0ccfdfb14ccd27b7fd425d9849cedba14" - integrity sha512-Bhe4gnR6Az11u7OAOw0UDZKM6emUjIQtQ2PVdPDWke6ryC0DWMg9vTYbVPf3lDHBv5hy5eAyDst30N5E91SuYw== - dependencies: - "@walletconnect/socket-transport" "^1.1.0" - "@walletconnect/types" "^1.1.0" - "@walletconnect/utils" "^1.1.0" - -"@walletconnect/http-connection@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@walletconnect/http-connection/-/http-connection-1.1.0.tgz#c6650c12a07244d30f20647420cdcd8c69c6daca" - integrity sha512-ugxDW/NaSgn7rmdPZhrpJIS79gASLvzBnGHScMs8zpYDHwcFxh2DP3HTspC8o5FyMqjRlEGtNi4zSGKY6EOrkw== - dependencies: - "@walletconnect/types" "^1.1.0" - "@walletconnect/utils" "^1.1.0" +"@walletconnect/client@^1.1.1-alpha.0": + version "1.1.1-alpha.0" + resolved "https://registry.yarnpkg.com/@walletconnect/client/-/client-1.1.1-alpha.0.tgz#18362a6b05150f02adfd281ca2251539cd727606" + integrity sha512-/aOvwouwXgSMnAMypVlZB6MhIbLwEZOHF2Waa6CvcRRFYe9dA/LqI+vF/dABevg7B4R2q012ZF22NQmhZOVZsw== + dependencies: + "@walletconnect/core" "^1.1.1-alpha.0" + "@walletconnect/iso-crypto" "^1.1.1-alpha.0" + "@walletconnect/types" "^1.1.1-alpha.0" + "@walletconnect/utils" "^1.1.1-alpha.0" + +"@walletconnect/core@^1.1.1-alpha.0": + version "1.1.1-alpha.0" + resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-1.1.1-alpha.0.tgz#ffc80babfe271ff7de07a1159f2e52e5a6487e34" + integrity sha512-EZf2aqB/nAouHX9T/niCcBxRTPat4B92hcHKKOhBZgKwXF4ajB0LfC1tXwhTDeQGt6PpJ1HLjtnCCJ7+/TLhJg== + dependencies: + "@walletconnect/socket-transport" "^1.1.1-alpha.0" + "@walletconnect/types" "^1.1.1-alpha.0" + "@walletconnect/utils" "^1.1.1-alpha.0" + +"@walletconnect/http-connection@^1.1.1-alpha.0": + version "1.1.1-alpha.0" + resolved "https://registry.yarnpkg.com/@walletconnect/http-connection/-/http-connection-1.1.1-alpha.0.tgz#5d02efa2a4dd70d502bbf917adc6798ff068c2b0" + integrity sha512-IBAwBu9xCmnDMRNiHqaRHgbNibGf4tqtY5BfzU2I49Awmbk//H8TmZ4pDRlXe6/ADWkB2CFcsDZpdjRAkfAWvg== + dependencies: + "@walletconnect/types" "^1.1.1-alpha.0" + "@walletconnect/utils" "^1.1.1-alpha.0" xhr2-cookies "1.1.0" -"@walletconnect/iso-crypto@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@walletconnect/iso-crypto/-/iso-crypto-1.1.0.tgz#a8235049c1b239adcf9fc6a6c38b7e9ad13004a6" - integrity sha512-ttWLj4rTy2NGQnSAKnAar1LSrsJuCQ2JnQUl8hsgc9oTwXKgnRvtxGy2Kajoih/tNKnK959Ilj4WI2HaSJ9G1g== +"@walletconnect/iso-crypto@^1.1.1-alpha.0": + version "1.1.1-alpha.0" + resolved "https://registry.yarnpkg.com/@walletconnect/iso-crypto/-/iso-crypto-1.1.1-alpha.0.tgz#f18a336e310341427603b201f973bb8ee13800cd" + integrity sha512-A8U57SgskexAF9TBisfYvtXc+rhjSEakF/hOJxY/vyGlITN4fS9fp/qmz+8dqSOTO4vmTj69dXHowaAhKj+PpQ== dependencies: - "@walletconnect/types" "^1.1.0" - "@walletconnect/utils" "^1.1.0" + "@walletconnect/types" "^1.1.1-alpha.0" + "@walletconnect/utils" "^1.1.1-alpha.0" eccrypto-js "5.2.0" -"@walletconnect/mobile-registry@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@walletconnect/mobile-registry/-/mobile-registry-1.1.0.tgz#72173a4fcee61f4f8819f6d9fc7cfbf824ed3548" - integrity sha512-OOHQa4NeK2lbfI9WD2d+hTHGwSDzBLoTCeofdLNO2ibaTltQ6S+WNDAVuho6U8CkUTzs5cHPFgLJ6nxYZ8sr/g== +"@walletconnect/mobile-registry@^1.1.1-alpha.0": + version "1.1.1-alpha.0" + resolved "https://registry.yarnpkg.com/@walletconnect/mobile-registry/-/mobile-registry-1.1.1-alpha.0.tgz#65e6708df784e838d05fbe03459eb30a7fedc0ba" + integrity sha512-ncX2+XOEYu6OoIXrcLJiy2mrrMTJDuXgcJUpv5Ghwl9CbLczaiq7AlVDNPjMxZJFQj4aalR/idz6RKJbPBA6SQ== -"@walletconnect/qrcode-modal@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@walletconnect/qrcode-modal/-/qrcode-modal-1.1.0.tgz#4cd0c2c2c713be3f49ef00293a1b23a079d4c7b7" - integrity sha512-vYsu1MBE0D+kx1+xdXmaCs7JqhhWPw8orKk9Br64YIPF5pv/48i+Yi/m28/0myJm54YPlVcgzTnuf8PzAH7jgA== +"@walletconnect/qrcode-modal@^1.1.1-alpha.0": + version "1.1.1-alpha.0" + resolved "https://registry.yarnpkg.com/@walletconnect/qrcode-modal/-/qrcode-modal-1.1.1-alpha.0.tgz#42eea4e8b091ded92bb47ba497cd3772196b7a3a" + integrity sha512-bD7LFVdTzlAb2GFaOR2ymbPVkta8Ezct39VPkteip41KKyta468sZN2AzFTY/wPbTBn+YYwhOliOZhj+Gm9U3A== dependencies: - "@walletconnect/mobile-registry" "^1.1.0" - "@walletconnect/types" "^1.1.0" - "@walletconnect/utils" "^1.1.0" + "@walletconnect/mobile-registry" "^1.1.1-alpha.0" + "@walletconnect/types" "^1.1.1-alpha.0" + "@walletconnect/utils" "^1.1.1-alpha.0" preact "10.4.1" qrcode "1.4.4" -"@walletconnect/socket-transport@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@walletconnect/socket-transport/-/socket-transport-1.1.0.tgz#d80b5e6b3b904f131961259ca16de816ae2b003b" - integrity sha512-plo5WHjL3RTDENH7MTgs7D/ePGHfSuc/HLzkVGvgZSOtoPlRR916nSZNeL4bStYF1ZRJCrds10x36C0DlZjpQg== +"@walletconnect/socket-transport@^1.1.1-alpha.0": + version "1.1.1-alpha.0" + resolved "https://registry.yarnpkg.com/@walletconnect/socket-transport/-/socket-transport-1.1.1-alpha.0.tgz#6a1f4abb537f891a1e6c282e0a2a8d1410270554" + integrity sha512-epS/zNL4GQclYZ3dDiumR0krwYEpHHGC+LsaNxkrSHTh/URuqfmf6QqCOdcjTU6qW5G1cliHC9Kk0UKcC+VeDA== dependencies: - "@walletconnect/types" "^1.1.0" + "@walletconnect/types" "^1.1.1-alpha.0" ws "7.3.0" -"@walletconnect/types@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-1.1.0.tgz#1e4efbf033ad89910cbb86f1f381cd5fe7e764fd" - integrity sha512-cgDEuYHZZTiaXFRwQs3Zhhar+l2T58/YjhWrfZTMKWuc77geIbF7682i9lE9bNEQqQvQ76jjKxJfSLGjCu++sA== +"@walletconnect/types@^1.1.1-alpha.0": + version "1.1.1-alpha.0" + resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-1.1.1-alpha.0.tgz#9fbb8356aa347240f4356abea4aaa8b0137396d0" + integrity sha512-ro6yJ53kTG8aibKyoUv79CFMujkZk6W5FNHCk6SJdIkPec03XICrt9cqLUaPDt0wx+FM5z94ZHAg46Wzqkh5NA== -"@walletconnect/utils@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-1.1.0.tgz#7b0bcf5c77e8079ac055013537a9620244db2da9" - integrity sha512-y5v8PCmd/2kASOncYaz5QJiAzwBRT5MK398PmIkImX9tNEeBh00ifeQGZKkCGi6JYXbde0UC5jsGTGkH8hdxeg== +"@walletconnect/utils@^1.1.1-alpha.0": + version "1.1.1-alpha.0" + resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-1.1.1-alpha.0.tgz#62a274290263dcca45102d1cb0cea617827c1849" + integrity sha512-35NbpD7JeyzKzh/UPM+TYuxJgudeIr1LuCWdbhGX9KyIoeYxXPVJiCu7RkX5cXI0Fh1dGghQx7++4O3xkqLVcg== dependencies: - "@walletconnect/types" "^1.1.0" + "@walletconnect/types" "^1.1.1-alpha.0" detect-browser "5.1.0" enc-utils "2.1.0" js-sha3 "0.8.0" -"@walletconnect/web3-provider@^1.0.11": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@walletconnect/web3-provider/-/web3-provider-1.1.0.tgz#c8a30c4121d3ade159022b10d3a18ecd804c8993" - integrity sha512-1DaYG+aK2pjCBKXrB0c2JKeFk27ObUsu09LlZN1VvIi1+zvHftaubNsSGViLmrq25w72yPle/SDjhgmxvKVMQQ== - dependencies: - "@walletconnect/client" "^1.1.0" - "@walletconnect/http-connection" "^1.1.0" - "@walletconnect/qrcode-modal" "^1.1.0" - "@walletconnect/types" "^1.1.0" - "@walletconnect/utils" "^1.1.0" +"@walletconnect/web3-provider@1.1.1-alpha.0", "@walletconnect/web3-provider@^1.0.11": + version "1.1.1-alpha.0" + resolved "https://registry.yarnpkg.com/@walletconnect/web3-provider/-/web3-provider-1.1.1-alpha.0.tgz#b8ca2158da4974b692f57e4f939b5128d8e4696f" + integrity sha512-1AoTeCOtK8u2jIH+0NsvisPv2TySZLWHwWu0BIb72wzvzJeG3uD383/stHX8mBOI6a0aPoyDEYzA2R4c/O0vWQ== + dependencies: + "@walletconnect/client" "^1.1.1-alpha.0" + "@walletconnect/http-connection" "^1.1.1-alpha.0" + "@walletconnect/qrcode-modal" "^1.1.1-alpha.0" + "@walletconnect/types" "^1.1.1-alpha.0" + "@walletconnect/utils" "^1.1.1-alpha.0" web3-provider-engine "15.0.12" "@web3-react/abstract-connector@^6.0.7":