Show token tickers on pool page

This commit is contained in:
Gary Wang 2020-12-10 23:22:49 +08:00
parent 3f21f90f77
commit cc6d2707ef
7 changed files with 135 additions and 45 deletions

View File

@ -0,0 +1,42 @@
import React from 'react';
import { PublicKey } from '@solana/web3.js';
import { abbreviateAddress } from '../utils/utils';
import { useMintToTickers } from '../utils/tokens';
import { Popover } from 'antd';
import LinkAddress from './LinkAddress';
import { InfoCircleOutlined } from '@ant-design/icons';
export function MintName({
mint,
showAddress = false,
}: {
mint: string | PublicKey | null | undefined;
showAddress?: boolean;
}) {
const mintToTickers = useMintToTickers();
if (!mint) {
return null;
}
const mintKey = typeof mint === 'string' ? new PublicKey(mint) : mint;
const mintAddress = typeof mint === 'string' ? mint : mint.toBase58();
const ticker = mintToTickers[mintAddress] ?? abbreviateAddress(mintKey);
return (
<>
{ticker}
{showAddress ? (
<>
{' '}
<Popover
content={<LinkAddress address={mintAddress} />}
placement="bottomRight"
title="Token mint"
trigger="hover"
>
<InfoCircleOutlined style={{ color: '#2abdd2' }} />
</Popover>
</>
) : null}
</>
);
}

View File

