bridge_ui: stats page enhancements
Change-Id: I1a4c3fd2145f51d7bdeb80bdd173769db2d8daad
This commit is contained in:
parent
a1bc316590
commit
94f26e1637
|
@ -0,0 +1,132 @@
|
||||||
|
import {
|
||||||
|
CHAIN_ID_BSC,
|
||||||
|
CHAIN_ID_ETH,
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
CHAIN_ID_TERRA,
|
||||||
|
} from "@certusone/wormhole-sdk";
|
||||||
|
import { makeStyles, Typography } from "@material-ui/core";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import {
|
||||||
|
getNFTBridgeAddressForChain,
|
||||||
|
getTokenBridgeAddressForChain,
|
||||||
|
SOL_CUSTODY_ADDRESS,
|
||||||
|
SOL_NFT_CUSTODY_ADDRESS,
|
||||||
|
} from "../../utils/consts";
|
||||||
|
import SmartAddress from "../SmartAddress";
|
||||||
|
import MuiReactTable from "./tableComponents/MuiReactTable";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
flexBox: {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "flex-end",
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
textAlign: "left",
|
||||||
|
[theme.breakpoints.down("sm")]: {
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "unset",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grower: {
|
||||||
|
flexGrow: 1,
|
||||||
|
},
|
||||||
|
explainerContainer: {},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const CustodyAddresses: React.FC<any> = () => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const data = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
chainName: "Ethereum",
|
||||||
|
chainId: CHAIN_ID_ETH,
|
||||||
|
tokenAddress: getTokenBridgeAddressForChain(CHAIN_ID_ETH),
|
||||||
|
nftAddress: getNFTBridgeAddressForChain(CHAIN_ID_ETH),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chainName: "Solana",
|
||||||
|
chainId: CHAIN_ID_SOLANA,
|
||||||
|
tokenAddress: SOL_CUSTODY_ADDRESS,
|
||||||
|
nftAddress: SOL_NFT_CUSTODY_ADDRESS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chainName: "Binance Smart Chain",
|
||||||
|
chainId: CHAIN_ID_BSC,
|
||||||
|
tokenAddress: getTokenBridgeAddressForChain(CHAIN_ID_BSC),
|
||||||
|
nftAddress: getNFTBridgeAddressForChain(CHAIN_ID_BSC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chainName: "Terra",
|
||||||
|
chainId: CHAIN_ID_TERRA,
|
||||||
|
tokenAddress: getTokenBridgeAddressForChain(CHAIN_ID_TERRA),
|
||||||
|
nftAddress: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const tvlColumns = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{ Header: "Chain", accessor: "chainName", disableGroupBy: true },
|
||||||
|
{
|
||||||
|
Header: "Token Address",
|
||||||
|
id: "tokenAddress",
|
||||||
|
accessor: "address",
|
||||||
|
disableGroupBy: true,
|
||||||
|
Cell: (value: any) =>
|
||||||
|
value.row?.original?.tokenAddress && value.row?.original?.chainId ? (
|
||||||
|
<SmartAddress
|
||||||
|
chainId={value.row?.original?.chainId}
|
||||||
|
address={value.row?.original?.tokenAddress}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: "NFT Address",
|
||||||
|
id: "nftAddress",
|
||||||
|
accessor: "address",
|
||||||
|
disableGroupBy: true,
|
||||||
|
Cell: (value: any) =>
|
||||||
|
value.row?.original?.nftAddress && value.row?.original?.chainId ? (
|
||||||
|
<SmartAddress
|
||||||
|
chainId={value.row?.original?.chainId}
|
||||||
|
address={value.row?.original?.nftAddress}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const header = (
|
||||||
|
<div className={classes.flexBox}>
|
||||||
|
<div className={classes.explainerContainer}>
|
||||||
|
<Typography variant="h5">Custody Addresses</Typography>
|
||||||
|
<Typography variant="subtitle2" color="textSecondary">
|
||||||
|
These are the custody addresses which hold collateralized assets for
|
||||||
|
the token bridge.
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<div className={classes.grower} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const table = (
|
||||||
|
<MuiReactTable
|
||||||
|
columns={tvlColumns}
|
||||||
|
data={data || []}
|
||||||
|
skipPageReset={false}
|
||||||
|
initialState={{}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{header}
|
||||||
|
{table}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustodyAddresses;
|
|
@ -0,0 +1,253 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
CircularProgress,
|
||||||
|
makeStyles,
|
||||||
|
Typography,
|
||||||
|
} from "@material-ui/core";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import numeral from "numeral";
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import useNFTTVL from "../../hooks/useNFTTVL";
|
||||||
|
import {
|
||||||
|
CHAINS_WITH_NFT_SUPPORT,
|
||||||
|
getNFTBridgeAddressForChain,
|
||||||
|
} from "../../utils/consts";
|
||||||
|
import NFTViewer from "../TokenSelectors/NFTViewer";
|
||||||
|
import MuiReactTable from "./tableComponents/MuiReactTable";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
logoPositioner: {
|
||||||
|
height: "30px",
|
||||||
|
width: "30px",
|
||||||
|
maxWidth: "30px",
|
||||||
|
marginRight: theme.spacing(1),
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
maxHeight: "100%",
|
||||||
|
maxWidth: "100%",
|
||||||
|
},
|
||||||
|
tokenContainer: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
flexBox: {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "flex-end",
|
||||||
|
marginBottom: theme.spacing(1),
|
||||||
|
textAlign: "left",
|
||||||
|
[theme.breakpoints.down("sm")]: {
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "unset",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grower: {
|
||||||
|
flexGrow: 1,
|
||||||
|
},
|
||||||
|
explainerContainer: {},
|
||||||
|
totalContainer: {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "flex-end",
|
||||||
|
paddingBottom: 1, // line up with left text bottom
|
||||||
|
[theme.breakpoints.down("sm")]: {
|
||||||
|
marginTop: theme.spacing(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
totalValue: {
|
||||||
|
marginLeft: theme.spacing(0.5),
|
||||||
|
marginBottom: "-.125em", // line up number with label
|
||||||
|
},
|
||||||
|
tableBox: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-start",
|
||||||
|
"& > *": {
|
||||||
|
margin: theme.spacing(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
randomButton: {
|
||||||
|
margin: "0px auto 8px",
|
||||||
|
display: "block",
|
||||||
|
},
|
||||||
|
randomNftContainer: {
|
||||||
|
minHeight: "550px",
|
||||||
|
},
|
||||||
|
alignCenter: {
|
||||||
|
margin: "0 auto",
|
||||||
|
display: "block",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const BLACKLIST = ["D9cX654dGb4GFzqq3RY7rhZbRkQqUkfggDZdnYxqv97g"];
|
||||||
|
|
||||||
|
const NFTStats: React.FC<any> = () => {
|
||||||
|
const classes = useStyles();
|
||||||
|
const nftTVL = useNFTTVL();
|
||||||
|
|
||||||
|
//Disable this to quickly turn off
|
||||||
|
//TODO also change what data is fetched off this
|
||||||
|
const enableRandomNFT = true;
|
||||||
|
|
||||||
|
const [randomNumber, setRandomNumber] = useState<number | null>(null);
|
||||||
|
const randomNft = useMemo(
|
||||||
|
() =>
|
||||||
|
(randomNumber !== null && nftTVL.data && nftTVL.data[randomNumber]) ||
|
||||||
|
null,
|
||||||
|
[randomNumber, nftTVL.data]
|
||||||
|
);
|
||||||
|
const genRandomNumber = useCallback(() => {
|
||||||
|
if (!nftTVL || !nftTVL.data || !nftTVL.data?.length || nftTVL.isFetching) {
|
||||||
|
setRandomNumber(null);
|
||||||
|
} else {
|
||||||
|
let found = false;
|
||||||
|
let nextNumber = Math.floor(Math.random() * nftTVL.data.length);
|
||||||
|
|
||||||
|
while (!found) {
|
||||||
|
if (!nftTVL.data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const item = nftTVL?.data[nextNumber]?.mintKey?.toLowerCase() || null;
|
||||||
|
if (!BLACKLIST.find((x) => x.toLowerCase() === item)) {
|
||||||
|
found = true;
|
||||||
|
} else {
|
||||||
|
nextNumber = Math.floor(Math.random() * nftTVL.data.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setRandomNumber(nextNumber);
|
||||||
|
}
|
||||||
|
}, [nftTVL]);
|
||||||
|
useEffect(() => {
|
||||||
|
genRandomNumber();
|
||||||
|
}, [nftTVL.isFetching, genRandomNumber]);
|
||||||
|
|
||||||
|
const data = useMemo(() => {
|
||||||
|
const output: any[] = [];
|
||||||
|
if (nftTVL.data && !nftTVL.isFetching) {
|
||||||
|
CHAINS_WITH_NFT_SUPPORT.forEach((chain) => {
|
||||||
|
output.push({
|
||||||
|
nfts: nftTVL?.data?.filter((x) => x.chainId === chain.id),
|
||||||
|
chainName: chain.name,
|
||||||
|
chainId: chain.id,
|
||||||
|
chainLogo: chain.logo,
|
||||||
|
contractAddress: getNFTBridgeAddressForChain(chain.id),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}, [nftTVL]);
|
||||||
|
|
||||||
|
const tvlColumns = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{ Header: "Chain", accessor: "chainName", disableGroupBy: true },
|
||||||
|
// {
|
||||||
|
// Header: "Address",
|
||||||
|
// accessor: "contractAddress",
|
||||||
|
// disableGroupBy: true,
|
||||||
|
// Cell: (value: any) =>
|
||||||
|
// value.row?.original?.contractAddress &&
|
||||||
|
// value.row?.original?.chainId ? (
|
||||||
|
// <SmartAddress
|
||||||
|
// chainId={value.row?.original?.chainId}
|
||||||
|
// address={value.row?.original?.contractAddress}
|
||||||
|
// />
|
||||||
|
// ) : (
|
||||||
|
// ""
|
||||||
|
// ),
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
Header: "NFTs Locked",
|
||||||
|
id: "nftCount",
|
||||||
|
accessor: "nftCount",
|
||||||
|
align: "right",
|
||||||
|
disableGroupBy: true,
|
||||||
|
Cell: (value: any) =>
|
||||||
|
value.row?.original?.nfts?.length !== undefined
|
||||||
|
? numeral(value.row?.original?.nfts?.length).format("0 a")
|
||||||
|
: "",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const header = (
|
||||||
|
<div className={classes.flexBox}>
|
||||||
|
<div className={classes.explainerContainer}>
|
||||||
|
<Typography variant="h5">Total NFTs Locked</Typography>
|
||||||
|
<Typography variant="subtitle2" color="textSecondary">
|
||||||
|
These NFTs are currently locked by the NFT Bridge contracts.
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<div className={classes.grower} />
|
||||||
|
{!nftTVL.isFetching ? (
|
||||||
|
<div
|
||||||
|
className={clsx(classes.explainerContainer, classes.totalContainer)}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="textSecondary"
|
||||||
|
component="div"
|
||||||
|
noWrap
|
||||||
|
>
|
||||||
|
{"Total "}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="h3"
|
||||||
|
component="div"
|
||||||
|
noWrap
|
||||||
|
className={classes.totalValue}
|
||||||
|
>
|
||||||
|
{nftTVL.data?.length || "0"}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const table = (
|
||||||
|
<MuiReactTable
|
||||||
|
columns={tvlColumns}
|
||||||
|
data={data || []}
|
||||||
|
skipPageReset={false}
|
||||||
|
initialState={{ sortBy: [{ id: "nftCount", desc: true }] }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const randomNFTContent =
|
||||||
|
enableRandomNFT && randomNft ? (
|
||||||
|
<div className={classes.randomNftContainer}>
|
||||||
|
<Button
|
||||||
|
className={classes.randomButton}
|
||||||
|
variant="contained"
|
||||||
|
onClick={genRandomNumber}
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
Load Random Wormhole NFT
|
||||||
|
</Button>
|
||||||
|
<NFTViewer chainId={randomNft.chainId} value={randomNft} />
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
// const allNfts =
|
||||||
|
// nftTVL?.data?.map((thing) => (
|
||||||
|
// <NFTViewer chainId={thing.chainId} value={thing} />
|
||||||
|
// )) || [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{header}
|
||||||
|
{nftTVL.isFetching ? (
|
||||||
|
<CircularProgress className={classes.alignCenter} />
|
||||||
|
) : (
|
||||||
|
<div className={classes.tableBox}>
|
||||||
|
{table}
|
||||||
|
{randomNFTContent}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* {allNfts} */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NFTStats;
|
|
@ -14,6 +14,8 @@ import useTVL from "../../hooks/useTVL";
|
||||||
import { COLORS } from "../../muiTheme";
|
import { COLORS } from "../../muiTheme";
|
||||||
import SmartAddress from "../SmartAddress";
|
import SmartAddress from "../SmartAddress";
|
||||||
import { balancePretty } from "../TokenSelectors/TokenPicker";
|
import { balancePretty } from "../TokenSelectors/TokenPicker";
|
||||||
|
import CustodyAddresses from "./CustodyAddresses";
|
||||||
|
import NFTStats from "./NFTStats";
|
||||||
import MuiReactTable from "./tableComponents/MuiReactTable";
|
import MuiReactTable from "./tableComponents/MuiReactTable";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
@ -36,11 +38,11 @@ const useStyles = makeStyles((theme) => ({
|
||||||
},
|
},
|
||||||
mainPaper: {
|
mainPaper: {
|
||||||
backgroundColor: COLORS.nearBlackWithMinorTransparency,
|
backgroundColor: COLORS.nearBlackWithMinorTransparency,
|
||||||
textAlign: "center",
|
|
||||||
padding: "2rem",
|
padding: "2rem",
|
||||||
"& > h, p ": {
|
"& > h, & > p ": {
|
||||||
margin: ".5rem",
|
margin: ".5rem",
|
||||||
},
|
},
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
},
|
},
|
||||||
flexBox: {
|
flexBox: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
@ -68,11 +70,16 @@ const useStyles = makeStyles((theme) => ({
|
||||||
marginLeft: theme.spacing(0.5),
|
marginLeft: theme.spacing(0.5),
|
||||||
marginBottom: "-.125em", // line up number with label
|
marginBottom: "-.125em", // line up number with label
|
||||||
},
|
},
|
||||||
|
alignCenter: {
|
||||||
|
margin: "0 auto",
|
||||||
|
display: "block",
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const StatsRoot: React.FC<any> = () => {
|
const StatsRoot: React.FC<any> = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const tvl = useTVL();
|
const tvl = useTVL();
|
||||||
|
|
||||||
const sortTokens = useMemo(() => {
|
const sortTokens = useMemo(() => {
|
||||||
return (rowA: any, rowB: any) => {
|
return (rowA: any, rowB: any) => {
|
||||||
if (rowA.isGrouped && rowB.isGrouped) {
|
if (rowA.isGrouped && rowB.isGrouped) {
|
||||||
|
@ -195,19 +202,16 @@ const StatsRoot: React.FC<any> = () => {
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="lg">
|
<Container maxWidth="lg">
|
||||||
<Paper className={classes.mainPaper}>
|
<Paper className={classes.mainPaper}>
|
||||||
{tvl.isFetching ? (
|
|
||||||
<CircularProgress />
|
|
||||||
) : (
|
|
||||||
<>
|
<>
|
||||||
<div className={classes.flexBox}>
|
<div className={classes.flexBox}>
|
||||||
<div className={classes.explainerContainer}>
|
<div className={classes.explainerContainer}>
|
||||||
<Typography variant="h5">Total Value Locked</Typography>
|
<Typography variant="h5">Total Value Locked</Typography>
|
||||||
<Typography variant="subtitle2" color="textSecondary">
|
<Typography variant="subtitle2" color="textSecondary">
|
||||||
These assets are currently locked by the Token Bridge
|
These assets are currently locked by the Token Bridge contracts.
|
||||||
contracts.
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.grower} />
|
<div className={classes.grower} />
|
||||||
|
{!tvl.isFetching ? (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
classes.explainerContainer,
|
classes.explainerContainer,
|
||||||
|
@ -231,15 +235,25 @@ const StatsRoot: React.FC<any> = () => {
|
||||||
{tvlString}
|
{tvlString}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
{!tvl.isFetching ? (
|
||||||
<MuiReactTable
|
<MuiReactTable
|
||||||
columns={tvlColumns}
|
columns={tvlColumns}
|
||||||
data={tvl.data}
|
data={tvl.data}
|
||||||
skipPageReset={false}
|
skipPageReset={false}
|
||||||
initialState={{ sortBy: [{ id: "totalValue", desc: true }] }}
|
initialState={{ sortBy: [{ id: "totalValue", desc: true }] }}
|
||||||
/>
|
/>
|
||||||
</>
|
) : (
|
||||||
|
<CircularProgress className={classes.alignCenter} />
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
</Paper>
|
||||||
|
<Paper className={classes.mainPaper}>
|
||||||
|
<NFTStats />
|
||||||
|
</Paper>
|
||||||
|
<Paper className={classes.mainPaper}>
|
||||||
|
<CustodyAddresses />
|
||||||
</Paper>
|
</Paper>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
Typography,
|
Typography,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useEffect, useState } from "react";
|
import { useCallback, useEffect, useLayoutEffect, useState } from "react";
|
||||||
import { NFTParsedTokenAccount } from "../../store/nftSlice";
|
import { NFTParsedTokenAccount } from "../../store/nftSlice";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import {
|
import {
|
||||||
|
@ -22,6 +22,8 @@ import bscIcon from "../../icons/bsc.svg";
|
||||||
import ethIcon from "../../icons/eth.svg";
|
import ethIcon from "../../icons/eth.svg";
|
||||||
import solanaIcon from "../../icons/solana.svg";
|
import solanaIcon from "../../icons/solana.svg";
|
||||||
import useCopyToClipboard from "../../hooks/useCopyToClipboard";
|
import useCopyToClipboard from "../../hooks/useCopyToClipboard";
|
||||||
|
import { Skeleton } from "@material-ui/lab";
|
||||||
|
import Wormhole from "../../icons/wormhole-network.svg";
|
||||||
|
|
||||||
const safeIPFS = (uri: string) =>
|
const safeIPFS = (uri: string) =>
|
||||||
uri.startsWith("ipfs://ipfs/")
|
uri.startsWith("ipfs://ipfs/")
|
||||||
|
@ -128,12 +130,16 @@ const useStyles = makeStyles((theme) => ({
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
background: "transparent",
|
background: "transparent",
|
||||||
border: "1px solid #ffb347",
|
|
||||||
margin: theme.spacing(0, 2),
|
margin: theme.spacing(0, 2),
|
||||||
|
"& > img, & > video": {
|
||||||
|
border: "1px solid #ffb347",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
solanaMediaBorder: {
|
solanaMediaBorder: {
|
||||||
|
"& > img, & > video": {
|
||||||
borderColor: "#D7DDE8",
|
borderColor: "#D7DDE8",
|
||||||
},
|
},
|
||||||
|
},
|
||||||
// thanks https://cssgradient.io/
|
// thanks https://cssgradient.io/
|
||||||
eth: {
|
eth: {
|
||||||
// colors from https://en.wikipedia.org/wiki/Ethereum#/media/File:Ethereum-icon-purple.svg
|
// colors from https://en.wikipedia.org/wiki/Ethereum#/media/File:Ethereum-icon-purple.svg
|
||||||
|
@ -153,8 +159,54 @@ const useStyles = makeStyles((theme) => ({
|
||||||
background:
|
background:
|
||||||
"linear-gradient(45deg, rgba(153,69,255,1) 0%, rgba(121,98,231,1) 20%, rgba(0,209,140,1) 100%)",
|
"linear-gradient(45deg, rgba(153,69,255,1) 0%, rgba(121,98,231,1) 20%, rgba(0,209,140,1) 100%)",
|
||||||
},
|
},
|
||||||
|
hidden: {
|
||||||
|
display: "none",
|
||||||
|
},
|
||||||
|
skeleton: {
|
||||||
|
borderRadius: 9,
|
||||||
|
display: "grid",
|
||||||
|
placeItems: "center",
|
||||||
|
position: "absolute",
|
||||||
|
},
|
||||||
|
wormholeIcon: {
|
||||||
|
height: 48,
|
||||||
|
width: 48,
|
||||||
|
filter: "contrast(0)",
|
||||||
|
transition: "filter 0.5s",
|
||||||
|
"&:hover": {
|
||||||
|
filter: "contrast(1)",
|
||||||
|
},
|
||||||
|
verticalAlign: "middle",
|
||||||
|
marginRight: theme.spacing(1),
|
||||||
|
zIndex: 10,
|
||||||
|
},
|
||||||
|
wormholePositioner: {
|
||||||
|
display: "grid",
|
||||||
|
placeItems: "center",
|
||||||
|
position: "relative",
|
||||||
|
height: "500px",
|
||||||
|
width: "400px",
|
||||||
|
margin: `${theme.spacing(1)}px auto`,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const ViewerLoader = () => {
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.wormholePositioner}>
|
||||||
|
<Skeleton
|
||||||
|
width="400px"
|
||||||
|
height="500px"
|
||||||
|
variant="rect"
|
||||||
|
animation="wave"
|
||||||
|
className={classes.skeleton}
|
||||||
|
/>
|
||||||
|
<img src={Wormhole} alt="Wormhole" className={classes.wormholeIcon} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default function NFTViewer({
|
export default function NFTViewer({
|
||||||
value,
|
value,
|
||||||
chainId,
|
chainId,
|
||||||
|
@ -168,6 +220,7 @@ export default function NFTViewer({
|
||||||
animation_url: value.animation_url,
|
animation_url: value.animation_url,
|
||||||
nftName: value.nftName,
|
nftName: value.nftName,
|
||||||
description: value.description,
|
description: value.description,
|
||||||
|
isLoading: !!uri,
|
||||||
});
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMetadata({
|
setMetadata({
|
||||||
|
@ -175,12 +228,14 @@ export default function NFTViewer({
|
||||||
animation_url: value.animation_url,
|
animation_url: value.animation_url,
|
||||||
nftName: value.nftName,
|
nftName: value.nftName,
|
||||||
description: value.description,
|
description: value.description,
|
||||||
|
isLoading: !!uri,
|
||||||
});
|
});
|
||||||
}, [value]);
|
}, [value, uri]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (uri) {
|
if (uri) {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
(async () => {
|
(async () => {
|
||||||
|
try {
|
||||||
const result = await axios.get(uri);
|
const result = await axios.get(uri);
|
||||||
if (!cancelled && result && result.data) {
|
if (!cancelled && result && result.data) {
|
||||||
setMetadata({
|
setMetadata({
|
||||||
|
@ -188,7 +243,13 @@ export default function NFTViewer({
|
||||||
animation_url: result.data.animation_url,
|
animation_url: result.data.animation_url,
|
||||||
nftName: result.data.name,
|
nftName: result.data.name,
|
||||||
description: result.data.description,
|
description: result.data.description,
|
||||||
|
isLoading: false,
|
||||||
});
|
});
|
||||||
|
} else if (!cancelled) {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -196,6 +257,14 @@ export default function NFTViewer({
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [uri]);
|
}, [uri]);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const onLoad = useCallback(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
}, []);
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
setIsLoading(true);
|
||||||
|
}, [value, chainId]);
|
||||||
|
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const animLower = metadata.animation_url?.toLowerCase();
|
const animLower = metadata.animation_url?.toLowerCase();
|
||||||
// const has3DModel = animLower?.endsWith('gltf') || animLower?.endsWith('glb')
|
// const has3DModel = animLower?.endsWith('gltf') || animLower?.endsWith('glb')
|
||||||
|
@ -212,18 +281,62 @@ export default function NFTViewer({
|
||||||
animLower?.endsWith("flac") ||
|
animLower?.endsWith("flac") ||
|
||||||
animLower?.endsWith("wav") ||
|
animLower?.endsWith("wav") ||
|
||||||
animLower?.endsWith("oga");
|
animLower?.endsWith("oga");
|
||||||
|
const hasImage = metadata.image;
|
||||||
const image = (
|
const image = (
|
||||||
<img
|
<img
|
||||||
src={safeIPFS(metadata.image || "")}
|
src={safeIPFS(metadata.image || "")}
|
||||||
alt={metadata.nftName || ""}
|
alt={metadata.nftName || ""}
|
||||||
style={{ maxWidth: "100%" }}
|
style={{ maxWidth: "100%" }}
|
||||||
|
onLoad={onLoad}
|
||||||
|
onError={onLoad}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const copyTokenId = useCopyToClipboard(value.tokenId || "");
|
const copyTokenId = useCopyToClipboard(value.tokenId || "");
|
||||||
|
|
||||||
|
//report that loading is done, if the item has no reasonable media
|
||||||
|
useEffect(() => {
|
||||||
|
if (!metadata.isLoading && !hasVideo && !hasAudio && !hasImage) {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [metadata.isLoading, hasVideo, hasAudio, hasImage]);
|
||||||
|
|
||||||
|
const media = (
|
||||||
|
<>
|
||||||
|
{hasVideo ? (
|
||||||
|
<video
|
||||||
|
autoPlay
|
||||||
|
controls
|
||||||
|
loop
|
||||||
|
style={{ maxWidth: "100%" }}
|
||||||
|
onLoad={onLoad}
|
||||||
|
onError={onLoad}
|
||||||
|
>
|
||||||
|
<source src={safeIPFS(metadata.animation_url || "")} />
|
||||||
|
{image}
|
||||||
|
</video>
|
||||||
|
) : hasImage ? (
|
||||||
|
image
|
||||||
|
) : null}
|
||||||
|
{hasAudio ? (
|
||||||
|
<audio
|
||||||
|
controls
|
||||||
|
src={safeIPFS(metadata.animation_url || "")}
|
||||||
|
onLoad={onLoad}
|
||||||
|
onError={onLoad}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<div className={!isLoading ? classes.hidden : ""}>
|
||||||
|
<ViewerLoader />
|
||||||
|
</div>
|
||||||
<Card
|
<Card
|
||||||
className={clsx(classes.card, {
|
className={clsx(classes.card, {
|
||||||
[classes.solanaBorder]: chainId === CHAIN_ID_SOLANA,
|
[classes.solanaBorder]: chainId === CHAIN_ID_SOLANA,
|
||||||
|
[classes.hidden]: isLoading,
|
||||||
})}
|
})}
|
||||||
elevation={10}
|
elevation={10}
|
||||||
>
|
>
|
||||||
|
@ -255,17 +368,7 @@ export default function NFTViewer({
|
||||||
[classes.solanaMediaBorder]: chainId === CHAIN_ID_SOLANA,
|
[classes.solanaMediaBorder]: chainId === CHAIN_ID_SOLANA,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{hasVideo ? (
|
{media}
|
||||||
<video autoPlay controls loop style={{ maxWidth: "100%" }}>
|
|
||||||
<source src={safeIPFS(metadata.animation_url || "")} />
|
|
||||||
{image}
|
|
||||||
</video>
|
|
||||||
) : (
|
|
||||||
image
|
|
||||||
)}
|
|
||||||
{hasAudio ? (
|
|
||||||
<audio controls src={safeIPFS(metadata.animation_url || "")} />
|
|
||||||
) : null}
|
|
||||||
</CardMedia>
|
</CardMedia>
|
||||||
<CardContent className={classes.detailsContent}>
|
<CardContent className={classes.detailsContent}>
|
||||||
{metadata.description ? (
|
{metadata.description ? (
|
||||||
|
@ -287,5 +390,6 @@ export default function NFTViewer({
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ export type GenericMetadata = {
|
||||||
tokenName?: string;
|
tokenName?: string;
|
||||||
decimals?: number;
|
decimals?: number;
|
||||||
//TODO more items
|
//TODO more items
|
||||||
|
raw?: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
const constructSolanaMetadata = (
|
const constructSolanaMetadata = (
|
||||||
|
@ -39,6 +40,7 @@ const constructSolanaMetadata = (
|
||||||
logo: tokenInfo?.logoURI || metaplex?.data.uri || undefined, //TODO is URI on metaplex actually the logo? If not, where is it?
|
logo: tokenInfo?.logoURI || metaplex?.data.uri || undefined, //TODO is URI on metaplex actually the logo? If not, where is it?
|
||||||
tokenName: tokenInfo?.name || metaplex?.data.name || undefined,
|
tokenName: tokenInfo?.name || metaplex?.data.name || undefined,
|
||||||
decimals: tokenInfo?.decimals || undefined, //TODO decimals are actually on the mint, not the metaplex account.
|
decimals: tokenInfo?.decimals || undefined, //TODO decimals are actually on the mint, not the metaplex account.
|
||||||
|
raw: metaplex,
|
||||||
};
|
};
|
||||||
data.set(address, obj);
|
data.set(address, obj);
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,264 @@
|
||||||
|
import {
|
||||||
|
ChainId,
|
||||||
|
CHAIN_ID_BSC,
|
||||||
|
CHAIN_ID_ETH,
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
} from "@certusone/wormhole-sdk";
|
||||||
|
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||||
|
import {
|
||||||
|
AccountInfo,
|
||||||
|
Connection,
|
||||||
|
ParsedAccountData,
|
||||||
|
PublicKey,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import axios from "axios";
|
||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
import { DataWrapper } from "../store/helpers";
|
||||||
|
import { NFTParsedTokenAccount } from "../store/nftSlice";
|
||||||
|
import {
|
||||||
|
BSC_NFT_BRIDGE_ADDRESS,
|
||||||
|
COVALENT_GET_TOKENS_URL,
|
||||||
|
ETH_NFT_BRIDGE_ADDRESS,
|
||||||
|
getNFTBridgeAddressForChain,
|
||||||
|
SOLANA_HOST,
|
||||||
|
SOL_NFT_CUSTODY_ADDRESS,
|
||||||
|
} from "../utils/consts";
|
||||||
|
import { Metadata } from "../utils/metaplex";
|
||||||
|
import useMetadata, { GenericMetadata } from "./useMetadata";
|
||||||
|
|
||||||
|
export type NFTTVL = NFTParsedTokenAccount & { chainId: ChainId };
|
||||||
|
|
||||||
|
const calcEvmTVL = (covalentReport: any, chainId: ChainId): NFTTVL[] => {
|
||||||
|
const output: NFTTVL[] = [];
|
||||||
|
if (!covalentReport?.data?.items?.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
covalentReport.data.items.forEach((item: any) => {
|
||||||
|
//TODO remove non nfts
|
||||||
|
if (item.balance > 0 && item.contract_address && item.nft_data) {
|
||||||
|
item.nft_data.forEach((nftData: any) => {
|
||||||
|
if (nftData.token_id) {
|
||||||
|
output.push({
|
||||||
|
amount: item.balance,
|
||||||
|
mintKey: item.contract_address,
|
||||||
|
tokenId: nftData.token_id,
|
||||||
|
publicKey: getNFTBridgeAddressForChain(chainId),
|
||||||
|
decimals: 0,
|
||||||
|
uiAmount: 0,
|
||||||
|
uiAmountString: item.balance.toString(),
|
||||||
|
chainId: chainId,
|
||||||
|
uri: nftData.token_url,
|
||||||
|
animation_url: nftData.external_data?.animation_url,
|
||||||
|
external_url: nftData.external_data?.external_url,
|
||||||
|
image: nftData.external_data?.image,
|
||||||
|
image_256: nftData.external_data?.image_256,
|
||||||
|
nftName: nftData.external_data?.name,
|
||||||
|
description: nftData.external_data?.description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
const calcSolanaTVL = (
|
||||||
|
accounts:
|
||||||
|
| { pubkey: PublicKey; account: AccountInfo<ParsedAccountData> }[]
|
||||||
|
| undefined,
|
||||||
|
metaData: DataWrapper<Map<string, GenericMetadata>>
|
||||||
|
) => {
|
||||||
|
const output: NFTTVL[] = [];
|
||||||
|
if (
|
||||||
|
!accounts ||
|
||||||
|
!accounts.length ||
|
||||||
|
metaData.isFetching ||
|
||||||
|
metaData.error ||
|
||||||
|
!metaData.data
|
||||||
|
) {
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
accounts.forEach((item) => {
|
||||||
|
const genericMetadata = metaData.data?.get(
|
||||||
|
item.account.data.parsed?.info?.mint?.toString()
|
||||||
|
);
|
||||||
|
const raw: Metadata | undefined = genericMetadata?.raw;
|
||||||
|
|
||||||
|
if (
|
||||||
|
item.account.data.parsed?.info?.tokenAmount?.uiAmount > 0 &&
|
||||||
|
item.account.data.parsed?.info?.tokenAmount?.decimals === 0
|
||||||
|
) {
|
||||||
|
output.push({
|
||||||
|
amount: item.account.data.parsed?.info?.tokenAmount?.amount,
|
||||||
|
mintKey: item.account.data.parsed?.info?.mint,
|
||||||
|
publicKey: getNFTBridgeAddressForChain(CHAIN_ID_SOLANA),
|
||||||
|
decimals: 0,
|
||||||
|
uiAmount: 0,
|
||||||
|
uiAmountString:
|
||||||
|
item.account.data.parsed?.info?.tokenAmount?.uiAmountString,
|
||||||
|
chainId: CHAIN_ID_SOLANA,
|
||||||
|
uri: raw?.data?.uri,
|
||||||
|
symbol: raw?.data?.symbol,
|
||||||
|
// external_url: nftData.external_data?.external_url,
|
||||||
|
// image: nftData.external_data?.image,
|
||||||
|
// image_256: nftData.external_data?.image_256,
|
||||||
|
// nftName: nftData.external_data?.name,
|
||||||
|
// description: nftData.external_data?.description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useNFTTVL = (): DataWrapper<NFTTVL[]> => {
|
||||||
|
const [ethCovalentData, setEthCovalentData] = useState(undefined);
|
||||||
|
const [ethCovalentIsLoading, setEthCovalentIsLoading] = useState(false);
|
||||||
|
const [ethCovalentError, setEthCovalentError] = useState("");
|
||||||
|
|
||||||
|
const [bscCovalentData, setBscCovalentData] = useState(undefined);
|
||||||
|
const [bscCovalentIsLoading, setBscCovalentIsLoading] = useState(false);
|
||||||
|
const [bscCovalentError, setBscCovalentError] = useState("");
|
||||||
|
|
||||||
|
const [solanaCustodyTokens, setSolanaCustodyTokens] = useState<
|
||||||
|
{ pubkey: PublicKey; account: AccountInfo<ParsedAccountData> }[] | undefined
|
||||||
|
>(undefined);
|
||||||
|
const [solanaCustodyTokensLoading, setSolanaCustodyTokensLoading] =
|
||||||
|
useState(false);
|
||||||
|
const [solanaCustodyTokensError, setSolanaCustodyTokensError] = useState("");
|
||||||
|
const mintAddresses = useMemo(() => {
|
||||||
|
const addresses: string[] = [];
|
||||||
|
solanaCustodyTokens?.forEach((item) => {
|
||||||
|
const mintKey = item.account.data.parsed?.info?.mint?.toString();
|
||||||
|
if (mintKey) {
|
||||||
|
addresses.push(mintKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return addresses;
|
||||||
|
}, [solanaCustodyTokens]);
|
||||||
|
|
||||||
|
const solanaMetadata = useMetadata(CHAIN_ID_SOLANA, mintAddresses);
|
||||||
|
|
||||||
|
const solanaTVL = useMemo(
|
||||||
|
() => calcSolanaTVL(solanaCustodyTokens, solanaMetadata),
|
||||||
|
[solanaCustodyTokens, solanaMetadata]
|
||||||
|
);
|
||||||
|
const ethTVL = useMemo(
|
||||||
|
() => calcEvmTVL(ethCovalentData, CHAIN_ID_ETH),
|
||||||
|
[ethCovalentData]
|
||||||
|
);
|
||||||
|
const bscTVL = useMemo(
|
||||||
|
() => calcEvmTVL(bscCovalentData, CHAIN_ID_BSC),
|
||||||
|
[bscCovalentData]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
setEthCovalentIsLoading(true);
|
||||||
|
axios
|
||||||
|
.get(
|
||||||
|
COVALENT_GET_TOKENS_URL(
|
||||||
|
CHAIN_ID_ETH,
|
||||||
|
ETH_NFT_BRIDGE_ADDRESS,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
(results) => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setEthCovalentData(results.data);
|
||||||
|
setEthCovalentIsLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setEthCovalentError("Unable to retrieve Ethereum TVL.");
|
||||||
|
setEthCovalentIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
setBscCovalentIsLoading(true);
|
||||||
|
axios
|
||||||
|
.get(
|
||||||
|
COVALENT_GET_TOKENS_URL(
|
||||||
|
CHAIN_ID_BSC,
|
||||||
|
BSC_NFT_BRIDGE_ADDRESS,
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
(results) => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setBscCovalentData(results.data);
|
||||||
|
setBscCovalentIsLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setBscCovalentError("Unable to retrieve BSC TVL.");
|
||||||
|
setBscCovalentIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||||
|
setSolanaCustodyTokensLoading(true);
|
||||||
|
connection
|
||||||
|
.getParsedTokenAccountsByOwner(new PublicKey(SOL_NFT_CUSTODY_ADDRESS), {
|
||||||
|
programId: TOKEN_PROGRAM_ID,
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
(results) => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setSolanaCustodyTokens(results.value);
|
||||||
|
setSolanaCustodyTokensLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setSolanaCustodyTokensLoading(false);
|
||||||
|
setSolanaCustodyTokensError(
|
||||||
|
"Unable to retrieve Solana locked tokens."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
const tvlArray = [...ethTVL, ...bscTVL, ...solanaTVL];
|
||||||
|
|
||||||
|
return {
|
||||||
|
isFetching:
|
||||||
|
ethCovalentIsLoading ||
|
||||||
|
bscCovalentIsLoading ||
|
||||||
|
solanaCustodyTokensLoading,
|
||||||
|
error: ethCovalentError || bscCovalentError || solanaCustodyTokensError,
|
||||||
|
receivedAt: null,
|
||||||
|
data: tvlArray,
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
ethCovalentError,
|
||||||
|
ethCovalentIsLoading,
|
||||||
|
bscCovalentError,
|
||||||
|
bscCovalentIsLoading,
|
||||||
|
ethTVL,
|
||||||
|
bscTVL,
|
||||||
|
solanaTVL,
|
||||||
|
solanaCustodyTokensError,
|
||||||
|
solanaCustodyTokensLoading,
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useNFTTVL;
|
|
@ -267,6 +267,7 @@ const useTVL = (): DataWrapper<TVL[]> => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||||
|
setSolanaCustodyTokensLoading(true);
|
||||||
connection
|
connection
|
||||||
.getParsedTokenAccountsByOwner(new PublicKey(SOL_CUSTODY_ADDRESS), {
|
.getParsedTokenAccountsByOwner(new PublicKey(SOL_CUSTODY_ADDRESS), {
|
||||||
programId: TOKEN_PROGRAM_ID,
|
programId: TOKEN_PROGRAM_ID,
|
||||||
|
|
|
@ -225,6 +225,8 @@ export const SOL_TOKEN_BRIDGE_ADDRESS =
|
||||||
|
|
||||||
export const SOL_CUSTODY_ADDRESS =
|
export const SOL_CUSTODY_ADDRESS =
|
||||||
"GugU1tP7doLeTw9hQP51xRJyS8Da1fWxuiy2rVrnMD2m";
|
"GugU1tP7doLeTw9hQP51xRJyS8Da1fWxuiy2rVrnMD2m";
|
||||||
|
export const SOL_NFT_CUSTODY_ADDRESS =
|
||||||
|
"D63bhHo634eXSj4Jq3xgu2fjB5XKc8DFHzDY9iZk7fv1";
|
||||||
export const TERRA_TEST_TOKEN_ADDRESS =
|
export const TERRA_TEST_TOKEN_ADDRESS =
|
||||||
"terra13nkgqrfymug724h8pprpexqj9h629sa3ncw7sh";
|
"terra13nkgqrfymug724h8pprpexqj9h629sa3ncw7sh";
|
||||||
export const TERRA_BRIDGE_ADDRESS =
|
export const TERRA_BRIDGE_ADDRESS =
|
||||||
|
@ -278,7 +280,8 @@ export const COVALENT_BSC_MAINNET = "56";
|
||||||
export const COVALENT_GET_TOKENS_URL = (
|
export const COVALENT_GET_TOKENS_URL = (
|
||||||
chainId: ChainId,
|
chainId: ChainId,
|
||||||
walletAddress: string,
|
walletAddress: string,
|
||||||
nft?: boolean
|
nft?: boolean,
|
||||||
|
noNftMetadata?: boolean
|
||||||
) => {
|
) => {
|
||||||
const chainNum =
|
const chainNum =
|
||||||
chainId === CHAIN_ID_ETH
|
chainId === CHAIN_ID_ETH
|
||||||
|
@ -289,7 +292,7 @@ export const COVALENT_GET_TOKENS_URL = (
|
||||||
// https://www.covalenthq.com/docs/api/#get-/v1/{chain_id}/address/{address}/balances_v2/
|
// https://www.covalenthq.com/docs/api/#get-/v1/{chain_id}/address/{address}/balances_v2/
|
||||||
return `https://api.covalenthq.com/v1/${chainNum}/address/${walletAddress}/balances_v2/?key=${COVALENT_API_KEY}${
|
return `https://api.covalenthq.com/v1/${chainNum}/address/${walletAddress}/balances_v2/?key=${COVALENT_API_KEY}${
|
||||||
nft ? "&nft=true" : ""
|
nft ? "&nft=true" : ""
|
||||||
}`;
|
}${noNftMetadata ? "&no-nft-fetch=true" : ""}`;
|
||||||
};
|
};
|
||||||
export const TERRA_SWAPRATE_URL =
|
export const TERRA_SWAPRATE_URL =
|
||||||
"https://fcd.terra.dev/v1/market/swaprate/uusd";
|
"https://fcd.terra.dev/v1/market/swaprate/uusd";
|
||||||
|
|
Loading…
Reference in New Issue