utils#calculateGasMargin TypeScript Examples
The following examples show how to use
utils#calculateGasMargin.
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: hooks.ts From interface-v2 with GNU General Public License v3.0 | 6 votes |
export function useDelegateCallback(): (
delegatee: string | undefined,
) => undefined | Promise<string> {
const { account, chainId, library } = useActiveWeb3React();
const addTransaction = useTransactionAdder();
const uniContract = useUniContract();
return useCallback(
(delegatee: string | undefined) => {
if (!library || !chainId || !account || !isAddress(delegatee ?? ''))
return undefined;
const args = [delegatee];
if (!uniContract) throw new Error('No QUICK Contract!');
return uniContract.estimateGas
.delegate(...args, {})
.then((estimatedGasLimit) => {
return uniContract
.delegate(...args, {
value: null,
gasLimit: calculateGasMargin(estimatedGasLimit),
})
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: `Delegated votes`,
});
return response.hash;
});
});
},
[account, addTransaction, chainId, library, uniContract],
);
}
Example #2
Source File: hooks.ts From interface-v2 with GNU General Public License v3.0 | 6 votes |
export function useVoteCallback(): {
voteCallback: (
proposalId: string | undefined,
support: boolean,
) => undefined | Promise<string>;
} {
const { account } = useActiveWeb3React();
const govContract = useGovernanceContract();
const addTransaction = useTransactionAdder();
const voteCallback = useCallback(
(proposalId: string | undefined, support: boolean) => {
if (!account || !govContract || !proposalId) return;
const args = [proposalId, support];
return govContract.estimateGas
.castVote(...args, {})
.then((estimatedGasLimit) => {
return govContract
.castVote(...args, {
value: null,
gasLimit: calculateGasMargin(estimatedGasLimit),
})
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: `Voted ${
support ? 'for ' : 'against'
} proposal ${proposalId}`,
});
return response.hash;
});
});
},
[account, addTransaction, govContract],
);
return { voteCallback };
}
Example #3
Source File: hooks.ts From interface-v2 with GNU General Public License v3.0 | 5 votes |
export function useClaimCallback(
account: string | null | undefined,
): {
claimCallback: () => Promise<string>;
} {
// get claim data for this account
const { library, chainId } = useActiveWeb3React();
const claimData = useUserClaimData(account);
// used for popup summary
const unClaimedAmount: TokenAmount | undefined = useUserUnclaimedAmount(
account,
);
const addTransaction = useTransactionAdder();
const distributorContract = useMerkleDistributorContract();
const claimCallback = async function() {
if (!claimData || !account || !library || !chainId || !distributorContract)
return;
const args = [claimData.index, account, claimData.amount, claimData.proof];
return distributorContract.estimateGas['claim'](...args, {}).then(
(estimatedGasLimit) => {
return distributorContract
.claim(...args, {
value: null,
gasLimit: calculateGasMargin(estimatedGasLimit),
})
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: `Claimed ${formatTokenAmount(unClaimedAmount)} QUICK`,
claim: { recipient: account },
});
return response.hash;
});
},
);
};
return { claimCallback };
}
Example #4
Source File: index.test.ts From vvs-ui with GNU General Public License v3.0 | 5 votes |
describe('utils', () => {
describe('#getBscScanLink', () => {
it('correct for tx', () => {
expect(getExplorerLink('abc', 'transaction', ChainId.MAINNET)).toEqual('https://bscscan.com/tx/abc')
})
it('correct for token', () => {
expect(getExplorerLink('abc', 'token', ChainId.MAINNET)).toEqual('https://bscscan.com/token/abc')
})
it('correct for address', () => {
expect(getExplorerLink('abc', 'address', ChainId.MAINNET)).toEqual('https://bscscan.com/address/abc')
})
it('enum', () => {
expect(getExplorerLink('abc', 'address', ChainId.TESTNET)).toEqual('https://testnet.bscscan.com/address/abc')
})
})
describe('#calculateSlippageAmount', () => {
it('bounds are correct', () => {
const tokenAmount = new TokenAmount(new Token(ChainId.MAINNET, AddressZero, 0), '100')
expect(() => calculateSlippageAmount(tokenAmount, -1)).toThrow()
expect(calculateSlippageAmount(tokenAmount, 0).map((bound) => bound.toString())).toEqual(['100', '100'])
expect(calculateSlippageAmount(tokenAmount, 100).map((bound) => bound.toString())).toEqual(['99', '101'])
expect(calculateSlippageAmount(tokenAmount, 200).map((bound) => bound.toString())).toEqual(['98', '102'])
expect(calculateSlippageAmount(tokenAmount, 10000).map((bound) => bound.toString())).toEqual(['0', '200'])
expect(() => calculateSlippageAmount(tokenAmount, 10001)).toThrow()
})
})
describe('#isAddress', () => {
it('returns false if not', () => {
expect(isAddress('')).toBe(false)
expect(isAddress('0x0000')).toBe(false)
expect(isAddress(1)).toBe(false)
expect(isAddress({})).toBe(false)
expect(isAddress(undefined)).toBe(false)
})
it('returns the checksummed address', () => {
expect(isAddress('0xf164fc0ec4e93095b804a4795bbe1e041497b92a')).toBe('0xf164fC0Ec4E93095b804a4795bBe1e041497b92a')
expect(isAddress('0xf164fC0Ec4E93095b804a4795bBe1e041497b92a')).toBe('0xf164fC0Ec4E93095b804a4795bBe1e041497b92a')
})
it('succeeds even without prefix', () => {
expect(isAddress('f164fc0ec4e93095b804a4795bbe1e041497b92a')).toBe('0xf164fC0Ec4E93095b804a4795bBe1e041497b92a')
})
it('fails if too long', () => {
expect(isAddress('f164fc0ec4e93095b804a4795bbe1e041497b92a0')).toBe(false)
})
})
describe('#calculateGasMargin', () => {
it('adds 10%', () => {
expect(calculateGasMargin(BigNumber.from(1000)).toString()).toEqual('1100')
expect(calculateGasMargin(BigNumber.from(50)).toString()).toEqual('55')
})
})
describe('#basisPointsToPercent', () => {
it('converts basis points numbers to percents', () => {
expect(basisPointsToPercent(100).equalTo(new Percent(JSBI.BigInt(1), JSBI.BigInt(100)))).toBeTruthy()
expect(basisPointsToPercent(500).equalTo(new Percent(JSBI.BigInt(5), JSBI.BigInt(100)))).toBeTruthy()
expect(basisPointsToPercent(50).equalTo(new Percent(JSBI.BigInt(5), JSBI.BigInt(1000)))).toBeTruthy()
})
})
})
Example #5
Source File: AddLiquidity.tsx From interface-v2 with GNU General Public License v3.0 | 4 votes |
AddLiquidity: React.FC<{
currency0?: Currency;
currency1?: Currency;
currencyBg?: string;
}> = ({ currency0, currency1, currencyBg }) => {
const classes = useStyles({});
const { palette } = useTheme();
const { t } = useTranslation();
const [addLiquidityErrorMessage, setAddLiquidityErrorMessage] = useState<
string | null
>(null);
const { account, chainId, library } = useActiveWeb3React();
const [showConfirm, setShowConfirm] = useState(false);
const [attemptingTxn, setAttemptingTxn] = useState(false);
const [txPending, setTxPending] = useState(false);
const [allowedSlippage] = useUserSlippageTolerance();
const deadline = useTransactionDeadline();
const [txHash, setTxHash] = useState('');
const addTransaction = useTransactionAdder();
const finalizedTransaction = useTransactionFinalizer();
const { independentField, typedValue, otherTypedValue } = useMintState();
const expertMode = useIsExpertMode();
const {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useDerivedMintInfo();
const liquidityTokenData = {
amountA: formatTokenAmount(parsedAmounts[Field.CURRENCY_A]),
symbolA: currencies[Field.CURRENCY_A]?.symbol,
amountB: formatTokenAmount(parsedAmounts[Field.CURRENCY_B]),
symbolB: currencies[Field.CURRENCY_B]?.symbol,
};
const pendingText = t('supplyingTokens', liquidityTokenData);
const {
onFieldAInput,
onFieldBInput,
onCurrencySelection,
} = useMintActionHandlers(noLiquidity);
const maxAmounts: { [field in Field]?: TokenAmount } = [
Field.CURRENCY_A,
Field.CURRENCY_B,
].reduce((accumulator, field) => {
return {
...accumulator,
[field]: maxAmountSpend(currencyBalances[field]),
};
}, {});
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: noLiquidity
? otherTypedValue
: parsedAmounts[dependentField]?.toExact() ?? '',
};
const { ethereum } = window as any;
const toggleWalletModal = useWalletModalToggle();
const [approvingA, setApprovingA] = useState(false);
const [approvingB, setApprovingB] = useState(false);
const [approvalA, approveACallback] = useApproveCallback(
parsedAmounts[Field.CURRENCY_A],
chainId ? GlobalConst.addresses.ROUTER_ADDRESS[chainId] : undefined,
);
const [approvalB, approveBCallback] = useApproveCallback(
parsedAmounts[Field.CURRENCY_B],
chainId ? GlobalConst.addresses.ROUTER_ADDRESS[chainId] : undefined,
);
const userPoolBalance = useTokenBalance(
account ?? undefined,
pair?.liquidityToken,
);
const atMaxAmounts: { [field in Field]?: TokenAmount } = [
Field.CURRENCY_A,
Field.CURRENCY_B,
].reduce((accumulator, field) => {
return {
...accumulator,
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0'),
};
}, {});
const handleCurrencyASelect = useCallback(
(currencyA: Currency) => {
onCurrencySelection(Field.CURRENCY_A, currencyA);
},
[onCurrencySelection],
);
const handleCurrencyBSelect = useCallback(
(currencyB: Currency) => {
onCurrencySelection(Field.CURRENCY_B, currencyB);
},
[onCurrencySelection],
);
useEffect(() => {
if (currency0) {
onCurrencySelection(Field.CURRENCY_A, currency0);
} else {
onCurrencySelection(Field.CURRENCY_A, Token.ETHER);
}
if (currency1) {
onCurrencySelection(Field.CURRENCY_B, currency1);
} else {
onCurrencySelection(Field.CURRENCY_B, returnTokenFromKey('QUICK'));
}
}, [onCurrencySelection, currency0, currency1]);
const onAdd = () => {
if (expertMode) {
onAddLiquidity();
} else {
setShowConfirm(true);
}
};
const router = useRouterContract();
const onAddLiquidity = async () => {
if (!chainId || !library || !account || !router) return;
const {
[Field.CURRENCY_A]: parsedAmountA,
[Field.CURRENCY_B]: parsedAmountB,
} = parsedAmounts;
if (
!parsedAmountA ||
!parsedAmountB ||
!currencies[Field.CURRENCY_A] ||
!currencies[Field.CURRENCY_B] ||
!deadline
) {
return;
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(
parsedAmountA,
noLiquidity ? 0 : allowedSlippage,
)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(
parsedAmountB,
noLiquidity ? 0 : allowedSlippage,
)[0],
};
let estimate,
method: (...args: any) => Promise<TransactionResponse>,
args: Array<string | string[] | number>,
value: BigNumber | null;
if (
currencies[Field.CURRENCY_A] === ETHER ||
currencies[Field.CURRENCY_B] === ETHER
) {
const tokenBIsETH = currencies[Field.CURRENCY_B] === ETHER;
estimate = router.estimateGas.addLiquidityETH;
method = router.addLiquidityETH;
args = [
wrappedCurrency(
tokenBIsETH
? currencies[Field.CURRENCY_A]
: currencies[Field.CURRENCY_B],
chainId,
)?.address ?? '', // token
(tokenBIsETH ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[
tokenBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B
].toString(), // token min
amountsMin[
tokenBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A
].toString(), // eth min
account,
deadline.toHexString(),
];
value = BigNumber.from(
(tokenBIsETH ? parsedAmountB : parsedAmountA).raw.toString(),
);
} else {
estimate = router.estimateGas.addLiquidity;
method = router.addLiquidity;
args = [
wrappedCurrency(currencies[Field.CURRENCY_A], chainId)?.address ?? '',
wrappedCurrency(currencies[Field.CURRENCY_B], chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadline.toHexString(),
];
value = null;
}
setAttemptingTxn(true);
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then(async (response) => {
setAttemptingTxn(false);
setTxPending(true);
const summary = t('addLiquidityTokens', liquidityTokenData);
addTransaction(response, {
summary,
});
setTxHash(response.hash);
try {
const receipt = await response.wait();
finalizedTransaction(receipt, {
summary,
});
setTxPending(false);
} catch (error) {
setTxPending(false);
setAddLiquidityErrorMessage(t('errorInTx'));
}
ReactGA.event({
category: 'Liquidity',
action: 'Add',
label: [
currencies[Field.CURRENCY_A]?.symbol,
currencies[Field.CURRENCY_B]?.symbol,
].join('/'),
});
}),
)
.catch((error) => {
setAttemptingTxn(false);
setAddLiquidityErrorMessage(t('txRejected'));
// we only care if the error is something _other_ than the user rejected the tx
if (error?.code !== 4001) {
console.error(error);
}
});
};
const connectWallet = () => {
if (ethereum && !isSupportedNetwork(ethereum)) {
addMaticToMetamask();
} else {
toggleWalletModal();
}
};
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false);
// if there was a tx hash, we want to clear the input
if (txHash) {
onFieldAInput('');
}
setTxHash('');
}, [onFieldAInput, txHash]);
const buttonText = useMemo(() => {
if (account) {
return error ?? t('supply');
} else if (ethereum && !isSupportedNetwork(ethereum)) {
return t('switchPolygon');
}
return t('connectWallet');
}, [account, ethereum, error, t]);
const modalHeader = () => {
return (
<Box>
<Box mt={10} mb={3} display='flex' justifyContent='center'>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={48}
/>
</Box>
<Box mb={6} color={palette.text.primary} textAlign='center'>
<Typography variant='h6'>
{t('supplyingTokens', liquidityTokenData)}
<br />
{t('receiveLPTokens', {
amount: formatTokenAmount(liquidityMinted),
symbolA: currencies[Field.CURRENCY_A]?.symbol,
symbolB: currencies[Field.CURRENCY_B]?.symbol,
})}
</Typography>
</Box>
<Box mb={3} color={palette.text.secondary} textAlign='center'>
<Typography variant='body2'>
{t('outputEstimated', { slippage: allowedSlippage / 100 })}
</Typography>
</Box>
<Box className={classes.swapButtonWrapper}>
<Button onClick={onAddLiquidity}>{t('confirmSupply')}</Button>
</Box>
</Box>
);
};
return (
<Box>
{showConfirm && (
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
txPending={txPending}
hash={txHash}
content={() =>
addLiquidityErrorMessage ? (
<TransactionErrorContent
onDismiss={handleDismissConfirmation}
message={addLiquidityErrorMessage}
/>
) : (
<ConfirmationModalContent
title={t('supplyingliquidity')}
onDismiss={handleDismissConfirmation}
content={modalHeader}
/>
)
}
pendingText={pendingText}
modalContent={
txPending ? t('submittedTxLiquidity') : t('successAddedliquidity')
}
/>
)}
<CurrencyInput
id='add-liquidity-input-tokena'
title={`${t('token')} 1:`}
currency={currencies[Field.CURRENCY_A]}
showHalfButton={Boolean(maxAmounts[Field.CURRENCY_A])}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
onMax={() =>
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}
onHalf={() =>
onFieldAInput(
maxAmounts[Field.CURRENCY_A]
? (Number(maxAmounts[Field.CURRENCY_A]?.toExact()) / 2).toString()
: '',
)
}
handleCurrencySelect={handleCurrencyASelect}
amount={formattedAmounts[Field.CURRENCY_A]}
setAmount={onFieldAInput}
bgColor={currencyBg}
/>
<Box className={classes.exchangeSwap}>
<AddLiquidityIcon />
</Box>
<CurrencyInput
id='add-liquidity-input-tokenb'
title={`${t('token')} 2:`}
showHalfButton={Boolean(maxAmounts[Field.CURRENCY_B])}
currency={currencies[Field.CURRENCY_B]}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
onHalf={() =>
onFieldBInput(
maxAmounts[Field.CURRENCY_B]
? (Number(maxAmounts[Field.CURRENCY_B]?.toExact()) / 2).toString()
: '',
)
}
onMax={() =>
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}
handleCurrencySelect={handleCurrencyBSelect}
amount={formattedAmounts[Field.CURRENCY_B]}
setAmount={onFieldBInput}
bgColor={currencyBg}
/>
{currencies[Field.CURRENCY_A] &&
currencies[Field.CURRENCY_B] &&
pairState !== PairState.INVALID &&
price && (
<Box my={2}>
<Box className={classes.swapPrice}>
<Typography variant='body2'>
1 {currencies[Field.CURRENCY_A]?.symbol} ={' '}
{price.toSignificant(3)} {currencies[Field.CURRENCY_B]?.symbol}{' '}
</Typography>
<Typography variant='body2'>
1 {currencies[Field.CURRENCY_B]?.symbol} ={' '}
{price.invert().toSignificant(3)}{' '}
{currencies[Field.CURRENCY_A]?.symbol}{' '}
</Typography>
</Box>
<Box className={classes.swapPrice}>
<Typography variant='body2'>{t('yourPoolShare')}:</Typography>
<Typography variant='body2'>
{poolTokenPercentage
? poolTokenPercentage.toSignificant(6) + '%'
: '-'}
</Typography>
</Box>
<Box className={classes.swapPrice}>
<Typography variant='body2'>{t('lpTokenReceived')}:</Typography>
<Typography variant='body2'>
{formatTokenAmount(userPoolBalance)} {t('lpTokens')}
</Typography>
</Box>
</Box>
)}
<Box className={classes.swapButtonWrapper}>
{(approvalA === ApprovalState.NOT_APPROVED ||
approvalA === ApprovalState.PENDING ||
approvalB === ApprovalState.NOT_APPROVED ||
approvalB === ApprovalState.PENDING) &&
!error && (
<Box className={classes.approveButtons}>
{approvalA !== ApprovalState.APPROVED && (
<Box
width={approvalB !== ApprovalState.APPROVED ? '48%' : '100%'}
>
<Button
onClick={async () => {
setApprovingA(true);
try {
await approveACallback();
setApprovingA(false);
} catch (e) {
setApprovingA(false);
}
}}
disabled={approvingA || approvalA === ApprovalState.PENDING}
>
{approvalA === ApprovalState.PENDING
? `${t('approving')} ${
currencies[Field.CURRENCY_A]?.symbol
}`
: `${t('approve')} ${
currencies[Field.CURRENCY_A]?.symbol
}`}
</Button>
</Box>
)}
{approvalB !== ApprovalState.APPROVED && (
<Box
width={approvalA !== ApprovalState.APPROVED ? '48%' : '100%'}
>
<Button
onClick={async () => {
setApprovingB(true);
try {
await approveBCallback();
setApprovingB(false);
} catch (e) {
setApprovingB(false);
}
}}
disabled={approvingB || approvalB === ApprovalState.PENDING}
>
{approvalB === ApprovalState.PENDING
? `${t('approving')} ${
currencies[Field.CURRENCY_B]?.symbol
}`
: `${t('approve')} ${
currencies[Field.CURRENCY_B]?.symbol
}`}
</Button>
</Box>
)}
</Box>
)}
<Button
disabled={
Boolean(account) &&
(Boolean(error) ||
approvalA !== ApprovalState.APPROVED ||
approvalB !== ApprovalState.APPROVED)
}
onClick={account ? onAdd : connectWallet}
>
{buttonText}
</Button>
</Box>
</Box>
);
}
Example #6
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 #7
Source File: useApproveCallback.ts From interface-v2 with GNU General Public License v3.0 | 4 votes |
// returns a variable indicating the state of the approval and a function which approves if necessary or early returns
export function useApproveCallback(
amountToApprove?: CurrencyAmount,
spender?: string,
): [ApprovalState, () => Promise<void>] {
const { account } = useActiveWeb3React();
const token =
amountToApprove instanceof TokenAmount ? amountToApprove.token : undefined;
const currentAllowance = useTokenAllowance(
token,
account ?? undefined,
spender,
);
const pendingApproval = useHasPendingApproval(token?.address, spender);
// check the current approval status
const approvalState: ApprovalState = useMemo(() => {
if (!amountToApprove || !spender) return ApprovalState.UNKNOWN;
if (amountToApprove.currency === ETHER) return ApprovalState.APPROVED;
// we might not have enough data to know whether or not we need to approve
if (!currentAllowance) return ApprovalState.UNKNOWN;
// amountToApprove will be defined if currentAllowance is
return currentAllowance.lessThan(amountToApprove)
? pendingApproval
? ApprovalState.PENDING
: ApprovalState.NOT_APPROVED
: ApprovalState.APPROVED;
}, [amountToApprove, currentAllowance, pendingApproval, spender]);
const tokenContract = useTokenContract(token?.address);
const addTransaction = useTransactionAdder();
const approve = useCallback(async (): Promise<void> => {
if (approvalState !== ApprovalState.NOT_APPROVED) {
console.error('approve was called unnecessarily');
return;
}
if (!token) {
console.error('no token');
return;
}
if (!tokenContract) {
console.error('tokenContract is null');
return;
}
if (!amountToApprove) {
console.error('missing amount to approve');
return;
}
if (!spender) {
console.error('no spender');
return;
}
let useExact = false;
const estimatedGas = await tokenContract.estimateGas
.approve(spender, MaxUint256)
.catch(() => {
// general fallback for tokens who restrict approval amounts
useExact = true;
return tokenContract.estimateGas.approve(
spender,
amountToApprove.raw.toString(),
);
});
return tokenContract
.approve(
spender,
useExact ? amountToApprove.raw.toString() : MaxUint256,
{
gasLimit: calculateGasMargin(estimatedGas),
},
)
.then(async (response: TransactionResponse) => {
addTransaction(response, {
summary: 'Approve ' + amountToApprove.currency.symbol,
approval: { tokenAddress: token.address, spender: spender },
});
try {
await response.wait();
} catch (e) {
console.debug('Failed to approve token', e);
throw e;
}
})
.catch((error: Error) => {
console.debug('Failed to approve token', error);
throw error;
});
}, [
approvalState,
token,
tokenContract,
amountToApprove,
spender,
addTransaction,
]);
return [approvalState, approve];
}
Example #8
Source File: useSwapCallback.ts From interface-v2 with GNU General Public License v3.0 | 4 votes |
// returns a function that will execute a swap, if the parameters are all valid
// and the user has approved the slippage adjusted input amount for the trade
export function useSwapCallback(
trade: Trade | undefined, // trade to execute, required
allowedSlippage: number = GlobalConst.utils.INITIAL_ALLOWED_SLIPPAGE, // in bips
recipientAddressOrName: string | null, // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
): {
state: SwapCallbackState;
callback:
| null
| (() => Promise<{ response: TransactionResponse; summary: string }>);
error: string | null;
} {
const { account, chainId, library } = useActiveWeb3React();
const swapCalls = useSwapCallArguments(
trade,
allowedSlippage,
recipientAddressOrName,
);
const addTransaction = useTransactionAdder();
const { address: recipientAddress } = useENS(recipientAddressOrName);
const recipient =
recipientAddressOrName === null ? account : recipientAddress;
return useMemo(() => {
if (!trade || !library || !account || !chainId) {
return {
state: SwapCallbackState.INVALID,
callback: null,
error: 'Missing dependencies',
};
}
if (!recipient) {
if (recipientAddressOrName !== null) {
return {
state: SwapCallbackState.INVALID,
callback: null,
error: 'Invalid recipient',
};
} else {
return {
state: SwapCallbackState.LOADING,
callback: null,
error: null,
};
}
}
const tradeVersion = Version.v2;
return {
state: SwapCallbackState.VALID,
callback: async function onSwap(): Promise<{
response: TransactionResponse;
summary: string;
}> {
const estimatedCalls: EstimatedSwapCall[] = await Promise.all(
swapCalls.map((call) => {
const {
parameters: { methodName, args, value },
contract,
} = call;
const options = !value || isZero(value) ? {} : { value };
return contract.estimateGas[methodName](...args, options)
.then((gasEstimate) => {
return {
call,
gasEstimate: gasEstimate.add(100000),
};
})
.catch((gasError) => {
console.debug(
'Gas estimate failed, trying eth_call to extract error',
call,
);
return contract.callStatic[methodName](...args, options)
.then((result) => {
console.debug(
'Unexpected successful call after failed estimate gas',
call,
gasError,
result,
);
return {
call,
error: new Error(
'Unexpected issue with estimating the gas. Please try again.',
),
};
})
.catch((callError) => {
console.debug('Call threw error', call, callError);
let errorMessage: string;
switch (callError.reason) {
case 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT':
case 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT':
errorMessage =
'This transaction will not succeed either due to price movement or fee on transfer. Try increasing your slippage tolerance.';
break;
default:
errorMessage = `The transaction cannot succeed due to error: ${callError.reason}. This is probably an issue with one of the tokens you are swapping.`;
}
return { call, error: new Error(errorMessage) };
});
});
}),
);
// a successful estimation is a bignumber gas estimate and the next call is also a bignumber gas estimate
const successfulEstimation = estimatedCalls.find(
(el, ix, list): el is SuccessfulCall =>
'gasEstimate' in el &&
(ix === list.length - 1 || 'gasEstimate' in list[ix + 1]),
);
if (!successfulEstimation) {
const errorCalls = estimatedCalls.filter(
(call): call is FailedCall => 'error' in call,
);
if (errorCalls.length > 0)
throw errorCalls[errorCalls.length - 1].error;
throw new Error(
'Unexpected error. Please contact support: none of the calls threw an error',
);
}
const {
call: {
contract,
parameters: { methodName, args, value },
},
gasEstimate,
} = successfulEstimation;
return contract[methodName](...args, {
gasLimit: calculateGasMargin(gasEstimate),
...(value && !isZero(value)
? { value, from: account }
: { from: account }),
})
.then((response: TransactionResponse) => {
const inputSymbol = trade.inputAmount.currency.symbol;
const outputSymbol = trade.outputAmount.currency.symbol;
const inputAmount = formatTokenAmount(trade.inputAmount);
const outputAmount = formatTokenAmount(trade.outputAmount);
const base = `Swap ${inputAmount} ${inputSymbol} for ${outputAmount} ${outputSymbol}`;
const withRecipient =
recipient === account
? base
: `${base} to ${
recipientAddressOrName && isAddress(recipientAddressOrName)
? shortenAddress(recipientAddressOrName)
: recipientAddressOrName
}`;
const withVersion =
tradeVersion === Version.v2
? withRecipient
: `${withRecipient} on ${(tradeVersion as any).toUpperCase()}`;
addTransaction(response, {
summary: withVersion,
});
return { response, summary: withVersion };
})
.catch((error: any) => {
// if the user rejected the tx, pass this along
if (error?.code === 4001) {
throw new Error('Transaction rejected.');
} else {
// otherwise, the error was unexpected and we need to convey that
console.error(`Swap failed`, error, methodName, args, value);
throw new Error(`Swap failed: ${error.message}`);
}
});
},
error: null,
};
}, [
trade,
library,
account,
chainId,
recipient,
recipientAddressOrName,
swapCalls,
addTransaction,
]);
}
Example #9
Source File: index.test.ts From glide-frontend with GNU General Public License v3.0 | 4 votes |
describe('utils', () => {
describe('#getBscScanLink', () => {
it('correct for tx', () => {
expect(getBscScanLink('abc', 'transaction', ChainId.MAINNET)).toEqual('https://explorer.com/tx/abc')
})
it('correct for token', () => {
expect(getBscScanLink('abc', 'token', ChainId.MAINNET)).toEqual('https://explorer.com/token/abc')
})
it('correct for address', () => {
expect(getBscScanLink('abc', 'address', ChainId.MAINNET)).toEqual('https://explorer.com/address/abc')
})
it('enum', () => {
expect(getBscScanLink('abc', 'address', ChainId.TESTNET)).toEqual('https://testnet.explorer.com/address/abc')
})
})
describe('#calculateSlippageAmount', () => {
it('bounds are correct', () => {
const tokenAmount = new TokenAmount(new Token(ChainId.MAINNET, AddressZero, 0), '100')
expect(() => calculateSlippageAmount(tokenAmount, -1)).toThrow()
expect(calculateSlippageAmount(tokenAmount, 0).map((bound) => bound.toString())).toEqual(['100', '100'])
expect(calculateSlippageAmount(tokenAmount, 100).map((bound) => bound.toString())).toEqual(['99', '101'])
expect(calculateSlippageAmount(tokenAmount, 200).map((bound) => bound.toString())).toEqual(['98', '102'])
expect(calculateSlippageAmount(tokenAmount, 10000).map((bound) => bound.toString())).toEqual(['0', '200'])
expect(() => calculateSlippageAmount(tokenAmount, 10001)).toThrow()
})
})
describe('#isAddress', () => {
it('returns false if not', () => {
expect(isAddress('')).toBe(false)
expect(isAddress('0x0000')).toBe(false)
expect(isAddress(1)).toBe(false)
expect(isAddress({})).toBe(false)
expect(isAddress(undefined)).toBe(false)
})
it('returns the checksummed address', () => {
expect(isAddress('0xf164fc0ec4e93095b804a4795bbe1e041497b92a')).toBe('0xf164fC0Ec4E93095b804a4795bBe1e041497b92a')
expect(isAddress('0xf164fC0Ec4E93095b804a4795bBe1e041497b92a')).toBe('0xf164fC0Ec4E93095b804a4795bBe1e041497b92a')
})
it('succeeds even without prefix', () => {
expect(isAddress('f164fc0ec4e93095b804a4795bbe1e041497b92a')).toBe('0xf164fC0Ec4E93095b804a4795bBe1e041497b92a')
})
it('fails if too long', () => {
expect(isAddress('f164fc0ec4e93095b804a4795bbe1e041497b92a0')).toBe(false)
})
})
describe('#shortenAddress', () => {
it('throws on invalid address', () => {
expect(() => shortenAddress('abc')).toThrow("Invalid 'address'")
})
it('truncates middle characters', () => {
expect(shortenAddress('0xf164fc0ec4e93095b804a4795bbe1e041497b92a')).toBe('0xf164...b92a')
})
it('truncates middle characters even without prefix', () => {
expect(shortenAddress('f164fc0ec4e93095b804a4795bbe1e041497b92a')).toBe('0xf164...b92a')
})
it('renders checksummed address', () => {
expect(shortenAddress('0x2E1b342132A67Ea578e4E3B814bae2107dc254CC'.toLowerCase())).toBe('0x2E1b...54CC')
})
})
describe('#calculateGasMargin', () => {
it('adds 10%', () => {
expect(calculateGasMargin(BigNumber.from(1000)).toString()).toEqual('1100')
expect(calculateGasMargin(BigNumber.from(50)).toString()).toEqual('55')
})
})
describe('#basisPointsToPercent', () => {
it('converts basis points numbers to percents', () => {
expect(basisPointsToPercent(100).equalTo(new Percent(JSBI.BigInt(1), JSBI.BigInt(100)))).toBeTruthy()
expect(basisPointsToPercent(500).equalTo(new Percent(JSBI.BigInt(5), JSBI.BigInt(100)))).toBeTruthy()
expect(basisPointsToPercent(50).equalTo(new Percent(JSBI.BigInt(5), JSBI.BigInt(1000)))).toBeTruthy()
})
})
})
Example #10
Source File: index.tsx From goose-frontend-amm with GNU General Public License v3.0 | 4 votes |
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB },
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React()
const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB)
const oneCurrencyIsWETH = Boolean(
chainId &&
((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
(currencyB && currencyEquals(currencyB, WETH[chainId])))
)
const expertMode = useIsExpertMode()
// mint state
const { independentField, typedValue, otherTypedValue } = useMintState()
const {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
// txn values
const [deadline] = useUserDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: noLiquidity ? otherTypedValue : parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
// get the max amounts user can add
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmountSpend(currencyBalances[field]),
}
},
{}
)
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0'),
}
},
{}
)
// check whether the user has approved the router on the tokens
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS)
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS)
const addTransaction = useTransactionAdder()
async function onAdd() {
if (!chainId || !library || !account) return
const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) {
return
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0],
}
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate
let method: (...args: any) => Promise<TransactionResponse>
let args: Array<string | string[] | number>
let value: BigNumber | null
if (currencyA === ETHER || currencyB === ETHER) {
const tokenBIsETH = currencyB === ETHER
estimate = router.estimateGas.addLiquidityETH
method = router.addLiquidityETH
args = [
wrappedCurrency(tokenBIsETH ? currencyA : currencyB, chainId)?.address ?? '', // token
(tokenBIsETH ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[tokenBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account,
deadlineFromNow,
]
value = BigNumber.from((tokenBIsETH ? parsedAmountB : parsedAmountA).raw.toString())
} else {
estimate = router.estimateGas.addLiquidity
method = router.addLiquidity
args = [
wrappedCurrency(currencyA, chainId)?.address ?? '',
wrappedCurrency(currencyB, chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
value = null
}
setAttemptingTxn(true)
// const aa = await estimate(...args, value ? { value } : {})
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then((response) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Add ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencies[Field.CURRENCY_B]?.symbol}`,
})
setTxHash(response.hash)
})
)
.catch((e) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (e?.code !== 4001) {
console.error(e)
}
})
}
const modalHeader = () => {
return noLiquidity ? (
<AutoColumn gap="20px">
<LightCard mt="20px" borderRadius="20px">
<RowFlat>
<UIKitText fontSize="48px" mr="8px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol}`}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
</LightCard>
</AutoColumn>
) : (
<AutoColumn gap="20px">
<RowFlat style={{ marginTop: '20px' }}>
<UIKitText fontSize="48px" mr="8px">
{liquidityMinted?.toSignificant(6)}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
<Row>
<UIKitText fontSize="24px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol} Pool Tokens`}
</UIKitText>
</Row>
<Italic fontSize={12} textAlign="left" padding="8px 0 0 0 ">
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</Italic>
</AutoColumn>
)
}
const modalBottom = () => {
return (
<ConfirmAddModalBottom
price={price}
currencies={currencies}
parsedAmounts={parsedAmounts}
noLiquidity={noLiquidity}
onAdd={onAdd}
poolTokenPercentage={poolTokenPercentage}
/>
)
}
const pendingText = `Supplying ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencies[Field.CURRENCY_B]?.symbol}`
const handleCurrencyASelect = useCallback(
(currA: Currency) => {
const newCurrencyIdA = currencyId(currA)
if (newCurrencyIdA === currencyIdB) {
history.push(`/add/${currencyIdB}/${currencyIdA}`)
} else {
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`)
}
},
[currencyIdB, history, currencyIdA]
)
const handleCurrencyBSelect = useCallback(
(currB: Currency) => {
const newCurrencyIdB = currencyId(currB)
if (currencyIdA === newCurrencyIdB) {
if (currencyIdB) {
history.push(`/add/${currencyIdB}/${newCurrencyIdB}`)
} else {
history.push(`/add/${newCurrencyIdB}`)
}
} else {
history.push(`/add/${currencyIdA || 'ETH'}/${newCurrencyIdB}`)
}
},
[currencyIdA, history, currencyIdB]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
// if there was a tx hash, we want to clear the input
if (txHash) {
onFieldAInput('')
}
setTxHash('')
}, [onFieldAInput, txHash])
return (
<>
<CardNav activeIndex={1} />
<AppBody>
<AddRemoveTabs adding />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash}
content={() => (
<ConfirmationModalContent
title={noLiquidity ? 'You are creating a pool' : 'You will receive'}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<CardBody>
<AutoColumn gap="20px">
{noLiquidity && (
<ColumnCenter>
<Pane>
<AutoColumn gap="12px">
<UIKitText>You are the first liquidity provider.</UIKitText>
<UIKitText>The ratio of tokens you add will set the price of this pool.</UIKitText>
<UIKitText>Once you are happy with the rate click supply to review.</UIKitText>
</AutoColumn>
</Pane>
</ColumnCenter>
)}
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
onMax={() => {
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}}
onCurrencySelect={handleCurrencyASelect}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A]}
id="add-liquidity-input-tokena"
showCommonBases={false}
/>
<ColumnCenter>
<AddIcon color="textSubtle" />
</ColumnCenter>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onFieldBInput}
onCurrencySelect={handleCurrencyBSelect}
onMax={() => {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
currency={currencies[Field.CURRENCY_B]}
id="add-liquidity-input-tokenb"
showCommonBases={false}
/>
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<div>
<UIKitText
style={{ textTransform: 'uppercase', fontWeight: 600 }}
color="textSubtle"
fontSize="12px"
mb="2px"
>
{noLiquidity ? 'Initial prices and pool share' : 'Prices and pool share'}
</UIKitText>
<Pane>
<PoolPriceBar
currencies={currencies}
poolTokenPercentage={poolTokenPercentage}
noLiquidity={noLiquidity}
price={price}
/>
</Pane>
</div>
)}
{!account ? (
<ConnectWalletButton fullWidth />
) : (
<AutoColumn gap="md">
{(approvalA === ApprovalState.NOT_APPROVED ||
approvalA === ApprovalState.PENDING ||
approvalB === ApprovalState.NOT_APPROVED ||
approvalB === ApprovalState.PENDING) &&
isValid && (
<RowBetween>
{approvalA !== ApprovalState.APPROVED && (
<Button
onClick={approveACallback}
disabled={approvalA === ApprovalState.PENDING}
style={{ width: approvalB !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalA === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_A]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_A]?.symbol}`
)}
</Button>
)}
{approvalB !== ApprovalState.APPROVED && (
<Button
onClick={approveBCallback}
disabled={approvalB === ApprovalState.PENDING}
style={{ width: approvalA !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalB === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_B]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_B]?.symbol}`
)}
</Button>
)}
</RowBetween>
)}
<Button
onClick={() => {
if (expertMode) {
onAdd()
} else {
setShowConfirm(true)
}
}}
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
fullWidth
>
{error ?? 'Supply'}
</Button>
</AutoColumn>
)}
</AutoColumn>
</CardBody>
</Wrapper>
</AppBody>
{pair && !noLiquidity && pairState !== PairState.INVALID ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
</AutoColumn>
) : null}
</>
)
}
Example #11
Source File: index.tsx From mozartfinance-swap-interface with GNU General Public License v3.0 | 4 votes |
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB },
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React()
const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB)
const TranslateString = useI18n()
const oneCurrencyIsWBNB = Boolean(
chainId &&
((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
(currencyB && currencyEquals(currencyB, WETH[chainId])))
)
const expertMode = useIsExpertMode()
// mint state
const { independentField, typedValue, otherTypedValue } = useMintState()
const {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
// txn values
const [deadline] = useUserDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: noLiquidity ? otherTypedValue : parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
// get the max amounts user can add
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmountSpend(currencyBalances[field]),
}
},
{}
)
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0'),
}
},
{}
)
// check whether the user has approved the router on the tokens
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS)
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS)
const addTransaction = useTransactionAdder()
async function onAdd() {
if (!chainId || !library || !account) return
const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) {
return
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0],
}
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate
let method: (...args: any) => Promise<TransactionResponse>
let args: Array<string | string[] | number>
let value: BigNumber | null
if (currencyA === ETHER || currencyB === ETHER) {
const tokenBIsBNB = currencyB === ETHER
estimate = router.estimateGas.addLiquidityETH
method = router.addLiquidityETH
args = [
wrappedCurrency(tokenBIsBNB ? currencyA : currencyB, chainId)?.address ?? '', // token
(tokenBIsBNB ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[tokenBIsBNB ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsBNB ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account,
deadlineFromNow,
]
value = BigNumber.from((tokenBIsBNB ? parsedAmountB : parsedAmountA).raw.toString())
} else {
estimate = router.estimateGas.addLiquidity
method = router.addLiquidity
args = [
wrappedCurrency(currencyA, chainId)?.address ?? '',
wrappedCurrency(currencyB, chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
value = null
}
setAttemptingTxn(true)
// const aa = await estimate(...args, value ? { value } : {})
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then((response) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Add ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencies[Field.CURRENCY_B]?.symbol}`,
})
setTxHash(response.hash)
})
)
.catch((e) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (e?.code !== 4001) {
console.error(e)
}
})
}
const modalHeader = () => {
return noLiquidity ? (
<AutoColumn gap="20px">
<LightCard mt="20px" borderRadius="20px">
<RowFlat>
<UIKitText fontSize="48px" mr="8px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol}`}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
</LightCard>
</AutoColumn>
) : (
<AutoColumn gap="20px">
<RowFlat style={{ marginTop: '20px' }}>
<UIKitText fontSize="48px" mr="8px">
{liquidityMinted?.toSignificant(6)}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
<Row>
<UIKitText fontSize="24px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol} Pool Tokens`}
</UIKitText>
</Row>
<UIKitText small textAlign="left" padding="8px 0 0 0 " style={{ fontStyle: 'italic' }}>
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</UIKitText>
</AutoColumn>
)
}
const modalBottom = () => {
return (
<ConfirmAddModalBottom
price={price}
currencies={currencies}
parsedAmounts={parsedAmounts}
noLiquidity={noLiquidity}
onAdd={onAdd}
poolTokenPercentage={poolTokenPercentage}
/>
)
}
const pendingText = `Supplying ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencies[Field.CURRENCY_B]?.symbol}`
const handleCurrencyASelect = useCallback(
(currA: Currency) => {
const newCurrencyIdA = currencyId(currA)
if (newCurrencyIdA === currencyIdB) {
history.push(`/add/${currencyIdB}/${currencyIdA}`)
} else {
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`)
}
},
[currencyIdB, history, currencyIdA]
)
const handleCurrencyBSelect = useCallback(
(currB: Currency) => {
const newCurrencyIdB = currencyId(currB)
if (currencyIdA === newCurrencyIdB) {
if (currencyIdB) {
history.push(`/add/${currencyIdB}/${newCurrencyIdB}`)
} else {
history.push(`/add/${newCurrencyIdB}`)
}
} else {
history.push(`/add/${currencyIdA || 'BNB'}/${newCurrencyIdB}`)
}
},
[currencyIdA, history, currencyIdB]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
// if there was a tx hash, we want to clear the input
if (txHash) {
onFieldAInput('')
}
setTxHash('')
}, [onFieldAInput, txHash])
return (
<>
<CardNav activeIndex={1} />
<AppBody>
<AddRemoveTabs adding />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash}
content={() => (
<ConfirmationModalContent
title={
noLiquidity
? TranslateString(1154, 'You are creating a pool')
: TranslateString(1156, 'You will receive')
}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<CardBody>
<AutoColumn gap="20px">
{noLiquidity && (
<ColumnCenter>
<Pane>
<AutoColumn gap="12px">
<UIKitText>{TranslateString(1158, 'You are the first liquidity provider.')}</UIKitText>
<UIKitText>
{TranslateString(1160, 'The ratio of tokens you add will set the price of this pool.')}
</UIKitText>
<UIKitText>
{TranslateString(1162, 'Once you are happy with the rate click supply to review.')}
</UIKitText>
</AutoColumn>
</Pane>
</ColumnCenter>
)}
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
onMax={() => {
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}}
onCurrencySelect={handleCurrencyASelect}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A]}
id="add-liquidity-input-tokena"
showCommonBases={false}
/>
<ColumnCenter>
<AddIcon color="textSubtle" />
</ColumnCenter>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onFieldBInput}
onCurrencySelect={handleCurrencyBSelect}
onMax={() => {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
currency={currencies[Field.CURRENCY_B]}
id="add-liquidity-input-tokenb"
showCommonBases={false}
/>
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<div>
<UIKitText
style={{ textTransform: 'uppercase', fontWeight: 600 }}
color="textSubtle"
fontSize="12px"
mb="2px"
>
{noLiquidity
? TranslateString(1164, 'Initial prices and pool share')
: TranslateString(1166, 'Prices and pool share')}
</UIKitText>
<Pane>
<PoolPriceBar
currencies={currencies}
poolTokenPercentage={poolTokenPercentage}
noLiquidity={noLiquidity}
price={price}
/>
</Pane>
</div>
)}
{!account ? (
<ConnectWalletButton width="100%" />
) : (
<AutoColumn gap="md">
{(approvalA === ApprovalState.NOT_APPROVED ||
approvalA === ApprovalState.PENDING ||
approvalB === ApprovalState.NOT_APPROVED ||
approvalB === ApprovalState.PENDING) &&
isValid && (
<RowBetween>
{approvalA !== ApprovalState.APPROVED && (
<Button
onClick={approveACallback}
disabled={approvalA === ApprovalState.PENDING}
style={{ width: approvalB !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalA === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_A]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_A]?.symbol}`
)}
</Button>
)}
{approvalB !== ApprovalState.APPROVED && (
<Button
onClick={approveBCallback}
disabled={approvalB === ApprovalState.PENDING}
style={{ width: approvalA !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalB === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_B]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_B]?.symbol}`
)}
</Button>
)}
</RowBetween>
)}
<Button
onClick={() => {
if (expertMode) {
onAdd()
} else {
setShowConfirm(true)
}
}}
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
width="100%"
>
{error ?? 'Supply'}
</Button>
</AutoColumn>
)}
</AutoColumn>
</CardBody>
</Wrapper>
</AppBody>
{pair && !noLiquidity && pairState !== PairState.INVALID ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWBNB} pair={pair} />
</AutoColumn>
) : null}
</>
)
}
Example #12
Source File: index.tsx From pancake-swap-exchange-testnet with GNU General Public License v3.0 | 4 votes |
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB },
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React()
const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB)
const TranslateString = useI18n()
const oneCurrencyIsWBNB = Boolean(
chainId &&
((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
(currencyB && currencyEquals(currencyB, WETH[chainId])))
)
const expertMode = useIsExpertMode()
// mint state
const { independentField, typedValue, otherTypedValue } = useMintState()
const {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
// txn values
const [deadline] = useUserDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: noLiquidity ? otherTypedValue : parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
// get the max amounts user can add
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmountSpend(currencyBalances[field]),
}
},
{}
)
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0'),
}
},
{}
)
// check whether the user has approved the router on the tokens
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS)
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS)
const addTransaction = useTransactionAdder()
async function onAdd() {
if (!chainId || !library || !account) return
const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) {
return
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0],
}
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate
let method: (...args: any) => Promise<TransactionResponse>
let args: Array<string | string[] | number>
let value: BigNumber | null
if (currencyA === ETHER || currencyB === ETHER) {
const tokenBIsBNB = currencyB === ETHER
estimate = router.estimateGas.addLiquidityETH
method = router.addLiquidityETH
args = [
wrappedCurrency(tokenBIsBNB ? currencyA : currencyB, chainId)?.address ?? '', // token
(tokenBIsBNB ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[tokenBIsBNB ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsBNB ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account,
deadlineFromNow,
]
value = BigNumber.from((tokenBIsBNB ? parsedAmountB : parsedAmountA).raw.toString())
} else {
estimate = router.estimateGas.addLiquidity
method = router.addLiquidity
args = [
wrappedCurrency(currencyA, chainId)?.address ?? '',
wrappedCurrency(currencyB, chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
value = null
}
setAttemptingTxn(true)
// const aa = await estimate(...args, value ? { value } : {})
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then((response) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Add ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencies[Field.CURRENCY_B]?.symbol}`,
})
setTxHash(response.hash)
})
)
.catch((e) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (e?.code !== 4001) {
console.error(e)
}
})
}
const modalHeader = () => {
return noLiquidity ? (
<AutoColumn gap="20px">
<LightCard mt="20px" borderRadius="20px">
<RowFlat>
<UIKitText fontSize="48px" mr="8px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol}`}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
</LightCard>
</AutoColumn>
) : (
<AutoColumn gap="20px">
<RowFlat style={{ marginTop: '20px' }}>
<UIKitText fontSize="48px" mr="8px">
{liquidityMinted?.toSignificant(6)}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
<Row>
<UIKitText fontSize="24px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol} Pool Tokens`}
</UIKitText>
</Row>
<UIKitText small textAlign="left" padding="8px 0 0 0 " style={{ fontStyle: 'italic' }}>
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</UIKitText>
</AutoColumn>
)
}
const modalBottom = () => {
return (
<ConfirmAddModalBottom
price={price}
currencies={currencies}
parsedAmounts={parsedAmounts}
noLiquidity={noLiquidity}
onAdd={onAdd}
poolTokenPercentage={poolTokenPercentage}
/>
)
}
const pendingText = `Supplying ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencies[Field.CURRENCY_B]?.symbol}`
const handleCurrencyASelect = useCallback(
(currA: Currency) => {
const newCurrencyIdA = currencyId(currA)
if (newCurrencyIdA === currencyIdB) {
history.push(`/add/${currencyIdB}/${currencyIdA}`)
} else {
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`)
}
},
[currencyIdB, history, currencyIdA]
)
const handleCurrencyBSelect = useCallback(
(currB: Currency) => {
const newCurrencyIdB = currencyId(currB)
if (currencyIdA === newCurrencyIdB) {
if (currencyIdB) {
history.push(`/add/${currencyIdB}/${newCurrencyIdB}`)
} else {
history.push(`/add/${newCurrencyIdB}`)
}
} else {
history.push(`/add/${currencyIdA || 'BNB'}/${newCurrencyIdB}`)
}
},
[currencyIdA, history, currencyIdB]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
// if there was a tx hash, we want to clear the input
if (txHash) {
onFieldAInput('')
}
setTxHash('')
}, [onFieldAInput, txHash])
return (
<Container>
<CardNav activeIndex={1} />
<AppBody>
<AddRemoveTabs adding />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash}
content={() => (
<ConfirmationModalContent
title={
noLiquidity
? TranslateString(1154, 'You are creating a pool')
: TranslateString(1156, 'You will receive')
}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<CardBody>
<AutoColumn gap="20px">
{noLiquidity && (
<ColumnCenter>
<Pane>
<AutoColumn gap="12px">
<UIKitText>{TranslateString(1158, 'You are the first liquidity provider.')}</UIKitText>
<UIKitText>
{TranslateString(1160, 'The ratio of tokens you add will set the price of this pool.')}
</UIKitText>
<UIKitText>
{TranslateString(1162, 'Once you are happy with the rate click supply to review.')}
</UIKitText>
</AutoColumn>
</Pane>
</ColumnCenter>
)}
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
onMax={() => {
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}}
onCurrencySelect={handleCurrencyASelect}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A]}
id="add-liquidity-input-tokena"
showCommonBases={false}
/>
<ColumnCenter>
<AddIcon color="textSubtle" />
</ColumnCenter>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onFieldBInput}
onCurrencySelect={handleCurrencyBSelect}
onMax={() => {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
currency={currencies[Field.CURRENCY_B]}
id="add-liquidity-input-tokenb"
showCommonBases={false}
/>
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<div>
<UIKitText
style={{ textTransform: 'uppercase', fontWeight: 600 }}
color="textSubtle"
fontSize="12px"
mb="2px"
>
{noLiquidity
? TranslateString(1164, 'Initial prices and pool share')
: TranslateString(1166, 'Prices and pool share')}
</UIKitText>
<Pane>
<PoolPriceBar
currencies={currencies}
poolTokenPercentage={poolTokenPercentage}
noLiquidity={noLiquidity}
price={price}
/>
</Pane>
</div>
)}
{!account ? (
<ConnectWalletButton width="100%" />
) : (
<AutoColumn gap="md">
{(approvalA === ApprovalState.NOT_APPROVED ||
approvalA === ApprovalState.PENDING ||
approvalB === ApprovalState.NOT_APPROVED ||
approvalB === ApprovalState.PENDING) &&
isValid && (
<RowBetween>
{approvalA !== ApprovalState.APPROVED && (
<Button
onClick={approveACallback}
disabled={approvalA === ApprovalState.PENDING}
style={{ width: approvalB !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalA === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_A]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_A]?.symbol}`
)}
</Button>
)}
{approvalB !== ApprovalState.APPROVED && (
<Button
onClick={approveBCallback}
disabled={approvalB === ApprovalState.PENDING}
style={{ width: approvalA !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalB === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_B]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_B]?.symbol}`
)}
</Button>
)}
</RowBetween>
)}
<Button
onClick={() => {
if (expertMode) {
onAdd()
} else {
setShowConfirm(true)
}
}}
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
width="100%"
>
{error ?? 'Supply'}
</Button>
</AutoColumn>
)}
</AutoColumn>
</CardBody>
</Wrapper>
</AppBody>
{pair && !noLiquidity && pairState !== PairState.INVALID ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWBNB} pair={pair} />
</AutoColumn>
) : null}
</Container>
)
}
Example #13
Source File: index.tsx From pancake-swap-testnet with MIT License | 4 votes |
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB },
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React()
const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB)
const TranslateString = useI18n()
const oneCurrencyIsWBNB = Boolean(
chainId &&
((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
(currencyB && currencyEquals(currencyB, WETH[chainId])))
)
const expertMode = useIsExpertMode()
// mint state
const { independentField, typedValue, otherTypedValue } = useMintState()
const {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
// txn values
const [deadline] = useUserDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: noLiquidity ? otherTypedValue : parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
// get the max amounts user can add
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmountSpend(currencyBalances[field]),
}
},
{}
)
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0'),
}
},
{}
)
// check whether the user has approved the router on the tokens
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS)
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS)
const addTransaction = useTransactionAdder()
async function onAdd() {
if (!chainId || !library || !account) return
const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) {
return
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0],
}
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate
let method: (...args: any) => Promise<TransactionResponse>
let args: Array<string | string[] | number>
let value: BigNumber | null
if (currencyA === ETHER || currencyB === ETHER) {
const tokenBIsBNB = currencyB === ETHER
estimate = router.estimateGas.addLiquidityETH
method = router.addLiquidityETH
args = [
wrappedCurrency(tokenBIsBNB ? currencyA : currencyB, chainId)?.address ?? '', // token
(tokenBIsBNB ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[tokenBIsBNB ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsBNB ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account,
deadlineFromNow,
]
value = BigNumber.from((tokenBIsBNB ? parsedAmountB : parsedAmountA).raw.toString())
} else {
estimate = router.estimateGas.addLiquidity
method = router.addLiquidity
args = [
wrappedCurrency(currencyA, chainId)?.address ?? '',
wrappedCurrency(currencyB, chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
value = null
}
setAttemptingTxn(true)
// const aa = await estimate(...args, value ? { value } : {})
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then((response) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Add ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencies[Field.CURRENCY_B]?.symbol}`,
})
setTxHash(response.hash)
})
)
.catch((e) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (e?.code !== 4001) {
console.error(e)
}
})
}
const modalHeader = () => {
return noLiquidity ? (
<AutoColumn gap="20px">
<LightCard mt="20px" borderRadius="20px">
<RowFlat>
<UIKitText fontSize="48px" mr="8px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol}`}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
</LightCard>
</AutoColumn>
) : (
<AutoColumn gap="20px">
<RowFlat style={{ marginTop: '20px' }}>
<UIKitText fontSize="48px" mr="8px">
{liquidityMinted?.toSignificant(6)}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
<Row>
<UIKitText fontSize="24px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol} Pool Tokens`}
</UIKitText>
</Row>
<UIKitText small textAlign="left" padding="8px 0 0 0 " style={{ fontStyle: 'italic' }}>
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</UIKitText>
</AutoColumn>
)
}
const modalBottom = () => {
return (
<ConfirmAddModalBottom
price={price}
currencies={currencies}
parsedAmounts={parsedAmounts}
noLiquidity={noLiquidity}
onAdd={onAdd}
poolTokenPercentage={poolTokenPercentage}
/>
)
}
const pendingText = `Supplying ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencies[Field.CURRENCY_B]?.symbol}`
const handleCurrencyASelect = useCallback(
(currA: Currency) => {
const newCurrencyIdA = currencyId(currA)
if (newCurrencyIdA === currencyIdB) {
history.push(`/add/${currencyIdB}/${currencyIdA}`)
} else {
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`)
}
},
[currencyIdB, history, currencyIdA]
)
const handleCurrencyBSelect = useCallback(
(currB: Currency) => {
const newCurrencyIdB = currencyId(currB)
if (currencyIdA === newCurrencyIdB) {
if (currencyIdB) {
history.push(`/add/${currencyIdB}/${newCurrencyIdB}`)
} else {
history.push(`/add/${newCurrencyIdB}`)
}
} else {
history.push(`/add/${currencyIdA || 'BNB'}/${newCurrencyIdB}`)
}
},
[currencyIdA, history, currencyIdB]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
// if there was a tx hash, we want to clear the input
if (txHash) {
onFieldAInput('')
}
setTxHash('')
}, [onFieldAInput, txHash])
return (
<>
<CardNav activeIndex={1} />
<AppBody>
<AddRemoveTabs adding />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash}
content={() => (
<ConfirmationModalContent
title={
noLiquidity
? TranslateString(1154, 'You are creating a pool')
: TranslateString(1156, 'You will receive')
}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<CardBody>
<AutoColumn gap="20px">
{noLiquidity && (
<ColumnCenter>
<Pane>
<AutoColumn gap="12px">
<UIKitText>{TranslateString(1158, 'You are the first liquidity provider.')}</UIKitText>
<UIKitText>
{TranslateString(1160, 'The ratio of tokens you add will set the price of this pool.')}
</UIKitText>
<UIKitText>
{TranslateString(1162, 'Once you are happy with the rate click supply to review.')}
</UIKitText>
</AutoColumn>
</Pane>
</ColumnCenter>
)}
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
onMax={() => {
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}}
onCurrencySelect={handleCurrencyASelect}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A]}
id="add-liquidity-input-tokena"
showCommonBases={false}
/>
<ColumnCenter>
<AddIcon color="textSubtle" />
</ColumnCenter>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onFieldBInput}
onCurrencySelect={handleCurrencyBSelect}
onMax={() => {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
currency={currencies[Field.CURRENCY_B]}
id="add-liquidity-input-tokenb"
showCommonBases={false}
/>
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<div>
<UIKitText
style={{ textTransform: 'uppercase', fontWeight: 600 }}
color="textSubtle"
fontSize="12px"
mb="2px"
>
{noLiquidity
? TranslateString(1164, 'Initial prices and pool share')
: TranslateString(1166, 'Prices and pool share')}
</UIKitText>
<Pane>
<PoolPriceBar
currencies={currencies}
poolTokenPercentage={poolTokenPercentage}
noLiquidity={noLiquidity}
price={price}
/>
</Pane>
</div>
)}
{!account ? (
<ConnectWalletButton width="100%" />
) : (
<AutoColumn gap="md">
{(approvalA === ApprovalState.NOT_APPROVED ||
approvalA === ApprovalState.PENDING ||
approvalB === ApprovalState.NOT_APPROVED ||
approvalB === ApprovalState.PENDING) &&
isValid && (
<RowBetween>
{approvalA !== ApprovalState.APPROVED && (
<Button
onClick={approveACallback}
disabled={approvalA === ApprovalState.PENDING}
style={{ width: approvalB !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalA === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_A]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_A]?.symbol}`
)}
</Button>
)}
{approvalB !== ApprovalState.APPROVED && (
<Button
onClick={approveBCallback}
disabled={approvalB === ApprovalState.PENDING}
style={{ width: approvalA !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalB === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_B]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_B]?.symbol}`
)}
</Button>
)}
</RowBetween>
)}
<Button
onClick={() => {
if (expertMode) {
onAdd()
} else {
setShowConfirm(true)
}
}}
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
width="100%"
>
{error ?? 'Supply'}
</Button>
</AutoColumn>
)}
</AutoColumn>
</CardBody>
</Wrapper>
</AppBody>
{pair && !noLiquidity && pairState !== PairState.INVALID ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWBNB} pair={pair} />
</AutoColumn>
) : null}
</>
)
}
Example #14
Source File: index.tsx From pancake-swap-interface-v1 with GNU General Public License v3.0 | 4 votes |
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB },
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React()
const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB)
const TranslateString = useI18n()
const oneCurrencyIsWBNB = Boolean(
chainId &&
((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
(currencyB && currencyEquals(currencyB, WETH[chainId])))
)
const expertMode = useIsExpertMode()
// mint state
const { independentField, typedValue, otherTypedValue } = useMintState()
const {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
// txn values
const [deadline] = useUserDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: noLiquidity ? otherTypedValue : parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
// get the max amounts user can add
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmountSpend(currencyBalances[field]),
}
},
{}
)
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0'),
}
},
{}
)
// check whether the user has approved the router on the tokens
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS)
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS)
const addTransaction = useTransactionAdder()
async function onAdd() {
if (!chainId || !library || !account) return
const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) {
return
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0],
}
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate
let method: (...args: any) => Promise<TransactionResponse>
let args: Array<string | string[] | number>
let value: BigNumber | null
if (currencyA === ETHER || currencyB === ETHER) {
const tokenBIsBNB = currencyB === ETHER
estimate = router.estimateGas.addLiquidityETH
method = router.addLiquidityETH
args = [
wrappedCurrency(tokenBIsBNB ? currencyA : currencyB, chainId)?.address ?? '', // token
(tokenBIsBNB ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[tokenBIsBNB ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsBNB ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account,
deadlineFromNow,
]
value = BigNumber.from((tokenBIsBNB ? parsedAmountB : parsedAmountA).raw.toString())
} else {
estimate = router.estimateGas.addLiquidity
method = router.addLiquidity
args = [
wrappedCurrency(currencyA, chainId)?.address ?? '',
wrappedCurrency(currencyB, chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
value = null
}
setAttemptingTxn(true)
// const aa = await estimate(...args, value ? { value } : {})
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then((response) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Add ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencies[Field.CURRENCY_B]?.symbol}`,
})
setTxHash(response.hash)
})
)
.catch((e) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (e?.code !== 4001) {
console.error(e)
}
})
}
const modalHeader = () => {
return noLiquidity ? (
<AutoColumn gap="20px">
<LightCard mt="20px" borderRadius="20px">
<RowFlat>
<UIKitText fontSize="48px" mr="8px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol}`}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
</LightCard>
</AutoColumn>
) : (
<AutoColumn gap="20px">
<RowFlat style={{ marginTop: '20px' }}>
<UIKitText fontSize="48px" mr="8px">
{liquidityMinted?.toSignificant(6)}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
<Row>
<UIKitText fontSize="24px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol} Pool Tokens`}
</UIKitText>
</Row>
<UIKitText small textAlign="left" padding="8px 0 0 0 " style={{ fontStyle: 'italic' }}>
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</UIKitText>
</AutoColumn>
)
}
const modalBottom = () => {
return (
<ConfirmAddModalBottom
price={price}
currencies={currencies}
parsedAmounts={parsedAmounts}
noLiquidity={noLiquidity}
onAdd={onAdd}
poolTokenPercentage={poolTokenPercentage}
/>
)
}
const pendingText = `Supplying ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencies[Field.CURRENCY_B]?.symbol}`
const handleCurrencyASelect = useCallback(
(currA: Currency) => {
const newCurrencyIdA = currencyId(currA)
if (newCurrencyIdA === currencyIdB) {
history.push(`/add/${currencyIdB}/${currencyIdA}`)
} else {
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`)
}
},
[currencyIdB, history, currencyIdA]
)
const handleCurrencyBSelect = useCallback(
(currB: Currency) => {
const newCurrencyIdB = currencyId(currB)
if (currencyIdA === newCurrencyIdB) {
if (currencyIdB) {
history.push(`/add/${currencyIdB}/${newCurrencyIdB}`)
} else {
history.push(`/add/${newCurrencyIdB}`)
}
} else {
history.push(`/add/${currencyIdA || 'BNB'}/${newCurrencyIdB}`)
}
},
[currencyIdA, history, currencyIdB]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
// if there was a tx hash, we want to clear the input
if (txHash) {
onFieldAInput('')
}
setTxHash('')
}, [onFieldAInput, txHash])
return (
<Container>
<CardNav activeIndex={1} />
<AppBody>
<AddRemoveTabs adding />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash}
content={() => (
<ConfirmationModalContent
title={
noLiquidity
? TranslateString(1154, 'You are creating a pool')
: TranslateString(1156, 'You will receive')
}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<CardBody>
<AutoColumn gap="20px">
{noLiquidity && (
<ColumnCenter>
<Pane>
<AutoColumn gap="12px">
<UIKitText>{TranslateString(1158, 'You are the first liquidity provider.')}</UIKitText>
<UIKitText>
{TranslateString(1160, 'The ratio of tokens you add will set the price of this pool.')}
</UIKitText>
<UIKitText>
{TranslateString(1162, 'Once you are happy with the rate click supply to review.')}
</UIKitText>
</AutoColumn>
</Pane>
</ColumnCenter>
)}
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
onMax={() => {
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}}
onCurrencySelect={handleCurrencyASelect}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A]}
id="add-liquidity-input-tokena"
showCommonBases={false}
/>
<ColumnCenter>
<AddIcon color="textSubtle" />
</ColumnCenter>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onFieldBInput}
onCurrencySelect={handleCurrencyBSelect}
onMax={() => {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
currency={currencies[Field.CURRENCY_B]}
id="add-liquidity-input-tokenb"
showCommonBases={false}
/>
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<div>
<UIKitText
style={{ textTransform: 'uppercase', fontWeight: 600 }}
color="textSubtle"
fontSize="12px"
mb="2px"
>
{noLiquidity
? TranslateString(1164, 'Initial prices and pool share')
: TranslateString(1166, 'Prices and pool share')}
</UIKitText>
<Pane>
<PoolPriceBar
currencies={currencies}
poolTokenPercentage={poolTokenPercentage}
noLiquidity={noLiquidity}
price={price}
/>
</Pane>
</div>
)}
{!account ? (
<ConnectWalletButton width="100%" />
) : (
<AutoColumn gap="md">
{(approvalA === ApprovalState.NOT_APPROVED ||
approvalA === ApprovalState.PENDING ||
approvalB === ApprovalState.NOT_APPROVED ||
approvalB === ApprovalState.PENDING) &&
isValid && (
<RowBetween>
{approvalA !== ApprovalState.APPROVED && (
<Button
onClick={approveACallback}
disabled={approvalA === ApprovalState.PENDING}
style={{ width: approvalB !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalA === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_A]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_A]?.symbol}`
)}
</Button>
)}
{approvalB !== ApprovalState.APPROVED && (
<Button
onClick={approveBCallback}
disabled={approvalB === ApprovalState.PENDING}
style={{ width: approvalA !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalB === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_B]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_B]?.symbol}`
)}
</Button>
)}
</RowBetween>
)}
<Button
onClick={() => {
if (expertMode) {
onAdd()
} else {
setShowConfirm(true)
}
}}
disabled
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
width="100%"
>
{error ?? "You can't add liquidity on V1"}
</Button>
</AutoColumn>
)}
</AutoColumn>
</CardBody>
</Wrapper>
</AppBody>
{pair && !noLiquidity && pairState !== PairState.INVALID ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWBNB} pair={pair} />
</AutoColumn>
) : null}
</Container>
)
}
Example #15
Source File: index.tsx From panther-frontend-dex with GNU General Public License v3.0 | 4 votes |
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB },
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React()
const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB)
const oneCurrencyIsWETH = Boolean(
chainId &&
((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
(currencyB && currencyEquals(currencyB, WETH[chainId])))
)
const expertMode = useIsExpertMode()
// mint state
const { independentField, typedValue, otherTypedValue } = useMintState()
const {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
// txn values
const [deadline] = useUserDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: noLiquidity ? otherTypedValue : parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
// get the max amounts user can add
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmountSpend(currencyBalances[field]),
}
},
{}
)
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0'),
}
},
{}
)
// check whether the user has approved the router on the tokens
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS)
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS)
const addTransaction = useTransactionAdder()
async function onAdd() {
if (!chainId || !library || !account) return
const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) {
return
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0],
}
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate
let method: (...args: any) => Promise<TransactionResponse>
let args: Array<string | string[] | number>
let value: BigNumber | null
if (currencyA === ETHER || currencyB === ETHER) {
const tokenBIsETH = currencyB === ETHER
estimate = router.estimateGas.addLiquidityETH
method = router.addLiquidityETH
args = [
wrappedCurrency(tokenBIsETH ? currencyA : currencyB, chainId)?.address ?? '', // token
(tokenBIsETH ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[tokenBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account,
deadlineFromNow,
]
value = BigNumber.from((tokenBIsETH ? parsedAmountB : parsedAmountA).raw.toString())
} else {
estimate = router.estimateGas.addLiquidity
method = router.addLiquidity
args = [
wrappedCurrency(currencyA, chainId)?.address ?? '',
wrappedCurrency(currencyB, chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow,
]
value = null
}
setAttemptingTxn(true)
// const aa = await estimate(...args, value ? { value } : {})
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then((response) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Add ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencies[Field.CURRENCY_B]?.symbol}`,
})
setTxHash(response.hash)
})
)
.catch((e) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (e?.code !== 4001) {
console.error(e)
}
})
}
const modalHeader = () => {
return noLiquidity ? (
<AutoColumn gap="20px">
<LightCard mt="20px" borderRadius="20px">
<RowFlat>
<UIKitText fontSize="48px" mr="8px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol}`}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
</LightCard>
</AutoColumn>
) : (
<AutoColumn gap="20px">
<RowFlat style={{ marginTop: '20px' }}>
<UIKitText fontSize="48px" mr="8px">
{liquidityMinted?.toSignificant(6)}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
<Row>
<UIKitText fontSize="24px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol} Pool Tokens`}
</UIKitText>
</Row>
<Italic fontSize={12} textAlign="left" padding="8px 0 0 0 ">
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</Italic>
</AutoColumn>
)
}
const modalBottom = () => {
return (
<ConfirmAddModalBottom
price={price}
currencies={currencies}
parsedAmounts={parsedAmounts}
noLiquidity={noLiquidity}
onAdd={onAdd}
poolTokenPercentage={poolTokenPercentage}
/>
)
}
const pendingText = `Supplying ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencies[Field.CURRENCY_B]?.symbol}`
const handleCurrencyASelect = useCallback(
(currA: Currency) => {
const newCurrencyIdA = currencyId(currA)
if (newCurrencyIdA === currencyIdB) {
history.push(`/add/${currencyIdB}/${currencyIdA}`)
} else {
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`)
}
},
[currencyIdB, history, currencyIdA]
)
const handleCurrencyBSelect = useCallback(
(currB: Currency) => {
const newCurrencyIdB = currencyId(currB)
if (currencyIdA === newCurrencyIdB) {
if (currencyIdB) {
history.push(`/add/${currencyIdB}/${newCurrencyIdB}`)
} else {
history.push(`/add/${newCurrencyIdB}`)
}
} else {
history.push(`/add/${currencyIdA || 'ETH'}/${newCurrencyIdB}`)
}
},
[currencyIdA, history, currencyIdB]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
// if there was a tx hash, we want to clear the input
if (txHash) {
onFieldAInput('')
}
setTxHash('')
}, [onFieldAInput, txHash])
return (
<>
<CardNav activeIndex={1} />
<AppBody>
<AddRemoveTabs adding />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash}
content={() => (
<ConfirmationModalContent
title={noLiquidity ? 'You are creating a pool' : 'You will receive'}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<CardBody>
<AutoColumn gap="20px">
{noLiquidity && (
<ColumnCenter>
<Pane>
<AutoColumn gap="12px">
<UIKitText>You are the first liquidity provider.</UIKitText>
<UIKitText>The ratio of tokens you add will set the price of this pool.</UIKitText>
<UIKitText>Once you are happy with the rate click supply to review.</UIKitText>
</AutoColumn>
</Pane>
</ColumnCenter>
)}
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
onMax={() => {
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}}
onCurrencySelect={handleCurrencyASelect}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A]}
id="add-liquidity-input-tokena"
showCommonBases={false}
/>
<ColumnCenter>
<AddIcon color="textSubtle" />
</ColumnCenter>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onFieldBInput}
onCurrencySelect={handleCurrencyBSelect}
onMax={() => {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
currency={currencies[Field.CURRENCY_B]}
id="add-liquidity-input-tokenb"
showCommonBases={false}
/>
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<div>
<UIKitText
style={{ textTransform: 'uppercase', fontWeight: 600 }}
color="textSubtle"
fontSize="12px"
mb="2px"
>
{noLiquidity ? 'Initial prices and pool share' : 'Prices and pool share'}
</UIKitText>
<Pane>
<PoolPriceBar
currencies={currencies}
poolTokenPercentage={poolTokenPercentage}
noLiquidity={noLiquidity}
price={price}
/>
</Pane>
</div>
)}
{!account ? (
<ConnectWalletButton fullWidth />
) : (
<AutoColumn gap="md">
{(approvalA === ApprovalState.NOT_APPROVED ||
approvalA === ApprovalState.PENDING ||
approvalB === ApprovalState.NOT_APPROVED ||
approvalB === ApprovalState.PENDING) &&
isValid && (
<RowBetween>
{approvalA !== ApprovalState.APPROVED && (
<Button
onClick={approveACallback}
disabled={approvalA === ApprovalState.PENDING}
style={{ width: approvalB !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalA === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_A]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_A]?.symbol}`
)}
</Button>
)}
{approvalB !== ApprovalState.APPROVED && (
<Button
onClick={approveBCallback}
disabled={approvalB === ApprovalState.PENDING}
style={{ width: approvalA !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalB === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_B]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_B]?.symbol}`
)}
</Button>
)}
</RowBetween>
)}
<Button
onClick={() => {
if (expertMode) {
onAdd()
} else {
setShowConfirm(true)
}
}}
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
fullWidth
>
{error ?? 'Supply'}
</Button>
</AutoColumn>
)}
</AutoColumn>
</CardBody>
</Wrapper>
</AppBody>
{pair && !noLiquidity && pairState !== PairState.INVALID ? (
<AutoColumn style={{ maxWidth: '436px', width: '100%', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
</AutoColumn>
) : null}
</>
)
}
Example #16
Source File: index.tsx From pancakeswap-testnet with GNU General Public License v3.0 | 4 votes |
export default function AddLiquidity({
match: {
params: { currencyIdA, currencyIdB },
},
history,
}: RouteComponentProps<{ currencyIdA?: string; currencyIdB?: string }>) {
const { account, chainId, library } = useActiveWeb3React()
const currencyA = useCurrency(currencyIdA)
const currencyB = useCurrency(currencyIdB)
const TranslateString = useI18n()
const oneCurrencyIsWBNB = Boolean(
chainId &&
((currencyA && currencyEquals(currencyA, WETH[chainId])) ||
(currencyB && currencyEquals(currencyB, WETH[chainId])))
)
const expertMode = useIsExpertMode()
// mint state
const { independentField, typedValue, otherTypedValue } = useMintState()
const {
dependentField,
currencies,
pair,
pairState,
currencyBalances,
parsedAmounts,
price,
noLiquidity,
liquidityMinted,
poolTokenPercentage,
error,
} = useDerivedMintInfo(currencyA ?? undefined, currencyB ?? undefined)
const { onFieldAInput, onFieldBInput } = useMintActionHandlers(noLiquidity)
const isValid = !error
// modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false)
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
// txn values
const [deadline] = useUserDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
// get formatted amounts
const formattedAmounts = {
[independentField]: typedValue,
[dependentField]: noLiquidity ? otherTypedValue : parsedAmounts[dependentField]?.toSignificant(6) ?? '',
}
// get the max amounts user can add
const maxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmountSpend(currencyBalances[field]),
}
},
{}
)
const atMaxAmounts: { [field in Field]?: TokenAmount } = [Field.CURRENCY_A, Field.CURRENCY_B].reduce(
(accumulator, field) => {
return {
...accumulator,
[field]: maxAmounts[field]?.equalTo(parsedAmounts[field] ?? '0'),
}
},
{}
)
// check whether the user has approved the router on the tokens
const [approvalA, approveACallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_A], ROUTER_ADDRESS)
const [approvalB, approveBCallback] = useApproveCallback(parsedAmounts[Field.CURRENCY_B], ROUTER_ADDRESS)
const addTransaction = useTransactionAdder()
async function onAdd() {
if (!chainId || !library || !account) return
const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) {
return
}
const amountsMin = {
[Field.CURRENCY_A]: calculateSlippageAmount(parsedAmountA, noLiquidity ? 0 : allowedSlippage)[0],
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0],
}
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate
let method: (...args: any) => Promise<TransactionResponse>
let args: Array<string | string[] | number>
let value: BigNumber | null
if (currencyA === ETHER || currencyB === ETHER) {
const tokenBIsBNB = currencyB === ETHER
estimate = router.estimateGas.addLiquidityETH
method = router.addLiquidityETH
args = [
wrappedCurrency(tokenBIsBNB ? currencyA : currencyB, chainId)?.address ?? '', // token
(tokenBIsBNB ? parsedAmountA : parsedAmountB).raw.toString(), // token desired
amountsMin[tokenBIsBNB ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsBNB ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account,
deadlineFromNow,
]
value = BigNumber.from((tokenBIsBNB ? parsedAmountB : parsedAmountA).raw.toString())
} else {
estimate = router.estimateGas.addLiquidity
method = router.addLiquidity
args = [
wrappedCurrency(currencyA, chainId)?.address ?? '',
wrappedCurrency(currencyB, chainId)?.address ?? '',
parsedAmountA.raw.toString(),
parsedAmountB.raw.toString(),
"0",
"0",
account,
deadlineFromNow,
]
value = null
}
setAttemptingTxn(true)
// const aa = await estimate(...args, value ? { value } : {})
await estimate(...args, value ? { value } : {})
.then((estimatedGasLimit) =>
method(...args, {
...(value ? { value } : {}),
gasLimit: calculateGasMargin(estimatedGasLimit),
}).then((response) => {
setAttemptingTxn(false)
addTransaction(response, {
summary: `Add ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(3)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(3)} ${currencies[Field.CURRENCY_B]?.symbol}`,
})
setTxHash(response.hash)
})
)
.catch((e) => {
setAttemptingTxn(false)
// we only care if the error is something _other_ than the user rejected the tx
if (e?.code !== 4001) {
console.error(e)
}
})
}
const modalHeader = () => {
return noLiquidity ? (
<AutoColumn gap="20px">
<LightCard mt="20px" borderRadius="20px">
<RowFlat>
<UIKitText fontSize="48px" mr="8px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol}`}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
</LightCard>
</AutoColumn>
) : (
<AutoColumn gap="20px">
<RowFlat style={{ marginTop: '20px' }}>
<UIKitText fontSize="48px" mr="8px">
{liquidityMinted?.toSignificant(6)}
</UIKitText>
<DoubleCurrencyLogo
currency0={currencies[Field.CURRENCY_A]}
currency1={currencies[Field.CURRENCY_B]}
size={30}
/>
</RowFlat>
<Row>
<UIKitText fontSize="24px">
{`${currencies[Field.CURRENCY_A]?.symbol}/${currencies[Field.CURRENCY_B]?.symbol} Pool Tokens`}
</UIKitText>
</Row>
<UIKitText small textAlign="left" padding="8px 0 0 0 " style={{ fontStyle: 'italic' }}>
{`Output is estimated. If the price changes by more than ${
allowedSlippage / 100
}% your transaction will revert.`}
</UIKitText>
</AutoColumn>
)
}
const modalBottom = () => {
return (
<ConfirmAddModalBottom
price={price}
currencies={currencies}
parsedAmounts={parsedAmounts}
noLiquidity={noLiquidity}
onAdd={onAdd}
poolTokenPercentage={poolTokenPercentage}
/>
)
}
const pendingText = `Supplying ${parsedAmounts[Field.CURRENCY_A]?.toSignificant(6)} ${
currencies[Field.CURRENCY_A]?.symbol
} and ${parsedAmounts[Field.CURRENCY_B]?.toSignificant(6)} ${currencies[Field.CURRENCY_B]?.symbol}`
const handleCurrencyASelect = useCallback(
(currA: Currency) => {
const newCurrencyIdA = currencyId(currA)
if (newCurrencyIdA === currencyIdB) {
history.push(`/add/${currencyIdB}/${currencyIdA}`)
} else {
history.push(`/add/${newCurrencyIdA}/${currencyIdB}`)
}
},
[currencyIdB, history, currencyIdA]
)
const handleCurrencyBSelect = useCallback(
(currB: Currency) => {
const newCurrencyIdB = currencyId(currB)
if (currencyIdA === newCurrencyIdB) {
if (currencyIdB) {
history.push(`/add/${currencyIdB}/${newCurrencyIdB}`)
} else {
history.push(`/add/${newCurrencyIdB}`)
}
} else {
history.push(`/add/${currencyIdA || 'BNB'}/${newCurrencyIdB}`)
}
},
[currencyIdA, history, currencyIdB]
)
const handleDismissConfirmation = useCallback(() => {
setShowConfirm(false)
// if there was a tx hash, we want to clear the input
if (txHash) {
onFieldAInput('')
}
setTxHash('')
}, [onFieldAInput, txHash])
return (
<>
<CardNav activeIndex={1} />
<AppBody>
<AddRemoveTabs adding />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn}
hash={txHash}
content={() => (
<ConfirmationModalContent
title={
noLiquidity
? TranslateString(1154, 'You are creating a pool')
: TranslateString(1156, 'You will receive')
}
onDismiss={handleDismissConfirmation}
topContent={modalHeader}
bottomContent={modalBottom}
/>
)}
pendingText={pendingText}
/>
<CardBody>
<AutoColumn gap="20px">
{noLiquidity && (
<ColumnCenter>
<Pane>
<AutoColumn gap="12px">
<UIKitText>{TranslateString(1158, 'You are the first liquidity provider.')}</UIKitText>
<UIKitText>
{TranslateString(1160, 'The ratio of tokens you add will set the price of this pool.')}
</UIKitText>
<UIKitText>
{TranslateString(1162, 'Once you are happy with the rate click supply to review.')}
</UIKitText>
</AutoColumn>
</Pane>
</ColumnCenter>
)}
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
onMax={() => {
onFieldAInput(maxAmounts[Field.CURRENCY_A]?.toExact() ?? '')
}}
onCurrencySelect={handleCurrencyASelect}
showMaxButton={!atMaxAmounts[Field.CURRENCY_A]}
currency={currencies[Field.CURRENCY_A]}
id="add-liquidity-input-tokena"
showCommonBases={false}
/>
<ColumnCenter>
<AddIcon color="textSubtle" />
</ColumnCenter>
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_B]}
onUserInput={onFieldBInput}
onCurrencySelect={handleCurrencyBSelect}
onMax={() => {
onFieldBInput(maxAmounts[Field.CURRENCY_B]?.toExact() ?? '')
}}
showMaxButton={!atMaxAmounts[Field.CURRENCY_B]}
currency={currencies[Field.CURRENCY_B]}
id="add-liquidity-input-tokenb"
showCommonBases={false}
/>
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<div>
<UIKitText
style={{ textTransform: 'uppercase', fontWeight: 600 }}
color="textSubtle"
fontSize="12px"
mb="2px"
>
{noLiquidity
? TranslateString(1164, 'Initial prices and pool share')
: TranslateString(1166, 'Prices and pool share')}
</UIKitText>
<Pane>
<PoolPriceBar
currencies={currencies}
poolTokenPercentage={poolTokenPercentage}
noLiquidity={noLiquidity}
price={price}
/>
</Pane>
</div>
)}
{!account ? (
<ConnectWalletButton width="100%" />
) : (
<AutoColumn gap="md">
{(approvalA === ApprovalState.NOT_APPROVED ||
approvalA === ApprovalState.PENDING ||
approvalB === ApprovalState.NOT_APPROVED ||
approvalB === ApprovalState.PENDING) &&
isValid && (
<RowBetween>
{approvalA !== ApprovalState.APPROVED && (
<Button
onClick={approveACallback}
disabled={approvalA === ApprovalState.PENDING}
style={{ width: approvalB !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalA === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_A]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_A]?.symbol}`
)}
</Button>
)}
{approvalB !== ApprovalState.APPROVED && (
<Button
onClick={approveBCallback}
disabled={approvalB === ApprovalState.PENDING}
style={{ width: approvalA !== ApprovalState.APPROVED ? '48%' : '100%' }}
>
{approvalB === ApprovalState.PENDING ? (
<Dots>Approving {currencies[Field.CURRENCY_B]?.symbol}</Dots>
) : (
`Approve ${currencies[Field.CURRENCY_B]?.symbol}`
)}
</Button>
)}
</RowBetween>
)}
<Button
onClick={() => {
if (expertMode) {
onAdd()
} else {
setShowConfirm(true)
}
}}
disabled={!isValid || approvalA !== ApprovalState.APPROVED || approvalB !== ApprovalState.APPROVED}
variant={
!isValid && !!parsedAmounts[Field.CURRENCY_A] && !!parsedAmounts[Field.CURRENCY_B]
? 'danger'
: 'primary'
}
width="100%"
>
{error ?? 'Supply'}
</Button>
</AutoColumn>
)}
</AutoColumn>
</CardBody>
</Wrapper>
</AppBody>
{pair && !noLiquidity && pairState !== PairState.INVALID ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWBNB} pair={pair} />
</AutoColumn>
) : null}
</>
)
}