diff --git a/cloud_functions/src/computeNTTRateLimits.ts b/cloud_functions/src/computeNTTRateLimits.ts index bd0ea053..70226674 100644 --- a/cloud_functions/src/computeNTTRateLimits.ts +++ b/cloud_functions/src/computeNTTRateLimits.ts @@ -65,35 +65,42 @@ async function computeNTTRateLimits_( tokenDecimals = await getEvmTokenDecimals(rpcEndpoint, managerContract); } - const outboundCapacity = await ntt.getCurrentOutboundCapacity(); - const normalizedOutboundCapacity = outboundCapacity / BigInt(10 ** tokenDecimals); - const inboundChains = NTT_SUPPORTED_CHAINS(network, token).filter( (inboundChain) => inboundChain !== chain ); - let totalInboundCapacity = BigInt(0); + let totalInboundCapacity = 0n; const inboundRateLimits = await Promise.all( inboundChains.map(async (inboundChain): Promise => { const inboundCapacity = await ntt.getCurrentInboundCapacity(inboundChain); - const normalizedInboundCapacity = inboundCapacity / BigInt(10 ** tokenDecimals); - totalInboundCapacity += normalizedInboundCapacity; + totalInboundCapacity += inboundCapacity; return { tokenName: token, srcChain: chainToChainId(inboundChain), destChain: chainToChainId(chain), - amount: normalizedInboundCapacity.toString(), + amount: { + amount: inboundCapacity.toString(), + decimals: tokenDecimals, + }, }; }) ); + const outboundCapacity = await ntt.getCurrentOutboundCapacity(); + return { tokenName: token, srcChain: chainToChainId(chain), - amount: normalizedOutboundCapacity.toString(), + amount: { + amount: outboundCapacity.toString(), + decimals: tokenDecimals, + }, inboundCapacity: inboundRateLimits, - totalInboundCapacity: totalInboundCapacity.toString(), + totalInboundCapacity: { + amount: totalInboundCapacity.toString(), + decimals: tokenDecimals, + }, }; } diff --git a/cloud_functions/src/computeTotalSupplyAndLocked.ts b/cloud_functions/src/computeTotalSupplyAndLocked.ts index 22899370..e7846a0b 100644 --- a/cloud_functions/src/computeTotalSupplyAndLocked.ts +++ b/cloud_functions/src/computeTotalSupplyAndLocked.ts @@ -5,6 +5,7 @@ import { derivePda, getEvmTokenDecimals, getEvmTotalSupply, + normalizeToDecimals, } from '@wormhole-foundation/wormhole-monitor-common'; import { PublicKey } from '@solana/web3.js'; import { @@ -25,23 +26,6 @@ const cacheBucket = storage.bucket(bucketName); const cacheFileName = 'ntt-total-supply-and-locked.json'; const cloudStorageCache = cacheBucket.file(cacheFileName); -async function getEvmNormalizedTotalSupply( - network: Network, - token: string, - chain: Chain -): Promise { - const tokenDecimals = await getEvmTokenDecimals( - rpc.rpcAddress(network, chain), - NTT_MANAGER_CONTRACT[network][token][chain]! - ); - const tokenSupply = await getEvmTotalSupply( - rpc.rpcAddress(network, chain), - NTT_TOKENS[network][token][chain]! - ); - - return tokenSupply / 10 ** tokenDecimals; -} - async function fetchTotalSupplyAndLocked(network: Network): Promise { const tokens = NTT_MANAGER_CONTRACT[network]; const totalSupplyVsLocked: NTTTotalSupplyAndLockedData[] = []; @@ -51,32 +35,49 @@ async function fetchTotalSupplyAndLocked(network: Network): Promise { +export async function getEvmTotalSupply(rpc: string, contractAddress: string): Promise { const methodId = getMethodId('totalSupply()'); const result = await callContractMethod(rpc, contractAddress, methodId); - return Number(result); + return BigInt(result); } diff --git a/common/src/solana.ts b/common/src/solana.ts index 4bd2d5c7..791ea355 100644 --- a/common/src/solana.ts +++ b/common/src/solana.ts @@ -5,10 +5,11 @@ import { MessageV0, PublicKeyInitData, PublicKey, -} from '@solana/web3.js'; // NOTE: types only for bundling size -import { decode } from 'bs58'; +} from '@solana/web3.js'; import axios from 'axios'; +import { decode } from 'bs58'; import { encoding } from '@wormhole-foundation/sdk-base'; +import { TokenAmount } from './types'; export const isLegacyMessage = (message: Message | MessageV0): message is Message => { return message.version === 'legacy'; @@ -72,12 +73,18 @@ export async function getCustody(rpcUrl: string, programAddress: string): Promis return pubkey.toString(); } -export async function getCustodyAmount(rpcUrl: string, programAddress: string): Promise { +export async function getCustodyAmount( + rpcUrl: string, + programAddress: string +): Promise { const accountInfo = await makeRpcCall(rpcUrl, 'getAccountInfo', [programAddress], 'jsonParsed'); if (!accountInfo.value?.data?.parsed?.info?.tokenAmount?.uiAmount) { throw new Error('Custody amount not found or missing data'); } - return Number(accountInfo.value.data.parsed.info.tokenAmount.uiAmount); + return { + amount: accountInfo.value.data.parsed.info.tokenAmount.amount, + decimals: accountInfo.value.data.parsed.info.tokenAmount.decimals, + }; } // Helper function to make JSON-RPC requests diff --git a/common/src/types.ts b/common/src/types.ts index f4c50e33..d2429bda 100644 --- a/common/src/types.ts +++ b/common/src/types.ts @@ -1,10 +1,36 @@ import { ChainId } from '@wormhole-foundation/sdk-base'; +export type TokenAmount = { + amount: string; + decimals: number; +}; + +export function normalizeToDecimals(tokenAmount: TokenAmount, targetDecimals: number): bigint { + const { amount, decimals } = tokenAmount; + const bigIntAmount = BigInt(amount); + let normalizedAmount: bigint; + + if (decimals < targetDecimals) { + // If less decimals, multiply to shift the decimal point to the right + const factor = BigInt(10 ** (targetDecimals - decimals)); + normalizedAmount = bigIntAmount * factor; + } else if (decimals > targetDecimals) { + // If more decimals, divide to shift the decimal point to the left + const factor = BigInt(10 ** (decimals - targetDecimals)); + normalizedAmount = bigIntAmount / factor; + } else { + normalizedAmount = bigIntAmount; + } + + return normalizedAmount; +} + export type NTTTotalSupplyAndLockedData = { tokenName: string; chain: ChainId; - amountLocked?: Number; - totalSupply: Number; + // this is bigint but for the sake of precision we are using string + amountLocked?: TokenAmount; + totalSupply?: TokenAmount; evmTotalSupply?: NTTTotalSupplyAndLockedData[]; }; @@ -12,7 +38,7 @@ export type NTTRateLimit = { tokenName: string; srcChain?: ChainId; destChain?: ChainId; - amount?: string; - totalInboundCapacity?: string; + amount?: TokenAmount; + totalInboundCapacity?: TokenAmount; inboundCapacity?: NTTRateLimit[]; }; diff --git a/dashboard/src/components/NTTMetrics.tsx b/dashboard/src/components/NTTMetrics.tsx index 0b5b3824..ddca854d 100644 --- a/dashboard/src/components/NTTMetrics.tsx +++ b/dashboard/src/components/NTTMetrics.tsx @@ -1,6 +1,5 @@ -import { Box, Divider } from '@mui/material'; +import { Divider } from '@mui/material'; import { useNetworkContext } from '../contexts/NetworkContext'; -import CollapsibleSection from './CollapsibleSection'; import { LookerDashboard } from './LookerDashboard'; import { NTTRateLimits } from './NTTRateLimits'; import { NTTTotalSupplyAndLocked } from './NTTTotalSupplyAndLocked'; @@ -16,39 +15,9 @@ function NTTMetrics() { hasTabs /> - - Rate Limit Capacity - - } - > - - + - - Total Supply and Locked - - } - > - - + ); } @@ -60,39 +29,9 @@ function NTTMetrics() { src="https://lookerstudio.google.com/embed/reporting/a47057a8-15a0-4cc7-8086-eb00f5d09d2a/page/SPpuD" /> - - Rate Limit Capacity - - } - > - - + - - Total Supply and Locked - - } - > - - + ); } diff --git a/dashboard/src/components/NTTRateLimits.tsx b/dashboard/src/components/NTTRateLimits.tsx index 79ff91b6..adb1bb2b 100644 --- a/dashboard/src/components/NTTRateLimits.tsx +++ b/dashboard/src/components/NTTRateLimits.tsx @@ -14,6 +14,8 @@ import { useState } from 'react'; import { useNetworkContext } from '../contexts/NetworkContext'; import { KeyboardArrowDown, KeyboardArrowRight } from '@mui/icons-material'; import { NTTRateLimit, chainIdToName } from '@wormhole-foundation/wormhole-monitor-common'; +import CollapsibleSection from './CollapsibleSection'; +import { normalizeBigNumber } from '../utils/normalizeBigNumber'; const rateLimitColumnHelper = createColumnHelper(); @@ -82,7 +84,7 @@ const rateLimitColumns = [ ? info.row.original.destChain ? null : info.row.original.amount - ? `${BigInt(info.row.original.amount).toLocaleString()}` + ? `${normalizeBigNumber(info.row.original.amount, 2)}` : null : null} @@ -94,8 +96,8 @@ const rateLimitColumns = [ {info.row.original.srcChain ? info.row.original.destChain - ? `${BigInt(info.row.original.amount!).toLocaleString()}` - : `${BigInt(info.row.original.totalInboundCapacity!).toLocaleString()}` + ? `${normalizeBigNumber(info.row.original.amount, 2)}` + : `${normalizeBigNumber(info.row.original.totalInboundCapacity, 2)}` : null} ), @@ -126,10 +128,25 @@ export function NTTRateLimits() { }); return ( - - - table={table} /> - - + + Rate Limit Capacity + + } + > + + + table={table} /> + + + ); } diff --git a/dashboard/src/components/NTTTotalSupplyAndLocked.tsx b/dashboard/src/components/NTTTotalSupplyAndLocked.tsx index a0d58446..059584b9 100644 --- a/dashboard/src/components/NTTTotalSupplyAndLocked.tsx +++ b/dashboard/src/components/NTTTotalSupplyAndLocked.tsx @@ -15,6 +15,8 @@ import { KeyboardArrowDown, KeyboardArrowRight } from '@mui/icons-material'; import { chainIdToName } from '@wormhole-foundation/wormhole-monitor-common'; import { useTotalSupplyAndLocked } from '../hooks/useTotalSupplyAndLocked'; import { NTTTotalSupplyAndLockedData } from '@wormhole-foundation/wormhole-monitor-common'; +import CollapsibleSection from './CollapsibleSection'; +import { normalizeBigNumber } from '../utils/normalizeBigNumber'; const totalSupplyAndLockedColumnHelper = createColumnHelper(); const rateLimitColumns = [ @@ -53,13 +55,17 @@ const rateLimitColumns = [ header: () => Locked, cell: (info) => ( - {info.row.original.evmTotalSupply ? info.row.original.amountLocked?.toLocaleString() : null} + {info.row.original.evmTotalSupply + ? normalizeBigNumber(info.row.original.amountLocked, 2) + : null} ), }), totalSupplyAndLockedColumnHelper.accessor('totalSupply', { header: () => Total EVM Supply, - cell: (info) => {info.row.original.totalSupply.toLocaleString()}, + cell: (info) => ( + {normalizeBigNumber(info.row.original.totalSupply, 2)} + ), }), ]; @@ -90,10 +96,25 @@ export function NTTTotalSupplyAndLocked() { }); return ( - - - table={table} /> - - + + Total Supply and Locked + + } + > + + + table={table} /> + + + ); } diff --git a/dashboard/src/utils/normalizeBigNumber.ts b/dashboard/src/utils/normalizeBigNumber.ts new file mode 100644 index 00000000..95e25c89 --- /dev/null +++ b/dashboard/src/utils/normalizeBigNumber.ts @@ -0,0 +1,20 @@ +import { TokenAmount } from '@wormhole-foundation/wormhole-monitor-common'; + +export function normalizeBigNumber( + tokenAmount: TokenAmount | undefined, + decimalPlaces: number +): string { + if (!tokenAmount) { + return '0'; + } + + const bigIntValue = BigInt(tokenAmount.amount); + const divisor = BigInt(10 ** tokenAmount.decimals); + const integerPart = bigIntValue / divisor; + const fractionalPart = bigIntValue % divisor; + + // Pad the fractional part with leading zeros if necessary + const fractionalString = fractionalPart.toString().padStart(tokenAmount.decimals, '0'); + + return `${integerPart.toLocaleString()}.${fractionalString.slice(0, decimalPlaces)}`; +} diff --git a/package-lock.json b/package-lock.json index 2e091dc8..faba0754 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,6 @@ "@google-cloud/storage": "^6.8.0", "@solana/web3.js": "^1.87.3", "@wormhole-foundation/sdk-base": "^0.6.2", - "@wormhole-foundation/sdk-definitions": "^0.6.2", "@wormhole-foundation/sdk-evm-ntt": "^0.0.1-beta.4", "@wormhole-foundation/sdk-solana": "^0.6.2", "@wormhole-foundation/sdk-solana-ntt": "^0.0.1-beta.4", @@ -31805,7 +31804,6 @@ "@google-cloud/storage": "^6.8.0", "@solana/web3.js": "^1.87.3", "@wormhole-foundation/sdk-base": "^0.6.2", - "@wormhole-foundation/sdk-definitions": "^0.6.2", "@wormhole-foundation/sdk-evm-ntt": "^0.0.1-beta.4", "@wormhole-foundation/sdk-solana": "^0.6.2", "@wormhole-foundation/sdk-solana-ntt": "^0.0.1-beta.4",