diff --git a/src/hooks/useSwapCallback.ts b/src/hooks/useSwapCallback.ts index d7421cb7ed..6485356014 100644 --- a/src/hooks/useSwapCallback.ts +++ b/src/hooks/useSwapCallback.ts @@ -62,12 +62,13 @@ 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 // the ENS name or address of the recipient of the trade + 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) { const { account, chainId, library } = useActiveWeb3React() const addTransaction = useTransactionAdder() - const { address: recipient } = useENS(recipientAddressOrName) + const { address: recipientAddress } = useENS(recipientAddressOrName) + const recipient = recipientAddressOrName === null ? account : recipientAddress const tradeVersion = getTradeVersion(trade) const v1Exchange = useV1ExchangeContract(useV1TradeExchangeAddress(trade), true) @@ -284,7 +285,9 @@ export function useSwapCallback( recipient === account ? base : `${base} to ${ - isAddress(recipientAddressOrName) ? shortenAddress(recipientAddressOrName) : recipientAddressOrName + recipientAddressOrName && isAddress(recipientAddressOrName) + ? shortenAddress(recipientAddressOrName) + : recipientAddressOrName }` const withVersion = diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx index 6ade0fac73..fc6bdea8eb 100644 --- a/src/pages/Swap/index.tsx +++ b/src/pages/Swap/index.tsx @@ -1,5 +1,5 @@ import { JSBI, TokenAmount, WETH } from '@uniswap/sdk' -import React, { useContext, useState, useEffect } from 'react' +import React, { useContext, useState, useEffect, useCallback } from 'react' import { ArrowDown } from 'react-feather' import ReactGA from 'react-ga' import { Text } from 'rebass' @@ -61,18 +61,19 @@ export default function Swap() { // swap state const { independentField, typedValue, recipient } = useSwapState() - const { bestTrade: bestTradeV2, tokenBalances, parsedAmount, tokens, error, v1Trade } = useDerivedSwapInfo() + const { v1Trade, v2Trade, tokenBalances, parsedAmount, tokens, error } = useDerivedSwapInfo() const { address: recipientAddress } = useENSAddress(recipient) const toggledVersion = useToggledVersion() - const trade = { - [Version.v1]: v1Trade, - [Version.v2]: bestTradeV2 - }[toggledVersion] + const trade = + { + [Version.v1]: v1Trade, + [Version.v2]: v2Trade + }[toggledVersion] ?? undefined const betterTradeLinkVersion: Version | undefined = - toggledVersion === Version.v2 && isTradeBetter(bestTradeV2, v1Trade, BETTER_TRADE_LINK_THRESHOLD) + toggledVersion === Version.v2 && isTradeBetter(v2Trade, v1Trade, BETTER_TRADE_LINK_THRESHOLD) ? Version.v1 - : toggledVersion === Version.v1 && isTradeBetter(v1Trade, bestTradeV2) + : toggledVersion === Version.v1 && isTradeBetter(v1Trade, v2Trade) ? Version.v2 : undefined @@ -85,6 +86,19 @@ export default function Swap() { const isValid = !error const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT + const handleTypeInput = useCallback( + (field, value) => { + onUserInput(Field.INPUT, value) + }, + [onUserInput] + ) + const handleTypeOutput = useCallback( + (field, value) => { + onUserInput(Field.OUTPUT, value) + }, + [onUserInput] + ) + // modal and loading const [showConfirm, setShowConfirm] = useState(false) // show confirmation modal const [attemptingTxn, setAttemptingTxn] = useState(false) // waiting for user confirmaion/rejection @@ -92,15 +106,13 @@ export default function Swap() { const formattedAmounts = { [independentField]: typedValue, - [dependentField]: parsedAmounts[dependentField] ? parsedAmounts[dependentField].toSignificant(6) : '' + [dependentField]: parsedAmounts[dependentField]?.toSignificant(6) ?? '' } const route = trade?.route - const userHasSpecifiedInputOutput = - !!tokens[Field.INPUT] && - !!tokens[Field.OUTPUT] && - !!parsedAmounts[independentField] && - parsedAmounts[independentField].greaterThan(JSBI.BigInt(0)) + const userHasSpecifiedInputOutput = Boolean( + tokens[Field.INPUT] && tokens[Field.OUTPUT] && parsedAmounts[independentField]?.greaterThan(JSBI.BigInt(0)) + ) const noRoute = !route // check whether the user has approved the router on the input token @@ -116,19 +128,22 @@ export default function Swap() { } }, [approval, approvalSubmitted]) - const maxAmountInput: TokenAmount = - !!tokenBalances[Field.INPUT] && - !!tokens[Field.INPUT] && - !!WETH[chainId] && - tokenBalances[Field.INPUT].greaterThan( - new TokenAmount(tokens[Field.INPUT], tokens[Field.INPUT].equals(WETH[chainId]) ? MIN_ETH : '0') - ) - ? tokens[Field.INPUT].equals(WETH[chainId]) - ? tokenBalances[Field.INPUT].subtract(new TokenAmount(WETH[chainId], MIN_ETH)) - : tokenBalances[Field.INPUT] - : undefined - const atMaxAmountInput: boolean = - maxAmountInput && parsedAmounts[Field.INPUT] ? maxAmountInput.equalTo(parsedAmounts[Field.INPUT]) : undefined + let maxAmountInput: TokenAmount | undefined + { + const inputToken = tokens[Field.INPUT] + maxAmountInput = + inputToken && + chainId && + WETH[chainId] && + tokenBalances[Field.INPUT]?.greaterThan( + new TokenAmount(inputToken, inputToken.equals(WETH[chainId]) ? MIN_ETH : '0') + ) + ? inputToken.equals(WETH[chainId]) + ? tokenBalances[Field.INPUT]?.subtract(new TokenAmount(WETH[chainId], MIN_ETH)) + : tokenBalances[Field.INPUT] + : undefined + } + const atMaxAmountInput = Boolean(maxAmountInput && parsedAmounts[Field.INPUT]?.equalTo(maxAmountInput)) const slippageAdjustedAmounts = computeSlippageAdjustedAmounts(trade, allowedSlippage) @@ -141,6 +156,9 @@ export default function Swap() { if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) { return } + if (!swapCallback) { + return + } setAttemptingTxn(true) swapCallback() .then(hash => { @@ -155,7 +173,9 @@ export default function Swap() { : (recipientAddress ?? recipient) === account ? 'Swap w/o Send + recipient' : 'Swap w/ Send', - label: [trade.inputAmount.token.symbol, trade.outputAmount.token.symbol, getTradeVersion(trade)].join('/') + label: [trade?.inputAmount?.token?.symbol, trade?.outputAmount?.token?.symbol, getTradeVersion(trade)].join( + '/' + ) }) }) .catch(error => { @@ -248,7 +268,7 @@ export default function Swap() { value={formattedAmounts[Field.INPUT]} showMaxButton={!atMaxAmountInput} token={tokens[Field.INPUT]} - onUserInput={onUserInput} + onUserInput={handleTypeInput} onMax={() => { maxAmountInput && onUserInput(Field.INPUT, maxAmountInput.toExact()) }} @@ -284,7 +304,7 @@ export default function Swap() { } diff --git a/src/pages/Swap/tsconfig.json b/src/pages/Swap/tsconfig.json new file mode 100644 index 0000000000..638227fff6 --- /dev/null +++ b/src/pages/Swap/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../../tsconfig.strict.json", + "include": ["**/*"] +} \ No newline at end of file diff --git a/src/state/swap/hooks.ts b/src/state/swap/hooks.ts index 9d43ce76be..5c34eccd17 100644 --- a/src/state/swap/hooks.ts +++ b/src/state/swap/hooks.ts @@ -91,7 +91,7 @@ export function useDerivedSwapInfo(): { tokens: { [field in Field]?: Token } tokenBalances: { [field in Field]?: TokenAmount } parsedAmount: TokenAmount | undefined - bestTrade: Trade | null + v2Trade: Trade | undefined error?: string v1Trade: Trade | undefined } { @@ -123,7 +123,7 @@ export function useDerivedSwapInfo(): { const bestTradeExactIn = useTradeExactIn(isExactIn ? parsedAmount : undefined, tokenOut ?? undefined) const bestTradeExactOut = useTradeExactOut(tokenIn ?? undefined, !isExactIn ? parsedAmount : undefined) - const bestTrade = isExactIn ? bestTradeExactIn : bestTradeExactOut + const v2Trade = isExactIn ? bestTradeExactIn : bestTradeExactOut const tokenBalances = { [Field.INPUT]: relevantTokenBalances?.[tokenIn?.address ?? ''], @@ -157,8 +157,7 @@ export function useDerivedSwapInfo(): { const [allowedSlippage] = useUserSlippageTolerance() - const slippageAdjustedAmounts = - bestTrade && allowedSlippage && computeSlippageAdjustedAmounts(bestTrade, allowedSlippage) + const slippageAdjustedAmounts = v2Trade && allowedSlippage && computeSlippageAdjustedAmounts(v2Trade, allowedSlippage) const slippageAdjustedAmountsV1 = v1Trade && allowedSlippage && computeSlippageAdjustedAmounts(v1Trade, allowedSlippage) @@ -183,7 +182,7 @@ export function useDerivedSwapInfo(): { tokens, tokenBalances, parsedAmount, - bestTrade, + v2Trade: v2Trade ?? undefined, error, v1Trade } diff --git a/src/utils/prices.ts b/src/utils/prices.ts index 0431d6dfd1..e4e14e13d1 100644 --- a/src/utils/prices.ts +++ b/src/utils/prices.ts @@ -1,4 +1,4 @@ -import { BLOCKED_PRICE_IMPACT_NON_EXPERT } from './../constants/index' +import { BLOCKED_PRICE_IMPACT_NON_EXPERT } from '../constants' import { Fraction, JSBI, Percent, TokenAmount, Trade } from '@uniswap/sdk' import { ALLOWED_PRICE_IMPACT_HIGH, ALLOWED_PRICE_IMPACT_LOW, ALLOWED_PRICE_IMPACT_MEDIUM } from '../constants' import { Field } from '../state/swap/actions' @@ -42,7 +42,7 @@ export function computeTradePriceBreakdown( // computes the minimum amount out and maximum amount in for a trade given a user specified allowed slippage in bips export function computeSlippageAdjustedAmounts( - trade: Trade, + trade: Trade | undefined, allowedSlippage: number ): { [field in Field]?: TokenAmount } { const pct = basisPointsToPercent(allowedSlippage) @@ -52,7 +52,7 @@ export function computeSlippageAdjustedAmounts( } } -export function warningSeverity(priceImpact: Percent): 0 | 1 | 2 | 3 | 4 { +export function warningSeverity(priceImpact: Percent | undefined): 0 | 1 | 2 | 3 | 4 { if (!priceImpact?.lessThan(BLOCKED_PRICE_IMPACT_NON_EXPERT)) return 4 if (!priceImpact?.lessThan(ALLOWED_PRICE_IMPACT_HIGH)) return 3 if (!priceImpact?.lessThan(ALLOWED_PRICE_IMPACT_MEDIUM)) return 2