import React, { useState } from 'react'; 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'; import { useSolanaExplorerUrlSuffix } from '../utils/connection'; import Typography from '@material-ui/core/Typography'; import DialogActions from '@material-ui/core/DialogActions'; import Button from '@material-ui/core/Button'; import { useAsyncData } from '../utils/fetch-loop'; import tuple from 'immutable-tuple'; import { useCallAsync } from '../utils/notifications'; import { ConnectToMetamaskButton, getErc20Balance, swapErc20ToSpl, useEthAccount, estimateErc20SwapFees, } 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'; import Tabs from '@material-ui/core/Tabs'; import Tab from '@material-ui/core/Tab'; import { DialogContentText, Tooltip } from '@material-ui/core'; import { EthFeeEstimate } from './EthFeeEstimate'; const DISABLED_MINTS = new Set(['ABE7D8RU1eHfCJWzHYZZeymeE8k9nPPXfqge2NQYyKoL']); export default function DepositDialog({ open, onClose, publicKey, balanceInfo, swapInfo, isAssociatedToken, }) { const ethAccount = useEthAccount(); const urlSuffix = useSolanaExplorerUrlSuffix(); const { mint, tokenName, tokenSymbol, owner } = balanceInfo; const [tab, setTab] = useState(0); // SwapInfos to ignore. if (swapInfo && swapInfo.coin && swapInfo.coin.erc20Contract === '0x2b2e04bf86978b45bb2edf54aca876973bdd43c0') { swapInfo = null; } let tabs = null; if (swapInfo) { let firstTab = `SPL ${tokenSymbol ?? swapInfo.coin.ticker}`; let secondTab = swapInfo.coin.ticker; if (!mint) { firstTab = 'SOL'; } else { secondTab = `${ swapInfo.coin.erc20Contract ? 'ERC20' : 'Native' } ${secondTab}`; } tabs = ( setTab(value)} textColor="primary" indicatorColor="primary" > {(!DISABLED_MINTS.has(mint && mint.toString()) || localStorage.getItem('sollet-private')) && } ); } const displaySolAddress = publicKey.equals(owner) || isAssociatedToken; const depositAddressStr = displaySolAddress ? owner.toBase58() : publicKey.toBase58(); return ( Deposit {tokenName ?? mint.toBase58()} {tokenSymbol ? ` (${tokenSymbol})` : null} {ethAccount && (
Metamask connected: {ethAccount}
)}
{tabs} {tab === 0 ? ( <> {!displaySolAddress && isAssociatedToken === false ? ( This address can only be used to receive{' '} {tokenSymbol ?? abbreviateAddress(mint)}. Do not send SOL to this address.
WARNING: 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.
) : ( This address can be used to receive{' '} {tokenSymbol ?? abbreviateAddress(mint)}. )} View on Solscan ) : ( )}
); } 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, }, ); if (!swapInfo) { return null; } 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; const { blockchain, address, memo, coin } = swapInfo; const { mint, tokenName } = balanceInfo; if (blockchain === 'btc' && memo === null) { return ( <> Native BTC can be converted to SPL {tokenName} by sending it to the following address: ); } if (blockchain === 'eth') { return ( <> {coin.erc20Contract ? 'ERC20' : 'Native'} {coin.ticker} can be converted to {mint ? 'SPL' : 'native'} {tokenName} via MetaMask. To convert, you must already have SOL in your wallet. Estimated withdrawal transaction fee: ); } return null; } function MetamaskDeposit({ swapInfo, insufficientEthBalance }) { 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; const [maxAmount, maxAmountLoaded] = useAsyncData(async () => { if (ethAccount) { return Math.min( await getErc20Balance(ethAccount, erc20Address), swapInfo.maxSize ?? Infinity, ); } return 0; }, tuple(getErc20Balance, ethAccount, erc20Address)); if (!ethAccount) { return ; } async function submit() { setSubmitted(true); setStatus({ step: 0 }); await callAsync( (async () => { let parsedAmount = parseFloat(amount); if (!parsedAmount || parsedAmount > maxAmount || parsedAmount <= 0) { throw new Error('Invalid amount'); } await swapErc20ToSpl({ ethAccount, erc20Address, swapAddress, destination, amount, onStatusChange: (e) => setStatus((status) => ({ ...status, ...e })), }); })(), { onError: () => setSubmitted(false) }, ); } if (!submitted) { let convertButton = ( ); if (insufficientEthBalance) { convertButton = ( {convertButton} ); } return (
{ticker} ), inputProps: { step: 'any', }, }} value={amount} onChange={(e) => setAmount(e.target.value.trim())} helperText={ maxAmountLoaded ? ( setAmount(maxAmount.toFixed(6))}> Max: {maxAmount.toFixed(6)} ) : null } /> {convertButton}
); } return ( <> Approve Conversion Send Funds Wait for Confirmations {status.step === 2 ? ( <>
{status.confirms ? ( {status.confirms} / 12 Confirmations ) : ( Transaction Pending )} View on Etherscan
) : null} ); }