mirror of https://github.com/certusone/oyster.git
fix: icons
This commit is contained in:
parent
de293017fd
commit
f7eef7c3b6
|
@ -6,10 +6,9 @@ import { useEthereum } from '../../contexts';
|
||||||
import { ASSET_CHAIN } from "../../models/bridge/constants";
|
import { ASSET_CHAIN } from "../../models/bridge/constants";
|
||||||
import './style.less';
|
import './style.less';
|
||||||
|
|
||||||
export const TokenDisplay = ({ asset, chain, token }: { asset?: string, chain?: ASSET_CHAIN, token?: TokenInfo }) => {
|
export const TokenDisplay = ({ asset, chain, token, logo }: { asset?: string, chain?: ASSET_CHAIN, token?: TokenInfo, logo?: string }) => {
|
||||||
|
|
||||||
return <div className="token-chain-logo">
|
return <div className="token-chain-logo">
|
||||||
<img className="token-logo" alt="" src={token?.logoURI} />
|
<img className="token-logo" alt="" src={logo || token?.logoURI} />
|
||||||
<img className="chain-logo" alt="" src={chain === ASSET_CHAIN.Ethereum ? '/blockchains/ETH.svg' : '/blockchains/solana.webp'} />
|
<img className="chain-logo" alt="" src={chain === ASSET_CHAIN.Ethereum ? '/blockchains/ETH.svg' : '/blockchains/solana.webp'} />
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
.token-chain-logo {
|
.token-chain-logo {
|
||||||
position: relative
|
position: relative;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.token-logo {
|
.token-logo {
|
||||||
|
@ -11,12 +14,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.chain-logo {
|
.chain-logo {
|
||||||
order: 1px solid hsla(0, 0%, 50.2%, 0.5);
|
border: 1px solid hsla(0, 0%, 50.2%, 0.5);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
bottom: 9px;
|
bottom: 3px;
|
||||||
right: -5px;
|
right: 3px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,6 @@ export const EthereumProvider: FunctionComponent = ({children}) => {
|
||||||
const signer = provider.getSigner();
|
const signer = provider.getSigner();
|
||||||
signer.getAddress().then(account => setAccounts([account]));
|
signer.getAddress().then(account => setAccounts([account]));
|
||||||
|
|
||||||
|
|
||||||
setProvider(provider);
|
setProvider(provider);
|
||||||
}
|
}
|
||||||
}, [connected])
|
}, [connected])
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import {useCallback, useEffect, useRef, useState} from "react";
|
import {useCallback, useEffect, useRef, useState} from "react";
|
||||||
import { useConnection, useConnectionConfig, MintParser, cache, getMultipleAccounts, ParsedAccount, TokenAccountParser, programIds} from "@oyster/common";
|
import { useConnection, useConnectionConfig, MintParser, cache, getMultipleAccounts, ParsedAccount, TokenAccountParser, programIds, formatTokenAmount, fromLamports} from "@oyster/common";
|
||||||
import {WORMHOLE_PROGRAM_ID} from "../utils/ids";
|
import {WORMHOLE_PROGRAM_ID} from "../utils/ids";
|
||||||
import {ASSET_CHAIN} from "../utils/assets";
|
import {ASSET_CHAIN} from "../utils/assets";
|
||||||
import { useEthereum } from "../contexts";
|
import { useEthereum } from "../contexts";
|
||||||
import { Connection, PublicKey } from "@solana/web3.js";
|
import { Connection, PublicKey } from "@solana/web3.js";
|
||||||
import { models } from "@oyster/common";
|
import { models } from "@oyster/common";
|
||||||
import { MintInfo } from "@solana/spl-token";
|
import { AccountInfo, MintInfo } from "@solana/spl-token";
|
||||||
import { bridgeAuthorityKey, wrappedAssetMintKey, WrappedMetaLayout } from './../models/bridge';
|
import { bridgeAuthorityKey, wrappedAssetMintKey, WrappedMetaLayout } from './../models/bridge';
|
||||||
|
|
||||||
import bs58 from "bs58";
|
import bs58 from "bs58";
|
||||||
|
@ -30,11 +30,11 @@ type WrappedAssetMeta = {
|
||||||
|
|
||||||
|
|
||||||
const queryWrappedMetaAccounts = async (
|
const queryWrappedMetaAccounts = async (
|
||||||
|
authorityKey: PublicKey,
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
setExternalAssets: (arr: WrappedAssetMeta[]) => void
|
setExternalAssets: (arr: WrappedAssetMeta[]) => void
|
||||||
) => {
|
) => {
|
||||||
// authority -> query for token accounts to get locked assets
|
|
||||||
let authorityKey = await bridgeAuthorityKey(programIds().wormhole.pubkey);
|
|
||||||
|
|
||||||
const filters = [
|
const filters = [
|
||||||
{
|
{
|
||||||
|
@ -81,6 +81,8 @@ const queryWrappedMetaAccounts = async (
|
||||||
mintKey: '',
|
mintKey: '',
|
||||||
amount: 0,
|
amount: 0,
|
||||||
amountInUSD: 0,
|
amountInUSD: 0,
|
||||||
|
// TODO: customize per chain
|
||||||
|
explorer: `https://etherscan.io/address/0x${assetAddress}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,6 +126,7 @@ const queryWrappedMetaAccounts = async (
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
asset.mint = cache.get(key);
|
asset.mint = cache.get(key);
|
||||||
|
asset.wrappedExplorer = `https://explorer.solana.com/address/${asset.mintKey}`;
|
||||||
|
|
||||||
if(asset.mint) {
|
if(asset.mint) {
|
||||||
|
|
||||||
|
@ -139,17 +142,48 @@ const queryWrappedMetaAccounts = async (
|
||||||
asset.mint = cache.get(key);
|
asset.mint = cache.get(key);
|
||||||
asset.amount = asset.mint?.info.supply.toNumber() || 0;
|
asset.amount = asset.mint?.info.supply.toNumber() || 0;
|
||||||
|
|
||||||
setExternalAssets([...assets.values()]
|
setExternalAssets([...assets.values()]);
|
||||||
.sort((a, b) => a?.symbol?.localeCompare(b.symbol || '') || 0));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setExternalAssets([...assets.values()]
|
setExternalAssets([...assets.values()]);
|
||||||
.sort((a, b) => a?.symbol?.localeCompare(b.symbol || '') || 0));
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const queryCustodyAccounts = async (authorityKey: PublicKey, connection: Connection) => {
|
||||||
|
debugger;
|
||||||
|
const tokenAccounts = await connection.getTokenAccountsByOwner(
|
||||||
|
authorityKey,
|
||||||
|
{
|
||||||
|
programId: programIds().token,
|
||||||
|
}).then(acc => acc.value.map(a => cache.add(a.pubkey, a.account, TokenAccountParser) as ParsedAccount<AccountInfo>));
|
||||||
|
|
||||||
|
// query for mints
|
||||||
|
await getMultipleAccounts(connection, tokenAccounts.map(a => a.info.mint.toBase58()), 'single').then(({ keys, array }) => {
|
||||||
|
keys.forEach((key, index) => {
|
||||||
|
if(!array[index]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache.add(key, array[index], MintParser);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
return tokenAccounts.map(token => {
|
||||||
|
const mint = cache.get(token.info.mint) as ParsedAccount<MintInfo>;
|
||||||
|
const asset = mint.pubkey.toBase58()
|
||||||
|
return {
|
||||||
|
address: asset,
|
||||||
|
chain: ASSET_CHAIN.Solana,
|
||||||
|
amount: fromLamports(token, mint.info),
|
||||||
|
mintKey: asset,
|
||||||
|
mint,
|
||||||
|
decimals: 9,
|
||||||
|
amountInUSD: 0,
|
||||||
|
explorer: `https://explorer.solana.com/address/${asset}`,
|
||||||
|
} as WrappedAssetMeta;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
export const useWormholeAccounts = () => {
|
export const useWormholeAccounts = () => {
|
||||||
const connection = useConnection();
|
const connection = useConnection();
|
||||||
|
@ -163,33 +197,35 @@ export const useWormholeAccounts = () => {
|
||||||
const [externalAssets, setExternalAssets] = useState<WrappedAssetMeta[]>([]);
|
const [externalAssets, setExternalAssets] = useState<WrappedAssetMeta[]>([]);
|
||||||
const [amountInUSD, setAmountInUSD] = useState<number>(0);
|
const [amountInUSD, setAmountInUSD] = useState<number>(0);
|
||||||
|
|
||||||
/// TODO:
|
|
||||||
/// assets that left Solana
|
|
||||||
// 1. getTokenAccountsByOwner with bridge PDA
|
|
||||||
// 2. get prices from serum ?
|
|
||||||
// 3. multiply account balances by
|
|
||||||
|
|
||||||
/// assets locked from ETH
|
|
||||||
// 1. get asset address from proposal [x]
|
|
||||||
// 2. find the asset in the coingecko list or call abi? []
|
|
||||||
// 3. build mint address using PDA [x]
|
|
||||||
// 4. query all mints [x]
|
|
||||||
// 5. multiply mint supply by asset price from coingecko [x]
|
|
||||||
// 6. aggregate all assets [x]
|
|
||||||
// 7. subscribe to program accounts
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
queryWrappedMetaAccounts(connection, setExternalAssets).then(() => setLoading(false));
|
let wormholeSubId = 0;
|
||||||
|
(async () => {
|
||||||
|
// authority -> query for token accounts to get locked assets
|
||||||
|
let authorityKey = await bridgeAuthorityKey(programIds().wormhole.pubkey);
|
||||||
|
|
||||||
const subId = connection.onProgramAccountChange(WORMHOLE_PROGRAM_ID, (info) => {
|
// get all accounts that moved assets from solana to other chains
|
||||||
|
const custodyAccounts = await queryCustodyAccounts(authorityKey, connection);
|
||||||
|
|
||||||
|
// query wrapped assets that were imported to solana from other chains
|
||||||
|
queryWrappedMetaAccounts(authorityKey, connection, (assets) => {
|
||||||
|
setExternalAssets([...custodyAccounts, ...assets]
|
||||||
|
.sort((a, b) => a?.symbol?.localeCompare(b.symbol || '') || 0))
|
||||||
|
}).then(() => setLoading(false));
|
||||||
|
|
||||||
|
// TODO: listen to solana accounts for updates
|
||||||
|
|
||||||
|
wormholeSubId = connection.onProgramAccountChange(WORMHOLE_PROGRAM_ID, (info) => {
|
||||||
if (info.accountInfo.data.length === WrappedMetaLayout.span) {
|
if (info.accountInfo.data.length === WrappedMetaLayout.span) {
|
||||||
// TODO: check if new account and update external assets
|
// TODO: check if new account and update external assets
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
connection.removeProgramAccountChangeListener(subId);
|
connection.removeProgramAccountChangeListener(wormholeSubId);
|
||||||
};
|
};
|
||||||
}, [connection, setExternalAssets]);
|
}, [connection, setExternalAssets]);
|
||||||
|
|
||||||
|
@ -200,14 +236,13 @@ export const useWormholeAccounts = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const addressToId = new Map<string, string>();
|
const addressToId = new Map<string, string>();
|
||||||
const idToAsset = new Map<string, WrappedAssetMeta>();
|
const idToAsset = new Map<string, WrappedAssetMeta[]>();
|
||||||
|
|
||||||
const assetsToQueryNames: WrappedAssetMeta[] = [];
|
const assetsToQueryNames: WrappedAssetMeta[] = [];
|
||||||
|
|
||||||
const ids = externalAssets.map(asset => {
|
const ids = externalAssets.map(asset => {
|
||||||
// TODO: add different nets/clusters
|
// TODO: add different nets/clusters
|
||||||
asset.explorer = `https://etherscan.io/address/0x${asset.address}`;
|
|
||||||
asset.wrappedExplorer = `https://explorer.solana.com/address/${asset.mintKey}`;
|
|
||||||
|
|
||||||
let knownToken = tokenMap.get(asset.mintKey);
|
let knownToken = tokenMap.get(asset.mintKey);
|
||||||
if (knownToken) {
|
if (knownToken) {
|
||||||
|
@ -227,7 +262,7 @@ export const useWormholeAccounts = () => {
|
||||||
let coinInfo = coinList.get(asset.symbol.toLowerCase());
|
let coinInfo = coinList.get(asset.symbol.toLowerCase());
|
||||||
|
|
||||||
if(coinInfo) {
|
if(coinInfo) {
|
||||||
idToAsset.set(coinInfo.id, asset);
|
idToAsset.set(coinInfo.id, [...(idToAsset.get(coinInfo.id) || []), asset]);
|
||||||
addressToId.set(asset.address, coinInfo.id);
|
addressToId.set(asset.address, coinInfo.id);
|
||||||
return coinInfo.id;
|
return coinInfo.id;
|
||||||
}
|
}
|
||||||
|
@ -248,15 +283,18 @@ export const useWormholeAccounts = () => {
|
||||||
let totalInUSD = 0;
|
let totalInUSD = 0;
|
||||||
|
|
||||||
Object.keys(data).forEach(key => {
|
Object.keys(data).forEach(key => {
|
||||||
let asset = idToAsset.get(key);
|
let assets = idToAsset.get(key);
|
||||||
if(!asset) {
|
|
||||||
|
if(!assets) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assets.forEach(asset => {
|
||||||
asset.price = data[key]?.usd || 1;
|
asset.price = data[key]?.usd || 1;
|
||||||
asset.amountInUSD = Math.round(asset.amount * (asset.price || 1) * 100) / 100;
|
asset.amountInUSD = Math.round(asset.amount * (asset.price || 1) * 100) / 100;
|
||||||
totalInUSD += asset.amountInUSD;
|
totalInUSD += asset.amountInUSD;
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
setAmountInUSD(totalInUSD);
|
setAmountInUSD(totalInUSD);
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
|
|
||||||
import { Table, Col, Row, Statistic, Button } from 'antd';
|
import { Table, Col, Row, Statistic, Button } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { GUTTER, LABELS } from '../../constants';
|
import { GUTTER } from '../../constants';
|
||||||
import { formatNumber, formatTokenAmount, formatUSD, shortenAddress} from '@oyster/common';
|
import { formatNumber, formatUSD, shortenAddress} from '@oyster/common';
|
||||||
import './itemStyle.less';
|
import './itemStyle.less';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import {useWormholeAccounts} from "../../hooks/useWormholeAccounts";
|
import {useWormholeAccounts} from "../../hooks/useWormholeAccounts";
|
||||||
|
import { TokenDisplay } from '../../components/TokenDisplay';
|
||||||
|
|
||||||
export const HomeView = () => {
|
export const HomeView = () => {
|
||||||
const {
|
const {
|
||||||
|
@ -14,7 +15,6 @@ export const HomeView = () => {
|
||||||
totalInUSD
|
totalInUSD
|
||||||
} = useWormholeAccounts();
|
} = useWormholeAccounts();
|
||||||
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: 'Symbol',
|
title: 'Symbol',
|
||||||
|
@ -26,7 +26,7 @@ export const HomeView = () => {
|
||||||
style: {},
|
style: {},
|
||||||
},
|
},
|
||||||
children: (
|
children: (
|
||||||
<span>{record.logo && <img style={{ width: 30, height: 30, margin: 4 }} src={record.logo} />} {record.symbol}</span>
|
<span style={{ display: 'inline-flex', alignItems: 'center' }}>{record.logo && <TokenDisplay logo={record.logo} chain={record.chain} />} {record.symbol}</span>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -187,52 +187,7 @@ export const cache = {
|
||||||
}
|
}
|
||||||
|
|
||||||
return pubkey;
|
return pubkey;
|
||||||
},
|
|
||||||
queryMint: async (connection: Connection, pubKey: string | PublicKey) => {
|
|
||||||
let id: PublicKey;
|
|
||||||
if (typeof pubKey === 'string') {
|
|
||||||
id = new PublicKey(pubKey);
|
|
||||||
} else {
|
|
||||||
id = pubKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const address = id.toBase58();
|
|
||||||
let mint = mintCache.get(address);
|
|
||||||
if (mint) {
|
|
||||||
return mint;
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = pendingMintCalls.get(address);
|
|
||||||
if (query) {
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
query = getMintInfo(connection, id).then((data) => {
|
|
||||||
pendingMintCalls.delete(address);
|
|
||||||
|
|
||||||
mintCache.set(address, data);
|
|
||||||
return data;
|
|
||||||
}) as Promise<MintInfo>;
|
|
||||||
pendingMintCalls.set(address, query as any);
|
|
||||||
|
|
||||||
return query;
|
|
||||||
},
|
|
||||||
getMint: (pubKey: string | PublicKey) => {
|
|
||||||
let key: string;
|
|
||||||
if (typeof pubKey !== 'string') {
|
|
||||||
key = pubKey.toBase58();
|
|
||||||
} else {
|
|
||||||
key = pubKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mintCache.get(key);
|
|
||||||
},
|
|
||||||
addMint: (pubKey: PublicKey, obj: AccountInfo<Buffer>) => {
|
|
||||||
const mint = deserializeMint(obj.data);
|
|
||||||
const id = pubKey.toBase58();
|
|
||||||
mintCache.set(id, mint);
|
|
||||||
return mint;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAccountsContext = () => {
|
export const useAccountsContext = () => {
|
||||||
|
|
Loading…
Reference in New Issue