Skip to content

Commit d740f83

Browse files
committed
dashboard: ntt total supply on evm vs total locked on sol
Signed-off-by: bingyuyap <bingyu.yap.21@gmail.com>
1 parent 207e10a commit d740f83

21 files changed

+708
-428
lines changed

cloud_functions/scripts/deploy.sh

+4
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ gcloud functions --project "$GCP_PROJECT" deploy reobserve-vaas --entry-point ge
130130
gcloud functions --project "$GCP_PROJECT" deploy tvl --entry-point getTVL --runtime nodejs18 --trigger-http --allow-unauthenticated --timeout 300 --memory 256MB --region europe-west3 --set-env-vars FIRESTORE_TVL_COLLECTION=$FIRESTORE_TVL_COLLECTION,NETWORK=$NETWORK
131131
gcloud functions --project "$GCP_PROJECT" deploy tvl-history --entry-point getTVLHistory --runtime nodejs18 --trigger-http --allow-unauthenticated --timeout 300 --memory 256MB --region europe-west3 --set-env-vars FIRESTORE_TVL_HISTORY_COLLECTION=$FIRESTORE_TVL_HISTORY_COLLECTION,NETWORK=$NETWORK
132132
gcloud functions --project "$GCP_PROJECT" deploy vaas-by-tx-hash --entry-point getVaasByTxHash --runtime nodejs18 --trigger-http --allow-unauthenticated --timeout 300 --memory 256MB --region europe-west3 --set-env-vars BIGTABLE_INSTANCE_ID=$BIGTABLE_INSTANCE_ID,BIGTABLE_SIGNED_VAAS_TABLE_ID=$BIGTABLE_SIGNED_VAAS_TABLE_ID,BIGTABLE_VAAS_BY_TX_HASH_TABLE_ID=$BIGTABLE_VAAS_BY_TX_HASH_TABLE_ID,NETWORK=$NETWORK
133+
gcloud functions --project "$GCP_PROJECT" deploy get-ntt-rate-limits --entry-point getNTTRateLimits --runtime nodejs18 --trigger-http --allow-unauthenticated --timeout 300 --memory 256MB --region europe-west3 --set-env-vars NETWORK=$NETWORK
134+
gcloud functions --project "$GCP_PROJECT" deploy compute-ntt-rate-limits --entry-point computeNTTRateLimits --runtime nodejs18 --trigger-http --allow-unauthenticated --timeout 300 --memory 256MB --region europe-west3 --set-env-vars NETWORK=$NETWORK
135+
gcloud functions --project "$GCP_PROJECT" deploy get-total-supply-and-locked --entry-point getTotalSupplyAndLocked --runtime nodejs18 --trigger-http --allow-unauthenticated --timeout 300 --memory 256MB --region europe-west3 --set-env-vars NETWORK=$NETWORK
136+
gcloud functions --project "$GCP_PROJECT" deploy compute-total-supply-and-locked --entry-point computeTotalSupplyAndLocked --runtime nodejs18 --trigger-http --allow-unauthenticated --timeout 300 --memory 256MB --region europe-west3 --set-env-vars NETWORK=$NETWORK
133137

