2022-10-19 18:17:29 -07:00
|
|
|
import React, { useState } from 'react';
|
2020-08-09 04:19:15 -07:00
|
|
|
import DialogTitle from '@material-ui/core/DialogTitle';
|
|
|
|
import DialogContent from '@material-ui/core/DialogContent';
|
|
|
|
import DialogForm from './DialogForm';
|
|
|
|
import { abbreviateAddress } from '../utils/utils';
|
|
|
|
import CopyableDisplay from './CopyableDisplay';
|
2021-02-19 21:32:24 -08:00
|
|
|
import { useSolanaExplorerUrlSuffix } from '../utils/connection';
|
2020-09-10 18:30:54 -07:00
|
|
|
import Typography from '@material-ui/core/Typography';
|
2020-09-10 00:25:53 -07:00
|
|
|
import DialogActions from '@material-ui/core/DialogActions';
|
|
|
|
import Button from '@material-ui/core/Button';
|
2020-09-10 18:30:54 -07:00
|
|
|
import { useAsyncData } from '../utils/fetch-loop';
|
|
|
|
import tuple from 'immutable-tuple';
|
|
|
|
import { useCallAsync } from '../utils/notifications';
|
|
|
|
import {
|
2020-09-11 03:38:43 -07:00
|
|
|
ConnectToMetamaskButton,
|
2020-09-10 18:30:54 -07:00
|
|
|
getErc20Balance,
|
|
|
|
swapErc20ToSpl,
|
|
|
|
useEthAccount,
|
2021-02-28 22:01:20 -08:00
|
|
|
estimateErc20SwapFees,
|
2020-09-10 18:30:54 -07:00
|
|
|
} from '../utils/swap/eth';
|
|
|
|
import InputAdornment from '@material-ui/core/InputAdornment';
|
|
|
|
import TextField from '@material-ui/core/TextField';
|
|
|
|
import Stepper from '@material-ui/core/Stepper';
|
|
|
|
import Step from '@material-ui/core/Step';
|
|
|
|
import StepLabel from '@material-ui/core/StepLabel';
|
|
|
|
import CircularProgress from '@material-ui/core/CircularProgress';
|
|
|
|
import Link from '@material-ui/core/Link';
|
2020-09-11 04:57:57 -07:00
|
|
|
import Tabs from '@material-ui/core/Tabs';
|
|
|
|
import Tab from '@material-ui/core/Tab';
|
2021-02-28 22:01:20 -08:00
|
|
|
import { DialogContentText, Tooltip } from '@material-ui/core';
|
|
|
|
import { EthFeeEstimate } from './EthFeeEstimate';
|
2020-08-09 04:19:15 -07:00
|
|
|
|
2022-02-14 09:38:06 -08:00
|
|
|
const DISABLED_MINTS = new Set([
|
|
|
|
'ABE7D8RU1eHfCJWzHYZZeymeE8k9nPPXfqge2NQYyKoL',
|
|
|
|
]);
|
2021-05-31 21:38:25 -07:00
|
|
|
|
2020-08-09 04:19:15 -07:00
|
|
|
export default function DepositDialog({
|
|
|
|
open,
|
|
|
|
onClose,
|
|
|
|
publicKey,
|
|
|
|
balanceInfo,
|
2021-02-19 21:32:24 -08:00
|
|
|
swapInfo,
|
2021-04-07 14:24:01 -07:00
|
|
|
isAssociatedToken,
|
2020-08-09 04:19:15 -07:00
|
|
|
}) {
|
2021-02-19 20:50:36 -08:00
|
|
|
const ethAccount = useEthAccount();
|
2020-09-10 00:25:53 -07:00
|
|
|
const urlSuffix = useSolanaExplorerUrlSuffix();
|
2020-09-11 22:24:16 -07:00
|
|
|
const { mint, tokenName, tokenSymbol, owner } = balanceInfo;
|
2020-09-11 04:57:57 -07:00
|
|
|
const [tab, setTab] = useState(0);
|
2020-09-11 22:24:16 -07:00
|
|
|
|
2021-09-15 11:21:12 -07:00
|
|
|
// SwapInfos to ignore.
|
2022-02-14 09:38:06 -08:00
|
|
|
if (
|
|
|
|
swapInfo &&
|
|
|
|
swapInfo.coin &&
|
|
|
|
swapInfo.coin.erc20Contract === '0x2b2e04bf86978b45bb2edf54aca876973bdd43c0'
|
|
|
|
) {
|
2021-09-15 11:21:12 -07:00
|
|
|
swapInfo = null;
|
|
|
|
}
|
|
|
|
|
2020-09-11 22:24:16 -07:00
|
|
|
let tabs = null;
|
|
|
|
if (swapInfo) {
|
|
|
|
let firstTab = `SPL ${tokenSymbol ?? swapInfo.coin.ticker}`;
|
|
|
|
let secondTab = swapInfo.coin.ticker;
|
|
|
|
if (!mint) {
|
|
|
|
firstTab = 'SOL';
|
|
|
|
} else {
|
2022-10-23 09:43:00 -07:00
|
|
|
if (localStorage.getItem('sollet-private') || swapInfo.blockchain !== 'eth') {
|
2022-02-14 09:38:06 -08:00
|
|
|
secondTab = `${
|
|
|
|
swapInfo.coin.erc20Contract ? 'ERC20' : 'Native'
|
|
|
|
} ${secondTab}`;
|
|
|
|
} else {
|
|
|
|
secondTab = null;
|
|
|
|
}
|
2020-09-11 04:57:57 -07:00
|
|
|
}
|
2020-09-11 22:24:16 -07:00
|
|
|
tabs = (
|
|
|
|
<Tabs
|
|
|
|
value={tab}
|
|
|
|
variant="fullWidth"
|
|
|
|
onChange={(e, value) => setTab(value)}
|
|
|
|
textColor="primary"
|
|
|
|
indicatorColor="primary"
|
|
|
|
>
|
|
|
|
<Tab label={firstTab} />
|
2021-05-31 23:38:06 -07:00
|
|
|
{(!DISABLED_MINTS.has(mint && mint.toString()) ||
|
2022-02-14 09:38:06 -08:00
|
|
|
localStorage.getItem('sollet-private')) &&
|
|
|
|
secondTab && <Tab label={secondTab} />}
|
2020-09-11 22:24:16 -07:00
|
|
|
</Tabs>
|
|
|
|
);
|
|
|
|
}
|
2021-04-07 14:24:01 -07:00
|
|
|
const displaySolAddress = publicKey.equals(owner) || isAssociatedToken;
|
|
|
|
const depositAddressStr = displaySolAddress
|
|
|
|
? owner.toBase58()
|
|
|
|
: publicKey.toBase58();
|
2020-08-09 04:19:15 -07:00
|
|
|
return (
|
2021-04-07 14:24:01 -07:00
|
|
|
<DialogForm open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
2020-08-09 04:19:15 -07:00
|
|
|
<DialogTitle>
|
2020-09-10 00:25:53 -07:00
|
|
|
Deposit {tokenName ?? mint.toBase58()}
|
2020-08-09 04:19:15 -07:00
|
|
|
{tokenSymbol ? ` (${tokenSymbol})` : null}
|
2021-02-19 20:50:36 -08:00
|
|
|
{ethAccount && (
|
|
|
|
<div>
|
|
|
|
<Typography color="textSecondary" style={{ fontSize: '14px' }}>
|
|
|
|
Metamask connected: {ethAccount}
|
|
|
|
</Typography>
|
|
|
|
</div>
|
|
|
|
)}
|
2020-08-09 04:19:15 -07:00
|
|
|
</DialogTitle>
|
2022-10-23 09:43:00 -07:00
|
|
|
{tabs}
|
2020-09-11 04:57:57 -07:00
|
|
|
<DialogContent style={{ paddingTop: 16 }}>
|
|
|
|
{tab === 0 ? (
|
|
|
|
<>
|
2021-04-07 14:24:01 -07:00
|
|
|
{!displaySolAddress && isAssociatedToken === false ? (
|
2020-09-11 04:57:57 -07:00
|
|
|
<DialogContentText>
|
|
|
|
This address can only be used to receive{' '}
|
|
|
|
{tokenSymbol ?? abbreviateAddress(mint)}. Do not send SOL to
|
|
|
|
this address.
|
2021-07-07 22:37:25 -07:00
|
|
|
<br />
|
2022-02-14 09:38:06 -08:00
|
|
|
<b style={{ color: 'red' }}>WARNING</b>: You are using a
|
|
|
|
deprecated account type. Please migrate your tokens. Ideally,
|
|
|
|
create a new wallet. If you send to this address from a poorly
|
|
|
|
implemented wallet, you may burn tokens.
|
2020-09-11 04:57:57 -07:00
|
|
|
</DialogContentText>
|
2021-04-07 14:24:01 -07:00
|
|
|
) : (
|
|
|
|
<DialogContentText>
|
|
|
|
This address can be used to receive{' '}
|
|
|
|
{tokenSymbol ?? abbreviateAddress(mint)}.
|
|
|
|
</DialogContentText>
|
2020-09-11 04:57:57 -07:00
|
|
|
)}
|
|
|
|
<CopyableDisplay
|
2021-04-07 14:24:01 -07:00
|
|
|
value={depositAddressStr}
|
2020-09-11 04:57:57 -07:00
|
|
|
label={'Deposit Address'}
|
|
|
|
autoFocus
|
|
|
|
qrCode
|
|
|
|
/>
|
|
|
|
<DialogContentText variant="body2">
|
|
|
|
<Link
|
|
|
|
href={
|
2022-02-14 09:38:06 -08:00
|
|
|
`https://solscan.io/account/${depositAddressStr}` + urlSuffix
|
2020-09-11 04:57:57 -07:00
|
|
|
}
|
|
|
|
target="_blank"
|
|
|
|
rel="noopener"
|
|
|
|
>
|
2021-06-09 08:39:57 -07:00
|
|
|
View on Solscan
|
2020-09-11 04:57:57 -07:00
|
|
|
</Link>
|
|
|
|
</DialogContentText>
|
|
|
|
</>
|
2020-08-09 04:19:15 -07:00
|
|
|
) : (
|
2020-09-10 18:30:54 -07:00
|
|
|
<SolletSwapDepositAddress
|
|
|
|
balanceInfo={balanceInfo}
|
2020-09-11 04:57:57 -07:00
|
|
|
swapInfo={swapInfo}
|
2021-02-28 22:01:20 -08:00
|
|
|
ethAccount={ethAccount}
|
2020-09-10 18:30:54 -07:00
|
|
|
/>
|
2020-09-11 04:57:57 -07:00
|
|
|
)}
|
2020-08-09 04:19:15 -07:00
|
|
|
</DialogContent>
|
2020-09-10 00:25:53 -07:00
|
|
|
<DialogActions>
|
|
|
|
<Button onClick={onClose}>Close</Button>
|
|
|
|
</DialogActions>
|
2020-08-09 04:19:15 -07:00
|
|
|
</DialogForm>
|
|
|
|
);
|
|
|
|
}
|
2020-09-10 18:30:54 -07:00
|
|
|
|
2021-02-28 22:01:20 -08:00
|
|
|
function SolletSwapDepositAddress({ balanceInfo, swapInfo, ethAccount }) {
|
|
|
|
const [ethBalance] = useAsyncData(
|
|
|
|
() => getErc20Balance(ethAccount),
|
|
|
|
'ethBalance',
|
|
|
|
{
|
|
|
|
refreshInterval: 2000,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
const ethFeeData = useAsyncData(
|
|
|
|
swapInfo.coin &&
|
|
|
|
(() =>
|
|
|
|
estimateErc20SwapFees({
|
|
|
|
erc20Address: swapInfo.coin.erc20Contract,
|
|
|
|
swapAddress: swapInfo.address,
|
|
|
|
ethAccount,
|
|
|
|
})),
|
|
|
|
'depositEthFee',
|
|
|
|
{
|
|
|
|
refreshInterval: 2000,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2020-09-10 18:30:54 -07:00
|
|
|
if (!swapInfo) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-02-28 22:01:20 -08:00
|
|
|
const ethFeeEstimate = Array.isArray(ethFeeData[0])
|
|
|
|
? ethFeeData[0].reduce((acc, elem) => acc + elem)
|
|
|
|
: ethFeeData[0];
|
|
|
|
const insufficientEthBalance =
|
|
|
|
typeof ethBalance === 'number' &&
|
|
|
|
typeof ethFeeEstimate === 'number' &&
|
|
|
|
ethBalance < ethFeeEstimate;
|
|
|
|
|
2020-09-10 18:30:54 -07:00
|
|
|
const { blockchain, address, memo, coin } = swapInfo;
|
2020-09-11 22:24:16 -07:00
|
|
|
const { mint, tokenName } = balanceInfo;
|
2020-09-10 18:30:54 -07:00
|
|
|
|
|
|
|
if (blockchain === 'btc' && memo === null) {
|
|
|
|
return (
|
|
|
|
<>
|
2020-09-11 04:57:57 -07:00
|
|
|
<DialogContentText>
|
2020-09-10 18:30:54 -07:00
|
|
|
Native BTC can be converted to SPL {tokenName} by sending it to the
|
|
|
|
following address:
|
2020-09-11 04:57:57 -07:00
|
|
|
</DialogContentText>
|
2020-09-10 18:30:54 -07:00
|
|
|
<CopyableDisplay
|
|
|
|
value={address}
|
|
|
|
label="Native BTC Deposit Address"
|
2020-09-11 05:25:19 -07:00
|
|
|
autoFocus
|
2020-09-10 18:30:54 -07:00
|
|
|
qrCode={`bitcoin:${address}`}
|
|
|
|
/>
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-09-29 20:05:05 -07:00
|
|
|
if (localStorage.getItem('sollet-private') && blockchain === 'eth') {
|
2020-09-10 18:30:54 -07:00
|
|
|
return (
|
|
|
|
<>
|
2020-09-11 04:57:57 -07:00
|
|
|
<DialogContentText>
|
2020-09-10 18:30:54 -07:00
|
|
|
{coin.erc20Contract ? 'ERC20' : 'Native'} {coin.ticker} can be
|
2021-02-19 21:32:24 -08:00
|
|
|
converted to {mint ? 'SPL' : 'native'} {tokenName} via MetaMask. To
|
|
|
|
convert, you must already have SOL in your wallet.
|
2020-09-11 04:57:57 -07:00
|
|
|
</DialogContentText>
|
2021-02-28 22:01:20 -08:00
|
|
|
<DialogContentText>
|
|
|
|
Estimated withdrawal transaction fee:
|
|
|
|
<EthFeeEstimate
|
|
|
|
ethFeeData={ethFeeData}
|
|
|
|
insufficientEthBalance={insufficientEthBalance}
|
|
|
|
/>
|
|
|
|
</DialogContentText>
|
|
|
|
<MetamaskDeposit
|
|
|
|
swapInfo={swapInfo}
|
|
|
|
insufficientEthBalance={insufficientEthBalance}
|
|
|
|
/>
|
2020-09-10 18:30:54 -07:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2021-02-28 22:01:20 -08:00
|
|
|
function MetamaskDeposit({ swapInfo, insufficientEthBalance }) {
|
2020-09-10 18:30:54 -07:00
|
|
|
const ethAccount = useEthAccount();
|
|
|
|
const [amount, setAmount] = useState('');
|
|
|
|
const [submitted, setSubmitted] = useState(false);
|
|
|
|
const [status, setStatus] = useState(null);
|
|
|
|
const callAsync = useCallAsync();
|
|
|
|
|
|
|
|
const {
|
|
|
|
address: swapAddress,
|
|
|
|
memo: destination,
|
|
|
|
coin: { erc20Contract: erc20Address, ticker },
|
|
|
|
} = swapInfo;
|
|
|
|
|
2020-09-11 22:24:16 -07:00
|
|
|
const [maxAmount, maxAmountLoaded] = useAsyncData(async () => {
|
2020-09-10 18:30:54 -07:00
|
|
|
if (ethAccount) {
|
2020-09-11 22:24:16 -07:00
|
|
|
return Math.min(
|
|
|
|
await getErc20Balance(ethAccount, erc20Address),
|
|
|
|
swapInfo.maxSize ?? Infinity,
|
|
|
|
);
|
2020-09-10 18:30:54 -07:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}, tuple(getErc20Balance, ethAccount, erc20Address));
|
|
|
|
|
|
|
|
if (!ethAccount) {
|
2020-09-11 03:38:43 -07:00
|
|
|
return <ConnectToMetamaskButton />;
|
2020-09-10 18:30:54 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
async function submit() {
|
|
|
|
setSubmitted(true);
|
|
|
|
setStatus({ step: 0 });
|
|
|
|
await callAsync(
|
|
|
|
(async () => {
|
|
|
|
let parsedAmount = parseFloat(amount);
|
2020-09-11 03:38:43 -07:00
|
|
|
|
2020-09-16 05:22:09 -07:00
|
|
|
if (!parsedAmount || parsedAmount > maxAmount || parsedAmount <= 0) {
|
2020-09-10 18:30:54 -07:00
|
|
|
throw new Error('Invalid amount');
|
|
|
|
}
|
|
|
|
await swapErc20ToSpl({
|
|
|
|
ethAccount,
|
|
|
|
erc20Address,
|
|
|
|
swapAddress,
|
|
|
|
destination,
|
2020-09-16 05:22:09 -07:00
|
|
|
amount,
|
2020-09-10 18:30:54 -07:00
|
|
|
onStatusChange: (e) => setStatus((status) => ({ ...status, ...e })),
|
|
|
|
});
|
|
|
|
})(),
|
|
|
|
{ onError: () => setSubmitted(false) },
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!submitted) {
|
2021-02-28 22:01:20 -08:00
|
|
|
let convertButton = (
|
|
|
|
<Button
|
|
|
|
color="primary"
|
|
|
|
style={{ marginLeft: 8 }}
|
|
|
|
onClick={submit}
|
|
|
|
disabled={insufficientEthBalance}
|
|
|
|
>
|
|
|
|
Convert
|
|
|
|
</Button>
|
|
|
|
);
|
|
|
|
|
|
|
|
if (insufficientEthBalance) {
|
|
|
|
convertButton = (
|
|
|
|
<Tooltip
|
|
|
|
title="Insufficient ETH for withdrawal transaction fee"
|
|
|
|
placement="top"
|
|
|
|
>
|
|
|
|
<span>{convertButton}</span>
|
|
|
|
</Tooltip>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-09-10 18:30:54 -07:00
|
|
|
return (
|
|
|
|
<div style={{ display: 'flex', alignItems: 'baseline' }}>
|
|
|
|
<TextField
|
|
|
|
label="Amount"
|
|
|
|
fullWidth
|
|
|
|
variant="outlined"
|
|
|
|
margin="normal"
|
|
|
|
type="number"
|
|
|
|
InputProps={{
|
|
|
|
endAdornment: (
|
|
|
|
<InputAdornment position="end">{ticker}</InputAdornment>
|
|
|
|
),
|
|
|
|
inputProps: {
|
|
|
|
step: 'any',
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
value={amount}
|
|
|
|
onChange={(e) => setAmount(e.target.value.trim())}
|
2020-09-11 03:38:43 -07:00
|
|
|
helperText={
|
|
|
|
maxAmountLoaded ? (
|
|
|
|
<span onClick={() => setAmount(maxAmount.toFixed(6))}>
|
|
|
|
Max: {maxAmount.toFixed(6)}
|
|
|
|
</span>
|
|
|
|
) : null
|
|
|
|
}
|
2020-09-10 18:30:54 -07:00
|
|
|
/>
|
2021-02-28 22:01:20 -08:00
|
|
|
{convertButton}
|
2020-09-10 18:30:54 -07:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<Stepper activeStep={status.step}>
|
|
|
|
<Step>
|
|
|
|
<StepLabel>Approve Conversion</StepLabel>
|
|
|
|
</Step>
|
|
|
|
<Step>
|
|
|
|
<StepLabel>Send Funds</StepLabel>
|
|
|
|
</Step>
|
|
|
|
<Step>
|
|
|
|
<StepLabel>Wait for Confirmations</StepLabel>
|
|
|
|
</Step>
|
|
|
|
</Stepper>
|
|
|
|
{status.step === 2 ? (
|
|
|
|
<>
|
|
|
|
<div
|
|
|
|
style={{
|
|
|
|
display: 'flex',
|
|
|
|
justifyContent: 'center',
|
|
|
|
alignItems: 'center',
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<div style={{ marginRight: 16 }}>
|
|
|
|
<CircularProgress />
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
{status.confirms ? (
|
|
|
|
<Typography>{status.confirms} / 12 Confirmations</Typography>
|
|
|
|
) : (
|
|
|
|
<Typography>Transaction Pending</Typography>
|
|
|
|
)}
|
|
|
|
<Typography variant="body2">
|
|
|
|
<Link
|
|
|
|
href={`https://etherscan.io/tx/${status.txid}`}
|
|
|
|
target="_blank"
|
|
|
|
rel="noopener"
|
|
|
|
>
|
|
|
|
View on Etherscan
|
|
|
|
</Link>
|
|
|
|
</Typography>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
) : null}
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|