feat: show owned-tokens

feat: show owned-tokens
This commit is contained in:
Simranjeet Singh 2021-09-29 04:07:48 +05:30
parent 3f213aa5ae
commit 1d0f407680
4 changed files with 136 additions and 21 deletions

View File

@ -13,9 +13,12 @@ import {
Typography,
Tabs,
Tab,
ListItemText,
ListItemAvatar,
Box,
} from "@material-ui/core";
import { TokenIcon } from "./Swap";
import { useSwappableTokens } from "../context/TokenList";
import { useSwappableTokens, useTokenListContext } from "../context/TokenList";
import { useMediaQuery } from "@material-ui/core";
const useStyles = makeStyles((theme) => ({
@ -151,27 +154,32 @@ function TokenListItem({
onClick: (mint: PublicKey) => void;
}) {
const mint = new PublicKey(tokenInfo.address);
const { ownedTokensDetailed } = useTokenListContext();
const details = ownedTokensDetailed.filter(
(t) => t.address === tokenInfo.address
)?.[0];
return (
<ListItem
button
onClick={() => onClick(mint)}
style={{ padding: "10px 20px" }}
>
<TokenIcon mint={mint} style={{ width: "30px", borderRadius: "15px" }} />
<TokenName tokenInfo={tokenInfo} />
<ListItemAvatar>
<TokenIcon
mint={mint}
style={{ width: "30px", borderRadius: "15px" }}
/>
</ListItemAvatar>
<ListItemText primary={tokenInfo?.symbol} secondary={tokenInfo?.name} />
{+details?.balance > 0 && (
<Box mr={1} textAlign="end">
<ListItemText
primary={details?.balance}
secondary={`$${details?.usd}`}
/>
</Box>
)}
</ListItem>
);
}
function TokenName({ tokenInfo }: { tokenInfo: TokenInfo }) {
return (
<div style={{ marginLeft: "16px" }}>
<Typography style={{ fontWeight: "bold" }}>
{tokenInfo?.symbol}
</Typography>
<Typography color="textSecondary" style={{ fontSize: "14px" }}>
{tokenInfo?.name}
</Typography>
</div>
);
}

View File

@ -1,6 +1,12 @@
import React, { useContext, useMemo } from "react";
import React, { useContext, useEffect, useMemo, useState } from "react";
import { TokenInfo } from "@solana/spl-token-registry";
import { SOL_MINT } from "../utils/pubkeys";
import { PublicKey } from "@solana/web3.js";
import {
fetchSolPrice,
getUserTokens,
OwnedTokenDetailed,
} from "../utils/userTokens";
type TokenListContext = {
tokenMap: Map<string, TokenInfo>;
@ -9,6 +15,7 @@ type TokenListContext = {
swappableTokens: TokenInfo[];
swappableTokensSollet: TokenInfo[];
swappableTokensWormhole: TokenInfo[];
ownedTokensDetailed: OwnedTokenDetailed[];
};
const _TokenListContext = React.createContext<null | TokenListContext>(null);
@ -37,6 +44,10 @@ const SOL_TOKEN_INFO = {
};
export function TokenListContextProvider(props: any) {
const [ownedTokensDetailed, setOwnedTokensDetailed] = useState<
OwnedTokenDetailed[]
>([]);
const tokenList = useMemo(() => {
const list = props.tokenList.filterByClusterSlug("mainnet-beta").getList();
// Manually add a fake SOL mint for the native token. The component is
@ -45,6 +56,8 @@ export function TokenListContextProvider(props: any) {
return list;
}, [props.tokenList]);
const pk: PublicKey | undefined = props?.provider?.wallet?.publicKey;
// Token map for quick lookup.
const tokenMap = useMemo(() => {
const tokenMap = new Map();
@ -54,18 +67,62 @@ export function TokenListContextProvider(props: any) {
return tokenMap;
}, [tokenList]);
useEffect(() => {
(async () => {
let solBalance: number = 0;
if (pk) solBalance = await props.provider.connection.getBalance(pk);
const tokens = await getUserTokens(pk?.toString());
const solPrice = await fetchSolPrice();
solBalance = solBalance / 10 ** +SOL_TOKEN_INFO.decimals;
const SolDetails = {
address: SOL_TOKEN_INFO.address,
balance: solBalance.toFixed(6),
usd: +(solBalance * solPrice).toFixed(4),
};
// only show the sol token if wallet is connected
if (pk) {
setOwnedTokensDetailed([SolDetails, ...tokens]);
} else {
// on disconnect, tokens = []
setOwnedTokensDetailed(tokens);
}
})();
}, [pk]);
// Tokens with USD(x) quoted markets.
const swappableTokens = useMemo(() => {
const tokens = tokenList.filter((t: TokenInfo) => {
const allTokens = tokenList.filter((t: TokenInfo) => {
const isUsdxQuoted =
t.extensions?.serumV3Usdt || t.extensions?.serumV3Usdc;
return isUsdxQuoted;
});
tokens.sort((a: TokenInfo, b: TokenInfo) =>
const ownedTokensList = ownedTokensDetailed.map((t) => t.address);
// Partition allTokens (pass & fail reduce)
const [ownedTokens, notOwnedtokens] = allTokens.reduce(
([p, f]: [TokenInfo[], TokenInfo[]], t: TokenInfo) =>
// pass & fail condition
ownedTokensList.includes(t.address) ? [[...p, t], f] : [p, [...f, t]],
[[], []]
);
notOwnedtokens.sort((a: TokenInfo, b: TokenInfo) =>
a.symbol < b.symbol ? -1 : a.symbol > b.symbol ? 1 : 0
);
// sort by price in USD
ownedTokens.sort(
(a: TokenInfo, b: TokenInfo) =>
+ownedTokensDetailed.filter((t: any) => t.address === b.address)?.[0]
.usd -
+ownedTokensDetailed.filter((t: any) => t.address === a.address)?.[0]
.usd
);
const tokens = ownedTokens.concat(notOwnedtokens);
return tokens;
}, [tokenList, tokenMap]);
}, [tokenList, tokenMap, ownedTokensDetailed]);
// Sollet wrapped tokens.
const [swappableTokensSollet, solletMap] = useMemo(() => {
@ -106,6 +163,7 @@ export function TokenListContextProvider(props: any) {
swappableTokens,
swappableTokensWormhole,
swappableTokensSollet,
ownedTokensDetailed,
}}
>
{props.children}

View File

@ -80,7 +80,7 @@ export default function Swap(props: SwapProps): ReactElement {
);
return (
<ThemeProvider theme={theme}>
<TokenListContextProvider tokenList={tokenList}>
<TokenListContextProvider tokenList={tokenList} provider={provider}>
<TokenContextProvider provider={provider}>
<DexContextProvider swapClient={swapClient}>
<SwapContextProvider

49
src/utils/userTokens.ts Normal file
View File

@ -0,0 +1,49 @@
export type OwnedTokenDetailed = {
address: string;
balance: string;
usd: number;
};
export const fetchSolPrice = async (): Promise<number> => {
try {
const response = await fetch("https://api.solscan.io/market?symbol=SOL");
const json = await response.json();
return json.data.priceUsdt;
} catch (error) {
console.error(error);
return 0;
}
};
// TODO: use web3 library
export const getUserTokens = async (
pk?: string
): Promise<OwnedTokenDetailed[]> => {
let data: OwnedTokenDetailed[] = [];
// for testing
// pk = "CuieVDEDtLo7FypA9SbLM9saXFdb1dsshEkyErMqkRQq"
try {
if (pk) {
let tokens = await (
await fetch(
`https://api.solscan.io/account/tokens?address=${pk}&price=1`
)
).json();
data = tokens.data.map((token: any) => {
return {
address: token.tokenAddress,
balance: token.tokenAmount.uiAmountString,
usd: +(token.tokenAmount.uiAmount * (token.priceUsdt ?? 0)).toFixed(
4
),
};
});
}
} catch (error) {
console.error(error);
}
return data.filter((t: OwnedTokenDetailed) => +t.balance > 0);
};