feat(warpMonitor): Support collateral value monitoring (#4545)

### Description
- Support continuous value monitoring by allowing promethues to scrape
value metrics for native and collateral tokens
- Uses the `CoinGeckoTokenPriceGetter`
- Added `getTokenPriceByIds` helper method to the
`CoinGeckoTokenPriceGetter`, to support fetching prices using the coin
gecko id only
- Add a warp_route_id label to metrics

### Testing

Manual
pull/4751/head
Mohammed Hussan 1 month ago committed by GitHub
parent 1bd8e3e384
commit d5bdb2c28a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      typescript/infra/config/environments/mainnet3/warp/EZETH-deployments.yaml
  2. 1
      typescript/infra/config/environments/mainnet3/warp/ancient8-USDC-deployments.yaml
  3. 1
      typescript/infra/config/environments/mainnet3/warp/bsc-lumia-LUMIA-deployments.yaml
  4. 1
      typescript/infra/config/environments/mainnet3/warp/eclipse-SOL-deployments.yaml
  5. 2
      typescript/infra/config/environments/mainnet3/warp/eclipse-USDC-deployments.yaml
  6. 1
      typescript/infra/config/environments/mainnet3/warp/eclipse-WIF-deployments.yaml
  7. 1
      typescript/infra/config/environments/mainnet3/warp/ethereumUSDC-inevm-deployments.yaml
  8. 1
      typescript/infra/config/environments/mainnet3/warp/ethereumUSDT-inevm-deployments.yaml
  9. 1
      typescript/infra/config/environments/mainnet3/warp/injective-inevm-deployments.yaml
  10. 1
      typescript/infra/config/environments/mainnet3/warp/neutron-mantapacific-deployments.yaml
  11. 1
      typescript/infra/config/environments/mainnet3/warp/sei-FASTUSD-deployments.yaml
  12. 1
      typescript/infra/config/environments/mainnet3/warp/viction-ethereum-ETH-deployments.yaml
  13. 1
      typescript/infra/config/environments/mainnet3/warp/viction-ethereum-USDC-deployments.yaml
  14. 1
      typescript/infra/config/environments/mainnet3/warp/viction-ethereum-USDT-deployments.yaml
  15. 2
      typescript/infra/package.json
  16. 253
      typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts
  17. 91
      typescript/sdk/src/gas/token-prices.ts
  18. 1
      typescript/sdk/src/metadata/warpRouteConfig.ts
  19. 12
      yarn.lock

@ -10,6 +10,7 @@ data:
symbol: ezETH
hypAddress: '0xC59336D8edDa9722B4f1Ec104007191Ec16f7087'
tokenAddress: '0xbf5495Efe5DB9ce00f80364C8B423567e58d2110'
tokenCoinGeckoId: renzo-restaked-eth
decimals: 18
bsc:
protocolType: ethereum

@ -10,6 +10,7 @@ data:
type: collateral
hypAddress: '0x8b4192B9Ad1fCa440A5808641261e5289e6de95D'
tokenAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' # USDC
tokenCoinGeckoId: usd-coin
name: USDC
symbol: USDC
decimals: 6

@ -10,6 +10,7 @@ data:
type: collateral
hypAddress: '0xdD313D475f8A9d81CBE2eA953a357f52e10BA357'
tokenAddress: '0xd9343a049d5dbd89cd19dc6bca8c48fb3a0a42a7'
tokenCoinGeckoId: lumia
name: Lumia Token
symbol: LUMIA
decimals: 18

@ -9,6 +9,7 @@ data:
protocolType: sealevel
type: native
hypAddress: '8DtAGQpcMuD5sG3KdxDy49ydqXUggR1LQtebh2TECbAc'
tokenCoinGeckoId: solana
name: Solana
symbol: SOL
decimals: 9

@ -10,6 +10,7 @@ data:
type: collateral
hypAddress: '0xe1De9910fe71cC216490AC7FCF019e13a34481D7'
tokenAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' # USDC
tokenCoinGeckoId: usd-coin
name: USDC
symbol: USDC
decimals: 6
@ -18,6 +19,7 @@ data:
type: collateral
hypAddress: '3EpVCPUgyjq2MfGeCttyey6bs5zya5wjYZ2BE6yDg6bm'
tokenAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'
tokenCoinGeckoId: usd-coin
isSpl2022: false
name: USDC
symbol: USDC

@ -10,6 +10,7 @@ data:
type: collateral
hypAddress: 'CuQmsT4eSF4dYiiGUGYYQxJ7c58pUAD5ADE3BbFGzQKx'
tokenAddress: 'EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm'
tokenCoinGeckoId: dogwifcoin
name: dogwifhat
symbol: WIF
decimals: 9

@ -10,6 +10,7 @@ data:
type: collateral
hypAddress: '0xED56728fb977b0bBdacf65bCdD5e17Bb7e84504f'
tokenAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' # USDC
tokenCoinGeckoId: usd-coin
name: USDC
symbol: USDC
decimals: 6

@ -10,6 +10,7 @@ data:
type: collateral
hypAddress: '0xab852e67bf03E74C89aF67C4BA97dd1088D3dA19'
tokenAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7' # USDT
tokenCoinGeckoId: tether
name: Tether USD
symbol: USDT
decimals: 6

@ -9,6 +9,7 @@ data:
protocolType: cosmos
type: native
hypAddress: inj1mv9tjvkaw7x8w8y9vds8pkfq46g2vcfkjehc6k
tokenCoinGeckoId: injective-protocol
name: Injective Coin
symbol: INJ
decimals: 18

@ -10,6 +10,7 @@ data:
type: collateral
hypAddress: neutron1ch7x3xgpnj62weyes8vfada35zff6z59kt2psqhnx9gjnt2ttqdqtva3pa
tokenAddress: ibc/773B4D0A3CD667B2275D5A4A7A2F0909C0BA0F4059C0B9181E680DDF4965DCC7
tokenCoinGeckoId: celestia
name: Celestia
symbol: TIA
decimals: 6

@ -8,6 +8,7 @@ data:
type: collateral
hypAddress: '0x9AD81058c6C3Bf552C9014CB30E824717A0ee21b'
tokenAddress: '0x15700B564Ca08D9439C58cA5053166E8317aa138'
tokenCoinGeckoId: elixir-deusd # unique setup where we want deUSD to be deposited as collateral and we want fastUSD to be minted as a synthetic on sei
name: fastUSD
symbol: fastUSD
decimals: 18

@ -9,6 +9,7 @@ data:
protocolType: ethereum
type: native
hypAddress: '0x15b5D6B614242B118AA404528A7f3E2Ad241e4A4'
tokenCoinGeckoId: ethereum
name: Ether
symbol: ETH
decimals: 18

@ -10,6 +10,7 @@ data:
type: collateral
hypAddress: '0x31Dca7762930f56D81292f85E65c9D67575804fE'
tokenAddress: '0x31Dca7762930f56D81292f85E65c9D67575804fE' # USDC
tokenCoinGeckoId: usd-coin
name: USD Coin
symbol: USDC
decimals: 6

@ -10,6 +10,7 @@ data:
type: collateral
hypAddress: '0x4221a16A01F61c2b38A03C52d828a7041f6AAA49'
tokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7' # USDT
tokenCoinGeckoId: tether
name: Tether USD
symbol: USDT
decimals: 6

@ -14,7 +14,7 @@
"@ethersproject/providers": "^5.7.2",
"@google-cloud/secret-manager": "^5.5.0",
"@hyperlane-xyz/helloworld": "5.6.1",
"@hyperlane-xyz/registry": "4.7.0",
"@hyperlane-xyz/registry": "4.10.0",
"@hyperlane-xyz/sdk": "5.6.1",
"@hyperlane-xyz/utils": "5.6.1",
"@inquirer/prompts": "^5.3.8",

@ -9,10 +9,12 @@ import {
IXERC20__factory,
} from '@hyperlane-xyz/core';
import { ERC20__factory } from '@hyperlane-xyz/core';
import { createWarpRouteConfigId } from '@hyperlane-xyz/registry';
import {
ChainMap,
ChainMetadata,
ChainName,
CoinGeckoTokenPriceGetter,
CosmNativeTokenAdapter,
CwNativeTokenAdapter,
MultiProtocolProvider,
@ -38,17 +40,41 @@ import { getEnvironmentConfig } from '../core-utils.js';
const logger = rootLogger.child({ module: 'warp-balance-monitor' });
const metricsRegister = new Registry();
const warpRouteTokenBalance = new Gauge({
name: 'hyperlane_warp_route_token_balance',
help: 'HypERC20 token balance of a Warp Route',
registers: [metricsRegister],
labelNames: [
interface WarpRouteMetrics {
chain_name: ChainName;
token_address: string;
token_name: string;
wallet_address: string;
token_type: TokenType;
warp_route_id: string;
related_chain_names: string;
}
type WarpRouteMetricLabels = keyof WarpRouteMetrics;
const warpRouteMetricLabels: WarpRouteMetricLabels[] = [
'chain_name',
'token_address',
'token_name',
'wallet_address',
'token_type',
],
'warp_route_id',
'related_chain_names',
];
const warpRouteTokenBalance = new Gauge({
name: 'hyperlane_warp_route_token_balance',
help: 'HypERC20 token balance of a Warp Route',
registers: [metricsRegister],
labelNames: warpRouteMetricLabels,
});
const warpRouteCollateralValue = new Gauge({
name: 'hyperlane_warp_route_collateral_value',
help: 'Total value of collateral held in a HypERC20Collateral or HypNative contract of a Warp Route',
registers: [metricsRegister],
labelNames: warpRouteMetricLabels,
});
const xERC20LimitsGauge = new Gauge({
@ -66,6 +92,11 @@ interface xERC20Limit {
burnMax: number;
}
interface WarpRouteInfo {
balance: number;
valueUSD?: number;
}
export function readWarpRouteConfig(filePath: string) {
const config = readYaml(filePath);
if (!config) throw new Error(`No warp config found at ${filePath}`);
@ -112,7 +143,8 @@ async function main(): Promise<boolean> {
async function checkBalance(
tokenConfig: WarpRouteConfig,
multiProtocolProvider: MultiProtocolProvider,
): Promise<ChainMap<number>> {
tokenPriceGetter: CoinGeckoTokenPriceGetter,
): Promise<ChainMap<WarpRouteInfo>> {
const output = objMap(
tokenConfig,
async (chain: ChainName, token: WarpRouteConfig[ChainName]) => {
@ -122,8 +154,12 @@ async function checkBalance(
case ProtocolType.Ethereum: {
const provider = multiProtocolProvider.getEthersV5Provider(chain);
const nativeBalance = await provider.getBalance(token.hypAddress);
return parseFloat(
ethers.utils.formatUnits(nativeBalance, token.decimals),
return getNativeTokenWarpInfo(
nativeBalance,
token.decimals,
tokenPriceGetter,
chain,
);
}
case ProtocolType.Sealevel: {
@ -142,8 +178,12 @@ async function checkBalance(
const balance = ethers.BigNumber.from(
await adapter.getBalance(token.hypAddress),
);
return parseFloat(
ethers.utils.formatUnits(balance, token.decimals),
return getNativeTokenWarpInfo(
balance,
token.decimals,
tokenPriceGetter,
chain,
);
}
case ProtocolType.Cosmos: {
@ -156,8 +196,12 @@ async function checkBalance(
{ ibcDenom: token.ibcDenom },
);
const tokenBalance = await adapter.getBalance(token.hypAddress);
return parseFloat(
ethers.utils.formatUnits(tokenBalance, token.decimals),
return getNativeTokenWarpInfo(
tokenBalance,
token.decimals,
tokenPriceGetter,
chain,
);
}
}
@ -177,8 +221,11 @@ async function checkBalance(
token.hypAddress,
);
return parseFloat(
ethers.utils.formatUnits(collateralBalance, token.decimals),
return getCollateralTokenWarpInfo(
collateralBalance,
token.decimals,
tokenPriceGetter,
token.tokenCoinGeckoId,
);
}
case ProtocolType.Sealevel: {
@ -198,8 +245,12 @@ async function checkBalance(
const collateralBalance = ethers.BigNumber.from(
await adapter.getBalance(token.hypAddress),
);
return parseFloat(
ethers.utils.formatUnits(collateralBalance, token.decimals),
return getCollateralTokenWarpInfo(
collateralBalance,
token.decimals,
tokenPriceGetter,
token.tokenCoinGeckoId,
);
}
case ProtocolType.Cosmos: {
@ -216,8 +267,12 @@ async function checkBalance(
const collateralBalance = ethers.BigNumber.from(
await adapter.getBalance(token.hypAddress),
);
return parseFloat(
ethers.utils.formatUnits(collateralBalance, token.decimals),
return getCollateralTokenWarpInfo(
collateralBalance,
token.decimals,
tokenPriceGetter,
token.tokenCoinGeckoId,
);
}
}
@ -232,9 +287,11 @@ async function checkBalance(
provider,
);
const syntheticBalance = await tokenContract.totalSupply();
return parseFloat(
return {
balance: parseFloat(
ethers.utils.formatUnits(syntheticBalance, token.decimals),
);
),
};
}
case ProtocolType.Sealevel: {
if (!token.tokenAddress)
@ -253,13 +310,15 @@ async function checkBalance(
const syntheticBalance = ethers.BigNumber.from(
await adapter.getTotalSupply(),
);
return parseFloat(
return {
balance: parseFloat(
ethers.utils.formatUnits(syntheticBalance, token.decimals),
);
),
};
}
case ProtocolType.Cosmos:
// TODO - cosmos synthetic
return 0;
return { balance: 0 };
}
break;
}
@ -275,9 +334,11 @@ async function checkBalance(
const xerc20 = IXERC20__factory.connect(xerc20Address, provider);
const syntheticBalance = await xerc20.totalSupply();
return parseFloat(
return {
balance: parseFloat(
ethers.utils.formatUnits(syntheticBalance, token.decimals),
);
),
};
}
default:
throw new Error(
@ -307,8 +368,11 @@ async function checkBalance(
xerc20LockboxAddress,
);
return parseFloat(
ethers.utils.formatUnits(collateralBalance, token.decimals),
return getCollateralTokenWarpInfo(
collateralBalance,
token.decimals,
tokenPriceGetter,
token.tokenCoinGeckoId,
);
}
default:
@ -318,7 +382,7 @@ async function checkBalance(
}
}
}
return 0;
return { balance: 0 };
},
);
@ -327,22 +391,44 @@ async function checkBalance(
export function updateTokenBalanceMetrics(
tokenConfig: WarpRouteConfig,
balances: ChainMap<number>,
balances: ChainMap<WarpRouteInfo>,
) {
objMap(tokenConfig, (chain: ChainName, token: WarpRouteConfig[ChainName]) => {
warpRouteTokenBalance
.labels({
const metrics: WarpRouteMetrics = {
chain_name: chain,
token_address: token.tokenAddress ?? ethers.constants.AddressZero,
token_name: token.name,
wallet_address: token.hypAddress,
token_type: token.type,
})
.set(balances[chain]);
warp_route_id: createWarpRouteConfigId(
token.symbol,
Object.keys(tokenConfig) as ChainName[],
),
related_chain_names: Object.keys(tokenConfig)
.filter((chainName) => chainName !== chain)
.sort()
.join(','),
};
warpRouteTokenBalance.labels(metrics).set(balances[chain].balance);
if (balances[chain].valueUSD) {
warpRouteCollateralValue
.labels(metrics)
.set(balances[chain].valueUSD as number);
logger.debug('Collateral value updated for chain', {
chain,
related_chain_names: metrics.related_chain_names,
warp_route_id: metrics.warp_route_id,
token: metrics.token_name,
value: balances[chain].valueUSD,
});
}
logger.debug('Wallet balance updated for chain', {
chain,
token: token.name,
balance: balances[chain],
related_chain_names: metrics.related_chain_names,
warp_route_id: metrics.warp_route_id,
token: metrics.token_name,
balance: balances[chain].balance,
});
});
}
@ -468,21 +554,113 @@ const getXERC20Limit = async (
};
};
async function getTokenPriceByChain(
chain: ChainName,
tokenPriceGetter: CoinGeckoTokenPriceGetter,
): Promise<number | undefined> {
try {
return await tokenPriceGetter.getTokenPrice(chain);
} catch (e) {
logger.warn('Error getting token price', e);
return undefined;
}
}
async function getNativeTokenValue(
chain: ChainName,
balanceFloat: number,
tokenPriceGetter: CoinGeckoTokenPriceGetter,
): Promise<number | undefined> {
const price = await getTokenPriceByChain(chain, tokenPriceGetter);
logger.debug(`${chain} native token price ${price}`);
if (!price) return undefined;
return balanceFloat * price;
}
async function getNativeTokenWarpInfo(
balance: ethers.BigNumber | bigint,
decimal: number,
tokenPriceGetter: CoinGeckoTokenPriceGetter,
chain: ChainName,
): Promise<WarpRouteInfo> {
const balanceFloat = parseFloat(ethers.utils.formatUnits(balance, decimal));
const value = await getNativeTokenValue(
chain,
balanceFloat,
tokenPriceGetter,
);
return { balance: balanceFloat, valueUSD: value };
}
async function getCollateralTokenPrice(
tokenCoinGeckoId: string | undefined,
tokenPriceGetter: CoinGeckoTokenPriceGetter,
): Promise<number | undefined> {
if (!tokenCoinGeckoId) return undefined;
const prices = await tokenPriceGetter.getTokenPriceByIds([tokenCoinGeckoId]);
if (!prices) return undefined;
return prices[0];
}
async function getCollateralTokenValue(
tokenCoinGeckoId: string | undefined,
balanceFloat: number,
tokenPriceGetter: CoinGeckoTokenPriceGetter,
): Promise<number | undefined> {
const price = await getCollateralTokenPrice(
tokenCoinGeckoId,
tokenPriceGetter,
);
logger.debug(`${tokenCoinGeckoId} token price ${price}`);
if (!price) return undefined;
return balanceFloat * price;
}
async function getCollateralTokenWarpInfo(
balance: ethers.BigNumber | bigint,
decimal: number,
tokenPriceGetter: CoinGeckoTokenPriceGetter,
tokenCoinGeckoId?: string,
): Promise<WarpRouteInfo> {
const balanceFloat = parseFloat(ethers.utils.formatUnits(balance, decimal));
const value = await getCollateralTokenValue(
tokenCoinGeckoId,
balanceFloat,
tokenPriceGetter,
);
return { balance: balanceFloat, valueUSD: value };
}
async function checkWarpRouteMetrics(
checkFrequency: number,
tokenConfig: WarpRouteConfig,
chainMetadata: ChainMap<ChainMetadata>,
) {
const tokenPriceGetter =
CoinGeckoTokenPriceGetter.withDefaultCoinGecko(chainMetadata);
setInterval(async () => {
try {
const multiProtocolProvider = new MultiProtocolProvider(chainMetadata);
const balances = await checkBalance(tokenConfig, multiProtocolProvider);
const balances = await checkBalance(
tokenConfig,
multiProtocolProvider,
tokenPriceGetter,
);
logger.info('Token Balances:', balances);
updateTokenBalanceMetrics(tokenConfig, balances);
} catch (e) {
logger.error('Error checking balances', e);
}
// only check xERC20 limits if there are xERC20 tokens in the config
if (
Object.keys(tokenConfig).some(
(chain) =>
tokenConfig[chain].type === TokenType.XERC20 ||
tokenConfig[chain].type === TokenType.XERC20Lockbox,
)
) {
try {
const xERC20Limits = await getXERC20Limits(tokenConfig, chainMetadata);
logger.info('xERC20 Limits:', xERC20Limits);
@ -490,6 +668,7 @@ async function checkWarpRouteMetrics(
} catch (e) {
logger.error('Error checking xERC20 limits', e);
}
}
}, checkFrequency);
}

@ -23,23 +23,23 @@ type TokenPriceCacheEntry = {
};
class TokenPriceCache {
protected cache: Map<ChainName, TokenPriceCacheEntry>;
protected cache: Map<string, TokenPriceCacheEntry>;
protected freshSeconds: number;
protected evictionSeconds: number;
constructor(freshSeconds = 60, evictionSeconds = 3 * 60 * 60) {
this.cache = new Map<ChainName, TokenPriceCacheEntry>();
this.cache = new Map<string, TokenPriceCacheEntry>();
this.freshSeconds = freshSeconds;
this.evictionSeconds = evictionSeconds;
}
put(chain: ChainName, price: number): void {
put(id: string, price: number): void {
const now = new Date();
this.cache.set(chain, { timestamp: now, price });
this.cache.set(id, { timestamp: now, price });
}
isFresh(chain: ChainName): boolean {
const entry = this.cache.get(chain);
isFresh(id: string): boolean {
const entry = this.cache.get(id);
if (!entry) return false;
const expiryTime = new Date(
@ -49,17 +49,17 @@ class TokenPriceCache {
return now < expiryTime;
}
fetch(chain: ChainName): number {
const entry = this.cache.get(chain);
fetch(id: string): number {
const entry = this.cache.get(id);
if (!entry) {
throw new Error(`no entry found for ${chain} in token price cache`);
throw new Error(`no entry found for ${id} in token price cache`);
}
const evictionTime = new Date(
entry.timestamp.getTime() + 1000 * this.evictionSeconds,
);
const now = new Date();
if (now > evictionTime) {
throw new Error(`evicted entry found for ${chain} in token price cache`);
throw new Error(`evicted entry found for ${id} in token price cache`);
}
return entry.price;
}
@ -97,20 +97,30 @@ export class CoinGeckoTokenPriceGetter implements TokenPriceGetter {
);
}
async getTokenPrice(chain: ChainName): Promise<number> {
const [price] = await this.getTokenPrices([chain]);
async getTokenPrice(
chain: ChainName,
currency: string = 'usd',
): Promise<number> {
const [price] = await this.getTokenPrices([chain], currency);
return price;
}
async getTokenExchangeRate(
base: ChainName,
quote: ChainName,
currency: string = 'usd',
): Promise<number> {
const [basePrice, quotePrice] = await this.getTokenPrices([base, quote]);
const [basePrice, quotePrice] = await this.getTokenPrices(
[base, quote],
currency,
);
return basePrice / quotePrice;
}
private async getTokenPrices(chains: ChainName[]): Promise<number[]> {
private async getTokenPrices(
chains: ChainName[],
currency: string = 'usd',
): Promise<number[]> {
const isMainnet = chains.map((c) => !this.metadata[c].isTestnet);
const allMainnets = isMainnet.every((v) => v === true);
const allTestnets = isMainnet.every((v) => v === false);
@ -125,32 +135,43 @@ export class CoinGeckoTokenPriceGetter implements TokenPriceGetter {
);
}
const toQuery = chains.filter((c) => !this.cache.isFresh(c));
if (toQuery.length > 0) {
try {
await this.queryTokenPrices(toQuery);
} catch (e) {
rootLogger.warn('Failed to query token prices', e);
}
}
return chains.map((chain) => this.cache.fetch(chain));
}
private async queryTokenPrices(chains: ChainName[]): Promise<void> {
const currency = 'usd';
// The CoinGecko API expects, in some cases, IDs that do not match
// ChainNames.
const ids = chains.map(
(chain) => this.metadata[chain].gasCurrencyCoinGeckoId || chain,
);
// Coingecko rate limits, so we are adding this sleep
await this.getTokenPriceByIds(ids, currency);
return chains.map((chain) =>
this.cache.fetch(this.metadata[chain].gasCurrencyCoinGeckoId || chain),
);
}
public async getTokenPriceByIds(
ids: string[],
currency: string = 'usd',
): Promise<number[] | undefined> {
const toQuery = ids.filter((id) => !this.cache.isFresh(id));
await sleep(this.sleepMsBetweenRequests);
const response = await this.coinGecko.simple.price({
ids,
if (toQuery.length > 0) {
let response: any;
try {
response = await this.coinGecko.simple.price({
ids: toQuery,
vs_currencies: [currency],
});
const prices = ids.map((id) => response.data[id][currency]);
// Update the cache with the newly fetched prices
chains.map((chain, i) => this.cache.put(chain, prices[i]));
if (response.success === true) {
const prices = toQuery.map((id) => response.data[id][currency]);
toQuery.map((id, i) => this.cache.put(id, prices[i]));
} else {
rootLogger.warn('Failed to query token prices', response.message);
return undefined;
}
} catch (e) {
rootLogger.warn('Error when querying token prices', e);
return undefined;
}
}
return ids.map((id) => this.cache.fetch(id));
}
}

@ -10,6 +10,7 @@ const TokenConfigSchema = z.object({
type: z.nativeEnum(TokenType),
hypAddress: z.string(), // HypERC20Collateral, HypERC20Synthetic, HypNativeToken address
tokenAddress: z.string().optional(), // external token address needed for collateral type eg tokenAddress.balanceOf(hypAddress)
tokenCoinGeckoId: z.string().optional(), // CoinGecko id for token
name: z.string(),
symbol: z.string(),
decimals: z.number(),

@ -7971,7 +7971,7 @@ __metadata:
"@ethersproject/providers": "npm:^5.7.2"
"@google-cloud/secret-manager": "npm:^5.5.0"
"@hyperlane-xyz/helloworld": "npm:5.6.1"
"@hyperlane-xyz/registry": "npm:4.7.0"
"@hyperlane-xyz/registry": "npm:4.10.0"
"@hyperlane-xyz/sdk": "npm:5.6.1"
"@hyperlane-xyz/utils": "npm:5.6.1"
"@inquirer/prompts": "npm:^5.3.8"
@ -8029,6 +8029,16 @@ __metadata:
languageName: unknown
linkType: soft
"@hyperlane-xyz/registry@npm:4.10.0":
version: 4.10.0
resolution: "@hyperlane-xyz/registry@npm:4.10.0"
dependencies:
yaml: "npm:2.4.5"
zod: "npm:^3.21.2"
checksum: 22bb18f426cbada8b97db0894fe5d0980dfc08ecbd5174c978b7aeb6d8df9706f93d7e9cf0630644d2455ad05feee714dc2a38ec515a717b0b257184637902fb
languageName: node
linkType: hard
"@hyperlane-xyz/registry@npm:4.7.0":
version: 4.7.0
resolution: "@hyperlane-xyz/registry@npm:4.7.0"

Loading…
Cancel
Save