@ -11,16 +11,17 @@ import {
import { sendTransaction } from '../../../utils/send';
import { notify } from '../../../utils/notifications';
import { PublicKey, Transaction } from '@solana/web3.js';
import { Button, Input, Tabs } from 'antd';
import { Button, Input, Select, Tabs } from 'antd';
import {
createAssociatedTokenAccount,
getAssociatedTokenAddress,
} from '@project-serum/associated-token';
import { parseTokenMintData } from '../../../utils/tokens';
import { parseTokenMintData, useMintToTickers } from '../../../utils/tokens';
import BN from 'bn.js';
import { refreshAllCaches } from '../../../utils/fetch-loop';
const { TabPane } = Tabs;
const { Option } = Select;
export function PoolAdminPanel({ poolInfo }: { poolInfo: PoolInfo }) {
return (
@ -41,7 +42,7 @@ export function PoolAdminPanel({ poolInfo }: { poolInfo: PoolInfo }) {
<TabPane tab="Withdraw" key="withdraw">
<WithdrawTab poolInfo={poolInfo} />
</TabPane>
<TabPane tab="Change Fee" key="updateFee">
<TabPane tab="Modify Fee" key="updateFee">
<UpdateFeeTab poolInfo={poolInfo} />
</TabPane>
</Tabs>
@ -177,10 +178,11 @@ function RemoveAssetTab({ poolInfo }: TabParams) {
return (
<form onSubmit={onSubmit}>
<Input
addonBefore={<>Token Mint Address</>}
<MintInPoolSelector
poolInfo={poolInfo}
label="Token Mint Address"
value={address}
onChange={(e) => setAddress(e.target.value.trim())}
onChange={(value) => setAddress(value)}
style={{ marginBottom: 24 }}
/>
<SubmitButton canSubmit={canSubmit} submitting={submitting} />
@ -242,10 +244,11 @@ function DepositTab({ poolInfo }: TabParams) {
return (
<form onSubmit={onSubmit}>
<Input
addonBefore={<>Token Mint Address</>}
<MintInPoolSelector
poolInfo={poolInfo}
label="Token Mint Address"
value={address}
onChange={(e) => setAddress(e.target.value.trim())}
onChange={(value) => setAddress(value)}
style={{ marginBottom: 24 }}
/>
<Input
@ -318,10 +321,11 @@ function WithdrawTab({ poolInfo }: TabParams) {
return (
<form onSubmit={onSubmit}>
<Input
addonBefore={<>Token Mint Address</>}
<MintInPoolSelector
poolInfo={poolInfo}
label="Token Mint Address"
value={address}
onChange={(e) => setAddress(e.target.value.trim())}
onChange={(value) => setAddress(value)}
style={{ marginBottom: 24 }}
/>
<Input
@ -422,3 +426,37 @@ function SubmitButton({ canSubmit, submitting }) {
</Button>
);
}
function MintInPoolSelector({
poolInfo,
label,
value,
onChange,
style,
}: {
poolInfo: PoolInfo;
label: string;
value: string;
onChange: (string) => void;
style: any;
}) {
const mintToTickers = useMintToTickers();
return (
<Input.Group style={style}>
<span className="ant-input-group-addon">{label}</span>
<Select onChange={onChange} value={value} style={{ width: '100%' }}>
{poolInfo.state.assets.map((asset) => (
<Option value={asset.mint.toBase58()} key={asset.mint.toBase58()}>
{mintToTickers[asset.mint.toBase58()] ? (
<>
{mintToTickers[asset.mint.toBase58()]} ({asset.mint.toBase58()})
</>
) : (
asset.mint.toBase58()
)}
</Option>
))}
</Select>
</Input.Group>
);
}

View File

@ -6,9 +6,11 @@ import {
parseTokenAccountData,
parseTokenMintData,
} from '../../../utils/tokens';
import { Spin, Tabs } from 'antd';
import { Button, Spin, Tabs } from 'antd';
import FloatingElement from '../../../components/layout/FloatingElement';
import { useTokenAccounts } from '../../../utils/markets';
import { MintName } from '../../../components/MintName';
import { LinkOutlined } from '@ant-design/icons';
const { TabPane } = Tabs;
@ -90,7 +92,14 @@ function BalanceItem({ mint, publicKey }: BalanceItemProps) {
return (
<li>
{quantityDisplay} {mint.toBase58()} {publicKey.toBase58()}
{quantityDisplay} <MintName mint={mint} />{' '}
<Button
type="link"
icon={<LinkOutlined />}
href={'https://explorer.solana.com/address/' + publicKey.toBase58()}
target="_blank"
rel="noopener noreferrer"
/>
</li>
);
}

View File

@ -5,10 +5,11 @@ import { PublicKey } from '@solana/web3.js';
import { useAccountInfo } from '../../../utils/connection';
import { parseTokenMintData } from '../../../utils/tokens';
import { Spin } from 'antd';
import { MintName } from '../../../components/MintName';
interface BasketDisplayProps {
poolInfo: PoolInfo;
basket: Basket;
basket?: Basket | null | undefined;
}
export default function PoolBasketDisplay({
@ -21,7 +22,7 @@ export default function PoolBasketDisplay({
<BasketItem
key={index}
mint={asset.mint}
quantity={basket.quantities[index]}
quantity={basket?.quantities[index]}
/>
))}
</ul>
@ -30,20 +31,20 @@ export default function PoolBasketDisplay({
interface BasketItemProps {
mint: PublicKey;
quantity: BN;
quantity?: BN;
}
function BasketItem({ mint, quantity }: BasketItemProps) {
const [mintAccountInfo] = useAccountInfo(mint);
let quantityDisplay = <Spin size="small" />;
if (mintAccountInfo) {
if (mintAccountInfo && quantity) {
const mintInfo = parseTokenMintData(mintAccountInfo.data);
quantityDisplay = <>{quantity.toNumber() / 10 ** mintInfo.decimals}</>;
}
return (
<li>
{quantityDisplay} {mint.toBase58()}
{quantityDisplay} <MintName mint={mint} showAddress />
</li>
);
}

View File

@ -1,7 +1,7 @@
import { getPoolBasket, PoolInfo } from '@project-serum/pool';
import React from 'react';
import FloatingElement from '../../../components/layout/FloatingElement';
import { Spin, Typography } from 'antd';
import { Typography } from 'antd';
import { MintInfo } from '../../../utils/tokens';
import { useAsyncData } from '../../../utils/fetch-loop';
import { useConnection } from '../../../utils/connection';
@ -52,11 +52,7 @@ export default function PoolInfoPanel({ poolInfo, mintInfo }: PoolInfoProps) {
</Paragraph>
<Text>Total assets:</Text>
<div>
{!totalBasket ? (
<Spin />
) : (
<PoolBasketDisplay poolInfo={poolInfo} basket={totalBasket} />
)}
<PoolBasketDisplay poolInfo={poolInfo} basket={totalBasket} />
</div>
</FloatingElement>
);

View File

@ -10,6 +10,7 @@ import { useConnection } from './connection';
import { useAsyncData } from './fetch-loop';
import tuple from 'immutable-tuple';
import BN from 'bn.js';
import { useMemo } from 'react';
export const ACCOUNT_LAYOUT = BufferLayout.struct([
BufferLayout.blob(32, 'mint'),
@ -147,28 +148,31 @@ export async function getTokenAccountInfo(
export function useMintToTickers(): { [mint: string]: string } {
const { customMarkets } = useMarket();
const [markets] = useAllMarkets(customMarkets);
const mintsToTickers = Object.fromEntries(
TOKEN_MINTS.map((mint) => [mint.address.toBase58(), mint.name]),
);
for (let market of markets || []) {
const customMarketInfo = customMarkets.find(
(customMarket) =>
customMarket.address === market.market.address.toBase58(),
return useMemo(() => {
const mintsToTickers = Object.fromEntries(
TOKEN_MINTS.map((mint) => [mint.address.toBase58(), mint.name]),
);
if (!(market.market.baseMintAddress.toBase58() in mintsToTickers)) {
if (customMarketInfo) {
mintsToTickers[market.market.baseMintAddress.toBase58()] =
customMarketInfo.baseLabel || `${customMarketInfo.name}_BASE`;
for (let market of markets || []) {
const customMarketInfo = customMarkets.find(
(customMarket) =>
customMarket.address === market.market.address.toBase58(),
);
if (!(market.market.baseMintAddress.toBase58() in mintsToTickers)) {
if (customMarketInfo) {
mintsToTickers[market.market.baseMintAddress.toBase58()] =
customMarketInfo.baseLabel || `${customMarketInfo.name}_BASE`;
}
}
if (!(market.market.quoteMintAddress.toBase58() in mintsToTickers)) {
if (customMarketInfo) {
mintsToTickers[market.market.quoteMintAddress.toBase58()] =
customMarketInfo.quoteLabel || `${customMarketInfo.name}_QUOTE`;
}
}
}
if (!(market.market.quoteMintAddress.toBase58() in mintsToTickers)) {
if (customMarketInfo) {
mintsToTickers[market.market.quoteMintAddress.toBase58()] =
customMarketInfo.quoteLabel || `${customMarketInfo.name}_QUOTE`;
}
}
}
return mintsToTickers;
return mintsToTickers;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [markets?.length, customMarkets.length]);
}
const _VERY_SLOW_REFRESH_INTERVAL = 5000 * 1000;

View File

@ -132,7 +132,7 @@ export function useListener(emitter, eventName) {
}, [emitter, eventName]);
}
export function abbreviateAddress(address, size = 4) {
export function abbreviateAddress(address: PublicKey, size = 4) {
const base58 = address.toBase58();
return base58.slice(0, size) + '…' + base58.slice(-size);
}