state/user/hooks#useUserSlippageTolerance TypeScript Examples
The following examples show how to use
state/user/hooks#useUserSlippageTolerance.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: AdvancedSwapDetails.tsx From interface-v2 with GNU General Public License v3.0 | 6 votes |
AdvancedSwapDetails: React.FC<AdvancedSwapDetailsProps> = ({
trade,
}) => {
const [allowedSlippage] = useUserSlippageTolerance();
return (
<>
{trade && (
<TradeSummary trade={trade} allowedSlippage={allowedSlippage} />
)}
</>
);
}
Example #2
Source File: AdvancedSwapDetails.tsx From vvs-ui with GNU General Public License v3.0 | 6 votes |
export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
const { t } = useTranslation()
const [allowedSlippage] = useUserSlippageTolerance()
const showRoute = Boolean(trade && trade.route.path.length > 2)
return (
<AutoColumn gap="0px">
{trade && (
<>
<TradeSummary trade={trade} allowedSlippage={allowedSlippage} />
{showRoute && (
<>
<RowBetween style={{ padding: '0 16px' }}>
<span style={{ display: 'flex', alignItems: 'center' }}>
<Text fontSize="14px" color="textSubtle">
{t('Route')}
</Text>
<QuestionHelper
text={t('Routing through these tokens resulted in the best price for your trade.')}
ml="4px"
/>
</span>
<SwapRoute trade={trade} />
</RowBetween>
</>
)}
</>
)}
</AutoColumn>
)
}
Example #3
Source File: AdvancedSwapDetails.tsx From glide-frontend with GNU General Public License v3.0 | 6 votes |
export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
const [allowedSlippage] = useUserSlippageTolerance()
const showRoute = Boolean(trade && trade.route.path.length > 2)
return (
<AutoColumn gap="0px">
{trade && (
<>
<TradeSummary trade={trade} allowedSlippage={allowedSlippage} />
{showRoute && (
<>
<RowBetween style={{ padding: '0 16px' }}>
<span style={{ display: 'flex', alignItems: 'center' }}>
<Text fontSize="14px" color="textSubtle">
Route
</Text>
<QuestionHelper
text="Routing through these tokens resulted in the best price for your trade."
ml="4px"
/>
</span>
<SwapRoute trade={trade} />
</RowBetween>
</>
)}
</>
)}
</AutoColumn>
)
}
Example #4
Source File: AdvancedSwapDetails.tsx From glide-frontend with GNU General Public License v3.0 | 6 votes |
export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
const [allowedSlippage] = useUserSlippageTolerance()
const showRoute = Boolean(trade && trade.route.path.length > 2)
return (
<AutoColumn gap="0px">
{trade && (
<>
<TradeSummary trade={trade} allowedSlippage={allowedSlippage} />
{showRoute && (
<>
<RowBetween style={{ padding: '0 16px' }}>
<span style={{ display: 'flex', alignItems: 'center' }}>
<Text fontSize="14px" color="textSubtle">
Route
</Text>
<QuestionHelper
text="Routing through these tokens resulted in the best price for your trade."
ml="4px"
/>
</span>
<SwapRoute trade={trade} />
</RowBetween>
</>
)}
</>
)}
</AutoColumn>
)
}
Example #5
Source File: SlippageToleranceSetting.tsx From pancake-swap-testnet with MIT License | 4 votes |
SlippageToleranceSettings = ({ translateString }: SlippageToleranceSettingsModalProps) => {
const [userSlippageTolerance, setUserslippageTolerance] = useUserSlippageTolerance()
const [value, setValue] = useState(userSlippageTolerance / 100)
const [error, setError] = useState<string | null>(null)
const handleChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
const { value: inputValue } = evt.target
setValue(parseFloat(inputValue))
}
// Updates local storage if value is valid
useEffect(() => {
try {
const rawValue = value * 100
if (!Number.isNaN(rawValue) && rawValue > 0 && rawValue < MAX_SLIPPAGE) {
setUserslippageTolerance(rawValue)
setError(null)
} else {
setError(translateString(1144, 'Enter a valid slippage percentage'))
}
} catch {
setError(translateString(1144, 'Enter a valid slippage percentage'))
}
}, [value, setError, setUserslippageTolerance, translateString])
// Notify user if slippage is risky
useEffect(() => {
if (userSlippageTolerance < RISKY_SLIPPAGE_LOW) {
setError(translateString(1146, 'Your transaction may fail'))
} else if (userSlippageTolerance > RISKY_SLIPPAGE_HIGH) {
setError(translateString(1148, 'Your transaction may be frontrun'))
}
}, [userSlippageTolerance, setError, translateString])
return (
<Box mb="16px">
<Flex alignItems="center" mb="8px">
<Text bold>{translateString(88, 'Slippage tolerance')}</Text>
<QuestionHelper
text={translateString(
186,
'Your transaction will revert if the price changes unfavorably by more than this percentage.'
)}
/>
</Flex>
<Options>
<Flex mb={['8px', '8px', 0]} mr={[0, 0, '8px']}>
{predefinedValues.map(({ label, value: predefinedValue }) => {
const handleClick = () => setValue(predefinedValue)
return (
<Option key={predefinedValue}>
<Button variant={value === predefinedValue ? 'primary' : 'tertiary'} onClick={handleClick}>
{label}
</Button>
</Option>
)
})}
</Flex>
<Flex alignItems="center">
<Option>
<Input
type="number"
scale="lg"
step={0.1}
min={0.1}
placeholder="5%"
value={value}
onChange={handleChange}
isWarning={error !== null}
/>
</Option>
<Option>
<Text fontSize="18px">%</Text>
</Option>
</Flex>
</Options>
{error && (
<Text mt="8px" color="failure">
{error}
</Text>
)}
</Box>
)
}
Example #6
Source File: SlippageToleranceSetting.tsx From pancake-swap-exchange-testnet with GNU General Public License v3.0 | 4 votes |
SlippageToleranceSettings = ({ translateString }: SlippageToleranceSettingsModalProps) => {
const [userSlippageTolerance, setUserslippageTolerance] = useUserSlippageTolerance()
const [value, setValue] = useState(userSlippageTolerance / 100)
const [error, setError] = useState<string | null>(null)
const handleChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
const { value: inputValue } = evt.target
setValue(parseFloat(inputValue))
}
// Updates local storage if value is valid
useEffect(() => {
try {
const rawValue = value * 100
if (!Number.isNaN(rawValue) && rawValue > 0 && rawValue < MAX_SLIPPAGE) {
setUserslippageTolerance(rawValue)
setError(null)
} else {
setError(translateString(1144, 'Enter a valid slippage percentage'))
}
} catch {
setError(translateString(1144, 'Enter a valid slippage percentage'))
}
}, [value, setError, setUserslippageTolerance, translateString])
// Notify user if slippage is risky
useEffect(() => {
if (userSlippageTolerance < RISKY_SLIPPAGE_LOW) {
setError(translateString(1146, 'Your transaction may fail'))
} else if (userSlippageTolerance > RISKY_SLIPPAGE_HIGH) {
setError(translateString(1148, 'Your transaction may be frontrun'))
}
}, [userSlippageTolerance, setError, translateString])
return (
<Box mb="16px">
<Flex alignItems="center" mb="8px">
<Text bold>{translateString(88, 'Slippage tolerance')}</Text>
<QuestionHelper
text={translateString(
186,
'Your transaction will revert if the price changes unfavorably by more than this percentage.'
)}
/>
</Flex>
<Options>
<Flex mb={['8px', '8px', 0]} mr={[0, 0, '8px']}>
{predefinedValues.map(({ label, value: predefinedValue }) => {
const handleClick = () => setValue(predefinedValue)
return (
<Option key={predefinedValue}>
<Button variant={value === predefinedValue ? 'primary' : 'tertiary'} onClick={handleClick}>
{label}
</Button>
</Option>
)
})}
</Flex>
<Flex alignItems="center">
<Option>
<Input
type="number"
scale="lg"
step={0.1}
min={0.1}
placeholder="5%"
value={value}
onChange={handleChange}
isWarning={error !== null}
/>
</Option>
<Option>
<Text fontSize="18px">%</Text>
</Option>
</Flex>
</Options>
{error && (
<Text mt="8px" color="failure">
{error}
</Text>
)}
</Box>
)
}
Example #7
Source File: index.tsx From pancake-swap-testnet with MIT License | 4 votes |
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB },
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React()
const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB)
const TranslateString = useI18n()
const oneCurrencyIsWBNB = Boolean(
chainId &&
((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
(currencyB && currencyEquals(currencyB, WETH[chainId])))
)
const expertMode = useIsExpertMode()
// mint state
const { independentField, typedValue, otherTypedValue } = useMintState()
const {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
// txn values
const [deadline] = useUserDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: noLiquidity ? otherTypedValue : parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
// get the max amounts user can add
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmountSpend(currencyBalances[field]),
}
},
{}
)
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0'),
}
},
{}
)
// check whether the user has approved the router on the tokens
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS)
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS)
const addTransaction = useTransactionAdder()
async function onAdd() {
if (!chainId || !library || !account) return
const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) {
return
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0],
}
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate
let method: (...args: any) => Promise<TransactionResponse>
let args: Array<string | string[] | number>
let value: BigNumber | null
if (currencyA === ETHER || currencyB === ETHER) {
const tokenBIsBNB = currencyB === ETHER
estimate = router.estimateGas.addLiquidityETH
method = router.addLiquidityETH
args = [
wrappedCurrency(tokenBIsBNB ? currencyA : currencyB, chainId)?.address ?? '', // token
(tokenBIsBNB ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[tokenBIsBNB ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsBNB ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account,
deadlineFromNow,
]
value = BigNumber.from((tokenBIsBNB ? parsedAmountB : parsedAmountA).raw.toString())
} else {
estimate = router.estimateGas.addLiquidity
method = router.addLiquidity
args = [
wrappedCurrency(currencyA, chainId)?.address ?? '',
wrappedCurrency(currencyB, chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
value = null
}
setAttemptingTxn(true)
// const aa = await estimate(...args, value ? { value } : {})
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then((response) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Add ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencies[Field.CURRENCY_B]?.symbol}`,
})
setTxHash(response.hash)
})
)
.catch((e) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (e?.code !== 4001) {
console.error(e)
}
})
}
const modalHeader = () => {
return noLiquidity ? (
<AutoColumn gap="20px">
<LightCard mt="20px" borderRadius="20px">
<RowFlat>
<UIKitText fontSize="48px" mr="8px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol}`}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
</LightCard>
</AutoColumn>
) : (
<AutoColumn gap="20px">
<RowFlat style={{ marginTop: '20px' }}>
<UIKitText fontSize="48px" mr="8px">
{liquidityMinted?.toSignificant(6)}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
<Row>
<UIKitText fontSize="24px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol} Pool Tokens`}
</UIKitText>
</Row>
<UIKitText small textAlign="left" padding="8px 0 0 0 " style={{ fontStyle: 'italic' }}>
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</UIKitText>
</AutoColumn>
)
}
const modalBottom = () => {
return (
<ConfirmAddModalBottom
price={price}
currencies={currencies}
parsedAmounts={parsedAmounts}
noLiquidity={noLiquidity}
onAdd={onAdd}
poolTokenPercentage={poolTokenPercentage}
/>
)
}
const pendingText = `Supplying ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencies[Field.CURRENCY_B]?.symbol}`
const handleCurrencyASelect = useCallback(
(currA: Currency) => {
const newCurrencyIdA = currencyId(currA)
if (newCurrencyIdA === currencyIdB) {
history.push(`/add/${currencyIdB}/${currencyIdA}`)
} else {
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`)
}
},
[currencyIdB, history, currencyIdA]
)
const handleCurrencyBSelect = useCallback(
(currB: Currency) => {
const newCurrencyIdB = currencyId(currB)
if (currencyIdA === newCurrencyIdB) {
if (currencyIdB) {
history.push(`/add/${currencyIdB}/${newCurrencyIdB}`)
} else {
history.push(`/add/${newCurrencyIdB}`)
}
} else {
history.push(`/add/${currencyIdA || 'BNB'}/${newCurrencyIdB}`)
}
},
[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 (
<>
<CardNav activeIndex={1} />
<AppBody>
<AddRemoveTabs adding />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash}
content={() => (
<ConfirmationModalContent
title={
noLiquidity
? TranslateString(1154, 'You are creating a pool')
: TranslateString(1156, 'You will receive')
}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<CardBody>
<AutoColumn gap="20px">
{noLiquidity && (
<ColumnCenter>
<Pane>
<AutoColumn gap="12px">
<UIKitText>{TranslateString(1158, 'You are the first liquidity provider.')}</UIKitText>
<UIKitText>
{TranslateString(1160, 'The ratio of tokens you add will set the price of this pool.')}
</UIKitText>
<UIKitText>
{TranslateString(1162, 'Once you are happy with the rate click supply to review.')}
</UIKitText>
</AutoColumn>
</Pane>
</ColumnCenter>
)}
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
onMax={() => {
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}}
onCurrencySelect={handleCurrencyASelect}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A]}
id="add-liquidity-input-tokena"
showCommonBases={false}
/>
<ColumnCenter>
<AddIcon color="textSubtle" />
</ColumnCenter>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onFieldBInput}
onCurrencySelect={handleCurrencyBSelect}
onMax={() => {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
currency={currencies[Field.CURRENCY_B]}
id="add-liquidity-input-tokenb"
showCommonBases={false}
/>
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<div>
<UIKitText
style={{ textTransform: 'uppercase', fontWeight: 600 }}
color="textSubtle"
fontSize="12px"
mb="2px"
>
{noLiquidity
? TranslateString(1164, 'Initial prices and pool share')
: TranslateString(1166, 'Prices and pool share')}
</UIKitText>
<Pane>
<PoolPriceBar
currencies={currencies}
poolTokenPercentage={poolTokenPercentage}
noLiquidity={noLiquidity}
price={price}
/>
</Pane>
</div>
)}
{!account ? (
<ConnectWalletButton width="100%" />
) : (
<AutoColumn gap="md">
{(approvalA === ApprovalState.NOT_APPROVED ||
approvalA === ApprovalState.PENDING ||
approvalB === ApprovalState.NOT_APPROVED ||
approvalB === ApprovalState.PENDING) &&
isValid && (
<RowBetween>
{approvalA !== ApprovalState.APPROVED && (
<Button
onClick={approveACallback}
disabled={approvalA === ApprovalState.PENDING}
style={{ width: approvalB !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalA === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_A]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_A]?.symbol}`
)}
</Button>
)}
{approvalB !== ApprovalState.APPROVED && (
<Button
onClick={approveBCallback}
disabled={approvalB === ApprovalState.PENDING}
style={{ width: approvalA !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalB === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_B]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_B]?.symbol}`
)}
</Button>
)}
</RowBetween>
)}
<Button
onClick={() => {
if (expertMode) {
onAdd()
} else {
setShowConfirm(true)
}
}}
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
width="100%"
>
{error ?? 'Supply'}
</Button>
</AutoColumn>
)}
</AutoColumn>
</CardBody>
</Wrapper>
</AppBody>
{pair && !noLiquidity && pairState !== PairState.INVALID ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWBNB} pair={pair} />
</AutoColumn>
) : null}
</>
)
}
Example #8
Source File: index.tsx From pancake-swap-testnet with MIT License | 4 votes |
Swap = () => {
const loadedUrlParams = useDefaultsFromURLSearch()
const TranslateString = useI18n()
// token warning stuff
const [loadedInputCurrency, loadedOutputCurrency] = [
useCurrency(loadedUrlParams?.inputCurrencyId),
useCurrency(loadedUrlParams?.outputCurrencyId),
]
const [dismissTokenWarning, setDismissTokenWarning] = useState<boolean>(false)
const [transactionWarning, setTransactionWarning] = useState<{
selectedToken: string | null
purchaseType: string | null
}>({
selectedToken: null,
purchaseType: null,
})
const urlLoadedTokens: Token[] = useMemo(
() => [loadedInputCurrency, loadedOutputCurrency]?.filter((c): c is Token => c instanceof Token) ?? [],
[loadedInputCurrency, loadedOutputCurrency]
)
const handleConfirmTokenWarning = useCallback(() => {
setDismissTokenWarning(true)
}, [])
const handleConfirmWarning = () => {
setTransactionWarning({
selectedToken: null,
purchaseType: null,
})
}
const { account } = useActiveWeb3React()
const theme = useContext(ThemeContext)
const [isExpertMode] = useExpertModeManager()
// get custom setting values for user
const [deadline] = useUserDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
// swap state
const { independentField, typedValue, recipient } = useSwapState()
const { v2Trade, currencyBalances, parsedAmount, currencies, inputError: swapInputError } = useDerivedSwapInfo()
const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback(
currencies[Field.INPUT],
currencies[Field.OUTPUT],
typedValue
)
const showWrap: boolean = wrapType !== WrapType.NOT_APPLICABLE
const trade = showWrap ? undefined : v2Trade
const parsedAmounts = showWrap
? {
[Field.INPUT]: parsedAmount,
[Field.OUTPUT]: parsedAmount,
}
: {
[Field.INPUT]: independentField === Field.INPUT ? parsedAmount : trade?.inputAmount,
[Field.OUTPUT]: independentField === Field.OUTPUT ? parsedAmount : trade?.outputAmount,
}
const { onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient } = useSwapActionHandlers()
const isValid = !swapInputError
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
const handleTypeInput = useCallback(
(value: string) => {
onUserInput(Field.INPUT, value)
},
[onUserInput]
)
const handleTypeOutput = useCallback(
(value: string) => {
onUserInput(Field.OUTPUT, value)
},
[onUserInput]
)
// modal and loading
const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{
showConfirm: boolean
tradeToConfirm: Trade | undefined
attemptingTxn: boolean
swapErrorMessage: string | undefined
txHash: string | undefined
}>({
showConfirm: false,
tradeToConfirm: undefined,
attemptingTxn: false,
swapErrorMessage: undefined,
txHash: undefined,
})
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: showWrap
? parsedAmounts[independentField]?.toExact() ?? ''
: parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
const route = trade?.route
const userHasSpecifiedInputOutput = Boolean(
currencies[Field.INPUT] && currencies[Field.OUTPUT] && parsedAmounts[independentField]?.greaterThan(JSBI.BigInt(0))
)
const noRoute = !route
// check whether the user has approved the router on the input token
const [approval, approveCallback] = useApproveCallbackFromTrade(trade, allowedSlippage)
// check if user has gone through approval process, used to show two step buttons, reset on token change
const [approvalSubmitted, setApprovalSubmitted] = useState<boolean>(false)
// mark when a user has submitted an approval, reset onTokenSelection for input field
useEffect(() => {
if (approval === ApprovalState.PENDING) {
setApprovalSubmitted(true)
}
}, [approval, approvalSubmitted])
const maxAmountInput: CurrencyAmount | undefined = maxAmountSpend(currencyBalances[Field.INPUT])
const atMaxAmountInput = Boolean(maxAmountInput && parsedAmounts[Field.INPUT]?.equalTo(maxAmountInput))
// the callback to execute the swap
const { callback: swapCallback, error: swapCallbackError } = useSwapCallback(
trade,
allowedSlippage,
deadline,
recipient
)
const { priceImpactWithoutFee } = computeTradePriceBreakdown(trade)
const handleSwap = useCallback(() => {
if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) {
return
}
if (!swapCallback) {
return
}
setSwapState((prevState) => ({ ...prevState, attemptingTxn: true, swapErrorMessage: undefined, txHash: undefined }))
swapCallback()
.then((hash) => {
setSwapState((prevState) => ({
...prevState,
attemptingTxn: false,
swapErrorMessage: undefined,
txHash: hash,
}))
})
.catch((error) => {
setSwapState((prevState) => ({
...prevState,
attemptingTxn: false,
swapErrorMessage: error.message,
txHash: undefined,
}))
})
}, [priceImpactWithoutFee, swapCallback, setSwapState])
// errors
const [showInverted, setShowInverted] = useState<boolean>(false)
// warnings on slippage
const priceImpactSeverity = warningSeverity(priceImpactWithoutFee)
// 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 =
!swapInputError &&
(approval === ApprovalState.NOT_APPROVED ||
approval === ApprovalState.PENDING ||
(approvalSubmitted && approval === ApprovalState.APPROVED)) &&
!(priceImpactSeverity > 3 && !isExpertMode)
const handleConfirmDismiss = useCallback(() => {
setSwapState((prevState) => ({ ...prevState, showConfirm: false }))
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.INPUT, '')
}
}, [onUserInput, txHash, setSwapState])
const handleAcceptChanges = useCallback(() => {
setSwapState((prevState) => ({ ...prevState, tradeToConfirm: trade }))
}, [trade])
// This will check to see if the user has selected Syrup or SafeMoon to either buy or sell.
// If so, they will be alerted with a warning message.
const checkForWarning = useCallback(
(selected: string, purchaseType: string) => {
if (['SYRUP', 'SAFEMOON'].includes(selected)) {
setTransactionWarning({
selectedToken: selected,
purchaseType,
})
}
},
[setTransactionWarning]
)
const handleInputSelect = useCallback(
(inputCurrency) => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onCurrencySelection(Field.INPUT, inputCurrency)
if (inputCurrency.symbol === 'SYRUP') {
checkForWarning(inputCurrency.symbol, 'Selling')
}
if (inputCurrency.symbol === 'SAFEMOON') {
checkForWarning(inputCurrency.symbol, 'Selling')
}
},
[onCurrencySelection, setApprovalSubmitted, checkForWarning]
)
const handleMaxInput = useCallback(() => {
if (maxAmountInput) {
onUserInput(Field.INPUT, maxAmountInput.toExact())
}
}, [maxAmountInput, onUserInput])
const handleOutputSelect = useCallback(
(outputCurrency) => {
onCurrencySelection(Field.OUTPUT, outputCurrency)
if (outputCurrency.symbol === 'SYRUP') {
checkForWarning(outputCurrency.symbol, 'Buying')
}
if (outputCurrency.symbol === 'SAFEMOON') {
checkForWarning(outputCurrency.symbol, 'Buying')
}
},
[onCurrencySelection, checkForWarning]
)
return (
<>
<TokenWarningModal
isOpen={urlLoadedTokens.length > 0 && !dismissTokenWarning}
tokens={urlLoadedTokens}
onConfirm={handleConfirmTokenWarning}
/>
<SyrupWarningModal
isOpen={transactionWarning.selectedToken === 'SYRUP'}
transactionType={transactionWarning.purchaseType}
onConfirm={handleConfirmWarning}
/>
<SafeMoonWarningModal isOpen={transactionWarning.selectedToken === 'SAFEMOON'} onConfirm={handleConfirmWarning} />
<CardNav />
<AppBody>
<Wrapper id="swap-page">
<ConfirmSwapModal
isOpen={showConfirm}
trade={trade}
originalTrade={tradeToConfirm}
onAcceptChanges={handleAcceptChanges}
attemptingTxn={attemptingTxn}
txHash={txHash}
recipient={recipient}
allowedSlippage={allowedSlippage}
onConfirm={handleSwap}
swapErrorMessage={swapErrorMessage}
onDismiss={handleConfirmDismiss}
/>
<PageHeader
title={TranslateString(8, 'Exchange')}
description={TranslateString(1192, 'Trade tokens in an instant')}
/>
<CardBody>
<AutoColumn gap="md">
<CurrencyInputPanel
label={
independentField === Field.OUTPUT && !showWrap && trade
? TranslateString(194, 'From (estimated)')
: TranslateString(76, 'From')
}
value={formattedAmounts[Field.INPUT]}
showMaxButton={!atMaxAmountInput}
currency={currencies[Field.INPUT]}
onUserInput={handleTypeInput}
onMax={handleMaxInput}
onCurrencySelect={handleInputSelect}
otherCurrency={currencies[Field.OUTPUT]}
id="swap-currency-input"
/>
<AutoColumn justify="space-between">
<AutoRow justify={isExpertMode ? 'space-between' : 'center'} style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable>
<IconButton
variant="tertiary"
onClick={() => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onSwitchTokens()
}}
style={{ borderRadius: '50%' }}
scale="sm"
>
<ArrowDownIcon color="primary" width="24px" />
</IconButton>
</ArrowWrapper>
{recipient === null && !showWrap && isExpertMode ? (
<LinkStyledButton id="add-recipient-button" onClick={() => onChangeRecipient('')}>
+ Add a send (optional)
</LinkStyledButton>
) : null}
</AutoRow>
</AutoColumn>
<CurrencyInputPanel
value={formattedAmounts[Field.OUTPUT]}
onUserInput={handleTypeOutput}
label={
independentField === Field.INPUT && !showWrap && trade
? TranslateString(196, 'To (estimated)')
: TranslateString(80, 'To')
}
showMaxButton={false}
currency={currencies[Field.OUTPUT]}
onCurrencySelect={handleOutputSelect}
otherCurrency={currencies[Field.INPUT]}
id="swap-currency-output"
/>
{recipient !== null && !showWrap ? (
<>
<AutoRow justify="space-between" style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable={false}>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ArrowWrapper>
<LinkStyledButton id="remove-recipient-button" onClick={() => onChangeRecipient(null)}>
- Remove send
</LinkStyledButton>
</AutoRow>
<AddressInputPanel id="recipient" value={recipient} onChange={onChangeRecipient} />
</>
) : null}
{showWrap ? null : (
<Card padding=".25rem .75rem 0 .75rem" borderRadius="20px">
<AutoColumn gap="4px">
{Boolean(trade) && (
<RowBetween align="center">
<Text fontSize="14px">{TranslateString(1182, 'Price')}</Text>
<TradePrice
price={trade?.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween>
)}
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
<RowBetween align="center">
<Text fontSize="14px">{TranslateString(88, 'Slippage Tolerance')}</Text>
<Text fontSize="14px">{allowedSlippage / 100}%</Text>
</RowBetween>
)}
</AutoColumn>
</Card>
)}
</AutoColumn>
<BottomGrouping>
{!account ? (
<ConnectWalletButton width="100%" />
) : showWrap ? (
<Button disabled={Boolean(wrapInputError)} onClick={onWrap} width="100%">
{wrapInputError ??
(wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
</Button>
) : noRoute && userHasSpecifiedInputOutput ? (
<GreyCard style={{ textAlign: 'center' }}>
<Text mb="4px">{TranslateString(1194, 'Insufficient liquidity for this trade.')}</Text>
</GreyCard>
) : showApproveFlow ? (
<RowBetween>
<Button
onClick={approveCallback}
disabled={approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
style={{ width: '48%' }}
variant={approval === ApprovalState.APPROVED ? 'success' : 'primary'}
>
{approval === ApprovalState.PENDING ? (
<AutoRow gap="6px" justify="center">
Approving <Loader stroke="white" />
</AutoRow>
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
'Approved'
) : (
`Approve ${currencies[Field.INPUT]?.symbol}`
)}
</Button>
<Button
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
})
}
}}
style={{ width: '48%' }}
id="swap-button"
disabled={
!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !isExpertMode)
}
variant={isValid && priceImpactSeverity > 2 ? 'danger' : 'primary'}
>
{priceImpactSeverity > 3 && !isExpertMode
? `Price Impact High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Button>
</RowBetween>
) : (
<Button
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
})
}
}}
id="swap-button"
disabled={!isValid || (priceImpactSeverity > 3 && !isExpertMode) || !!swapCallbackError}
variant={isValid && priceImpactSeverity > 2 && !swapCallbackError ? 'danger' : 'primary'}
width="100%"
>
{swapInputError ||
(priceImpactSeverity > 3 && !isExpertMode
? `Price Impact Too High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`)}
</Button>
)}
{showApproveFlow && <ProgressSteps steps={[approval === ApprovalState.APPROVED]} />}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
</BottomGrouping>
</CardBody>
</Wrapper>
</AppBody>
<AdvancedSwapDetailsDropdown trade={trade} />
</>
)
}
Example #9
Source File: index.tsx From pancake-swap-interface-v1 with GNU General Public License v3.0 | 4 votes |
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB },
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React()
const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB)
const TranslateString = useI18n()
const oneCurrencyIsWBNB = Boolean(
chainId &&
((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
(currencyB && currencyEquals(currencyB, WETH[chainId])))
)
const expertMode = useIsExpertMode()
// mint state
const { independentField, typedValue, otherTypedValue } = useMintState()
const {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
// txn values
const [deadline] = useUserDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: noLiquidity ? otherTypedValue : parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
// get the max amounts user can add
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmountSpend(currencyBalances[field]),
}
},
{}
)
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0'),
}
},
{}
)
// check whether the user has approved the router on the tokens
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS)
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS)
const addTransaction = useTransactionAdder()
async function onAdd() {
if (!chainId || !library || !account) return
const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) {
return
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0],
}
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate
let method: (...args: any) => Promise<TransactionResponse>
let args: Array<string | string[] | number>
let value: BigNumber | null
if (currencyA === ETHER || currencyB === ETHER) {
const tokenBIsBNB = currencyB === ETHER
estimate = router.estimateGas.addLiquidityETH
method = router.addLiquidityETH
args = [
wrappedCurrency(tokenBIsBNB ? currencyA : currencyB, chainId)?.address ?? '', // token
(tokenBIsBNB ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[tokenBIsBNB ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsBNB ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account,
deadlineFromNow,
]
value = BigNumber.from((tokenBIsBNB ? parsedAmountB : parsedAmountA).raw.toString())
} else {
estimate = router.estimateGas.addLiquidity
method = router.addLiquidity
args = [
wrappedCurrency(currencyA, chainId)?.address ?? '',
wrappedCurrency(currencyB, chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
value = null
}
setAttemptingTxn(true)
// const aa = await estimate(...args, value ? { value } : {})
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then((response) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Add ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencies[Field.CURRENCY_B]?.symbol}`,
})
setTxHash(response.hash)
})
)
.catch((e) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (e?.code !== 4001) {
console.error(e)
}
})
}
const modalHeader = () => {
return noLiquidity ? (
<AutoColumn gap="20px">
<LightCard mt="20px" borderRadius="20px">
<RowFlat>
<UIKitText fontSize="48px" mr="8px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol}`}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
</LightCard>
</AutoColumn>
) : (
<AutoColumn gap="20px">
<RowFlat style={{ marginTop: '20px' }}>
<UIKitText fontSize="48px" mr="8px">
{liquidityMinted?.toSignificant(6)}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
<Row>
<UIKitText fontSize="24px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol} Pool Tokens`}
</UIKitText>
</Row>
<UIKitText small textAlign="left" padding="8px 0 0 0 " style={{ fontStyle: 'italic' }}>
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</UIKitText>
</AutoColumn>
)
}
const modalBottom = () => {
return (
<ConfirmAddModalBottom
price={price}
currencies={currencies}
parsedAmounts={parsedAmounts}
noLiquidity={noLiquidity}
onAdd={onAdd}
poolTokenPercentage={poolTokenPercentage}
/>
)
}
const pendingText = `Supplying ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencies[Field.CURRENCY_B]?.symbol}`
const handleCurrencyASelect = useCallback(
(currA: Currency) => {
const newCurrencyIdA = currencyId(currA)
if (newCurrencyIdA === currencyIdB) {
history.push(`/add/${currencyIdB}/${currencyIdA}`)
} else {
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`)
}
},
[currencyIdB, history, currencyIdA]
)
const handleCurrencyBSelect = useCallback(
(currB: Currency) => {
const newCurrencyIdB = currencyId(currB)
if (currencyIdA === newCurrencyIdB) {
if (currencyIdB) {
history.push(`/add/${currencyIdB}/${newCurrencyIdB}`)
} else {
history.push(`/add/${newCurrencyIdB}`)
}
} else {
history.push(`/add/${currencyIdA || 'BNB'}/${newCurrencyIdB}`)
}
},
[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 (
<Container>
<CardNav activeIndex={1} />
<AppBody>
<AddRemoveTabs adding />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash}
content={() => (
<ConfirmationModalContent
title={
noLiquidity
? TranslateString(1154, 'You are creating a pool')
: TranslateString(1156, 'You will receive')
}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<CardBody>
<AutoColumn gap="20px">
{noLiquidity && (
<ColumnCenter>
<Pane>
<AutoColumn gap="12px">
<UIKitText>{TranslateString(1158, 'You are the first liquidity provider.')}</UIKitText>
<UIKitText>
{TranslateString(1160, 'The ratio of tokens you add will set the price of this pool.')}
</UIKitText>
<UIKitText>
{TranslateString(1162, 'Once you are happy with the rate click supply to review.')}
</UIKitText>
</AutoColumn>
</Pane>
</ColumnCenter>
)}
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
onMax={() => {
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}}
onCurrencySelect={handleCurrencyASelect}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A]}
id="add-liquidity-input-tokena"
showCommonBases={false}
/>
<ColumnCenter>
<AddIcon color="textSubtle" />
</ColumnCenter>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onFieldBInput}
onCurrencySelect={handleCurrencyBSelect}
onMax={() => {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
currency={currencies[Field.CURRENCY_B]}
id="add-liquidity-input-tokenb"
showCommonBases={false}
/>
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<div>
<UIKitText
style={{ textTransform: 'uppercase', fontWeight: 600 }}
color="textSubtle"
fontSize="12px"
mb="2px"
>
{noLiquidity
? TranslateString(1164, 'Initial prices and pool share')
: TranslateString(1166, 'Prices and pool share')}
</UIKitText>
<Pane>
<PoolPriceBar
currencies={currencies}
poolTokenPercentage={poolTokenPercentage}
noLiquidity={noLiquidity}
price={price}
/>
</Pane>
</div>
)}
{!account ? (
<ConnectWalletButton width="100%" />
) : (
<AutoColumn gap="md">
{(approvalA === ApprovalState.NOT_APPROVED ||
approvalA === ApprovalState.PENDING ||
approvalB === ApprovalState.NOT_APPROVED ||
approvalB === ApprovalState.PENDING) &&
isValid && (
<RowBetween>
{approvalA !== ApprovalState.APPROVED && (
<Button
onClick={approveACallback}
disabled={approvalA === ApprovalState.PENDING}
style={{ width: approvalB !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalA === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_A]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_A]?.symbol}`
)}
</Button>
)}
{approvalB !== ApprovalState.APPROVED && (
<Button
onClick={approveBCallback}
disabled={approvalB === ApprovalState.PENDING}
style={{ width: approvalA !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalB === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_B]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_B]?.symbol}`
)}
</Button>
)}
</RowBetween>
)}
<Button
onClick={() => {
if (expertMode) {
onAdd()
} else {
setShowConfirm(true)
}
}}
disabled
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
width="100%"
>
{error ?? "You can't add liquidity on V1"}
</Button>
</AutoColumn>
)}
</AutoColumn>
</CardBody>
</Wrapper>
</AppBody>
{pair && !noLiquidity && pairState !== PairState.INVALID ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWBNB} pair={pair} />
</AutoColumn>
) : null}
</Container>
)
}
Example #10
Source File: index.tsx From pancake-swap-interface-v1 with GNU General Public License v3.0 | 4 votes |
Swap = () => {
const loadedUrlParams = useDefaultsFromURLSearch()
const TranslateString = useI18n()
const [modalCountdownSecondsRemaining, setModalCountdownSecondsRemaining] = useState(5)
const [disableSwap, setDisableSwap] = useState(false)
const [hasPoppedModal, setHasPoppedModal] = useState(false)
const [interruptRedirectCountdown, setInterruptRedirectCountdown] = useState(false)
const [onPresentV2ExchangeRedirectModal] = useModal(
<V2ExchangeRedirectModal handleCloseModal={() => setInterruptRedirectCountdown(true)} />
)
const onPresentV2ExchangeRedirectModalRef = useRef(onPresentV2ExchangeRedirectModal)
// token warning stuff
const [loadedInputCurrency, loadedOutputCurrency] = [
useCurrency(loadedUrlParams?.inputCurrencyId),
useCurrency(loadedUrlParams?.outputCurrencyId),
]
const [dismissTokenWarning, setDismissTokenWarning] = useState<boolean>(false)
const [transactionWarning, setTransactionWarning] = useState<{
selectedToken: string | null
purchaseType: string | null
}>({
selectedToken: null,
purchaseType: null,
})
const urlLoadedTokens: Token[] = useMemo(
() => [loadedInputCurrency, loadedOutputCurrency]?.filter((c): c is Token => c instanceof Token) ?? [],
[loadedInputCurrency, loadedOutputCurrency]
)
const handleConfirmTokenWarning = useCallback(() => {
setDismissTokenWarning(true)
}, [])
const handleConfirmWarning = () => {
setTransactionWarning({
selectedToken: null,
purchaseType: null,
})
}
const { account } = useActiveWeb3React()
const theme = useContext(ThemeContext)
const [isExpertMode] = useExpertModeManager()
// get custom setting values for user
const [deadline] = useUserDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
// swap state
const { independentField, typedValue, recipient } = useSwapState()
const { v2Trade, currencyBalances, parsedAmount, currencies, inputError: swapInputError } = useDerivedSwapInfo()
const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback(
currencies[Field.INPUT],
currencies[Field.OUTPUT],
typedValue
)
const showWrap: boolean = wrapType !== WrapType.NOT_APPLICABLE
const trade = showWrap ? undefined : v2Trade
// Manage disabled trading pairs that should redirect users to V2
useEffect(() => {
const disabledSwaps = [
'BNB',
'BUSD',
'USDT',
'USDC',
'CAKE',
'BUNNY',
'ETH',
'BTCB',
'AUTO',
'XVS',
'SAFEMOON',
'DAI',
'ADA',
'DOT',
'ElonGate',
'TWT',
'ALPACA',
'IOTX',
'BAND',
'ATOM',
'EOS',
'SFP',
'SWINGBY',
]
const inputCurrencySymbol = currencies[Field.INPUT]?.symbol || ''
const outputCurrencySymbol = currencies[Field.OUTPUT]?.symbol || ''
const doesInputMatch = disabledSwaps.includes(inputCurrencySymbol)
const doesOutputMatch = disabledSwaps.includes(outputCurrencySymbol)
if (doesInputMatch && doesOutputMatch) {
// Prevent infinite re-render of modal with this condition
if (!hasPoppedModal) {
setHasPoppedModal(true)
onPresentV2ExchangeRedirectModalRef.current()
}
// Controls the swap buttons being disabled & renders a message
setDisableSwap(true)
const tick = () => {
setModalCountdownSecondsRemaining((prevSeconds) => prevSeconds - 1)
}
const timerInterval = setInterval(() => tick(), 1000)
if (interruptRedirectCountdown) {
// Reset timer if countdown is interrupted
clearInterval(timerInterval)
setModalCountdownSecondsRemaining(5)
}
if (modalCountdownSecondsRemaining <= 0) {
window.location.href = 'https://exchange.pancakeswap.finance/#/swap'
}
return () => {
clearInterval(timerInterval)
}
}
// Unset disableSwap state if the swap inputs & outputs dont match disabledSwaps
setDisableSwap(false)
return undefined
}, [
currencies,
hasPoppedModal,
modalCountdownSecondsRemaining,
onPresentV2ExchangeRedirectModalRef,
interruptRedirectCountdown,
])
const parsedAmounts = showWrap
? {
[Field.INPUT]: parsedAmount,
[Field.OUTPUT]: parsedAmount,
}
: {
[Field.INPUT]: independentField === Field.INPUT ? parsedAmount : trade?.inputAmount,
[Field.OUTPUT]: independentField === Field.OUTPUT ? parsedAmount : trade?.outputAmount,
}
const { onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient } = useSwapActionHandlers()
const isValid = !swapInputError
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
const handleTypeInput = useCallback(
(value: string) => {
onUserInput(Field.INPUT, value)
},
[onUserInput]
)
const handleTypeOutput = useCallback(
(value: string) => {
onUserInput(Field.OUTPUT, value)
},
[onUserInput]
)
// modal and loading
const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{
showConfirm: boolean
tradeToConfirm: Trade | undefined
attemptingTxn: boolean
swapErrorMessage: string | undefined
txHash: string | undefined
}>({
showConfirm: false,
tradeToConfirm: undefined,
attemptingTxn: false,
swapErrorMessage: undefined,
txHash: undefined,
})
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: showWrap
? parsedAmounts[independentField]?.toExact() ?? ''
: parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
const route = trade?.route
const userHasSpecifiedInputOutput = Boolean(
currencies[Field.INPUT] && currencies[Field.OUTPUT] && parsedAmounts[independentField]?.greaterThan(JSBI.BigInt(0))
)
const noRoute = !route
// check whether the user has approved the router on the input token
const [approval, approveCallback] = useApproveCallbackFromTrade(trade, allowedSlippage)
// check if user has gone through approval process, used to show two step buttons, reset on token change
const [approvalSubmitted, setApprovalSubmitted] = useState<boolean>(false)
// mark when a user has submitted an approval, reset onTokenSelection for input field
useEffect(() => {
if (approval === ApprovalState.PENDING) {
setApprovalSubmitted(true)
}
}, [approval, approvalSubmitted])
const maxAmountInput: CurrencyAmount | undefined = maxAmountSpend(currencyBalances[Field.INPUT])
const atMaxAmountInput = Boolean(maxAmountInput && parsedAmounts[Field.INPUT]?.equalTo(maxAmountInput))
// the callback to execute the swap
const { callback: swapCallback, error: swapCallbackError } = useSwapCallback(
trade,
allowedSlippage,
deadline,
recipient
)
const { priceImpactWithoutFee } = computeTradePriceBreakdown(trade)
const handleSwap = useCallback(() => {
if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) {
return
}
if (!swapCallback) {
return
}
setSwapState((prevState) => ({ ...prevState, attemptingTxn: true, swapErrorMessage: undefined, txHash: undefined }))
swapCallback()
.then((hash) => {
setSwapState((prevState) => ({
...prevState,
attemptingTxn: false,
swapErrorMessage: undefined,
txHash: hash,
}))
})
.catch((error) => {
setSwapState((prevState) => ({
...prevState,
attemptingTxn: false,
swapErrorMessage: error.message,
txHash: undefined,
}))
})
}, [priceImpactWithoutFee, swapCallback, setSwapState])
// errors
const [showInverted, setShowInverted] = useState<boolean>(false)
// warnings on slippage
const priceImpactSeverity = warningSeverity(priceImpactWithoutFee)
// 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 =
!swapInputError &&
(approval === ApprovalState.NOT_APPROVED ||
approval === ApprovalState.PENDING ||
(approvalSubmitted && approval === ApprovalState.APPROVED)) &&
!(priceImpactSeverity > 3 && !isExpertMode)
const handleConfirmDismiss = useCallback(() => {
setSwapState((prevState) => ({ ...prevState, showConfirm: false }))
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.INPUT, '')
}
}, [onUserInput, txHash, setSwapState])
const handleAcceptChanges = useCallback(() => {
setSwapState((prevState) => ({ ...prevState, tradeToConfirm: trade }))
}, [trade])
// This will check to see if the user has selected Syrup or SafeMoon to either buy or sell.
// If so, they will be alerted with a warning message.
const checkForWarning = useCallback(
(selected: string, purchaseType: string) => {
if (['SYRUP', 'SAFEMOON'].includes(selected)) {
setTransactionWarning({
selectedToken: selected,
purchaseType,
})
}
},
[setTransactionWarning]
)
const handleInputSelect = useCallback(
(inputCurrency) => {
setHasPoppedModal(false)
setInterruptRedirectCountdown(false)
setApprovalSubmitted(false) // reset 2 step UI for approvals
onCurrencySelection(Field.INPUT, inputCurrency)
if (inputCurrency.symbol === 'SYRUP') {
checkForWarning(inputCurrency.symbol, 'Selling')
}
if (inputCurrency.symbol === 'SAFEMOON') {
checkForWarning(inputCurrency.symbol, 'Selling')
}
},
[onCurrencySelection, setApprovalSubmitted, checkForWarning]
)
const handleMaxInput = useCallback(() => {
if (maxAmountInput) {
onUserInput(Field.INPUT, maxAmountInput.toExact())
}
}, [maxAmountInput, onUserInput])
const handleOutputSelect = useCallback(
(outputCurrency) => {
setHasPoppedModal(false)
setInterruptRedirectCountdown(false)
onCurrencySelection(Field.OUTPUT, outputCurrency)
if (outputCurrency.symbol === 'SYRUP') {
checkForWarning(outputCurrency.symbol, 'Buying')
}
if (outputCurrency.symbol === 'SAFEMOON') {
checkForWarning(outputCurrency.symbol, 'Buying')
}
},
[onCurrencySelection, checkForWarning]
)
return (
<Container>
<TokenWarningModal
isOpen={urlLoadedTokens.length > 0 && !dismissTokenWarning}
tokens={urlLoadedTokens}
onConfirm={handleConfirmTokenWarning}
/>
<SyrupWarningModal
isOpen={transactionWarning.selectedToken === 'SYRUP'}
transactionType={transactionWarning.purchaseType}
onConfirm={handleConfirmWarning}
/>
<SafeMoonWarningModal isOpen={transactionWarning.selectedToken === 'SAFEMOON'} onConfirm={handleConfirmWarning} />
<CardNav />
<AppBody>
<Wrapper id="swap-page">
<ConfirmSwapModal
isOpen={showConfirm}
trade={trade}
originalTrade={tradeToConfirm}
onAcceptChanges={handleAcceptChanges}
attemptingTxn={attemptingTxn}
txHash={txHash}
recipient={recipient}
allowedSlippage={allowedSlippage}
onConfirm={handleSwap}
swapErrorMessage={swapErrorMessage}
onDismiss={handleConfirmDismiss}
/>
<PageHeader
title={TranslateString(8, 'Exchange')}
description={TranslateString(1192, 'Trade tokens in an instant')}
/>
<CardBody>
<AutoColumn gap="md">
<CurrencyInputPanel
label={
independentField === Field.OUTPUT && !showWrap && trade
? TranslateString(194, 'From (estimated)')
: TranslateString(76, 'From')
}
value={formattedAmounts[Field.INPUT]}
showMaxButton={!atMaxAmountInput}
currency={currencies[Field.INPUT]}
onUserInput={handleTypeInput}
onMax={handleMaxInput}
onCurrencySelect={handleInputSelect}
otherCurrency={currencies[Field.OUTPUT]}
id="swap-currency-input"
/>
<AutoColumn justify="space-between">
<AutoRow justify={isExpertMode ? 'space-between' : 'center'} style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable>
<IconButton
variant="tertiary"
onClick={() => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onSwitchTokens()
}}
style={{ borderRadius: '50%' }}
scale="sm"
>
<ArrowDownIcon color="primary" width="24px" />
</IconButton>
</ArrowWrapper>
{recipient === null && !showWrap && isExpertMode ? (
<LinkStyledButton id="add-recipient-button" onClick={() => onChangeRecipient('')}>
+ Add a send (optional)
</LinkStyledButton>
) : null}
</AutoRow>
</AutoColumn>
<CurrencyInputPanel
value={formattedAmounts[Field.OUTPUT]}
onUserInput={handleTypeOutput}
label={
independentField === Field.INPUT && !showWrap && trade
? TranslateString(196, 'To (estimated)')
: TranslateString(80, 'To')
}
showMaxButton={false}
currency={currencies[Field.OUTPUT]}
onCurrencySelect={handleOutputSelect}
otherCurrency={currencies[Field.INPUT]}
id="swap-currency-output"
/>
{recipient !== null && !showWrap ? (
<>
<AutoRow justify="space-between" style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable={false}>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ArrowWrapper>
<LinkStyledButton id="remove-recipient-button" onClick={() => onChangeRecipient(null)}>
- Remove send
</LinkStyledButton>
</AutoRow>
<AddressInputPanel id="recipient" value={recipient} onChange={onChangeRecipient} />
</>
) : null}
{showWrap ? null : (
<Card padding=".25rem .75rem 0 .75rem" borderRadius="20px">
<AutoColumn gap="4px">
{Boolean(trade) && (
<RowBetween align="center">
<Text fontSize="14px">{TranslateString(1182, 'Price')}</Text>
<TradePrice
price={trade?.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween>
)}
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
<RowBetween align="center">
<Text fontSize="14px">{TranslateString(88, 'Slippage Tolerance')}</Text>
<Text fontSize="14px">{allowedSlippage / 100}%</Text>
</RowBetween>
)}
</AutoColumn>
</Card>
)}
</AutoColumn>
<BottomGrouping>
{disableSwap && (
<Flex alignItems="center" justifyContent="center" mb="1rem">
<Text color="failure">
Please use{' '}
<StyledLink external href="https://exchange.pancakeswap.finance">
PancakeSwap V2
</StyledLink>{' '}
to make this trade
</Text>
</Flex>
)}
{!account ? (
<ConnectWalletButton width="100%" />
) : showWrap ? (
<Button disabled={Boolean(wrapInputError)} onClick={onWrap} width="100%">
{wrapInputError ??
(wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
</Button>
) : noRoute && userHasSpecifiedInputOutput ? (
<GreyCard style={{ textAlign: 'center' }}>
<Text mb="4px">{TranslateString(1194, 'Insufficient liquidity for this trade.')}</Text>
</GreyCard>
) : showApproveFlow ? (
<RowBetween>
<Button
onClick={approveCallback}
disabled={disableSwap || approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
style={{ width: '48%' }}
variant={approval === ApprovalState.APPROVED ? 'success' : 'primary'}
>
{approval === ApprovalState.PENDING ? (
<AutoRow gap="6px" justify="center">
Approving <Loader stroke="white" />
</AutoRow>
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
'Approved'
) : (
`Approve ${currencies[Field.INPUT]?.symbol}`
)}
</Button>
<Button
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
})
}
}}
style={{ width: '48%' }}
id="swap-button"
disabled={
disableSwap ||
!isValid ||
approval !== ApprovalState.APPROVED ||
(priceImpactSeverity > 3 && !isExpertMode)
}
variant={isValid && priceImpactSeverity > 2 ? 'danger' : 'primary'}
>
{priceImpactSeverity > 3 && !isExpertMode
? `Price Impact High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Button>
</RowBetween>
) : (
<Button
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
})
}
}}
id="swap-button"
disabled={
disableSwap || !isValid || (priceImpactSeverity > 3 && !isExpertMode) || !!swapCallbackError
}
variant={isValid && priceImpactSeverity > 2 && !swapCallbackError ? 'danger' : 'primary'}
width="100%"
>
{swapInputError ||
(priceImpactSeverity > 3 && !isExpertMode
? `Price Impact Too High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`)}
</Button>
)}
{showApproveFlow && <ProgressSteps steps={[approval === ApprovalState.APPROVED]} />}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
</BottomGrouping>
</CardBody>
</Wrapper>
</AppBody>
<AdvancedSwapDetailsDropdown trade={trade} />
</Container>
)
}
Example #11
Source File: SlippageToleranceSetting.tsx From panther-frontend-dex with GNU General Public License v3.0 | 4 votes |
SlippageToleranceSettings = () => {
const [userSlippageTolerance, setUserslippageTolerance] = useUserSlippageTolerance()
const [value, setValue] = useState(userSlippageTolerance / 100)
const [error, setError] = useState<string | null>(null)
const handleChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
const { value: inputValue } = evt.target
setValue(parseFloat(inputValue))
}
// Updates local storage if value is valid
useEffect(() => {
try {
const rawValue = value * 100
if (!Number.isNaN(rawValue) && rawValue > 0 && rawValue < MAX_SLIPPAGE) {
setUserslippageTolerance(rawValue)
setError(null)
} else {
setError('Enter a valid slippage percentage')
}
} catch {
setError('Enter a valid slippage percentage')
}
}, [value, setError, setUserslippageTolerance])
// Notify user if slippage is risky
useEffect(() => {
if (userSlippageTolerance < RISKY_SLIPPAGE_LOW) {
setError('Your transaction may fail')
} else if (userSlippageTolerance > RISKY_SLIPPAGE_HIGH) {
setError('Your transaction may be frontrun')
}
}, [userSlippageTolerance, setError])
return (
<StyledSlippageToleranceSettings>
<Label>
<Text style={{ fontWeight: 600 }}>
<TranslatedText translationId={88}>Slippage tolerance</TranslatedText>
</Text>
<QuestionHelper text="Your transaction will revert if the price changes unfavorably by more than this percentage." />
</Label>
<Options>
<Flex mb={['8px', 0]} mr={[0, '8px']}>
{predefinedValues.map(({ label, value: predefinedValue }) => {
const handleClick = () => setValue(predefinedValue)
return (
<Option key={predefinedValue}>
<Button variant={value === predefinedValue ? 'primary' : 'tertiary'} onClick={handleClick}>
{label}
</Button>
</Option>
)
})}
</Flex>
<Flex alignItems="center">
<Option>
<Input
type="number"
scale="lg"
step={0.1}
min={0.1}
placeholder="5%"
value={value}
onChange={handleChange}
isWarning={error !== null}
/>
</Option>
<Option>
<Text fontSize="18px">%</Text>
</Option>
</Flex>
</Options>
{error && (
<Text mt="8px" color="failure">
{error}
</Text>
)}
</StyledSlippageToleranceSettings>
)
}
Example #12
Source File: index.tsx From panther-frontend-dex with GNU General Public License v3.0 | 4 votes |
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB },
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React()
const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB)
const oneCurrencyIsWETH = Boolean(
chainId &&
((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
(currencyB && currencyEquals(currencyB, WETH[chainId])))
)
const expertMode = useIsExpertMode()
// mint state
const { independentField, typedValue, otherTypedValue } = useMintState()
const {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
// txn values
const [deadline] = useUserDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: noLiquidity ? otherTypedValue : parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
// get the max amounts user can add
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmountSpend(currencyBalances[field]),
}
},
{}
)
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0'),
}
},
{}
)
// check whether the user has approved the router on the tokens
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS)
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS)
const addTransaction = useTransactionAdder()
async function onAdd() {
if (!chainId || !library || !account) return
const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) {
return
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0],
}
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate
let method: (...args: any) => Promise<TransactionResponse>
let args: Array<string | string[] | number>
let value: BigNumber | null
if (currencyA === ETHER || currencyB === ETHER) {
const tokenBIsETH = currencyB === ETHER
estimate = router.estimateGas.addLiquidityETH
method = router.addLiquidityETH
args = [
wrappedCurrency(tokenBIsETH ? currencyA : currencyB, chainId)?.address ?? '', // token
(tokenBIsETH ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[tokenBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account,
deadlineFromNow,
]
value = BigNumber.from((tokenBIsETH ? parsedAmountB : parsedAmountA).raw.toString())
} else {
estimate = router.estimateGas.addLiquidity
method = router.addLiquidity
args = [
wrappedCurrency(currencyA, chainId)?.address ?? '',
wrappedCurrency(currencyB, chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
value = null
}
setAttemptingTxn(true)
// const aa = await estimate(...args, value ? { value } : {})
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then((response) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Add ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencies[Field.CURRENCY_B]?.symbol}`,
})
setTxHash(response.hash)
})
)
.catch((e) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (e?.code !== 4001) {
console.error(e)
}
})
}
const modalHeader = () => {
return noLiquidity ? (
<AutoColumn gap="20px">
<LightCard mt="20px" borderRadius="20px">
<RowFlat>
<UIKitText fontSize="48px" mr="8px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol}`}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
</LightCard>
</AutoColumn>
) : (
<AutoColumn gap="20px">
<RowFlat style={{ marginTop: '20px' }}>
<UIKitText fontSize="48px" mr="8px">
{liquidityMinted?.toSignificant(6)}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
<Row>
<UIKitText fontSize="24px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol} Pool Tokens`}
</UIKitText>
</Row>
<Italic fontSize={12} textAlign="left" padding="8px 0 0 0 ">
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</Italic>
</AutoColumn>
)
}
const modalBottom = () => {
return (
<ConfirmAddModalBottom
price={price}
currencies={currencies}
parsedAmounts={parsedAmounts}
noLiquidity={noLiquidity}
onAdd={onAdd}
poolTokenPercentage={poolTokenPercentage}
/>
)
}
const pendingText = `Supplying ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencies[Field.CURRENCY_B]?.symbol}`
const handleCurrencyASelect = useCallback(
(currA: Currency) => {
const newCurrencyIdA = currencyId(currA)
if (newCurrencyIdA === currencyIdB) {
history.push(`/add/${currencyIdB}/${currencyIdA}`)
} else {
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`)
}
},
[currencyIdB, history, currencyIdA]
)
const handleCurrencyBSelect = useCallback(
(currB: Currency) => {
const newCurrencyIdB = currencyId(currB)
if (currencyIdA === newCurrencyIdB) {
if (currencyIdB) {
history.push(`/add/${currencyIdB}/${newCurrencyIdB}`)
} else {
history.push(`/add/${newCurrencyIdB}`)
}
} else {
history.push(`/add/${currencyIdA || 'ETH'}/${newCurrencyIdB}`)
}
},
[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 (
<>
<CardNav activeIndex={1} />
<AppBody>
<AddRemoveTabs adding />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash}
content={() => (
<ConfirmationModalContent
title={noLiquidity ? 'You are creating a pool' : 'You will receive'}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<CardBody>
<AutoColumn gap="20px">
{noLiquidity && (
<ColumnCenter>
<Pane>
<AutoColumn gap="12px">
<UIKitText>You are the first liquidity provider.</UIKitText>
<UIKitText>The ratio of tokens you add will set the price of this pool.</UIKitText>
<UIKitText>Once you are happy with the rate click supply to review.</UIKitText>
</AutoColumn>
</Pane>
</ColumnCenter>
)}
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
onMax={() => {
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}}
onCurrencySelect={handleCurrencyASelect}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A]}
id="add-liquidity-input-tokena"
showCommonBases={false}
/>
<ColumnCenter>
<AddIcon color="textSubtle" />
</ColumnCenter>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onFieldBInput}
onCurrencySelect={handleCurrencyBSelect}
onMax={() => {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
currency={currencies[Field.CURRENCY_B]}
id="add-liquidity-input-tokenb"
showCommonBases={false}
/>
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<div>
<UIKitText
style={{ textTransform: 'uppercase', fontWeight: 600 }}
color="textSubtle"
fontSize="12px"
mb="2px"
>
{noLiquidity ? 'Initial prices and pool share' : 'Prices and pool share'}
</UIKitText>
<Pane>
<PoolPriceBar
currencies={currencies}
poolTokenPercentage={poolTokenPercentage}
noLiquidity={noLiquidity}
price={price}
/>
</Pane>
</div>
)}
{!account ? (
<ConnectWalletButton fullWidth />
) : (
<AutoColumn gap="md">
{(approvalA === ApprovalState.NOT_APPROVED ||
approvalA === ApprovalState.PENDING ||
approvalB === ApprovalState.NOT_APPROVED ||
approvalB === ApprovalState.PENDING) &&
isValid && (
<RowBetween>
{approvalA !== ApprovalState.APPROVED && (
<Button
onClick={approveACallback}
disabled={approvalA === ApprovalState.PENDING}
style={{ width: approvalB !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalA === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_A]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_A]?.symbol}`
)}
</Button>
)}
{approvalB !== ApprovalState.APPROVED && (
<Button
onClick={approveBCallback}
disabled={approvalB === ApprovalState.PENDING}
style={{ width: approvalA !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalB === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_B]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_B]?.symbol}`
)}
</Button>
)}
</RowBetween>
)}
<Button
onClick={() => {
if (expertMode) {
onAdd()
} else {
setShowConfirm(true)
}
}}
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
fullWidth
>
{error ?? 'Supply'}
</Button>
</AutoColumn>
)}
</AutoColumn>
</CardBody>
</Wrapper>
</AppBody>
{pair && !noLiquidity && pairState !== PairState.INVALID ? (
<AutoColumn style={{ maxWidth: '436px', width: '100%', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
</AutoColumn>
) : null}
</>
)
}
Example #13
Source File: index.tsx From panther-frontend-dex with GNU General Public License v3.0 | 4 votes |
Swap = () => {
const loadedUrlParams = useDefaultsFromURLSearch()
// token warning stuff
const [loadedInputCurrency, loadedOutputCurrency] = [
useCurrency(loadedUrlParams?.inputCurrencyId),
useCurrency(loadedUrlParams?.outputCurrencyId),
]
const [dismissTokenWarning, setDismissTokenWarning] = useState<boolean>(false)
const [isSyrup, setIsSyrup] = useState<boolean>(false)
const [syrupTransactionType, setSyrupTransactionType] = useState<string>('')
const urlLoadedTokens: Token[] = useMemo(
() => [loadedInputCurrency, loadedOutputCurrency]?.filter((c): c is Token => c instanceof Token) ?? [],
[loadedInputCurrency, loadedOutputCurrency]
)
const handleConfirmTokenWarning = useCallback(() => {
setDismissTokenWarning(true)
}, [])
const handleConfirmSyrupWarning = useCallback(() => {
setIsSyrup(false)
setSyrupTransactionType('')
}, [])
const { account } = useActiveWeb3React()
const theme = useContext(ThemeContext)
const [isExpertMode] = useExpertModeManager()
// get custom setting values for user
const [deadline] = useUserDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
// swap state
const { independentField, typedValue, recipient } = useSwapState()
const { v2Trade, currencyBalances, parsedAmount, currencies, inputError: swapInputError } = useDerivedSwapInfo()
const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback(
currencies[Field.INPUT],
currencies[Field.OUTPUT],
typedValue
)
const showWrap: boolean = wrapType !== WrapType.NOT_APPLICABLE
const trade = v2Trade
const parsedAmounts = showWrap
? {
[Field.INPUT]: parsedAmount,
[Field.OUTPUT]: parsedAmount,
}
: {
[Field.INPUT]: independentField === Field.INPUT ? parsedAmount : trade?.inputAmount,
[Field.OUTPUT]: independentField === Field.OUTPUT ? parsedAmount : trade?.outputAmount,
}
const { onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient } = useSwapActionHandlers()
const isValid = !swapInputError
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
const handleTypeInput = useCallback(
(value: string) => {
onUserInput(Field.INPUT, value)
},
[onUserInput]
)
const handleTypeOutput = useCallback(
(value: string) => {
onUserInput(Field.OUTPUT, value)
},
[onUserInput]
)
// modal and loading
const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{
showConfirm: boolean
tradeToConfirm: Trade | undefined
attemptingTxn: boolean
swapErrorMessage: string | undefined
txHash: string | undefined
}>({
showConfirm: false,
tradeToConfirm: undefined,
attemptingTxn: false,
swapErrorMessage: undefined,
txHash: undefined,
})
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: showWrap
? parsedAmounts[independentField]?.toExact() ?? ''
: parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
const route = trade?.route
const userHasSpecifiedInputOutput = Boolean(
currencies[Field.INPUT] && currencies[Field.OUTPUT] && parsedAmounts[independentField]?.greaterThan(JSBI.BigInt(0))
)
const noRoute = !route
// check whether the user has approved the router on the input token
const [approval, approveCallback] = useApproveCallbackFromTrade(trade, allowedSlippage)
// check if user has gone through approval process, used to show two step buttons, reset on token change
const [approvalSubmitted, setApprovalSubmitted] = useState<boolean>(false)
// mark when a user has submitted an approval, reset onTokenSelection for input field
useEffect(() => {
if (approval === ApprovalState.PENDING) {
setApprovalSubmitted(true)
}
}, [approval, approvalSubmitted])
const maxAmountInput: CurrencyAmount | undefined = maxAmountSpend(currencyBalances[Field.INPUT])
const atMaxAmountInput = Boolean(maxAmountInput && parsedAmounts[Field.INPUT]?.equalTo(maxAmountInput))
// the callback to execute the swap
const { callback: swapCallback, error: swapCallbackError } = useSwapCallback(
trade,
allowedSlippage,
deadline,
recipient
)
const { priceImpactWithoutFee } = computeTradePriceBreakdown(trade)
const handleSwap = useCallback(() => {
if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) {
return
}
if (!swapCallback) {
return
}
setSwapState((prevState) => ({ ...prevState, attemptingTxn: true, swapErrorMessage: undefined, txHash: undefined }))
swapCallback()
.then((hash) => {
setSwapState((prevState) => ({
...prevState,
attemptingTxn: false,
swapErrorMessage: undefined,
txHash: hash,
}))
})
.catch((error) => {
setSwapState((prevState) => ({
...prevState,
attemptingTxn: false,
swapErrorMessage: error.message,
txHash: undefined,
}))
})
}, [priceImpactWithoutFee, swapCallback, setSwapState])
// errors
const [showInverted, setShowInverted] = useState<boolean>(false)
// warnings on slippage
const priceImpactSeverity = warningSeverity(priceImpactWithoutFee)
// 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 =
!swapInputError &&
(approval === ApprovalState.NOT_APPROVED ||
approval === ApprovalState.PENDING ||
(approvalSubmitted && approval === ApprovalState.APPROVED)) &&
!(priceImpactSeverity > 3 && !isExpertMode)
const handleConfirmDismiss = useCallback(() => {
setSwapState((prevState) => ({ ...prevState, showConfirm: false }))
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.INPUT, '')
}
}, [onUserInput, txHash, setSwapState])
const handleAcceptChanges = useCallback(() => {
setSwapState((prevState) => ({ ...prevState, tradeToConfirm: trade }))
}, [trade])
// This will check to see if the user has selected Syrup to either buy or sell.
// If so, they will be alerted with a warning message.
const checkForSyrup = useCallback(
(selected: string, purchaseType: string) => {
if (selected === 'syrup') {
setIsSyrup(true)
setSyrupTransactionType(purchaseType)
}
},
[setIsSyrup, setSyrupTransactionType]
)
const handleInputSelect = useCallback(
(inputCurrency) => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onCurrencySelection(Field.INPUT, inputCurrency)
if (inputCurrency.symbol.toLowerCase() === 'syrup') {
checkForSyrup(inputCurrency.symbol.toLowerCase(), 'Selling')
}
},
[onCurrencySelection, setApprovalSubmitted, checkForSyrup]
)
const handleMaxInput = useCallback(() => {
if (maxAmountInput) {
onUserInput(Field.INPUT, maxAmountInput.toExact())
}
}, [maxAmountInput, onUserInput])
const handleOutputSelect = useCallback(
(outputCurrency) => {
onCurrencySelection(Field.OUTPUT, outputCurrency)
if (outputCurrency.symbol.toLowerCase() === 'syrup') {
checkForSyrup(outputCurrency.symbol.toLowerCase(), 'Buying')
}
},
[onCurrencySelection, checkForSyrup]
)
return (
<>
<TokenWarningModal
isOpen={urlLoadedTokens.length > 0 && !dismissTokenWarning}
tokens={urlLoadedTokens}
onConfirm={handleConfirmTokenWarning}
/>
<SyrupWarningModal
isOpen={isSyrup}
transactionType={syrupTransactionType}
onConfirm={handleConfirmSyrupWarning}
/>
<CardNav />
<AppBody>
<Wrapper id="swap-page">
<ConfirmSwapModal
isOpen={showConfirm}
trade={trade}
originalTrade={tradeToConfirm}
onAcceptChanges={handleAcceptChanges}
attemptingTxn={attemptingTxn}
txHash={txHash}
recipient={recipient}
allowedSlippage={allowedSlippage}
onConfirm={handleSwap}
swapErrorMessage={swapErrorMessage}
onDismiss={handleConfirmDismiss}
/>
<PageHeader title="Exchange" description="Trade tokens in an instant" />
<CardBody>
<AutoColumn gap="md">
<CurrencyInputPanel
label={
independentField === Field.OUTPUT && !showWrap && trade
? 'From (estimated)'
: TranslateString(76, 'From')
}
value={formattedAmounts[Field.INPUT]}
showMaxButton={!atMaxAmountInput}
currency={currencies[Field.INPUT]}
onUserInput={handleTypeInput}
onMax={handleMaxInput}
onCurrencySelect={handleInputSelect}
otherCurrency={currencies[Field.OUTPUT]}
id="swap-currency-input"
/>
<AutoColumn justify="space-between">
<AutoRow justify={isExpertMode ? 'space-between' : 'center'} style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable>
<IconButton
variant="tertiary"
onClick={() => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onSwitchTokens()
}}
style={{ borderRadius: '50%' }}
size="sm"
>
<ArrowDownIcon color="primary" width="24px" />
</IconButton>
</ArrowWrapper>
{recipient === null && !showWrap && isExpertMode ? (
<LinkStyledButton id="add-recipient-button" onClick={() => onChangeRecipient('')}>
+ Add a send (optional)
</LinkStyledButton>
) : null}
</AutoRow>
</AutoColumn>
<CurrencyInputPanel
value={formattedAmounts[Field.OUTPUT]}
onUserInput={handleTypeOutput}
label={
independentField === Field.INPUT && !showWrap && trade ? 'To (estimated)' : TranslateString(80, 'To')
}
showMaxButton={false}
currency={currencies[Field.OUTPUT]}
onCurrencySelect={handleOutputSelect}
otherCurrency={currencies[Field.INPUT]}
id="swap-currency-output"
/>
{recipient !== null && !showWrap ? (
<>
<AutoRow justify="space-between" style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable={false}>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ArrowWrapper>
<LinkStyledButton id="remove-recipient-button" onClick={() => onChangeRecipient(null)}>
- Remove send
</LinkStyledButton>
</AutoRow>
<AddressInputPanel id="recipient" value={recipient} onChange={onChangeRecipient} />
</>
) : null}
{showWrap ? null : (
<Card padding=".25rem .75rem 0 .75rem" borderRadius="20px">
<AutoColumn gap="4px">
{Boolean(trade) && (
<RowBetween align="center">
<Text fontSize="14px">Price</Text>
<TradePrice
price={trade?.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween>
)}
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
<RowBetween align="center">
<Text fontSize="14px">Slippage Tolerance</Text>
<Text fontSize="14px">{allowedSlippage / 100}%</Text>
</RowBetween>
)}
</AutoColumn>
</Card>
)}
</AutoColumn>
<BottomGrouping>
{!account ? (
<ConnectWalletButton fullWidth />
) : showWrap ? (
<Button disabled={Boolean(wrapInputError)} onClick={onWrap} fullWidth>
{wrapInputError ??
(wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
</Button>
) : noRoute && userHasSpecifiedInputOutput ? (
<GreyCard style={{ textAlign: 'center' }}>
<Main mb="4px">Insufficient liquidity for this trade.</Main>
</GreyCard>
) : showApproveFlow ? (
<RowBetween>
<Button
onClick={approveCallback}
disabled={approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
style={{ width: '48%' }}
variant={approval === ApprovalState.APPROVED ? 'success' : 'primary'}
>
{approval === ApprovalState.PENDING ? (
<AutoRow gap="6px" justify="center">
Approving <Loader stroke="white" />
</AutoRow>
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
'Approved'
) : (
`Approve ${currencies[Field.INPUT]?.symbol}`
)}
</Button>
<Button
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
})
}
}}
style={{ width: '48%' }}
id="swap-button"
disabled={
!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !isExpertMode)
}
variant={isValid && priceImpactSeverity > 2 ? 'danger' : 'primary'}
>
{priceImpactSeverity > 3 && !isExpertMode
? `Price Impact High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Button>
</RowBetween>
) : (
<Button
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
})
}
}}
id="swap-button"
disabled={!isValid || (priceImpactSeverity > 3 && !isExpertMode) || !!swapCallbackError}
variant={isValid && priceImpactSeverity > 2 && !swapCallbackError ? 'danger' : 'primary'}
fullWidth
>
{swapInputError ||
(priceImpactSeverity > 3 && !isExpertMode
? `Price Impact Too High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`)}
</Button>
)}
{showApproveFlow && <ProgressSteps steps={[approval === ApprovalState.APPROVED]} />}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
</BottomGrouping>
</CardBody>
</Wrapper>
</AppBody>
<AdvancedSwapDetailsDropdown trade={trade} />
</>
)
}
Example #14
Source File: TransactionSettings.tsx From vvs-ui with GNU General Public License v3.0 | 4 votes |
SlippageTabs = () => {
const [userSlippageTolerance, setUserSlippageTolerance] = useUserSlippageTolerance()
const [ttl, setTtl] = useUserTransactionTTL()
const [slippageInput, setSlippageInput] = useState('')
const [deadlineInput, setDeadlineInput] = useState('')
const { t } = useTranslation()
const slippageInputIsValid =
slippageInput === '' || (userSlippageTolerance / 100).toFixed(2) === Number.parseFloat(slippageInput).toFixed(2)
const deadlineInputIsValid = deadlineInput === '' || (ttl / 60).toString() === deadlineInput
let slippageError: SlippageError | undefined
if (slippageInput !== '' && !slippageInputIsValid) {
slippageError = SlippageError.InvalidInput
} else if (slippageInputIsValid && userSlippageTolerance < 50) {
slippageError = SlippageError.RiskyLow
} else if (slippageInputIsValid && userSlippageTolerance > 500) {
slippageError = SlippageError.RiskyHigh
} else {
slippageError = undefined
}
let deadlineError: DeadlineError | undefined
if (deadlineInput !== '' && !deadlineInputIsValid) {
deadlineError = DeadlineError.InvalidInput
} else {
deadlineError = undefined
}
const parseCustomSlippage = (value: string) => {
setSlippageInput(value)
try {
const valueAsIntFromRoundedFloat = Number.parseInt((Number.parseFloat(value) * 100).toString())
if (!Number.isNaN(valueAsIntFromRoundedFloat) && valueAsIntFromRoundedFloat < 5000) {
setUserSlippageTolerance(valueAsIntFromRoundedFloat)
}
} catch (error) {
console.error(error)
}
}
const parseCustomDeadline = (value: string) => {
setDeadlineInput(value)
try {
const valueAsInt: number = Number.parseInt(value) * 60
if (!Number.isNaN(valueAsInt) && valueAsInt > 0) {
setTtl(valueAsInt)
}
} catch (error) {
console.error(error)
}
}
return (
<Flex flexDirection="column">
<Flex flexDirection="column" mb="24px">
<Flex mb="12px">
<Text color="darkBlue" fontSize="14px">
{t('Slippage Tolerance')}
</Text>
<QuestionHelper
text={t(
'Slippage settings adjusts the acceptable % difference between quoted and executed prices. High tolerance can help transactions succeed, but you may get a less favourable price. Use with caution!',
)}
placement="top-start"
ml="4px"
mt="4px"
/>
</Flex>
<Flex flexWrap="wrap">
<Button
mt="4px"
mr="4px"
scale="sm"
style={ButtonStyles}
onClick={() => {
setSlippageInput('')
setUserSlippageTolerance(10)
}}
variant={userSlippageTolerance === 10 ? 'primary' : 'light'}
>
0.1%
</Button>
<Button
mt="4px"
mr="4px"
scale="sm"
style={ButtonStyles}
onClick={() => {
setSlippageInput('')
setUserSlippageTolerance(50)
}}
variant={userSlippageTolerance === 50 ? 'primary' : 'light'}
>
0.5%
</Button>
<Button
mr="4px"
mt="4px"
scale="sm"
style={ButtonStyles}
onClick={() => {
setSlippageInput('')
setUserSlippageTolerance(100)
}}
variant={userSlippageTolerance === 100 ? 'primary' : 'light'}
>
1.0%
</Button>
<Flex alignItems="center">
<Box width="76px" mt="4px">
<Input
scale="sm"
placeholder={(userSlippageTolerance / 100).toFixed(2)}
value={slippageInput}
style={InputStyles}
onBlur={() => {
parseCustomSlippage((userSlippageTolerance / 100).toFixed(2))
}}
onChange={(e) => parseCustomSlippage(e.target.value)}
isWarning={!slippageInputIsValid}
isSuccess={![10, 50, 100].includes(userSlippageTolerance)}
/>
</Box>
<Text color="primary" bold ml="2px">
%
</Text>
</Flex>
</Flex>
{!!slippageError && (
<Text fontSize="14px" color={slippageError === SlippageError.InvalidInput ? 'red' : '#F3841E'} mt="8px">
{slippageError === SlippageError.InvalidInput
? t('Enter a valid slippage percentage')
: slippageError === SlippageError.RiskyLow
? t('Your transaction may fail')
: t('Your transaction may be frontrun')}
</Text>
)}
</Flex>
<Flex justifyContent="space-between" alignItems="center" mb="24px">
<Flex alignItems="center">
<Text color="darkBlue" fontSize="14px">
{t('Tx deadline (mins)')}
</Text>
<QuestionHelper
text={t('Your transaction will revert if it is left confirming for longer than this time.')}
placement="top-start"
ml="4px"
mt="4px"
/>
</Flex>
<Flex>
<Box width="58px" mt="4px">
<Input
scale="sm"
color={deadlineError ? 'red' : undefined}
style={InputStyles}
onBlur={() => {
parseCustomDeadline((ttl / 60).toString())
}}
placeholder={(ttl / 60).toString()}
value={deadlineInput}
onChange={(e) => parseCustomDeadline(e.target.value)}
/>
</Box>
</Flex>
</Flex>
</Flex>
)
}
Example #15
Source File: SlippageToleranceSetting.tsx From pancakeswap-testnet with GNU General Public License v3.0 | 4 votes |
SlippageToleranceSettings = ({ translateString }: SlippageToleranceSettingsModalProps) => {
const TranslateString = translateString
const [userSlippageTolerance, setUserslippageTolerance] = useUserSlippageTolerance()
const [value, setValue] = useState(userSlippageTolerance / 100)
const [error, setError] = useState<string | null>(null)
const handleChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
const { value: inputValue } = evt.target
setValue(parseFloat(inputValue))
}
// Updates local storage if value is valid
useEffect(() => {
try {
const rawValue = value * 100
if (!Number.isNaN(rawValue) && rawValue > 0 && rawValue < MAX_SLIPPAGE) {
setUserslippageTolerance(rawValue)
setError(null)
} else {
setError(TranslateString(1144, 'Enter a valid slippage percentage'))
}
} catch {
setError(TranslateString(1144, 'Enter a valid slippage percentage'))
}
}, [value, setError, setUserslippageTolerance, TranslateString])
// Notify user if slippage is risky
useEffect(() => {
if (userSlippageTolerance < RISKY_SLIPPAGE_LOW) {
setError(TranslateString(1146, 'Your transaction may fail'))
} else if (userSlippageTolerance > RISKY_SLIPPAGE_HIGH) {
setError(TranslateString(1148, 'Your transaction may be frontrun'))
}
}, [userSlippageTolerance, setError, TranslateString])
return (
<StyledSlippageToleranceSettings>
<Label>
<Text style={{ fontWeight: 600 }}>{TranslateString(88, 'Slippage tolerance')}</Text>
<QuestionHelper
text={TranslateString(
186,
'Your transaction will revert if the price changes unfavorably by more than this percentage.'
)}
/>
</Label>
<Options>
<Flex mb={['8px', 0]} mr={[0, '8px']}>
{predefinedValues.map(({ label, value: predefinedValue }) => {
const handleClick = () => setValue(predefinedValue)
return (
<Option key={predefinedValue}>
<Button variant={value === predefinedValue ? 'primary' : 'tertiary'} onClick={handleClick}>
{label}
</Button>
</Option>
)
})}
</Flex>
<Flex alignItems="center">
<Option>
<Input
type="number"
scale="lg"
step={0.1}
min={0.1}
placeholder="5%"
value={value}
onChange={handleChange}
isWarning={error !== null}
/>
</Option>
<Option>
<Text fontSize="18px">%</Text>
</Option>
</Flex>
</Options>
{error && (
<Text mt="8px" color="failure">
{error}
</Text>
)}
</StyledSlippageToleranceSettings>
)
}
Example #16
Source File: index.tsx From pancakeswap-testnet with GNU General Public License v3.0 | 4 votes |
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB },
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React()
const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB)
const TranslateString = useI18n()
const oneCurrencyIsWBNB = Boolean(
chainId &&
((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
(currencyB && currencyEquals(currencyB, WETH[chainId])))
)
const expertMode = useIsExpertMode()
// mint state
const { independentField, typedValue, otherTypedValue } = useMintState()
const {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
// txn values
const [deadline] = useUserDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: noLiquidity ? otherTypedValue : parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
// get the max amounts user can add
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmountSpend(currencyBalances[field]),
}
},
{}
)
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0'),
}
},
{}
)
// check whether the user has approved the router on the tokens
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS)
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS)
const addTransaction = useTransactionAdder()
async function onAdd() {
if (!chainId || !library || !account) return
const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) {
return
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0],
}
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate
let method: (...args: any) => Promise<TransactionResponse>
let args: Array<string | string[] | number>
let value: BigNumber | null
if (currencyA === ETHER || currencyB === ETHER) {
const tokenBIsBNB = currencyB === ETHER
estimate = router.estimateGas.addLiquidityETH
method = router.addLiquidityETH
args = [
wrappedCurrency(tokenBIsBNB ? currencyA : currencyB, chainId)?.address ?? '', // token
(tokenBIsBNB ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[tokenBIsBNB ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsBNB ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account,
deadlineFromNow,
]
value = BigNumber.from((tokenBIsBNB ? parsedAmountB : parsedAmountA).raw.toString())
} else {
estimate = router.estimateGas.addLiquidity
method = router.addLiquidity
args = [
wrappedCurrency(currencyA, chainId)?.address ?? '',
wrappedCurrency(currencyB, chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
"0",
"0",
account,
deadlineFromNow,
]
value = null
}
setAttemptingTxn(true)
// const aa = await estimate(...args, value ? { value } : {})
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then((response) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Add ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencies[Field.CURRENCY_B]?.symbol}`,
})
setTxHash(response.hash)
})
)
.catch((e) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (e?.code !== 4001) {
console.error(e)
}
})
}
const modalHeader = () => {
return noLiquidity ? (
<AutoColumn gap="20px">
<LightCard mt="20px" borderRadius="20px">
<RowFlat>
<UIKitText fontSize="48px" mr="8px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol}`}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
</LightCard>
</AutoColumn>
) : (
<AutoColumn gap="20px">
<RowFlat style={{ marginTop: '20px' }}>
<UIKitText fontSize="48px" mr="8px">
{liquidityMinted?.toSignificant(6)}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
<Row>
<UIKitText fontSize="24px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol} Pool Tokens`}
</UIKitText>
</Row>
<UIKitText small textAlign="left" padding="8px 0 0 0 " style={{ fontStyle: 'italic' }}>
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</UIKitText>
</AutoColumn>
)
}
const modalBottom = () => {
return (
<ConfirmAddModalBottom
price={price}
currencies={currencies}
parsedAmounts={parsedAmounts}
noLiquidity={noLiquidity}
onAdd={onAdd}
poolTokenPercentage={poolTokenPercentage}
/>
)
}
const pendingText = `Supplying ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencies[Field.CURRENCY_B]?.symbol}`
const handleCurrencyASelect = useCallback(
(currA: Currency) => {
const newCurrencyIdA = currencyId(currA)
if (newCurrencyIdA === currencyIdB) {
history.push(`/add/${currencyIdB}/${currencyIdA}`)
} else {
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`)
}
},
[currencyIdB, history, currencyIdA]
)
const handleCurrencyBSelect = useCallback(
(currB: Currency) => {
const newCurrencyIdB = currencyId(currB)
if (currencyIdA === newCurrencyIdB) {
if (currencyIdB) {
history.push(`/add/${currencyIdB}/${newCurrencyIdB}`)
} else {
history.push(`/add/${newCurrencyIdB}`)
}
} else {
history.push(`/add/${currencyIdA || 'BNB'}/${newCurrencyIdB}`)
}
},
[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 (
<>
<CardNav activeIndex={1} />
<AppBody>
<AddRemoveTabs adding />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash}
content={() => (
<ConfirmationModalContent
title={
noLiquidity
? TranslateString(1154, 'You are creating a pool')
: TranslateString(1156, 'You will receive')
}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<CardBody>
<AutoColumn gap="20px">
{noLiquidity && (
<ColumnCenter>
<Pane>
<AutoColumn gap="12px">
<UIKitText>{TranslateString(1158, 'You are the first liquidity provider.')}</UIKitText>
<UIKitText>
{TranslateString(1160, 'The ratio of tokens you add will set the price of this pool.')}
</UIKitText>
<UIKitText>
{TranslateString(1162, 'Once you are happy with the rate click supply to review.')}
</UIKitText>
</AutoColumn>
</Pane>
</ColumnCenter>
)}
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
onMax={() => {
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}}
onCurrencySelect={handleCurrencyASelect}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A]}
id="add-liquidity-input-tokena"
showCommonBases={false}
/>
<ColumnCenter>
<AddIcon color="textSubtle" />
</ColumnCenter>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onFieldBInput}
onCurrencySelect={handleCurrencyBSelect}
onMax={() => {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
currency={currencies[Field.CURRENCY_B]}
id="add-liquidity-input-tokenb"
showCommonBases={false}
/>
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<div>
<UIKitText
style={{ textTransform: 'uppercase', fontWeight: 600 }}
color="textSubtle"
fontSize="12px"
mb="2px"
>
{noLiquidity
? TranslateString(1164, 'Initial prices and pool share')
: TranslateString(1166, 'Prices and pool share')}
</UIKitText>
<Pane>
<PoolPriceBar
currencies={currencies}
poolTokenPercentage={poolTokenPercentage}
noLiquidity={noLiquidity}
price={price}
/>
</Pane>
</div>
)}
{!account ? (
<ConnectWalletButton width="100%" />
) : (
<AutoColumn gap="md">
{(approvalA === ApprovalState.NOT_APPROVED ||
approvalA === ApprovalState.PENDING ||
approvalB === ApprovalState.NOT_APPROVED ||
approvalB === ApprovalState.PENDING) &&
isValid && (
<RowBetween>
{approvalA !== ApprovalState.APPROVED && (
<Button
onClick={approveACallback}
disabled={approvalA === ApprovalState.PENDING}
style={{ width: approvalB !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalA === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_A]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_A]?.symbol}`
)}
</Button>
)}
{approvalB !== ApprovalState.APPROVED && (
<Button
onClick={approveBCallback}
disabled={approvalB === ApprovalState.PENDING}
style={{ width: approvalA !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalB === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_B]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_B]?.symbol}`
)}
</Button>
)}
</RowBetween>
)}
<Button
onClick={() => {
if (expertMode) {
onAdd()
} else {
setShowConfirm(true)
}
}}
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
width="100%"
>
{error ?? 'Supply'}
</Button>
</AutoColumn>
)}
</AutoColumn>
</CardBody>
</Wrapper>
</AppBody>
{pair && !noLiquidity && pairState !== PairState.INVALID ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWBNB} pair={pair} />
</AutoColumn>
) : null}
</>
)
}
Example #17
Source File: index.tsx From pancakeswap-testnet with GNU General Public License v3.0 | 4 votes |
Swap = () => {
const loadedUrlParams = useDefaultsFromURLSearch()
const TranslateString = useI18n()
// token warning stuff
const [loadedInputCurrency, loadedOutputCurrency] = [
useCurrency(loadedUrlParams?.inputCurrencyId),
useCurrency(loadedUrlParams?.outputCurrencyId),
]
const [dismissTokenWarning, setDismissTokenWarning] = useState<boolean>(false)
const [isSyrup, setIsSyrup] = useState<boolean>(false)
const [syrupTransactionType, setSyrupTransactionType] = useState<string>('')
const urlLoadedTokens: Token[] = useMemo(
() => [loadedInputCurrency, loadedOutputCurrency]?.filter((c): c is Token => c instanceof Token) ?? [],
[loadedInputCurrency, loadedOutputCurrency]
)
const handleConfirmTokenWarning = useCallback(() => {
setDismissTokenWarning(true)
}, [])
const handleConfirmSyrupWarning = useCallback(() => {
setIsSyrup(false)
setSyrupTransactionType('')
}, [])
const { account } = useActiveWeb3React()
const theme = useContext(ThemeContext)
const [isExpertMode] = useExpertModeManager()
// get custom setting values for user
const [deadline] = useUserDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
// swap state
const { independentField, typedValue, recipient } = useSwapState()
const { v2Trade, currencyBalances, parsedAmount, currencies, inputError: swapInputError } = useDerivedSwapInfo()
const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback(
currencies[Field.INPUT],
currencies[Field.OUTPUT],
typedValue
)
const showWrap: boolean = wrapType !== WrapType.NOT_APPLICABLE
const trade = showWrap ? undefined : v2Trade
const parsedAmounts = showWrap
? {
[Field.INPUT]: parsedAmount,
[Field.OUTPUT]: parsedAmount,
}
: {
[Field.INPUT]: independentField === Field.INPUT ? parsedAmount : trade?.inputAmount,
[Field.OUTPUT]: independentField === Field.OUTPUT ? parsedAmount : trade?.outputAmount,
}
const { onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient } = useSwapActionHandlers()
const isValid = !swapInputError
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
const handleTypeInput = useCallback(
(value: string) => {
onUserInput(Field.INPUT, value)
},
[onUserInput]
)
const handleTypeOutput = useCallback(
(value: string) => {
onUserInput(Field.OUTPUT, value)
},
[onUserInput]
)
// modal and loading
const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{
showConfirm: boolean
tradeToConfirm: Trade | undefined
attemptingTxn: boolean
swapErrorMessage: string | undefined
txHash: string | undefined
}>({
showConfirm: false,
tradeToConfirm: undefined,
attemptingTxn: false,
swapErrorMessage: undefined,
txHash: undefined,
})
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: showWrap
? parsedAmounts[independentField]?.toExact() ?? ''
: parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
const route = trade?.route
const userHasSpecifiedInputOutput = Boolean(
currencies[Field.INPUT] && currencies[Field.OUTPUT] && parsedAmounts[independentField]?.greaterThan(JSBI.BigInt(0))
)
const noRoute = !route
// check whether the user has approved the router on the input token
const [approval, approveCallback] = useApproveCallbackFromTrade(trade, allowedSlippage)
// check if user has gone through approval process, used to show two step buttons, reset on token change
const [approvalSubmitted, setApprovalSubmitted] = useState<boolean>(false)
// mark when a user has submitted an approval, reset onTokenSelection for input field
useEffect(() => {
if (approval === ApprovalState.PENDING) {
setApprovalSubmitted(true)
}
}, [approval, approvalSubmitted])
const maxAmountInput: CurrencyAmount | undefined = maxAmountSpend(currencyBalances[Field.INPUT])
const atMaxAmountInput = Boolean(maxAmountInput && parsedAmounts[Field.INPUT]?.equalTo(maxAmountInput))
// the callback to execute the swap
const { callback: swapCallback, error: swapCallbackError } = useSwapCallback(
trade,
allowedSlippage,
deadline,
recipient
)
const { priceImpactWithoutFee } = computeTradePriceBreakdown(trade)
const handleSwap = useCallback(() => {
if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) {
return
}
if (!swapCallback) {
return
}
setSwapState((prevState) => ({ ...prevState, attemptingTxn: true, swapErrorMessage: undefined, txHash: undefined }))
swapCallback()
.then((hash) => {
setSwapState((prevState) => ({
...prevState,
attemptingTxn: false,
swapErrorMessage: undefined,
txHash: hash,
}))
})
.catch((error) => {
setSwapState((prevState) => ({
...prevState,
attemptingTxn: false,
swapErrorMessage: error.message,
txHash: undefined,
}))
})
}, [priceImpactWithoutFee, swapCallback, setSwapState])
// errors
const [showInverted, setShowInverted] = useState<boolean>(false)
// warnings on slippage
const priceImpactSeverity = warningSeverity(priceImpactWithoutFee)
// 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 =
!swapInputError &&
(approval === ApprovalState.NOT_APPROVED ||
approval === ApprovalState.PENDING ||
(approvalSubmitted && approval === ApprovalState.APPROVED)) &&
!(priceImpactSeverity > 3 && !isExpertMode)
const handleConfirmDismiss = useCallback(() => {
setSwapState((prevState) => ({ ...prevState, showConfirm: false }))
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.INPUT, '')
}
}, [onUserInput, txHash, setSwapState])
const handleAcceptChanges = useCallback(() => {
setSwapState((prevState) => ({ ...prevState, tradeToConfirm: trade }))
}, [trade])
// This will check to see if the user has selected Syrup to either buy or sell.
// If so, they will be alerted with a warning message.
const checkForSyrup = useCallback(
(selected: string, purchaseType: string) => {
if (selected === 'syrup') {
setIsSyrup(true)
setSyrupTransactionType(purchaseType)
}
},
[setIsSyrup, setSyrupTransactionType]
)
const handleInputSelect = useCallback(
(inputCurrency) => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onCurrencySelection(Field.INPUT, inputCurrency)
if (inputCurrency.symbol.toLowerCase() === 'syrup') {
checkForSyrup(inputCurrency.symbol.toLowerCase(), 'Selling')
}
},
[onCurrencySelection, setApprovalSubmitted, checkForSyrup]
)
const handleMaxInput = useCallback(() => {
if (maxAmountInput) {
onUserInput(Field.INPUT, maxAmountInput.toExact())
}
}, [maxAmountInput, onUserInput])
const handleOutputSelect = useCallback(
(outputCurrency) => {
onCurrencySelection(Field.OUTPUT, outputCurrency)
if (outputCurrency.symbol.toLowerCase() === 'syrup') {
checkForSyrup(outputCurrency.symbol.toLowerCase(), 'Buying')
}
},
[onCurrencySelection, checkForSyrup]
)
return (
<>
<TokenWarningModal
isOpen={urlLoadedTokens.length > 0 && !dismissTokenWarning}
tokens={urlLoadedTokens}
onConfirm={handleConfirmTokenWarning}
/>
<SyrupWarningModal
isOpen={isSyrup}
transactionType={syrupTransactionType}
onConfirm={handleConfirmSyrupWarning}
/>
<CardNav />
<AppBody>
<Wrapper id="swap-page">
<ConfirmSwapModal
isOpen={showConfirm}
trade={trade}
originalTrade={tradeToConfirm}
onAcceptChanges={handleAcceptChanges}
attemptingTxn={attemptingTxn}
txHash={txHash}
recipient={recipient}
allowedSlippage={allowedSlippage}
onConfirm={handleSwap}
swapErrorMessage={swapErrorMessage}
onDismiss={handleConfirmDismiss}
/>
<PageHeader
title={TranslateString(8, 'Exchange')}
description={TranslateString(1192, 'Trade tokens in an instant')}
/>
<CardBody>
<AutoColumn gap="md">
<CurrencyInputPanel
label={
independentField === Field.OUTPUT && !showWrap && trade
? TranslateString(194, 'From (estimated)')
: TranslateString(76, 'From')
}
value={formattedAmounts[Field.INPUT]}
showMaxButton={!atMaxAmountInput}
currency={currencies[Field.INPUT]}
onUserInput={handleTypeInput}
onMax={handleMaxInput}
onCurrencySelect={handleInputSelect}
otherCurrency={currencies[Field.OUTPUT]}
id="swap-currency-input"
/>
<AutoColumn justify="space-between">
<AutoRow justify={isExpertMode ? 'space-between' : 'center'} style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable>
<IconButton
variant="tertiary"
onClick={() => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onSwitchTokens()
}}
style={{ borderRadius: '50%' }}
scale="sm"
>
<ArrowDownIcon color="primary" width="24px" />
</IconButton>
</ArrowWrapper>
{recipient === null && !showWrap && isExpertMode ? (
<LinkStyledButton id="add-recipient-button" onClick={() => onChangeRecipient('')}>
+ Add a send (optional)
</LinkStyledButton>
) : null}
</AutoRow>
</AutoColumn>
<CurrencyInputPanel
value={formattedAmounts[Field.OUTPUT]}
onUserInput={handleTypeOutput}
label={
independentField === Field.INPUT && !showWrap && trade
? TranslateString(196, 'To (estimated)')
: TranslateString(80, 'To')
}
showMaxButton={false}
currency={currencies[Field.OUTPUT]}
onCurrencySelect={handleOutputSelect}
otherCurrency={currencies[Field.INPUT]}
id="swap-currency-output"
/>
{recipient !== null && !showWrap ? (
<>
<AutoRow justify="space-between" style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable={false}>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ArrowWrapper>
<LinkStyledButton id="remove-recipient-button" onClick={() => onChangeRecipient(null)}>
- Remove send
</LinkStyledButton>
</AutoRow>
<AddressInputPanel id="recipient" value={recipient} onChange={onChangeRecipient} />
</>
) : null}
{showWrap ? null : (
<Card padding=".25rem .75rem 0 .75rem" borderRadius="20px">
<AutoColumn gap="4px">
{Boolean(trade) && (
<RowBetween align="center">
<Text fontSize="14px">{TranslateString(1182, 'Price')}</Text>
<TradePrice
price={trade?.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween>
)}
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
<RowBetween align="center">
<Text fontSize="14px">{TranslateString(88, 'Slippage Tolerance')}</Text>
<Text fontSize="14px">{allowedSlippage / 100}%</Text>
</RowBetween>
)}
</AutoColumn>
</Card>
)}
</AutoColumn>
<BottomGrouping>
{!account ? (
<ConnectWalletButton width="100%" />
) : showWrap ? (
<Button disabled={Boolean(wrapInputError)} onClick={onWrap} width="100%">
{wrapInputError ??
(wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
</Button>
) : noRoute && userHasSpecifiedInputOutput ? (
<GreyCard style={{ textAlign: 'center' }}>
<Text mb="4px">{TranslateString(1194, 'Insufficient liquidity for this trade.')}</Text>
</GreyCard>
) : showApproveFlow ? (
<RowBetween>
<Button
onClick={approveCallback}
disabled={approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
style={{ width: '48%' }}
variant={approval === ApprovalState.APPROVED ? 'success' : 'primary'}
>
{approval === ApprovalState.PENDING ? (
<AutoRow gap="6px" justify="center">
Approving <Loader stroke="white" />
</AutoRow>
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
'Approved'
) : (
`Approve ${currencies[Field.INPUT]?.symbol}`
)}
</Button>
<Button
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
})
}
}}
style={{ width: '48%' }}
id="swap-button"
disabled={
!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !isExpertMode)
}
variant={isValid && priceImpactSeverity > 2 ? 'danger' : 'primary'}
>
{priceImpactSeverity > 3 && !isExpertMode
? `Price Impact High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Button>
</RowBetween>
) : (
<Button
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
})
}
}}
id="swap-button"
disabled={!isValid || (priceImpactSeverity > 3 && !isExpertMode) || !!swapCallbackError}
variant={isValid && priceImpactSeverity > 2 && !swapCallbackError ? 'danger' : 'primary'}
width="100%"
>
{swapInputError ||
(priceImpactSeverity > 3 && !isExpertMode
? `Price Impact Too High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`)}
</Button>
)}
{showApproveFlow && <ProgressSteps steps={[approval === ApprovalState.APPROVED]} />}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
</BottomGrouping>
</CardBody>
</Wrapper>
</AppBody>
<AdvancedSwapDetailsDropdown trade={trade} />
</>
)
}
Example #18
Source File: index.tsx From pancake-swap-exchange-testnet with GNU General Public License v3.0 | 4 votes |
Swap = () => {
const loadedUrlParams = useDefaultsFromURLSearch()
const TranslateString = useI18n()
// token warning stuff
const [loadedInputCurrency, loadedOutputCurrency] = [
useCurrency(loadedUrlParams?.inputCurrencyId),
useCurrency(loadedUrlParams?.outputCurrencyId),
]
const [dismissTokenWarning, setDismissTokenWarning] = useState<boolean>(false)
const [transactionWarning, setTransactionWarning] = useState<{
selectedToken: string | null
purchaseType: string | null
}>({
selectedToken: null,
purchaseType: null,
})
const urlLoadedTokens: Token[] = useMemo(
() => [loadedInputCurrency, loadedOutputCurrency]?.filter((c): c is Token => c instanceof Token) ?? [],
[loadedInputCurrency, loadedOutputCurrency]
)
const handleConfirmTokenWarning = useCallback(() => {
setDismissTokenWarning(true)
}, [])
const handleConfirmWarning = () => {
setTransactionWarning({
selectedToken: null,
purchaseType: null,
})
}
const { account } = useActiveWeb3React()
const theme = useContext(ThemeContext)
const [isExpertMode] = useExpertModeManager()
// get custom setting values for user
const [deadline] = useUserDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
// swap state
const { independentField, typedValue, recipient } = useSwapState()
const { v2Trade, currencyBalances, parsedAmount, currencies, inputError: swapInputError } = useDerivedSwapInfo()
const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback(
currencies[Field.INPUT],
currencies[Field.OUTPUT],
typedValue
)
const showWrap: boolean = wrapType !== WrapType.NOT_APPLICABLE
const trade = showWrap ? undefined : v2Trade
const parsedAmounts = showWrap
? {
[Field.INPUT]: parsedAmount,
[Field.OUTPUT]: parsedAmount,
}
: {
[Field.INPUT]: independentField === Field.INPUT ? parsedAmount : trade?.inputAmount,
[Field.OUTPUT]: independentField === Field.OUTPUT ? parsedAmount : trade?.outputAmount,
}
const { onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient } = useSwapActionHandlers()
const isValid = !swapInputError
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
const handleTypeInput = useCallback(
(value: string) => {
onUserInput(Field.INPUT, value)
},
[onUserInput]
)
const handleTypeOutput = useCallback(
(value: string) => {
onUserInput(Field.OUTPUT, value)
},
[onUserInput]
)
// modal and loading
const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{
showConfirm: boolean
tradeToConfirm: Trade | undefined
attemptingTxn: boolean
swapErrorMessage: string | undefined
txHash: string | undefined
}>({
showConfirm: false,
tradeToConfirm: undefined,
attemptingTxn: false,
swapErrorMessage: undefined,
txHash: undefined,
})
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: showWrap
? parsedAmounts[independentField]?.toExact() ?? ''
: parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
const route = trade?.route
const userHasSpecifiedInputOutput = Boolean(
currencies[Field.INPUT] && currencies[Field.OUTPUT] && parsedAmounts[independentField]?.greaterThan(JSBI.BigInt(0))
)
const noRoute = !route
// check whether the user has approved the router on the input token
const [approval, approveCallback] = useApproveCallbackFromTrade(trade, allowedSlippage)
// check if user has gone through approval process, used to show two step buttons, reset on token change
const [approvalSubmitted, setApprovalSubmitted] = useState<boolean>(false)
// mark when a user has submitted an approval, reset onTokenSelection for input field
useEffect(() => {
if (approval === ApprovalState.PENDING) {
setApprovalSubmitted(true)
}
}, [approval, approvalSubmitted])
const maxAmountInput: CurrencyAmount | undefined = maxAmountSpend(currencyBalances[Field.INPUT])
const atMaxAmountInput = Boolean(maxAmountInput && parsedAmounts[Field.INPUT]?.equalTo(maxAmountInput))
// the callback to execute the swap
const { callback: swapCallback, error: swapCallbackError } = useSwapCallback(
trade,
allowedSlippage,
deadline,
recipient
)
const { priceImpactWithoutFee } = computeTradePriceBreakdown(trade)
const handleSwap = useCallback(() => {
if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) {
return
}
if (!swapCallback) {
return
}
setSwapState((prevState) => ({ ...prevState, attemptingTxn: true, swapErrorMessage: undefined, txHash: undefined }))
swapCallback()
.then((hash) => {
setSwapState((prevState) => ({
...prevState,
attemptingTxn: false,
swapErrorMessage: undefined,
txHash: hash,
}))
})
.catch((error) => {
setSwapState((prevState) => ({
...prevState,
attemptingTxn: false,
swapErrorMessage: error.message,
txHash: undefined,
}))
})
}, [priceImpactWithoutFee, swapCallback, setSwapState])
// errors
const [showInverted, setShowInverted] = useState<boolean>(false)
// warnings on slippage
const priceImpactSeverity = warningSeverity(priceImpactWithoutFee)
// 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 =
!swapInputError &&
(approval === ApprovalState.NOT_APPROVED ||
approval === ApprovalState.PENDING ||
(approvalSubmitted && approval === ApprovalState.APPROVED)) &&
!(priceImpactSeverity > 3 && !isExpertMode)
const handleConfirmDismiss = useCallback(() => {
setSwapState((prevState) => ({ ...prevState, showConfirm: false }))
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.INPUT, '')
}
}, [onUserInput, txHash, setSwapState])
const handleAcceptChanges = useCallback(() => {
setSwapState((prevState) => ({ ...prevState, tradeToConfirm: trade }))
}, [trade])
// This will check to see if the user has selected Syrup or SafeMoon to either buy or sell.
// If so, they will be alerted with a warning message.
const checkForWarning = useCallback(
(selected: string, purchaseType: string) => {
if (['SYRUP', 'SAFEMOON'].includes(selected)) {
setTransactionWarning({
selectedToken: selected,
purchaseType,
})
}
},
[setTransactionWarning]
)
const handleInputSelect = useCallback(
(inputCurrency) => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onCurrencySelection(Field.INPUT, inputCurrency)
if (inputCurrency.symbol === 'SYRUP') {
checkForWarning(inputCurrency.symbol, 'Selling')
}
if (inputCurrency.symbol === 'SAFEMOON') {
checkForWarning(inputCurrency.symbol, 'Selling')
}
},
[onCurrencySelection, setApprovalSubmitted, checkForWarning]
)
const handleMaxInput = useCallback(() => {
if (maxAmountInput) {
onUserInput(Field.INPUT, maxAmountInput.toExact())
}
}, [maxAmountInput, onUserInput])
const handleOutputSelect = useCallback(
(outputCurrency) => {
onCurrencySelection(Field.OUTPUT, outputCurrency)
if (outputCurrency.symbol === 'SYRUP') {
checkForWarning(outputCurrency.symbol, 'Buying')
}
if (outputCurrency.symbol === 'SAFEMOON') {
checkForWarning(outputCurrency.symbol, 'Buying')
}
},
[onCurrencySelection, checkForWarning]
)
return (
<Container>
<TokenWarningModal
isOpen={urlLoadedTokens.length > 0 && !dismissTokenWarning}
tokens={urlLoadedTokens}
onConfirm={handleConfirmTokenWarning}
/>
<SyrupWarningModal
isOpen={transactionWarning.selectedToken === 'SYRUP'}
transactionType={transactionWarning.purchaseType}
onConfirm={handleConfirmWarning}
/>
<SafeMoonWarningModal isOpen={transactionWarning.selectedToken === 'SAFEMOON'} onConfirm={handleConfirmWarning} />
<CardNav />
<AppBody>
<Wrapper id="swap-page">
<ConfirmSwapModal
isOpen={showConfirm}
trade={trade}
originalTrade={tradeToConfirm}
onAcceptChanges={handleAcceptChanges}
attemptingTxn={attemptingTxn}
txHash={txHash}
recipient={recipient}
allowedSlippage={allowedSlippage}
onConfirm={handleSwap}
swapErrorMessage={swapErrorMessage}
onDismiss={handleConfirmDismiss}
/>
<PageHeader
title={TranslateString(8, 'Exchange')}
description={TranslateString(1192, 'Trade tokens in an instant')}
/>
<CardBody>
<AutoColumn gap="md">
<CurrencyInputPanel
label={
independentField === Field.OUTPUT && !showWrap && trade
? TranslateString(194, 'From (estimated)')
: TranslateString(76, 'From')
}
value={formattedAmounts[Field.INPUT]}
showMaxButton={!atMaxAmountInput}
currency={currencies[Field.INPUT]}
onUserInput={handleTypeInput}
onMax={handleMaxInput}
onCurrencySelect={handleInputSelect}
otherCurrency={currencies[Field.OUTPUT]}
id="swap-currency-input"
/>
<AutoColumn justify="space-between">
<AutoRow justify={isExpertMode ? 'space-between' : 'center'} style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable>
<IconButton
variant="tertiary"
onClick={() => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onSwitchTokens()
}}
style={{ borderRadius: '50%' }}
scale="sm"
>
<ArrowDownIcon color="primary" width="24px" />
</IconButton>
</ArrowWrapper>
{recipient === null && !showWrap && isExpertMode ? (
<LinkStyledButton id="add-recipient-button" onClick={() => onChangeRecipient('')}>
+ Add a send (optional)
</LinkStyledButton>
) : null}
</AutoRow>
</AutoColumn>
<CurrencyInputPanel
value={formattedAmounts[Field.OUTPUT]}
onUserInput={handleTypeOutput}
label={
independentField === Field.INPUT && !showWrap && trade
? TranslateString(196, 'To (estimated)')
: TranslateString(80, 'To')
}
showMaxButton={false}
currency={currencies[Field.OUTPUT]}
onCurrencySelect={handleOutputSelect}
otherCurrency={currencies[Field.INPUT]}
id="swap-currency-output"
/>
{recipient !== null && !showWrap ? (
<>
<AutoRow justify="space-between" style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable={false}>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ArrowWrapper>
<LinkStyledButton id="remove-recipient-button" onClick={() => onChangeRecipient(null)}>
- Remove send
</LinkStyledButton>
</AutoRow>
<AddressInputPanel id="recipient" value={recipient} onChange={onChangeRecipient} />
</>
) : null}
{showWrap ? null : (
<Card padding=".25rem .75rem 0 .75rem" borderRadius="20px">
<AutoColumn gap="4px">
{Boolean(trade) && (
<RowBetween align="center">
<Text fontSize="14px">{TranslateString(1182, 'Price')}</Text>
<TradePrice
price={trade?.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween>
)}
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
<RowBetween align="center">
<Text fontSize="14px">{TranslateString(88, 'Slippage Tolerance')}</Text>
<Text fontSize="14px">{allowedSlippage / 100}%</Text>
</RowBetween>
)}
</AutoColumn>
</Card>
)}
</AutoColumn>
<BottomGrouping>
{!account ? (
<ConnectWalletButton width="100%" />
) : showWrap ? (
<Button disabled={Boolean(wrapInputError)} onClick={onWrap} width="100%">
{wrapInputError ??
(wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
</Button>
) : noRoute && userHasSpecifiedInputOutput ? (
<GreyCard style={{ textAlign: 'center' }}>
<Text mb="4px">{TranslateString(1194, 'Insufficient liquidity for this trade.')}</Text>
</GreyCard>
) : showApproveFlow ? (
<RowBetween>
<Button
onClick={approveCallback}
disabled={approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
style={{ width: '48%' }}
variant={approval === ApprovalState.APPROVED ? 'success' : 'primary'}
>
{approval === ApprovalState.PENDING ? (
<AutoRow gap="6px" justify="center">
Approving <Loader stroke="white" />
</AutoRow>
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
'Approved'
) : (
`Approve ${currencies[Field.INPUT]?.symbol}`
)}
</Button>
<Button
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
})
}
}}
style={{ width: '48%' }}
id="swap-button"
disabled={
!isValid ||
approval !== ApprovalState.APPROVED ||
(priceImpactSeverity > 3 && !isExpertMode)
}
variant={isValid && priceImpactSeverity > 2 ? 'danger' : 'primary'}
>
{priceImpactSeverity > 3 && !isExpertMode
? `Price Impact High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Button>
</RowBetween>
) : (
<Button
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
})
}
}}
id="swap-button"
disabled={
!isValid || (priceImpactSeverity > 3 && !isExpertMode) || !!swapCallbackError
}
variant={isValid && priceImpactSeverity > 2 && !swapCallbackError ? 'danger' : 'primary'}
width="100%"
>
{swapInputError ||
(priceImpactSeverity > 3 && !isExpertMode
? `Price Impact Too High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`)}
</Button>
)}
{showApproveFlow && <ProgressSteps steps={[approval === ApprovalState.APPROVED]} />}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
</BottomGrouping>
</CardBody>
</Wrapper>
</AppBody>
<AdvancedSwapDetailsDropdown trade={trade} />
</Container>
)
}
Example #19
Source File: index.tsx From pancake-swap-exchange-testnet with GNU General Public License v3.0 | 4 votes |
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB },
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React()
const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB)
const TranslateString = useI18n()
const oneCurrencyIsWBNB = Boolean(
chainId &&
((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
(currencyB && currencyEquals(currencyB, WETH[chainId])))
)
const expertMode = useIsExpertMode()
// mint state
const { independentField, typedValue, otherTypedValue } = useMintState()
const {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
// txn values
const [deadline] = useUserDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: noLiquidity ? otherTypedValue : parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
// get the max amounts user can add
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmountSpend(currencyBalances[field]),
}
},
{}
)
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0'),
}
},
{}
)
// check whether the user has approved the router on the tokens
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS)
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS)
const addTransaction = useTransactionAdder()
async function onAdd() {
if (!chainId || !library || !account) return
const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) {
return
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0],
}
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate
let method: (...args: any) => Promise<TransactionResponse>
let args: Array<string | string[] | number>
let value: BigNumber | null
if (currencyA === ETHER || currencyB === ETHER) {
const tokenBIsBNB = currencyB === ETHER
estimate = router.estimateGas.addLiquidityETH
method = router.addLiquidityETH
args = [
wrappedCurrency(tokenBIsBNB ? currencyA : currencyB, chainId)?.address ?? '', // token
(tokenBIsBNB ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[tokenBIsBNB ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsBNB ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account,
deadlineFromNow,
]
value = BigNumber.from((tokenBIsBNB ? parsedAmountB : parsedAmountA).raw.toString())
} else {
estimate = router.estimateGas.addLiquidity
method = router.addLiquidity
args = [
wrappedCurrency(currencyA, chainId)?.address ?? '',
wrappedCurrency(currencyB, chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
value = null
}
setAttemptingTxn(true)
// const aa = await estimate(...args, value ? { value } : {})
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then((response) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Add ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencies[Field.CURRENCY_B]?.symbol}`,
})
setTxHash(response.hash)
})
)
.catch((e) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (e?.code !== 4001) {
console.error(e)
}
})
}
const modalHeader = () => {
return noLiquidity ? (
<AutoColumn gap="20px">
<LightCard mt="20px" borderRadius="20px">
<RowFlat>
<UIKitText fontSize="48px" mr="8px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol}`}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
</LightCard>
</AutoColumn>
) : (
<AutoColumn gap="20px">
<RowFlat style={{ marginTop: '20px' }}>
<UIKitText fontSize="48px" mr="8px">
{liquidityMinted?.toSignificant(6)}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
<Row>
<UIKitText fontSize="24px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol} Pool Tokens`}
</UIKitText>
</Row>
<UIKitText small textAlign="left" padding="8px 0 0 0 " style={{ fontStyle: 'italic' }}>
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</UIKitText>
</AutoColumn>
)
}
const modalBottom = () => {
return (
<ConfirmAddModalBottom
price={price}
currencies={currencies}
parsedAmounts={parsedAmounts}
noLiquidity={noLiquidity}
onAdd={onAdd}
poolTokenPercentage={poolTokenPercentage}
/>
)
}
const pendingText = `Supplying ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencies[Field.CURRENCY_B]?.symbol}`
const handleCurrencyASelect = useCallback(
(currA: Currency) => {
const newCurrencyIdA = currencyId(currA)
if (newCurrencyIdA === currencyIdB) {
history.push(`/add/${currencyIdB}/${currencyIdA}`)
} else {
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`)
}
},
[currencyIdB, history, currencyIdA]
)
const handleCurrencyBSelect = useCallback(
(currB: Currency) => {
const newCurrencyIdB = currencyId(currB)
if (currencyIdA === newCurrencyIdB) {
if (currencyIdB) {
history.push(`/add/${currencyIdB}/${newCurrencyIdB}`)
} else {
history.push(`/add/${newCurrencyIdB}`)
}
} else {
history.push(`/add/${currencyIdA || 'BNB'}/${newCurrencyIdB}`)
}
},
[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 (
<Container>
<CardNav activeIndex={1} />
<AppBody>
<AddRemoveTabs adding />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash}
content={() => (
<ConfirmationModalContent
title={
noLiquidity
? TranslateString(1154, 'You are creating a pool')
: TranslateString(1156, 'You will receive')
}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<CardBody>
<AutoColumn gap="20px">
{noLiquidity && (
<ColumnCenter>
<Pane>
<AutoColumn gap="12px">
<UIKitText>{TranslateString(1158, 'You are the first liquidity provider.')}</UIKitText>
<UIKitText>
{TranslateString(1160, 'The ratio of tokens you add will set the price of this pool.')}
</UIKitText>
<UIKitText>
{TranslateString(1162, 'Once you are happy with the rate click supply to review.')}
</UIKitText>
</AutoColumn>
</Pane>
</ColumnCenter>
)}
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
onMax={() => {
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}}
onCurrencySelect={handleCurrencyASelect}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A]}
id="add-liquidity-input-tokena"
showCommonBases={false}
/>
<ColumnCenter>
<AddIcon color="textSubtle" />
</ColumnCenter>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onFieldBInput}
onCurrencySelect={handleCurrencyBSelect}
onMax={() => {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
currency={currencies[Field.CURRENCY_B]}
id="add-liquidity-input-tokenb"
showCommonBases={false}
/>
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<div>
<UIKitText
style={{ textTransform: 'uppercase', fontWeight: 600 }}
color="textSubtle"
fontSize="12px"
mb="2px"
>
{noLiquidity
? TranslateString(1164, 'Initial prices and pool share')
: TranslateString(1166, 'Prices and pool share')}
</UIKitText>
<Pane>
<PoolPriceBar
currencies={currencies}
poolTokenPercentage={poolTokenPercentage}
noLiquidity={noLiquidity}
price={price}
/>
</Pane>
</div>
)}
{!account ? (
<ConnectWalletButton width="100%" />
) : (
<AutoColumn gap="md">
{(approvalA === ApprovalState.NOT_APPROVED ||
approvalA === ApprovalState.PENDING ||
approvalB === ApprovalState.NOT_APPROVED ||
approvalB === ApprovalState.PENDING) &&
isValid && (
<RowBetween>
{approvalA !== ApprovalState.APPROVED && (
<Button
onClick={approveACallback}
disabled={approvalA === ApprovalState.PENDING}
style={{ width: approvalB !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalA === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_A]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_A]?.symbol}`
)}
</Button>
)}
{approvalB !== ApprovalState.APPROVED && (
<Button
onClick={approveBCallback}
disabled={approvalB === ApprovalState.PENDING}
style={{ width: approvalA !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalB === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_B]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_B]?.symbol}`
)}
</Button>
)}
</RowBetween>
)}
<Button
onClick={() => {
if (expertMode) {
onAdd()
} else {
setShowConfirm(true)
}
}}
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
width="100%"
>
{error ?? 'Supply'}
</Button>
</AutoColumn>
)}
</AutoColumn>
</CardBody>
</Wrapper>
</AppBody>
{pair && !noLiquidity && pairState !== PairState.INVALID ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWBNB} pair={pair} />
</AutoColumn>
) : null}
</Container>
)
}
Example #20
Source File: AddLiquidity.tsx From interface-v2 with GNU General Public License v3.0 | 4 votes |
AddLiquidity: React.FC<{
currency0?: Currency;
currency1?: Currency;
currencyBg?: string;
}> = ({ currency0, currency1, currencyBg }) => {
const classes = useStyles({});
const { palette } = useTheme();
const { t } = useTranslation();
const [addLiquidityErrorMessage, setAddLiquidityErrorMessage] = useState<
string | null
>(null);
const { account, chainId, library } = useActiveWeb3React();
const [showConfirm, setShowConfirm] = useState(false);
const [attemptingTxn, setAttemptingTxn] = useState(false);
const [txPending, setTxPending] = useState(false);
const [allowedSlippage] = useUserSlippageTolerance();
const deadline = useTransactionDeadline();
const [txHash, setTxHash] = useState('');
const addTransaction = useTransactionAdder();
const finalizedTransaction = useTransactionFinalizer();
const { independentField, typedValue, otherTypedValue } = useMintState();
const expertMode = useIsExpertMode();
const {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useDerivedMintInfo();
const liquidityTokenData = {
amountA: formatTokenAmount(parsedAmounts[Field.CURRENCY_A]),
symbolA: currencies[Field.CURRENCY_A]?.symbol,
amountB: formatTokenAmount(parsedAmounts[Field.CURRENCY_B]),
symbolB: currencies[Field.CURRENCY_B]?.symbol,
};
const pendingText = t('supplyingTokens', liquidityTokenData);
const {
onFieldAInput,
onFieldBInput,
onCurrencySelection,
} = useMintActionHandlers(noLiquidity);
const maxAmounts: { [field in Field]?: TokenAmount } = [
Field.CURRENCY_A,
Field.CURRENCY_B,
].reduce((accumulator, field) => {
return {
...accumulator,
[field]: maxAmountSpend(currencyBalances[field]),
};
}, {});
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: noLiquidity
? otherTypedValue
: parsedAmounts[dependentField]?.toExact() ?? '',
};
const { ethereum } = window as any;
const toggleWalletModal = useWalletModalToggle();
const [approvingA, setApprovingA] = useState(false);
const [approvingB, setApprovingB] = useState(false);
const [approvalA, approveACallback] = useApproveCallback(
parsedAmounts[Field.CURRENCY_A],
chainId ? GlobalConst.addresses.ROUTER_ADDRESS[chainId] : undefined,
);
const [approvalB, approveBCallback] = useApproveCallback(
parsedAmounts[Field.CURRENCY_B],
chainId ? GlobalConst.addresses.ROUTER_ADDRESS[chainId] : undefined,
);
const userPoolBalance = useTokenBalance(
account ?? undefined,
pair?.liquidityToken,
);
const atMaxAmounts: { [field in Field]?: TokenAmount } = [
Field.CURRENCY_A,
Field.CURRENCY_B,
].reduce((accumulator, field) => {
return {
...accumulator,
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0'),
};
}, {});
const handleCurrencyASelect = useCallback(
(currencyA: Currency) => {
onCurrencySelection(Field.CURRENCY_A, currencyA);
},
[onCurrencySelection],
);
const handleCurrencyBSelect = useCallback(
(currencyB: Currency) => {
onCurrencySelection(Field.CURRENCY_B, currencyB);
},
[onCurrencySelection],
);
useEffect(() => {
if (currency0) {
onCurrencySelection(Field.CURRENCY_A, currency0);
} else {
onCurrencySelection(Field.CURRENCY_A, Token.ETHER);
}
if (currency1) {
onCurrencySelection(Field.CURRENCY_B, currency1);
} else {
onCurrencySelection(Field.CURRENCY_B, returnTokenFromKey('QUICK'));
}
}, [onCurrencySelection, currency0, currency1]);
const onAdd = () => {
if (expertMode) {
onAddLiquidity();
} else {
setShowConfirm(true);
}
};
const router = useRouterContract();
const onAddLiquidity = async () => {
if (!chainId || !library || !account || !router) return;
const {
[Field.CURRENCY_A]: parsedAmountA,
[Field.CURRENCY_B]: parsedAmountB,
} = parsedAmounts;
if (
!parsedAmountA ||
!parsedAmountB ||
!currencies[Field.CURRENCY_A] ||
!currencies[Field.CURRENCY_B] ||
!deadline
) {
return;
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(
parsedAmountA,
noLiquidity ? 0 : allowedSlippage,
)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(
parsedAmountB,
noLiquidity ? 0 : allowedSlippage,
)[0],
};
let estimate,
method: (...args: any) => Promise<TransactionResponse>,
args: Array<string | string[] | number>,
value: BigNumber | null;
if (
currencies[Field.CURRENCY_A] === ETHER ||
currencies[Field.CURRENCY_B] === ETHER
) {
const tokenBIsETH = currencies[Field.CURRENCY_B] === ETHER;
estimate = router.estimateGas.addLiquidityETH;
method = router.addLiquidityETH;
args = [
wrappedCurrency(
tokenBIsETH
? currencies[Field.CURRENCY_A]
: currencies[Field.CURRENCY_B],
chainId,
)?.address ?? '', // token
(tokenBIsETH ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[
tokenBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B
].toString(), // token min
amountsMin[
tokenBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A
].toString(), // eth min
account,
deadline.toHexString(),
];
value = BigNumber.from(
(tokenBIsETH ? parsedAmountB : parsedAmountA).raw.toString(),
);
} else {
estimate = router.estimateGas.addLiquidity;
method = router.addLiquidity;
args = [
wrappedCurrency(currencies[Field.CURRENCY_A], chainId)?.address ?? '',
wrappedCurrency(currencies[Field.CURRENCY_B], chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadline.toHexString(),
];
value = null;
}
setAttemptingTxn(true);
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then(async (response) => {
setAttemptingTxn(false);
setTxPending(true);
const summary = t('addLiquidityTokens', liquidityTokenData);
addTransaction(response, {
summary,
});
setTxHash(response.hash);
try {
const receipt = await response.wait();
finalizedTransaction(receipt, {
summary,
});
setTxPending(false);
} catch (error) {
setTxPending(false);
setAddLiquidityErrorMessage(t('errorInTx'));
}
ReactGA.event({
category: 'Liquidity',
action: 'Add',
label: [
currencies[Field.CURRENCY_A]?.symbol,
currencies[Field.CURRENCY_B]?.symbol,
].join('/'),
});
}),
)
.catch((error) => {
setAttemptingTxn(false);
setAddLiquidityErrorMessage(t('txRejected'));
// we only care if the error is something _other_ than the user rejected the tx
if (error?.code !== 4001) {
console.error(error);
}
});
};
const connectWallet = () => {
if (ethereum && !isSupportedNetwork(ethereum)) {
addMaticToMetamask();
} else {
toggleWalletModal();
}
};
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false);
// if there was a tx hash, we want to clear the input
if (txHash) {
onFieldAInput('');
}
setTxHash('');
}, [onFieldAInput, txHash]);
const buttonText = useMemo(() => {
if (account) {
return error ?? t('supply');
} else if (ethereum && !isSupportedNetwork(ethereum)) {
return t('switchPolygon');
}
return t('connectWallet');
}, [account, ethereum, error, t]);
const modalHeader = () => {
return (
<Box>
<Box mt={10} mb={3} display='flex' justifyContent='center'>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={48}
/>
</Box>
<Box mb={6} color={palette.text.primary} textAlign='center'>
<Typography variant='h6'>
{t('supplyingTokens', liquidityTokenData)}
<br />
{t('receiveLPTokens', {
amount: formatTokenAmount(liquidityMinted),
symbolA: currencies[Field.CURRENCY_A]?.symbol,
symbolB: currencies[Field.CURRENCY_B]?.symbol,
})}
</Typography>
</Box>
<Box mb={3} color={palette.text.secondary} textAlign='center'>
<Typography variant='body2'>
{t('outputEstimated', { slippage: allowedSlippage / 100 })}
</Typography>
</Box>
<Box className={classes.swapButtonWrapper}>
<Button onClick={onAddLiquidity}>{t('confirmSupply')}</Button>
</Box>
</Box>
);
};
return (
<Box>
{showConfirm && (
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
txPending={txPending}
hash={txHash}
content={() =>
addLiquidityErrorMessage ? (
<TransactionErrorContent
onDismiss={handleDismissConfirmation}
message={addLiquidityErrorMessage}
/>
) : (
<ConfirmationModalContent
title={t('supplyingliquidity')}
onDismiss={handleDismissConfirmation}
content={modalHeader}
/>
)
}
pendingText={pendingText}
modalContent={
txPending ? t('submittedTxLiquidity') : t('successAddedliquidity')
}
/>
)}
<CurrencyInput
id='add-liquidity-input-tokena'
title={`${t('token')} 1:`}
currency={currencies[Field.CURRENCY_A]}
showHalfButton={Boolean(maxAmounts[Field.CURRENCY_A])}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
onMax={() =>
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}
onHalf={() =>
onFieldAInput(
maxAmounts[Field.CURRENCY_A]
? (Number(maxAmounts[Field.CURRENCY_A]?.toExact()) / 2).toString()
: '',
)
}
handleCurrencySelect={handleCurrencyASelect}
amount={formattedAmounts[Field.CURRENCY_A]}
setAmount={onFieldAInput}
bgColor={currencyBg}
/>
<Box className={classes.exchangeSwap}>
<AddLiquidityIcon />
</Box>
<CurrencyInput
id='add-liquidity-input-tokenb'
title={`${t('token')} 2:`}
showHalfButton={Boolean(maxAmounts[Field.CURRENCY_B])}
currency={currencies[Field.CURRENCY_B]}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
onHalf={() =>
onFieldBInput(
maxAmounts[Field.CURRENCY_B]
? (Number(maxAmounts[Field.CURRENCY_B]?.toExact()) / 2).toString()
: '',
)
}
onMax={() =>
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}
handleCurrencySelect={handleCurrencyBSelect}
amount={formattedAmounts[Field.CURRENCY_B]}
setAmount={onFieldBInput}
bgColor={currencyBg}
/>
{currencies[Field.CURRENCY_A] &&
currencies[Field.CURRENCY_B] &&
pairState !== PairState.INVALID &&
price && (
<Box my={2}>
<Box className={classes.swapPrice}>
<Typography variant='body2'>
1 {currencies[Field.CURRENCY_A]?.symbol} ={' '}
{price.toSignificant(3)} {currencies[Field.CURRENCY_B]?.symbol}{' '}
</Typography>
<Typography variant='body2'>
1 {currencies[Field.CURRENCY_B]?.symbol} ={' '}
{price.invert().toSignificant(3)}{' '}
{currencies[Field.CURRENCY_A]?.symbol}{' '}
</Typography>
</Box>
<Box className={classes.swapPrice}>
<Typography variant='body2'>{t('yourPoolShare')}:</Typography>
<Typography variant='body2'>
{poolTokenPercentage
? poolTokenPercentage.toSignificant(6) + '%'
: '-'}
</Typography>
</Box>
<Box className={classes.swapPrice}>
<Typography variant='body2'>{t('lpTokenReceived')}:</Typography>
<Typography variant='body2'>
{formatTokenAmount(userPoolBalance)} {t('lpTokens')}
</Typography>
</Box>
</Box>
)}
<Box className={classes.swapButtonWrapper}>
{(approvalA === ApprovalState.NOT_APPROVED ||
approvalA === ApprovalState.PENDING ||
approvalB === ApprovalState.NOT_APPROVED ||
approvalB === ApprovalState.PENDING) &&
!error && (
<Box className={classes.approveButtons}>
{approvalA !== ApprovalState.APPROVED && (
<Box
width={approvalB !== ApprovalState.APPROVED ? '48%' : '100%'}
>
<Button
onClick={async () => {
setApprovingA(true);
try {
await approveACallback();
setApprovingA(false);
} catch (e) {
setApprovingA(false);
}
}}
disabled={approvingA || approvalA === ApprovalState.PENDING}
>
{approvalA === ApprovalState.PENDING
? `${t('approving')} ${
currencies[Field.CURRENCY_A]?.symbol
}`
: `${t('approve')} ${
currencies[Field.CURRENCY_A]?.symbol
}`}
</Button>
</Box>
)}
{approvalB !== ApprovalState.APPROVED && (
<Box
width={approvalA !== ApprovalState.APPROVED ? '48%' : '100%'}
>
<Button
onClick={async () => {
setApprovingB(true);
try {
await approveBCallback();
setApprovingB(false);
} catch (e) {
setApprovingB(false);
}
}}
disabled={approvingB || approvalB === ApprovalState.PENDING}
>
{approvalB === ApprovalState.PENDING
? `${t('approving')} ${
currencies[Field.CURRENCY_B]?.symbol
}`
: `${t('approve')} ${
currencies[Field.CURRENCY_B]?.symbol
}`}
</Button>
</Box>
)}
</Box>
)}
<Button
disabled={
Boolean(account) &&
(Boolean(error) ||
approvalA !== ApprovalState.APPROVED ||
approvalB !== ApprovalState.APPROVED)
}
onClick={account ? onAdd : connectWallet}
>
{buttonText}
</Button>
</Box>
</Box>
);
}
Example #21
Source File: index.tsx From mozartfinance-swap-interface with GNU General Public License v3.0 | 4 votes |
Swap = () => {
const loadedUrlParams = useDefaultsFromURLSearch()
const TranslateString = useI18n()
// token warning stuff
const [loadedInputCurrency, loadedOutputCurrency] = [
useCurrency(loadedUrlParams?.inputCurrencyId),
useCurrency(loadedUrlParams?.outputCurrencyId),
]
const [dismissTokenWarning, setDismissTokenWarning] = useState<boolean>(false)
const [isSyrup, setIsSyrup] = useState<boolean>(false)
const [syrupTransactionType, setSyrupTransactionType] = useState<string>('')
const urlLoadedTokens: Token[] = useMemo(
() => [loadedInputCurrency, loadedOutputCurrency]?.filter((c): c is Token => c instanceof Token) ?? [],
[loadedInputCurrency, loadedOutputCurrency]
)
const handleConfirmTokenWarning = useCallback(() => {
setDismissTokenWarning(true)
}, [])
const handleConfirmSyrupWarning = useCallback(() => {
setIsSyrup(false)
setSyrupTransactionType('')
}, [])
const { account } = useActiveWeb3React()
const theme = useContext(ThemeContext)
const [isExpertMode] = useExpertModeManager()
// get custom setting values for user
const [deadline] = useUserDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
// swap state
const { independentField, typedValue, recipient } = useSwapState()
const { v2Trade, currencyBalances, parsedAmount, currencies, inputError: swapInputError } = useDerivedSwapInfo()
const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback(
currencies[Field.INPUT],
currencies[Field.OUTPUT],
typedValue
)
const showWrap: boolean = wrapType !== WrapType.NOT_APPLICABLE
const trade = showWrap ? undefined : v2Trade
const parsedAmounts = showWrap
? {
[Field.INPUT]: parsedAmount,
[Field.OUTPUT]: parsedAmount,
}
: {
[Field.INPUT]: independentField === Field.INPUT ? parsedAmount : trade?.inputAmount,
[Field.OUTPUT]: independentField === Field.OUTPUT ? parsedAmount : trade?.outputAmount,
}
const { onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient } = useSwapActionHandlers()
const isValid = !swapInputError
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
const handleTypeInput = useCallback(
(value: string) => {
onUserInput(Field.INPUT, value)
},
[onUserInput]
)
const handleTypeOutput = useCallback(
(value: string) => {
onUserInput(Field.OUTPUT, value)
},
[onUserInput]
)
// modal and loading
const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{
showConfirm: boolean
tradeToConfirm: Trade | undefined
attemptingTxn: boolean
swapErrorMessage: string | undefined
txHash: string | undefined
}>({
showConfirm: false,
tradeToConfirm: undefined,
attemptingTxn: false,
swapErrorMessage: undefined,
txHash: undefined,
})
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: showWrap
? parsedAmounts[independentField]?.toExact() ?? ''
: parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
const route = trade?.route
const userHasSpecifiedInputOutput = Boolean(
currencies[Field.INPUT] && currencies[Field.OUTPUT] && parsedAmounts[independentField]?.greaterThan(JSBI.BigInt(0))
)
const noRoute = !route
// check whether the user has approved the router on the input token
const [approval, approveCallback] = useApproveCallbackFromTrade(trade, allowedSlippage)
// check if user has gone through approval process, used to show two step buttons, reset on token change
const [approvalSubmitted, setApprovalSubmitted] = useState<boolean>(false)
// mark when a user has submitted an approval, reset onTokenSelection for input field
useEffect(() => {
if (approval === ApprovalState.PENDING) {
setApprovalSubmitted(true)
}
}, [approval, approvalSubmitted])
const maxAmountInput: CurrencyAmount | undefined = maxAmountSpend(currencyBalances[Field.INPUT])
const atMaxAmountInput = Boolean(maxAmountInput && parsedAmounts[Field.INPUT]?.equalTo(maxAmountInput))
// the callback to execute the swap
const { callback: swapCallback, error: swapCallbackError } = useSwapCallback(
trade,
allowedSlippage,
deadline,
recipient
)
const { priceImpactWithoutFee } = computeTradePriceBreakdown(trade)
const handleSwap = useCallback(() => {
if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) {
return
}
if (!swapCallback) {
return
}
setSwapState((prevState) => ({ ...prevState, attemptingTxn: true, swapErrorMessage: undefined, txHash: undefined }))
swapCallback()
.then((hash) => {
setSwapState((prevState) => ({
...prevState,
attemptingTxn: false,
swapErrorMessage: undefined,
txHash: hash,
}))
})
.catch((error) => {
setSwapState((prevState) => ({
...prevState,
attemptingTxn: false,
swapErrorMessage: error.message,
txHash: undefined,
}))
})
}, [priceImpactWithoutFee, swapCallback, setSwapState])
// errors
const [showInverted, setShowInverted] = useState<boolean>(false)
// warnings on slippage
const priceImpactSeverity = warningSeverity(priceImpactWithoutFee)
// 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 =
!swapInputError &&
(approval === ApprovalState.NOT_APPROVED ||
approval === ApprovalState.PENDING ||
(approvalSubmitted && approval === ApprovalState.APPROVED)) &&
!(priceImpactSeverity > 3 && !isExpertMode)
const handleConfirmDismiss = useCallback(() => {
setSwapState((prevState) => ({ ...prevState, showConfirm: false }))
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.INPUT, '')
}
}, [onUserInput, txHash, setSwapState])
const handleAcceptChanges = useCallback(() => {
setSwapState((prevState) => ({ ...prevState, tradeToConfirm: trade }))
}, [trade])
// This will check to see if the user has selected Syrup to either buy or sell.
// If so, they will be alerted with a warning message.
const checkForSyrup = useCallback(
(selected: string, purchaseType: string) => {
if (selected === 'syrup') {
setIsSyrup(true)
setSyrupTransactionType(purchaseType)
}
},
[setIsSyrup, setSyrupTransactionType]
)
const handleInputSelect = useCallback(
(inputCurrency) => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onCurrencySelection(Field.INPUT, inputCurrency)
if (inputCurrency.symbol.toLowerCase() === 'syrup') {
checkForSyrup(inputCurrency.symbol.toLowerCase(), 'Selling')
}
},
[onCurrencySelection, setApprovalSubmitted, checkForSyrup]
)
const handleMaxInput = useCallback(() => {
if (maxAmountInput) {
onUserInput(Field.INPUT, maxAmountInput.toExact())
}
}, [maxAmountInput, onUserInput])
const handleOutputSelect = useCallback(
(outputCurrency) => {
onCurrencySelection(Field.OUTPUT, outputCurrency)
if (outputCurrency.symbol.toLowerCase() === 'syrup') {
checkForSyrup(outputCurrency.symbol.toLowerCase(), 'Buying')
}
},
[onCurrencySelection, checkForSyrup]
)
return (
<>
<TokenWarningModal
isOpen={urlLoadedTokens.length > 0 && !dismissTokenWarning}
tokens={urlLoadedTokens}
onConfirm={handleConfirmTokenWarning}
/>
<SyrupWarningModal
isOpen={isSyrup}
transactionType={syrupTransactionType}
onConfirm={handleConfirmSyrupWarning}
/>
<CardNav />
<AppBody>
<Wrapper id="swap-page">
<ConfirmSwapModal
isOpen={showConfirm}
trade={trade}
originalTrade={tradeToConfirm}
onAcceptChanges={handleAcceptChanges}
attemptingTxn={attemptingTxn}
txHash={txHash}
recipient={recipient}
allowedSlippage={allowedSlippage}
onConfirm={handleSwap}
swapErrorMessage={swapErrorMessage}
onDismiss={handleConfirmDismiss}
/>
<PageHeader
title={TranslateString(8, 'Exchange')}
description={TranslateString(1192, 'Trade tokens in an instant')}
/>
<CardBody>
<AutoColumn gap="md">
<CurrencyInputPanel
label={
independentField === Field.OUTPUT && !showWrap && trade
? TranslateString(194, 'From (estimated)')
: TranslateString(76, 'From')
}
value={formattedAmounts[Field.INPUT]}
showMaxButton={!atMaxAmountInput}
currency={currencies[Field.INPUT]}
onUserInput={handleTypeInput}
onMax={handleMaxInput}
onCurrencySelect={handleInputSelect}
otherCurrency={currencies[Field.OUTPUT]}
id="swap-currency-input"
/>
<AutoColumn justify="space-between">
<AutoRow justify={isExpertMode ? 'space-between' : 'center'} style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable>
<IconButton
variant="tertiary"
onClick={() => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onSwitchTokens()
}}
style={{ borderRadius: '50%' }}
scale="sm"
>
<ArrowDownIcon color="primary" width="24px" />
</IconButton>
</ArrowWrapper>
{recipient === null && !showWrap && isExpertMode ? (
<LinkStyledButton id="add-recipient-button" onClick={() => onChangeRecipient('')}>
+ Add a send (optional)
</LinkStyledButton>
) : null}
</AutoRow>
</AutoColumn>
<CurrencyInputPanel
value={formattedAmounts[Field.OUTPUT]}
onUserInput={handleTypeOutput}
label={
independentField === Field.INPUT && !showWrap && trade
? TranslateString(196, 'To (estimated)')
: TranslateString(80, 'To')
}
showMaxButton={false}
currency={currencies[Field.OUTPUT]}
onCurrencySelect={handleOutputSelect}
otherCurrency={currencies[Field.INPUT]}
id="swap-currency-output"
/>
{recipient !== null && !showWrap ? (
<>
<AutoRow justify="space-between" style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable={false}>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ArrowWrapper>
<LinkStyledButton id="remove-recipient-button" onClick={() => onChangeRecipient(null)}>
- Remove send
</LinkStyledButton>
</AutoRow>
<AddressInputPanel id="recipient" value={recipient} onChange={onChangeRecipient} />
</>
) : null}
{showWrap ? null : (
<Card padding=".25rem .75rem 0 .75rem" borderRadius="20px">
<AutoColumn gap="4px">
{Boolean(trade) && (
<RowBetween align="center">
<Text fontSize="14px">{TranslateString(1182, 'Price')}</Text>
<TradePrice
price={trade?.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween>
)}
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
<RowBetween align="center">
<Text fontSize="14px">{TranslateString(88, 'Slippage Tolerance')}</Text>
<Text fontSize="14px">{allowedSlippage / 100}%</Text>
</RowBetween>
)}
</AutoColumn>
</Card>
)}
</AutoColumn>
<BottomGrouping>
{!account ? (
<ConnectWalletButton width="100%" />
) : showWrap ? (
<Button disabled={Boolean(wrapInputError)} onClick={onWrap} width="100%">
{wrapInputError ??
(wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
</Button>
) : noRoute && userHasSpecifiedInputOutput ? (
<GreyCard style={{ textAlign: 'center' }}>
<Text mb="4px">{TranslateString(1194, 'Insufficient liquidity for this trade.')}</Text>
</GreyCard>
) : showApproveFlow ? (
<RowBetween>
<Button
onClick={approveCallback}
disabled={approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
style={{ width: '48%' }}
variant={approval === ApprovalState.APPROVED ? 'success' : 'primary'}
>
{approval === ApprovalState.PENDING ? (
<AutoRow gap="6px" justify="center">
Approving <Loader stroke="white" />
</AutoRow>
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
'Approved'
) : (
`Approve ${currencies[Field.INPUT]?.symbol}`
)}
</Button>
<Button
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
})
}
}}
style={{ width: '48%' }}
id="swap-button"
disabled={
!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !isExpertMode)
}
variant={isValid && priceImpactSeverity > 2 ? 'danger' : 'primary'}
>
{priceImpactSeverity > 3 && !isExpertMode
? `Price Impact High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Button>
</RowBetween>
) : (
<Button
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
})
}
}}
id="swap-button"
disabled={!isValid || (priceImpactSeverity > 3 && !isExpertMode) || !!swapCallbackError}
variant={isValid && priceImpactSeverity > 2 && !swapCallbackError ? 'danger' : 'primary'}
width="100%"
>
{swapInputError ||
(priceImpactSeverity > 3 && !isExpertMode
? `Price Impact Too High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`)}
</Button>
)}
{showApproveFlow && <ProgressSteps steps={[approval === ApprovalState.APPROVED]} />}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
</BottomGrouping>
</CardBody>
</Wrapper>
</AppBody>
<AdvancedSwapDetailsDropdown trade={trade} />
</>
)
}
Example #22
Source File: index.tsx From mozartfinance-swap-interface with GNU General Public License v3.0 | 4 votes |
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB },
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React()
const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB)
const TranslateString = useI18n()
const oneCurrencyIsWBNB = Boolean(
chainId &&
((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
(currencyB && currencyEquals(currencyB, WETH[chainId])))
)
const expertMode = useIsExpertMode()
// mint state
const { independentField, typedValue, otherTypedValue } = useMintState()
const {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
// txn values
const [deadline] = useUserDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: noLiquidity ? otherTypedValue : parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
// get the max amounts user can add
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmountSpend(currencyBalances[field]),
}
},
{}
)
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0'),
}
},
{}
)
// check whether the user has approved the router on the tokens
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS)
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS)
const addTransaction = useTransactionAdder()
async function onAdd() {
if (!chainId || !library || !account) return
const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) {
return
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0],
}
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate
let method: (...args: any) => Promise<TransactionResponse>
let args: Array<string | string[] | number>
let value: BigNumber | null
if (currencyA === ETHER || currencyB === ETHER) {
const tokenBIsBNB = currencyB === ETHER
estimate = router.estimateGas.addLiquidityETH
method = router.addLiquidityETH
args = [
wrappedCurrency(tokenBIsBNB ? currencyA : currencyB, chainId)?.address ?? '', // token
(tokenBIsBNB ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[tokenBIsBNB ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsBNB ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account,
deadlineFromNow,
]
value = BigNumber.from((tokenBIsBNB ? parsedAmountB : parsedAmountA).raw.toString())
} else {
estimate = router.estimateGas.addLiquidity
method = router.addLiquidity
args = [
wrappedCurrency(currencyA, chainId)?.address ?? '',
wrappedCurrency(currencyB, chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
value = null
}
setAttemptingTxn(true)
// const aa = await estimate(...args, value ? { value } : {})
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then((response) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Add ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencies[Field.CURRENCY_B]?.symbol}`,
})
setTxHash(response.hash)
})
)
.catch((e) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (e?.code !== 4001) {
console.error(e)
}
})
}
const modalHeader = () => {
return noLiquidity ? (
<AutoColumn gap="20px">
<LightCard mt="20px" borderRadius="20px">
<RowFlat>
<UIKitText fontSize="48px" mr="8px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol}`}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
</LightCard>
</AutoColumn>
) : (
<AutoColumn gap="20px">
<RowFlat style={{ marginTop: '20px' }}>
<UIKitText fontSize="48px" mr="8px">
{liquidityMinted?.toSignificant(6)}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
<Row>
<UIKitText fontSize="24px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol} Pool Tokens`}
</UIKitText>
</Row>
<UIKitText small textAlign="left" padding="8px 0 0 0 " style={{ fontStyle: 'italic' }}>
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</UIKitText>
</AutoColumn>
)
}
const modalBottom = () => {
return (
<ConfirmAddModalBottom
price={price}
currencies={currencies}
parsedAmounts={parsedAmounts}
noLiquidity={noLiquidity}
onAdd={onAdd}
poolTokenPercentage={poolTokenPercentage}
/>
)
}
const pendingText = `Supplying ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencies[Field.CURRENCY_B]?.symbol}`
const handleCurrencyASelect = useCallback(
(currA: Currency) => {
const newCurrencyIdA = currencyId(currA)
if (newCurrencyIdA === currencyIdB) {
history.push(`/add/${currencyIdB}/${currencyIdA}`)
} else {
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`)
}
},
[currencyIdB, history, currencyIdA]
)
const handleCurrencyBSelect = useCallback(
(currB: Currency) => {
const newCurrencyIdB = currencyId(currB)
if (currencyIdA === newCurrencyIdB) {
if (currencyIdB) {
history.push(`/add/${currencyIdB}/${newCurrencyIdB}`)
} else {
history.push(`/add/${newCurrencyIdB}`)
}
} else {
history.push(`/add/${currencyIdA || 'BNB'}/${newCurrencyIdB}`)
}
},
[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 (
<>
<CardNav activeIndex={1} />
<AppBody>
<AddRemoveTabs adding />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash}
content={() => (
<ConfirmationModalContent
title={
noLiquidity
? TranslateString(1154, 'You are creating a pool')
: TranslateString(1156, 'You will receive')
}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<CardBody>
<AutoColumn gap="20px">
{noLiquidity && (
<ColumnCenter>
<Pane>
<AutoColumn gap="12px">
<UIKitText>{TranslateString(1158, 'You are the first liquidity provider.')}</UIKitText>
<UIKitText>
{TranslateString(1160, 'The ratio of tokens you add will set the price of this pool.')}
</UIKitText>
<UIKitText>
{TranslateString(1162, 'Once you are happy with the rate click supply to review.')}
</UIKitText>
</AutoColumn>
</Pane>
</ColumnCenter>
)}
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
onMax={() => {
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}}
onCurrencySelect={handleCurrencyASelect}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A]}
id="add-liquidity-input-tokena"
showCommonBases={false}
/>
<ColumnCenter>
<AddIcon color="textSubtle" />
</ColumnCenter>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onFieldBInput}
onCurrencySelect={handleCurrencyBSelect}
onMax={() => {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
currency={currencies[Field.CURRENCY_B]}
id="add-liquidity-input-tokenb"
showCommonBases={false}
/>
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<div>
<UIKitText
style={{ textTransform: 'uppercase', fontWeight: 600 }}
color="textSubtle"
fontSize="12px"
mb="2px"
>
{noLiquidity
? TranslateString(1164, 'Initial prices and pool share')
: TranslateString(1166, 'Prices and pool share')}
</UIKitText>
<Pane>
<PoolPriceBar
currencies={currencies}
poolTokenPercentage={poolTokenPercentage}
noLiquidity={noLiquidity}
price={price}
/>
</Pane>
</div>
)}
{!account ? (
<ConnectWalletButton width="100%" />
) : (
<AutoColumn gap="md">
{(approvalA === ApprovalState.NOT_APPROVED ||
approvalA === ApprovalState.PENDING ||
approvalB === ApprovalState.NOT_APPROVED ||
approvalB === ApprovalState.PENDING) &&
isValid && (
<RowBetween>
{approvalA !== ApprovalState.APPROVED && (
<Button
onClick={approveACallback}
disabled={approvalA === ApprovalState.PENDING}
style={{ width: approvalB !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalA === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_A]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_A]?.symbol}`
)}
</Button>
)}
{approvalB !== ApprovalState.APPROVED && (
<Button
onClick={approveBCallback}
disabled={approvalB === ApprovalState.PENDING}
style={{ width: approvalA !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalB === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_B]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_B]?.symbol}`
)}
</Button>
)}
</RowBetween>
)}
<Button
onClick={() => {
if (expertMode) {
onAdd()
} else {
setShowConfirm(true)
}
}}
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
width="100%"
>
{error ?? 'Supply'}
</Button>
</AutoColumn>
)}
</AutoColumn>
</CardBody>
</Wrapper>
</AppBody>
{pair && !noLiquidity && pairState !== PairState.INVALID ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWBNB} pair={pair} />
</AutoColumn>
) : null}
</>
)
}
Example #23
Source File: SlippageToleranceSetting.tsx From mozartfinance-swap-interface with GNU General Public License v3.0 | 4 votes |
SlippageToleranceSettings = ({ translateString }: SlippageToleranceSettingsModalProps) => {
const TranslateString = translateString
const [userSlippageTolerance, setUserslippageTolerance] = useUserSlippageTolerance()
const [value, setValue] = useState(userSlippageTolerance / 100)
const [error, setError] = useState<string | null>(null)
const handleChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
const { value: inputValue } = evt.target
setValue(parseFloat(inputValue))
}
// Updates local storage if value is valid
useEffect(() => {
try {
const rawValue = value * 100
if (!Number.isNaN(rawValue) && rawValue > 0 && rawValue < MAX_SLIPPAGE) {
setUserslippageTolerance(rawValue)
setError(null)
} else {
setError(TranslateString(1144, 'Enter a valid slippage percentage'))
}
} catch {
setError(TranslateString(1144, 'Enter a valid slippage percentage'))
}
}, [value, setError, setUserslippageTolerance, TranslateString])
// Notify user if slippage is risky
useEffect(() => {
if (userSlippageTolerance < RISKY_SLIPPAGE_LOW) {
setError(TranslateString(1146, 'Your transaction may fail'))
} else if (userSlippageTolerance > RISKY_SLIPPAGE_HIGH) {
setError(TranslateString(1148, 'Your transaction may be frontrun'))
}
}, [userSlippageTolerance, setError, TranslateString])
return (
<StyledSlippageToleranceSettings>
<Label>
<Text style={{ fontWeight: 600 }}>{TranslateString(88, 'Slippage tolerance')}</Text>
<QuestionHelper
text={TranslateString(
186,
'Your transaction will revert if the price changes unfavorably by more than this percentage.'
)}
/>
</Label>
<Options>
<Flex mb={['8px', 0]} mr={[0, '8px']}>
{predefinedValues.map(({ label, value: predefinedValue }) => {
const handleClick = () => setValue(predefinedValue)
return (
<Option key={predefinedValue}>
<Button variant={value === predefinedValue ? 'primary' : 'tertiary'} onClick={handleClick}>
{label}
</Button>
</Option>
)
})}
</Flex>
<Flex alignItems="center">
<Option>
<Input
type="number"
scale="lg"
step={0.1}
min={0.1}
placeholder="5%"
value={value}
onChange={handleChange}
isWarning={error !== null}
/>
</Option>
<Option>
<Text fontSize="18px">%</Text>
</Option>
</Flex>
</Options>
{error && (
<Text mt="8px" color="failure">
{error}
</Text>
)}
</StyledSlippageToleranceSettings>
)
}
Example #24
Source File: index.tsx From goose-frontend-amm with GNU General Public License v3.0 | 4 votes |
Swap = () => {
const loadedUrlParams = useDefaultsFromURLSearch()
// token warning stuff
const [loadedInputCurrency, loadedOutputCurrency] = [
useCurrency(loadedUrlParams?.inputCurrencyId),
useCurrency(loadedUrlParams?.outputCurrencyId),
]
const [dismissTokenWarning, setDismissTokenWarning] = useState<boolean>(false)
const [isSyrup, setIsSyrup] = useState<boolean>(false)
const [syrupTransactionType, setSyrupTransactionType] = useState<string>('')
const urlLoadedTokens: Token[] = useMemo(
() => [loadedInputCurrency, loadedOutputCurrency]?.filter((c): c is Token => c instanceof Token) ?? [],
[loadedInputCurrency, loadedOutputCurrency]
)
const handleConfirmTokenWarning = useCallback(() => {
setDismissTokenWarning(true)
}, [])
const handleConfirmSyrupWarning = useCallback(() => {
setIsSyrup(false)
setSyrupTransactionType('')
}, [])
const { account } = useActiveWeb3React()
const theme = useContext(ThemeContext)
const [isExpertMode] = useExpertModeManager()
// get custom setting values for user
const [deadline] = useUserDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
// swap state
const { independentField, typedValue, recipient } = useSwapState()
const {
v1Trade,
v2Trade,
currencyBalances,
parsedAmount,
currencies,
inputError: swapInputError,
} = useDerivedSwapInfo()
const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback(
currencies[Field.INPUT],
currencies[Field.OUTPUT],
typedValue
)
const showWrap: boolean = wrapType !== WrapType.NOT_APPLICABLE
// const { address: recipientAddress } = useENSAddress(recipient)
const toggledVersion = useToggledVersion()
const trade = showWrap
? undefined
: {
[Version.v1]: v1Trade,
[Version.v2]: v2Trade,
}[toggledVersion]
const betterTradeLinkVersion: Version | undefined =
toggledVersion === Version.v2 && isTradeBetter(v2Trade, v1Trade, BETTER_TRADE_LINK_THRESHOLD)
? Version.v1
: toggledVersion === Version.v1 && isTradeBetter(v1Trade, v2Trade)
? Version.v2
: undefined
const parsedAmounts = showWrap
? {
[Field.INPUT]: parsedAmount,
[Field.OUTPUT]: parsedAmount,
}
: {
[Field.INPUT]: independentField === Field.INPUT ? parsedAmount : trade?.inputAmount,
[Field.OUTPUT]: independentField === Field.OUTPUT ? parsedAmount : trade?.outputAmount,
}
const { onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient } = useSwapActionHandlers()
const isValid = !swapInputError
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
const handleTypeInput = useCallback(
(value: string) => {
onUserInput(Field.INPUT, value)
},
[onUserInput]
)
const handleTypeOutput = useCallback(
(value: string) => {
onUserInput(Field.OUTPUT, value)
},
[onUserInput]
)
// modal and loading
const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{
showConfirm: boolean
tradeToConfirm: Trade | undefined
attemptingTxn: boolean
swapErrorMessage: string | undefined
txHash: string | undefined
}>({
showConfirm: false,
tradeToConfirm: undefined,
attemptingTxn: false,
swapErrorMessage: undefined,
txHash: undefined,
})
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: showWrap
? parsedAmounts[independentField]?.toExact() ?? ''
: parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
const route = trade?.route
const userHasSpecifiedInputOutput = Boolean(
currencies[Field.INPUT] && currencies[Field.OUTPUT] && parsedAmounts[independentField]?.greaterThan(JSBI.BigInt(0))
)
const noRoute = !route
// check whether the user has approved the router on the input token
const [approval, approveCallback] = useApproveCallbackFromTrade(trade, allowedSlippage)
// check if user has gone through approval process, used to show two step buttons, reset on token change
const [approvalSubmitted, setApprovalSubmitted] = useState<boolean>(false)
// mark when a user has submitted an approval, reset onTokenSelection for input field
useEffect(() => {
if (approval === ApprovalState.PENDING) {
setApprovalSubmitted(true)
}
}, [approval, approvalSubmitted])
const maxAmountInput: CurrencyAmount | undefined = maxAmountSpend(currencyBalances[Field.INPUT])
const atMaxAmountInput = Boolean(maxAmountInput && parsedAmounts[Field.INPUT]?.equalTo(maxAmountInput))
// the callback to execute the swap
const { callback: swapCallback, error: swapCallbackError } = useSwapCallback(
trade,
allowedSlippage,
deadline,
recipient
)
const { priceImpactWithoutFee } = computeTradePriceBreakdown(trade)
const handleSwap = useCallback(() => {
if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) {
return
}
if (!swapCallback) {
return
}
setSwapState((prevState) => ({ ...prevState, attemptingTxn: true, swapErrorMessage: undefined, txHash: undefined }))
swapCallback()
.then((hash) => {
setSwapState((prevState) => ({
...prevState,
attemptingTxn: false,
swapErrorMessage: undefined,
txHash: hash,
}))
})
.catch((error) => {
setSwapState((prevState) => ({
...prevState,
attemptingTxn: false,
swapErrorMessage: error.message,
txHash: undefined,
}))
})
}, [priceImpactWithoutFee, swapCallback, setSwapState])
// errors
const [showInverted, setShowInverted] = useState<boolean>(false)
// warnings on slippage
const priceImpactSeverity = warningSeverity(priceImpactWithoutFee)
// 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 =
!swapInputError &&
(approval === ApprovalState.NOT_APPROVED ||
approval === ApprovalState.PENDING ||
(approvalSubmitted && approval === ApprovalState.APPROVED)) &&
!(priceImpactSeverity > 3 && !isExpertMode)
const handleConfirmDismiss = useCallback(() => {
setSwapState((prevState) => ({ ...prevState, showConfirm: false }))
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.INPUT, '')
}
}, [onUserInput, txHash, setSwapState])
const handleAcceptChanges = useCallback(() => {
setSwapState((prevState) => ({ ...prevState, tradeToConfirm: trade }))
}, [trade])
// This will check to see if the user has selected Syrup to either buy or sell.
// If so, they will be alerted with a warning message.
const checkForSyrup = useCallback(
(selected: string, purchaseType: string) => {
if (selected === 'syrup') {
setIsSyrup(true)
setSyrupTransactionType(purchaseType)
}
},
[setIsSyrup, setSyrupTransactionType]
)
const handleInputSelect = useCallback(
(inputCurrency) => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onCurrencySelection(Field.INPUT, inputCurrency)
if (inputCurrency.symbol.toLowerCase() === 'syrup') {
checkForSyrup(inputCurrency.symbol.toLowerCase(), 'Selling')
}
},
[onCurrencySelection, setApprovalSubmitted, checkForSyrup]
)
const handleMaxInput = useCallback(() => {
if (maxAmountInput) {
onUserInput(Field.INPUT, maxAmountInput.toExact())
}
}, [maxAmountInput, onUserInput])
const handleOutputSelect = useCallback(
(outputCurrency) => {
onCurrencySelection(Field.OUTPUT, outputCurrency)
if (outputCurrency.symbol.toLowerCase() === 'syrup') {
checkForSyrup(outputCurrency.symbol.toLowerCase(), 'Buying')
}
},
[onCurrencySelection, checkForSyrup]
)
return (
<>
<TokenWarningModal
isOpen={urlLoadedTokens.length > 0 && !dismissTokenWarning}
tokens={urlLoadedTokens}
onConfirm={handleConfirmTokenWarning}
/>
<SyrupWarningModal
isOpen={isSyrup}
transactionType={syrupTransactionType}
onConfirm={handleConfirmSyrupWarning}
/>
<CardNav />
<AppBody>
<Wrapper id="swap-page">
<ConfirmSwapModal
isOpen={showConfirm}
trade={trade}
originalTrade={tradeToConfirm}
onAcceptChanges={handleAcceptChanges}
attemptingTxn={attemptingTxn}
txHash={txHash}
recipient={recipient}
allowedSlippage={allowedSlippage}
onConfirm={handleSwap}
swapErrorMessage={swapErrorMessage}
onDismiss={handleConfirmDismiss}
/>
<PageHeader title="Exchange" description="Trade tokens in an instant" />
<CardBody>
<AutoColumn gap="md">
<CurrencyInputPanel
label={
independentField === Field.OUTPUT && !showWrap && trade
? 'From (estimated)'
: TranslateString(76, 'From')
}
value={formattedAmounts[Field.INPUT]}
showMaxButton={!atMaxAmountInput}
currency={currencies[Field.INPUT]}
onUserInput={handleTypeInput}
onMax={handleMaxInput}
onCurrencySelect={handleInputSelect}
otherCurrency={currencies[Field.OUTPUT]}
id="swap-currency-input"
/>
<AutoColumn justify="space-between">
<AutoRow justify={isExpertMode ? 'space-between' : 'center'} style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable>
<IconButton
variant="tertiary"
onClick={() => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onSwitchTokens()
}}
style={{ borderRadius: '50%' }}
size="sm"
>
<ArrowDownIcon color="primary" width="24px" />
</IconButton>
</ArrowWrapper>
{recipient === null && !showWrap && isExpertMode ? (
<LinkStyledButton id="add-recipient-button" onClick={() => onChangeRecipient('')}>
+ Add a send (optional)
</LinkStyledButton>
) : null}
</AutoRow>
</AutoColumn>
<CurrencyInputPanel
value={formattedAmounts[Field.OUTPUT]}
onUserInput={handleTypeOutput}
label={
independentField === Field.INPUT && !showWrap && trade ? 'To (estimated)' : TranslateString(80, 'To')
}
showMaxButton={false}
currency={currencies[Field.OUTPUT]}
onCurrencySelect={handleOutputSelect}
otherCurrency={currencies[Field.INPUT]}
id="swap-currency-output"
/>
{recipient !== null && !showWrap ? (
<>
<AutoRow justify="space-between" style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable={false}>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ArrowWrapper>
<LinkStyledButton id="remove-recipient-button" onClick={() => onChangeRecipient(null)}>
- Remove send
</LinkStyledButton>
</AutoRow>
<AddressInputPanel id="recipient" value={recipient} onChange={onChangeRecipient} />
</>
) : null}
{showWrap ? null : (
<Card padding=".25rem .75rem 0 .75rem" borderRadius="20px">
<AutoColumn gap="4px">
{Boolean(trade) && (
<RowBetween align="center">
<Text fontSize="14px">Price</Text>
<TradePrice
price={trade?.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween>
)}
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
<RowBetween align="center">
<Text fontSize="14px">Slippage Tolerance</Text>
<Text fontSize="14px">{allowedSlippage / 100}%</Text>
</RowBetween>
)}
</AutoColumn>
</Card>
)}
</AutoColumn>
<BottomGrouping>
{!account ? (
<ConnectWalletButton fullWidth />
) : showWrap ? (
<Button disabled={Boolean(wrapInputError)} onClick={onWrap} fullWidth>
{wrapInputError ??
(wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
</Button>
) : noRoute && userHasSpecifiedInputOutput ? (
<GreyCard style={{ textAlign: 'center' }}>
<Main mb="4px">Insufficient liquidity for this trade.</Main>
</GreyCard>
) : showApproveFlow ? (
<RowBetween>
<Button
onClick={approveCallback}
disabled={approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
style={{ width: '48%' }}
variant={approval === ApprovalState.APPROVED ? 'success' : 'primary'}
>
{approval === ApprovalState.PENDING ? (
<AutoRow gap="6px" justify="center">
Approving <Loader stroke="white" />
</AutoRow>
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
'Approved'
) : (
`Approve ${currencies[Field.INPUT]?.symbol}`
)}
</Button>
<Button
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
})
}
}}
style={{ width: '48%' }}
id="swap-button"
disabled={
!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !isExpertMode)
}
variant={isValid && priceImpactSeverity > 2 ? 'danger' : 'primary'}
>
{priceImpactSeverity > 3 && !isExpertMode
? `Price Impact High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Button>
</RowBetween>
) : (
<Button
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
})
}
}}
id="swap-button"
disabled={!isValid || (priceImpactSeverity > 3 && !isExpertMode) || !!swapCallbackError}
variant={isValid && priceImpactSeverity > 2 && !swapCallbackError ? 'danger' : 'primary'}
fullWidth
>
{swapInputError ||
(priceImpactSeverity > 3 && !isExpertMode
? `Price Impact Too High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`)}
</Button>
)}
{showApproveFlow && <ProgressSteps steps={[approval === ApprovalState.APPROVED]} />}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
{betterTradeLinkVersion && <BetterTradeLink version={betterTradeLinkVersion} />}
</BottomGrouping>
</CardBody>
</Wrapper>
</AppBody>
<AdvancedSwapDetailsDropdown trade={trade} />
</>
)
}
Example #25
Source File: index.tsx From goose-frontend-amm with GNU General Public License v3.0 | 4 votes |
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB },
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React()
const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB)
const oneCurrencyIsWETH = Boolean(
chainId &&
((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
(currencyB && currencyEquals(currencyB, WETH[chainId])))
)
const expertMode = useIsExpertMode()
// mint state
const { independentField, typedValue, otherTypedValue } = useMintState()
const {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
// txn values
const [deadline] = useUserDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: noLiquidity ? otherTypedValue : parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
// get the max amounts user can add
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmountSpend(currencyBalances[field]),
}
},
{}
)
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0'),
}
},
{}
)
// check whether the user has approved the router on the tokens
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS)
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS)
const addTransaction = useTransactionAdder()
async function onAdd() {
if (!chainId || !library || !account) return
const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) {
return
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0],
}
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate
let method: (...args: any) => Promise<TransactionResponse>
let args: Array<string | string[] | number>
let value: BigNumber | null
if (currencyA === ETHER || currencyB === ETHER) {
const tokenBIsETH = currencyB === ETHER
estimate = router.estimateGas.addLiquidityETH
method = router.addLiquidityETH
args = [
wrappedCurrency(tokenBIsETH ? currencyA : currencyB, chainId)?.address ?? '', // token
(tokenBIsETH ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[tokenBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account,
deadlineFromNow,
]
value = BigNumber.from((tokenBIsETH ? parsedAmountB : parsedAmountA).raw.toString())
} else {
estimate = router.estimateGas.addLiquidity
method = router.addLiquidity
args = [
wrappedCurrency(currencyA, chainId)?.address ?? '',
wrappedCurrency(currencyB, chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
value = null
}
setAttemptingTxn(true)
// const aa = await estimate(...args, value ? { value } : {})
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then((response) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Add ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencies[Field.CURRENCY_B]?.symbol}`,
})
setTxHash(response.hash)
})
)
.catch((e) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (e?.code !== 4001) {
console.error(e)
}
})
}
const modalHeader = () => {
return noLiquidity ? (
<AutoColumn gap="20px">
<LightCard mt="20px" borderRadius="20px">
<RowFlat>
<UIKitText fontSize="48px" mr="8px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol}`}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
</LightCard>
</AutoColumn>
) : (
<AutoColumn gap="20px">
<RowFlat style={{ marginTop: '20px' }}>
<UIKitText fontSize="48px" mr="8px">
{liquidityMinted?.toSignificant(6)}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
<Row>
<UIKitText fontSize="24px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol} Pool Tokens`}
</UIKitText>
</Row>
<Italic fontSize={12} textAlign="left" padding="8px 0 0 0 ">
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</Italic>
</AutoColumn>
)
}
const modalBottom = () => {
return (
<ConfirmAddModalBottom
price={price}
currencies={currencies}
parsedAmounts={parsedAmounts}
noLiquidity={noLiquidity}
onAdd={onAdd}
poolTokenPercentage={poolTokenPercentage}
/>
)
}
const pendingText = `Supplying ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencies[Field.CURRENCY_B]?.symbol}`
const handleCurrencyASelect = useCallback(
(currA: Currency) => {
const newCurrencyIdA = currencyId(currA)
if (newCurrencyIdA === currencyIdB) {
history.push(`/add/${currencyIdB}/${currencyIdA}`)
} else {
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`)
}
},
[currencyIdB, history, currencyIdA]
)
const handleCurrencyBSelect = useCallback(
(currB: Currency) => {
const newCurrencyIdB = currencyId(currB)
if (currencyIdA === newCurrencyIdB) {
if (currencyIdB) {
history.push(`/add/${currencyIdB}/${newCurrencyIdB}`)
} else {
history.push(`/add/${newCurrencyIdB}`)
}
} else {
history.push(`/add/${currencyIdA || 'ETH'}/${newCurrencyIdB}`)
}
},
[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 (
<>
<CardNav activeIndex={1} />
<AppBody>
<AddRemoveTabs adding />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash}
content={() => (
<ConfirmationModalContent
title={noLiquidity ? 'You are creating a pool' : 'You will receive'}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<CardBody>
<AutoColumn gap="20px">
{noLiquidity && (
<ColumnCenter>
<Pane>
<AutoColumn gap="12px">
<UIKitText>You are the first liquidity provider.</UIKitText>
<UIKitText>The ratio of tokens you add will set the price of this pool.</UIKitText>
<UIKitText>Once you are happy with the rate click supply to review.</UIKitText>
</AutoColumn>
</Pane>
</ColumnCenter>
)}
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
onMax={() => {
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}}
onCurrencySelect={handleCurrencyASelect}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A]}
id="add-liquidity-input-tokena"
showCommonBases={false}
/>
<ColumnCenter>
<AddIcon color="textSubtle" />
</ColumnCenter>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onFieldBInput}
onCurrencySelect={handleCurrencyBSelect}
onMax={() => {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
currency={currencies[Field.CURRENCY_B]}
id="add-liquidity-input-tokenb"
showCommonBases={false}
/>
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<div>
<UIKitText
style={{ textTransform: 'uppercase', fontWeight: 600 }}
color="textSubtle"
fontSize="12px"
mb="2px"
>
{noLiquidity ? 'Initial prices and pool share' : 'Prices and pool share'}
</UIKitText>
<Pane>
<PoolPriceBar
currencies={currencies}
poolTokenPercentage={poolTokenPercentage}
noLiquidity={noLiquidity}
price={price}
/>
</Pane>
</div>
)}
{!account ? (
<ConnectWalletButton fullWidth />
) : (
<AutoColumn gap="md">
{(approvalA === ApprovalState.NOT_APPROVED ||
approvalA === ApprovalState.PENDING ||
approvalB === ApprovalState.NOT_APPROVED ||
approvalB === ApprovalState.PENDING) &&
isValid && (
<RowBetween>
{approvalA !== ApprovalState.APPROVED && (
<Button
onClick={approveACallback}
disabled={approvalA === ApprovalState.PENDING}
style={{ width: approvalB !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalA === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_A]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_A]?.symbol}`
)}
</Button>
)}
{approvalB !== ApprovalState.APPROVED && (
<Button
onClick={approveBCallback}
disabled={approvalB === ApprovalState.PENDING}
style={{ width: approvalA !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalB === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_B]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_B]?.symbol}`
)}
</Button>
)}
</RowBetween>
)}
<Button
onClick={() => {
if (expertMode) {
onAdd()
} else {
setShowConfirm(true)
}
}}
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
fullWidth
>
{error ?? 'Supply'}
</Button>
</AutoColumn>
)}
</AutoColumn>
</CardBody>
</Wrapper>
</AppBody>
{pair && !noLiquidity && pairState !== PairState.INVALID ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
</AutoColumn>
) : null}
</>
)
}
Example #26
Source File: SlippageToleranceSetting.tsx From goose-frontend-amm with GNU General Public License v3.0 | 4 votes |
SlippageToleranceSettings = () => {
const [userSlippageTolerance, setUserslippageTolerance] = useUserSlippageTolerance()
const [value, setValue] = useState(userSlippageTolerance / 100)
const [error, setError] = useState<string | null>(null)
const handleChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
const { value: inputValue } = evt.target
setValue(parseFloat(inputValue))
}
// Updates local storage if value is valid
useEffect(() => {
try {
const rawValue = value * 100
if (!Number.isNaN(rawValue) && rawValue > 0 && rawValue < MAX_SLIPPAGE) {
setUserslippageTolerance(rawValue)
setError(null)
} else {
setError('Enter a valid slippage percentage')
}
} catch {
setError('Enter a valid slippage percentage')
}
}, [value, setError, setUserslippageTolerance])
// Notify user if slippage is risky
useEffect(() => {
if (userSlippageTolerance < RISKY_SLIPPAGE_LOW) {
setError('Your transaction may fail')
} else if (userSlippageTolerance > RISKY_SLIPPAGE_HIGH) {
setError('Your transaction may be frontrun')
}
}, [userSlippageTolerance, setError])
return (
<StyledSlippageToleranceSettings>
<Label>
<Text style={{ fontWeight: 600 }}>
<TranslatedText translationId={88}>Slippage tolerance</TranslatedText>
</Text>
<QuestionHelper text="Your transaction will revert if the price changes unfavorably by more than this percentage." />
</Label>
<Options>
<Flex mb={['8px', 0]} mr={[0, '8px']}>
{predefinedValues.map(({ label, value: predefinedValue }) => {
const handleClick = () => setValue(predefinedValue)
return (
<Option key={predefinedValue}>
<Button variant={value === predefinedValue ? 'primary' : 'tertiary'} onClick={handleClick}>
{label}
</Button>
</Option>
)
})}
</Flex>
<Flex alignItems="center">
<Option>
<Input
type="number"
scale="lg"
step={0.1}
min={0.1}
placeholder="5%"
value={value}
onChange={handleChange}
isWarning={error !== null}
/>
</Option>
<Option>
<Text fontSize="18px">%</Text>
</Option>
</Flex>
</Options>
{error && (
<Text mt="8px" color="failure">
{error}
</Text>
)}
</StyledSlippageToleranceSettings>
)
}
Example #27
Source File: TransactionSettings.tsx From glide-frontend with GNU General Public License v3.0 | 4 votes |
SlippageTabs = () => {
const [userSlippageTolerance, setUserslippageTolerance] = useUserSlippageTolerance()
const [ttl, setTtl] = useUserTransactionTTL()
const [slippageInput, setSlippageInput] = useState('')
const [deadlineInput, setDeadlineInput] = useState('')
const { t } = useTranslation()
const slippageInputIsValid =
slippageInput === '' || (userSlippageTolerance / 100).toFixed(2) === Number.parseFloat(slippageInput).toFixed(2)
const deadlineInputIsValid = deadlineInput === '' || (ttl / 60).toString() === deadlineInput
let slippageError: SlippageError | undefined
if (slippageInput !== '' && !slippageInputIsValid) {
slippageError = SlippageError.InvalidInput
} else if (slippageInputIsValid && userSlippageTolerance < 50) {
slippageError = SlippageError.RiskyLow
} else if (slippageInputIsValid && userSlippageTolerance > 500) {
slippageError = SlippageError.RiskyHigh
} else {
slippageError = undefined
}
let deadlineError: DeadlineError | undefined
if (deadlineInput !== '' && !deadlineInputIsValid) {
deadlineError = DeadlineError.InvalidInput
} else {
deadlineError = undefined
}
const parseCustomSlippage = (value: string) => {
setSlippageInput(value)
try {
const valueAsIntFromRoundedFloat = Number.parseInt((Number.parseFloat(value) * 100).toString())
if (!Number.isNaN(valueAsIntFromRoundedFloat) && valueAsIntFromRoundedFloat < 5000) {
setUserslippageTolerance(valueAsIntFromRoundedFloat)
}
} catch (error) {
console.error(error)
}
}
const parseCustomDeadline = (value: string) => {
setDeadlineInput(value)
try {
const valueAsInt: number = Number.parseInt(value) * 60
if (!Number.isNaN(valueAsInt) && valueAsInt > 0) {
setTtl(valueAsInt)
}
} catch (error) {
console.error(error)
}
}
return (
<Flex flexDirection="column">
<Flex flexDirection="column" mb="24px">
<Flex mb="12px">
<Text>{t('Slippage Tolerance')}</Text>
<QuestionHelper
text={t(
'Setting a high slippage tolerance can help transactions succeed, but you may not get such a good price. Use with caution.',
)}
ml="4px"
/>
</Flex>
<Flex flexWrap="wrap">
<Button
mt="4px"
mr="4px"
scale="sm"
onClick={() => {
setSlippageInput('')
setUserslippageTolerance(10)
}}
variant={userSlippageTolerance === 10 ? 'primary' : 'tertiary'}
>
0.1%
</Button>
<Button
mt="4px"
mr="4px"
scale="sm"
onClick={() => {
setSlippageInput('')
setUserslippageTolerance(50)
}}
variant={userSlippageTolerance === 50 ? 'primary' : 'tertiary'}
>
0.5%
</Button>
<Button
mr="4px"
mt="4px"
scale="sm"
onClick={() => {
setSlippageInput('')
setUserslippageTolerance(100)
}}
variant={userSlippageTolerance === 100 ? 'primary' : 'tertiary'}
>
1.0%
</Button>
<Flex alignItems="center">
<Box width="76px" mt="4px">
<Input
scale="sm"
placeholder={(userSlippageTolerance / 100).toFixed(2)}
value={slippageInput}
onBlur={() => {
parseCustomSlippage((userSlippageTolerance / 100).toFixed(2))
}}
onChange={(e) => parseCustomSlippage(e.target.value)}
isWarning={!slippageInputIsValid}
isSuccess={![10, 50, 100].includes(userSlippageTolerance)}
/>
</Box>
<Text color="primary" bold ml="2px">
%
</Text>
</Flex>
</Flex>
{!!slippageError && (
<Text fontSize="14px" color={slippageError === SlippageError.InvalidInput ? ' #d11576' : '#F3841E'} mt="8px">
{slippageError === SlippageError.InvalidInput
? t('Enter a valid slippage percentage')
: slippageError === SlippageError.RiskyLow
? t('Your transaction may fail')
: t('Your transaction may be frontrun')}
</Text>
)}
</Flex>
<Flex justifyContent="space-between" alignItems="center" mb="24px">
<Flex alignItems="center">
<Text>{t('Tx deadline (mins)')}</Text>
<QuestionHelper
text={t('Your transaction will revert if it is left confirming for longer than this time.')}
ml="4px"
/>
</Flex>
<Flex>
<Box width="52px" mt="4px">
<Input
scale="sm"
color={deadlineError ? ' #d11576' : undefined}
onBlur={() => {
parseCustomDeadline((ttl / 60).toString())
}}
placeholder={(ttl / 60).toString()}
value={deadlineInput}
onChange={(e) => parseCustomDeadline(e.target.value)}
/>
</Box>
</Flex>
</Flex>
</Flex>
)
}
Example #28
Source File: hooks.ts From interface-v2 with GNU General Public License v3.0 | 4 votes |
// from the current swap inputs, compute the best trade and return it.
export function useDerivedSwapInfo(): {
currencies: { [field in Field]?: Currency };
currencyBalances: { [field in Field]?: CurrencyAmount };
parsedAmount: CurrencyAmount | undefined;
v2Trade: Trade | undefined;
inputError?: string;
v1Trade: Trade | undefined;
} {
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: string | null = (recipient === null ? account : recipient) ?? null;
const relevantTokenBalances = useCurrencyBalances(account ?? undefined, [
inputCurrency ?? undefined,
outputCurrency ?? undefined,
]);
const isExactIn: boolean = 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 in Field]?: Currency } = {
[Field.INPUT]: inputCurrency ?? undefined,
[Field.OUTPUT]: outputCurrency ?? undefined,
};
let inputError: string | undefined;
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);
// compare input balance to max input based on version
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,
v1Trade: undefined,
};
}
Example #29
Source File: Swap.tsx From interface-v2 with GNU General Public License v3.0 | 4 votes |
Swap: React.FC<{
currency0?: Currency;
currency1?: Currency;
currencyBg?: string;
}> = ({ currency0, currency1, currencyBg }) => {
const { account } = useActiveWeb3React();
const { independentField, typedValue, recipient } = useSwapState();
const {
v1Trade,
v2Trade,
currencyBalances,
parsedAmount,
currencies,
inputError: swapInputError,
} = useDerivedSwapInfo();
const toggledVersion = useToggledVersion();
const finalizedTransaction = useTransactionFinalizer();
const [isExpertMode] = useExpertModeManager();
const {
wrapType,
execute: onWrap,
inputError: wrapInputError,
} = useWrapCallback(
currencies[Field.INPUT],
currencies[Field.OUTPUT],
typedValue,
);
const allTokens = useAllTokens();
const showWrap: boolean = wrapType !== WrapType.NOT_APPLICABLE;
const tradesByVersion = {
[Version.v1]: v1Trade,
[Version.v2]: v2Trade,
};
const trade = showWrap ? undefined : tradesByVersion[toggledVersion];
const {
onSwitchTokens,
onCurrencySelection,
onUserInput,
onChangeRecipient,
} = useSwapActionHandlers();
const { address: recipientAddress } = useENSAddress(recipient);
const [allowedSlippage] = useUserSlippageTolerance();
const [approving, setApproving] = useState(false);
const [approval, approveCallback] = useApproveCallbackFromTrade(
trade,
allowedSlippage,
);
const dependentField: Field =
independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT;
const parsedAmounts = useMemo(() => {
return showWrap
? {
[Field.INPUT]: parsedAmount,
[Field.OUTPUT]: parsedAmount,
}
: {
[Field.INPUT]:
independentField === Field.INPUT
? parsedAmount
: trade?.inputAmount,
[Field.OUTPUT]:
independentField === Field.OUTPUT
? parsedAmount
: trade?.outputAmount,
};
}, [parsedAmount, independentField, trade, showWrap]);
const formattedAmounts = useMemo(() => {
return {
[independentField]: typedValue,
[dependentField]: showWrap
? parsedAmounts[independentField]?.toExact() ?? ''
: parsedAmounts[dependentField]?.toExact() ?? '',
};
}, [independentField, typedValue, dependentField, showWrap, parsedAmounts]);
const route = trade?.route;
const userHasSpecifiedInputOutput = Boolean(
currencies[Field.INPUT] &&
currencies[Field.OUTPUT] &&
parsedAmounts[independentField]?.greaterThan(JSBI.BigInt(0)),
);
const noRoute = !route;
const { priceImpactWithoutFee } = computeTradePriceBreakdown(trade);
const [approvalSubmitted, setApprovalSubmitted] = useState<boolean>(false);
const { ethereum } = window as any;
const [mainPrice, setMainPrice] = useState(true);
const priceImpactSeverity = warningSeverity(priceImpactWithoutFee);
const isValid = !swapInputError;
const showApproveFlow =
!swapInputError &&
(approval === ApprovalState.NOT_APPROVED ||
approval === ApprovalState.PENDING ||
(approvalSubmitted && approval === ApprovalState.APPROVED)) &&
!(priceImpactSeverity > 3 && !isExpertMode);
const classes = useStyles({ showApproveFlow });
const toggleWalletModal = useWalletModalToggle();
useEffect(() => {
if (approval === ApprovalState.PENDING) {
setApprovalSubmitted(true);
}
}, [approval, approvalSubmitted]);
const connectWallet = () => {
if (ethereum && !isSupportedNetwork(ethereum)) {
addMaticToMetamask();
} else {
toggleWalletModal();
}
};
const handleCurrencySelect = useCallback(
(inputCurrency) => {
setApprovalSubmitted(false); // reset 2 step UI for approvals
onCurrencySelection(Field.INPUT, inputCurrency);
},
[onCurrencySelection],
);
const handleOtherCurrencySelect = useCallback(
(outputCurrency) => onCurrencySelection(Field.OUTPUT, outputCurrency),
[onCurrencySelection],
);
const { callback: swapCallback, error: swapCallbackError } = useSwapCallback(
trade,
allowedSlippage,
recipient,
);
const swapButtonText = useMemo(() => {
if (account) {
if (!currencies[Field.INPUT] || !currencies[Field.OUTPUT]) {
return 'Select a token';
} else if (
formattedAmounts[Field.INPUT] === '' &&
formattedAmounts[Field.OUTPUT] === ''
) {
return 'Enter Amount';
} else if (showWrap) {
return wrapType === WrapType.WRAP
? 'Wrap'
: wrapType === WrapType.UNWRAP
? 'UnWrap'
: '';
} else if (noRoute && userHasSpecifiedInputOutput) {
return 'Insufficient liquidity for this trade.';
} else {
return swapInputError ?? 'Swap';
}
} else {
return ethereum && !isSupportedNetwork(ethereum)
? 'Switch to Polygon'
: 'Connect Wallet';
}
}, [
formattedAmounts,
currencies,
account,
ethereum,
noRoute,
userHasSpecifiedInputOutput,
showWrap,
wrapType,
swapInputError,
]);
const swapButtonDisabled = useMemo(() => {
if (account) {
if (showWrap) {
return Boolean(wrapInputError);
} else if (noRoute && userHasSpecifiedInputOutput) {
return true;
} else if (showApproveFlow) {
return (
!isValid ||
approval !== ApprovalState.APPROVED ||
(priceImpactSeverity > 3 && !isExpertMode)
);
} else {
return (
!isValid ||
(priceImpactSeverity > 3 && !isExpertMode) ||
!!swapCallbackError
);
}
} else {
return false;
}
}, [
account,
showWrap,
wrapInputError,
noRoute,
userHasSpecifiedInputOutput,
showApproveFlow,
approval,
priceImpactSeverity,
isValid,
swapCallbackError,
isExpertMode,
]);
const [
{
showConfirm,
txPending,
tradeToConfirm,
swapErrorMessage,
attemptingTxn,
txHash,
},
setSwapState,
] = useState<{
showConfirm: boolean;
txPending?: boolean;
tradeToConfirm: Trade | undefined;
attemptingTxn: boolean;
swapErrorMessage: string | undefined;
txHash: string | undefined;
}>({
showConfirm: false,
txPending: false,
tradeToConfirm: undefined,
attemptingTxn: false,
swapErrorMessage: undefined,
txHash: undefined,
});
const handleTypeInput = useCallback(
(value: string) => {
onUserInput(Field.INPUT, value);
},
[onUserInput],
);
const handleTypeOutput = useCallback(
(value: string) => {
onUserInput(Field.OUTPUT, value);
},
[onUserInput],
);
const maxAmountInput: CurrencyAmount | undefined = maxAmountSpend(
currencyBalances[Field.INPUT],
);
const halfAmountInput: CurrencyAmount | undefined = halfAmountSpend(
currencyBalances[Field.INPUT],
);
const handleMaxInput = useCallback(() => {
maxAmountInput && onUserInput(Field.INPUT, maxAmountInput.toExact());
}, [maxAmountInput, onUserInput]);
const handleHalfInput = useCallback(() => {
halfAmountInput && onUserInput(Field.INPUT, halfAmountInput.toExact());
}, [halfAmountInput, onUserInput]);
const atMaxAmountInput = Boolean(
maxAmountInput && parsedAmounts[Field.INPUT]?.equalTo(maxAmountInput),
);
const onSwap = () => {
if (showWrap && onWrap) {
onWrap();
} else if (isExpertMode) {
handleSwap();
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined,
});
}
};
useEffect(() => {
onCurrencySelection(Field.INPUT, Token.ETHER);
}, [onCurrencySelection, allTokens]);
useEffect(() => {
if (currency0) {
onCurrencySelection(Field.INPUT, currency0);
}
if (currency1) {
onCurrencySelection(Field.OUTPUT, currency1);
}
}, [onCurrencySelection, currency0, currency1]);
const handleAcceptChanges = useCallback(() => {
setSwapState({
tradeToConfirm: trade,
swapErrorMessage,
txHash,
attemptingTxn,
showConfirm,
});
}, [attemptingTxn, showConfirm, swapErrorMessage, trade, txHash]);
const handleConfirmDismiss = useCallback(() => {
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, tradeToConfirm, txHash]);
const handleSwap = useCallback(() => {
if (
priceImpactWithoutFee &&
!confirmPriceImpactWithoutFee(priceImpactWithoutFee)
) {
return;
}
if (!swapCallback) {
return;
}
setSwapState({
attemptingTxn: true,
tradeToConfirm,
showConfirm,
swapErrorMessage: undefined,
txHash: undefined,
});
swapCallback()
.then(async ({ response, summary }) => {
setSwapState({
attemptingTxn: false,
txPending: true,
tradeToConfirm,
showConfirm,
swapErrorMessage: undefined,
txHash: response.hash,
});
try {
const receipt = await response.wait();
finalizedTransaction(receipt, {
summary,
});
setSwapState({
attemptingTxn: false,
txPending: false,
tradeToConfirm,
showConfirm,
swapErrorMessage: undefined,
txHash: response.hash,
});
ReactGA.event({
category: 'Swap',
action:
recipient === null
? 'Swap w/o Send'
: (recipientAddress ?? recipient) === account
? 'Swap w/o Send + recipient'
: 'Swap w/ Send',
label: [
trade?.inputAmount?.currency?.symbol,
trade?.outputAmount?.currency?.symbol,
].join('/'),
});
} catch (error) {
setSwapState({
attemptingTxn: false,
tradeToConfirm,
showConfirm,
swapErrorMessage: (error as any).message,
txHash: undefined,
});
}
})
.catch((error) => {
setSwapState({
attemptingTxn: false,
tradeToConfirm,
showConfirm,
swapErrorMessage: error.message,
txHash: undefined,
});
});
}, [
tradeToConfirm,
account,
priceImpactWithoutFee,
recipient,
recipientAddress,
showConfirm,
swapCallback,
finalizedTransaction,
trade,
]);
return (
<Box>
{showConfirm && (
<ConfirmSwapModal
isOpen={showConfirm}
trade={trade}
originalTrade={tradeToConfirm}
onAcceptChanges={handleAcceptChanges}
attemptingTxn={attemptingTxn}
txPending={txPending}
txHash={txHash}
recipient={recipient}
allowedSlippage={allowedSlippage}
onConfirm={handleSwap}
swapErrorMessage={swapErrorMessage}
onDismiss={handleConfirmDismiss}
/>
)}
<CurrencyInput
title='From:'
id='swap-currency-input'
currency={currencies[Field.INPUT]}
onHalf={handleHalfInput}
onMax={handleMaxInput}
showHalfButton={true}
showMaxButton={!atMaxAmountInput}
otherCurrency={currencies[Field.OUTPUT]}
handleCurrencySelect={handleCurrencySelect}
amount={formattedAmounts[Field.INPUT]}
setAmount={handleTypeInput}
bgColor={currencyBg}
/>
<Box className={classes.exchangeSwap}>
<ExchangeIcon onClick={onSwitchTokens} />
</Box>
<CurrencyInput
title='To (estimate):'
id='swap-currency-output'
currency={currencies[Field.OUTPUT]}
showPrice={Boolean(trade && trade.executionPrice)}
showMaxButton={false}
otherCurrency={currencies[Field.INPUT]}
handleCurrencySelect={handleOtherCurrencySelect}
amount={formattedAmounts[Field.OUTPUT]}
setAmount={handleTypeOutput}
bgColor={currencyBg}
/>
{trade && trade.executionPrice && (
<Box className={classes.swapPrice}>
<Typography variant='body2'>Price:</Typography>
<Typography variant='body2'>
1{' '}
{
(mainPrice ? currencies[Field.INPUT] : currencies[Field.OUTPUT])
?.symbol
}{' '}
={' '}
{(mainPrice
? trade.executionPrice
: trade.executionPrice.invert()
).toSignificant(6)}{' '}
{
(mainPrice ? currencies[Field.OUTPUT] : currencies[Field.INPUT])
?.symbol
}{' '}
<PriceExchangeIcon
onClick={() => {
setMainPrice(!mainPrice);
}}
/>
</Typography>
</Box>
)}
{!showWrap && isExpertMode && (
<Box className={classes.recipientInput}>
<Box className='header'>
{recipient !== null ? (
<ArrowDown size='16' color='white' />
) : (
<Box />
)}
<Button
onClick={() => onChangeRecipient(recipient !== null ? null : '')}
>
{recipient !== null ? '- Remove send' : '+ Add a send (optional)'}
</Button>
</Box>
{recipient !== null && (
<AddressInput
label='Recipient'
placeholder='Wallet Address or ENS name'
value={recipient}
onChange={onChangeRecipient}
/>
)}
</Box>
)}
<AdvancedSwapDetails trade={trade} />
<Box className={classes.swapButtonWrapper}>
{showApproveFlow && (
<Button
color='primary'
disabled={
approving ||
approval !== ApprovalState.NOT_APPROVED ||
approvalSubmitted
}
onClick={async () => {
setApproving(true);
try {
await approveCallback();
setApproving(false);
} catch (err) {
setApproving(false);
}
}}
>
{approval === ApprovalState.PENDING ? (
<Box className='content'>
Approving <CircularProgress size={16} />
</Box>
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
'Approved'
) : (
'Approve ' + currencies[Field.INPUT]?.symbol
)}
</Button>
)}
<Button
disabled={swapButtonDisabled as boolean}
onClick={account ? onSwap : connectWallet}
>
{swapButtonText}
</Button>
</Box>
</Box>
);
}