parent
4b9c87b2e1
commit
274bff965a
|
@ -1,42 +1,70 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import DialogContentText from '@material-ui/core/DialogContentText';
|
import DialogContentText from '@material-ui/core/DialogContentText';
|
||||||
import DialogActions from '@material-ui/core/DialogActions';
|
import DialogActions from '@material-ui/core/DialogActions';
|
||||||
import Button from '@material-ui/core/Button';
|
import Button from '@material-ui/core/Button';
|
||||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||||
import DialogContent from '@material-ui/core/DialogContent';
|
import DialogContent from '@material-ui/core/DialogContent';
|
||||||
import TextField from '@material-ui/core/TextField';
|
import TextField from '@material-ui/core/TextField';
|
||||||
import DialogForm from './DialogForm';
|
import Dialog from '@material-ui/core/Dialog';
|
||||||
import { refreshWalletPublicKeys, useWallet } from '../utils/wallet';
|
import { refreshWalletPublicKeys, useWallet } from '../utils/wallet';
|
||||||
import { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
|
import { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
|
||||||
import { useUpdateTokenName } from '../utils/tokens/names';
|
import { useUpdateTokenName } from '../utils/tokens/names';
|
||||||
import { useAsyncData } from '../utils/fetch-loop';
|
import { useAsyncData } from '../utils/fetch-loop';
|
||||||
import LoadingIndicator from './LoadingIndicator';
|
import LoadingIndicator from './LoadingIndicator';
|
||||||
|
import { Tab, Tabs, makeStyles } from '@material-ui/core';
|
||||||
import { useSendTransaction } from '../utils/notifications';
|
import { useSendTransaction } from '../utils/notifications';
|
||||||
import { useBalanceInfo } from '../utils/wallet';
|
import List from '@material-ui/core/List';
|
||||||
|
import ListItem from '@material-ui/core/ListItem';
|
||||||
|
import ListItemText from '@material-ui/core/ListItemText';
|
||||||
|
import { abbreviateAddress } from '../utils/utils';
|
||||||
|
import { TOKENS } from '../utils/tokens/names';
|
||||||
|
import ExpandLess from '@material-ui/icons/ExpandLess';
|
||||||
|
import ExpandMore from '@material-ui/icons/ExpandMore';
|
||||||
|
import Collapse from '@material-ui/core/Collapse';
|
||||||
|
import {
|
||||||
|
useConnectionConfig,
|
||||||
|
useSolanaExplorerUrlSuffix,
|
||||||
|
} from '../utils/connection';
|
||||||
|
import Link from '@material-ui/core/Link';
|
||||||
|
import CopyableDisplay from './CopyableDisplay';
|
||||||
|
|
||||||
const feeFormat = new Intl.NumberFormat(undefined, {
|
const feeFormat = new Intl.NumberFormat(undefined, {
|
||||||
minimumFractionDigits: 6,
|
minimumFractionDigits: 6,
|
||||||
maximumFractionDigits: 6,
|
maximumFractionDigits: 6,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
tabs: {
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
borderBottom: `1px solid ${theme.palette.background.paper}`,
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
export default function AddTokenDialog({ open, onClose }) {
|
export default function AddTokenDialog({ open, onClose }) {
|
||||||
let wallet = useWallet();
|
let wallet = useWallet();
|
||||||
let [tokenAccountCost] = useAsyncData(
|
let [tokenAccountCost] = useAsyncData(
|
||||||
wallet.tokenAccountCost,
|
wallet.tokenAccountCost,
|
||||||
'tokenAccountCost',
|
'tokenAccountCost',
|
||||||
);
|
);
|
||||||
|
let classes = useStyles();
|
||||||
let updateTokenName = useUpdateTokenName();
|
let updateTokenName = useUpdateTokenName();
|
||||||
|
|
||||||
let [mintAddress, setMintAddress] = useState('');
|
let [mintAddress, setMintAddress] = useState('');
|
||||||
let [tokenName, setTokenName] = useState('');
|
let [tokenName, setTokenName] = useState('');
|
||||||
let [tokenSymbol, setTokenSymbol] = useState('');
|
let [tokenSymbol, setTokenSymbol] = useState('');
|
||||||
let [sendTransaction, sending] = useSendTransaction();
|
let [sendTransaction, sending] = useSendTransaction();
|
||||||
let balanceInfo = useBalanceInfo(wallet.account.publicKey);
|
const { endpoint } = useConnectionConfig();
|
||||||
|
const popularTokens = TOKENS[endpoint];
|
||||||
|
const [tab, setTab] = useState(!!popularTokens ? 'popular' : 'manual');
|
||||||
|
|
||||||
let { amount } = balanceInfo || {};
|
useEffect(() => {
|
||||||
let noSol = amount === 0;
|
if (!popularTokens) {
|
||||||
|
setTab('manual');
|
||||||
|
}
|
||||||
|
}, [popularTokens]);
|
||||||
|
|
||||||
function onSubmit() {
|
function onSubmit({ mintAddress, tokenName, tokenSymbol }) {
|
||||||
let mint = new PublicKey(mintAddress);
|
let mint = new PublicKey(mintAddress);
|
||||||
sendTransaction(wallet.createTokenAccount(mint), {
|
sendTransaction(wallet.createTokenAccount(mint), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
@ -48,7 +76,7 @@ export default function AddTokenDialog({ open, onClose }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogForm open={open} onClose={onClose} onSubmit={onSubmit}>
|
<Dialog open={open} onClose={onClose}>
|
||||||
<DialogTitle>Add Token</DialogTitle>
|
<DialogTitle>Add Token</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
{tokenAccountCost ? (
|
{tokenAccountCost ? (
|
||||||
|
@ -59,37 +87,129 @@ export default function AddTokenDialog({ open, onClose }) {
|
||||||
) : (
|
) : (
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
)}
|
)}
|
||||||
<TextField
|
{!!popularTokens && (
|
||||||
label="Token Mint Address"
|
<Tabs
|
||||||
fullWidth
|
value={tab}
|
||||||
variant="outlined"
|
variant="standard"
|
||||||
margin="normal"
|
textColor="primary"
|
||||||
value={mintAddress}
|
indicatorColor="primary"
|
||||||
onChange={(e) => setMintAddress(e.target.value)}
|
className={classes.tabs}
|
||||||
/>
|
onChange={(e, value) => setTab(value)}
|
||||||
<TextField
|
>
|
||||||
label="Token Name"
|
<Tab
|
||||||
fullWidth
|
label={'Popular Tokens'}
|
||||||
variant="outlined"
|
value="popular"
|
||||||
margin="normal"
|
style={{ textDecoration: 'none', width: '50%' }}
|
||||||
value={tokenName}
|
/>
|
||||||
onChange={(e) => setTokenName(e.target.value)}
|
<Tab
|
||||||
/>
|
label={'Manual Input'}
|
||||||
<TextField
|
value="manual"
|
||||||
label="Token Symbol"
|
style={{ textDecoration: 'none', width: '50%' }}
|
||||||
fullWidth
|
/>
|
||||||
variant="outlined"
|
</Tabs>
|
||||||
margin="normal"
|
)}
|
||||||
value={tokenSymbol}
|
{tab === 'manual' ? (
|
||||||
onChange={(e) => setTokenSymbol(e.target.value)}
|
<React.Fragment>
|
||||||
/>
|
<TextField
|
||||||
|
label="Token Mint Address"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
value={mintAddress}
|
||||||
|
onChange={(e) => setMintAddress(e.target.value)}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Token Name"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
value={tokenName}
|
||||||
|
onChange={(e) => setTokenName(e.target.value)}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Token Symbol"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
margin="normal"
|
||||||
|
value={tokenSymbol}
|
||||||
|
onChange={(e) => setTokenSymbol(e.target.value)}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
) : (
|
||||||
|
<List disablePadding>
|
||||||
|
{popularTokens.map((token) => (
|
||||||
|
<TokenListItem
|
||||||
|
{...token}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
disalbed={sending}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={onClose}>Cancel</Button>
|
<Button onClick={onClose}>Cancel</Button>
|
||||||
<Button type="submit" color="primary" disabled={sending || noSol}>
|
{tab === 'manual' && (
|
||||||
{noSol ? 'Requires SOL Balance' : 'Add'}
|
<Button
|
||||||
</Button>
|
type="submit"
|
||||||
|
color="primary"
|
||||||
|
disabled={sending}
|
||||||
|
onClick={() => onSubmit({ tokenName, tokenSymbol, mintAddress })}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</DialogForm>
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TokenListItem({
|
||||||
|
tokenName,
|
||||||
|
tokenSymbol,
|
||||||
|
mintAddress,
|
||||||
|
onSubmit,
|
||||||
|
disabled,
|
||||||
|
}) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const urlSuffix = useSolanaExplorerUrlSuffix();
|
||||||
|
const alreadyExists = false; // TODO
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<div style={{ display: 'flex' }} key={tokenName}>
|
||||||
|
<ListItem button onClick={() => setOpen((open) => !open)}>
|
||||||
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
<Link
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
href={
|
||||||
|
`https://explorer.solana.com/account/${mintAddress}` +
|
||||||
|
urlSuffix
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{tokenName ?? abbreviateAddress(mintAddress)}
|
||||||
|
{tokenSymbol ? ` (${tokenSymbol})` : null}
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{open ? <ExpandLess /> : <ExpandMore />}
|
||||||
|
</ListItem>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
color="primary"
|
||||||
|
disabled={disabled || alreadyExists}
|
||||||
|
onClick={() => onSubmit({ tokenName, tokenSymbol, mintAddress })}
|
||||||
|
>
|
||||||
|
{alreadyExists ? 'Added' : 'Add'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Collapse in={open} timeout="auto" unmountOnExit>
|
||||||
|
<CopyableDisplay
|
||||||
|
value={mintAddress}
|
||||||
|
label={`${tokenSymbol} Mint Address`}
|
||||||
|
/>
|
||||||
|
</Collapse>
|
||||||
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,22 @@ import EventEmitter from 'events';
|
||||||
import { useConnectionConfig } from '../connection';
|
import { useConnectionConfig } from '../connection';
|
||||||
import { useListener } from '../utils';
|
import { useListener } from '../utils';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
import { clusterApiUrl } from '@solana/web3.js';
|
||||||
|
|
||||||
|
export const TOKENS = {
|
||||||
|
[clusterApiUrl('mainnet-beta')]: [
|
||||||
|
{
|
||||||
|
mintAddress: '7JMYnisD7vu9c5LQDxaEfmiGrvAa11nT8M6QW3YZ3xN4',
|
||||||
|
tokenName: 'Serum',
|
||||||
|
tokenSymbol: 'SRM',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mintAddress: 'MSRMmR98uWsTBgusjwyNkE8nDtV79sJznTedhJLzS4B',
|
||||||
|
tokenName: 'MegaSerum',
|
||||||
|
tokenSymbol: 'MSRM',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
const customTokenNamesByNetwork = JSON.parse(
|
const customTokenNamesByNetwork = JSON.parse(
|
||||||
localStorage.getItem('tokenNames') ?? '{}',
|
localStorage.getItem('tokenNames') ?? '{}',
|
||||||
|
@ -18,7 +34,15 @@ export function useTokenName(mint) {
|
||||||
return { name: null, symbol: null };
|
return { name: null, symbol: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
const info = customTokenNamesByNetwork?.[endpoint]?.[mint.toBase58()];
|
let info = customTokenNamesByNetwork?.[endpoint]?.[mint.toBase58()];
|
||||||
|
if (!info) {
|
||||||
|
let match = TOKENS?.[endpoint]?.find(
|
||||||
|
(token) => token.mintAddress === mint.toBase58(),
|
||||||
|
);
|
||||||
|
if (match) {
|
||||||
|
info = { name: match.tokenName, symbol: match.tokenSymbol };
|
||||||
|
}
|
||||||
|
}
|
||||||
return { name: info?.name, symbol: info?.symbol };
|
return { name: info?.name, symbol: info?.symbol };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue