@humanode-hyperlane/metrics 0.0.0-stage → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/LICENSE.md +193 -0
  2. package/dist/balance.d.ts +93 -0
  3. package/dist/balance.d.ts.map +1 -0
  4. package/dist/balance.js +235 -0
  5. package/dist/balance.js.map +1 -0
  6. package/dist/gauges.d.ts +41 -0
  7. package/dist/gauges.d.ts.map +1 -0
  8. package/dist/gauges.js +100 -0
  9. package/dist/gauges.js.map +1 -0
  10. package/dist/index.d.ts +8 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +13 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/index.test.d.ts +2 -0
  15. package/dist/index.test.d.ts.map +1 -0
  16. package/dist/index.test.js +45 -0
  17. package/dist/index.test.js.map +1 -0
  18. package/dist/pushgateway.d.ts +23 -0
  19. package/dist/pushgateway.d.ts.map +1 -0
  20. package/dist/pushgateway.js +54 -0
  21. package/dist/pushgateway.js.map +1 -0
  22. package/dist/server.d.ts +15 -0
  23. package/dist/server.d.ts.map +1 -0
  24. package/dist/server.js +63 -0
  25. package/dist/server.js.map +1 -0
  26. package/dist/server.test.d.ts +2 -0
  27. package/dist/server.test.d.ts.map +1 -0
  28. package/dist/server.test.js +23 -0
  29. package/dist/server.test.js.map +1 -0
  30. package/dist/types.d.ts +64 -0
  31. package/dist/types.d.ts.map +1 -0
  32. package/dist/types.js +2 -0
  33. package/dist/types.js.map +1 -0
  34. package/dist/update.d.ts +51 -0
  35. package/dist/update.d.ts.map +1 -0
  36. package/dist/update.js +163 -0
  37. package/dist/update.js.map +1 -0
  38. package/dist/utils.d.ts +7 -0
  39. package/dist/utils.d.ts.map +1 -0
  40. package/dist/utils.js +9 -0
  41. package/dist/utils.js.map +1 -0
  42. package/package.json +52 -3
  43. package/src/balance.ts +380 -0
  44. package/src/gauges.ts +136 -0
  45. package/src/index.test.ts +52 -0
  46. package/src/index.ts +53 -0
  47. package/src/pushgateway.ts +66 -0
  48. package/src/server.test.ts +32 -0
  49. package/src/server.ts +76 -0
  50. package/src/types.ts +74 -0
  51. package/src/update.ts +239 -0
  52. package/src/utils.ts +10 -0
  53. package/README.md +0 -3
package/dist/update.js ADDED
@@ -0,0 +1,163 @@
1
+ import { TokenStandard, } from '@humanode-hyperlane/sdk';
2
+ import { isNullish } from '@humanode-hyperlane/utils';
3
+ /**
4
+ * Updates token balance metrics for a warp route token.
5
+ *
6
+ * @param gauges - The warp metrics gauges to update
7
+ * @param warpCore - The WarpCore instance for the route
8
+ * @param token - The token to update metrics for
9
+ * @param balanceInfo - The balance information for the token
10
+ * @param warpRouteId - The warp route identifier
11
+ * @param logger - The logger instance
12
+ */
13
+ export function updateTokenBalanceMetrics(gauges, warpCore, token, balanceInfo, warpRouteId, logger) {
14
+ const allChains = warpCore.getTokenChains().sort();
15
+ const relatedChains = allChains.filter((chainName) => chainName !== token.chainName);
16
+ const metrics = {
17
+ chain_name: token.chainName,
18
+ token_address: balanceInfo.tokenAddress,
19
+ token_name: token.name,
20
+ wallet_address:
21
+ // the balance for an EvmHypERC20 token is returned as the total supply of the xERC20 token,
22
+ // therefore we set the wallet address to the token address,
23
+ // we follow the same pattern or synthetic tokens
24
+ token.standard !== TokenStandard.EvmHypXERC20
25
+ ? token.addressOrDenom
26
+ : balanceInfo.tokenAddress,
27
+ token_standard:
28
+ // as we are reporting the total supply for clarity we report the standard as xERC20
29
+ token.standard !== TokenStandard.EvmHypXERC20 ? token.standard : 'xERC20',
30
+ warp_route_id: warpRouteId,
31
+ // TODO: consider deprecating this label given that we have the value at risk metric
32
+ related_chain_names: relatedChains.join(','),
33
+ };
34
+ gauges.warpRouteTokenBalance.labels({ ...metrics }).set(balanceInfo.balance);
35
+ logger.info({
36
+ labels: metrics,
37
+ balance: balanceInfo.balance,
38
+ }, 'Wallet balance updated for token');
39
+ if (!isNullish(balanceInfo.valueUSD)) {
40
+ // TODO: consider deprecating this metric in favor of the value at risk metric
41
+ gauges.warpRouteCollateralValue
42
+ .labels({ ...metrics })
43
+ .set(balanceInfo.valueUSD);
44
+ logger.info({
45
+ labels: metrics,
46
+ valueUSD: balanceInfo.valueUSD,
47
+ }, 'Wallet value updated for token');
48
+ for (const chainName of allChains) {
49
+ const labels = {
50
+ chain_name: chainName,
51
+ collateral_chain_name: metrics.chain_name,
52
+ token_address: metrics.token_address,
53
+ token_name: metrics.token_name,
54
+ collateral_token_standard: metrics.token_standard,
55
+ warp_route_id: metrics.warp_route_id,
56
+ };
57
+ gauges.warpRouteValueAtRisk
58
+ .labels({ ...labels })
59
+ .set(balanceInfo.valueUSD);
60
+ logger.info({
61
+ labels,
62
+ valueUSD: balanceInfo.valueUSD,
63
+ }, `Value at risk on ${chainName} updated for token`);
64
+ }
65
+ }
66
+ }
67
+ /**
68
+ * Updates managed lockbox balance metrics.
69
+ *
70
+ * @param gauges - The warp metrics gauges to update
71
+ * @param warpCore - The WarpCore instance for the route
72
+ * @param chainName - The chain name where the lockbox is deployed
73
+ * @param tokenName - The name of the collateral token
74
+ * @param tokenAddress - The address of the collateral token
75
+ * @param lockBoxAddress - The address of the managed lockbox
76
+ * @param balanceInfo - The balance information for the lockbox
77
+ * @param warpRouteId - The warp route identifier
78
+ * @param logger - The logger instance
79
+ */
80
+ export function updateManagedLockboxBalanceMetrics(gauges, warpCore, chainName, tokenName, tokenAddress, lockBoxAddress, balanceInfo, warpRouteId, logger) {
81
+ const metrics = {
82
+ chain_name: chainName,
83
+ token_address: tokenAddress,
84
+ token_name: tokenName,
85
+ wallet_address: lockBoxAddress,
86
+ token_standard: 'EvmManagedLockbox', // TODO: we should eventually add a new TokenStandard for this
87
+ warp_route_id: warpRouteId,
88
+ related_chain_names: warpCore
89
+ .getTokenChains()
90
+ .filter((_chainName) => _chainName !== chainName)
91
+ .sort()
92
+ .join(','),
93
+ };
94
+ gauges.warpRouteTokenBalance.labels({ ...metrics }).set(balanceInfo.balance);
95
+ logger.info({
96
+ labels: metrics,
97
+ balance: balanceInfo.balance,
98
+ }, 'ManagedLockbox collateral balance updated');
99
+ if (!isNullish(balanceInfo.valueUSD)) {
100
+ gauges.warpRouteCollateralValue
101
+ .labels({ ...metrics })
102
+ .set(balanceInfo.valueUSD);
103
+ logger.info({
104
+ labels: metrics,
105
+ valueUSD: balanceInfo.valueUSD,
106
+ }, 'ManagedLockbox value updated for token');
107
+ }
108
+ }
109
+ /**
110
+ * Updates native wallet balance metrics.
111
+ *
112
+ * @param gauges - The warp metrics gauges to update
113
+ * @param balance - The native wallet balance information
114
+ * @param logger - The logger instance
115
+ */
116
+ export function updateNativeWalletBalanceMetrics(gauges, balance, logger) {
117
+ gauges.walletBalanceGauge
118
+ .labels({
119
+ chain: balance.chain,
120
+ wallet_address: balance.walletAddress,
121
+ wallet_name: balance.walletName,
122
+ token_address: 'native',
123
+ token_symbol: 'Native',
124
+ token_name: 'Native',
125
+ })
126
+ .set(balance.balance);
127
+ logger.info('Native wallet balance updated', {
128
+ balanceInfo: balance,
129
+ });
130
+ }
131
+ /**
132
+ * Updates xERC20 limits metrics.
133
+ *
134
+ * @param gauges - The warp metrics gauges to update
135
+ * @param token - The token to update metrics for
136
+ * @param limits - The xERC20 limits
137
+ * @param bridgeAddress - The address of the bridge
138
+ * @param bridgeLabel - A label for the bridge (e.g., token standard)
139
+ * @param xERC20Address - The address of the xERC20 token
140
+ * @param logger - The logger instance
141
+ */
142
+ export function updateXERC20LimitsMetrics(gauges, token, limits, bridgeAddress, bridgeLabel, xERC20Address, logger) {
143
+ const labels = {
144
+ chain_name: token.chainName,
145
+ token_name: token.name,
146
+ bridge_address: bridgeAddress,
147
+ token_address: xERC20Address,
148
+ bridge_label: bridgeLabel,
149
+ };
150
+ for (const [limitType, limit] of Object.entries(limits)) {
151
+ gauges.xERC20LimitsGauge
152
+ .labels({
153
+ ...labels,
154
+ limit_type: limitType,
155
+ })
156
+ .set(limit);
157
+ }
158
+ logger.info({
159
+ ...labels,
160
+ limits,
161
+ }, 'xERC20 limits updated for bridge on token');
162
+ }
163
+ //# sourceMappingURL=update.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.js","sourceRoot":"","sources":["../src/update.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { Token } from '@humanode-hyperlane/sdk';
2
+ /**
3
+ * Formats a bigint value to a number using the token's decimal precision.
4
+ * Returns 0 if the result is non-finite (NaN, Infinity) to avoid Prometheus dropping the metric.
5
+ */
6
+ export declare function formatBigInt(token: Token, num: bigint): number;
7
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAErD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAG9D"}
package/dist/utils.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Formats a bigint value to a number using the token's decimal precision.
3
+ * Returns 0 if the result is non-finite (NaN, Infinity) to avoid Prometheus dropping the metric.
4
+ */
5
+ export function formatBigInt(token, num) {
6
+ const result = Number(token.amount(num).getDecimalFormattedAmount());
7
+ return Number.isFinite(result) ? result : 0;
8
+ }
9
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAAY,EAAE,GAAW;IACpD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,yBAAyB,EAAE,CAAC,CAAC;IACrE,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,55 @@
1
1
  {
2
2
  "name": "@humanode-hyperlane/metrics",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.8",
4
+ "description": "Shared Prometheus metrics utilities for Hyperlane monitoring",
5
+ "keywords": [
6
+ "hyperlane",
7
+ "metrics",
8
+ "monitoring",
9
+ "prometheus",
10
+ "warp-route"
11
+ ],
12
+ "license": "Apache-2.0",
13
+ "author": "Abacus Works, Inc.",
14
+ "repository": "https://github.com/humanode-network/hyperlane-monorepo",
15
+ "files": [
16
+ "dist",
17
+ "src"
18
+ ],
19
+ "type": "module",
20
+ "main": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "dependencies": {
23
+ "ethers": "^5.8.0",
24
+ "pino": "^8.19.0",
25
+ "prom-client": "^14.0.1",
26
+ "@humanode-hyperlane/core": "11.2.0",
27
+ "@humanode-hyperlane/sdk": "30.0.0",
28
+ "@humanode-hyperlane/utils": "30.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/chai": "^4.3.11",
32
+ "@types/mocha": "^10.0.1",
33
+ "@types/node": "^24.10.9",
34
+ "@types/sinon": "^17.0.1",
35
+ "chai": "^4.5.0",
36
+ "mocha": "^11.5.0",
37
+ "oxfmt": "^0.42.0",
38
+ "sinon": "^13.0.2",
39
+ "tsx": "^4.19.1",
40
+ "typescript": "6.0.2",
41
+ "@humanode-hyperlane/tsconfig": "^30.0.0"
42
+ },
43
+ "engines": {
44
+ "node": ">=18"
45
+ },
46
+ "scripts": {
47
+ "build": "tsc",
48
+ "clean": "rm -rf dist",
49
+ "dev": "tsc --watch",
50
+ "lint": "oxlint -c ../../oxlint.json",
51
+ "format": "oxfmt --write ./src",
52
+ "test": "mocha --config .mocharc.json './src/**/*.test.ts' --exit",
53
+ "test:ci": "pnpm test"
54
+ }
6
55
  }
