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 DialogActions from '@material-ui/core/DialogActions';
import Button from '@material-ui/core/Button';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
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 { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
import { useUpdateTokenName } from '../utils/tokens/names';
import { useAsyncData } from '../utils/fetch-loop';
import LoadingIndicator from './LoadingIndicator';
import { Tab, Tabs, makeStyles } from '@material-ui/core';
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, {
minimumFractionDigits: 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 }) {
let wallet = useWallet();
let [tokenAccountCost] = useAsyncData(
wallet.tokenAccountCost,
'tokenAccountCost',
);
let classes = useStyles();
let updateTokenName = useUpdateTokenName();
let [mintAddress, setMintAddress] = useState('');
let [tokenName, setTokenName] = useState('');
let [tokenSymbol, setTokenSymbol] = useState('');
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 || {};
let noSol = amount === 0;
useEffect(() => {
if (!popularTokens) {
setTab('manual');
}
}, [popularTokens]);
function onSubmit() {
function onSubmit({ mintAddress, tokenName, tokenSymbol }) {
let mint = new PublicKey(mintAddress);
sendTransaction(wallet.createTokenAccount(mint), {
onSuccess: () => {
@ -48,7 +76,7 @@ export default function AddTokenDialog({ open, onClose }) {
}
return (
<DialogForm open={open} onClose={onClose} onSubmit={onSubmit}>
<Dialog open={open} onClose={onClose}>
<DialogTitle>Add Token</DialogTitle>
<DialogContent>
{tokenAccountCost ? (
@ -59,37 +87,129 @@ export default function AddTokenDialog({ open, onClose }) {
) : (
<LoadingIndicator />
)}
<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)}
/>
{!!popularTokens && (
<Tabs
value={tab}
variant="standard"
textColor="primary"
indicatorColor="primary"
className={classes.tabs}
onChange={(e, value) => setTab(value)}
>
<Tab
label={'Popular Tokens'}
value="popular"
style={{ textDecoration: 'none', width: '50%' }}
/>
<Tab
label={'Manual Input'}
value="manual"
style={{ textDecoration: 'none', width: '50%' }}
/>
</Tabs>
)}
{tab === 'manual' ? (
<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>
<DialogActions>
<Button onClick={onClose}>Cancel</Button>
<Button type="submit" color="primary" disabled={sending || noSol}>
{noSol ? 'Requires SOL Balance' : 'Add'}
</Button>
{tab === 'manual' && (
<Button
type="submit"
color="primary"
disabled={sending}
onClick={() => onSubmit({ tokenName, tokenSymbol, mintAddress })}
>
Add
</Button>
)}
</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 { useListener } from '../utils';
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(
localStorage.getItem('tokenNames') ?? '{}',
@ -18,7 +34,15 @@ export function useTokenName(mint) {
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 };
}