react-feather#ArrowDown TypeScript Examples
The following examples show how to use
react-feather#ArrowDown.
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: LandingPage.tsx From gateway-ui with BSD 3-Clause "New" or "Revised" License | 5 votes |
LandingPage = (): ReactElement => {
const classes = useStyles()
const navigate = useNavigate()
return (
<Layout
top={[
<Header key="top1">
<Logo />
</Header>,
<Typography key="top2" variant="subtitle1">
{text.landingPage.tagline}
</Typography>,
]}
center={[
<Button
key="center1"
className={classes.button}
size="large"
variant="contained"
onClick={() => navigate(ROUTES.SHARE)}
>
<ArrowUp strokeWidth={1} />
{text.landingPage.shareAction}
{/* Needed to properly align icon to the right and label to center */}
<ArrowUp style={{ opacity: 0 }} />
</Button>,
<Button
key="center2"
className={classes.button}
size="large"
variant="contained"
onClick={() => navigate(ROUTES.ACCESS)}
>
<ArrowDown strokeWidth={1} />
{text.landingPage.accessAction}
{/* Needed to properly align icon to the right and label to center */}
<ArrowDown style={{ opacity: 0 }} />
</Button>,
]}
bottom={[
<Typography key="bottom1" variant="subtitle2">
{text.landingPage.disclaimer}
</Typography>,
<Divider key="bottom2" variant="middle" />,
<small key="bottom3" className={classes.spread}>
{text.landingPage.links.map(({ label, link, internal }) => {
let action: { href: string } | { onClick: () => void } = { href: link }
if (internal) action = { onClick: () => navigate(link) }
return (
<Link
key={label}
className={classes.spreadItems}
color="inherit"
underline="always"
target="blank"
{...action}
>
{label}
</Link>
)
})}
</small>,
]}
/>
)
}
Example #2
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 #3
Source File: SwapModalHeader.tsx From pancake-swap-exchange-testnet with GNU General Public License v3.0 | 4 votes |
export default function SwapModalHeader({
trade,
allowedSlippage,
recipient,
showAcceptChanges,
onAcceptChanges,
}: {
trade: Trade
allowedSlippage: number
recipient: string | null
showAcceptChanges: boolean
onAcceptChanges: () => void
}) {
const slippageAdjustedAmounts = useMemo(() => computeSlippageAdjustedAmounts(trade, allowedSlippage), [
trade,
allowedSlippage,
])
const { priceImpactWithoutFee } = useMemo(() => computeTradePriceBreakdown(trade), [trade])
const priceImpactSeverity = warningSeverity(priceImpactWithoutFee)
const theme = useContext(ThemeContext)
return (
<AutoColumn gap="md" style={{ marginTop: '20px' }}>
<RowBetween align="flex-end">
<RowFixed gap="0px">
<CurrencyLogo currency={trade.inputAmount.currency} size="24px" style={{ marginRight: '12px' }} />
<Text
fontSize="24px"
color={showAcceptChanges && trade.tradeType === TradeType.EXACT_OUTPUT ? theme.colors.primary : 'text'}
>
{trade.inputAmount.toSignificant(6)}
</Text>
</RowFixed>
<RowFixed gap="0px">
<Text fontSize="24px" style={{ marginLeft: '10px', fontWeight: 500 }}>
{trade.inputAmount.currency.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<ArrowDown size="16" color={theme.colors.textSubtle} style={{ marginLeft: '4px', minWidth: '16px' }} />
</RowFixed>
<RowBetween align="flex-end">
<RowFixed gap="0px">
<CurrencyLogo currency={trade.outputAmount.currency} size="24px" style={{ marginRight: '12px' }} />
<Text
fontSize="24px"
style={{ marginLeft: '10px', fontWeight: 500 }}
color={
priceImpactSeverity > 2
? theme.colors.failure
: showAcceptChanges && trade.tradeType === TradeType.EXACT_INPUT
? theme.colors.primary
: 'text'
}
>
{trade.outputAmount.toSignificant(6)}
</Text>
</RowFixed>
<RowFixed gap="0px">
<Text fontSize="24px" style={{ marginLeft: '10px', fontWeight: 500 }}>
{trade.outputAmount.currency.symbol}
</Text>
</RowFixed>
</RowBetween>
{showAcceptChanges ? (
<SwapShowAcceptChanges justify="flex-start" gap="0px">
<RowBetween>
<RowFixed>
<AlertTriangle size={20} style={{ marginRight: '8px', minWidth: 24 }} />
<Text color="primary"> Price Updated</Text>
</RowFixed>
<Button onClick={onAcceptChanges}>Accept</Button>
</RowBetween>
</SwapShowAcceptChanges>
) : null}
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '16px 0 0' }}>
{trade.tradeType === TradeType.EXACT_INPUT ? (
<PriceInfoText>
{`Output is estimated. You will receive at least `}
<span>
{slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {trade.outputAmount.currency.symbol}
</span>
{' or the transaction will revert.'}
</PriceInfoText>
) : (
<PriceInfoText>
{`Input is estimated. You will sell at most `}
<span>
{slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {trade.inputAmount.currency.symbol}
</span>
{' or the transaction will revert.'}
</PriceInfoText>
)}
</AutoColumn>
{recipient !== null ? (
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '16px 0 0' }}>
<Text>
Output will be sent to{' '}
<b title={recipient}>{isAddress(recipient) ? shortenAddress(recipient) : recipient}</b>
</Text>
</AutoColumn>
) : null}
</AutoColumn>
)
}
Example #4
Source File: index.tsx From pancake-swap-exchange-testnet with GNU General Public License v3.0 | 4 votes |
export default function RemoveLiquidity({
history,
match: {
params: { currencyIdA, currencyIdB },
},
}: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
const [currencyA, currencyB] = [useCurrency(currencyIdA) ?? undefined, useCurrency(currencyIdB) ?? undefined]
const { account, chainId, library } = useActiveWeb3React()
const TranslateString = useI18n()
const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
currencyA,
currencyB,
chainId,
])
const theme = useContext(ThemeContext)
// burn state
const { independentField, typedValue } = useBurnState()
const { pair, parsedAmounts, error } = useDerivedBurnInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onUserInput: _onUserInput } = useBurnActionHandlers()
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [showDetailed, setShowDetailed] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirm
// txn values
const [txHash, setTxHash] = useState<string>('')
const [deadline] = useUserDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
const formattedAmounts = {
[Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo('0')
? '0'
: parsedAmounts[Field.LIQUIDITY_PERCENT].lessThan(new Percent('1', '100'))
? '<1'
: parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0),
[Field.LIQUIDITY]:
independentField === Field.LIQUIDITY ? typedValue : parsedAmounts[Field.LIQUIDITY]?.toSignificant(6) ?? '',
[Field.CURRENCY_A]:
independentField === Field.CURRENCY_A ? typedValue : parsedAmounts[Field.CURRENCY_A]?.toSignificant(6) ?? '',
[Field.CURRENCY_B]:
independentField === Field.CURRENCY_B ? typedValue : parsedAmounts[Field.CURRENCY_B]?.toSignificant(6) ?? '',
}
const atMaxAmount = parsedAmounts[Field.LIQUIDITY_PERCENT]?.equalTo(new Percent('1'))
// pair contract
const pairContract: Contract | null = usePairContract(pair?.liquidityToken?.address)
// allowance handling
const [signatureData, setSignatureData] = useState<{ v: number; r: string; s: string; deadline: number } | null>(null)
const [approval, approveCallback] = useApproveCallback(parsedAmounts[Field.LIQUIDITY], ROUTER_ADDRESS)
async function onAttemptToApprove() {
if (!pairContract || !pair || !library) throw new Error('missing dependencies')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
// try to gather a signature for permission
const nonce = await pairContract.nonces(account)
const deadlineForSignature: number = Math.ceil(Date.now() / 1000) + deadline
const EIP712Domain = [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
]
const domain = {
name: 'Pancake LPs',
version: '1',
chainId,
verifyingContract: pair.liquidityToken.address,
}
const Permit = [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
]
const message = {
owner: account,
spender: ROUTER_ADDRESS,
value: liquidityAmount.raw.toString(),
nonce: nonce.toHexString(),
deadline: deadlineForSignature,
}
const data = JSON.stringify({
types: {
EIP712Domain,
Permit,
},
domain,
primaryType: 'Permit',
message,
})
library
.send('eth_signTypedData_v4', [account, data])
.then(splitSignature)
.then((signature) => {
setSignatureData({
v: signature.v,
r: signature.r,
s: signature.s,
deadline: deadlineForSignature,
})
})
.catch((e) => {
// for all errors other than 4001 (EIP-1193 user rejected request), fall back to manual approve
if (e?.code !== 4001) {
approveCallback()
}
})
}
// wrapped onUserInput to clear signatures
const onUserInput = useCallback(
(field: Field, val: string) => {
setSignatureData(null)
return _onUserInput(field, val)
},
[_onUserInput]
)
const onLiquidityInput = useCallback((val: string): void => onUserInput(Field.LIQUIDITY, val), [onUserInput])
const onCurrencyAInput = useCallback((val: string): void => onUserInput(Field.CURRENCY_A, val), [onUserInput])
const onCurrencyBInput = useCallback((val: string): void => onUserInput(Field.CURRENCY_B, val), [onUserInput])
// tx sending
const addTransaction = useTransactionAdder()
async function onRemove() {
if (!chainId || !library || !account) throw new Error('missing dependencies')
const { [Field.CURRENCY_A]: currencyAmountA, [Field.CURRENCY_B]: currencyAmountB } = parsedAmounts
if (!currencyAmountA || !currencyAmountB) {
throw new Error('missing currency amounts')
}
const router = getRouterContract(chainId, library, account)
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(currencyAmountA, allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(currencyAmountB, allowedSlippage)[0],
}
if (!currencyA || !currencyB) throw new Error('missing tokens')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
const currencyBIsETH = currencyB === ETHER
const oneCurrencyIsETH = currencyA === ETHER || currencyBIsETH
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
if (!tokenA || !tokenB) throw new Error('could not wrap')
let methodNames: string[]
let args: Array<string | string[] | number | boolean>
// we have approval, use normal remove liquidity
if (approval === ApprovalState.APPROVED) {
// removeLiquidityETH
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETH', 'removeLiquidityETHSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
deadlineFromNow,
]
}
// removeLiquidity
else {
methodNames = ['removeLiquidity']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
}
}
// we have a signataure, use permit versions of remove liquidity
else if (signatureData !== null) {
// removeLiquidityETHWithPermit
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETHWithPermit', 'removeLiquidityETHWithPermitSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s,
]
}
// removeLiquidityETHWithPermit
else {
methodNames = ['removeLiquidityWithPermit']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s,
]
}
} else {
throw new Error('Attempting to confirm without approval or a signature. Please contact support.')
}
const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
methodNames.map((methodName, index) =>
router.estimateGas[methodName](...args)
.then(calculateGasMargin)
.catch((e) => {
console.error(`estimateGas failed`, index, methodName, args, e)
return undefined
})
)
)
const indexOfSuccessfulEstimation = safeGasEstimates.findIndex((safeGasEstimate) =>
BigNumber.isBigNumber(safeGasEstimate)
)
// all estimations failed...
if (indexOfSuccessfulEstimation === -1) {
console.error('This transaction would fail. Please contact support.')
} else {
const methodName = methodNames[indexOfSuccessfulEstimation]
const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation]
setAttemptingTxn(true)
await router[methodName](...args, {
gasLimit: safeGasEstimate,
})
.then((response: TransactionResponse) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Remove ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencyA?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencyB?.symbol}`,
})
setTxHash(response.hash)
})
.catch((e: Error) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
console.error(e)
})
}
}
function modalHeader() {
return (
<AutoColumn gap="md" style={{ marginTop: '20px' }}>
<RowBetween align="flex-end">
<Text fontSize="24px">{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyA} size="24px" />
<Text fontSize="24px" style={{ marginLeft: '10px' }}>
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<Plus size="16" color={theme.colors.textSubtle} />
</RowFixed>
<RowBetween align="flex-end">
<Text fontSize="24px">{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyB} size="24px" />
<Text fontSize="24px" style={{ marginLeft: '10px' }}>
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
<Text small color="textSubtle" textAlign="left" padding="12px 0 0 0" style={{ fontStyle: 'italic' }}>
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</Text>
</AutoColumn>
)
}
function modalBottom() {
return (
<>
<RowBetween>
<Text color="textSubtle">{`LP ${currencyA?.symbol}/${currencyB?.symbol}`} Burned</Text>
<RowFixed>
<DoubleCurrencyLogo currency0={currencyA} currency1={currencyB} margin />
<Text>{parsedAmounts[Field.LIQUIDITY]?.toSignificant(6)}</Text>
</RowFixed>
</RowBetween>
{pair && (
<>
<RowBetween>
<Text color="textSubtle">{TranslateString(1182, 'Price')}</Text>
<Text>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</Text>
</RowBetween>
<RowBetween>
<div />
<Text>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</Text>
</RowBetween>
</>
)}
<Button disabled={!(approval === ApprovalState.APPROVED || signatureData !== null)} onClick={onRemove}>
{TranslateString(1136, 'Confirm')}
</Button>
</>
)
}
const pendingText = `Removing ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencyA?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencyB?.symbol}`
const liquidityPercentChangeCallback = useCallback(
(value: number) => {
onUserInput(Field.LIQUIDITY_PERCENT, value.toString())
},
[onUserInput]
)
const oneCurrencyIsETH = currencyA === ETHER || currencyB === ETHER
const oneCurrencyIsWETH = Boolean(
chainId &&
((currencyA && currencyEquals(WETH[chainId], currencyA)) ||
(currencyB && currencyEquals(WETH[chainId], currencyB)))
)
const handleSelectCurrencyA = useCallback(
(currency: Currency) => {
if (currencyIdB && currencyId(currency) === currencyIdB) {
history.push(`/remove/${currencyId(currency)}/${currencyIdA}`)
} else {
history.push(`/remove/${currencyId(currency)}/${currencyIdB}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleSelectCurrencyB = useCallback(
(currency: Currency) => {
if (currencyIdA && currencyId(currency) === currencyIdA) {
history.push(`/remove/${currencyIdB}/${currencyId(currency)}`)
} else {
history.push(`/remove/${currencyIdA}/${currencyId(currency)}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
setSignatureData(null) // important that we clear signature data to avoid bad sigs
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.LIQUIDITY_PERCENT, '0')
}
setTxHash('')
}, [onUserInput, txHash])
const [innerLiquidityPercentage, setInnerLiquidityPercentage] = useDebouncedChangeHandler(
Number.parseInt(parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0)),
liquidityPercentChangeCallback
)
return (
<Container>
<AppBody>
<AddRemoveTabs adding={false} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash || ''}
content={() => (
<ConfirmationModalContent
title={TranslateString(1156, 'You will receive')}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<AutoColumn gap="md">
<Body>
<OutlineCard>
<AutoColumn>
<RowBetween>
<Text>Amount</Text>
<ClickableText
onClick={() => {
setShowDetailed(!showDetailed)
}}
>
{showDetailed ? TranslateString(1184, 'Simple') : TranslateString(1186, 'Detailed')}
</ClickableText>
</RowBetween>
<Flex justifyContent="start">
<Text fontSize="64px">{formattedAmounts[Field.LIQUIDITY_PERCENT]}%</Text>
</Flex>
{!showDetailed && (
<>
<Flex mb="8px">
<Slider value={innerLiquidityPercentage} onChange={setInnerLiquidityPercentage} />
</Flex>
<Flex justifyContent="space-around">
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '25')}
>
25%
</Button>
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '50')}
>
50%
</Button>
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '75')}
>
75%
</Button>
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
>
{TranslateString(166, 'Max')}
</Button>
</Flex>
</>
)}
</AutoColumn>
</OutlineCard>
</Body>
{!showDetailed && (
<>
<ColumnCenter>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<Body>
<OutlineCard>
<AutoColumn gap="10px">
<RowBetween>
<Text fontSize="24px">{formattedAmounts[Field.CURRENCY_A] || '-'}</Text>
<RowFixed>
<CurrencyLogo currency={currencyA} style={{ marginRight: '12px' }} />
<Text fontSize="24px" id="remove-liquidity-tokena-symbol">
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowBetween>
<Text fontSize="24px">{formattedAmounts[Field.CURRENCY_B] || '-'}</Text>
<RowFixed>
<CurrencyLogo currency={currencyB} style={{ marginRight: '12px' }} />
<Text fontSize="24px" id="remove-liquidity-tokenb-symbol">
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
{chainId && (oneCurrencyIsWETH || oneCurrencyIsETH) ? (
<RowBetween style={{ justifyContent: 'flex-end' }}>
{oneCurrencyIsETH ? (
<StyledInternalLink
to={`/remove/${currencyA === ETHER ? WETH[chainId].address : currencyIdA}/${
currencyB === ETHER ? WETH[chainId].address : currencyIdB
}`}
>
{TranslateString(1188, 'Receive WBNB')}
</StyledInternalLink>
) : oneCurrencyIsWETH ? (
<StyledInternalLink
to={`/remove/${
currencyA && currencyEquals(currencyA, WETH[chainId]) ? 'BNB' : currencyIdA
}/${currencyB && currencyEquals(currencyB, WETH[chainId]) ? 'BNB' : currencyIdB}`}
>
{TranslateString(1190, 'Receive BNB')}
</StyledInternalLink>
) : null}
</RowBetween>
) : null}
</AutoColumn>
</OutlineCard>
</Body>
</>
)}
<Body style={{ paddingBottom: '24px' }}>
{showDetailed && (
<>
<CurrencyInputPanel
value={formattedAmounts[Field.LIQUIDITY]}
onUserInput={onLiquidityInput}
onMax={() => {
onUserInput(Field.LIQUIDITY_PERCENT, '100')
}}
showMaxButton={!atMaxAmount}
disableCurrencySelect
currency={pair?.liquidityToken}
pair={pair}
id="liquidity-amount"
/>
<ColumnCenter>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onCurrencyAInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyA}
label="Output"
onCurrencySelect={handleSelectCurrencyA}
id="remove-liquidity-tokena"
/>
<ColumnCenter>
<Plus size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onCurrencyBInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyB}
label="Output"
onCurrencySelect={handleSelectCurrencyB}
id="remove-liquidity-tokenb"
/>
</>
)}
{pair && (
<div style={{ padding: '24px' }}>
<Flex justifyContent="space-between" mb="8px">
Price:
<div>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</div>
</Flex>
<Flex justifyContent="space-between">
<div />
<div>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</div>
</Flex>
</div>
)}
<div style={{ position: 'relative' }}>
{!account ? (
<ConnectWalletButton width="100%" />
) : (
<RowBetween>
<Button
onClick={onAttemptToApprove}
variant={approval === ApprovalState.APPROVED || signatureData !== null ? 'success' : 'primary'}
disabled={approval !== ApprovalState.NOT_APPROVED || signatureData !== null}
mr="8px"
>
{approval === ApprovalState.PENDING ? (
<Dots>Approving</Dots>
) : approval === ApprovalState.APPROVED || signatureData !== null ? (
'Approved'
) : (
'Approve'
)}
</Button>
<Button
onClick={() => {
setShowConfirm(true)
}}
disabled={!isValid || (signatureData === null && approval !== ApprovalState.APPROVED)}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
>
{error || 'Remove'}
</Button>
</RowBetween>
)}
</div>
</Body>
</AutoColumn>
</Wrapper>
</AppBody>
{pair ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
</AutoColumn>
) : null}
</Container>
)
}
Example #5
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 #6
Source File: SwapModalHeader.tsx From pancake-swap-testnet with MIT License | 4 votes |
export default function SwapModalHeader({
trade,
allowedSlippage,
recipient,
showAcceptChanges,
onAcceptChanges,
}: {
trade: Trade
allowedSlippage: number
recipient: string | null
showAcceptChanges: boolean
onAcceptChanges: () => void
}) {
const slippageAdjustedAmounts = useMemo(() => computeSlippageAdjustedAmounts(trade, allowedSlippage), [
trade,
allowedSlippage,
])
const { priceImpactWithoutFee } = useMemo(() => computeTradePriceBreakdown(trade), [trade])
const priceImpactSeverity = warningSeverity(priceImpactWithoutFee)
const theme = useContext(ThemeContext)
return (
<AutoColumn gap="md" style={{ marginTop: '20px' }}>
<RowBetween align="flex-end">
<RowFixed gap="0px">
<CurrencyLogo currency={trade.inputAmount.currency} size="24px" style={{ marginRight: '12px' }} />
<Text
fontSize="24px"
color={showAcceptChanges && trade.tradeType === TradeType.EXACT_OUTPUT ? theme.colors.primary : 'text'}
>
{trade.inputAmount.toSignificant(6)}
</Text>
</RowFixed>
<RowFixed gap="0px">
<Text fontSize="24px" style={{ marginLeft: '10px', fontWeight: 500 }}>
{trade.inputAmount.currency.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<ArrowDown size="16" color={theme.colors.textSubtle} style={{ marginLeft: '4px', minWidth: '16px' }} />
</RowFixed>
<RowBetween align="flex-end">
<RowFixed gap="0px">
<CurrencyLogo currency={trade.outputAmount.currency} size="24px" style={{ marginRight: '12px' }} />
<Text
fontSize="24px"
style={{ marginLeft: '10px', fontWeight: 500 }}
color={
priceImpactSeverity > 2
? theme.colors.failure
: showAcceptChanges && trade.tradeType === TradeType.EXACT_INPUT
? theme.colors.primary
: 'text'
}
>
{trade.outputAmount.toSignificant(6)}
</Text>
</RowFixed>
<RowFixed gap="0px">
<Text fontSize="24px" style={{ marginLeft: '10px', fontWeight: 500 }}>
{trade.outputAmount.currency.symbol}
</Text>
</RowFixed>
</RowBetween>
{showAcceptChanges ? (
<SwapShowAcceptChanges justify="flex-start" gap="0px">
<RowBetween>
<RowFixed>
<AlertTriangle size={20} style={{ marginRight: '8px', minWidth: 24 }} />
<Text color="primary"> Price Updated</Text>
</RowFixed>
<Button onClick={onAcceptChanges}>Accept</Button>
</RowBetween>
</SwapShowAcceptChanges>
) : null}
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '16px 0 0' }}>
{trade.tradeType === TradeType.EXACT_INPUT ? (
<PriceInfoText>
{`Output is estimated. You will receive at least `}
<span>
{slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {trade.outputAmount.currency.symbol}
</span>
{' or the transaction will revert.'}
</PriceInfoText>
) : (
<PriceInfoText>
{`Input is estimated. You will sell at most `}
<span>
{slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {trade.inputAmount.currency.symbol}
</span>
{' or the transaction will revert.'}
</PriceInfoText>
)}
</AutoColumn>
{recipient !== null ? (
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '16px 0 0' }}>
<Text>
Output will be sent to{' '}
<b title={recipient}>{isAddress(recipient) ? shortenAddress(recipient) : recipient}</b>
</Text>
</AutoColumn>
) : null}
</AutoColumn>
)
}
Example #7
Source File: index.tsx From pancake-swap-testnet with MIT License | 4 votes |
export default function RemoveLiquidity({
history,
match: {
params: { currencyIdA, currencyIdB },
},
}: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
const [currencyA, currencyB] = [useCurrency(currencyIdA) ?? undefined, useCurrency(currencyIdB) ?? undefined]
const { account, chainId, library } = useActiveWeb3React()
const TranslateString = useI18n()
const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
currencyA,
currencyB,
chainId,
])
const theme = useContext(ThemeContext)
// burn state
const { independentField, typedValue } = useBurnState()
const { pair, parsedAmounts, error } = useDerivedBurnInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onUserInput: _onUserInput } = useBurnActionHandlers()
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [showDetailed, setShowDetailed] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirm
// txn values
const [txHash, setTxHash] = useState<string>('')
const [deadline] = useUserDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
const formattedAmounts = {
[Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo('0')
? '0'
: parsedAmounts[Field.LIQUIDITY_PERCENT].lessThan(new Percent('1', '100'))
? '<1'
: parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0),
[Field.LIQUIDITY]:
independentField === Field.LIQUIDITY ? typedValue : parsedAmounts[Field.LIQUIDITY]?.toSignificant(6) ?? '',
[Field.CURRENCY_A]:
independentField === Field.CURRENCY_A ? typedValue : parsedAmounts[Field.CURRENCY_A]?.toSignificant(6) ?? '',
[Field.CURRENCY_B]:
independentField === Field.CURRENCY_B ? typedValue : parsedAmounts[Field.CURRENCY_B]?.toSignificant(6) ?? '',
}
const atMaxAmount = parsedAmounts[Field.LIQUIDITY_PERCENT]?.equalTo(new Percent('1'))
// pair contract
const pairContract: Contract | null = usePairContract(pair?.liquidityToken?.address)
// allowance handling
const [signatureData, setSignatureData] = useState<{ v: number; r: string; s: string; deadline: number } | null>(null)
const [approval, approveCallback] = useApproveCallback(parsedAmounts[Field.LIQUIDITY], ROUTER_ADDRESS)
async function onAttemptToApprove() {
if (!pairContract || !pair || !library) throw new Error('missing dependencies')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
// try to gather a signature for permission
const nonce = await pairContract.nonces(account)
const deadlineForSignature: number = Math.ceil(Date.now() / 1000) + deadline
const EIP712Domain = [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
]
const domain = {
name: 'Pancake LPs',
version: '1',
chainId,
verifyingContract: pair.liquidityToken.address,
}
const Permit = [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
]
const message = {
owner: account,
spender: ROUTER_ADDRESS,
value: liquidityAmount.raw.toString(),
nonce: nonce.toHexString(),
deadline: deadlineForSignature,
}
const data = JSON.stringify({
types: {
EIP712Domain,
Permit,
},
domain,
primaryType: 'Permit',
message,
})
library
.send('eth_signTypedData_v4', [account, data])
.then(splitSignature)
.then((signature) => {
setSignatureData({
v: signature.v,
r: signature.r,
s: signature.s,
deadline: deadlineForSignature,
})
})
.catch((e) => {
// for all errors other than 4001 (EIP-1193 user rejected request), fall back to manual approve
if (e?.code !== 4001) {
approveCallback()
}
})
}
// wrapped onUserInput to clear signatures
const onUserInput = useCallback(
(field: Field, val: string) => {
setSignatureData(null)
return _onUserInput(field, val)
},
[_onUserInput]
)
const onLiquidityInput = useCallback((val: string): void => onUserInput(Field.LIQUIDITY, val), [onUserInput])
const onCurrencyAInput = useCallback((val: string): void => onUserInput(Field.CURRENCY_A, val), [onUserInput])
const onCurrencyBInput = useCallback((val: string): void => onUserInput(Field.CURRENCY_B, val), [onUserInput])
// tx sending
const addTransaction = useTransactionAdder()
async function onRemove() {
if (!chainId || !library || !account) throw new Error('missing dependencies')
const { [Field.CURRENCY_A]: currencyAmountA, [Field.CURRENCY_B]: currencyAmountB } = parsedAmounts
if (!currencyAmountA || !currencyAmountB) {
throw new Error('missing currency amounts')
}
const router = getRouterContract(chainId, library, account)
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(currencyAmountA, allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(currencyAmountB, allowedSlippage)[0],
}
if (!currencyA || !currencyB) throw new Error('missing tokens')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
const currencyBIsETH = currencyB === ETHER
const oneCurrencyIsETH = currencyA === ETHER || currencyBIsETH
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
if (!tokenA || !tokenB) throw new Error('could not wrap')
let methodNames: string[]
let args: Array<string | string[] | number | boolean>
// we have approval, use normal remove liquidity
if (approval === ApprovalState.APPROVED) {
// removeLiquidityETH
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETH', 'removeLiquidityETHSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
deadlineFromNow,
]
}
// removeLiquidity
else {
methodNames = ['removeLiquidity']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
}
}
// we have a signataure, use permit versions of remove liquidity
else if (signatureData !== null) {
// removeLiquidityETHWithPermit
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETHWithPermit', 'removeLiquidityETHWithPermitSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s,
]
}
// removeLiquidityETHWithPermit
else {
methodNames = ['removeLiquidityWithPermit']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s,
]
}
} else {
throw new Error('Attempting to confirm without approval or a signature. Please contact support.')
}
const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
methodNames.map((methodName, index) =>
router.estimateGas[methodName](...args)
.then(calculateGasMargin)
.catch((e) => {
console.error(`estimateGas failed`, index, methodName, args, e)
return undefined
})
)
)
const indexOfSuccessfulEstimation = safeGasEstimates.findIndex((safeGasEstimate) =>
BigNumber.isBigNumber(safeGasEstimate)
)
// all estimations failed...
if (indexOfSuccessfulEstimation === -1) {
console.error('This transaction would fail. Please contact support.')
} else {
const methodName = methodNames[indexOfSuccessfulEstimation]
const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation]
setAttemptingTxn(true)
await router[methodName](...args, {
gasLimit: safeGasEstimate,
})
.then((response: TransactionResponse) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Remove ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencyA?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencyB?.symbol}`,
})
setTxHash(response.hash)
})
.catch((e: Error) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
console.error(e)
})
}
}
function modalHeader() {
return (
<AutoColumn gap="md" style={{ marginTop: '20px' }}>
<RowBetween align="flex-end">
<Text fontSize="24px">{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyA} size="24px" />
<Text fontSize="24px" style={{ marginLeft: '10px' }}>
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<Plus size="16" color={theme.colors.textSubtle} />
</RowFixed>
<RowBetween align="flex-end">
<Text fontSize="24px">{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyB} size="24px" />
<Text fontSize="24px" style={{ marginLeft: '10px' }}>
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
<Text small color="textSubtle" textAlign="left" padding="12px 0 0 0" style={{ fontStyle: 'italic' }}>
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</Text>
</AutoColumn>
)
}
function modalBottom() {
return (
<>
<RowBetween>
<Text color="textSubtle">{`LP ${currencyA?.symbol}/${currencyB?.symbol}`} Burned</Text>
<RowFixed>
<DoubleCurrencyLogo currency0={currencyA} currency1={currencyB} margin />
<Text>{parsedAmounts[Field.LIQUIDITY]?.toSignificant(6)}</Text>
</RowFixed>
</RowBetween>
{pair && (
<>
<RowBetween>
<Text color="textSubtle">{TranslateString(1182, 'Price')}</Text>
<Text>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</Text>
</RowBetween>
<RowBetween>
<div />
<Text>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</Text>
</RowBetween>
</>
)}
<Button disabled={!(approval === ApprovalState.APPROVED || signatureData !== null)} onClick={onRemove}>
{TranslateString(1136, 'Confirm')}
</Button>
</>
)
}
const pendingText = `Removing ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencyA?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencyB?.symbol}`
const liquidityPercentChangeCallback = useCallback(
(value: number) => {
onUserInput(Field.LIQUIDITY_PERCENT, value.toString())
},
[onUserInput]
)
const oneCurrencyIsETH = currencyA === ETHER || currencyB === ETHER
const oneCurrencyIsWETH = Boolean(
chainId &&
((currencyA && currencyEquals(WETH[chainId], currencyA)) ||
(currencyB && currencyEquals(WETH[chainId], currencyB)))
)
const handleSelectCurrencyA = useCallback(
(currency: Currency) => {
if (currencyIdB && currencyId(currency) === currencyIdB) {
history.push(`/remove/${currencyId(currency)}/${currencyIdA}`)
} else {
history.push(`/remove/${currencyId(currency)}/${currencyIdB}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleSelectCurrencyB = useCallback(
(currency: Currency) => {
if (currencyIdA && currencyId(currency) === currencyIdA) {
history.push(`/remove/${currencyIdB}/${currencyId(currency)}`)
} else {
history.push(`/remove/${currencyIdA}/${currencyId(currency)}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
setSignatureData(null) // important that we clear signature data to avoid bad sigs
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.LIQUIDITY_PERCENT, '0')
}
setTxHash('')
}, [onUserInput, txHash])
const [innerLiquidityPercentage, setInnerLiquidityPercentage] = useDebouncedChangeHandler(
Number.parseInt(parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0)),
liquidityPercentChangeCallback
)
return (
<>
<AppBody>
<AddRemoveTabs adding={false} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash || ''}
content={() => (
<ConfirmationModalContent
title={TranslateString(1156, 'You will receive')}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<AutoColumn gap="md">
<Body>
<OutlineCard>
<AutoColumn>
<RowBetween>
<Text>Amount</Text>
<ClickableText
onClick={() => {
setShowDetailed(!showDetailed)
}}
>
{showDetailed ? TranslateString(1184, 'Simple') : TranslateString(1186, 'Detailed')}
</ClickableText>
</RowBetween>
<Flex justifyContent="start">
<Text fontSize="64px">{formattedAmounts[Field.LIQUIDITY_PERCENT]}%</Text>
</Flex>
{!showDetailed && (
<>
<Flex mb="8px">
<Slider value={innerLiquidityPercentage} onChange={setInnerLiquidityPercentage} />
</Flex>
<Flex justifyContent="space-around">
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '25')}
>
25%
</Button>
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '50')}
>
50%
</Button>
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '75')}
>
75%
</Button>
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
>
{TranslateString(166, 'Max')}
</Button>
</Flex>
</>
)}
</AutoColumn>
</OutlineCard>
</Body>
{!showDetailed && (
<>
<ColumnCenter>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<Body>
<OutlineCard>
<AutoColumn gap="10px">
<RowBetween>
<Text fontSize="24px">{formattedAmounts[Field.CURRENCY_A] || '-'}</Text>
<RowFixed>
<CurrencyLogo currency={currencyA} style={{ marginRight: '12px' }} />
<Text fontSize="24px" id="remove-liquidity-tokena-symbol">
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowBetween>
<Text fontSize="24px">{formattedAmounts[Field.CURRENCY_B] || '-'}</Text>
<RowFixed>
<CurrencyLogo currency={currencyB} style={{ marginRight: '12px' }} />
<Text fontSize="24px" id="remove-liquidity-tokenb-symbol">
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
{chainId && (oneCurrencyIsWETH || oneCurrencyIsETH) ? (
<RowBetween style={{ justifyContent: 'flex-end' }}>
{oneCurrencyIsETH ? (
<StyledInternalLink
to={`/remove/${currencyA === ETHER ? WETH[chainId].address : currencyIdA}/${
currencyB === ETHER ? WETH[chainId].address : currencyIdB
}`}
>
{TranslateString(1188, 'Receive WBNB')}
</StyledInternalLink>
) : oneCurrencyIsWETH ? (
<StyledInternalLink
to={`/remove/${
currencyA && currencyEquals(currencyA, WETH[chainId]) ? 'BNB' : currencyIdA
}/${currencyB && currencyEquals(currencyB, WETH[chainId]) ? 'BNB' : currencyIdB}`}
>
{TranslateString(1190, 'Receive BNB')}
</StyledInternalLink>
) : null}
</RowBetween>
) : null}
</AutoColumn>
</OutlineCard>
</Body>
</>
)}
<Body style={{ paddingBottom: '24px' }}>
{showDetailed && (
<>
<CurrencyInputPanel
value={formattedAmounts[Field.LIQUIDITY]}
onUserInput={onLiquidityInput}
onMax={() => {
onUserInput(Field.LIQUIDITY_PERCENT, '100')
}}
showMaxButton={!atMaxAmount}
disableCurrencySelect
currency={pair?.liquidityToken}
pair={pair}
id="liquidity-amount"
/>
<ColumnCenter>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onCurrencyAInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyA}
label="Output"
onCurrencySelect={handleSelectCurrencyA}
id="remove-liquidity-tokena"
/>
<ColumnCenter>
<Plus size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onCurrencyBInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyB}
label="Output"
onCurrencySelect={handleSelectCurrencyB}
id="remove-liquidity-tokenb"
/>
</>
)}
{pair && (
<div style={{ padding: '24px' }}>
<Flex justifyContent="space-between" mb="8px">
Price:
<div>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</div>
</Flex>
<Flex justifyContent="space-between">
<div />
<div>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</div>
</Flex>
</div>
)}
<div style={{ position: 'relative' }}>
{!account ? (
<ConnectWalletButton width="100%" />
) : (
<RowBetween>
<Button
onClick={onAttemptToApprove}
variant={approval === ApprovalState.APPROVED || signatureData !== null ? 'success' : 'primary'}
disabled={approval !== ApprovalState.NOT_APPROVED || signatureData !== null}
mr="8px"
>
{approval === ApprovalState.PENDING ? (
<Dots>Approving</Dots>
) : approval === ApprovalState.APPROVED || signatureData !== null ? (
'Approved'
) : (
'Approve'
)}
</Button>
<Button
onClick={() => {
setShowConfirm(true)
}}
disabled={!isValid || (signatureData === null && approval !== ApprovalState.APPROVED)}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
>
{error || 'Remove'}
</Button>
</RowBetween>
)}
</div>
</Body>
</AutoColumn>
</Wrapper>
</AppBody>
{pair ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} 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 RemoveLiquidity({
history,
match: {
params: { currencyIdA, currencyIdB },
},
}: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
const [currencyA, currencyB] = [useCurrency(currencyIdA) ?? undefined, useCurrency(currencyIdB) ?? undefined]
const { account, chainId, library } = useActiveWeb3React()
const TranslateString = useI18n()
const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
currencyA,
currencyB,
chainId,
])
const theme = useContext(ThemeContext)
// burn state
const { independentField, typedValue } = useBurnState()
const { pair, parsedAmounts, error } = useDerivedBurnInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onUserInput: _onUserInput } = useBurnActionHandlers()
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [showDetailed, setShowDetailed] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirm
// txn values
const [txHash, setTxHash] = useState<string>('')
const [deadline] = useUserDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
const formattedAmounts = {
[Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo('0')
? '0'
: parsedAmounts[Field.LIQUIDITY_PERCENT].lessThan(new Percent('1', '100'))
? '<1'
: parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0),
[Field.LIQUIDITY]:
independentField === Field.LIQUIDITY ? typedValue : parsedAmounts[Field.LIQUIDITY]?.toSignificant(6) ?? '',
[Field.CURRENCY_A]:
independentField === Field.CURRENCY_A ? typedValue : parsedAmounts[Field.CURRENCY_A]?.toSignificant(6) ?? '',
[Field.CURRENCY_B]:
independentField === Field.CURRENCY_B ? typedValue : parsedAmounts[Field.CURRENCY_B]?.toSignificant(6) ?? '',
}
const atMaxAmount = parsedAmounts[Field.LIQUIDITY_PERCENT]?.equalTo(new Percent('1'))
// pair contract
const pairContract: Contract | null = usePairContract(pair?.liquidityToken?.address)
// allowance handling
const [signatureData, setSignatureData] = useState<{ v: number; r: string; s: string; deadline: number } | null>(null)
const [approval, approveCallback] = useApproveCallback(parsedAmounts[Field.LIQUIDITY], ROUTER_ADDRESS)
async function onAttemptToApprove() {
if (!pairContract || !pair || !library) throw new Error('missing dependencies')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
// try to gather a signature for permission
const nonce = await pairContract.nonces(account)
const deadlineForSignature: number = Math.ceil(Date.now() / 1000) + deadline
const EIP712Domain = [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
]
const domain = {
name: 'Pancake LPs',
version: '1',
chainId,
verifyingContract: pair.liquidityToken.address,
}
const Permit = [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
]
const message = {
owner: account,
spender: ROUTER_ADDRESS,
value: liquidityAmount.raw.toString(),
nonce: nonce.toHexString(),
deadline: deadlineForSignature,
}
const data = JSON.stringify({
types: {
EIP712Domain,
Permit,
},
domain,
primaryType: 'Permit',
message,
})
library
.send('eth_signTypedData_v4', [account, data])
.then(splitSignature)
.then((signature) => {
setSignatureData({
v: signature.v,
r: signature.r,
s: signature.s,
deadline: deadlineForSignature,
})
})
.catch((e) => {
// for all errors other than 4001 (EIP-1193 user rejected request), fall back to manual approve
if (e?.code !== 4001) {
approveCallback()
}
})
}
// wrapped onUserInput to clear signatures
const onUserInput = useCallback(
(field: Field, val: string) => {
setSignatureData(null)
return _onUserInput(field, val)
},
[_onUserInput]
)
const onLiquidityInput = useCallback((val: string): void => onUserInput(Field.LIQUIDITY, val), [onUserInput])
const onCurrencyAInput = useCallback((val: string): void => onUserInput(Field.CURRENCY_A, val), [onUserInput])
const onCurrencyBInput = useCallback((val: string): void => onUserInput(Field.CURRENCY_B, val), [onUserInput])
// tx sending
const addTransaction = useTransactionAdder()
async function onRemove() {
if (!chainId || !library || !account) throw new Error('missing dependencies')
const { [Field.CURRENCY_A]: currencyAmountA, [Field.CURRENCY_B]: currencyAmountB } = parsedAmounts
if (!currencyAmountA || !currencyAmountB) {
throw new Error('missing currency amounts')
}
const router = getRouterContract(chainId, library, account)
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(currencyAmountA, allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(currencyAmountB, allowedSlippage)[0],
}
if (!currencyA || !currencyB) throw new Error('missing tokens')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
const currencyBIsETH = currencyB === ETHER
const oneCurrencyIsETH = currencyA === ETHER || currencyBIsETH
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
if (!tokenA || !tokenB) throw new Error('could not wrap')
let methodNames: string[]
let args: Array<string | string[] | number | boolean>
// we have approval, use normal remove liquidity
if (approval === ApprovalState.APPROVED) {
// removeLiquidityETH
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETH', 'removeLiquidityETHSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
deadlineFromNow,
]
}
// removeLiquidity
else {
methodNames = ['removeLiquidity']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
}
}
// we have a signataure, use permit versions of remove liquidity
else if (signatureData !== null) {
// removeLiquidityETHWithPermit
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETHWithPermit', 'removeLiquidityETHWithPermitSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s,
]
}
// removeLiquidityETHWithPermit
else {
methodNames = ['removeLiquidityWithPermit']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s,
]
}
} else {
throw new Error('Attempting to confirm without approval or a signature. Please contact support.')
}
const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
methodNames.map((methodName, index) =>
router.estimateGas[methodName](...args)
.then(calculateGasMargin)
.catch((e) => {
console.error(`estimateGas failed`, index, methodName, args, e)
return undefined
})
)
)
const indexOfSuccessfulEstimation = safeGasEstimates.findIndex((safeGasEstimate) =>
BigNumber.isBigNumber(safeGasEstimate)
)
// all estimations failed...
if (indexOfSuccessfulEstimation === -1) {
console.error('This transaction would fail. Please contact support.')
} else {
const methodName = methodNames[indexOfSuccessfulEstimation]
const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation]
setAttemptingTxn(true)
await router[methodName](...args, {
gasLimit: safeGasEstimate,
})
.then((response: TransactionResponse) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Remove ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencyA?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencyB?.symbol}`,
})
setTxHash(response.hash)
})
.catch((e: Error) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
console.error(e)
})
}
}
function modalHeader() {
return (
<AutoColumn gap="md" style={{ marginTop: '20px' }}>
<RowBetween align="flex-end">
<Text fontSize="24px">{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyA} size="24px" />
<Text fontSize="24px" style={{ marginLeft: '10px' }}>
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<Plus size="16" color={theme.colors.textSubtle} />
</RowFixed>
<RowBetween align="flex-end">
<Text fontSize="24px">{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyB} size="24px" />
<Text fontSize="24px" style={{ marginLeft: '10px' }}>
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
<Text small color="textSubtle" textAlign="left" padding="12px 0 0 0" style={{ fontStyle: 'italic' }}>
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</Text>
</AutoColumn>
)
}
function modalBottom() {
return (
<>
<RowBetween>
<Text color="textSubtle">{`LP ${currencyA?.symbol}/${currencyB?.symbol}`} Burned</Text>
<RowFixed>
<DoubleCurrencyLogo currency0={currencyA} currency1={currencyB} margin />
<Text>{parsedAmounts[Field.LIQUIDITY]?.toSignificant(6)}</Text>
</RowFixed>
</RowBetween>
{pair && (
<>
<RowBetween>
<Text color="textSubtle">{TranslateString(1182, 'Price')}</Text>
<Text>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</Text>
</RowBetween>
<RowBetween>
<div />
<Text>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</Text>
</RowBetween>
</>
)}
<Button disabled={!(approval === ApprovalState.APPROVED || signatureData !== null)} onClick={onRemove}>
{TranslateString(1136, 'Confirm')}
</Button>
</>
)
}
const pendingText = `Removing ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencyA?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencyB?.symbol}`
const liquidityPercentChangeCallback = useCallback(
(value: number) => {
onUserInput(Field.LIQUIDITY_PERCENT, value.toString())
},
[onUserInput]
)
const oneCurrencyIsETH = currencyA === ETHER || currencyB === ETHER
const oneCurrencyIsWETH = Boolean(
chainId &&
((currencyA && currencyEquals(WETH[chainId], currencyA)) ||
(currencyB && currencyEquals(WETH[chainId], currencyB)))
)
const handleSelectCurrencyA = useCallback(
(currency: Currency) => {
if (currencyIdB && currencyId(currency) === currencyIdB) {
history.push(`/remove/${currencyId(currency)}/${currencyIdA}`)
} else {
history.push(`/remove/${currencyId(currency)}/${currencyIdB}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleSelectCurrencyB = useCallback(
(currency: Currency) => {
if (currencyIdA && currencyId(currency) === currencyIdA) {
history.push(`/remove/${currencyIdB}/${currencyId(currency)}`)
} else {
history.push(`/remove/${currencyIdA}/${currencyId(currency)}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
setSignatureData(null) // important that we clear signature data to avoid bad sigs
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.LIQUIDITY_PERCENT, '0')
}
setTxHash('')
}, [onUserInput, txHash])
const [innerLiquidityPercentage, setInnerLiquidityPercentage] = useDebouncedChangeHandler(
Number.parseInt(parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0)),
liquidityPercentChangeCallback
)
return (
<Container>
<AppBody>
<AddRemoveTabs adding={false} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash || ''}
content={() => (
<ConfirmationModalContent
title={TranslateString(1156, 'You will receive')}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<AutoColumn gap="md">
<Body>
<OutlineCard>
<AutoColumn>
<RowBetween>
<Text>Amount</Text>
<ClickableText
onClick={() => {
setShowDetailed(!showDetailed)
}}
>
{showDetailed ? TranslateString(1184, 'Simple') : TranslateString(1186, 'Detailed')}
</ClickableText>
</RowBetween>
<Flex justifyContent="start">
<Text fontSize="64px">{formattedAmounts[Field.LIQUIDITY_PERCENT]}%</Text>
</Flex>
{!showDetailed && (
<>
<Flex mb="8px">
<Slider value={innerLiquidityPercentage} onChange={setInnerLiquidityPercentage} />
</Flex>
<Flex justifyContent="space-around">
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '25')}
>
25%
</Button>
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '50')}
>
50%
</Button>
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '75')}
>
75%
</Button>
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
>
{TranslateString(166, 'Max')}
</Button>
</Flex>
</>
)}
</AutoColumn>
</OutlineCard>
</Body>
{!showDetailed && (
<>
<ColumnCenter>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<Body>
<OutlineCard>
<AutoColumn gap="10px">
<RowBetween>
<Text fontSize="24px">{formattedAmounts[Field.CURRENCY_A] || '-'}</Text>
<RowFixed>
<CurrencyLogo currency={currencyA} style={{ marginRight: '12px' }} />
<Text fontSize="24px" id="remove-liquidity-tokena-symbol">
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowBetween>
<Text fontSize="24px">{formattedAmounts[Field.CURRENCY_B] || '-'}</Text>
<RowFixed>
<CurrencyLogo currency={currencyB} style={{ marginRight: '12px' }} />
<Text fontSize="24px" id="remove-liquidity-tokenb-symbol">
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
{chainId && (oneCurrencyIsWETH || oneCurrencyIsETH) ? (
<RowBetween style={{ justifyContent: 'flex-end' }}>
{oneCurrencyIsETH ? (
<StyledInternalLink
to={`/remove/${currencyA === ETHER ? WETH[chainId].address : currencyIdA}/${
currencyB === ETHER ? WETH[chainId].address : currencyIdB
}`}
>
{TranslateString(1188, 'Receive WBNB')}
</StyledInternalLink>
) : oneCurrencyIsWETH ? (
<StyledInternalLink
to={`/remove/${
currencyA && currencyEquals(currencyA, WETH[chainId]) ? 'BNB' : currencyIdA
}/${currencyB && currencyEquals(currencyB, WETH[chainId]) ? 'BNB' : currencyIdB}`}
>
{TranslateString(1190, 'Receive BNB')}
</StyledInternalLink>
) : null}
</RowBetween>
) : null}
</AutoColumn>
</OutlineCard>
</Body>
</>
)}
<Body style={{ paddingBottom: '24px' }}>
{showDetailed && (
<>
<CurrencyInputPanel
value={formattedAmounts[Field.LIQUIDITY]}
onUserInput={onLiquidityInput}
onMax={() => {
onUserInput(Field.LIQUIDITY_PERCENT, '100')
}}
showMaxButton={!atMaxAmount}
disableCurrencySelect
currency={pair?.liquidityToken}
pair={pair}
id="liquidity-amount"
/>
<ColumnCenter>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onCurrencyAInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyA}
label="Output"
onCurrencySelect={handleSelectCurrencyA}
id="remove-liquidity-tokena"
/>
<ColumnCenter>
<Plus size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onCurrencyBInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyB}
label="Output"
onCurrencySelect={handleSelectCurrencyB}
id="remove-liquidity-tokenb"
/>
</>
)}
{pair && (
<div style={{ padding: '24px' }}>
<Flex justifyContent="space-between" mb="8px">
Price:
<div>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</div>
</Flex>
<Flex justifyContent="space-between">
<div />
<div>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</div>
</Flex>
</div>
)}
<div style={{ position: 'relative' }}>
{!account ? (
<ConnectWalletButton width="100%" />
) : (
<RowBetween>
<Button
onClick={onAttemptToApprove}
variant={approval === ApprovalState.APPROVED || signatureData !== null ? 'success' : 'primary'}
disabled={approval !== ApprovalState.NOT_APPROVED || signatureData !== null}
mr="8px"
>
{approval === ApprovalState.PENDING ? (
<Dots>Approving</Dots>
) : approval === ApprovalState.APPROVED || signatureData !== null ? (
'Approved'
) : (
'Approve'
)}
</Button>
<Button
onClick={() => {
setShowConfirm(true)
}}
disabled={!isValid || (signatureData === null && approval !== ApprovalState.APPROVED)}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
>
{error || 'Remove'}
</Button>
</RowBetween>
)}
</div>
</Body>
</AutoColumn>
</Wrapper>
</AppBody>
{pair ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} 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: SwapModalHeader.tsx From panther-frontend-dex with GNU General Public License v3.0 | 4 votes |
export default function SwapModalHeader({
trade,
allowedSlippage,
recipient,
showAcceptChanges,
onAcceptChanges,
}: {
trade: Trade
allowedSlippage: number
recipient: string | null
showAcceptChanges: boolean
onAcceptChanges: () => void
}) {
const slippageAdjustedAmounts = useMemo(() => computeSlippageAdjustedAmounts(trade, allowedSlippage), [
trade,
allowedSlippage,
])
const { priceImpactWithoutFee } = useMemo(() => computeTradePriceBreakdown(trade), [trade])
const priceImpactSeverity = warningSeverity(priceImpactWithoutFee)
const theme = useContext(ThemeContext)
return (
<AutoColumn gap="md" style={{ marginTop: '20px' }}>
<RowBetween align="flex-end">
<RowFixed gap="0px">
<CurrencyLogo currency={trade.inputAmount.currency} size="24px" style={{ marginRight: '12px' }} />
<Text
fontSize="24px"
color={showAcceptChanges && trade.tradeType === TradeType.EXACT_OUTPUT ? theme.colors.primary : 'text'}
>
{trade.inputAmount.toSignificant(6)}
</Text>
</RowFixed>
<RowFixed gap="0px">
<Text fontSize="24px" style={{ marginLeft: '10px', fontWeight: 500 }}>
{trade.inputAmount.currency.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<ArrowDown size="16" color={theme.colors.textSubtle} style={{ marginLeft: '4px', minWidth: '16px' }} />
</RowFixed>
<RowBetween align="flex-end">
<RowFixed gap="0px">
<CurrencyLogo currency={trade.outputAmount.currency} size="24px" style={{ marginRight: '12px' }} />
<Text
fontSize="24px"
style={{ marginLeft: '10px', fontWeight: 500 }}
color={
priceImpactSeverity > 2
? theme.colors.failure
: showAcceptChanges && trade.tradeType === TradeType.EXACT_INPUT
? theme.colors.primary
: 'text'
}
>
{trade.outputAmount.toSignificant(6)}
</Text>
</RowFixed>
<RowFixed gap="0px">
<Text fontSize="24px" style={{ marginLeft: '10px', fontWeight: 500 }}>
{trade.outputAmount.currency.symbol}
</Text>
</RowFixed>
</RowBetween>
{showAcceptChanges ? (
<SwapShowAcceptChanges justify="flex-start" gap="0px">
<RowBetween>
<RowFixed>
<AlertTriangle size={20} style={{ marginRight: '8px', minWidth: 24 }} />
<Main color={theme.colors.primary}> Price Updated</Main>
</RowFixed>
<Button onClick={onAcceptChanges}>Accept</Button>
</RowBetween>
</SwapShowAcceptChanges>
) : null}
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '16px 0 0' }}>
{trade.tradeType === TradeType.EXACT_INPUT ? (
<PriceInfoText>
{`Output is estimated. You will receive at least `}
<span>
{slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {trade.outputAmount.currency.symbol}
</span>
{' or the transaction will revert.'}
</PriceInfoText>
) : (
<PriceInfoText>
{`Input is estimated. You will sell at most `}
<span>
{slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {trade.inputAmount.currency.symbol}
</span>
{' or the transaction will revert.'}
</PriceInfoText>
)}
</AutoColumn>
{recipient !== null ? (
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '16px 0 0' }}>
<Main>
Output will be sent to{' '}
<b title={recipient}>{isAddress(recipient) ? shortenAddress(recipient) : recipient}</b>
</Main>
</AutoColumn>
) : null}
</AutoColumn>
)
}
Example #12
Source File: index.tsx From panther-frontend-dex with GNU General Public License v3.0 | 4 votes |
export default function RemoveLiquidity({
history,
match: {
params: { currencyIdA, currencyIdB },
},
}: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
const [currencyA, currencyB] = [useCurrency(currencyIdA) ?? undefined, useCurrency(currencyIdB) ?? undefined]
const { account, chainId, library } = useActiveWeb3React()
const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
currencyA,
currencyB,
chainId,
])
const theme = useContext(ThemeContext)
// burn state
const { independentField, typedValue } = useBurnState()
const { pair, parsedAmounts, error } = useDerivedBurnInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onUserInput: _onUserInput } = useBurnActionHandlers()
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [showDetailed, setShowDetailed] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirm
// txn values
const [txHash, setTxHash] = useState<string>('')
const [deadline] = useUserDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
const formattedAmounts = {
[Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo('0')
? '0'
: parsedAmounts[Field.LIQUIDITY_PERCENT].lessThan(new Percent('1', '100'))
? '<1'
: parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0),
[Field.LIQUIDITY]:
independentField === Field.LIQUIDITY ? typedValue : parsedAmounts[Field.LIQUIDITY]?.toSignificant(6) ?? '',
[Field.CURRENCY_A]:
independentField === Field.CURRENCY_A ? typedValue : parsedAmounts[Field.CURRENCY_A]?.toSignificant(6) ?? '',
[Field.CURRENCY_B]:
independentField === Field.CURRENCY_B ? typedValue : parsedAmounts[Field.CURRENCY_B]?.toSignificant(6) ?? '',
}
const atMaxAmount = parsedAmounts[Field.LIQUIDITY_PERCENT]?.equalTo(new Percent('1'))
// pair contract
const pairContract: Contract | null = usePairContract(pair?.liquidityToken?.address)
// allowance handling
const [signatureData, setSignatureData] = useState<{ v: number; r: string; s: string; deadline: number } | null>(null)
const [approval, approveCallback] = useApproveCallback(parsedAmounts[Field.LIQUIDITY], ROUTER_ADDRESS)
async function onAttemptToApprove() {
if (!pairContract || !pair || !library) throw new Error('missing dependencies')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
// try to gather a signature for permission
const nonce = await pairContract.nonces(account)
const deadlineForSignature: number = Math.ceil(Date.now() / 1000) + deadline
const EIP712Domain = [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
]
const domain = {
name: 'PantherSwap LPs',
version: '1',
chainId,
verifyingContract: pair.liquidityToken.address,
}
const Permit = [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
]
const message = {
owner: account,
spender: ROUTER_ADDRESS,
value: liquidityAmount.raw.toString(),
nonce: nonce.toHexString(),
deadline: deadlineForSignature,
}
const data = JSON.stringify({
types: {
EIP712Domain,
Permit,
},
domain,
primaryType: 'Permit',
message,
})
library
.send('eth_signTypedData_v4', [account, data])
.then(splitSignature)
.then((signature) => {
setSignatureData({
v: signature.v,
r: signature.r,
s: signature.s,
deadline: deadlineForSignature,
})
})
.catch((e) => {
// for all errors other than 4001 (EIP-1193 user rejected request), fall back to manual approve
if (e?.code !== 4001) {
approveCallback()
}
})
}
// wrapped onUserInput to clear signatures
const onUserInput = useCallback(
(field: Field, val: string) => {
setSignatureData(null)
return _onUserInput(field, val)
},
[_onUserInput]
)
const onLiquidityInput = useCallback((val: string): void => onUserInput(Field.LIQUIDITY, val), [onUserInput])
const onCurrencyAInput = useCallback((val: string): void => onUserInput(Field.CURRENCY_A, val), [onUserInput])
const onCurrencyBInput = useCallback((val: string): void => onUserInput(Field.CURRENCY_B, val), [onUserInput])
// tx sending
const addTransaction = useTransactionAdder()
async function onRemove() {
if (!chainId || !library || !account) throw new Error('missing dependencies')
const { [Field.CURRENCY_A]: currencyAmountA, [Field.CURRENCY_B]: currencyAmountB } = parsedAmounts
if (!currencyAmountA || !currencyAmountB) {
throw new Error('missing currency amounts')
}
const router = getRouterContract(chainId, library, account)
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(currencyAmountA, allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(currencyAmountB, allowedSlippage)[0],
}
if (!currencyA || !currencyB) throw new Error('missing tokens')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
const currencyBIsETH = currencyB === ETHER
const oneCurrencyIsETH = currencyA === ETHER || currencyBIsETH
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
if (!tokenA || !tokenB) throw new Error('could not wrap')
let methodNames: string[]
let args: Array<string | string[] | number | boolean>
// we have approval, use normal remove liquidity
if (approval === ApprovalState.APPROVED) {
// removeLiquidityETH
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETH', 'removeLiquidityETHSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
deadlineFromNow,
]
}
// removeLiquidity
else {
methodNames = ['removeLiquidity']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
}
}
// we have a signataure, use permit versions of remove liquidity
else if (signatureData !== null) {
// removeLiquidityETHWithPermit
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETHWithPermit', 'removeLiquidityETHWithPermitSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s,
]
}
// removeLiquidityETHWithPermit
else {
methodNames = ['removeLiquidityWithPermit']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s,
]
}
} else {
throw new Error('Attempting to confirm without approval or a signature. Please contact support.')
}
const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
methodNames.map((methodName, index) =>
router.estimateGas[methodName](...args)
.then(calculateGasMargin)
.catch((e) => {
console.error(`estimateGas failed`, index, methodName, args, e)
return undefined
})
)
)
const indexOfSuccessfulEstimation = safeGasEstimates.findIndex((safeGasEstimate) =>
BigNumber.isBigNumber(safeGasEstimate)
)
// all estimations failed...
if (indexOfSuccessfulEstimation === -1) {
console.error('This transaction would fail. Please contact support.')
} else {
const methodName = methodNames[indexOfSuccessfulEstimation]
const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation]
setAttemptingTxn(true)
await router[methodName](...args, {
gasLimit: safeGasEstimate,
})
.then((response: TransactionResponse) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Remove ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencyA?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencyB?.symbol}`,
})
setTxHash(response.hash)
})
.catch((e: Error) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
console.error(e)
})
}
}
function modalHeader() {
return (
<AutoColumn gap="md" style={{ marginTop: '20px' }}>
<RowBetween align="flex-end">
<Text fontSize="24px">{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyA} size="24px" />
<Text fontSize="24px" style={{ marginLeft: '10px' }}>
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<Plus size="16" color={theme.colors.textSubtle} />
</RowFixed>
<RowBetween align="flex-end">
<Text fontSize="24px">{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyB} size="24px" />
<Text fontSize="24px" style={{ marginLeft: '10px' }}>
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
<Italic fontSize={12} color={theme.colors.textSubtle} textAlign="left" padding="12px 0 0 0">
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</Italic>
</AutoColumn>
)
}
function modalBottom() {
return (
<>
<RowBetween>
<Text color="textSubtle">{`FLIP ${currencyA?.symbol}/${currencyB?.symbol}`} Burned</Text>
<RowFixed>
<DoubleCurrencyLogo currency0={currencyA} currency1={currencyB} margin />
<Text>{parsedAmounts[Field.LIQUIDITY]?.toSignificant(6)}</Text>
</RowFixed>
</RowBetween>
{pair && (
<>
<RowBetween>
<Text color="textSubtle">Price</Text>
<Text>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</Text>
</RowBetween>
<RowBetween>
<div />
<Text>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</Text>
</RowBetween>
</>
)}
<Button disabled={!(approval === ApprovalState.APPROVED || signatureData !== null)} onClick={onRemove}>
Confirm
</Button>
</>
)
}
const pendingText = `Removing ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencyA?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencyB?.symbol}`
const liquidityPercentChangeCallback = useCallback(
(value: number) => {
onUserInput(Field.LIQUIDITY_PERCENT, value.toString())
},
[onUserInput]
)
const oneCurrencyIsETH = currencyA === ETHER || currencyB === ETHER
const oneCurrencyIsWETH = Boolean(
chainId &&
((currencyA && currencyEquals(WETH[chainId], currencyA)) ||
(currencyB && currencyEquals(WETH[chainId], currencyB)))
)
const handleSelectCurrencyA = useCallback(
(currency: Currency) => {
if (currencyIdB && currencyId(currency) === currencyIdB) {
history.push(`/remove/${currencyId(currency)}/${currencyIdA}`)
} else {
history.push(`/remove/${currencyId(currency)}/${currencyIdB}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleSelectCurrencyB = useCallback(
(currency: Currency) => {
if (currencyIdA && currencyId(currency) === currencyIdA) {
history.push(`/remove/${currencyIdB}/${currencyId(currency)}`)
} else {
history.push(`/remove/${currencyIdA}/${currencyId(currency)}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
setSignatureData(null) // important that we clear signature data to avoid bad sigs
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.LIQUIDITY_PERCENT, '0')
}
setTxHash('')
}, [onUserInput, txHash])
const [innerLiquidityPercentage, setInnerLiquidityPercentage] = useDebouncedChangeHandler(
Number.parseInt(parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0)),
liquidityPercentChangeCallback
)
return (
<>
<AppBody>
<AddRemoveTabs adding={false} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash || ''}
content={() => (
<ConfirmationModalContent
title="You will receive"
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<AutoColumn gap="md">
<Body>
<OutlineCard>
<AutoColumn>
<RowBetween>
<Text>Amount</Text>
<ClickableText
onClick={() => {
setShowDetailed(!showDetailed)
}}
>
{showDetailed ? 'Simple' : 'Detailed'}
</ClickableText>
</RowBetween>
<Flex justifyContent="start">
<Text fontSize="64px">{formattedAmounts[Field.LIQUIDITY_PERCENT]}%</Text>
</Flex>
{!showDetailed && (
<>
<Flex mb="8px">
<Slider value={innerLiquidityPercentage} onChange={setInnerLiquidityPercentage} />
</Flex>
<Flex justifyContent="space-around">
<Button variant="tertiary" size="sm" onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '25')}>
25%
</Button>
<Button variant="tertiary" size="sm" onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '50')}>
50%
</Button>
<Button variant="tertiary" size="sm" onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '75')}>
75%
</Button>
<Button
variant="tertiary"
size="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
>
Max
</Button>
</Flex>
</>
)}
</AutoColumn>
</OutlineCard>
</Body>
{!showDetailed && (
<>
<ColumnCenter>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<Body>
<OutlineCard>
<AutoColumn gap="10px">
<RowBetween>
<Text fontSize="24px">{formattedAmounts[Field.CURRENCY_A] || '-'}</Text>
<RowFixed>
<CurrencyLogo currency={currencyA} style={{ marginRight: '12px' }} />
<Text fontSize="24px" id="remove-liquidity-tokena-symbol">
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowBetween>
<Text fontSize="24px">{formattedAmounts[Field.CURRENCY_B] || '-'}</Text>
<RowFixed>
<CurrencyLogo currency={currencyB} style={{ marginRight: '12px' }} />
<Text fontSize="24px" id="remove-liquidity-tokenb-symbol">
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
{chainId && (oneCurrencyIsWETH || oneCurrencyIsETH) ? (
<RowBetween style={{ justifyContent: 'flex-end' }}>
{oneCurrencyIsETH ? (
<StyledInternalLink
to={`/remove/${currencyA === ETHER ? WETH[chainId].address : currencyIdA}/${
currencyB === ETHER ? WETH[chainId].address : currencyIdB
}`}
>
Receive WBNB
</StyledInternalLink>
) : oneCurrencyIsWETH ? (
<StyledInternalLink
to={`/remove/${
currencyA && currencyEquals(currencyA, WETH[chainId]) ? 'ETH' : currencyIdA
}/${currencyB && currencyEquals(currencyB, WETH[chainId]) ? 'ETH' : currencyIdB}`}
>
Receive BNB
</StyledInternalLink>
) : null}
</RowBetween>
) : null}
</AutoColumn>
</OutlineCard>
</Body>
</>
)}
<Body style={{ paddingBottom: '24px' }}>
{showDetailed && (
<>
<CurrencyInputPanel
value={formattedAmounts[Field.LIQUIDITY]}
onUserInput={onLiquidityInput}
onMax={() => {
onUserInput(Field.LIQUIDITY_PERCENT, '100')
}}
showMaxButton={!atMaxAmount}
disableCurrencySelect
currency={pair?.liquidityToken}
pair={pair}
id="liquidity-amount"
/>
<ColumnCenter>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onCurrencyAInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyA}
label="Output"
onCurrencySelect={handleSelectCurrencyA}
id="remove-liquidity-tokena"
/>
<ColumnCenter>
<Plus size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onCurrencyBInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyB}
label="Output"
onCurrencySelect={handleSelectCurrencyB}
id="remove-liquidity-tokenb"
/>
</>
)}
{pair && (
<div style={{ padding: '24px' }}>
<Flex justifyContent="space-between" mb="8px">
Price:
<div>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</div>
</Flex>
<Flex justifyContent="space-between">
<div />
<div>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</div>
</Flex>
</div>
)}
<div style={{ position: 'relative' }}>
{!account ? (
<ConnectWalletButton fullWidth />
) : (
<RowBetween>
<Button
onClick={onAttemptToApprove}
variant={approval === ApprovalState.APPROVED || signatureData !== null ? 'success' : 'primary'}
disabled={approval !== ApprovalState.NOT_APPROVED || signatureData !== null}
mr="8px"
>
{approval === ApprovalState.PENDING ? (
<Dots>Approving</Dots>
) : approval === ApprovalState.APPROVED || signatureData !== null ? (
'Approved'
) : (
'Approve'
)}
</Button>
<Button
onClick={() => {
setShowConfirm(true)
}}
disabled={!isValid || (signatureData === null && approval !== ApprovalState.APPROVED)}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
>
{error || 'Remove'}
</Button>
</RowBetween>
)}
</div>
</Body>
</AutoColumn>
</Wrapper>
</AppBody>
{pair ? (
<AutoColumn style={{ maxWidth: '436px', width: '100%', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
</AutoColumn>
) : null}
</>
)
}
Example #13
Source File: index.tsx From mozartfinance-swap-interface with GNU General Public License v3.0 | 4 votes |
export default function RemoveLiquidity({
history,
match: {
params: { currencyIdA, currencyIdB },
},
}: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
const [currencyA, currencyB] = [useCurrency(currencyIdA) ?? undefined, useCurrency(currencyIdB) ?? undefined]
const { account, chainId, library } = useActiveWeb3React()
const TranslateString = useI18n()
const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
currencyA,
currencyB,
chainId,
])
const theme = useContext(ThemeContext)
// burn state
const { independentField, typedValue } = useBurnState()
const { pair, parsedAmounts, error } = useDerivedBurnInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onUserInput: _onUserInput } = useBurnActionHandlers()
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [showDetailed, setShowDetailed] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirm
// txn values
const [txHash, setTxHash] = useState<string>('')
const [deadline] = useUserDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
const formattedAmounts = {
[Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo('0')
? '0'
: parsedAmounts[Field.LIQUIDITY_PERCENT].lessThan(new Percent('1', '100'))
? '<1'
: parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0),
[Field.LIQUIDITY]:
independentField === Field.LIQUIDITY ? typedValue : parsedAmounts[Field.LIQUIDITY]?.toSignificant(6) ?? '',
[Field.CURRENCY_A]:
independentField === Field.CURRENCY_A ? typedValue : parsedAmounts[Field.CURRENCY_A]?.toSignificant(6) ?? '',
[Field.CURRENCY_B]:
independentField === Field.CURRENCY_B ? typedValue : parsedAmounts[Field.CURRENCY_B]?.toSignificant(6) ?? '',
}
const atMaxAmount = parsedAmounts[Field.LIQUIDITY_PERCENT]?.equalTo(new Percent('1'))
// pair contract
const pairContract: Contract | null = usePairContract(pair?.liquidityToken?.address)
// allowance handling
const [signatureData, setSignatureData] = useState<{ v: number; r: string; s: string; deadline: number } | null>(null)
const [approval, approveCallback] = useApproveCallback(parsedAmounts[Field.LIQUIDITY], ROUTER_ADDRESS)
async function onAttemptToApprove() {
if (!pairContract || !pair || !library) throw new Error('missing dependencies')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
// try to gather a signature for permission
const nonce = await pairContract.nonces(account)
const deadlineForSignature: number = Math.ceil(Date.now() / 1000) + deadline
const EIP712Domain = [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
]
const domain = {
name: 'Pancake LPs',
version: '1',
chainId,
verifyingContract: pair.liquidityToken.address,
}
const Permit = [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
]
const message = {
owner: account,
spender: ROUTER_ADDRESS,
value: liquidityAmount.raw.toString(),
nonce: nonce.toHexString(),
deadline: deadlineForSignature,
}
const data = JSON.stringify({
types: {
EIP712Domain,
Permit,
},
domain,
primaryType: 'Permit',
message,
})
library
.send('eth_signTypedData_v4', [account, data])
.then(splitSignature)
.then((signature) => {
setSignatureData({
v: signature.v,
r: signature.r,
s: signature.s,
deadline: deadlineForSignature,
})
})
.catch((e) => {
// for all errors other than 4001 (EIP-1193 user rejected request), fall back to manual approve
if (e?.code !== 4001) {
approveCallback()
}
})
}
// wrapped onUserInput to clear signatures
const onUserInput = useCallback(
(field: Field, val: string) => {
setSignatureData(null)
return _onUserInput(field, val)
},
[_onUserInput]
)
const onLiquidityInput = useCallback((val: string): void => onUserInput(Field.LIQUIDITY, val), [onUserInput])
const onCurrencyAInput = useCallback((val: string): void => onUserInput(Field.CURRENCY_A, val), [onUserInput])
const onCurrencyBInput = useCallback((val: string): void => onUserInput(Field.CURRENCY_B, val), [onUserInput])
// tx sending
const addTransaction = useTransactionAdder()
async function onRemove() {
if (!chainId || !library || !account) throw new Error('missing dependencies')
const { [Field.CURRENCY_A]: currencyAmountA, [Field.CURRENCY_B]: currencyAmountB } = parsedAmounts
if (!currencyAmountA || !currencyAmountB) {
throw new Error('missing currency amounts')
}
const router = getRouterContract(chainId, library, account)
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(currencyAmountA, allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(currencyAmountB, allowedSlippage)[0],
}
if (!currencyA || !currencyB) throw new Error('missing tokens')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
const currencyBIsETH = currencyB === ETHER
const oneCurrencyIsETH = currencyA === ETHER || currencyBIsETH
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
if (!tokenA || !tokenB) throw new Error('could not wrap')
let methodNames: string[]
let args: Array<string | string[] | number | boolean>
// we have approval, use normal remove liquidity
if (approval === ApprovalState.APPROVED) {
// removeLiquidityETH
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETH', 'removeLiquidityETHSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
deadlineFromNow,
]
}
// removeLiquidity
else {
methodNames = ['removeLiquidity']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
}
}
// we have a signataure, use permit versions of remove liquidity
else if (signatureData !== null) {
// removeLiquidityETHWithPermit
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETHWithPermit', 'removeLiquidityETHWithPermitSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s,
]
}
// removeLiquidityETHWithPermit
else {
methodNames = ['removeLiquidityWithPermit']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s,
]
}
} else {
throw new Error('Attempting to confirm without approval or a signature. Please contact support.')
}
const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
methodNames.map((methodName, index) =>
router.estimateGas[methodName](...args)
.then(calculateGasMargin)
.catch((e) => {
console.error(`estimateGas failed`, index, methodName, args, e)
return undefined
})
)
)
const indexOfSuccessfulEstimation = safeGasEstimates.findIndex((safeGasEstimate) =>
BigNumber.isBigNumber(safeGasEstimate)
)
// all estimations failed...
if (indexOfSuccessfulEstimation === -1) {
console.error('This transaction would fail. Please contact support.')
} else {
const methodName = methodNames[indexOfSuccessfulEstimation]
const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation]
setAttemptingTxn(true)
await router[methodName](...args, {
gasLimit: safeGasEstimate,
})
.then((response: TransactionResponse) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Remove ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencyA?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencyB?.symbol}`,
})
setTxHash(response.hash)
})
.catch((e: Error) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
console.error(e)
})
}
}
function modalHeader() {
return (
<AutoColumn gap="md" style={{ marginTop: '20px' }}>
<RowBetween align="flex-end">
<Text fontSize="24px">{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyA} size="24px" />
<Text fontSize="24px" style={{ marginLeft: '10px' }}>
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<Plus size="16" color={theme.colors.textSubtle} />
</RowFixed>
<RowBetween align="flex-end">
<Text fontSize="24px">{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyB} size="24px" />
<Text fontSize="24px" style={{ marginLeft: '10px' }}>
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
<Text small color="textSubtle" textAlign="left" padding="12px 0 0 0" style={{ fontStyle: 'italic' }}>
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</Text>
</AutoColumn>
)
}
function modalBottom() {
return (
<>
<RowBetween>
<Text color="textSubtle">{`LP ${currencyA?.symbol}/${currencyB?.symbol}`} Burned</Text>
<RowFixed>
<DoubleCurrencyLogo currency0={currencyA} currency1={currencyB} margin />
<Text>{parsedAmounts[Field.LIQUIDITY]?.toSignificant(6)}</Text>
</RowFixed>
</RowBetween>
{pair && (
<>
<RowBetween>
<Text color="textSubtle">{TranslateString(1182, 'Price')}</Text>
<Text>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</Text>
</RowBetween>
<RowBetween>
<div />
<Text>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</Text>
</RowBetween>
</>
)}
<Button disabled={!(approval === ApprovalState.APPROVED || signatureData !== null)} onClick={onRemove}>
{TranslateString(1136, 'Confirm')}
</Button>
</>
)
}
const pendingText = `Removing ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencyA?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencyB?.symbol}`
const liquidityPercentChangeCallback = useCallback(
(value: number) => {
onUserInput(Field.LIQUIDITY_PERCENT, value.toString())
},
[onUserInput]
)
const oneCurrencyIsETH = currencyA === ETHER || currencyB === ETHER
const oneCurrencyIsWETH = Boolean(
chainId &&
((currencyA && currencyEquals(WETH[chainId], currencyA)) ||
(currencyB && currencyEquals(WETH[chainId], currencyB)))
)
const handleSelectCurrencyA = useCallback(
(currency: Currency) => {
if (currencyIdB && currencyId(currency) === currencyIdB) {
history.push(`/remove/${currencyId(currency)}/${currencyIdA}`)
} else {
history.push(`/remove/${currencyId(currency)}/${currencyIdB}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleSelectCurrencyB = useCallback(
(currency: Currency) => {
if (currencyIdA && currencyId(currency) === currencyIdA) {
history.push(`/remove/${currencyIdB}/${currencyId(currency)}`)
} else {
history.push(`/remove/${currencyIdA}/${currencyId(currency)}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
setSignatureData(null) // important that we clear signature data to avoid bad sigs
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.LIQUIDITY_PERCENT, '0')
}
setTxHash('')
}, [onUserInput, txHash])
const [innerLiquidityPercentage, setInnerLiquidityPercentage] = useDebouncedChangeHandler(
Number.parseInt(parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0)),
liquidityPercentChangeCallback
)
return (
<>
<AppBody>
<AddRemoveTabs adding={false} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash || ''}
content={() => (
<ConfirmationModalContent
title={TranslateString(1156, 'You will receive')}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<AutoColumn gap="md">
<Body>
<OutlineCard>
<AutoColumn>
<RowBetween>
<Text>Amount</Text>
<ClickableText
onClick={() => {
setShowDetailed(!showDetailed)
}}
>
{showDetailed ? TranslateString(1184, 'Simple') : TranslateString(1186, 'Detailed')}
</ClickableText>
</RowBetween>
<Flex justifyContent="start">
<Text fontSize="64px">{formattedAmounts[Field.LIQUIDITY_PERCENT]}%</Text>
</Flex>
{!showDetailed && (
<>
<Flex mb="8px">
<Slider value={innerLiquidityPercentage} onChange={setInnerLiquidityPercentage} />
</Flex>
<Flex justifyContent="space-around">
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '25')}
>
25%
</Button>
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '50')}
>
50%
</Button>
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '75')}
>
75%
</Button>
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
>
{TranslateString(166, 'Max')}
</Button>
</Flex>
</>
)}
</AutoColumn>
</OutlineCard>
</Body>
{!showDetailed && (
<>
<ColumnCenter>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<Body>
<OutlineCard>
<AutoColumn gap="10px">
<RowBetween>
<Text fontSize="24px">{formattedAmounts[Field.CURRENCY_A] || '-'}</Text>
<RowFixed>
<CurrencyLogo currency={currencyA} style={{ marginRight: '12px' }} />
<Text fontSize="24px" id="remove-liquidity-tokena-symbol">
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowBetween>
<Text fontSize="24px">{formattedAmounts[Field.CURRENCY_B] || '-'}</Text>
<RowFixed>
<CurrencyLogo currency={currencyB} style={{ marginRight: '12px' }} />
<Text fontSize="24px" id="remove-liquidity-tokenb-symbol">
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
{chainId && (oneCurrencyIsWETH || oneCurrencyIsETH) ? (
<RowBetween style={{ justifyContent: 'flex-end' }}>
{oneCurrencyIsETH ? (
<StyledInternalLink
to={`/remove/${currencyA === ETHER ? WETH[chainId].address : currencyIdA}/${
currencyB === ETHER ? WETH[chainId].address : currencyIdB
}`}
>
{TranslateString(1188, 'Receive WBNB')}
</StyledInternalLink>
) : oneCurrencyIsWETH ? (
<StyledInternalLink
to={`/remove/${
currencyA && currencyEquals(currencyA, WETH[chainId]) ? 'BNB' : currencyIdA
}/${currencyB && currencyEquals(currencyB, WETH[chainId]) ? 'BNB' : currencyIdB}`}
>
{TranslateString(1190, 'Receive BNB')}
</StyledInternalLink>
) : null}
</RowBetween>
) : null}
</AutoColumn>
</OutlineCard>
</Body>
</>
)}
<Body style={{ paddingBottom: '24px' }}>
{showDetailed && (
<>
<CurrencyInputPanel
value={formattedAmounts[Field.LIQUIDITY]}
onUserInput={onLiquidityInput}
onMax={() => {
onUserInput(Field.LIQUIDITY_PERCENT, '100')
}}
showMaxButton={!atMaxAmount}
disableCurrencySelect
currency={pair?.liquidityToken}
pair={pair}
id="liquidity-amount"
/>
<ColumnCenter>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onCurrencyAInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyA}
label="Output"
onCurrencySelect={handleSelectCurrencyA}
id="remove-liquidity-tokena"
/>
<ColumnCenter>
<Plus size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onCurrencyBInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyB}
label="Output"
onCurrencySelect={handleSelectCurrencyB}
id="remove-liquidity-tokenb"
/>
</>
)}
{pair && (
<div style={{ padding: '24px' }}>
<Flex justifyContent="space-between" mb="8px">
Price:
<div>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</div>
</Flex>
<Flex justifyContent="space-between">
<div />
<div>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</div>
</Flex>
</div>
)}
<div style={{ position: 'relative' }}>
{!account ? (
<ConnectWalletButton width="100%" />
) : (
<RowBetween>
<Button
onClick={onAttemptToApprove}
variant={approval === ApprovalState.APPROVED || signatureData !== null ? 'success' : 'primary'}
disabled={approval !== ApprovalState.NOT_APPROVED || signatureData !== null}
mr="8px"
>
{approval === ApprovalState.PENDING ? (
<Dots>Approving</Dots>
) : approval === ApprovalState.APPROVED || signatureData !== null ? (
'Approved'
) : (
'Approve'
)}
</Button>
<Button
onClick={() => {
setShowConfirm(true)
}}
disabled={!isValid || (signatureData === null && approval !== ApprovalState.APPROVED)}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
>
{error || 'Remove'}
</Button>
</RowBetween>
)}
</div>
</Body>
</AutoColumn>
</Wrapper>
</AppBody>
{pair ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
</AutoColumn>
) : null}
</>
)
}
Example #14
Source File: index.tsx From forward.swaps with GNU General Public License v3.0 | 4 votes |
export default function RemoveLiquidity({
history,
match: {
params: { currencyIdA, currencyIdB }
}
}: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
const [currencyA, currencyB] = [useCurrency(currencyIdA) ?? undefined, useCurrency(currencyIdB) ?? undefined]
const { account, chainId, library } = useActiveWeb3React()
const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
currencyA,
currencyB,
chainId
])
const theme = useContext(ThemeContext)
// toggle wallet when disconnected
const toggleWalletModal = useWalletModalToggle()
// burn state
const { independentField, typedValue } = useBurnState()
const { pair, parsedAmounts, error } = useDerivedBurnInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onUserInput: _onUserInput } = useBurnActionHandlers()
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [showDetailed, setShowDetailed] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirm
// txn values
const [txHash, setTxHash] = useState<string>('')
const deadline = useTransactionDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
const formattedAmounts = {
[Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo('0')
? '0'
: parsedAmounts[Field.LIQUIDITY_PERCENT].lessThan(new Percent('1', '100'))
? '<1'
: parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0),
[Field.LIQUIDITY]:
independentField === Field.LIQUIDITY ? typedValue : parsedAmounts[Field.LIQUIDITY]?.toSignificant(6) ?? '',
[Field.CURRENCY_A]:
independentField === Field.CURRENCY_A ? typedValue : parsedAmounts[Field.CURRENCY_A]?.toSignificant(6) ?? '',
[Field.CURRENCY_B]:
independentField === Field.CURRENCY_B ? typedValue : parsedAmounts[Field.CURRENCY_B]?.toSignificant(6) ?? ''
}
const atMaxAmount = parsedAmounts[Field.LIQUIDITY_PERCENT]?.equalTo(new Percent('1'))
// pair contract
const pairContract: Contract | null = usePairContract(pair?.liquidityToken?.address)
// allowance handling
const [signatureData, setSignatureData] = useState<{ v: number; r: string; s: string; deadline: number } | null>(null)
const [approval, approveCallback] = useApproveCallback(parsedAmounts[Field.LIQUIDITY], ROUTER_ADDRESS)
const isArgentWallet = useIsArgentWallet()
async function onAttemptToApprove() {
if (!pairContract || !pair || !library || !deadline) throw new Error('missing dependencies')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
if (isArgentWallet) {
return approveCallback()
}
// try to gather a signature for permission
const nonce = await pairContract.nonces(account)
const EIP712Domain = [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' }
]
const domain = {
name: 'Uniswap V2',
version: '1',
chainId: chainId,
verifyingContract: pair.liquidityToken.address
}
const Permit = [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' }
]
const message = {
owner: account,
spender: ROUTER_ADDRESS,
value: liquidityAmount.raw.toString(),
nonce: nonce.toHexString(),
deadline: deadline.toNumber()
}
const data = JSON.stringify({
types: {
EIP712Domain,
Permit
},
domain,
primaryType: 'Permit',
message
})
library
.send('eth_signTypedData_v4', [account, data])
.then(splitSignature)
.then(signature => {
setSignatureData({
v: signature.v,
r: signature.r,
s: signature.s,
deadline: deadline.toNumber()
})
})
.catch(error => {
// for all errors other than 4001 (EIP-1193 user rejected request), fall back to manual approve
if (error?.code !== 4001) {
approveCallback()
}
})
}
// wrapped onUserInput to clear signatures
const onUserInput = useCallback(
(field: Field, typedValue: string) => {
setSignatureData(null)
return _onUserInput(field, typedValue)
},
[_onUserInput]
)
const onLiquidityInput = useCallback((typedValue: string): void => onUserInput(Field.LIQUIDITY, typedValue), [
onUserInput
])
const onCurrencyAInput = useCallback((typedValue: string): void => onUserInput(Field.CURRENCY_A, typedValue), [
onUserInput
])
const onCurrencyBInput = useCallback((typedValue: string): void => onUserInput(Field.CURRENCY_B, typedValue), [
onUserInput
])
// tx sending
const addTransaction = useTransactionAdder()
async function onRemove() {
if (!chainId || !library || !account || !deadline) throw new Error('missing dependencies')
const { [Field.CURRENCY_A]: currencyAmountA, [Field.CURRENCY_B]: currencyAmountB } = parsedAmounts
if (!currencyAmountA || !currencyAmountB) {
throw new Error('missing currency amounts')
}
const router = getRouterContract(chainId, library, account)
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(currencyAmountA, allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(currencyAmountB, allowedSlippage)[0]
}
if (!currencyA || !currencyB) throw new Error('missing tokens')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
const currencyBIsETH = currencyB === ETHER
const oneCurrencyIsETH = currencyA === ETHER || currencyBIsETH
if (!tokenA || !tokenB) throw new Error('could not wrap')
let methodNames: string[], args: Array<string | string[] | number | boolean>
// we have approval, use normal remove liquidity
if (approval === ApprovalState.APPROVED) {
// removeLiquidityETH
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETH', 'removeLiquidityETHSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
deadline.toHexString()
]
}
// removeLiquidity
else {
methodNames = ['removeLiquidity']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadline.toHexString()
]
}
}
// we have a signataure, use permit versions of remove liquidity
else if (signatureData !== null) {
// removeLiquidityETHWithPermit
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETHWithPermit', 'removeLiquidityETHWithPermitSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s
]
}
// removeLiquidityETHWithPermit
else {
methodNames = ['removeLiquidityWithPermit']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s
]
}
} else {
throw new Error('Attempting to confirm without approval or a signature. Please contact support.')
}
const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
methodNames.map(methodName =>
router.estimateGas[methodName](...args)
.then(calculateGasMargin)
.catch(error => {
console.error(`estimateGas failed`, methodName, args, error)
return undefined
})
)
)
const indexOfSuccessfulEstimation = safeGasEstimates.findIndex(safeGasEstimate =>
BigNumber.isBigNumber(safeGasEstimate)
)
// all estimations failed...
if (indexOfSuccessfulEstimation === -1) {
console.error('This transaction would fail. Please contact support.')
} else {
const methodName = methodNames[indexOfSuccessfulEstimation]
const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation]
setAttemptingTxn(true)
await router[methodName](...args, {
gasLimit: safeGasEstimate
})
.then((response: TransactionResponse) => {
setAttemptingTxn(false)
addTransaction(response, {
summary:
'Remove ' +
parsedAmounts[Field.CURRENCY_A]?.toSignificant(3) +
' ' +
currencyA?.symbol +
' and ' +
parsedAmounts[Field.CURRENCY_B]?.toSignificant(3) +
' ' +
currencyB?.symbol
})
setTxHash(response.hash)
ReactGA.event({
category: 'Liquidity',
action: 'Remove',
label: [currencyA?.symbol, currencyB?.symbol].join('/')
})
})
.catch((error: Error) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
console.error(error)
})
}
}
function modalHeader() {
return (
<AutoColumn gap={'md'} style={{ marginTop: '20px' }}>
<RowBetween align="flex-end">
<Text fontSize={24} fontWeight={500}>
{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}
</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyA} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<Plus size="16" color={theme.text2} />
</RowFixed>
<RowBetween align="flex-end">
<Text fontSize={24} fontWeight={500}>
{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}
</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyB} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
<TYPE.italic fontSize={12} color={theme.text2} textAlign="left" padding={'12px 0 0 0'}>
{`Output is estimated. If the price changes by more than ${allowedSlippage /
100}% your transaction will revert.`}
</TYPE.italic>
</AutoColumn>
)
}
function modalBottom() {
return (
<>
<RowBetween>
<Text color={theme.text2} fontWeight={500} fontSize={16}>
{'UNI ' + currencyA?.symbol + '/' + currencyB?.symbol} Burned
</Text>
<RowFixed>
<DoubleCurrencyLogo currency0={currencyA} currency1={currencyB} margin={true} />
<Text fontWeight={500} fontSize={16}>
{parsedAmounts[Field.LIQUIDITY]?.toSignificant(6)}
</Text>
</RowFixed>
</RowBetween>
{pair && (
<>
<RowBetween>
<Text color={theme.text2} fontWeight={500} fontSize={16}>
Price
</Text>
<Text fontWeight={500} fontSize={16} color={theme.text1}>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</Text>
</RowBetween>
<RowBetween>
<div />
<Text fontWeight={500} fontSize={16} color={theme.text1}>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</Text>
</RowBetween>
</>
)}
<ButtonPrimary disabled={!(approval === ApprovalState.APPROVED || signatureData !== null)} onClick={onRemove}>
<Text fontWeight={500} fontSize={20}>
Confirm
</Text>
</ButtonPrimary>
</>
)
}
const pendingText = `Removing ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencyA?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencyB?.symbol}`
const liquidityPercentChangeCallback = useCallback(
(value: number) => {
onUserInput(Field.LIQUIDITY_PERCENT, value.toString())
},
[onUserInput]
)
const oneCurrencyIsETH = currencyA === ETHER || currencyB === ETHER
const oneCurrencyIsWETH = Boolean(
chainId &&
((currencyA && currencyEquals(WETH[chainId], currencyA)) ||
(currencyB && currencyEquals(WETH[chainId], currencyB)))
)
const handleSelectCurrencyA = useCallback(
(currency: Currency) => {
if (currencyIdB && currencyId(currency) === currencyIdB) {
history.push(`/remove/${currencyId(currency)}/${currencyIdA}`)
} else {
history.push(`/remove/${currencyId(currency)}/${currencyIdB}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleSelectCurrencyB = useCallback(
(currency: Currency) => {
if (currencyIdA && currencyId(currency) === currencyIdA) {
history.push(`/remove/${currencyIdB}/${currencyId(currency)}`)
} else {
history.push(`/remove/${currencyIdA}/${currencyId(currency)}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
setSignatureData(null) // important that we clear signature data to avoid bad sigs
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.LIQUIDITY_PERCENT, '0')
}
setTxHash('')
}, [onUserInput, txHash])
const [innerLiquidityPercentage, setInnerLiquidityPercentage] = useDebouncedChangeHandler(
Number.parseInt(parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0)),
liquidityPercentChangeCallback
)
return (
<>
<AppBody>
<AddRemoveTabs creating={false} adding={false} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash ? txHash : ''}
content={() => (
<ConfirmationModalContent
title={'You will receive'}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<AutoColumn gap="md">
<BlueCard>
<AutoColumn gap="10px">
<TYPE.link fontWeight={400} color={'primaryText1'}>
<b>Tip:</b> Removing pool tokens converts your position back into underlying tokens at the current
rate, proportional to your share of the pool. Accrued fees are included in the amounts you receive.
</TYPE.link>
</AutoColumn>
</BlueCard>
<LightCard>
<AutoColumn gap="20px">
<RowBetween>
<Text fontWeight={500}>Amount</Text>
<ClickableText
fontWeight={500}
onClick={() => {
setShowDetailed(!showDetailed)
}}
>
{showDetailed ? 'Simple' : 'Detailed'}
</ClickableText>
</RowBetween>
<Row style={{ alignItems: 'flex-end' }}>
<Text fontSize={72} fontWeight={500}>
{formattedAmounts[Field.LIQUIDITY_PERCENT]}%
</Text>
</Row>
{!showDetailed && (
<>
<Slider value={innerLiquidityPercentage} onChange={setInnerLiquidityPercentage} />
<RowBetween>
<MaxButton onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '25')} width="20%">
25%
</MaxButton>
<MaxButton onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '50')} width="20%">
50%
</MaxButton>
<MaxButton onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '75')} width="20%">
75%
</MaxButton>
<MaxButton onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')} width="20%">
Max
</MaxButton>
</RowBetween>
</>
)}
</AutoColumn>
</LightCard>
{!showDetailed && (
<>
<ColumnCenter>
<ArrowDown size="16" color={theme.text2} />
</ColumnCenter>
<LightCard>
<AutoColumn gap="10px">
<RowBetween>
<Text fontSize={24} fontWeight={500}>
{formattedAmounts[Field.CURRENCY_A] || '-'}
</Text>
<RowFixed>
<CurrencyLogo currency={currencyA} style={{ marginRight: '12px' }} />
<Text fontSize={24} fontWeight={500} id="remove-liquidity-tokena-symbol">
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowBetween>
<Text fontSize={24} fontWeight={500}>
{formattedAmounts[Field.CURRENCY_B] || '-'}
</Text>
<RowFixed>
<CurrencyLogo currency={currencyB} style={{ marginRight: '12px' }} />
<Text fontSize={24} fontWeight={500} id="remove-liquidity-tokenb-symbol">
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
{chainId && (oneCurrencyIsWETH || oneCurrencyIsETH) ? (
<RowBetween style={{ justifyContent: 'flex-end' }}>
{oneCurrencyIsETH ? (
<StyledInternalLink
to={`/remove/${currencyA === ETHER ? WETH[chainId].address : currencyIdA}/${
currencyB === ETHER ? WETH[chainId].address : currencyIdB
}`}
>
Receive WETH
</StyledInternalLink>
) : oneCurrencyIsWETH ? (
<StyledInternalLink
to={`/remove/${
currencyA && currencyEquals(currencyA, WETH[chainId]) ? 'ETH' : currencyIdA
}/${currencyB && currencyEquals(currencyB, WETH[chainId]) ? 'ETH' : currencyIdB}`}
>
Receive ETH
</StyledInternalLink>
) : null}
</RowBetween>
) : null}
</AutoColumn>
</LightCard>
</>
)}
{showDetailed && (
<>
<CurrencyInputPanel
value={formattedAmounts[Field.LIQUIDITY]}
onUserInput={onLiquidityInput}
onMax={() => {
onUserInput(Field.LIQUIDITY_PERCENT, '100')
}}
showMaxButton={!atMaxAmount}
disableCurrencySelect
currency={pair?.liquidityToken}
pair={pair}
id="liquidity-amount"
/>
<ColumnCenter>
<ArrowDown size="16" color={theme.text2} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance={true}
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onCurrencyAInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyA}
label={'Output'}
onCurrencySelect={handleSelectCurrencyA}
id="remove-liquidity-tokena"
/>
<ColumnCenter>
<Plus size="16" color={theme.text2} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance={true}
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onCurrencyBInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyB}
label={'Output'}
onCurrencySelect={handleSelectCurrencyB}
id="remove-liquidity-tokenb"
/>
</>
)}
{pair && (
<div style={{ padding: '10px 20px' }}>
<RowBetween>
Price:
<div>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</div>
</RowBetween>
<RowBetween>
<div />
<div>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</div>
</RowBetween>
</div>
)}
<div style={{ position: 'relative' }}>
{!account ? (
<ButtonLight onClick={toggleWalletModal}>Connect Wallet</ButtonLight>
) : (
<RowBetween>
<ButtonConfirmed
onClick={onAttemptToApprove}
confirmed={approval === ApprovalState.APPROVED || signatureData !== null}
disabled={approval !== ApprovalState.NOT_APPROVED || signatureData !== null}
mr="0.5rem"
fontWeight={500}
fontSize={16}
>
{approval === ApprovalState.PENDING ? (
<Dots>Approving</Dots>
) : approval === ApprovalState.APPROVED || signatureData !== null ? (
'Approved'
) : (
'Approve'
)}
</ButtonConfirmed>
<ButtonError
onClick={() => {
setShowConfirm(true)
}}
disabled={!isValid || (signatureData === null && approval !== ApprovalState.APPROVED)}
error={!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]}
>
<Text fontSize={16} fontWeight={500}>
{error || 'Remove'}
</Text>
</ButtonError>
</RowBetween>
)}
</div>
</AutoColumn>
</Wrapper>
</AppBody>
{pair ? (
<AutoColumn style={{ minWidth: '20rem', width: '100%', maxWidth: '400px', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
</AutoColumn>
) : null}
</>
)
}
Example #15
Source File: index.tsx From forward.swaps with GNU General Public License v3.0 | 4 votes |
// import { Contract } from '@ethersproject/contracts'
// const activeClassName = 'ACTIVE'
// const StyledCenter = styled.div`
// align-items: left;
// border-radius: 3rem;
// outline: none;
// cursor: pointer;
// text-decoration: none;
// color: ${({ theme }) => theme.text2};
// font-size: 1rem;
// width: fit-content;
// margin: 10px 12px;
// font-weight: 600;
// &.${activeClassName} {
// border-radius: 12px;
// font-weight: 600;
// color: ${({ theme }) => theme.text1};
// }
// :hover,
// :focus {
// color: ${({ theme }) => darken(0.1, theme.text1)};
// }
// ${({ theme }) => theme.mediaWidth.upToExtraSmall`
// display: none;
// `}
// `
export default function Swap() {
const [gasModalEnable, setGasModalEnable] = useState(false)
const [gasToken, setGasToken] = useState('')
const loadedUrlParams = useDefaultsFromURLSearch()
const { onChangeGasModal } = useWaitActionHandlers()
const { isGasModal } = useWaitState()
// token warning stuff
const [loadedInputCurrency, loadedOutputCurrency] = [
useCurrency(loadedUrlParams?.inputCurrencyId),
useCurrency(loadedUrlParams?.outputCurrencyId)
]
const [dismissTokenWarning, setDismissTokenWarning] = useState<boolean>(false)
const urlLoadedTokens: Token[] = useMemo(
() => [loadedInputCurrency, loadedOutputCurrency]?.filter((c): c is Token => c instanceof Token) ?? [],
[loadedInputCurrency, loadedOutputCurrency]
)
const handleConfirmTokenWarning = useCallback(() => {
setDismissTokenWarning(true)
}, [])
// dismiss warning if all imported tokens are in active lists
const defaultTokens = useAllTokens()
const importTokensNotInDefault =
urlLoadedTokens &&
urlLoadedTokens.filter((token: Token) => {
return !Boolean(token.address in defaultTokens)
})
const { account } = useActiveWeb3React()
const theme = useContext(ThemeContext)
// toggle wallet when disconnected
const toggleWalletModal = useWalletModalToggle()
// for expert mode
const toggleSettings = useToggleSettingsMenu()
const [isExpertMode] = useExpertModeManager()
// get custom setting values for user
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 tradesByVersion = {
[Version.v1]: v1Trade,
[Version.v2]: v2Trade
}
const trade = showWrap ? undefined : tradesByVersion[toggledVersion]
const defaultTrade = showWrap ? undefined : tradesByVersion[DEFAULT_VERSION]
const paths: any = []
const len: any | undefined = trade?.route?.path?.length
if (len > 0) {
for (let i = 0; i < parseInt(len); i++) {
paths[i] = trade?.route.path[i].address
}
}
const betterTradeLinkV2: Version | undefined =
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, '')
},
[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))
const { callback } = useBiconomySwapper(trade, allowedSlippage, recipient)
// the callback to execute the swap
const {
// callback: swapCallback,
error: swapCallbackError
} = useSwapCallback(trade, allowedSlippage, recipient)
const { priceImpactWithoutFee } = computeTradePriceBreakdown(trade)
const [singleHopOnly] = useUserSingleHopOnly()
// const handleSwap = useCallback(async () => {
// if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) {
// return
// }
// if (!swapCallback) {
// return
// }
// setSwapState({ attemptingTxn: true, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: undefined })
// swapCallback()
// .then(hash => {
// setSwapState({ attemptingTxn: false, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: 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,
// getTradeVersion(trade)
// ].join('/')
// })
// ReactGA.event({
// category: 'Routing',
// action: singleHopOnly ? 'Swap with multihop disabled' : 'Swap with multihop enabled'
// })
// })
// .catch(error => {
// setSwapState({
// attemptingTxn: false,
// tradeToConfirm,
// showConfirm,
// swapErrorMessage: error.message,
// txHash: undefined
// })
// })
// }, [
// priceImpactWithoutFee,
// swapCallback,
// tradeToConfirm,
// showConfirm,
// recipient,
// recipientAddress,
// account,
// trade,
// singleHopOnly
// ])
const handleDeposit = useCallback(async () => {
try {
if (!callback) {
return
}
} catch (error) {
console.log('Error: ', error)
}
}, [callback, gasToken])
const setGasTokenAndSwapCallback = useCallback(
(gasTokenValue: any) => {
try {
setGasToken(gasTokenValue)
if (!callback) {
return
}
callback(gasTokenValue)
// wipeInput()
// setGasModalEnable(false)
} catch (error) {
console.log('Error1: ', error)
}
},
[
gasToken,
callback,
priceImpactWithoutFee,
tradeToConfirm,
showConfirm,
recipient,
recipientAddress,
account,
trade,
singleHopOnly
]
)
const hadaleGasModalEnable = useCallback(async () => {
try {
if (gasModalEnable) {
setGasModalEnable(false)
// onUserInput(Field.INPUT, '')
// onUserInput(Field.OUTPUT, '')
} else {
setGasModalEnable(true)
}
} catch (error) {
console.log('Error: ', error)
}
}, [gasModalEnable])
const hadaleIsGasModal = useCallback(async () => {
try {
onChangeGasModal(true)
} catch (error) {
console.log('Error: ', error)
}
}, [isGasModal])
const wipeInput = useCallback(async () => {
try {
onUserInput(Field.INPUT, '')
onUserInput(Field.OUTPUT, '')
} catch (error) {
console.log('Error: ', error)
}
}, [])
// 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({ 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 handleAcceptChanges = useCallback(() => {
setSwapState({ tradeToConfirm: trade, swapErrorMessage, txHash, attemptingTxn, showConfirm })
}, [attemptingTxn, showConfirm, swapErrorMessage, trade, txHash])
const handleInputSelect = useCallback(
(inputCurrency: any) => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onCurrencySelection(Field.INPUT, inputCurrency)
},
[onCurrencySelection]
)
const handleMaxInput = useCallback(() => {
maxAmountInput && onUserInput(Field.INPUT, maxAmountInput.toExact())
}, [maxAmountInput, onUserInput])
const handleOutputSelect = useCallback(
(outputCurrency: any) => {
onCurrencySelection(Field.OUTPUT, outputCurrency)
},
[onCurrencySelection]
)
const swapIsUnsupported = useIsTransactionUnsupported(currencies?.INPUT, currencies?.OUTPUT)
return (
<>
<TokenWarningModal
isOpen={importTokensNotInDefault.length > 0 && !dismissTokenWarning}
tokens={importTokensNotInDefault}
onConfirm={handleConfirmTokenWarning}
/>
<SwapPoolTabs active={'swap'} />
<AppBody>
<SwapHeader />
<Wrapper id="swap-page">
<ConfirmSwapModal
isOpen={showConfirm}
trade={trade}
originalTrade={tradeToConfirm}
onAcceptChanges={handleAcceptChanges}
attemptingTxn={attemptingTxn}
txHash={txHash}
recipient={recipient}
allowedSlippage={allowedSlippage}
onConfirm={handleDeposit}
swapErrorMessage={swapErrorMessage}
onDismiss={handleConfirmDismiss}
/>
<AutoColumn gap={'md'}>
<CurrencyInputPanel
label={independentField === Field.OUTPUT && !showWrap && trade ? 'From (estimated)' : '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>
<ArrowDown
size="16"
onClick={() => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onSwitchTokens()
wipeInput()
}}
color={currencies[Field.INPUT] && currencies[Field.OUTPUT] ? theme.primary1 : theme.text2}
/>
</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)' : '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.text2} />
</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={showWrap ? '.25rem 1rem 0 1rem' : '0px'} borderRadius={'20px'}>
<AutoColumn gap="8px" style={{ padding: '0 16px' }}>
{Boolean(trade) && (
<RowBetween align="center">
<Text fontWeight={500} fontSize={14} color={theme.text2}>
Price
</Text>
<TradePrice
price={trade?.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween>
)}
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
<RowBetween align="center">
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
Slippage Tolerance
</ClickableText>
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
{allowedSlippage / 100}%
</ClickableText>
</RowBetween>
)}
</AutoColumn>
</Card>
)}
</AutoColumn>
{/* //Trial approve */}
{approval !== 3 ? (
<BottomGrouping>
{currencies[Field.INPUT]?.symbol == 'ETH' ? (
<GreyCard style={{ textAlign: 'center' }}>
<TYPE.main mb="4px">ETH is not supported.</TYPE.main>
{singleHopOnly && <TYPE.main mb="4px">Try enabling multi-hop trades.</TYPE.main>}
</GreyCard>
) : swapIsUnsupported ? (
<ButtonPrimary disabled={true}>
<TYPE.main mb="4px">Unsupported Asset</TYPE.main>
</ButtonPrimary>
) : !account ? (
<ButtonLight onClick={toggleWalletModal}>Connect Wallet</ButtonLight>
) : showWrap ? (
<ButtonPrimary disabled={Boolean(wrapInputError)} onClick={onWrap}>
{wrapInputError ??
(wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
</ButtonPrimary>
) : noRoute && userHasSpecifiedInputOutput ? (
<GreyCard style={{ textAlign: 'center' }}>
<TYPE.main mb="4px">Insufficient liquidity for this trade.</TYPE.main>
{singleHopOnly && <TYPE.main mb="4px">Try enabling multi-hop trades.</TYPE.main>}
</GreyCard>
) : (
<ButtonConfirmed
onClick={approveCallback}
disabled={approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
width="100%"
altDisabledStyle={approval === ApprovalState.PENDING} // show solid button while waiting
confirmed={approval === ApprovalState.APPROVED}
>
{approval === ApprovalState.PENDING ? (
<AutoRow gap="6px" justify="center">
Approving <Loader stroke="white" />
</AutoRow>
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
'Approved'
) : (
'Approve ' + currencies[Field.INPUT]?.symbol
)}
</ButtonConfirmed>
)}
{showApproveFlow && (
<Column style={{ marginTop: '1rem' }}>
<ProgressSteps steps={[approval === ApprovalState.APPROVED]} />
</Column>
)}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
{betterTradeLinkV2 && !swapIsUnsupported && toggledVersion === Version.v1 ? (
<BetterTradeLink version={betterTradeLinkV2} />
) : toggledVersion !== DEFAULT_VERSION && defaultTrade ? (
<DefaultVersionLink />
) : null}
</BottomGrouping>
) : (
<BottomGrouping>
<ButtonError
onClick={() => {
// hadaleGasModalEnable()
hadaleIsGasModal()
}}
id="swap-button"
disabled={!isValid || (priceImpactSeverity > 3 && !isExpertMode) || !!swapCallbackError}
error={isValid && priceImpactSeverity > 2 && !swapCallbackError}
>
<Text fontSize={20} fontWeight={500}>
{swapInputError
? swapInputError
: priceImpactSeverity > 3 && !isExpertMode
? `Price Impact Too High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Text>
</ButtonError>
</BottomGrouping>
)}
{/* // Original */}
{/* <BottomGrouping>
{swapIsUnsupported ? (
<ButtonPrimary disabled={true}>
<TYPE.main mb="4px">Unsupported Asset</TYPE.main>
</ButtonPrimary>
) : !account ? (
<ButtonLight onClick={toggleWalletModal}>Connect Wallet</ButtonLight>
) : showWrap ? (
<ButtonPrimary disabled={Boolean(wrapInputError)} onClick={onWrap}>
{wrapInputError ??
(wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
</ButtonPrimary>
) : noRoute && userHasSpecifiedInputOutput ? (
<GreyCard style={{ textAlign: 'center' }}>
<TYPE.main mb="4px">Insufficient liquidity for this trade.</TYPE.main>
{singleHopOnly && <TYPE.main mb="4px">Try enabling multi-hop trades.</TYPE.main>}
</GreyCard>
) : showApproveFlow ? (
<RowBetween>
<ButtonConfirmed
onClick={approveCallback}
disabled={approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
width="48%"
altDisabledStyle={approval === ApprovalState.PENDING} // show solid button while waiting
confirmed={approval === ApprovalState.APPROVED}
>
{approval === ApprovalState.PENDING ? (
<AutoRow gap="6px" justify="center">
Approving <Loader stroke="white" />
</AutoRow>
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
'Approved'
) : (
'Approve ' + currencies[Field.INPUT]?.symbol
)}
</ButtonConfirmed>
<ButtonError
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined
})
}
}}
width="48%"
id="swap-button"
disabled={
!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !isExpertMode)
}
error={isValid && priceImpactSeverity > 2}
>
<Text fontSize={16} fontWeight={500}>
{priceImpactSeverity > 3 && !isExpertMode
? `Price Impact High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Text>
</ButtonError>
</RowBetween>
) : (
<ButtonError
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}
error={isValid && priceImpactSeverity > 2 && !swapCallbackError}
>
<Text fontSize={20} fontWeight={500}>
{swapInputError
? swapInputError
: priceImpactSeverity > 3 && !isExpertMode
? `Price Impact Too High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Text>
</ButtonError>
)}
{showApproveFlow && (
<Column style={{ marginTop: '1rem' }}>
<ProgressSteps steps={[approval === ApprovalState.APPROVED]} />
</Column>
)}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
{betterTradeLinkV2 && !swapIsUnsupported && toggledVersion === Version.v1 ? (
<BetterTradeLink version={betterTradeLinkV2} />
) : toggledVersion !== DEFAULT_VERSION && defaultTrade ? (
<DefaultVersionLink />
) : null}
</BottomGrouping> */}
{/* // TRIAL */}
{/* <BottomGrouping>
{
<ButtonError
onClick={() => {
handleDeposit()
}}
id="swap-button"
error={isValid && priceImpactSeverity > 2 && !swapCallbackError}
>
<Text fontSize={20} fontWeight={500}>
{'Forward Swap'}
</Text>
</ButtonError>
}
{showApproveFlow && (
<Column style={{ marginTop: '1rem' }}>
<ProgressSteps steps={[approval === ApprovalState.APPROVED]} />
</Column>
)}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
{betterTradeLinkV2 && !swapIsUnsupported && toggledVersion === Version.v1 ? (
<BetterTradeLink version={betterTradeLinkV2} />
) : toggledVersion !== DEFAULT_VERSION && defaultTrade ? (
<DefaultVersionLink />
) : null}
</BottomGrouping>
*/}
{isGasModal ? (
<BottomGrouping>
<GasModal
handleDeposit={handleDeposit}
path0={trade && trade.route.path[0].address}
path1={trade && trade.route.path[1].address}
paths={paths}
inputToken={trade && trade.route.input.symbol}
decimals={trade && trade.inputAmount.currency.decimals}
inputAmount={formattedAmounts[Field.INPUT]}
hadaleGasModalEnable={hadaleGasModalEnable}
setGasTokenAndSwapCallback={setGasTokenAndSwapCallback}
wipeInput={wipeInput}
/>
</BottomGrouping>
) : (
''
)}
</Wrapper>
</AppBody>
{!swapIsUnsupported ? (
<AdvancedSwapDetailsDropdown trade={trade} />
) : (
<UnsupportedCurrencyFooter show={swapIsUnsupported} currencies={[currencies.INPUT, currencies.OUTPUT]} />
)}
</>
)
}
Example #16
Source File: index.tsx From sushiswap-exchange with GNU General Public License v3.0 | 4 votes |
export default function RemoveLiquidity({
history,
match: {
params: { currencyIdA, currencyIdB }
}
}: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
const [currencyA, currencyB] = [useCurrency(currencyIdA) ?? undefined, useCurrency(currencyIdB) ?? undefined]
const { account, chainId, library } = useActiveWeb3React()
const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
currencyA,
currencyB,
chainId
])
const theme = useContext(ThemeContext)
// toggle wallet when disconnected
const toggleWalletModal = useWalletModalToggle()
// burn state
const { independentField, typedValue } = useBurnState()
const { pair, parsedAmounts, error } = useDerivedBurnInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onUserInput: _onUserInput } = useBurnActionHandlers()
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [showDetailed, setShowDetailed] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirm
// txn values
const [txHash, setTxHash] = useState<string>('')
const [deadline] = useUserDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
const formattedAmounts = {
[Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo('0')
? '0'
: parsedAmounts[Field.LIQUIDITY_PERCENT].lessThan(new Percent('1', '100'))
? '<1'
: parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0),
[Field.LIQUIDITY]:
independentField === Field.LIQUIDITY ? typedValue : parsedAmounts[Field.LIQUIDITY]?.toSignificant(6) ?? '',
[Field.CURRENCY_A]:
independentField === Field.CURRENCY_A ? typedValue : parsedAmounts[Field.CURRENCY_A]?.toSignificant(6) ?? '',
[Field.CURRENCY_B]:
independentField === Field.CURRENCY_B ? typedValue : parsedAmounts[Field.CURRENCY_B]?.toSignificant(6) ?? ''
}
const atMaxAmount = parsedAmounts[Field.LIQUIDITY_PERCENT]?.equalTo(new Percent('1'))
// pair contract
const pairContract: Contract | null = usePairContract(pair?.liquidityToken?.address)
// allowance handling
const [signatureData, setSignatureData] = useState<{ v: number; r: string; s: string; deadline: number } | null>(null)
const [approval, approveCallback] = useApproveCallback(parsedAmounts[Field.LIQUIDITY], ROUTER_ADDRESS)
async function onAttemptToApprove() {
if (!pairContract || !pair || !library) throw new Error('missing dependencies')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
// try to gather a signature for permission
const nonce = await pairContract.nonces(account)
const deadlineForSignature: number = Math.ceil(Date.now() / 1000) + deadline
const EIP712Domain = [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' }
]
const domain = {
name: 'SushiSwap LP Token',
version: '1',
chainId: chainId,
verifyingContract: pair.liquidityToken.address
}
const Permit = [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' }
]
const message = {
owner: account,
spender: ROUTER_ADDRESS,
value: liquidityAmount.raw.toString(),
nonce: nonce.toHexString(),
deadline: deadlineForSignature
}
const data = JSON.stringify({
types: {
EIP712Domain,
Permit
},
domain,
primaryType: 'Permit',
message
})
library
.send('eth_signTypedData_v4', [account, data])
.then(splitSignature)
.then(signature => {
setSignatureData({
v: signature.v,
r: signature.r,
s: signature.s,
deadline: deadlineForSignature
})
})
.catch(error => {
// for all errors other than 4001 (EIP-1193 user rejected request), fall back to manual approve
if (error?.code !== 4001) {
approveCallback()
}
})
}
// wrapped onUserInput to clear signatures
const onUserInput = useCallback(
(field: Field, typedValue: string) => {
setSignatureData(null)
return _onUserInput(field, typedValue)
},
[_onUserInput]
)
const onLiquidityInput = useCallback((typedValue: string): void => onUserInput(Field.LIQUIDITY, typedValue), [
onUserInput
])
const onCurrencyAInput = useCallback((typedValue: string): void => onUserInput(Field.CURRENCY_A, typedValue), [
onUserInput
])
const onCurrencyBInput = useCallback((typedValue: string): void => onUserInput(Field.CURRENCY_B, typedValue), [
onUserInput
])
// tx sending
const addTransaction = useTransactionAdder()
async function onRemove() {
if (!chainId || !library || !account) throw new Error('missing dependencies')
const { [Field.CURRENCY_A]: currencyAmountA, [Field.CURRENCY_B]: currencyAmountB } = parsedAmounts
if (!currencyAmountA || !currencyAmountB) {
throw new Error('missing currency amounts')
}
const router = getRouterContract(chainId, library, account)
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(currencyAmountA, allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(currencyAmountB, allowedSlippage)[0]
}
if (!currencyA || !currencyB) throw new Error('missing tokens')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
const currencyBIsETH = currencyB === ETHER
const oneCurrencyIsETH = currencyA === ETHER || currencyBIsETH
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
if (!tokenA || !tokenB) throw new Error('could not wrap')
let methodNames: string[], args: Array<string | string[] | number | boolean>
// we have approval, use normal remove liquidity
if (approval === ApprovalState.APPROVED) {
// removeLiquidityETH
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETH', 'removeLiquidityETHSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
deadlineFromNow
]
}
// removeLiquidity
else {
methodNames = ['removeLiquidity']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow
]
}
}
// we have a signataure, use permit versions of remove liquidity
else if (signatureData !== null) {
// removeLiquidityETHWithPermit
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETHWithPermit', 'removeLiquidityETHWithPermitSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s
]
}
// removeLiquidityETHWithPermit
else {
methodNames = ['removeLiquidityWithPermit']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s
]
}
} else {
throw new Error('Attempting to confirm without approval or a signature. Please contact support.')
}
const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
methodNames.map(methodName =>
router.estimateGas[methodName](...args)
.then(calculateGasMargin)
.catch(error => {
console.error(`estimateGas failed`, methodName, args, error)
return undefined
})
)
)
const indexOfSuccessfulEstimation = safeGasEstimates.findIndex(safeGasEstimate =>
BigNumber.isBigNumber(safeGasEstimate)
)
// all estimations failed...
if (indexOfSuccessfulEstimation === -1) {
console.error('This transaction would fail. Please contact support.')
} else {
const methodName = methodNames[indexOfSuccessfulEstimation]
const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation]
setAttemptingTxn(true)
await router[methodName](...args, {
gasLimit: safeGasEstimate
})
.then((response: TransactionResponse) => {
setAttemptingTxn(false)
addTransaction(response, {
summary:
'Remove ' +
parsedAmounts[Field.CURRENCY_A]?.toSignificant(3) +
' ' +
currencyA?.symbol +
' and ' +
parsedAmounts[Field.CURRENCY_B]?.toSignificant(3) +
' ' +
currencyB?.symbol
})
setTxHash(response.hash)
ReactGA.event({
category: 'Liquidity',
action: 'Remove',
label: [currencyA?.symbol, currencyB?.symbol].join('/')
})
})
.catch((error: Error) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
console.error(error)
})
}
}
function modalHeader() {
return (
<AutoColumn gap={'md'} style={{ marginTop: '20px' }}>
<RowBetween align="flex-end">
<Text fontSize={24} fontWeight={500}>
{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}
</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyA} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<Plus size="16" color={theme.text2} />
</RowFixed>
<RowBetween align="flex-end">
<Text fontSize={24} fontWeight={500}>
{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}
</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyB} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
<TYPE.italic fontSize={12} color={theme.text2} textAlign="left" padding={'12px 0 0 0'}>
{`Output is estimated. If the price changes by more than ${allowedSlippage /
100}% your transaction will revert.`}
</TYPE.italic>
</AutoColumn>
)
}
function modalBottom() {
return (
<>
<RowBetween>
<Text color={theme.text2} fontWeight={500} fontSize={16}>
{'UNI ' + currencyA?.symbol + '/' + currencyB?.symbol} Burned
</Text>
<RowFixed>
<DoubleCurrencyLogo currency0={currencyA} currency1={currencyB} margin={true} />
<Text fontWeight={500} fontSize={16}>
{parsedAmounts[Field.LIQUIDITY]?.toSignificant(6)}
</Text>
</RowFixed>
</RowBetween>
{pair && (
<>
<RowBetween>
<Text color={theme.text2} fontWeight={500} fontSize={16}>
Price
</Text>
<Text fontWeight={500} fontSize={16} color={theme.text1}>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</Text>
</RowBetween>
<RowBetween>
<div />
<Text fontWeight={500} fontSize={16} color={theme.text1}>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</Text>
</RowBetween>
</>
)}
<ButtonPrimary disabled={!(approval === ApprovalState.APPROVED || signatureData !== null)} onClick={onRemove}>
<Text fontWeight={500} fontSize={20}>
Confirm
</Text>
</ButtonPrimary>
</>
)
}
const pendingText = `Removing ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencyA?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencyB?.symbol}`
const liquidityPercentChangeCallback = useCallback(
(value: number) => {
onUserInput(Field.LIQUIDITY_PERCENT, value.toString())
},
[onUserInput]
)
const oneCurrencyIsETH = currencyA === ETHER || currencyB === ETHER
const oneCurrencyIsWETH = Boolean(
chainId &&
((currencyA && currencyEquals(WETH[chainId], currencyA)) ||
(currencyB && currencyEquals(WETH[chainId], currencyB)))
)
const handleSelectCurrencyA = useCallback(
(currency: Currency) => {
if (currencyIdB && currencyId(currency) === currencyIdB) {
history.push(`/remove/${currencyId(currency)}/${currencyIdA}`)
} else {
history.push(`/remove/${currencyId(currency)}/${currencyIdB}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleSelectCurrencyB = useCallback(
(currency: Currency) => {
if (currencyIdA && currencyId(currency) === currencyIdA) {
history.push(`/remove/${currencyIdB}/${currencyId(currency)}`)
} else {
history.push(`/remove/${currencyIdA}/${currencyId(currency)}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
setSignatureData(null) // important that we clear signature data to avoid bad sigs
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.LIQUIDITY_PERCENT, '0')
}
setTxHash('')
}, [onUserInput, txHash])
const [innerLiquidityPercentage, setInnerLiquidityPercentage] = useDebouncedChangeHandler(
Number.parseInt(parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0)),
liquidityPercentChangeCallback
)
return (
<>
<AppBody>
<AddRemoveTabs adding={false} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash ? txHash : ''}
content={() => (
<ConfirmationModalContent
title={'You will receive'}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<AutoColumn gap="md">
<LightCard>
<AutoColumn gap="20px">
<RowBetween>
<Text fontWeight={500}>Amount</Text>
<ClickableText
fontWeight={500}
onClick={() => {
setShowDetailed(!showDetailed)
}}
>
{showDetailed ? 'Simple' : 'Detailed'}
</ClickableText>
</RowBetween>
<Row style={{ alignItems: 'flex-end' }}>
<Text fontSize={72} fontWeight={500}>
{formattedAmounts[Field.LIQUIDITY_PERCENT]}%
</Text>
</Row>
{!showDetailed && (
<>
<Slider value={innerLiquidityPercentage} onChange={setInnerLiquidityPercentage} />
<RowBetween>
<MaxButton onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '25')} width="20%">
25%
</MaxButton>
<MaxButton onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '50')} width="20%">
50%
</MaxButton>
<MaxButton onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '75')} width="20%">
75%
</MaxButton>
<MaxButton onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')} width="20%">
Max
</MaxButton>
</RowBetween>
</>
)}
</AutoColumn>
</LightCard>
{!showDetailed && (
<>
<ColumnCenter>
<ArrowDown size="16" color={theme.text2} />
</ColumnCenter>
<LightCard>
<AutoColumn gap="10px">
<RowBetween>
<Text fontSize={24} fontWeight={500}>
{formattedAmounts[Field.CURRENCY_A] || '-'}
</Text>
<RowFixed>
<CurrencyLogo currency={currencyA} style={{ marginRight: '12px' }} />
<Text fontSize={24} fontWeight={500} id="remove-liquidity-tokena-symbol">
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowBetween>
<Text fontSize={24} fontWeight={500}>
{formattedAmounts[Field.CURRENCY_B] || '-'}
</Text>
<RowFixed>
<CurrencyLogo currency={currencyB} style={{ marginRight: '12px' }} />
<Text fontSize={24} fontWeight={500} id="remove-liquidity-tokenb-symbol">
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
{chainId && (oneCurrencyIsWETH || oneCurrencyIsETH) ? (
<RowBetween style={{ justifyContent: 'flex-end' }}>
{oneCurrencyIsETH ? (
<StyledInternalLink
to={`/remove/${currencyA === ETHER ? WETH[chainId].address : currencyIdA}/${
currencyB === ETHER ? WETH[chainId].address : currencyIdB
}`}
>
Receive WETH
</StyledInternalLink>
) : oneCurrencyIsWETH ? (
<StyledInternalLink
to={`/remove/${
currencyA && currencyEquals(currencyA, WETH[chainId]) ? 'ETH' : currencyIdA
}/${currencyB && currencyEquals(currencyB, WETH[chainId]) ? 'ETH' : currencyIdB}`}
>
Receive ETH
</StyledInternalLink>
) : null}
</RowBetween>
) : null}
</AutoColumn>
</LightCard>
</>
)}
{showDetailed && (
<>
<CurrencyInputPanel
value={formattedAmounts[Field.LIQUIDITY]}
onUserInput={onLiquidityInput}
onMax={() => {
onUserInput(Field.LIQUIDITY_PERCENT, '100')
}}
showMaxButton={!atMaxAmount}
disableCurrencySelect
currency={pair?.liquidityToken}
pair={pair}
id="liquidity-amount"
/>
<ColumnCenter>
<ArrowDown size="16" color={theme.text2} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance={true}
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onCurrencyAInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyA}
label={'Output'}
onCurrencySelect={handleSelectCurrencyA}
id="remove-liquidity-tokena"
/>
<ColumnCenter>
<Plus size="16" color={theme.text2} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance={true}
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onCurrencyBInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyB}
label={'Output'}
onCurrencySelect={handleSelectCurrencyB}
id="remove-liquidity-tokenb"
/>
</>
)}
{pair && (
<div style={{ padding: '10px 20px' }}>
<RowBetween>
Price:
<div>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</div>
</RowBetween>
<RowBetween>
<div />
<div>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</div>
</RowBetween>
</div>
)}
<div style={{ position: 'relative' }}>
{!account ? (
<ButtonLight onClick={toggleWalletModal}>Connect Wallet</ButtonLight>
) : (
<RowBetween>
<ButtonConfirmed
onClick={onAttemptToApprove}
confirmed={approval === ApprovalState.APPROVED || signatureData !== null}
disabled={approval !== ApprovalState.NOT_APPROVED || signatureData !== null}
mr="0.5rem"
fontWeight={500}
fontSize={16}
>
{approval === ApprovalState.PENDING ? (
<Dots>Approving</Dots>
) : approval === ApprovalState.APPROVED || signatureData !== null ? (
'Approved'
) : (
'Approve'
)}
</ButtonConfirmed>
<ButtonError
onClick={() => {
setShowConfirm(true)
}}
disabled={!isValid || (signatureData === null && approval !== ApprovalState.APPROVED)}
error={!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]}
>
<Text fontSize={16} fontWeight={500}>
{error || 'Remove'}
</Text>
</ButtonError>
</RowBetween>
)}
</div>
</AutoColumn>
</Wrapper>
</AppBody>
{pair ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
</AutoColumn>
) : null}
</>
)
}
Example #17
Source File: index.tsx From sushiswap-exchange with GNU General Public License v3.0 | 4 votes |
export default function Swap() {
const loadedUrlParams = useDefaultsFromURLSearch()
// token warning stuff
const [loadedInputCurrency, loadedOutputCurrency] = [
useCurrency(loadedUrlParams?.inputCurrencyId),
useCurrency(loadedUrlParams?.outputCurrencyId)
]
const [dismissTokenWarning, setDismissTokenWarning] = useState<boolean>(false)
const urlLoadedTokens: Token[] = useMemo(
() => [loadedInputCurrency, loadedOutputCurrency]?.filter((c): c is Token => c instanceof Token) ?? [],
[loadedInputCurrency, loadedOutputCurrency]
)
const handleConfirmTokenWarning = useCallback(() => {
setDismissTokenWarning(true)
}, [])
const { account } = useActiveWeb3React()
const theme = useContext(ThemeContext)
// toggle wallet when disconnected
const toggleWalletModal = useWalletModalToggle()
// for expert mode
const toggleSettings = useToggleSettingsMenu()
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
)
console.log('?', swapCallback, trade, allowedSlippage, deadline, recipient)
const { priceImpactWithoutFee } = computeTradePriceBreakdown(trade)
const handleSwap = useCallback(() => {
if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) {
return
}
if (!swapCallback) {
return
}
setSwapState({ attemptingTxn: true, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: undefined })
swapCallback()
.then(hash => {
setSwapState({ attemptingTxn: false, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: 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,
getTradeVersion(trade)
].join('/')
})
})
.catch(error => {
setSwapState({
attemptingTxn: false,
tradeToConfirm,
showConfirm,
swapErrorMessage: error.message,
txHash: undefined
})
})
}, [tradeToConfirm, account, priceImpactWithoutFee, recipient, recipientAddress, showConfirm, swapCallback, trade])
// 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({ 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 handleAcceptChanges = useCallback(() => {
setSwapState({ tradeToConfirm: trade, swapErrorMessage, txHash, attemptingTxn, showConfirm })
}, [attemptingTxn, showConfirm, swapErrorMessage, trade, txHash])
const handleInputSelect = useCallback(
inputCurrency => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onCurrencySelection(Field.INPUT, inputCurrency)
},
[onCurrencySelection]
)
const handleMaxInput = useCallback(() => {
maxAmountInput && onUserInput(Field.INPUT, maxAmountInput.toExact())
}, [maxAmountInput, onUserInput])
const handleOutputSelect = useCallback(outputCurrency => onCurrencySelection(Field.OUTPUT, outputCurrency), [
onCurrencySelection
])
return (
<>
<TokenWarningModal
isOpen={urlLoadedTokens.length > 0 && !dismissTokenWarning}
tokens={urlLoadedTokens}
onConfirm={handleConfirmTokenWarning}
/>
<AppBody>
<SwapPoolTabs active={'swap'} />
<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}
/>
<AutoColumn gap={'md'}>
<CurrencyInputPanel
label={independentField === Field.OUTPUT && !showWrap && trade ? 'From (estimated)' : '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>
<ArrowDown
size="16"
onClick={() => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onSwitchTokens()
}}
color={currencies[Field.INPUT] && currencies[Field.OUTPUT] ? theme.primary1 : theme.text2}
/>
</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)' : '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.text2} />
</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 fontWeight={500} fontSize={14} color={theme.text2}>
Price
</Text>
<TradePrice
price={trade?.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween>
)}
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
<RowBetween align="center">
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
Slippage Tolerance
</ClickableText>
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
{allowedSlippage / 100}%
</ClickableText>
</RowBetween>
)}
</AutoColumn>
</Card>
)}
</AutoColumn>
<BottomGrouping>
{!account ? (
<ButtonLight onClick={toggleWalletModal}>Connect Wallet</ButtonLight>
) : showWrap ? (
<ButtonPrimary disabled={Boolean(wrapInputError)} onClick={onWrap}>
{wrapInputError ??
(wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
</ButtonPrimary>
) : noRoute && userHasSpecifiedInputOutput ? (
<GreyCard style={{ textAlign: 'center' }}>
<TYPE.main mb="4px">Insufficient liquidity for this trade.</TYPE.main>
</GreyCard>
) : showApproveFlow ? (
<RowBetween>
<ButtonConfirmed
onClick={approveCallback}
disabled={approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
width="48%"
altDisabledStyle={approval === ApprovalState.PENDING} // show solid button while waiting
confirmed={approval === ApprovalState.APPROVED}
>
{approval === ApprovalState.PENDING ? (
<AutoRow gap="6px" justify="center">
Approving <Loader stroke="white" />
</AutoRow>
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
'Approved'
) : (
'Approve ' + currencies[Field.INPUT]?.symbol
)}
</ButtonConfirmed>
<ButtonError
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined
})
}
}}
width="48%"
id="swap-button"
disabled={
!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !isExpertMode)
}
error={isValid && priceImpactSeverity > 2}
>
<Text fontSize={16} fontWeight={500}>
{priceImpactSeverity > 3 && !isExpertMode
? `Price Impact High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Text>
</ButtonError>
</RowBetween>
) : (
<ButtonError
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}
error={isValid && priceImpactSeverity > 2 && !swapCallbackError}
>
<Text fontSize={20} fontWeight={500}>
{swapInputError
? swapInputError
: priceImpactSeverity > 3 && !isExpertMode
? `Price Impact Too High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Text>
</ButtonError>
)}
{showApproveFlow && <ProgressSteps steps={[approval === ApprovalState.APPROVED]} />}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
{betterTradeLinkVersion && <BetterTradeLink version={betterTradeLinkVersion} />}
</BottomGrouping>
</Wrapper>
</AppBody>
<AdvancedSwapDetailsDropdown trade={trade} />
</>
)
}
Example #18
Source File: SwapModalHeader.tsx From luaswap-interface with GNU General Public License v3.0 | 4 votes |
export default function SwapModalHeader({
trade,
allowedSlippage,
recipient,
showAcceptChanges,
onAcceptChanges
}: {
trade: Trade
allowedSlippage: number
recipient: string | null
showAcceptChanges: boolean
onAcceptChanges: () => void
}) {
const slippageAdjustedAmounts = useMemo(() => computeSlippageAdjustedAmounts(trade, allowedSlippage), [
trade,
allowedSlippage
])
const { priceImpactWithoutFee } = useMemo(() => computeTradePriceBreakdown(trade), [trade])
const priceImpactSeverity = warningSeverity(priceImpactWithoutFee)
const theme = useContext(ThemeContext)
return (
<AutoColumn gap={'md'} style={{ marginTop: '20px' }}>
<RowBetween align="flex-end">
<RowFixed gap={'0px'}>
<CurrencyLogo currency={trade.inputAmount.currency} size={'24px'} style={{ marginRight: '12px' }} />
<TruncatedText
fontSize={24}
fontWeight={500}
color={showAcceptChanges && trade.tradeType === TradeType.EXACT_OUTPUT ? theme.primary1 : ''}
>
{trade.inputAmount.toSignificant(6)}
</TruncatedText>
</RowFixed>
<RowFixed gap={'0px'}>
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{trade.inputAmount.currency.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<ArrowDown size="16" color={theme.text2} style={{ marginLeft: '4px', minWidth: '16px' }} />
</RowFixed>
<RowBetween align="flex-end">
<RowFixed gap={'0px'}>
<CurrencyLogo currency={trade.outputAmount.currency} size={'24px'} style={{ marginRight: '12px' }} />
<TruncatedText
fontSize={24}
fontWeight={500}
color={
priceImpactSeverity > 2
? theme.red1
: showAcceptChanges && trade.tradeType === TradeType.EXACT_INPUT
? theme.primary1
: ''
}
>
{trade.outputAmount.toSignificant(6)}
</TruncatedText>
</RowFixed>
<RowFixed gap={'0px'}>
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{trade.outputAmount.currency.symbol}
</Text>
</RowFixed>
</RowBetween>
{showAcceptChanges ? (
<SwapShowAcceptChanges justify="flex-start" gap={'0px'}>
<RowBetween>
<RowFixed>
<AlertTriangle size={20} style={{ marginRight: '8px', minWidth: 24 }} />
<TYPE.main color={theme.primary1}> Price Updated</TYPE.main>
</RowFixed>
<ButtonPrimary
style={{ padding: '.5rem', width: 'fit-content', fontSize: '0.825rem', borderRadius: '12px' }}
onClick={onAcceptChanges}
>
Accept
</ButtonPrimary>
</RowBetween>
</SwapShowAcceptChanges>
) : null}
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '12px 0 0 0px' }}>
{trade.tradeType === TradeType.EXACT_INPUT ? (
<TYPE.italic textAlign="left" style={{ width: '100%' }}>
{`Output is estimated. You will receive at least `}
<b>
{slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {trade.outputAmount.currency.symbol}
</b>
{' or the transaction will revert.'}
</TYPE.italic>
) : (
<TYPE.italic textAlign="left" style={{ width: '100%' }}>
{`Input is estimated. You will sell at most `}
<b>
{slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {trade.inputAmount.currency.symbol}
</b>
{' or the transaction will revert.'}
</TYPE.italic>
)}
</AutoColumn>
{recipient !== null ? (
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '12px 0 0 0px' }}>
<TYPE.main>
Output will be sent to{' '}
<b title={recipient}>{isAddress(recipient) ? shortenAddress(recipient) : recipient}</b>
</TYPE.main>
</AutoColumn>
) : null}
</AutoColumn>
)
}
Example #19
Source File: index.tsx From luaswap-interface with GNU General Public License v3.0 | 4 votes |
export default function RemoveLiquidity({
history,
match: {
params: { currencyIdA, currencyIdB }
}
}: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
const [currencyA, currencyB] = [useCurrency(currencyIdA) ?? undefined, useCurrency(currencyIdB) ?? undefined]
const { account, chainId, library } = useActiveWeb3React()
const NATIVE_TOKEN_TEXT = getTextNativeToken(chainId)
const IsTomo = IsTomoChain(chainId)
const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
currencyA,
currencyB,
chainId
])
const theme = useContext(ThemeContext)
// toggle wallet when disconnected
const toggleWalletModal = useWalletModalToggle()
// burn state
const { independentField, typedValue } = useBurnState()
const { pair, parsedAmounts, error } = useDerivedBurnInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onUserInput: _onUserInput } = useBurnActionHandlers()
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [showDetailed, setShowDetailed] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirm
// txn values
const [txHash, setTxHash] = useState<string>('')
const deadline = useTransactionDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
const formattedAmounts = {
[Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo('0')
? '0'
: parsedAmounts[Field.LIQUIDITY_PERCENT].lessThan(new Percent('1', '100'))
? '<1'
: parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0),
[Field.LIQUIDITY]:
independentField === Field.LIQUIDITY ? typedValue : parsedAmounts[Field.LIQUIDITY]?.toSignificant(6) ?? '',
[Field.CURRENCY_A]:
independentField === Field.CURRENCY_A ? typedValue : parsedAmounts[Field.CURRENCY_A]?.toSignificant(6) ?? '',
[Field.CURRENCY_B]:
independentField === Field.CURRENCY_B ? typedValue : parsedAmounts[Field.CURRENCY_B]?.toSignificant(6) ?? ''
}
const atMaxAmount = parsedAmounts[Field.LIQUIDITY_PERCENT]?.equalTo(new Percent('1'))
// pair contract
const pairContract: Contract | null = usePairContract(pair?.liquidityToken?.address)
// allowance handling
const [signatureData, setSignatureData] = useState<{ v: number; r: string; s: string; deadline: number } | null>(null)
const [approval, approveCallback] = useApproveCallback(
parsedAmounts[Field.LIQUIDITY],
IsTomo ? TOMO_ROUTER_ADDRESS : ROUTER_ADDRESS
)
const isArgentWallet = useIsArgentWallet()
async function onAttemptToApprove() {
if (!pairContract || !pair || !library || !deadline) throw new Error('missing dependencies')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
if (isArgentWallet) {
return approveCallback()
}
// try to gather a signature for permission
const nonce = await pairContract.nonces(account)
const EIP712Domain = [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' }
]
const domain = {
name: 'LuaSwap LP Token V1',
version: '1',
chainId: chainId,
verifyingContract: pair.liquidityToken.address
}
const Permit = [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' }
]
const message = {
owner: account,
spender: IsTomo ? TOMO_ROUTER_ADDRESS : ROUTER_ADDRESS,
value: liquidityAmount.raw.toString(),
nonce: nonce.toHexString(),
deadline: deadline.toNumber()
}
const data = JSON.stringify({
types: {
EIP712Domain,
Permit
},
domain,
primaryType: 'Permit',
message
})
library
.send('eth_signTypedData_v4', [account, data])
.then(splitSignature)
.then(signature => {
setSignatureData({
v: signature.v,
r: signature.r,
s: signature.s,
deadline: deadline.toNumber()
})
})
.catch(error => {
// for all errors other than 4001 (EIP-1193 user rejected request), fall back to manual approve
if (error?.code !== 4001) {
approveCallback()
}
})
}
// wrapped onUserInput to clear signatures
const onUserInput = useCallback(
(field: Field, typedValue: string) => {
setSignatureData(null)
return _onUserInput(field, typedValue)
},
[_onUserInput]
)
const onLiquidityInput = useCallback((typedValue: string): void => onUserInput(Field.LIQUIDITY, typedValue), [
onUserInput
])
const onCurrencyAInput = useCallback((typedValue: string): void => onUserInput(Field.CURRENCY_A, typedValue), [
onUserInput
])
const onCurrencyBInput = useCallback((typedValue: string): void => onUserInput(Field.CURRENCY_B, typedValue), [
onUserInput
])
// tx sending
const addTransaction = useTransactionAdder()
async function onRemove() {
if (!chainId || !library || !account || !deadline) throw new Error('missing dependencies')
const { [Field.CURRENCY_A]: currencyAmountA, [Field.CURRENCY_B]: currencyAmountB } = parsedAmounts
if (!currencyAmountA || !currencyAmountB) {
throw new Error('missing currency amounts')
}
const router = getRouterContract(chainId, library, account)
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(currencyAmountA, allowedSlippage + 10)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(currencyAmountB, allowedSlippage + 10)[0]
}
if (!currencyA || !currencyB) throw new Error('missing tokens')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
const currencyBIsETH = currencyB === ETHER || currencyB === TOMO
const oneCurrencyIsETH = currencyA === ETHER || currencyA === TOMO || currencyBIsETH
if (!tokenA || !tokenB) throw new Error('could not wrap')
let methodNames: string[], args: Array<string | string[] | number | boolean>
// we have approval, use normal remove liquidity
if (approval === ApprovalState.APPROVED) {
// removeLiquidityETH
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETH', 'removeLiquidityETHSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
deadline.toHexString()
]
}
// removeLiquidity
else {
methodNames = ['removeLiquidity']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadline.toHexString()
]
}
}
// we have a signataure, use permit versions of remove liquidity
else if (signatureData !== null) {
// removeLiquidityETHWithPermit
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETHWithPermit', 'removeLiquidityETHWithPermitSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s
]
}
// removeLiquidityETHWithPermit
else {
methodNames = ['removeLiquidityWithPermit']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s
]
}
} else {
throw new Error('Attempting to confirm without approval or a signature. Please contact support.')
}
const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
methodNames.map(methodName =>
router.estimateGas[methodName](...args)
.then(calculateGasMargin)
.catch(error => {
console.error(`estimateGas failed`, methodName, args, error)
return undefined
})
)
)
const indexOfSuccessfulEstimation = safeGasEstimates.findIndex(safeGasEstimate =>
BigNumber.isBigNumber(safeGasEstimate)
)
// all estimations failed...
if (indexOfSuccessfulEstimation === -1) {
console.error('This transaction would fail. Please contact support.')
} else {
const methodName = methodNames[indexOfSuccessfulEstimation]
const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation]
setAttemptingTxn(true)
await router[methodName](...args, {
gasLimit: safeGasEstimate
})
.then((response: TransactionResponse) => {
setAttemptingTxn(false)
addTransaction(response, {
summary:
'Remove ' +
parsedAmounts[Field.CURRENCY_A]?.toSignificant(3) +
' ' +
currencyA?.symbol +
' and ' +
parsedAmounts[Field.CURRENCY_B]?.toSignificant(3) +
' ' +
currencyB?.symbol
})
setTxHash(response.hash)
ReactGA.event({
category: 'Liquidity',
action: 'Remove',
label: [currencyA?.symbol, currencyB?.symbol].join('/')
})
})
.catch((error: Error) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
console.error(error)
})
}
}
function modalHeader() {
return (
<AutoColumn gap={'md'} style={{ marginTop: '20px' }}>
<RowBetween align="flex-end">
<Text fontSize={24} fontWeight={500}>
{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}
</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyA} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<Plus size="16" color={theme.text2} />
</RowFixed>
<RowBetween align="flex-end">
<Text fontSize={24} fontWeight={500}>
{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}
</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyB} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
<TYPE.italic fontSize={12} color={theme.text2} textAlign="left" padding={'12px 0 0 0'}>
{`Output is estimated. If the price changes by more than ${allowedSlippage /
100}% your transaction will revert.`}
</TYPE.italic>
</AutoColumn>
)
}
function modalBottom() {
return (
<>
<RowBetween>
<Text color={theme.text2} fontWeight={500} fontSize={16}>
{currencyA?.symbol + '/' + currencyB?.symbol} Burned
</Text>
<RowFixed>
<DoubleCurrencyLogo currency0={currencyA} currency1={currencyB} margin={true} />
<Text fontWeight={500} fontSize={16}>
{parsedAmounts[Field.LIQUIDITY]?.toSignificant(6)}
</Text>
</RowFixed>
</RowBetween>
{pair && (
<div style={{ padding: '20px 0px', fontSize: '14px' }}>
<RowBetween>
Withdraw Fee:
<div>
{formattedAmounts[Field.CURRENCY_A]
? (parseFloat(formattedAmounts[Field.CURRENCY_A]) * BASE_WITHDRAW_FEE).toFixed(6)
: 0}{' '}
{currencyA?.symbol}
</div>
</RowBetween>
<RowBetween>
<div />
<div>
{formattedAmounts[Field.CURRENCY_B]
? (parseFloat(formattedAmounts[Field.CURRENCY_B]) * BASE_WITHDRAW_FEE).toFixed(6)
: 0}{' '}
{currencyB?.symbol}
</div>
</RowBetween>
</div>
)}
<ButtonPrimary disabled={!(approval === ApprovalState.APPROVED || signatureData !== null)} onClick={onRemove}>
<Text fontWeight={500} fontSize={20}>
Confirm
</Text>
</ButtonPrimary>
</>
)
}
const pendingText = `Removing ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencyA?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencyB?.symbol}`
const liquidityPercentChangeCallback = useCallback(
(value: number) => {
onUserInput(Field.LIQUIDITY_PERCENT, value.toString())
},
[onUserInput]
)
const oneCurrencyIsETH = currencyA === ETHER || currencyB === ETHER || currencyA === TOMO || currencyB === TOMO
const oneCurrencyIsWETH = Boolean(
chainId &&
((currencyA && currencyEquals(WETH[chainId], currencyA)) ||
(currencyB && currencyEquals(WETH[chainId], currencyB)))
)
const handleSelectCurrencyA = useCallback(
(currency: Currency) => {
if (currencyIdB && currencyId(currency) === currencyIdB) {
history.push(`/remove/${currencyId(currency)}/${currencyIdA}`)
} else {
history.push(`/remove/${currencyId(currency)}/${currencyIdB}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleSelectCurrencyB = useCallback(
(currency: Currency) => {
if (currencyIdA && currencyId(currency) === currencyIdA) {
history.push(`/remove/${currencyIdB}/${currencyId(currency)}`)
} else {
history.push(`/remove/${currencyIdA}/${currencyId(currency)}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
setSignatureData(null) // important that we clear signature data to avoid bad sigs
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.LIQUIDITY_PERCENT, '0')
}
setTxHash('')
}, [onUserInput, txHash])
const [innerLiquidityPercentage, setInnerLiquidityPercentage] = useDebouncedChangeHandler(
Number.parseInt(parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0)),
liquidityPercentChangeCallback
)
return (
<>
<AppBody>
<AddRemoveTabs creating={false} adding={false} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash ? txHash : ''}
content={() => (
<ConfirmationModalContent
title={'You will receive'}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<AutoColumn gap="md">
<LightCard>
<AutoColumn gap="20px">
<RowBetween>
<Text fontWeight={500}>Amount</Text>
<ClickableText
fontWeight={500}
onClick={() => {
setShowDetailed(!showDetailed)
}}
>
{showDetailed ? 'Simple' : 'Detailed'}
</ClickableText>
</RowBetween>
<Row style={{ alignItems: 'flex-end' }}>
<Text fontSize={72} fontWeight={500}>
{formattedAmounts[Field.LIQUIDITY_PERCENT]}%
</Text>
</Row>
{!showDetailed && (
<>
<Slider value={innerLiquidityPercentage} onChange={setInnerLiquidityPercentage} />
<RowBetween>
<MaxButton onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '25')} width="20%">
25%
</MaxButton>
<MaxButton onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '50')} width="20%">
50%
</MaxButton>
<MaxButton onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '75')} width="20%">
75%
</MaxButton>
<MaxButton onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')} width="20%">
Max
</MaxButton>
</RowBetween>
</>
)}
</AutoColumn>
</LightCard>
{!showDetailed && (
<>
<ColumnCenter>
<ArrowDown size="16" color={theme.text2} />
</ColumnCenter>
<LightCard>
<AutoColumn gap="10px">
<RowBetween>
<Text fontSize={24} fontWeight={500}>
{formattedAmounts[Field.CURRENCY_A] || '-'}
</Text>
<RowFixed>
<CurrencyLogo currency={currencyA} style={{ marginRight: '12px' }} />
<Text fontSize={24} fontWeight={500} id="remove-liquidity-tokena-symbol">
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
{pair && (
<RowRight color={theme.text3}>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'}{' '}
{currencyB?.symbol}
</RowRight>
)}
<RowBetween>
<Text fontSize={24} fontWeight={500}>
{formattedAmounts[Field.CURRENCY_B] || '-'}
</Text>
<RowFixed>
<CurrencyLogo currency={currencyB} style={{ marginRight: '12px' }} />
<Text fontSize={24} fontWeight={500} id="remove-liquidity-tokenb-symbol">
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
{pair && (
<RowRight color={theme.text3}>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'}{' '}
{currencyA?.symbol}
</RowRight>
)}
{chainId && (oneCurrencyIsWETH || oneCurrencyIsETH) ? (
<RowBetween style={{ justifyContent: 'flex-end' }}>
{oneCurrencyIsETH ? (
<StyledInternalLink
to={`/remove/${
currencyA === ETHER || currencyA === TOMO ? WETH[chainId].address : currencyIdA
}/${currencyB === ETHER || currencyB === TOMO ? WETH[chainId].address : currencyIdB}`}
>
Receive W{NATIVE_TOKEN_TEXT}
</StyledInternalLink>
) : oneCurrencyIsWETH ? (
<StyledInternalLink
to={`/remove/${
currencyA && currencyEquals(currencyA, WETH[chainId]) && IsTomo
? 'TOMO'
: currencyA && currencyEquals(currencyA, WETH[chainId])
? 'ETH'
: currencyIdA
}/${
currencyB && currencyEquals(currencyB, WETH[chainId]) && IsTomo
? 'TOMO'
: currencyB && currencyEquals(currencyB, WETH[chainId])
? 'ETH'
: currencyIdB
}`}
>
Receive {NATIVE_TOKEN_TEXT}
</StyledInternalLink>
) : null}
</RowBetween>
) : null}
</AutoColumn>
</LightCard>
</>
)}
{showDetailed && (
<>
<CurrencyInputPanel
value={formattedAmounts[Field.LIQUIDITY]}
onUserInput={onLiquidityInput}
onMax={() => {
onUserInput(Field.LIQUIDITY_PERCENT, '100')
}}
showMaxButton={!atMaxAmount}
disableCurrencySelect
currency={pair?.liquidityToken}
pair={pair}
id="liquidity-amount"
/>
{pair && (
<div
style={{
padding: '0 20px 0 20px',
display: 'flex',
fontSize: '12px',
justifyContent: 'space-between'
}}
>
<RowBetween color={theme.text3}>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</RowBetween>
<RowRight color={theme.text3}>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</RowRight>
</div>
)}
<ColumnCenter>
<ArrowDown size="16" color={theme.text2} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance={true}
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onCurrencyAInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyA}
label={'Output'}
onCurrencySelect={handleSelectCurrencyA}
id="remove-liquidity-tokena"
/>
<ColumnCenter>
<Plus size="16" color={theme.text2} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance={true}
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onCurrencyBInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyB}
label={'Output'}
onCurrencySelect={handleSelectCurrencyB}
id="remove-liquidity-tokenb"
/>
</>
)}
{pair && (
<div style={{ padding: '10px 20px', fontSize: '14px' }}>
<RowBetween>
Withdraw Fee:
<div>
{formattedAmounts[Field.CURRENCY_A]
? (parseFloat(formattedAmounts[Field.CURRENCY_A]) * BASE_WITHDRAW_FEE).toFixed(6)
: 0}{' '}
{currencyA?.symbol}
</div>
</RowBetween>
<RowBetween>
<div />
<div>
{formattedAmounts[Field.CURRENCY_B]
? (parseFloat(formattedAmounts[Field.CURRENCY_B]) * BASE_WITHDRAW_FEE).toFixed(6)
: 0}{' '}
{currencyB?.symbol}
</div>
</RowBetween>
</div>
)}
<div style={{ position: 'relative', padding: '20px' }}>
{!account ? (
<ButtonLight onClick={toggleWalletModal}>Connect Wallet</ButtonLight>
) : (
<RowBetween>
<ButtonConfirmed
onClick={onAttemptToApprove}
confirmed={approval === ApprovalState.APPROVED || signatureData !== null}
disabled={approval !== ApprovalState.NOT_APPROVED || signatureData !== null}
mr="0.5rem"
fontWeight={500}
fontSize={16}
>
{approval === ApprovalState.PENDING ? (
<Dots>Approving</Dots>
) : approval === ApprovalState.APPROVED || signatureData !== null ? (
'Approved'
) : (
'Approve'
)}
</ButtonConfirmed>
<ButtonError
onClick={() => {
setShowConfirm(true)
}}
disabled={!isValid || (signatureData === null && approval !== ApprovalState.APPROVED)}
error={!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]}
>
<Text fontSize={16} fontWeight={500}>
{error || 'Remove'}
</Text>
</ButtonError>
</RowBetween>
)}
</div>
</AutoColumn>
</Wrapper>
</AppBody>
{pair ? (
<AutoColumn
style={{
minWidth: '20rem',
width: '100%',
maxWidth: '400px',
marginTop: '1rem',
marginLeft: 'auto',
marginRight: 'auto'
}}
>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
</AutoColumn>
) : null}
</>
)
}
Example #20
Source File: index.tsx From luaswap-interface with GNU General Public License v3.0 | 4 votes |
export default function Swap() {
const loadedUrlParams = useDefaultsFromURLSearch()
// token warning stuff
const [loadedInputCurrency, loadedOutputCurrency] = [
useCurrency(loadedUrlParams?.inputCurrencyId),
useCurrency(loadedUrlParams?.outputCurrencyId)
]
const [dismissTokenWarning, setDismissTokenWarning] = useState<boolean>(false)
const urlLoadedTokens: Token[] = useMemo(
() => [loadedInputCurrency, loadedOutputCurrency]?.filter((c): c is Token => c instanceof Token) ?? [],
[loadedInputCurrency, loadedOutputCurrency]
)
const handleConfirmTokenWarning = useCallback(() => {
setDismissTokenWarning(true)
}, [])
const { account } = useActiveWeb3React()
const theme = useContext(ThemeContext)
// toggle wallet when disconnected
const toggleWalletModal = useWalletModalToggle()
// for expert mode
const toggleSettings = useToggleSettingsMenu()
const [isExpertMode] = useExpertModeManager()
// get custom setting values for user
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 tradesByVersion = {
[Version.v1]: v1Trade,
[Version.v2]: v2Trade
}
const trade = showWrap ? undefined : tradesByVersion[toggledVersion]
const defaultTrade = showWrap ? undefined : tradesByVersion[DEFAULT_VERSION]
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, recipient)
const { priceImpactWithoutFee } = computeTradePriceBreakdown(trade)
const handleSwap = useCallback(() => {
// remove alert Confirm Price Impact High on Mobile
if (!isMobile && priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) {
return
}
if (!swapCallback) {
return
}
setSwapState({ attemptingTxn: true, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: undefined })
swapCallback()
.then(hash => {
setSwapState({ attemptingTxn: false, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: 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,
getTradeVersion(trade)
].join('/')
})
})
.catch(error => {
setSwapState({
attemptingTxn: false,
tradeToConfirm,
showConfirm,
swapErrorMessage: error.message,
txHash: undefined
})
})
}, [tradeToConfirm, account, priceImpactWithoutFee, recipient, recipientAddress, showConfirm, swapCallback, trade])
// 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({ 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 handleAcceptChanges = useCallback(() => {
setSwapState({ tradeToConfirm: trade, swapErrorMessage, txHash, attemptingTxn, showConfirm })
}, [attemptingTxn, showConfirm, swapErrorMessage, trade, txHash])
const handleInputSelect = useCallback(
inputCurrency => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onCurrencySelection(Field.INPUT, inputCurrency)
},
[onCurrencySelection]
)
const handleMaxInput = useCallback(() => {
maxAmountInput && onUserInput(Field.INPUT, maxAmountInput.toExact())
}, [maxAmountInput, onUserInput])
const handleOutputSelect = useCallback(outputCurrency => onCurrencySelection(Field.OUTPUT, outputCurrency), [
onCurrencySelection
])
return (
<>
<NoticeTomoBridge />
<TokenWarningModal
isOpen={urlLoadedTokens.length > 0 && !dismissTokenWarning}
tokens={urlLoadedTokens}
onConfirm={handleConfirmTokenWarning}
/>
<AppBody>
<SwapPoolTabs active={'swap'} />
<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}
/>
<AutoColumn gap={'md'} style={{ padding: '2em 1em 0 1em' }}>
<CurrencyInputPanel
label={independentField === Field.OUTPUT && !showWrap && trade ? 'From (estimated)' : '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>
<ArrowDown
size="16"
onClick={() => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onSwitchTokens()
}}
color={currencies[Field.INPUT] && currencies[Field.OUTPUT] ? theme.primary1 : theme.text2}
/>
</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)' : '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.text2} />
</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 fontWeight={500} fontSize={14} color={theme.text2}>
Price
</Text>
<TradePrice
price={trade?.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween>
)}
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
<RowBetween align="center">
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
Slippage Tolerance
</ClickableText>
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
{allowedSlippage / 100}%
</ClickableText>
</RowBetween>
)}
</AutoColumn>
</Card>
)}
</AutoColumn>
<BottomGrouping>
{!account ? (
<ButtonLight onClick={toggleWalletModal}>Connect Wallet</ButtonLight>
) : showWrap ? (
<ButtonPrimary disabled={Boolean(wrapInputError)} onClick={onWrap}>
{wrapInputError ??
(wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
</ButtonPrimary>
) : noRoute && userHasSpecifiedInputOutput ? (
<GreyCard style={{ textAlign: 'center' }}>
<TYPE.main mb="4px">Insufficient liquidity for this trade.</TYPE.main>
</GreyCard>
) : showApproveFlow ? (
<RowBetween>
<ButtonConfirmed
onClick={approveCallback}
disabled={approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
width="48%"
altDisabledStyle={approval === ApprovalState.PENDING} // show solid button while waiting
confirmed={approval === ApprovalState.APPROVED}
>
{approval === ApprovalState.PENDING ? (
<AutoRow gap="6px" justify="center">
Approving <Loader stroke="white" />
</AutoRow>
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
'Approved'
) : (
'Approve ' + currencies[Field.INPUT]?.symbol
)}
</ButtonConfirmed>
<ButtonError
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined
})
}
}}
width="48%"
id="swap-button"
disabled={
!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !isExpertMode)
}
error={isValid && priceImpactSeverity > 2}
>
<Text fontSize={16} fontWeight={500}>
{priceImpactSeverity > 3 && !isExpertMode
? `Price Impact High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Text>
</ButtonError>
</RowBetween>
) : (
<ButtonError
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}
error={isValid && priceImpactSeverity > 2 && !swapCallbackError}
>
<Text fontSize={20} fontWeight={500}>
{swapInputError
? swapInputError
: priceImpactSeverity > 3 && !isExpertMode
? `Price Impact Too High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Text>
</ButtonError>
)}
{showApproveFlow && (
<Column style={{ marginTop: '1rem' }}>
<ProgressSteps steps={[approval === ApprovalState.APPROVED]} />
</Column>
)}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
{betterTradeLinkVersion ? null : toggledVersion !== DEFAULT_VERSION && defaultTrade ? ( // <BetterTradeLink version={betterTradeLinkVersion} />
<DefaultVersionLink />
) : null}
</BottomGrouping>
</Wrapper>
</AppBody>
<AdvancedSwapDetailsDropdown trade={trade} />
</>
)
}
Example #21
Source File: SwapModalHeader.tsx From pancakeswap-testnet with GNU General Public License v3.0 | 4 votes |
export default function SwapModalHeader({
trade,
allowedSlippage,
recipient,
showAcceptChanges,
onAcceptChanges,
}: {
trade: Trade
allowedSlippage: number
recipient: string | null
showAcceptChanges: boolean
onAcceptChanges: () => void
}) {
const slippageAdjustedAmounts = useMemo(() => computeSlippageAdjustedAmounts(trade, allowedSlippage), [
trade,
allowedSlippage,
])
const { priceImpactWithoutFee } = useMemo(() => computeTradePriceBreakdown(trade), [trade])
const priceImpactSeverity = warningSeverity(priceImpactWithoutFee)
const theme = useContext(ThemeContext)
return (
<AutoColumn gap="md" style={{ marginTop: '20px' }}>
<RowBetween align="flex-end">
<RowFixed gap="0px">
<CurrencyLogo currency={trade.inputAmount.currency} size="24px" style={{ marginRight: '12px' }} />
<Text
fontSize="24px"
color={showAcceptChanges && trade.tradeType === TradeType.EXACT_OUTPUT ? theme.colors.primary : 'text'}
>
{trade.inputAmount.toSignificant(6)}
</Text>
</RowFixed>
<RowFixed gap="0px">
<Text fontSize="24px" style={{ marginLeft: '10px', fontWeight: 500 }}>
{trade.inputAmount.currency.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<ArrowDown size="16" color={theme.colors.textSubtle} style={{ marginLeft: '4px', minWidth: '16px' }} />
</RowFixed>
<RowBetween align="flex-end">
<RowFixed gap="0px">
<CurrencyLogo currency={trade.outputAmount.currency} size="24px" style={{ marginRight: '12px' }} />
<Text
fontSize="24px"
style={{ marginLeft: '10px', fontWeight: 500 }}
color={
priceImpactSeverity > 2
? theme.colors.failure
: showAcceptChanges && trade.tradeType === TradeType.EXACT_INPUT
? theme.colors.primary
: 'text'
}
>
{trade.outputAmount.toSignificant(6)}
</Text>
</RowFixed>
<RowFixed gap="0px">
<Text fontSize="24px" style={{ marginLeft: '10px', fontWeight: 500 }}>
{trade.outputAmount.currency.symbol}
</Text>
</RowFixed>
</RowBetween>
{showAcceptChanges ? (
<SwapShowAcceptChanges justify="flex-start" gap="0px">
<RowBetween>
<RowFixed>
<AlertTriangle size={20} style={{ marginRight: '8px', minWidth: 24 }} />
<Text color="primary"> Price Updated</Text>
</RowFixed>
<Button onClick={onAcceptChanges}>Accept</Button>
</RowBetween>
</SwapShowAcceptChanges>
) : null}
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '16px 0 0' }}>
{trade.tradeType === TradeType.EXACT_INPUT ? (
<PriceInfoText>
{`Output is estimated. You will receive at least `}
<span>
{slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {trade.outputAmount.currency.symbol}
</span>
{' or the transaction will revert.'}
</PriceInfoText>
) : (
<PriceInfoText>
{`Input is estimated. You will sell at most `}
<span>
{slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {trade.inputAmount.currency.symbol}
</span>
{' or the transaction will revert.'}
</PriceInfoText>
)}
</AutoColumn>
{recipient !== null ? (
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '16px 0 0' }}>
<Text>
Output will be sent to{' '}
<b title={recipient}>{isAddress(recipient) ? shortenAddress(recipient) : recipient}</b>
</Text>
</AutoColumn>
) : null}
</AutoColumn>
)
}
Example #22
Source File: index.tsx From pancakeswap-testnet with GNU General Public License v3.0 | 4 votes |
export default function RemoveLiquidity({
history,
match: {
params: { currencyIdA, currencyIdB },
},
}: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
const [currencyA, currencyB] = [useCurrency(currencyIdA) ?? undefined, useCurrency(currencyIdB) ?? undefined]
const { account, chainId, library } = useActiveWeb3React()
const TranslateString = useI18n()
const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
currencyA,
currencyB,
chainId,
])
const theme = useContext(ThemeContext)
// burn state
const { independentField, typedValue } = useBurnState()
const { pair, parsedAmounts, error } = useDerivedBurnInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onUserInput: _onUserInput } = useBurnActionHandlers()
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [showDetailed, setShowDetailed] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirm
// txn values
const [txHash, setTxHash] = useState<string>('')
const [deadline] = useUserDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
const formattedAmounts = {
[Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo('0')
? '0'
: parsedAmounts[Field.LIQUIDITY_PERCENT].lessThan(new Percent('1', '100'))
? '<1'
: parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0),
[Field.LIQUIDITY]:
independentField === Field.LIQUIDITY ? typedValue : parsedAmounts[Field.LIQUIDITY]?.toSignificant(6) ?? '',
[Field.CURRENCY_A]:
independentField === Field.CURRENCY_A ? typedValue : parsedAmounts[Field.CURRENCY_A]?.toSignificant(6) ?? '',
[Field.CURRENCY_B]:
independentField === Field.CURRENCY_B ? typedValue : parsedAmounts[Field.CURRENCY_B]?.toSignificant(6) ?? '',
}
const atMaxAmount = parsedAmounts[Field.LIQUIDITY_PERCENT]?.equalTo(new Percent('1'))
// pair contract
const pairContract: Contract | null = usePairContract(pair?.liquidityToken?.address)
// allowance handling
const [signatureData, setSignatureData] = useState<{ v: number; r: string; s: string; deadline: number } | null>(null)
const [approval, approveCallback] = useApproveCallback(parsedAmounts[Field.LIQUIDITY], ROUTER_ADDRESS)
async function onAttemptToApprove() {
if (!pairContract || !pair || !library) throw new Error('missing dependencies')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
// try to gather a signature for permission
const nonce = await pairContract.nonces(account)
const deadlineForSignature: number = Math.ceil(Date.now() / 1000) + deadline
const EIP712Domain = [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
]
const domain = {
name: 'Pancake LPs',
version: '1',
chainId,
verifyingContract: pair.liquidityToken.address,
}
const Permit = [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
]
const message = {
owner: account,
spender: ROUTER_ADDRESS,
value: liquidityAmount.raw.toString(),
nonce: nonce.toHexString(),
deadline: deadlineForSignature,
}
const data = JSON.stringify({
types: {
EIP712Domain,
Permit,
},
domain,
primaryType: 'Permit',
message,
})
library
.send('eth_signTypedData_v4', [account, data])
.then(splitSignature)
.then((signature) => {
setSignatureData({
v: signature.v,
r: signature.r,
s: signature.s,
deadline: deadlineForSignature,
})
})
.catch((e) => {
// for all errors other than 4001 (EIP-1193 user rejected request), fall back to manual approve
if (e?.code !== 4001) {
approveCallback()
}
})
}
// wrapped onUserInput to clear signatures
const onUserInput = useCallback(
(field: Field, val: string) => {
setSignatureData(null)
return _onUserInput(field, val)
},
[_onUserInput]
)
const onLiquidityInput = useCallback((val: string): void => onUserInput(Field.LIQUIDITY, val), [onUserInput])
const onCurrencyAInput = useCallback((val: string): void => onUserInput(Field.CURRENCY_A, val), [onUserInput])
const onCurrencyBInput = useCallback((val: string): void => onUserInput(Field.CURRENCY_B, val), [onUserInput])
// tx sending
const addTransaction = useTransactionAdder()
async function onRemove() {
if (!chainId || !library || !account) throw new Error('missing dependencies')
const { [Field.CURRENCY_A]: currencyAmountA, [Field.CURRENCY_B]: currencyAmountB } = parsedAmounts
if (!currencyAmountA || !currencyAmountB) {
throw new Error('missing currency amounts')
}
const router = getRouterContract(chainId, library, account)
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(currencyAmountA, allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(currencyAmountB, allowedSlippage)[0],
}
if (!currencyA || !currencyB) throw new Error('missing tokens')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
const currencyBIsETH = currencyB === ETHER
const oneCurrencyIsETH = currencyA === ETHER || currencyBIsETH
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
if (!tokenA || !tokenB) throw new Error('could not wrap')
let methodNames: string[]
let args: Array<string | string[] | number | boolean>
// we have approval, use normal remove liquidity
if (approval === ApprovalState.APPROVED) {
// removeLiquidityETH
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETH', 'removeLiquidityETHSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
deadlineFromNow,
]
}
// removeLiquidity
else {
methodNames = ['removeLiquidity']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
}
}
// we have a signataure, use permit versions of remove liquidity
else if (signatureData !== null) {
// removeLiquidityETHWithPermit
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETHWithPermit', 'removeLiquidityETHWithPermitSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s,
]
}
// removeLiquidityETHWithPermit
else {
methodNames = ['removeLiquidityWithPermit']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s,
]
}
} else {
throw new Error('Attempting to confirm without approval or a signature. Please contact support.')
}
const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
methodNames.map((methodName, index) =>
router.estimateGas[methodName](...args)
.then(calculateGasMargin)
.catch((e) => {
console.error(`estimateGas failed`, index, methodName, args, e)
return undefined
})
)
)
const indexOfSuccessfulEstimation = safeGasEstimates.findIndex((safeGasEstimate) =>
BigNumber.isBigNumber(safeGasEstimate)
)
// all estimations failed...
if (indexOfSuccessfulEstimation === -1) {
console.error('This transaction would fail. Please contact support.')
} else {
const methodName = methodNames[indexOfSuccessfulEstimation]
const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation]
setAttemptingTxn(true)
await router[methodName](...args, {
gasLimit: safeGasEstimate,
})
.then((response: TransactionResponse) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Remove ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencyA?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencyB?.symbol}`,
})
setTxHash(response.hash)
})
.catch((e: Error) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
console.error(e)
})
}
}
function modalHeader() {
return (
<AutoColumn gap="md" style={{ marginTop: '20px' }}>
<RowBetween align="flex-end">
<Text fontSize="24px">{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyA} size="24px" />
<Text fontSize="24px" style={{ marginLeft: '10px' }}>
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<Plus size="16" color={theme.colors.textSubtle} />
</RowFixed>
<RowBetween align="flex-end">
<Text fontSize="24px">{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyB} size="24px" />
<Text fontSize="24px" style={{ marginLeft: '10px' }}>
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
<Text small color="textSubtle" textAlign="left" padding="12px 0 0 0" style={{ fontStyle: 'italic' }}>
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</Text>
</AutoColumn>
)
}
function modalBottom() {
return (
<>
<RowBetween>
<Text color="textSubtle">{`LP ${currencyA?.symbol}/${currencyB?.symbol}`} Burned</Text>
<RowFixed>
<DoubleCurrencyLogo currency0={currencyA} currency1={currencyB} margin />
<Text>{parsedAmounts[Field.LIQUIDITY]?.toSignificant(6)}</Text>
</RowFixed>
</RowBetween>
{pair && (
<>
<RowBetween>
<Text color="textSubtle">{TranslateString(1182, 'Price')}</Text>
<Text>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</Text>
</RowBetween>
<RowBetween>
<div />
<Text>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</Text>
</RowBetween>
</>
)}
<Button disabled={!(approval === ApprovalState.APPROVED || signatureData !== null)} onClick={onRemove}>
{TranslateString(1136, 'Confirm')}
</Button>
</>
)
}
const pendingText = `Removing ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencyA?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencyB?.symbol}`
const liquidityPercentChangeCallback = useCallback(
(value: number) => {
onUserInput(Field.LIQUIDITY_PERCENT, value.toString())
},
[onUserInput]
)
const oneCurrencyIsETH = currencyA === ETHER || currencyB === ETHER
const oneCurrencyIsWETH = Boolean(
chainId &&
((currencyA && currencyEquals(WETH[chainId], currencyA)) ||
(currencyB && currencyEquals(WETH[chainId], currencyB)))
)
const handleSelectCurrencyA = useCallback(
(currency: Currency) => {
if (currencyIdB && currencyId(currency) === currencyIdB) {
history.push(`/remove/${currencyId(currency)}/${currencyIdA}`)
} else {
history.push(`/remove/${currencyId(currency)}/${currencyIdB}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleSelectCurrencyB = useCallback(
(currency: Currency) => {
if (currencyIdA && currencyId(currency) === currencyIdA) {
history.push(`/remove/${currencyIdB}/${currencyId(currency)}`)
} else {
history.push(`/remove/${currencyIdA}/${currencyId(currency)}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
setSignatureData(null) // important that we clear signature data to avoid bad sigs
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.LIQUIDITY_PERCENT, '0')
}
setTxHash('')
}, [onUserInput, txHash])
const [innerLiquidityPercentage, setInnerLiquidityPercentage] = useDebouncedChangeHandler(
Number.parseInt(parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0)),
liquidityPercentChangeCallback
)
return (
<>
<AppBody>
<AddRemoveTabs adding={false} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash || ''}
content={() => (
<ConfirmationModalContent
title={TranslateString(1156, 'You will receive')}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<AutoColumn gap="md">
<Body>
<OutlineCard>
<AutoColumn>
<RowBetween>
<Text>Amount</Text>
<ClickableText
onClick={() => {
setShowDetailed(!showDetailed)
}}
>
{showDetailed ? TranslateString(1184, 'Simple') : TranslateString(1186, 'Detailed')}
</ClickableText>
</RowBetween>
<Flex justifyContent="start">
<Text fontSize="64px">{formattedAmounts[Field.LIQUIDITY_PERCENT]}%</Text>
</Flex>
{!showDetailed && (
<>
<Flex mb="8px">
<Slider value={innerLiquidityPercentage} onChange={setInnerLiquidityPercentage} />
</Flex>
<Flex justifyContent="space-around">
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '25')}
>
25%
</Button>
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '50')}
>
50%
</Button>
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '75')}
>
75%
</Button>
<Button
variant="tertiary"
scale="sm"
onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
>
{TranslateString(166, 'Max')}
</Button>
</Flex>
</>
)}
</AutoColumn>
</OutlineCard>
</Body>
{!showDetailed && (
<>
<ColumnCenter>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<Body>
<OutlineCard>
<AutoColumn gap="10px">
<RowBetween>
<Text fontSize="24px">{formattedAmounts[Field.CURRENCY_A] || '-'}</Text>
<RowFixed>
<CurrencyLogo currency={currencyA} style={{ marginRight: '12px' }} />
<Text fontSize="24px" id="remove-liquidity-tokena-symbol">
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowBetween>
<Text fontSize="24px">{formattedAmounts[Field.CURRENCY_B] || '-'}</Text>
<RowFixed>
<CurrencyLogo currency={currencyB} style={{ marginRight: '12px' }} />
<Text fontSize="24px" id="remove-liquidity-tokenb-symbol">
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
{chainId && (oneCurrencyIsWETH || oneCurrencyIsETH) ? (
<RowBetween style={{ justifyContent: 'flex-end' }}>
{oneCurrencyIsETH ? (
<StyledInternalLink
to={`/remove/${currencyA === ETHER ? WETH[chainId].address : currencyIdA}/${
currencyB === ETHER ? WETH[chainId].address : currencyIdB
}`}
>
{TranslateString(1188, 'Receive WBNB')}
</StyledInternalLink>
) : oneCurrencyIsWETH ? (
<StyledInternalLink
to={`/remove/${
currencyA && currencyEquals(currencyA, WETH[chainId]) ? 'BNB' : currencyIdA
}/${currencyB && currencyEquals(currencyB, WETH[chainId]) ? 'BNB' : currencyIdB}`}
>
{TranslateString(1190, 'Receive BNB')}
</StyledInternalLink>
) : null}
</RowBetween>
) : null}
</AutoColumn>
</OutlineCard>
</Body>
</>
)}
<Body style={{ paddingBottom: '24px' }}>
{showDetailed && (
<>
<CurrencyInputPanel
value={formattedAmounts[Field.LIQUIDITY]}
onUserInput={onLiquidityInput}
onMax={() => {
onUserInput(Field.LIQUIDITY_PERCENT, '100')
}}
showMaxButton={!atMaxAmount}
disableCurrencySelect
currency={pair?.liquidityToken}
pair={pair}
id="liquidity-amount"
/>
<ColumnCenter>
<ArrowDown size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onCurrencyAInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyA}
label="Output"
onCurrencySelect={handleSelectCurrencyA}
id="remove-liquidity-tokena"
/>
<ColumnCenter>
<Plus size="16" color={theme.colors.textSubtle} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onCurrencyBInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyB}
label="Output"
onCurrencySelect={handleSelectCurrencyB}
id="remove-liquidity-tokenb"
/>
</>
)}
{pair && (
<div style={{ padding: '24px' }}>
<Flex justifyContent="space-between" mb="8px">
Price:
<div>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</div>
</Flex>
<Flex justifyContent="space-between">
<div />
<div>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</div>
</Flex>
</div>
)}
<div style={{ position: 'relative' }}>
{!account ? (
<ConnectWalletButton width="100%" />
) : (
<RowBetween>
<Button
onClick={onAttemptToApprove}
variant={approval === ApprovalState.APPROVED || signatureData !== null ? 'success' : 'primary'}
disabled={approval !== ApprovalState.NOT_APPROVED || signatureData !== null}
mr="8px"
>
{approval === ApprovalState.PENDING ? (
<Dots>Approving</Dots>
) : approval === ApprovalState.APPROVED || signatureData !== null ? (
'Approved'
) : (
'Approve'
)}
</Button>
<Button
onClick={() => {
setShowConfirm(true)
}}
disabled={!isValid || (signatureData === null && approval !== ApprovalState.APPROVED)}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
>
{error || 'Remove'}
</Button>
</RowBetween>
)}
</div>
</Body>
</AutoColumn>
</Wrapper>
</AppBody>
{pair ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
</AutoColumn>
) : null}
</>
)
}
Example #23
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 #24
Source File: index.tsx From dyp with Do What The F*ck You Want To Public License | 4 votes |
export default function Swap() {
const loadedUrlParams = useDefaultsFromURLSearch()
// token warning stuff
const [loadedInputCurrency, loadedOutputCurrency] = [
useCurrency(loadedUrlParams?.inputCurrencyId),
useCurrency(loadedUrlParams?.outputCurrencyId)
]
const [dismissTokenWarning, setDismissTokenWarning] = useState<boolean>(false)
const urlLoadedTokens: Token[] = useMemo(
() => [loadedInputCurrency, loadedOutputCurrency]?.filter((c): c is Token => c instanceof Token) ?? [],
[loadedInputCurrency, loadedOutputCurrency]
)
const handleConfirmTokenWarning = useCallback(() => {
setDismissTokenWarning(true)
}, [])
const { account } = useActiveWeb3React()
const theme = useContext(ThemeContext)
// toggle wallet when disconnected
const toggleWalletModal = useWalletModalToggle()
// for expert mode
const toggleSettings = useToggleSettingsMenu()
const [isExpertMode] = useExpertModeManager()
// get custom setting values for user
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 tradesByVersion = {
[Version.v1]: v1Trade,
[Version.v2]: v2Trade
}
const trade = showWrap ? undefined : tradesByVersion[toggledVersion]
const defaultTrade = showWrap ? undefined : tradesByVersion[DEFAULT_VERSION]
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, recipient)
const { priceImpactWithoutFee } = computeTradePriceBreakdown(trade)
const handleSwap = useCallback(() => {
if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) {
return
}
if (!swapCallback) {
return
}
setSwapState({ attemptingTxn: true, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: undefined })
swapCallback()
.then(hash => {
setSwapState({ attemptingTxn: false, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: 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,
getTradeVersion(trade)
].join('/')
})
})
.catch(error => {
setSwapState({
attemptingTxn: false,
tradeToConfirm,
showConfirm,
swapErrorMessage: error.message,
txHash: undefined
})
})
}, [tradeToConfirm, account, priceImpactWithoutFee, recipient, recipientAddress, showConfirm, swapCallback, trade])
// 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({ 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 handleAcceptChanges = useCallback(() => {
setSwapState({ tradeToConfirm: trade, swapErrorMessage, txHash, attemptingTxn, showConfirm })
}, [attemptingTxn, showConfirm, swapErrorMessage, trade, txHash])
const handleInputSelect = useCallback(
inputCurrency => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onCurrencySelection(Field.INPUT, inputCurrency)
},
[onCurrencySelection]
)
const handleMaxInput = useCallback(() => {
maxAmountInput && onUserInput(Field.INPUT, maxAmountInput.toExact())
}, [maxAmountInput, onUserInput])
const handleOutputSelect = useCallback(outputCurrency => onCurrencySelection(Field.OUTPUT, outputCurrency), [
onCurrencySelection
])
return (
<>
<TokenWarningModal
isOpen={urlLoadedTokens.length > 0 && !dismissTokenWarning}
tokens={urlLoadedTokens}
onConfirm={handleConfirmTokenWarning}
/>
<AppBody>
<SwapPoolTabs active={'swap'} />
<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}
/>
<AutoColumn gap={'md'}>
<CurrencyInputPanel
label={independentField === Field.OUTPUT && !showWrap && trade ? 'From (estimated)' : '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>
<ArrowDown
size="16"
onClick={() => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onSwitchTokens()
}}
color={currencies[Field.INPUT] && currencies[Field.OUTPUT] ? theme.primary1 : theme.text2}
/>
</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)' : '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.text2} />
</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 fontWeight={500} fontSize={14} color={theme.text2}>
Price
</Text>
<TradePrice
price={trade?.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween>
)}
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
<RowBetween align="center">
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
Slippage Tolerance
</ClickableText>
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
{allowedSlippage / 100}%
</ClickableText>
</RowBetween>
)}
</AutoColumn>
</Card>
)}
</AutoColumn>
<BottomGrouping>
{!account ? (
<ButtonLight onClick={toggleWalletModal}>Connect Wallet</ButtonLight>
) : showWrap ? (
<ButtonPrimary disabled={Boolean(wrapInputError)} onClick={onWrap}>
{wrapInputError ??
(wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
</ButtonPrimary>
) : noRoute && userHasSpecifiedInputOutput ? (
<GreyCard style={{ textAlign: 'center' }}>
<TYPE.main mb="4px">Insufficient liquidity for this trade.</TYPE.main>
</GreyCard>
) : showApproveFlow ? (
<RowBetween>
<ButtonConfirmed
onClick={approveCallback}
disabled={approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
width="48%"
altDisabledStyle={approval === ApprovalState.PENDING} // show solid button while waiting
confirmed={approval === ApprovalState.APPROVED}
>
{approval === ApprovalState.PENDING ? (
<AutoRow gap="6px" justify="center">
Approving <Loader stroke="white" />
</AutoRow>
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
'Approved'
) : (
'Approve ' + currencies[Field.INPUT]?.symbol
)}
</ButtonConfirmed>
<ButtonError
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined
})
}
}}
width="48%"
id="swap-button"
disabled={
!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !isExpertMode)
}
error={isValid && priceImpactSeverity > 2}
>
<Text fontSize={16} fontWeight={500}>
{priceImpactSeverity > 3 && !isExpertMode
? `Price Impact High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Text>
</ButtonError>
</RowBetween>
) : (
<ButtonError
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}
error={isValid && priceImpactSeverity > 2 && !swapCallbackError}
>
<Text fontSize={20} fontWeight={500}>
{swapInputError
? swapInputError
: priceImpactSeverity > 3 && !isExpertMode
? `Price Impact Too High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Text>
</ButtonError>
)}
{showApproveFlow && (
<Column style={{ marginTop: '1rem' }}>
<ProgressSteps steps={[approval === ApprovalState.APPROVED]} />
</Column>
)}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
{betterTradeLinkVersion ? (
<BetterTradeLink version={betterTradeLinkVersion} />
) : toggledVersion !== DEFAULT_VERSION && defaultTrade ? (
<DefaultVersionLink />
) : null}
</BottomGrouping>
</Wrapper>
</AppBody>
<AdvancedSwapDetailsDropdown trade={trade} />
</>
)
}
Example #25
Source File: index.tsx From cuiswap with GNU General Public License v3.0 | 4 votes |
export default function RemoveLiquidity({
history,
match: {
params: { currencyIdA, currencyIdB }
}
}: RouteComponentProps<{ currencyIdA: string; currencyIdB: string }>) {
const [currencyA, currencyB] = [useCurrency(currencyIdA) ?? undefined, useCurrency(currencyIdB) ?? undefined]
const { account, chainId, library } = useActiveWeb3React()
const [tokenA, tokenB] = useMemo(() => [wrappedCurrency(currencyA, chainId), wrappedCurrency(currencyB, chainId)], [
currencyA,
currencyB,
chainId
])
const theme = useContext(ThemeContext)
// toggle wallet when disconnected
const toggleWalletModal = useWalletModalToggle()
// burn state
const { independentField, typedValue } = useBurnState()
const { pair, parsedAmounts, error } = useDerivedBurnInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onUserInput: _onUserInput } = useBurnActionHandlers()
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [showDetailed, setShowDetailed] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState(false) // clicked confirm
// txn values
const [txHash, setTxHash] = useState<string>('')
const [deadline] = useUserDeadline()
const [allowedSlippage] = useUserSlippageTolerance()
const formattedAmounts = {
[Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo('0')
? '0'
: parsedAmounts[Field.LIQUIDITY_PERCENT].lessThan(new Percent('1', '100'))
? '<1'
: parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0),
[Field.LIQUIDITY]:
independentField === Field.LIQUIDITY ? typedValue : parsedAmounts[Field.LIQUIDITY]?.toSignificant(6) ?? '',
[Field.CURRENCY_A]:
independentField === Field.CURRENCY_A ? typedValue : parsedAmounts[Field.CURRENCY_A]?.toSignificant(6) ?? '',
[Field.CURRENCY_B]:
independentField === Field.CURRENCY_B ? typedValue : parsedAmounts[Field.CURRENCY_B]?.toSignificant(6) ?? ''
}
const atMaxAmount = parsedAmounts[Field.LIQUIDITY_PERCENT]?.equalTo(new Percent('1'))
// pair contract
const pairContract: Contract | null = usePairContract(pair?.liquidityToken?.address)
// allowance handling
const [signatureData, setSignatureData] = useState<{ v: number; r: string; s: string; deadline: number } | null>(null)
const [approval, approveCallback] = useApproveCallback(parsedAmounts[Field.LIQUIDITY], ROUTER_ADDRESS)
async function onAttemptToApprove() {
if (!pairContract || !pair || !library) throw new Error('missing dependencies')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
// try to gather a signature for permission
const nonce = await pairContract.nonces(account)
const deadlineForSignature: number = Math.ceil(Date.now() / 1000) + deadline
const EIP712Domain = [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' }
]
const domain = {
name: 'Uniswap V2',
version: '1',
chainId: chainId,
verifyingContract: pair.liquidityToken.address
}
const Permit = [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' }
]
const message = {
owner: account,
spender: ROUTER_ADDRESS,
value: liquidityAmount.raw.toString(),
nonce: nonce.toHexString(),
deadline: deadlineForSignature
}
const data = JSON.stringify({
types: {
EIP712Domain,
Permit
},
domain,
primaryType: 'Permit',
message
})
library
.send('eth_signTypedData_v4', [account, data])
.then(splitSignature)
.then(signature => {
setSignatureData({
v: signature.v,
r: signature.r,
s: signature.s,
deadline: deadlineForSignature
})
})
.catch(error => {
// for all errors other than 4001 (EIP-1193 user rejected request), fall back to manual approve
if (error?.code !== 4001) {
approveCallback()
}
})
}
// wrapped onUserInput to clear signatures
const onUserInput = useCallback(
(field: Field, typedValue: string) => {
setSignatureData(null)
return _onUserInput(field, typedValue)
},
[_onUserInput]
)
const onLiquidityInput = useCallback((typedValue: string): void => onUserInput(Field.LIQUIDITY, typedValue), [
onUserInput
])
const onCurrencyAInput = useCallback((typedValue: string): void => onUserInput(Field.CURRENCY_A, typedValue), [
onUserInput
])
const onCurrencyBInput = useCallback((typedValue: string): void => onUserInput(Field.CURRENCY_B, typedValue), [
onUserInput
])
// tx sending
const addTransaction = useTransactionAdder()
async function onRemove() {
if (!chainId || !library || !account) throw new Error('missing dependencies')
const { [Field.CURRENCY_A]: currencyAmountA, [Field.CURRENCY_B]: currencyAmountB } = parsedAmounts
if (!currencyAmountA || !currencyAmountB) {
throw new Error('missing currency amounts')
}
const router = getRouterContract(chainId, library, account)
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(currencyAmountA, allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(currencyAmountB, allowedSlippage)[0]
}
if (!currencyA || !currencyB) throw new Error('missing tokens')
const liquidityAmount = parsedAmounts[Field.LIQUIDITY]
if (!liquidityAmount) throw new Error('missing liquidity amount')
const currencyBIsETH = currencyB === ETHER
const oneCurrencyIsETH = currencyA === ETHER || currencyBIsETH
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
if (!tokenA || !tokenB) throw new Error('could not wrap')
let methodNames: string[], args: Array<string | string[] | number | boolean>
// we have approval, use normal remove liquidity
if (approval === ApprovalState.APPROVED) {
// removeLiquidityETH
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETH', 'removeLiquidityETHSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
deadlineFromNow
]
}
// removeLiquidity
else {
methodNames = ['removeLiquidity']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow
]
}
}
// we have a signataure, use permit versions of remove liquidity
else if (signatureData !== null) {
// removeLiquidityETHWithPermit
if (oneCurrencyIsETH) {
methodNames = ['removeLiquidityETHWithPermit', 'removeLiquidityETHWithPermitSupportingFeeOnTransferTokens']
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(),
amountsMin[currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s
]
}
// removeLiquidityETHWithPermit
else {
methodNames = ['removeLiquidityWithPermit']
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
signatureData.deadline,
false,
signatureData.v,
signatureData.r,
signatureData.s
]
}
} else {
throw new Error('Attempting to confirm without approval or a signature. Please contact support.')
}
const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
methodNames.map(methodName =>
router.estimateGas[methodName](...args)
.then(calculateGasMargin)
.catch(error => {
console.error(`estimateGas failed`, methodName, args, error)
return undefined
})
)
)
const indexOfSuccessfulEstimation = safeGasEstimates.findIndex(safeGasEstimate =>
BigNumber.isBigNumber(safeGasEstimate)
)
// all estimations failed...
if (indexOfSuccessfulEstimation === -1) {
console.error('This transaction would fail. Please contact support.')
} else {
const methodName = methodNames[indexOfSuccessfulEstimation]
const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation]
setAttemptingTxn(true)
await router[methodName](...args, {
gasLimit: safeGasEstimate
})
.then((response: TransactionResponse) => {
setAttemptingTxn(false)
addTransaction(response, {
summary:
'Remove ' +
parsedAmounts[Field.CURRENCY_A]?.toSignificant(3) +
' ' +
currencyA?.symbol +
' and ' +
parsedAmounts[Field.CURRENCY_B]?.toSignificant(3) +
' ' +
currencyB?.symbol
})
setTxHash(response.hash)
ReactGA.event({
category: 'Liquidity',
action: 'Remove',
label: [currencyA?.symbol, currencyB?.symbol].join('/')
})
})
.catch((error: Error) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
console.error(error)
})
}
}
function modalHeader() {
return (
<AutoColumn gap={'md'} style={{ marginTop: '20px' }}>
<RowBetween align="flex-end">
<Text fontSize={24} fontWeight={500}>
{parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)}
</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyA} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowFixed>
<Plus size="16" color={theme.text2} />
</RowFixed>
<RowBetween align="flex-end">
<Text fontSize={24} fontWeight={500}>
{parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)}
</Text>
<RowFixed gap="4px">
<CurrencyLogo currency={currencyB} size={'24px'} />
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
<TYPE.italic fontSize={12} color={theme.text2} textAlign="left" padding={'12px 0 0 0'}>
{`Output is estimated. If the price changes by more than ${allowedSlippage /
100}% your transaction will revert.`}
</TYPE.italic>
</AutoColumn>
)
}
function modalBottom() {
return (
<>
<RowBetween>
<Text color={theme.text2} fontWeight={500} fontSize={16}>
{'UNI ' + currencyA?.symbol + '/' + currencyB?.symbol} Burned
</Text>
<RowFixed>
<DoubleCurrencyLogo currency0={currencyA} currency1={currencyB} margin={true} />
<Text fontWeight={500} fontSize={16}>
{parsedAmounts[Field.LIQUIDITY]?.toSignificant(6)}
</Text>
</RowFixed>
</RowBetween>
{pair && (
<>
<RowBetween>
<Text color={theme.text2} fontWeight={500} fontSize={16}>
Price
</Text>
<Text fontWeight={500} fontSize={16} color={theme.text1}>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</Text>
</RowBetween>
<RowBetween>
<div />
<Text fontWeight={500} fontSize={16} color={theme.text1}>
1 {currencyB?.symbol} = {tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'} {currencyA?.symbol}
</Text>
</RowBetween>
</>
)}
<ButtonPrimary disabled={!(approval === ApprovalState.APPROVED || signatureData !== null)} onClick={onRemove}>
<Text fontWeight={500} fontSize={20}>
Confirm
</Text>
</ButtonPrimary>
</>
)
}
const pendingText = `Removing ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencyA?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencyB?.symbol}`
const liquidityPercentChangeCallback = useCallback(
(value: number) => {
onUserInput(Field.LIQUIDITY_PERCENT, value.toString())
},
[onUserInput]
)
const oneCurrencyIsETH = currencyA === ETHER || currencyB === ETHER
const oneCurrencyIsWETH = Boolean(
chainId &&
((currencyA && currencyEquals(WETH[chainId], currencyA)) ||
(currencyB && currencyEquals(WETH[chainId], currencyB)))
)
const handleSelectCurrencyA = useCallback(
(currency: Currency) => {
if (currencyIdB && currencyId(currency) === currencyIdB) {
history.push(`/remove/${currencyId(currency)}/${currencyIdA}`)
} else {
history.push(`/remove/${currencyId(currency)}/${currencyIdB}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleSelectCurrencyB = useCallback(
(currency: Currency) => {
if (currencyIdA && currencyId(currency) === currencyIdA) {
history.push(`/remove/${currencyIdB}/${currencyId(currency)}`)
} else {
history.push(`/remove/${currencyIdA}/${currencyId(currency)}`)
}
},
[currencyIdA, currencyIdB, history]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
setSignatureData(null) // important that we clear signature data to avoid bad sigs
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.LIQUIDITY_PERCENT, '0')
}
setTxHash('')
}, [onUserInput, txHash])
return (
<>
<AppBody>
<AddRemoveTabs adding={false} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash ? txHash : ''}
content={() => (
<ConfirmationModalContent
title={'You will receive'}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<AutoColumn gap="md">
<LightCard>
<AutoColumn gap="20px">
<RowBetween>
<Text fontWeight={500}>Amount</Text>
<ClickableText
fontWeight={500}
onClick={() => {
setShowDetailed(!showDetailed)
}}
>
{showDetailed ? 'Simple' : 'Detailed'}
</ClickableText>
</RowBetween>
<Row style={{ alignItems: 'flex-end' }}>
<Text fontSize={72} fontWeight={500}>
{formattedAmounts[Field.LIQUIDITY_PERCENT]}%
</Text>
</Row>
{!showDetailed && (
<>
<Slider
value={Number.parseInt(parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0))}
onChange={liquidityPercentChangeCallback}
/>
<RowBetween>
<MaxButton onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '25')} width="20%">
25%
</MaxButton>
<MaxButton onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '50')} width="20%">
50%
</MaxButton>
<MaxButton onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '75')} width="20%">
75%
</MaxButton>
<MaxButton onClick={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')} width="20%">
Max
</MaxButton>
</RowBetween>
</>
)}
</AutoColumn>
</LightCard>
{!showDetailed && (
<>
<ColumnCenter>
<ArrowDown size="16" color={theme.text2} />
</ColumnCenter>
<LightCard>
<AutoColumn gap="10px">
<RowBetween>
<Text fontSize={24} fontWeight={500}>
{formattedAmounts[Field.CURRENCY_A] || '-'}
</Text>
<RowFixed>
<CurrencyLogo currency={currencyA} style={{ marginRight: '12px' }} />
<Text fontSize={24} fontWeight={500} id="remove-liquidity-tokena-symbol">
{currencyA?.symbol}
</Text>
</RowFixed>
</RowBetween>
<RowBetween>
<Text fontSize={24} fontWeight={500}>
{formattedAmounts[Field.CURRENCY_B] || '-'}
</Text>
<RowFixed>
<CurrencyLogo currency={currencyB} style={{ marginRight: '12px' }} />
<Text fontSize={24} fontWeight={500} id="remove-liquidity-tokenb-symbol">
{currencyB?.symbol}
</Text>
</RowFixed>
</RowBetween>
{chainId && (oneCurrencyIsWETH || oneCurrencyIsETH) ? (
<RowBetween style={{ justifyContent: 'flex-end' }}>
{oneCurrencyIsETH ? (
<StyledInternalLink
to={`/remove/${currencyA === ETHER ? WETH[chainId].address : currencyIdA}/${
currencyB === ETHER ? WETH[chainId].address : currencyIdB
}`}
>
Receive WETH
</StyledInternalLink>
) : oneCurrencyIsWETH ? (
<StyledInternalLink
to={`/remove/${
currencyA && currencyEquals(currencyA, WETH[chainId]) ? 'ETH' : currencyIdA
}/${currencyB && currencyEquals(currencyB, WETH[chainId]) ? 'ETH' : currencyIdB}`}
>
Receive ETH
</StyledInternalLink>
) : null}
</RowBetween>
) : null}
</AutoColumn>
</LightCard>
</>
)}
{showDetailed && (
<>
<CurrencyInputPanel
value={formattedAmounts[Field.LIQUIDITY]}
onUserInput={onLiquidityInput}
onMax={() => {
onUserInput(Field.LIQUIDITY_PERCENT, '100')
}}
showMaxButton={!atMaxAmount}
disableCurrencySelect
currency={pair?.liquidityToken}
pair={pair}
id="liquidity-amount"
/>
<ColumnCenter>
<ArrowDown size="16" color={theme.text2} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance={true}
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onCurrencyAInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyA}
label={'Output'}
onCurrencySelect={handleSelectCurrencyA}
id="remove-liquidity-tokena"
/>
<ColumnCenter>
<Plus size="16" color={theme.text2} />
</ColumnCenter>
<CurrencyInputPanel
hideBalance={true}
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onCurrencyBInput}
onMax={() => onUserInput(Field.LIQUIDITY_PERCENT, '100')}
showMaxButton={!atMaxAmount}
currency={currencyB}
label={'Output'}
onCurrencySelect={handleSelectCurrencyB}
id="remove-liquidity-tokenb"
/>
</>
)}
{pair && (
<div style={{ padding: '10px 20px' }}>
<RowBetween>
Price:
<div>
1 {currencyA?.symbol} = {tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'} {currencyB?.symbol}
</div>
</RowBetween>
<RowBetween>
<div />
<div>
1 {currencyB?.symbol} ={' '}
{tokenB
? pair
.priceOf(tokenB)
.invert()
.toSignificant(6)
: '-'}{' '}
{currencyA?.symbol}
</div>
</RowBetween>
</div>
)}
<div style={{ position: 'relative' }}>
{!account ? (
<ButtonLight onClick={toggleWalletModal}>Connect Wallet</ButtonLight>
) : (
<RowBetween>
<ButtonConfirmed
onClick={onAttemptToApprove}
confirmed={approval === ApprovalState.APPROVED || signatureData !== null}
disabled={approval !== ApprovalState.NOT_APPROVED || signatureData !== null}
mr="0.5rem"
fontWeight={500}
fontSize={16}
>
{approval === ApprovalState.PENDING ? (
<Dots>Approving</Dots>
) : approval === ApprovalState.APPROVED || signatureData !== null ? (
'Approved'
) : (
'Approve'
)}
</ButtonConfirmed>
<ButtonError
onClick={() => {
setShowConfirm(true)
}}
disabled={!isValid || (signatureData === null && approval !== ApprovalState.APPROVED)}
error={!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]}
>
<Text fontSize={16} fontWeight={500}>
{error || 'Remove'}
</Text>
</ButtonError>
</RowBetween>
)}
</div>
</AutoColumn>
</Wrapper>
</AppBody>
{pair ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
</AutoColumn>
) : null}
</>
)
}
Example #26
Source File: index.tsx From cuiswap with GNU General Public License v3.0 | 4 votes |
export default function Swap() {
useDefaultsFromURLSearch()
const { account, chainId } = useActiveWeb3React()
const theme = useContext(ThemeContext)
// toggle wallet when disconnected
const toggleWalletModal = useWalletModalToggle()
// for expert mode
const toggleSettings = useToggleSettingsMenu()
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({ attemptingTxn: true, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: undefined })
swapCallback()
.then(hash => {
setSwapState({ attemptingTxn: false, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: 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,
getTradeVersion(trade)
].join('/')
})
})
.catch(error => {
setSwapState({
attemptingTxn: false,
tradeToConfirm,
showConfirm,
swapErrorMessage: error.message,
txHash: undefined
})
})
}, [tradeToConfirm, account, priceImpactWithoutFee, recipient, recipientAddress, showConfirm, swapCallback, trade])
// 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 [dismissedToken0] = useTokenWarningDismissal(chainId, currencies[Field.INPUT])
const [dismissedToken1] = useTokenWarningDismissal(chainId, currencies[Field.OUTPUT])
const showWarning =
(!dismissedToken0 && !!currencies[Field.INPUT]) || (!dismissedToken1 && !!currencies[Field.OUTPUT])
const handleConfirmDismiss = useCallback(() => {
setSwapState({ showConfirm: false, 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 handleAcceptChanges = useCallback(() => {
setSwapState({ tradeToConfirm: trade, swapErrorMessage, txHash, attemptingTxn, showConfirm })
}, [attemptingTxn, showConfirm, swapErrorMessage, trade, txHash])
return (
<>
{showWarning && <TokenWarningCards currencies={currencies} />}
<AppBody disabled={showWarning}>
<SwapPoolTabs active={'swap'} />
<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}
/>
<AutoColumn gap={'md'}>
<CurrencyInputPanel
label={independentField === Field.OUTPUT && !showWrap ? 'From (estimated)' : 'From'}
value={formattedAmounts[Field.INPUT]}
showMaxButton={!atMaxAmountInput}
currency={currencies[Field.INPUT]}
onUserInput={handleTypeInput}
onMax={() => {
maxAmountInput && onUserInput(Field.INPUT, maxAmountInput.toExact())
}}
onCurrencySelect={currency => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onCurrencySelection(Field.INPUT, currency)
}}
otherCurrency={currencies[Field.OUTPUT]}
id="swap-currency-input"
/>
<AutoColumn justify="space-between">
<AutoRow justify="space-between" style={{ padding: '0 1rem' }}>
<ArrowWrapper clickable>
<ArrowDown
size="16"
onClick={() => {
setApprovalSubmitted(false) // reset 2 step UI for approvals
onSwitchTokens()
}}
color={currencies[Field.INPUT] && currencies[Field.OUTPUT] ? theme.primary1 : theme.text2}
/>
</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 ? 'To (estimated)' : 'To'}
showMaxButton={false}
currency={currencies[Field.OUTPUT]}
onCurrencySelect={address => onCurrencySelection(Field.OUTPUT, address)}
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.text2} />
</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">
<RowBetween align="center">
<Text fontWeight={500} fontSize={14} color={theme.text2}>
Price
</Text>
<TradePrice
inputCurrency={currencies[Field.INPUT]}
outputCurrency={currencies[Field.OUTPUT]}
price={trade?.executionPrice}
showInverted={showInverted}
setShowInverted={setShowInverted}
/>
</RowBetween>
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
<RowBetween align="center">
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
Slippage Tolerance
</ClickableText>
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
{allowedSlippage ? allowedSlippage / 100 : '-'}%
</ClickableText>
</RowBetween>
)}
</AutoColumn>
</Card>
)}
</AutoColumn>
<BottomGrouping>
{!account ? (
<ButtonLight onClick={toggleWalletModal}>Connect Wallet</ButtonLight>
) : showWrap ? (
<ButtonPrimary disabled={Boolean(wrapInputError)} onClick={onWrap}>
{wrapInputError ??
(wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
</ButtonPrimary>
) : noRoute && userHasSpecifiedInputOutput ? (
<GreyCard style={{ textAlign: 'center' }}>
<TYPE.main mb="4px">Insufficient liquidity for this trade.</TYPE.main>
</GreyCard>
) : showApproveFlow ? (
<RowBetween>
<ButtonPrimary
onClick={approveCallback}
disabled={approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
width="48%"
altDisbaledStyle={approval === ApprovalState.PENDING} // show solid button while waiting
>
{approval === ApprovalState.PENDING ? (
<Dots>Approving</Dots>
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
'Approved'
) : (
'Approve ' + currencies[Field.INPUT]?.symbol
)}
</ButtonPrimary>
<ButtonError
onClick={() => {
if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined
})
}
}}
width="48%"
id="swap-button"
disabled={
!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !isExpertMode)
}
error={isValid && priceImpactSeverity > 2}
>
<Text fontSize={16} fontWeight={500}>
{priceImpactSeverity > 3 && !isExpertMode
? `Price Impact High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Text>
</ButtonError>
</RowBetween>
) : (
<ButtonError
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}
error={isValid && priceImpactSeverity > 2 && !swapCallbackError}
>
<Text fontSize={20} fontWeight={500}>
{swapInputError
? swapInputError
: priceImpactSeverity > 3 && !isExpertMode
? `Price Impact Too High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Text>
</ButtonError>
)}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
{betterTradeLinkVersion && <BetterTradeLink version={betterTradeLinkVersion} />}
</BottomGrouping>
</Wrapper>
</AppBody>
<AdvancedSwapDetailsDropdown trade={trade} />
</>
)
}
Example #27
Source File: RemoveLiquidityModal.tsx From interface-v2 with GNU General Public License v3.0 | 4 votes |
RemoveLiquidityModal: React.FC<RemoveLiquidityModalProps> = ({
currency0,
currency1,
open,
onClose,
}) => {
const classes = useStyles();
const { palette } = useTheme();
const [showConfirm, setShowConfirm] = useState(false);
const [txPending, setTxPending] = useState(false);
const [approving, setApproving] = useState(false);
const [attemptingTxn, setAttemptingTxn] = useState(false);
const [removeErrorMessage, setRemoveErrorMessage] = useState('');
const [errorMsg, setErrorMsg] = useState('');
const [txHash, setTxHash] = useState('');
const addTransaction = useTransactionAdder();
const finalizedTransaction = useTransactionFinalizer();
const { chainId, account, library } = useActiveWeb3React();
const [tokenA, tokenB] = useMemo(
() => [
wrappedCurrency(currency0, chainId),
wrappedCurrency(currency1, chainId),
],
[currency0, currency1, chainId],
);
const { independentField, typedValue } = useBurnState();
const { pair, parsedAmounts, error } = useDerivedBurnInfo(
currency0,
currency1,
);
const deadline = useTransactionDeadline();
const { onUserInput: _onUserInput } = useBurnActionHandlers();
const [allowedSlippage] = useUserSlippageTolerance();
const onUserInput = useCallback(
(field: Field, typedValue: string) => {
return _onUserInput(field, typedValue);
},
[_onUserInput],
);
const onLiquidityInput = useCallback(
(typedValue: string): void => onUserInput(Field.LIQUIDITY, typedValue),
[onUserInput],
);
const liquidityPercentChangeCallback = useCallback(
(value: number) => {
onUserInput(Field.LIQUIDITY_PERCENT, value.toString());
},
[onUserInput],
);
const [
innerLiquidityPercentage,
setInnerLiquidityPercentage,
] = useDebouncedChangeHandler(
Number.parseInt(parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0)),
liquidityPercentChangeCallback,
);
const userPoolBalance = useTokenBalance(
account ?? undefined,
pair?.liquidityToken,
);
const totalPoolTokens = useTotalSupply(pair?.liquidityToken);
const poolTokenPercentage =
!!userPoolBalance &&
!!totalPoolTokens &&
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
? new Percent(userPoolBalance.raw, totalPoolTokens.raw)
: undefined;
const formattedAmounts = {
[Field.LIQUIDITY_PERCENT]: parsedAmounts[Field.LIQUIDITY_PERCENT].equalTo(
'0',
)
? '0'
: parsedAmounts[Field.LIQUIDITY_PERCENT].lessThan(new Percent('1', '100'))
? '<1'
: parsedAmounts[Field.LIQUIDITY_PERCENT].toFixed(0),
[Field.LIQUIDITY]:
independentField === Field.LIQUIDITY
? typedValue
: parsedAmounts[Field.LIQUIDITY]?.toExact() ?? '',
[Field.CURRENCY_A]:
independentField === Field.CURRENCY_A
? typedValue
: parsedAmounts[Field.CURRENCY_A]?.toExact() ?? '',
[Field.CURRENCY_B]:
independentField === Field.CURRENCY_B
? typedValue
: parsedAmounts[Field.CURRENCY_B]?.toExact() ?? '',
};
const [token0Deposited, token1Deposited] =
!!pair &&
!!totalPoolTokens &&
!!userPoolBalance &&
JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
? [
pair.getLiquidityValue(
pair.token0,
totalPoolTokens,
userPoolBalance,
false,
),
pair.getLiquidityValue(
pair.token1,
totalPoolTokens,
userPoolBalance,
false,
),
]
: [undefined, undefined];
const pairContract: Contract | null = usePairContract(
pair?.liquidityToken?.address,
);
const [approval, approveCallback] = useApproveCallback(
parsedAmounts[Field.LIQUIDITY],
chainId ? GlobalConst.addresses.ROUTER_ADDRESS[chainId] : undefined,
);
const onAttemptToApprove = async () => {
if (!pairContract || !pair || !library || !deadline) {
setErrorMsg('missing dependencies');
return;
}
const liquidityAmount = parsedAmounts[Field.LIQUIDITY];
if (!liquidityAmount) {
setErrorMsg('missing liquidity amount');
return;
}
setApproving(true);
try {
await approveCallback();
setApproving(false);
} catch (e) {
setApproving(false);
}
};
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false);
setTxHash('');
}, []);
const router = useRouterContract();
const onRemove = async () => {
if (!chainId || !library || !account || !deadline || !router)
throw new Error('missing dependencies');
const {
[Field.CURRENCY_A]: currencyAmountA,
[Field.CURRENCY_B]: currencyAmountB,
} = parsedAmounts;
if (!currencyAmountA || !currencyAmountB) {
throw new Error('missing currency amounts');
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(
currencyAmountA,
allowedSlippage,
)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(
currencyAmountB,
allowedSlippage,
)[0],
};
const liquidityAmount = parsedAmounts[Field.LIQUIDITY];
if (!liquidityAmount) throw new Error('missing liquidity amount');
const currencyBIsETH = currency1 === ETHER;
const oneCurrencyIsETH = currency0 === ETHER || currencyBIsETH;
if (!tokenA || !tokenB) throw new Error('could not wrap');
let methodNames: string[],
args: Array<string | string[] | number | boolean>;
// we have approval, use normal remove liquidity
if (approval === ApprovalState.APPROVED) {
// removeLiquidityETH
if (oneCurrencyIsETH) {
methodNames = [
'removeLiquidityETH',
'removeLiquidityETHSupportingFeeOnTransferTokens',
];
args = [
currencyBIsETH ? tokenA.address : tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[
currencyBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B
].toString(),
amountsMin[
currencyBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A
].toString(),
account,
deadline.toHexString(),
];
}
// removeLiquidity
else {
methodNames = ['removeLiquidity'];
args = [
tokenA.address,
tokenB.address,
liquidityAmount.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadline.toHexString(),
];
}
} else {
throw new Error(
'Attempting to confirm without approval. Please contact support.',
);
}
const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
methodNames.map((methodName) =>
router.estimateGas[methodName](...args)
.then(calculateGasMargin)
.catch((error) => {
console.error(`estimateGas failed`, methodName, args, error);
return undefined;
}),
),
);
const indexOfSuccessfulEstimation = safeGasEstimates.findIndex(
(safeGasEstimate) => BigNumber.isBigNumber(safeGasEstimate),
);
// all estimations failed...
if (indexOfSuccessfulEstimation === -1) {
console.error('This transaction would fail. Please contact support.');
} else {
const methodName = methodNames[indexOfSuccessfulEstimation];
const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation];
setAttemptingTxn(true);
await router[methodName](...args, {
gasLimit: safeGasEstimate,
})
.then(async (response: TransactionResponse) => {
setAttemptingTxn(false);
setTxPending(true);
const summary =
'Remove ' +
parsedAmounts[Field.CURRENCY_A]?.toSignificant(3) +
' ' +
currency0.symbol +
' and ' +
parsedAmounts[Field.CURRENCY_B]?.toSignificant(3) +
' ' +
currency1.symbol;
addTransaction(response, {
summary,
});
setTxHash(response.hash);
try {
const receipt = await response.wait();
finalizedTransaction(receipt, {
summary,
});
setTxPending(false);
} catch (error) {
setTxPending(false);
setRemoveErrorMessage('There is an error in transaction.');
}
ReactGA.event({
category: 'Liquidity',
action: 'Remove',
label: [currency0.symbol, currency1.symbol].join('/'),
});
})
.catch((error: Error) => {
setAttemptingTxn(false);
// we only care if the error is something _other_ than the user rejected the tx
console.error(error);
});
}
};
const modalHeader = () => {
return (
<Box>
<Box mt={10} mb={3} display='flex' justifyContent='center'>
<DoubleCurrencyLogo
currency0={currency0}
currency1={currency1}
size={48}
/>
</Box>
<Box mb={6} color={palette.text.primary} textAlign='center'>
<Typography variant='h6'>
Removing {formattedAmounts[Field.LIQUIDITY]} {currency0.symbol} /{' '}
{currency1.symbol} LP Tokens
<br />
You will receive {parsedAmounts[Field.CURRENCY_A]?.toSignificant(
2,
)}{' '}
{currency0.symbol} and{' '}
{parsedAmounts[Field.CURRENCY_B]?.toSignificant(2)}{' '}
{currency1.symbol}
</Typography>
</Box>
<Box mb={3} color={palette.text.secondary} textAlign='center'>
<Typography variant='body2'>
{`Output is estimated. If the price changes by more than ${allowedSlippage /
100}% your transaction will revert.`}
</Typography>
</Box>
<Box mt={2}>
<Button
style={{ width: '100%' }}
className={classes.removeButton}
onClick={onRemove}
>
Confirm
</Button>
</Box>
</Box>
);
};
return (
<CustomModal open={open} onClose={onClose}>
<Box paddingX={3} paddingY={4}>
{showConfirm && (
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
txPending={txPending}
hash={txHash}
content={() =>
removeErrorMessage ? (
<TransactionErrorContent
onDismiss={handleDismissConfirmation}
message={removeErrorMessage}
/>
) : (
<ConfirmationModalContent
title='Removing Liquidity'
onDismiss={handleDismissConfirmation}
content={modalHeader}
/>
)
}
pendingText=''
modalContent={
txPending
? 'Submitted transaction to remove liquidity'
: 'Successfully removed liquidity'
}
/>
)}
<Box display='flex' alignItems='center' justifyContent='space-between'>
<ArrowLeft
color={palette.text.secondary}
style={{ cursor: 'pointer' }}
onClick={onClose}
/>
<Typography
variant='subtitle2'
style={{ color: palette.text.primary }}
>
Remove Liquidity
</Typography>
<CloseIcon style={{ cursor: 'pointer' }} onClick={onClose} />
</Box>
<Box
mt={3}
bgcolor={palette.background.default}
border='1px solid rgba(105, 108, 128, 0.12)'
borderRadius='10px'
padding='16px'
>
<Box
display='flex'
alignItems='center'
justifyContent='space-between'
>
<Typography variant='body2'>
{currency0.symbol} / {currency1.symbol} LP
</Typography>
<Typography variant='body2'>
Balance: {formatTokenAmount(userPoolBalance)}
</Typography>
</Box>
<Box mt={2}>
<NumericalInput
placeholder='0'
value={formattedAmounts[Field.LIQUIDITY]}
fontSize={28}
onUserInput={(value) => {
onLiquidityInput(value);
}}
/>
</Box>
<Box display='flex' alignItems='center'>
<Box flex={1} mr={2} mt={0.5}>
<ColoredSlider
min={1}
max={100}
step={1}
value={innerLiquidityPercentage}
onChange={(evt: any, value) =>
setInnerLiquidityPercentage(value as number)
}
/>
</Box>
<Typography variant='body2'>
{formattedAmounts[Field.LIQUIDITY_PERCENT]}%
</Typography>
</Box>
</Box>
<Box display='flex' my={3} justifyContent='center'>
<ArrowDown color={palette.text.secondary} />
</Box>
<Box
padding='16px'
bgcolor={palette.secondary.light}
borderRadius='10px'
>
<Box
display='flex'
justifyContent='space-between'
alignItems='center'
>
<Typography variant='body1'>Pooled {currency0.symbol}</Typography>
<Box display='flex' alignItems='center'>
<Typography variant='body1' style={{ marginRight: 6 }}>
{formatTokenAmount(token0Deposited)}
</Typography>
<CurrencyLogo currency={currency0} />
</Box>
</Box>
<Box
mt={1}
display='flex'
justifyContent='space-between'
alignItems='center'
>
<Typography
variant='body1'
style={{ color: 'rgba(68, 138, 255, 0.5)' }}
>
- Withdraw {currency0.symbol}
</Typography>
<Typography
variant='body1'
style={{ color: 'rgba(68, 138, 255, 0.5)' }}
>
{formattedAmounts[Field.CURRENCY_A]}
</Typography>
</Box>
<Box
mt={1}
display='flex'
justifyContent='space-between'
alignItems='center'
>
<Typography variant='body1'>Pooled {currency1.symbol}</Typography>
<Box display='flex' alignItems='center'>
<Typography variant='body1' style={{ marginRight: 6 }}>
{formatTokenAmount(token1Deposited)}
</Typography>
<CurrencyLogo currency={currency1} />
</Box>
</Box>
<Box
mt={1}
display='flex'
justifyContent='space-between'
alignItems='center'
>
<Typography
variant='body1'
style={{ color: 'rgba(68, 138, 255, 0.5)' }}
>
- Withdraw {currency1.symbol}
</Typography>
<Typography
variant='body1'
style={{ color: 'rgba(68, 138, 255, 0.5)' }}
>
{formattedAmounts[Field.CURRENCY_B]}
</Typography>
</Box>
<Box
mt={1}
display='flex'
justifyContent='space-between'
alignItems='center'
>
<Typography variant='body1'>Your Pool Share</Typography>
<Typography variant='body1'>
{poolTokenPercentage
? poolTokenPercentage.toSignificant() + '%'
: '-'}
</Typography>
</Box>
</Box>
{pair && (
<Box
display='flex'
mt={2}
px={2}
alignItems='center'
justifyContent='space-between'
>
<Typography variant='body2'>
1 {currency0.symbol} ={' '}
{tokenA ? pair.priceOf(tokenA).toSignificant(6) : '-'}{' '}
{currency1.symbol}
</Typography>
<Typography variant='body2'>
1 {currency1.symbol} ={' '}
{tokenB ? pair.priceOf(tokenB).toSignificant(6) : '-'}{' '}
{currency0.symbol}
</Typography>
</Box>
)}
<Box
mt={2}
display='flex'
alignItems='center'
justifyContent='space-between'
>
<Button
className={classes.removeButton}
onClick={onAttemptToApprove}
disabled={approving || approval !== ApprovalState.NOT_APPROVED}
>
{approving
? 'Approving...'
: approval === ApprovalState.APPROVED
? 'Approved'
: 'Approve'}
</Button>
<Button
className={classes.removeButton}
onClick={() => {
setShowConfirm(true);
}}
disabled={Boolean(error) || approval !== ApprovalState.APPROVED}
>
{error || 'Remove'}
</Button>
</Box>
<Box mt={2}>
<Typography variant='body1' style={{ color: palette.error.main }}>
{errorMsg}
</Typography>
</Box>
</Box>
</CustomModal>
);
}
Example #28
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>
);
}
Example #29
Source File: DragonsSyrup.tsx From interface-v2 with GNU General Public License v3.0 | 4 votes |
DragonsSyrup: React.FC = () => {
const { palette, breakpoints } = useTheme();
const isMobile = useMediaQuery(breakpoints.down('xs'));
const [isEndedSyrup, setIsEndedSyrup] = useState(false);
const [pageIndex, setPageIndex] = useState(0);
const [sortBy, setSortBy] = useState(0);
const [sortDesc, setSortDesc] = useState(false);
const [stakedOnly, setStakeOnly] = useState(false);
const [syrupSearch, setSyrupSearch] = useState('');
const [syrupSearchInput, setSyrupSearchInput] = useDebouncedChangeHandler(
syrupSearch,
setSyrupSearch,
);
const lairInfo = useLairInfo();
const dQUICKAPY = useLairDQUICKAPY(lairInfo);
const addedStakingSyrupInfos = useSyrupInfo(
null,
isEndedSyrup ? 0 : undefined,
isEndedSyrup ? 0 : undefined,
{ search: syrupSearch, isStaked: stakedOnly },
);
const addedOldSyrupInfos = useOldSyrupInfo(
null,
isEndedSyrup ? undefined : 0,
isEndedSyrup ? undefined : 0,
{ search: syrupSearch, isStaked: stakedOnly },
);
const addedSyrupInfos = isEndedSyrup
? addedOldSyrupInfos
: addedStakingSyrupInfos;
const sortIndex = sortDesc ? 1 : -1;
const sortByToken = useCallback(
(a: SyrupInfo, b: SyrupInfo) => {
const syrupStrA = a.token.symbol ?? '';
const syrupStrB = b.token.symbol ?? '';
return (syrupStrA > syrupStrB ? -1 : 1) * sortIndex;
},
[sortIndex],
);
const sortByDeposit = useCallback(
(a: SyrupInfo, b: SyrupInfo) => {
const depositA =
a.valueOfTotalStakedAmountInUSDC ??
getExactTokenAmount(a.totalStakedAmount);
const depositB =
b.valueOfTotalStakedAmountInUSDC ??
getExactTokenAmount(b.totalStakedAmount);
return (depositA > depositB ? -1 : 1) * sortIndex;
},
[sortIndex],
);
const sortByAPR = useCallback(
(a: SyrupInfo, b: SyrupInfo) => {
return (getTokenAPRSyrup(a) > getTokenAPRSyrup(b) ? -1 : 1) * sortIndex;
},
[sortIndex],
);
const sortByEarned = useCallback(
(a: SyrupInfo, b: SyrupInfo) => {
const earnedUSDA =
getExactTokenAmount(a.earnedAmount) * (a.rewardTokenPriceinUSD ?? 0);
const earnedUSDB =
getExactTokenAmount(b.earnedAmount) * (b.rewardTokenPriceinUSD ?? 0);
return (earnedUSDA > earnedUSDB ? -1 : 1) * sortIndex;
},
[sortIndex],
);
const sortedSyrupInfos = useMemo(() => {
return addedSyrupInfos.sort((a, b) => {
if (sortBy === TOKEN_COLUMN) {
return sortByToken(a, b);
} else if (sortBy === DEPOSIT_COLUMN) {
return sortByDeposit(a, b);
} else if (sortBy === APR_COLUMN) {
return sortByAPR(a, b);
} else if (sortBy === EARNED_COLUMN) {
return sortByEarned(a, b);
}
return 1;
});
}, [
addedSyrupInfos,
sortBy,
sortByToken,
sortByDeposit,
sortByAPR,
sortByEarned,
]);
const syrupRewardAddress = useMemo(
() =>
sortedSyrupInfos
.map((syrupInfo) => syrupInfo.stakingRewardAddress.toLowerCase())
.reduce((totStr, str) => totStr + str, ''),
[sortedSyrupInfos],
);
useEffect(() => {
setPageIndex(0);
}, [syrupRewardAddress]);
const syrupInfos = useMemo(() => {
return sortedSyrupInfos
? sortedSyrupInfos.slice(
0,
getPageItemsToLoad(pageIndex, LOADSYRUP_COUNT),
)
: null;
}, [sortedSyrupInfos, pageIndex]);
const loadNext = () => {
setPageIndex(pageIndex + 1);
};
const { loadMoreRef } = useInfiniteLoading(loadNext);
const renderStakedOnly = () => (
<Box display='flex' alignItems='center'>
<Typography
variant='body2'
style={{ color: palette.text.disabled, marginRight: 8 }}
>
Staked Only
</Typography>
<ToggleSwitch
toggled={stakedOnly}
onToggle={() => setStakeOnly(!stakedOnly)}
/>
</Box>
);
const syrupStatusItems = [
{
text: 'Active',
onClick: () => setIsEndedSyrup(false),
condition: !isEndedSyrup,
},
{
text: 'Ended',
onClick: () => setIsEndedSyrup(true),
condition: isEndedSyrup,
},
];
const sortColumns = [
{
text: 'Earn',
index: TOKEN_COLUMN,
width: 0.3,
},
{
text: 'Deposits',
index: DEPOSIT_COLUMN,
width: 0.3,
},
{
text: 'APR',
index: APR_COLUMN,
width: 0.2,
},
{
text: 'Earned',
index: EARNED_COLUMN,
width: 0.2,
justify: 'flex-end',
},
];
const sortByDesktopItems = sortColumns.map((item) => {
return {
...item,
onClick: () => {
if (sortBy === item.index) {
setSortDesc(!sortDesc);
} else {
setSortBy(item.index);
setSortDesc(false);
}
},
};
});
const sortByMobileItems = sortColumns.map((item) => {
return { text: item.text, onClick: () => setSortBy(item.index) };
});
return (
<>
<Box display='flex' flexWrap='wrap' alignItems='center' mb={3.5}>
<Box
display='flex'
justifyContent='space-between'
width={returnFullWidthMobile(isMobile)}
flex={isMobile ? 'unset' : 1}
>
<Box width={isMobile ? 'calc(100% - 150px)' : 1} mr={2} my={2}>
<SearchInput
placeholder={
isMobile ? 'Search' : 'Search name, symbol or paste address'
}
value={syrupSearchInput}
setValue={setSyrupSearchInput}
/>
</Box>
{isMobile && renderStakedOnly()}
</Box>
<Box
width={returnFullWidthMobile(isMobile)}
display='flex'
flexWrap='wrap'
alignItems='center'
>
<Box mr={2}>
<CustomSwitch width={160} height={40} items={syrupStatusItems} />
</Box>
{isMobile ? (
<>
<Box height={40} flex={1}>
<CustomMenu title='Sort By' menuItems={sortByMobileItems} />
</Box>
<Box mt={2} width={1} display='flex' alignItems='center'>
<Typography
variant='body2'
style={{ color: palette.text.disabled, marginRight: 8 }}
>
Sort {sortDesc ? 'Desc' : 'Asc'}
</Typography>
<ToggleSwitch
toggled={sortDesc}
onToggle={() => setSortDesc(!sortDesc)}
/>
</Box>
</>
) : (
renderStakedOnly()
)}
</Box>
</Box>
<Divider />
{!isMobile && (
<Box mt={2.5} display='flex' paddingX={2}>
{sortByDesktopItems.map((item) => (
<Box
key={item.index}
display='flex'
alignItems='center'
width={item.width}
style={{ cursor: 'pointer' }}
justifyContent={item.justify}
onClick={item.onClick}
color={
sortBy === item.index
? palette.text.primary
: palette.text.secondary
}
>
<Typography variant='body2'>{item.text}</Typography>
<Box display='flex' ml={0.5}>
{sortBy === item.index && sortDesc ? (
<ArrowDown size={20} />
) : (
<ArrowUp size={20} />
)}
</Box>
</Box>
))}
</Box>
)}
{syrupInfos ? (
syrupInfos.map((syrup, ind) => (
<SyrupCard key={ind} syrup={syrup} dQUICKAPY={dQUICKAPY} />
))
) : (
<>
<Skeleton width='100%' height={120} />
<Skeleton width='100%' height={120} />
<Skeleton width='100%' height={120} />
<Skeleton width='100%' height={120} />
<Skeleton width='100%' height={120} />
</>
)}
<div ref={loadMoreRef} />
</>
);
}