Monitor warp balances script (#2682)
### Description - Script to monitor token balances for Nautilus-Zebec-Solana warp route (hardcoded) - Helm command to deploy warp route monitoring script to Kubernetes ### Drive-by changes - Updated default version to testnet3 for kathy and liquidity-layer ### Related issues - issue https://github.com/hyperlane-xyz/issues/issues/549 ### Backward compatibility Yes ### Testing Manualtoken-testing-forge
parent
a9b522c5bd
commit
2b5d4c7b24
@ -0,0 +1,24 @@ |
||||
apiVersion: v2 |
||||
name: warp-routes |
||||
description: Warp Routes |
||||
|
||||
# A chart can be either an 'application' or a 'library' chart. |
||||
# |
||||
# Application charts are a collection of templates that can be packaged into versioned archives |
||||
# to be deployed. |
||||
# |
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as |
||||
# a dependency of application charts to inject those utilities and functions into the rendering |
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed. |
||||
type: application |
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes |
||||
# to the chart and its templates, including the app version. |
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/) |
||||
version: 0.1.0 |
||||
|
||||
# This is the version number of the application being deployed. This version number should be |
||||
# incremented each time you make changes to the application. Versions are not expected to |
||||
# follow Semantic Versioning. They should reflect the version the application is using. |
||||
# It is recommended to use it with quotes. |
||||
appVersion: '1.16.0' |
@ -0,0 +1,66 @@ |
||||
{{/* |
||||
Expand the name of the chart. |
||||
*/}} |
||||
{{- define "hyperlane.name" -}} |
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} |
||||
{{- end }} |
||||
|
||||
{{/* |
||||
Create a default fully qualified app name. |
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). |
||||
If release name contains chart name it will be used as a full name. |
||||
*/}} |
||||
{{- define "hyperlane.fullname" -}} |
||||
{{- if .Values.fullnameOverride }} |
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} |
||||
{{- else }} |
||||
{{- $name := default .Chart.Name .Values.nameOverride }} |
||||
{{- if contains $name .Release.Name }} |
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }} |
||||
{{- else }} |
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} |
||||
{{- end }} |
||||
{{- end }} |
||||
{{- end }} |
||||
|
||||
{{/* |
||||
Create chart name and version as used by the chart label. |
||||
*/}} |
||||
{{- define "hyperlane.chart" -}} |
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} |
||||
{{- end }} |
||||
|
||||
{{/* |
||||
Common labels |
||||
*/}} |
||||
{{- define "hyperlane.labels" -}} |
||||
helm.sh/chart: {{ include "hyperlane.chart" . }} |
||||
hyperlane/deployment: {{ .Values.hyperlane.runEnv | quote }} |
||||
{{ include "hyperlane.selectorLabels" . }} |
||||
{{- if .Chart.AppVersion }} |
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} |
||||
{{- end }} |
||||
app.kubernetes.io/managed-by: {{ .Release.Service }} |
||||
{{- end }} |
||||
|
||||
{{/* |
||||
Selector labels |
||||
*/}} |
||||
{{- define "hyperlane.selectorLabels" -}} |
||||
app.kubernetes.io/name: {{ include "hyperlane.name" . }} |
||||
app.kubernetes.io/instance: {{ .Release.Name }} |
||||
{{- end }} |
||||
|
||||
{{/* |
||||
The warp-routes container |
||||
*/}} |
||||
{{- define "hyperlane.warp-routes.container" }} |
||||
- name: warp-routes |
||||
image: {{ .Values.image.repository }}:{{ .Values.image.tag }} |
||||
imagePullPolicy: IfNotPresent |
||||
command: |
||||
- ./node_modules/.bin/ts-node |
||||
- ./typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts |
||||
- -c |
||||
- "10000" |
||||
{{- end }} |
@ -0,0 +1,24 @@ |
||||
{{- if not .Values.hyperlane.cycleOnce }} |
||||
apiVersion: apps/v1 |
||||
kind: StatefulSet |
||||
metadata: |
||||
name: {{ include "hyperlane.fullname" . }} |
||||
labels: &metadata_labels |
||||
hyperlane/deployment: {{ .Values.hyperlane.runEnv | quote }} |
||||
hyperlane/context: {{ .Values.hyperlane.context | quote }} |
||||
app.kubernetes.io/component: warp-routes |
||||
spec: |
||||
selector: |
||||
matchLabels: *metadata_labels |
||||
replicas: 1 |
||||
serviceName: {{ include "hyperlane.fullname" . }} |
||||
template: |
||||
metadata: |
||||
labels: *metadata_labels |
||||
annotations: |
||||
prometheus.io/port: "9090" |
||||
prometheus.io/scrape: "true" |
||||
spec: |
||||
containers: |
||||
{{- include "hyperlane.warp-routes.container" . | indent 6 }} |
||||
{{- end }} |
@ -0,0 +1,8 @@ |
||||
image: |
||||
repository: gcr.io/hyperlane-labs-dev/hyperlane-monorepo |
||||
tag: |
||||
hyperlane: |
||||
runEnv: mainnet2 |
||||
context: hyperlane |
||||
nameOverride: "" |
||||
fullnameOverride: "" |
@ -0,0 +1,11 @@ |
||||
import { HelmCommand } from '../../src/utils/helm'; |
||||
|
||||
import { runWarpRouteHelmCommand } from './helm'; |
||||
|
||||
async function main() { |
||||
await runWarpRouteHelmCommand(HelmCommand.InstallOrUpgrade, 'mainnet2'); |
||||
} |
||||
|
||||
main() |
||||
.then(() => console.log('Deploy successful!')) |
||||
.catch(console.error); |
@ -0,0 +1,35 @@ |
||||
import { DeployEnvironment } from '../../src/config'; |
||||
import { HelmCommand, helmifyValues } from '../../src/utils/helm'; |
||||
import { execCmd } from '../../src/utils/utils'; |
||||
import { assertCorrectKubeContext, getEnvironmentConfig } from '../utils'; |
||||
|
||||
export async function runWarpRouteHelmCommand( |
||||
helmCommand: HelmCommand, |
||||
runEnv: DeployEnvironment, |
||||
) { |
||||
const envConfig = getEnvironmentConfig(runEnv); |
||||
await assertCorrectKubeContext(envConfig); |
||||
const values = getWarpRoutesHelmValues(); |
||||
|
||||
return execCmd( |
||||
`helm ${helmCommand} ${getHelmReleaseName( |
||||
'zebec', |
||||
)} ./helm/warp-routes --namespace ${runEnv} ${values.join( |
||||
' ', |
||||
)} --set fullnameOverride="${getHelmReleaseName('zebec')}"`,
|
||||
); |
||||
} |
||||
|
||||
function getHelmReleaseName(route: string): string { |
||||
return `hyperlane-warp-route-${route}`; |
||||
} |
||||
|
||||
function getWarpRoutesHelmValues() { |
||||
const values = { |
||||
image: { |
||||
repository: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', |
||||
tag: '962d34b-20230905-194531', |
||||
}, |
||||
}; |
||||
return helmifyValues(values); |
||||
} |
@ -0,0 +1,147 @@ |
||||
import { Connection } from '@solana/web3.js'; |
||||
import { ethers } from 'ethers'; |
||||
import { Gauge, Registry } from 'prom-client'; |
||||
import yargs from 'yargs'; |
||||
|
||||
import { |
||||
ERC20__factory, |
||||
SealevelHypCollateralAdapter, |
||||
TokenType, |
||||
} from '@hyperlane-xyz/hyperlane-token'; |
||||
import { ChainMap, ChainName, MultiProvider } from '@hyperlane-xyz/sdk'; |
||||
import { |
||||
ProtocolType, |
||||
debug, |
||||
objMap, |
||||
promiseObjAll, |
||||
} from '@hyperlane-xyz/utils'; |
||||
|
||||
import { |
||||
WarpTokenConfig, |
||||
tokenList, |
||||
} from '../../src/config/nautilus_token_config'; |
||||
import { startMetricsServer } from '../../src/utils/metrics'; |
||||
|
||||
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: [ |
||||
'chain_name', |
||||
'token_address', |
||||
'token_name', |
||||
'wallet_address', |
||||
'token_type', |
||||
], |
||||
}); |
||||
|
||||
async function main(): Promise<boolean> { |
||||
const { checkFrequency } = await yargs(process.argv.slice(2)) |
||||
.describe('checkFrequency', 'frequency to check balances in ms') |
||||
.demandOption('checkFrequency') |
||||
.alias('c', 'checkFrequency') |
||||
.number('checkFrequency') |
||||
.parse(); |
||||
|
||||
startMetricsServer(metricsRegister); |
||||
|
||||
const multiProvider = new MultiProvider(); |
||||
|
||||
setInterval(async () => { |
||||
try { |
||||
console.log('Checking balances'); |
||||
const balances = await checkBalance(tokenList, multiProvider); |
||||
updateTokenBalanceMetrics(tokenList, balances); |
||||
} catch (e) { |
||||
console.error('Error checking balances', e); |
||||
} |
||||
}, checkFrequency); |
||||
return true; |
||||
} |
||||
|
||||
// TODO: see issue https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2708
|
||||
async function checkBalance( |
||||
tokenConfig: WarpTokenConfig, |
||||
multiprovider: MultiProvider, |
||||
): Promise<ChainMap<number>> { |
||||
const output: ChainMap<Promise<number>> = objMap( |
||||
tokenConfig, |
||||
async (chain: ChainName, token: WarpTokenConfig[ChainName]) => { |
||||
const provider = multiprovider.getProvider(chain); |
||||
if (token.type === TokenType.native) { |
||||
if (token.protocolType === ProtocolType.Ethereum) { |
||||
const nativeBalance = await provider.getBalance( |
||||
token.hypNativeAddress, |
||||
); |
||||
return parseFloat( |
||||
ethers.utils.formatUnits(nativeBalance, token.decimals), |
||||
); |
||||
} else { |
||||
// TODO - solana native
|
||||
return 0; |
||||
} |
||||
} else { |
||||
if (token.protocolType === ProtocolType.Ethereum) { |
||||
const tokenContract = ERC20__factory.connect(token.address, provider); |
||||
const collateralBalance = await tokenContract.balanceOf( |
||||
token.hypCollateralAddress, |
||||
); |
||||
|
||||
return parseFloat( |
||||
ethers.utils.formatUnits(collateralBalance, token.decimals), |
||||
); |
||||
} else { |
||||
const connection = new Connection(multiprovider.getRpcUrl(chain)); |
||||
const adapter = new SealevelHypCollateralAdapter( |
||||
connection, |
||||
token.hypCollateralAddress, |
||||
token.address, |
||||
token.isSpl2022, |
||||
); |
||||
const collateralBalance = ethers.BigNumber.from( |
||||
await adapter.getBalance(token.hypCollateralAddress), |
||||
); |
||||
return parseFloat( |
||||
ethers.utils.formatUnits(collateralBalance, token.decimals), |
||||
); |
||||
} |
||||
} |
||||
}, |
||||
); |
||||
|
||||
return await promiseObjAll(output); |
||||
} |
||||
|
||||
function updateTokenBalanceMetrics( |
||||
tokenConfig: WarpTokenConfig, |
||||
balances: ChainMap<number>, |
||||
) { |
||||
objMap(tokenConfig, (chain: ChainName, token: WarpTokenConfig[ChainName]) => { |
||||
const tokenAddress = |
||||
token.type === TokenType.native |
||||
? ethers.constants.AddressZero |
||||
: token.address; |
||||
const walletAddress = |
||||
token.type === TokenType.native |
||||
? token.hypNativeAddress |
||||
: token.hypCollateralAddress; |
||||
|
||||
warpRouteTokenBalance |
||||
.labels({ |
||||
chain_name: chain, |
||||
token_address: tokenAddress, |
||||
token_name: token.name, |
||||
wallet_address: walletAddress, |
||||
token_type: token.type, |
||||
}) |
||||
.set(balances[chain]); |
||||
debug('Wallet balance updated for chain', { |
||||
chain, |
||||
token: token.name, |
||||
balance: balances[chain], |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
main().then(console.log).catch(console.error); |
@ -0,0 +1,68 @@ |
||||
import { TokenType } from '@hyperlane-xyz/hyperlane-token'; |
||||
import { ChainMap } from '@hyperlane-xyz/sdk'; |
||||
import { ProtocolType } from '@hyperlane-xyz/utils'; |
||||
|
||||
interface NativeTokenConfig { |
||||
chainId: number; |
||||
symbol: string; |
||||
name: string; |
||||
type: TokenType.native; |
||||
decimals: number; |
||||
hypNativeAddress: string; |
||||
protocolType: ProtocolType.Ethereum | ProtocolType.Sealevel; |
||||
} |
||||
|
||||
interface CollateralTokenConfig { |
||||
type: TokenType.collateral; |
||||
address: string; |
||||
chainId: number; |
||||
decimals: number; |
||||
symbol: string; |
||||
name: string; |
||||
hypCollateralAddress: string; |
||||
isSpl2022?: boolean; |
||||
protocolType: ProtocolType.Ethereum | ProtocolType.Sealevel; |
||||
} |
||||
|
||||
// TODO: migrate and dedupe to SDK from infra and Warp UI
|
||||
export type WarpTokenConfig = ChainMap< |
||||
CollateralTokenConfig | NativeTokenConfig |
||||
>; |
||||
|
||||
export const tokenList: WarpTokenConfig = { |
||||
// bsc
|
||||
bsc: { |
||||
type: TokenType.collateral, |
||||
chainId: 56, |
||||
address: '0x37a56cdcD83Dce2868f721De58cB3830C44C6303', |
||||
hypCollateralAddress: '0xC27980812E2E66491FD457D488509b7E04144b98', |
||||
symbol: 'ZBC', |
||||
name: 'Zebec', |
||||
decimals: 9, |
||||
protocolType: ProtocolType.Ethereum, |
||||
}, |
||||
|
||||
// nautilus
|
||||
nautilus: { |
||||
type: TokenType.native, |
||||
chainId: 22222, |
||||
hypNativeAddress: '0x4501bBE6e731A4bC5c60C03A77435b2f6d5e9Fe7', |
||||
symbol: 'ZBC', |
||||
name: 'Zebec', |
||||
decimals: 18, |
||||
protocolType: ProtocolType.Ethereum, |
||||
}, |
||||
|
||||
// solana
|
||||
solana: { |
||||
type: TokenType.collateral, |
||||
chainId: 1399811149, |
||||
address: 'wzbcJyhGhQDLTV1S99apZiiBdE4jmYfbw99saMMdP59', |
||||
hypCollateralAddress: 'EJqwFjvVJSAxH8Ur2PYuMfdvoJeutjmH6GkoEFQ4MdSa', |
||||
name: 'Zebec', |
||||
symbol: 'ZBC', |
||||
decimals: 9, |
||||
isSpl2022: false, |
||||
protocolType: ProtocolType.Sealevel, |
||||
}, |
||||
}; |
Loading…
Reference in new issue