diff --git a/cloud_functions/src/getNTTRateLimits.ts b/cloud_functions/src/getNTTRateLimits.ts new file mode 100644 index 00000000..13a012ed --- /dev/null +++ b/cloud_functions/src/getNTTRateLimits.ts @@ -0,0 +1,130 @@ +import { + assertEnvironmentVariable, + NTT_MANAGER_CONTRACT, + NTT_TOKENS, + NTT_TRANSCEIVER_CONTRACT, + NTT_SUPPORTED_CHAINS, + getEvmTokenDecimals, + getSolanaTokenDecimals, +} from '@wormhole-foundation/wormhole-monitor-common'; +import { EvmPlatform, EvmChains } from '@wormhole-foundation/sdk-evm'; +import { SolanaPlatform } from '@wormhole-foundation/sdk-solana'; +import { EvmNtt } from '@wormhole-foundation/sdk-evm-ntt'; +import { SolanaNtt } from '@wormhole-foundation/sdk-solana-ntt'; +import { Network, Chain, contracts, rpc } from '@wormhole-foundation/sdk-base'; +import { Gauge, register } from 'prom-client'; + +const outboundCapacityGauge = new Gauge({ + name: 'ntt_outbound_capacity', + help: 'NTT outbound capacity for token on chain', + labelNames: ['token', 'chain', 'network', 'product'], +}); + +const inboundCapacityGauge = new Gauge({ + name: 'ntt_inbound_capacity', + help: 'NTT inbound capacity for token on chain from inbound_chain', + labelNames: ['token', 'chain', 'inbound_chain', 'network', 'product'], +}); + +const PRODUCT = 'cloud_functions_ntt'; + +async function setCapacityGauge( + gauge: Gauge, + labels: Record, + capacity: bigint +) { + gauge.set(labels, Number(capacity)); +} + +async function getRateLimits(network: Network, token: string, chain: Chain) { + let ntt: EvmNtt | SolanaNtt; + let tokenDecimals: number; + const rpcEndpoint = rpc.rpcAddress(network, chain as Chain); + const tokenAddress = NTT_TOKENS[network][token][chain]!; + const managerContract = NTT_MANAGER_CONTRACT[network][token][chain]!; + const transceiverContract = NTT_TRANSCEIVER_CONTRACT[network][token][chain]!; + + if (chain === 'Solana') { + const platform = new SolanaPlatform(network); + ntt = new SolanaNtt(network, chain, platform.getRpc(chain), { + coreBridge: contracts.coreBridge(network, chain), + ntt: { + token: tokenAddress, + manager: managerContract, + transceiver: { + wormhole: transceiverContract, + }, + }, + }); + tokenDecimals = await getSolanaTokenDecimals(rpcEndpoint, tokenAddress); + } else { + const evmChain = chain as EvmChains; + const platform = new EvmPlatform(network); + ntt = new EvmNtt(network, evmChain, platform.getRpc(evmChain), { + ntt: { + token: tokenAddress, + manager: managerContract, + transceiver: { + wormhole: transceiverContract, + }, + }, + }); + tokenDecimals = await getEvmTokenDecimals(rpcEndpoint, managerContract); + } + + const outboundCapacity = await ntt.getCurrentOutboundCapacity(); + const normalizedOutboundCapacity = outboundCapacity / BigInt(10 ** tokenDecimals); + + await setCapacityGauge( + outboundCapacityGauge, + { token, chain, network, product: PRODUCT }, + normalizedOutboundCapacity + ); + + const inboundChains = NTT_SUPPORTED_CHAINS(network, token).filter( + (inboundChain) => inboundChain !== chain + ); + await Promise.all( + inboundChains.map(async (inboundChain) => { + const inboundCapacity = await ntt.getCurrentInboundCapacity(inboundChain); + const normalizedInboundCapacity = inboundCapacity / BigInt(10 ** tokenDecimals); + await setCapacityGauge( + inboundCapacityGauge, + { token, chain, inbound_chain: inboundChain, network, product: PRODUCT }, + normalizedInboundCapacity + ); + }) + ); +} + +export async function getNTTRateLimits(req: any, res: any) { + res.set('Access-Control-Allow-Origin', '*'); + if (req.method === 'OPTIONS') { + res.set('Access-Control-Allow-Methods', 'GET'); + res.set('Access-Control-Allow-Headers', 'Content-Type'); + res.set('Access-Control-Max-Age', '3600'); + res.sendStatus(204); + return; + } + + try { + const network = assertEnvironmentVariable('NETWORK') as Network; + const managerContracts = NTT_MANAGER_CONTRACT[network]; + + const rateLimitPromises = Object.entries(managerContracts).flatMap(([token, manager]) => + Object.entries(manager) + .map(([chain, contract]) => + contract ? getRateLimits(network, token, chain as Chain) : null + ) + .filter(Boolean) + ); + + await Promise.all(rateLimitPromises); + + res.set('Content-Type', register.contentType); + res.end(await register.metrics()); + } catch (e) { + console.error(e); + res.sendStatus(500); + } +} diff --git a/cloud_functions/src/index.ts b/cloud_functions/src/index.ts index 8e637aeb..e6b3c10f 100644 --- a/cloud_functions/src/index.ts +++ b/cloud_functions/src/index.ts @@ -25,7 +25,7 @@ export const { getReobserveVaas } = require('./getReobserveVaas'); export const { wormchainMonitor } = require('./wormchainMonitor'); export const { getLatestTokenData } = require('./getLatestTokenData'); export const { getSolanaEvents } = require('./getSolanaEvents'); -export const { pushNTTRateLimits } = require('./pushNTTRateLimits'); +export const { getNTTRateLimits } = require('./getNTTRateLimits'); // Register an HTTP function with the Functions Framework that will be executed // when you make an HTTP request to the deployed function's endpoint. @@ -52,4 +52,4 @@ functions.http('getReobserveVaas', getReobserveVaas); functions.http('wormchainMonitor', wormchainMonitor); functions.http('latestTokenData', getLatestTokenData); functions.http('getSolanaEvents', getSolanaEvents); -functions.http('pushNTTRateLimits', pushNTTRateLimits); +functions.http('getNTTRateLimits', getNTTRateLimits); diff --git a/cloud_functions/src/pushNttRateLimits.ts b/cloud_functions/src/pushNttRateLimits.ts deleted file mode 100644 index ed05ab60..00000000 --- a/cloud_functions/src/pushNttRateLimits.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { - assertEnvironmentVariable, - NTT_MANAGER_CONTRACT, - NTT_TOKENS, - NTT_TRANSCEIVER_CONTRACT, - SUPPORTED_CHAINS, - getEvmTokenDecimals, - getSolanaTokenDecimals, -} from '@wormhole-foundation/wormhole-monitor-common'; -import { EvmPlatform } from '@wormhole-foundation/sdk-evm'; -import { SolanaPlatform } from '@wormhole-foundation/sdk-solana'; -import { EvmNtt } from '@wormhole-foundation/sdk-evm-ntt'; -import { SolanaNtt } from '@wormhole-foundation/sdk-solana-ntt'; -import { Chain, Network, contracts, rpc } from '@wormhole-foundation/sdk-base'; -import { EvmChains } from '@wormhole-foundation/sdk-evm'; -import { Gauge } from 'prom-client'; - - -const outboundCapacityGauge = new Gauge({ - name: 'ntt_outbound_capacity', - help: 'NTT outbound capacity', - labelNames: ['token', 'chain'], -}); - -const inboundCapacityGauge = new Gauge({ - name: 'ntt_inbound_capacity', - help: 'NTT inbound capacity', - labelNames: ['token', 'chain', 'inbound_chain'], -}); - -const PRODUCT = 'cloud_functions_ntt'; - -async function getEvmRateLimits(network: Network, chain: EvmChains, token: string) { - const platform = new EvmPlatform(network); - const evmNtt = new EvmNtt(network, chain as EvmChains, platform.getRpc(chain as EvmChains), { - ntt: { - token: NTT_TOKENS[network][token][chain as Chain] as string, - manager: NTT_MANAGER_CONTRACT[network][token][chain as Chain] as string, - transceiver: { - wormhole: NTT_TRANSCEIVER_CONTRACT[network][token][chain as Chain] as string, - }, - }, - }); - const tokenDecimals = await getEvmTokenDecimals( - rpc.rpcAddress(network, chain as Chain), - NTT_MANAGER_CONTRACT[network][token][chain as Chain]! - ); - const outboundCapacity = await evmNtt.getCurrentOutboundCapacity(); - const outboundCapacityNormalized = outboundCapacity / BigInt(10 ** tokenDecimals); - outboundCapacityGauge.set({ token, chain }, Number(outboundCapacityNormalized)); - - console.log(`capacity for ${token} on ${chain}: ${outboundCapacityNormalized}`); - for (const inboundChain of SUPPORTED_CHAINS(network, token)) { - if (inboundChain === chain) continue; - const inboundCapacity = await evmNtt.getCurrentInboundCapacity(inboundChain); - const inboundCapacityNormalized = inboundCapacity / BigInt(10 ** tokenDecimals); - inboundCapacityGauge.set( - { token, chain, inbound_chain: inboundChain }, - Number(inboundCapacityNormalized) - ); - - console.log( - `capacity for ${token} on ${chain} from ${inboundChain}: ${inboundCapacityNormalized}` - ); - } -} - -async function getSolanaRateLimits(network: Network, token: string) { - const platform = new SolanaPlatform(network); - const solNtt = new SolanaNtt(network, 'Solana', platform.getRpc('Solana'), { - coreBridge: contracts.coreBridge(network, 'Solana'), - ntt: { - token: NTT_TOKENS[network][token].Solana as string, - manager: NTT_MANAGER_CONTRACT[network][token].Solana as string, - transceiver: { - wormhole: NTT_TRANSCEIVER_CONTRACT[network][token].Solana as string, - }, - }, - }); - - const tokenDecimals = await getSolanaTokenDecimals( - platform.getRpc('Solana').rpcEndpoint, - NTT_TOKENS[network][token].Solana! - ); - const outboundCapacity = await solNtt.getCurrentOutboundCapacity(); - const outboundCapacityNormalized = outboundCapacity / BigInt(10 ** tokenDecimals); - outboundCapacityGauge.set({ token, chain: 'Solana' }, Number(outboundCapacityNormalized)); - - console.log(`capacity for ${token} on Solana: ${outboundCapacityNormalized}`); - for (const inboundChain of SUPPORTED_CHAINS(network, token)) { - if (inboundChain === 'Solana') continue; - const inboundCapacity = await solNtt.getCurrentInboundCapacity(inboundChain as Chain); - const inboundCapacityNormalized = inboundCapacity / BigInt(10 ** tokenDecimals); - inboundCapacityGauge.set( - { token, chain: 'Solana', inbound_chain: inboundChain }, - Number(inboundCapacityNormalized) - ); - - console.log( - `capacity for ${token} on Solana from ${inboundChain}: ${inboundCapacityNormalized}` - ); - } -} - -export async function pushNTTRateLimits(req: any, res: any) { - res.set('Access-Control-Allow-Origin', '*'); - if (req.method === 'OPTIONS') { - // Send response to OPTIONS requests - res.set('Access-Control-Allow-Methods', 'GET'); - res.set('Access-Control-Allow-Headers', 'Content-Type'); - res.set('Access-Control-Max-Age', '3600'); - res.sendStatus(204); - return; - } - try { - const network = assertEnvironmentVariable('NETWORK') as Network; - const managerContracts = NTT_MANAGER_CONTRACT[network]; - - for (const [token, manager] of Object.entries(managerContracts)) { - for (const [chain, contract] of Object.entries(manager)) { - if (!contract) continue; - - if (chain === 'Solana') { - await getSolanaRateLimits(network, token); - } else { - await getEvmRateLimits(network, chain as EvmChains, token); - } - } - } - - - res.json({}); - } catch (e) { - console.error(e); - res.sendStatus(500); - } -} diff --git a/dashboard/src/utils/evmNttHelpers.ts b/dashboard/src/utils/evmNttHelpers.ts index 174664ad..df9930f3 100644 --- a/dashboard/src/utils/evmNttHelpers.ts +++ b/dashboard/src/utils/evmNttHelpers.ts @@ -1,7 +1,4 @@ -import { - callContractMethod, - getMethodId, -} from '@wormhole-foundation/wormhole-monitor-common'; +import { callContractMethod, getMethodId } from '@wormhole-foundation/wormhole-monitor-common'; export async function getCurrentOutboundCapacity( rpc: string, diff --git a/dashboard/src/utils/nttHelpers.ts b/dashboard/src/utils/nttHelpers.ts index 8e38477b..63fe593b 100644 --- a/dashboard/src/utils/nttHelpers.ts +++ b/dashboard/src/utils/nttHelpers.ts @@ -10,13 +10,10 @@ import { NTT_MANAGER_CONTRACT, NTT_TOKENS, NTT_SUPPORTED_CHAINS, + getEvmTokenDecimals, } from '@wormhole-foundation/wormhole-monitor-common'; -import { - getCurrentInboundCapacity, - getCurrentOutboundCapacity, - getTokenDecimals, -} from './evmNttHelpers'; +import { getCurrentInboundCapacity, getCurrentOutboundCapacity } from './evmNttHelpers'; import { getTokenDecimals as getSolTokenDecimals,