134138
#
135139
# Bail out if we are only deploying TESTNET functions
+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import {
2+
assertEnvironmentVariable,
3+
NTT_MANAGER_CONTRACT,
4+
NTT_TOKENS,
5+
NTT_TRANSCEIVER_CONTRACT,
6+
NTT_SUPPORTED_CHAINS,
7+
getEvmTokenDecimals,
8+
getSolanaTokenDecimals,
9+
NTTRateLimit,
10+
} from '@wormhole-foundation/wormhole-monitor-common';
11+
import { EvmPlatform, EvmChains } from '@wormhole-foundation/sdk-evm';
12+
import { SolanaPlatform } from '@wormhole-foundation/sdk-solana';
13+
import { EvmNtt } from '@wormhole-foundation/sdk-evm-ntt';
14+
import { SolanaNtt } from '@wormhole-foundation/sdk-solana-ntt';
15+
import { Network, Chain, contracts, rpc, chainToChainId } from '@wormhole-foundation/sdk-base';
16+
import { Storage } from '@google-cloud/storage';
17+
18+
const storage = new Storage();
19+
let bucketName: string = 'wormhole-ntt-cache';
20+
if (assertEnvironmentVariable('NETWORK') === 'Testnet') {
21+
bucketName = 'wormhole-ntt-cache-testnet';
22+
}
23+
24+
const cacheBucket = storage.bucket(bucketName);
25+
const cacheFileName = 'ntt-rate-limits-cache.json';
26+
const cloudStorageCache = cacheBucket.file(cacheFileName);
27+
28+
async function computeNTTRateLimits_(
29+
network: Network,
30+
token: string,
31+
chain: Chain
32+
): Promise<NTTRateLimit> {
33+
let ntt: EvmNtt<Network, EvmChains> | SolanaNtt<Network, 'Solana'>;
34+
let tokenDecimals: number;
35+
const rpcEndpoint = rpc.rpcAddress(network, chain as Chain);
36+
const tokenAddress = NTT_TOKENS[network][token][chain]!;
37+
const managerContract = NTT_MANAGER_CONTRACT[network][token][chain]!;
38+
const transceiverContract = NTT_TRANSCEIVER_CONTRACT[network][token][chain]!;
39+
40+
if (chain === 'Solana') {
41+
const platform = new SolanaPlatform(network);
42+
ntt = new SolanaNtt(network, chain, platform.getRpc(chain), {
43+
coreBridge: contracts.coreBridge(network, chain),
44+
ntt: {
45+
token: tokenAddress,
46+
manager: managerContract,
47+
transceiver: {
48+
wormhole: transceiverContract,
49+
},
50+
},
51+
});
52+
tokenDecimals = await getSolanaTokenDecimals(rpcEndpoint, tokenAddress);
53+
} else {
54+
const evmChain = chain as EvmChains;
55+
const platform = new EvmPlatform(network);
56+
ntt = new EvmNtt(network, evmChain, platform.getRpc(evmChain), {
57+
ntt: {
58+
token: tokenAddress,
59+
manager: managerContract,
60+
transceiver: {
61+
wormhole: transceiverContract,
62+
},
63+
},
64+
});
65+
tokenDecimals = await getEvmTokenDecimals(rpcEndpoint, managerContract);
66+
}
67+
68+
const outboundCapacity = await ntt.getCurrentOutboundCapacity();
69+
const normalizedOutboundCapacity = outboundCapacity / BigInt(10 ** tokenDecimals);
70+
71+
const inboundChains = NTT_SUPPORTED_CHAINS(network, token).filter(
72+
(inboundChain) => inboundChain !== chain
73+
);
74+
75+
let totalInboundCapacity = BigInt(0);
76+
const inboundRateLimits = await Promise.all(
77+
inboundChains.map(async (inboundChain): Promise<NTTRateLimit> => {
78+
const inboundCapacity = await ntt.getCurrentInboundCapacity(inboundChain);
79+
const normalizedInboundCapacity = inboundCapacity / BigInt(10 ** tokenDecimals);
80+
totalInboundCapacity += normalizedInboundCapacity;
81+
82+
return {
83+
tokenName: token,
84+
srcChain: chainToChainId(inboundChain),
85+
destChain: chainToChainId(chain),
86+
amount: normalizedInboundCapacity.toString(),
87+
};
88+
})
89+
);
90+
91+
return {
92+
tokenName: token,
93+
srcChain: chainToChainId(chain),
94+
amount: normalizedOutboundCapacity.toString(),
95+
inboundCapacity: inboundRateLimits,
96+
totalInboundCapacity: totalInboundCapacity.toString(),
97+
};
98+
}
99+
100+
export async function computeNTTRateLimits(req: any, res: any) {
101+
res.set('Access-Control-Allow-Origin', '*');
102+
if (req.method === 'OPTIONS') {
103+
res.set('Access-Control-Allow-Methods', 'GET');
104+
res.set('Access-Control-Allow-Headers', 'Content-Type');
105+
res.set('Access-Control-Max-Age', '3600');
106+
res.sendStatus(204);
107+
return;
108+
}
109+
110+
try {
111+
const network = assertEnvironmentVariable('NETWORK') as Network;
112+
const managerContracts = NTT_MANAGER_CONTRACT[network];
113+
114+
const rateLimits = await Promise.all(
115+
Object.entries(managerContracts).map(async ([token, manager]) => {
116+
const inboundCapacityPromises = Object.entries(manager)
117+
.map(([chain, contract]) =>
118+
contract ? computeNTTRateLimits_(network, token, chain as Chain) : null
119+
)
120+
.filter(Boolean);
121+
122+
const inboundCapacity = await Promise.all(inboundCapacityPromises);
123+
124+
return {
125+
tokenName: token,
126+
inboundCapacity: inboundCapacity,
127+
};
128+
})
129+
);
130+
await cloudStorageCache.save(JSON.stringify(rateLimits));
131+
res.status(200).send('Rate limits saved');
132+
} catch (e) {
133+
console.error(e);
134+
res.sendStatus(500);
135+
}
136+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import {
2+
NTT_MANAGER_CONTRACT,
3+
NTT_TOKENS,
4+
assertEnvironmentVariable,
5+
derivePda,
6+
getEvmTokenDecimals,
7+
getEvmTotalSupply,
8+
} from '@wormhole-foundation/wormhole-monitor-common';
9+
import { PublicKey } from '@solana/web3.js';
10+
import {
11+
getCustody,
12+
getCustodyAmount,
13+
NTTTotalSupplyAndLockedData,
14+
} from '@wormhole-foundation/wormhole-monitor-common';
15+
import { Network, rpc, Chain, chainToChainId } from '@wormhole-foundation/sdk-base';
16+
import { Storage } from '@google-cloud/storage';
17+
18+
const storage = new Storage();
19+
let bucketName: string = 'wormhole-ntt-cache';
20+
if (assertEnvironmentVariable('NETWORK') === 'Testnet') {
21+
bucketName = 'wormhole-ntt-cache-testnet';
22+
}
23+
24+
const cacheBucket = storage.bucket(bucketName);
25+
const cacheFileName = 'ntt-total-supply-and-locked.json';
26+
const cloudStorageCache = cacheBucket.file(cacheFileName);
27+
28+
async function getEvmNormalizedTotalSupply(
29+
network: Network,
30+
token: string,
31+
chain: Chain
32+
): Promise<number> {
33+
const tokenDecimals = await getEvmTokenDecimals(
34+
rpc.rpcAddress(network, chain),
35+
NTT_MANAGER_CONTRACT[network][token][chain]!
36+
);
37+
const tokenSupply = await getEvmTotalSupply(
38+
rpc.rpcAddress(network, chain),
39+
NTT_TOKENS[network][token][chain]!
40+
);
41+
42+
return tokenSupply / 10 ** tokenDecimals;
43+
}
44+
45+
async function fetchTotalSupplyAndLocked(network: Network): Promise<NTTTotalSupplyAndLockedData[]> {
46+
const tokens = NTT_MANAGER_CONTRACT[network];
47+
const totalSupplyVsLocked: NTTTotalSupplyAndLockedData[] = [];
48+
for (const token in tokens) {
49+
if (!NTT_MANAGER_CONTRACT[network][token].Solana) continue;
50+
51+
const programId = new PublicKey(NTT_MANAGER_CONTRACT[network][token].Solana!);
52+
const pda = derivePda('config', programId);
53+
const custody = await getCustody(rpc.rpcAddress(network, 'Solana'), pda.toBase58());
54+
const locked = await getCustodyAmount(rpc.rpcAddress(network, 'Solana'), custody);
55+
56+
const evmTotalSupply: NTTTotalSupplyAndLockedData[] = [];
57+
let totalSupply = 0;
58+
for (const [supportedChain] of Object.entries(NTT_TOKENS[network][token])) {
59+
if (supportedChain === 'Solana') continue;
60+
const tokenSupplyNormalized = await getEvmNormalizedTotalSupply(
61+
network,
62+
token,
63+
supportedChain as Chain
64+
);
65+
66+
evmTotalSupply.push({
67+
tokenName: token,
68+
chain: chainToChainId(supportedChain as Chain),
69+
totalSupply: tokenSupplyNormalized,
70+
});
71+
72+
totalSupply += tokenSupplyNormalized;
73+
}
74+
75+
totalSupplyVsLocked.push({
76+
chain: chainToChainId('Solana'),
77+
tokenName: token,
78+
amountLocked: locked,
79+
totalSupply,
80+
evmTotalSupply,
81+
});
82+
}
83+
84+
return totalSupplyVsLocked;
85+
}
86+
87+
export async function computeTotalSupplyAndLocked(req: any, res: any) {
88+
res.set('Access-Control-Allow-Origin', '*');
89+
if (req.method === 'OPTIONS') {
90+
res.set('Access-Control-Allow-Methods', 'GET');
91+
res.set('Access-Control-Allow-Headers', 'Content-Type');
92+
res.set('Access-Control-Max-Age', '3600');
93+
res.sendStatus(204);
94+
return;
95+
}
96+
97+
try {
98+
const network = assertEnvironmentVariable('NETWORK') as Network;
99+
const totalSupplyAndLocked = await fetchTotalSupplyAndLocked(network);
100+
await cloudStorageCache.save(JSON.stringify(totalSupplyAndLocked));
101+
102+
res.status(200).send('Total supply and locked saved');
103+
} catch (e) {
104+
console.error(e);
105+
res.sendStatus(500);
106+
}
107+
}

0 commit comments

Comments
 (0)