Add popular tokens easily (#9)

Add popular tokens easily
This commit is contained in:
nishadsingh1 2020-08-09 20:22:32 +08:00 committed by GitHub
parent 4b9c87b2e1
commit 274bff965a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 181 additions and 37 deletions

View File

@ -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>
); );
} }

View File

@ -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 };
} }