From de2b9a6d4ab6753af71ded49f3bef4e8d5a13c2d Mon Sep 17 00:00:00 2001 From: pbio <10051819+paulbalaji@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:56:19 +0000 Subject: [PATCH] feat!: separate rebalancer package --- .github/workflows/rebalancer-docker.yml | 134 ++++++ Dockerfile | 1 + package.json | 1 + solhint-plugin/package.json | 2 +- typescript/ccip-server/package.json | 4 +- typescript/cli/package.json | 5 +- typescript/cli/src/commands/warp.ts | 68 ++- .../context/strategies/chain/chainResolver.ts | 2 +- typescript/cli/src/rebalancer/README.md | 177 -------- .../config/RebalancerConfig.test.ts | 387 ------------------ .../rebalancer/core/WithInflightGuard.test.ts | 131 ------ .../src/rebalancer/core/WithSemaphore.test.ts | 112 ----- typescript/cli/src/rebalancer/index.ts | 5 - typescript/cli/src/rebalancer/runner.ts | 286 ------------- typescript/helloworld/package.json | 2 +- typescript/http-registry-server/package.json | 2 +- .../http-registry-server/src/routes/chain.ts | 4 +- .../http-registry-server/src/routes/root.ts | 4 +- .../http-registry-server/src/routes/warp.ts | 4 +- .../helm/rebalancer/templates/_helpers.tpl | 27 +- typescript/infra/helm/rebalancer/values.yaml | 2 +- typescript/infra/package.json | 5 +- typescript/infra/src/utils/metrics.ts | 6 +- typescript/rebalancer/.gitignore | 3 + typescript/rebalancer/.mocharc.json | 3 + typescript/rebalancer/Dockerfile | 89 ++++ typescript/rebalancer/README.md | 257 ++++++++++++ typescript/rebalancer/eslint.config.mjs | 14 + typescript/rebalancer/package.json | 70 ++++ .../src}/config/RebalancerConfig.ts | 2 +- .../src}/core/Rebalancer.ts | 0 .../rebalancer/src/core/RebalancerService.ts | 333 +++++++++++++++ .../src}/core/WithInflightGuard.ts | 0 .../src}/core/WithSemaphore.ts | 0 .../factories/RebalancerContextFactory.ts | 53 ++- typescript/rebalancer/src/index.ts | 68 +++ .../src}/interfaces/IMetrics.ts | 0 .../src}/interfaces/IMonitor.ts | 2 +- .../src}/interfaces/IRebalancer.ts | 0 .../src}/interfaces/IStrategy.ts | 0 .../src}/metrics/Metrics.ts | 0 .../src}/metrics/PriceGetter.ts | 0 .../src}/metrics/scripts/metrics.ts | 0 .../src}/metrics/types.ts | 0 .../src}/metrics/utils/metrics.ts | 0 .../src}/monitor/Monitor.ts | 0 typescript/rebalancer/src/service.ts | 144 +++++++ .../src}/strategy/BaseStrategy.ts | 0 .../src}/strategy/MinAmountStrategy.test.ts | 0 .../src}/strategy/MinAmountStrategy.ts | 0 .../src}/strategy/StrategyFactory.test.ts | 0 .../src}/strategy/StrategyFactory.ts | 0 .../src}/strategy/WeightedStrategy.test.ts | 0 .../src}/strategy/WeightedStrategy.ts | 0 .../src}/strategy/index.ts | 0 .../src}/test/helpers.ts | 0 .../src}/utils/ExplorerClient.ts | 0 .../src}/utils/balanceUtils.test.ts | 0 .../src}/utils/balanceUtils.ts | 0 .../src}/utils/bridgeUtils.test.ts | 0 .../src}/utils/bridgeUtils.ts | 0 typescript/rebalancer/src/utils/errors.ts | 5 + typescript/rebalancer/src/utils/files.ts | 266 ++++++++++++ .../src}/utils/generalUtils.ts | 0 .../src}/utils/index.ts | 0 .../src}/utils/tokenUtils.ts | 0 typescript/rebalancer/tsconfig.json | 8 + typescript/widgets/package.json | 2 +- yarn.lock | 89 ++-- 69 files changed, 1581 insertions(+), 1198 deletions(-) create mode 100644 .github/workflows/rebalancer-docker.yml delete mode 100644 typescript/cli/src/rebalancer/README.md delete mode 100644 typescript/cli/src/rebalancer/config/RebalancerConfig.test.ts delete mode 100644 typescript/cli/src/rebalancer/core/WithInflightGuard.test.ts delete mode 100644 typescript/cli/src/rebalancer/core/WithSemaphore.test.ts delete mode 100644 typescript/cli/src/rebalancer/index.ts delete mode 100644 typescript/cli/src/rebalancer/runner.ts create mode 100644 typescript/rebalancer/.gitignore create mode 100644 typescript/rebalancer/.mocharc.json create mode 100644 typescript/rebalancer/Dockerfile create mode 100644 typescript/rebalancer/README.md create mode 100644 typescript/rebalancer/eslint.config.mjs create mode 100644 typescript/rebalancer/package.json rename typescript/{cli/src/rebalancer => rebalancer/src}/config/RebalancerConfig.ts (94%) rename typescript/{cli/src/rebalancer => rebalancer/src}/core/Rebalancer.ts (100%) create mode 100644 typescript/rebalancer/src/core/RebalancerService.ts rename typescript/{cli/src/rebalancer => rebalancer/src}/core/WithInflightGuard.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/core/WithSemaphore.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/factories/RebalancerContextFactory.ts (80%) create mode 100644 typescript/rebalancer/src/index.ts rename typescript/{cli/src/rebalancer => rebalancer/src}/interfaces/IMetrics.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/interfaces/IMonitor.ts (96%) rename typescript/{cli/src/rebalancer => rebalancer/src}/interfaces/IRebalancer.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/interfaces/IStrategy.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/metrics/Metrics.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/metrics/PriceGetter.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/metrics/scripts/metrics.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/metrics/types.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/metrics/utils/metrics.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/monitor/Monitor.ts (100%) create mode 100644 typescript/rebalancer/src/service.ts rename typescript/{cli/src/rebalancer => rebalancer/src}/strategy/BaseStrategy.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/strategy/MinAmountStrategy.test.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/strategy/MinAmountStrategy.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/strategy/StrategyFactory.test.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/strategy/StrategyFactory.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/strategy/WeightedStrategy.test.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/strategy/WeightedStrategy.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/strategy/index.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/test/helpers.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/utils/ExplorerClient.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/utils/balanceUtils.test.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/utils/balanceUtils.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/utils/bridgeUtils.test.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/utils/bridgeUtils.ts (100%) create mode 100644 typescript/rebalancer/src/utils/errors.ts create mode 100644 typescript/rebalancer/src/utils/files.ts rename typescript/{cli/src/rebalancer => rebalancer/src}/utils/generalUtils.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/utils/index.ts (100%) rename typescript/{cli/src/rebalancer => rebalancer/src}/utils/tokenUtils.ts (100%) create mode 100644 typescript/rebalancer/tsconfig.json diff --git a/.github/workflows/rebalancer-docker.yml b/.github/workflows/rebalancer-docker.yml new file mode 100644 index 00000000000..f7d7d0a0339 --- /dev/null +++ b/.github/workflows/rebalancer-docker.yml @@ -0,0 +1,134 @@ +name: Build and Push Rebalancer Image to GCR +on: + push: + branches: [main] + tags: + - '**' + paths: + - 'typescript/rebalancer/**' + - 'typescript/sdk/**' + - 'typescript/provider-sdk/**' + - 'typescript/utils/**' + - '.registryrc' + - '.github/workflows/rebalancer-docker.yml' + pull_request: + paths: + - 'typescript/rebalancer/**' + - 'typescript/sdk/**' + - 'typescript/provider-sdk/**' + - 'typescript/utils/**' + - '.registryrc' + - '.github/workflows/rebalancer-docker.yml' + workflow_dispatch: + inputs: + include_arm64: + description: 'Include arm64 in the build' + required: false + default: 'false' + +concurrency: + group: build-push-rebalancer-${{ github.ref }} + cancel-in-progress: true + +jobs: + check-env: + runs-on: ubuntu-latest + outputs: + gcloud-service-key: ${{ steps.gcloud-service-key.outputs.defined }} + steps: + - id: gcloud-service-key + env: + GCLOUD_SERVICE_KEY: ${{ secrets.GCLOUD_SERVICE_KEY }} + if: "${{ env.GCLOUD_SERVICE_KEY != '' }}" + run: echo "defined=true" >> $GITHUB_OUTPUT + + build-and-push-to-gcr: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + pull-requests: write + + needs: [check-env] + if: needs.check-env.outputs.gcloud-service-key == 'true' + + steps: + - name: Generate GitHub App Token + id: generate-token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.HYPER_GONK_APP_ID }} + private-key: ${{ secrets.HYPER_GONK_PRIVATE_KEY }} + + - uses: actions/checkout@v5 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + submodules: recursive + + - name: Generate tag data + id: taggen + run: | + echo "TAG_DATE=$(date +'%Y%m%d-%H%M%S')" >> $GITHUB_OUTPUT + echo "TAG_SHA=$(echo '${{ github.event.pull_request.head.sha || github.sha }}' | cut -b 1-7)" >> $GITHUB_OUTPUT + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + gcr.io/abacus-labs-dev/hyperlane-rebalancer + tags: | + type=ref,event=branch + type=ref,event=pr + type=raw,value=${{ steps.taggen.outputs.TAG_SHA }}-${{ steps.taggen.outputs.TAG_DATE }} + + - name: Set up Depot CLI + uses: depot/setup-action@v1 + + - name: Login to GCR + uses: docker/login-action@v3 + with: + registry: gcr.io + username: _json_key + password: ${{ secrets.GCLOUD_SERVICE_KEY }} + + - name: Read .registryrc + shell: bash + run: | + REGISTRY_VERSION=$(cat .registryrc) + echo "REGISTRY_VERSION=$REGISTRY_VERSION" >> $GITHUB_ENV + + - name: Determine platforms + id: determine-platforms + run: | + if [ "${{ github.event.inputs.include_arm64 }}" == "true" ]; then + echo "platforms=linux/amd64,linux/arm64" >> $GITHUB_OUTPUT + else + echo "platforms=linux/amd64" >> $GITHUB_OUTPUT + fi + + - name: Build and push + id: build + uses: depot/build-push-action@v1 + with: + project: 3cpjhx94qv + context: ./ + file: ./typescript/rebalancer/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + REGISTRY_COMMIT=${{ env.REGISTRY_VERSION }} + platforms: ${{ steps.determine-platforms.outputs.platforms }} + + - name: Comment image tags on PR + if: github.event_name == 'pull_request' && always() + uses: ./.github/actions/docker-image-comment + with: + comment_tag: rebalancer-docker-image + image_name: Rebalancer Docker Image + emoji: ♻️ + image_tags: ${{ steps.meta.outputs.tags }} + pr_number: ${{ github.event.pull_request.number }} + github_token: ${{ steps.generate-token.outputs.token }} + job_status: ${{ job.status }} diff --git a/Dockerfile b/Dockerfile index f052c9c7b57..899f5582cb9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,6 +24,7 @@ COPY typescript/http-registry-server/package.json ./typescript/http-registry-ser COPY typescript/infra/package.json ./typescript/infra/ COPY typescript/provider-sdk/package.json ./typescript/provider-sdk/ COPY typescript/radix-sdk/package.json ./typescript/radix-sdk/ +COPY typescript/rebalancer/package.json ./typescript/rebalancer/ COPY typescript/sdk/package.json ./typescript/sdk/ COPY typescript/tsconfig/package.json ./typescript/tsconfig/ COPY typescript/utils/package.json ./typescript/utils/ diff --git a/package.json b/package.json index 3a621d3ad45..d9ca51dcb13 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "starknet" ], "resolutions": { + "zod": "3.21.2", "async": "^2.6.4", "fetch-ponyfill": "^7.1", "flat": "^5.0.2", diff --git a/solhint-plugin/package.json b/solhint-plugin/package.json index c0473462ec4..f5c4ffc9c35 100644 --- a/solhint-plugin/package.json +++ b/solhint-plugin/package.json @@ -1,7 +1,7 @@ { "name": "solhint-plugin-hyperlane", "private": true, - "version": "19.9.0", + "version": "19.10.0", "description": "", "license": "Apache-2.0", "type": "commonjs", diff --git a/typescript/ccip-server/package.json b/typescript/ccip-server/package.json index 6ca394f8b0b..638f0f2249b 100644 --- a/typescript/ccip-server/package.json +++ b/typescript/ccip-server/package.json @@ -48,7 +48,7 @@ "@eth-optimism/sdk": "^3.3.3", "@google-cloud/pino-logging-gcp-config": "^1.0.6", "@hyperlane-xyz/core": "10.0.4", - "@hyperlane-xyz/registry": "20.0.0", + "@hyperlane-xyz/registry": "23.6.0", "@hyperlane-xyz/sdk": "19.10.0", "@hyperlane-xyz/utils": "19.10.0", "@prisma/client": "^6.8.2", @@ -58,7 +58,7 @@ "express": "^4.17.1", "pino-http": "^10.2.0", "prisma": "^6.8.2", - "prom-client": "^14.0.1", + "prom-client": "^15.1.0", "zod": "^3.21.2" } } diff --git a/typescript/cli/package.json b/typescript/cli/package.json index 01d43142f7f..1319a7ab420 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -15,7 +15,8 @@ "@hyperlane-xyz/http-registry-server": "19.10.0", "@hyperlane-xyz/provider-sdk": "0.2.0", "@hyperlane-xyz/radix-sdk": "19.10.0", - "@hyperlane-xyz/registry": "20.0.0", + "@hyperlane-xyz/rebalancer": "0.1.0", + "@hyperlane-xyz/registry": "23.6.0", "@hyperlane-xyz/sdk": "19.10.0", "@hyperlane-xyz/tsconfig": "workspace:^", "@hyperlane-xyz/utils": "19.10.0", @@ -49,7 +50,7 @@ "mocha": "^11.5.0", "pino": "^8.19.0", "prettier": "^3.5.3", - "prom-client": "^14.0.1", + "prom-client": "^15.1.0", "sinon": "^13.0.2", "terminal-link": "^3.0.0", "testcontainers": "11.7.0", diff --git a/typescript/cli/src/commands/warp.ts b/typescript/cli/src/commands/warp.ts index 2ee9d8ed90f..b732851a431 100644 --- a/typescript/cli/src/commands/warp.ts +++ b/typescript/cli/src/commands/warp.ts @@ -2,6 +2,7 @@ import util from 'util'; import { stringify as yamlStringify } from 'yaml'; import { CommandModule } from 'yargs'; +import { RebalancerConfig, RebalancerService } from '@hyperlane-xyz/rebalancer'; import { RawForkedChainConfigByChain, RawForkedChainConfigByChainSchema, @@ -15,6 +16,7 @@ import { difference, intersection, objFilter, + rootLogger, } from '@hyperlane-xyz/utils'; import { runWarpRouteCheck } from '../check/warp.js'; @@ -35,7 +37,6 @@ import { logGreen, } from '../logger.js'; import { getWarpRouteConfigsByCore, runWarpRouteRead } from '../read/warp.js'; -import { RebalancerRunner } from '../rebalancer/runner.js'; import { sendTestTransfer } from '../send/transfer.js'; import { ExtendedChainSubmissionStrategySchema } from '../submitters/types.js'; import { @@ -490,18 +491,63 @@ export const rebalancer: CommandModuleWithWriteContext<{ }, }, handler: async (args) => { - let runner: RebalancerRunner; - try { - const { context, ...rest } = args; - runner = await RebalancerRunner.create(rest, context); - } catch (e: any) { - // exit on startup errors - errorRed(`Rebalancer startup error: ${util.format(e)}`); - process.exit(1); - } + const { + context, + config: configPath, + checkFrequency, + withMetrics, + monitorOnly, + manual, + origin, + destination, + amount, + } = args; + + logCommandHeader('Hyperlane Warp Route Rebalancer'); try { - await runner.run(); + // Load rebalancer configuration + const rebalancerConfig = RebalancerConfig.load(configPath); + + // Determine execution mode + const mode = manual ? 'manual' : 'daemon'; + + // Create rebalancer service + const service = new RebalancerService( + context.multiProvider, + context.multiProtocolProvider, + context.registry, + rebalancerConfig, + { + mode, + checkFrequency, + withMetrics, + monitorOnly, + coingeckoApiKey: process.env.COINGECKO_API_KEY, + logger: rootLogger.child({ module: 'rebalancer' }), + }, + ); + + // Execute based on mode + if (manual) { + if (!origin || !destination || !amount) { + errorRed( + 'Origin, destination, and amount are required for manual rebalance', + ); + process.exit(1); + } + + await service.executeManual({ + origin, + destination, + amount, + }); + + logGreen('✅ Manual rebalance completed successfully'); + } else { + // Start daemon mode + await service.start(); + } } catch (e: any) { errorRed(`Rebalancer error: ${util.format(e)}`); process.exit(1); diff --git a/typescript/cli/src/context/strategies/chain/chainResolver.ts b/typescript/cli/src/context/strategies/chain/chainResolver.ts index 87ee90673d0..748a6d9225e 100644 --- a/typescript/cli/src/context/strategies/chain/chainResolver.ts +++ b/typescript/cli/src/context/strategies/chain/chainResolver.ts @@ -1,3 +1,4 @@ +import { RebalancerConfig } from '@hyperlane-xyz/rebalancer'; import { ChainName, DeployedCoreAddresses, @@ -9,7 +10,6 @@ import { ProtocolType, assert } from '@hyperlane-xyz/utils'; import { CommandType } from '../../../commands/signCommands.js'; import { readCoreDeployConfigs } from '../../../config/core.js'; import { getWarpRouteDeployConfig } from '../../../config/warp.js'; -import { RebalancerConfig } from '../../../rebalancer/config/RebalancerConfig.js'; import { runMultiChainSelectionStep, runSingleChainSelectionStep, diff --git a/typescript/cli/src/rebalancer/README.md b/typescript/cli/src/rebalancer/README.md deleted file mode 100644 index a375f41da95..00000000000 --- a/typescript/cli/src/rebalancer/README.md +++ /dev/null @@ -1,177 +0,0 @@ -# Hyperlane Warp Rebalancer - -The Hyperlane Warp Rebalancer is a tool that automatically manages the balance of collateral across chains in a Warp Route. It ensures that each chain maintains an optimal balance of tokens based on the configured strategy. - -## Configuration - -The rebalancer uses a configuration file that defines both global settings and chain-specific configurations. The configuration file can be in either YAML or JSON format. - -The basic structure of the configuration is as follows: - -```yaml -# Required: Unique identifier for the Warp Route. This is used to identify the -# HypERC20 token that is being rebalanced. -# The format is /