Skip to content

Commit

Permalink
dashboard: accountant TVL and TVM
Browse files Browse the repository at this point in the history
  • Loading branch information
evan-gray committed Nov 14, 2023
1 parent f3d73a6 commit b791923
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 4 deletions.
68 changes: 64 additions & 4 deletions dashboard/src/components/Accountant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,15 @@ import {
} from '@wormhole-foundation/wormhole-monitor-common';
import CollapsibleSection from './CollapsibleSection';
import Table from './Table';
import useTokenData, { TokenDataEntry } from '../hooks/useTokenData';
import numeral from 'numeral';

type PendingTransferForAcct = PendingTransfer & { isEnqueuedInGov: boolean };
type AccountWithTokenData = Account & {
tokenData?: TokenDataEntry;
tvlTvm: number;
adjBalance: number;
};

function getNumSignatures(signatures: string) {
let bitfield = Number(signatures);
Expand Down Expand Up @@ -182,7 +189,7 @@ const pendingTransferColumns = [
}),
];

const accountsColumnHelper = createColumnHelper<Account>();
const accountsColumnHelper = createColumnHelper<AccountWithTokenData>();

const accountsColumns = [
accountsColumnHelper.accessor('key.chain_id', {
Expand All @@ -195,11 +202,41 @@ const accountsColumns = [
cell: (info) => `${chainIdToName(info.getValue())} (${info.getValue()})`,
sortingFn: `text`,
}),
accountsColumnHelper.accessor('tokenData.native_address', {
header: () => 'Native Address',
}),
accountsColumnHelper.accessor('tokenData.name', {
header: () => 'Name',
}),
accountsColumnHelper.accessor('tokenData.symbol', {
header: () => 'Symbol',
}),
accountsColumnHelper.accessor('tokenData.price_usd', {
header: () => 'Price',
cell: (info) => (info.getValue() ? numeral(info.getValue()).format('$0,0.0000') : ''),
}),
accountsColumnHelper.accessor('adjBalance', {
header: () => 'Adjusted Balance',
cell: (info) =>
info.getValue() < 1
? info.getValue().toFixed(4)
: numeral(info.getValue()).format('0,0.0000'),
}),
accountsColumnHelper.accessor('tvlTvm', {
header: () => 'TVL/TVM',
cell: (info) =>
info.getValue() < 1
? `$${info.getValue().toFixed(4)}`
: numeral(info.getValue()).format('$0,0.0000'),
}),
accountsColumnHelper.accessor('tokenData.decimals', {
header: () => 'Decimals',
}),
accountsColumnHelper.accessor('key.token_address', {
header: () => 'Token Address',
}),
accountsColumnHelper.accessor('balance', {
header: () => 'Balance',
header: () => 'Raw Balance',
}),
];

Expand All @@ -208,6 +245,8 @@ function Accountant({ governorInfo }: { governorInfo: CloudGovernorInfo }) {

const accountsInfo = useGetAccountantAccounts();

const tokenData = useTokenData();

const pendingTransfersForAcct: PendingTransferForAcct[] = useMemo(
() =>
pendingTransferInfo.map((transfer) => ({
Expand Down Expand Up @@ -238,6 +277,27 @@ function Accountant({ governorInfo }: { governorInfo: CloudGovernorInfo }) {
}
return stats;
}, [pendingTransferInfo]);

const accountsWithTokenData: AccountWithTokenData[] = useMemo(() => {
return accountsInfo.map<AccountWithTokenData>((a) => {
const thisTokenData = tokenData?.[`${a.key.token_chain}/${a.key.token_address}`];
if (!thisTokenData)
return {
...a,
adjBalance: 0,
tvlTvm: 0,
};
const adjBalance = Number(a.balance) / 10 ** thisTokenData.decimals;
const tvlTvm = adjBalance * Number(thisTokenData.price_usd);
return {
...a,
tokenData: thisTokenData,
adjBalance,
tvlTvm,
};
});
}, [accountsInfo, tokenData]);

const [guardianSigningSorting, setGuardianSigningSorting] = useState<SortingState>([]);
const guardianSigning = useReactTable({
columns: guardianSigningColumns,
Expand Down Expand Up @@ -267,7 +327,7 @@ function Accountant({ governorInfo }: { governorInfo: CloudGovernorInfo }) {
const [accountsSorting, setAccountsSorting] = useState<SortingState>([]);
const accounts = useReactTable({
columns: accountsColumns,
data: accountsInfo,
data: accountsWithTokenData,
state: {
sorting: accountsSorting,
},
Expand Down Expand Up @@ -359,7 +419,7 @@ function Accountant({ governorInfo }: { governorInfo: CloudGovernorInfo }) {
<Typography>Accounts ({accountsInfo.length})</Typography>
</AccordionSummary>
<AccordionDetails>
<Table<Account> table={accounts} paginated />
<Table<AccountWithTokenData> table={accounts} paginated />
</AccordionDetails>
</Accordion>
</Card>
Expand Down
45 changes: 45 additions & 0 deletions dashboard/src/hooks/useTokenData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import axios from 'axios';
import { useEffect, useState } from 'react';

export type TokenDataEntry = {
token_chain: number;
token_address: string;
native_address: string;
coin_gecko_coin_id: string;
decimals: number;
symbol: string;
name: string;
price_usd: string;
};

export type TokenDataByChainAddress = {
[chainAddress: string]: TokenDataEntry;
};

function useTokenData(): TokenDataByChainAddress | null {
const [tokenData, setTokenData] = useState<TokenDataByChainAddress | null>(null);
useEffect(() => {
let cancelled = false;
(async () => {
while (!cancelled) {
const response = await axios.get<{ data: TokenDataEntry[] }>(
'https://europe-west3-wormhole-message-db-mainnet.cloudfunctions.net/latest-tokendata'
);
if (!cancelled) {
setTokenData(
response.data?.data.reduce<TokenDataByChainAddress>((obj, tokenData) => {
obj[`${tokenData.token_chain}/${tokenData.token_address}`] = tokenData;
return obj;
}, {}) || null
);
await new Promise((resolve) => setTimeout(resolve, 60000));
}
}
})();
return () => {
cancelled = true;
};
}, []);
return tokenData;
}
export default useTokenData;

0 comments on commit b791923

Please sign in to comment.