package/src/balance.ts ADDED
@@ -0,0 +1,380 @@
1
+ import { Contract, type PopulatedTransaction } from 'ethers';
2
+ import type { Logger } from 'pino';
3
+
4
+ import { IXERC20VS__factory } from '@humanode-hyperlane/core';
5
+ import {
6
+ type EvmHypXERC20Adapter,
7
+ type EvmHypXERC20LockboxAdapter,
8
+ EvmTokenAdapter,
9
+ type IHypXERC20Adapter,
10
+ type MultiProtocolProvider,
11
+ type SealevelHypTokenAdapter,
12
+ Token,
13
+ TokenStandard,
14
+ type WarpCore,
15
+ } from '@humanode-hyperlane/sdk';
16
+ import { type Address, ProtocolType } from '@humanode-hyperlane/utils';
17
+
18
+ import type {
19
+ NativeWalletBalance,
20
+ WarpRouteBalance,
21
+ XERC20Info,
22
+ XERC20Limit,
23
+ } from './types.js';
24
+ import { formatBigInt } from './utils.js';
25
+
26
+ /**
27
+ * Minimal ABI for managed lockbox contracts.
28
+ */
29
+ export const MANAGED_LOCKBOX_MINIMAL_ABI = [
30
+ 'function XERC20() view returns (address)',
31
+ 'function ERC20() view returns (address)',
32
+ ] as const;
33
+
34
+ /**
35
+ * Interface for token price getter to allow different implementations.
36
+ */
37
+ export interface TokenPriceGetter {
38
+ tryGetTokenPrice(token: Token): Promise<number | undefined>;
39
+ }
40
+
41
+ /**
42
+ * Gets the bridged balance and value of a token in a warp route.
43
+ *
44
+ * @param warpCore - The WarpCore instance for the route
45
+ * @param token - The token to get the balance for
46
+ * @param tokenPriceGetter - Price getter for calculating USD value
47
+ * @param logger - Logger instance
48
+ * @param bridgedSupply - Optional pre-fetched bridged supply
49
+ * @returns The balance information or undefined if not available
50
+ */
51
+ export async function getTokenBridgedBalance(
52
+ warpCore: WarpCore,
53
+ token: Token,
54
+ tokenPriceGetter: TokenPriceGetter,
55
+ logger: Logger,
56
+ bridgedSupply?: bigint,
57
+ ): Promise<WarpRouteBalance | undefined> {
58
+ if (!token.isHypToken()) {
59
+ logger.warn(
60
+ { token: token.symbol, chain: token.chainName },
61
+ 'No support for bridged balance on non-Hyperlane token',
62
+ );
63
+ return undefined;
64
+ }
65
+
66
+ const adapter = token.getHypAdapter(warpCore.multiProvider);
67
+ let tokenAddress = token.collateralAddressOrDenom ?? token.addressOrDenom;
68
+
69
+ // If bridged supply is not provided, fetch it
70
+ const supply = bridgedSupply ?? (await adapter.getBridgedSupply());
71
+ if (supply === undefined) {
72
+ logger.warn(
73
+ { token: token.symbol, chain: token.chainName },
74
+ 'Failed to get bridged supply',
75
+ );
76
+ return undefined;
77
+ }
78
+
79
+ const balance = token.amount(supply).getDecimalFormattedAmount();
80
+
81
+ let tokenPrice;
82
+ // Only record value for collateralized and xERC20 lockbox tokens.
83
+ if (
84
+ token.isCollateralized() ||
85
+ token.standard === TokenStandard.EvmHypXERC20Lockbox ||
86
+ token.standard === TokenStandard.EvmHypVSXERC20Lockbox
87
+ ) {
88
+ tokenPrice = await tokenPriceGetter.tryGetTokenPrice(token);
89
+ }
90
+
91
+ if (
92
+ token.standard === TokenStandard.EvmHypXERC20Lockbox ||
93
+ token.standard === TokenStandard.EvmHypVSXERC20Lockbox
94
+ ) {
95
+ tokenAddress = (await (adapter as EvmHypXERC20LockboxAdapter).getXERC20())
96
+ .address;
97
+ }
98
+
99
+ return {
100
+ balance,
101
+ valueUSD: tokenPrice ? balance * tokenPrice : undefined,
102
+ tokenAddress,
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Gets the native balance of the ATA payer for Sealevel tokens.
108
+ *
109
+ * @param warpCore - The WarpCore instance for the route
110
+ * @param token - The Sealevel token
111
+ * @param warpRouteId - The warp route identifier
112
+ * @returns The native wallet balance information
113
+ */
114
+ export async function getSealevelAtaPayerBalance(
115
+ warpCore: WarpCore,
116
+ token: Token,
117
+ warpRouteId: string,
118
+ ): Promise<NativeWalletBalance> {
119
+ if (token.protocol !== ProtocolType.Sealevel || token.isNative()) {
120
+ throw new Error(
121
+ `Unsupported ATA payer protocol type ${token.protocol} or standard ${token.standard}`,
122
+ );
123
+ }
124
+ const adapter = token.getHypAdapter(
125
+ warpCore.multiProvider,
126
+ ) as SealevelHypTokenAdapter;
127
+
128
+ const ataPayer = adapter.deriveAtaPayerAccount().toString();
129
+ const nativeToken = Token.FromChainMetadataNativeToken(
130
+ warpCore.multiProvider.getChainMetadata(token.chainName),
131
+ );
132
+ const ataPayerBalance = await nativeToken.getBalance(
133
+ warpCore.multiProvider,
134
+ ataPayer,
135
+ );
136
+
137
+ return {
138
+ chain: token.chainName,
139
+ walletAddress: ataPayer.toString(),
140
+ walletName: `${warpRouteId}/ata-payer`,
141
+ balance: ataPayerBalance.getDecimalFormattedAmount(),
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Gets xERC20 information for a token.
147
+ *
148
+ * @param warpCore - The WarpCore instance for the route
149
+ * @param token - The xERC20 token
150
+ * @returns The xERC20 info including limits and address
151
+ */
152
+ export async function getXERC20Info(
153
+ warpCore: WarpCore,
154
+ token: Token,
155
+ ): Promise<XERC20Info> {
156
+ if (
157
+ token.protocol !== ProtocolType.Ethereum &&
158
+ token.protocol !== ProtocolType.Tron
159
+ ) {
160
+ throw new Error(`Unsupported XERC20 protocol type ${token.protocol}`);
161
+ }
162
+
163
+ if (
164
+ token.standard === TokenStandard.EvmHypXERC20 ||
165
+ token.standard === TokenStandard.EvmHypVSXERC20 ||
166
+ token.standard == TokenStandard.TronHypVSXERC20 ||
167
+ token.standard == TokenStandard.TronHypXERC20
168
+ ) {
169
+ const adapter = token.getAdapter(
170
+ warpCore.multiProvider,
171
+ ) as EvmHypXERC20Adapter;
172
+ return {
173
+ limits: await getXERC20Limit(token, adapter),
174
+ xERC20Address: (await adapter.getXERC20()).address,
175
+ };
176
+ } else if (
177
+ token.standard === TokenStandard.EvmHypXERC20Lockbox ||
178
+ token.standard === TokenStandard.EvmHypVSXERC20Lockbox ||
179
+ token.standard === TokenStandard.TronHypXERC20Lockbox ||
180
+ token.standard === TokenStandard.TronHypVSXERC20Lockbox
181
+ ) {
182
+ const adapter = token.getAdapter(
183
+ warpCore.multiProvider,
184
+ ) as EvmHypXERC20LockboxAdapter;
185
+ return {
186
+ limits: await getXERC20Limit(token, adapter),
187
+ xERC20Address: (await adapter.getXERC20()).address,
188
+ };
189
+ }
190
+
191
+ throw new Error(`Unsupported XERC20 token standard ${token.standard}`);
192
+ }
193
+
194
+ /**
195
+ * Gets xERC20 limits from an adapter.
196
+ *
197
+ * @param token - The token to get limits for
198
+ * @param xerc20 - The xERC20 adapter
199
+ * @returns The xERC20 limits
200
+ */
201
+ export async function getXERC20Limit(
202
+ token: Token,
203
+ xerc20: IHypXERC20Adapter<PopulatedTransaction>,
204
+ ): Promise<XERC20Limit> {
205
+ const [mintCurrent, mintMax, burnCurrent, burnMax] = await Promise.all([
206
+ xerc20.getMintLimit(),
207
+ xerc20.getMintMaxLimit(),
208
+ xerc20.getBurnLimit(),
209
+ xerc20.getBurnMaxLimit(),
210
+ ]);
211
+
212
+ return {
213
+ mint: formatBigInt(token, mintCurrent),
214
+ mintMax: formatBigInt(token, mintMax),
215
+ burn: formatBigInt(token, burnCurrent),
216
+ burnMax: formatBigInt(token, burnMax),
217
+ };
218
+ }
219
+
220
+ /**
221
+ * Gets a managed lockbox contract instance.
222
+ *
223
+ * @param multiProvider - The multi-protocol provider
224
+ * @param chainName - The chain name
225
+ * @param lockboxAddress - The lockbox contract address
226
+ * @returns The Contract instance
227
+ */
228
+ export function getManagedLockBox(
229
+ multiProvider: MultiProtocolProvider,
230
+ chainName: string,
231
+ lockboxAddress: Address,
232
+ ): Contract {
233
+ const provider = multiProvider.getEthersV5Provider(chainName);
234
+ return new Contract(lockboxAddress, MANAGED_LOCKBOX_MINIMAL_ABI, provider);
235
+ }
236
+
237
+ /**
238
+ * Gets extra lockbox information for xERC20 tokens.
239
+ *
240
+ * @param multiProvider - The multi-protocol provider
241
+ * @param warpToken - The warp token
242
+ * @param lockboxAddress - The lockbox contract address
243
+ * @returns The xERC20 info for the lockbox
244
+ */
245
+ export async function getExtraLockboxInfo(
246
+ multiProvider: MultiProtocolProvider,
247
+ warpToken: Token,
248
+ lockboxAddress: Address,
249
+ ): Promise<XERC20Info> {
250
+ const currentChainProvider = multiProvider.getEthersV5Provider(
251
+ warpToken.chainName,
252
+ );
253
+ const lockboxInstance = getManagedLockBox(
254
+ multiProvider,
255
+ warpToken.chainName,
256
+ lockboxAddress,
257
+ );
258
+
259
+ const xERC20Address = await lockboxInstance['XERC20']();
260
+ const vsXERC20Instance = IXERC20VS__factory.connect(
261
+ xERC20Address,
262
+ currentChainProvider,
263
+ );
264
+
265
+ const [mintMax, burnMax, mint, burn] = await Promise.all([
266
+ vsXERC20Instance.mintingMaxLimitOf(lockboxAddress),
267
+ vsXERC20Instance.burningMaxLimitOf(lockboxAddress),
268
+ vsXERC20Instance.mintingCurrentLimitOf(lockboxAddress),
269
+ vsXERC20Instance.burningCurrentLimitOf(lockboxAddress),
270
+ ]);
271
+
272
+ return {
273
+ limits: {
274
+ burn: formatBigInt(warpToken, burn.toBigInt()),
275
+ burnMax: formatBigInt(warpToken, burnMax.toBigInt()),
276
+ mint: formatBigInt(warpToken, mint.toBigInt()),
277
+ mintMax: formatBigInt(warpToken, mintMax.toBigInt()),
278
+ },
279
+ xERC20Address,
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Gets the balance of an extra lockbox.
285
+ *
286
+ * @param multiProvider - The multi-protocol provider
287
+ * @param warpToken - The warp token
288
+ * @param tokenPriceGetter - Price getter for calculating USD value
289
+ * @param lockboxAddress - The lockbox contract address
290
+ * @param logger - Logger instance
291
+ * @returns The balance information or undefined if not available
292
+ */
293
+ export async function getExtraLockboxBalance(
294
+ multiProvider: MultiProtocolProvider,
295
+ warpToken: Token,
296
+ tokenPriceGetter: TokenPriceGetter,
297
+ lockboxAddress: Address,
298
+ logger: Logger,
299
+ ): Promise<WarpRouteBalance | undefined> {
300
+ if (!warpToken.isXerc20()) {
301
+ return undefined;
302
+ }
303
+
304
+ const lockboxInstance = getManagedLockBox(
305
+ multiProvider,
306
+ warpToken.chainName,
307
+ lockboxAddress,
308
+ );
309
+
310
+ const erc20TokenAddress = await lockboxInstance['ERC20']();
311
+ const erc20tokenAdapter = new EvmTokenAdapter(
312
+ warpToken.chainName,
313
+ multiProvider,
314
+ {
315
+ token: erc20TokenAddress,
316
+ },
317
+ );
318
+
319
+ let balance;
320
+ try {
321
+ balance = await erc20tokenAdapter.getBalance(lockboxAddress);
322
+ } catch (err) {
323
+ logger.error(
324
+ {
325
+ err,
326
+ chain: warpToken.chainName,
327
+ token: warpToken.symbol,
328
+ lockboxAddress,
329
+ erc20TokenAddress,
330
+ },
331
+ 'Failed to get balance for contract at lockbox address',
332
+ );
333
+ return undefined;
334
+ }
335
+
336
+ const tokenPrice = await tokenPriceGetter.tryGetTokenPrice(warpToken);
337
+ const balanceNumber = formatBigInt(warpToken, balance);
338
+
339
+ return {
340
+ balance: balanceNumber,
341
+ valueUSD: tokenPrice ? balanceNumber * tokenPrice : undefined,
342
+ tokenAddress: erc20TokenAddress,
343
+ };
344
+ }
345
+
346
+ /**
347
+ * Gets collateral info for a managed lockbox.
348
+ *
349
+ * @param multiProvider - The multi-protocol provider
350
+ * @param warpToken - The warp token
351
+ * @param lockBoxAddress - The lockbox contract address
352
+ * @returns The token name and address of the collateral
353
+ */
354
+ export async function getManagedLockBoxCollateralInfo(
355
+ multiProvider: MultiProtocolProvider,
356
+ warpToken: Token,
357
+ lockBoxAddress: Address,
358
+ ): Promise<{ tokenName: string; tokenAddress: Address }> {
359
+ const lockBoxInstance = getManagedLockBox(
360
+ multiProvider,
361
+ warpToken.chainName,
362
+ lockBoxAddress,
363
+ );
364
+
365
+ const collateralTokenAddress = await lockBoxInstance['ERC20']();
366
+ const collateralTokenAdapter = new EvmTokenAdapter(
367
+ warpToken.chainName,
368
+ multiProvider,
369
+ {
370
+ token: collateralTokenAddress,
371
+ },
372
+ );
373
+
374
+ const { name } = await collateralTokenAdapter.getMetadata();
375
+
376
+ return {
377
+ tokenName: name,
378
+ tokenAddress: collateralTokenAddress,
379
+ };
380
+ }