import { parseUnits } from '@ethersproject/units' ; import { Currency , CurrencyAmount , ETHER , JSBI , Token , TokenAmount , Trade } from '@pancakeswap-libs/sdk' ; import { ParsedQs } from 'qs' ; import { useCallback , useEffect , useState } from 'react' ; import { useDispatch , useSelector } from 'react-redux' ; import useENS from '../../hooks/useENS' ; import { useActiveWeb3React } from '../../hooks' ; import { useCurrency } from '../../hooks/Tokens' ; import { useTradeExactIn , useTradeExactOut } from '../../hooks/Trades' ; import useParsedQueryString from '../../hooks/useParsedQueryString' ; import { isAddress } from '../../utils' ; import { AppDispatch , AppState } from '../index' ; import { useCurrencyBalances } from '../wallet/hooks' ; import { Field , replaceSwapState , selectCurrency , setRecipient , switchCurrencies , typeInput } from './actions' ; import { SwapState } from './reducer' ; import { useUserSlippageTolerance } from '../user/hooks' ; import { computeSlippageAdjustedAmounts } from '../../utils/prices' ; export function useSwapState ( ) { return useSelector < AppState , AppState [ 'swap' ] > ( ( state ) => state . swap ) ; } export function useSwapActionHandlers ( ) { const dispatch = useDispatch < AppDispatch > ( ) ; const onCurrencySelection = useCallback ( ( field , currency ) => { dispatch ( selectCurrency ( { field , currencyId : currency instanceof Token ? currency . address : currency === ETHER ? 'BNB' : '' , } ) ) ; } , [ dispatch ] ) ; const onSwitchTokens = useCallback ( ( ) => { dispatch ( switchCurrencies ( ) ) ; } , [ dispatch ] ) ; const onUserInput = useCallback ( ( field , typedValue ) => { dispatch ( typeInput ( { field , typedValue } ) ) ; } , [ dispatch ] ) ; const onChangeRecipient = useCallback ( ( recipient ) => { dispatch ( setRecipient ( { recipient } ) ) ; } , [ dispatch ] ) ; return { onSwitchTokens , onCurrencySelection , onUserInput , onChangeRecipient , } ; } export function tryParseAmount ( value ? , currency ? ) { if ( ! value || ! currency ) { return undefined ; } try { const typedValueParsed = parseUnits ( value , currency . decimals ) . toString ( ) ; if ( typedValueParsed !== '0' ) { return currency instanceof Token ? new TokenAmount ( currency , JSBI . BigInt ( typedValueParsed ) ) : CurrencyAmount . ether ( JSBI . BigInt ( typedValueParsed ) ) ; } } catch ( error ) { console . info ( `Failed to parse input amount: "${ value }"` , error ) ; } return undefined ; } const BAD_RECIPIENT_ADDRESSES = [ '0xBCfCcbde45cE874adCB698cC183deBcF17952812' , '0xf164fC0Ec4E93095b804a4795bBe1e041497b92a' , '0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F' , ] ; param trade param checksummedAddress function involvesAddress ( trade , checksummedAddress ) { return ( trade . route . path . some ( ( token ) => token . address === checksummedAddress ) || trade . route . pairs . some ( ( pair ) => pair . liquidityToken . address === checksummedAddress ) ) ; } export function useDerivedSwapInfo ( ) { const { account } = useActiveWeb3React ( ) ; const { independentField , typedValue , [ Field . INPUT ] : { currencyId : inputCurrencyId } , [ Field . OUTPUT ] : { currencyId : outputCurrencyId } , recipient , } = useSwapState ( ) ; const inputCurrency = useCurrency ( inputCurrencyId ) ; const outputCurrency = useCurrency ( outputCurrencyId ) ; const recipientLookup = useENS ( recipient ?? undefined ) ; const to = ( recipient === null ? account : recipientLookup . address ) ?? null ; const relevantTokenBalances = useCurrencyBalances ( account ?? undefined , [ inputCurrency ?? undefined , outputCurrency ?? undefined , ] ) ; const isExactIn = independentField === Field . INPUT ; const parsedAmount = tryParseAmount ( typedValue , ( isExactIn ? inputCurrency : outputCurrency ) ?? undefined ) ; const bestTradeExactIn = useTradeExactIn ( isExactIn ? parsedAmount : undefined , outputCurrency ?? undefined ) ; const bestTradeExactOut = useTradeExactOut ( inputCurrency ?? undefined , ! isExactIn ? parsedAmount : undefined ) ; const v2Trade = isExactIn ? bestTradeExactIn : bestTradeExactOut ; const currencyBalances = { [ Field . INPUT ] : relevantTokenBalances [ 0 ] , [ Field . OUTPUT ] : relevantTokenBalances [ 1 ] , } ; const currencies = { [ Field . INPUT ] : inputCurrency ?? undefined , [ Field . OUTPUT ] : outputCurrency ?? undefined , } ; let inputError ; if ( ! account ) { inputError = 'Connect Wallet' ; } if ( ! parsedAmount ) { inputError = inputError ?? 'Enter an amount' ; } if ( ! currencies [ Field . INPUT ] || ! currencies [ Field . OUTPUT ] ) { inputError = inputError ?? 'Select a token' ; } const formattedTo = isAddress ( to ) ; if ( ! to || ! formattedTo ) { inputError = inputError ?? 'Enter a recipient' ; } else if ( BAD_RECIPIENT_ADDRESSES . indexOf ( formattedTo ) !== - 1 || ( bestTradeExactIn && involvesAddress ( bestTradeExactIn , formattedTo ) ) || ( bestTradeExactOut && involvesAddress ( bestTradeExactOut , formattedTo ) ) ) { inputError = inputError ?? 'Invalid recipient' ; } const [ allowedSlippage ] = useUserSlippageTolerance ( ) ; const slippageAdjustedAmounts = v2Trade && allowedSlippage && computeSlippageAdjustedAmounts ( v2Trade , allowedSlippage ) ; const [ balanceIn , amountIn ] = [ currencyBalances [ Field . INPUT ] , slippageAdjustedAmounts ? slippageAdjustedAmounts [ Field . INPUT ] : null , ] ; if ( balanceIn && amountIn && balanceIn . lessThan ( amountIn ) ) { inputError = `Insufficient ${ amountIn . currency . symbol } balance` ; } return { currencies , currencyBalances , parsedAmount , v2Trade : v2Trade ?? undefined , inputError , } ; } function parseCurrencyFromURLParameter ( urlParam ) { if ( typeof urlParam === 'string' ) { const valid = isAddress ( urlParam ) ; if ( valid ) return valid ; if ( urlParam . toUpperCase ( ) === 'BNB' ) return 'BNB' ; if ( valid === false ) return 'BNB' ; } return 'BNB' ?? '' ; } function parseTokenAmountURLParameter ( urlParam ) { return typeof urlParam === 'string' && ! isNaN ( parseFloat ( urlParam ) ) ? urlParam : '' ; } function parseIndependentFieldURLParameter ( urlParam ) { return typeof urlParam === 'string' && urlParam . toLowerCase ( ) === 'output' ? Field . OUTPUT : Field . INPUT ; } const ENS_NAME_REGEX = /^[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)?$/ ; const ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/ ; function validatedRecipient ( recipient ) { if ( typeof recipient !== 'string' ) return null ; const address = isAddress ( recipient ) ; if ( address ) return address ; if ( ENS_NAME_REGEX . test ( recipient ) ) return recipient ; if ( ADDRESS_REGEX . test ( recipient ) ) return recipient ; return null ; } export function queryParametersToSwapState ( parsedQs ) { let inputCurrency = parseCurrencyFromURLParameter ( parsedQs . inputCurrency ) ; let outputCurrency = parseCurrencyFromURLParameter ( parsedQs . outputCurrency ) ; if ( inputCurrency === outputCurrency ) { if ( typeof parsedQs . outputCurrency === 'string' ) { inputCurrency = '' ; } else { outputCurrency = '' ; } } const recipient = validatedRecipient ( parsedQs . recipient ) ; return { [ Field . INPUT ] : { currencyId : inputCurrency , } , [ Field . OUTPUT ] : { currencyId : outputCurrency , } , typedValue : parseTokenAmountURLParameter ( parsedQs . exactAmount ) , independentField : parseIndependentFieldURLParameter ( parsedQs . exactField ) , recipient , } ; } export function useDefaultsFromURLSearch ( ) { const { chainId } = useActiveWeb3React ( ) ; const dispatch = useDispatch < AppDispatch > ( ) ; const parsedQs = useParsedQueryString ( ) ; const [ result , setResult ] = useState < { inputCurrencyId : string | undefined ; outputCurrencyId : string | undefined ; } | undefined > ( ) ; useEffect ( ( ) => { if ( ! chainId ) return ; const parsed = queryParametersToSwapState ( parsedQs ) ; dispatch ( replaceSwapState ( { typedValue : parsed . typedValue , field : parsed . independentField , inputCurrencyId : parsed [ Field . INPUT ] . currencyId , outputCurrencyId : parsed [ Field . OUTPUT ] . currencyId , recipient : parsed . recipient , } ) ) ; setResult ( { inputCurrencyId : parsed [ Field . INPUT ] . currencyId , outputCurrencyId : parsed [ Field . OUTPUT ] . currencyId } ) ; } , [ dispatch , chainId ] ) ; return result ; }