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 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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue