diff --git a/.changeset/brave-lions-beg.md b/.changeset/brave-lions-beg.md new file mode 100644 index 00000000000..d64fcb920a3 --- /dev/null +++ b/.changeset/brave-lions-beg.md @@ -0,0 +1,5 @@ +--- +"@hyperlane-xyz/sdk": minor +--- + +export radix core reader diff --git a/.changeset/breezy-comics-sniff.md b/.changeset/breezy-comics-sniff.md deleted file mode 100644 index e9ab605790a..00000000000 --- a/.changeset/breezy-comics-sniff.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hyperlane-xyz/sdk": minor ---- - -Add the `EvmTimelockReader` class to get pending/scheduled transaction from a timelock contract. Add the `EvmEventLogsReader` to read logs on a given chain reliably either using the rpc or the block explorer api depending on what is available in the registry diff --git a/.changeset/clever-llamas-travel.md b/.changeset/clever-llamas-travel.md new file mode 100644 index 00000000000..f2af0af994b --- /dev/null +++ b/.changeset/clever-llamas-travel.md @@ -0,0 +1,5 @@ +--- +"@hyperlane-xyz/sdk": minor +--- + +Add signer abstraction for different protocol types by defining the IMultiProtocolSigner interface diff --git a/.changeset/four-dolphins-attend.md b/.changeset/four-dolphins-attend.md new file mode 100644 index 00000000000..06063ac1363 --- /dev/null +++ b/.changeset/four-dolphins-attend.md @@ -0,0 +1,5 @@ +--- +"@hyperlane-xyz/core": patch +--- + +Updated import statements for ProxyAdmin and TransparentUpgradeableProxy contracts as they weren't included in the build artifacts used for verification with the old import diff --git a/.changeset/four-olives-allow.md b/.changeset/four-olives-allow.md new file mode 100644 index 00000000000..f4a5bb2fbdf --- /dev/null +++ b/.changeset/four-olives-allow.md @@ -0,0 +1,6 @@ +--- +"@hyperlane-xyz/sdk": patch +"@hyperlane-xyz/widgets": patch +--- + +now token.isFungibleWith() will also check for isHypNative() tokens diff --git a/.changeset/gentle-paws-grow.md b/.changeset/gentle-paws-grow.md new file mode 100644 index 00000000000..3eac8a31bc0 --- /dev/null +++ b/.changeset/gentle-paws-grow.md @@ -0,0 +1,5 @@ +--- +"@hyperlane-xyz/utils": patch +--- + +fix cosmos native bytes to address diff --git a/.changeset/hungry-rabbits-mix.md b/.changeset/hungry-rabbits-mix.md new file mode 100644 index 00000000000..7665c522811 --- /dev/null +++ b/.changeset/hungry-rabbits-mix.md @@ -0,0 +1,6 @@ +--- +"@hyperlane-xyz/radix-sdk": minor +"@hyperlane-xyz/sdk": minor +--- + +export radix hook reader diff --git a/.changeset/modern-snails-drum.md b/.changeset/modern-snails-drum.md new file mode 100644 index 00000000000..2caf4a17e00 --- /dev/null +++ b/.changeset/modern-snails-drum.md @@ -0,0 +1,5 @@ +--- +"@hyperlane-xyz/sdk": minor +--- + +Add getBridgedSupply to EvmHypNativeAdapter to return the native token balance as collateral diff --git a/.changeset/ninety-queens-clean.md b/.changeset/ninety-queens-clean.md new file mode 100644 index 00000000000..53b322395c9 --- /dev/null +++ b/.changeset/ninety-queens-clean.md @@ -0,0 +1,5 @@ +--- +"@hyperlane-xyz/cli": minor +--- + +Dont check for token collateralAddressOrDenom to enable HypNative router collateral balance reads diff --git a/.changeset/proud-cows-lay.md b/.changeset/proud-cows-lay.md deleted file mode 100644 index 7ea8f69ae27..00000000000 --- a/.changeset/proud-cows-lay.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hyperlane-xyz/sdk": patch ---- - -Updated the HypERC20Checker to use a default anvil address instead of the signer address when asserting if a token is a hyp native diff --git a/.changeset/rare-turtles-cover.md b/.changeset/rare-turtles-cover.md new file mode 100644 index 00000000000..31032568b95 --- /dev/null +++ b/.changeset/rare-turtles-cover.md @@ -0,0 +1,5 @@ +--- +"@hyperlane-xyz/cli": patch +--- + +Fixed pre deploy balance check logic that attempted to convert to bignumber decimal numbers diff --git a/.changeset/silly-boxes-hang.md b/.changeset/silly-boxes-hang.md new file mode 100644 index 00000000000..f1a3c9a34f3 --- /dev/null +++ b/.changeset/silly-boxes-hang.md @@ -0,0 +1,5 @@ +--- +"@hyperlane-xyz/sdk": patch +--- + +Avoid extra Safe API call when only creating local JSON files for manual upload. When proposing, the Safe UI will automatically update the tx nonce. So we can return 0 and save ourselves from Safe API unreliability. diff --git a/.changeset/smart-buses-thank.md b/.changeset/smart-buses-thank.md new file mode 100644 index 00000000000..aff6e84c0c7 --- /dev/null +++ b/.changeset/smart-buses-thank.md @@ -0,0 +1,6 @@ +--- +"@hyperlane-xyz/utils": minor +"@hyperlane-xyz/sdk": minor +--- + +export radix ism module diff --git a/.changeset/tender-spiders-nail.md b/.changeset/tender-spiders-nail.md deleted file mode 100644 index a5bf71be38d..00000000000 --- a/.changeset/tender-spiders-nail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@hyperlane-xyz/sdk": patch ---- - -Handle etherscan v2 api migration diff --git a/.changeset/tidy-squids-search.md b/.changeset/tidy-squids-search.md new file mode 100644 index 00000000000..d78748d0f41 --- /dev/null +++ b/.changeset/tidy-squids-search.md @@ -0,0 +1,5 @@ +--- +"@hyperlane-xyz/sdk": minor +--- + +export RadixIsmTypes enum to consumers diff --git a/.codespell/ignore.txt b/.codespell/ignore.txt index e61cb18d9ce..2b73e974945 100644 --- a/.codespell/ignore.txt +++ b/.codespell/ignore.txt @@ -6,4 +6,5 @@ ser readded re-use superseed -pris \ No newline at end of file +pris +towords diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 632b432820b..859156964e6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,17 +1,11 @@ -# File extension owners - -*.sol @yorhodes @larryob -*.ts @yorhodes @ltyu @xeno097 -*.rs @tkporter @daniel-savu @ameten - # Package owners ## Contracts -solidity/ @yorhodes @tkporter @ltyu @larryob -starknet/ @yorhodes @tkporter @troykessler +solidity/ @yorhodes @ltyu @larryob +starknet/ @yorhodes @troykessler ## Agents -rust/ @tkporter @daniel-savu @ameten @kamiyaa +rust/ @ameten @kamiyaa @yjamin ## SDK typescript/utils @yorhodes @ltyu @paulbalaji @xaroz @xeno097 @antigremlin @@ -22,7 +16,7 @@ typescript/widgets @xaroz @cmcewen @antigremlin typescript/cli @yorhodes @ltyu @xeno097 @antigremlin ## Infra -typescript/infra @tkporter @paulbalaji @Mo-Hussain +typescript/infra @paulbalaji @Mo-Hussain ## Cosmos typescript/cosmos-sdk @troykessler @yjamin @@ -30,3 +24,6 @@ typescript/cosmos-types @troykessler @yjamin ## CCIP Server typescript/ccip-server @Mo-Hussain @nambrot + +## Radix +typescript/radix-sdk @troykessler @yjamin diff --git a/.github/workflows/agent-release-artifacts.yml b/.github/workflows/agent-release-artifacts.yml.disabled similarity index 100% rename from .github/workflows/agent-release-artifacts.yml rename to .github/workflows/agent-release-artifacts.yml.disabled diff --git a/.github/workflows/monorepo-docker.yml b/.github/workflows/monorepo-docker.yml.disabled similarity index 100% rename from .github/workflows/monorepo-docker.yml rename to .github/workflows/monorepo-docker.yml.disabled diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml.disabled similarity index 100% rename from .github/workflows/release.yml rename to .github/workflows/release.yml.disabled diff --git a/.github/workflows/rust-docker.yml b/.github/workflows/rust-docker.yml.disabled similarity index 100% rename from .github/workflows/rust-docker.yml rename to .github/workflows/rust-docker.yml.disabled diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index dc4463fdbf7..8807d662270 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -3,14 +3,17 @@ name: rust on: # Triggers the workflow on pushes to main branch push: - branches: [main] + branches: + - main + - main-dym # Triggers on pull requests pull_request: branches: - - '*' + - '*' merge_group: workflow_dispatch: + concurrency: group: rust-${{ github.ref }} cancel-in-progress: true @@ -22,115 +25,124 @@ env: RUSTC_WRAPPER: sccache jobs: - lander-coverage: - runs-on: depot-ubuntu-24.04-8 - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - - uses: dtolnay/rust-toolchain@stable - - name: Free disk space - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf "/usr/local/share/boost" - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.8 + # lander-coverage: + # runs-on: depot-ubuntu-24.04-8 + # steps: + # - uses: actions/checkout@v4 + # with: + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + # - uses: dtolnay/rust-toolchain@stable + # - name: Free disk space + # run: | + # sudo rm -rf /usr/share/dotnet + # sudo rm -rf /opt/ghc + # sudo rm -rf "/usr/local/share/boost" + # sudo rm -rf "$AGENT_TOOLSDIRECTORY" + # - name: Run sccache-cache + # uses: mozilla-actions/sccache-action@v0.0.8 - - name: Install cargo-llvm-cov - run: | - cargo install cargo-llvm-cov@0.5.39 --locked - rustup component add llvm-tools-preview + # - name: Install cargo-llvm-cov + # run: | + # cargo install cargo-llvm-cov@0.5.39 --locked + # rustup component add llvm-tools-preview - - name: Generate coverage for lander package - run: cargo llvm-cov --package lander --json --output-path coverage.json - working-directory: ./rust/main + # - name: Generate coverage for lander package + # run: cargo llvm-cov --package lander --json --output-path coverage.json + # working-directory: ./rust/main - - name: Check coverage threshold - run: | - COVERAGE=$(cat coverage.json | jq -r '.data[0].totals.lines.percent') - echo "Coverage: $COVERAGE%" - if (( $(echo "$COVERAGE * 100 < $MIN_LANDERCOVERAGE_PERCENTAGE" | bc -l) )); then - echo "Code coverage is below minimum threshold of $MIN_LANDERCOVERAGE_PERCENTAGE percent" - exit 1 - fi - working-directory: ./rust/main + # - name: Check coverage threshold + # run: | + # COVERAGE=$(cat coverage.json | jq -r '.data[0].totals.lines.percent') + # echo "Coverage: $COVERAGE%" + # if (( $(echo "$COVERAGE * 100 < $MIN_LANDERCOVERAGE_PERCENTAGE" | bc -l) )); then + # echo "Code coverage is below minimum threshold of $MIN_LANDERCOVERAGE_PERCENTAGE percent" + # exit 1 + # fi + # working-directory: ./rust/main - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 - with: - files: coverage.json - flags: ./rust/main/lander - fail_ci_if_error: true - token: ${{ secrets.CODECOV_TOKEN }} + # - name: Upload coverage to Codecov + # uses: codecov/codecov-action@v4 + # with: + # files: coverage.json + # flags: ./rust/main/lander + # fail_ci_if_error: true + # token: ${{ secrets.CODECOV_TOKEN }} test-rs: runs-on: depot-ubuntu-24.04-8 steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - - uses: dtolnay/rust-toolchain@stable - - name: Free disk space - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf "/usr/local/share/boost" - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - - name: Install mold linker - uses: rui314/setup-mold@v1 - with: - mold-version: 2.0.0 - make-default: true - - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.8 - - name: Run tests for main workspace - run: cargo test - working-directory: ./rust/main - - name: Run tests for sealevel workspace - run: cargo test - working-directory: ./rust/sealevel + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + - uses: dtolnay/rust-toolchain@stable + - name: Free disk space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + - name: Install mold linker + uses: rui314/setup-mold@v1 + with: + mold-version: 2.0.0 + make-default: true + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.8 + - name: Run tests for main workspace + run: cargo test + working-directory: ./rust/main + - name: Run tests for libs/kaspa workspace + run: cargo test + working-directory: ./dymension/libs/kaspa + - name: Run tests for sealevel workspace + run: cargo test + working-directory: ./rust/sealevel lint-rs: runs-on: depot-ubuntu-24.04-8 steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - - uses: dtolnay/rust-toolchain@stable - with: - components: rustfmt, clippy - target: wasm32-unknown-unknown - - name: Free disk space - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf "/usr/local/share/boost" - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.8 - - name: Check for main workspace - run: cargo check --release --all-features --all-targets - working-directory: ./rust/main - - name: Check for sealevel workspace - run: cargo check --release --all-features --all-targets - working-directory: ./rust/sealevel - - name: Rustfmt for main workspace - run: cargo fmt --all -- --check - working-directory: ./rust/main - - name: Rustfmt for sealevel workspace - run: cargo fmt --all --check - working-directory: ./rust/sealevel - - name: Clippy for main workspace - run: cargo clippy -- -D warnings - working-directory: ./rust/main - - name: Clippy for sealevel workspace - run: cargo clippy -- -D warnings - working-directory: ./rust/sealevel - - name: Setup WASM for main workspace - run: rustup target add wasm32-unknown-unknown - working-directory: ./rust/main - - name: Check WASM for hyperlane-core - run: cargo check --release -p hyperlane-core --features=strum,test-utils --target wasm32-unknown-unknown - working-directory: ./rust/main + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + target: wasm32-unknown-unknown + - name: Free disk space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + - name: Run sccache-cache + uses: mozilla-actions/sccache-action@v0.0.8 + - name: Check for main workspace + run: cargo check --release --all-features --all-targets + working-directory: ./rust/main + - name: Check for sealevel workspace + run: cargo check --release --all-features --all-targets + working-directory: ./rust/sealevel + - name: Rustfmt for main workspace + run: cargo fmt --all -- --check + working-directory: ./rust/main + - name: Rustfmt for libs/kaspa workspace + run: cargo fmt --all -- --check + working-directory: ./dymension/libs/kaspa + - name: Rustfmt for sealevel workspace + run: cargo fmt --all --check + working-directory: ./rust/sealevel + - name: Clippy for main workspace + run: cargo clippy -- -D warnings + working-directory: ./rust/main + - name: Clippy for sealevel workspace + run: cargo clippy -- -D warnings + working-directory: ./rust/sealevel + - name: Setup WASM for main workspace + run: rustup target add wasm32-unknown-unknown + working-directory: ./rust/main + - name: Check WASM for hyperlane-core + run: cargo check --release -p hyperlane-core --features=strum,test-utils --target wasm32-unknown-unknown + working-directory: ./rust/main + - name: Check Dymension Kaspa workspace + run: cargo check --release --all-features --all-targets + working-directory: ./dymension/libs/kaspa/ diff --git a/.github/workflows/simapp-docker.yml b/.github/workflows/simapp-docker.yml.disabled similarity index 100% rename from .github/workflows/simapp-docker.yml rename to .github/workflows/simapp-docker.yml.disabled diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c4f384cb4cb..2b503136b48 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -64,7 +64,7 @@ jobs: run: yarn syncpack list-mismatches lint-prettier: - runs-on: depot-ubuntu-24.04-4 + runs-on: depot-ubuntu-24.04-8 timeout-minutes: 5 steps: - uses: actions/checkout@v4 @@ -189,6 +189,13 @@ jobs: - name: foundry-install uses: foundry-rs/foundry-toolchain@v1 + - name: gitleaks-install + if: github.event_name != 'merge_group' + uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE}} + - name: yarn-build uses: ./.github/actions/yarn-build-with-cache with: @@ -212,47 +219,47 @@ jobs: job_name: 'Yarn Test' result: ${{ needs.yarn-test-run.result }} - infra-test: - runs-on: depot-ubuntu-latest - needs: [yarn-install, set-base-sha] - env: - GRAFANA_SERVICE_ACCOUNT_TOKEN: ${{ secrets.GRAFANA_SERVICE_ACCOUNT_TOKEN }} - BALANCE_CHANGES: false - WARP_CONFIG_CHANGES: false - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - submodules: recursive - fetch-depth: 0 - - - name: Check for balance/config changes - run: | - if git diff ${{ needs.set-base-sha.outputs.base_sha }}..${{ needs.set-base-sha.outputs.current_sha }} --name-only | grep -qE '^typescript/infra/config/environments/mainnet3/balances|^.registryrc$'; then - echo "BALANCE_CHANGES=true" >> $GITHUB_ENV - fi - if git diff ${{ needs.set-base-sha.outputs.base_sha }}..${{ needs.set-base-sha.outputs.current_sha }} --name-only | grep -qE '^typescript/infra/|^.registryrc$'; then - echo "WARP_CONFIG_CHANGES=true" >> $GITHUB_ENV - fi - - - name: yarn-build - uses: ./.github/actions/yarn-build-with-cache - if: env.BALANCE_CHANGES == 'true' || env.WARP_CONFIG_CHANGES == 'true' - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - cache-provider: github - - - name: Checkout registry - if: env.BALANCE_CHANGES == 'true' || env.WARP_CONFIG_CHANGES == 'true' - uses: ./.github/actions/checkout-registry - - - name: Balance Tests - if: env.BALANCE_CHANGES == 'true' - run: yarn --cwd typescript/infra test:balance - - - name: Warp Config Tests - if: env.WARP_CONFIG_CHANGES == 'true' - run: yarn --cwd typescript/infra test:warp + # infra-test: + # runs-on: depot-ubuntu-latest + # needs: [yarn-install, set-base-sha] + # env: + # GRAFANA_SERVICE_ACCOUNT_TOKEN: ${{ secrets.GRAFANA_SERVICE_ACCOUNT_TOKEN }} + # BALANCE_CHANGES: false + # WARP_CONFIG_CHANGES: false + # steps: + # - uses: actions/checkout@v4 + # with: + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + # submodules: recursive + # fetch-depth: 0 + + # - name: Check for balance/config changes + # run: | + # if git diff ${{ needs.set-base-sha.outputs.base_sha }}..${{ needs.set-base-sha.outputs.current_sha }} --name-only | grep -qE '^typescript/infra/config/environments/mainnet3/balances|^.registryrc$'; then + # echo "BALANCE_CHANGES=true" >> $GITHUB_ENV + # fi + # if git diff ${{ needs.set-base-sha.outputs.base_sha }}..${{ needs.set-base-sha.outputs.current_sha }} --name-only | grep -qE '^typescript/infra/|^.registryrc$'; then + # echo "WARP_CONFIG_CHANGES=true" >> $GITHUB_ENV + # fi + + # - name: yarn-build + # uses: ./.github/actions/yarn-build-with-cache + # if: env.BALANCE_CHANGES == 'true' || env.WARP_CONFIG_CHANGES == 'true' + # with: + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + # cache-provider: github + + # - name: Checkout registry + # if: env.BALANCE_CHANGES == 'true' || env.WARP_CONFIG_CHANGES == 'true' + # uses: ./.github/actions/checkout-registry + + # - name: Balance Tests + # if: env.BALANCE_CHANGES == 'true' + # run: yarn --cwd typescript/infra test:balance + + # - name: Warp Config Tests + # if: env.WARP_CONFIG_CHANGES == 'true' + # run: yarn --cwd typescript/infra test:warp cli-install-test-run: runs-on: ubuntu-latest @@ -284,416 +291,462 @@ jobs: job_name: 'CLI Install Test' result: ${{ needs.cli-install-test-run.result }} - cli-e2e-matrix: - runs-on: depot-ubuntu-latest - needs: [rust-only] - if: needs.rust-only.outputs.only_rust == 'false' - strategy: - fail-fast: false - matrix: - protocol: - - ethereum - - cosmosnative - test: - # Core Commands - - core-apply - - core-check - - core-deploy - - core-init - - core-read - # Other commands - - relay - # Warp Commands - - warp-apply-submitters - - warp-apply - - warp-bridge-1 - - warp-bridge-2 - - warp-check - - warp-deploy - - warp-extend-basic - - warp-extend-config - - warp-extend-recovery - - warp-init - - warp-read - - warp-send - - warp-rebalancer - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - submodules: recursive - fetch-depth: 0 - - - name: foundry-install - uses: foundry-rs/foundry-toolchain@v1 - - - name: install-hyperlane-cli - id: install-hyperlane-cli - uses: ./.github/actions/install-cli - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - cache-provider: github - - - name: Checkout registry - uses: ./.github/actions/checkout-registry - - - name: CLI ${{ matrix.protocol }} e2e tests (${{ matrix.test }}) - run: yarn --cwd typescript/cli test:${{ matrix.protocol }}:e2e - env: - CLI_E2E_TEST: ${{ matrix.test }} - - cli-e2e: - runs-on: ubuntu-latest - needs: [cli-e2e-matrix] - if: always() - steps: - - uses: actions/checkout@v4 - - name: Check cli-e2e matrix status - uses: ./.github/actions/check-job-status - with: - job_name: 'CLI E2E' - result: ${{ needs.cli-e2e-matrix.result }} - - cosmos-sdk-e2e-run: - runs-on: depot-ubuntu-latest - needs: [rust-only] - if: needs.rust-only.outputs.only_rust == 'false' - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - submodules: recursive - fetch-depth: 0 - - - name: yarn-build - uses: ./.github/actions/yarn-build-with-cache - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - cache-provider: github - - - name: Cosmos SDK e2e tests - run: yarn --cwd typescript/cosmos-sdk test:e2e - - cosmos-sdk-e2e: - runs-on: ubuntu-latest - needs: [cosmos-sdk-e2e-run] - if: always() - steps: - - uses: actions/checkout@v4 - - name: Check cosmos-sdk-e2e status - uses: ./.github/actions/check-job-status - with: - job_name: 'Cosmos SDK E2E' - result: ${{ needs.cosmos-sdk-e2e-run.result }} - - agent-configs: - runs-on: depot-ubuntu-latest - strategy: - fail-fast: false - matrix: - environment: [mainnet3, testnet4] - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - fetch-depth: 0 - - - name: yarn-build - uses: ./.github/actions/yarn-build-with-cache - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - cache-provider: github - - - name: Checkout registry - uses: ./.github/actions/checkout-registry - - - name: Generate ${{ matrix.environment }} agent config - run: | - cd typescript/infra - yarn tsx ./scripts/agents/update-agent-config.ts -e ${{ matrix.environment }} - CHANGES=$(git status -s . :/rust/main/config) - if [[ ! -z $CHANGES ]]; then - echo "Changes found in agent config: $CHANGES" - exit 1 - fi - - e2e-matrix: - runs-on: depot-ubuntu-24.04-8 - if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main' || github.base_ref == 'cli-2.0') || github.event_name == 'merge_group' - strategy: - fail-fast: false - matrix: - e2e-type: [cosmwasm, cosmosnative, evm, sealevel, starknet] - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - submodules: recursive - fetch-depth: 0 - - - uses: actions/setup-node@v4 - with: - node-version-file: .nvmrc - - # Set rust_changes to true if: - # - on the main branch - # - PR with changes to ./rust - # - PR with changes to this workflow - - name: Check for Rust file changes - id: check-rust-changes - env: - GH_TOKEN: ${{ github.token }} - run: | - if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then - # For main branch, check changes in the current commit - PARENT_SHA=$(git rev-parse HEAD^) - CHANGES=$(git diff --name-only $PARENT_SHA HEAD -- ./rust ./.github/workflows/test.yml) - - if [[ -n "$CHANGES" ]]; then - echo "rust_changes=true" >> $GITHUB_OUTPUT - echo "Changes detected in Rust files or workflow on main branch:" - echo "$CHANGES" - else - echo "rust_changes=false" >> $GITHUB_OUTPUT - fi - elif [[ "${{ github.event_name }}" == "pull_request" ]]; then - # For PRs, use GitHub CLI to check changes - PR_NUMBER=${{ github.event.pull_request.number }} - CHANGES=$(gh pr view $PR_NUMBER --json files -q '.files[].path | select(startswith("rust/") or . == ".github/workflows/test.yml")') - - if [[ -n "$CHANGES" ]]; then - echo "rust_changes=true" >> $GITHUB_OUTPUT - echo "Changes detected in Rust files or workflow in PR:" - echo "$CHANGES" - else - echo "rust_changes=false" >> $GITHUB_OUTPUT - fi - else - echo "rust_changes=false" >> $GITHUB_OUTPUT - fi - - # If there are no rust changes and the e2e-type is not evm, - # then we want to skip this e2e test to save billing time. - - name: Check rust and e2e conditions - id: check-conditions - run: | - if [[ "${{ steps.check-rust-changes.outputs.rust_changes }}" == "false" && "${{ matrix.e2e-type }}" != "evm" ]]; then - echo "skip-e2e=true" >> $GITHUB_OUTPUT - echo "No rust changes detected and e2e-type is not evm. Will skip remaining steps." - else - echo "skip-e2e=false" >> $GITHUB_OUTPUT - fi - - - name: foundry-install - if: steps.check-conditions.outputs.skip-e2e != 'true' - uses: foundry-rs/foundry-toolchain@v1 - - - name: setup rust - if: steps.check-conditions.outputs.skip-e2e != 'true' - uses: dtolnay/rust-toolchain@stable - - - name: rust cache - if: steps.check-conditions.outputs.skip-e2e != 'true' - uses: Swatinem/rust-cache@v2 - with: - prefix-key: 'v3-rust-e2e' - shared-key: ${{ matrix.e2e-type }} - cache-provider: 'github' - save-if: ${{ !startsWith(github.ref, 'refs/heads/gh-readonly-queue') }} - workspaces: | - ./rust/main - ${{ matrix.e2e-type == 'sealevel' && './rust/sealevel' || '' }} - - - name: Free disk space - if: steps.check-conditions.outputs.skip-e2e != 'true' - run: | - # Based on https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf "/usr/local/share/boost" - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - - - name: Install mold linker - if: steps.check-conditions.outputs.skip-e2e != 'true' - uses: rui314/setup-mold@v1 - with: - mold-version: 2.0.0 - make-default: true - - - name: yarn-build - if: steps.check-conditions.outputs.skip-e2e != 'true' - uses: ./.github/actions/yarn-build-with-cache - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - cache-provider: github - - - name: Install system dependencies - if: steps.check-conditions.outputs.skip-e2e != 'true' - run: | - sudo apt-get update -qq - sudo apt-get install -qq -y libudev-dev pkg-config protobuf-compiler - - - name: Install OpenSSL 1.1 for Solana toolchain - if: steps.check-conditions.outputs.skip-e2e != 'true' - run: | - wget -O libssl1.1.deb https://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.24_amd64.deb - sudo dpkg -i libssl1.1.deb - - - name: Checkout registry - if: steps.check-conditions.outputs.skip-e2e != 'true' - uses: ./.github/actions/checkout-registry - - - name: Run sccache-cache - if: steps.check-conditions.outputs.skip-e2e != 'true' - uses: mozilla-actions/sccache-action@v0.0.8 - - - name: agent tests (CosmWasm) - if: ${{ matrix.e2e-type == 'cosmwasm' && steps.check-conditions.outputs.skip-e2e != 'true' }} - run: cargo test --release --package run-locally --bin run-locally --features cosmos -- cosmos::test --nocapture - working-directory: ./rust/main - env: - RUST_BACKTRACE: 'full' - - - name: agent tests (CosmosNative) - run: cargo test --release --package run-locally --bin run-locally --features cosmosnative -- cosmosnative::test --nocapture - if: ${{ matrix.e2e-type == 'cosmosnative' && steps.check-rust-changes.outputs.rust_changes == 'true' }} - working-directory: ./rust/main - env: - RUST_BACKTRACE: 'full' - - - name: agent tests (EVM) - if: ${{ matrix.e2e-type == 'evm' && steps.check-conditions.outputs.skip-e2e != 'true' }} - run: cargo run --release --bin run-locally --features test-utils - working-directory: ./rust/main - env: - E2E_CI_MODE: 'true' - E2E_CI_TIMEOUT_SEC: '600' - E2E_KATHY_MESSAGES: '20' - RUST_BACKTRACE: 'full' - - - name: agent tests (Sealevel) - if: ${{ matrix.e2e-type == 'sealevel' && steps.check-conditions.outputs.skip-e2e != 'true' }} - run: cargo test --release --package run-locally --bin run-locally --features sealevel -- sealevel::test --nocapture - working-directory: ./rust/main - env: - E2E_CI_MODE: 'true' - E2E_CI_TIMEOUT_SEC: '600' - RUST_BACKTRACE: 'full' - - - name: agent tests with Starknet - run: cargo test --release --package run-locally --bin run-locally --features starknet -- starknet::test --nocapture - if: ${{ matrix.e2e-type == 'starknet' && steps.check-rust-changes.outputs.rust_changes == 'true' }} - working-directory: ./rust/main - env: - RUST_BACKTRACE: 'full' - - e2e: - runs-on: ubuntu-latest - needs: e2e-matrix - if: always() - steps: - - name: Check e2e matrix status - if: ${{ needs.e2e-matrix.result != 'success' }} - run: | - echo "E2E tests failed" - exit 1 - - env-test-matrix: - runs-on: depot-ubuntu-latest - needs: [rust-only] - if: needs.rust-only.outputs.only_rust == 'false' - env: - MAINNET3_ARBITRUM_RPC_URLS: ${{ secrets.MAINNET3_ARBITRUM_RPC_URLS }} - MAINNET3_OPTIMISM_RPC_URLS: ${{ secrets.MAINNET3_OPTIMISM_RPC_URLS }} - MAINNET3_ETHEREUM_RPC_URLS: ${{ secrets.MAINNET3_ETHEREUM_RPC_URLS }} - TESTNET4_SEPOLIA_RPC_URLS: ${{ secrets.TESTNET4_SEPOLIA_RPC_URLS }} - - timeout-minutes: 10 - strategy: - fail-fast: false - matrix: - environment: [mainnet3] - chain: [ethereum, arbitrum, optimism, inevm] - module: [core, igp] - include: - - environment: testnet4 - chain: sepolia - module: core - - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - - - name: foundry-install - uses: foundry-rs/foundry-toolchain@v1 - - - name: yarn-build - uses: ./.github/actions/yarn-build-with-cache - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - cache-provider: github - - - name: Checkout registry - uses: ./.github/actions/checkout-registry - - - name: Fork test ${{ matrix.environment }} ${{ matrix.module }} ${{ matrix.chain }} deployment with retries - uses: nick-fields/retry@v3 - with: - timeout_minutes: 8 - max_attempts: 3 - retry_wait_seconds: 30 - command: cd typescript/infra && ./fork.sh ${{ matrix.environment }} ${{ matrix.module }} ${{ matrix.chain }} - on_retry_command: | - echo "Test failed, waiting before retry..." - - env-test: - runs-on: ubuntu-latest - needs: env-test-matrix - if: always() - steps: - - uses: actions/checkout@v4 - - name: Check env-test matrix status - uses: ./.github/actions/check-job-status - with: - job_name: 'Env Test' - result: ${{ needs.env-test-matrix.result }} - - coverage-run: - runs-on: ubuntu-latest - needs: [rust-only] - if: needs.rust-only.outputs.only_rust == 'false' - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - fetch-depth: 0 - - - name: yarn-build - uses: ./.github/actions/yarn-build-with-cache - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - - name: foundry-install - uses: foundry-rs/foundry-toolchain@v1 - - - name: Run tests with coverage - run: yarn coverage - env: - NODE_OPTIONS: --max_old_space_size=4096 - - - name: Upload coverage reports to Codecov with GitHub Action - uses: codecov/codecov-action@v4 - with: - token: ${{ secrets.CODECOV_TOKEN }} - - coverage: - runs-on: ubuntu-latest - needs: [coverage-run] - if: always() - steps: - - uses: actions/checkout@v4 - - name: Check coverage status - uses: ./.github/actions/check-job-status - with: - job_name: 'Coverage' - result: ${{ needs.coverage-run.result }} + # cli-evm-e2e-matrix: + # runs-on: depot-ubuntu-latest + # needs: [rust-only] + # if: needs.rust-only.outputs.only_rust == 'false' + # strategy: + # fail-fast: false + # matrix: + # test: + # # Core Commands + # - core-apply + # - core-check + # - core-deploy + # - core-init + # - core-read + # # Other commands + # - relay + # # Warp Commands + # - warp-apply-1 + # - warp-apply-2 + # - warp-apply-submitters + # - warp-bridge-1 + # - warp-bridge-2 + # - warp-check-1 + # - warp-check-2 + # - warp-check-3 + # - warp-deploy + # - warp-extend-basic + # - warp-extend-config + # - warp-extend-recovery + # - warp-init + # - warp-read + # - warp-rebalancer + # - warp-send + # steps: + # - uses: actions/checkout@v4 + # with: + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + # submodules: recursive + # fetch-depth: 0 + + # - name: foundry-install + # uses: foundry-rs/foundry-toolchain@v1 + + # - name: install-hyperlane-cli + # id: install-hyperlane-cli + # uses: ./.github/actions/install-cli + # with: + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + # cache-provider: github + + # - name: Checkout registry + # uses: ./.github/actions/checkout-registry + + # - name: CLI ethereum e2e tests (${{ matrix.test }}) + # run: yarn --cwd typescript/cli test:ethereum:e2e + # env: + # CLI_E2E_TEST: ${{ matrix.test }} + + # cli-cosmos-e2e-matrix: + # runs-on: depot-ubuntu-latest + # needs: [rust-only] + # if: needs.rust-only.outputs.only_rust == 'false' + # strategy: + # fail-fast: false + # matrix: + # test: + # # Core Commands + # - core-apply + # - core-check + # - core-deploy + # - core-read + # # Warp Commands + # - warp-deploy + # - warp-read + # steps: + # - uses: actions/checkout@v4 + # with: + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + # submodules: recursive + # fetch-depth: 0 + + # - name: foundry-install + # uses: foundry-rs/foundry-toolchain@v1 + + # - name: install-hyperlane-cli + # id: install-hyperlane-cli + # uses: ./.github/actions/install-cli + # with: + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + # cache-provider: github + + # - name: Checkout registry + # uses: ./.github/actions/checkout-registry + + # - name: CLI cosmosnative e2e tests (${{ matrix.test }}) + # run: yarn --cwd typescript/cli test:cosmosnative:e2e + # env: + # CLI_E2E_TEST: ${{ matrix.test }} + + # cli-e2e: + # runs-on: ubuntu-latest + # needs: [cli-evm-e2e-matrix, cli-cosmos-e2e-matrix] + # if: always() + # steps: + # - uses: actions/checkout@v4 + # - name: Check CLI EVM E2E status + # uses: ./.github/actions/check-job-status + # with: + # job_name: 'CLI EVM E2E' + # result: ${{ needs.cli-evm-e2e-matrix.result }} + # - name: Check CLI CosmosNative E2E status + # uses: ./.github/actions/check-job-status + # with: + # job_name: 'CLI CosmosNative E2E' + # result: ${{ needs.cli-cosmos-e2e-matrix.result }} + + # cosmos-sdk-e2e-run: + # runs-on: depot-ubuntu-latest + # needs: [rust-only] + # if: needs.rust-only.outputs.only_rust == 'false' + # steps: + # - uses: actions/checkout@v4 + # with: + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + # submodules: recursive + # fetch-depth: 0 + + # - name: yarn-build + # uses: ./.github/actions/yarn-build-with-cache + # with: + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + # cache-provider: github + + # - name: Cosmos SDK e2e tests + # run: yarn --cwd typescript/cosmos-sdk test:e2e + + # cosmos-sdk-e2e: + # runs-on: ubuntu-latest + # needs: [cosmos-sdk-e2e-run] + # if: always() + # steps: + # - uses: actions/checkout@v4 + # - name: Check cosmos-sdk-e2e status + # uses: ./.github/actions/check-job-status + # with: + # job_name: 'Cosmos SDK E2E' + # result: ${{ needs.cosmos-sdk-e2e-run.result }} + + # agent-configs: + # runs-on: depot-ubuntu-latest + # strategy: + # fail-fast: false + # matrix: + # environment: [mainnet3, testnet4] + # steps: + # - uses: actions/checkout@v4 + # with: + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + # fetch-depth: 0 + + # - name: yarn-build + # uses: ./.github/actions/yarn-build-with-cache + # with: + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + # cache-provider: github + + # - name: Checkout registry + # uses: ./.github/actions/checkout-registry + + # - name: Generate ${{ matrix.environment }} agent config + # run: | + # cd typescript/infra + # yarn tsx ./scripts/agents/update-agent-config.ts -e ${{ matrix.environment }} + # CHANGES=$(git status -s . :/rust/main/config) + # if [[ ! -z $CHANGES ]]; then + # echo "Changes found in agent config: $CHANGES" + # exit 1 + # fi + + # e2e-matrix: + # runs-on: depot-ubuntu-24.04-8 + # if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main' || github.base_ref == 'cli-2.0') || github.event_name == 'merge_group' + # strategy: + # fail-fast: false + # matrix: + # e2e-type: [cosmwasm, cosmosnative, evm, sealevel, starknet] + # steps: + # # - uses: actions/checkout@v4 + # with: + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + # submodules: recursive + # fetch-depth: 0 + + # - uses: actions/setup-node@v4 + # with: + # node-version-file: .nvmrc + + # # Set rust_changes to true if: + # # - on the main branch + # # - PR with changes to ./rust + # # - PR with changes to this workflow + # - name: Check for Rust file changes + # id: check-rust-changes + # env: + # GH_TOKEN: ${{ github.token }} + # run: | + # if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then + # # For main branch, check changes in the current commit + # PARENT_SHA=$(git rev-parse HEAD^) + # CHANGES=$(git diff --name-only $PARENT_SHA HEAD -- ./rust ./.github/workflows/test.yml) + + # if [[ -n "$CHANGES" ]]; then + # echo "rust_changes=true" >> $GITHUB_OUTPUT + # echo "Changes detected in Rust files or workflow on main branch:" + # echo "$CHANGES" + # else + # echo "rust_changes=false" >> $GITHUB_OUTPUT + # fi + # elif [[ "${{ github.event_name }}" == "pull_request" ]]; then + # # For PRs, use GitHub CLI to check changes + # PR_NUMBER=${{ github.event.pull_request.number }} + # CHANGES=$(gh pr view $PR_NUMBER --json files -q '.files[].path | select(startswith("rust/") or . == ".github/workflows/test.yml")') + + # if [[ -n "$CHANGES" ]]; then + # echo "rust_changes=true" >> $GITHUB_OUTPUT + # echo "Changes detected in Rust files or workflow in PR:" + # echo "$CHANGES" + # else + # echo "rust_changes=false" >> $GITHUB_OUTPUT + # fi + # else + # echo "rust_changes=false" >> $GITHUB_OUTPUT + # fi + + # # If there are no rust changes and the e2e-type is not evm, + # # then we want to skip this e2e test to save billing time. + # - name: Check rust and e2e conditions + # id: check-conditions + # run: | + # if [[ "${{ steps.check-rust-changes.outputs.rust_changes }}" == "false" && "${{ matrix.e2e-type }}" != "evm" ]]; then + # echo "skip-e2e=true" >> $GITHUB_OUTPUT + # echo "No rust changes detected and e2e-type is not evm. Will skip remaining steps." + # else + # echo "skip-e2e=false" >> $GITHUB_OUTPUT + # fi + + # - name: foundry-install + # if: steps.check-conditions.outputs.skip-e2e != 'true' + # uses: foundry-rs/foundry-toolchain@v1 + + # - name: setup rust + # if: steps.check-conditions.outputs.skip-e2e != 'true' + # uses: dtolnay/rust-toolchain@stable + + # - name: rust cache + # if: steps.check-conditions.outputs.skip-e2e != 'true' + # uses: Swatinem/rust-cache@v2 + # with: + # prefix-key: 'v3-rust-e2e' + # shared-key: ${{ matrix.e2e-type }} + # cache-provider: 'github' + # save-if: ${{ !startsWith(github.ref, 'refs/heads/gh-readonly-queue') }} + # workspaces: | + # ./rust/main + # ${{ matrix.e2e-type == 'sealevel' && './rust/sealevel' || '' }} + + # - name: Free disk space + # if: steps.check-conditions.outputs.skip-e2e != 'true' + # run: | + # # Based on https://github.com/actions/runner-images/issues/2840#issuecomment-790492173 + # sudo rm -rf /usr/share/dotnet + # sudo rm -rf /opt/ghc + # sudo rm -rf "/usr/local/share/boost" + # sudo rm -rf "$AGENT_TOOLSDIRECTORY" + + # - name: Install mold linker + # if: steps.check-conditions.outputs.skip-e2e != 'true' + # uses: rui314/setup-mold@v1 + # with: + # mold-version: 2.0.0 + # make-default: true + + # - name: yarn-build + # if: steps.check-conditions.outputs.skip-e2e != 'true' + # uses: ./.github/actions/yarn-build-with-cache + # with: + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + # cache-provider: github + + # - name: Install system dependencies + # if: steps.check-conditions.outputs.skip-e2e != 'true' + # run: | + # sudo apt-get update -qq + # sudo apt-get install -qq -y libudev-dev pkg-config protobuf-compiler + + # - name: Install OpenSSL 1.1 for Solana toolchain + # if: steps.check-conditions.outputs.skip-e2e != 'true' + # run: | + # wget -O libssl1.1.deb https://archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.24_amd64.deb + # sudo dpkg -i libssl1.1.deb + + # - name: Checkout registry + # if: steps.check-conditions.outputs.skip-e2e != 'true' + # uses: ./.github/actions/checkout-registry + + # - name: Run sccache-cache + # if: steps.check-conditions.outputs.skip-e2e != 'true' + # uses: mozilla-actions/sccache-action@v0.0.8 + + # - name: agent tests (CosmWasm) + # if: ${{ matrix.e2e-type == 'cosmwasm' && steps.check-conditions.outputs.skip-e2e != 'true' }} + # run: cargo test --release --package run-locally --bin run-locally --features cosmos -- cosmos::test --nocapture + # working-directory: ./rust/main + # env: + # RUST_BACKTRACE: 'full' + + # - name: agent tests (CosmosNative) + # run: cargo test --release --package run-locally --bin run-locally --features cosmosnative -- cosmosnative::test --nocapture + # if: ${{ matrix.e2e-type == 'cosmosnative' && steps.check-rust-changes.outputs.rust_changes == 'true' }} + # working-directory: ./rust/main + # env: + # RUST_BACKTRACE: 'full' + + # - name: agent tests (EVM) + # if: ${{ matrix.e2e-type == 'evm' && steps.check-conditions.outputs.skip-e2e != 'true' }} + # run: cargo run --release --bin run-locally --features test-utils + # working-directory: ./rust/main + # env: + # E2E_CI_MODE: 'true' + # E2E_CI_TIMEOUT_SEC: '600' + # E2E_KATHY_MESSAGES: '20' + # RUST_BACKTRACE: 'full' + + # - name: agent tests (Sealevel) + # if: ${{ matrix.e2e-type == 'sealevel' && steps.check-conditions.outputs.skip-e2e != 'true' }} + # run: cargo test --release --package run-locally --bin run-locally --features sealevel -- sealevel::test --nocapture + # working-directory: ./rust/main + # env: + # E2E_CI_MODE: 'true' + # E2E_CI_TIMEOUT_SEC: '600' + # RUST_BACKTRACE: 'full' + + # - name: agent tests with Starknet + # run: cargo test --release --package run-locally --bin run-locally --features starknet -- starknet::test --nocapture + # if: ${{ matrix.e2e-type == 'starknet' && steps.check-rust-changes.outputs.rust_changes == 'true' }} + # working-directory: ./rust/main + # env: + # RUST_BACKTRACE: 'full' + + # e2e: + # runs-on: ubuntu-latest + # needs: e2e-matrix + # if: always() + # steps: + # - name: Check e2e matrix status + # if: ${{ needs.e2e-matrix.result != 'success' }} + # run: | + # echo "E2E tests failed" + # exit 1 + + # env-test-matrix: + # runs-on: depot-ubuntu-latest + # needs: [rust-only] + # if: needs.rust-only.outputs.only_rust == 'false' + # env: + # MAINNET3_ARBITRUM_RPC_URLS: ${{ secrets.MAINNET3_ARBITRUM_RPC_URLS }} + # MAINNET3_OPTIMISM_RPC_URLS: ${{ secrets.MAINNET3_OPTIMISM_RPC_URLS }} + # MAINNET3_ETHEREUM_RPC_URLS: ${{ secrets.MAINNET3_ETHEREUM_RPC_URLS }} + # TESTNET4_SEPOLIA_RPC_URLS: ${{ secrets.TESTNET4_SEPOLIA_RPC_URLS }} + + # timeout-minutes: 10 + # strategy: + # fail-fast: false + # matrix: + # environment: [mainnet3] + # chain: [ethereum, arbitrum, optimism, inevm] + # module: [core, igp] + # include: + # - environment: testnet4 + # chain: sepolia + # module: core + + # steps: + # - uses: actions/checkout@v4 + # with: + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + + # - name: foundry-install + # uses: foundry-rs/foundry-toolchain@v1 + + # - name: yarn-build + # uses: ./.github/actions/yarn-build-with-cache + # with: + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + # cache-provider: github + + # - name: Checkout registry + # uses: ./.github/actions/checkout-registry + + # - name: Fork test ${{ matrix.environment }} ${{ matrix.module }} ${{ matrix.chain }} deployment with retries + # uses: nick-fields/retry@v3 + # with: + # timeout_minutes: 8 + # max_attempts: 3 + # retry_wait_seconds: 30 + # command: cd typescript/infra && ./fork.sh ${{ matrix.environment }} ${{ matrix.module }} ${{ matrix.chain }} + # on_retry_command: | + # echo "Test failed, waiting before retry..." + + # env-test: + # runs-on: ubuntu-latest + # needs: env-test-matrix + # if: always() + # steps: + # - uses: actions/checkout@v4 + # - name: Check env-test matrix status + # uses: ./.github/actions/check-job-status + # with: + # job_name: 'Env Test' + # result: ${{ needs.env-test-matrix.result }} + + # coverage-run: + # runs-on: ubuntu-latest + # needs: [rust-only] + # if: needs.rust-only.outputs.only_rust == 'false' + # steps: + # - uses: actions/checkout@v4 + # with: + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + # fetch-depth: 0 + + # - name: yarn-build + # uses: ./.github/actions/yarn-build-with-cache + # with: + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + # - name: foundry-install + # uses: foundry-rs/foundry-toolchain@v1 + + # - name: Run tests with coverage + # run: yarn coverage + # env: + # NODE_OPTIONS: --max_old_space_size=4096 + + # - name: Upload coverage reports to Codecov with GitHub Action + # uses: codecov/codecov-action@v4 + # with: + # token: ${{ secrets.CODECOV_TOKEN }} + + # coverage: + # runs-on: ubuntu-latest + # needs: [coverage-run] + # if: always() + # steps: + # - uses: actions/checkout@v4 + # - name: Check coverage status + # uses: ./.github/actions/check-job-status + # with: + # job_name: 'Coverage' + # result: ${{ needs.coverage-run.result }} diff --git a/.gitignore b/.gitignore index 26a42fdf9a6..3a6cfd895bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +**/*repomix* + node_modules test_deploy.env @@ -30,3 +32,13 @@ yarn-error.log **/*.ignore tsconfig.editor.json + +.aider.* +# yalc +yalc.lock +.yalc + +**/keys +**/warp-route-deployer-key.json +**/key.json +**/deployer-key.json diff --git a/.gitleaks.toml b/.gitleaks.toml index 5bc3fb67b40..37c211ce387 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -70,7 +70,7 @@ tags = ["key", "Dwellir"] [[rules]] id = "startale-api-key" description = "Startale API Key" -regex = '''https://[a-zA-Z0-9.-]+\.startale\.com.*\?apikey=[a-zA-Z0-9]+''' +regex = '''https://[a-zA-Z0-9.-]+\.startale\.com.*[\?&]apikey=[a-zA-Z0-9]+''' keywords = [ "startale", "startale.com" @@ -106,3 +106,18 @@ keywords = [ "ccnodes.com" ] tags = ["key", "Crypto Crew Nodes"] + +[[rules]] +id = "svm-cli-private-key" +description = "Solana CLI private key" +regex = '''\[[\s]*(?:\d{1,3}[\s]*,[\s]*){63}\d{1,3}[\s]*\]''' +tags = ["private key", "svm"] + +[[rules]] +id = "svm-base58-private-key" +description = "Solana wallet private key" +regex = '''\b[1-9A-HJ-NP-Za-km-z]{87,88}\b''' +tags = ["private key", "svm"] + +# Not testing evm, starknet or cosmos private key as +# they might trigger false positives with transaction hashes diff --git a/.husky/pre-commit b/.husky/pre-commit index 6a6290ebfeb..c30202ad98f 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -31,4 +31,8 @@ if git diff --staged --exit-code --name-only | grep -q -E ".*\.rs$"; then echo "Running cargo fmt pre-commit hook for rust/sealevel" cargo fmt --all --check --manifest-path rust/sealevel/Cargo.toml + + echo "Running cargo fmt pre-commit hook for dymension/libs/kaspa" + cargo fmt --all --check --manifest-path dymension/libs/kaspa/Cargo.toml + fi diff --git a/.registryrc b/.registryrc index e52186091dd..cd547bdee11 100644 --- a/.registryrc +++ b/.registryrc @@ -1 +1 @@ -7c3439aa17ec03d064732d3d3e6bdae73dd6f98a +3e3330ab7d1bf6bc66406b89bad599793bce98ed diff --git a/.vscode/settings.json b/.vscode/settings.json index 6cb83cc9f63..ff1579bdeb2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "rust-analyzer.linkedProjects": [ + "./rust/main/Cargo.toml", "./rust/sealevel/Cargo.toml", ], diff --git a/Dockerfile b/Dockerfile index e5db184d694..b68c1260ea7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,7 @@ COPY typescript/github-proxy/package.json ./typescript/github-proxy/ COPY typescript/helloworld/package.json ./typescript/helloworld/ COPY typescript/http-registry-server/package.json ./typescript/http-registry-server/ COPY typescript/infra/package.json ./typescript/infra/ +COPY typescript/radix-sdk/package.json ./typescript/radix-sdk/ COPY typescript/sdk/package.json ./typescript/sdk/ COPY typescript/tsconfig/package.json ./typescript/tsconfig/ COPY typescript/utils/package.json ./typescript/utils/ diff --git a/docs/ai-agents/mcp-server-setup.md b/docs/ai-agents/mcp-server-setup.md new file mode 100644 index 00000000000..f42b9a21c17 --- /dev/null +++ b/docs/ai-agents/mcp-server-setup.md @@ -0,0 +1,56 @@ +# MCP Server Setup + +Quick setup guide for MCP servers used by the Abacus Works team. + +## Prerequisites + +Create credentials directory and add required keys: + +```bash +mkdir -p ~/.mcp +# Add your Google Cloud service account key to ~/.mcp/claude-logging-key.json +# Add your Grafana API key to ~/.mcp/grafana-service-account.key +``` + +## Setup Commands + +### Google Cloud MCP Server + +```bash +claude mcp add google-cloud-mcp -- docker run -i \ + -e GOOGLE_APPLICATION_CREDENTIALS=/credentials/gcp-service-key.json \ + -e GOOGLE_CLOUD_PROJECT=abacus-labs-dev \ + -v ~/.mcp/gcp-service-key.json:/credentials/gcp-service-key.json \ + us-east1-docker.pkg.dev/abacus-labs-dev/hyperlane/mcp-google-cloud:latest +``` + +### Grafana MCP Server + +```bash +claude mcp add grafana -- docker run -i \ + -e GRAFANA_URL=https://abacusworks.grafana.net/ \ + -e GRAFANA_API_KEY=$(cat ~/.mcp/grafana-service-account.key) \ + mcp/grafana -t stdio +``` + +### Hyperlane Explorer MCP Server + +```bash +claude mcp add hyperlane-explorer -- docker run -i \ + -e ENDPOINT=https://explorer4.hasura.app/v1/graphql \ + -e SCHEMA=/usr/src/app/schema/hyperlane-schema.graphql \ + us-east1-docker.pkg.dev/abacus-labs-dev/hyperlane/mcp-graphql:latest +``` + +### Notion MCP Server + +```bash +claude mcp add notion https://mcp.notion.com/mcp +``` + +## Verification + +```bash +claude mcp list +# All servers should show "✓ Connected" +``` diff --git a/docs/ai-agents/operational-debugging.md b/docs/ai-agents/operational-debugging.md new file mode 100644 index 00000000000..f502c6b9224 --- /dev/null +++ b/docs/ai-agents/operational-debugging.md @@ -0,0 +1,999 @@ +# Hyperlane Operational Debugging with AI Agents + +This guide shows how to use Claude with Grafana and GCP logging integration to debug Hyperlane operational incidents. + +## Overview + +Claude can automatically query Grafana alerts/dashboards and GCP logs to help diagnose common operational issues. This enables faster incident response and reduces the need for manual investigation. + +**IMPORTANT DEBUGGING STRATEGY:** + +1. **ALWAYS start with Grafana alerts and dashboards first** - Get immediate context about the incident +2. **Then query GCP logs for detailed analysis** - Dive into specifics only after understanding the high-level issue +3. **All Hyperlane logs are in GCP Logging**, NOT in Grafana Loki - Grafana is for metrics/alerts only + +## Configuration References + +For debugging warp route issues, token imbalances, or deployment problems: + +**Canonical Registry**: https://github.com/hyperlane-xyz/hyperlane-registry/ + +- **Warp Route Configurations**: `/deployments/warp_routes/[TOKEN]/` - Contains current token addresses, standards, and cross-chain connections +- **Chain Configurations**: `/chains/` - Network settings and contract addresses +- **Always check the canonical registry first** when investigating token bridge issues or imbalances + +**Common Token Investigation Patterns**: + +- Check if scaling factors are correctly configured between chains +- Verify collateral vs synthetic token standards match expectations +- Confirm contract addresses match between local config and canonical registry +- Look for recent ownership changes or contract upgrades + +## Incident Investigation Workflow + +### Step 1: Check Grafana Alerts and Context + +**Generally start your investigation here:** + +1. **Query active alerts** to understand the incident: + + ``` + list_alert_rules(label_selectors=[{'filters': [{'name': 'severity', 'type': '=', 'value': 'critical'}]}]) + ``` + +2. **Check recent incidents** in Grafana Incident: + + ``` + list_incidents(status='active') + ``` + +3. **Review relevant dashboards** based on the alert type + +### Step 2: Grafana Dashboard Analysis + +After identifying the alert, immediately check the corresponding dashboards for context: + +### Key Dashboards for Debugging + +1. **Easy Dashboard** (`uid: fdf6ada6uzvgga`) + + - **Primary panels for incidents:** + - "Prepare queues per Hyperlane App" - Filter by `app_context` to see specific application issues + - "Critical Reprepare Reasons" - Shows operation_status breakdown for stuck messages + - "Prepare queue diffs" - Identifies sudden queue buildups + - **Variables:** Set `chain` and `RelayerContext` filters to narrow down issues + +2. **Relayers v2 & v3** (`uid: k4aYDtK4k`) + + - "Prepare Queues by Remote" - Overall queue health by destination chain + - "Queue Lengths" - Detailed view with queue_name breakdown + - "Messages Processed" - Verify if messages are flowing + +3. **RPC Usage & Errors** (`uid: bdbwtrzoms5c0c`) + + - Check RPC error rates when suspecting infrastructure issues + - Monitor specific chain RPC health + +4. **Lander Dashboard** (`uid: 197feea9-f831-48ce-b936-eaaa3294a3f6`) + + - **Transaction submission metrics:** + - "Building Queue Length" - Messages waiting to be built into transactions + - "Inclusion Queue Length" - Transactions waiting for inclusion on-chain + - "Finality Queue Length" - Transactions waiting for finalization + - **Gas management:** + - "Gas Price (Gwei)" - Current gas prices being used + - "Priority Fee" - EIP-1559 priority fees + - **Transaction health:** + - "Finalized Transactions" - Successfully submitted txs + - "Dropped Transactions" - Failed transaction submissions + - "Avg (Re)submissions Per Tx" - Gas escalation behavior + - **Key for debugging:** High inclusion queue length indicates transaction submission issues + +5. **Validator Dashboard - In-house** (`uid: xrNCvpK4k`) + + - **Critical validator health metrics:** + - "Unsigned Messages" - Messages observed but not yet signed by validators + - "Messages Signed" - Rate of checkpoint signing activity + - "Observation Lag" - Difference between validator instances + - **Key variables:** Set `chain` parameter to filter by origin chain (e.g., `hyperevm`) + +6. **Validator Dashboard - External** (`uid: cdqntgxna4vswd`) + - **Critical validator health metrics:** + - "Diff observed - processed checkpoints" - Unsigned messages waiting for validator signatures + - "Signed Checkpoint Diffs (30m)" - Validator signing activity + - "Contract Sync Liveness" - Validator chain synchronization health + - **Key for validator issues:** Non-zero "Diff observed - processed" indicates validators not signing checkpoints + +### Key Metrics for Queue Debugging + +**Find stuck messages with specific error types:** + +```promql +hyperlane_submitter_queue_length{ + app_context="EZETH/renzo-prod", # Specific app + remote="linea", # Destination chain + operation_status=~"Retry\\(Error estimating costs.*\\)" +} +``` + +**Check prepare queue by app context:** + +```promql +sum by (app_context, remote, operation_status) ( + hyperlane_submitter_queue_length{ + queue_name="prepare_queue", + remote="linea", + operation_status!="FirstPrepareAttempt" + } +) +``` + +**Identify messages with high retry counts:** + +```promql +hyperlane_operations_processed_count{ + phase="failed", + chain="linea" +} +``` + +**Monitor Lander transaction submission stages:** + +```promql +# Check if transactions are stuck in inclusion +hyperlane_lander_inclusion_stage_pool_length{ + destination="linea" +} + +# Monitor gas price escalation +hyperlane_lander_gas_price{destination="linea"} / 1000000000 + +# Check transaction finalization rate +rate(hyperlane_lander_finalized_transactions[5m]) +``` + +### Debugging Workflow with Grafana + GCP + +1. **Start with Grafana metrics:** + - Check Easy Dashboard for queue length alerts + - Filter by `app_context` to isolate the problematic application + - Look at `operation_status` labels to understand error type +2. **Identify error patterns:** + + - `Retry(Error estimating costs for process call)` → Gas estimation failure, check for contract reverts + - `Retry(Could not fetch metadata)` → Usually temporary, only investigate if persistent + - `Retry(ApplicationReport(...))` → Application-specific errors + +3. **Then query GCP logs for details:** + - Use message IDs from metrics to search logs + - Focus on `eth_estimateGas` errors for gas estimation failures + - Decode revert data with `cast 4byte` + +## Hyperlane Explorer Integration + +Use the Explorer GraphQL endpoint to identify stuck messages **before** querying GCP logs. + +**Endpoint:** `https://explorer4.hasura.app/v1/graphql` + +**Find unprocessed messages:** + +```graphql +query GetMessages { + message_view( + where: { + # Modify filters as needed: + origin_domain: { _eq: "hyperevm" } + destination_domain: { _eq: "ethereum" } + is_delivered: { _eq: false } # false = undelivered + send_occurred_at: { _gte: "2025-08-09T00:00:00Z" } # adjust time range + } + limit: 10 + order_by: { send_occurred_at: desc } + ) { + msg_id + send_occurred_at + delivery_occurred_at + is_delivered + origin_tx_hash + destination_tx_hash + } +} +``` + +**Workflow:** + +1. Query Explorer for stuck messages (`is_delivered: false` with old timestamps) +2. Extract `msg_id` (convert bytea `\x05bf...` to hex `0x05bf...`) +3. Use message ID in targeted GCP log query: `[BASE_QUERY] AND "0x05bf..."` + +This provides concrete message IDs for efficient log analysis instead of guessing which messages are stuck. + +## Quick Diagnosis for Common Issues + +### RPC Provider Errors (>10% error rate) + +**Immediate action - run this query first:** + +```promql +sum by (provider_node, status) ( + hyperlane_request_count{chain="[CHAIN_NAME]", method="eth_blockNumber"} +) +``` + +- If one provider shows failures and another shows successes → **Bad endpoint in config** +- If all providers fail → **Chain-wide issue or rate limiting** + +### Stuck Messages / High Queue Length + +**Check operation status breakdown:** + +```promql +sum by (operation_status) ( + hyperlane_submitter_queue_length{ + queue_name="prepare_queue", + remote="[DESTINATION_CHAIN]" + } +) +``` + +- `Retry(Error estimating costs...)` → **Contract revert, check gas estimation** +- `Retry(Could not fetch metadata)` → **Validator delays, check validator dashboards** +- High retry counts (>40) → **Persistent issue needs investigation** + +## Common Debugging Workflows + +### 1. Investigate any incident (RPC errors, stuck messages, validator issues, etc.) + +**Ask Claude:** + +``` +Investigate the [INCIDENT_NAME] incident +``` + +**What Claude will do:** + +1. **Query Grafana alerts first** to understand the incident context +2. **Check relevant dashboards** based on the alert type +3. **Analyze metrics** to identify patterns and affected components +4. **Then query GCP logs** for detailed root cause analysis +5. **Provide recommendations** for resolution + +### 2. Check if a message was processed + +**Ask Claude:** + +``` +Was message ID 0x[MESSAGE_ID] processed? Check the relayer logs. +``` + +**What Claude will do:** + +- First check Grafana dashboards for queue metrics related to the message +- Query GCP logs for the specific message ID +- Check both prepare and confirm queue logs +- Analyze the message status and retry counts +- Report if the message completed successfully or got stuck + +### 3. Debug stuck messages with metadata issues + +**Ask Claude:** + +``` +Why are messages stuck in the prepare queue? Check for CouldNotFetchMetadata errors. +``` + +**What Claude will do:** + +- **CHECK VALIDATOR DASHBOARDS FIRST**: Query validator metrics to identify signing delays +- Review validator alert status in Grafana +- Search for messages with CouldNotFetchMetadata status in GCP logs +- Look for validator inconsistencies and lagging validators +- Identify specific validators that are behind on checkpoint signing +- Cross-reference with validator alert rules for inconsistent checkpoints + +### 4. Investigate RPC provider issues + +**Ask Claude:** + +``` +Are there RPC errors affecting [CHAIN] in the last hour? +``` + +**What Claude will do:** + +- **First check RPC Usage & Errors dashboard** (`uid: bdbwtrzoms5c0c`) for error rates +- Review RPC-related alerts in Grafana +- Then query GCP logs for SerdeJson errors and 503 responses +- Identify which chains/domains are affected +- Show patterns in RPC failures +- Help determine if provider rotation is needed + +#### Efficient RPC Debugging Workflow + +**CRITICAL: For any chain with >10% RPC error rate, immediately check these in order:** + +1. **Check provider-specific success rates** (finds misconfigured endpoints instantly): + +```promql +sum by (provider_node, status) ( + hyperlane_request_count{chain="[CHAIN]", method="eth_blockNumber"} +) +``` + +If one provider shows 100% failures while another succeeds, you've found a bad endpoint. + +2. **Compare request rates across chains** (identifies excessive polling): + +```promql +topk(10, sum by (chain) ( + rate(hyperlane_request_count{method="eth_blockNumber"}[5m]) +)) +``` + +Chains with >8 req/s for blockNumber are likely misconfigured or hitting rate limits. + +3. **Check if multiple providers are configured**: + +```promql +count by (chain) ( + group by (chain, provider_node) (hyperlane_request_count{chain="[CHAIN]"}) +) +``` + +If count > 1, there are multiple providers and likely one is failing. + +4. **Only then check logs** if the above doesn't reveal the issue. + +#### Common RPC Error Patterns + +| Error Pattern | Root Cause | Quick Fix | +| ------------------------------------------- | ---------------------------------- | --------------------------------- | +| 50% error rate, two providers shown | One provider is down/misconfigured | Check config for bad endpoint | +| High error rate on Arbitrum/Optimism chains | Nitro chains + aggressive polling | Check if block production stopped | +| 100% failure on one provider, 0% on another | DNS change or deprecated endpoint | Update RPC URL in config | +| Gradually increasing error rate | Rate limiting kicking in | Reduce polling frequency | +| Sudden spike to 100% errors | Provider outage | Switch to backup provider | + +#### Request Rate Red Flags + +- **>10 req/s for eth_blockNumber**: Excessive polling, likely misconfiguration +- **>5 req/s for eth_getLogs**: May hit rate limits on free tiers +- **Nitro chains with constant polling**: These produce blocks on-demand, adjust expectations + +### 5. Check relayer balance issues + +**Ask Claude:** + +``` +Is the relayer running out of funds on [CHAIN]? Check balance warnings. +``` + +**What Claude will do:** + +- Check Grafana dashboards for balance-related metrics +- Look for balance-related logs and warnings in GCP +- Check for gas estimation errors that indicate low balances +- Identify which chains need funding +- Show recent funding operations + +### 6. Monitor Lander (transaction submitter) issues + +**Ask Claude:** + +``` +Are there any Lander errors causing transaction submission failures? +``` + +**What Claude will do:** + +- **First check Lander Dashboard** (`uid: 197feea9-f831-48ce-b936-eaaa3294a3f6`) for queue metrics +- Review transaction submission stages and gas escalation patterns +- Query Lander-specific logs from GCP +- Identify stuck or failed transaction submissions +- Suggest whether to switch back to Classic submitter + +### 7. Debug validator inconsistency and checkpoint signing issues + +**Ask Claude:** + +``` +Are there validator inconsistencies causing metadata delays for [ORIGIN_CHAIN]? +``` + +**What Claude will do:** + +- **First check Validator Dashboards** (`uid: xrNCvpK4k` and `uid: cdqntgxna4vswd`) +- Query validator inconsistency alerts (`uid: e26839dc-2f4c-4ff3-9e31-734dbf9cf061`) +- **Use `hyperlane_observed_validator_latest_index` metric** (relayer's view of ALL validators, including external ones) +- **Convert validator addresses to names** using multisigIsm.ts mapping +- Then query GCP logs for specific validator issues +- Identify specific validator operators that are behind on checkpoint signing +- Calculate the signing delay impact on message processing +- Correlate validator delays with relayer metadata fetch failures + +**CRITICAL LESSON LEARNED:** +When debugging validator checkpoint status, **always use the relayer's perspective** with `hyperlane_observed_validator_latest_index{origin="[chain]"}` rather than internal validator metrics. This shows ALL validators (including external ones like Merkly, Imperator, etc.) and their actual checkpoint signing status as seen by message processing. Internal validator metrics only show Abacus Works validators. + +**Validator Debugging Methodology:** + +1. **Check alert status**: Query the "Inconsistent latest checkpoints" alert +2. **Identify lagging validators**: Compare latest checkpoint indices across validator set +3. **Measure delay impact**: Calculate how far behind the lagging validators are +4. **Cross-reference with relayer issues**: Connect validator delays to `CouldNotFetchMetadata` errors +5. **Track resolution**: Monitor when lagging validators catch up + +**Alternative: Use TypeScript Infra Script for Direct Validator Status** + +When Grafana metrics are incomplete or missing validator data for a chain, use the infra script: + +```bash +# From typescript/infra directory +yarn tsx scripts/validators/print-latest-checkpoints.ts -e mainnet3 --chains [CHAIN_NAME] + +# Example for HyperEVM: +yarn tsx scripts/validators/print-latest-checkpoints.ts -e mainnet3 --chains hyperevm +``` + +**This script shows:** + +- All validator addresses and their aliases (Abacus Works, Merkly, Imperator, etc.) +- Latest checkpoint index each validator has signed +- Their S3 bucket URLs for checkpoint storage +- Default validator status (✅ if configured properly) + +**Use this when:** + +- `hyperlane_observed_validator_latest_index` returns no data for a chain +- Grafana metrics are incomplete or missing +- You need to verify specific validator S3 bucket accessibility +- Debugging new chain integrations + +**Key Metrics for Validator Issues:** + +```promql +# Find validators behind in checkpoint signing (relayer's view - shows ALL validators) +hyperlane_observed_validator_latest_index{ + origin="hyperevm", + hyperlane_deployment="mainnet3", + app_context="default_ism" +} + +# Check validator signing gaps over time +max by(chain) ( + hyperlane_latest_checkpoint{ + agent="validator", + phase="validator_observed", + chain="hyperevm" + } +) - max by(chain) ( + hyperlane_latest_checkpoint{ + agent="validator", + phase="validator_processed", + chain="hyperevm" + } +) +``` + +### 8. Debug gas estimation errors and contract reverts + +**Ask Claude:** + +``` +Why are EZETH/renzo-prod messages failing with gas estimation errors on Linea? +``` + +**What Claude will do:** + +- **First check Easy Dashboard** (`uid: fdf6ada6uzvgga`) for queue metrics with app_context filter +- Review "Critical Reprepare Reasons" panel for error patterns +- Find stuck messages with high retry counts in GCP logs +- Look for `eth_estimateGas` errors in the logs +- Extract contract revert reasons from error data +- Use `cast 4byte` to decode revert selectors (e.g., `0x0b6842aa` = `IXERC20_NotHighEnoughLimits()`) +- Identify root causes like bridging limits, insufficient approvals, or contract state issues + +**Debugging Methodology for Gas Estimation Failures:** + +1. Find stuck operations: `jsonPayload.fields.num_retries>=5` + specific app context +2. Extract message IDs from stuck operations +3. **Deprioritize common transient errors**: Don't focus on isolated nonce errors, connection resets, or RPC 503s unless they're persistent over longer periods +4. Search for gas estimation: `"0x[MESSAGE_ID]" AND "eth_estimateGas"` +5. Look for revert data: `"execution reverted, data: Some(String(\"0x..."` +6. Decode with: `cast 4byte 0x[selector]` + +### 9. Debug Warp Route Collateral-Synthetic Imbalances + +**Ask Claude:** + +``` +Investigate the collateral-synthetic imbalance on [WARP_ROUTE_ID]. Check for undelivered messages causing the imbalance. +``` + +**What Claude will do:** + +1. **Check Warp Routes dashboard first**: Query Grafana for current imbalance metrics and trends +2. **Review related alerts**: Check for any Warp Route imbalance alerts +3. **Find undelivered messages**: Search Hyperlane Explorer for `is_delivered: false` messages +4. **Query rebalancer logs in GCP**: Check why automatic rebalancing isn't working +5. **Identify large transfers**: Look for message amounts matching the imbalance size +6. **Analyze root cause**: Determine if insufficient collateral is preventing message delivery + +**Key Understanding of Warp Route Architecture:** + +- **Collateral chains** (Ethereum, Arbitrum, Base, etc.): Hold actual USDC/tokens as collateral +- **Synthetic chains** (Mode, etc.): Hold synthetic/wrapped versions that can be minted/burned +- **Normal balance**: Total collateral ≈ Total synthetic tokens +- **Imbalance cause**: Undelivered messages from synthetic→collateral transfers + +**Debugging Methodology for Warp Route Imbalances:** + +1. **Query Warp Routes dashboard**: Get current imbalance amount and trend +2. **Check rebalancer logs first**: Query GCP logs for rebalancer activity +3. **Search Explorer for undelivered messages**: + ```graphql + query UndeliveredMessages { + message_view( + where: { + is_delivered: { _eq: false } + send_occurred_at: { _gte: "[TIME_RANGE]" } + # Filter by warp route contract addresses if known + } + order_by: { send_occurred_at: desc } + limit: 20 + ) { + msg_id + sender + recipient + origin_domain + destination_domain + send_occurred_at + message_body # Contains transfer amount + } + } + ``` +4. **Decode message amounts**: Check if message body contains amount matching imbalance +5. **Root cause analysis**: For synthetic→collateral transfers, insufficient collateral prevents delivery +6. **Resolution**: Either fund the destination chain or wait for organic rebalancing + +**Rebalancer Log Queries:** +The rebalancer automatically attempts to fix imbalances by moving tokens between chains. Check its logs to understand why rebalancing isn't working: + +``` +resource.type="k8s_container" +resource.labels.project_id="abacus-labs-dev" +resource.labels.location="us-east1-c" +resource.labels.cluster_name="hyperlane-mainnet" +resource.labels.namespace_name="mainnet3" +labels."k8s-pod/app_kubernetes_io/name"="rebalancer" +timestamp>="2025-08-09T20:00:00Z" +``` + +**Filter by specific warp route:** + +``` +[REBALANCER_BASE_QUERY] AND "USDC/paradex" +``` + +**Common rebalancer log patterns:** + +- **"Insufficient balance"**: Not enough tokens on source chain to rebalance +- **"Rebalancing [amount] from [source] to [dest]"**: Active rebalancing operation +- **"Skipping rebalance"**: Conditions not met (thresholds, limits) +- **"Failed to estimate gas"**: Transaction simulation failed +- **"Waiting for [msg_id] to be delivered"**: Rebalancer waiting for stuck message + +**Critical Insight**: + +- When messages transfer FROM synthetic chains TO collateral chains, the synthetic tokens are burned immediately on send +- BUT the collateral tokens are only transferred on delivery +- If insufficient collateral exists on destination chain, message stays undelivered +- This creates an imbalance: less synthetic + same collateral = excess collateral + +**Common Imbalance Patterns:** + +- **Positive imbalance** (collateral > synthetic): Undelivered synthetic→collateral transfers +- **Negative imbalance** (synthetic > collateral): Undelivered collateral→synthetic transfers +- **Large sudden imbalances**: Look for single large undelivered message matching the amount + +**Critical Debugging Rules:** + +- **Deprioritize nonce errors** - these are normal during transaction submission unless persistent over hours +- **Deprioritize connection resets** - these are normal RPC hiccups unless occurring repeatedly +- **Skip metadata errors initially** - validators need finality time +- **Prioritize gas estimation errors** - these contain the real root cause +- **Focus on high retry counts** - messages stuck for 40+ retries indicate persistent issues +- When user mentions "queue length > 0 for X minutes", immediately search for stuck messages with `num_retries>=5` + +## GCP Log Queries Reference + +Claude uses optimized, progressive queries to minimize tokens and maximize relevance: + +### Base Agent Query Templates (with noise filtering) + +**Relayer Query:** + +``` +resource.type="k8s_container" +resource.labels.project_id="abacus-labs-dev" +resource.labels.location="us-east1-c" +resource.labels.cluster_name="hyperlane-mainnet" +resource.labels.namespace_name="mainnet3" +labels."k8s-pod/app_kubernetes_io/component"="relayer" +labels."k8s-pod/app_kubernetes_io/instance"="omniscient-relayer" +labels."k8s-pod/app_kubernetes_io/name"="hyperlane-agent" +-jsonPayload.fields.message="No message found in DB for leaf index" +-jsonPayload.fields.message="Found log(s) in index range" +-jsonPayload.fields.message="Popped OpQueue operations" +-jsonPayload.fields.message="fallback_request" +``` + +**Scraper Query:** + +``` +resource.type="k8s_container" +resource.labels.project_id="abacus-labs-dev" +resource.labels.location="us-east1-c" +resource.labels.cluster_name="hyperlane-mainnet" +resource.labels.namespace_name="mainnet3" +labels.k8s-pod/app_kubernetes_io/component="scraper3" +labels.k8s-pod/app_kubernetes_io/instance="omniscient-scraper" +labels.k8s-pod/app_kubernetes_io/name="hyperlane-agent" +``` + +**Validator Query:** + +``` +resource.type="k8s_container" +resource.labels.project_id="abacus-labs-dev" +resource.labels.location="us-east1-c" +resource.labels.cluster_name="hyperlane-mainnet" +resource.labels.namespace_name="mainnet3" +labels.k8s-pod/app_kubernetes_io/component="validator" +labels.k8s-pod/app_kubernetes_io/name="hyperlane-agent" +``` + +### Progressive Query Strategy + +Use this token-efficient approach for all debugging scenarios: + +**1. Start specific and minimal:** + +``` +[BASE_QUERY] AND "0x[MESSAGE_ID]" +[BASE_QUERY] AND "EZETH/renzo-prod" AND jsonPayload.fields.num_retries>=5 +[BASE_QUERY] AND severity>="WARNING" AND timestamp>="-1h" +``` + +_Get targeted results first - only what's directly relevant_ + +**2. Add error context if needed:** + +``` +[BASE_QUERY] AND "0x[MESSAGE_ID]" AND severity>="WARNING" +[BASE_QUERY] AND "CouldNotFetchMetadata" AND jsonPayload.spans.domain:"ethereum" +``` + +_Layer in additional filters to understand problems_ + +**3. Get full context only when necessary:** + +``` +[BASE_QUERY] AND "0x[MESSAGE_ID]" +``` + +_Remove noise filters to see complete lifecycle - use sparingly due to token costs_ + +**4. Extract specific fields to reduce tokens:** +When requesting logs, focus on essential fields like `jsonPayload.fields.message`, `jsonPayload.fields.error`, `jsonPayload.spans.domain` rather than full log entries. + +### Targeted Query Patterns + +**Message status tracking:** + +``` +[BASE_QUERY] AND "0x[MESSAGE_ID]" AND ( + jsonPayload.fields.message:"confirmed" OR + jsonPayload.fields.message:"delivered" OR + jsonPayload.spans.name:"delivered" +) +``` + +**Error investigation:** + +``` +[BASE_QUERY] AND severity>="WARNING" AND ( + jsonPayload.fields.message:"CouldNotFetchMetadata" OR + jsonPayload.fields.message:"SerdeJson error" OR + jsonPayload.fields.error:"503 Service Temporarily Unavailable" +) +``` + +**Stuck message analysis:** + +``` +[BASE_QUERY] AND jsonPayload.fields.num_retries>=5 +``` + +**Chain-specific issues:** + +``` +[BASE_QUERY] AND jsonPayload.spans.domain:"[CHAIN]" AND severity>="WARNING" +``` + +**Time-bounded queries:** + +``` +[BASE_QUERY] AND timestamp>="2025-08-09T00:30:00Z" AND [SPECIFIC_FILTER] +``` + +**Lander transaction submitter:** + +``` +[BASE_QUERY] AND jsonPayload.target:"lander" +``` + +**Rebalancer logs:** + +``` +resource.type="k8s_container" +resource.labels.project_id="abacus-labs-dev" +resource.labels.location="us-east1-c" +resource.labels.cluster_name="hyperlane-mainnet" +resource.labels.namespace_name="mainnet3" +labels."k8s-pod/app_kubernetes_io/name"="rebalancer" +``` + +### Query Efficiency Tips + +- **Start specific**: Always begin with the most targeted query first +- **Use time bounds**: Add timestamp filters to limit results (`timestamp>=`) +- **Filter by severity**: Use `severity>="WARNING"` when looking for problems +- **Target specific fields**: Search `jsonPayload.fields.message:"exact_text"` rather than broad searches +- **Progressive detail**: Only request full log context when basic queries indicate issues +- **Avoid noise**: The base query already filters out common noisy log patterns + +## Key Log Fields + +When Claude analyzes logs, it focuses on these important fields: + +- **`jsonPayload.fields.message`**: Main log message content +- **`jsonPayload.spans[].domain`**: Which chain/domain the operation involves +- **`jsonPayload.fields.operations`**: Details about pending messages and status +- **`jsonPayload.fields.error`**: Specific error information +- **`jsonPayload.spans[].name`**: Operation type (e.g., "confirm_classic_task", "finality_stage") +- **`jsonPayload.fields.num_retries`**: How many times a message has been retried +- **`jsonPayload.fields.status`**: Current message processing status + +## Example Analysis Requests + +Here are examples of how to ask Claude to help with specific debugging scenarios: + +``` +"Check message 0xabc123... processing status" + +"Find CouldNotFetchMetadata errors in the last 2 hours" + +"Look for RPC errors affecting polygon in the last hour" + +"Show messages with 5+ retries stuck in queue" + +"Check validator issues preventing message confirmation" + +"Find relayer balance warnings on arbitrum" + +"Show Lander transaction submission errors" + +"Are there 503 RPC errors in the last 30 minutes?" + +"Debug EZETH/renzo-prod queue length > 0 alert on Linea" + +"Why are messages stuck with gas estimation errors?" +``` + +## Debugging Heuristics + +Based on operational patterns, Claude should follow these heuristics: + +**When investigating "queue length > 0" alerts:** + +1. **Start with stuck operations**: Look for high retry counts (`num_retries>=5`) in the specific app context +2. **Get message IDs**: Extract the actual message IDs from stuck operations +3. **Deprioritize transient errors**: Don't focus on isolated nonce errors, connection resets, or occasional RPC failures unless they're persistent over longer periods +4. **For CouldNotFetchMetadata errors - check validators only after 5+ minutes of delays**: + - Query validator dashboards (`uid: xrNCvpK4k` and `uid: cdqntgxna4vswd`) with origin chain filter + - Look for "Unsigned Messages" > 0 or "Diff observed - processed checkpoints" > 0 + - Check validator inconsistency alerts (`uid: e26839dc-2f4c-4ff3-9e31-734dbf9cf061`) + - Identify specific lagging validators with Prometheus queries + - **Convert validator addresses to operator names** using multisigIsm.ts for targeted outreach +5. **Go directly to gas estimation**: Search for `eth_estimateGas` errors with the stuck message IDs +6. **Decode contract errors**: Use `cast 4byte` for any revert selectors found + +**For efficient analysis:** + +1. **Message ID first**: Always start with the specific message causing issues +2. **Progressive detail**: Basic → error context → full logs only when needed +3. **Focus on relevant spans**: Look for spans with the target chain in `jsonPayload.spans.domain` +4. **Time bounds matter**: Use `timestamp>=` filters to limit search scope +5. **Extract key fields**: Request specific JSON fields rather than full log entries + +**Common error patterns:** + +- `CouldNotFetchMetadata` → Normal during initial attempts (validators need finality) - **only check validators after 5+ minutes of persistent delays** +- `eth_estimateGas` failures → **TRUE ROOT CAUSE**, look for revert data immediately +- `503 Service Temporarily Unavailable` → RPC provider issues - **deprioritize unless persistent over hours** +- `nonce too low/high` → Normal during gas escalation - **deprioritize unless persistent over hours** +- `connection reset by peer` → Normal RPC hiccups - **deprioritize unless frequent** +- High `num_retries` with same error → **FOCUS HERE** - persistent problem needing attention +- `IXERC20_NotHighEnoughLimits()` → Bridge hit daily/rate limits +- `SerdeJson error` → Usually RPC response parsing issues - **deprioritize unless persistent** + +**Validator-Related Patterns:** + +- **5+ minute delays with high retries** → Then check validator dashboards for checkpoint signing gaps +- **Validator inconsistency alerts** → Specific validators lagging behind in checkpoint signing +- **Metadata timeouts correlating with validator gaps** → Validator availability causing relayer delays + +### Optimized Query Examples + +Claude will use these efficiency patterns automatically: + +**Message tracking (token-efficient):** + +1. First: `"0xabc123..."` - Gets basic status +2. If issues: `"0xabc123..." AND severity>="WARNING"` - Gets error details +3. If needed: Remove noise filters for complete context + +**Error investigation (targeted):** + +1. `severity>="WARNING" AND timestamp>="-1h"` - Recent problems only +2. `jsonPayload.fields.message:"CouldNotFetchMetadata"` - Specific error type +3. `jsonPayload.spans.domain:"ethereum"` - Chain-specific issues + +## Hyperlane Registry Integration + +The [Hyperlane Registry](https://github.com/hyperlane-xyz/hyperlane-registry/) provides essential reference data for debugging operational issues. Use your local registry clone to quickly access chain information. + +### Registry Usage for Debugging + +**Local Registry Path**: `/Users/nambrot/devstuff/hyperlane-registry` + +**Quick Chain Lookups:** + +```bash +# Get contract addresses for any chain +cat /Users/nambrot/devstuff/hyperlane-registry/chains/ethereum/addresses.yaml + +# Check RPC endpoints and block explorer URLs +cat /Users/nambrot/devstuff/hyperlane-registry/chains/ethereum/metadata.yaml + +# Find chains by name pattern +ls /Users/nambrot/devstuff/hyperlane-registry/chains/ | grep -i arbitrum +``` + +**Chain Information Available:** + +- **Contract addresses** (`addresses.yaml`): Mailbox, ISMs, hooks, gas oracles, validators +- **Chain metadata** (`metadata.yaml`): RPC URLs, block explorers, gas settings, reorg periods +- **Warp routes** (`warp_routes.json`): Token bridge configurations across chains + +**Integration with Debugging Workflow:** + +1. **Contract Address Verification**: When debugging transaction failures, verify contract addresses against registry +2. **RPC Failover**: Use backup RPC URLs from registry when primary endpoints fail +3. **Block Explorer Links**: Quickly access transaction details using explorer URLs from registry +4. **Chain Configuration**: Verify block confirmation requirements and finalization settings +5. **Warp Route Analysis**: Cross-reference token bridge deployments during imbalance debugging + +**Programmatic Access:** + +```typescript +import { FileSystemRegistry } from '@hyperlane-xyz/registry'; + +const registry = new FileSystemRegistry({ + uri: '/Users/nambrot/devstuff/hyperlane-registry', +}); +const chainData = await registry.getChainMetadata('ethereum'); +const addresses = await registry.getChainAddresses('ethereum'); +``` + +**JSON Query Examples:** + +```bash +# Find chain by ID +jq '.[] | select(.chainId == 42161)' /Users/nambrot/devstuff/hyperlane-registry/chains.json + +# Get all mainnet chains +jq '.[] | select(.environment == "mainnet")' /Users/nambrot/devstuff/hyperlane-registry/chains.json + +# Find warp routes for specific token +jq '.[] | select(.token | contains("USDC"))' /Users/nambrot/devstuff/hyperlane-registry/warp_routes.json +``` + +## Validator Address to Name Mapping + +When debugging validator issues, use the multisig ISM configuration to convert validator addresses to human-readable names. + +**Location**: `/Users/nambrot/devstuff/hyperlane-monorepo/typescript/sdk/src/consts/multisigIsm.ts` + +**Validator Name Lookup:** + +```bash +# Find validator name by address (case-insensitive search) +grep -i -A 1 -B 1 "0x03c842db86a6a3e524d4a6615390c1ea8e2b9541" /Users/nambrot/devstuff/hyperlane-monorepo/typescript/sdk/src/consts/multisigIsm.ts + +# Get all validators for a specific chain +grep -A 20 "ethereum:" /Users/nambrot/devstuff/hyperlane-monorepo/typescript/sdk/src/consts/multisigIsm.ts +``` + +**Common Validator Names:** + +- `0x03c842db86a6a3e524d4a6615390c1ea8e2b9541` → **Abacus Works** (Ethereum) +- `0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f` → **Merkly** (Multiple chains) +- `0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36` → **Mitosis** (Multiple chains) +- `0x5450447aee7b544c462c9352bef7cad049b0c2dc` → **Zee Prime** (Multiple chains) +- `0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8` → **Everstake** (Multiple chains) +- `0xb3ac35d3988bca8c2ffd195b1c6bee18536b317b` → **Staked** (Multiple chains) + +**Integration with Validator Debugging:** +When investigating validator inconsistencies: + +1. **Get lagging validator addresses** from Grafana metrics +2. **Look up validator names** using the multisigIsm.ts file +3. **Identify specific validator operators** for targeted communication +4. **Cross-reference with alert thresholds** to understand impact severity + +**Example Usage:** + +```bash +# During validator debugging - convert address to name +echo "Validator 0x36f2bd8200ede5f969d63a0a28e654392c51a193 is behind" +grep -i "0x36f2bd8200ede5f969d63a0a28e654392c51a193" /path/to/multisigIsm.ts +# Output: "alias: 'Imperator'" - so you know Imperator validator is behind +``` + +**Programmatic Access:** + +```typescript +import { defaultMultisigConfigs } from '@hyperlane-xyz/sdk'; + +// Get validator info for a chain +const chainValidators = defaultMultisigConfigs.ethereum.validators; +const validatorName = chainValidators.find( + (v) => + v.address.toLowerCase() === '0x03c842db86a6a3e524d4a6615390c1ea8e2b9541', +)?.alias; // Returns "Abacus Works" +``` + +## Integration with Existing Runbook + +This AI-powered debugging complements the existing manual runbook procedures. When Claude identifies an issue, it can reference specific runbook sections for resolution steps. + +**Primary Runbook Reference**: [Hyperlane Operations Runbook](https://www.notion.so/hyperlanexyz/Runbook-AI-Agent-24a6d35200d680229b38e8501164ca66) - The comprehensive manual runbook containing detailed operational procedures for deployment, debugging, and incident response. + +For complex issues requiring manual intervention (like validator restarts, RPC rotations, or balance funding), Claude will identify the problem and direct you to the appropriate runbook section. + +**Key Runbook Sections Referenced by AI Debugging:** + +- **Agent Deployment & Redeployment**: Deploy new agent versions and restart failed pods +- **RPC URL Rotation**: Update failing RPC endpoints when provider errors are detected +- **Validator Operations**: Handle validator inconsistencies and reorg recovery procedures +- **Message Processing**: Manually process stuck messages and retry failed operations +- **Balance Management**: Fund relayer keys when balance warnings are triggered +- **Security Incident Response**: Emergency procedures for compromised systems or smart contracts +- **Lander/Transaction Submitter Operations**: Debug and configure transaction submission issues + +## Benefits + +- **Faster triage**: Quickly identify if an issue is RPC-related, validator-related, or balance-related +- **Pattern detection**: Spot trends across multiple chains or time periods +- **Reduced context switching**: Get answers without manually constructing GCP queries +- **Historical analysis**: Easily compare current issues to past patterns + +## Next Steps + +As this integration matures, we can extend Claude's capabilities to: + +- Automatically cross-reference with validator status dashboards +- Correlate with blockchain network issues +- Suggest specific remediation actions +- Generate incident reports with log evidence diff --git a/dymension/README.md b/dymension/README.md new file mode 100644 index 00000000000..659ac1021dc --- /dev/null +++ b/dymension/README.md @@ -0,0 +1,94 @@ +# Hyperlane + +https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/README.md + +``` + +Maintenance + (in root) + # recommend node v20^ + yarn install # crucial to also download turbo tool + yarn build + yarn clean + yarn lint + (in rust/main and rust/sealevel) + cargo build + +Testing + Global (everything, to ensure no regressions, and correct merges/rebases etc) + (in root) + yarn test + (in rust/main and rust/sealevel) + cargo test + Our stuff (our new logic, contracts etc) + yarn test:forge --match-contract HypERC20MemoTest + yarn test:forge --match-contract HypERC20CollateralMemoTest + yarn test:forge --match-contract HypNativeMemoTest + cargo test --test functional + +Notes + Ethereum + HypERC20 = Synthetic + HypERC20Collateral = Collateral + HypNative = Native + Solana + hyperlane-sealevel-token = Synthetic + hyperlane-sealevel-token-collateral = Collateral + hyperlane-sealevel-token-native = Native + +Change list + Ethereum + Copied HypERC20 and modified to include memo in transferFromSender + Copied test for HypERC20 and added memo check + Copied HypNative and modified to include memo in transferFromSender + Copied test for HypNative and added memo check + Extended HypERC20Collateral with override to include memo in transferFromSender + Copied test for HypeERC20Collateral and added memo check + Solana + Added hyperlane-sealevel-token-native-memo + Added hyperlane-sealevel-token-collateral-memo + Added hyperlane-sealevel-token-memo + +How to work on the typescript CLI (locally): + The CLI depends on the SDK, so first do yarn build from typescript/sdk, and only then will yarn build from typescript/cli work + How to rebuild and reinstall? + # in top level (hyperlane-monorepo) + yarn clean; yarn install; yarn build; # CLEAN IS VERY IMPORTANT! + #in typescript/cli + npm uninstall -g @hyperlane-xyz/cli; + yarn install + yarn build + yarn bundle + npm install -g + hyperlane --version + +Publishing our CLI fork: + node use 20 + npm whoami + npm config list + npm config get registry + # visit https://www.npmjs.com/settings/daniel.dymension.xyz/packages + # change typescript/cli/package.js name field to @daniel.dymension.xyz/hyperlane-cli + yarn clean; yarn install; yarn build; yarn bundle; + npm publish --access public + + +How to build rust agents: + cd rust/main + cargo build --release --bin relayer + cargo build --release --bin validator + TODO: scraper + +How to manage solana versions: + # Several versions needed + # v1.14.20 for building programs, v2 for launching a local node, v1.18.18 for deploying programs + + # initial install + curl --proto '=https' --tlsv1.2 -sSfL https://solana-install.solana.workers.dev | bash + # then agave-install or solana-install + solana-install init v1.18.18 + solana-install init v1.14.20 + sh -c "$(curl -sSfL https://release.anza.xyz/v2.2.13/install)" + + +``` diff --git a/dymension/libs/kaspa/.gitignore b/dymension/libs/kaspa/.gitignore new file mode 100644 index 00000000000..f2a4093411b --- /dev/null +++ b/dymension/libs/kaspa/.gitignore @@ -0,0 +1 @@ +**/target \ No newline at end of file diff --git a/dymension/libs/kaspa/CONTRIBUTING.md b/dymension/libs/kaspa/CONTRIBUTING.md new file mode 100644 index 00000000000..c41c93c97b6 --- /dev/null +++ b/dymension/libs/kaspa/CONTRIBUTING.md @@ -0,0 +1,21 @@ +# Kaspa + +## + +See Epic for more info + +## Structure + +``` +├── tooling // tooling for users, validator operators, developers etc +├── lib +│ ├── core // shared by relayer and validator libs +│ ├── relayer // not used by validator lib +│ └── validator // not used by relayer lib +``` + +## HL integrations + +Actual binaries should go in the appropriate HL directories and call our libs. + +See https://github.com/dymensionxyz/hyperlane-monorepo/tree/main-dym/rust/main/agents, https://github.com/dymensionxyz/hyperlane-monorepo/tree/main-dym/rust/main/chains. diff --git a/dymension/libs/kaspa/Cargo.lock b/dymension/libs/kaspa/Cargo.lock new file mode 100644 index 00000000000..2c84160452c --- /dev/null +++ b/dymension/libs/kaspa/Cargo.lock @@ -0,0 +1,6812 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "accessory" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87537f9ae7cfa78d5b8ebd1a1db25959f5e737126be4d8eb44a5452fc4b63cde" +dependencies = [ + "macroific", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if 1.0.1", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if 1.0.1", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.3.1", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" +dependencies = [ + "async-lock", + "cfg-if 1.0.1", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.4.0", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-std" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24" +dependencies = [ + "async-attributes", + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-config" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c478f5b10ce55c9a33f87ca3404ca92768b144fc1bfdede7c0121214a8283a25" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 1.3.1", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf26925f4a5b59eb76722b63c2892b1d70d06fa053c72e4a100ec308c1d47bc" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-lc-rs" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "aws-runtime" +version = "1.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c034a1bc1d70e16e7f4e4caf7e9f7693e4c9c24cd91cf17c2a0b21abaebc7c8b" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid 1.11.0", +] + +[[package]] +name = "aws-sdk-kms" +version = "1.85.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "861b59d319d5a504cc3dd4d27542f4f7bc19fa4de27203a923882fc31e994c8a" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-secretsmanager" +version = "1.86.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e969001a87cadc914c7021644681dd4a9cd7f22484056fac281c3c9b49b5f97a" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.82.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b069e4973dc25875bbd54e4c6658bdb4086a846ee9ed50f328d4d4c33ebf9857" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.83.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b49e8fe57ff100a2f717abfa65bdd94e39702fa5ab3f60cddc6ac7784010c68" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91abcdbfb48c38a0419eb75e0eac772a4783a96750392680e4f3c25a8a0535b9" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffc03068fbb9c8dd5ce1c6fb240678a5cffb86fb2b7b1985c999c4b83c8df68" +dependencies = [ + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.3.1", + "percent-encoding", + "sha2", + "time", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "127fcfad33b7dfc531141fda7e1c402ac65f88aca5511a4d31e2e3d2cd01ce9c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-http" +version = "0.62.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445d5d720c99eed0b4aa674ed00d835d9b1427dd73e04adaf2f94c6b2d6f9fca" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f108f1ca850f3feef3009bdcc977be201bca9a91058864d9de0684e64514bee0" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.3.26", + "h2 0.4.10", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper 1.6.0", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.7", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.28", + "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tower 0.5.2", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.61.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2db31f727935fc63c6eeae8b37b438847639ec330a9161ece694efba257e0c54" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d1881b1ea6d313f9890710d65c158bdab6fb08c91ea825f74c1c8c357baf4cc" +dependencies = [ + "aws-smithy-runtime-api", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d28a63441360c477465f80c7abac3b9c4d075ca638f982e605b7dc2a2c7156c9" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e107ce0783019dbff59b3a244aa0c114e4a8c9d93498af9162608cd5474e796" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "http-body 1.0.1", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7204f9fd94749a7c53b26da1b961b4ac36bf070ef1e0b94bb09f79d4f6c193" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.3.1", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f535879a207fce0db74b679cfc3e91a3159c8144d717d55f5832aea9eef46e" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab77cdd036b11056d2a30a7af7b775789fb024bf216acc13884c6c97752ae56" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2fd329bf0e901ff3f60425691410c69094dc2a1f34b331f37bfc4e9ac1565a1" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "tracing", +] + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if 1.0.1", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.9.1", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.104", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel 2.3.1", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "borsh" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd1d3c0c2f5833f22386f252fe8ed005c7f59fdcddeef025c01b4c3b9fd9ac3" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "sha2", + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + +[[package]] +name = "camino" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "cc" +version = "1.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfb-mode" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330" +dependencies = [ + "cipher", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if 1.0.1", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrome-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c631c2cf4b95746cf065f732219ec0f2eb1497cd4c7fe07cb336ddf0d7c503" +dependencies = [ + "js-sys", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link 0.1.3", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width 0.2.1", + "windows-sys 0.59.0", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core" +version = "0.2.0" +dependencies = [ + "bytes", + "clap", + "eyre", + "governor", + "hardcode", + "hex", + "kaspa-addresses", + "kaspa-consensus-client", + "kaspa-consensus-core", + "kaspa-core", + "kaspa-grpc-client", + "kaspa-hashes", + "kaspa-rpc-core", + "kaspa-txscript", + "kaspa-wallet-core", + "kaspa-wallet-keys", + "kaspa-wallet-pskt", + "kaspa-wrpc-client", + "log", + "openapi", + "reqwest", + "reqwest-middleware", + "reqwest-ratelimit", + "reqwest-retry", + "secp256k1", + "serde", + "serde-value", + "serde_json", + "tokio", + "tracing", + "url", + "workflow-core", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if 1.0.1", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "crypto_box" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16182b4f39a82ec8a6851155cc4c0cda3065bb1db33651726a29e1951de0f009" +dependencies = [ + "aead", + "chacha20", + "crypto_secretbox", + "curve25519-dalek", + "salsa20", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto_secretbox" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" +dependencies = [ + "aead", + "chacha20", + "cipher", + "generic-array", + "poly1305", + "salsa20", + "subtle", + "zeroize", +] + +[[package]] +name = "ctrlc" +version = "3.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" +dependencies = [ + "nix 0.30.1", + "windows-sys 0.59.0", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if 1.0.1", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.104", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if 1.0.1", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core 0.9.11", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "delegate-display" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98a85201f233142ac819bbf6226e36d0b5e129a47bd325084674261c82d4cd66" +dependencies = [ + "macroific", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.104", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.104", +] + +[[package]] +name = "destructure_traitobject" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" + +[[package]] +name = "deunicode" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "duct" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ab5718d1224b63252cd0c6f74f6480f9ffeb117438a2e0f5cf6d9a4798929c" +dependencies = [ + "libc", + "once_cell", + "os_pipe", + "shared_child", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dym-kas-kms" +version = "0.1.0" +dependencies = [ + "aws-config", + "aws-sdk-kms", + "aws-sdk-secretsmanager", + "eyre", + "secp256k1", + "serde_json", + "tokio", + "tracing", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if 1.0.1", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.0", + "pin-project-lite", +] + +[[package]] +name = "evpkdf" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb9671766b4540458f291944466ca7ce2be8f9c81e510b10b5064a8735998d9" +dependencies = [ + "digest", +] + +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fancy_constructor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07b19d0e43eae2bfbafe4931b5e79c73fb1a849ca15cd41a761a7b8587f9a1a2" +dependencies = [ + "macroific", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "faster-hex" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" +dependencies = [ + "serde", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if 1.0.1", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "fixedstr" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7fe1d85100f2405b683f73d7f0c2bdb09bcea9e817c6a0e07755a83c35be8a" +dependencies = [ + "serde", +] + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if 1.0.1", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if 1.0.1", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "governor" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbe789d04bf14543f03c4b60cd494148aa79438c8440ae7d81a7778147745c3" +dependencies = [ + "cfg-if 1.0.1", + "dashmap", + "futures-sink", + "futures-timer", + "futures-util", + "getrandom 0.3.3", + "hashbrown 0.15.4", + "nonzero_ext", + "parking_lot 0.12.4", + "portable-atomic", + "quanta", + "rand 0.9.1", + "smallvec", + "spinning_top", + "web-time", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.9.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.3.1", + "indexmap 2.9.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hardcode" +version = "0.2.0" +dependencies = [ + "kaspa-addresses", + "kaspa-consensus-core", + "openapi", + "reqwest", + "reqwest-middleware", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hexplay" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da1f4f846e8dcc1b5225caf702924816cabd855e4b46115c334ba09d5254a21" +dependencies = [ + "atty", + "termcolor", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.3.1", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.10", + "http 1.3.1", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "log", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.3.1", + "hyper 1.6.0", + "hyper-util", + "rustls 0.23.28", + "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.2", + "tower-service", + "webpki-roots 1.0.1", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper 1.6.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.6.0", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.61.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexed_db_futures" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43315957678a70eb21fb0d2384fe86dde0d6c859a01e24ce127eb65a0143d28c" +dependencies = [ + "accessory", + "cfg-if 1.0.1", + "delegate-display", + "fancy_constructor", + "js-sys", + "uuid 1.11.0", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown 0.15.4", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if 1.0.1", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "intertrait" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00fc6ef7d878dfcf59d9e556ef1b368d7f55b9da5813ed481a3573eef485a01" +dependencies = [ + "intertrait-macros", + "linkme", + "once_cell", +] + +[[package]] +name = "intertrait-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d56984da2d4c9d6d7de8463892e65a9354f4238f641c246fe99176150e97bb8" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "uuid 0.8.2", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kaspa-addresses" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "borsh", + "js-sys", + "serde", + "smallvec", + "thiserror 1.0.69", + "wasm-bindgen", + "workflow-log", + "workflow-wasm", +] + +[[package]] +name = "kaspa-bip32" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "borsh", + "bs58", + "getrandom 0.2.16", + "hmac", + "js-sys", + "kaspa-consensus-core", + "kaspa-utils", + "once_cell", + "pbkdf2", + "rand 0.8.5", + "rand_core 0.6.4", + "ripemd", + "secp256k1", + "serde", + "sha2", + "subtle", + "thiserror 1.0.69", + "wasm-bindgen", + "workflow-wasm", + "zeroize", +] + +[[package]] +name = "kaspa-consensus-client" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "ahash", + "cfg-if 1.0.1", + "faster-hex", + "hex", + "itertools 0.13.0", + "js-sys", + "kaspa-addresses", + "kaspa-consensus-core", + "kaspa-hashes", + "kaspa-math", + "kaspa-txscript", + "kaspa-utils", + "kaspa-wasm-core", + "rand 0.8.5", + "secp256k1", + "serde", + "serde-wasm-bindgen", + "serde_json", + "thiserror 1.0.69", + "wasm-bindgen", + "workflow-log", + "workflow-wasm", +] + +[[package]] +name = "kaspa-consensus-core" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "arc-swap", + "async-trait", + "borsh", + "cfg-if 1.0.1", + "faster-hex", + "futures-util", + "getrandom 0.2.16", + "itertools 0.13.0", + "js-sys", + "kaspa-addresses", + "kaspa-core", + "kaspa-hashes", + "kaspa-math", + "kaspa-merkle", + "kaspa-muhash", + "kaspa-txscript-errors", + "kaspa-utils", + "rand 0.8.5", + "secp256k1", + "serde", + "serde-wasm-bindgen", + "serde_json", + "smallvec", + "thiserror 1.0.69", + "wasm-bindgen", + "workflow-core", + "workflow-log", + "workflow-serializer", + "workflow-wasm", +] + +[[package]] +name = "kaspa-consensus-notify" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "async-channel 2.3.1", + "cfg-if 1.0.1", + "derive_more", + "futures", + "kaspa-consensus-core", + "kaspa-core", + "kaspa-hashes", + "kaspa-notify", + "kaspa-utils", + "log", + "paste", + "thiserror 1.0.69", + "triggered", +] + +[[package]] +name = "kaspa-consensus-wasm" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "cfg-if 1.0.1", + "faster-hex", + "js-sys", + "kaspa-addresses", + "kaspa-consensus-client", + "kaspa-consensus-core", + "kaspa-hashes", + "kaspa-txscript", + "kaspa-utils", + "rand 0.8.5", + "secp256k1", + "serde", + "serde-wasm-bindgen", + "serde_json", + "thiserror 1.0.69", + "wasm-bindgen", + "workflow-log", + "workflow-wasm", +] + +[[package]] +name = "kaspa-core" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "anyhow", + "cfg-if 1.0.1", + "ctrlc", + "futures-util", + "intertrait", + "log", + "log4rs", + "num_cpus", + "thiserror 1.0.69", + "tokio", + "triggered", + "wasm-bindgen", + "workflow-log", +] + +[[package]] +name = "kaspa-grpc-client" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "async-channel 2.3.1", + "async-stream", + "async-trait", + "faster-hex", + "futures", + "futures-util", + "h2 0.4.10", + "itertools 0.13.0", + "kaspa-core", + "kaspa-grpc-core", + "kaspa-notify", + "kaspa-rpc-core", + "kaspa-utils", + "kaspa-utils-tower", + "log", + "parking_lot 0.12.4", + "paste", + "prost", + "rand 0.8.5", + "regex", + "rustls 0.23.28", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tonic", + "triggered", +] + +[[package]] +name = "kaspa-grpc-core" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "async-channel 2.3.1", + "async-stream", + "async-trait", + "faster-hex", + "futures", + "h2 0.4.10", + "kaspa-addresses", + "kaspa-consensus-core", + "kaspa-core", + "kaspa-notify", + "kaspa-rpc-core", + "kaspa-utils", + "log", + "paste", + "prost", + "rand 0.8.5", + "regex", + "thiserror 1.0.69", + "tokio", + "tokio-stream", + "tonic", + "tonic-build", + "triggered", + "workflow-core", +] + +[[package]] +name = "kaspa-hashes" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "blake2b_simd", + "borsh", + "cc", + "faster-hex", + "js-sys", + "kaspa-utils", + "keccak", + "once_cell", + "serde", + "sha2", + "wasm-bindgen", + "workflow-wasm", +] + +[[package]] +name = "kaspa-index-core" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "async-channel 2.3.1", + "async-trait", + "derive_more", + "futures", + "kaspa-consensus-core", + "kaspa-hashes", + "kaspa-notify", + "kaspa-utils", + "log", + "paste", + "serde", + "thiserror 1.0.69", + "triggered", +] + +[[package]] +name = "kaspa-math" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "borsh", + "faster-hex", + "js-sys", + "kaspa-utils", + "malachite-base", + "malachite-nz", + "serde", + "serde-wasm-bindgen", + "thiserror 1.0.69", + "wasm-bindgen", + "workflow-core", + "workflow-log", + "workflow-wasm", +] + +[[package]] +name = "kaspa-merkle" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "kaspa-hashes", +] + +[[package]] +name = "kaspa-metrics-core" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "async-trait", + "borsh", + "futures", + "kaspa-core", + "kaspa-rpc-core", + "separator", + "serde", + "thiserror 1.0.69", + "workflow-core", + "workflow-log", +] + +[[package]] +name = "kaspa-mining-errors" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "kaspa-consensus-core", + "thiserror 1.0.69", +] + +[[package]] +name = "kaspa-muhash" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "kaspa-hashes", + "kaspa-math", + "rand_chacha 0.3.1", + "serde", +] + +[[package]] +name = "kaspa-notify" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "async-channel 2.3.1", + "async-trait", + "borsh", + "derive_more", + "futures", + "futures-util", + "indexmap 2.9.0", + "itertools 0.13.0", + "kaspa-addresses", + "kaspa-consensus-core", + "kaspa-core", + "kaspa-hashes", + "kaspa-txscript", + "kaspa-txscript-errors", + "kaspa-utils", + "log", + "parking_lot 0.12.4", + "paste", + "rand 0.8.5", + "serde", + "thiserror 1.0.69", + "triggered", + "workflow-core", + "workflow-log", + "workflow-serializer", +] + +[[package]] +name = "kaspa-rpc-core" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "async-channel 2.3.1", + "async-trait", + "borsh", + "cfg-if 1.0.1", + "derive_more", + "downcast", + "faster-hex", + "hex", + "js-sys", + "kaspa-addresses", + "kaspa-consensus-client", + "kaspa-consensus-core", + "kaspa-consensus-notify", + "kaspa-consensus-wasm", + "kaspa-core", + "kaspa-hashes", + "kaspa-index-core", + "kaspa-math", + "kaspa-mining-errors", + "kaspa-notify", + "kaspa-rpc-macros", + "kaspa-txscript", + "kaspa-utils", + "log", + "paste", + "rand 0.8.5", + "serde", + "serde-wasm-bindgen", + "smallvec", + "thiserror 1.0.69", + "uuid 1.11.0", + "wasm-bindgen", + "workflow-core", + "workflow-serializer", + "workflow-wasm", +] + +[[package]] +name = "kaspa-rpc-macros" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "convert_case 0.6.0", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", +] + +[[package]] +name = "kaspa-txscript" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "blake2b_simd", + "borsh", + "cfg-if 1.0.1", + "hexplay", + "indexmap 2.9.0", + "itertools 0.13.0", + "kaspa-addresses", + "kaspa-consensus-core", + "kaspa-hashes", + "kaspa-txscript-errors", + "kaspa-utils", + "kaspa-wasm-core", + "log", + "parking_lot 0.12.4", + "rand 0.8.5", + "secp256k1", + "serde", + "serde-wasm-bindgen", + "serde_json", + "sha2", + "smallvec", + "thiserror 1.0.69", + "wasm-bindgen", + "workflow-wasm", +] + +[[package]] +name = "kaspa-txscript-errors" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "secp256k1", + "thiserror 1.0.69", +] + +[[package]] +name = "kaspa-utils" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "arc-swap", + "async-channel 2.3.1", + "borsh", + "cfg-if 1.0.1", + "duct", + "event-listener 2.5.3", + "faster-hex", + "ipnet", + "itertools 0.13.0", + "log", + "mac_address", + "num_cpus", + "once_cell", + "parking_lot 0.12.4", + "rlimit", + "serde", + "sha2", + "smallvec", + "sysinfo", + "thiserror 1.0.69", + "triggered", + "uuid 1.11.0", + "wasm-bindgen", +] + +[[package]] +name = "kaspa-utils-tower" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "bytes", + "cfg-if 1.0.1", + "futures", + "http-body 1.0.1", + "http-body-util", + "log", + "pin-project-lite", + "tokio", + "tower 0.5.2", + "tower-http 0.5.2", +] + +[[package]] +name = "kaspa-wallet-core" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "aes", + "ahash", + "argon2", + "async-channel 2.3.1", + "async-std", + "async-trait", + "base64 0.22.1", + "borsh", + "cfb-mode", + "cfg-if 1.0.1", + "chacha20poly1305", + "convert_case 0.6.0", + "crypto_box", + "dashmap", + "derivative", + "downcast", + "evpkdf", + "faster-hex", + "fixedstr", + "futures", + "heapless", + "hmac", + "home", + "indexed_db_futures", + "itertools 0.13.0", + "js-sys", + "kaspa-addresses", + "kaspa-bip32", + "kaspa-consensus-client", + "kaspa-consensus-core", + "kaspa-consensus-wasm", + "kaspa-core", + "kaspa-hashes", + "kaspa-metrics-core", + "kaspa-notify", + "kaspa-rpc-core", + "kaspa-txscript", + "kaspa-txscript-errors", + "kaspa-utils", + "kaspa-wallet-keys", + "kaspa-wallet-macros", + "kaspa-wallet-pskt", + "kaspa-wasm-core", + "kaspa-wrpc-client", + "kaspa-wrpc-wasm", + "md-5", + "pad", + "pbkdf2", + "rand 0.8.5", + "regex", + "ripemd", + "secp256k1", + "separator", + "serde", + "serde-wasm-bindgen", + "serde_json", + "sha1", + "sha2", + "slugify-rs", + "sorted-insert", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "workflow-core", + "workflow-log", + "workflow-node", + "workflow-rpc", + "workflow-store", + "workflow-wasm", + "xxhash-rust", + "zeroize", +] + +[[package]] +name = "kaspa-wallet-keys" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "async-trait", + "borsh", + "downcast", + "faster-hex", + "hmac", + "js-sys", + "kaspa-addresses", + "kaspa-bip32", + "kaspa-consensus-core", + "kaspa-txscript", + "kaspa-txscript-errors", + "kaspa-utils", + "kaspa-wasm-core", + "rand 0.8.5", + "ripemd", + "secp256k1", + "serde", + "serde-wasm-bindgen", + "serde_json", + "sha2", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", + "workflow-core", + "workflow-wasm", + "zeroize", +] + +[[package]] +name = "kaspa-wallet-macros" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "convert_case 0.5.0", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "xxhash-rust", +] + +[[package]] +name = "kaspa-wallet-pskt" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "bincode", + "derive_builder", + "futures", + "hex", + "js-sys", + "kaspa-addresses", + "kaspa-bip32", + "kaspa-consensus-client", + "kaspa-consensus-core", + "kaspa-txscript", + "kaspa-txscript-errors", + "kaspa-utils", + "secp256k1", + "separator", + "serde", + "serde-value", + "serde-wasm-bindgen", + "serde_json", + "serde_repr", + "thiserror 1.0.69", + "wasm-bindgen", + "workflow-wasm", +] + +[[package]] +name = "kaspa-wasm-core" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "faster-hex", + "hexplay", + "js-sys", + "wasm-bindgen", + "workflow-wasm", +] + +[[package]] +name = "kaspa-wrpc-client" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "async-std", + "async-trait", + "borsh", + "cfg-if 1.0.1", + "futures", + "js-sys", + "kaspa-addresses", + "kaspa-consensus-core", + "kaspa-consensus-wasm", + "kaspa-notify", + "kaspa-rpc-core", + "kaspa-rpc-macros", + "paste", + "rand 0.8.5", + "regex", + "rustls 0.23.28", + "serde", + "serde-wasm-bindgen", + "serde_json", + "thiserror 1.0.69", + "toml", + "wasm-bindgen", + "wasm-bindgen-futures", + "workflow-core", + "workflow-dom", + "workflow-http", + "workflow-log", + "workflow-rpc", + "workflow-serializer", + "workflow-wasm", +] + +[[package]] +name = "kaspa-wrpc-wasm" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "ahash", + "async-std", + "cfg-if 1.0.1", + "futures", + "js-sys", + "kaspa-addresses", + "kaspa-consensus-client", + "kaspa-consensus-core", + "kaspa-consensus-wasm", + "kaspa-notify", + "kaspa-rpc-core", + "kaspa-rpc-macros", + "kaspa-wasm-core", + "kaspa-wrpc-client", + "ring", + "serde", + "serde-wasm-bindgen", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", + "workflow-core", + "workflow-log", + "workflow-rpc", + "workflow-wasm", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if 1.0.1", + "windows-link 0.2.1", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libredox" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" +dependencies = [ + "bitflags 2.9.1", + "libc", + "redox_syscall 0.5.13", +] + +[[package]] +name = "linkme" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd4ad156b9934dc21cad96fd17278a7cb6f30a5657a9d976cd7b71d6d49c02c" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fd9dc7072de7168cbdaba9125e8f742cd3a965aa12bde994b4611a174488d8" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +dependencies = [ + "serde", + "value-bag", +] + +[[package]] +name = "log-mdc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" + +[[package]] +name = "log4rs" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0816135ae15bd0391cf284eab37e6e3ee0a6ee63d2ceeb659862bd8d0a984ca6" +dependencies = [ + "anyhow", + "arc-swap", + "chrono", + "derivative", + "flate2", + "fnv", + "humantime", + "libc", + "log", + "log-mdc", + "once_cell", + "parking_lot 0.12.4", + "rand 0.8.5", + "serde", + "serde-value", + "serde_json", + "serde_yaml", + "thiserror 1.0.69", + "thread-id", + "typemap-ors", + "winapi", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "mac_address" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +dependencies = [ + "nix 0.29.0", + "winapi", +] + +[[package]] +name = "macroific" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05c00ac596022625d01047c421a0d97d7f09a18e429187b341c201cb631b9dd" +dependencies = [ + "macroific_attr_parse", + "macroific_core", + "macroific_macro", +] + +[[package]] +name = "macroific_attr_parse" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd94d5da95b30ae6e10621ad02340909346ad91661f3f8c0f2b62345e46a2f67" +dependencies = [ + "cfg-if 1.0.1", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "macroific_core" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13198c120864097a565ccb3ff947672d969932b7975ebd4085732c9f09435e55" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "macroific_macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c9853143cbed7f1e41dc39fee95f9b361bec65c8dc2a01bf609be01b61f5ae" +dependencies = [ + "macroific_attr_parse", + "macroific_core", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "malachite-base" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea0ed76adf7defc1a92240b5c36d5368cfe9251640dcce5bd2d0b7c1fd87aeb" +dependencies = [ + "hashbrown 0.14.5", + "itertools 0.11.0", + "libm", + "ryu", +] + +[[package]] +name = "malachite-nz" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34a79feebb2bc9aa7762047c8e5495269a367da6b5a90a99882a0aeeac1841f7" +dependencies = [ + "itertools 0.11.0", + "libm", + "malachite-base", +] + +[[package]] +name = "manual_future" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943968aefb9b0fdf36cccc03f6cd9d6698b23574ab49eccc185ae6c5cb6ad43e" +dependencies = [ + "futures", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if 1.0.1", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand 0.8.5", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.9.1", + "cfg-if 1.0.1", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.1", + "cfg-if 1.0.1", + "cfg_aliases", + "libc", +] + +[[package]] +name = "node-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d75eee6e8b159228449706773f9f2af60720250308124e9c3d38bd8070844a" +dependencies = [ + "cfg-if 0.1.10", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi 0.5.2", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openapi" +version = "0.9.9" +dependencies = [ + "reqwest", + "reqwest-middleware", + "serde", + "serde_json", + "serde_repr", + "url", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "os_pipe" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "pad" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.11", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if 1.0.1", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if 1.0.1", + "libc", + "redox_syscall 0.5.13", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "parse-variants" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbdc211954226807e86041c663407640ff205e514bec8b25731653fd1d3bea82" +dependencies = [ + "parse-variants-derive", +] + +[[package]] +name = "parse-variants-derive" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27b3870ede76ad9fc5cf19aa770877ad429a8134168a55183bc588acc648ab2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap 2.9.0", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "polling" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" +dependencies = [ + "cfg-if 1.0.1", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" +dependencies = [ + "proc-macro2", + "syn 2.0.104", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck", + "itertools 0.14.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.104", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi 0.11.1+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + +[[package]] +name = "quinn" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.28", + "socket2", + "thiserror 2.0.12", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +dependencies = [ + "bytes", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.1", + "ring", + "rustc-hash", + "rustls 0.23.28", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "raw-cpuid" +version = "11.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.10", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-rustls 0.27.7", + "hyper-util", + "js-sys", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.28", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls 0.26.2", + "tower 0.5.2", + "tower-http 0.6.6", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 1.0.1", +] + +[[package]] +name = "reqwest-middleware" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57f17d28a6e6acfe1733fe24bcd30774d13bffa4b8a22535b4c8c98423088d4e" +dependencies = [ + "anyhow", + "async-trait", + "http 1.3.1", + "reqwest", + "serde", + "thiserror 1.0.69", + "tower-service", +] + +[[package]] +name = "reqwest-ratelimit" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8fff0d8036f23dcad6c27605ca3baa8ae3867438d0a8b34072f40f6c8bf628" +dependencies = [ + "async-trait", + "http 1.3.1", + "reqwest", + "reqwest-middleware", +] + +[[package]] +name = "reqwest-retry" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c73e4195a6bfbcb174b790d9b3407ab90646976c55de58a6515da25d851178" +dependencies = [ + "anyhow", + "async-trait", + "futures", + "getrandom 0.2.16", + "http 1.3.1", + "hyper 1.6.0", + "parking_lot 0.11.2", + "reqwest", + "reqwest-middleware", + "retry-policies", + "thiserror 1.0.69", + "tokio", + "tracing", + "wasm-timer", +] + +[[package]] +name = "retry-policies" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5875471e6cab2871bc150ecb8c727db5113c9338cc3354dc5ee3425b6aa40a1c" +dependencies = [ + "rand 0.8.5", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if 1.0.1", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest", +] + +[[package]] +name = "rlimit" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7043b63bd0cd1aaa628e476b80e6d4023a3b50eb32789f2728908107bd0c793a" +dependencies = [ + "libc", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.3", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework 2.11.1", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.2.0", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "rand 0.8.5", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "separator" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.9.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if 1.0.1", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if 1.0.1", + "cpufeatures", + "digest", +] + +[[package]] +name = "shared_child" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2778001df1384cf20b6dc5a5a90f48da35539885edaaefd887f8d744e939c0b" +dependencies = [ + "libc", + "sigchld", + "windows-sys 0.60.2", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "sigchld" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1219ef50fc0fdb04fcc243e6aa27f855553434ffafe4fa26554efb78b5b4bf89" +dependencies = [ + "libc", + "os_pipe", + "signal-hook", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "slugify-rs" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c88cdb6ea794da1dde6f267c3a363b2373ce24386b136828d66402a97ebdbff3" +dependencies = [ + "deunicode", + "nanoid", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "sorted-insert" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eec75fe132d95908f1c030f93630bc20b76f3ebaeca789a6180553b770ddcd39" + +[[package]] +name = "spinning_top" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "sysinfo" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "thread-id" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe8f25bbdd100db7e1d34acf7fd2dc59c4bf8f7483f505eaa7d4f12f76cc0ea" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot 0.12.4", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls 0.23.28", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +dependencies = [ + "futures-util", + "log", + "rustls 0.23.28", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.2", + "tungstenite", + "webpki-roots 0.26.11", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.9.0", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "flate2", + "h2 0.4.10", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "rustls-pemfile 2.2.0", + "socket2", + "tokio", + "tokio-rustls 0.26.2", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", + "webpki-roots 0.26.11", +] + +[[package]] +name = "tonic-build" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "iri-string", + "pin-project-lite", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "triggered" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "593eddbc8a11f3e099e942c8c065fe376b9d1776741430888f2796682e08ab43" + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.3.1", + "httparse", + "log", + "rand 0.8.5", + "rustls 0.23.28", + "rustls-pki-types", + "sha1", + "thiserror 1.0.69", + "utf-8", +] + +[[package]] +name = "typemap-ors" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68c24b707f02dd18f1e4ccceb9d49f2058c2fb86384ef9972592904d7a28867" +dependencies = [ + "unsafe-any-ors", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "unsafe-any-ors" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a303d30665362d9680d7d91d78b23f5f899504d4f08b3c4cf08d055d87c0ad" +dependencies = [ + "destructure_traitobject", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom 0.2.16", + "rand 0.8.5", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "value-bag" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" + +[[package]] +name = "vergen" +version = "8.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2990d9ea5967266ea0ccf413a4aa5c42a93dbcfda9cb49a97de6931726b12566" +dependencies = [ + "anyhow", + "cargo_metadata", + "cfg-if 1.0.1", + "regex", + "rustc_version", + "rustversion", + "time", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if 1.0.1", + "once_cell", + "rustversion", + "serde", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.104", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if 1.0.1", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.1", +] + +[[package]] +name = "webpki-roots" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "workflow-chrome" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0c0dfbc178cb7c3a47bd2aabf6902364d2db7e4c4f5b0dad57b75d78c6fe1f" +dependencies = [ + "cfg-if 1.0.1", + "chrome-sys", + "js-sys", + "thiserror 1.0.69", + "wasm-bindgen", + "workflow-core", + "workflow-log", +] + +[[package]] +name = "workflow-core" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d67bbe225ea90aa6979167f28935275506696ac867661e218893d3a42e1666" +dependencies = [ + "async-channel 2.3.1", + "async-std", + "borsh", + "bs58", + "cfg-if 1.0.1", + "chrono", + "dirs", + "faster-hex", + "futures", + "getrandom 0.2.16", + "instant", + "js-sys", + "rand 0.8.5", + "rlimit", + "serde", + "serde-wasm-bindgen", + "thiserror 1.0.69", + "tokio", + "triggered", + "vergen", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "workflow-core-macros", + "workflow-log", +] + +[[package]] +name = "workflow-core-macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65659ed208b0066a9344142218abda353eb6c6cc1fc3ae4808b750c560de004b" +dependencies = [ + "convert_case 0.6.0", + "parse-variants", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "sha2", + "syn 1.0.109", + "workflow-macro-tools", +] + +[[package]] +name = "workflow-dom" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503bba85907753c960ddfd73b4e79bffadf521cc3c992ef2b2a29fd3af09a957" +dependencies = [ + "futures", + "js-sys", + "regex", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "workflow-core", + "workflow-log", + "workflow-wasm", +] + +[[package]] +name = "workflow-http" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c654c7395e448001c658309377a44a8c3d7c28c7acb30e9babbaeacb575bb0" +dependencies = [ + "cfg-if 1.0.1", + "reqwest", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "wasm-bindgen", + "workflow-core", +] + +[[package]] +name = "workflow-log" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64bf52c539193f219b7a79eb0c7c5f6c222ccf9b95c5e0bd59e924feb762256f" +dependencies = [ + "cfg-if 1.0.1", + "console", + "downcast", + "hexplay", + "lazy_static", + "log", + "termcolor", + "wasm-bindgen", +] + +[[package]] +name = "workflow-macro-tools" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "085d3045d5ca780fb589d230030e34fec962b3638d6c69806a72a7d7d1affea4" +dependencies = [ + "convert_case 0.6.0", + "parse-variants", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "workflow-node" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85c9add43b5da3bed3d0d6d92eb3a2c5986c0ae65c7c3f5189876c19648154" +dependencies = [ + "borsh", + "futures", + "js-sys", + "lazy_static", + "node-sys", + "serde", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", + "workflow-core", + "workflow-log", + "workflow-task", + "workflow-wasm", +] + +[[package]] +name = "workflow-panic-hook" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c76ca8b459e4f0c949f06ce2d45565a6769748e83ca7064d36671bbd67b4da" +dependencies = [ + "cfg-if 1.0.1", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "workflow-rpc" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4235eb167f0bef3bcbdf0c578823a0105ab5303115e3b2afb4d526e2498b08" +dependencies = [ + "ahash", + "async-std", + "async-trait", + "borsh", + "downcast-rs", + "futures", + "futures-util", + "getrandom 0.2.16", + "manual_future", + "rand 0.8.5", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tungstenite", + "wasm-bindgen", + "workflow-core", + "workflow-log", + "workflow-rpc-macros", + "workflow-task", + "workflow-wasm", + "workflow-websocket", +] + +[[package]] +name = "workflow-rpc-macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f048ca6b1c551f468c3c0c829f958e83dd15b20138b5466bb617ffde500e8cf4" +dependencies = [ + "parse-variants", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "workflow-serializer" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64679db6856852a472caff4ce869e3ecebe291fbccc9406e9643eb5951a0904a" +dependencies = [ + "ahash", + "borsh", + "serde", +] + +[[package]] +name = "workflow-store" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d161c4b844eee479f81306f2526266f9608a663e0a679d9fc0572ee15c144e06" +dependencies = [ + "async-std", + "base64 0.22.1", + "cfg-if 1.0.1", + "chrome-sys", + "faster-hex", + "filetime", + "home", + "js-sys", + "lazy_static", + "serde", + "serde_json", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "workflow-chrome", + "workflow-core", + "workflow-log", + "workflow-node", + "workflow-wasm", +] + +[[package]] +name = "workflow-task" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1a90743bb4d3f68606cb4e9a78551a53399ebc35ddba981cbb56bf2b31940a" +dependencies = [ + "futures", + "thiserror 1.0.69", + "workflow-core", + "workflow-task-macros", +] + +[[package]] +name = "workflow-task-macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecf6be36b52dc1e16d11b55f717d9ec2fec5804aff7f392af591933ba4af45e" +dependencies = [ + "convert_case 0.6.0", + "parse-variants", + "proc-macro-error", + "proc-macro2", + "quote", + "sha2", + "syn 1.0.109", + "workflow-macro-tools", +] + +[[package]] +name = "workflow-wasm" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e5fbf266e0fffb5c24d6103735eb2b94bb31f93b664b91eaaf63b4f959804" +dependencies = [ + "cfg-if 1.0.1", + "faster-hex", + "futures", + "js-sys", + "serde", + "serde-wasm-bindgen", + "thiserror 1.0.69", + "wasm-bindgen", + "wasm-bindgen-futures", + "workflow-core", + "workflow-log", + "workflow-panic-hook", + "workflow-wasm-macros", +] + +[[package]] +name = "workflow-wasm-macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40237c65ecff78dbfedb13985e33f802a31f6f7de72dff12a6674fcdcf601822" +dependencies = [ + "js-sys", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen", +] + +[[package]] +name = "workflow-websocket" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "515483a047477c91b5142e1090cce0afc21a0139d9c0c06ea42f0d3dbf3a6fcd" +dependencies = [ + "ahash", + "async-channel 2.3.1", + "async-std", + "async-trait", + "cfg-if 1.0.1", + "downcast-rs", + "futures", + "futures-util", + "js-sys", + "thiserror 1.0.69", + "tokio", + "tokio-tungstenite", + "triggered", + "tungstenite", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "workflow-core", + "workflow-log", + "workflow-task", + "workflow-wasm", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] diff --git a/dymension/libs/kaspa/Cargo.toml b/dymension/libs/kaspa/Cargo.toml new file mode 100644 index 00000000000..7f3955cec5f --- /dev/null +++ b/dymension/libs/kaspa/Cargo.toml @@ -0,0 +1,55 @@ +[workspace] +resolver = "2" # Enables new feature resolver, good practice for new workspaces +# TODO: docs, edition, homepage, license + +members = [ + "lib/core", + "lib/api", + "lib/hardcode", + "lib/kms", +] + +[workspace.dependencies] +# Fix for cosmwasm-crypto incompatibility with ed25519-zebra v4.1.0 +# See: https://github.com/CosmWasm/cosmwasm/issues/2525 +ed25519-zebra = "=4.0.3" + +# Match the protobuf version used in rust/main workspace to avoid conflicts +# rust/main uses v2.28.0, while cargo would pick v3.x without this constraint +protobuf = "=2.28.0" + +kaspa-consensus-core = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-txscript = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-wallet-pskt = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-wrpc-client = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-addresses = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-wallet-core = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-wallet-keys = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-rpc-core = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-core = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-grpc-client = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-hashes = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-bip32 = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-consensus-client = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } + +rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs", "std"] } + + +api-rs = { path = "../api", package = "openapi" } +secp256k1 = { version = "0.29.0", features = ["global-context", "rand-std", "serde"] } +serde_json = "1.0.107" +tokio = { version = "1.36", features = ["full"] } +clap = "4.5.39" +log = "0.4.20" +workflow-core = { version = "0.18.0" } +tracing = { version = "0.1" } +tracing-error = "0.2" +tracing-futures = "0.2" +tracing-subscriber = { version = "0.3", default-features = false } +reqwest = { version = "0.12", default-features = false, features = ["json", "multipart"] } # had to use 0.12 to be compatible with open ai generation +bytes = "1" +reqwest-middleware = "0.4" +hex = "0.4.3" +axum = "0.8.4" +url = "2.3" +eyre = "=0.6.8" diff --git a/dymension/libs/kaspa/README.md b/dymension/libs/kaspa/README.md new file mode 100644 index 00000000000..012eb19b707 --- /dev/null +++ b/dymension/libs/kaspa/README.md @@ -0,0 +1,177 @@ +# Kaspa + +## Contributing  + +See [Contributing](./CONTRIBUTING.md) + +## Kaspa Cheatsheet (v1.0.0) + +### Resources + +API: https://api.kaspa.org/docs + +### Urls + +typical port alignment is + +- Mainnet RPC 16110 +- Mainnet P2P Listen 16111 +- TestNet 10 RPC 16210 +- TestNet 10 WRPC 17210 +- TestNet 10 P2P Listen 16211 +- TestNet 11 RPC 16310 +- TestNet 11 P2P Listen 16311 + +### Wallet + +https://kaspa-ng.org/ You can enable developer mode and choose between main and testnet + +### Cmds + +```bash +# node +cargo run --release --bin kaspad -- --help +cargo run --release --bin kaspad -- C + +# Use CLI wallet (local web wasn't working) +# https://github.com/kaspanet/rusty-kaspa/blob/eb71df4d284593fccd1342094c37edc8c000da85/wallet/README.md#L23 +cd wallet/native +cargo run +help + +# Useful +connect # connect to rpc server +monitor # watch balance +server # set rpc addr +``` + +Exhaustive config: https://github.com/kaspanet/rusty-kaspa/blob/eb71df4d284593fccd1342094c37edc8c000da85/kaspad/src/args.rs#L27-L94 + +Log levels: https://github.com/kaspanet/rusty-kaspa/blob/eb71df4d284593fccd1342094c37edc8c000da85/kaspad/src/args.rs#L27-L94 + +### Client + +⚠️ ⚠️ IMPORTANT ⚠️ ⚠️ + +In Kaspa, clients using the high level rust lib wallet API should connect to a node via WRPC. That node must also be running a GRPC server, and it must have a UTXO index. It will not allow sending TX's if the node is unsynced or syncing. + +### Units, currency and conversions + +https://github.com/kaspanet/rusty-kaspa/blob/eb71df4d284593fccd1342094c37edc8c000da85/consensus/core/src/constants.rs#L12-L24 + +### Tx Construction + +**_Scripts_** + +Let's first understand TX semantics. + +This article (https://bitcoin.stackexchange.com/a/75169, https://developer.bitcoin.org/devguide/transactions.html#p2pkh-script-validation) explains. Each output has a script_public_key, the input that spends it has a signature_script. Take an example + +``` +script_public_key = OP_DUP OP_HASH160 OP_EQUAL OP_CHECKSIG +signature_script = +// They are combined + OP_DUP OP_HASH160 OP_EQUAL OP_CHECKSIG +``` + +This is run through a stack machine and the transaction is allowed if it has `true` on top at the end. + +To understand we should know some popular op codes: + +``` +OP_CHECKSIG = it pops a pubkey and sig and it checks that a) the pubkey produced the sig, and b) the sig is corresponds to the containing TX +OP_HASH160 = it shrinks data using a hash. It's commonly used in the above pattern +``` + +So this script is run over the stack machine and essentially it does + +1. ensure the signing pub key of the spending tx is the one referenced in the original utxo +2. ensure the signing pub key actually signed the spending tx + +Therefore ensuring that the person who spends the utxo is the person intended by the utxo creator. + +**_Data structures_** + +(Note: In the snippets below, our remarks are prefixed 'REMARK') + +```rust +pub struct TransactionOutpoint { + #[serde(with = "serde_bytes_fixed_ref")] + pub transaction_id: TransactionId, // REMARK: a hash + pub index: TransactionIndexType, // REMARK: an index (0, 1 etc) +} + +// REMARK: aka Locking Script https://bitcoin.stackexchange.com/a/75169 +pub struct ScriptPublicKey { + pub version: ScriptPublicKeyVersion, // REMARK: always zero https://github.com/kaspanet/rusty-kaspa/blob/eb71df4d284593fccd1342094c37edc8c000da85/crypto/txscript/src/script_class.rs#L96-L98 + + /* + REMARK: See typical patterns, but not all BTC patterns are supported on Kaspa + Found in Kaspa: + - p2pk https://learnmeabitcoin.com/technical/script/p2pk/ + - p2sh https://learnmeabitcoin.com/technical/script/p2sh/ + */ + pub(super) script: ScriptVec, // Kept private to preserve read-only semantics (REMARK: ??) +} + +pub struct TransactionOutput { + pub value: u64, // REMARK: in sompis. Require > 0. Max is 29 billion KAS + pub script_public_key: ScriptPublicKey, +} + +pub struct TransactionInput { + // REMARK: what is being spent + pub previous_outpoint: TransactionOutpoint, + #[serde(with = "serde_bytes")] + + /* + REMARK: the unlocking script, typically contains the signature of whoever has the relevant pub key specified by the locking script. + Note: does NOT influence TX id. + */ + pub signature_script: Vec, // TODO: Consider using SmallVec + + // REMARK used in conjunction with TX.lock_time for complex TX timing tricks. Out of scope. + pub sequence: u64, + + // TODO: Since this field is used for calculating mass context free, and we already commit + // to the mass in a dedicated field (on the tx level), it follows that this field is no longer + // needed, and can be removed if we ever implement a v2 transaction + pub sig_op_count: u8, // REMARK: not sure if we need to populate it +} + +pub struct Transaction { + // REMARK: semver for format and content + pub version: u16, + + pub inputs: Vec, + pub outputs: Vec, + + // REMARK: dissallows accepting the TX until a wall clock time passes. Out of scope. + pub lock_time: u64, + + /* + REMARK: https://github.com/kaspanet/rusty-kaspa/blob/eb71df4d284593fccd1342094c37edc8c000da85/consensus/core/src/subnets.rs#L130-L137 + Seems to be for special transactions + */ + pub subnetwork_id: SubnetworkId, + + // REMARK: intended for use in conjunction with atypical subnetworks. Usually zero. https://github.com/kaspanet/rusty-kaspa/blob/eb71df4d284593fccd1342094c37edc8c000da85/consensus/src/processes/transaction_validator/tx_validation_in_isolation.rs#L126 + pub gas: u64, + #[serde(with = "serde_bytes")] + + + // REMARK: you can put arbitrary data in here. There is a size limit + pub payload: Vec, + + /// Holds a commitment to the storage mass (KIP-0009) + /// TODO: rename field and related methods to storage_mass + #[serde(default)] + mass: TransactionMass, // REMARK: aka gas/size (influences cost). Does NOT impact TX id. + + // A field that is used to cache the transaction ID. + // Always use the corresponding self.id() instead of accessing this field directly + #[serde(with = "serde_bytes_fixed_ref")] + id: TransactionId, // REMARK: a hash over various things +} + +``` diff --git a/dymension/libs/kaspa/lib/api/.gitignore b/dymension/libs/kaspa/lib/api/.gitignore new file mode 100644 index 00000000000..6aa106405a4 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/.gitignore @@ -0,0 +1,3 @@ +/target/ +**/*.rs.bk +Cargo.lock diff --git a/dymension/libs/kaspa/lib/api/.openapi-generator-ignore b/dymension/libs/kaspa/lib/api/.openapi-generator-ignore new file mode 100644 index 00000000000..7484ee590a3 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/dymension/libs/kaspa/lib/api/.openapi-generator/FILES b/dymension/libs/kaspa/lib/api/.openapi-generator/FILES new file mode 100644 index 00000000000..2e02e998ffb --- /dev/null +++ b/dymension/libs/kaspa/lib/api/.openapi-generator/FILES @@ -0,0 +1,161 @@ +.gitignore +.travis.yml +Cargo.toml +README.md +docs/AcceptanceMode.md +docs/AddressName.md +docs/AddressesActiveRequest.md +docs/BalanceRequest.md +docs/BalanceResponse.md +docs/BalancesByAddressEntry.md +docs/BlockModel.md +docs/BlockResponse.md +docs/BlockRewardResponse.md +docs/BlockTxInputModel.md +docs/BlockTxInputPreviousOutpointModel.md +docs/BlockTxModel.md +docs/BlockTxOutputModel.md +docs/BlockTxOutputScriptPublicKeyModel.md +docs/BlockTxOutputVerboseDataModel.md +docs/BlockTxVerboseDataModel.md +docs/BlockdagResponse.md +docs/BlueScoreResponse.md +docs/CoinSupplyResponse.md +docs/DbCheckStatus.md +docs/EndpointsGetBlocksBlockHeader.md +docs/EndpointsGetHashrateBlockHeader.md +docs/ExperimentalKaspaVirtualChainApi.md +docs/ExtraModel.md +docs/FeeEstimateBucket.md +docs/FeeEstimateResponse.md +docs/HalvingResponse.md +docs/HashrateHistoryResponse.md +docs/HashrateResponse.md +docs/HealthResponse.md +docs/HttpValidationError.md +docs/KaspaAddressesApi.md +docs/KaspaBlocksApi.md +docs/KaspaNetworkInfoApi.md +docs/KaspaTransactionsApi.md +docs/KaspadInfoResponse.md +docs/KaspadResponse.md +docs/MarketCapResponse.md +docs/MaxHashrateResponse.md +docs/OutpointModel.md +docs/ParentHashModel.md +docs/PreviousOutpointLookupMode.md +docs/PriceResponse.md +docs/ResponseGetBlockrewardInfoBlockrewardGet.md +docs/ResponseGetHalvingInfoHalvingGet.md +docs/ResponseGetHashrateInfoHashrateGet.md +docs/ResponseGetMarketcapInfoMarketcapGet.md +docs/ResponseGetPriceInfoPriceGet.md +docs/ScriptPublicKeyModel.md +docs/SubmitTransactionRequest.md +docs/SubmitTransactionResponse.md +docs/SubmitTxInput.md +docs/SubmitTxModel.md +docs/SubmitTxOutpoint.md +docs/SubmitTxOutput.md +docs/SubmitTxScriptPublicKey.md +docs/TransactionCount.md +docs/TxAcceptanceRequest.md +docs/TxAcceptanceResponse.md +docs/TxIdResponse.md +docs/TxInput.md +docs/TxMass.md +docs/TxModel.md +docs/TxOutput.md +docs/TxSearch.md +docs/TxSearchAcceptingBlueScores.md +docs/UtxoModel.md +docs/UtxoRequest.md +docs/UtxoResponse.md +docs/ValidationError.md +docs/ValidationErrorLocInner.md +docs/VcBlockModel.md +docs/VcTxInput.md +docs/VcTxModel.md +docs/VcTxOutput.md +docs/VerboseDataModel.md +git_push.sh +src/apis/configuration.rs +src/apis/experimental_kaspa_virtual_chain_api.rs +src/apis/kaspa_addresses_api.rs +src/apis/kaspa_blocks_api.rs +src/apis/kaspa_network_info_api.rs +src/apis/kaspa_transactions_api.rs +src/apis/mod.rs +src/lib.rs +src/models/acceptance_mode.rs +src/models/address_name.rs +src/models/addresses_active_request.rs +src/models/balance_request.rs +src/models/balance_response.rs +src/models/balances_by_address_entry.rs +src/models/block_model.rs +src/models/block_response.rs +src/models/block_reward_response.rs +src/models/block_tx_input_model.rs +src/models/block_tx_input_previous_outpoint_model.rs +src/models/block_tx_model.rs +src/models/block_tx_output_model.rs +src/models/block_tx_output_script_public_key_model.rs +src/models/block_tx_output_verbose_data_model.rs +src/models/block_tx_verbose_data_model.rs +src/models/blockdag_response.rs +src/models/blue_score_response.rs +src/models/coin_supply_response.rs +src/models/db_check_status.rs +src/models/endpoints__get_blocks__block_header.rs +src/models/endpoints__get_hashrate__block_header.rs +src/models/extra_model.rs +src/models/fee_estimate_bucket.rs +src/models/fee_estimate_response.rs +src/models/halving_response.rs +src/models/hashrate_history_response.rs +src/models/hashrate_response.rs +src/models/health_response.rs +src/models/http_validation_error.rs +src/models/kaspad_info_response.rs +src/models/kaspad_response.rs +src/models/market_cap_response.rs +src/models/max_hashrate_response.rs +src/models/mod.rs +src/models/outpoint_model.rs +src/models/parent_hash_model.rs +src/models/previous_outpoint_lookup_mode.rs +src/models/price_response.rs +src/models/response_get_blockreward_info_blockreward_get.rs +src/models/response_get_halving_info_halving_get.rs +src/models/response_get_hashrate_info_hashrate_get.rs +src/models/response_get_marketcap_info_marketcap_get.rs +src/models/response_get_price_info_price_get.rs +src/models/script_public_key_model.rs +src/models/submit_transaction_request.rs +src/models/submit_transaction_response.rs +src/models/submit_tx_input.rs +src/models/submit_tx_model.rs +src/models/submit_tx_outpoint.rs +src/models/submit_tx_output.rs +src/models/submit_tx_script_public_key.rs +src/models/transaction_count.rs +src/models/tx_acceptance_request.rs +src/models/tx_acceptance_response.rs +src/models/tx_id_response.rs +src/models/tx_input.rs +src/models/tx_mass.rs +src/models/tx_model.rs +src/models/tx_output.rs +src/models/tx_search.rs +src/models/tx_search_accepting_blue_scores.rs +src/models/utxo_model.rs +src/models/utxo_request.rs +src/models/utxo_response.rs +src/models/validation_error.rs +src/models/validation_error_loc_inner.rs +src/models/vc_block_model.rs +src/models/vc_tx_input.rs +src/models/vc_tx_model.rs +src/models/vc_tx_output.rs +src/models/verbose_data_model.rs diff --git a/dymension/libs/kaspa/lib/api/.openapi-generator/VERSION b/dymension/libs/kaspa/lib/api/.openapi-generator/VERSION new file mode 100644 index 00000000000..eb1dc6a51a0 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.13.0 diff --git a/dymension/libs/kaspa/lib/api/.travis.yml b/dymension/libs/kaspa/lib/api/.travis.yml new file mode 100644 index 00000000000..22761ba7ee1 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/.travis.yml @@ -0,0 +1 @@ +language: rust diff --git a/dymension/libs/kaspa/lib/api/Cargo.toml b/dymension/libs/kaspa/lib/api/Cargo.toml new file mode 100644 index 00000000000..46e364c445d --- /dev/null +++ b/dymension/libs/kaspa/lib/api/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "openapi" +version = "0.9.9" +authors = ["OpenAPI Generator team and contributors"] +description = "This server is to communicate with kaspa network via REST-API" +# Override this license by providing a License Object in the OpenAPI. +license = "Unlicense" +edition = "2021" + +[dependencies] +serde = { version = "^1.0", features = ["derive"] } +serde_json = "^1.0" +serde_repr = "^0.1" +url = "^2.5" +reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart"] } +reqwest-middleware = { version = "^0.4", features = ["json", "multipart"] } diff --git a/dymension/libs/kaspa/lib/api/README.md b/dymension/libs/kaspa/lib/api/README.md new file mode 100644 index 00000000000..8982602bb7e --- /dev/null +++ b/dymension/libs/kaspa/lib/api/README.md @@ -0,0 +1,144 @@ +# Rust API client for openapi + +This server is to communicate with kaspa network via REST-API + +## Overview + +This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [openapi-spec](https://openapis.org) from a remote server, you can easily generate an API client. + +- API version: a6a9569 +- Package version: a6a9569 +- Generator version: 7.13.0 +- Build package: `org.openapitools.codegen.languages.RustClientCodegen` + +## Installation + +Put the package under your project folder in a directory named `openapi` and add the following to `Cargo.toml` under `[dependencies]`: + +``` +openapi = { path = "./openapi" } +``` + +## Documentation for API Endpoints + +All URIs are relative to _http://localhost_ + +| Class | Method | HTTP request | Description | +| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------- | +| _ExperimentalKaspaVirtualChainApi_ | [**get_virtual_chain_transactions_virtual_chain_get**](docs/ExperimentalKaspaVirtualChainApi.md#get_virtual_chain_transactions_virtual_chain_get) | **GET** /virtual-chain | Get Virtual Chain Transactions | +| _KaspaAddressesApi_ | [**get_addresses_active_addresses_active_post**](docs/KaspaAddressesApi.md#get_addresses_active_addresses_active_post) | **POST** /addresses/active | Get Addresses Active | +| _KaspaAddressesApi_ | [**get_addresses_names_addresses_names_get**](docs/KaspaAddressesApi.md#get_addresses_names_addresses_names_get) | **GET** /addresses/names | Get Addresses Names | +| _KaspaAddressesApi_ | [**get_balance_from_kaspa_address_addresses_kaspa_address_balance_get**](docs/KaspaAddressesApi.md#get_balance_from_kaspa_address_addresses_kaspa_address_balance_get) | **GET** /addresses/{kaspaAddress}/balance | Get Balance From Kaspa Address | +| _KaspaAddressesApi_ | [**get_balances_from_kaspa_addresses_addresses_balances_post**](docs/KaspaAddressesApi.md#get_balances_from_kaspa_addresses_addresses_balances_post) | **POST** /addresses/balances | Get Balances From Kaspa Addresses | +| _KaspaAddressesApi_ | [**get_full_transactions_for_address_addresses_kaspa_address_full_transactions_get**](docs/KaspaAddressesApi.md#get_full_transactions_for_address_addresses_kaspa_address_full_transactions_get) | **GET** /addresses/{kaspaAddress}/full-transactions | Get Full Transactions For Address | +| _KaspaAddressesApi_ | [**get_full_transactions_for_address_page_addresses_kaspa_address_full_transactions_page_get**](docs/KaspaAddressesApi.md#get_full_transactions_for_address_page_addresses_kaspa_address_full_transactions_page_get) | **GET** /addresses/{kaspaAddress}/full-transactions-page | Get Full Transactions For Address Page | +| _KaspaAddressesApi_ | [**get_name_for_address_addresses_kaspa_address_name_get**](docs/KaspaAddressesApi.md#get_name_for_address_addresses_kaspa_address_name_get) | **GET** /addresses/{kaspaAddress}/name | Get Name For Address | +| _KaspaAddressesApi_ | [**get_transaction_count_for_address_addresses_kaspa_address_transactions_count_get**](docs/KaspaAddressesApi.md#get_transaction_count_for_address_addresses_kaspa_address_transactions_count_get) | **GET** /addresses/{kaspaAddress}/transactions-count | Get Transaction Count For Address | +| _KaspaAddressesApi_ | [**get_utxos_for_address_addresses_kaspa_address_utxos_get**](docs/KaspaAddressesApi.md#get_utxos_for_address_addresses_kaspa_address_utxos_get) | **GET** /addresses/{kaspaAddress}/utxos | Get Utxos For Address | +| _KaspaAddressesApi_ | [**get_utxos_for_addresses_addresses_utxos_post**](docs/KaspaAddressesApi.md#get_utxos_for_addresses_addresses_utxos_post) | **POST** /addresses/utxos | Get Utxos For Addresses | +| _KaspaBlocksApi_ | [**get_block_blocks_block_id_get**](docs/KaspaBlocksApi.md#get_block_blocks_block_id_get) | **GET** /blocks/{blockId} | Get Block | +| _KaspaBlocksApi_ | [**get_blocks_blocks_get**](docs/KaspaBlocksApi.md#get_blocks_blocks_get) | **GET** /blocks | Get Blocks | +| _KaspaBlocksApi_ | [**get_blocks_from_bluescore_blocks_from_bluescore_get**](docs/KaspaBlocksApi.md#get_blocks_from_bluescore_blocks_from_bluescore_get) | **GET** /blocks-from-bluescore | Get Blocks From Bluescore | +| _KaspaNetworkInfoApi_ | [**get_blockdag_info_blockdag_get**](docs/KaspaNetworkInfoApi.md#get_blockdag_info_blockdag_get) | **GET** /info/blockdag | Get Blockdag | +| _KaspaNetworkInfoApi_ | [**get_blockreward_info_blockreward_get**](docs/KaspaNetworkInfoApi.md#get_blockreward_info_blockreward_get) | **GET** /info/blockreward | Get Blockreward | +| _KaspaNetworkInfoApi_ | [**get_circulating_coins_info_coinsupply_circulating_get**](docs/KaspaNetworkInfoApi.md#get_circulating_coins_info_coinsupply_circulating_get) | **GET** /info/coinsupply/circulating | Get Circulating Coins | +| _KaspaNetworkInfoApi_ | [**get_coinsupply_info_coinsupply_get**](docs/KaspaNetworkInfoApi.md#get_coinsupply_info_coinsupply_get) | **GET** /info/coinsupply | Get Coinsupply | +| _KaspaNetworkInfoApi_ | [**get_fee_estimate_info_fee_estimate_get**](docs/KaspaNetworkInfoApi.md#get_fee_estimate_info_fee_estimate_get) | **GET** /info/fee-estimate | Get Fee Estimate | +| _KaspaNetworkInfoApi_ | [**get_halving_info_halving_get**](docs/KaspaNetworkInfoApi.md#get_halving_info_halving_get) | **GET** /info/halving | Get Halving | +| _KaspaNetworkInfoApi_ | [**get_hashrate_history_info_hashrate_history_get**](docs/KaspaNetworkInfoApi.md#get_hashrate_history_info_hashrate_history_get) | **GET** /info/hashrate/history | Get Hashrate History | +| _KaspaNetworkInfoApi_ | [**get_hashrate_info_hashrate_get**](docs/KaspaNetworkInfoApi.md#get_hashrate_info_hashrate_get) | **GET** /info/hashrate | Get Hashrate | +| _KaspaNetworkInfoApi_ | [**get_kaspad_info_info_kaspad_get**](docs/KaspaNetworkInfoApi.md#get_kaspad_info_info_kaspad_get) | **GET** /info/kaspad | Get Kaspad Info | +| _KaspaNetworkInfoApi_ | [**get_marketcap_info_marketcap_get**](docs/KaspaNetworkInfoApi.md#get_marketcap_info_marketcap_get) | **GET** /info/marketcap | Get Marketcap | +| _KaspaNetworkInfoApi_ | [**get_max_hashrate_info_hashrate_max_get**](docs/KaspaNetworkInfoApi.md#get_max_hashrate_info_hashrate_max_get) | **GET** /info/hashrate/max | Get Max Hashrate | +| _KaspaNetworkInfoApi_ | [**get_network_info_network_get**](docs/KaspaNetworkInfoApi.md#get_network_info_network_get) | **GET** /info/network | Get Network | +| _KaspaNetworkInfoApi_ | [**get_price_info_price_get**](docs/KaspaNetworkInfoApi.md#get_price_info_price_get) | **GET** /info/price | Get Price | +| _KaspaNetworkInfoApi_ | [**get_total_coins_info_coinsupply_total_get**](docs/KaspaNetworkInfoApi.md#get_total_coins_info_coinsupply_total_get) | **GET** /info/coinsupply/total | Get Total Coins | +| _KaspaNetworkInfoApi_ | [**get_virtual_selected_parent_blue_score_info_virtual_chain_blue_score_get**](docs/KaspaNetworkInfoApi.md#get_virtual_selected_parent_blue_score_info_virtual_chain_blue_score_get) | **GET** /info/virtual-chain-blue-score | Get Virtual Selected Parent Blue Score | +| _KaspaNetworkInfoApi_ | [**health_state_info_health_get**](docs/KaspaNetworkInfoApi.md#health_state_info_health_get) | **GET** /info/health | Health State | +| _KaspaTransactionsApi_ | [**calculate_transaction_mass_transactions_mass_post**](docs/KaspaTransactionsApi.md#calculate_transaction_mass_transactions_mass_post) | **POST** /transactions/mass | Calculate Transaction Mass | +| _KaspaTransactionsApi_ | [**get_transaction_acceptance_transactions_acceptance_post**](docs/KaspaTransactionsApi.md#get_transaction_acceptance_transactions_acceptance_post) | **POST** /transactions/acceptance | Get Transaction Acceptance | +| _KaspaTransactionsApi_ | [**get_transaction_transactions_transaction_id_get**](docs/KaspaTransactionsApi.md#get_transaction_transactions_transaction_id_get) | **GET** /transactions/{transactionId} | Get Transaction | +| _KaspaTransactionsApi_ | [**search_for_transactions_transactions_search_post**](docs/KaspaTransactionsApi.md#search_for_transactions_transactions_search_post) | **POST** /transactions/search | Search For Transactions | +| _KaspaTransactionsApi_ | [**submit_a_new_transaction_transactions_post**](docs/KaspaTransactionsApi.md#submit_a_new_transaction_transactions_post) | **POST** /transactions | Submit A New Transaction | + +## Documentation For Models + +- [AcceptanceMode](docs/AcceptanceMode.md) +- [AddressName](docs/AddressName.md) +- [AddressesActiveRequest](docs/AddressesActiveRequest.md) +- [BalanceRequest](docs/BalanceRequest.md) +- [BalanceResponse](docs/BalanceResponse.md) +- [BalancesByAddressEntry](docs/BalancesByAddressEntry.md) +- [BlockModel](docs/BlockModel.md) +- [BlockResponse](docs/BlockResponse.md) +- [BlockRewardResponse](docs/BlockRewardResponse.md) +- [BlockTxInputModel](docs/BlockTxInputModel.md) +- [BlockTxInputPreviousOutpointModel](docs/BlockTxInputPreviousOutpointModel.md) +- [BlockTxModel](docs/BlockTxModel.md) +- [BlockTxOutputModel](docs/BlockTxOutputModel.md) +- [BlockTxOutputScriptPublicKeyModel](docs/BlockTxOutputScriptPublicKeyModel.md) +- [BlockTxOutputVerboseDataModel](docs/BlockTxOutputVerboseDataModel.md) +- [BlockTxVerboseDataModel](docs/BlockTxVerboseDataModel.md) +- [BlockdagResponse](docs/BlockdagResponse.md) +- [BlueScoreResponse](docs/BlueScoreResponse.md) +- [CoinSupplyResponse](docs/CoinSupplyResponse.md) +- [DbCheckStatus](docs/DbCheckStatus.md) +- [EndpointsGetBlocksBlockHeader](docs/EndpointsGetBlocksBlockHeader.md) +- [EndpointsGetHashrateBlockHeader](docs/EndpointsGetHashrateBlockHeader.md) +- [ExtraModel](docs/ExtraModel.md) +- [FeeEstimateBucket](docs/FeeEstimateBucket.md) +- [FeeEstimateResponse](docs/FeeEstimateResponse.md) +- [HalvingResponse](docs/HalvingResponse.md) +- [HashrateHistoryResponse](docs/HashrateHistoryResponse.md) +- [HashrateResponse](docs/HashrateResponse.md) +- [HealthResponse](docs/HealthResponse.md) +- [HttpValidationError](docs/HttpValidationError.md) +- [KaspadInfoResponse](docs/KaspadInfoResponse.md) +- [KaspadResponse](docs/KaspadResponse.md) +- [MarketCapResponse](docs/MarketCapResponse.md) +- [MaxHashrateResponse](docs/MaxHashrateResponse.md) +- [OutpointModel](docs/OutpointModel.md) +- [ParentHashModel](docs/ParentHashModel.md) +- [PreviousOutpointLookupMode](docs/PreviousOutpointLookupMode.md) +- [PriceResponse](docs/PriceResponse.md) +- [ResponseGetBlockrewardInfoBlockrewardGet](docs/ResponseGetBlockrewardInfoBlockrewardGet.md) +- [ResponseGetHalvingInfoHalvingGet](docs/ResponseGetHalvingInfoHalvingGet.md) +- [ResponseGetHashrateInfoHashrateGet](docs/ResponseGetHashrateInfoHashrateGet.md) +- [ResponseGetMarketcapInfoMarketcapGet](docs/ResponseGetMarketcapInfoMarketcapGet.md) +- [ResponseGetPriceInfoPriceGet](docs/ResponseGetPriceInfoPriceGet.md) +- [ScriptPublicKeyModel](docs/ScriptPublicKeyModel.md) +- [SubmitTransactionRequest](docs/SubmitTransactionRequest.md) +- [SubmitTransactionResponse](docs/SubmitTransactionResponse.md) +- [SubmitTxInput](docs/SubmitTxInput.md) +- [SubmitTxModel](docs/SubmitTxModel.md) +- [SubmitTxOutpoint](docs/SubmitTxOutpoint.md) +- [SubmitTxOutput](docs/SubmitTxOutput.md) +- [SubmitTxScriptPublicKey](docs/SubmitTxScriptPublicKey.md) +- [TransactionCount](docs/TransactionCount.md) +- [TxAcceptanceRequest](docs/TxAcceptanceRequest.md) +- [TxAcceptanceResponse](docs/TxAcceptanceResponse.md) +- [TxIdResponse](docs/TxIdResponse.md) +- [TxInput](docs/TxInput.md) +- [TxMass](docs/TxMass.md) +- [TxModel](docs/TxModel.md) +- [TxOutput](docs/TxOutput.md) +- [TxSearch](docs/TxSearch.md) +- [TxSearchAcceptingBlueScores](docs/TxSearchAcceptingBlueScores.md) +- [UtxoModel](docs/UtxoModel.md) +- [UtxoRequest](docs/UtxoRequest.md) +- [UtxoResponse](docs/UtxoResponse.md) +- [ValidationError](docs/ValidationError.md) +- [ValidationErrorLocInner](docs/ValidationErrorLocInner.md) +- [VcBlockModel](docs/VcBlockModel.md) +- [VcTxInput](docs/VcTxInput.md) +- [VcTxModel](docs/VcTxModel.md) +- [VcTxOutput](docs/VcTxOutput.md) +- [VerboseDataModel](docs/VerboseDataModel.md) + +To get access to the crate's generated documentation, use: + +``` +cargo doc --open +``` + +## Author diff --git a/dymension/libs/kaspa/lib/api/docs/AcceptanceMode.md b/dymension/libs/kaspa/lib/api/docs/AcceptanceMode.md new file mode 100644 index 00000000000..4f027e9a84e --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/AcceptanceMode.md @@ -0,0 +1,10 @@ +# AcceptanceMode + +## Enum Variants + +| Name | Value | +| -------- | -------- | +| Accepted | accepted | +| Rejected | rejected | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/AddressName.md b/dymension/libs/kaspa/lib/api/docs/AddressName.md new file mode 100644 index 00000000000..ddc5aac2483 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/AddressName.md @@ -0,0 +1,10 @@ +# AddressName + +## Properties + +| Name | Type | Description | Notes | +| ----------- | ---------- | ----------- | ----- | +| **address** | **String** | | +| **name** | **String** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/AddressesActiveRequest.md b/dymension/libs/kaspa/lib/api/docs/AddressesActiveRequest.md new file mode 100644 index 00000000000..d0bc741a4d6 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/AddressesActiveRequest.md @@ -0,0 +1,9 @@ +# AddressesActiveRequest + +## Properties + +| Name | Type | Description | Notes | +| ------------- | ----------------------- | ----------- | ------------------------------------------------------------------------------------------- | +| **addresses** | Option<**Vec**> | | [optional]default to [kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73]] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/BalanceRequest.md b/dymension/libs/kaspa/lib/api/docs/BalanceRequest.md new file mode 100644 index 00000000000..8aa7b80b9dc --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/BalanceRequest.md @@ -0,0 +1,9 @@ +# BalanceRequest + +## Properties + +| Name | Type | Description | Notes | +| ------------- | ----------------------- | ----------- | ------------------------------------------------------------------------------------------- | +| **addresses** | Option<**Vec**> | | [optional]default to [kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73]] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/BalanceResponse.md b/dymension/libs/kaspa/lib/api/docs/BalanceResponse.md new file mode 100644 index 00000000000..26f75c0a86e --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/BalanceResponse.md @@ -0,0 +1,10 @@ +# BalanceResponse + +## Properties + +| Name | Type | Description | Notes | +| ----------- | ------------------ | ----------- | ------------------------------------------------------------------------------------------ | +| **address** | Option<**String**> | | [optional][default to kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73] | +| **balance** | Option<**i32**> | | [optional][default to 38240000000] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/BalancesByAddressEntry.md b/dymension/libs/kaspa/lib/api/docs/BalancesByAddressEntry.md new file mode 100644 index 00000000000..0e1d20deb89 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/BalancesByAddressEntry.md @@ -0,0 +1,10 @@ +# BalancesByAddressEntry + +## Properties + +| Name | Type | Description | Notes | +| ----------- | ------------------ | ----------- | ------------------------------------------------------------------------------------------ | +| **address** | Option<**String**> | | [optional][default to kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73] | +| **balance** | Option<**i32**> | | [optional][default to 12451591699] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/BlockModel.md b/dymension/libs/kaspa/lib/api/docs/BlockModel.md new file mode 100644 index 00000000000..6e381f2602d --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/BlockModel.md @@ -0,0 +1,12 @@ +# BlockModel + +## Properties + +| Name | Type | Description | Notes | +| ---------------- | ---------------------------------------------------------------------------------- | ----------- | ---------- | +| **header** | [**models::EndpointsGetBlocksBlockHeader**](endpoints__get_blocks__BlockHeader.md) | | +| **transactions** | Option<[**Vec**](BlockTxModel.md)> | | [optional] | +| **verbose_data** | [**models::VerboseDataModel**](VerboseDataModel.md) | | +| **extra** | Option<[**models::ExtraModel**](ExtraModel.md)> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/BlockResponse.md b/dymension/libs/kaspa/lib/api/docs/BlockResponse.md new file mode 100644 index 00000000000..167da40d59a --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/BlockResponse.md @@ -0,0 +1,10 @@ +# BlockResponse + +## Properties + +| Name | Type | Description | Notes | +| ---------------- | ---------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **block_hashes** | Option<**Vec**> | | [optional]default to [44edf9bfd32aa154bfad64485882f184372b64bd60565ba121b42fc3cb1238f3, 18c7afdf8f447ca06adb8b4946dc45f5feb1188c7d177da6094dfbc760eca699, 9a822351cd293a653f6721afec1646bd1690da7124b5fbe87001711406010604, 2fda0dad4ec879b4ad02ebb68c757955cab305558998129a7de111ab852e7dcb]] | +| **blocks** | Option<[**Vec**](BlockModel.md)> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/BlockRewardResponse.md b/dymension/libs/kaspa/lib/api/docs/BlockRewardResponse.md new file mode 100644 index 00000000000..4aa897cb1b5 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/BlockRewardResponse.md @@ -0,0 +1,9 @@ +# BlockRewardResponse + +## Properties + +| Name | Type | Description | Notes | +| --------------- | --------------- | ----------- | ------------------------------- | +| **blockreward** | Option<**f64**> | | [optional][default to 12000132] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/BlockTxInputModel.md b/dymension/libs/kaspa/lib/api/docs/BlockTxInputModel.md new file mode 100644 index 00000000000..3b816f2ff60 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/BlockTxInputModel.md @@ -0,0 +1,12 @@ +# BlockTxInputModel + +## Properties + +| Name | Type | Description | Notes | +| --------------------- | --------------------------------------------------------------------------------------------- | ----------- | ---------- | +| **previous_outpoint** | Option<[**models::BlockTxInputPreviousOutpointModel**](BlockTxInputPreviousOutpointModel.md)> | | [optional] | +| **signature_script** | Option<**String**> | | [optional] | +| **sig_op_count** | Option<**i32**> | | [optional] | +| **sequence** | Option<**i32**> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/BlockTxInputPreviousOutpointModel.md b/dymension/libs/kaspa/lib/api/docs/BlockTxInputPreviousOutpointModel.md new file mode 100644 index 00000000000..9149ba36fa9 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/BlockTxInputPreviousOutpointModel.md @@ -0,0 +1,10 @@ +# BlockTxInputPreviousOutpointModel + +## Properties + +| Name | Type | Description | Notes | +| ------------------ | ---------- | ----------- | ----- | +| **transaction_id** | **String** | | +| **index** | **i32** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/BlockTxModel.md b/dymension/libs/kaspa/lib/api/docs/BlockTxModel.md new file mode 100644 index 00000000000..c7bf3fb6ffd --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/BlockTxModel.md @@ -0,0 +1,17 @@ +# BlockTxModel + +## Properties + +| Name | Type | Description | Notes | +| ----------------- | -------------------------------------------------------------------- | ----------- | ---------- | +| **inputs** | Option<[**Vec**](BlockTxInputModel.md)> | | [optional] | +| **outputs** | Option<[**Vec**](BlockTxOutputModel.md)> | | [optional] | +| **subnetwork_id** | Option<**String**> | | [optional] | +| **payload** | Option<**String**> | | [optional] | +| **verbose_data** | [**models::BlockTxVerboseDataModel**](BlockTxVerboseDataModel.md) | | +| **lock_time** | Option<**i32**> | | [optional] | +| **gas** | Option<**i32**> | | [optional] | +| **mass** | Option<**i32**> | | [optional] | +| **version** | Option<**i32**> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/BlockTxOutputModel.md b/dymension/libs/kaspa/lib/api/docs/BlockTxOutputModel.md new file mode 100644 index 00000000000..352114c5630 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/BlockTxOutputModel.md @@ -0,0 +1,11 @@ +# BlockTxOutputModel + +## Properties + +| Name | Type | Description | Notes | +| --------------------- | --------------------------------------------------------------------------------------------- | ----------- | ---------- | +| **amount** | Option<**i32**> | | [optional] | +| **script_public_key** | Option<[**models::BlockTxOutputScriptPublicKeyModel**](BlockTxOutputScriptPublicKeyModel.md)> | | [optional] | +| **verbose_data** | Option<[**models::BlockTxOutputVerboseDataModel**](BlockTxOutputVerboseDataModel.md)> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/BlockTxOutputScriptPublicKeyModel.md b/dymension/libs/kaspa/lib/api/docs/BlockTxOutputScriptPublicKeyModel.md new file mode 100644 index 00000000000..d241fbc8016 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/BlockTxOutputScriptPublicKeyModel.md @@ -0,0 +1,10 @@ +# BlockTxOutputScriptPublicKeyModel + +## Properties + +| Name | Type | Description | Notes | +| --------------------- | ------------------ | ----------- | ---------- | +| **script_public_key** | Option<**String**> | | [optional] | +| **version** | Option<**i32**> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/BlockTxOutputVerboseDataModel.md b/dymension/libs/kaspa/lib/api/docs/BlockTxOutputVerboseDataModel.md new file mode 100644 index 00000000000..5b3ca22520f --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/BlockTxOutputVerboseDataModel.md @@ -0,0 +1,10 @@ +# BlockTxOutputVerboseDataModel + +## Properties + +| Name | Type | Description | Notes | +| ----------------------------- | ------------------ | ----------- | ---------- | +| **script_public_key_type** | Option<**String**> | | [optional] | +| **script_public_key_address** | Option<**String**> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/BlockTxVerboseDataModel.md b/dymension/libs/kaspa/lib/api/docs/BlockTxVerboseDataModel.md new file mode 100644 index 00000000000..48bf925e518 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/BlockTxVerboseDataModel.md @@ -0,0 +1,13 @@ +# BlockTxVerboseDataModel + +## Properties + +| Name | Type | Description | Notes | +| ------------------ | ------------------ | ----------- | ---------- | +| **transaction_id** | **String** | | +| **hash** | Option<**String**> | | [optional] | +| **compute_mass** | Option<**i32**> | | [optional] | +| **block_hash** | Option<**String**> | | [optional] | +| **block_time** | Option<**i32**> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/BlockdagResponse.md b/dymension/libs/kaspa/lib/api/docs/BlockdagResponse.md new file mode 100644 index 00000000000..17547047a5a --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/BlockdagResponse.md @@ -0,0 +1,18 @@ +# BlockdagResponse + +## Properties + +| Name | Type | Description | Notes | +| ------------------------- | --------------- | ----------- | ----- | +| **network_name** | **String** | | +| **block_count** | **String** | | +| **header_count** | **String** | | +| **tip_hashes** | **Vec** | | +| **difficulty** | **f64** | | +| **past_median_time** | **String** | | +| **virtual_parent_hashes** | **Vec** | | +| **pruning_point_hash** | **String** | | +| **virtual_daa_score** | **String** | | +| **sink** | **String** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/BlueScoreResponse.md b/dymension/libs/kaspa/lib/api/docs/BlueScoreResponse.md new file mode 100644 index 00000000000..6bf2eafd2c4 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/BlueScoreResponse.md @@ -0,0 +1,9 @@ +# BlueScoreResponse + +## Properties + +| Name | Type | Description | Notes | +| -------------- | --------------- | ----------- | ----------------------------- | +| **blue_score** | Option<**i32**> | | [optional][default to 260890] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/CoinSupplyResponse.md b/dymension/libs/kaspa/lib/api/docs/CoinSupplyResponse.md new file mode 100644 index 00000000000..4924d4f87a3 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/CoinSupplyResponse.md @@ -0,0 +1,10 @@ +# CoinSupplyResponse + +## Properties + +| Name | Type | Description | Notes | +| ---------------------- | ------------------ | ----------- | ------------------------------------------ | +| **circulating_supply** | Option<**String**> | | [optional][default to 1000900697580640180] | +| **max_supply** | Option<**String**> | | [optional][default to 2900000000000000000] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/DbCheckStatus.md b/dymension/libs/kaspa/lib/api/docs/DbCheckStatus.md new file mode 100644 index 00000000000..53e033c7cac --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/DbCheckStatus.md @@ -0,0 +1,13 @@ +# DbCheckStatus + +## Properties + +| Name | Type | Description | Notes | +| ------------------------------- | ---------------- | ----------- | --------------------------- | +| **is_synced** | Option<**bool**> | | [optional][default to true] | +| **blue_score** | Option<**i32**> | | [optional] | +| **blue_score_diff** | Option<**i32**> | | [optional] | +| **accepted_tx_block_time** | Option<**i32**> | | [optional] | +| **accepted_tx_block_time_diff** | Option<**i32**> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/EndpointsGetBlocksBlockHeader.md b/dymension/libs/kaspa/lib/api/docs/EndpointsGetBlocksBlockHeader.md new file mode 100644 index 00000000000..b0bd9a5df5b --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/EndpointsGetBlocksBlockHeader.md @@ -0,0 +1,20 @@ +# EndpointsGetBlocksBlockHeader + +## Properties + +| Name | Type | Description | Notes | +| --------------------------- | -------------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------- | +| **version** | Option<**i32**> | | [optional][default to 1] | +| **hash_merkle_root** | Option<**String**> | | [optional][default to e6641454e16cff4f232b899564eeaa6e480b66069d87bee6a2b2476e63fcd887] | +| **accepted_id_merkle_root** | Option<**String**> | | [optional][default to 9bab45b027a0b2b47135b6f6f866e5e4040fc1fdf2fe56eb0c90a603ce86092b] | +| **utxo_commitment** | Option<**String**> | | [optional][default to 236d5f9ffd19b317a97693322c3e2ae11a44b5df803d71f1ccf6c2393bc6143c] | +| **timestamp** | Option<**String**> | | [optional][default to 1656450648874] | +| **bits** | Option<**i32**> | | [optional][default to 455233226] | +| **nonce** | Option<**String**> | | [optional][default to 14797571275553019490] | +| **daa_score** | Option<**String**> | | [optional][default to 19984482] | +| **blue_work** | Option<**String**> | | [optional][default to 2d1b3f04f8a0dcd31] | +| **parents** | Option<[**Vec**](ParentHashModel.md)> | | [optional] | +| **blue_score** | Option<**String**> | | [optional][default to 18483232] | +| **pruning_point** | Option<**String**> | | [optional][default to 5d32a9403273a34b6551b84340a1459ddde2ae6ba59a47987a6374340ba41d5d] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/EndpointsGetHashrateBlockHeader.md b/dymension/libs/kaspa/lib/api/docs/EndpointsGetHashrateBlockHeader.md new file mode 100644 index 00000000000..d9a9d819311 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/EndpointsGetHashrateBlockHeader.md @@ -0,0 +1,13 @@ +# EndpointsGetHashrateBlockHeader + +## Properties + +| Name | Type | Description | Notes | +| -------------- | ------------------ | ----------- | --------------------------------------------------------------------------------------- | +| **hash** | Option<**String**> | | [optional][default to e6641454e16cff4f232b899564eeaa6e480b66069d87bee6a2b2476e63fcd887] | +| **timestamp** | Option<**String**> | | [optional][default to 1656450648874] | +| **difficulty** | Option<**i32**> | | [optional][default to 1212312312] | +| **daa_score** | Option<**String**> | | [optional][default to 19984482] | +| **blue_score** | Option<**String**> | | [optional][default to 18483232] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/ExperimentalKaspaVirtualChainApi.md b/dymension/libs/kaspa/lib/api/docs/ExperimentalKaspaVirtualChainApi.md new file mode 100644 index 00000000000..89571464d3a --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/ExperimentalKaspaVirtualChainApi.md @@ -0,0 +1,38 @@ +# \ExperimentalKaspaVirtualChainApi + +All URIs are relative to _http://localhost_ + +| Method | HTTP request | Description | +| -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------ | +| [**get_virtual_chain_transactions_virtual_chain_get**](ExperimentalKaspaVirtualChainApi.md#get_virtual_chain_transactions_virtual_chain_get) | **GET** /virtual-chain | Get Virtual Chain Transactions | + +## get_virtual_chain_transactions_virtual_chain_get + +> Vec get_virtual_chain_transactions_virtual_chain_get(blue_score_gte, limit, resolve_inputs, include_coinbase) +> Get Virtual Chain Transactions + +EXPERIMENTAL - EXPECT BREAKING CHANGES: Get virtual chain transactions by blue score. + +### Parameters + +| Name | Type | Description | Required | Notes | +| -------------------- | ---------------- | ------------------ | ---------- | ------------------ | +| **blue_score_gte** | **i32** | Divisible by limit | [required] | +| **limit** | Option<**i32**> | | | [default to 10] | +| **resolve_inputs** | Option<**bool**> | | | [default to false] | +| **include_coinbase** | Option<**bool**> | | | [default to true] | + +### Return type + +[**Vec**](VcBlockModel.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/ExtraModel.md b/dymension/libs/kaspa/lib/api/docs/ExtraModel.md new file mode 100644 index 00000000000..769eed43927 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/ExtraModel.md @@ -0,0 +1,11 @@ +# ExtraModel + +## Properties + +| Name | Type | Description | Notes | +| ----------------- | ------------------ | ----------- | ---------- | +| **color** | Option<**String**> | | [optional] | +| **miner_address** | Option<**String**> | | [optional] | +| **miner_info** | Option<**String**> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/FeeEstimateBucket.md b/dymension/libs/kaspa/lib/api/docs/FeeEstimateBucket.md new file mode 100644 index 00000000000..7e7749bf466 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/FeeEstimateBucket.md @@ -0,0 +1,10 @@ +# FeeEstimateBucket + +## Properties + +| Name | Type | Description | Notes | +| --------------------- | --------------- | ----------- | ---------------------------- | +| **feerate** | Option<**i32**> | | [optional][default to 1] | +| **estimated_seconds** | Option<**f64**> | | [optional][default to 0.004] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/FeeEstimateResponse.md b/dymension/libs/kaspa/lib/api/docs/FeeEstimateResponse.md new file mode 100644 index 00000000000..8da9ac74109 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/FeeEstimateResponse.md @@ -0,0 +1,11 @@ +# FeeEstimateResponse + +## Properties + +| Name | Type | Description | Notes | +| ------------------- | ---------------------------------------------------------- | ----------- | ----- | +| **priority_bucket** | [**models::FeeEstimateBucket**](FeeEstimateBucket.md) | | +| **normal_buckets** | [**Vec**](FeeEstimateBucket.md) | | +| **low_buckets** | [**Vec**](FeeEstimateBucket.md) | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/HalvingResponse.md b/dymension/libs/kaspa/lib/api/docs/HalvingResponse.md new file mode 100644 index 00000000000..9c14ea0305c --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/HalvingResponse.md @@ -0,0 +1,11 @@ +# HalvingResponse + +## Properties + +| Name | Type | Description | Notes | +| -------------------------- | ------------------ | ----------- | ---------------------------------------------- | +| **next_halving_timestamp** | Option<**i32**> | | [optional][default to 1662837270000] | +| **next_halving_date** | Option<**String**> | | [optional][default to 2022-09-10 19:38:52 UTC] | +| **next_halving_amount** | Option<**f64**> | | [optional][default to 155.123123] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/HashrateHistoryResponse.md b/dymension/libs/kaspa/lib/api/docs/HashrateHistoryResponse.md new file mode 100644 index 00000000000..c29af700edc --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/HashrateHistoryResponse.md @@ -0,0 +1,15 @@ +# HashrateHistoryResponse + +## Properties + +| Name | Type | Description | Notes | +| --------------- | --------------- | ----------- | ---------- | +| **daa_score** | **i32** | | +| **blue_score** | **i32** | | +| **timestamp** | **i32** | | +| **date_time** | **String** | | +| **bits** | Option<**i32**> | | [optional] | +| **difficulty** | **i32** | | +| **hashrate_kh** | **i32** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/HashrateResponse.md b/dymension/libs/kaspa/lib/api/docs/HashrateResponse.md new file mode 100644 index 00000000000..4caf2f7a359 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/HashrateResponse.md @@ -0,0 +1,9 @@ +# HashrateResponse + +## Properties + +| Name | Type | Description | Notes | +| ------------ | --------------- | ----------- | ------------------------------- | +| **hashrate** | Option<**f64**> | | [optional][default to 12000132] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/HealthResponse.md b/dymension/libs/kaspa/lib/api/docs/HealthResponse.md new file mode 100644 index 00000000000..7117ad2a52b --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/HealthResponse.md @@ -0,0 +1,10 @@ +# HealthResponse + +## Properties + +| Name | Type | Description | Notes | +| ------------------ | ---------------------------------------------------- | ----------- | ----- | +| **kaspad_servers** | [**Vec**](KaspadResponse.md) | | +| **database** | [**models::DbCheckStatus**](DBCheckStatus.md) | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/HttpValidationError.md b/dymension/libs/kaspa/lib/api/docs/HttpValidationError.md new file mode 100644 index 00000000000..258220e9cbf --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/HttpValidationError.md @@ -0,0 +1,9 @@ +# HttpValidationError + +## Properties + +| Name | Type | Description | Notes | +| ---------- | -------------------------------------------------------------- | ----------- | ---------- | +| **detail** | Option<[**Vec**](ValidationError.md)> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/KaspaAddressesApi.md b/dymension/libs/kaspa/lib/api/docs/KaspaAddressesApi.md new file mode 100644 index 00000000000..ead710eb038 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/KaspaAddressesApi.md @@ -0,0 +1,304 @@ +# \KaspaAddressesApi + +All URIs are relative to _http://localhost_ + +| Method | HTTP request | Description | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | -------------------------------------- | +| [**get_addresses_active_addresses_active_post**](KaspaAddressesApi.md#get_addresses_active_addresses_active_post) | **POST** /addresses/active | Get Addresses Active | +| [**get_addresses_names_addresses_names_get**](KaspaAddressesApi.md#get_addresses_names_addresses_names_get) | **GET** /addresses/names | Get Addresses Names | +| [**get_balance_from_kaspa_address_addresses_kaspa_address_balance_get**](KaspaAddressesApi.md#get_balance_from_kaspa_address_addresses_kaspa_address_balance_get) | **GET** /addresses/{kaspaAddress}/balance | Get Balance From Kaspa Address | +| [**get_balances_from_kaspa_addresses_addresses_balances_post**](KaspaAddressesApi.md#get_balances_from_kaspa_addresses_addresses_balances_post) | **POST** /addresses/balances | Get Balances From Kaspa Addresses | +| [**get_full_transactions_for_address_addresses_kaspa_address_full_transactions_get**](KaspaAddressesApi.md#get_full_transactions_for_address_addresses_kaspa_address_full_transactions_get) | **GET** /addresses/{kaspaAddress}/full-transactions | Get Full Transactions For Address | +| [**get_full_transactions_for_address_page_addresses_kaspa_address_full_transactions_page_get**](KaspaAddressesApi.md#get_full_transactions_for_address_page_addresses_kaspa_address_full_transactions_page_get) | **GET** /addresses/{kaspaAddress}/full-transactions-page | Get Full Transactions For Address Page | +| [**get_name_for_address_addresses_kaspa_address_name_get**](KaspaAddressesApi.md#get_name_for_address_addresses_kaspa_address_name_get) | **GET** /addresses/{kaspaAddress}/name | Get Name For Address | +| [**get_transaction_count_for_address_addresses_kaspa_address_transactions_count_get**](KaspaAddressesApi.md#get_transaction_count_for_address_addresses_kaspa_address_transactions_count_get) | **GET** /addresses/{kaspaAddress}/transactions-count | Get Transaction Count For Address | +| [**get_utxos_for_address_addresses_kaspa_address_utxos_get**](KaspaAddressesApi.md#get_utxos_for_address_addresses_kaspa_address_utxos_get) | **GET** /addresses/{kaspaAddress}/utxos | Get Utxos For Address | +| [**get_utxos_for_addresses_addresses_utxos_post**](KaspaAddressesApi.md#get_utxos_for_addresses_addresses_utxos_post) | **POST** /addresses/utxos | Get Utxos For Addresses | + +## get_addresses_active_addresses_active_post + +> Vec get_addresses_active_addresses_active_post(addresses_active_request) +> Get Addresses Active + +This endpoint checks if addresses have had any transaction activity in the past. It is specifically designed for HD Wallets to verify historical address activity. + +### Parameters + +| Name | Type | Description | Required | Notes | +| ---------------------------- | ------------------------------------------------------- | ----------- | ---------- | ----- | +| **addresses_active_request** | [**AddressesActiveRequest**](AddressesActiveRequest.md) | | [required] | + +### Return type + +[**Vec**](TxIdResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: application/json +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_addresses_names_addresses_names_get + +> Vec get_addresses_names_addresses_names_get() +> Get Addresses Names + +Get the name for an address + +### Parameters + +This endpoint does not need any parameter. + +### Return type + +[**Vec**](AddressName.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_balance_from_kaspa_address_addresses_kaspa_address_balance_get + +> models::BalanceResponse get_balance_from_kaspa_address_addresses_kaspa_address_balance_get(kaspa_address) +> Get Balance From Kaspa Address + +Get balance for a given kaspa address + +### Parameters + +| Name | Type | Description | Required | Notes | +| ----------------- | ---------- | ------------------------------------------------------------------------------------------------ | ---------- | ----- | +| **kaspa_address** | **String** | Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73 | [required] | + +### Return type + +[**models::BalanceResponse**](BalanceResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_balances_from_kaspa_addresses_addresses_balances_post + +> Vec get_balances_from_kaspa_addresses_addresses_balances_post(balance_request) +> Get Balances From Kaspa Addresses + +Get balances for multiple kaspa addresses + +### Parameters + +| Name | Type | Description | Required | Notes | +| ------------------- | --------------------------------------- | ----------- | ---------- | ----- | +| **balance_request** | [**BalanceRequest**](BalanceRequest.md) | | [required] | + +### Return type + +[**Vec**](BalancesByAddressEntry.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: application/json +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_full_transactions_for_address_addresses_kaspa_address_full_transactions_get + +> Vec get_full_transactions_for_address_addresses_kaspa_address_full_transactions_get(kaspa_address, limit, offset, fields, resolve_previous_outpoints) +> Get Full Transactions For Address + +Get all transactions for a given address from database. And then get their related full transaction data + +### Parameters + +| Name | Type | Description | Required | Notes | +| ------------------------------ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | --------------- | +| **kaspa_address** | **String** | Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73 | [required] | +| **limit** | Option<**i32**> | The number of records to get | | [default to 50] | +| **offset** | Option<**i32**> | The offset from which to get records | | [default to 0] | +| **fields** | Option<**String**> | | | [default to ] | +| **resolve_previous_outpoints** | Option<**String**> | Use this parameter if you want to fetch the TransactionInput previous outpoint details. Light fetches only the adress and amount. Full fetches the whole TransactionOutput and adds it into each TxInput. | | [default to no] | + +### Return type + +[**Vec**](TxModel.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_full_transactions_for_address_page_addresses_kaspa_address_full_transactions_page_get + +> Vec get_full_transactions_for_address_page_addresses_kaspa_address_full_transactions_page_get(kaspa_address, limit, before, after, fields, resolve_previous_outpoints, acceptance) +> Get Full Transactions For Address Page + +Get all transactions for a given address from database. And then get their related full transaction data + +### Parameters + +| Name | Type | Description | Required | Notes | +| ------------------------------ | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | --------------- | +| **kaspa_address** | **String** | Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73 | [required] | +| **limit** | Option<**i32**> | The max number of records to get. For paging combine with using 'before/after' from oldest previous result. Use value of X-Next-Page-Before/-After as long as header is present to continue paging. The actual number of transactions returned for each page can be > limit. | | [default to 50] | +| **before** | Option<**i32**> | Only include transactions with block time before this (epoch-millis) | | [default to 0] | +| **after** | Option<**i32**> | Only include transactions with block time after this (epoch-millis) | | [default to 0] | +| **fields** | Option<**String**> | | | [default to ] | +| **resolve_previous_outpoints** | Option<**String**> | Use this parameter if you want to fetch the TransactionInput previous outpoint details. Light fetches only the adress and amount. Full fetches the whole TransactionOutput and adds it into each TxInput. | | [default to no] | +| **acceptance** | Option<[**AcceptanceMode**](.md)> | | | + +### Return type + +[**Vec**](TxModel.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_name_for_address_addresses_kaspa_address_name_get + +> models::AddressName get_name_for_address_addresses_kaspa_address_name_get(kaspa_address) +> Get Name For Address + +Get the name for an address + +### Parameters + +| Name | Type | Description | Required | Notes | +| ----------------- | ---------- | ------------------------------------------------------------------------------------------------ | ---------- | ----- | +| **kaspa_address** | **String** | Kaspa address as string e.g. kaspa:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkx9awp4e | [required] | + +### Return type + +[**models::AddressName**](AddressName.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_transaction_count_for_address_addresses_kaspa_address_transactions_count_get + +> models::TransactionCount get_transaction_count_for_address_addresses_kaspa_address_transactions_count_get(kaspa_address) +> Get Transaction Count For Address + +Count the number of transactions associated with this address + +### Parameters + +| Name | Type | Description | Required | Notes | +| ----------------- | ---------- | ------------------------------------------------------------------------------------------------ | ---------- | ----- | +| **kaspa_address** | **String** | Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73 | [required] | + +### Return type + +[**models::TransactionCount**](TransactionCount.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_utxos_for_address_addresses_kaspa_address_utxos_get + +> Vec get_utxos_for_address_addresses_kaspa_address_utxos_get(kaspa_address) +> Get Utxos For Address + +Lists all open utxo for a given kaspa address + +### Parameters + +| Name | Type | Description | Required | Notes | +| ----------------- | ---------- | ------------------------------------------------------------------------------------------------ | ---------- | ----- | +| **kaspa_address** | **String** | Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73 | [required] | + +### Return type + +[**Vec**](UtxoResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_utxos_for_addresses_addresses_utxos_post + +> Vec get_utxos_for_addresses_addresses_utxos_post(utxo_request) +> Get Utxos For Addresses + +Lists all open utxo for a given kaspa address + +### Parameters + +| Name | Type | Description | Required | Notes | +| ---------------- | --------------------------------- | ----------- | ---------- | ----- | +| **utxo_request** | [**UtxoRequest**](UtxoRequest.md) | | [required] | + +### Return type + +[**Vec**](UtxoResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: application/json +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/KaspaBlocksApi.md b/dymension/libs/kaspa/lib/api/docs/KaspaBlocksApi.md new file mode 100644 index 00000000000..59aa2364350 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/KaspaBlocksApi.md @@ -0,0 +1,98 @@ +# \KaspaBlocksApi + +All URIs are relative to _http://localhost_ + +| Method | HTTP request | Description | +| -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | ------------------------- | +| [**get_block_blocks_block_id_get**](KaspaBlocksApi.md#get_block_blocks_block_id_get) | **GET** /blocks/{blockId} | Get Block | +| [**get_blocks_blocks_get**](KaspaBlocksApi.md#get_blocks_blocks_get) | **GET** /blocks | Get Blocks | +| [**get_blocks_from_bluescore_blocks_from_bluescore_get**](KaspaBlocksApi.md#get_blocks_from_bluescore_blocks_from_bluescore_get) | **GET** /blocks-from-bluescore | Get Blocks From Bluescore | + +## get_block_blocks_block_id_get + +> models::BlockModel get_block_blocks_block_id_get(block_id, include_transactions, include_color) +> Get Block + +Get block information for a given block id + +### Parameters + +| Name | Type | Description | Required | Notes | +| ------------------------ | ---------------- | ----------- | ---------- | ------------------ | +| **block_id** | **String** | | [required] | +| **include_transactions** | Option<**bool**> | | | [default to true] | +| **include_color** | Option<**bool**> | | | [default to false] | + +### Return type + +[**models::BlockModel**](BlockModel.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_blocks_blocks_get + +> models::BlockResponse get_blocks_blocks_get(low_hash, include_blocks, include_transactions) +> Get Blocks + +Lists block beginning from a low hash (block id). + +### Parameters + +| Name | Type | Description | Required | Notes | +| ------------------------ | ---------------- | ----------- | ---------- | ------------------ | +| **low_hash** | **String** | | [required] | +| **include_blocks** | Option<**bool**> | | | [default to false] | +| **include_transactions** | Option<**bool**> | | | [default to false] | + +### Return type + +[**models::BlockResponse**](BlockResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_blocks_from_bluescore_blocks_from_bluescore_get + +> Vec get_blocks_from_bluescore_blocks_from_bluescore_get(blue_score, include_transactions) +> Get Blocks From Bluescore + +Lists blocks of a given blueScore + +### Parameters + +| Name | Type | Description | Required | Notes | +| ------------------------ | ---------------- | ----------- | -------- | --------------------- | +| **blue_score** | Option<**i32**> | | | [default to 43679173] | +| **include_transactions** | Option<**bool**> | | | [default to false] | + +### Return type + +[**Vec**](BlockModel.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/KaspaNetworkInfoApi.md b/dymension/libs/kaspa/lib/api/docs/KaspaNetworkInfoApi.md new file mode 100644 index 00000000000..f0dea1b5779 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/KaspaNetworkInfoApi.md @@ -0,0 +1,454 @@ +# \KaspaNetworkInfoApi + +All URIs are relative to _http://localhost_ + +| Method | HTTP request | Description | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | -------------------------------------- | +| [**get_blockdag_info_blockdag_get**](KaspaNetworkInfoApi.md#get_blockdag_info_blockdag_get) | **GET** /info/blockdag | Get Blockdag | +| [**get_blockreward_info_blockreward_get**](KaspaNetworkInfoApi.md#get_blockreward_info_blockreward_get) | **GET** /info/blockreward | Get Blockreward | +| [**get_circulating_coins_info_coinsupply_circulating_get**](KaspaNetworkInfoApi.md#get_circulating_coins_info_coinsupply_circulating_get) | **GET** /info/coinsupply/circulating | Get Circulating Coins | +| [**get_coinsupply_info_coinsupply_get**](KaspaNetworkInfoApi.md#get_coinsupply_info_coinsupply_get) | **GET** /info/coinsupply | Get Coinsupply | +| [**get_fee_estimate_info_fee_estimate_get**](KaspaNetworkInfoApi.md#get_fee_estimate_info_fee_estimate_get) | **GET** /info/fee-estimate | Get Fee Estimate | +| [**get_halving_info_halving_get**](KaspaNetworkInfoApi.md#get_halving_info_halving_get) | **GET** /info/halving | Get Halving | +| [**get_hashrate_history_info_hashrate_history_get**](KaspaNetworkInfoApi.md#get_hashrate_history_info_hashrate_history_get) | **GET** /info/hashrate/history | Get Hashrate History | +| [**get_hashrate_info_hashrate_get**](KaspaNetworkInfoApi.md#get_hashrate_info_hashrate_get) | **GET** /info/hashrate | Get Hashrate | +| [**get_kaspad_info_info_kaspad_get**](KaspaNetworkInfoApi.md#get_kaspad_info_info_kaspad_get) | **GET** /info/kaspad | Get Kaspad Info | +| [**get_marketcap_info_marketcap_get**](KaspaNetworkInfoApi.md#get_marketcap_info_marketcap_get) | **GET** /info/marketcap | Get Marketcap | +| [**get_max_hashrate_info_hashrate_max_get**](KaspaNetworkInfoApi.md#get_max_hashrate_info_hashrate_max_get) | **GET** /info/hashrate/max | Get Max Hashrate | +| [**get_network_info_network_get**](KaspaNetworkInfoApi.md#get_network_info_network_get) | **GET** /info/network | Get Network | +| [**get_price_info_price_get**](KaspaNetworkInfoApi.md#get_price_info_price_get) | **GET** /info/price | Get Price | +| [**get_total_coins_info_coinsupply_total_get**](KaspaNetworkInfoApi.md#get_total_coins_info_coinsupply_total_get) | **GET** /info/coinsupply/total | Get Total Coins | +| [**get_virtual_selected_parent_blue_score_info_virtual_chain_blue_score_get**](KaspaNetworkInfoApi.md#get_virtual_selected_parent_blue_score_info_virtual_chain_blue_score_get) | **GET** /info/virtual-chain-blue-score | Get Virtual Selected Parent Blue Score | +| [**health_state_info_health_get**](KaspaNetworkInfoApi.md#health_state_info_health_get) | **GET** /info/health | Health State | + +## get_blockdag_info_blockdag_get + +> models::BlockdagResponse get_blockdag_info_blockdag_get() +> Get Blockdag + +Get Kaspa BlockDAG information + +### Parameters + +This endpoint does not need any parameter. + +### Return type + +[**models::BlockdagResponse**](BlockdagResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_blockreward_info_blockreward_get + +> models::ResponseGetBlockrewardInfoBlockrewardGet get_blockreward_info_blockreward_get(string_only) +> Get Blockreward + +Returns the current blockreward in KAS/block + +### Parameters + +| Name | Type | Description | Required | Notes | +| --------------- | ---------------- | ----------- | -------- | ------------------ | +| **string_only** | Option<**bool**> | | | [default to false] | + +### Return type + +[**models::ResponseGetBlockrewardInfoBlockrewardGet**](Response_Get_Blockreward_Info_Blockreward_Get.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_circulating_coins_info_coinsupply_circulating_get + +> String get_circulating_coins_info_coinsupply_circulating_get(in_billion) +> Get Circulating Coins + +Get circulating amount of $KAS token as numerical value + +### Parameters + +| Name | Type | Description | Required | Notes | +| -------------- | ---------------- | ----------- | -------- | ------------------ | +| **in_billion** | Option<**bool**> | | | [default to false] | + +### Return type + +**String** + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: text/plain, application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_coinsupply_info_coinsupply_get + +> models::CoinSupplyResponse get_coinsupply_info_coinsupply_get() +> Get Coinsupply + +Get $KAS coin supply information + +### Parameters + +This endpoint does not need any parameter. + +### Return type + +[**models::CoinSupplyResponse**](CoinSupplyResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_fee_estimate_info_fee_estimate_get + +> models::FeeEstimateResponse get_fee_estimate_info_fee_estimate_get() +> Get Fee Estimate + +Get fee estimate from Kaspad. For all buckets, feerate values represent fee/mass of a transaction in `sompi/gram` units.
Given a feerate value recommendation, calculate the required fee by taking the transaction mass and multiplying it by feerate: `fee = feerate * mass(tx)` + +### Parameters + +This endpoint does not need any parameter. + +### Return type + +[**models::FeeEstimateResponse**](FeeEstimateResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_halving_info_halving_get + +> models::ResponseGetHalvingInfoHalvingGet get_halving_info_halving_get(field) +> Get Halving + +Returns information about chromatic halving + +### Parameters + +| Name | Type | Description | Required | Notes | +| --------- | ------------------ | ----------- | -------- | ----- | +| **field** | Option<**String**> | | | + +### Return type + +[**models::ResponseGetHalvingInfoHalvingGet**](Response_Get_Halving_Info_Halving_Get.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_hashrate_history_info_hashrate_history_get + +> Vec get_hashrate_history_info_hashrate_history_get(resolution) +> Get Hashrate History + +Get historical hashrate samples with optional resolution (default = 1h) + +### Parameters + +| Name | Type | Description | Required | Notes | +| -------------- | ------------------ | ----------- | -------- | ----- | +| **resolution** | Option<**String**> | | | + +### Return type + +[**Vec**](HashrateHistoryResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_hashrate_info_hashrate_get + +> models::ResponseGetHashrateInfoHashrateGet get_hashrate_info_hashrate_get(string_only) +> Get Hashrate + +Returns the current hashrate for Kaspa network in TH/s. + +### Parameters + +| Name | Type | Description | Required | Notes | +| --------------- | ---------------- | ----------- | -------- | ------------------ | +| **string_only** | Option<**bool**> | | | [default to false] | + +### Return type + +[**models::ResponseGetHashrateInfoHashrateGet**](Response_Get_Hashrate_Info_Hashrate_Get.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_kaspad_info_info_kaspad_get + +> models::KaspadInfoResponse get_kaspad_info_info_kaspad_get() +> Get Kaspad Info + +Get some information for kaspad instance, which is currently connected. + +### Parameters + +This endpoint does not need any parameter. + +### Return type + +[**models::KaspadInfoResponse**](KaspadInfoResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_marketcap_info_marketcap_get + +> models::ResponseGetMarketcapInfoMarketcapGet get_marketcap_info_marketcap_get(string_only) +> Get Marketcap + +Get $KAS price and market cap. Price info is from coingecko.com + +### Parameters + +| Name | Type | Description | Required | Notes | +| --------------- | ---------------- | ----------- | -------- | ------------------ | +| **string_only** | Option<**bool**> | | | [default to false] | + +### Return type + +[**models::ResponseGetMarketcapInfoMarketcapGet**](Response_Get_Marketcap_Info_Marketcap_Get.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_max_hashrate_info_hashrate_max_get + +> models::MaxHashrateResponse get_max_hashrate_info_hashrate_max_get() +> Get Max Hashrate + +Returns the current hashrate for Kaspa network in TH/s. + +### Parameters + +This endpoint does not need any parameter. + +### Return type + +[**models::MaxHashrateResponse**](MaxHashrateResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_network_info_network_get + +> models::BlockdagResponse get_network_info_network_get() +> Get Network + +Alias for /info/blockdag + +### Parameters + +This endpoint does not need any parameter. + +### Return type + +[**models::BlockdagResponse**](BlockdagResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_price_info_price_get + +> models::ResponseGetPriceInfoPriceGet get_price_info_price_get(string_only) +> Get Price + +Returns the current price for Kaspa in USD. + +### Parameters + +| Name | Type | Description | Required | Notes | +| --------------- | ---------------- | ----------- | -------- | ------------------ | +| **string_only** | Option<**bool**> | | | [default to false] | + +### Return type + +[**models::ResponseGetPriceInfoPriceGet**](Response_Get_Price_Info_Price_Get.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_total_coins_info_coinsupply_total_get + +> String get_total_coins_info_coinsupply_total_get(in_billion) +> Get Total Coins + +Get total amount of $KAS token as numerical value + +### Parameters + +| Name | Type | Description | Required | Notes | +| -------------- | ---------------- | ----------- | -------- | ------------------ | +| **in_billion** | Option<**bool**> | | | [default to false] | + +### Return type + +**String** + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: text/plain, application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_virtual_selected_parent_blue_score_info_virtual_chain_blue_score_get + +> models::BlueScoreResponse get_virtual_selected_parent_blue_score_info_virtual_chain_blue_score_get() +> Get Virtual Selected Parent Blue Score + +Returns the blue score of the sink + +### Parameters + +This endpoint does not need any parameter. + +### Return type + +[**models::BlueScoreResponse**](BlueScoreResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## health_state_info_health_get + +> models::HealthResponse health_state_info_health_get() +> Health State + +Checks node and database health by comparing blue score and sync status. Returns health details or 503 if the database lags by ~10min or no nodes are synced. + +### Parameters + +This endpoint does not need any parameter. + +### Return type + +[**models::HealthResponse**](HealthResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/KaspaTransactionsApi.md b/dymension/libs/kaspa/lib/api/docs/KaspaTransactionsApi.md new file mode 100644 index 00000000000..3de3e6f78a0 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/KaspaTransactionsApi.md @@ -0,0 +1,157 @@ +# \KaspaTransactionsApi + +All URIs are relative to _http://localhost_ + +| Method | HTTP request | Description | +| ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | -------------------------- | +| [**calculate_transaction_mass_transactions_mass_post**](KaspaTransactionsApi.md#calculate_transaction_mass_transactions_mass_post) | **POST** /transactions/mass | Calculate Transaction Mass | +| [**get_transaction_acceptance_transactions_acceptance_post**](KaspaTransactionsApi.md#get_transaction_acceptance_transactions_acceptance_post) | **POST** /transactions/acceptance | Get Transaction Acceptance | +| [**get_transaction_transactions_transaction_id_get**](KaspaTransactionsApi.md#get_transaction_transactions_transaction_id_get) | **GET** /transactions/{transactionId} | Get Transaction | +| [**search_for_transactions_transactions_search_post**](KaspaTransactionsApi.md#search_for_transactions_transactions_search_post) | **POST** /transactions/search | Search For Transactions | +| [**submit_a_new_transaction_transactions_post**](KaspaTransactionsApi.md#submit_a_new_transaction_transactions_post) | **POST** /transactions | Submit A New Transaction | + +## calculate_transaction_mass_transactions_mass_post + +> models::TxMass calculate_transaction_mass_transactions_mass_post(submit_tx_model) +> Calculate Transaction Mass + +This function calculates and returns the mass of a transaction, which is essential for determining the minimum fee. The mass calculation takes into account the storage mass as defined in KIP-0009. Note: Be aware that if the transaction has a very low output amount or a high number of outputs, the mass can become significantly large. + +### Parameters + +| Name | Type | Description | Required | Notes | +| ------------------- | ------------------------------------- | ----------- | ---------- | ----- | +| **submit_tx_model** | [**SubmitTxModel**](SubmitTxModel.md) | | [required] | + +### Return type + +[**models::TxMass**](TxMass.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: application/json +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_transaction_acceptance_transactions_acceptance_post + +> Vec get_transaction_acceptance_transactions_acceptance_post(tx_acceptance_request) +> Get Transaction Acceptance + +Given a list of transaction_ids, return whether each one is accepted + +### Parameters + +| Name | Type | Description | Required | Notes | +| ------------------------- | ------------------------------------------------- | ----------- | ---------- | ----- | +| **tx_acceptance_request** | [**TxAcceptanceRequest**](TxAcceptanceRequest.md) | | [required] | + +### Return type + +[**Vec**](TxAcceptanceResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: application/json +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## get_transaction_transactions_transaction_id_get + +> models::TxModel get_transaction_transactions_transaction_id_get(transaction_id, block_hash, inputs, outputs, resolve_previous_outpoints) +> Get Transaction + +Get details for a given transaction id + +### Parameters + +| Name | Type | Description | Required | Notes | +| ------------------------------ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ----------------- | +| **transaction_id** | **String** | | [required] | +| **block_hash** | Option<**String**> | Specify a containing block (if known) for faster lookup | | +| **inputs** | Option<**bool**> | | | [default to true] | +| **outputs** | Option<**bool**> | | | [default to true] | +| **resolve_previous_outpoints** | Option<**String**> | Use this parameter if you want to fetch the TransactionInput previous outpoint details. Light fetches only the address and amount. Full fetches the whole TransactionOutput and adds it into each TxInput. | | [default to no] | + +### Return type + +[**models::TxModel**](TxModel.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: Not defined +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## search_for_transactions_transactions_search_post + +> Vec search_for_transactions_transactions_search_post(tx_search, fields, resolve_previous_outpoints, acceptance) +> Search For Transactions + +Search for transactions by transaction_ids or blue_score + +### Parameters + +| Name | Type | Description | Required | Notes | +| ------------------------------ | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | --------------- | +| **tx_search** | [**TxSearch**](TxSearch.md) | | [required] | +| **fields** | Option<**String**> | | | [default to ] | +| **resolve_previous_outpoints** | Option<[**models::PreviousOutpointLookupMode**](.md)> | Use this parameter if you want to fetch the TransactionInput previous outpoint details. Light fetches only the address and amount. Full fetches the whole TransactionOutput and adds it into each TxInput. | | [default to no] | +| **acceptance** | Option<[**models::AcceptanceMode**](.md)> | Only used when searching using transactionIds | | + +### Return type + +[**Vec**](TxModel.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: application/json +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +## submit_a_new_transaction_transactions_post + +> models::SubmitTransactionResponse submit_a_new_transaction_transactions_post(submit_transaction_request, replace_by_fee) +> Submit A New Transaction + +### Parameters + +| Name | Type | Description | Required | Notes | +| ------------------------------ | ----------------------------------------------------------- | ---------------------------------------------- | ---------- | ------------------ | +| **submit_transaction_request** | [**SubmitTransactionRequest**](SubmitTransactionRequest.md) | | [required] | +| **replace_by_fee** | Option<**bool**> | Replace an existing transaction in the mempool | | [default to false] | + +### Return type + +[**models::SubmitTransactionResponse**](SubmitTransactionResponse.md) + +### Authorization + +No authorization required + +### HTTP request headers + +- **Content-Type**: application/json +- **Accept**: application/json + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/KaspadInfoResponse.md b/dymension/libs/kaspa/lib/api/docs/KaspadInfoResponse.md new file mode 100644 index 00000000000..dd033cef29f --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/KaspadInfoResponse.md @@ -0,0 +1,13 @@ +# KaspadInfoResponse + +## Properties + +| Name | Type | Description | Notes | +| ------------------- | ------------------ | ----------- | --------------------------------------------------------------------------------------- | +| **mempool_size** | Option<**String**> | | [optional][default to 1] | +| **server_version** | Option<**String**> | | [optional][default to 0.12.2] | +| **is_utxo_indexed** | Option<**bool**> | | [optional][default to true] | +| **is_synced** | Option<**bool**> | | [optional][default to true] | +| **p2p_id_hashed** | Option<**String**> | | [optional][default to 36a17cd8644eef34fc7fe4719655e06dbdf117008900c46975e66c35acd09b01] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/KaspadResponse.md b/dymension/libs/kaspa/lib/api/docs/KaspadResponse.md new file mode 100644 index 00000000000..b99aafc10ed --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/KaspadResponse.md @@ -0,0 +1,14 @@ +# KaspadResponse + +## Properties + +| Name | Type | Description | Notes | +| ------------------- | ------------------ | ----------- | ------------------------------ | +| **kaspad_host** | Option<**String**> | | [optional] | +| **server_version** | Option<**String**> | | [optional][default to 0.12.6] | +| **is_utxo_indexed** | Option<**bool**> | | [optional][default to true] | +| **is_synced** | Option<**bool**> | | [optional][default to true] | +| **p2p_id** | Option<**String**> | | [optional][default to 1231312] | +| **blue_score** | Option<**i32**> | | [optional][default to 0] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/MarketCapResponse.md b/dymension/libs/kaspa/lib/api/docs/MarketCapResponse.md new file mode 100644 index 00000000000..3b0a1a8e4d3 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/MarketCapResponse.md @@ -0,0 +1,9 @@ +# MarketCapResponse + +## Properties + +| Name | Type | Description | Notes | +| ------------- | --------------- | ----------- | ------------------------------- | +| **marketcap** | Option<**i32**> | | [optional][default to 12000132] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/MaxHashrateResponse.md b/dymension/libs/kaspa/lib/api/docs/MaxHashrateResponse.md new file mode 100644 index 00000000000..42a5f8708e4 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/MaxHashrateResponse.md @@ -0,0 +1,10 @@ +# MaxHashrateResponse + +## Properties + +| Name | Type | Description | Notes | +| --------------- | -------------------------------------------------------------------------------------- | ----------- | ------------------------------- | +| **hashrate** | Option<**f64**> | | [optional][default to 12000132] | +| **blockheader** | [**models::EndpointsGetHashrateBlockHeader**](endpoints__get_hashrate__BlockHeader.md) | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/OutpointModel.md b/dymension/libs/kaspa/lib/api/docs/OutpointModel.md new file mode 100644 index 00000000000..23143ee92c2 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/OutpointModel.md @@ -0,0 +1,10 @@ +# OutpointModel + +## Properties + +| Name | Type | Description | Notes | +| ------------------ | ------------------ | ----------- | --------------------------------------------------------------------------------------- | +| **transaction_id** | Option<**String**> | | [optional][default to ef62efbc2825d3ef9ec1cf9b80506876ac077b64b11a39c8ef5e028415444dc9] | +| **index** | Option<**i32**> | | [optional][default to 0] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/ParentHashModel.md b/dymension/libs/kaspa/lib/api/docs/ParentHashModel.md new file mode 100644 index 00000000000..ca35a806ef6 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/ParentHashModel.md @@ -0,0 +1,9 @@ +# ParentHashModel + +## Properties + +| Name | Type | Description | Notes | +| ----------------- | ----------------------- | ----------- | ---------------------------------------------------------------------------------------- | +| **parent_hashes** | Option<**Vec**> | | [optional]default to [580f65c8da9d436480817f6bd7c13eecd9223b37f0d34ae42fb17e1e9fda397e]] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/PreviousOutpointLookupMode.md b/dymension/libs/kaspa/lib/api/docs/PreviousOutpointLookupMode.md new file mode 100644 index 00000000000..9edd1f36020 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/PreviousOutpointLookupMode.md @@ -0,0 +1,11 @@ +# PreviousOutpointLookupMode + +## Enum Variants + +| Name | Value | +| ----- | ----- | +| No | no | +| Light | light | +| Full | full | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/PriceResponse.md b/dymension/libs/kaspa/lib/api/docs/PriceResponse.md new file mode 100644 index 00000000000..c8ab5b55a96 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/PriceResponse.md @@ -0,0 +1,9 @@ +# PriceResponse + +## Properties + +| Name | Type | Description | Notes | +| --------- | --------------- | ----------- | ------------------------------- | +| **price** | Option<**f64**> | | [optional][default to 0.025235] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/ResponseGetBlockrewardInfoBlockrewardGet.md b/dymension/libs/kaspa/lib/api/docs/ResponseGetBlockrewardInfoBlockrewardGet.md new file mode 100644 index 00000000000..6ec79bb426e --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/ResponseGetBlockrewardInfoBlockrewardGet.md @@ -0,0 +1,9 @@ +# ResponseGetBlockrewardInfoBlockrewardGet + +## Properties + +| Name | Type | Description | Notes | +| --------------- | --------------- | ----------- | ------------------------------- | +| **blockreward** | Option<**f64**> | | [optional][default to 12000132] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/ResponseGetHalvingInfoHalvingGet.md b/dymension/libs/kaspa/lib/api/docs/ResponseGetHalvingInfoHalvingGet.md new file mode 100644 index 00000000000..34be5980adb --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/ResponseGetHalvingInfoHalvingGet.md @@ -0,0 +1,11 @@ +# ResponseGetHalvingInfoHalvingGet + +## Properties + +| Name | Type | Description | Notes | +| -------------------------- | ------------------ | ----------- | ---------------------------------------------- | +| **next_halving_timestamp** | Option<**i32**> | | [optional][default to 1662837270000] | +| **next_halving_date** | Option<**String**> | | [optional][default to 2022-09-10 19:38:52 UTC] | +| **next_halving_amount** | Option<**f64**> | | [optional][default to 155.123123] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/ResponseGetHashrateInfoHashrateGet.md b/dymension/libs/kaspa/lib/api/docs/ResponseGetHashrateInfoHashrateGet.md new file mode 100644 index 00000000000..de09ac66f62 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/ResponseGetHashrateInfoHashrateGet.md @@ -0,0 +1,9 @@ +# ResponseGetHashrateInfoHashrateGet + +## Properties + +| Name | Type | Description | Notes | +| ------------ | --------------- | ----------- | ------------------------------- | +| **hashrate** | Option<**f64**> | | [optional][default to 12000132] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/ResponseGetMarketcapInfoMarketcapGet.md b/dymension/libs/kaspa/lib/api/docs/ResponseGetMarketcapInfoMarketcapGet.md new file mode 100644 index 00000000000..8a22410c086 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/ResponseGetMarketcapInfoMarketcapGet.md @@ -0,0 +1,9 @@ +# ResponseGetMarketcapInfoMarketcapGet + +## Properties + +| Name | Type | Description | Notes | +| ------------- | --------------- | ----------- | ------------------------------- | +| **marketcap** | Option<**i32**> | | [optional][default to 12000132] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/ResponseGetPriceInfoPriceGet.md b/dymension/libs/kaspa/lib/api/docs/ResponseGetPriceInfoPriceGet.md new file mode 100644 index 00000000000..c2c8d562a74 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/ResponseGetPriceInfoPriceGet.md @@ -0,0 +1,9 @@ +# ResponseGetPriceInfoPriceGet + +## Properties + +| Name | Type | Description | Notes | +| --------- | --------------- | ----------- | ------------------------------- | +| **price** | Option<**f64**> | | [optional][default to 0.025235] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/ScriptPublicKeyModel.md b/dymension/libs/kaspa/lib/api/docs/ScriptPublicKeyModel.md new file mode 100644 index 00000000000..bef73d10412 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/ScriptPublicKeyModel.md @@ -0,0 +1,9 @@ +# ScriptPublicKeyModel + +## Properties + +| Name | Type | Description | Notes | +| --------------------- | ------------------ | ----------- | ------------------------------------------------------------------------------------------- | +| **script_public_key** | Option<**String**> | | [optional][default to 20c5629ce85f6618cd3ed1ac1c99dc6d3064ed244013555c51385d9efab0d0072fac] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/SubmitTransactionRequest.md b/dymension/libs/kaspa/lib/api/docs/SubmitTransactionRequest.md new file mode 100644 index 00000000000..944a6a98c2b --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/SubmitTransactionRequest.md @@ -0,0 +1,10 @@ +# SubmitTransactionRequest + +## Properties + +| Name | Type | Description | Notes | +| ---------------- | --------------------------------------------- | ----------- | ---------------------------- | +| **transaction** | [**models::SubmitTxModel**](SubmitTxModel.md) | | +| **allow_orphan** | Option<**bool**> | | [optional][default to false] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/SubmitTransactionResponse.md b/dymension/libs/kaspa/lib/api/docs/SubmitTransactionResponse.md new file mode 100644 index 00000000000..bba6a8cb60f --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/SubmitTransactionResponse.md @@ -0,0 +1,10 @@ +# SubmitTransactionResponse + +## Properties + +| Name | Type | Description | Notes | +| ------------------ | ------------------ | ----------- | ---------- | +| **transaction_id** | Option<**String**> | | [optional] | +| **error** | Option<**String**> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/SubmitTxInput.md b/dymension/libs/kaspa/lib/api/docs/SubmitTxInput.md new file mode 100644 index 00000000000..1ba0ad8a2fc --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/SubmitTxInput.md @@ -0,0 +1,12 @@ +# SubmitTxInput + +## Properties + +| Name | Type | Description | Notes | +| --------------------- | --------------------------------------------------- | ----------- | ----- | +| **previous_outpoint** | [**models::SubmitTxOutpoint**](SubmitTxOutpoint.md) | | +| **signature_script** | **String** | | +| **sequence** | **i32** | | +| **sig_op_count** | **i32** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/SubmitTxModel.md b/dymension/libs/kaspa/lib/api/docs/SubmitTxModel.md new file mode 100644 index 00000000000..93466c37c04 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/SubmitTxModel.md @@ -0,0 +1,13 @@ +# SubmitTxModel + +## Properties + +| Name | Type | Description | Notes | +| ----------------- | ---------------------------------------------------- | ----------- | ------------------------ | +| **version** | **i32** | | +| **inputs** | [**Vec**](SubmitTxInput.md) | | +| **outputs** | [**Vec**](SubmitTxOutput.md) | | +| **lock_time** | Option<**i32**> | | [optional][default to 0] | +| **subnetwork_id** | Option<**String**> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/SubmitTxOutpoint.md b/dymension/libs/kaspa/lib/api/docs/SubmitTxOutpoint.md new file mode 100644 index 00000000000..18b3775399b --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/SubmitTxOutpoint.md @@ -0,0 +1,10 @@ +# SubmitTxOutpoint + +## Properties + +| Name | Type | Description | Notes | +| ------------------ | ---------- | ----------- | ----- | +| **transaction_id** | **String** | | +| **index** | **i32** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/SubmitTxOutput.md b/dymension/libs/kaspa/lib/api/docs/SubmitTxOutput.md new file mode 100644 index 00000000000..4e7a9097f02 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/SubmitTxOutput.md @@ -0,0 +1,10 @@ +# SubmitTxOutput + +## Properties + +| Name | Type | Description | Notes | +| --------------------- | ----------------------------------------------------------------- | ----------- | ----- | +| **amount** | **i32** | | +| **script_public_key** | [**models::SubmitTxScriptPublicKey**](SubmitTxScriptPublicKey.md) | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/SubmitTxScriptPublicKey.md b/dymension/libs/kaspa/lib/api/docs/SubmitTxScriptPublicKey.md new file mode 100644 index 00000000000..5933afdf438 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/SubmitTxScriptPublicKey.md @@ -0,0 +1,10 @@ +# SubmitTxScriptPublicKey + +## Properties + +| Name | Type | Description | Notes | +| --------------------- | ---------- | ----------- | ----- | +| **version** | **i32** | | +| **script_public_key** | **String** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/TransactionCount.md b/dymension/libs/kaspa/lib/api/docs/TransactionCount.md new file mode 100644 index 00000000000..82ae4db952a --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/TransactionCount.md @@ -0,0 +1,10 @@ +# TransactionCount + +## Properties + +| Name | Type | Description | Notes | +| ------------------ | -------- | ----------- | ----- | +| **total** | **i32** | | +| **limit_exceeded** | **bool** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/TxAcceptanceRequest.md b/dymension/libs/kaspa/lib/api/docs/TxAcceptanceRequest.md new file mode 100644 index 00000000000..6f6b1d6583c --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/TxAcceptanceRequest.md @@ -0,0 +1,9 @@ +# TxAcceptanceRequest + +## Properties + +| Name | Type | Description | Notes | +| ------------------- | ----------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **transaction_ids** | Option<**Vec**> | | [optional]default to [b9382bdee4aa364acf73eda93914eaae61d0e78334d1b8a637ab89ef5e224e41, 1e098b3830c994beb28768f7924a38286cec16e85e9757e0dc3574b85f624c34, 6dd5ee1add449c60d2c2b9545a8dfca7cfbc9a29ce2d3f3ec8bbde14dd97610d]] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/TxAcceptanceResponse.md b/dymension/libs/kaspa/lib/api/docs/TxAcceptanceResponse.md new file mode 100644 index 00000000000..75ccb774950 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/TxAcceptanceResponse.md @@ -0,0 +1,10 @@ +# TxAcceptanceResponse + +## Properties + +| Name | Type | Description | Notes | +| ------------------ | ------------------ | ----------- | --------------------------------------------------------------------------------------- | +| **transaction_id** | Option<**String**> | | [optional][default to b9382bdee4aa364acf73eda93914eaae61d0e78334d1b8a637ab89ef5e224e41] | +| **accepted** | **bool** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/TxIdResponse.md b/dymension/libs/kaspa/lib/api/docs/TxIdResponse.md new file mode 100644 index 00000000000..0da950506f2 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/TxIdResponse.md @@ -0,0 +1,10 @@ +# TxIdResponse + +## Properties + +| Name | Type | Description | Notes | +| ----------- | ---------- | ----------- | ----- | +| **address** | **String** | | +| **active** | **bool** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/TxInput.md b/dymension/libs/kaspa/lib/api/docs/TxInput.md new file mode 100644 index 00000000000..f061254adbf --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/TxInput.md @@ -0,0 +1,17 @@ +# TxInput + +## Properties + +| Name | Type | Description | Notes | +| ------------------------------ | ------------------------------------------- | ----------- | ---------- | +| **transaction_id** | **String** | | +| **index** | **i32** | | +| **previous_outpoint_hash** | **String** | | +| **previous_outpoint_index** | **String** | | +| **previous_outpoint_resolved** | Option<[**models::TxOutput**](TxOutput.md)> | | [optional] | +| **previous_outpoint_address** | Option<**String**> | | [optional] | +| **previous_outpoint_amount** | Option<**i32**> | | [optional] | +| **signature_script** | Option<**String**> | | [optional] | +| **sig_op_count** | Option<**String**> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/TxMass.md b/dymension/libs/kaspa/lib/api/docs/TxMass.md new file mode 100644 index 00000000000..2f20b1b809c --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/TxMass.md @@ -0,0 +1,11 @@ +# TxMass + +## Properties + +| Name | Type | Description | Notes | +| ---------------- | ------- | ----------- | ----- | +| **mass** | **i32** | | +| **storage_mass** | **i32** | | +| **compute_mass** | **i32** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/TxModel.md b/dymension/libs/kaspa/lib/api/docs/TxModel.md new file mode 100644 index 00000000000..1008db96027 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/TxModel.md @@ -0,0 +1,21 @@ +# TxModel + +## Properties + +| Name | Type | Description | Notes | +| ------------------------------ | ------------------------------------------------ | ----------- | ---------- | +| **subnetwork_id** | Option<**String**> | | [optional] | +| **transaction_id** | Option<**String**> | | [optional] | +| **hash** | Option<**String**> | | [optional] | +| **mass** | Option<**String**> | | [optional] | +| **payload** | Option<**String**> | | [optional] | +| **block_hash** | Option<**Vec**> | | [optional] | +| **block_time** | Option<**i32**> | | [optional] | +| **is_accepted** | Option<**bool**> | | [optional] | +| **accepting_block_hash** | Option<**String**> | | [optional] | +| **accepting_block_blue_score** | Option<**i32**> | | [optional] | +| **accepting_block_time** | Option<**i32**> | | [optional] | +| **inputs** | Option<[**Vec**](TxInput.md)> | | [optional] | +| **outputs** | Option<[**Vec**](TxOutput.md)> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/TxOutput.md b/dymension/libs/kaspa/lib/api/docs/TxOutput.md new file mode 100644 index 00000000000..c9ede6d27a0 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/TxOutput.md @@ -0,0 +1,15 @@ +# TxOutput + +## Properties + +| Name | Type | Description | Notes | +| ----------------------------- | ------------------ | ----------- | ---------- | +| **transaction_id** | **String** | | +| **index** | **i32** | | +| **amount** | **i32** | | +| **script_public_key** | Option<**String**> | | [optional] | +| **script_public_key_address** | Option<**String**> | | [optional] | +| **script_public_key_type** | Option<**String**> | | [optional] | +| **accepting_block_hash** | Option<**String**> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/TxSearch.md b/dymension/libs/kaspa/lib/api/docs/TxSearch.md new file mode 100644 index 00000000000..5aca2ca080d --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/TxSearch.md @@ -0,0 +1,10 @@ +# TxSearch + +## Properties + +| Name | Type | Description | Notes | +| ------------------------- | --------------------------------------------------------------------------------- | ----------- | ---------- | +| **transaction_ids** | Option<**Vec**> | | [optional] | +| **accepting_blue_scores** | Option<[**models::TxSearchAcceptingBlueScores**](TxSearchAcceptingBlueScores.md)> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/TxSearchAcceptingBlueScores.md b/dymension/libs/kaspa/lib/api/docs/TxSearchAcceptingBlueScores.md new file mode 100644 index 00000000000..7c8770c4d02 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/TxSearchAcceptingBlueScores.md @@ -0,0 +1,10 @@ +# TxSearchAcceptingBlueScores + +## Properties + +| Name | Type | Description | Notes | +| ------- | ------- | ----------- | ----- | +| **gte** | **i32** | | +| **lt** | **i32** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/UtxoModel.md b/dymension/libs/kaspa/lib/api/docs/UtxoModel.md new file mode 100644 index 00000000000..2a6f5613393 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/UtxoModel.md @@ -0,0 +1,12 @@ +# UtxoModel + +## Properties + +| Name | Type | Description | Notes | +| --------------------- | ----------------------------------------------------------- | ----------- | ----------------------------------- | +| **amount** | Option<**String**> | | [optional]default to [11501593788]] | +| **script_public_key** | [**models::ScriptPublicKeyModel**](ScriptPublicKeyModel.md) | | +| **block_daa_score** | Option<**String**> | | [optional][default to 18867232] | +| **is_coinbase** | Option<**bool**> | | [optional][default to false] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/UtxoRequest.md b/dymension/libs/kaspa/lib/api/docs/UtxoRequest.md new file mode 100644 index 00000000000..804d73e62dc --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/UtxoRequest.md @@ -0,0 +1,9 @@ +# UtxoRequest + +## Properties + +| Name | Type | Description | Notes | +| ------------- | ----------------------- | ----------- | ------------------------------------------------------------------------------------------- | +| **addresses** | Option<**Vec**> | | [optional]default to [kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73]] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/UtxoResponse.md b/dymension/libs/kaspa/lib/api/docs/UtxoResponse.md new file mode 100644 index 00000000000..df287251f8f --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/UtxoResponse.md @@ -0,0 +1,11 @@ +# UtxoResponse + +## Properties + +| Name | Type | Description | Notes | +| -------------- | --------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------ | +| **address** | Option<**String**> | | [optional][default to kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73] | +| **outpoint** | [**models::OutpointModel**](OutpointModel.md) | | +| **utxo_entry** | [**models::UtxoModel**](UtxoModel.md) | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/ValidationError.md b/dymension/libs/kaspa/lib/api/docs/ValidationError.md new file mode 100644 index 00000000000..02bc6036a18 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/ValidationError.md @@ -0,0 +1,11 @@ +# ValidationError + +## Properties + +| Name | Type | Description | Notes | +| ---------- | ------------------------------------------------------------------------ | ----------- | ----- | +| **loc** | [**Vec**](ValidationError_loc_inner.md) | | +| **msg** | **String** | | +| **r#type** | **String** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/ValidationErrorLocInner.md b/dymension/libs/kaspa/lib/api/docs/ValidationErrorLocInner.md new file mode 100644 index 00000000000..a808b5f357d --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/ValidationErrorLocInner.md @@ -0,0 +1,8 @@ +# ValidationErrorLocInner + +## Properties + +| Name | Type | Description | Notes | +| ---- | ---- | ----------- | ----- | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/VcBlockModel.md b/dymension/libs/kaspa/lib/api/docs/VcBlockModel.md new file mode 100644 index 00000000000..e8df2b5cfa4 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/VcBlockModel.md @@ -0,0 +1,13 @@ +# VcBlockModel + +## Properties + +| Name | Type | Description | Notes | +| ---------------- | -------------------------------------------------- | ----------- | ---------- | +| **hash** | **String** | | +| **blue_score** | **i32** | | +| **daa_score** | Option<**i32**> | | [optional] | +| **timestamp** | Option<**i32**> | | [optional] | +| **transactions** | Option<[**Vec**](VcTxModel.md)> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/VcTxInput.md b/dymension/libs/kaspa/lib/api/docs/VcTxInput.md new file mode 100644 index 00000000000..b0d64da1181 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/VcTxInput.md @@ -0,0 +1,13 @@ +# VcTxInput + +## Properties + +| Name | Type | Description | Notes | +| ----------------------------- | ------------------ | ----------- | ---------- | +| **previous_outpoint_hash** | **String** | | +| **previous_outpoint_index** | **i32** | | +| **previous_outpoint_script** | Option<**String**> | | [optional] | +| **previous_outpoint_address** | Option<**String**> | | [optional] | +| **previous_outpoint_amount** | Option<**i32**> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/VcTxModel.md b/dymension/libs/kaspa/lib/api/docs/VcTxModel.md new file mode 100644 index 00000000000..6cd159b6cc3 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/VcTxModel.md @@ -0,0 +1,12 @@ +# VcTxModel + +## Properties + +| Name | Type | Description | Notes | +| ------------------ | ---------------------------------------------------- | ----------- | --------------------------- | +| **transaction_id** | **String** | | +| **is_accepted** | Option<**bool**> | | [optional][default to true] | +| **inputs** | Option<[**Vec**](VcTxInput.md)> | | [optional] | +| **outputs** | Option<[**Vec**](VcTxOutput.md)> | | [optional] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/VcTxOutput.md b/dymension/libs/kaspa/lib/api/docs/VcTxOutput.md new file mode 100644 index 00000000000..1fa015c05c5 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/VcTxOutput.md @@ -0,0 +1,11 @@ +# VcTxOutput + +## Properties + +| Name | Type | Description | Notes | +| ----------------------------- | ---------- | ----------- | ----- | +| **script_public_key** | **String** | | +| **script_public_key_address** | **String** | | +| **amount** | **i32** | | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/docs/VerboseDataModel.md b/dymension/libs/kaspa/lib/api/docs/VerboseDataModel.md new file mode 100644 index 00000000000..2cd5e6bdf60 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/docs/VerboseDataModel.md @@ -0,0 +1,17 @@ +# VerboseDataModel + +## Properties + +| Name | Type | Description | Notes | +| -------------------------- | ----------------------- | ----------- | ---------------------------------------------------------------------------------------- | +| **hash** | Option<**String**> | | [optional][default to 18c7afdf8f447ca06adb8b4946dc45f5feb1188c7d177da6094dfbc760eca699] | +| **difficulty** | Option<**f64**> | | [optional]default to [4.10220452325294E12]] | +| **selected_parent_hash** | Option<**String**> | | [optional][default to 580f65c8da9d436480817f6bd7c13eecd9223b37f0d34ae42fb17e1e9fda397e] | +| **transaction_ids** | Option<**Vec**> | | [optional]default to [533f8314bf772259fe517f53507a79ebe61c8c6a11748d93a0835551233b3311]] | +| **blue_score** | Option<**String**> | | [optional][default to 18483232] | +| **children_hashes** | Option<**Vec**> | | [optional] | +| **merge_set_blues_hashes** | Option<**Vec**> | | [optional]default to []] | +| **merge_set_reds_hashes** | Option<**Vec**> | | [optional]default to []] | +| **is_chain_block** | Option<**bool**> | | [optional][default to false] | + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/dymension/libs/kaspa/lib/api/git_push.sh b/dymension/libs/kaspa/lib/api/git_push.sh new file mode 100644 index 00000000000..f53a75d4fab --- /dev/null +++ b/dymension/libs/kaspa/lib/api/git_push.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 +git_host=$4 + +if [ "$git_host" = "" ]; then + git_host="github.com" + echo "[INFO] No command line input provided. Set \$git_host to $git_host" +fi + +if [ "$git_user_id" = "" ]; then + git_user_id="GIT_USER_ID" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="GIT_REPO_ID" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="Minor update" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=$(git remote) +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' diff --git a/dymension/libs/kaspa/lib/api/src/apis/configuration.rs b/dymension/libs/kaspa/lib/api/src/apis/configuration.rs new file mode 100644 index 00000000000..178d3a2e499 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/apis/configuration.rs @@ -0,0 +1,48 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +#[derive(Debug, Clone)] +pub struct Configuration { + pub base_path: String, + pub user_agent: Option, + pub client: reqwest_middleware::ClientWithMiddleware, + pub basic_auth: Option, + pub oauth_access_token: Option, + pub bearer_access_token: Option, + pub api_key: Option, +} + +pub type BasicAuth = (String, Option); + +#[derive(Debug, Clone)] +pub struct ApiKey { + pub prefix: Option, + pub key: String, +} + +impl Configuration { + pub fn new() -> Configuration { + Configuration::default() + } +} + +impl Default for Configuration { + fn default() -> Self { + Configuration { + base_path: "http://localhost".to_owned(), + user_agent: Some("OpenAPI-Generator/a6a9569/rust".to_owned()), + client: reqwest_middleware::ClientBuilder::new(reqwest::Client::new()).build(), + basic_auth: None, + oauth_access_token: None, + bearer_access_token: None, + api_key: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/apis/experimental_kaspa_virtual_chain_api.rs b/dymension/libs/kaspa/lib/api/src/apis/experimental_kaspa_virtual_chain_api.rs new file mode 100644 index 00000000000..aed16cd7add --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/apis/experimental_kaspa_virtual_chain_api.rs @@ -0,0 +1,84 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use super::{configuration, ContentType, Error}; +use crate::{apis::ResponseContent, models}; +use reqwest; +use serde::{de::Error as _, Deserialize, Serialize}; + +/// struct for passing parameters to the method [`get_virtual_chain_transactions_virtual_chain_get`] +#[derive(Clone, Debug)] +pub struct GetVirtualChainTransactionsVirtualChainGetParams { + /// Divisible by limit + pub blue_score_gte: i64, + pub limit: Option, + pub resolve_inputs: Option, + pub include_coinbase: Option, +} + +/// struct for typed errors of method [`get_virtual_chain_transactions_virtual_chain_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetVirtualChainTransactionsVirtualChainGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// EXPERIMENTAL - EXPECT BREAKING CHANGES: Get virtual chain transactions by blue score. +pub async fn get_virtual_chain_transactions_virtual_chain_get( + configuration: &configuration::Configuration, + params: GetVirtualChainTransactionsVirtualChainGetParams, +) -> Result, Error> { + let uri_str = format!("{}/virtual-chain", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + req_builder = req_builder.query(&[("blueScoreGte", ¶ms.blue_score_gte.to_string())]); + if let Some(ref param_value) = params.limit { + req_builder = req_builder.query(&[("limit", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.resolve_inputs { + req_builder = req_builder.query(&[("resolveInputs", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.include_coinbase { + req_builder = req_builder.query(&[("includeCoinbase", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `Vec<models::VcBlockModel>`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `Vec<models::VcBlockModel>`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} diff --git a/dymension/libs/kaspa/lib/api/src/apis/kaspa_addresses_api.rs b/dymension/libs/kaspa/lib/api/src/apis/kaspa_addresses_api.rs new file mode 100644 index 00000000000..4fad559535d --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/apis/kaspa_addresses_api.rs @@ -0,0 +1,673 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use super::{configuration, ContentType, Error}; +use crate::{apis::ResponseContent, models}; +use reqwest; +use serde::{de::Error as _, Deserialize, Serialize}; + +/// struct for passing parameters to the method [`get_addresses_active_addresses_active_post`] +#[derive(Clone, Debug)] +pub struct GetAddressesActiveAddressesActivePostParams { + pub addresses_active_request: models::AddressesActiveRequest, +} + +/// struct for passing parameters to the method [`get_balance_from_kaspa_address_addresses_kaspa_address_balance_get`] +#[derive(Clone, Debug)] +pub struct GetBalanceFromKaspaAddressAddressesKaspaAddressBalanceGetParams { + /// Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73 + pub kaspa_address: String, +} + +/// struct for passing parameters to the method [`get_balances_from_kaspa_addresses_addresses_balances_post`] +#[derive(Clone, Debug)] +pub struct GetBalancesFromKaspaAddressesAddressesBalancesPostParams { + pub balance_request: models::BalanceRequest, +} + +/// struct for passing parameters to the method [`get_full_transactions_for_address_addresses_kaspa_address_full_transactions_get`] +#[derive(Clone, Debug)] +pub struct GetFullTransactionsForAddressAddressesKaspaAddressFullTransactionsGetParams { + /// Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73 + pub kaspa_address: String, + /// The number of records to get + pub limit: Option, + /// The offset from which to get records + pub offset: Option, + pub fields: Option, + /// Use this parameter if you want to fetch the TransactionInput previous outpoint details. Light fetches only the adress and amount. Full fetches the whole TransactionOutput and adds it into each TxInput. + pub resolve_previous_outpoints: Option, +} + +/// struct for passing parameters to the method [`get_full_transactions_for_address_page_addresses_kaspa_address_full_transactions_page_get`] +#[derive(Clone, Debug)] +pub struct GetFullTransactionsForAddressPageAddressesKaspaAddressFullTransactionsPageGetParams { + /// Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73 + pub kaspa_address: String, + /// The max number of records to get. For paging combine with using 'before/after' from oldest previous result. Use value of X-Next-Page-Before/-After as long as header is present to continue paging. The actual number of transactions returned for each page can be > limit. + pub limit: Option, + /// Only include transactions with block time before this (epoch-millis) + pub before: Option, + /// Only include transactions with block time after this (epoch-millis) + pub after: Option, + pub fields: Option, + /// Use this parameter if you want to fetch the TransactionInput previous outpoint details. Light fetches only the adress and amount. Full fetches the whole TransactionOutput and adds it into each TxInput. + pub resolve_previous_outpoints: Option, + pub acceptance: Option, +} + +/// struct for passing parameters to the method [`get_name_for_address_addresses_kaspa_address_name_get`] +#[derive(Clone, Debug)] +pub struct GetNameForAddressAddressesKaspaAddressNameGetParams { + /// Kaspa address as string e.g. kaspa:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkx9awp4e + pub kaspa_address: String, +} + +/// struct for passing parameters to the method [`get_transaction_count_for_address_addresses_kaspa_address_transactions_count_get`] +#[derive(Clone, Debug)] +pub struct GetTransactionCountForAddressAddressesKaspaAddressTransactionsCountGetParams { + /// Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73 + pub kaspa_address: String, +} + +/// struct for passing parameters to the method [`get_utxos_for_address_addresses_kaspa_address_utxos_get`] +#[derive(Clone, Debug)] +pub struct GetUtxosForAddressAddressesKaspaAddressUtxosGetParams { + /// Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73 + pub kaspa_address: String, +} + +/// struct for passing parameters to the method [`get_utxos_for_addresses_addresses_utxos_post`] +#[derive(Clone, Debug)] +pub struct GetUtxosForAddressesAddressesUtxosPostParams { + pub utxo_request: models::UtxoRequest, +} + +/// struct for typed errors of method [`get_addresses_active_addresses_active_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetAddressesActiveAddressesActivePostError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_addresses_names_addresses_names_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetAddressesNamesAddressesNamesGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_balance_from_kaspa_address_addresses_kaspa_address_balance_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetBalanceFromKaspaAddressAddressesKaspaAddressBalanceGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_balances_from_kaspa_addresses_addresses_balances_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetBalancesFromKaspaAddressesAddressesBalancesPostError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_full_transactions_for_address_addresses_kaspa_address_full_transactions_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetFullTransactionsForAddressAddressesKaspaAddressFullTransactionsGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_full_transactions_for_address_page_addresses_kaspa_address_full_transactions_page_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetFullTransactionsForAddressPageAddressesKaspaAddressFullTransactionsPageGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_name_for_address_addresses_kaspa_address_name_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetNameForAddressAddressesKaspaAddressNameGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_transaction_count_for_address_addresses_kaspa_address_transactions_count_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetTransactionCountForAddressAddressesKaspaAddressTransactionsCountGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_utxos_for_address_addresses_kaspa_address_utxos_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetUtxosForAddressAddressesKaspaAddressUtxosGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_utxos_for_addresses_addresses_utxos_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetUtxosForAddressesAddressesUtxosPostError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// This endpoint checks if addresses have had any transaction activity in the past. It is specifically designed for HD Wallets to verify historical address activity. +pub async fn get_addresses_active_addresses_active_post( + configuration: &configuration::Configuration, + params: GetAddressesActiveAddressesActivePostParams, +) -> Result, Error> { + let uri_str = format!("{}/addresses/active", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(¶ms.addresses_active_request); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `Vec<models::TxIdResponse>`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `Vec<models::TxIdResponse>`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Get the name for an address +pub async fn get_addresses_names_addresses_names_get( + configuration: &configuration::Configuration, +) -> Result, Error> { + let uri_str = format!("{}/addresses/names", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `Vec<models::AddressName>`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `Vec<models::AddressName>`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Get balance for a given kaspa address +pub async fn get_balance_from_kaspa_address_addresses_kaspa_address_balance_get( + configuration: &configuration::Configuration, + params: GetBalanceFromKaspaAddressAddressesKaspaAddressBalanceGetParams, +) -> Result< + models::BalanceResponse, + Error, +> { + let uri_str = format!( + "{}/addresses/{kaspaAddress}/balance", + configuration.base_path, + kaspaAddress = crate::apis::urlencode(params.kaspa_address) + ); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::BalanceResponse`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::BalanceResponse`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Get balances for multiple kaspa addresses +pub async fn get_balances_from_kaspa_addresses_addresses_balances_post( + configuration: &configuration::Configuration, + params: GetBalancesFromKaspaAddressesAddressesBalancesPostParams, +) -> Result< + Vec, + Error, +> { + let uri_str = format!("{}/addresses/balances", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(¶ms.balance_request); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `Vec<models::BalancesByAddressEntry>`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `Vec<models::BalancesByAddressEntry>`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Get all transactions for a given address from database. And then get their related full transaction data +pub async fn get_full_transactions_for_address_addresses_kaspa_address_full_transactions_get( + configuration: &configuration::Configuration, + params: GetFullTransactionsForAddressAddressesKaspaAddressFullTransactionsGetParams, +) -> Result< + Vec, + Error, +> { + let uri_str = format!( + "{}/addresses/{kaspaAddress}/full-transactions", + configuration.base_path, + kaspaAddress = crate::apis::urlencode(params.kaspa_address) + ); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref param_value) = params.limit { + req_builder = req_builder.query(&[("limit", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.offset { + req_builder = req_builder.query(&[("offset", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.fields { + req_builder = req_builder.query(&[("fields", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.resolve_previous_outpoints { + req_builder = + req_builder.query(&[("resolve_previous_outpoints", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `Vec<models::TxModel>`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `Vec<models::TxModel>`")))), + } + } else { + let content = resp.text().await?; + let entity: Option< + GetFullTransactionsForAddressAddressesKaspaAddressFullTransactionsGetError, + > = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Get all transactions for a given address from database. And then get their related full transaction data +pub async fn get_full_transactions_for_address_page_addresses_kaspa_address_full_transactions_page_get( + configuration: &configuration::Configuration, + params: GetFullTransactionsForAddressPageAddressesKaspaAddressFullTransactionsPageGetParams, +) -> Result< + Vec, + Error, +> { + let uri_str = format!( + "{}/addresses/{kaspaAddress}/full-transactions-page", + configuration.base_path, + kaspaAddress = crate::apis::urlencode(params.kaspa_address) + ); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref param_value) = params.limit { + req_builder = req_builder.query(&[("limit", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.before { + req_builder = req_builder.query(&[("before", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.after { + req_builder = req_builder.query(&[("after", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.fields { + req_builder = req_builder.query(&[("fields", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.resolve_previous_outpoints { + req_builder = + req_builder.query(&[("resolve_previous_outpoints", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.acceptance { + req_builder = req_builder.query(&[("acceptance", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `Vec<models::TxModel>`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `Vec<models::TxModel>`")))), + } + } else { + let content = resp.text().await?; + let entity: Option< + GetFullTransactionsForAddressPageAddressesKaspaAddressFullTransactionsPageGetError, + > = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Get the name for an address +pub async fn get_name_for_address_addresses_kaspa_address_name_get( + configuration: &configuration::Configuration, + params: GetNameForAddressAddressesKaspaAddressNameGetParams, +) -> Result> { + let uri_str = format!( + "{}/addresses/{kaspaAddress}/name", + configuration.base_path, + kaspaAddress = crate::apis::urlencode(params.kaspa_address) + ); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::AddressName`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::AddressName`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Count the number of transactions associated with this address +pub async fn get_transaction_count_for_address_addresses_kaspa_address_transactions_count_get( + configuration: &configuration::Configuration, + params: GetTransactionCountForAddressAddressesKaspaAddressTransactionsCountGetParams, +) -> Result< + models::TransactionCount, + Error, +> { + let uri_str = format!( + "{}/addresses/{kaspaAddress}/transactions-count", + configuration.base_path, + kaspaAddress = crate::apis::urlencode(params.kaspa_address) + ); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::TransactionCount`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::TransactionCount`")))), + } + } else { + let content = resp.text().await?; + let entity: Option< + GetTransactionCountForAddressAddressesKaspaAddressTransactionsCountGetError, + > = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Lists all open utxo for a given kaspa address +pub async fn get_utxos_for_address_addresses_kaspa_address_utxos_get( + configuration: &configuration::Configuration, + params: GetUtxosForAddressAddressesKaspaAddressUtxosGetParams, +) -> Result, Error> +{ + let uri_str = format!( + "{}/addresses/{kaspaAddress}/utxos", + configuration.base_path, + kaspaAddress = crate::apis::urlencode(params.kaspa_address) + ); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `Vec<models::UtxoResponse>`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `Vec<models::UtxoResponse>`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Lists all open utxo for a given kaspa address +pub async fn get_utxos_for_addresses_addresses_utxos_post( + configuration: &configuration::Configuration, + params: GetUtxosForAddressesAddressesUtxosPostParams, +) -> Result, Error> { + let uri_str = format!("{}/addresses/utxos", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(¶ms.utxo_request); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `Vec<models::UtxoResponse>`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `Vec<models::UtxoResponse>`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} diff --git a/dymension/libs/kaspa/lib/api/src/apis/kaspa_blocks_api.rs b/dymension/libs/kaspa/lib/api/src/apis/kaspa_blocks_api.rs new file mode 100644 index 00000000000..739d6d19fe3 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/apis/kaspa_blocks_api.rs @@ -0,0 +1,208 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use super::{configuration, ContentType, Error}; +use crate::{apis::ResponseContent, models}; +use reqwest; +use serde::{de::Error as _, Deserialize, Serialize}; + +/// struct for passing parameters to the method [`get_block_blocks_block_id_get`] +#[derive(Clone, Debug)] +pub struct GetBlockBlocksBlockIdGetParams { + pub block_id: String, + pub include_transactions: Option, + pub include_color: Option, +} + +/// struct for passing parameters to the method [`get_blocks_blocks_get`] +#[derive(Clone, Debug)] +pub struct GetBlocksBlocksGetParams { + pub low_hash: String, + pub include_blocks: Option, + pub include_transactions: Option, +} + +/// struct for passing parameters to the method [`get_blocks_from_bluescore_blocks_from_bluescore_get`] +#[derive(Clone, Debug)] +pub struct GetBlocksFromBluescoreBlocksFromBluescoreGetParams { + pub blue_score: Option, + pub include_transactions: Option, +} + +/// struct for typed errors of method [`get_block_blocks_block_id_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetBlockBlocksBlockIdGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_blocks_blocks_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetBlocksBlocksGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_blocks_from_bluescore_blocks_from_bluescore_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetBlocksFromBluescoreBlocksFromBluescoreGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// Get block information for a given block id +pub async fn get_block_blocks_block_id_get( + configuration: &configuration::Configuration, + params: GetBlockBlocksBlockIdGetParams, +) -> Result> { + let uri_str = format!( + "{}/blocks/{blockId}", + configuration.base_path, + blockId = crate::apis::urlencode(params.block_id) + ); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref param_value) = params.include_transactions { + req_builder = req_builder.query(&[("includeTransactions", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.include_color { + req_builder = req_builder.query(&[("includeColor", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::BlockModel`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::BlockModel`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Lists block beginning from a low hash (block id). +pub async fn get_blocks_blocks_get( + configuration: &configuration::Configuration, + params: GetBlocksBlocksGetParams, +) -> Result> { + let uri_str = format!("{}/blocks", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + req_builder = req_builder.query(&[("lowHash", ¶ms.low_hash.to_string())]); + if let Some(ref param_value) = params.include_blocks { + req_builder = req_builder.query(&[("includeBlocks", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.include_transactions { + req_builder = req_builder.query(&[("includeTransactions", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::BlockResponse`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::BlockResponse`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Lists blocks of a given blueScore +pub async fn get_blocks_from_bluescore_blocks_from_bluescore_get( + configuration: &configuration::Configuration, + params: GetBlocksFromBluescoreBlocksFromBluescoreGetParams, +) -> Result, Error> { + let uri_str = format!("{}/blocks-from-bluescore", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref param_value) = params.blue_score { + req_builder = req_builder.query(&[("blueScore", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.include_transactions { + req_builder = req_builder.query(&[("includeTransactions", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `Vec<models::BlockModel>`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `Vec<models::BlockModel>`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} diff --git a/dymension/libs/kaspa/lib/api/src/apis/kaspa_network_info_api.rs b/dymension/libs/kaspa/lib/api/src/apis/kaspa_network_info_api.rs new file mode 100644 index 00000000000..1808a2e1701 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/apis/kaspa_network_info_api.rs @@ -0,0 +1,872 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use super::{configuration, ContentType, Error}; +use crate::{apis::ResponseContent, models}; +use reqwest; +use serde::{de::Error as _, Deserialize, Serialize}; + +/// struct for passing parameters to the method [`get_blockreward_info_blockreward_get`] +#[derive(Clone, Debug)] +pub struct GetBlockrewardInfoBlockrewardGetParams { + pub string_only: Option, +} + +/// struct for passing parameters to the method [`get_circulating_coins_info_coinsupply_circulating_get`] +#[derive(Clone, Debug)] +pub struct GetCirculatingCoinsInfoCoinsupplyCirculatingGetParams { + pub in_billion: Option, +} + +/// struct for passing parameters to the method [`get_halving_info_halving_get`] +#[derive(Clone, Debug)] +pub struct GetHalvingInfoHalvingGetParams { + pub field: Option, +} + +/// struct for passing parameters to the method [`get_hashrate_history_info_hashrate_history_get`] +#[derive(Clone, Debug)] +pub struct GetHashrateHistoryInfoHashrateHistoryGetParams { + pub resolution: Option, +} + +/// struct for passing parameters to the method [`get_hashrate_info_hashrate_get`] +#[derive(Clone, Debug)] +pub struct GetHashrateInfoHashrateGetParams { + pub string_only: Option, +} + +/// struct for passing parameters to the method [`get_marketcap_info_marketcap_get`] +#[derive(Clone, Debug)] +pub struct GetMarketcapInfoMarketcapGetParams { + pub string_only: Option, +} + +/// struct for passing parameters to the method [`get_price_info_price_get`] +#[derive(Clone, Debug)] +pub struct GetPriceInfoPriceGetParams { + pub string_only: Option, +} + +/// struct for passing parameters to the method [`get_total_coins_info_coinsupply_total_get`] +#[derive(Clone, Debug)] +pub struct GetTotalCoinsInfoCoinsupplyTotalGetParams { + pub in_billion: Option, +} + +/// struct for typed errors of method [`get_blockdag_info_blockdag_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetBlockdagInfoBlockdagGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_blockreward_info_blockreward_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetBlockrewardInfoBlockrewardGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_circulating_coins_info_coinsupply_circulating_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetCirculatingCoinsInfoCoinsupplyCirculatingGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_coinsupply_info_coinsupply_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetCoinsupplyInfoCoinsupplyGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_fee_estimate_info_fee_estimate_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetFeeEstimateInfoFeeEstimateGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_halving_info_halving_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetHalvingInfoHalvingGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_hashrate_history_info_hashrate_history_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetHashrateHistoryInfoHashrateHistoryGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_hashrate_info_hashrate_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetHashrateInfoHashrateGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_kaspad_info_info_kaspad_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetKaspadInfoInfoKaspadGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_marketcap_info_marketcap_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetMarketcapInfoMarketcapGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_max_hashrate_info_hashrate_max_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetMaxHashrateInfoHashrateMaxGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_network_info_network_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetNetworkInfoNetworkGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_price_info_price_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetPriceInfoPriceGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_total_coins_info_coinsupply_total_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetTotalCoinsInfoCoinsupplyTotalGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_virtual_selected_parent_blue_score_info_virtual_chain_blue_score_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetVirtualSelectedParentBlueScoreInfoVirtualChainBlueScoreGetError { + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`health_state_info_health_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum HealthStateInfoHealthGetError { + UnknownValue(serde_json::Value), +} + +/// Get Kaspa BlockDAG information +pub async fn get_blockdag_info_blockdag_get( + configuration: &configuration::Configuration, +) -> Result> { + let uri_str = format!("{}/info/blockdag", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::BlockdagResponse`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::BlockdagResponse`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Returns the current blockreward in KAS/block +pub async fn get_blockreward_info_blockreward_get( + configuration: &configuration::Configuration, + params: GetBlockrewardInfoBlockrewardGetParams, +) -> Result< + models::ResponseGetBlockrewardInfoBlockrewardGet, + Error, +> { + let uri_str = format!("{}/info/blockreward", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref param_value) = params.string_only { + req_builder = req_builder.query(&[("stringOnly", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ResponseGetBlockrewardInfoBlockrewardGet`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::ResponseGetBlockrewardInfoBlockrewardGet`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Get circulating amount of $KAS token as numerical value +pub async fn get_circulating_coins_info_coinsupply_circulating_get( + configuration: &configuration::Configuration, + params: GetCirculatingCoinsInfoCoinsupplyCirculatingGetParams, +) -> Result> { + let uri_str = format!("{}/info/coinsupply/circulating", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref param_value) = params.in_billion { + req_builder = req_builder.query(&[("in_billion", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Ok(content), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `String`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Get $KAS coin supply information +pub async fn get_coinsupply_info_coinsupply_get( + configuration: &configuration::Configuration, +) -> Result> { + let uri_str = format!("{}/info/coinsupply", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::CoinSupplyResponse`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::CoinSupplyResponse`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Get fee estimate from Kaspad. For all buckets, feerate values represent fee/mass of a transaction in `sompi/gram` units.
Given a feerate value recommendation, calculate the required fee by taking the transaction mass and multiplying it by feerate: `fee = feerate * mass(tx)` +pub async fn get_fee_estimate_info_fee_estimate_get( + configuration: &configuration::Configuration, +) -> Result> { + let uri_str = format!("{}/info/fee-estimate", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::FeeEstimateResponse`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::FeeEstimateResponse`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Returns information about chromatic halving +pub async fn get_halving_info_halving_get( + configuration: &configuration::Configuration, + params: GetHalvingInfoHalvingGetParams, +) -> Result> { + let uri_str = format!("{}/info/halving", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref param_value) = params.field { + req_builder = req_builder.query(&[("field", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ResponseGetHalvingInfoHalvingGet`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::ResponseGetHalvingInfoHalvingGet`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Get historical hashrate samples with optional resolution (default = 1h) +pub async fn get_hashrate_history_info_hashrate_history_get( + configuration: &configuration::Configuration, + params: GetHashrateHistoryInfoHashrateHistoryGetParams, +) -> Result< + Vec, + Error, +> { + let uri_str = format!("{}/info/hashrate/history", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref param_value) = params.resolution { + req_builder = req_builder.query(&[("resolution", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `Vec<models::HashrateHistoryResponse>`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `Vec<models::HashrateHistoryResponse>`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Returns the current hashrate for Kaspa network in TH/s. +pub async fn get_hashrate_info_hashrate_get( + configuration: &configuration::Configuration, + params: GetHashrateInfoHashrateGetParams, +) -> Result> { + let uri_str = format!("{}/info/hashrate", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref param_value) = params.string_only { + req_builder = req_builder.query(&[("stringOnly", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ResponseGetHashrateInfoHashrateGet`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::ResponseGetHashrateInfoHashrateGet`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Get some information for kaspad instance, which is currently connected. +pub async fn get_kaspad_info_info_kaspad_get( + configuration: &configuration::Configuration, +) -> Result> { + let uri_str = format!("{}/info/kaspad", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::KaspadInfoResponse`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::KaspadInfoResponse`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Get $KAS price and market cap. Price info is from coingecko.com +pub async fn get_marketcap_info_marketcap_get( + configuration: &configuration::Configuration, + params: GetMarketcapInfoMarketcapGetParams, +) -> Result> +{ + let uri_str = format!("{}/info/marketcap", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref param_value) = params.string_only { + req_builder = req_builder.query(&[("stringOnly", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ResponseGetMarketcapInfoMarketcapGet`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::ResponseGetMarketcapInfoMarketcapGet`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Returns the current hashrate for Kaspa network in TH/s. +pub async fn get_max_hashrate_info_hashrate_max_get( + configuration: &configuration::Configuration, +) -> Result> { + let uri_str = format!("{}/info/hashrate/max", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::MaxHashrateResponse`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::MaxHashrateResponse`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Alias for /info/blockdag +pub async fn get_network_info_network_get( + configuration: &configuration::Configuration, +) -> Result> { + let uri_str = format!("{}/info/network", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::BlockdagResponse`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::BlockdagResponse`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Returns the current price for Kaspa in USD. +pub async fn get_price_info_price_get( + configuration: &configuration::Configuration, + params: GetPriceInfoPriceGetParams, +) -> Result> { + let uri_str = format!("{}/info/price", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref param_value) = params.string_only { + req_builder = req_builder.query(&[("stringOnly", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::ResponseGetPriceInfoPriceGet`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::ResponseGetPriceInfoPriceGet`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Get total amount of $KAS token as numerical value +pub async fn get_total_coins_info_coinsupply_total_get( + configuration: &configuration::Configuration, + params: GetTotalCoinsInfoCoinsupplyTotalGetParams, +) -> Result> { + let uri_str = format!("{}/info/coinsupply/total", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref param_value) = params.in_billion { + req_builder = req_builder.query(&[("in_billion", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Ok(content), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `String`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Returns the blue score of the sink +pub async fn get_virtual_selected_parent_blue_score_info_virtual_chain_blue_score_get( + configuration: &configuration::Configuration, +) -> Result< + models::BlueScoreResponse, + Error, +> { + let uri_str = format!("{}/info/virtual-chain-blue-score", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::BlueScoreResponse`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::BlueScoreResponse`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Checks node and database health by comparing blue score and sync status. Returns health details or 503 if the database lags by ~10min or no nodes are synced. +pub async fn health_state_info_health_get( + configuration: &configuration::Configuration, +) -> Result> { + let uri_str = format!("{}/info/health", configuration.base_path); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::HealthResponse`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::HealthResponse`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} diff --git a/dymension/libs/kaspa/lib/api/src/apis/kaspa_transactions_api.rs b/dymension/libs/kaspa/lib/api/src/apis/kaspa_transactions_api.rs new file mode 100644 index 00000000000..fe878b8c125 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/apis/kaspa_transactions_api.rs @@ -0,0 +1,352 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use super::{configuration, ContentType, Error}; +use crate::{apis::ResponseContent, models}; +use reqwest; +use serde::{de::Error as _, Deserialize, Serialize}; + +/// struct for passing parameters to the method [`calculate_transaction_mass_transactions_mass_post`] +#[derive(Clone, Debug)] +pub struct CalculateTransactionMassTransactionsMassPostParams { + pub submit_tx_model: models::SubmitTxModel, +} + +/// struct for passing parameters to the method [`get_transaction_acceptance_transactions_acceptance_post`] +#[derive(Clone, Debug)] +pub struct GetTransactionAcceptanceTransactionsAcceptancePostParams { + pub tx_acceptance_request: models::TxAcceptanceRequest, +} + +/// struct for passing parameters to the method [`get_transaction_transactions_transaction_id_get`] +#[derive(Clone, Debug)] +pub struct GetTransactionTransactionsTransactionIdGetParams { + pub transaction_id: String, + /// Specify a containing block (if known) for faster lookup + pub block_hash: Option, + pub inputs: Option, + pub outputs: Option, + /// Use this parameter if you want to fetch the TransactionInput previous outpoint details. Light fetches only the address and amount. Full fetches the whole TransactionOutput and adds it into each TxInput. + pub resolve_previous_outpoints: Option, +} + +/// struct for passing parameters to the method [`search_for_transactions_transactions_search_post`] +#[derive(Clone, Debug)] +pub struct SearchForTransactionsTransactionsSearchPostParams { + pub tx_search: models::TxSearch, + pub fields: Option, + /// Use this parameter if you want to fetch the TransactionInput previous outpoint details. Light fetches only the address and amount. Full fetches the whole TransactionOutput and adds it into each TxInput. + pub resolve_previous_outpoints: Option, + /// Only used when searching using transactionIds + pub acceptance: Option, +} + +/// struct for passing parameters to the method [`submit_a_new_transaction_transactions_post`] +#[derive(Clone, Debug)] +pub struct SubmitANewTransactionTransactionsPostParams { + pub submit_transaction_request: models::SubmitTransactionRequest, + /// Replace an existing transaction in the mempool + pub replace_by_fee: Option, +} + +/// struct for typed errors of method [`calculate_transaction_mass_transactions_mass_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum CalculateTransactionMassTransactionsMassPostError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_transaction_acceptance_transactions_acceptance_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetTransactionAcceptanceTransactionsAcceptancePostError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`get_transaction_transactions_transaction_id_get`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum GetTransactionTransactionsTransactionIdGetError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`search_for_transactions_transactions_search_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SearchForTransactionsTransactionsSearchPostError { + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// struct for typed errors of method [`submit_a_new_transaction_transactions_post`] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SubmitANewTransactionTransactionsPostError { + Status400(models::SubmitTransactionResponse), + Status422(models::HttpValidationError), + UnknownValue(serde_json::Value), +} + +/// This function calculates and returns the mass of a transaction, which is essential for determining the minimum fee. The mass calculation takes into account the storage mass as defined in KIP-0009. Note: Be aware that if the transaction has a very low output amount or a high number of outputs, the mass can become significantly large. +pub async fn calculate_transaction_mass_transactions_mass_post( + configuration: &configuration::Configuration, + params: CalculateTransactionMassTransactionsMassPostParams, +) -> Result> { + let uri_str = format!("{}/transactions/mass", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(¶ms.submit_tx_model); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::TxMass`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::TxMass`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Given a list of transaction_ids, return whether each one is accepted +pub async fn get_transaction_acceptance_transactions_acceptance_post( + configuration: &configuration::Configuration, + params: GetTransactionAcceptanceTransactionsAcceptancePostParams, +) -> Result< + Vec, + Error, +> { + let uri_str = format!("{}/transactions/acceptance", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(¶ms.tx_acceptance_request); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `Vec<models::TxAcceptanceResponse>`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `Vec<models::TxAcceptanceResponse>`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Get details for a given transaction id +pub async fn get_transaction_transactions_transaction_id_get( + configuration: &configuration::Configuration, + params: GetTransactionTransactionsTransactionIdGetParams, +) -> Result> { + let uri_str = format!( + "{}/transactions/{transactionId}", + configuration.base_path, + transactionId = crate::apis::urlencode(params.transaction_id) + ); + let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str); + + if let Some(ref param_value) = params.block_hash { + req_builder = req_builder.query(&[("blockHash", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.inputs { + req_builder = req_builder.query(&[("inputs", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.outputs { + req_builder = req_builder.query(&[("outputs", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.resolve_previous_outpoints { + req_builder = + req_builder.query(&[("resolve_previous_outpoints", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::TxModel`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::TxModel`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +/// Search for transactions by transaction_ids or blue_score +pub async fn search_for_transactions_transactions_search_post( + configuration: &configuration::Configuration, + params: SearchForTransactionsTransactionsSearchPostParams, +) -> Result, Error> { + let uri_str = format!("{}/transactions/search", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref param_value) = params.fields { + req_builder = req_builder.query(&[("fields", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.resolve_previous_outpoints { + req_builder = + req_builder.query(&[("resolve_previous_outpoints", ¶m_value.to_string())]); + } + if let Some(ref param_value) = params.acceptance { + req_builder = req_builder.query(&[("acceptance", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(¶ms.tx_search); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `Vec<models::TxModel>`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `Vec<models::TxModel>`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} + +pub async fn submit_a_new_transaction_transactions_post( + configuration: &configuration::Configuration, + params: SubmitANewTransactionTransactionsPostParams, +) -> Result> { + let uri_str = format!("{}/transactions", configuration.base_path); + let mut req_builder = configuration + .client + .request(reqwest::Method::POST, &uri_str); + + if let Some(ref param_value) = params.replace_by_fee { + req_builder = req_builder.query(&[("replaceByFee", ¶m_value.to_string())]); + } + if let Some(ref user_agent) = configuration.user_agent { + req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone()); + } + req_builder = req_builder.json(¶ms.submit_transaction_request); + + let req = req_builder.build()?; + let resp = configuration.client.execute(req).await?; + + let status = resp.status(); + let content_type = resp + .headers() + .get("content-type") + .and_then(|v| v.to_str().ok()) + .unwrap_or("application/octet-stream"); + let content_type = super::ContentType::from(content_type); + + if !status.is_client_error() && !status.is_server_error() { + let content = resp.text().await?; + match content_type { + ContentType::Json => serde_json::from_str(&content).map_err(Error::from), + ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::SubmitTransactionResponse`"))), + ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::SubmitTransactionResponse`")))), + } + } else { + let content = resp.text().await?; + let entity: Option = + serde_json::from_str(&content).ok(); + Err(Error::ResponseError(ResponseContent { + status, + content, + entity, + })) + } +} diff --git a/dymension/libs/kaspa/lib/api/src/apis/mod.rs b/dymension/libs/kaspa/lib/api/src/apis/mod.rs new file mode 100644 index 00000000000..bc773652dde --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/apis/mod.rs @@ -0,0 +1,131 @@ +use std::error; +use std::fmt; + +#[derive(Debug, Clone)] +pub struct ResponseContent { + pub status: reqwest::StatusCode, + pub content: String, + pub entity: Option, +} + +#[derive(Debug)] +pub enum Error { + Reqwest(reqwest::Error), + ReqwestMiddleware(reqwest_middleware::Error), + Serde(serde_json::Error), + Io(std::io::Error), + ResponseError(ResponseContent), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (module, e) = match self { + Error::Reqwest(e) => ("reqwest", e.to_string()), + Error::ReqwestMiddleware(e) => ("reqwest-middleware", e.to_string()), + Error::Serde(e) => ("serde", e.to_string()), + Error::Io(e) => ("IO", e.to_string()), + Error::ResponseError(e) => ("response", format!("status code {}", e.status)), + }; + write!(f, "error in {}: {}", module, e) + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + Some(match self { + Error::Reqwest(e) => e, + Error::ReqwestMiddleware(e) => e, + Error::Serde(e) => e, + Error::Io(e) => e, + Error::ResponseError(_) => return None, + }) + } +} + +impl From for Error { + fn from(e: reqwest::Error) -> Self { + Error::Reqwest(e) + } +} + +impl From for Error { + fn from(e: reqwest_middleware::Error) -> Self { + Error::ReqwestMiddleware(e) + } +} + +impl From for Error { + fn from(e: serde_json::Error) -> Self { + Error::Serde(e) + } +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Error::Io(e) + } +} + +pub fn urlencode>(s: T) -> String { + ::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect() +} + +pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> { + if let serde_json::Value::Object(object) = value { + let mut params = vec![]; + + for (key, value) in object { + match value { + serde_json::Value::Object(_) => params.append(&mut parse_deep_object( + &format!("{}[{}]", prefix, key), + value, + )), + serde_json::Value::Array(array) => { + for (i, value) in array.iter().enumerate() { + params.append(&mut parse_deep_object( + &format!("{}[{}][{}]", prefix, key, i), + value, + )); + } + } + serde_json::Value::String(s) => { + params.push((format!("{}[{}]", prefix, key), s.clone())) + } + _ => params.push((format!("{}[{}]", prefix, key), value.to_string())), + } + } + + return params; + } + + unimplemented!("Only objects are supported with style=deepObject") +} + +/// Internal use only +/// A content type supported by this client. +#[allow(dead_code)] +enum ContentType { + Json, + Text, + Unsupported(String), +} + +impl From<&str> for ContentType { + fn from(content_type: &str) -> Self { + if content_type.starts_with("application") && content_type.contains("json") { + return Self::Json; + } else if content_type.starts_with("text/plain") { + return Self::Text; + } else { + return Self::Unsupported(content_type.to_string()); + } + } +} + +pub mod experimental_kaspa_virtual_chain_api; +pub mod kaspa_addresses_api; +pub mod kaspa_blocks_api; +pub mod kaspa_network_info_api; +pub mod kaspa_transactions_api; + +pub mod configuration; diff --git a/dymension/libs/kaspa/lib/api/src/lib.rs b/dymension/libs/kaspa/lib/api/src/lib.rs new file mode 100644 index 00000000000..ae2753ec6fe --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/lib.rs @@ -0,0 +1,13 @@ +#![allow(unused_imports)] +#![allow(non_snake_case)] +#![allow(clippy::all)] // disable clippy for codegen +#![allow(clippy::too_many_arguments)] + +extern crate reqwest; +extern crate serde; +extern crate serde_json; +extern crate serde_repr; +extern crate url; + +pub mod apis; +pub mod models; diff --git a/dymension/libs/kaspa/lib/api/src/models/acceptance_mode.rs b/dymension/libs/kaspa/lib/api/src/models/acceptance_mode.rs new file mode 100644 index 00000000000..8ef535a4156 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/acceptance_mode.rs @@ -0,0 +1,37 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +/// AcceptanceMode : An enumeration. +/// An enumeration. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum AcceptanceMode { + #[serde(rename = "accepted")] + Accepted, + #[serde(rename = "rejected")] + Rejected, +} + +impl std::fmt::Display for AcceptanceMode { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::Accepted => write!(f, "accepted"), + Self::Rejected => write!(f, "rejected"), + } + } +} + +impl Default for AcceptanceMode { + fn default() -> AcceptanceMode { + Self::Accepted + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/address_name.rs b/dymension/libs/kaspa/lib/api/src/models/address_name.rs new file mode 100644 index 00000000000..7e7182213d5 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/address_name.rs @@ -0,0 +1,26 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct AddressName { + #[serde(rename = "address")] + pub address: String, + #[serde(rename = "name")] + pub name: String, +} + +impl AddressName { + pub fn new(address: String, name: String) -> AddressName { + AddressName { address, name } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/addresses_active_request.rs b/dymension/libs/kaspa/lib/api/src/models/addresses_active_request.rs new file mode 100644 index 00000000000..781c4f5cff9 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/addresses_active_request.rs @@ -0,0 +1,24 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct AddressesActiveRequest { + #[serde(rename = "addresses", skip_serializing_if = "Option::is_none")] + pub addresses: Option>, +} + +impl AddressesActiveRequest { + pub fn new() -> AddressesActiveRequest { + AddressesActiveRequest { addresses: None } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/balance_request.rs b/dymension/libs/kaspa/lib/api/src/models/balance_request.rs new file mode 100644 index 00000000000..49fcd5068e2 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/balance_request.rs @@ -0,0 +1,24 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct BalanceRequest { + #[serde(rename = "addresses", skip_serializing_if = "Option::is_none")] + pub addresses: Option>, +} + +impl BalanceRequest { + pub fn new() -> BalanceRequest { + BalanceRequest { addresses: None } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/balance_response.rs b/dymension/libs/kaspa/lib/api/src/models/balance_response.rs new file mode 100644 index 00000000000..6c80a42fad6 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/balance_response.rs @@ -0,0 +1,29 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct BalanceResponse { + #[serde(rename = "address", skip_serializing_if = "Option::is_none")] + pub address: Option, + #[serde(rename = "balance", skip_serializing_if = "Option::is_none")] + pub balance: Option, +} + +impl BalanceResponse { + pub fn new() -> BalanceResponse { + BalanceResponse { + address: None, + balance: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/balances_by_address_entry.rs b/dymension/libs/kaspa/lib/api/src/models/balances_by_address_entry.rs new file mode 100644 index 00000000000..2c42eac004c --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/balances_by_address_entry.rs @@ -0,0 +1,29 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct BalancesByAddressEntry { + #[serde(rename = "address", skip_serializing_if = "Option::is_none")] + pub address: Option, + #[serde(rename = "balance", skip_serializing_if = "Option::is_none")] + pub balance: Option, +} + +impl BalancesByAddressEntry { + pub fn new() -> BalancesByAddressEntry { + BalancesByAddressEntry { + address: None, + balance: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/block_model.rs b/dymension/libs/kaspa/lib/api/src/models/block_model.rs new file mode 100644 index 00000000000..8267b634c53 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/block_model.rs @@ -0,0 +1,38 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct BlockModel { + #[serde(rename = "header")] + pub header: Box, + #[serde(rename = "transactions", skip_serializing_if = "Option::is_none")] + pub transactions: Option>, + #[serde(rename = "verboseData")] + pub verbose_data: Box, + #[serde(rename = "extra", skip_serializing_if = "Option::is_none")] + pub extra: Option>, +} + +impl BlockModel { + pub fn new( + header: models::EndpointsGetBlocksBlockHeader, + verbose_data: models::VerboseDataModel, + ) -> BlockModel { + BlockModel { + header: Box::new(header), + transactions: None, + verbose_data: Box::new(verbose_data), + extra: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/block_response.rs b/dymension/libs/kaspa/lib/api/src/models/block_response.rs new file mode 100644 index 00000000000..e85e11db64a --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/block_response.rs @@ -0,0 +1,29 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct BlockResponse { + #[serde(rename = "blockHashes", skip_serializing_if = "Option::is_none")] + pub block_hashes: Option>, + #[serde(rename = "blocks", skip_serializing_if = "Option::is_none")] + pub blocks: Option>, +} + +impl BlockResponse { + pub fn new() -> BlockResponse { + BlockResponse { + block_hashes: None, + blocks: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/block_reward_response.rs b/dymension/libs/kaspa/lib/api/src/models/block_reward_response.rs new file mode 100644 index 00000000000..01b32bae583 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/block_reward_response.rs @@ -0,0 +1,24 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct BlockRewardResponse { + #[serde(rename = "blockreward", skip_serializing_if = "Option::is_none")] + pub blockreward: Option, +} + +impl BlockRewardResponse { + pub fn new() -> BlockRewardResponse { + BlockRewardResponse { blockreward: None } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/block_tx_input_model.rs b/dymension/libs/kaspa/lib/api/src/models/block_tx_input_model.rs new file mode 100644 index 00000000000..cb5814e2d12 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/block_tx_input_model.rs @@ -0,0 +1,35 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct BlockTxInputModel { + #[serde(rename = "previousOutpoint", skip_serializing_if = "Option::is_none")] + pub previous_outpoint: Option>, + #[serde(rename = "signatureScript", skip_serializing_if = "Option::is_none")] + pub signature_script: Option, + #[serde(rename = "sigOpCount", skip_serializing_if = "Option::is_none")] + pub sig_op_count: Option, + #[serde(rename = "sequence", skip_serializing_if = "Option::is_none")] + pub sequence: Option, +} + +impl BlockTxInputModel { + pub fn new() -> BlockTxInputModel { + BlockTxInputModel { + previous_outpoint: None, + signature_script: None, + sig_op_count: None, + sequence: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/block_tx_input_previous_outpoint_model.rs b/dymension/libs/kaspa/lib/api/src/models/block_tx_input_previous_outpoint_model.rs new file mode 100644 index 00000000000..807543cbefd --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/block_tx_input_previous_outpoint_model.rs @@ -0,0 +1,29 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct BlockTxInputPreviousOutpointModel { + #[serde(rename = "transactionId")] + pub transaction_id: String, + #[serde(rename = "index")] + pub index: i64, +} + +impl BlockTxInputPreviousOutpointModel { + pub fn new(transaction_id: String, index: i64) -> BlockTxInputPreviousOutpointModel { + BlockTxInputPreviousOutpointModel { + transaction_id, + index, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/block_tx_model.rs b/dymension/libs/kaspa/lib/api/src/models/block_tx_model.rs new file mode 100644 index 00000000000..67e0aa0288d --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/block_tx_model.rs @@ -0,0 +1,50 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct BlockTxModel { + #[serde(rename = "inputs", skip_serializing_if = "Option::is_none")] + pub inputs: Option>, + #[serde(rename = "outputs", skip_serializing_if = "Option::is_none")] + pub outputs: Option>, + #[serde(rename = "subnetworkId", skip_serializing_if = "Option::is_none")] + pub subnetwork_id: Option, + #[serde(rename = "payload", skip_serializing_if = "Option::is_none")] + pub payload: Option, + #[serde(rename = "verboseData")] + pub verbose_data: Box, + #[serde(rename = "lockTime", skip_serializing_if = "Option::is_none")] + pub lock_time: Option, + #[serde(rename = "gas", skip_serializing_if = "Option::is_none")] + pub gas: Option, + #[serde(rename = "mass", skip_serializing_if = "Option::is_none")] + pub mass: Option, + #[serde(rename = "version", skip_serializing_if = "Option::is_none")] + pub version: Option, +} + +impl BlockTxModel { + pub fn new(verbose_data: models::BlockTxVerboseDataModel) -> BlockTxModel { + BlockTxModel { + inputs: None, + outputs: None, + subnetwork_id: None, + payload: None, + verbose_data: Box::new(verbose_data), + lock_time: None, + gas: None, + mass: None, + version: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/block_tx_output_model.rs b/dymension/libs/kaspa/lib/api/src/models/block_tx_output_model.rs new file mode 100644 index 00000000000..f9edeb01403 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/block_tx_output_model.rs @@ -0,0 +1,32 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct BlockTxOutputModel { + #[serde(rename = "amount", skip_serializing_if = "Option::is_none")] + pub amount: Option, + #[serde(rename = "scriptPublicKey", skip_serializing_if = "Option::is_none")] + pub script_public_key: Option>, + #[serde(rename = "verboseData", skip_serializing_if = "Option::is_none")] + pub verbose_data: Option>, +} + +impl BlockTxOutputModel { + pub fn new() -> BlockTxOutputModel { + BlockTxOutputModel { + amount: None, + script_public_key: None, + verbose_data: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/block_tx_output_script_public_key_model.rs b/dymension/libs/kaspa/lib/api/src/models/block_tx_output_script_public_key_model.rs new file mode 100644 index 00000000000..fd8f0168d80 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/block_tx_output_script_public_key_model.rs @@ -0,0 +1,29 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct BlockTxOutputScriptPublicKeyModel { + #[serde(rename = "scriptPublicKey", skip_serializing_if = "Option::is_none")] + pub script_public_key: Option, + #[serde(rename = "version", skip_serializing_if = "Option::is_none")] + pub version: Option, +} + +impl BlockTxOutputScriptPublicKeyModel { + pub fn new() -> BlockTxOutputScriptPublicKeyModel { + BlockTxOutputScriptPublicKeyModel { + script_public_key: None, + version: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/block_tx_output_verbose_data_model.rs b/dymension/libs/kaspa/lib/api/src/models/block_tx_output_verbose_data_model.rs new file mode 100644 index 00000000000..a560de8ca5d --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/block_tx_output_verbose_data_model.rs @@ -0,0 +1,35 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct BlockTxOutputVerboseDataModel { + #[serde( + rename = "scriptPublicKeyType", + skip_serializing_if = "Option::is_none" + )] + pub script_public_key_type: Option, + #[serde( + rename = "scriptPublicKeyAddress", + skip_serializing_if = "Option::is_none" + )] + pub script_public_key_address: Option, +} + +impl BlockTxOutputVerboseDataModel { + pub fn new() -> BlockTxOutputVerboseDataModel { + BlockTxOutputVerboseDataModel { + script_public_key_type: None, + script_public_key_address: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/block_tx_verbose_data_model.rs b/dymension/libs/kaspa/lib/api/src/models/block_tx_verbose_data_model.rs new file mode 100644 index 00000000000..b8f5cecabf2 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/block_tx_verbose_data_model.rs @@ -0,0 +1,38 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct BlockTxVerboseDataModel { + #[serde(rename = "transactionId")] + pub transaction_id: String, + #[serde(rename = "hash", skip_serializing_if = "Option::is_none")] + pub hash: Option, + #[serde(rename = "computeMass", skip_serializing_if = "Option::is_none")] + pub compute_mass: Option, + #[serde(rename = "blockHash", skip_serializing_if = "Option::is_none")] + pub block_hash: Option, + #[serde(rename = "blockTime", skip_serializing_if = "Option::is_none")] + pub block_time: Option, +} + +impl BlockTxVerboseDataModel { + pub fn new(transaction_id: String) -> BlockTxVerboseDataModel { + BlockTxVerboseDataModel { + transaction_id, + hash: None, + compute_mass: None, + block_hash: None, + block_time: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/blockdag_response.rs b/dymension/libs/kaspa/lib/api/src/models/blockdag_response.rs new file mode 100644 index 00000000000..ff4daf10803 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/blockdag_response.rs @@ -0,0 +1,64 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct BlockdagResponse { + #[serde(rename = "networkName")] + pub network_name: String, + #[serde(rename = "blockCount")] + pub block_count: String, + #[serde(rename = "headerCount")] + pub header_count: String, + #[serde(rename = "tipHashes")] + pub tip_hashes: Vec, + #[serde(rename = "difficulty")] + pub difficulty: f64, + #[serde(rename = "pastMedianTime")] + pub past_median_time: String, + #[serde(rename = "virtualParentHashes")] + pub virtual_parent_hashes: Vec, + #[serde(rename = "pruningPointHash")] + pub pruning_point_hash: String, + #[serde(rename = "virtualDaaScore")] + pub virtual_daa_score: String, + #[serde(rename = "sink")] + pub sink: String, +} + +impl BlockdagResponse { + pub fn new( + network_name: String, + block_count: String, + header_count: String, + tip_hashes: Vec, + difficulty: f64, + past_median_time: String, + virtual_parent_hashes: Vec, + pruning_point_hash: String, + virtual_daa_score: String, + sink: String, + ) -> BlockdagResponse { + BlockdagResponse { + network_name, + block_count, + header_count, + tip_hashes, + difficulty, + past_median_time, + virtual_parent_hashes, + pruning_point_hash, + virtual_daa_score, + sink, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/blue_score_response.rs b/dymension/libs/kaspa/lib/api/src/models/blue_score_response.rs new file mode 100644 index 00000000000..e2cbc22290b --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/blue_score_response.rs @@ -0,0 +1,24 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct BlueScoreResponse { + #[serde(rename = "blueScore", skip_serializing_if = "Option::is_none")] + pub blue_score: Option, +} + +impl BlueScoreResponse { + pub fn new() -> BlueScoreResponse { + BlueScoreResponse { blue_score: None } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/coin_supply_response.rs b/dymension/libs/kaspa/lib/api/src/models/coin_supply_response.rs new file mode 100644 index 00000000000..26dc63c7baf --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/coin_supply_response.rs @@ -0,0 +1,29 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct CoinSupplyResponse { + #[serde(rename = "circulatingSupply", skip_serializing_if = "Option::is_none")] + pub circulating_supply: Option, + #[serde(rename = "maxSupply", skip_serializing_if = "Option::is_none")] + pub max_supply: Option, +} + +impl CoinSupplyResponse { + pub fn new() -> CoinSupplyResponse { + CoinSupplyResponse { + circulating_supply: None, + max_supply: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/db_check_status.rs b/dymension/libs/kaspa/lib/api/src/models/db_check_status.rs new file mode 100644 index 00000000000..2cbb6dc54fa --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/db_check_status.rs @@ -0,0 +1,44 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct DbCheckStatus { + #[serde(rename = "isSynced", skip_serializing_if = "Option::is_none")] + pub is_synced: Option, + #[serde(rename = "blueScore", skip_serializing_if = "Option::is_none")] + pub blue_score: Option, + #[serde(rename = "blueScoreDiff", skip_serializing_if = "Option::is_none")] + pub blue_score_diff: Option, + #[serde( + rename = "acceptedTxBlockTime", + skip_serializing_if = "Option::is_none" + )] + pub accepted_tx_block_time: Option, + #[serde( + rename = "acceptedTxBlockTimeDiff", + skip_serializing_if = "Option::is_none" + )] + pub accepted_tx_block_time_diff: Option, +} + +impl DbCheckStatus { + pub fn new() -> DbCheckStatus { + DbCheckStatus { + is_synced: None, + blue_score: None, + blue_score_diff: None, + accepted_tx_block_time: None, + accepted_tx_block_time_diff: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/endpoints__get_blocks__block_header.rs b/dymension/libs/kaspa/lib/api/src/models/endpoints__get_blocks__block_header.rs new file mode 100644 index 00000000000..678321fb969 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/endpoints__get_blocks__block_header.rs @@ -0,0 +1,62 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct EndpointsGetBlocksBlockHeader { + #[serde(rename = "version", skip_serializing_if = "Option::is_none")] + pub version: Option, + #[serde(rename = "hashMerkleRoot", skip_serializing_if = "Option::is_none")] + pub hash_merkle_root: Option, + #[serde( + rename = "acceptedIdMerkleRoot", + skip_serializing_if = "Option::is_none" + )] + pub accepted_id_merkle_root: Option, + #[serde(rename = "utxoCommitment", skip_serializing_if = "Option::is_none")] + pub utxo_commitment: Option, + #[serde(rename = "timestamp", skip_serializing_if = "Option::is_none")] + pub timestamp: Option, + #[serde(rename = "bits", skip_serializing_if = "Option::is_none")] + pub bits: Option, + #[serde(rename = "nonce", skip_serializing_if = "Option::is_none")] + pub nonce: Option, + #[serde(rename = "daaScore", skip_serializing_if = "Option::is_none")] + pub daa_score: Option, + #[serde(rename = "blueWork", skip_serializing_if = "Option::is_none")] + pub blue_work: Option, + #[serde(rename = "parents", skip_serializing_if = "Option::is_none")] + pub parents: Option>, + #[serde(rename = "blueScore", skip_serializing_if = "Option::is_none")] + pub blue_score: Option, + #[serde(rename = "pruningPoint", skip_serializing_if = "Option::is_none")] + pub pruning_point: Option, +} + +impl EndpointsGetBlocksBlockHeader { + pub fn new() -> EndpointsGetBlocksBlockHeader { + EndpointsGetBlocksBlockHeader { + version: None, + hash_merkle_root: None, + accepted_id_merkle_root: None, + utxo_commitment: None, + timestamp: None, + bits: None, + nonce: None, + daa_score: None, + blue_work: None, + parents: None, + blue_score: None, + pruning_point: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/endpoints__get_hashrate__block_header.rs b/dymension/libs/kaspa/lib/api/src/models/endpoints__get_hashrate__block_header.rs new file mode 100644 index 00000000000..0e125c9a7e4 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/endpoints__get_hashrate__block_header.rs @@ -0,0 +1,38 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct EndpointsGetHashrateBlockHeader { + #[serde(rename = "hash", skip_serializing_if = "Option::is_none")] + pub hash: Option, + #[serde(rename = "timestamp", skip_serializing_if = "Option::is_none")] + pub timestamp: Option, + #[serde(rename = "difficulty", skip_serializing_if = "Option::is_none")] + pub difficulty: Option, + #[serde(rename = "daaScore", skip_serializing_if = "Option::is_none")] + pub daa_score: Option, + #[serde(rename = "blueScore", skip_serializing_if = "Option::is_none")] + pub blue_score: Option, +} + +impl EndpointsGetHashrateBlockHeader { + pub fn new() -> EndpointsGetHashrateBlockHeader { + EndpointsGetHashrateBlockHeader { + hash: None, + timestamp: None, + difficulty: None, + daa_score: None, + blue_score: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/extra_model.rs b/dymension/libs/kaspa/lib/api/src/models/extra_model.rs new file mode 100644 index 00000000000..86efc8670c8 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/extra_model.rs @@ -0,0 +1,32 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ExtraModel { + #[serde(rename = "color", skip_serializing_if = "Option::is_none")] + pub color: Option, + #[serde(rename = "minerAddress", skip_serializing_if = "Option::is_none")] + pub miner_address: Option, + #[serde(rename = "minerInfo", skip_serializing_if = "Option::is_none")] + pub miner_info: Option, +} + +impl ExtraModel { + pub fn new() -> ExtraModel { + ExtraModel { + color: None, + miner_address: None, + miner_info: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/fee_estimate_bucket.rs b/dymension/libs/kaspa/lib/api/src/models/fee_estimate_bucket.rs new file mode 100644 index 00000000000..e5bfa0f4c08 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/fee_estimate_bucket.rs @@ -0,0 +1,29 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct FeeEstimateBucket { + #[serde(rename = "feerate", skip_serializing_if = "Option::is_none")] + pub feerate: Option, + #[serde(rename = "estimatedSeconds", skip_serializing_if = "Option::is_none")] + pub estimated_seconds: Option, +} + +impl FeeEstimateBucket { + pub fn new() -> FeeEstimateBucket { + FeeEstimateBucket { + feerate: None, + estimated_seconds: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/fee_estimate_response.rs b/dymension/libs/kaspa/lib/api/src/models/fee_estimate_response.rs new file mode 100644 index 00000000000..257b3fdbf4a --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/fee_estimate_response.rs @@ -0,0 +1,36 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct FeeEstimateResponse { + #[serde(rename = "priorityBucket")] + pub priority_bucket: Box, + #[serde(rename = "normalBuckets")] + pub normal_buckets: Vec, + #[serde(rename = "lowBuckets")] + pub low_buckets: Vec, +} + +impl FeeEstimateResponse { + pub fn new( + priority_bucket: models::FeeEstimateBucket, + normal_buckets: Vec, + low_buckets: Vec, + ) -> FeeEstimateResponse { + FeeEstimateResponse { + priority_bucket: Box::new(priority_bucket), + normal_buckets, + low_buckets, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/halving_response.rs b/dymension/libs/kaspa/lib/api/src/models/halving_response.rs new file mode 100644 index 00000000000..fca58ff2122 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/halving_response.rs @@ -0,0 +1,35 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct HalvingResponse { + #[serde( + rename = "nextHalvingTimestamp", + skip_serializing_if = "Option::is_none" + )] + pub next_halving_timestamp: Option, + #[serde(rename = "nextHalvingDate", skip_serializing_if = "Option::is_none")] + pub next_halving_date: Option, + #[serde(rename = "nextHalvingAmount", skip_serializing_if = "Option::is_none")] + pub next_halving_amount: Option, +} + +impl HalvingResponse { + pub fn new() -> HalvingResponse { + HalvingResponse { + next_halving_timestamp: None, + next_halving_date: None, + next_halving_amount: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/hashrate_history_response.rs b/dymension/libs/kaspa/lib/api/src/models/hashrate_history_response.rs new file mode 100644 index 00000000000..f8c64619436 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/hashrate_history_response.rs @@ -0,0 +1,51 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct HashrateHistoryResponse { + #[serde(rename = "daaScore")] + pub daa_score: i64, + #[serde(rename = "blueScore")] + pub blue_score: i64, + #[serde(rename = "timestamp")] + pub timestamp: i64, + #[serde(rename = "date_time")] + pub date_time: String, + #[serde(rename = "bits", skip_serializing_if = "Option::is_none")] + pub bits: Option, + #[serde(rename = "difficulty")] + pub difficulty: i64, + #[serde(rename = "hashrate_kh")] + pub hashrate_kh: i64, +} + +impl HashrateHistoryResponse { + pub fn new( + daa_score: i64, + blue_score: i64, + timestamp: i64, + date_time: String, + difficulty: i64, + hashrate_kh: i64, + ) -> HashrateHistoryResponse { + HashrateHistoryResponse { + daa_score, + blue_score, + timestamp, + date_time, + bits: None, + difficulty, + hashrate_kh, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/hashrate_response.rs b/dymension/libs/kaspa/lib/api/src/models/hashrate_response.rs new file mode 100644 index 00000000000..fafe0a8e42a --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/hashrate_response.rs @@ -0,0 +1,24 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct HashrateResponse { + #[serde(rename = "hashrate", skip_serializing_if = "Option::is_none")] + pub hashrate: Option, +} + +impl HashrateResponse { + pub fn new() -> HashrateResponse { + HashrateResponse { hashrate: None } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/health_response.rs b/dymension/libs/kaspa/lib/api/src/models/health_response.rs new file mode 100644 index 00000000000..bfeae7c8e2c --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/health_response.rs @@ -0,0 +1,32 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct HealthResponse { + #[serde(rename = "kaspadServers")] + pub kaspad_servers: Vec, + #[serde(rename = "database")] + pub database: Box, +} + +impl HealthResponse { + pub fn new( + kaspad_servers: Vec, + database: models::DbCheckStatus, + ) -> HealthResponse { + HealthResponse { + kaspad_servers, + database: Box::new(database), + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/http_validation_error.rs b/dymension/libs/kaspa/lib/api/src/models/http_validation_error.rs new file mode 100644 index 00000000000..4b78e98fab2 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/http_validation_error.rs @@ -0,0 +1,24 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct HttpValidationError { + #[serde(rename = "detail", skip_serializing_if = "Option::is_none")] + pub detail: Option>, +} + +impl HttpValidationError { + pub fn new() -> HttpValidationError { + HttpValidationError { detail: None } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/kaspad_info_response.rs b/dymension/libs/kaspa/lib/api/src/models/kaspad_info_response.rs new file mode 100644 index 00000000000..a639f920a41 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/kaspad_info_response.rs @@ -0,0 +1,38 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct KaspadInfoResponse { + #[serde(rename = "mempoolSize", skip_serializing_if = "Option::is_none")] + pub mempool_size: Option, + #[serde(rename = "serverVersion", skip_serializing_if = "Option::is_none")] + pub server_version: Option, + #[serde(rename = "isUtxoIndexed", skip_serializing_if = "Option::is_none")] + pub is_utxo_indexed: Option, + #[serde(rename = "isSynced", skip_serializing_if = "Option::is_none")] + pub is_synced: Option, + #[serde(rename = "p2pIdHashed", skip_serializing_if = "Option::is_none")] + pub p2p_id_hashed: Option, +} + +impl KaspadInfoResponse { + pub fn new() -> KaspadInfoResponse { + KaspadInfoResponse { + mempool_size: None, + server_version: None, + is_utxo_indexed: None, + is_synced: None, + p2p_id_hashed: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/kaspad_response.rs b/dymension/libs/kaspa/lib/api/src/models/kaspad_response.rs new file mode 100644 index 00000000000..3f2b2652957 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/kaspad_response.rs @@ -0,0 +1,41 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct KaspadResponse { + #[serde(rename = "kaspadHost", skip_serializing_if = "Option::is_none")] + pub kaspad_host: Option, + #[serde(rename = "serverVersion", skip_serializing_if = "Option::is_none")] + pub server_version: Option, + #[serde(rename = "isUtxoIndexed", skip_serializing_if = "Option::is_none")] + pub is_utxo_indexed: Option, + #[serde(rename = "isSynced", skip_serializing_if = "Option::is_none")] + pub is_synced: Option, + #[serde(rename = "p2pId", skip_serializing_if = "Option::is_none")] + pub p2p_id: Option, + #[serde(rename = "blueScore", skip_serializing_if = "Option::is_none")] + pub blue_score: Option, +} + +impl KaspadResponse { + pub fn new() -> KaspadResponse { + KaspadResponse { + kaspad_host: None, + server_version: None, + is_utxo_indexed: None, + is_synced: None, + p2p_id: None, + blue_score: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/market_cap_response.rs b/dymension/libs/kaspa/lib/api/src/models/market_cap_response.rs new file mode 100644 index 00000000000..a467553429e --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/market_cap_response.rs @@ -0,0 +1,24 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct MarketCapResponse { + #[serde(rename = "marketcap", skip_serializing_if = "Option::is_none")] + pub marketcap: Option, +} + +impl MarketCapResponse { + pub fn new() -> MarketCapResponse { + MarketCapResponse { marketcap: None } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/max_hashrate_response.rs b/dymension/libs/kaspa/lib/api/src/models/max_hashrate_response.rs new file mode 100644 index 00000000000..1c55b9b7a61 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/max_hashrate_response.rs @@ -0,0 +1,29 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct MaxHashrateResponse { + #[serde(rename = "hashrate", skip_serializing_if = "Option::is_none")] + pub hashrate: Option, + #[serde(rename = "blockheader")] + pub blockheader: Box, +} + +impl MaxHashrateResponse { + pub fn new(blockheader: models::EndpointsGetHashrateBlockHeader) -> MaxHashrateResponse { + MaxHashrateResponse { + hashrate: None, + blockheader: Box::new(blockheader), + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/mod.rs b/dymension/libs/kaspa/lib/api/src/models/mod.rs new file mode 100644 index 00000000000..94bb9539023 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/mod.rs @@ -0,0 +1,142 @@ +pub mod acceptance_mode; +pub use self::acceptance_mode::AcceptanceMode; +pub mod address_name; +pub use self::address_name::AddressName; +pub mod addresses_active_request; +pub use self::addresses_active_request::AddressesActiveRequest; +pub mod balance_request; +pub use self::balance_request::BalanceRequest; +pub mod balance_response; +pub use self::balance_response::BalanceResponse; +pub mod balances_by_address_entry; +pub use self::balances_by_address_entry::BalancesByAddressEntry; +pub mod block_model; +pub use self::block_model::BlockModel; +pub mod block_response; +pub use self::block_response::BlockResponse; +pub mod block_reward_response; +pub use self::block_reward_response::BlockRewardResponse; +pub mod block_tx_input_model; +pub use self::block_tx_input_model::BlockTxInputModel; +pub mod block_tx_input_previous_outpoint_model; +pub use self::block_tx_input_previous_outpoint_model::BlockTxInputPreviousOutpointModel; +pub mod block_tx_model; +pub use self::block_tx_model::BlockTxModel; +pub mod block_tx_output_model; +pub use self::block_tx_output_model::BlockTxOutputModel; +pub mod block_tx_output_script_public_key_model; +pub use self::block_tx_output_script_public_key_model::BlockTxOutputScriptPublicKeyModel; +pub mod block_tx_output_verbose_data_model; +pub use self::block_tx_output_verbose_data_model::BlockTxOutputVerboseDataModel; +pub mod block_tx_verbose_data_model; +pub use self::block_tx_verbose_data_model::BlockTxVerboseDataModel; +pub mod blockdag_response; +pub use self::blockdag_response::BlockdagResponse; +pub mod blue_score_response; +pub use self::blue_score_response::BlueScoreResponse; +pub mod coin_supply_response; +pub use self::coin_supply_response::CoinSupplyResponse; +pub mod db_check_status; +pub use self::db_check_status::DbCheckStatus; +pub mod endpoints__get_blocks__block_header; +pub use self::endpoints__get_blocks__block_header::EndpointsGetBlocksBlockHeader; +pub mod endpoints__get_hashrate__block_header; +pub use self::endpoints__get_hashrate__block_header::EndpointsGetHashrateBlockHeader; +pub mod extra_model; +pub use self::extra_model::ExtraModel; +pub mod fee_estimate_bucket; +pub use self::fee_estimate_bucket::FeeEstimateBucket; +pub mod fee_estimate_response; +pub use self::fee_estimate_response::FeeEstimateResponse; +pub mod halving_response; +pub use self::halving_response::HalvingResponse; +pub mod hashrate_history_response; +pub use self::hashrate_history_response::HashrateHistoryResponse; +pub mod hashrate_response; +pub use self::hashrate_response::HashrateResponse; +pub mod health_response; +pub use self::health_response::HealthResponse; +pub mod http_validation_error; +pub use self::http_validation_error::HttpValidationError; +pub mod kaspad_info_response; +pub use self::kaspad_info_response::KaspadInfoResponse; +pub mod kaspad_response; +pub use self::kaspad_response::KaspadResponse; +pub mod market_cap_response; +pub use self::market_cap_response::MarketCapResponse; +pub mod max_hashrate_response; +pub use self::max_hashrate_response::MaxHashrateResponse; +pub mod outpoint_model; +pub use self::outpoint_model::OutpointModel; +pub mod parent_hash_model; +pub use self::parent_hash_model::ParentHashModel; +pub mod previous_outpoint_lookup_mode; +pub use self::previous_outpoint_lookup_mode::PreviousOutpointLookupMode; +pub mod price_response; +pub use self::price_response::PriceResponse; +pub mod response_get_blockreward_info_blockreward_get; +pub use self::response_get_blockreward_info_blockreward_get::ResponseGetBlockrewardInfoBlockrewardGet; +pub mod response_get_halving_info_halving_get; +pub use self::response_get_halving_info_halving_get::ResponseGetHalvingInfoHalvingGet; +pub mod response_get_hashrate_info_hashrate_get; +pub use self::response_get_hashrate_info_hashrate_get::ResponseGetHashrateInfoHashrateGet; +pub mod response_get_marketcap_info_marketcap_get; +pub use self::response_get_marketcap_info_marketcap_get::ResponseGetMarketcapInfoMarketcapGet; +pub mod response_get_price_info_price_get; +pub use self::response_get_price_info_price_get::ResponseGetPriceInfoPriceGet; +pub mod script_public_key_model; +pub use self::script_public_key_model::ScriptPublicKeyModel; +pub mod submit_transaction_request; +pub use self::submit_transaction_request::SubmitTransactionRequest; +pub mod submit_transaction_response; +pub use self::submit_transaction_response::SubmitTransactionResponse; +pub mod submit_tx_input; +pub use self::submit_tx_input::SubmitTxInput; +pub mod submit_tx_model; +pub use self::submit_tx_model::SubmitTxModel; +pub mod submit_tx_outpoint; +pub use self::submit_tx_outpoint::SubmitTxOutpoint; +pub mod submit_tx_output; +pub use self::submit_tx_output::SubmitTxOutput; +pub mod submit_tx_script_public_key; +pub use self::submit_tx_script_public_key::SubmitTxScriptPublicKey; +pub mod transaction_count; +pub use self::transaction_count::TransactionCount; +pub mod tx_acceptance_request; +pub use self::tx_acceptance_request::TxAcceptanceRequest; +pub mod tx_acceptance_response; +pub use self::tx_acceptance_response::TxAcceptanceResponse; +pub mod tx_id_response; +pub use self::tx_id_response::TxIdResponse; +pub mod tx_input; +pub use self::tx_input::TxInput; +pub mod tx_mass; +pub use self::tx_mass::TxMass; +pub mod tx_model; +pub use self::tx_model::TxModel; +pub mod tx_output; +pub use self::tx_output::TxOutput; +pub mod tx_search; +pub use self::tx_search::TxSearch; +pub mod tx_search_accepting_blue_scores; +pub use self::tx_search_accepting_blue_scores::TxSearchAcceptingBlueScores; +pub mod utxo_model; +pub use self::utxo_model::UtxoModel; +pub mod utxo_request; +pub use self::utxo_request::UtxoRequest; +pub mod utxo_response; +pub use self::utxo_response::UtxoResponse; +pub mod validation_error; +pub use self::validation_error::ValidationError; +pub mod validation_error_loc_inner; +pub use self::validation_error_loc_inner::ValidationErrorLocInner; +pub mod vc_block_model; +pub use self::vc_block_model::VcBlockModel; +pub mod vc_tx_input; +pub use self::vc_tx_input::VcTxInput; +pub mod vc_tx_model; +pub use self::vc_tx_model::VcTxModel; +pub mod vc_tx_output; +pub use self::vc_tx_output::VcTxOutput; +pub mod verbose_data_model; +pub use self::verbose_data_model::VerboseDataModel; diff --git a/dymension/libs/kaspa/lib/api/src/models/outpoint_model.rs b/dymension/libs/kaspa/lib/api/src/models/outpoint_model.rs new file mode 100644 index 00000000000..50c61a83770 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/outpoint_model.rs @@ -0,0 +1,29 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct OutpointModel { + #[serde(rename = "transactionId", skip_serializing_if = "Option::is_none")] + pub transaction_id: Option, + #[serde(rename = "index", skip_serializing_if = "Option::is_none")] + pub index: Option, +} + +impl OutpointModel { + pub fn new() -> OutpointModel { + OutpointModel { + transaction_id: None, + index: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/parent_hash_model.rs b/dymension/libs/kaspa/lib/api/src/models/parent_hash_model.rs new file mode 100644 index 00000000000..ee3743fe0d8 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/parent_hash_model.rs @@ -0,0 +1,26 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ParentHashModel { + #[serde(rename = "parentHashes", skip_serializing_if = "Option::is_none")] + pub parent_hashes: Option>, +} + +impl ParentHashModel { + pub fn new() -> ParentHashModel { + ParentHashModel { + parent_hashes: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/previous_outpoint_lookup_mode.rs b/dymension/libs/kaspa/lib/api/src/models/previous_outpoint_lookup_mode.rs new file mode 100644 index 00000000000..ef32f5354fa --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/previous_outpoint_lookup_mode.rs @@ -0,0 +1,40 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +/// PreviousOutpointLookupMode : An enumeration. +/// An enumeration. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +pub enum PreviousOutpointLookupMode { + #[serde(rename = "no")] + No, + #[serde(rename = "light")] + Light, + #[serde(rename = "full")] + Full, +} + +impl std::fmt::Display for PreviousOutpointLookupMode { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::No => write!(f, "no"), + Self::Light => write!(f, "light"), + Self::Full => write!(f, "full"), + } + } +} + +impl Default for PreviousOutpointLookupMode { + fn default() -> PreviousOutpointLookupMode { + Self::No + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/price_response.rs b/dymension/libs/kaspa/lib/api/src/models/price_response.rs new file mode 100644 index 00000000000..9ab677053ef --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/price_response.rs @@ -0,0 +1,24 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct PriceResponse { + #[serde(rename = "price", skip_serializing_if = "Option::is_none")] + pub price: Option, +} + +impl PriceResponse { + pub fn new() -> PriceResponse { + PriceResponse { price: None } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/response_get_blockreward_info_blockreward_get.rs b/dymension/libs/kaspa/lib/api/src/models/response_get_blockreward_info_blockreward_get.rs new file mode 100644 index 00000000000..797d20ff2bc --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/response_get_blockreward_info_blockreward_get.rs @@ -0,0 +1,24 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ResponseGetBlockrewardInfoBlockrewardGet { + #[serde(rename = "blockreward", skip_serializing_if = "Option::is_none")] + pub blockreward: Option, +} + +impl ResponseGetBlockrewardInfoBlockrewardGet { + pub fn new() -> ResponseGetBlockrewardInfoBlockrewardGet { + ResponseGetBlockrewardInfoBlockrewardGet { blockreward: None } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/response_get_halving_info_halving_get.rs b/dymension/libs/kaspa/lib/api/src/models/response_get_halving_info_halving_get.rs new file mode 100644 index 00000000000..ded42cdce5d --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/response_get_halving_info_halving_get.rs @@ -0,0 +1,35 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ResponseGetHalvingInfoHalvingGet { + #[serde( + rename = "nextHalvingTimestamp", + skip_serializing_if = "Option::is_none" + )] + pub next_halving_timestamp: Option, + #[serde(rename = "nextHalvingDate", skip_serializing_if = "Option::is_none")] + pub next_halving_date: Option, + #[serde(rename = "nextHalvingAmount", skip_serializing_if = "Option::is_none")] + pub next_halving_amount: Option, +} + +impl ResponseGetHalvingInfoHalvingGet { + pub fn new() -> ResponseGetHalvingInfoHalvingGet { + ResponseGetHalvingInfoHalvingGet { + next_halving_timestamp: None, + next_halving_date: None, + next_halving_amount: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/response_get_hashrate_info_hashrate_get.rs b/dymension/libs/kaspa/lib/api/src/models/response_get_hashrate_info_hashrate_get.rs new file mode 100644 index 00000000000..cec57c5c147 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/response_get_hashrate_info_hashrate_get.rs @@ -0,0 +1,24 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ResponseGetHashrateInfoHashrateGet { + #[serde(rename = "hashrate", skip_serializing_if = "Option::is_none")] + pub hashrate: Option, +} + +impl ResponseGetHashrateInfoHashrateGet { + pub fn new() -> ResponseGetHashrateInfoHashrateGet { + ResponseGetHashrateInfoHashrateGet { hashrate: None } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/response_get_marketcap_info_marketcap_get.rs b/dymension/libs/kaspa/lib/api/src/models/response_get_marketcap_info_marketcap_get.rs new file mode 100644 index 00000000000..c27e3cb0604 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/response_get_marketcap_info_marketcap_get.rs @@ -0,0 +1,24 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ResponseGetMarketcapInfoMarketcapGet { + #[serde(rename = "marketcap", skip_serializing_if = "Option::is_none")] + pub marketcap: Option, +} + +impl ResponseGetMarketcapInfoMarketcapGet { + pub fn new() -> ResponseGetMarketcapInfoMarketcapGet { + ResponseGetMarketcapInfoMarketcapGet { marketcap: None } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/response_get_price_info_price_get.rs b/dymension/libs/kaspa/lib/api/src/models/response_get_price_info_price_get.rs new file mode 100644 index 00000000000..32a0c399242 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/response_get_price_info_price_get.rs @@ -0,0 +1,24 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ResponseGetPriceInfoPriceGet { + #[serde(rename = "price", skip_serializing_if = "Option::is_none")] + pub price: Option, +} + +impl ResponseGetPriceInfoPriceGet { + pub fn new() -> ResponseGetPriceInfoPriceGet { + ResponseGetPriceInfoPriceGet { price: None } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/script_public_key_model.rs b/dymension/libs/kaspa/lib/api/src/models/script_public_key_model.rs new file mode 100644 index 00000000000..cf8d5ae6cac --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/script_public_key_model.rs @@ -0,0 +1,26 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ScriptPublicKeyModel { + #[serde(rename = "scriptPublicKey", skip_serializing_if = "Option::is_none")] + pub script_public_key: Option, +} + +impl ScriptPublicKeyModel { + pub fn new() -> ScriptPublicKeyModel { + ScriptPublicKeyModel { + script_public_key: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/submit_transaction_request.rs b/dymension/libs/kaspa/lib/api/src/models/submit_transaction_request.rs new file mode 100644 index 00000000000..989c2338371 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/submit_transaction_request.rs @@ -0,0 +1,29 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct SubmitTransactionRequest { + #[serde(rename = "transaction")] + pub transaction: Box, + #[serde(rename = "allowOrphan", skip_serializing_if = "Option::is_none")] + pub allow_orphan: Option, +} + +impl SubmitTransactionRequest { + pub fn new(transaction: models::SubmitTxModel) -> SubmitTransactionRequest { + SubmitTransactionRequest { + transaction: Box::new(transaction), + allow_orphan: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/submit_transaction_response.rs b/dymension/libs/kaspa/lib/api/src/models/submit_transaction_response.rs new file mode 100644 index 00000000000..bfd6264fbf6 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/submit_transaction_response.rs @@ -0,0 +1,29 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct SubmitTransactionResponse { + #[serde(rename = "transactionId", skip_serializing_if = "Option::is_none")] + pub transaction_id: Option, + #[serde(rename = "error", skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +impl SubmitTransactionResponse { + pub fn new() -> SubmitTransactionResponse { + SubmitTransactionResponse { + transaction_id: None, + error: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/submit_tx_input.rs b/dymension/libs/kaspa/lib/api/src/models/submit_tx_input.rs new file mode 100644 index 00000000000..47997bdb592 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/submit_tx_input.rs @@ -0,0 +1,40 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct SubmitTxInput { + #[serde(rename = "previousOutpoint")] + pub previous_outpoint: Box, + #[serde(rename = "signatureScript")] + pub signature_script: String, + #[serde(rename = "sequence")] + pub sequence: i64, + #[serde(rename = "sigOpCount")] + pub sig_op_count: i64, +} + +impl SubmitTxInput { + pub fn new( + previous_outpoint: models::SubmitTxOutpoint, + signature_script: String, + sequence: i64, + sig_op_count: i64, + ) -> SubmitTxInput { + SubmitTxInput { + previous_outpoint: Box::new(previous_outpoint), + signature_script, + sequence, + sig_op_count, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/submit_tx_model.rs b/dymension/libs/kaspa/lib/api/src/models/submit_tx_model.rs new file mode 100644 index 00000000000..6cfa098e965 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/submit_tx_model.rs @@ -0,0 +1,42 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct SubmitTxModel { + #[serde(rename = "version")] + pub version: i64, + #[serde(rename = "inputs")] + pub inputs: Vec, + #[serde(rename = "outputs")] + pub outputs: Vec, + #[serde(rename = "lockTime", skip_serializing_if = "Option::is_none")] + pub lock_time: Option, + #[serde(rename = "subnetworkId", skip_serializing_if = "Option::is_none")] + pub subnetwork_id: Option, +} + +impl SubmitTxModel { + pub fn new( + version: i64, + inputs: Vec, + outputs: Vec, + ) -> SubmitTxModel { + SubmitTxModel { + version, + inputs, + outputs, + lock_time: None, + subnetwork_id: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/submit_tx_outpoint.rs b/dymension/libs/kaspa/lib/api/src/models/submit_tx_outpoint.rs new file mode 100644 index 00000000000..f66a5c77be2 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/submit_tx_outpoint.rs @@ -0,0 +1,29 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct SubmitTxOutpoint { + #[serde(rename = "transactionId")] + pub transaction_id: String, + #[serde(rename = "index")] + pub index: i64, +} + +impl SubmitTxOutpoint { + pub fn new(transaction_id: String, index: i64) -> SubmitTxOutpoint { + SubmitTxOutpoint { + transaction_id, + index, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/submit_tx_output.rs b/dymension/libs/kaspa/lib/api/src/models/submit_tx_output.rs new file mode 100644 index 00000000000..a7f954204f1 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/submit_tx_output.rs @@ -0,0 +1,29 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct SubmitTxOutput { + #[serde(rename = "amount")] + pub amount: i64, + #[serde(rename = "scriptPublicKey")] + pub script_public_key: Box, +} + +impl SubmitTxOutput { + pub fn new(amount: i64, script_public_key: models::SubmitTxScriptPublicKey) -> SubmitTxOutput { + SubmitTxOutput { + amount, + script_public_key: Box::new(script_public_key), + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/submit_tx_script_public_key.rs b/dymension/libs/kaspa/lib/api/src/models/submit_tx_script_public_key.rs new file mode 100644 index 00000000000..0eb7b6a92f8 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/submit_tx_script_public_key.rs @@ -0,0 +1,29 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct SubmitTxScriptPublicKey { + #[serde(rename = "version")] + pub version: i64, + #[serde(rename = "scriptPublicKey")] + pub script_public_key: String, +} + +impl SubmitTxScriptPublicKey { + pub fn new(version: i64, script_public_key: String) -> SubmitTxScriptPublicKey { + SubmitTxScriptPublicKey { + version, + script_public_key, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/transaction_count.rs b/dymension/libs/kaspa/lib/api/src/models/transaction_count.rs new file mode 100644 index 00000000000..60e52a1ce9e --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/transaction_count.rs @@ -0,0 +1,29 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct TransactionCount { + #[serde(rename = "total")] + pub total: i64, + #[serde(rename = "limit_exceeded")] + pub limit_exceeded: bool, +} + +impl TransactionCount { + pub fn new(total: i64, limit_exceeded: bool) -> TransactionCount { + TransactionCount { + total, + limit_exceeded, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/tx_acceptance_request.rs b/dymension/libs/kaspa/lib/api/src/models/tx_acceptance_request.rs new file mode 100644 index 00000000000..612111df22b --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/tx_acceptance_request.rs @@ -0,0 +1,26 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct TxAcceptanceRequest { + #[serde(rename = "transactionIds", skip_serializing_if = "Option::is_none")] + pub transaction_ids: Option>, +} + +impl TxAcceptanceRequest { + pub fn new() -> TxAcceptanceRequest { + TxAcceptanceRequest { + transaction_ids: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/tx_acceptance_response.rs b/dymension/libs/kaspa/lib/api/src/models/tx_acceptance_response.rs new file mode 100644 index 00000000000..fd9f891e3fd --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/tx_acceptance_response.rs @@ -0,0 +1,29 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct TxAcceptanceResponse { + #[serde(rename = "transactionId", skip_serializing_if = "Option::is_none")] + pub transaction_id: Option, + #[serde(rename = "accepted")] + pub accepted: bool, +} + +impl TxAcceptanceResponse { + pub fn new(accepted: bool) -> TxAcceptanceResponse { + TxAcceptanceResponse { + transaction_id: None, + accepted, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/tx_id_response.rs b/dymension/libs/kaspa/lib/api/src/models/tx_id_response.rs new file mode 100644 index 00000000000..4f2565d7122 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/tx_id_response.rs @@ -0,0 +1,26 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct TxIdResponse { + #[serde(rename = "address")] + pub address: String, + #[serde(rename = "active")] + pub active: bool, +} + +impl TxIdResponse { + pub fn new(address: String, active: bool) -> TxIdResponse { + TxIdResponse { address, active } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/tx_input.rs b/dymension/libs/kaspa/lib/api/src/models/tx_input.rs new file mode 100644 index 00000000000..0f31178252e --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/tx_input.rs @@ -0,0 +1,64 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct TxInput { + #[serde(rename = "transaction_id")] + pub transaction_id: String, + #[serde(rename = "index")] + pub index: i64, + #[serde(rename = "previous_outpoint_hash")] + pub previous_outpoint_hash: String, + #[serde(rename = "previous_outpoint_index")] + pub previous_outpoint_index: String, + #[serde( + rename = "previous_outpoint_resolved", + skip_serializing_if = "Option::is_none" + )] + pub previous_outpoint_resolved: Option>, + #[serde( + rename = "previous_outpoint_address", + skip_serializing_if = "Option::is_none" + )] + pub previous_outpoint_address: Option, + #[serde( + rename = "previous_outpoint_amount", + skip_serializing_if = "Option::is_none" + )] + pub previous_outpoint_amount: Option, + #[serde(rename = "signature_script", skip_serializing_if = "Option::is_none")] + pub signature_script: Option, + #[serde(rename = "sig_op_count", skip_serializing_if = "Option::is_none")] + pub sig_op_count: Option, +} + +impl TxInput { + pub fn new( + transaction_id: String, + index: i64, + previous_outpoint_hash: String, + previous_outpoint_index: String, + ) -> TxInput { + TxInput { + transaction_id, + index, + previous_outpoint_hash, + previous_outpoint_index, + previous_outpoint_resolved: None, + previous_outpoint_address: None, + previous_outpoint_amount: None, + signature_script: None, + sig_op_count: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/tx_mass.rs b/dymension/libs/kaspa/lib/api/src/models/tx_mass.rs new file mode 100644 index 00000000000..6879135c1bc --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/tx_mass.rs @@ -0,0 +1,32 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct TxMass { + #[serde(rename = "mass")] + pub mass: i64, + #[serde(rename = "storage_mass")] + pub storage_mass: i64, + #[serde(rename = "compute_mass")] + pub compute_mass: i64, +} + +impl TxMass { + pub fn new(mass: i64, storage_mass: i64, compute_mass: i64) -> TxMass { + TxMass { + mass, + storage_mass, + compute_mass, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/tx_model.rs b/dymension/libs/kaspa/lib/api/src/models/tx_model.rs new file mode 100644 index 00000000000..475b5ff69f1 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/tx_model.rs @@ -0,0 +1,71 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct TxModel { + #[serde(rename = "subnetwork_id", skip_serializing_if = "Option::is_none")] + pub subnetwork_id: Option, + #[serde(rename = "transaction_id", skip_serializing_if = "Option::is_none")] + pub transaction_id: Option, + #[serde(rename = "hash", skip_serializing_if = "Option::is_none")] + pub hash: Option, + #[serde(rename = "mass", skip_serializing_if = "Option::is_none")] + pub mass: Option, + #[serde(rename = "payload", skip_serializing_if = "Option::is_none")] + pub payload: Option, + #[serde(rename = "block_hash", skip_serializing_if = "Option::is_none")] + pub block_hash: Option>, + #[serde(rename = "block_time", skip_serializing_if = "Option::is_none")] + pub block_time: Option, + #[serde(rename = "is_accepted", skip_serializing_if = "Option::is_none")] + pub is_accepted: Option, + #[serde( + rename = "accepting_block_hash", + skip_serializing_if = "Option::is_none" + )] + pub accepting_block_hash: Option, + #[serde( + rename = "accepting_block_blue_score", + skip_serializing_if = "Option::is_none" + )] + pub accepting_block_blue_score: Option, + #[serde( + rename = "accepting_block_time", + skip_serializing_if = "Option::is_none" + )] + pub accepting_block_time: Option, + #[serde(rename = "inputs", skip_serializing_if = "Option::is_none")] + pub inputs: Option>, + #[serde(rename = "outputs", skip_serializing_if = "Option::is_none")] + pub outputs: Option>, +} + +impl TxModel { + pub fn new() -> TxModel { + TxModel { + subnetwork_id: None, + transaction_id: None, + hash: None, + mass: None, + payload: None, + block_hash: None, + block_time: None, + is_accepted: None, + accepting_block_hash: None, + accepting_block_blue_score: None, + accepting_block_time: None, + inputs: None, + outputs: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/tx_output.rs b/dymension/libs/kaspa/lib/api/src/models/tx_output.rs new file mode 100644 index 00000000000..016ea2915f2 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/tx_output.rs @@ -0,0 +1,53 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct TxOutput { + #[serde(rename = "transaction_id")] + pub transaction_id: String, + #[serde(rename = "index")] + pub index: i64, + #[serde(rename = "amount")] + pub amount: i64, + #[serde(rename = "script_public_key", skip_serializing_if = "Option::is_none")] + pub script_public_key: Option, + #[serde( + rename = "script_public_key_address", + skip_serializing_if = "Option::is_none" + )] + pub script_public_key_address: Option, + #[serde( + rename = "script_public_key_type", + skip_serializing_if = "Option::is_none" + )] + pub script_public_key_type: Option, + #[serde( + rename = "accepting_block_hash", + skip_serializing_if = "Option::is_none" + )] + pub accepting_block_hash: Option, +} + +impl TxOutput { + pub fn new(transaction_id: String, index: i64, amount: i64) -> TxOutput { + TxOutput { + transaction_id, + index, + amount, + script_public_key: None, + script_public_key_address: None, + script_public_key_type: None, + accepting_block_hash: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/tx_search.rs b/dymension/libs/kaspa/lib/api/src/models/tx_search.rs new file mode 100644 index 00000000000..78230613431 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/tx_search.rs @@ -0,0 +1,32 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct TxSearch { + #[serde(rename = "transactionIds", skip_serializing_if = "Option::is_none")] + pub transaction_ids: Option>, + #[serde( + rename = "acceptingBlueScores", + skip_serializing_if = "Option::is_none" + )] + pub accepting_blue_scores: Option>, +} + +impl TxSearch { + pub fn new() -> TxSearch { + TxSearch { + transaction_ids: None, + accepting_blue_scores: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/tx_search_accepting_blue_scores.rs b/dymension/libs/kaspa/lib/api/src/models/tx_search_accepting_blue_scores.rs new file mode 100644 index 00000000000..a2614c1705e --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/tx_search_accepting_blue_scores.rs @@ -0,0 +1,26 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct TxSearchAcceptingBlueScores { + #[serde(rename = "gte")] + pub gte: i64, + #[serde(rename = "lt")] + pub lt: i64, +} + +impl TxSearchAcceptingBlueScores { + pub fn new(gte: i64, lt: i64) -> TxSearchAcceptingBlueScores { + TxSearchAcceptingBlueScores { gte, lt } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/utxo_model.rs b/dymension/libs/kaspa/lib/api/src/models/utxo_model.rs new file mode 100644 index 00000000000..8f17eb3e16d --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/utxo_model.rs @@ -0,0 +1,35 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct UtxoModel { + #[serde(rename = "amount", skip_serializing_if = "Option::is_none")] + pub amount: Option, + #[serde(rename = "scriptPublicKey")] + pub script_public_key: Box, + #[serde(rename = "blockDaaScore", skip_serializing_if = "Option::is_none")] + pub block_daa_score: Option, + #[serde(rename = "isCoinbase", skip_serializing_if = "Option::is_none")] + pub is_coinbase: Option, +} + +impl UtxoModel { + pub fn new(script_public_key: models::ScriptPublicKeyModel) -> UtxoModel { + UtxoModel { + amount: None, + script_public_key: Box::new(script_public_key), + block_daa_score: None, + is_coinbase: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/utxo_request.rs b/dymension/libs/kaspa/lib/api/src/models/utxo_request.rs new file mode 100644 index 00000000000..48802b726b4 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/utxo_request.rs @@ -0,0 +1,24 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct UtxoRequest { + #[serde(rename = "addresses", skip_serializing_if = "Option::is_none")] + pub addresses: Option>, +} + +impl UtxoRequest { + pub fn new() -> UtxoRequest { + UtxoRequest { addresses: None } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/utxo_response.rs b/dymension/libs/kaspa/lib/api/src/models/utxo_response.rs new file mode 100644 index 00000000000..9c659b1e491 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/utxo_response.rs @@ -0,0 +1,32 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct UtxoResponse { + #[serde(rename = "address", skip_serializing_if = "Option::is_none")] + pub address: Option, + #[serde(rename = "outpoint")] + pub outpoint: Box, + #[serde(rename = "utxoEntry")] + pub utxo_entry: Box, +} + +impl UtxoResponse { + pub fn new(outpoint: models::OutpointModel, utxo_entry: models::UtxoModel) -> UtxoResponse { + UtxoResponse { + address: None, + outpoint: Box::new(outpoint), + utxo_entry: Box::new(utxo_entry), + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/validation_error.rs b/dymension/libs/kaspa/lib/api/src/models/validation_error.rs new file mode 100644 index 00000000000..4cf2caad4e1 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/validation_error.rs @@ -0,0 +1,32 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ValidationError { + #[serde(rename = "loc")] + pub loc: Vec, + #[serde(rename = "msg")] + pub msg: String, + #[serde(rename = "type")] + pub r#type: String, +} + +impl ValidationError { + pub fn new( + loc: Vec, + msg: String, + r#type: String, + ) -> ValidationError { + ValidationError { loc, msg, r#type } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/validation_error_loc_inner.rs b/dymension/libs/kaspa/lib/api/src/models/validation_error_loc_inner.rs new file mode 100644 index 00000000000..f73458016e8 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/validation_error_loc_inner.rs @@ -0,0 +1,21 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct ValidationErrorLocInner {} + +impl ValidationErrorLocInner { + pub fn new() -> ValidationErrorLocInner { + ValidationErrorLocInner {} + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/vc_block_model.rs b/dymension/libs/kaspa/lib/api/src/models/vc_block_model.rs new file mode 100644 index 00000000000..5c0ef0a8b4b --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/vc_block_model.rs @@ -0,0 +1,38 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct VcBlockModel { + #[serde(rename = "hash")] + pub hash: String, + #[serde(rename = "blue_score")] + pub blue_score: i64, + #[serde(rename = "daa_score", skip_serializing_if = "Option::is_none")] + pub daa_score: Option, + #[serde(rename = "timestamp", skip_serializing_if = "Option::is_none")] + pub timestamp: Option, + #[serde(rename = "transactions", skip_serializing_if = "Option::is_none")] + pub transactions: Option>, +} + +impl VcBlockModel { + pub fn new(hash: String, blue_score: i64) -> VcBlockModel { + VcBlockModel { + hash, + blue_score, + daa_score: None, + timestamp: None, + transactions: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/vc_tx_input.rs b/dymension/libs/kaspa/lib/api/src/models/vc_tx_input.rs new file mode 100644 index 00000000000..31fac6183cf --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/vc_tx_input.rs @@ -0,0 +1,47 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct VcTxInput { + #[serde(rename = "previous_outpoint_hash")] + pub previous_outpoint_hash: String, + #[serde(rename = "previous_outpoint_index")] + pub previous_outpoint_index: i64, + #[serde( + rename = "previous_outpoint_script", + skip_serializing_if = "Option::is_none" + )] + pub previous_outpoint_script: Option, + #[serde( + rename = "previous_outpoint_address", + skip_serializing_if = "Option::is_none" + )] + pub previous_outpoint_address: Option, + #[serde( + rename = "previous_outpoint_amount", + skip_serializing_if = "Option::is_none" + )] + pub previous_outpoint_amount: Option, +} + +impl VcTxInput { + pub fn new(previous_outpoint_hash: String, previous_outpoint_index: i64) -> VcTxInput { + VcTxInput { + previous_outpoint_hash, + previous_outpoint_index, + previous_outpoint_script: None, + previous_outpoint_address: None, + previous_outpoint_amount: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/vc_tx_model.rs b/dymension/libs/kaspa/lib/api/src/models/vc_tx_model.rs new file mode 100644 index 00000000000..5a3f724db38 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/vc_tx_model.rs @@ -0,0 +1,35 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct VcTxModel { + #[serde(rename = "transaction_id")] + pub transaction_id: String, + #[serde(rename = "is_accepted", skip_serializing_if = "Option::is_none")] + pub is_accepted: Option, + #[serde(rename = "inputs", skip_serializing_if = "Option::is_none")] + pub inputs: Option>, + #[serde(rename = "outputs", skip_serializing_if = "Option::is_none")] + pub outputs: Option>, +} + +impl VcTxModel { + pub fn new(transaction_id: String) -> VcTxModel { + VcTxModel { + transaction_id, + is_accepted: None, + inputs: None, + outputs: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/vc_tx_output.rs b/dymension/libs/kaspa/lib/api/src/models/vc_tx_output.rs new file mode 100644 index 00000000000..a95b4f6d6d4 --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/vc_tx_output.rs @@ -0,0 +1,36 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct VcTxOutput { + #[serde(rename = "script_public_key")] + pub script_public_key: String, + #[serde(rename = "script_public_key_address")] + pub script_public_key_address: String, + #[serde(rename = "amount")] + pub amount: i64, +} + +impl VcTxOutput { + pub fn new( + script_public_key: String, + script_public_key_address: String, + amount: i64, + ) -> VcTxOutput { + VcTxOutput { + script_public_key, + script_public_key_address, + amount, + } + } +} diff --git a/dymension/libs/kaspa/lib/api/src/models/verbose_data_model.rs b/dymension/libs/kaspa/lib/api/src/models/verbose_data_model.rs new file mode 100644 index 00000000000..fb0d7cb487a --- /dev/null +++ b/dymension/libs/kaspa/lib/api/src/models/verbose_data_model.rs @@ -0,0 +1,53 @@ +/* + * Kaspa REST-API server + * + * This server is to communicate with kaspa network via REST-API + * + * The version of the OpenAPI document: a6a9569 + * + * Generated by: https://openapi-generator.tech + */ + +use crate::models; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct VerboseDataModel { + #[serde(rename = "hash", skip_serializing_if = "Option::is_none")] + pub hash: Option, + #[serde(rename = "difficulty", skip_serializing_if = "Option::is_none")] + pub difficulty: Option, + #[serde(rename = "selectedParentHash", skip_serializing_if = "Option::is_none")] + pub selected_parent_hash: Option, + #[serde(rename = "transactionIds", skip_serializing_if = "Option::is_none")] + pub transaction_ids: Option>, + #[serde(rename = "blueScore", skip_serializing_if = "Option::is_none")] + pub blue_score: Option, + #[serde(rename = "childrenHashes", skip_serializing_if = "Option::is_none")] + pub children_hashes: Option>, + #[serde( + rename = "mergeSetBluesHashes", + skip_serializing_if = "Option::is_none" + )] + pub merge_set_blues_hashes: Option>, + #[serde(rename = "mergeSetRedsHashes", skip_serializing_if = "Option::is_none")] + pub merge_set_reds_hashes: Option>, + #[serde(rename = "isChainBlock", skip_serializing_if = "Option::is_none")] + pub is_chain_block: Option, +} + +impl VerboseDataModel { + pub fn new() -> VerboseDataModel { + VerboseDataModel { + hash: None, + difficulty: None, + selected_parent_hash: None, + transaction_ids: None, + blue_score: None, + children_hashes: None, + merge_set_blues_hashes: None, + merge_set_reds_hashes: None, + is_chain_block: None, + } + } +} diff --git a/dymension/libs/kaspa/lib/core/Cargo.toml b/dymension/libs/kaspa/lib/core/Cargo.toml new file mode 100644 index 00000000000..f7baf546f3d --- /dev/null +++ b/dymension/libs/kaspa/lib/core/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "core" +version = "0.2.0" +edition = "2021" + +[dependencies] +hardcode = { path = "../hardcode" } +hex = { workspace = true } +kaspa-addresses = { workspace = true } +kaspa-consensus-core = { workspace = true } +kaspa-consensus-client = { workspace = true } +kaspa-grpc-client = { workspace = true } +kaspa-rpc-core = { workspace = true } +kaspa-core = { workspace = true } +kaspa-wrpc-client = { workspace = true } +kaspa-wallet-core = { workspace = true } +kaspa-wallet-keys = { workspace = true } +kaspa-wallet-pskt = { workspace = true } +kaspa-txscript = { workspace = true } +kaspa-hashes = { workspace = true } +secp256k1 = { workspace = true } +workflow-core = { workspace = true } +clap = { workspace = true } +log = { workspace = true } +tracing = { workspace = true } +reqwest = { workspace = true } +api-rs = { path = "../api", package = "openapi" } +tokio = { workspace = true } +url = { workspace = true } +eyre = { workspace = true } +bytes = { workspace = true } +serde_json = { workspace = true } + +serde-value = "0.7.0" +reqwest-middleware = "0.4" +reqwest-ratelimit = "0.4.1" +reqwest-retry = "0.7" +governor = "0.10" +serde = { version = "^1.0", features = ["derive"] } diff --git a/dymension/libs/kaspa/lib/core/README.md b/dymension/libs/kaspa/lib/core/README.md new file mode 100644 index 00000000000..822dcf0306c --- /dev/null +++ b/dymension/libs/kaspa/lib/core/README.md @@ -0,0 +1,3 @@ +## What? + +Contains logic and types shared by validator and relayer agents. diff --git a/dymension/libs/kaspa/lib/core/indexer.md b/dymension/libs/kaspa/lib/core/indexer.md new file mode 100644 index 00000000000..2565569cce2 --- /dev/null +++ b/dymension/libs/kaspa/lib/core/indexer.md @@ -0,0 +1,10 @@ +https://proxy.kaspa.ws/ https://hub.docker.com/r/kaspanet/kaspa-rest-proxy +https://api.kaspa.org/docs https://github.com/supertypo/simply-kaspa-indexer + +https://github.com/supertypo/simply-kaspa-indexer (with https://github.com/kaspa-ng/kaspa-rest-server) + https://api-tn10.kaspa.org/docs + https://api.kaspa.org/docs + +https://proxy.kaspa.ws/ + +https://hub.docker.com/r/kaspanet/kaspa-rest-proxy \ No newline at end of file diff --git a/dymension/libs/kaspa/lib/core/src/api/base.rs b/dymension/libs/kaspa/lib/core/src/api/base.rs new file mode 100644 index 00000000000..df89d6710bf --- /dev/null +++ b/dymension/libs/kaspa/lib/core/src/api/base.rs @@ -0,0 +1,66 @@ +use api_rs::apis::configuration::Configuration; +use governor::{DefaultDirectRateLimiter, Quota, RateLimiter}; +use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; +use reqwest_retry::policies::ExponentialBackoff; +use std::num::NonZeroU32; +use std::sync::Arc; +use std::time::Duration; + +struct FooRateLimiter { + limiter: Arc, +} + +impl reqwest_ratelimit::RateLimiter for FooRateLimiter { + async fn acquire_permit(&self) { + self.limiter.until_ready().await; + } +} + +pub struct RateLimitConfig { + pub max_req_per_second: u32, +} + +impl RateLimitConfig { + pub fn new(max_req_per_minute: u32) -> Self { + Self { + max_req_per_second: max_req_per_minute, + } + } +} + +impl Default for RateLimitConfig { + fn default() -> Self { + Self::new(10) + } +} + +pub fn get_client(config: RateLimitConfig) -> ClientWithMiddleware { + let base = reqwest::Client::new(); + let governor_limiter = RateLimiter::direct(Quota::per_second( + NonZeroU32::new(config.max_req_per_second).unwrap(), + )); + let rl = FooRateLimiter { + limiter: Arc::new(governor_limiter), + }; + + ClientBuilder::new(base) + .with(reqwest_ratelimit::all(rl)) + .with(reqwest_retry::RetryTransientMiddleware::new_with_policy( + ExponentialBackoff::builder() + .retry_bounds(Duration::from_millis(200), Duration::from_secs(10)) + .build_with_max_retries(10), + )) + .build() +} + +pub fn get_config(url: &str, client: ClientWithMiddleware) -> Configuration { + Configuration { + base_path: url.to_string(), + user_agent: Some("OpenAPI-Generator/a6a9569/rust".to_owned()), + client, + basic_auth: None, + oauth_access_token: None, + bearer_access_token: None, + api_key: None, + } +} diff --git a/dymension/libs/kaspa/lib/core/src/api/client.rs b/dymension/libs/kaspa/lib/core/src/api/client.rs new file mode 100644 index 00000000000..c7a9b084078 --- /dev/null +++ b/dymension/libs/kaspa/lib/core/src/api/client.rs @@ -0,0 +1,308 @@ +use super::base::RateLimitConfig; +use std::hash::{Hash, Hasher}; +use std::str::FromStr; + +use kaspa_consensus_core::tx::TransactionId; +use kaspa_hashes::Hash as KaspaHash; + +use super::base::{get_client, get_config}; +use api_rs::apis::configuration::Configuration; +use api_rs::apis::kaspa_addresses_api::get_balance_from_kaspa_address_addresses_kaspa_address_balance_get as get_balance; +use api_rs::apis::kaspa_addresses_api::GetBalanceFromKaspaAddressAddressesKaspaAddressBalanceGetParams; +use api_rs::apis::kaspa_addresses_api::{ + get_full_transactions_for_address_page_addresses_kaspa_address_full_transactions_page_get as transactions_page, + GetFullTransactionsForAddressPageAddressesKaspaAddressFullTransactionsPageGetParams as args, +}; +use api_rs::apis::kaspa_network_info_api::health_state_info_health_get as get_health; +use api_rs::apis::kaspa_transactions_api::{ + get_transaction_transactions_transaction_id_get as get_tx_by_id, + GetTransactionTransactionsTransactionIdGetParams as get_tx_by_id_params, +}; +use api_rs::models::{AcceptanceMode, TxModel, TxOutput}; +use eyre::{Error, Result}; +use reqwest_middleware::ClientWithMiddleware; +use tracing::info; + +#[derive(Debug, Clone)] +pub struct Deposit { + // ATM its a part of Transaction struct, only id, payload, accepted are populated + pub payload: Option, + // #[serde(with = "serde_bytes_fixed_ref")] // TODO: need? + pub id: TransactionId, + pub time: i64, + pub accepted: bool, + pub outputs: Vec, + pub accepting_block_hash: String, + pub accepting_block_time: i64, + pub accepting_block_blue_score: i64, + pub block_hashes: Vec, +} + +impl Hash for Deposit { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + +impl PartialEq for Deposit { + #[inline(always)] + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for Deposit {} + +impl TryFrom for Deposit { + type Error = Error; + + fn try_from(tx: TxModel) -> Result { + let tx_id = tx + .transaction_id + .ok_or(eyre::eyre!("Transaction ID is missing"))?; + let accepted = tx + .is_accepted + .ok_or(eyre::eyre!("Transaction accepted is missing"))?; + let tx_hash = KaspaHash::from_str(&tx_id)?; + let outputs = tx.outputs.ok_or(eyre::eyre!("Outputs are missing"))?; // TODO: outputs may be missing! + let time = tx.block_time.ok_or(eyre::eyre!("Block time not set"))?; + let accepting_block_hash = tx + .accepting_block_hash + .ok_or(eyre::eyre!("Accepting block hash is missing"))?; + let accepting_block_blue_score = tx + .accepting_block_blue_score + .ok_or(eyre::eyre!("Accepting block blue score is missing"))?; + let accepting_block_time = tx + .accepting_block_time + .ok_or(eyre::eyre!("Accepting block time is missing"))?; + let block_hashes = tx + .block_hash + .ok_or(eyre::eyre!("Block hashes are missing"))?; + + Ok(Deposit { + id: tx_hash, + payload: tx.payload, + accepted, + outputs, + time, + accepting_block_hash, + accepting_block_time, + accepting_block_blue_score, + block_hashes, + }) + } +} + +#[derive(Debug, Clone)] +pub struct HttpClient { + pub url: String, + client: ClientWithMiddleware, +} + +impl HttpClient { + pub fn new(url: String, config: RateLimitConfig) -> Self { + let c = get_client(config); + info!(url = %url, "kaspa: created REST API client"); + Self { url, client: c } + } + + pub async fn get_deposits_by_address( + &self, + from_unix_time: Option, + address: &str, + _domain_kas: u32, + ) -> Result> { + /* + https://api-tn10.kaspa.org/docs#/Kaspa%20addresses/get_full_transactions_for_address_page_addresses__kaspaAddress__full_transactions_page_get + */ + let limit: i64 = 500; + let c = self.get_config(); + + info!( + url = ?c.base_path, + address = %address, + from_unix_time = ?from_unix_time, + "kaspa: querying deposits" + ); + + let mut deposits: Vec = Vec::new(); + let mut after = from_unix_time; + + loop { + let page_txs = transactions_page( + &c, + args { + kaspa_address: address.to_string(), + limit: Some(limit), + before: None, + after, + fields: None, + resolve_previous_outpoints: Some("no".to_string()), + acceptance: Some(AcceptanceMode::Accepted), + }, + ) + .await?; + + let page_count = page_txs.len(); + info!( + page_count = page_count, + after = ?after, + "kaspa: received transaction page" + ); + + if page_txs.is_empty() { + break; + } + + let mut newest_block_time: Option = None; + + for tx in page_txs { + if let Some(block_time) = tx.block_time { + newest_block_time = Some( + newest_block_time + .map(|t| t.max(block_time)) + .unwrap_or(block_time), + ); + } + + if !is_valid_escrow_transfer(&tx, &address.to_string())? { + continue; + } + + // TODO: Add back HL payload validation when we have a way to do it without HL deps + // The payload validation was moved to kas_bridge module + // For now, we rely on other validation to filter invalid deposits + + let tx_id = tx.transaction_id.clone(); + let tx_time = tx.block_time; + match Deposit::try_from(tx) { + Ok(deposit) => deposits.push(deposit), + Err(e) => { + info!( + tx_id = ?tx_id, + block_time = ?tx_time, + error = ?e, + "kaspa: skipped invalid deposit" + ); + continue; + } + } + } + + if (page_count as i64) < limit { + break; + } + + after = newest_block_time; + } + + info!( + deposits_count = deposits.len(), + "kaspa: finished querying deposits" + ); + Ok(deposits) + } + + pub fn get_config(&self) -> Configuration { + let url = self.url.strip_suffix("/").unwrap_or(&self.url); + get_config(url, self.client.clone()) + } + + // TODO: we should pass block hash hint in validator (he can get it from relayer) + pub async fn get_tx_by_id(&self, tx_id: &str) -> Result { + info!(tx_id = ?tx_id, "kaspa: querying transaction by id"); + let c = self.get_config(); + + let tx = get_tx_by_id( + &c, + get_tx_by_id_params { + transaction_id: tx_id.to_string(), + block_hash: None, + inputs: Some(true), + outputs: Some(true), + resolve_previous_outpoints: Some("light".to_string()), + }, + ) + .await?; + Ok(tx) + } + + pub async fn get_tx_by_id_slim( + &self, + tx_id: &str, + block_hash_hint: Option, + ) -> Result { + info!(tx_id = ?tx_id, "kaspa: querying transaction by id (slim)"); + let c = self.get_config(); + + let tx = get_tx_by_id( + &c, + get_tx_by_id_params { + transaction_id: tx_id.to_string(), + block_hash: block_hash_hint, + inputs: Some(false), + outputs: Some(false), + resolve_previous_outpoints: Some("no".to_string()), + }, + ) + .await?; + Ok(tx) + } + + pub async fn get_blue_score(&self) -> Result { + let c = self.get_config(); + let res = get_health(&c).await?; + let blue_score = res + .database + .blue_score + .ok_or(eyre::eyre!("Blue score is missing"))?; + Ok(blue_score) + } + + pub async fn get_balance_by_address(&self, address: &str) -> Result { + let c = self.get_config(); + let res = get_balance( + &c, + GetBalanceFromKaspaAddressAddressesKaspaAddressBalanceGetParams { + kaspa_address: address.to_string(), + }, + ) + .await?; + match res.balance { + Some(balance) => Ok(balance), + None => Err(eyre::eyre!("Balance is missing")), + } + } +} + +fn is_valid_escrow_transfer(tx: &TxModel, address: &String) -> Result { + if let Some(output) = &tx.outputs { + for utxo in output { + if let Some(dest) = utxo.script_public_key_address.as_ref() { + if dest == address { + return Ok(true); + } + } + } + } + Ok(false) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + #[ignore] + async fn test_get_tx_by_id() { + let client = HttpClient::new( + "https://api-tn10.kaspa.org/".to_string(), + RateLimitConfig::default(), + ); + let tx_id = "1ffa672605af17906d99ba9506dd49406a2e8a3faa2969ab0c8929373aca51d1"; + let tx = client.get_tx_by_id(tx_id).await; + println!("Tx: {:?}", tx); + } + + // Removed HL-specific parse test - should be in kas_bridge module tests +} diff --git a/dymension/libs/kaspa/lib/core/src/api/mod.rs b/dymension/libs/kaspa/lib/core/src/api/mod.rs new file mode 100644 index 00000000000..8bb2cc843c2 --- /dev/null +++ b/dymension/libs/kaspa/lib/core/src/api/mod.rs @@ -0,0 +1,5 @@ +pub mod base; +pub mod client; + +#[cfg(test)] +mod test; diff --git a/dymension/libs/kaspa/lib/core/src/api/test.rs b/dymension/libs/kaspa/lib/core/src/api/test.rs new file mode 100644 index 00000000000..3076e90bf85 --- /dev/null +++ b/dymension/libs/kaspa/lib/core/src/api/test.rs @@ -0,0 +1,183 @@ +// see terminal output +// cargo test -- --nocapture + +#[cfg(test)] +mod tests { + use crate::api::client::HttpClient; + use eyre::Result; + + use api_rs::apis::kaspa_addresses_api::*; + use api_rs::apis::kaspa_transactions_api::*; + + use crate::api::base::RateLimitConfig; + + const DAN_TESTNET_ADDR: &str = + "kaspatest:qq3r5cj2r3a7kfne7wwwcf0n8kc8e5y3cy2xgm2tcuqygs4lrktswcc3d9l3p"; + + fn t_client() -> HttpClient { + HttpClient::new( + "https://api-tn10.kaspa.org/".to_string(), + RateLimitConfig::default(), + ) + } + + #[tokio::test] + #[ignore = "dont hit real api"] + async fn test_balance() { + let client = t_client(); + let config = client.get_config(); + let addr = DAN_TESTNET_ADDR; + let res = get_balance_from_kaspa_address_addresses_kaspa_address_balance_get( + &config, + GetBalanceFromKaspaAddressAddressesKaspaAddressBalanceGetParams { + kaspa_address: addr.to_string(), + }, + ) + .await + .unwrap(); + println!("res: {:?}", res); + } + + #[tokio::test] + #[ignore = "dont hit real api"] + async fn test_txs() { + let client = t_client(); + let config = client.get_config(); + let addr = DAN_TESTNET_ADDR; + let limit = Some(10); + let field = None; + // let resolve_previous_outpoints = None; + let resolve_previous_outpoints = Some("no".to_string()); + let acceptance = None; + // https://explorer-tn10.kaspa.org/addresses/kaspatest:qr0jmjgh2sx88q9gdegl449cuygp5rh6yarn5h9fh97whprvcsp2ksjkx456f?page=1 + // 2025-06-10 16:23:29 UTC is 1749505409 + // 2025-06-10 17:18:20 UTC is 1749508700 + // add 10 sec buffers + // Lower Bound: 1749505399 + // Upper Bound: 1749508710 + // let lower_bound = Some(1i32); + // let lower_bound = Some(1749505399i32); + // let upper_bound = Some(1749508710i32); + // let lower_bound = Some(0i64); + // let upper_bound = Some(0i64); + let lower_bound = None; + let upper_bound = None; + + // 1 749 572 304 176 + // let upper_bound = None; + + // TODO: i checked and this query indeed finds TXs which are DEPOSITS (as well as spends) + /* + Explorer example + curl 'https://api-tn10.kaspa.org/addresses/kaspatest:qq3r5cj2r3a7kfne7wwwcf0n8kc8e5y3cy2xgm2tcuqygs4lrktswcc3d9l3p/full-transactions?limit=20&offset=0' \ + -H 'accept: ...' \ + -H 'accept-language: en-GB-oxendict,en;q=0.6' \ + -H 'access-control-allow-origin: *' \ + -H 'content-type: application/json' \ + -H 'if-modified-since: Tue, 17 Jun 2025 16:08:14 GMT' \ + -H 'origin: https://explorer-tn10.kaspa.org' \ + -H 'priority: u=1, i' \ + -H 'referer: https://explorer-tn10.kaspa.org/' \ + -H 'sec-ch-ua: "Chromium";v="136", "Brave";v="136", "Not.A/Brand";v="99"' \ + -H 'sec-ch-ua-mobile: ?0' \ + -H 'sec-ch-ua-platform: "macOS"' \ + -H 'sec-fetch-dest: empty' \ + -H 'sec-fetch-mode: cors' \ + -H 'sec-fetch-site: same-site' \ + -H 'sec-gpc: 1' \ + -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36' + */ + + let res = get_full_transactions_for_address_page_addresses_kaspa_address_full_transactions_page_get( + &config, + GetFullTransactionsForAddressPageAddressesKaspaAddressFullTransactionsPageGetParams { + kaspa_address: addr.to_string(), + limit: limit, + before: lower_bound, + after: upper_bound, + fields: field, + resolve_previous_outpoints: resolve_previous_outpoints, + acceptance: acceptance, + }, + ) + .await + .unwrap(); + println!("res: {:?}", res); + } + + #[tokio::test] + #[ignore = "dont hit real api"] + async fn test_tx_by_id() { + let client = t_client(); + let config = client.get_config(); + let tx_id = "1ffa672605af17906d99ba9506dd49406a2e8a3faa2969ab0c8929373aca51d1"; + let tx = get_transaction_transactions_transaction_id_get( + &config, + GetTransactionTransactionsTransactionIdGetParams { + transaction_id: tx_id.to_string(), + block_hash: None, + inputs: Some(true), + outputs: Some(true), + resolve_previous_outpoints: Some("light".to_string()), + }, + ) + .await + .unwrap(); + println!("tx: {:?}", tx); + } + + #[tokio::test] + #[ignore = "dont hit real api"] + async fn test_get_deposits() { + // https://explorer-tn10.kaspa.org/addresses/kaspatest:pzlq49spp66vkjjex0w7z8708f6zteqwr6swy33fmy4za866ne90v7e6pyrfr?page=1 + let client = t_client(); + let address = "kaspatest:pzlq49spp66vkjjex0w7z8708f6zteqwr6swy33fmy4za866ne90v7e6pyrfr"; + + let deposits = client + .get_deposits_by_address( + Some(1751299515650), + address, + dymension_kaspa_hl_constants::HL_DOMAIN_KASPA_TEST10, + ) + .await; + + match deposits { + Ok(deposits) => { + println!("Found deposits: n = {:?}", deposits.len()); + for deposit in deposits { + println!("Deposit: {:?}", deposit); + } + } + Err(e) => { + println!("Query deposits: {:?}", e); + } + } + } + + #[tokio::test] + #[ignore = "dont hit real api"] + async fn test_get_tx_by_id() -> Result<()> { + let client = t_client(); + + let tx_id = "49601485182fa057b000d18993db7756fc5a58823c47b64495d5532add38d2ea"; + let tx = client + .get_tx_by_id(tx_id) + .await + .map_err(|e| eyre::eyre!(e))?; + + println!("Tx: {:?}", tx); + + let addr = tx + .outputs + .ok_or(eyre::eyre!("Tx has no outputs"))? + .first() + .ok_or(eyre::eyre!("Tx has no outputs"))? + .clone() + .script_public_key_address + .ok_or(eyre::eyre!("Tx output has no script public key address"))?; + + assert!(kaspa_addresses::Address::validate(addr.as_str())); + + Ok(()) + } +} diff --git a/dymension/libs/kaspa/lib/core/src/balance.rs b/dymension/libs/kaspa/lib/core/src/balance.rs new file mode 100644 index 00000000000..5b25067fa90 --- /dev/null +++ b/dymension/libs/kaspa/lib/core/src/balance.rs @@ -0,0 +1,28 @@ +use eyre::Result; +use kaspa_addresses::Address; +use kaspa_rpc_core::api::rpc::RpcApi; +use kaspa_wallet_core::error::Error; +use tracing::info; + +pub async fn check_balance( + source: &str, + rpc: &T, + addr: &Address, +) -> Result { + let utxos = rpc + .get_utxos_by_addresses(vec![addr.clone()]) + .await + .map_err(|e| Error::Custom(format!("Getting UTXOs for address: {e}")))?; + + let num = utxos.len(); + let balance: u64 = utxos.into_iter().map(|u| u.utxo_entry.amount).sum(); + + info!( + source = source, + utxo_count = num, + balance = balance, + "kaspa: checked balance" + ); + + Ok(balance) +} diff --git a/dymension/libs/kaspa/lib/core/src/consts.rs b/dymension/libs/kaspa/lib/core/src/consts.rs new file mode 100644 index 00000000000..3e5f739a42d --- /dev/null +++ b/dymension/libs/kaspa/lib/core/src/consts.rs @@ -0,0 +1 @@ +pub const RELAYER_SIG_OP_COUNT: u8 = 1; // relayer UTXOs always expect one single signature diff --git a/dymension/libs/kaspa/lib/core/src/env.rs b/dymension/libs/kaspa/lib/core/src/env.rs new file mode 100644 index 00000000000..99cfdd50b3d --- /dev/null +++ b/dymension/libs/kaspa/lib/core/src/env.rs @@ -0,0 +1,3 @@ +pub fn version() -> &'static str { + env!("CARGO_PKG_VERSION") +} diff --git a/dymension/libs/kaspa/lib/core/src/escrow.rs b/dymension/libs/kaspa/lib/core/src/escrow.rs new file mode 100644 index 00000000000..a8a3b35e16f --- /dev/null +++ b/dymension/libs/kaspa/lib/core/src/escrow.rs @@ -0,0 +1,117 @@ +use kaspa_addresses::{Address, Prefix}; +use kaspa_consensus_core::tx::ScriptPublicKey; +use kaspa_txscript::{ + extract_script_pub_key_address, multisig_redeem_script, pay_to_script_hash_script, +}; +use secp256k1::{rand::thread_rng, Keypair, PublicKey}; +use std::str::FromStr; + +pub fn generate_escrow_priv_key() -> Keypair { + Keypair::new(secp256k1::SECP256K1, &mut thread_rng()) +} + +pub struct Escrow { + pub keys: Vec, // private + required_signatures: u8, +} + +#[derive(Clone, Debug)] +pub struct EscrowPublic { + pub pubs: Vec, + required_signatures: u8, + pub redeem_script: Vec, + pub p2sh: ScriptPublicKey, + pub addr: Address, +} + +impl Escrow { + pub fn new(m: u8, n: u8) -> Self { + let kps = (0..n) + .map(|_| Keypair::new(secp256k1::SECP256K1, &mut thread_rng())) + .collect::>(); + + Self { + keys: kps, + required_signatures: m, + } + } + + pub fn n(&self) -> usize { + self.keys.len() + } + + pub fn m(&self) -> usize { + self.required_signatures as usize + } + + pub fn public(&self, address_prefix: Prefix) -> EscrowPublic { + let pubs = self.keys.iter().map(|kp| kp.public_key()).collect(); + EscrowPublic::from_pubs(pubs, address_prefix, self.m() as u8) + } +} + +impl EscrowPublic { + pub fn n(&self) -> usize { + self.pubs.len() + } + + pub fn m(&self) -> usize { + self.required_signatures as usize + } + + pub fn from_strs(pubs: Vec, prefix: Prefix, required_signatures: u8) -> Self { + let pubs = pubs + .iter() + .map(|pk| PublicKey::from_str(pk.as_str()).unwrap()) + .collect::>(); + Self::from_pubs(pubs, prefix, required_signatures) + } + + pub fn from_pubs(pubs: Vec, prefix: Prefix, required_signatures: u8) -> Self { + let redeem_script = multisig_redeem_script( + pubs.iter().map(|pk| pk.x_only_public_key().0.serialize()), + required_signatures as usize, + ) + .unwrap(); + + let p2sh = pay_to_script_hash_script(&redeem_script); + let addr = extract_script_pub_key_address(&p2sh, prefix).unwrap(); + + EscrowPublic { + required_signatures, + redeem_script, + p2sh, + addr, + pubs, + } + } + + pub fn has_pub(&self, pub_key: &PublicKey) -> bool { + self.pubs.contains(pub_key) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generate_escrow_priv_key() { + let kp = generate_escrow_priv_key(); + let s = serde_json::to_string(&kp).unwrap(); + let kp_parsed: Keypair = serde_json::from_str(&s).unwrap(); + assert_eq!(kp, kp_parsed); + } + + #[test] + fn test_from_pubs() { + let pubs = "035461e2ab2584bc80435c2a3f51c4cf12285992b5e4fdec57f1f8b506134a9087,0218a9fcc6059c1995c70b8f31b2256ac3d4aeca5dffa331fb941a8c5d4bffdd76,03d7a78be7d152498cfb9fb8a89b60723f011435303499e0de7c1bcbf88f87d1b9,02f02a8dc60f124b34e9a8800fb25cf25ac01a3bdcf5a6ea21d2e2569a173dd9b2,028586f127129710cdac6ca1d86be1869bd8a8746db9a2339fde71278dff7fb469,0214d0d6d828c2f3e5ce908978622c5677c1fc53372346a9cff60d1140c54b5e5e,029035bddc82d62454b2d425e205533363d09dc5d9c0d0f74c1f937c2d211c15a1,03e4b95346367e49178c8571e8a649584981d8bd6f920c648e37bbe24f055baf9c"; + let m = 6; + let epub = EscrowPublic::from_strs( + pubs.split(",").map(|s| s.to_string()).collect(), + Prefix::Testnet, + m, + ); + println!("escrow: {:?}", epub); + } +} diff --git a/dymension/libs/kaspa/lib/core/src/finality/README.md b/dymension/libs/kaspa/lib/core/src/finality/README.md new file mode 100644 index 00000000000..18a84b235fa --- /dev/null +++ b/dymension/libs/kaspa/lib/core/src/finality/README.md @@ -0,0 +1,8 @@ +Here is my suggestion: +Iterate get_virtual_chain_from_block(start_hash) - contains accepted_transaction_ids(block hash, txids), as well as removed_chain_block_hashes. Keep track of the accepting_block_hash for each txid and consider txs rejected whenever their accepting_block_hash is in removed_chain_block_hashes. (Make sure to process removed before accepted) +subscribe to SinkBlueScoreChanged (or pull get_sink_blue_score()) - to keep track of the current blue score +look up blue score of accepting block using get_block(accepting block hash) compare it to sink blue score for confirmation counter. +Re: the actual number of confirmations (blue score diff) you should require, it's up to you to decide. Go for some number above 120. Expect the diff to increase by ~10 for each second. + +The above will give you an accurate view of tx acceptance. To get block and tx details you will also need to iterate get_blocks(low_hash). +You can take a look at https://github.com/supertypo/simply-kaspa-indexer it keeps track of all block/tx details as well as acceptance data in a sql database. diff --git a/dymension/libs/kaspa/lib/core/src/finality/finality.rs b/dymension/libs/kaspa/lib/core/src/finality/finality.rs new file mode 100644 index 00000000000..7a442d00271 --- /dev/null +++ b/dymension/libs/kaspa/lib/core/src/finality/finality.rs @@ -0,0 +1,85 @@ +use crate::api::client::HttpClient; +use eyre::Result; +use hardcode::tx::REQUIRED_FINALITY_BLUE_SCORE_CONFIRMATIONS; +use kaspa_consensus_core::network::NetworkId; +use kaspa_wallet_core::utxo::NetworkParams; +use tracing::error; +/// Returns true if the block is unlikely to be reorged +/// Suitable only for sending transactions to Kaspa: the tranaction will fail if any input +/// is reorged. +/// Unsuitable for doing off-chain work such as minting on a bridge. +pub fn is_mature(daa_score_block: u64, daa_score_virtual: u64, network_id: NetworkId) -> bool { + // see https://github.com/kaspanet/rusty-kaspa/blob/v1.0.0/wallet/core/src/storage/transaction/record.rs + let params = NetworkParams::from(network_id); + let maturity = params.user_transaction_maturity_period_daa(); + daa_score_virtual >= daa_score_block + maturity +} + +/// Result of finality check with detailed status information +#[derive(Debug, Clone)] +pub struct FinalityStatus { + pub confirmations: i64, + pub required_confirmations: i64, +} + +impl FinalityStatus { + pub fn is_final(&self) -> bool { + self.confirmations >= self.required_confirmations + } +} + +/// Returns detailed finality status including confirmation count +/// NOTE: not using 'maturity' because don't want to confuse with the less safe maturity concept used by wallets +pub async fn is_safe_against_reorg( + rest_client: &HttpClient, + tx_id: &str, + block_hash_hint: Option, // enables faster lookup +) -> Result { + is_safe_against_reorg_n_confs( + rest_client, + REQUIRED_FINALITY_BLUE_SCORE_CONFIRMATIONS, + tx_id, + block_hash_hint, + ) + .await +} + +/// Checks if a transaction is safe against reorgs with a specified number of confirmations +/// Returns a FinalityStatus with detailed information +pub async fn is_safe_against_reorg_n_confs( + rest_client: &HttpClient, + required_confirmations: i64, + tx_id: &str, + containing_block_hash_hint: Option, // enables faster lookup +) -> Result { + // Note: we use the blue score from the rest client rather than querying against our own WPRC node because + // the rest server anyway delegates this call to its own WRPC node + // we want a consistent view of the network across both the TX query and the virtual blue score query + let virtual_blue_score = rest_client.get_blue_score().await?; + let tx = rest_client + .get_tx_by_id_slim(tx_id, containing_block_hash_hint) + .await?; + if !tx.is_accepted.unwrap_or(false) { + return Ok(FinalityStatus { + confirmations: 0, + required_confirmations, + }); + } + let accepting_blue_score = tx + .accepting_block_blue_score + .ok_or(eyre::eyre!("Accepting block blue score is missing"))?; + + let mut confirmations = virtual_blue_score - accepting_blue_score; + if confirmations < 0 { + confirmations = 0; // This can happen if the accepting block is not yet known to the node + error!( + virtual_blue_score = virtual_blue_score, + accepting_blue_score = accepting_blue_score, + "kaspa: virtual blue score is less than accepting block blue score" + ); + } + Ok(FinalityStatus { + confirmations, + required_confirmations, + }) +} diff --git a/dymension/libs/kaspa/lib/core/src/finality/mod.rs b/dymension/libs/kaspa/lib/core/src/finality/mod.rs new file mode 100644 index 00000000000..018d3858450 --- /dev/null +++ b/dymension/libs/kaspa/lib/core/src/finality/mod.rs @@ -0,0 +1,2 @@ +pub mod finality; +pub use finality::*; diff --git a/dymension/libs/kaspa/lib/core/src/hash.rs b/dymension/libs/kaspa/lib/core/src/hash.rs new file mode 100644 index 00000000000..0c88f4b6859 --- /dev/null +++ b/dymension/libs/kaspa/lib/core/src/hash.rs @@ -0,0 +1,11 @@ +use eyre::{eyre, Result}; +use kaspa_hashes::Hash as KaspaHash; + +/// Convert a hex string to a Kaspa hash. +pub fn hex_to_kaspa_hash(hex_str: &str) -> Result { + let bytes = hex::decode(hex_str).map_err(|e| eyre!("invalid hex: {}", e))?; + let arr: [u8; 32] = bytes + .try_into() + .map_err(|_| eyre!("invalid hash length: expected 32 bytes"))?; + Ok(KaspaHash::from_bytes(arr)) +} diff --git a/dymension/libs/kaspa/lib/core/src/lib.rs b/dymension/libs/kaspa/lib/core/src/lib.rs new file mode 100644 index 00000000000..c4d30db1e12 --- /dev/null +++ b/dymension/libs/kaspa/lib/core/src/lib.rs @@ -0,0 +1,11 @@ +pub mod api; +pub mod balance; +pub mod consts; +pub mod env; +pub mod escrow; +pub mod finality; +pub mod hash; +pub mod pskt; +pub mod wallet; + +pub use secp256k1::Keypair as KaspaSecpKeypair; diff --git a/dymension/libs/kaspa/lib/core/src/pskt.rs b/dymension/libs/kaspa/lib/core/src/pskt.rs new file mode 100644 index 00000000000..e0c47c7df26 --- /dev/null +++ b/dymension/libs/kaspa/lib/core/src/pskt.rs @@ -0,0 +1,222 @@ +use crate::consts::RELAYER_SIG_OP_COUNT; +use eyre::Result; +use kaspa_consensus_client::{ + TransactionOutpoint as ClientTransactionOutpoint, UtxoEntry as ClientUtxoEntry, +}; +use kaspa_consensus_core::config::params::Params; +use kaspa_consensus_core::constants::{TX_VERSION, UNACCEPTED_DAA_SCORE}; +use kaspa_consensus_core::hashing::sighash::{ + calc_schnorr_signature_hash, SigHashReusedValuesUnsync, +}; +use kaspa_consensus_core::hashing::sighash_type::{ + SigHashType, SIG_HASH_ALL, SIG_HASH_ANY_ONE_CAN_PAY, +}; +use kaspa_consensus_core::network::NetworkId; +use kaspa_consensus_core::subnets::SUBNETWORK_ID_NATIVE; +use kaspa_consensus_core::tx::{ + ScriptPublicKey, Transaction, TransactionInput, TransactionOutpoint, TransactionOutput, + UtxoEntry, +}; +use kaspa_hashes::Hash; +use kaspa_wallet_core::tx::MassCalculator; +use kaspa_wallet_core::utxo::UtxoEntryReference; +use kaspa_wallet_pskt::prelude::*; +use std::str::FromStr; + +/// A populated input is a tuple of (TransactionInput, UtxoEntry, optional redeem_script). +/// This represents an input with all the information needed for signing. +pub type PopulatedInput = (TransactionInput, UtxoEntry, Option>); + +/// Builder for creating PopulatedInput instances with sensible defaults +pub struct PopulatedInputBuilder { + tx_id: Hash, + index: u32, + amount: u64, + script_public_key: ScriptPublicKey, + sig_op_count: u8, + block_daa_score: u64, + redeem_script: Option>, +} + +impl PopulatedInputBuilder { + /// Create a new builder with required fields + pub fn new(tx_id: Hash, index: u32, amount: u64, script_public_key: ScriptPublicKey) -> Self { + Self { + tx_id, + index, + amount, + script_public_key, + sig_op_count: RELAYER_SIG_OP_COUNT, // defaults, can be overridden + block_daa_score: UNACCEPTED_DAA_SCORE, // defaults, can be overridden + redeem_script: None, + } + } + + /// Create from a transaction output + pub fn from_output(tx_id: Hash, index: u32, output: &TransactionOutput) -> Self { + Self::new(tx_id, index, output.value, output.script_public_key.clone()) + } + + /// Set sig_op_count (defaults to RELAYER_SIG_OP_COUNT) + pub fn sig_op_count(mut self, count: u8) -> Self { + self.sig_op_count = count; + self + } + + /// Set block DAA score (defaults to UNACCEPTED_DAA_SCORE) + pub fn block_daa_score(mut self, score: u64) -> Self { + self.block_daa_score = score; + self + } + + /// Set redeem script (defaults to None) + pub fn redeem_script(mut self, script: Option>) -> Self { + self.redeem_script = script; + self + } + + /// Build the PopulatedInput + pub fn build(self) -> PopulatedInput { + ( + TransactionInput::new( + TransactionOutpoint::new(self.tx_id, self.index), + vec![], // signature_script always starts empty + u64::MAX, // sequence always u64::MAX + self.sig_op_count, + ), + UtxoEntry::new( + self.amount, + self.script_public_key, + self.block_daa_score, + false, + ), + self.redeem_script, + ) + } +} + +/// Simple helper for common cases where you just need the defaults +pub fn populated_input( + tx_id: Hash, + index: u32, + amount: u64, + script_public_key: ScriptPublicKey, +) -> PopulatedInputBuilder { + PopulatedInputBuilder::new(tx_id, index, amount, script_public_key) +} + +/// Returns the standard sighash type used for Kaspa bridge transactions. +/// This combines SIG_HASH_ALL with SIG_HASH_ANY_ONE_CAN_PAY. +pub fn input_sighash_type() -> SigHashType { + SigHashType::from_u8(SIG_HASH_ALL.to_u8() | SIG_HASH_ANY_ONE_CAN_PAY.to_u8()).unwrap() +} + +/// Validates that the given sighash type matches the expected bridge sighash type. +pub fn is_valid_sighash_type(t: SigHashType) -> bool { + t.to_u8() == input_sighash_type().to_u8() +} + +pub fn sign_pskt( + pskt: PSKT, + key_pair: &secp256k1::Keypair, + source: Option, + input_filter: Option bool>, +) -> Result> { + // reused_values is something copied from the `pskb_signer_for_address` funciton + let reused_values = SigHashReusedValuesUnsync::new(); + + let ok: Vec = pskt + .inputs + .iter() + .map(|input| input_filter.as_ref().is_none_or(|filter| filter(input))) + .collect(); + + pskt.pass_signature_sync(|tx, sighash| { + tx.tx + .inputs + .iter() + .enumerate() + .map(|(idx, _input)| { + if !ok[idx] { + // we dont want to sign this input but the API constraints make us do it, so we supply junk data to make things not crash + return Ok(SignInputOk { + signature: Signature::Schnorr( + secp256k1::schnorr::Signature::from_slice(&[0; 64]).unwrap(), + ), + pub_key: secp256k1::PublicKey::from_str( + "02eea60b50f48beafdfd737fecf50be79cb2a415f4dc0210931ad8ffcb933e3370", + ) + .unwrap(), + key_source: None, + }); + } + let hash = calc_schnorr_signature_hash( + &tx.as_verifiable(), + idx, + sighash[idx], + &reused_values, + ); + let msg = secp256k1::Message::from_digest_slice(&hash.as_bytes()) + .map_err(|e| eyre::eyre!("Failed to convert hash to message: {}", e))?; + Ok(SignInputOk { + signature: Signature::Schnorr(key_pair.sign_schnorr(msg)), + pub_key: key_pair.public_key(), + key_source: source.clone(), + }) + }) + .collect() + }) +} + +/// Convert a PopulatedInput to a UtxoEntryReference for mass calculation. +pub fn utxo_reference_from_populated_input( + (input, entry, _redeem_script): PopulatedInput, +) -> UtxoEntryReference { + UtxoEntryReference::from(ClientUtxoEntry { + address: None, + outpoint: ClientTransactionOutpoint::from(input.previous_outpoint), + amount: entry.amount, + script_public_key: entry.script_public_key.clone(), + block_daa_score: entry.block_daa_score, + is_coinbase: entry.is_coinbase, + }) +} + +/// Estimate the transaction mass for a set of populated inputs, outputs, and payload. +/// This is used to determine if a transaction fits within Kaspa's mass limits. +pub fn estimate_mass( + populated_inputs: Vec, + outputs: Vec, + payload: Vec, + network_id: NetworkId, + min_signatures: u16, +) -> Result { + let (inputs, utxo_references): (Vec<_>, Vec<_>) = populated_inputs + .into_iter() + .map(|populated| { + let input = populated.0.clone(); + let utxo_ref = utxo_reference_from_populated_input(populated); + (input, utxo_ref) + }) + .unzip(); + + let tx = Transaction::new( + TX_VERSION, + inputs, + outputs, + 0, // no tx lock time + SUBNETWORK_ID_NATIVE, + 0, + payload, + ); + + let p = Params::from(network_id); + let m = MassCalculator::new(&p); + + m.calc_overall_mass_for_unsigned_consensus_transaction( + &tx, + utxo_references.as_slice(), + min_signatures, + ) + .map_err(|e| eyre::eyre!(e)) +} diff --git a/dymension/libs/kaspa/lib/core/src/wallet.rs b/dymension/libs/kaspa/lib/core/src/wallet.rs new file mode 100644 index 00000000000..d2179e84614 --- /dev/null +++ b/dymension/libs/kaspa/lib/core/src/wallet.rs @@ -0,0 +1,305 @@ +use eyre::Result; +use kaspa_addresses::{Prefix, Version}; +use kaspa_consensus_core::network::{NetworkId, NetworkType}; +use kaspa_wallet_core::api::WalletApi; +use kaspa_wallet_core::derivation::build_derivate_paths; +use kaspa_wallet_core::error::Error; +use kaspa_wallet_core::prelude::*; +use kaspa_wallet_core::storage::local::set_default_storage_folder as unsafe_set_default_storage_folder_kaspa; // Import the prelude for easy access to traits/structs +use kaspa_wallet_core::utxo::NetworkParams; +use kaspa_wallet_core::wallet::Wallet; +use kaspa_wallet_keys::secret::Secret; +use kaspa_wallet_pskt::prelude::KeySource; +use kaspa_wrpc_client::Resolver; +use std::fmt; +use std::sync::Arc; +use tracing::info; + +pub async fn get_wallet( + s: &Secret, + network_id: NetworkId, + url: String, + storage_folder: Option, +) -> Result, Error> { + if let Some(storage_folder) = storage_folder { + unsafe { unsafe_set_default_storage_folder_kaspa(storage_folder) }?; + } + + let local_store = Wallet::local_store() + .map_err(|e| Error::from(format!("Failed to open wallet local store: {e}")))?; + + let w = Arc::new( + Wallet::try_new(local_store, Some(Resolver::default()), Some(network_id)) + .map_err(|e| Error::from(format!("Failed to create wallet: {e}")))?, + ); + + // Start background services (UTXO processor, event handling). + w.start() + .await + .map_err(|e| Error::from(format!("Failed to start wallet: {e}")))?; + + w.clone() + .connect(Some(url), &network_id) + .await + .map_err(|e| Error::from(format!("Failed to connect wallet: {e}")))?; + + let is_c = w.is_connected(); + info!(connected = is_c, "kaspa: wallet connection status"); + + info!("kaspa: wallet secret loaded"); + + w.clone() + .wallet_open(s.clone(), None, true, false) + .await + .map_err(|e| Error::from(format!("Failed to open wallet: {e}")))?; + + let accounts = w + .clone() + .accounts_enumerate() + .await + .map_err(|e| Error::from(format!("Failed to enumerate accounts: {e}")))?; + + let account_descriptor = accounts.first().ok_or("Wallet has no accounts.")?; + + let account_id = account_descriptor.account_id; + info!( + account_id = ?account_id, + receive_address = ?account_descriptor.receive_address, + change_address = ?account_descriptor.change_address, + "kaspa: wallet account loaded" + ); + + w.clone() + .accounts_select(Some(account_id)) + .await + .map_err(|e| Error::from(format!("Failed to select wallet account: {e}")))?; + + w.clone() + .accounts_activate(Some(vec![account_id])) + .await + .map_err(|e| Error::from(format!("Failed to activate wallet account: {e}")))?; + + Ok(w) +} + +#[derive(Clone)] +pub struct EasyKaspaWallet { + pub wallet: Arc, + pub secret: Secret, + pub net: NetworkInfo, +} + +// Implement Debug for your wrapper +impl fmt::Debug for EasyKaspaWallet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "EasyKaspaWallet(<...>)") // TODO: + } +} + +pub struct EasyKaspaWalletArgs { + pub wallet_secret: String, // this the short password that protects the keychain, not the private key of the crypto account + pub wrpc_url: String, // .e.g localhost:16210 + pub net: Network, + pub storage_folder: Option, +} + +impl EasyKaspaWallet { + pub async fn try_new(args: EasyKaspaWalletArgs) -> Result { + let s = Secret::from(args.wallet_secret); + let info = NetworkInfo::new(args.net, args.wrpc_url); + let w = get_wallet( + &s, + info.clone().network_id, + info.clone().rpc_url, + args.storage_folder, + ) + .await?; + let node_info = w.rpc_api().get_server_info().await?; + if !node_info.is_synced { + return Err(eyre::eyre!("Kaspa WPRC node is not synced")); + } + if !node_info.has_utxo_index { + return Err(eyre::eyre!("Kaspa WPRC node does not have utxo index")); + } + Ok(Self { + wallet: w, + secret: s, + net: info, + }) + } + + fn api(&self) -> Arc { + self.wallet.rpc_api() + } + + pub fn account(&self) -> Arc { + self.wallet.account().unwrap() + } + + pub async fn signing_resources(&self) -> Result { + // The code above combines `Account.pskb_sign` and `pskb_signer_for_address` functions. + // It's a hack allowing to sign PSKT with a custom payload. + // https://github.com/kaspanet/rusty-kaspa/blob/eb71df4d284593fccd1342094c37edc8c000da85/wallet/core/src/account/pskb.rs#L154 + // https://github.com/kaspanet/rusty-kaspa/blob/eb71df4d284593fccd1342094c37edc8c000da85/wallet/core/src/account/mod.rs#L383 + let w = self.wallet.clone(); + let derivation = w.account()?.as_derivation_capable()?; + let keydata = w.account()?.prv_key_data(self.secret.clone()).await?; + let addr = w.account()?.change_address()?; + let (receive, change) = derivation.derivation().addresses_indexes(&[&addr])?; + let pks = derivation.create_private_keys(&keydata, &None, &receive, &change)?; + let (_, priv_key) = pks.first().unwrap(); + + let xprv = keydata.get_xprv(None)?; + let key_pair = secp256k1::Keypair::from_secret_key(secp256k1::SECP256K1, priv_key); + + // Get derivation path for the account. build_derivate_paths returns receive and change paths, respectively. + // Use receive one as it is used in `Account.pskb_sign`. + let (derivation_path, _) = build_derivate_paths( + &derivation.account_kind(), + derivation.account_index(), + derivation.cosigner_index(), + )?; + + let key_fingerprint = xprv.public_key().fingerprint(); + + Ok(SigningResources { + key_source: KeySource::new(key_fingerprint, derivation_path), + key_pair, + }) + } + + pub async fn pub_key(&self) -> Result { + Ok(self.signing_resources().await?.key_pair.public_key()) + } + + /// Reconnect the wallet's WRPC connection + pub async fn reconnect(&self) -> Result<()> { + self.wallet + .clone() + .connect(Some(self.net.rpc_url.clone()), &self.net.network_id) + .await + .map_err(|e| eyre::eyre!("reconnect WRPC: {}", e))?; + info!("kaspa: reconnected WRPC"); + Ok(()) + } + + /// Execute RPC operation with automatic reconnection on error + pub async fn rpc_with_reconnect(&self, op: F) -> Result + where + F: Fn(Arc) -> Fut, + Fut: std::future::Future>, + { + let api = self.api(); + match op(api).await { + Ok(result) => Ok(result), + Err(e) => { + let err_str = e.to_string(); + if err_str.contains("WebSocket is not connected") + || err_str.contains("not connected") + { + if let Err(reconnect_err) = self.reconnect().await { + tracing::error!(reconnect_error = %reconnect_err, "kaspa: failed to reconnect WRPC"); + return Err(e); + } + let new_api = self.api(); + op(new_api).await + } else { + Err(e) + } + } + } + } +} + +pub struct SigningResources { + pub key_source: KeySource, + pub key_pair: secp256k1::Keypair, +} + +#[derive(Clone, Debug)] +pub struct NetworkInfo { + pub network_id: NetworkId, + pub network_type: NetworkType, + pub address_prefix: Prefix, + pub address_version: Version, + pub rpc_url: String, +} + +impl NetworkInfo { + pub fn network_params(&self) -> &NetworkParams { + NetworkParams::from(self.network_id) + } +} + +#[derive(Clone, Debug)] +pub enum Network { + KaspaTest10, + KaspaMainnet, +} + +impl NetworkInfo { + pub fn new(network: Network, rpc_url: String) -> Self { + match network { + Network::KaspaTest10 => Self { + network_id: NetworkId::with_suffix(NetworkType::Testnet, 10), + network_type: NetworkType::Testnet, + address_prefix: Prefix::Testnet, + address_version: Version::PubKey, + rpc_url, + }, + Network::KaspaMainnet => Self { + network_id: NetworkId::new(NetworkType::Mainnet), + network_type: NetworkType::Mainnet, + address_prefix: Prefix::Mainnet, + address_version: Version::PubKey, + rpc_url, + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + #[test] + fn test_network_id() { + let network = NetworkId::with_suffix(NetworkType::Testnet, 10); + println!("network: {:?}", network.to_string()); + + let name = "testnet-10"; + let id = NetworkId::from_str(name).unwrap(); + println!("id: {:?}", id.to_string()); + } + + #[tokio::test] + #[ignore] + async fn test_create_new_easy_wallet() { + let rpc_url = "65.109.145.174".to_string(); // A public rpc url + let network = Network::KaspaTest10; + let _net_info = NetworkInfo::new(network.clone(), rpc_url.clone()); + + let secret = "lkjsdf"; + let easy_wallet = EasyKaspaWallet::try_new(EasyKaspaWalletArgs { + wallet_secret: secret.to_string(), + wrpc_url: rpc_url.clone(), + net: network, + storage_folder: None, + }) + .await + .unwrap(); + + let utxos = easy_wallet + .api() + .get_utxos_by_addresses(vec![Address::try_from( + // playground escrow + "kaspatest:pp07zhcxnm4zkw3k4d0vr6efhef8c7yg47ukdxe5uhmtgt5s4ayr6rud2mst2", + ) + .unwrap()]) + .await + .unwrap(); + assert!(!utxos.is_empty()); + assert!(0 < utxos.len()); + } +} diff --git a/dymension/libs/kaspa/lib/hardcode/Cargo.toml b/dymension/libs/kaspa/lib/hardcode/Cargo.toml new file mode 100644 index 00000000000..19f10c25438 --- /dev/null +++ b/dymension/libs/kaspa/lib/hardcode/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "hardcode" +version = "0.2.0" +edition = "2021" + +[dependencies] +kaspa-addresses = { workspace = true } +kaspa-consensus-core = { workspace = true } +api-rs = { path = "../../lib/api", package = "openapi" } +reqwest-middleware = "0.4" +reqwest = { workspace = true } \ No newline at end of file diff --git a/dymension/libs/kaspa/lib/hardcode/src/e2e.rs b/dymension/libs/kaspa/lib/hardcode/src/e2e.rs new file mode 100644 index 00000000000..6f95219e4de --- /dev/null +++ b/dymension/libs/kaspa/lib/hardcode/src/e2e.rs @@ -0,0 +1,33 @@ +/* For e2e tests and testing only */ + +use api_rs::apis::configuration; +use kaspa_addresses::{Prefix, Version}; +use kaspa_consensus_core::network::{NetworkId, NetworkType}; + +pub const NETWORK: NetworkType = NetworkType::Testnet; +pub const NETWORK_ID: NetworkId = NetworkId::with_suffix(NETWORK, 10); +pub const MIN_DEPOSIT_SOMPI: u64 = 4_000_000_000; +pub const ADDRESS_PREFIX: Prefix = Prefix::Testnet; +pub const ADDRESS_VERSION: Version = Version::PubKey; +pub const URL: &str = "localhost:17210"; // local node wrpc to testnet10 + +pub const DEPOSIT_AMOUNT: u64 = MIN_DEPOSIT_SOMPI; + +// How much relayer spends to deliver the tx to the network +pub const RELAYER_NETWORK_FEE: u64 = 5000; + +// TODO: remove +pub const ESCROW_ADDRESS: &str = + "kaspatest:qzwyrgapjnhtjqkxdrmp7fpm3yddw296v2ajv9nmgmw5k3z0r38guevxyk7j0"; + +pub fn get_tn10_config() -> configuration::Configuration { + configuration::Configuration { + base_path: "https://api-tn10.kaspa.org".to_string(), + user_agent: Some("OpenAPI-Generator/a6a9569/rust".to_owned()), + client: reqwest_middleware::ClientBuilder::new(reqwest::Client::new()).build(), + basic_auth: None, + oauth_access_token: None, + bearer_access_token: None, + api_key: None, + } +} diff --git a/dymension/libs/kaspa/lib/hardcode/src/lib.rs b/dymension/libs/kaspa/lib/hardcode/src/lib.rs new file mode 100644 index 00000000000..20a7b4ca349 --- /dev/null +++ b/dymension/libs/kaspa/lib/hardcode/src/lib.rs @@ -0,0 +1,2 @@ +pub mod e2e; +pub mod tx; diff --git a/dymension/libs/kaspa/lib/hardcode/src/tx.rs b/dymension/libs/kaspa/lib/hardcode/src/tx.rs new file mode 100644 index 00000000000..7a095f07b3b --- /dev/null +++ b/dymension/libs/kaspa/lib/hardcode/src/tx.rs @@ -0,0 +1,47 @@ +pub const DUST_AMOUNT: u64 = 20_000_001; + +// Kaspa sweeping PSKT generator incorrectly computes TX fee if there is one party in the multisig. +// Add this small priproty fee to every sweeping TX to ensure the sufficient fee even if there +// is one validator. 3_000 is a magic number. +// TODO: make it configurable? +pub const RELAYER_SWEEPING_PRIORITY_FEE: u64 = 3_000; + +// Only sweep is the threshold is exceeded. It doesn't make sense to sweep less UTXOs. +// The value is the total number of UTXOs including the anchor UTXO. +// TODO: make it configurable? +pub const SWEEPING_THRESHOLD: usize = 3; + +/* +In Kaspa, every node has a different and eventually converging view of the network. +Nodes run a local algorithm where they connect gossiped blocks in a DAG. The DAG has a set of blue blocks and a set of red blocks. +A TX can be contained in one or more blocks, but will not take effect on the state machine unless it is contained in a blue block, or +it's contained in a red block which is an ancestor of a blue block. Such a blue block is called the accepting block. + +A TX can appear to be accepted on a node but may be reorged if the node's view of the network changes. This can happen if a different +fork becomes heavier (has more blue blocks in its DAG). Therefore the way to ensure a low probability of reorg is to ensure that +- the accepting block is an ancestor of the current DAG 'tip' (where tip is a virtual block connected to all dangling blue blocks) +- the tip has a sufficiently higher blue score than the accepting block + +The REST API takes care of maintaining an up to date view of the network. It can tell us the blue score of the accepting block of a TX +as well as the current blue score of the tip. We numerically compare these to determine if the TX is final, where final means will +reorg with an acceptably low probability. + + +Probabilities: +- The numbers here are a combination of GPT and advice from discord, the true numbers can be derived from network parameters (K=124) +- Kucoin centralized exchange uses 1000 confirmations for Kaspa deposits (source Discord) +- 100-200 confirmations is suitable (source Discord) + + +Blue Score Difference (d) Time Elapsed (Approx) Upper Bound on Reorg Probability (vs 33% attacker) Security Level +10 1 second < 56% Very Low +100 10 seconds < 0.31% Low (Comparable to 1-2 Bitcoin confirmations) +200 20 seconds < 0.00095% (9.5 x 10^-6) Moderate (Comparable to 6 Bitcoin confirmations) +1,000 ~1.7 minutes < 8.8 x 10^-26 Extremely High (Far exceeds typical blockchain finality) +6,000 10 minutes < 2.8 x 10^-151 Effectively Absolute / Cryptographically Final + +We choose 1000, which is probably over conservative. + */ +pub const REQUIRED_FINALITY_BLUE_SCORE_CONFIRMATIONS: i64 = 1000; + +pub const MAX_MASS_MARGIN: f64 = 0.9; // 90% of the max mass diff --git a/dymension/libs/kaspa/lib/kms/Cargo.toml b/dymension/libs/kaspa/lib/kms/Cargo.toml new file mode 100644 index 00000000000..a86cecd610f --- /dev/null +++ b/dymension/libs/kaspa/lib/kms/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "dym-kas-kms" +version = "0.1.0" +edition = "2021" + +[dependencies] +aws-config = "1.5" +aws-sdk-secretsmanager = "1.52" +aws-sdk-kms = "1.48" +tokio = { workspace = true } +eyre = { workspace = true } +tracing = { workspace = true } +serde_json = { workspace = true } +secp256k1 = { workspace = true } diff --git a/dymension/libs/kaspa/lib/kms/src/lib.rs b/dymension/libs/kaspa/lib/kms/src/lib.rs new file mode 100644 index 00000000000..67aa5bea0f3 --- /dev/null +++ b/dymension/libs/kaspa/lib/kms/src/lib.rs @@ -0,0 +1,71 @@ +use aws_config::BehaviorVersion; +use aws_sdk_kms::Client as KmsClient; +use aws_sdk_secretsmanager::Client as SecretsManagerClient; +use eyre::{eyre, Result}; + +pub type KaspaSecpKeypair = secp256k1::Keypair; + +#[derive(Debug, Clone)] +pub struct AwsKeyConfig { + pub secret_id: String, + pub kms_key_id: String, + pub region: String, +} + +pub async fn load_kaspa_keypair_from_aws(config: &AwsKeyConfig) -> Result { + let aws_config = aws_config::defaults(BehaviorVersion::latest()) + .region(aws_config::Region::new(config.region.clone())) + .load() + .await; + + let secrets_client = SecretsManagerClient::new(&aws_config); + + let secret_value = secrets_client + .get_secret_value() + .secret_id(&config.secret_id) + .send() + .await + .map_err(|_| eyre!("get secret value from AWS Secrets Manager"))?; + + tracing::info!( + secret_id = %config.secret_id, + "fetched secret from AWS Secrets Manager" + ); + + let encrypted_key_material = secret_value + .secret_binary() + .ok_or_else(|| eyre!("secret binary data not found in AWS secret - ensure the secret was created with binary storage, not string"))?; + + let mut key_bytes = encrypted_key_material.as_ref().to_vec(); + + let kms_client = KmsClient::new(&aws_config); + + let decrypt_output = kms_client + .decrypt() + .key_id(&config.kms_key_id) + .ciphertext_blob(aws_sdk_kms::primitives::Blob::new(key_bytes.clone())) + .send() + .await + .map_err(|_| eyre!("decrypt key material using AWS KMS"))?; + + let decrypted_key_material = decrypt_output + .plaintext() + .ok_or_else(|| eyre!("plaintext not found in KMS decrypt response"))?; + + tracing::info!( + kms_key_id = %config.kms_key_id, + "decrypted key material using AWS KMS" + ); + + let decrypted_str = String::from_utf8(decrypted_key_material.as_ref().to_vec()) + .map_err(|_| eyre!("decrypted key material is not valid UTF-8"))?; + + let keypair: KaspaSecpKeypair = serde_json::from_str(&decrypted_str) + .map_err(|_| eyre!("parse decrypted key material as KaspaSecpKeypair JSON"))?; + + key_bytes.iter_mut().for_each(|b| *b = 0); + + tracing::info!("loaded Kaspa keypair from AWS"); + + Ok(keypair) +} diff --git a/dymension/libs/kaspa/scripts/open-api/.gitignore b/dymension/libs/kaspa/scripts/open-api/.gitignore new file mode 100644 index 00000000000..82a9c44291c --- /dev/null +++ b/dymension/libs/kaspa/scripts/open-api/.gitignore @@ -0,0 +1 @@ +stripped.json \ No newline at end of file diff --git a/dymension/libs/kaspa/scripts/open-api/README.md b/dymension/libs/kaspa/scripts/open-api/README.md new file mode 100644 index 00000000000..72b64bb282e --- /dev/null +++ b/dymension/libs/kaspa/scripts/open-api/README.md @@ -0,0 +1,41 @@ +## What? + +A generated rust library for + +- https://kas.fyi/ +- https://api-tn10.kaspa.org/docs + +generates to lib/api + +## FAQ + +1. X doesn't work | Maybe the codegen is wrong or the openapi spec is wrong. + +## Commit + +Used `Tue 17 Jun 2025 14:31:33 BST` version of https://api.kaspa.org/docs + +## Steps + +``` +brew install openapi-generator + +openapi-generator version +# 7.13.0 + +# the API author has included some non regular tags (learned in discord: https://github.com/supertypo/kaspa-rest-proxy/issues/1) +jq 'walk(if type == "object" and has("strict_query_params") then del(.strict_query_params) else . end)' openapi.json > stripped.json + +## (ALSO NEED TO REMOVE LICENSE PART OF JSON) + +openapi-generator generate -i stripped.json -g rust -o ../../lib/api --additional-properties=supportMiddleware=true,topLevelApiClient=true,useBonBuilder=true,useSingleRequestParameter=true,supportAsync=true + + +## NOTE: THEN IT IS NECESSARY TO FIX A BUILD ERROR(S) + +1. there is an incorrect path 'models::models::...' it should be just 'models::...' (manual fix) +2. Replace i32 with i64 +3. Rename cargo package name and fix the version number + + +``` diff --git a/dymension/libs/kaspa/scripts/open-api/openapi.json b/dymension/libs/kaspa/scripts/open-api/openapi.json new file mode 100644 index 00000000000..d536b1a1dae --- /dev/null +++ b/dymension/libs/kaspa/scripts/open-api/openapi.json @@ -0,0 +1,3372 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Kaspa REST-API server", + "description": "This server is to communicate with kaspa network via REST-API", + "contact": { + "name": "lAmeR1" + }, + "license": { + "name": "MIT LICENSE" + }, + "version": "a6a9569" + }, + "paths": { + "/addresses/{kaspaAddress}/balance": { + "get": { + "tags": [ + "Kaspa addresses" + ], + "summary": "Get Balance From Kaspa Address", + "description": "Get balance for a given kaspa address", + "operationId": "get_balance_from_kaspa_address_addresses__kaspaAddress__balance_get", + "parameters": [ + { + "description": "Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73", + "required": true, + "schema": { + "type": "string", + "pattern": "^kaspa:[a-z0-9]{61,63}$", + "title": "Kaspaaddress", + "description": "Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73" + }, + "name": "kaspaAddress", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BalanceResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/addresses/{kaspaAddress}/utxos": { + "get": { + "tags": [ + "Kaspa addresses" + ], + "summary": "Get Utxos For Address", + "description": "Lists all open utxo for a given kaspa address", + "operationId": "get_utxos_for_address_addresses__kaspaAddress__utxos_get", + "parameters": [ + { + "description": "Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73", + "required": true, + "schema": { + "type": "string", + "pattern": "^kaspa:[a-z0-9]{61,63}$", + "title": "Kaspaaddress", + "description": "Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73" + }, + "name": "kaspaAddress", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/UtxoResponse" + }, + "type": "array", + "title": "Response Get Utxos For Address Addresses Kaspaaddress Utxos Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "strict_query_params": true + } + }, + "/addresses/utxos": { + "post": { + "tags": [ + "Kaspa addresses" + ], + "summary": "Get Utxos For Addresses", + "description": "Lists all open utxo for a given kaspa address", + "operationId": "get_utxos_for_addresses_addresses_utxos_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UtxoRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/UtxoResponse" + }, + "type": "array", + "title": "Response Get Utxos For Addresses Addresses Utxos Post" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "strict_query_params": true + } + }, + "/info/virtual-chain-blue-score": { + "get": { + "tags": [ + "Kaspa network info" + ], + "summary": "Get Virtual Selected Parent Blue Score", + "description": "Returns the blue score of the sink", + "operationId": "get_virtual_selected_parent_blue_score_info_virtual_chain_blue_score_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlueScoreResponse" + } + } + } + } + } + } + }, + "/blocks/{blockId}": { + "get": { + "tags": [ + "Kaspa blocks" + ], + "summary": "Get Block", + "description": "Get block information for a given block id", + "operationId": "get_block_blocks__blockId__get", + "parameters": [ + { + "required": true, + "schema": { + "type": "string", + "pattern": "[a-f0-9]{64}", + "title": "Blockid" + }, + "name": "blockId", + "in": "path" + }, + { + "required": false, + "schema": { + "type": "boolean", + "title": "Includetransactions", + "default": true + }, + "name": "includeTransactions", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "boolean", + "title": "Includecolor", + "default": false + }, + "name": "includeColor", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockModel" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/blocks": { + "get": { + "tags": [ + "Kaspa blocks" + ], + "summary": "Get Blocks", + "description": "Lists block beginning from a low hash (block id).", + "operationId": "get_blocks_blocks_get", + "parameters": [ + { + "required": true, + "schema": { + "type": "string", + "pattern": "[a-f0-9]{64}", + "title": "Lowhash" + }, + "name": "lowHash", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "boolean", + "title": "Includeblocks", + "default": false + }, + "name": "includeBlocks", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "boolean", + "title": "Includetransactions", + "default": false + }, + "name": "includeTransactions", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/blocks-from-bluescore": { + "get": { + "tags": [ + "Kaspa blocks" + ], + "summary": "Get Blocks From Bluescore", + "description": "Lists blocks of a given blueScore", + "operationId": "get_blocks_from_bluescore_blocks_from_bluescore_get", + "parameters": [ + { + "required": false, + "schema": { + "type": "integer", + "title": "Bluescore", + "default": 43679173 + }, + "name": "blueScore", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "boolean", + "title": "Includetransactions", + "default": false + }, + "name": "includeTransactions", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/BlockModel" + }, + "type": "array", + "title": "Response Get Blocks From Bluescore Blocks From Bluescore Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/info/network": { + "get": { + "tags": [ + "Kaspa network info" + ], + "summary": "Get Network", + "description": "Alias for /info/blockdag", + "operationId": "get_network_info_network_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockdagResponse" + } + } + } + } + }, + "deprecated": true + } + }, + "/info/blockdag": { + "get": { + "tags": [ + "Kaspa network info" + ], + "summary": "Get Blockdag", + "description": "Get Kaspa BlockDAG information", + "operationId": "get_blockdag_info_blockdag_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BlockdagResponse" + } + } + } + } + } + } + }, + "/info/coinsupply": { + "get": { + "tags": [ + "Kaspa network info" + ], + "summary": "Get Coinsupply", + "description": "Get $KAS coin supply information", + "operationId": "get_coinsupply_info_coinsupply_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CoinSupplyResponse" + } + } + } + } + } + } + }, + "/info/coinsupply/circulating": { + "get": { + "tags": [ + "Kaspa network info" + ], + "summary": "Get Circulating Coins", + "description": "Get circulating amount of $KAS token as numerical value", + "operationId": "get_circulating_coins_info_coinsupply_circulating_get", + "parameters": [ + { + "required": false, + "schema": { + "type": "boolean", + "title": "In Billion", + "default": false + }, + "name": "in_billion", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/info/coinsupply/total": { + "get": { + "tags": [ + "Kaspa network info" + ], + "summary": "Get Total Coins", + "description": "Get total amount of $KAS token as numerical value", + "operationId": "get_total_coins_info_coinsupply_total_get", + "parameters": [ + { + "required": false, + "schema": { + "type": "boolean", + "title": "In Billion", + "default": false + }, + "name": "in_billion", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/info/kaspad": { + "get": { + "tags": [ + "Kaspa network info" + ], + "summary": "Get Kaspad Info", + "description": "Get some information for kaspad instance, which is currently connected.", + "operationId": "get_kaspad_info_info_kaspad_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KaspadInfoResponse" + } + } + } + } + } + } + }, + "/info/fee-estimate": { + "get": { + "tags": [ + "Kaspa network info" + ], + "summary": "Get Fee Estimate", + "description": "Get fee estimate from Kaspad.\n\nFor all buckets, feerate values represent fee/mass of a transaction in `sompi/gram` units.
\nGiven a feerate value recommendation, calculate the required fee by\ntaking the transaction mass and multiplying it by feerate: `fee = feerate * mass(tx)`", + "operationId": "get_fee_estimate_info_fee_estimate_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FeeEstimateResponse" + } + } + } + } + } + } + }, + "/info/price": { + "get": { + "tags": [ + "Kaspa network info" + ], + "summary": "Get Price", + "description": "Returns the current price for Kaspa in USD.", + "operationId": "get_price_info_price_get", + "parameters": [ + { + "required": false, + "schema": { + "type": "boolean", + "title": "Stringonly", + "default": false + }, + "name": "stringOnly", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/PriceResponse" + }, + { + "type": "string" + } + ], + "title": "Response Get Price Info Price Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/transactions/{transactionId}": { + "get": { + "tags": [ + "Kaspa transactions" + ], + "summary": "Get Transaction", + "description": "Get details for a given transaction id", + "operationId": "get_transaction_transactions__transactionId__get", + "parameters": [ + { + "required": true, + "schema": { + "type": "string", + "pattern": "[a-f0-9]{64}", + "title": "Transactionid" + }, + "name": "transactionId", + "in": "path" + }, + { + "description": "Specify a containing block (if known) for faster lookup", + "required": false, + "schema": { + "type": "string", + "title": "Blockhash", + "description": "Specify a containing block (if known) for faster lookup" + }, + "name": "blockHash", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "boolean", + "title": "Inputs", + "default": true + }, + "name": "inputs", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "boolean", + "title": "Outputs", + "default": true + }, + "name": "outputs", + "in": "query" + }, + { + "description": "Use this parameter if you want to fetch the TransactionInput previous outpoint details. Light fetches only the address and amount. Full fetches the whole TransactionOutput and adds it into each TxInput.", + "required": false, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PreviousOutpointLookupMode" + } + ], + "description": "Use this parameter if you want to fetch the TransactionInput previous outpoint details. Light fetches only the address and amount. Full fetches the whole TransactionOutput and adds it into each TxInput.", + "default": "no" + }, + "name": "resolve_previous_outpoints", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TxModel" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/transactions/search": { + "post": { + "tags": [ + "Kaspa transactions" + ], + "summary": "Search For Transactions", + "description": "Search for transactions by transaction_ids or blue_score", + "operationId": "search_for_transactions_transactions_search_post", + "parameters": [ + { + "required": false, + "schema": { + "type": "string", + "title": "Fields", + "default": "" + }, + "name": "fields", + "in": "query" + }, + { + "description": "Use this parameter if you want to fetch the TransactionInput previous outpoint details. Light fetches only the address and amount. Full fetches the whole TransactionOutput and adds it into each TxInput.", + "required": false, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PreviousOutpointLookupMode" + } + ], + "description": "Use this parameter if you want to fetch the TransactionInput previous outpoint details. Light fetches only the address and amount. Full fetches the whole TransactionOutput and adds it into each TxInput.", + "default": "no" + }, + "name": "resolve_previous_outpoints", + "in": "query" + }, + { + "description": "Only used when searching using transactionIds", + "required": false, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/AcceptanceMode" + } + ], + "description": "Only used when searching using transactionIds" + }, + "name": "acceptance", + "in": "query" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TxSearch" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/TxModel" + }, + "type": "array", + "title": "Response Search For Transactions Transactions Search Post" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/transactions/acceptance": { + "post": { + "tags": [ + "Kaspa transactions" + ], + "summary": "Get Transaction Acceptance", + "description": "Given a list of transaction_ids, return whether each one is accepted", + "operationId": "get_transaction_acceptance_transactions_acceptance_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TxAcceptanceRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/TxAcceptanceResponse" + }, + "type": "array", + "title": "Response Get Transaction Acceptance Transactions Acceptance Post" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "strict_query_params": true + } + }, + "/addresses/{kaspaAddress}/full-transactions": { + "get": { + "tags": [ + "Kaspa addresses" + ], + "summary": "Get Full Transactions For Address", + "description": "Get all transactions for a given address from database.\nAnd then get their related full transaction data", + "operationId": "get_full_transactions_for_address_addresses__kaspaAddress__full_transactions_get", + "parameters": [ + { + "description": "Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73", + "required": true, + "schema": { + "type": "string", + "pattern": "^kaspa:[a-z0-9]{61,63}$", + "title": "Kaspaaddress", + "description": "Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73" + }, + "name": "kaspaAddress", + "in": "path" + }, + { + "description": "The number of records to get", + "required": false, + "schema": { + "type": "integer", + "maximum": 500.0, + "minimum": 1.0, + "title": "Limit", + "description": "The number of records to get", + "default": 50 + }, + "name": "limit", + "in": "query" + }, + { + "description": "The offset from which to get records", + "required": false, + "schema": { + "type": "integer", + "minimum": 0.0, + "title": "Offset", + "description": "The offset from which to get records", + "default": 0 + }, + "name": "offset", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "string", + "title": "Fields", + "default": "" + }, + "name": "fields", + "in": "query" + }, + { + "description": "Use this parameter if you want to fetch the TransactionInput previous outpoint details. Light fetches only the adress and amount. Full fetches the whole TransactionOutput and adds it into each TxInput.", + "required": false, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PreviousOutpointLookupMode" + } + ], + "description": "Use this parameter if you want to fetch the TransactionInput previous outpoint details. Light fetches only the adress and amount. Full fetches the whole TransactionOutput and adds it into each TxInput.", + "default": "no" + }, + "name": "resolve_previous_outpoints", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/TxModel" + }, + "type": "array", + "title": "Response Get Full Transactions For Address Addresses Kaspaaddress Full Transactions Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "strict_query_params": true + } + }, + "/addresses/active": { + "post": { + "tags": [ + "Kaspa addresses" + ], + "summary": "Get Addresses Active", + "description": "This endpoint checks if addresses have had any transaction activity in the past.\nIt is specifically designed for HD Wallets to verify historical address activity.", + "operationId": "get_addresses_active_addresses_active_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddressesActiveRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/TxIdResponse" + }, + "type": "array", + "title": "Response Get Addresses Active Addresses Active Post" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "strict_query_params": true + } + }, + "/addresses/{kaspaAddress}/full-transactions-page": { + "get": { + "tags": [ + "Kaspa addresses" + ], + "summary": "Get Full Transactions For Address Page", + "description": "Get all transactions for a given address from database.\nAnd then get their related full transaction data", + "operationId": "get_full_transactions_for_address_page_addresses__kaspaAddress__full_transactions_page_get", + "parameters": [ + { + "description": "Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73", + "required": true, + "schema": { + "type": "string", + "pattern": "^kaspa:[a-z0-9]{61,63}$", + "title": "Kaspaaddress", + "description": "Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73" + }, + "name": "kaspaAddress", + "in": "path" + }, + { + "description": "The max number of records to get. For paging combine with using 'before/after' from oldest previous result. Use value of X-Next-Page-Before/-After as long as header is present to continue paging. The actual number of transactions returned for each page can be > limit.", + "required": false, + "schema": { + "type": "integer", + "maximum": 500.0, + "minimum": 1.0, + "title": "Limit", + "description": "The max number of records to get. For paging combine with using 'before/after' from oldest previous result. Use value of X-Next-Page-Before/-After as long as header is present to continue paging. The actual number of transactions returned for each page can be > limit.", + "default": 50 + }, + "name": "limit", + "in": "query" + }, + { + "description": "Only include transactions with block time before this (epoch-millis)", + "required": false, + "schema": { + "type": "integer", + "minimum": 0.0, + "title": "Before", + "description": "Only include transactions with block time before this (epoch-millis)", + "default": 0 + }, + "name": "before", + "in": "query" + }, + { + "description": "Only include transactions with block time after this (epoch-millis)", + "required": false, + "schema": { + "type": "integer", + "minimum": 0.0, + "title": "After", + "description": "Only include transactions with block time after this (epoch-millis)", + "default": 0 + }, + "name": "after", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "string", + "title": "Fields", + "default": "" + }, + "name": "fields", + "in": "query" + }, + { + "description": "Use this parameter if you want to fetch the TransactionInput previous outpoint details. Light fetches only the adress and amount. Full fetches the whole TransactionOutput and adds it into each TxInput.", + "required": false, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PreviousOutpointLookupMode" + } + ], + "description": "Use this parameter if you want to fetch the TransactionInput previous outpoint details. Light fetches only the adress and amount. Full fetches the whole TransactionOutput and adds it into each TxInput.", + "default": "no" + }, + "name": "resolve_previous_outpoints", + "in": "query" + }, + { + "required": false, + "schema": { + "$ref": "#/components/schemas/AcceptanceMode" + }, + "name": "acceptance", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/TxModel" + }, + "type": "array", + "title": "Response Get Full Transactions For Address Page Addresses Kaspaaddress Full Transactions Page Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "strict_query_params": true + } + }, + "/addresses/{kaspaAddress}/transactions-count": { + "get": { + "tags": [ + "Kaspa addresses" + ], + "summary": "Get Transaction Count For Address", + "description": "Count the number of transactions associated with this address", + "operationId": "get_transaction_count_for_address_addresses__kaspaAddress__transactions_count_get", + "parameters": [ + { + "description": "Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73", + "required": true, + "schema": { + "type": "string", + "pattern": "^kaspa:[a-z0-9]{61,63}$", + "title": "Kaspaaddress", + "description": "Kaspa address as string e.g. kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73" + }, + "name": "kaspaAddress", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionCount" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "strict_query_params": true + } + }, + "/addresses/names": { + "get": { + "tags": [ + "Kaspa addresses" + ], + "summary": "Get Addresses Names", + "description": "Get the name for an address", + "operationId": "get_addresses_names_addresses_names_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/AddressName" + }, + "type": "array", + "title": "Response Get Addresses Names Addresses Names Get" + } + } + } + } + }, + "strict_query_params": true + } + }, + "/addresses/{kaspaAddress}/name": { + "get": { + "tags": [ + "Kaspa addresses" + ], + "summary": "Get Name For Address", + "description": "Get the name for an address", + "operationId": "get_name_for_address_addresses__kaspaAddress__name_get", + "parameters": [ + { + "description": "Kaspa address as string e.g. kaspa:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkx9awp4e", + "required": true, + "schema": { + "type": "string", + "pattern": "^kaspa:[a-z0-9]{61,63}$", + "title": "Kaspaaddress", + "description": "Kaspa address as string e.g. kaspa:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkx9awp4e" + }, + "name": "kaspaAddress", + "in": "path" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AddressName" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + }, + "strict_query_params": true + } + }, + "/addresses/balances": { + "post": { + "tags": [ + "Kaspa addresses" + ], + "summary": "Get Balances From Kaspa Addresses", + "description": "Get balances for multiple kaspa addresses", + "operationId": "get_balances_from_kaspa_addresses_addresses_balances_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BalanceRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/BalancesByAddressEntry" + }, + "type": "array", + "title": "Response Get Balances From Kaspa Addresses Addresses Balances Post" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/info/blockreward": { + "get": { + "tags": [ + "Kaspa network info" + ], + "summary": "Get Blockreward", + "description": "Returns the current blockreward in KAS/block", + "operationId": "get_blockreward_info_blockreward_get", + "parameters": [ + { + "required": false, + "schema": { + "type": "boolean", + "title": "Stringonly", + "default": false + }, + "name": "stringOnly", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/BlockRewardResponse" + }, + { + "type": "string" + } + ], + "title": "Response Get Blockreward Info Blockreward Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/info/halving": { + "get": { + "tags": [ + "Kaspa network info" + ], + "summary": "Get Halving", + "description": "Returns information about chromatic halving", + "operationId": "get_halving_info_halving_get", + "parameters": [ + { + "required": false, + "schema": { + "type": "string", + "title": "Field" + }, + "name": "field", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/HalvingResponse" + }, + { + "type": "string" + } + ], + "title": "Response Get Halving Info Halving Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/info/hashrate": { + "get": { + "tags": [ + "Kaspa network info" + ], + "summary": "Get Hashrate", + "description": "Returns the current hashrate for Kaspa network in TH/s.", + "operationId": "get_hashrate_info_hashrate_get", + "parameters": [ + { + "required": false, + "schema": { + "type": "boolean", + "title": "Stringonly", + "default": false + }, + "name": "stringOnly", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/HashrateResponse" + }, + { + "type": "string" + } + ], + "title": "Response Get Hashrate Info Hashrate Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/info/hashrate/max": { + "get": { + "tags": [ + "Kaspa network info" + ], + "summary": "Get Max Hashrate", + "description": "Returns the current hashrate for Kaspa network in TH/s.", + "operationId": "get_max_hashrate_info_hashrate_max_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MaxHashrateResponse" + } + } + } + } + } + } + }, + "/info/hashrate/history": { + "get": { + "tags": [ + "Kaspa network info" + ], + "summary": "Get Hashrate History", + "description": "Get historical hashrate samples with optional resolution (default = 1h)", + "operationId": "get_hashrate_history_info_hashrate_history_get", + "parameters": [ + { + "required": false, + "schema": { + "type": "string", + "enum": [ + "1h", + "3h", + "1d", + "7d" + ], + "title": "Resolution" + }, + "name": "resolution", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/HashrateHistoryResponse" + }, + "type": "array", + "title": "Response Get Hashrate History Info Hashrate History Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/info/health": { + "get": { + "tags": [ + "Kaspa network info" + ], + "summary": "Health State", + "description": "Checks node and database health by comparing blue score and sync status.\nReturns health details or 503 if the database lags by ~10min or no nodes are synced.", + "operationId": "health_state_info_health_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HealthResponse" + } + } + } + } + } + } + }, + "/info/marketcap": { + "get": { + "tags": [ + "Kaspa network info" + ], + "summary": "Get Marketcap", + "description": "Get $KAS price and market cap. Price info is from coingecko.com", + "operationId": "get_marketcap_info_marketcap_get", + "parameters": [ + { + "required": false, + "schema": { + "type": "boolean", + "title": "Stringonly", + "default": false + }, + "name": "stringOnly", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/MarketCapResponse" + }, + { + "type": "string" + } + ], + "title": "Response Get Marketcap Info Marketcap Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/transactions": { + "post": { + "tags": [ + "Kaspa transactions" + ], + "summary": "Submit A New Transaction", + "operationId": "submit_a_new_transaction_transactions_post", + "parameters": [ + { + "description": "Replace an existing transaction in the mempool", + "required": false, + "schema": { + "type": "boolean", + "title": "Replacebyfee", + "description": "Replace an existing transaction in the mempool", + "default": false + }, + "name": "replaceByFee", + "in": "query" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubmitTransactionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubmitTransactionResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubmitTransactionResponse" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/transactions/mass": { + "post": { + "tags": [ + "Kaspa transactions" + ], + "summary": "Calculate Transaction Mass", + "description": "This function calculates and returns the mass of a transaction, which is essential for determining the minimum fee. The mass calculation takes into account the storage mass as defined in KIP-0009.\n\nNote: Be aware that if the transaction has a very low output amount or a high number of outputs, the mass can become significantly large.", + "operationId": "calculate_transaction_mass_transactions_mass_post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SubmitTxModel" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TxMass" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + }, + "/virtual-chain": { + "get": { + "tags": [ + "EXPERIMENTAL: Kaspa virtual chain" + ], + "summary": "Get Virtual Chain Transactions", + "description": "EXPERIMENTAL - EXPECT BREAKING CHANGES: Get virtual chain transactions by blue score.", + "operationId": "get_virtual_chain_transactions_virtual_chain_get", + "parameters": [ + { + "description": "Divisible by limit", + "required": true, + "schema": { + "type": "integer", + "minimum": 0.0, + "title": "Bluescoregte", + "description": "Divisible by limit" + }, + "example": 106329050, + "name": "blueScoreGte", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "integer", + "enum": [ + 10, + 100 + ], + "title": "Limit", + "default": 10 + }, + "name": "limit", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "boolean", + "title": "Resolveinputs", + "default": false + }, + "name": "resolveInputs", + "in": "query" + }, + { + "required": false, + "schema": { + "type": "boolean", + "title": "Includecoinbase", + "default": true + }, + "name": "includeCoinbase", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/VcBlockModel" + }, + "type": "array", + "title": "Response Get Virtual Chain Transactions Virtual Chain Get" + } + } + } + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "AcceptanceMode": { + "type": "string", + "enum": [ + "accepted", + "rejected" + ], + "title": "AcceptanceMode", + "description": "An enumeration." + }, + "AddressName": { + "properties": { + "address": { + "type": "string", + "title": "Address" + }, + "name": { + "type": "string", + "title": "Name" + } + }, + "type": "object", + "required": [ + "address", + "name" + ], + "title": "AddressName" + }, + "AddressesActiveRequest": { + "properties": { + "addresses": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Addresses", + "default": [ + "kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73" + ] + } + }, + "type": "object", + "title": "AddressesActiveRequest" + }, + "BalanceRequest": { + "properties": { + "addresses": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Addresses", + "default": [ + "kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73" + ] + } + }, + "type": "object", + "title": "BalanceRequest" + }, + "BalanceResponse": { + "properties": { + "address": { + "type": "string", + "title": "Address", + "default": "kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73" + }, + "balance": { + "type": "integer", + "title": "Balance", + "default": 38240000000 + } + }, + "type": "object", + "title": "BalanceResponse" + }, + "BalancesByAddressEntry": { + "properties": { + "address": { + "type": "string", + "title": "Address", + "default": "kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73" + }, + "balance": { + "type": "integer", + "title": "Balance", + "default": 12451591699 + } + }, + "type": "object", + "title": "BalancesByAddressEntry" + }, + "BlockModel": { + "properties": { + "header": { + "$ref": "#/components/schemas/endpoints__get_blocks__BlockHeader" + }, + "transactions": { + "items": { + "$ref": "#/components/schemas/BlockTxModel" + }, + "type": "array", + "title": "Transactions" + }, + "verboseData": { + "$ref": "#/components/schemas/VerboseDataModel" + }, + "extra": { + "$ref": "#/components/schemas/ExtraModel" + } + }, + "type": "object", + "required": [ + "header", + "verboseData" + ], + "title": "BlockModel" + }, + "BlockResponse": { + "properties": { + "blockHashes": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Blockhashes", + "default": [ + "44edf9bfd32aa154bfad64485882f184372b64bd60565ba121b42fc3cb1238f3", + "18c7afdf8f447ca06adb8b4946dc45f5feb1188c7d177da6094dfbc760eca699", + "9a822351cd293a653f6721afec1646bd1690da7124b5fbe87001711406010604", + "2fda0dad4ec879b4ad02ebb68c757955cab305558998129a7de111ab852e7dcb" + ] + }, + "blocks": { + "items": { + "$ref": "#/components/schemas/BlockModel" + }, + "type": "array", + "title": "Blocks" + } + }, + "type": "object", + "title": "BlockResponse" + }, + "BlockRewardResponse": { + "properties": { + "blockreward": { + "type": "number", + "title": "Blockreward", + "default": 12000132 + } + }, + "type": "object", + "title": "BlockRewardResponse" + }, + "BlockTxInputModel": { + "properties": { + "previousOutpoint": { + "$ref": "#/components/schemas/BlockTxInputPreviousOutpointModel" + }, + "signatureScript": { + "type": "string", + "title": "Signaturescript" + }, + "sigOpCount": { + "type": "integer", + "title": "Sigopcount" + }, + "sequence": { + "type": "integer", + "title": "Sequence" + } + }, + "type": "object", + "title": "BlockTxInputModel" + }, + "BlockTxInputPreviousOutpointModel": { + "properties": { + "transactionId": { + "type": "string", + "title": "Transactionid" + }, + "index": { + "type": "integer", + "title": "Index" + } + }, + "type": "object", + "required": [ + "transactionId", + "index" + ], + "title": "BlockTxInputPreviousOutpointModel" + }, + "BlockTxModel": { + "properties": { + "inputs": { + "items": { + "$ref": "#/components/schemas/BlockTxInputModel" + }, + "type": "array", + "title": "Inputs" + }, + "outputs": { + "items": { + "$ref": "#/components/schemas/BlockTxOutputModel" + }, + "type": "array", + "title": "Outputs" + }, + "subnetworkId": { + "type": "string", + "title": "Subnetworkid" + }, + "payload": { + "type": "string", + "title": "Payload" + }, + "verboseData": { + "$ref": "#/components/schemas/BlockTxVerboseDataModel" + }, + "lockTime": { + "type": "integer", + "title": "Locktime" + }, + "gas": { + "type": "integer", + "title": "Gas" + }, + "mass": { + "type": "integer", + "title": "Mass" + }, + "version": { + "type": "integer", + "title": "Version" + } + }, + "type": "object", + "required": [ + "verboseData" + ], + "title": "BlockTxModel" + }, + "BlockTxOutputModel": { + "properties": { + "amount": { + "type": "integer", + "title": "Amount" + }, + "scriptPublicKey": { + "$ref": "#/components/schemas/BlockTxOutputScriptPublicKeyModel" + }, + "verboseData": { + "$ref": "#/components/schemas/BlockTxOutputVerboseDataModel" + } + }, + "type": "object", + "title": "BlockTxOutputModel" + }, + "BlockTxOutputScriptPublicKeyModel": { + "properties": { + "scriptPublicKey": { + "type": "string", + "title": "Scriptpublickey" + }, + "version": { + "type": "integer", + "title": "Version" + } + }, + "type": "object", + "title": "BlockTxOutputScriptPublicKeyModel" + }, + "BlockTxOutputVerboseDataModel": { + "properties": { + "scriptPublicKeyType": { + "type": "string", + "title": "Scriptpublickeytype" + }, + "scriptPublicKeyAddress": { + "type": "string", + "title": "Scriptpublickeyaddress" + } + }, + "type": "object", + "title": "BlockTxOutputVerboseDataModel" + }, + "BlockTxVerboseDataModel": { + "properties": { + "transactionId": { + "type": "string", + "title": "Transactionid" + }, + "hash": { + "type": "string", + "title": "Hash" + }, + "computeMass": { + "type": "integer", + "title": "Computemass" + }, + "blockHash": { + "type": "string", + "title": "Blockhash" + }, + "blockTime": { + "type": "integer", + "title": "Blocktime" + } + }, + "type": "object", + "required": [ + "transactionId" + ], + "title": "BlockTxVerboseDataModel" + }, + "BlockdagResponse": { + "properties": { + "networkName": { + "type": "string", + "title": "Networkname", + "example": "kaspa-mainnet" + }, + "blockCount": { + "type": "string", + "title": "Blockcount", + "example": "260890" + }, + "headerCount": { + "type": "string", + "title": "Headercount", + "example": "2131312" + }, + "tipHashes": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Tiphashes", + "example": [ + "78273854a739e3e379dfd34a262bbe922400d8e360e30e3f31228519a334350a" + ] + }, + "difficulty": { + "type": "number", + "title": "Difficulty", + "example": 3870677677777.2 + }, + "pastMedianTime": { + "type": "string", + "title": "Pastmediantime", + "example": "1656455670700" + }, + "virtualParentHashes": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Virtualparenthashes", + "example": [ + "78273854a739e3e379dfd34a262bbe922400d8e360e30e3f31228519a334350a" + ] + }, + "pruningPointHash": { + "type": "string", + "title": "Pruningpointhash", + "example": "5d32a9403273a34b6551b84340a1459ddde2ae6ba59a47987a6374340ba41d5d" + }, + "virtualDaaScore": { + "type": "string", + "title": "Virtualdaascore", + "example": "19989141" + }, + "sink": { + "type": "string", + "title": "Sink", + "example": "366b1cf51146cc002672b79948634751a2914a2cc9e273afe358bdc1ae19dce9" + } + }, + "type": "object", + "required": [ + "networkName", + "blockCount", + "headerCount", + "tipHashes", + "difficulty", + "pastMedianTime", + "virtualParentHashes", + "pruningPointHash", + "virtualDaaScore", + "sink" + ], + "title": "BlockdagResponse" + }, + "BlueScoreResponse": { + "properties": { + "blueScore": { + "type": "integer", + "title": "Bluescore", + "default": 260890 + } + }, + "type": "object", + "title": "BlueScoreResponse" + }, + "CoinSupplyResponse": { + "properties": { + "circulatingSupply": { + "type": "string", + "title": "Circulatingsupply", + "default": "1000900697580640180" + }, + "maxSupply": { + "type": "string", + "title": "Maxsupply", + "default": "2900000000000000000" + } + }, + "type": "object", + "title": "CoinSupplyResponse" + }, + "DBCheckStatus": { + "properties": { + "isSynced": { + "type": "boolean", + "title": "Issynced", + "default": true + }, + "blueScore": { + "type": "integer", + "title": "Bluescore" + }, + "blueScoreDiff": { + "type": "integer", + "title": "Bluescorediff" + }, + "acceptedTxBlockTime": { + "type": "integer", + "title": "Acceptedtxblocktime" + }, + "acceptedTxBlockTimeDiff": { + "type": "integer", + "title": "Acceptedtxblocktimediff" + } + }, + "type": "object", + "title": "DBCheckStatus" + }, + "ExtraModel": { + "properties": { + "color": { + "type": "string", + "title": "Color" + }, + "minerAddress": { + "type": "string", + "title": "Mineraddress" + }, + "minerInfo": { + "type": "string", + "title": "Minerinfo" + } + }, + "type": "object", + "title": "ExtraModel" + }, + "FeeEstimateBucket": { + "properties": { + "feerate": { + "type": "integer", + "title": "Feerate", + "default": 1 + }, + "estimatedSeconds": { + "type": "number", + "title": "Estimatedseconds", + "default": 0.004 + } + }, + "type": "object", + "title": "FeeEstimateBucket" + }, + "FeeEstimateResponse": { + "properties": { + "priorityBucket": { + "$ref": "#/components/schemas/FeeEstimateBucket" + }, + "normalBuckets": { + "items": { + "$ref": "#/components/schemas/FeeEstimateBucket" + }, + "type": "array", + "title": "Normalbuckets" + }, + "lowBuckets": { + "items": { + "$ref": "#/components/schemas/FeeEstimateBucket" + }, + "type": "array", + "title": "Lowbuckets" + } + }, + "type": "object", + "required": [ + "priorityBucket", + "normalBuckets", + "lowBuckets" + ], + "title": "FeeEstimateResponse" + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail" + } + }, + "type": "object", + "title": "HTTPValidationError" + }, + "HalvingResponse": { + "properties": { + "nextHalvingTimestamp": { + "type": "integer", + "title": "Nexthalvingtimestamp", + "default": 1662837270000 + }, + "nextHalvingDate": { + "type": "string", + "title": "Nexthalvingdate", + "default": "2022-09-10 19:38:52 UTC" + }, + "nextHalvingAmount": { + "type": "number", + "title": "Nexthalvingamount", + "default": 155.123123 + } + }, + "type": "object", + "title": "HalvingResponse" + }, + "HashrateHistoryResponse": { + "properties": { + "daaScore": { + "type": "integer", + "title": "Daascore" + }, + "blueScore": { + "type": "integer", + "title": "Bluescore" + }, + "timestamp": { + "type": "integer", + "title": "Timestamp" + }, + "date_time": { + "type": "string", + "title": "Date Time" + }, + "bits": { + "type": "integer", + "title": "Bits" + }, + "difficulty": { + "type": "integer", + "title": "Difficulty" + }, + "hashrate_kh": { + "type": "integer", + "title": "Hashrate Kh" + } + }, + "type": "object", + "required": [ + "daaScore", + "blueScore", + "timestamp", + "date_time", + "difficulty", + "hashrate_kh" + ], + "title": "HashrateHistoryResponse" + }, + "HashrateResponse": { + "properties": { + "hashrate": { + "type": "number", + "title": "Hashrate", + "default": 12000132 + } + }, + "type": "object", + "title": "HashrateResponse" + }, + "HealthResponse": { + "properties": { + "kaspadServers": { + "items": { + "$ref": "#/components/schemas/KaspadResponse" + }, + "type": "array", + "title": "Kaspadservers" + }, + "database": { + "$ref": "#/components/schemas/DBCheckStatus" + } + }, + "type": "object", + "required": [ + "kaspadServers", + "database" + ], + "title": "HealthResponse" + }, + "KaspadInfoResponse": { + "properties": { + "mempoolSize": { + "type": "string", + "title": "Mempoolsize", + "default": "1" + }, + "serverVersion": { + "type": "string", + "title": "Serverversion", + "default": "0.12.2" + }, + "isUtxoIndexed": { + "type": "boolean", + "title": "Isutxoindexed", + "default": true + }, + "isSynced": { + "type": "boolean", + "title": "Issynced", + "default": true + }, + "p2pIdHashed": { + "type": "string", + "title": "P2Pidhashed", + "default": "36a17cd8644eef34fc7fe4719655e06dbdf117008900c46975e66c35acd09b01" + } + }, + "type": "object", + "title": "KaspadInfoResponse" + }, + "KaspadResponse": { + "properties": { + "kaspadHost": { + "type": "string", + "title": "Kaspadhost" + }, + "serverVersion": { + "type": "string", + "title": "Serverversion", + "default": "0.12.6" + }, + "isUtxoIndexed": { + "type": "boolean", + "title": "Isutxoindexed", + "default": true + }, + "isSynced": { + "type": "boolean", + "title": "Issynced", + "default": true + }, + "p2pId": { + "type": "string", + "title": "P2Pid", + "default": "1231312" + }, + "blueScore": { + "type": "integer", + "title": "Bluescore", + "default": 0 + } + }, + "type": "object", + "title": "KaspadResponse" + }, + "MarketCapResponse": { + "properties": { + "marketcap": { + "type": "integer", + "title": "Marketcap", + "default": 12000132 + } + }, + "type": "object", + "title": "MarketCapResponse" + }, + "MaxHashrateResponse": { + "properties": { + "hashrate": { + "type": "number", + "title": "Hashrate", + "default": 12000132 + }, + "blockheader": { + "$ref": "#/components/schemas/endpoints__get_hashrate__BlockHeader" + } + }, + "type": "object", + "required": [ + "blockheader" + ], + "title": "MaxHashrateResponse" + }, + "OutpointModel": { + "properties": { + "transactionId": { + "type": "string", + "title": "Transactionid", + "default": "ef62efbc2825d3ef9ec1cf9b80506876ac077b64b11a39c8ef5e028415444dc9" + }, + "index": { + "type": "integer", + "title": "Index", + "default": 0 + } + }, + "type": "object", + "title": "OutpointModel" + }, + "ParentHashModel": { + "properties": { + "parentHashes": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Parenthashes", + "default": [ + "580f65c8da9d436480817f6bd7c13eecd9223b37f0d34ae42fb17e1e9fda397e" + ] + } + }, + "type": "object", + "title": "ParentHashModel" + }, + "PreviousOutpointLookupMode": { + "type": "string", + "enum": [ + "no", + "light", + "full" + ], + "title": "PreviousOutpointLookupMode", + "description": "An enumeration." + }, + "PriceResponse": { + "properties": { + "price": { + "type": "number", + "title": "Price", + "default": 0.025235 + } + }, + "type": "object", + "title": "PriceResponse" + }, + "ScriptPublicKeyModel": { + "properties": { + "scriptPublicKey": { + "type": "string", + "title": "Scriptpublickey", + "default": "20c5629ce85f6618cd3ed1ac1c99dc6d3064ed244013555c51385d9efab0d0072fac" + } + }, + "type": "object", + "title": "ScriptPublicKeyModel" + }, + "SubmitTransactionRequest": { + "properties": { + "transaction": { + "$ref": "#/components/schemas/SubmitTxModel" + }, + "allowOrphan": { + "type": "boolean", + "title": "Alloworphan", + "default": false + } + }, + "type": "object", + "required": [ + "transaction" + ], + "title": "SubmitTransactionRequest" + }, + "SubmitTransactionResponse": { + "properties": { + "transactionId": { + "type": "string", + "title": "Transactionid" + }, + "error": { + "type": "string", + "title": "Error" + } + }, + "type": "object", + "title": "SubmitTransactionResponse" + }, + "SubmitTxInput": { + "properties": { + "previousOutpoint": { + "$ref": "#/components/schemas/SubmitTxOutpoint" + }, + "signatureScript": { + "type": "string", + "title": "Signaturescript" + }, + "sequence": { + "type": "integer", + "title": "Sequence" + }, + "sigOpCount": { + "type": "integer", + "title": "Sigopcount" + } + }, + "type": "object", + "required": [ + "previousOutpoint", + "signatureScript", + "sequence", + "sigOpCount" + ], + "title": "SubmitTxInput" + }, + "SubmitTxModel": { + "properties": { + "version": { + "type": "integer", + "title": "Version" + }, + "inputs": { + "items": { + "$ref": "#/components/schemas/SubmitTxInput" + }, + "type": "array", + "title": "Inputs" + }, + "outputs": { + "items": { + "$ref": "#/components/schemas/SubmitTxOutput" + }, + "type": "array", + "title": "Outputs" + }, + "lockTime": { + "type": "integer", + "title": "Locktime", + "default": 0 + }, + "subnetworkId": { + "type": "string", + "title": "Subnetworkid" + } + }, + "type": "object", + "required": [ + "version", + "inputs", + "outputs" + ], + "title": "SubmitTxModel" + }, + "SubmitTxOutpoint": { + "properties": { + "transactionId": { + "type": "string", + "title": "Transactionid" + }, + "index": { + "type": "integer", + "title": "Index" + } + }, + "type": "object", + "required": [ + "transactionId", + "index" + ], + "title": "SubmitTxOutpoint" + }, + "SubmitTxOutput": { + "properties": { + "amount": { + "type": "integer", + "title": "Amount" + }, + "scriptPublicKey": { + "$ref": "#/components/schemas/SubmitTxScriptPublicKey" + } + }, + "type": "object", + "required": [ + "amount", + "scriptPublicKey" + ], + "title": "SubmitTxOutput" + }, + "SubmitTxScriptPublicKey": { + "properties": { + "version": { + "type": "integer", + "title": "Version" + }, + "scriptPublicKey": { + "type": "string", + "title": "Scriptpublickey" + } + }, + "type": "object", + "required": [ + "version", + "scriptPublicKey" + ], + "title": "SubmitTxScriptPublicKey" + }, + "TransactionCount": { + "properties": { + "total": { + "type": "integer", + "title": "Total" + }, + "limit_exceeded": { + "type": "boolean", + "title": "Limit Exceeded" + } + }, + "type": "object", + "required": [ + "total", + "limit_exceeded" + ], + "title": "TransactionCount" + }, + "TxAcceptanceRequest": { + "properties": { + "transactionIds": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Transactionids", + "default": [ + "b9382bdee4aa364acf73eda93914eaae61d0e78334d1b8a637ab89ef5e224e41", + "1e098b3830c994beb28768f7924a38286cec16e85e9757e0dc3574b85f624c34", + "6dd5ee1add449c60d2c2b9545a8dfca7cfbc9a29ce2d3f3ec8bbde14dd97610d" + ] + } + }, + "type": "object", + "title": "TxAcceptanceRequest" + }, + "TxAcceptanceResponse": { + "properties": { + "transactionId": { + "type": "string", + "title": "Transactionid", + "default": "b9382bdee4aa364acf73eda93914eaae61d0e78334d1b8a637ab89ef5e224e41" + }, + "accepted": { + "type": "boolean", + "title": "Accepted" + } + }, + "type": "object", + "required": [ + "accepted" + ], + "title": "TxAcceptanceResponse" + }, + "TxIdResponse": { + "properties": { + "address": { + "type": "string", + "title": "Address" + }, + "active": { + "type": "boolean", + "title": "Active" + } + }, + "type": "object", + "required": [ + "address", + "active" + ], + "title": "TxIdResponse" + }, + "TxInput": { + "properties": { + "transaction_id": { + "type": "string", + "title": "Transaction Id" + }, + "index": { + "type": "integer", + "title": "Index" + }, + "previous_outpoint_hash": { + "type": "string", + "title": "Previous Outpoint Hash" + }, + "previous_outpoint_index": { + "type": "string", + "title": "Previous Outpoint Index" + }, + "previous_outpoint_resolved": { + "$ref": "#/components/schemas/TxOutput" + }, + "previous_outpoint_address": { + "type": "string", + "title": "Previous Outpoint Address" + }, + "previous_outpoint_amount": { + "type": "integer", + "title": "Previous Outpoint Amount" + }, + "signature_script": { + "type": "string", + "title": "Signature Script" + }, + "sig_op_count": { + "type": "string", + "title": "Sig Op Count" + } + }, + "type": "object", + "required": [ + "transaction_id", + "index", + "previous_outpoint_hash", + "previous_outpoint_index" + ], + "title": "TxInput" + }, + "TxMass": { + "properties": { + "mass": { + "type": "integer", + "title": "Mass" + }, + "storage_mass": { + "type": "integer", + "title": "Storage Mass" + }, + "compute_mass": { + "type": "integer", + "title": "Compute Mass" + } + }, + "type": "object", + "required": [ + "mass", + "storage_mass", + "compute_mass" + ], + "title": "TxMass" + }, + "TxModel": { + "properties": { + "subnetwork_id": { + "type": "string", + "title": "Subnetwork Id" + }, + "transaction_id": { + "type": "string", + "title": "Transaction Id" + }, + "hash": { + "type": "string", + "title": "Hash" + }, + "mass": { + "type": "string", + "title": "Mass" + }, + "payload": { + "type": "string", + "title": "Payload" + }, + "block_hash": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Block Hash" + }, + "block_time": { + "type": "integer", + "title": "Block Time" + }, + "is_accepted": { + "type": "boolean", + "title": "Is Accepted" + }, + "accepting_block_hash": { + "type": "string", + "title": "Accepting Block Hash" + }, + "accepting_block_blue_score": { + "type": "integer", + "title": "Accepting Block Blue Score" + }, + "accepting_block_time": { + "type": "integer", + "title": "Accepting Block Time" + }, + "inputs": { + "items": { + "$ref": "#/components/schemas/TxInput" + }, + "type": "array", + "title": "Inputs" + }, + "outputs": { + "items": { + "$ref": "#/components/schemas/TxOutput" + }, + "type": "array", + "title": "Outputs" + } + }, + "type": "object", + "title": "TxModel" + }, + "TxOutput": { + "properties": { + "transaction_id": { + "type": "string", + "title": "Transaction Id" + }, + "index": { + "type": "integer", + "title": "Index" + }, + "amount": { + "type": "integer", + "title": "Amount" + }, + "script_public_key": { + "type": "string", + "title": "Script Public Key" + }, + "script_public_key_address": { + "type": "string", + "title": "Script Public Key Address" + }, + "script_public_key_type": { + "type": "string", + "title": "Script Public Key Type" + }, + "accepting_block_hash": { + "type": "string", + "title": "Accepting Block Hash" + } + }, + "type": "object", + "required": [ + "transaction_id", + "index", + "amount" + ], + "title": "TxOutput" + }, + "TxSearch": { + "properties": { + "transactionIds": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Transactionids" + }, + "acceptingBlueScores": { + "$ref": "#/components/schemas/TxSearchAcceptingBlueScores" + } + }, + "type": "object", + "title": "TxSearch" + }, + "TxSearchAcceptingBlueScores": { + "properties": { + "gte": { + "type": "integer", + "title": "Gte" + }, + "lt": { + "type": "integer", + "title": "Lt" + } + }, + "type": "object", + "required": [ + "gte", + "lt" + ], + "title": "TxSearchAcceptingBlueScores" + }, + "UtxoModel": { + "properties": { + "amount": { + "type": "string", + "title": "Amount", + "default": [ + "11501593788" + ] + }, + "scriptPublicKey": { + "$ref": "#/components/schemas/ScriptPublicKeyModel" + }, + "blockDaaScore": { + "type": "string", + "title": "Blockdaascore", + "default": "18867232" + }, + "isCoinbase": { + "type": "boolean", + "title": "Iscoinbase", + "default": false + } + }, + "type": "object", + "required": [ + "scriptPublicKey" + ], + "title": "UtxoModel" + }, + "UtxoRequest": { + "properties": { + "addresses": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Addresses", + "default": [ + "kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73" + ] + } + }, + "type": "object", + "title": "UtxoRequest" + }, + "UtxoResponse": { + "properties": { + "address": { + "type": "string", + "title": "Address", + "default": "kaspa:qqkqkzjvr7zwxxmjxjkmxxdwju9kjs6e9u82uh59z07vgaks6gg62v8707g73" + }, + "outpoint": { + "$ref": "#/components/schemas/OutpointModel" + }, + "utxoEntry": { + "$ref": "#/components/schemas/UtxoModel" + } + }, + "type": "object", + "required": [ + "outpoint", + "utxoEntry" + ], + "title": "UtxoResponse" + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + } + ] + }, + "type": "array", + "title": "Location" + }, + "msg": { + "type": "string", + "title": "Message" + }, + "type": { + "type": "string", + "title": "Error Type" + } + }, + "type": "object", + "required": [ + "loc", + "msg", + "type" + ], + "title": "ValidationError" + }, + "VcBlockModel": { + "properties": { + "hash": { + "type": "string", + "title": "Hash" + }, + "blue_score": { + "type": "integer", + "title": "Blue Score" + }, + "daa_score": { + "type": "integer", + "title": "Daa Score" + }, + "timestamp": { + "type": "integer", + "title": "Timestamp" + }, + "transactions": { + "items": { + "$ref": "#/components/schemas/VcTxModel" + }, + "type": "array", + "title": "Transactions" + } + }, + "type": "object", + "required": [ + "hash", + "blue_score" + ], + "title": "VcBlockModel" + }, + "VcTxInput": { + "properties": { + "previous_outpoint_hash": { + "type": "string", + "title": "Previous Outpoint Hash" + }, + "previous_outpoint_index": { + "type": "integer", + "title": "Previous Outpoint Index" + }, + "previous_outpoint_script": { + "type": "string", + "title": "Previous Outpoint Script" + }, + "previous_outpoint_address": { + "type": "string", + "title": "Previous Outpoint Address" + }, + "previous_outpoint_amount": { + "type": "integer", + "title": "Previous Outpoint Amount" + } + }, + "type": "object", + "required": [ + "previous_outpoint_hash", + "previous_outpoint_index" + ], + "title": "VcTxInput" + }, + "VcTxModel": { + "properties": { + "transaction_id": { + "type": "string", + "title": "Transaction Id" + }, + "is_accepted": { + "type": "boolean", + "title": "Is Accepted", + "default": true + }, + "inputs": { + "items": { + "$ref": "#/components/schemas/VcTxInput" + }, + "type": "array", + "title": "Inputs" + }, + "outputs": { + "items": { + "$ref": "#/components/schemas/VcTxOutput" + }, + "type": "array", + "title": "Outputs" + } + }, + "type": "object", + "required": [ + "transaction_id" + ], + "title": "VcTxModel" + }, + "VcTxOutput": { + "properties": { + "script_public_key": { + "type": "string", + "title": "Script Public Key" + }, + "script_public_key_address": { + "type": "string", + "title": "Script Public Key Address" + }, + "amount": { + "type": "integer", + "title": "Amount" + } + }, + "type": "object", + "required": [ + "script_public_key", + "script_public_key_address", + "amount" + ], + "title": "VcTxOutput" + }, + "VerboseDataModel": { + "properties": { + "hash": { + "type": "string", + "title": "Hash", + "default": "18c7afdf8f447ca06adb8b4946dc45f5feb1188c7d177da6094dfbc760eca699" + }, + "difficulty": { + "type": "number", + "title": "Difficulty", + "default": [ + 4102204523252.94 + ] + }, + "selectedParentHash": { + "type": "string", + "title": "Selectedparenthash", + "default": "580f65c8da9d436480817f6bd7c13eecd9223b37f0d34ae42fb17e1e9fda397e" + }, + "transactionIds": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Transactionids", + "default": [ + "533f8314bf772259fe517f53507a79ebe61c8c6a11748d93a0835551233b3311" + ] + }, + "blueScore": { + "type": "string", + "title": "Bluescore", + "default": "18483232" + }, + "childrenHashes": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Childrenhashes" + }, + "mergeSetBluesHashes": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Mergesetblueshashes", + "default": [] + }, + "mergeSetRedsHashes": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Mergesetredshashes", + "default": [] + }, + "isChainBlock": { + "type": "boolean", + "title": "Ischainblock", + "default": false + } + }, + "type": "object", + "title": "VerboseDataModel" + }, + "endpoints__get_blocks__BlockHeader": { + "properties": { + "version": { + "type": "integer", + "title": "Version", + "default": 1 + }, + "hashMerkleRoot": { + "type": "string", + "title": "Hashmerkleroot", + "default": "e6641454e16cff4f232b899564eeaa6e480b66069d87bee6a2b2476e63fcd887" + }, + "acceptedIdMerkleRoot": { + "type": "string", + "title": "Acceptedidmerkleroot", + "default": "9bab45b027a0b2b47135b6f6f866e5e4040fc1fdf2fe56eb0c90a603ce86092b" + }, + "utxoCommitment": { + "type": "string", + "title": "Utxocommitment", + "default": "236d5f9ffd19b317a97693322c3e2ae11a44b5df803d71f1ccf6c2393bc6143c" + }, + "timestamp": { + "type": "string", + "title": "Timestamp", + "default": "1656450648874" + }, + "bits": { + "type": "integer", + "title": "Bits", + "default": 455233226 + }, + "nonce": { + "type": "string", + "title": "Nonce", + "default": "14797571275553019490" + }, + "daaScore": { + "type": "string", + "title": "Daascore", + "default": "19984482" + }, + "blueWork": { + "type": "string", + "title": "Bluework", + "default": "2d1b3f04f8a0dcd31" + }, + "parents": { + "items": { + "$ref": "#/components/schemas/ParentHashModel" + }, + "type": "array", + "title": "Parents" + }, + "blueScore": { + "type": "string", + "title": "Bluescore", + "default": "18483232" + }, + "pruningPoint": { + "type": "string", + "title": "Pruningpoint", + "default": "5d32a9403273a34b6551b84340a1459ddde2ae6ba59a47987a6374340ba41d5d" + } + }, + "type": "object", + "title": "BlockHeader" + }, + "endpoints__get_hashrate__BlockHeader": { + "properties": { + "hash": { + "type": "string", + "title": "Hash", + "default": "e6641454e16cff4f232b899564eeaa6e480b66069d87bee6a2b2476e63fcd887" + }, + "timestamp": { + "type": "string", + "title": "Timestamp", + "default": "1656450648874" + }, + "difficulty": { + "type": "integer", + "title": "Difficulty", + "default": 1212312312 + }, + "daaScore": { + "type": "string", + "title": "Daascore", + "default": "19984482" + }, + "blueScore": { + "type": "string", + "title": "Bluescore", + "default": "18483232" + } + }, + "type": "object", + "title": "BlockHeader" + } + } + } +} \ No newline at end of file diff --git a/dymension/tests/kaspa_hub_test_kas/agent-config.json b/dymension/tests/kaspa_hub_test_kas/agent-config.json new file mode 100644 index 00000000000..e8f38c76832 --- /dev/null +++ b/dymension/tests/kaspa_hub_test_kas/agent-config.json @@ -0,0 +1,116 @@ +{ + "chains": { + "dymension": { + "bech32Prefix": "dym", + "blocks": { + "confirmations": 1, + "estimateBlockTime": 6, + "reorgPeriod": 1 + }, + "canonicalAsset": "adym", + "chainId": "dymension_100-1", + "contractAddressBytes": 32, + "deployer": { + "name": "DYMENSION", + "url": "https://example.com" + }, + "displayName": "Dymension Hub Local", + "gasCurrencyCoinGeckoId": "dymension", + "domainId": 587907060, + "gasPrice": { + "amount": "100000000000.0", + "denom": "adym" + }, + "index": { + "from": 10 + }, + "grpcUrls": [ + { + "http": "http://127.0.0.1:8090" + } + ], + "isTestnet": true, + "name": "dymension", + "nativeToken": { + "decimals": 18, + "denom": "adym", + "name": "ADYM", + "symbol": "ADYM" + }, + "protocol": "cosmosnative", + "restUrls": [ + { + "http": "http://localhost:1318" + } + ], + "rpcUrls": [ + { + "http": "http://localhost:36657" + } + ], + "slip44": 118, + "technicalStack": "other", + "interchainGasPaymaster": "0x726f757465725f706f73745f6469737061746368000000000000000000000000", + "interchainSecurityModule": "0x726f757465725f69736d00000000000000000000000000000000000000000000", + "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "merkleTreeHook": "0x726f757465725f706f73745f6469737061746368000000030000000000000001", + "validatorAnnounce": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "signer": { + "type": "cosmosKey", + "prefix": "dym", + "key": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + } + }, + "kaspatest10": { + "protocol": "kaspa", + "name": "kaspatest10", + "domainId": 897658017, + "interchainGasPaymaster": "0x0000000000000000000000000000000000000000000000000000000000000000", + "mailbox": "0x0000000000000000000000000000000000000000000000000000000000000000", + "merkleTreeHook": "0x0000000000000000000000000000000000000000000000000000000000000000", + "validatorAnnounce": "0x0000000000000000000000000000000000000000000000000000000000000000", + "maxBatchSize": 100, + "maxSubmitQueueLength": 100, + "bypassBatchSimulation": false, + "rpcUrls": [ + { + "http": "https://www.google.com" + } + ], + "walletSecret": "123456qwe", + "kaspaUrlsWrpc": "api-kaspa.mzonder.com:17210", + "kaspaUrlsRest": "https://kaspa-testnet-rest.mzonder.com", + "kaspaValidators": [ + {"host": "http://localhost:9090", "ismAddress": "", "escrowPub": "02f1d9c3a546a7d25f59fe4f7ca03393477a83c32c9bd777fc4d72a3c5ea6831eb"} + ], + "kaspaMinDepositSompi": 4000000000, + "escrowAddress": "kaspatest:prc0upr64nthepvynew04vk6pzxjm6tekxnkgnynse42sdl0svtez2gffk6nv", + "kaspaEscrowPrivateKey": "\"47e2af5bc1b9ccf11555e3817d528b28c76a2d9096c94c8f13a6add121b61568\"", + "kaspaMultisigThresholdHubIsm": 1, + "kaspaMultisigThresholdEscrow": 1, + "hubMailboxId": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "depositLookBackMins": 15, + "validateDeposit": true, + "validateWithdrawal": true, + "validateWithdrawalConfirmation": true, + "grpcUrls": [ + { + "http": "http://127.0.0.1:8090" + } + ], + "hubDomain": 587907060, + "kasDomain": 897658017, + "hubTokenId": "0x726f757465725f61707000000000000000000000000000020000000000000000", + "kasTokenId": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + } + }, + "defaultRpcConsensusType": "fallback", + "origin_chains": [ + "kaspatest10", + "dymension" + ], + "destination_chains": [ + "kaspatest10", + "dymension" + ] +} \ No newline at end of file diff --git a/dymension/tests/kaspa_hub_test_kas/bootstrap.json b/dymension/tests/kaspa_hub_test_kas/bootstrap.json new file mode 100644 index 00000000000..d6fac9b1ef8 --- /dev/null +++ b/dymension/tests/kaspa_hub_test_kas/bootstrap.json @@ -0,0 +1,18 @@ +{ + "messages": [ + { + "@type": "/dymensionxyz.dymension.kas.MsgBootstrap", + "authority": "dym10d07y265gmmuvt4z0w9aw880jnsr700jgllrna", + "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "ism": "0x726f757465725f69736d00000000000000000000000000ff0000000000000000", + "outpoint": { + "transactionId": "DSp3mwVECC9HBl6Oedjig+T3+B0XgfszqjsMuVE60r8=", + "index": 0 + } + } + ], + "title": "Bootstrap KAS Module", + "summary": "This proposal initializes the Kaspa-Dymension bridge (KAS) module with its core components, enabling cross-chain operations.", + "metadata": "ipfs://CID", + "deposit": "20000000000adym" +} \ No newline at end of file diff --git a/dymension/tests/kaspa_hub_test_kas/chains/dymension/metadata.yaml b/dymension/tests/kaspa_hub_test_kas/chains/dymension/metadata.yaml new file mode 100644 index 00000000000..55b20e68ee7 --- /dev/null +++ b/dymension/tests/kaspa_hub_test_kas/chains/dymension/metadata.yaml @@ -0,0 +1,43 @@ +# yaml-language-server: $schema=../schema.json +# see https://github.com/hyperlane-xyz/hyperlane-registry/blob/main/chains/kyvetestnet/metadata.yaml +bech32Prefix: dym +# todo can add block explorers +blocks: + confirmations: 1 + estimateBlockTime: 6 + reorgPeriod: 1 +canonicalAsset: adym +chainId: dymension_100-1 +contractAddressBytes: 32 +deployer: + name: DYMENSION + url: https://example.com # TODO +displayName: Dymension Hub Local +gasCurrencyCoinGeckoId: dymension +# Generated from: console.log(parseInt('0x'+Buffer.from('DYMENSION').toString('hex'))) +domainId: 587907060 +gasPrice: + amount: "100000000000.0" + denom: adym +grpcUrls: +- http: http://127.0.0.1:8090 +index: + # TODO ?? + from: 10 # low block start +isTestnet: true +name: dymension +nativeToken: + decimals: 18 + denom: adym + name: ADYM # dym or adym + symbol: ADYM # dym or adym +protocol: cosmosnative +restUrls: +- http: http://localhost:1318 +rpcUrls: +# (JSON COMET ONE, e.g. /block?height) +- http: http://localhost:36657 +slip44: 118 #?? +technicalStack: other +transactionOverrides: + gasPrice: "2.0" # TODO: ?? diff --git a/dymension/tests/kaspa_hub_test_kas/commands.sh b/dymension/tests/kaspa_hub_test_kas/commands.sh new file mode 100644 index 00000000000..e009c1b011a --- /dev/null +++ b/dymension/tests/kaspa_hub_test_kas/commands.sh @@ -0,0 +1,276 @@ +## EXPLANATION + +### Need + +# - [ ] Local hub +# - [ ] Kaspa testnet 10 +# - [ ] WPRC node for kaspa testnet 10 + +## INSTRUCTIONS + +#### PREFACE + +# Recommended terminal tabs: +# 1. dymd +# 2. wrpc node +# 3. validator +# 4. relayer +# 5. deposit/withdraw + +################################### +#### Step 0. START KASPA RPC NODE +#### This is needed for WPRC queries and wallet connections. It takes a while to sync + +# see instructions at +# https://github.com/dymensionxyz/hyperlane-monorepo/blob/ad21e8a6554999033b39949cb80c13c208bc3581/dymension/libs/kaspa/demo/multisig/README.md#L32 + +################################### +#### Step 1. Setup escrow +#### Create hyperlane validators keys and addresses, and kaspa escrow keys and address, seed the escrow + +# in rust/main +cargo run -p kaspa-tools -- validator create local +Validator infos: { + "validator_ism_addr": "0x172ed756c7c04f6e5370f9fc181f85b7779643eb", + "validator_ism_priv_key": "a4d1c634e1b8cde0fc53013dfc62e1789535b59d15b0bbf4c8fbd2d4e79bc132", + "validator_escrow_secret": "\"afa4bcc6e5828eb28d70138ea784a32e0212d3560dfcdfac85bfa1dbabb11ac9\"", + "validator_escrow_pub_key": "027b75fcbedee53f82ebc43c19a69697100afad2df27202f107c994c740e9df5b8", + "multisig_escrow_addr": "kaspatest:prmapgdl0nsdqjsmd45fjykxuq3242g4npryzkqe3aeqq9yhrp20k20ymjrlk" +} +VALIDATOR_ISM_ADDR="0x172ed756c7c04f6e5370f9fc181f85b7779643eb" +VALIDATOR_ISM_PRIV_KEY="a4d1c634e1b8cde0fc53013dfc62e1789535b59d15b0bbf4c8fbd2d4e79bc132" +VALIDATOR_ESCROW_SECRET="\"afa4bcc6e5828eb28d70138ea784a32e0212d3560dfcdfac85bfa1dbabb11ac9\"" +VALIDATOR_ESCROW_PUB_KEY="027b75fcbedee53f82ebc43c19a69697100afad2df27202f107c994c740e9df5b8" +ESCROW_ADDR="kaspatest:prmapgdl0nsdqjsmd45fjykxuq3242g4npryzkqe3aeqq9yhrp20k20ymjrlk" +# THESE VALUES MUST CORRESPOND WITH agent-config.json (in this directory, REQUIRES EDITING) Do NOT unescape json quotes +# Update: +# kaspatest10.kaspaValidators[0].escrowPub = VALIDATOR_ESCROW_PUB_KEY +# kaspatest10.escrowAddress = ESCROW_ADDR +# kaspatest10.kaspaEscrowPrivateKey = VALIDATOR_ESCROW_SECRET + +#~~~~~~~ +# Seed escrow with 1 TKAS +# (Requires wallet from https://github.com/dymensionxyz/hyperlane-deployments/blob/main/e2e/assets/kaspa-wallet-funded-testnet-relayer/kaspa.wallet in ~/.kaspa) +cargo run -- deposit \ + --escrow-address $ESCROW_ADDR \ + --amount 100000000 \ + --wrpc-url localhost:17210 \ + --network-id testnet-10 \ + --wallet-secret lkjsdf + +#~~~~~~~ +# Or run with your own wallet +open +connect +select +send $ESCROW_ADDR 1 + +# PUT THE WALLET SECRET KEY IN agent-config.json – "kaspatest10.walletSecret" + +################################### +#### Step 2. Setup HUB +#### Deploy hyperlane entities + +MONODIR=/Users/danwt/Documents/dym/d-hyperlane-monorepo + +# clean slate +trash ~/.hyperlane; trash ~/.dymension +mkdir ~/.hyperlane; cp -r $MONODIR/dymension/tests/kaspa_hub_test_kas/chains ~/.hyperlane/chains + +# install hub binary (dymension/) +make install +source $MONODIR/dymension/tests/kaspa_hub_test_kas/env.sh +scripts/setup_local.sh +dymd start --log_level=debug + +# setup bridge objects on hub +REMOTE_ROUTER_ADDRESS="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" # no smart contracts on kaspa +dymd tx kas setup-bridge --validators "$VALIDATOR_ISM_ADDR" --threshold 1 --remote-router-address "$REMOTE_ROUTER_ADDRESS" "${HUB_FLAGS[@]}" --counterparty-domain $KASTEST_DOMAIN --hub-domain $HUB_DOMAIN + +MAILBOX=$(dymd q hyperlane mailboxes -o json | jq -r '.mailboxes[0].id') +TOKEN_ID=$(dymd q warp tokens -o json | jq -r '.tokens[0].id') +KAS_TOKEN_ID=$(dymd q warp remote-routers $TOKEN_ID -o json | jq -r '.remote_routers[0].receiver_contract') +# popoulate agent-config.json with hubMailboxId, hubTokenId, kasTokenId: +# hubMailboxId = MAILBOX +# hubTokenId = TOKEN_ID +# kasTokenId = KAS_TOKEN_ID + +################################### +#### Step 3. SETUP VALIDATOR +#### It will start listening for relayer requests + +AGENT_TMP=/Users/danwt/Documents/dym/aaa-dym-notes/all_tasks/tasks/202505_feat_kaspa/practical/e2e/tmp +DB_VALIDATOR=$AGENT_TMP/dbs/hyperlane_db_validator +DB_RELAYER=$AGENT_TMP/dbs/hyperlane_db_relayer +export CONFIG_FILES=$MONODIR/dymension/tests/kaspa_hub_test_kas/agent-config.json + +trash $AGENT_TMP/dbs +mkdir $AGENT_TMP/dbs + +## Build the binaries. In rust/main: +cargo build --release --bin relayer --bin validator + +./target/release/validator \ + --db $DB_VALIDATOR \ + --originChainName kaspatest10 \ + --reorgPeriod 1 \ + --checkpointSyncer.type localStorage \ + --checkpointSyncer.path ARBITRARY_VALUE_FOOBAR \ + --validator.key "0x${VALIDATOR_ISM_PRIV_KEY}" \ + --metrics-port 9090 \ + --log.level info + +# Or build & run right away. RUST_BACKTRACE helps debug. +RUST_BACKTRACE=full cargo run --release --bin validator -- \ + --db $DB_VALIDATOR \ + --originChainName kaspatest10 \ + --reorgPeriod 1 \ + --checkpointSyncer.type localStorage \ + --checkpointSyncer.path ARBITRARY_VALUE_FOOBAR \ + --validator.key "0x${VALIDATOR_ISM_PRIV_KEY}" \ + --metrics-port 9090 \ + --log.level info + +################################### +#### Step 4. BOOTSTRAP HUB +#### Need to declare to the hub that the bridge is ready, by specifying the escrow seed outpoint +#### We submit a gov proposal with the seed outpoint and mailbox + +# First construct the proposal + +dymd q auth module-account gov -o json | jq -r '.account.value.address' # get the authority dym10d07y265gmmuvt4z0w9aw880jnsr700jgllrna + +# get the kaspa seed outpoint + +curl -X 'GET' 'https://api-tn10.kaspa.org/addresses/kaspatest%3Apzlq49spp66vkjjex0w7z8708f6zteqwr6swy33fmy4za866ne90v7e6pyrfr/utxos' -H 'accept: application/json' # TODO: query escrow address (fix url encoding) +OUTPOINT="5e1cf6784e7af1808674a252eb417d8fa003135190dd4147caf98d8463a7e73a" +# need to convert outpoint from hex to base64 when passing to hub (note, zero index does not render) +echo $OUTPOINT | xxd -r -p | base64 # Xhz2eE568YCGdKJS60F9j6ADE1GQ3UFHyvmNhGOn5zo= +# note, reverse is ` echo $base64 | base64 -D | xxd -p ` + +# query the hub entities and reference them (REQUIRES EDITING bootstrap.json) +ISM=$(dymd q hyperlane ism isms -o json | jq -r '.isms[0].id') + +dymd tx gov submit-proposal $MONODIR/dymension/tests/kaspa_hub_test_kas/bootstrap.json \ + --from hub-user \ + --gas auto \ + --fees 10000000000000000adym \ + -y + +dymd tx gov vote 1 yes "${HUB_FLAGS[@]}" + + +################################### +#### Step 5. SETUP RELAYER +#### It will monitor kaspa and hub for deposits and withdrawals and make http calls direct to the validator + +# TODO: remove unused/unnecessary things (i.e. I think kaspatest10.signer not actually used) + +# fund the relayer address on the Hub + +dymd tx bank send $HUB_KEY_WITH_FUNDS $RELAYER_ADDR 1000000000000000000adym "${HUB_FLAGS[@]}" + +# Run the relayer +./target/release/relayer \ + --db $DB_RELAYER \ + --relayChains kaspatest10,dymension \ + --allowLocalCheckpointSyncers true \ + --defaultSigner.key $HYP_KEY \ + --chains.dymension.signer.type cosmosKey \ + --chains.dymension.signer.prefix dym \ + --chains.dymension.signer.key $HYP_KEY \ + --chains.kaspatest10.signer.type cosmosKey \ + --chains.kaspatest10.signer.prefix dym \ + --chains.kaspatest10.signer.key $HYP_KEY \ + --metrics-port 9091 \ + --log.level debug + +# Or build & run right away. RUST_BACKTRACE helps debug. +RUST_BACKTRACE=1 cargo run --release --bin relayer -- \ + --db $DB_RELAYER \ + --relayChains kaspatest10,dymension \ + --allowLocalCheckpointSyncers true \ + --defaultSigner.key $HYP_KEY \ + --chains.dymension.signer.type cosmosKey \ + --chains.dymension.signer.prefix dym \ + --chains.dymension.signer.key $HYP_KEY \ + --chains.kaspatest10.signer.type cosmosKey \ + --chains.kaspatest10.signer.prefix dym \ + --chains.kaspatest10.signer.key $HYP_KEY \ + --metrics-port 9091 \ + --log.level debug + +################################### +#### Step 6. TEST DEPOSITS/WITHDRAWALS +#### Phase 1, deposit: generate a HL message using Hub CLI tool and pass this in kaspa deposit tool + +############## +### *DEPOSITS* + +HUB_USER_ADDR=$(dymd keys show -a hub-user) #dym139mq752delxv78jvtmwxhasyrycufsvrw4aka9 + +DEPOSIT_AMT=10000000000 # 10_000 million sompi = 100 TKAS + +# get the HL message +# +# TODO: Command "hl-message-kaspa" is deprecated, use 'create-hl-message --source=kaspa --dest=hub' instead +dymd q forward hl-message-kaspa $TOKEN_ID $HUB_USER_ADDR $DEPOSIT_AMT $KAS_TOKEN_ID $KASTEST_DOMAIN $HUB_DOMAIN + +# NOTE: payload should not have 0x prefix +HL_PAYLOAD=$(dymd q forward hl-message-kaspa $TOKEN_ID $HUB_USER_ADDR $DEPOSIT_AMT $KAS_TOKEN_ID $KASTEST_DOMAIN $HUB_DOMAIN | cut -c 3-) + +# In hyperlane-monorepo/dymension/libs/kaspa/demo/relayer (Removed https://github.com/dymensionxyz/hyperlane-monorepo/pull/326) +# Put payload in the arguments +cargo run -- \ + --escrow-address $ESCROW_ADDR \ + --amount $DEPOSIT_AMT \ + --rpcserver localhost:17210 \ + --wallet-secret 123456qwe \ + --only-deposit \ + --payload "${HL_PAYLOAD}" + +# Validate the result + +KAS_TOKEN_DENOM=$(dymd q warp tokens -o json | jq -r '.tokens[0].origin_denom') + +# Should have $DEPOSIT_AMT Kaspa tokens +dymd q bank balance $HUB_USER_ADDR $KAS_TOKEN_DENOM + +################# +### *WITHDRAWALS* + +# convert your kaspa address to something that can be interpreted by Hub CLI +# in tooling/ +KASPA_RECIPIENT=$(cargo run recipient kaspatest:qrjmshvw4ucgyhm8rlc257g4mz9fy64kf0gkr8tgktsdwtplvtcs26durxukf) # (Dan's tn10 address, put your own address here) +# output like 0xdf2dc917540c7380a86e51fad4b8e1101a0efa27473a5ca9b97ceb846cc402ab + +# initiate the transfer +# dymd tx warp transfer [token-id] [destination-domain] [recipient] [amount] [flags] +# kastest10 domain is 897658017 +WITHDRAW_AMT=4000000002 # more than min deposit, 40 KAS +dymd tx warp transfer $TOKEN_ID $KASTEST_DOMAIN $KASPA_RECIPIENT $WITHDRAW_AMT --max-hyperlane-fee 1000adym "${HUB_FLAGS[@]}" + +# Validate the result + +# get the transaction ID of new anchor outpoint on the Hub +# it can be compared agains the change UTXO in the Kaspa explorer +echo $(dymd q kas outpoint -o json | jq -r '.outpoint.transaction_id') | base64 -D | xxd -p + +############################################################### +############################################################### +############################################################### +############################################################### +############################################################### +#### APPENDIX: DEBUG TIPS + +# check that validator server is working +curl -X POST -H "Content-Type: application/json" -d '{}' http://localhost:9090/kaspa-ping + +# emergency fix for hooks +# mailbox, default hook (e.g. IGP), required hook (e.g. merkle tree) +dymd tx hyperlane hooks noop create "${HUB_FLAGS[@]}" +NOOP_HOOK=$(curl -s http://localhost:1318/hyperlane/v1/noop_hooks | jq '.noop_hooks.[0].id' -r); echo $NOOP_HOOK; +dymd tx hyperlane mailbox set $MAILBOX --default-hook $NOOP_HOOK --required-hook $NOOP_HOOK "${HUB_FLAGS[@]}" +dymd tx hyperlane mailbox set $MAILBOX --default-hook 0x726f757465725f706f73745f6469737061746368000000000000000000000002 --required-hook 0x726f757465725f706f73745f6469737061746368000000030000000000000000 "${HUB_FLAGS[@]}" + +dymd tx kas indicate-progress --metadata AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfx31wOGjRk5MCQZOZhentVmmOhLE0M+Yji+bn2KYNMmWbVrYnFkIl/tXjhAM6scXm71gg30+pd0tGiQ5LrC+TGzd6er/un6SCbmj57jPzFkAgA5k8RIRjYWzJmhG5cA37Dqo15p5cKj/rGLRzFIyxQkPkOsGAba6cAiTezd7gHwAb2WO4YySk+cU1Y1lufSWkoIZFFBXkIA2FReg7PAGYYLdMxDu2OcrbfdzEPzy6wqRtkQcklcGw46BRaSWrXanowhw= --payload /Users/danwt/Documents/dym/d-hyperlane-monorepo/dymension/tests/kaspa_hub_test/scratch/indication.json "${HUB_FLAGS[@]}" diff --git a/dymension/tests/kaspa_hub_test_kas/env.sh b/dymension/tests/kaspa_hub_test_kas/env.sh new file mode 100644 index 00000000000..bd9661aeb64 --- /dev/null +++ b/dymension/tests/kaspa_hub_test_kas/env.sh @@ -0,0 +1,27 @@ +export BASE_DENOM="arax" +export BECH32_PREFIX=ethm +export CELESTIA_HOME_DIR="${HOME}/.da" +export CELESTIA_NETWORK="celestia" # for a testnet RollApp use "mocha", for mainnet - "celestia" +export DA_CLIENT="mock" # choose DA client: celestia, weavevm, next configuration can depends on it +export DENOM=$(echo "$BASE_DENOM" | sed 's/^.//') +export EXECUTABLE="rollapp-evm" # for rollapp +export HUB_CHAIN_ID="dymension_100-1" +export HUB_KEY_WITH_FUNDS="hub-user" +export HUB_PERMISSIONED_KEY="hub-user" +export HUB_REST_URL="http://localhost:1318" # required for relayer +export HUB_RPC_ENDPOINT="localhost" +export HUB_RPC_PORT="36657" # default: 36657 +export HUB_RPC_URL="http://${HUB_RPC_ENDPOINT}:${HUB_RPC_PORT}" +export KEY_NAME_ROLLAPP="rol-user" +export ROLLAPP_CHAIN_ID="rollappevm_1234-1" +export MONIKER="$ROLLAPP_CHAIN_ID-sequencer" +export ROLLAPP_HOME_DIR="$HOME/.rollapp_evm" +export ROLLAPP_SETTLEMENT_INIT_DIR_PATH="${ROLLAPP_HOME_DIR}/init" +export SETTLEMENT_EXECUTABLE=$(which dymd) +export SETTLEMENT_LAYER="dymension" # when running a local hub or a public network use "dymension" +export SKIP_EVM_BASE_FEE=true # optional, disables rollapp fees +export HUB_FLAGS=(--from hub-user --fees 100000000000000adym --gas auto --gas-adjustment 3 -y) +export RELAYER_ADDR="dym15428vq2uzwhm3taey9sr9x5vm6tk78ewtfeeth" # relayer derives from HYP_KEY +export HYP_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" # arbitrary ethereum style key, used in HL agents +export HUB_DOMAIN=587907060 +export KASTEST_DOMAIN=897658017 diff --git a/dymension/tests/kaspa_hub_test_kas/readme.md b/dymension/tests/kaspa_hub_test_kas/readme.md new file mode 100644 index 00000000000..3665389fc66 --- /dev/null +++ b/dymension/tests/kaspa_hub_test_kas/readme.md @@ -0,0 +1,3 @@ +What is this? + +A test for Kaspa -> Dymension Hub using Synthetic token type on the Hub diff --git a/dymension/tests/kaspa_hub_test_kas/scratch/commands_scratch.sh b/dymension/tests/kaspa_hub_test_kas/scratch/commands_scratch.sh new file mode 100644 index 00000000000..7429763da41 --- /dev/null +++ b/dymension/tests/kaspa_hub_test_kas/scratch/commands_scratch.sh @@ -0,0 +1,68 @@ +## NOTES + +- [ ] Launch validator and relayer +- [ ] Create ISM + + +# needed? +touch ~/.hyperlane/chains/dymension/tests/addresses.yaml +dasel put -f ~/.hyperlane/chains/dymension/tests/addresses.yaml 'interchainGasPaymaster' -v $NOOP_HOOK +dasel put -f ~/.hyperlane/chains/dymension/tests/addresses.yaml 'interchainSecurityModule' -v $ISM +dasel put -f ~/.hyperlane/chains/dymension/tests/addresses.yaml 'mailbox' -v $MAILBOX +dasel put -f ~/.hyperlane/chains/dymension/tests/addresses.yaml 'merkleTreeHook' -v $MERKLE_HOOK +dasel put -f ~/.hyperlane/chains/dymension/tests/addresses.yaml 'validatorAnnounce' -v $MAILBOX + +dasel put -f configs/warp-route-deployment.yaml 'dymension.token' -v $TOKEN_ID +dasel put -f configs/warp-route-deployment.yaml 'dymension.foreignDeployment' -v $TOKEN_ID +dasel put -f configs/warp-route-deployment.yaml 'dymension.mailbox' -v $MAILBOX + +dymd tx kas bootstrap \ + --mailbox "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B" \ + --ism "0x1234567890123456789012345678901234567890" \ + --outpoint '{"transaction_id": "EiIzRFVmd4iZqrvM3e7/ABEjM0RWZ3iJmqu8zd7v/AA=", "index": 0}' \ + --from my-validator-key \ + --chain-id dymension_1100-1 \ + -y + +# export AWS_ACCESS_KEY_ID=ABCDEFGHIJKLMNOP +# export AWS_SECRET_ACCESS_KEY=xX-haha-nice-try-Xx + +export RELAYER_ADDR="dym15428vq2uzwhm3taey9sr9x5vm6tk78ewtfeeth" # relayer derives from HYP_KEY +export VALIDATOR_SIGNATURES_DIR=$AGENT_TMP/signatures # official name + +dymd tx bank send hub-user $RELAYER_ADDR 1000000000000000000000adym "${HUB_FLAGS[@]}" + + +# gpt etc below +# run the Validator +./target/release/validator \ + --db $DB_VALIDATOR \ + --originChainName dymension \ + --reorgPeriod 1 \ + --validator.region us-east-1 \ + --checkpointSyncer.region us-east-1 \ + --validator.type aws \ + --chains..signer.type aws \ + --chains..signer.region \ + --validator.id alias/hyperlane-validator-signer- \ + --chains..signer.id alias/hyperlane-validator-signer- \ + --checkpointSyncer.type s3 \ + --checkpointSyncer.bucket hyperlane-validator-signatures-\ + + +# dymd q hyperlane ism announced-storage-locations +dymd q hyperlane ism announced-storage-locations 0x726f757465725f69736d00000000000000000000000000ff0000000000000000 0xc09dddbd26fb6dcea996ba643e8c2685c03cad57 + + +cargo build --release --bin relayer + +./target/release/relayer \ + --db $DB_RELAYER \ + --relayChains anvil0,dymension \ + --allowLocalCheckpointSyncers true \ + --defaultSigner.key $HYP_KEY \ + --chains.dymension.signer.type cosmosKey \ + --chains.dymension.signer.prefix dym \ + --chains.dymension.signer.key $HYP_KEY \ + --metrics-port 9091 \ + --log.level debug \ No newline at end of file diff --git a/dymension/tests/kaspa_hub_test_kas/scratch/draft_metadata.json b/dymension/tests/kaspa_hub_test_kas/scratch/draft_metadata.json new file mode 100644 index 00000000000..7c78fa34c47 --- /dev/null +++ b/dymension/tests/kaspa_hub_test_kas/scratch/draft_metadata.json @@ -0,0 +1,10 @@ +{ + "title": "foo", + "authors": [ + "slkdjf" + ], + "summary": "bar", + "details": "sdkfl", + "proposal_forum_url": "akdjf", + "vote_option_context": "sdkfj" +} \ No newline at end of file diff --git a/dymension/tests/kaspa_hub_test_kas/scratch/draft_proposal.json b/dymension/tests/kaspa_hub_test_kas/scratch/draft_proposal.json new file mode 100644 index 00000000000..731dec331dd --- /dev/null +++ b/dymension/tests/kaspa_hub_test_kas/scratch/draft_proposal.json @@ -0,0 +1,7 @@ +{ + "metadata": "ipfs://CID", + "deposit": "1000000000000000adym", + "title": "foo", + "summary": "bar", + "expedited": false +} \ No newline at end of file diff --git a/dymension/tests/kaspa_hub_test_kas/scratch/indication.json b/dymension/tests/kaspa_hub_test_kas/scratch/indication.json new file mode 100644 index 00000000000..8393d6d8de1 --- /dev/null +++ b/dymension/tests/kaspa_hub_test_kas/scratch/indication.json @@ -0,0 +1,11 @@ +{ + "old_outpoint": { + "transaction_id": "H/pnJgWvF5BtmbqVBt1JQGouij+qKWmrDIkpNzrKUdE=", + "index": 9 + }, + "new_outpoint": { + "transaction_id": "H/pnJgWvF5BtmbqVBt1JQGouij+qKWmrDIkpNzrKUdE=", + "index": 9 + }, + "processed_withdrawals": [] +} \ No newline at end of file diff --git a/dymension/validators/README.md b/dymension/validators/README.md new file mode 100644 index 00000000000..c20c8e379ce --- /dev/null +++ b/dymension/validators/README.md @@ -0,0 +1,19 @@ +[the difference between kaspa <> Dymension and other chains <> Dymension](./bridge/README.md) + +# Setting up the Kaspa <> Dymension bridge + +## 1. Run Kaspa full node + +Use [README_kaspa_full_node.md](./kaspa-full-node/README.md) to run a Kaspa full node. + +## 2. Run Kaspa validator + +> 💡 Use the kaspa node you've set up in the previous step in the `validator-config.json` file. + +Use [README_bridge_kaspa.md](./bridge/README_bridge_kaspa.md) to run a Kaspa validator. + +# Setting up the other chains <> Dymension bridge + +## 1. Run Hyperlane validator + +Use [README_bridge_other.md](./bridge/README_bridge_other.md) to run a validator for other chains. \ No newline at end of file diff --git a/dymension/validators/bridge/README.md b/dymension/validators/bridge/README.md new file mode 100644 index 00000000000..721762d0371 --- /dev/null +++ b/dymension/validators/bridge/README.md @@ -0,0 +1,59 @@ +# Validator instructions for HL based bridges between Dymension <-> Other Chains + +⚠️ IMPORTANT INFORMATION ⚠️ + +Dymension has bridges between + +- Kaspa <-> Dymension +- Ethereum <-> Dymension +- Solana <-> Dymension +- (soon) Base <-> Dymension +- (soon) Binance <-> Dymension + +There are two distinct technologies: + +1. Custom HL based bridge between Kaspa <-> Dymension +2. Vanilla HL bridge between the other chains and Dymension + +A 'validator' actor can + +1. Validate the Kaspa <-> Dymension bridge +2. Validate Dymension chain, for purposes of minting tokens on Ethereum/Solana/Base/Binance + +These are two DISTINCT activities and not related at all. + +## Kaspa <-> Dymension + +N validators are needed. Each validator is responsible for TWO things + +1. Mint wKAS on Dymension +2. Spend escrowed KAS on Kaspa + +This requires exactly TWO key pairs. The first is an Ethereum _type_ key, used to sign a multisig processed by Dymension chain logic. The second is a Kaspa key, used to sign a multisig processed by Kaspa network. The first key can be generated inside AWS KMS. The second key can be securely generated and used by a combination of KMS and Secret Manager. We provide an alternate guide for bare metal too. + +Both keys must be very secure because they control funds. + +See [README_bridge_kaspa](./README_bridge_kaspa.md) for full instructions on validating Kaspa <-> Dymension bridge. + +## Ethereum/Solana/Base/Binance <-> Dymension + +Vanilla HL tech works by having 'validators' observe merkle roots on a SINGLE chain. Therefore for the bridges, there are FIVE different sets of validators. + +1. Dymension +2. Ethereum +3. Solana +4. Base +5. Binance + +The validator sets for Ethereum/Solana/Base/Binance are large and already exist. We will choose a secure subset to process inbound messages from these chains on Dymension. + +For Blumbus, Dymension team and partners will run N validators for Dymension Blumbus chain; each validator is responsible for TWO things + +1. Observing Dymension chain HL mailbox entity merkle root, and signing a digest for it and posting the digest to a public S3 bucket +2. Announcing the S3 bucket path on Dymension chain state in a bookkeeping entity + +This requires exactly TWO key pairs. The first is an _Ethereum type_ key, used to sign the merkle root digests. The second is a Cosmos-SDK key, used to sign a one-time transaction (or more if needing to update later) to announce the S3 bucket path. + +The first key must be very secure, it controls funds. The second key is not so important: if it is leaked, funds are not at risk. + +HL already has comprehensive docs on setting up this kind of validator ([HL doc run validators](https://docs.hyperlane.xyz/docs/operate/validators/run-validators)) and we also have a guide at [README_bridge_other.md](./README_bridge_other.md) diff --git a/dymension/validators/bridge/README_bridge_kaspa.md b/dymension/validators/bridge/README_bridge_kaspa.md new file mode 100644 index 00000000000..10ffa6a4a18 --- /dev/null +++ b/dymension/validators/bridge/README_bridge_kaspa.md @@ -0,0 +1,384 @@ +# HOW TO RUN VALIDATOR INSTANCE FOR BRIDGING DYMENSION <-> KASPA + +As explained in the master [README.md](./README.md) you will run ONE validator containing TWO key pairs to facilitate sending KAS token from KASPA network to DYMENSION. + +This doc contains TWO _alternative_ instruction sets, one to use 'bare metal', i.e. systemd and locally available keys, and the other to run with AWS KMS and Docker etc. + +BOTH methods have the same structure + +1. Generate key pairs (⚠️ _the keys don't need to be funded_ ⚠️) +2. Share pub keys to Dymension team +3. Await config template file from Dymension team +4. Fill in template with own info (keys or key locations) +5. Run + +- [HOW TO RUN VALIDATOR INSTANCE FOR BRIDGING DYMENSION \<-\> KASPA](#how-to-run-validator-instance-for-bridging-dymension---kaspa) + - [HARDWARE REQUIREMENTS](#hardware-requirements) + - [INSTRUCTIONS BARE METAL](#instructions-bare-metal) + - [PHASE 1: KEY GENERATION AND SHARING](#phase-1-key-generation-and-sharing) + - [PHASE 2: CONFIG POPULATION AND RUNNING](#phase-2-config-population-and-running) + - [Config](#config) + - [Running](#running) + - [Updating](#updating) + - [INSTRUCTIONS AWS AND KMS](#instructions-aws-and-kms) + - [PHASE 0: MACHINE SETUP](#phase-0-machine-setup) + - [PHASE 1: KEY GENERATION AND SHARING](#phase-1-key-generation-and-sharing-1) + - [PHASE 2: CONFIG POPULATION AND RUNNING](#phase-2-config-population-and-running-1) + - [Config](#config-1) + - [Running](#running-1) + - [Updating](#updating-1) + +## HARDWARE REQUIREMENTS + +- 2GB of RAM +- 2 CPU cores +- 128GB of disk space + +## INSTRUCTIONS BARE METAL + +### PHASE 1: KEY GENERATION AND SHARING + +In `hyperlane-monorepo/rust/main` do `cargo run -p kaspa-tools -- validator create local -o kaspa-bridge-keys.json`. + +It outputs (to file) something like + +``` +[ + { + "validator_ism_addr": "0x2541ca4d67d89897d51c2bf25b1fb602eca4ae5c", + "validator_ism_priv_key": "92940b5c00eb0e8c62f4c0d344b4fee4064c3ac51297159bf77874744e47e016", + "validator_escrow_secret": "\"b55335e614dacb747ee4bfb5bd95e9cdb7291d32542b27924f06cb1299a2cc5a\"", + "validator_escrow_pub_key": "0200b77b8e8f871121cda5a5c98938c7057ddee9aed930eea0dbb86dd23cbfd300", + "multisig_escrow_addr": null + } +] +``` + +Give Dymension team `validator_ism_addr` and `validator_escrow_pub_key`. Don't worry about `multisig_escrow_addr`, its not used. Backup the private keys. + +### PHASE 2: CONFIG POPULATION AND RUNNING + +#### Config + +Use the config json template provided by Dymension team at: `hyperlane-monorepo/dymension/validators/bridge/artifacts//config/kaspa/validator-config.json`. + +Note: `` placeholder should be replaced with either `blubmus` or `mainnet` + +Populate `.chains..kaspaEscrowPrivateKey` with the escrow secret value `validator_escrow_secret` (keep quotes ⚠️). Also populate `.validator.key` with `validator_ism_priv_key` and prefix with '0x'. Your file should look something like: + +``` +{ + "chains": { + "": { + // ... + "kaspaEscrowPrivateKey": "\"b230e7e6dc106593e55049ecb594e10f5be7576cc654a580c8d8494c34ffd832\"", + // ... + } + }, + // ... + "validator": { + "key": "0x6fa2337092f165023e045e78e0f0711ccfd91467762f66b19eae71363581390c" + } +} +``` + +additionally, populate the following placeholders: + +| JSON Path | Description | Example | +| ----------------------------------------------- | --------------------------------------- | ------------------------------------- | +| `.chains..kaspaUrlsWrpc` | Kaspa network wRPC URL without protocol | `wrpc-kaspa.example.com:17210` | +| `.chains..kaspaUrlsGrpc` | Kaspa network gRPC URL | `grpc://grpc-kaspa.example.com:16210` | +| `.chains..kaspaUrlsRest` | Kaspa network REST API URL | `https://api-kaspa.example.com` | +| `.chains..grpcUrls[0].http` | Dymension hub gRPC URL with port | `https://grpc.example.com:443` | + +#### Running + +Copy the dummy `kaspa.wallet` from `hyperlane-monorepo/dymension/validators/bridge/artifacts//config/kaspa/kaspa.wallet` to `~/.kaspa/kaspa.wallet`: `cp ~/.kaspa/kaspa.wallet`. This wallet is just to stop the Kaspa query client crashing because it expects a key. Signing uses the `validator_escrow_secret` generated before! + +Make a database directory in place of your choosing (such as `mkdir valdb`) + +_Build_ + +```bash +cd ${HOME}/hyperlane-monorepo/rust/main +cargo build --release --bin validator +``` + +_Copy Binary to Standard Path_ + +```bash +sudo cp ${HOME}/hyperlane-monorepo/rust/main/target/release/validator /usr/local/bin/hyperlane-validator +``` + +_Setup Environment Variables_ + +```bash +export CONFIG_FILES= # REQUIRED!! +export DB_VALIDATOR= +export ORIGIN_CHAIN=kaspatest10 # or mainnet +``` + +_Option 1: Run with systemd (recommended)_ + +```bash +# Create systemd service +sudo tee </dev/null /etc/systemd/system/validator.service +[Unit] +Description=Kaspa Bridge Validator +After=network-online.target +[Service] +WorkingDirectory=${HOME}/hyperlane-monorepo/rust/main +User=$USER +Environment="CONFIG_FILES=${CONFIG_FILES}" +ExecStart=/usr/local/bin/hyperlane-validator \ +--db ${DB_VALIDATOR} \ +--originChainName ${ORIGIN_CHAIN} \ +--reorgPeriod 1 \ +--checkpointSyncer.type localStorage \ +--checkpointSyncer.path ARBITRARY_VALUE_FOOBAR \ +--metrics-port 9090 \ +--log.level info +Restart=on-failure +RestartSec=10 +LimitNOFILE=65535 +[Install] +WantedBy=multi-user.target +EOF + +# Reload systemd and start the service +sudo systemctl daemon-reload +sudo systemctl enable validator +sudo systemctl start validator + +# View logs +journalctl -u validator -f -o cat +``` + +_Option 2: Run with tmux_ + +```bash +tmux +echo $DB_VALIDATOR && echo $CONFIG_FILES && sleep 3s +/usr/local/bin/hyperlane-validator \ +--db $DB_VALIDATOR \ +--originChainName $ORIGIN_CHAIN \ +--reorgPeriod 1 \ +--checkpointSyncer.type localStorage \ +--checkpointSyncer.path ARBITRARY_VALUE_FOOBAR \ +--metrics-port 9090 \ +--log.level info +``` + +_Managing the systemd Service_ + +```bash +# Check status +sudo systemctl status validator + +# Restart +sudo systemctl restart validator + +# Stop +sudo systemctl stop validator + +# Disable autostart +sudo systemctl disable validator + +# View logs +journalctl -u validator -f -o cat +``` + +_Exposure_ + +Make sure 9090 or whatever chosen metrics-port is exposed and tell Dymension team. Your validator will answer queries at that port. + +Now you are finished + +#### Updating to a newer version + +To update the validator, pull the latest changes from the `main-dym` branch and build the validator. + +```bash +cd ~/hyperlane-monorepo +git pull origin main-dym +cd rust/main +cargo build --release --bin validator +sudo systemctl stop validator +sudo cp target/release/validator /usr/local/bin/hyperlane-validator +sudo systemctl start validator +``` + +## INSTRUCTIONS AWS AND KMS + +The architecture is to run using docker on a provisioned VM. The gist is to first setup the VM with dependencies, then use the key generation tool to generate keys, and then configure the validator application and run using docker. + +### PHASE 0: MACHINE SETUP + +use the `terraform/README.md` to provision the infrastructure. once the infrastructure is provisioned, proceed with the following steps. + +connect to the remote VM and clone the dymension's hyperlane-monorepo fork. + +```bash +ssh -i /path/to/your/private-key.pem \ + @ + +git clone https://github.com/dymensionxyz/hyperlane-monorepo.git --branch main-dym && cd hyperlane-monorepo/dymension/validators/bridge +``` + +install dependencies by running `scripts/install-dependencies.sh` + +```bash +chmod +x scripts/install-dependencies.sh +scripts/install-dependencies.sh +``` + +Install foundry: + +```bash +source ~/.bashrc +foundryup +``` + +install docker + +```bash +# Add Docker's official GPG key +sudo mkdir -p /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg + +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + +# Install Docker Engine +sudo apt-get update +sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + +# Add user to docker group +sudo usermod -aG docker $(whoami) +``` + +log out of the VM and log back in and verify installation + +```bash +docker --version +docker compose version + +go +foundryup +``` + +build the validator container (takes time), you can proceed with most of other steps in another terminal window while the container is building. Note - `` placeholder in the following path should be replaced with either `blumbus` or `mainnet`: + +⚠️ - It may require running as `sudo` + +```bash +docker build -t hyperlane-kaspa-validator \ + -f ~/hyperlane-monorepo/dymension/validators/bridge/artifacts//config/kaspa/Dockerfile \ + ~/hyperlane-monorepo +``` + +### PHASE 1: KEY GENERATION AND SHARING + +Use the key generation tool to securely store two key pairs in AWS. replace `` , `` and `` with relevant values. + +```bash +mkdir -p ~/kaspa/{db,config,logs} +echo '' > ~/kaspa/kaspa-kms-key-arn +echo '' > ~/kaspa/kaspa-secret-path + +cd ~/hyperlane-monorepo/rust/main + +cargo run -p kaspa-tools -- validator create aws --path $(cat ~/kaspa/kaspa-secret-path) --kms-key-id $(cat ~/kaspa/kaspa-kms-key-arn) + +echo '' > ~/kaspa/dym-kms-key-arn + +AWS_KMS_KEY_ID=$(cat ~/kaspa/dym-kms-key-arn) cast wallet address --aws +``` + +Give Dymension team `validator_ism_addr` and `validator_escrow_pub_key` + +### PHASE 2: CONFIG POPULATION AND RUNNING + +#### Config + +Copy config to a kaspa local directory. replace `` with either `blumbus` or `mainnet`: + +```bash +cp -r ${HOME}/hyperlane-monorepo/dymension/validators/bridge/artifacts//config/kaspa/* ${HOME}/kaspa/config/ +``` + +**Configure agent to use AWS KMS:** + +1. Add a pointer to your AWS hosted key which allows minting of KAS on Dymension (⚠️ *replacing* the preexisting 'validator' subobject ⚠️): + +```json +// in the TOP LEVEL object + "validator": { + "id": "", + "type": "aws", + "region": "eu-central-1" + } +``` + +2. Add a pointer to your AWS hosted key which allows release KAS escrow (⚠️ this is a new field ⚠️): + +```json +// in the chains. object + "kaspaKey": { + "type": "aws", + "secretId": "", + "kmsKeyId": "", + "region": "eu-central-1" + } +``` + +Now populate urls inside `${HOME}/kaspa/config/validator-config.json`. Below is all url placeholders that need to be configured: + +| JSON Path | Description | Example | +| ----------------------------------------------- | --------------------------------------- | ------------------------------------- | +| `.chains..kaspaUrlsWrpc` | Kaspa network wRPC URL without protocol | `wrpc-kaspa.example.com:17210` | +| `.chains..kaspaUrlsGrpc` | Kaspa network gRPC URL | `grpc://grpc-kaspa.example.com:16210` | +| `.chains..kaspaUrlsRest` | Kaspa network REST API URL | `https://api-kaspa.example.com` | +| `.chains..grpcUrls[0].http` | Dymension hub gRPC URL with port | `https://grpc.example.com:443` | + +#### Running + +Start the validators on the remote host + +```bash +cp ~/kaspa/config/docker-compose.yaml ~/kaspa/ +cd ~/kaspa +docker compose up -d +``` + +## APPENDIX (shouldn't run on happy flow) + +## Updating to a newer version + +In a case you need to update the validator docker image, pull the latest changes from the `main-dym` branch and build the docker image. + +```bash +cd ~/hyperlane-monorepo +git pull origin main-dym + +# OR +git clone https://github.com/dymensionxyz/hyperlane-monorepo.git --branch main-dym +cd hyperlane-monorepo +``` + +Re-build the docker image + +```bash +docker build -t hyperlane-kaspa-validator \ + -f dymension/validators/bridge/artifacts//config/kaspa/Dockerfile \ + . +``` + +Then, restart the validator container. + +```bash +cd ~/kaspa +docker compose down +docker compose up -d +``` diff --git a/dymension/validators/bridge/README_bridge_other.md b/dymension/validators/bridge/README_bridge_other.md new file mode 100644 index 00000000000..69f4fad102c --- /dev/null +++ b/dymension/validators/bridge/README_bridge_other.md @@ -0,0 +1,243 @@ +# HOW TO RUN VALIDATOR INSTANCE FOR BRIDGING DYMENSION <-> OTHER + +As explained in the master [README.md](./README.md) you will run ONE validator containing TWO key pairs to facilitate sending various tokens between DYMENSION and ETHEREUM/BASE/BINANCE/SOLANA etc. + +This doc contains TWO alternative instruction sets, one to use 'bare metal', i.e. systemd and locally available keys, and the other to run with AWS KMS and Docker etc. + +Dymension did NOT write any special code for this type of validation. Here we give a brief help, but please refer to [official Hyperlane docs](https://docs.hyperlane.xyz/docs/operate/validators/run-validators) for comprehensive information. Note that we and HL team strongly encourage using AWS AND KMS. + +## INSTRUCTIONS BARE METAL + +Pure bare metal is not possible, in the sense that, it's a requirement for this type of validation to be able to post to an S3 bucket and for that S3 bucket to be publicly readable. + +### PHASE 1: KEY GENERATION AND SHARING + +You need to generate TWO keypairs. + +The first is a permanent Ethereum style key pair which is used to sign merkle roots of the Hyperlane entity that exists on the Hub. The public key for this pair will be uploaded to contract state on Ethereum/Solana etc and allows HL messages to reach those chains. This pair must be kept safe. Roots signed with the key are uploaded to S3. + +The second pair is a Cosmos pair and is less important. It's simply for sending a one-time [MsgAnnounceValidator](https://github.com/dymensionxyz/hyperlane-cosmos/blob/7fd657cc291b0ba11d8a991a4ec70e196dc2ccb4/x/core/01_interchain_security/keeper/msg_server.go#L220) message to the Hub which announces the S3 bucket location. + +#### The Ethereum style merkle roots key + +Generate the first keypair type, see [hex key instructions](https://docs.hyperlane.xyz/docs/operate/set-up-agent-keys#generate-a-hexadecimal-key). + +To configure the binary to use the key, see [checkpoint signer instructions](https://docs.hyperlane.xyz/docs/operate/validators/run-validators#checkpoint-signer-configuration) + +Share the generated address with Dymension team. + +#### The Cosmos style announcement key + +The validator only supports 'vanilla' cosmos key type, i.e. `/cosmos.crypto.secp256k1.PubKey`. It doesn't support `ethermint.crypto.v1.ethsecp256k1.PubKey`. + +```bash +dymd keys add hyperlane-announcement --key-type secp256k1 --keyring-backend test +dymd keys export hyperlane-announcement --unarmored-hex --unsafe --keyring-backend test + +# save the exported key for later use. It will be necessary in the validator-config.json file +``` + +Fund the `hyperlane-announcement` key with a small amount of DYM tokens (<1 DYM). This address will be used to submit the `MsgAnnounceValidator` message to the Hub. + +### PHASE 2: CONFIG POPULATION AND RUNNING + +#### Config +- Open `validator-config.json` +- Use the private key of `hyperlane-announcement` we got above, set into the `.chains.dymension.signer.key`, make sure there is a `0x` prefix. It should look like +```json +{ + "chains": { + "dymension": { +// ... + "signer": { + "type": "cosmosKey", + "prefix": "dym", + "key": "0x485a13000989c3dfe8f0981c9858447a84f0b24c5b0757c06c7daeffae894555", + "accountAddressType": "Bitcoin" + } +``` +- [Configure S3 Bucket](https://docs.hyperlane.xyz/docs/operate/validators/validator-signatures-aws) and set +```json +// in the TOP LEVEL object + "checkpointSyncer": { + "type": "s3", + "bucket": "", + "region": "" + } +``` +- Remove the `.validator` sub object from the config (its only for AWS) +- Later provide flag `--validator.key` with the Ethereum style private key (not the Hyperlane Announcement) to the run cmd as instructed [here](https://docs.hyperlane.xyz/docs/operate/validators/run-validators#checkpoint-signer-configuration), make sure there is a `0x` prefix. + +#### Running + +Follow [run instructions](https://docs.hyperlane.xyz/docs/operate/validators/run-validators#setup) to run using docker + +## INSTRUCTIONS AWS AND KMS + +The architecture is to run using docker on a provisioned VM. The gist is to first setup the VM with dependencies, then use the key generation tool to generate keys, and then configure the validator application and run using docker. + +### PHASE 0: MACHINE SETUP + +connect to the remote VM and clone the dymension's hyperlane-monorepo fork. + +```bash +ssh -i /path/to/your/private-key.pem \ + @ + +git clone https://github.com/dymensionxyz/hyperlane-monorepo.git --branch main-dym && cd hyperlane-monorepo/dymension/validators/bridge +``` + +install dependencies by running `scripts/install-dependencies.sh` + +```bash +chmod +x scripts/install-dependencies.sh +scripts/install-dependencies.sh +``` + +install docker + +```bash +# Add Docker's official GPG key +sudo mkdir -p /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg + +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + +# Install Docker Engine +sudo apt-get update +sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + +# Add user to docker group +sudo usermod -aG docker $(whoami) +``` + +log out of the VM and log back in and verify installation + +```bash +docker --version +docker compose version + +go +foundryup +``` + +running the validator in docker is taken from [official Hyperlane docs](https://docs.hyperlane.xyz/docs/operate/validators/run-validators#setup) + +pull the validator container + +```bash +docker pull --platform linux/amd64 gcr.io/abacus-labs-dev/hyperlane-agent:agents-v1.7.0 +``` + +### PHASE 1: KEY GENERATION AND SHARING + +Use the key generation tool to securely store two key pairs in AWS + +```bash +mkdir -p ~/dym/{db,config,logs} +echo '' > ~/dym/dymension-kms-key-arn + +AWS_KMS_KEY_ID=$(cat ~/dym/dymension-kms-key-arn) cast wallet address --aws +# !! Give Dymension team the retrieved address !! +``` + +Create and retrieve the `hyperlane_announcement_priv_key` + +```bash +# install `dymd` binary +git clone https://github.com/dymensionxyz/dymension.git --branch v4.0.1 --depth 1 +cd dymension && make install && cd .. && rm -rf dymension +dymd version + + +dymd keys add hyperlane-announcement --key-type secp256k1 --keyring-backend test +dymd keys export hyperlane-announcement --unarmored-hex --unsafe --keyring-backend test + +# save the exported key for later use. It will be necessary in the validator-config.json file +``` + +Fund the announcement key with a small amount of DYM, enough to pay gas for one transaction. + +### PHASE 2: CONFIG POPULATION AND RUNNING + +Work from inside the unzipped directory: + +```bash +git clone https://github.com/dymensionxyz/hyperlane-monorepo.git --branch main-dym +cd hyperlane-monorepo/dymension/validators/bridge +``` + +Use `artifacts//config/dymension/validator-config.yaml` to configure the validator, once updated, copy the file to the remote host + +```bash +cp artifacts//config/dymension/validator-config.json ${HOME}/dym/config/validator-config.json +cp artifacts//config/dymension/docker-compose.yaml ${HOME}/dym/docker-compose.yaml +``` + +Update all placeholders inside and `${HOME}/dym/config/validator-config.json` files. + +1. Add a pointer to your AWS hosted key which allows will perform the signing + +```json +// in the TOP LEVEL object + "validator": { + "id": "", + "type": "aws", + "region": "eu-central-1" + } +``` + +2. Add the [s3 compatible bucket](https://docs.hyperlane.xyz/docs/operate/validators/validator-signatures-aws) for storing signatures + +```json +// in the TOP LEVEL object + "checkpointSyncer": { + "type": "s3", + "bucket": "", + "region": "eu-central-1" + } +``` + +3. Add the `hyperlane_announcement_priv_key` to the `chains.dymension.signer.key` subobject + +Start the validators on the remote host + +```bash +# retrieve the AWS credentials from the instance metadata +ROLE_NAME=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/) +creds=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME) + +export AWS_ACCESS_KEY_ID=$(echo "$creds" | jq -r '.AccessKeyId') +export AWS_SECRET_ACCESS_KEY=$(echo "$creds" | jq -r '.SecretAccessKey') +export AWS_SESSION_TOKEN=$(echo "$creds" | jq -r '.Token') + +cd ~/dym +docker compose up -d +``` + +4. Currently, hyperlane validator does not support automatic credential refresh. You need to manually refresh the credentials by running the following command: + +4.1. save the `./scripts/refresh-aws-for-vals.sh` script on the validator vm at `/usr/local/bin/refresh_aws_for_vals.sh` +4.2. make it runnable + +```bash +chmod +x /usr/local/bin/refresh_aws_for_vals.sh +touch /var/log/refresh_aws_dym.log +``` + +3. create a cron job + +```bash +crontab -e +``` + +3.1 add this line: + +```bash +SHELL=/bin/bash +HOME=/home/ubuntu + +0 * * * * /usr/local/bin/refresh_aws_and_restart_dym.sh >> /var/log/refresh_aws_dym.log 2>&1 +``` \ No newline at end of file diff --git a/dymension/validators/bridge/README_validator_observability.md b/dymension/validators/bridge/README_validator_observability.md new file mode 100644 index 00000000000..f79b3f3313d --- /dev/null +++ b/dymension/validators/bridge/README_validator_observability.md @@ -0,0 +1,92 @@ +# Kaspa-Dymension Validator Monitoring Guide + +Quick reference for monitoring validator logs and diagnosing issues. + +## Critical Errors + +### 1. AWS Secrets Manager & KMS Errors + +**Log Messages:** + +``` +get secret value from AWS Secrets Manager +decrypt key material using AWS KMS +``` + +**Root Causes:** + +- Wrong `secretId` or `kmsKeyId` in config +- Missing IAM permissions (`secretsmanager:GetSecretValue`, `kms:Decrypt`) +- Docker can't reach EC2 metadata (169.254.169.254) - need `network_mode: host` + +### 2. Hub gRPC Errors + +**Log Messages:** + +``` +Hub is not bootstrapped +Hub query error +``` + +**Root Causes:** + +- Wrong `grpcUrls` in config +- Hub not synced +- Firewall blocking gRPC port + +### 3. Kaspa REST API Errors + +**Log Messages:** + +``` +External API error +ValidationError::ExternalApiError +``` + +**Root Causes:** + +- Wrong `kaspaUrlsRest` in config +- REST API down or rate limited +- API behind chain state + +### 4. Kaspa Node (WRPC) Errors + +**Log Messages:** + +``` +Kaspa node error +Failed to create easy wallet +``` + +**Root Causes:** + +- Wrong `kaspaUrlsWrpc` format (should be `host:port` not `ws://host:port`) +- Node not synced or unreachable + +### 5. Wrong Deposit Address + +**Log Messages:** + +``` +WrongDepositAddress { expected: "...", actual: "..." } +``` + +**Root Causes:** + +- Wrong `kaspaValidators` escrowPub keys or order +- Wrong `kaspaMultisigThresholdEscrow` +- Network prefix mismatch + +### 6. Reorg Protection + +**Log Messages:** + +``` +not safe against reorg +confirmations= required= +``` + +**Root Causes:** + +- Normal - needs more confirmations (retryable) +- REST API stale data diff --git a/dymension/validators/bridge/artifacts/blumbus/config/dymension/Dockerfile b/dymension/validators/bridge/artifacts/blumbus/config/dymension/Dockerfile new file mode 100644 index 00000000000..a209b19bf00 --- /dev/null +++ b/dymension/validators/bridge/artifacts/blumbus/config/dymension/Dockerfile @@ -0,0 +1,58 @@ +FROM rust:1.75 as builder + +# Install dependencies +RUN apt-get update && apt-get install -y \ + build-essential \ + pkg-config \ + libssl-dev \ + protobuf-compiler \ + git \ + clang \ + libclang-dev \ + cmake \ + && rm -rf /var/lib/apt/lists/* + +# Configure git to use libcurl for better SSL handling +RUN git config --global http.postBuffer 524288000 && \ + git config --global http.sslVerify true && \ + git config --global http.lowSpeedLimit 0 && \ + git config --global http.lowSpeedTime 999999 + +# Configure cargo to use git CLI for fetching (better SSL handling) +RUN mkdir -p ~/.cargo && \ + echo '[net]' >> ~/.cargo/config.toml && \ + echo 'git-fetch-with-cli = true' >> ~/.cargo/config.toml + +# Set working directory +WORKDIR /workspace + +# Copy the entire repository (maintains full directory structure) +# This is needed because rust/main workspace depends on /dymension, /sealevel, etc. +COPY . . + +# Build validator from rust/main directory +WORKDIR /workspace/rust/main +RUN cargo build --release --bin validator + +# Runtime image +FROM debian:bookworm-slim + +# Install runtime dependencies +RUN apt-get update && apt-get install -y \ + ca-certificates \ + libssl3 \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Copy binary from builder +COPY --from=builder /workspace/rust/main/target/release/validator /usr/local/bin/validator + +# Create directories +RUN mkdir -p /root/.kaspa +WORKDIR /app + +# Expose metrics port +EXPOSE 9090 + +# Run validator +CMD ["validator"] \ No newline at end of file diff --git a/dymension/validators/bridge/artifacts/blumbus/config/dymension/docker-compose.yaml b/dymension/validators/bridge/artifacts/blumbus/config/dymension/docker-compose.yaml new file mode 100644 index 00000000000..89671353d31 --- /dev/null +++ b/dymension/validators/bridge/artifacts/blumbus/config/dymension/docker-compose.yaml @@ -0,0 +1,25 @@ +version: '3.8' + +services: + validator-dymension: + image: gcr.io/abacus-labs-dev/hyperlane-agent:agents-v1.7.0 + container_name: hyperlane-dym-validator + restart: unless-stopped + command: > + ./validator + --db /hyperlane_db + --originChainName dymension + --reorgPeriod 1 + volumes: + - ./db:/hyperlane_db + - ./config:/app/config:ro + - ./logs:/logs + environment: + - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} + - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} + - AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} + network_mode: "host" + ulimits: + nofile: + soft: 65536 + hard: 65536 \ No newline at end of file diff --git a/dymension/validators/bridge/artifacts/blumbus/config/dymension/validator-config.json b/dymension/validators/bridge/artifacts/blumbus/config/dymension/validator-config.json new file mode 100644 index 00000000000..bd0ffdb7f22 --- /dev/null +++ b/dymension/validators/bridge/artifacts/blumbus/config/dymension/validator-config.json @@ -0,0 +1,64 @@ +{ + "chains": { + "dymension": { + "name": "dymension", + "chainId": "blumbus_111-1", + "domainId": "482195613", + "protocol": "cosmosnative", + "bech32Prefix": "dym", + "rpcUrls": [ + { + "http": "https://rpc-blumbus.mzonder.com" + } + ], + "grpcUrls": [ + { + "http": "https://grpc-blumbus.mzonder.com:443" + } + ], + "gasPrice": { + "amount": "20000000000", + "denom": "adym" + }, + "slip44": 118, + "nativeToken": { + "name": "Dymension", + "symbol": "DYM", + "decimals": 18, + "denom": "adym" + }, + "contractAddressBytes": 32, + "canonicalAsset": "adym", + "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "interchainGasPaymaster": "0x726f757465725f706f73745f6469737061746368000000040000000000000001", + "validatorAnnounce": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "merkleTreeHook": "0x726f757465725f706f73745f6469737061746368000000030000000000000000", + "blocks": { + "reorgPeriod": 0, + "confirmations": 1, + "estimateBlockTime": 3 + }, + "signer": { + "type": "cosmosKey", + "prefix": "dym", + "key": "0x", + "accountAddressType": "Bitcoin" + } + } + }, + "defaultRpcConsensusType": "fallback", + "originChainName": "dymension", + "validator": { + "id": "", + "type": "aws", + "region": "eu-central-1" + }, + "checkpointSyncer": { + "type": "s3", + "bucket": "", + "region": "eu-central-1" + }, + "reorgPeriod": 0, + "interval": 30, + "metricsPort": 9091 + } \ No newline at end of file diff --git a/dymension/validators/bridge/artifacts/blumbus/config/kaspa/Dockerfile b/dymension/validators/bridge/artifacts/blumbus/config/kaspa/Dockerfile new file mode 100644 index 00000000000..a209b19bf00 --- /dev/null +++ b/dymension/validators/bridge/artifacts/blumbus/config/kaspa/Dockerfile @@ -0,0 +1,58 @@ +FROM rust:1.75 as builder + +# Install dependencies +RUN apt-get update && apt-get install -y \ + build-essential \ + pkg-config \ + libssl-dev \ + protobuf-compiler \ + git \ + clang \ + libclang-dev \ + cmake \ + && rm -rf /var/lib/apt/lists/* + +# Configure git to use libcurl for better SSL handling +RUN git config --global http.postBuffer 524288000 && \ + git config --global http.sslVerify true && \ + git config --global http.lowSpeedLimit 0 && \ + git config --global http.lowSpeedTime 999999 + +# Configure cargo to use git CLI for fetching (better SSL handling) +RUN mkdir -p ~/.cargo && \ + echo '[net]' >> ~/.cargo/config.toml && \ + echo 'git-fetch-with-cli = true' >> ~/.cargo/config.toml + +# Set working directory +WORKDIR /workspace + +# Copy the entire repository (maintains full directory structure) +# This is needed because rust/main workspace depends on /dymension, /sealevel, etc. +COPY . . + +# Build validator from rust/main directory +WORKDIR /workspace/rust/main +RUN cargo build --release --bin validator + +# Runtime image +FROM debian:bookworm-slim + +# Install runtime dependencies +RUN apt-get update && apt-get install -y \ + ca-certificates \ + libssl3 \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Copy binary from builder +COPY --from=builder /workspace/rust/main/target/release/validator /usr/local/bin/validator + +# Create directories +RUN mkdir -p /root/.kaspa +WORKDIR /app + +# Expose metrics port +EXPOSE 9090 + +# Run validator +CMD ["validator"] \ No newline at end of file diff --git a/dymension/validators/bridge/artifacts/blumbus/config/kaspa/docker-compose.yaml b/dymension/validators/bridge/artifacts/blumbus/config/kaspa/docker-compose.yaml new file mode 100644 index 00000000000..ced86881729 --- /dev/null +++ b/dymension/validators/bridge/artifacts/blumbus/config/kaspa/docker-compose.yaml @@ -0,0 +1,21 @@ +version: '3.8' + +services: + validator-kaspa: + image: hyperlane-kaspa-validator:latest + container_name: hyperlane-kaspa-validator + restart: unless-stopped + command: /usr/local/bin/validator --checkpointSyncer.type localStorage --checkpointSyncer.path ARBITRARY_VALUE_FOOBAR --originChainName kaspatest10 --reorgPeriod 1 + volumes: + - ./config:/app/config:ro + - ./config/kaspa.wallet:/root/.kaspa/kaspa.wallet:ro + - ./db:/hyperlane_db + - ./logs:/logs + - /var/run/docker.sock:/var/run/docker.sock:ro + environment: + - CONFIG_FILES=/app/config/validator-config.json + - RUST_BACKTRACE=full + - AWS_REGION=eu-central-1 + ports: + - "9090:9090" + network_mode: "host" \ No newline at end of file diff --git a/dymension/validators/bridge/artifacts/blumbus/config/kaspa/kaspa.wallet b/dymension/validators/bridge/artifacts/blumbus/config/kaspa/kaspa.wallet new file mode 100644 index 00000000000..7716e3e1baa Binary files /dev/null and b/dymension/validators/bridge/artifacts/blumbus/config/kaspa/kaspa.wallet differ diff --git a/dymension/validators/bridge/artifacts/blumbus/config/kaspa/validator-config.json b/dymension/validators/bridge/artifacts/blumbus/config/kaspa/validator-config.json new file mode 100644 index 00000000000..5347baffdbb --- /dev/null +++ b/dymension/validators/bridge/artifacts/blumbus/config/kaspa/validator-config.json @@ -0,0 +1,89 @@ +{ + "chains": { + "kaspatest10": { + "protocol": "kaspa", + "name": "kaspatest10", + "domainId": 80808082, + "interchainGasPaymaster": "0x726f757465725f706f73745f6469737061746368000000040000000000000001", + "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "merkleTreeHook": "0x726f757465725f706f73745f6469737061746368000000030000000000000000", + "validatorAnnounce": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "maxBatchSize": 100, + "maxSubmitQueueLength": 100, + "bypassBatchSimulation": false, + "rpcUrls": [ + { + "http": "https://www.google.com" + } + ], + "walletSecret": "lkjsdf", + "kaspaUrlsWrpc": ":17210", + "kaspaUrlsGrpc": "grpc://:16210", + "kaspaUrlsRest": "(http|https)://", + "kaspaValidatorsEscrow": [ + {"host": "", "escrowPub": "0333472f5b60a1226bee286bb9cb3ac918e98af1da2ca56ede8b912d255b125f84"}, + {"host": "", "escrowPub": "02ab80ce96c19aae12da2c815aee219803ccc26515ee9dad5630037ab4d67dd1b9"}, + {"host": "", "escrowPub": "020118f7b9735a22f852054e55a2755774729b2f4447152c0be43617940bf0881e"}, + {"host": "", "escrowPub": "03983054dfd85b18c1be240a6e207b75ac53f66de294cce4b3ea52bb831ddb7b0b"}, + {"host": "", "escrowPub": "02e926ea0c33857a19feaa939b2171e95bb42a2a3d14f39d1124ff58b249400c5d"}, + {"host": "", "escrowPub": "037c0d01608e7a6a212b5f4b1ed218b9dbc110f0e0342b1796bcdde8be2bd1b173"}, + {"host": "", "escrowPub": "024f41b3d42748985115372a2bc5c3ef8781a496ca819645dc4391e19082fee190"}, + {"host": "", "escrowPub": "02964c14bd7056faf56c8e5f148c79f50130dd493dbf3e628099123b976e502113"}, + {"host": "", "escrowPub": "03c9a2e4ca0592e2de37d94d3ee16e7423a7a0b575a933b12407ebc871b0c77e9c"}, + {"host": "", "escrowPub": "036611f55361dee173e1a5dcffdbcf6ccd142e37ba7a8d8b32caf1bbf8d2465a97"}, + {"host": "", "escrowPub": "038877fae74f896bde636821d85d569bcef0448f0093130e0f243d020e6a25c330"}, + {"host": "", "escrowPub": "02738607ae959b3e40fa5d942d9f40fc2b1b3f75e87613840295af8723d96aa50a"}, + {"host": "", "escrowPub": "0383b43f35d74e30a7e10e94f946bea5e6e5ea936b7ee4b503213450e88b66d52f"}, + {"host": "", "escrowPub": "0367730046e0f723d5c4ddf59c53b9d7a0c9e536b4b83c212205afa7cd14135ec3"}, + {"host": "", "escrowPub": "02b9dccbd579ee180f5bdea99fd2baabb08aadb7aa4092d5c9bb4d6f75ebd6cd84"} + ], + "kaspaValidatorsIsm": [ + {"host": "", "ismAddress": "0xA8F4c60CA8143B8eeD67980b0e7E794D3d96039a"}, + {"host": "", "ismAddress": "0xD0Ca3b8B2DB0bBFA8Bf4b58E991494b57a3914e5"}, + {"host": "", "ismAddress": "0x878DFD17352d8EbBFAD63c043D38dB18085d39FF"}, + {"host": "", "ismAddress": "0xe5A3c54964aB0b3CeA13816B9A3F3137EB07F6B1"}, + {"host": "", "ismAddress": "0x0b91fb2038230cf65f95d8510c787043cce39f83"}, + {"host": "", "ismAddress": "0xc6d7a0362e249c32ea98e110386eaba111b45621"}, + {"host": "", "ismAddress": "0xcb034c4c3e9adcc42d74a891a2acda8c1620fbf9"}, + {"host": "", "ismAddress": "0xC1EFC1f9bEa7D883f3f6B572a4a0ECDbd70a08b4"}, + {"host": "", "ismAddress": "0x90EE78Fd60fBd9a04ad719711d079018E30d5C77"}, + {"host": "", "ismAddress": "0x995871E50396de955989415fbF5564AeAd0819F0"}, + {"host": "", "ismAddress": "0xf9d7f7e119b61becc611b1671e1432b59b386ac5"}, + {"host": "", "ismAddress": "0x73f6f5b537c60098f560e2dd45a8832faf9cdce3"}, + {"host": "", "ismAddress": "0x016ea0b7b9a83e00e492d144c50acac75ae0a046"}, + {"host": "", "ismAddress": "0xaa6e423a6ab1076b02867a0b0f7eccfe033cc82f"}, + {"host": "", "ismAddress": "0xe705362c4d33f45bbf65bc26404d199809e97b45"} + ], + "kaspaEscrowPrivateKey": "", + "kaspaMultisigThresholdHubIsm": 8, + "kaspaMultisigThresholdEscrow": 8, + "kaspaMinDepositSompi": 4000000000, + "hubMailboxId": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "depositLookBackMins": 3, + "validateDeposit": true, + "validateWithdrawal": true, + "validateWithdrawalConfirmation": true, + "hubDomain": 482195613, + "hubTokenId": "0x726f757465725f61707000000000000000000000000000020000000000000002", + "kasDomain": 80808082, + "kasTokenId": "0x0000000000000000000000000000000000000000000000000000000000000000", + "grpcUrls": [ + { + "http": "(http|https)://:" + } + ] + } + }, + "defaultRpcConsensusType": "fallback", + "origin_chains": [ + "kaspatest10", + "dymension" + ], + "destination_chains": [ + "kaspatest10", + "dymension" + ], + "validator": { + "key": "" + } +} \ No newline at end of file diff --git a/dymension/validators/bridge/artifacts/mainnet/config/dymension/Dockerfile b/dymension/validators/bridge/artifacts/mainnet/config/dymension/Dockerfile new file mode 100644 index 00000000000..a209b19bf00 --- /dev/null +++ b/dymension/validators/bridge/artifacts/mainnet/config/dymension/Dockerfile @@ -0,0 +1,58 @@ +FROM rust:1.75 as builder + +# Install dependencies +RUN apt-get update && apt-get install -y \ + build-essential \ + pkg-config \ + libssl-dev \ + protobuf-compiler \ + git \ + clang \ + libclang-dev \ + cmake \ + && rm -rf /var/lib/apt/lists/* + +# Configure git to use libcurl for better SSL handling +RUN git config --global http.postBuffer 524288000 && \ + git config --global http.sslVerify true && \ + git config --global http.lowSpeedLimit 0 && \ + git config --global http.lowSpeedTime 999999 + +# Configure cargo to use git CLI for fetching (better SSL handling) +RUN mkdir -p ~/.cargo && \ + echo '[net]' >> ~/.cargo/config.toml && \ + echo 'git-fetch-with-cli = true' >> ~/.cargo/config.toml + +# Set working directory +WORKDIR /workspace + +# Copy the entire repository (maintains full directory structure) +# This is needed because rust/main workspace depends on /dymension, /sealevel, etc. +COPY . . + +# Build validator from rust/main directory +WORKDIR /workspace/rust/main +RUN cargo build --release --bin validator + +# Runtime image +FROM debian:bookworm-slim + +# Install runtime dependencies +RUN apt-get update && apt-get install -y \ + ca-certificates \ + libssl3 \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Copy binary from builder +COPY --from=builder /workspace/rust/main/target/release/validator /usr/local/bin/validator + +# Create directories +RUN mkdir -p /root/.kaspa +WORKDIR /app + +# Expose metrics port +EXPOSE 9090 + +# Run validator +CMD ["validator"] \ No newline at end of file diff --git a/dymension/validators/bridge/artifacts/mainnet/config/dymension/docker-compose.yaml b/dymension/validators/bridge/artifacts/mainnet/config/dymension/docker-compose.yaml new file mode 100644 index 00000000000..89671353d31 --- /dev/null +++ b/dymension/validators/bridge/artifacts/mainnet/config/dymension/docker-compose.yaml @@ -0,0 +1,25 @@ +version: '3.8' + +services: + validator-dymension: + image: gcr.io/abacus-labs-dev/hyperlane-agent:agents-v1.7.0 + container_name: hyperlane-dym-validator + restart: unless-stopped + command: > + ./validator + --db /hyperlane_db + --originChainName dymension + --reorgPeriod 1 + volumes: + - ./db:/hyperlane_db + - ./config:/app/config:ro + - ./logs:/logs + environment: + - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} + - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} + - AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} + network_mode: "host" + ulimits: + nofile: + soft: 65536 + hard: 65536 \ No newline at end of file diff --git a/dymension/validators/bridge/artifacts/mainnet/config/dymension/validator-config.json b/dymension/validators/bridge/artifacts/mainnet/config/dymension/validator-config.json new file mode 100644 index 00000000000..f911205a986 --- /dev/null +++ b/dymension/validators/bridge/artifacts/mainnet/config/dymension/validator-config.json @@ -0,0 +1,63 @@ +{ + "chains": { + "dymension": { + "name": "dymension", + "chainId": "dymension_1100-1", + "domainId": "1570310961", + "protocol": "cosmosnative", + "bech32Prefix": "dym", + "rpcUrls": [ + { + "http": "https://rpc-dymension.mzonder.com" + } + ], + "grpcUrls": [ + { + "http": "https://grpc-dymension.mzonder.com:443" + } + ], + "gasPrice": { + "amount": "20000000000", + "denom": "adym" + }, + "slip44": 118, + "nativeToken": { + "name": "Dymension", + "symbol": "DYM", + "decimals": 18, + "denom": "adym" + }, + "contractAddressBytes": 32, + "canonicalAsset": "adym", + "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "interchainGasPaymaster": "0x726f757465725f706f73745f6469737061746368000000040000000000000001", + "validatorAnnounce": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "merkleTreeHook": "0x726f757465725f706f73745f6469737061746368000000030000000000000000", + "blocks": { + "reorgPeriod": 0, + "confirmations": 1, + "estimateBlockTime": 3 + }, + "signer": { + "type": "cosmosKey", + "prefix": "dym", + "key": "" + } + } + }, + "defaultRpcConsensusType": "fallback", + "originChainName": "dymension", + "validator": { + "id": "", + "type": "aws", + "region": "eu-central-1" + }, + "checkpointSyncer": { + "type": "s3", + "bucket": "", + "region": "eu-central-1" + }, + "reorgPeriod": 0, + "interval": 30, + "metricsPort": 9091 + } \ No newline at end of file diff --git a/dymension/validators/bridge/artifacts/mainnet/config/kaspa/Dockerfile b/dymension/validators/bridge/artifacts/mainnet/config/kaspa/Dockerfile new file mode 100644 index 00000000000..a209b19bf00 --- /dev/null +++ b/dymension/validators/bridge/artifacts/mainnet/config/kaspa/Dockerfile @@ -0,0 +1,58 @@ +FROM rust:1.75 as builder + +# Install dependencies +RUN apt-get update && apt-get install -y \ + build-essential \ + pkg-config \ + libssl-dev \ + protobuf-compiler \ + git \ + clang \ + libclang-dev \ + cmake \ + && rm -rf /var/lib/apt/lists/* + +# Configure git to use libcurl for better SSL handling +RUN git config --global http.postBuffer 524288000 && \ + git config --global http.sslVerify true && \ + git config --global http.lowSpeedLimit 0 && \ + git config --global http.lowSpeedTime 999999 + +# Configure cargo to use git CLI for fetching (better SSL handling) +RUN mkdir -p ~/.cargo && \ + echo '[net]' >> ~/.cargo/config.toml && \ + echo 'git-fetch-with-cli = true' >> ~/.cargo/config.toml + +# Set working directory +WORKDIR /workspace + +# Copy the entire repository (maintains full directory structure) +# This is needed because rust/main workspace depends on /dymension, /sealevel, etc. +COPY . . + +# Build validator from rust/main directory +WORKDIR /workspace/rust/main +RUN cargo build --release --bin validator + +# Runtime image +FROM debian:bookworm-slim + +# Install runtime dependencies +RUN apt-get update && apt-get install -y \ + ca-certificates \ + libssl3 \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Copy binary from builder +COPY --from=builder /workspace/rust/main/target/release/validator /usr/local/bin/validator + +# Create directories +RUN mkdir -p /root/.kaspa +WORKDIR /app + +# Expose metrics port +EXPOSE 9090 + +# Run validator +CMD ["validator"] \ No newline at end of file diff --git a/dymension/validators/bridge/artifacts/mainnet/config/kaspa/docker-compose.yaml b/dymension/validators/bridge/artifacts/mainnet/config/kaspa/docker-compose.yaml new file mode 100644 index 00000000000..cefb5b9fac3 --- /dev/null +++ b/dymension/validators/bridge/artifacts/mainnet/config/kaspa/docker-compose.yaml @@ -0,0 +1,21 @@ +version: '3.8' + +services: + validator-kaspa: + image: hyperlane-kaspa-validator:latest + container_name: hyperlane-kaspa-validator + restart: unless-stopped + command: /usr/local/bin/validator --checkpointSyncer.type localStorage --checkpointSyncer.path ARBITRARY_VALUE_FOOBAR --originChainName kaspa --reorgPeriod 1 + volumes: + - ./config:/app/config:ro + - ./config/kaspa.wallet:/root/.kaspa/kaspa.wallet:ro + - ./db:/hyperlane_db + - ./logs:/logs + - /var/run/docker.sock:/var/run/docker.sock:ro + environment: + - CONFIG_FILES=/app/config/validator-config.json + - RUST_BACKTRACE=full + - AWS_REGION=eu-central-1 + ports: + - "9090:9090" + network_mode: "host" \ No newline at end of file diff --git a/dymension/validators/bridge/artifacts/mainnet/config/kaspa/kaspa.wallet b/dymension/validators/bridge/artifacts/mainnet/config/kaspa/kaspa.wallet new file mode 100644 index 00000000000..c375c2fbe0a Binary files /dev/null and b/dymension/validators/bridge/artifacts/mainnet/config/kaspa/kaspa.wallet differ diff --git a/dymension/validators/bridge/artifacts/mainnet/config/kaspa/validator-config.json b/dymension/validators/bridge/artifacts/mainnet/config/kaspa/validator-config.json new file mode 100644 index 00000000000..cd236b8bc84 --- /dev/null +++ b/dymension/validators/bridge/artifacts/mainnet/config/kaspa/validator-config.json @@ -0,0 +1,72 @@ +{ + "chains": { + "kaspa": { + "protocol": "kaspa", + "name": "kaspa", + "domainId": 1082673309, + "interchainGasPaymaster": "0x726f757465725f706f73745f6469737061746368000000040000000000000001", + "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "merkleTreeHook": "0x726f757465725f706f73745f6469737061746368000000030000000000000000", + "validatorAnnounce": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "maxBatchSize": 100, + "maxSubmitQueueLength": 100, + "bypassBatchSimulation": false, + "rpcUrls": [ + { + "http": "https://www.google.com" + } + ], + "walletSecret": "lkjsdf", + "kaspaUrlsWrpc": ":17210", + "kaspaUrlsGrpc": "grpc://:16210", + "kaspaUrlsRest": "(http|https)://", + "kaspaValidators": [ + {"host": "", "ismAddress": "0x091702F9f1a3f00d07bdAdcDCb116bb9E671fb7f", "escrowPub": "025739a31eeaeabd3d829431cff938e1ea50a131c2768b177f19645c91cfe1d525"}, + {"host": "", "ismAddress": "0x1a785538fa867bC11cAca68B59Cf22de6a45aea9", "escrowPub": "0356fc53d0ba057d151f87ee1404300d2ef81bb6d69a219e7ac1d1fbff1c1db9ad"}, + {"host": "", "ismAddress": "0x211E8aC7d3cEdddb6bEF53279318cFD625F8B788", "escrowPub": "0279a986dd7c53ddbc4103a2b32e70968ae14897baf5c006e6dda3c36eebfaddcf"}, + {"host": "", "ismAddress": "0x31e21d46cc979c40cb401a35557a02980a52a70c", "escrowPub": "02474595da82d56dc7854826253858d8e5a36c6794703c64ac9f6ceea5587a0712"}, + {"host": "", "ismAddress": "0x3b3da1d960b63af627134d2ff870eca5ce9721d1", "escrowPub": "0335ee8ecfc56e79fc8d08f432cd3aa1ad9cda50024ea1ec2766d9195ea3dde56d"}, + {"host": "", "ismAddress": "0x3f5ecdcdde4e11b83e664fbbec85bc580ac795f0", "escrowPub": "02b215fa75020c5aa1d5a3714304730c87b730e489224f4691480b4d51befbb3db"}, + {"host": "", "ismAddress": "0x6019462e4629af3d448e76af40b2d37dc68cdfbd", "escrowPub": "02a57e28de5a8d4157cabc87776acd1c4b76c3a0709c18fd258be1cec9f0a49133"}, + {"host": "", "ismAddress": "0x6386ceab76e8237e38025176e4264749ad9acc4a", "escrowPub": "03fb7a1884512e190f57c1d62cd39812ab6c5b053e99f40a7c25b2486e46bf5646"}, + {"host": "", "ismAddress": "0x72503c13092C96FF7721bD6d65715C6AF9fa6e21", "escrowPub": "0257423539cfcc9cca1a654abbac9959e9feb31caae3b1b23a965775749bc7d80a"}, + {"host": "", "ismAddress": "0x7b84a639332780c2c18419c6a0bc111611f0e540", "escrowPub": "03532d2a9740923d1d2d47f3c414dba4e2c48148cff421bbb3202b902369548758"}, + {"host": "", "ismAddress": "0x7bf2251f66e6143d52b378ddb1f7f8818a2d57a8", "escrowPub": "039a07b447546af7ec3d7b0a439a97497605b2e198eb0c1b5b5f4bffe0ef1019ac"}, + {"host": "", "ismAddress": "0x8f3133796e812d5d069a43f105f27b7928230d5f", "escrowPub": "03fb9b86c5461a51492ffbc941587a79aa510fe251d430809241ea03a363ab12eb"}, + {"host": "", "ismAddress": "0xD49328d6bca2f63bA627A94EF7eBD7497b1f7CbE", "escrowPub": "036a5953f4ff65435304f9b56aa7e64ed514355666f8ef50ee9ea644a8c6ba5797"}, + {"host": "", "ismAddress": "0xD79D6b494399fa942e77adD027471d282D2A6951", "escrowPub": "03d94f374f3c6bb71339c67b6a605c0d30b08e6d76e9780fbecb5781577eac0fc6"}, + {"host": "", "ismAddress": "0xd7b5af959d99416ae1fd025716ff27d09c825b19", "escrowPub": "03c1e8e4ea71276fa5754e39cc769c51364b14b7cdd3059013df5e3a2895d5d151"} + ], + "kaspaEscrowPrivateKey": "", + "kaspaMultisigThresholdHubIsm": 8, + "kaspaMultisigThresholdEscrow": 8, + "kaspaMinDepositSompi": 4000000000, + "hubMailboxId": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "depositLookBackMins": 3, + "validateDeposit": true, + "validateWithdrawal": true, + "validateWithdrawalConfirmation": true, + "hubDomain": 1570310961, + "hubTokenId": "0x726f757465725f61707000000000000000000000000000020000000000000000", + "kasDomain": 1082673309, + "kasTokenId": "0x0000000000000000000000000000000000000000000000000000000000000000", + "grpcUrls": [ + { + "http": "(http|https)://:" + } + ] + } + }, + "defaultRpcConsensusType": "fallback", + "origin_chains": [ + "kaspa", + "dymension" + ], + "destination_chains": [ + "kaspa", + "dymension" + ], + "validator": { + "key": "" + } +} \ No newline at end of file diff --git a/dymension/validators/bridge/scripts/install-dependencies.sh b/dymension/validators/bridge/scripts/install-dependencies.sh new file mode 100755 index 00000000000..cf8d0d1af47 --- /dev/null +++ b/dymension/validators/bridge/scripts/install-dependencies.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +set -e + +echo "==========================================" +echo "Starting Installation Setup" +echo "==========================================" + +# 1. Install dependencies +echo "" +echo "Step 1/3: Installing dependencies..." +echo "==========================================" +sudo apt update && sudo apt install build-essential net-tools pkg-config libssl-dev clang llvm-dev libclang-dev jq unzip -y + + +sudo apt install protobuf-compiler +curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" +unzip awscliv2.zip +sudo ./aws/install + +export GO_VERSION=1.23.0 +export CPU_ARCH="amd64" + +wget https://go.dev/dl/go"$GO_VERSION.linux-$CPU_ARCH".tar.gz +sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go"$GO_VERSION.linux-$CPU_ARCH".tar.gz +echo "export PATH=\$PATH:/usr/local/go/bin" >> ~/.bashrc +source ~/.bashrc +echo "export PATH=\$PATH:\$(go env GOPATH)" >> ~/.bashrc +echo "export PATH=\$PATH:\$(go env GOPATH)/bin" >> ~/.bashrc +source ~/.bashrc + +echo "Dependencies installed successfully!" + +# 2. Install Rust +echo "" +echo "Step 2/3: Installing Rust..." +echo "==========================================" +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + +# Load Rust environment +source "$HOME"/.cargo/env + +# Verify installation +rustc --version +cargo --version + +echo "Rust installed successfully!" + +# 3. Install Foundry cast +echo "" +echo "Step 3/3: Installing Foundry..." +echo "==========================================" +curl -L https://foundry.paradigm.xyz | bash +source ${HOME}/.bashrc + +foundryup + +echo "Foundry installed successfully!" + +echo "" +echo "==========================================" +echo "Installation Complete!" +echo "==========================================" +echo "" +echo "Please run: source ~/.bashrc" +echo "Or restart your terminal to load all environment variables." diff --git a/dymension/validators/bridge/scripts/refresh-aws-for-vals.sh b/dymension/validators/bridge/scripts/refresh-aws-for-vals.sh new file mode 100644 index 00000000000..e30c0320495 --- /dev/null +++ b/dymension/validators/bridge/scripts/refresh-aws-for-vals.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Config +DYM_DIR="${HOME}/dym" +IMDS_BASE="http://169.254.169.254/latest" + +# Get IMDSv2 token +TOKEN=$(curl -sS -X PUT "${IMDS_BASE}/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") + +# Get role name +ROLE_NAME=$(curl -sS -H "X-aws-ec2-metadata-token: ${TOKEN}" \ + "${IMDS_BASE}/meta-data/iam/security-credentials/") + +# Get credentials JSON +CREDS=$(curl -sS -H "X-aws-ec2-metadata-token: ${TOKEN}" \ + "${IMDS_BASE}/meta-data/iam/security-credentials/${ROLE_NAME}") + +AWS_ACCESS_KEY_ID=$(echo "$CREDS" | jq -r '.AccessKeyId') +AWS_SECRET_ACCESS_KEY=$(echo "$CREDS" | jq -r '.SecretAccessKey') +AWS_SESSION_TOKEN=$(echo "$CREDS" | jq -r '.Token') + +# Export for this shell so docker compose sees them +export AWS_ACCESS_KEY_ID +export AWS_SECRET_ACCESS_KEY +export AWS_SESSION_TOKEN + +cd "$DYM_DIR" + +# Restart stack so containers pick up new env +docker compose down +docker compose up -d diff --git a/dymension/validators/bridge/terraform/.gitignore b/dymension/validators/bridge/terraform/.gitignore new file mode 100644 index 00000000000..04d15deb3d8 --- /dev/null +++ b/dymension/validators/bridge/terraform/.gitignore @@ -0,0 +1,145 @@ +# .gitignore - Exclude files from version control + +# Local .terraform directories +**/.terraform/* + +# .tfstate files - NEVER commit state files (contain sensitive data) +*.tfstate +*.tfstate.* + +*.terraform.lock.hcl* + +# Crash log files +crash.log +crash.*.log + +# Exclude all .tfvars files, which may contain sensitive data +# IMPORTANT: These often contain secrets, API keys, passwords +*.tfvars +*.tfvars.json + +# Keep .tfvars.example files (these are templates) +!*.tfvars.example +!terraform.tfvars.example + +# Ignore override files as they are usually used to override resources locally +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Ignore transient lock info files created by terraform apply +.terraform.tfstate.lock.info + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +*.tfplan +*tfplan* + +# Ignore CLI configuration files +.terraformrc +terraform.rc + +# Ignore any private key files +*.pem +*.key +*.crt +*.p12 +*.pfx + +# SSH keys +id_rsa +id_rsa.pub +id_ed25519 +id_ed25519.pub + +# AWS credentials +credentials +*.credentials + +# Environment files +.env +.env.* +!.env.example + +# IDE and Editor files +.idea/ +.vscode/ +*.swp +*.swo +*.swn +*~ +.project +.classpath +.settings/ +*.sublime-project +*.sublime-workspace + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +desktop.ini + +# Temporary files +*.tmp +*.bak +*.backup +*.orig + +# Logs +*.log +logs/ + +# Backup files +*~ +*.backup +*.old + +# Archive files +*.zip +*.tar.gz +*.tgz +*.rar +*.7z + +# Node modules (if using any Node-based tooling) +node_modules/ + +# Python (if using any Python tooling) +__pycache__/ +*.py[cod] +*$py.class +.Python +venv/ +env/ +*.egg-info/ + +# Go (if using any Go tooling) +*.exe +*.dll +*.so +*.dylib +vendor/ + +# Terraform plugin cache +.terraform.d/plugins/ + +# Test directories and files +test-results/ +coverage/ +*.test + +# Documentation build artifacts +_site/ +.jekyll-cache/ + +# Terraform module cache +.terraform.lock.hcl.backup + +# Keep dependency lock file (recommended to commit this) +# Comment out if you want to ignore it +# .terraform.lock.hcl diff --git a/dymension/validators/bridge/terraform/.terraformignore b/dymension/validators/bridge/terraform/.terraformignore new file mode 100644 index 00000000000..f870aa85cd8 --- /dev/null +++ b/dymension/validators/bridge/terraform/.terraformignore @@ -0,0 +1,81 @@ +# .terraformignore - Exclude files/directories from Terraform operations +# Similar to .gitignore but specifically for Terraform + +# Local Terraform state files +*.tfstate +*.tfstate.* +*.tfstate.backup + +# Crash log files +crash.log +crash.*.log + +# Lock files (if using workspaces, may want to commit .terraform.lock.hcl) +# .terraform.lock.hcl + +# Directory for local Terraform modules +.terraform/ + +# Local override files (used for local development) +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Variable files that may contain sensitive data +terraform.tfvars +*.auto.tfvars +*.auto.tfvars.json + +# Ignore CLI configuration files +.terraformrc +terraform.rc + +# IDE and editor files +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store + +# OS files +Thumbs.db +desktop.ini + +# Temporary files +*.tmp +*.bak +*.backup + +# Documentation and examples that don't need to be uploaded +# (keep terraform.tfvars.example as it's a template) +docs/ +examples/ + +# Test files and directories +test/ +tests/ +*_test.go + +# CI/CD files +.github/ +.gitlab-ci.yml +.circleci/ + +# Version control +.git/ +.gitignore + +# Logs +*.log + +# Archive files +*.zip +*.tar.gz +*.tgz +*.rar + +# Plan files (may contain sensitive data) +*.tfplan +plan.out diff --git a/dymension/validators/bridge/terraform/README.md b/dymension/validators/bridge/terraform/README.md new file mode 100644 index 00000000000..20ec8d3dded --- /dev/null +++ b/dymension/validators/bridge/terraform/README.md @@ -0,0 +1,322 @@ +# Hyperlane Validator Infrastructure - Unified Deployment + +This directory contains a unified Terraform configuration that deploys complete validator infrastructure for multiple operators in a single deployment. + +## Overview + +This configuration manages: +- **1 KMS encryption key** per user for Kaspa (encrypts secrets in Secrets Manager) +- **1 KMS signing key** per user for Dymension (signs validator attestations) +- **2 S3 buckets** per user (one for each chain's signatures) +- **1 EC2 instance** per user (runs both validators) +- **Shared VPC** for all validators +- **Per-user IAM roles** with workload identity + +### Architecture Differences: Kaspa vs Dymension + +| Feature | Kaspa Validator | Dymension Validator | +|---------|----------------|---------------------| +| **Key Storage** | Private key in Secrets Manager | KMS signing key (no private key stored) | +| **KMS Key Type** | Symmetric (`ENCRYPT_DECRYPT`) | Asymmetric (`SIGN_VERIFY`, `ECC_SECG_P256K1`) | +| **KMS Usage** | Encrypt/decrypt the private key | Sign attestations directly | +| **Signing Method** | Validator reads decrypted key from Secrets Manager | Validator calls KMS Sign API | +| **Security Model** | Key stored encrypted at rest | Key never leaves KMS | + +## User Management + +Users are defined as a variable in `terraform.tfvars`: + +### Adding a New Validator + +1. Edit `terraform.tfvars` and add a new entry to `dym_validators`: + +```hcl +newuser = { + email = "newuser@dymension.xyz" + ssh_key = "ssh-ed25519 AAAAC3... newuser@dymension.xyz" + enabled = true + secret_paths = [ + "validators/newuser/hyperlane/tn/kaspa-key", + "validators/newuser/hyperlane/tn/dymension-key" + ] +} +``` + +2. Apply the changes: + +```bash +terraform apply +``` + +This will create: +- `kaspa-validator-newuser-{env}` KMS key +- `dym-validator-newuser-{env}` KMS key +- `hyperlane-kaspa-signatures-newuser-{env}` S3 bucket +- `hyperlane-dym-signatures-newuser-{env}` S3 bucket +- `hyperlane-validator-newuser-{env}` EC2 instance +- IAM instance profile with scoped permissions + +### Disabling a Validator ( Deleting the infrastructure associated with the validator ) + +Set `enabled = false` for the user in `terraform.tfvars`: + +Then apply: + +```bash +terraform apply +``` + +This will destroy all resources for that user. + +## Resource Naming Convention + +All resources follow a consistent naming pattern: + +| Resource Type | Pattern | Example | +|---------------|---------|---------| +| KMS Key (Kaspa Encryption) | `kaspa-validator-{user}-{env}` | `kaspa-validator-omri-tn` | +| KMS Key (Dymension Signing) | `dym-validator-{user}-{env}` | `dym-validator-omri-tn` | +| S3 Bucket (Kaspa) | `hyperlane-kaspa-signatures-{user}-{env}` | `hyperlane-kaspa-signatures-omri-tn` | +| S3 Bucket (Dymension) | `hyperlane-dym-signatures-{user}-{env}` | `hyperlane-dym-signatures-omri-tn` | +| EC2 Instance | `hyperlane-validator-{user}-{env}` | `hyperlane-validator-omri-tn` | +| Log Group | `/hyperlane/validators/{user}` | `/hyperlane/validators/omri` | +| Secrets Path | `validators/{user}/hyperlane/{env}/*` | `validators/omri/hyperlane/tn/kaspa-key` | + +## Deployment Guide + +### Prerequisites + +- AWS CLI configured with appropriate credentials +- Terraform >= 1.0 +- SSH public keys for each user + +### Step 1: Configure Users + +Create a `terraform.tfvars` file (copy from `terraform.tfvars.example`) and configure the `dym_validators` variable with: +- User email addresses +- SSH public keys +- Enable/disable flags +- Secret paths for Secrets Manager + +### Step 2: Configure Variables + +Edit `terraform.tfvars` with your environment settings: + +```hcl +# Environment Configuration +environment = "tn" # pg, tn, or mn +aws_region = "eu-central-1" + +# EC2 Configuration +instance_type = "t3.xlarge" +allocate_elastic_ip = true + +# User Configuration +dym_validators = { + yourname = { + email = "yourname@example.com" + ssh_key = "ssh-ed25519 AAAAC3... yourname@example.com" + enabled = true + secret_paths = [ + "validators/yourname/hyperlane/tn/kaspa-key", + "validators/yourname/hyperlane/tn/dymension-key" + ] + } +} + +# Optional: KMS key for encrypting secrets in Secrets Manager +# secrets_kms_key_arn = "arn:aws:kms:..." +``` + +### Step 3: Initialize Terraform + +```bash +terraform init +``` + +### Step 4: Review Plan + +```bash +terraform plan +``` + +Review the resources that will be created: +- For 3 enabled users, you should see: + - 6 KMS keys (2 per user) + - 6 S3 buckets (2 per user) + - 3 EC2 instances (1 per user) + - 3 IAM roles + instance profiles + - 3 security groups + - 3 EBS data volumes + - 1 shared VPC + +### Step 5: Apply Configuration + +```bash +terraform apply +``` + +Type `yes` to confirm. + +### Step 6: Retrieve Outputs + +```bash +# Get all validator details +terraform output +``` + +## Per-User Resources + +Each enabled user automatically gets: + +### KMS Keys +- **Kaspa Encryption Key**: `SYMMETRIC_DEFAULT` for encrypting secrets + - Alias: `alias/kaspa-validator-{user}-{env}` + - Usage: Encrypt/decrypt Kaspa validator private keys stored in Secrets Manager + - Key Usage: `ENCRYPT_DECRYPT` + +- **Dymension Signing Key**: `ECC_SECG_P256K1` for validator message signing + - Alias: `alias/dym-validator-{user}-{env}` + - Usage: Sign Dymension validator attestations directly via KMS + - Key Usage: `SIGN_VERIFY` + +### S3 Buckets +- **Kaspa Signatures Bucket** + - Public read access (for relayers) + - Write access: user's instance profile only + - Versioning enabled + - Name pattern: `hyperlane-kaspa-signatures-{user}-{env}` + +- **Dymension Signatures Bucket** + - Public read access (for relayers) + - Write access: user's instance profile only + - Versioning enabled + - Name pattern: `hyperlane-dym-signatures-{user}-{env}` + +### Secrets Manager Integration +- **Kaspa Private Key Storage**: Stored encrypted in AWS Secrets Manager + - Path configured via `secret_paths[0]` in user config + - Encrypted using the user's Kaspa KMS encryption key + - Example: `validators/yourname/hyperlane/tn/kaspa-key` +- **Dymension**: Uses KMS signing directly (no secret storage needed) + +### EC2 Instance +- **Instance Type**: Configurable (default: t3.xlarge) +- **AMI**: Ubuntu 24.04 LTS +- **Volumes**: + - Root: 50GB encrypted gp3 + - Data: 200GB encrypted gp3 +- **Networking**: + - Elastic IP (optional, recommended) + - Security group with ports: 22, 9090, 9091 +- **IAM**: Instance profile with scoped permissions + +### IAM Permissions + +The instance profile allows the VM to: +- ✅ **Dymension**: Sign with own KMS signing key +- ✅ **Kaspa**: Encrypt/decrypt with own KMS encryption key (for Secrets Manager) +- ✅ **S3**: Write to own signature buckets only (both Kaspa and Dymension) +- ✅ **Secrets Manager**: Create and read own secrets only (using configured `secret_paths`) +- ✅ **CloudWatch Logs**: Write to own log group only +- ✅ **SSM Parameter Store**: Read own parameters only +- ❌ **Cannot** access other users' resources + +## Accessing Resources + +### SSH to Your VM + +```bash +# Get SSH command from outputs +terraform output ssh_commands + +# SSH as specific user +ssh -i /path/to/ssh/key ubuntu@{public-ip} +``` + +## Outputs Reference + +### Main Outputs + +```bash +terraform output validators +``` + +Returns a map with each user's resource details: + +```json +{ + "yourname": { + "kaspa_kms_key_arn": "arn:aws:kms:eu-central-1:...:key/...", + "dymension_kms_key_arn": "arn:aws:kms:eu-central-1:...:key/...", + + "kaspa_s3_bucket_arn": "arn:aws:s3:::hyperlane-kaspa-signatures-yourname-tn", + "dymension_s3_bucket_arn": "arn:aws:s3:::hyperlane-dym-signatures-yourname-tn", + + "kaspa_metrics_url": "http://3.66.186.144:9090/metrics", + "dymension_metrics_url": "http://3.66.186.144:9091/metrics", + + "kaspa_secret_path": "validators/yourname/hyperlane/tn/kaspa-key", + "kaspa_secret_arn": "arn:aws:secretsmanager:eu-central-1:...:secret:validators/yourname/hyperlane/tn/kaspa-key*" + } +} +``` + +## Environment Management + +This configuration supports multiple environments: + +| Environment | Code | Purpose | +|-------------|------|---------| +| Playground | `pg` | Development & testing | +| Testnet | `tn` | Public testnet validation | +| Mainnet | `mn` | Production mainnet validation | + +### Deploying Multiple Environments + +Use Terraform workspaces or separate state files: + +#### Option 1: Workspaces (Recommended) + +```bash +# Playground environment +# Testnet environment +terraform workspace new testnet +terraform apply -var="environment=tn" -var-file="terraform.tn.tfvars" + +# Mainnet environment +terraform workspace new mainnet +terraform apply -var="environment=mn" -var-file="terraform.mn.tfvars" +``` + +## Security Features + +### Workload Identity +- EC2 instances use IAM instance profiles (no static credentials) +- Temporary credentials rotated automatically +- All AWS API calls logged to CloudTrail + +### User Isolation +- Tag-based access control via IAM policies +- Users can only access resources tagged with their email +- Instance profiles scoped to owner's resources + +### Data Protection +- All EBS volumes encrypted at rest +- S3 versioning enabled for audit trail +- KMS keys for signing with CloudTrail logging +- Secrets encrypted in Secrets Manager + +### Network Security +- IMDSv2 enabled (optional mode for compatibility) +- Instance metadata tags enabled +- Security groups limit access to required ports (SSH: 22, Metrics: 9090, 9091) +- Public metrics endpoints (read-only) + +## Support + +For issues or questions: +1. Check CloudWatch Logs: `/hyperlane/validators/{user}` +2. Review Terraform state: `terraform show` +3. Validate IAM permissions using AWS IAM Policy Simulator +4. Check validator container logs: `docker logs hyperlane-validator-kaspa` \ No newline at end of file diff --git a/dymension/validators/bridge/terraform/main.tf b/dymension/validators/bridge/terraform/main.tf new file mode 100644 index 00000000000..71ab6d59b0e --- /dev/null +++ b/dymension/validators/bridge/terraform/main.tf @@ -0,0 +1,735 @@ +# Unified Hyperlane Validator Infrastructure +# Deploys both KMS keys and EC2 VMs for multiple validator operators + +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = var.aws_region +} + +# ============================================================================ +# User Configuration +# ============================================================================ + +locals { + # Filter enabled validators + enabled_validators = { + for name, config in var.dym_validators : name => config + if config.enabled + } + + # Filter validators with Dymension-only signing key enabled + dym_enabled_validators = { + for name, config in local.enabled_validators : name => config + if config.with_dym_validator + } +} + +# ============================================================================ +# Data Sources +# ============================================================================ + +# Get available availability zones +data "aws_availability_zones" "available" { + state = "available" +} + +# Data source for the latest Ubuntu 24.04 LTS AMI +data "aws_ami" "ubuntu" { + most_recent = true + owners = ["099720109477"] # Canonical + + filter { + name = "name" + values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } +} + +# ============================================================================ +# KMS Keys - Per User, Per Chain +# ============================================================================ + +# Kaspa KMS encryption keys (one per user) +# Symmetric key for encrypting secrets in Secrets Manager +resource "aws_kms_key" "kaspa_validator_signer" { + for_each = local.enabled_validators + + description = "KMS Key for ${each.key} Kaspa Hyperlane Validator Secrets Encryption" + key_usage = "ENCRYPT_DECRYPT" + customer_master_key_spec = "SYMMETRIC_DEFAULT" + deletion_window_in_days = 30 + + tags = { + Name = "kaspa-validator-${each.key}-${var.environment}" + Owner = each.value.email + User = each.key + Chain = "kaspa" + Environment = var.environment + ManagedBy = "terraform" + } +} + +# Kaspa KMS key aliases +resource "aws_kms_alias" "kaspa_validator_signer" { + for_each = local.enabled_validators + + name = "alias/kaspa-validator-${each.key}-${var.environment}" + target_key_id = aws_kms_key.kaspa_validator_signer[each.key].key_id +} + +# Dymension KMS signing keys (one per user) +resource "aws_kms_key" "dymension_validator_signer" { + for_each = local.enabled_validators + + description = "KMS Key for ${each.key}'s Dymension Hyperlane Validator Signing" + key_usage = "SIGN_VERIFY" + customer_master_key_spec = "ECC_SECG_P256K1" + + tags = { + Name = "dym-validator-${each.key}-${var.environment}" + Owner = each.value.email + User = each.key + Chain = "dymension" + Environment = var.environment + ManagedBy = "terraform" + } +} + +# Dymension KMS key aliases +resource "aws_kms_alias" "dymension_validator_signer" { + for_each = local.enabled_validators + + name = "alias/kaspa-dym-validator-${each.key}-${var.environment}" + target_key_id = aws_kms_key.dymension_validator_signer[each.key].key_id +} + +# Dymension-only KMS signing keys (one per user with with_dym_validator enabled) +resource "aws_kms_key" "dymension_only_validator_signer" { + for_each = local.dym_enabled_validators + + description = "KMS Key for ${each.key}'s Dymension Hyperlane Validator Signing" + key_usage = "SIGN_VERIFY" + customer_master_key_spec = "ECC_SECG_P256K1" + + tags = { + Name = "dym-validator-${each.key}-${var.environment}" + Owner = each.value.email + User = each.key + Chain = "dymension" + Environment = var.environment + ManagedBy = "terraform" + } +} + +# Dymension-only KMS key aliases +resource "aws_kms_alias" "dymension_only_validator_signer" { + for_each = local.dym_enabled_validators + + name = "alias/dym-validator-${each.key}-${var.environment}" + target_key_id = aws_kms_key.dymension_only_validator_signer[each.key].key_id +} + +# ============================================================================ +# S3 Buckets - Per User, Per Chain +# ============================================================================ + +# Kaspa validator signature buckets +resource "aws_s3_bucket" "kaspa_signatures" { + for_each = local.enabled_validators + + bucket = "hyperlane-kaspa-signatures-${each.key}-${var.environment}" + + tags = { + Name = "hyperlane-kaspa-signatures-${each.key}-${var.environment}" + Owner = each.value.email + User = each.key + Chain = "kaspa" + Environment = var.environment + Purpose = "hyperlane-validator-signatures" + ManagedBy = "terraform" + } +} + +# Kaspa bucket versioning +resource "aws_s3_bucket_versioning" "kaspa_signatures" { + for_each = local.enabled_validators + + bucket = aws_s3_bucket.kaspa_signatures[each.key].id + + versioning_configuration { + status = "Enabled" + } +} + +# Kaspa bucket public access settings +resource "aws_s3_bucket_public_access_block" "kaspa_signatures" { + for_each = local.enabled_validators + + bucket = aws_s3_bucket.kaspa_signatures[each.key].id + + block_public_acls = true + block_public_policy = false # Allow public bucket policy + ignore_public_acls = true + restrict_public_buckets = false # Allow public read access +} + +# Kaspa bucket policy (public read, owner write) +resource "aws_s3_bucket_policy" "kaspa_signatures" { + for_each = local.enabled_validators + + bucket = aws_s3_bucket.kaspa_signatures[each.key].id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "PublicReadAccess" + Effect = "Allow" + Principal = "*" + Action = [ + "s3:GetObject", + "s3:ListBucket" + ] + Resource = [ + "${aws_s3_bucket.kaspa_signatures[each.key].arn}/*", + aws_s3_bucket.kaspa_signatures[each.key].arn + ] + }, + { + Sid = "ValidatorWriteAccess" + Effect = "Allow" + Principal = { + AWS = aws_iam_role.validator_instance[each.key].arn + } + Action = [ + "s3:PutObject", + "s3:DeleteObject" + ] + Resource = "${aws_s3_bucket.kaspa_signatures[each.key].arn}/*" + } + ] + }) + + depends_on = [ + aws_s3_bucket_public_access_block.kaspa_signatures + ] +} + +# Dymension validator signature buckets +resource "aws_s3_bucket" "dymension_signatures" { + for_each = local.enabled_validators + + bucket = "hyperlane-dym-signatures-${each.key}-${var.environment}" + + tags = { + Name = "hyperlane-dym-signatures-${each.key}-${var.environment}" + Owner = each.value.email + User = each.key + Chain = "dymension" + Environment = var.environment + Purpose = "hyperlane-validator-signatures" + ManagedBy = "terraform" + } +} + +# Dymension bucket versioning +resource "aws_s3_bucket_versioning" "dymension_signatures" { + for_each = local.enabled_validators + + bucket = aws_s3_bucket.dymension_signatures[each.key].id + + versioning_configuration { + status = "Enabled" + } +} + +# Dymension bucket public access settings +resource "aws_s3_bucket_public_access_block" "dymension_signatures" { + for_each = local.enabled_validators + + bucket = aws_s3_bucket.dymension_signatures[each.key].id + + block_public_acls = true + block_public_policy = false + ignore_public_acls = true + restrict_public_buckets = false +} + +# Dymension bucket policy +resource "aws_s3_bucket_policy" "dymension_signatures" { + for_each = local.enabled_validators + + bucket = aws_s3_bucket.dymension_signatures[each.key].id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "PublicReadAccess" + Effect = "Allow" + Principal = "*" + Action = [ + "s3:GetObject", + "s3:ListBucket" + ] + Resource = [ + "${aws_s3_bucket.dymension_signatures[each.key].arn}/*", + aws_s3_bucket.dymension_signatures[each.key].arn + ] + }, + { + Sid = "ValidatorWriteAccess" + Effect = "Allow" + Principal = { + AWS = aws_iam_role.validator_instance[each.key].arn + } + Action = [ + "s3:PutObject", + "s3:DeleteObject" + ] + Resource = "${aws_s3_bucket.dymension_signatures[each.key].arn}/*" + } + ] + }) + + depends_on = [ + aws_s3_bucket_public_access_block.dymension_signatures + ] +} + +# ============================================================================ +# VPC & Networking - Shared +# ============================================================================ + +# Shared VPC for all validators +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" + + name = "hyperlane-validators-vpc-${var.environment}" + cidr = var.vpc_cidr + + azs = [data.aws_availability_zones.available.names[0]] + public_subnets = [var.public_subnet_cidr] + + enable_nat_gateway = false + enable_vpn_gateway = false + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = "hyperlane-validators-vpc-${var.environment}" + Environment = var.environment + ManagedBy = "terraform" + Purpose = "hyperlane-validators" + } +} + +# ============================================================================ +# Security Groups - Per User +# ============================================================================ + +resource "aws_security_group" "validator" { + for_each = local.enabled_validators + + name_prefix = "hyperlane-validator-${each.key}-${var.environment}-" + description = "Security group for ${each.key} Hyperlane validator" + vpc_id = module.vpc.vpc_id + + # SSH access + ingress { + description = "SSH" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + # Kaspa validator metrics + ingress { + description = "Kaspa Validator Metrics" + from_port = 9090 + to_port = 9090 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + # Dymension validator metrics + ingress { + description = "Dymension Validator Metrics" + from_port = 9091 + to_port = 9091 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + # Allow all outbound traffic + egress { + description = "Allow all outbound" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "hyperlane-validator-${each.key}-${var.environment}" + Owner = each.value.email + User = each.key + Environment = var.environment + ManagedBy = "terraform" + } +} + +# ============================================================================ +# SSH Key Pairs - Per User +# ============================================================================ + +resource "aws_key_pair" "validator" { + for_each = local.enabled_validators + + key_name = "hyperlane-validator-${each.key}-${var.environment}" + public_key = each.value.ssh_key + + tags = { + Name = "hyperlane-validator-${each.key}-${var.environment}" + Owner = each.value.email + User = each.key + Environment = var.environment + ManagedBy = "terraform" + } +} + +# ============================================================================ +# IAM Roles - Per User (Instance Profiles with Workload Identity) +# ============================================================================ + +# IAM Role for EC2 instance +resource "aws_iam_role" "validator_instance" { + for_each = local.enabled_validators + + name_prefix = "hyperlane-validator-${each.key}-${var.environment}-" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ec2.amazonaws.com" + } + } + ] + }) + + tags = { + Name = "hyperlane-validator-instance-${each.key}-${var.environment}" + Owner = each.value.email + User = each.key + Environment = var.environment + ManagedBy = "terraform" + } +} + +# IAM Instance Profile +resource "aws_iam_instance_profile" "validator" { + for_each = local.enabled_validators + + name_prefix = "hyperlane-validator-${each.key}-${var.environment}-" + role = aws_iam_role.validator_instance[each.key].name + + tags = { + Name = "hyperlane-validator-${each.key}-${var.environment}" + Owner = each.value.email + User = each.key + Environment = var.environment + ManagedBy = "terraform" + } +} + +# IAM Policy for instance profile (user-scoped access) +resource "aws_iam_role_policy" "validator_instance_access" { + for_each = local.enabled_validators + + name_prefix = "validator-access-" + role = aws_iam_role.validator_instance[each.key].id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = concat([ + # KMS permissions for Kaspa-Dymension signing key + { + Sid = "KMSSignKaspaDymensionKeyOnly" + Effect = "Allow" + Action = [ + "kms:Sign", + "kms:GetPublicKey", + "kms:DescribeKey" + ] + Resource = [ + aws_kms_key.dymension_validator_signer[each.key].arn + ] + }, + # KMS permissions for Kaspa encryption key (used with Secrets Manager) + { + Sid = "KMSEncryptKaspaKeyOnly" + Effect = "Allow" + Action = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:GenerateDataKey", + "kms:GetPublicKey", + "kms:DescribeKey" + ] + Resource = [ + aws_kms_key.kaspa_validator_signer[each.key].arn + ] + }, + # S3 permissions - Write to own buckets only + { + Sid = "S3WriteOwnBucketsOnly" + Effect = "Allow" + Action = [ + "s3:PutObject", + "s3:GetObject", + "s3:ListBucket", + "s3:DeleteObject" + ] + Resource = [ + aws_s3_bucket.kaspa_signatures[each.key].arn, + "${aws_s3_bucket.kaspa_signatures[each.key].arn}/*", + aws_s3_bucket.dymension_signatures[each.key].arn, + "${aws_s3_bucket.dymension_signatures[each.key].arn}/*" + ] + }, + # Secrets Manager permissions - Read and Create own secrets only + { + Sid = "SecretsManageOwnSecretsOnly" + Effect = "Allow" + Action = [ + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret", + "secretsmanager:CreateSecret", + "secretsmanager:PutSecretValue", + "secretsmanager:TagResource", + ] + Resource = [ + for path in each.value.secret_paths : + "arn:aws:secretsmanager:${var.aws_region}:*:secret:${path}*" + ] + }, + # KMS Decrypt for secrets + { + Sid = "KMSDecryptForSecrets" + Effect = "Allow" + Action = "kms:Decrypt" + Resource = var.secrets_kms_key_arn != "" ? [var.secrets_kms_key_arn] : ["*"] + Condition = { + StringLike = { + "kms:EncryptionContext:SecretARN" = [ + for path in each.value.secret_paths : + "arn:aws:secretsmanager:${var.aws_region}:*:secret:${path}*" + ] + } + } + }, + # CloudWatch Logs permissions + { + Sid = "LogsWriteOwnGroupOnly" + Effect = "Allow" + Action = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ] + Resource = [ + "arn:aws:logs:${var.aws_region}:*:log-group:/hyperlane/validators/${each.key}:*" + ] + }, + # SSM Parameter Store permissions + { + Sid = "SSMReadConfig" + Effect = "Allow" + Action = [ + "ssm:GetParameter", + "ssm:GetParameters" + ] + Resource = [ + "arn:aws:ssm:${var.aws_region}:*:parameter/hyperlane/${each.key}/*" + ] + } + ], + # Conditionally add Dymension-only KMS key permissions if enabled + each.value.with_dym_validator ? [ + { + Sid = "KMSSignDymensionOnlyKey" + Effect = "Allow" + Action = [ + "kms:Sign", + "kms:GetPublicKey", + "kms:DescribeKey" + ] + Resource = [ + aws_kms_key.dymension_only_validator_signer[each.key].arn + ] + } + ] : []) + }) +} + +# ============================================================================ +# CloudWatch Log Groups - Per User +# ============================================================================ + +resource "aws_cloudwatch_log_group" "validator" { + for_each = local.enabled_validators + + name = "/hyperlane/validators/${each.key}" + retention_in_days = var.log_retention_days + + tags = { + Name = "hyperlane-validators-${each.key}-${var.environment}" + Owner = each.value.email + User = each.key + Environment = var.environment + ManagedBy = "terraform" + } +} + +# ============================================================================ +# EBS Volumes - Per User +# ============================================================================ + +resource "aws_ebs_volume" "validator_data" { + for_each = local.enabled_validators + + availability_zone = data.aws_availability_zones.available.names[0] + size = var.data_volume_size_gb + type = var.data_volume_type + encrypted = true + iops = var.data_volume_type == "gp3" ? var.data_volume_iops : null + throughput = var.data_volume_type == "gp3" ? var.data_volume_throughput : null + + tags = { + Name = "hyperlane-validator-data-${each.key}-${var.environment}" + Owner = each.value.email + User = each.key + Environment = var.environment + ManagedBy = "terraform" + Purpose = "validator-database" + } +} + +# ============================================================================ +# EC2 Instances - Per User +# ============================================================================ + +resource "aws_instance" "validator" { + for_each = local.enabled_validators + + ami = data.aws_ami.ubuntu.id + instance_type = var.instance_type + key_name = aws_key_pair.validator[each.key].key_name + subnet_id = module.vpc.public_subnets[0] + + availability_zone = data.aws_availability_zones.available.names[0] + associate_public_ip_address = true + + vpc_security_group_ids = [aws_security_group.validator[each.key].id] + iam_instance_profile = aws_iam_instance_profile.validator[each.key].name + + # Root volume + root_block_device { + volume_type = "gp3" + volume_size = var.root_volume_size_gb + delete_on_termination = true + encrypted = true + } + + # Ensure instance doesn't terminate on shutdown + instance_initiated_shutdown_behavior = "stop" + + # Enable detailed monitoring + monitoring = var.enable_detailed_monitoring + + # Metadata options (IMDSv2) + metadata_options { + http_endpoint = "enabled" + http_tokens = "optional" + http_put_response_hop_limit = 1 + instance_metadata_tags = "enabled" + } + + tags = { + Name = "hyperlane-validator-${each.key}-${var.environment}" + Owner = each.value.email + User = each.key + Environment = var.environment + ManagedBy = "terraform" + Purpose = "hyperlane-validators" + Validators = "kaspa,dymension" + } + + volume_tags = { + Name = "hyperlane-validator-root-${each.key}-${var.environment}" + Owner = each.value.email + User = each.key + Environment = var.environment + ManagedBy = "terraform" + VolumeType = "root" + } +} + +# Attach the data EBS volume to each instance +resource "aws_volume_attachment" "validator_data" { + for_each = local.enabled_validators + + device_name = "/dev/sdf" + volume_id = aws_ebs_volume.validator_data[each.key].id + instance_id = aws_instance.validator[each.key].id + + depends_on = [aws_instance.validator] +} + +# ============================================================================ +# Elastic IPs - Per User (Optional) +# ============================================================================ + +resource "aws_eip" "validator" { + for_each = var.allocate_elastic_ip ? local.enabled_validators : {} + + domain = "vpc" + + tags = { + Name = "hyperlane-validator-${each.key}-${var.environment}" + Owner = each.value.email + User = each.key + Environment = var.environment + ManagedBy = "terraform" + } + + depends_on = [module.vpc] +} + +# Associate Elastic IP with instances +resource "aws_eip_association" "validator" { + for_each = var.allocate_elastic_ip ? local.enabled_validators : {} + + instance_id = aws_instance.validator[each.key].id + allocation_id = aws_eip.validator[each.key].id +} diff --git a/dymension/validators/bridge/terraform/outputs.tf b/dymension/validators/bridge/terraform/outputs.tf new file mode 100644 index 00000000000..d9fc0a504a3 --- /dev/null +++ b/dymension/validators/bridge/terraform/outputs.tf @@ -0,0 +1,25 @@ +# Outputs for Unified Hyperlane Validator Infrastructure + +output "validators" { + description = "Essential information for each validator operator" + value = { + for name, config in local.enabled_validators : name => { + # KMS Key ARNs + kaspa_kms_key_arn = aws_kms_key.kaspa_validator_signer[name].arn + kaspa_dym_kms_key_arn = aws_kms_key.dymension_validator_signer[name].arn + dymension_kms_key_arn = lookup(aws_kms_key.dymension_only_validator_signer, name, null) != null ? aws_kms_key.dymension_only_validator_signer[name].arn : null + + # S3 Bucket ARNs + kaspa_s3_bucket_arn = aws_s3_bucket.kaspa_signatures[name].arn + dymension_s3_bucket_arn = aws_s3_bucket.dymension_signatures[name].arn + + # Metrics URLs + kaspa_metrics_url = "http://${var.allocate_elastic_ip ? aws_eip.validator[name].public_ip : aws_instance.validator[name].public_ip}:9090/metrics" + dymension_metrics_url = "http://${var.allocate_elastic_ip ? aws_eip.validator[name].public_ip : aws_instance.validator[name].public_ip}:9091/metrics" + + # Secrets Manager (for Kaspa keypair) + kaspa_secret_path = config.secret_paths[0] # validators//hyperlane/kaspa-key + kaspa_secret_arn = "arn:aws:secretsmanager:${var.aws_region}:*:secret:${config.secret_paths[0]}*" + } + } +} diff --git a/dymension/validators/bridge/terraform/terraform.tfvars.example b/dymension/validators/bridge/terraform/terraform.tfvars.example new file mode 100644 index 00000000000..01be43b781b --- /dev/null +++ b/dymension/validators/bridge/terraform/terraform.tfvars.example @@ -0,0 +1,20 @@ +# Environment Configuration +environment = "tn" # pg (playground), tn (testnet), or mn (mainnet) +aws_region = "eu-central-1" + +# EC2 Configuration +instance_type = "t3.xlarge" +allocate_elastic_ip = true + +# Validator Configuration +dym_validators = { + yourname = { + email = "yourname@example.com" + ssh_key = "ssh-ed25519 AAAAC3... yourname@example.com" + enabled = true + secret_paths = [ + "validators/yourname/hyperlane/tn/kaspa-key", + "validators/yourname/hyperlane/tn/dymension-key" + ] + } +} diff --git a/dymension/validators/bridge/terraform/variables.tf b/dymension/validators/bridge/terraform/variables.tf new file mode 100644 index 00000000000..ba90a72de4f --- /dev/null +++ b/dymension/validators/bridge/terraform/variables.tf @@ -0,0 +1,125 @@ +# Variables for Unified Hyperlane Validator Infrastructure + +variable "aws_region" { + description = "AWS region where resources will be deployed" + type = string + default = "eu-central-1" +} + +variable "environment" { + description = "Environment name (pg, tn, mn)" + type = string + default = "pg" + + validation { + condition = contains(["pg", "tn", "mn"], var.environment) + error_message = "Environment must be one of: pg (playground), tn (testnet), mn (mainnet)." + } +} + +# ============================================================================ +# VPC Configuration +# ============================================================================ + +variable "vpc_cidr" { + description = "CIDR block for the VPC" + type = string + default = "192.168.0.0/16" +} + +variable "public_subnet_cidr" { + description = "CIDR block for the public subnet" + type = string + default = "192.168.1.0/24" +} + +# ============================================================================ +# EC2 Instance Configuration +# ============================================================================ + +variable "instance_type" { + description = "EC2 instance type for running validators" + type = string + default = "t3.xlarge" # 4 vCPU, 16GB RAM +} + +variable "allocate_elastic_ip" { + description = "Whether to allocate and associate Elastic IPs with instances" + type = bool + default = true +} + +# ============================================================================ +# Storage Configuration +# ============================================================================ + +variable "root_volume_size_gb" { + description = "Size of the root volume in GB" + type = number + default = 50 +} + +variable "data_volume_size_gb" { + description = "Size of the data volume for validator databases in GB" + type = number + default = 200 +} + +variable "data_volume_type" { + description = "EBS volume type for data volume (gp3, gp2, io1, io2)" + type = string + default = "gp3" +} + +variable "data_volume_iops" { + description = "IOPS for data volume (only for gp3, io1, io2)" + type = number + default = 3000 +} + +variable "data_volume_throughput" { + description = "Throughput in MB/s for data volume (only for gp3)" + type = number + default = 125 +} + +# ============================================================================ +# Secrets Management +# ============================================================================ + +variable "secrets_kms_key_arn" { + description = "ARN of the KMS key used to encrypt secrets in Secrets Manager (shared across users)" + type = string + default = "" +} + +# ============================================================================ +# Monitoring Configuration +# ============================================================================ + +variable "enable_detailed_monitoring" { + description = "Enable detailed CloudWatch monitoring for instances" + type = bool + default = true +} + +variable "log_retention_days" { + description = "Number of days to retain CloudWatch logs" + type = number + default = 30 +} + +# ============================================================================ +# Validator Configuration +# ============================================================================ + +variable "dym_validators" { + description = "Map of validator operators and their metadata" + type = map(object({ + email = string + ssh_key = string + enabled = bool + secret_paths = list(string) + with_dym_validator = bool + })) +} diff --git a/dymension/validators/kaspa-full-node/README.md b/dymension/validators/kaspa-full-node/README.md new file mode 100644 index 00000000000..c4f95b399bf --- /dev/null +++ b/dymension/validators/kaspa-full-node/README.md @@ -0,0 +1,145 @@ +This document describes how to run a Kaspa full node on a remote machine. Assume the execution of all commands on the remote machine. + +- [Running a Kaspa full node as a Docker container](#running-a-kaspa-full-node-as-a-docker-container) +- [Running a Kaspa full node as a Kubernetes deployment](#running-a-kaspa-full-node-as-a-kubernetes-deployment) + + +## Running a Kaspa full node as a Docker container + +1. Install Docker + +```bash +sudo apt-get update +sudo apt-get install ca-certificates curl +sudo install -m 0755 -d /etc/apt/keyrings +sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc +sudo chmod a+r /etc/apt/keyrings/docker.asc + +# Add the repository to Apt sources: +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + +sudo apt-get update + +sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y + +docker version +sudo usermod -aG docker $USER +sudo service docker restart +``` + +2. Download the `simply-kaspa-indexer` repository + +```bash +cd $HOME +git clone https://github.com/supertypo/simply-kaspa-indexer +cd simply-kaspa-indexer +#git checkout 0e33b5 +git checkout v1.6.0-beta1 +``` + +3. Create a `docker-compose.yaml` file + +```yaml +volumes: + kaspa_db_data: + +services: + kaspa_explorer: + container_name: kaspa_explorer + image: supertypo/kaspa-explorer:latest + restart: unless-stopped + ports: + - "8080:8080" + environment: + API_URI: http://localhost:8000 + API_WS_URI: ws://localhost:8001 + + kaspa_rest_server: + container_name: kaspa_rest_server + image: kaspanet/kaspa-rest-server:latest + restart: unless-stopped + environment: + KASPAD_HOST_1: kaspad:16210 + SQL_URI: postgresql+asyncpg://postgres:postgres@kaspa_db:5432/postgres + NETWORK_TYPE: testnet + ports: + - "0.0.0.0:8000:8000" + + simply_kaspa_socket_server: + container_name: simply_kaspa_socket_server + image: supertypo/simply-kaspa-socket-server:unstable + restart: unless-stopped + environment: + NETWORK_TYPE: testnet + ports: + - "0.0.0.0:8001:8000" + command: -x 20 -s ws://kaspad:17210 --network testnet-10 + + simply_kaspa_indexer: + container_name: simply_kaspa_indexer + image: supertypo/simply-kaspa-indexer:latest + restart: unless-stopped + command: -u -s ws://kaspad:17210 -d postgresql://postgres:postgres@kaspa_db:5432/postgres --network testnet-10 + + kaspad: + container_name: kaspad + image: supertypo/rusty-kaspad:latest + restart: unless-stopped + ports: + - "0.0.0.0:16210:16210" + - "0.0.0.0:17210:17210" + volumes: + - /var/kaspad:/app/data/ + command: kaspad --yes --nologfiles --disable-upnp --utxoindex --rpclisten=0.0.0.0:16210 --rpclisten-borsh=0.0.0.0:17210 --testnet --netsuffix=10 + + kaspa_db: + container_name: kaspa_db + image: postgres:16-alpine + restart: unless-stopped + shm_size: 10G + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + ports: + - "127.0.0.1:5432:5432" + volumes: + - kaspa_db_data:/var/lib/postgresql/data +``` + +4. Start the full node + +```bash +docker compose up -d +``` + +## Running a Kaspa full node as a Kubernetes deployment + +`kubernetes` contains a kustomize configuration to deploy a Kaspa full node as a Kubernetes deployment. It assumes a deployment to a GCP K8s, adjust the persistent volume claims depending on your needs. + +the overlay's `kaspad-patch.yaml` patch controls which network the full node deployment will target. Specifically: + +``` + - --testnet + - --netsuffix=10 +``` + +After updating the `kubernetes/base` to meet your needs, things to update according to your needs are: + +- The Storage Class for postgres statefulset +- The Storage Class for kaspad statefulset + +you can deploy the full node to your K8s cluster by running: + +```bash +kubectl apply -k kubernetes/overlays/testnet/ +``` + +or + +```bash +kubectl apply -k kubernetes/overlays/mainnet/ +``` \ No newline at end of file diff --git a/dymension/validators/kaspa-full-node/kubernetes/README.md b/dymension/validators/kaspa-full-node/kubernetes/README.md new file mode 100644 index 00000000000..332724f22e1 --- /dev/null +++ b/dymension/validators/kaspa-full-node/kubernetes/README.md @@ -0,0 +1,216 @@ +> generated + +# Kaspa Node Kubernetes Deployment + +This directory contains Kustomize configurations for deploying a Kaspa blockchain node infrastructure on Kubernetes. + +## Architecture + +The deployment includes: +- **kaspad**: Kaspa blockchain node (StatefulSet with persistent volumes) +- **kaspa-db**: PostgreSQL database (StatefulSet with persistent volumes) +- **kaspa-rest-server**: REST API server +- **simply-kaspa-indexer**: Blockchain indexer +- **simply-kaspa-socket-server**: WebSocket server +- **kaspa-explorer**: Web UI explorer + +## Directory Structure + +``` +kubernetes/ +├── base/ # Base configuration +│ ├── kustomization.yaml +│ ├── namespace.yaml +│ ├── kaspad-statefulset.yaml +│ ├── postgres-statefulset.yaml +│ ├── deployments.yaml +│ ├── services.yaml +│ └── configmap.yaml +└── overlays/ + ├── testnet/ # Testnet configuration + │ ├── kustomization.yaml + │ ├── namespace.yaml + │ └── kaspad-patch.yaml + └── mainnet/ # Mainnet configuration + ├── kustomization.yaml + ├── namespace.yaml + └── kaspad-patch.yaml +``` + +## Features + +### StatefulSets with Persistent Volumes +- Each kaspad pod gets its own persistent volume claim (500Gi) +- PostgreSQL uses persistent storage (200Gi) +- Pods are named deterministically: `kaspad-0`, `kaspad-1`, `kaspad-2`, etc. + +### Environment-Specific Overlays + +**Testnet:** +- Namespace: `kaspa-node-testnet` +- 2 kaspad replicas +- Configured for testnet-10 + +**Mainnet:** +- Namespace: `kaspa-node-mainnet` +- 5 kaspad replicas +- Configured for mainnet + +## Prerequisites + +1. Kubernetes cluster with: + - StorageClass `standard-rwo` configured (GCP persistent disks) + - Sufficient resources for running blockchain nodes + - kubectl configured to access your cluster + +2. Kustomize installed (or use `kubectl apply -k`) + +## Deployment + +### Deploy to Testnet + +```bash +kubectl apply -k overlays/testnet/ +``` + +### Deploy to Mainnet + +```bash +kubectl apply -k overlays/mainnet/ +``` + +### Verify Deployment + +```bash +# Check testnet resources +kubectl get all -n kaspa-node-testnet + +# Check mainnet resources +kubectl get all -n kaspa-node-mainnet + +# Check persistent volume claims +kubectl get pvc -n kaspa-node-testnet +kubectl get pvc -n kaspa-node-mainnet +``` + +### View Logs + +```bash +# View kaspad logs (testnet) +kubectl logs -n kaspa-node-testnet kaspad-0 -f + +# View indexer logs +kubectl logs -n kaspa-node-testnet -l app=simply-kaspa-indexer -f +``` + +## Storage Configuration + +Each kaspad pod uses a separate persistent volume claim. This means: +- `kaspad-0` uses `kaspad-data-kaspad-0` +- `kaspad-1` uses `kaspad-data-kaspad-1` +- And so on... + +These are automatically created by the StatefulSet and backed by GCP persistent disks (or your configured StorageClass). + +## Accessing Services + +Services are exposed internally within the cluster: +- Kaspa Explorer: `http://kaspa-explorer:8080` +- REST API: `http://kaspa-rest-server:8000` +- WebSocket: `ws://simply-kaspa-socket-server:8001` +- kaspad RPC: `kaspad:16210` and `kaspad:17210` + +To expose externally, you can: +1. Create an Ingress resource +2. Use LoadBalancer service type +3. Use `kubectl port-forward` + +Example port-forward: +```bash +kubectl port-forward -n kaspa-node-testnet svc/kaspa-explorer 8080:8080 +``` + +## Resource Requirements + +Default resource allocations: +- **kaspad**: 2-4 CPU, 4-8Gi memory, 500Gi storage +- **PostgreSQL**: 1-2 CPU, 2-10Gi memory, 200Gi storage +- **REST server**: 250m-1 CPU, 512Mi-1Gi memory +- **Indexer**: 250m-1 CPU, 512Mi-1Gi memory +- **Socket server**: 100m-500m CPU, 256Mi-512Mi memory +- **Explorer**: 100m-500m CPU, 256Mi-512Mi memory + +## Customization + +To customize the deployment: + +1. Edit the base configuration in `base/` +2. Add environment-specific patches in `overlays/testnet/` or `overlays/mainnet/` +3. Modify resource requests/limits as needed +4. Update storage sizes in the StatefulSet definitions + +## Scaling + +To change the number of kaspad replicas: + +Edit the `replicas` field in the overlay's `kustomization.yaml`: + +```yaml +replicas: + - name: kaspad + count: 3 # Change to desired number +``` + +Then reapply: +```bash +kubectl apply -k overlays/testnet/ +``` + +## Cleanup + +```bash +# Delete testnet deployment +kubectl delete -k overlays/testnet/ + +# Delete mainnet deployment +kubectl delete -k overlays/mainnet/ + +# Note: PVCs are not automatically deleted. To delete them: +kubectl delete pvc -n kaspa-node-testnet --all +kubectl delete pvc -n kaspa-node-mainnet --all +``` + +## Monitoring + +Recommended monitoring: +- Check kaspad sync status via RPC +- Monitor PostgreSQL connection pool and queries +- Track resource usage (CPU/memory/disk) +- Monitor blockchain height and sync progress + +## Troubleshooting + +### Pod not starting +```bash +kubectl describe pod -n kaspa-node-testnet kaspad-0 +kubectl logs -n kaspa-node-testnet kaspad-0 +``` + +### PVC not binding +```bash +kubectl get pvc -n kaspa-node-testnet +kubectl describe pvc -n kaspa-node-testnet kaspad-data-kaspad-0 +``` + +### Database connection issues +```bash +kubectl logs -n kaspa-node-testnet -l app=kaspa-rest-server +kubectl exec -n kaspa-node-testnet kaspa-db-0 -- psql -U postgres -c "\l" +``` + +## Notes + +- The PostgreSQL shared memory size is configured via environment variables (10GB limit) +- kaspad stores blockchain data in `/app/data/` which is mounted to the PVC +- The setup uses headless services for StatefulSets to provide stable DNS names +- Each environment (testnet/mainnet) runs in its own namespace for isolation diff --git a/dymension/validators/kaspa-full-node/kubernetes/base/configmap.yaml b/dymension/validators/kaspa-full-node/kubernetes/base/configmap.yaml new file mode 100644 index 00000000000..b9fffdaddb4 --- /dev/null +++ b/dymension/validators/kaspa-full-node/kubernetes/base/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kaspa-config +data: + NETWORK_TYPE: "testnet" + NETWORK_SUFFIX: "10" diff --git a/dymension/validators/kaspa-full-node/kubernetes/base/deployments.yaml b/dymension/validators/kaspa-full-node/kubernetes/base/deployments.yaml new file mode 100644 index 00000000000..fc7bc4042d6 --- /dev/null +++ b/dymension/validators/kaspa-full-node/kubernetes/base/deployments.yaml @@ -0,0 +1,138 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kaspa-explorer +spec: + replicas: 1 + selector: + matchLabels: + app: kaspa-explorer + template: + metadata: + labels: + app: kaspa-explorer + spec: + containers: + - name: kaspa-explorer + image: supertypo/kaspa-explorer:latest + ports: + - containerPort: 8080 + name: http + env: + - name: API_URI + value: http://kaspa-rest-server:8000 + - name: API_WS_URI + value: ws://simply-kaspa-socket-server:8001 + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kaspa-rest-server +spec: + replicas: 1 + selector: + matchLabels: + app: kaspa-rest-server + template: + metadata: + labels: + app: kaspa-rest-server + spec: + containers: + - name: kaspa-rest-server + image: kaspanet/kaspa-rest-server:latest + ports: + - containerPort: 8000 + name: http + env: + - name: KASPAD_HOST_1 + value: kaspad:16210 + - name: SQL_URI + value: postgresql+asyncpg://postgres:postgres@kaspa-db:5432/postgres + - name: NETWORK_TYPE + value: testnet + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: simply-kaspa-socket-server +spec: + replicas: 1 + selector: + matchLabels: + app: simply-kaspa-socket-server + template: + metadata: + labels: + app: simply-kaspa-socket-server + spec: + containers: + - name: simply-kaspa-socket-server + image: supertypo/simply-kaspa-socket-server:unstable + ports: + - containerPort: 8000 + name: ws + env: + - name: NETWORK_TYPE + value: testnet + args: + - "-x" + - "20" + - "-s" + - "ws://kaspad-testnet:17210" + - "--network" + - "testnet-10" + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: simply-kaspa-indexer +spec: + replicas: 1 + selector: + matchLabels: + app: simply-kaspa-indexer + template: + metadata: + labels: + app: simply-kaspa-indexer + spec: + containers: + - name: simply-kaspa-indexer + image: supertypo/simply-kaspa-indexer:latest + args: + - "-u" + - "-s" + - "ws://kaspad-testnet:17210" + - "-d" + - "postgresql://postgres:postgres@kaspa-db-testnet:5432/postgres" + - "--network" + - "testnet-10" + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "1" diff --git a/dymension/validators/kaspa-full-node/kubernetes/base/kaspad-statefulset.yaml b/dymension/validators/kaspa-full-node/kubernetes/base/kaspad-statefulset.yaml new file mode 100644 index 00000000000..dc58e5c9790 --- /dev/null +++ b/dymension/validators/kaspa-full-node/kubernetes/base/kaspad-statefulset.yaml @@ -0,0 +1,70 @@ +apiVersion: v1 +kind: Service +metadata: + name: kaspad + labels: + app: kaspad +spec: + ports: + - port: 16210 + name: rpc + - port: 17210 + name: rpc-borsh + clusterIP: None + selector: + app: kaspad +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: kaspad +spec: + serviceName: kaspad + replicas: 1 + selector: + matchLabels: + app: kaspad + template: + metadata: + labels: + app: kaspad + spec: + containers: + - name: kaspad + image: supertypo/rusty-kaspad:latest + ports: + - containerPort: 16210 + name: rpc + - containerPort: 17210 + name: rpc-borsh + volumeMounts: + - name: kaspad-data + mountPath: /app/data + command: + - kaspad + args: + - --yes + - --nologfiles + - --disable-upnp + - --utxoindex + - --rpclisten=0.0.0.0:16210 + - --rpclisten-borsh=0.0.0.0:17210 + - --testnet + - --netsuffix=10 + - --retention-period-days=21 + resources: + requests: + memory: "4Gi" + cpu: "2" + limits: + memory: "8Gi" + cpu: "2" + volumeClaimTemplates: + - metadata: + name: kaspad-data + spec: + accessModes: [ "ReadWriteOnce" ] + storageClassName: standard-rwo + resources: + requests: + storage: 300Gi diff --git a/dymension/validators/kaspa-full-node/kubernetes/base/kustomization.yaml b/dymension/validators/kaspa-full-node/kubernetes/base/kustomization.yaml new file mode 100644 index 00000000000..bd7752da90b --- /dev/null +++ b/dymension/validators/kaspa-full-node/kubernetes/base/kustomization.yaml @@ -0,0 +1,15 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - namespace.yaml + - kaspad-statefulset.yaml + - postgres-statefulset.yaml + - deployments.yaml + - services.yaml + - configmap.yaml + +labels: + - pairs: + project: kaspa-node + managed-by: kustomize diff --git a/dymension/validators/kaspa-full-node/kubernetes/base/namespace.yaml b/dymension/validators/kaspa-full-node/kubernetes/base/namespace.yaml new file mode 100644 index 00000000000..48538e8c5a7 --- /dev/null +++ b/dymension/validators/kaspa-full-node/kubernetes/base/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: kaspa-node diff --git a/dymension/validators/kaspa-full-node/kubernetes/base/postgres-statefulset.yaml b/dymension/validators/kaspa-full-node/kubernetes/base/postgres-statefulset.yaml new file mode 100644 index 00000000000..a85a7d4ce3c --- /dev/null +++ b/dymension/validators/kaspa-full-node/kubernetes/base/postgres-statefulset.yaml @@ -0,0 +1,70 @@ +apiVersion: v1 +kind: Service +metadata: + name: kaspa-db + labels: + app: kaspa-db +spec: + ports: + - port: 5432 + name: postgres + clusterIP: None + selector: + app: kaspa-db +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: kaspa-db +spec: + serviceName: kaspa-db + replicas: 1 + selector: + matchLabels: + app: kaspa-db + template: + metadata: + labels: + app: kaspa-db + spec: + containers: + - name: postgres + image: postgres:16-alpine + ports: + - containerPort: 5432 + name: postgres + env: + - name: POSTGRES_USER + value: postgres + - name: POSTGRES_PASSWORD + value: postgres + - name: POSTGRES_DB + value: postgres + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + - name: dshm + mountPath: /dev/shm + resources: + requests: + memory: "2Gi" + cpu: "1" + limits: + memory: "10Gi" + cpu: "2" + volumes: + - name: dshm + emptyDir: + medium: Memory + sizeLimit: 10Gi + volumeClaimTemplates: + - metadata: + name: postgres-data + spec: + accessModes: [ "ReadWriteOnce" ] + storageClassName: standard-rwo + resources: + requests: + storage: 200Gi diff --git a/dymension/validators/kaspa-full-node/kubernetes/base/services.yaml b/dymension/validators/kaspa-full-node/kubernetes/base/services.yaml new file mode 100644 index 00000000000..a1a9b981d3c --- /dev/null +++ b/dymension/validators/kaspa-full-node/kubernetes/base/services.yaml @@ -0,0 +1,94 @@ +apiVersion: v1 +kind: Service +metadata: + name: kaspa-explorer + labels: + app: kaspa-explorer +spec: + type: ClusterIP + ports: + - port: 8080 + targetPort: 8080 + name: http + selector: + app: kaspa-explorer +--- +apiVersion: v1 +kind: Service +metadata: + name: kaspa-rest-server + labels: + app: kaspa-rest-server +spec: + type: ClusterIP + ports: + - port: 8000 + targetPort: 8000 + name: http + selector: + app: kaspa-rest-server +--- +apiVersion: v1 +kind: Service +metadata: + name: simply-kaspa-socket-server + labels: + app: simply-kaspa-socket-server +spec: + type: ClusterIP + ports: + - port: 8001 + targetPort: 8000 + name: ws + selector: + app: simply-kaspa-socket-server +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: kaspa-ingress + annotations: + external-dns.alpha.kubernetes.io/hostname: explorer.tn.kaspa.rollapp.network,rest.tn.kaspa.rollapp.network,ws.tn.kaspa.rollapp.network,rpc.tn.kaspa.rollapp.network +spec: + ingressClassName: nginx + rules: + - host: explorer.tn.kaspa.rollapp.network + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kaspa-explorer + port: + number: 8080 + - host: rest.tn.kaspa.rollapp.network + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kaspa-rest-server + port: + number: 8000 + - host: ws.tn.kaspa.rollapp.network + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: simply-kaspa-socket-server + port: + number: 8001 + - host: rpc.tn.kaspa.rollapp.network + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kaspad + port: + number: 16210 diff --git a/dymension/validators/kaspa-full-node/kubernetes/overlays/mainnet/deployments-patch.yaml b/dymension/validators/kaspa-full-node/kubernetes/overlays/mainnet/deployments-patch.yaml new file mode 100644 index 00000000000..9b60b2c0b0d --- /dev/null +++ b/dymension/validators/kaspa-full-node/kubernetes/overlays/mainnet/deployments-patch.yaml @@ -0,0 +1,69 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kaspa-explorer +spec: + template: + spec: + containers: + - name: kaspa-explorer + env: + - name: API_URI + value: http://kaspa-rest-server-mainnet:8000 + - name: API_WS_URI + value: ws://simply-kaspa-socket-server-mainnet:8001 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: kaspa-rest-server +spec: + template: + spec: + containers: + - name: kaspa-rest-server + env: + - name: KASPAD_HOST_1 + value: kaspad-mainnet:16110 + - name: SQL_URI + value: postgresql+asyncpg://postgres:postgres@kaspa-db-mainnet:5432/postgres + - name: NETWORK_TYPE + value: mainnet +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: simply-kaspa-socket-server +spec: + template: + spec: + containers: + - name: simply-kaspa-socket-server + env: + - name: NETWORK_TYPE + value: mainnet + args: + - "-x" + - "20" + - "-s" + - "ws://kaspad-mainnet:17110" + - "--network" + - "mainnet" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: simply-kaspa-indexer +spec: + template: + spec: + containers: + - name: simply-kaspa-indexer + args: + - "-u" + - "-s" + - "ws://kaspad-mainnet:17110" + - "-d" + - "postgresql://postgres:postgres@kaspa-db-mainnet:5432/postgres" + - "--network" + - "mainnet" diff --git a/dymension/validators/kaspa-full-node/kubernetes/overlays/mainnet/ingress-patch.yaml b/dymension/validators/kaspa-full-node/kubernetes/overlays/mainnet/ingress-patch.yaml new file mode 100644 index 00000000000..fa746d8f07c --- /dev/null +++ b/dymension/validators/kaspa-full-node/kubernetes/overlays/mainnet/ingress-patch.yaml @@ -0,0 +1,59 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: kaspa-ingress + annotations: + external-dns.alpha.kubernetes.io/hostname: explorer.mn.kaspa.rollapp.network,rest.mn.kaspa.rollapp.network,ws.mn.kaspa.rollapp.network,rpc.mn.kaspa.rollapp.network,wrpc.mn.kaspa.rollapp.network +spec: + ingressClassName: nginx + rules: + - host: explorer.mn.kaspa.rollapp.network + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kaspa-explorer-mainnet + port: + number: 8080 + - host: rest.mn.kaspa.rollapp.network + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kaspa-rest-server-mainnet + port: + number: 8000 + - host: ws.mn.kaspa.rollapp.network + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: simply-kaspa-socket-server-mainnet + port: + number: 8001 + - host: rpc.mn.kaspa.rollapp.network + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kaspad-mainnet + port: + number: 16110 + - host: wrpc.mn.kaspa.rollapp.network + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kaspad-mainnet + port: + number: 17110 diff --git a/dymension/validators/kaspa-full-node/kubernetes/overlays/mainnet/kaspad-patch.yaml b/dymension/validators/kaspa-full-node/kubernetes/overlays/mainnet/kaspad-patch.yaml new file mode 100644 index 00000000000..a6a749b1ebb --- /dev/null +++ b/dymension/validators/kaspa-full-node/kubernetes/overlays/mainnet/kaspad-patch.yaml @@ -0,0 +1,17 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: kaspad +spec: + template: + spec: + containers: + - name: kaspad + args: + - --yes + - --nologfiles + - --disable-upnp + - --utxoindex + - --rpclisten=0.0.0.0:16110 + - --rpclisten-borsh=0.0.0.0:17110 + - --retention-period-days=21 diff --git a/dymension/validators/kaspa-full-node/kubernetes/overlays/mainnet/kustomization.yaml b/dymension/validators/kaspa-full-node/kubernetes/overlays/mainnet/kustomization.yaml new file mode 100644 index 00000000000..009c43a8c84 --- /dev/null +++ b/dymension/validators/kaspa-full-node/kubernetes/overlays/mainnet/kustomization.yaml @@ -0,0 +1,35 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: kaspa-node-mainnet + +resources: + - ../../base + +nameSuffix: -mainnet + +patches: + - path: kaspad-patch.yaml + target: + kind: StatefulSet + name: kaspad + - patch: |- + - op: replace + path: /spec/ports + value: + - port: 16110 + name: rpc + - port: 17110 + name: rpc-borsh + target: + kind: Service + name: kaspad + - path: ingress-patch.yaml + target: + kind: Ingress + name: kaspa-ingress + - path: deployments-patch.yaml + +replicas: + - name: kaspad + count: 2 diff --git a/dymension/validators/kaspa-full-node/kubernetes/overlays/testnet/ingress-patch.yaml b/dymension/validators/kaspa-full-node/kubernetes/overlays/testnet/ingress-patch.yaml new file mode 100644 index 00000000000..64720452ca6 --- /dev/null +++ b/dymension/validators/kaspa-full-node/kubernetes/overlays/testnet/ingress-patch.yaml @@ -0,0 +1,59 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: kaspa-ingress + annotations: + external-dns.alpha.kubernetes.io/hostname: explorer.tn.kaspa.rollapp.network,rest.tn.kaspa.rollapp.network,ws.tn.kaspa.rollapp.network,rpc.tn.kaspa.rollapp.network,wrpc.tn.kaspa.rollapp.network +spec: + ingressClassName: nginx + rules: + - host: explorer.tn.kaspa.rollapp.network + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kaspa-explorer-testnet + port: + number: 8080 + - host: rest.tn.kaspa.rollapp.network + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kaspa-rest-server-testnet + port: + number: 8000 + - host: ws.tn.kaspa.rollapp.network + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: simply-kaspa-socket-server-testnet + port: + number: 8001 + - host: rpc.tn.kaspa.rollapp.network + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kaspad-testnet + port: + number: 16210 + - host: wrpc.tn.kaspa.rollapp.network + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: kaspad-testnet + port: + number: 17210 diff --git a/dymension/validators/kaspa-full-node/kubernetes/overlays/testnet/kaspad-patch.yaml b/dymension/validators/kaspa-full-node/kubernetes/overlays/testnet/kaspad-patch.yaml new file mode 100644 index 00000000000..ce1305a916c --- /dev/null +++ b/dymension/validators/kaspa-full-node/kubernetes/overlays/testnet/kaspad-patch.yaml @@ -0,0 +1,18 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: kaspad +spec: + template: + spec: + containers: + - name: kaspad + args: + - --yes + - --nologfiles + - --disable-upnp + - --utxoindex + - --rpclisten=0.0.0.0:16210 + - --rpclisten-borsh=0.0.0.0:17210 + - --testnet + - --netsuffix=10 diff --git a/dymension/validators/kaspa-full-node/kubernetes/overlays/testnet/kustomization.yaml b/dymension/validators/kaspa-full-node/kubernetes/overlays/testnet/kustomization.yaml new file mode 100644 index 00000000000..a52f99477c2 --- /dev/null +++ b/dymension/validators/kaspa-full-node/kubernetes/overlays/testnet/kustomization.yaml @@ -0,0 +1,19 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: kaspa-node-testnet + +resources: + - ../../base + +nameSuffix: -testnet + +patches: + - path: kaspad-patch.yaml + target: + kind: StatefulSet + name: kaspad + +replicas: + - name: kaspad + count: 1 diff --git a/dymension/validators/kaspa-full-node/kubernetes/overlays/testnet/namespace.yaml b/dymension/validators/kaspa-full-node/kubernetes/overlays/testnet/namespace.yaml new file mode 100644 index 00000000000..1a69d98464a --- /dev/null +++ b/dymension/validators/kaspa-full-node/kubernetes/overlays/testnet/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: kaspa-node-testnet diff --git a/rust/Dockerfile b/rust/Dockerfile index 8aa80990387..dcdf7b5c751 100644 --- a/rust/Dockerfile +++ b/rust/Dockerfile @@ -5,7 +5,7 @@ # -------- Base Image with Tools -------- # Base image containing all necessary build tools and dependencies -FROM rust:1.81.0 AS base +FROM rust:1.86.0 AS base RUN apt-get update && \ apt-get install -y --no-install-recommends musl-tools clang && \ apt-get clean && \ diff --git a/rust/check_merkle_db_integrity.sh b/rust/check_merkle_db_integrity.sh deleted file mode 100644 index 54da6402b52..00000000000 --- a/rust/check_merkle_db_integrity.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/bin/bash - -# Function to extract message_id from checkpoint response -extract_checkpoint_message_id() { - echo "$1" | jq -r '.value.message_id' 2>/dev/null || echo "$1" | grep -o '"value":{[^}]*"message_id":"[^"]*"' | grep -o '"message_id":"[^"]*"' | cut -d'"' -f4 -} - -# Function to extract message_id from merkle insertions response -extract_merkle_message_id() { - echo "$1" | grep -o '"message_id":"[^"]*' | head -1 | cut -d'"' -f4 -} - -# Function to pretty print JSON -pretty_print_json() { - if command -v jq &> /dev/null; then - echo "$1" | jq '.' - else - echo "$1" - fi -} - -# Check if start_index argument is provided -if [ $# -eq 0 ]; then - echo "Usage: $0 " - exit 1 -fi - -start_index=$1 -current_index=$start_index -mismatch_found=false - -echo "Starting comparison from index $start_index..." -echo "===============================================" - -while [ "$mismatch_found" = false ]; do - echo "Checking index $current_index..." - - # Fetch from checkpoint endpoint - checkpoint_url="https://hyperlane-validator-signatures-hyperevm.s3.ap-northeast-2.amazonaws.com/checkpoint_${current_index}_with_id.json" - echo -e "\n🌐 API Call: GET $checkpoint_url" - checkpoint_response=$(curl -s "$checkpoint_url") - echo "📥 Response size: $(echo "$checkpoint_response" | wc -c) bytes" - - # Check if checkpoint request was successful - if [[ "$checkpoint_response" == *"message_id"* ]]; then - # Debug: Print the relevant part of the response - echo "🔍 Debugging checkpoint response:" - echo "$checkpoint_response" - - checkpoint_message_id=$(extract_checkpoint_message_id "$checkpoint_response") - echo "📋 Extracted checkpoint message_id: $checkpoint_message_id" - - # Fetch from merkle insertions endpoint - merkle_url="http://0.0.0.0:9090/merkle_tree_insertions?leaf_index_start=${current_index}&leaf_index_end=$((current_index + 1))" - echo -e "\n🌐 API Call: GET $merkle_url" - merkle_response=$(curl -s "$merkle_url") - echo "📥 Response size: $(echo "$merkle_response" | wc -c) bytes" - - # Debug: Print the relevant part of the response - echo "🔍 Debugging merkle response:" - echo "$merkle_response" | grep -A 1 "message_id" - - # Check if merkle request was successful - if [[ "$merkle_response" == *"message_id"* ]]; then - merkle_message_id=$(extract_merkle_message_id "$merkle_response") - echo "📋 Extracted merkle message_id: $merkle_message_id" - - echo -e "\n📊 Comparison for index $current_index:" - echo " Checkpoint message_id: $checkpoint_message_id" - echo " Merkle message_id: $merkle_message_id" - - # Compare the message IDs - if [ "$checkpoint_message_id" != "$merkle_message_id" ]; then - echo -e "\n⚠️ MISMATCH FOUND at index $current_index:" - echo " Checkpoint: $checkpoint_message_id" - echo " Merkle: $merkle_message_id" - mismatch_found=true - else - echo " ✓ Match" - echo "===============================================" - current_index=$((current_index + 1)) - fi - else - echo -e "\n❌ Error: Failed to parse merkle data for index $current_index" - echo "Raw response:" - pretty_print_json "$merkle_response" - exit 1 - fi - else - echo -e "\n❌ Error: Failed to parse checkpoint data for index $current_index" - echo "Raw response:" - pretty_print_json "$checkpoint_response" - exit 1 - fi -done - -echo -e "\n✅ Comparison complete. First mismatch found at index $current_index." \ No newline at end of file diff --git a/rust/main/.gitignore b/rust/main/.gitignore new file mode 100644 index 00000000000..968fcc55f04 --- /dev/null +++ b/rust/main/.gitignore @@ -0,0 +1 @@ +ARBITRARY_VALUE_FOOBAR \ No newline at end of file diff --git a/rust/main/Cargo.lock b/rust/main/Cargo.lock index d8805580a4b..cd39e53072f 100644 --- a/rust/main/Cargo.lock +++ b/rust/main/Cargo.lock @@ -31,6 +31,18 @@ dependencies = [ "solana-program", ] +[[package]] +name = "accessory" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87537f9ae7cfa78d5b8ebd1a1db25959f5e737126be4d8eb44a5452fc4b63cde" +dependencies = [ + "macroific", + "proc-macro2 1.0.93", + "quote 1.0.37", + "syn 2.0.98", +] + [[package]] name = "account-utils" version = "0.1.0" @@ -77,7 +89,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cipher", "cpufeatures", ] @@ -114,7 +126,8 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", + "getrandom 0.2.15", "once_cell", "version_check", "zerocopy", @@ -171,6 +184,16 @@ dependencies = [ "libc", ] +[[package]] +name = "annotate-snippets" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9b665789884a7e8fb06c84b295e923b03ca51edbb7d08f91a6a50322ecbfe6" +dependencies = [ + "anstyle", + "unicode-width", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -231,9 +254,27 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "argon2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash 0.5.0", +] [[package]] name = "ark-bls12-381" @@ -419,6 +460,39 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote 1.0.37", + "syn 1.0.109", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-compression" version = "0.4.12" @@ -433,6 +507,54 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.3.1", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" +dependencies = [ + "async-lock", + "cfg-if 1.0.0", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.0.7", + "slab", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "async-lock" version = "3.4.0" @@ -463,6 +585,33 @@ dependencies = [ "event-listener 2.5.3", ] +[[package]] +name = "async-std" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24" +dependencies = [ + "async-attributes", + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -485,6 +634,12 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.86" @@ -573,9 +728,9 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "aws-config" -version = "1.1.7" +version = "1.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b96342ea8948ab9bef3e6234ea97fc32e2d8a88d8fb6a084e52267317f94b6b" +checksum = "a5d1c2c88936a73c699225d0bc00684a534166b0cebc2659c3cdf08de8edc64c" dependencies = [ "aws-credential-types", "aws-runtime", @@ -584,7 +739,7 @@ dependencies = [ "aws-sdk-sts", "aws-smithy-async", "aws-smithy-http 0.60.12", - "aws-smithy-json 0.60.7", + "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -593,11 +748,11 @@ dependencies = [ "fastrand", "hex 0.4.3", "http 0.2.12", - "hyper 0.14.30", "ring 0.17.8", "time", "tokio", "tracing", + "url", "zeroize", ] @@ -613,6 +768,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "aws-lc-rs" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879b6c89592deb404ba4dc0ae6b58ffd1795c78991cbb5b8bc441c48a070440d" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "107a4e9d9cab9963e04e84bb8dee0e25f2a987f9a8bad5ed054abd439caa8f8c" +dependencies = [ + "bindgen 0.72.1", + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "aws-runtime" version = "1.5.6" @@ -639,6 +817,29 @@ dependencies = [ "uuid 1.11.0", ] +[[package]] +name = "aws-sdk-kms" +version = "1.65.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5325c5e2badf4148e850017cc56cc205888c6e0b52c9e29d3501ec577005230" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http 0.62.0", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + [[package]] name = "aws-sdk-s3" version = "1.65.0" @@ -652,7 +853,7 @@ dependencies = [ "aws-smithy-checksums", "aws-smithy-eventstream", "aws-smithy-http 0.60.12", - "aws-smithy-json 0.61.3", + "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", @@ -673,22 +874,46 @@ dependencies = [ "url", ] +[[package]] +name = "aws-sdk-secretsmanager" +version = "1.68.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c18fb03c6aad6a5de2a36e44eebc83640eea9a025bf407579f503b5dc4cd0b0" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http 0.62.0", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + [[package]] name = "aws-sdk-sso" -version = "1.50.0" +version = "1.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ca43a4ef210894f93096039ef1d6fa4ad3edfabb3be92b80908b9f2e4b4eab" +checksum = "02d4bdb0e5f80f0689e61c77ab678b2b9304af329616af38aef5b6b967b8e736" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", - "aws-smithy-http 0.60.12", - "aws-smithy-json 0.61.3", + "aws-smithy-http 0.62.0", + "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", "bytes", + "fastrand", "http 0.2.12", "once_cell", "regex-lite", @@ -697,20 +922,21 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.50.0" +version = "1.65.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fea2f3a8bb3bd10932ae7ad59cc59f65f270fc9183a7e91f501dc5efbef7ee" +checksum = "acbbb3ce8da257aedbccdcb1aadafbbb6a5fe9adf445db0e1ea897bdc7e22d08" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", - "aws-smithy-http 0.60.12", - "aws-smithy-json 0.60.7", + "aws-smithy-http 0.62.0", + "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", "aws-types", "bytes", + "fastrand", "http 0.2.12", "once_cell", "regex-lite", @@ -719,21 +945,22 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.50.0" +version = "1.65.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ada54e5f26ac246dc79727def52f7f8ed38915cb47781e2a72213957dc3a7d5" +checksum = "96a78a8f50a1630db757b60f679c8226a8a70ee2ab5f5e6e51dc67f6c61c7cfd" dependencies = [ "aws-credential-types", "aws-runtime", "aws-smithy-async", - "aws-smithy-http 0.60.12", - "aws-smithy-json 0.60.7", + "aws-smithy-http 0.62.0", + "aws-smithy-json", "aws-smithy-query", "aws-smithy-runtime", "aws-smithy-runtime-api", "aws-smithy-types", "aws-smithy-xml", "aws-types", + "fastrand", "http 0.2.12", "once_cell", "regex-lite", @@ -865,24 +1092,23 @@ dependencies = [ "aws-smithy-types", "h2 0.4.7", "http 0.2.12", + "http 1.2.0", "http-body 0.4.6", "hyper 0.14.30", + "hyper 1.6.0", "hyper-rustls 0.24.2", + "hyper-rustls 0.27.6", + "hyper-util", "pin-project-lite", "rustls 0.21.12", + "rustls 0.23.19", + "rustls-native-certs 0.8.1", + "rustls-pki-types", "tokio", + "tower 0.5.2", "tracing", ] -[[package]] -name = "aws-smithy-json" -version = "0.60.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6" -dependencies = [ - "aws-smithy-types", -] - [[package]] name = "aws-smithy-json" version = "0.61.3" @@ -1169,7 +1395,7 @@ checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", - "cfg-if", + "cfg-if 1.0.0", "libc", "miniz_oxide 0.7.4", "object", @@ -1315,6 +1541,26 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.6.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2 1.0.93", + "quote 1.0.37", + "regex", + "rustc-hash 2.1.1", + "shlex", + "syn 2.0.98", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -1370,6 +1616,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "blake2b_simd" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq 0.3.1", +] + [[package]] name = "blake3" version = "1.4.0" @@ -1379,8 +1636,8 @@ dependencies = [ "arrayref", "arrayvec", "cc", - "cfg-if", - "constant_time_eq", + "cfg-if 1.0.0", + "constant_time_eq 0.2.6", "digest 0.10.7", ] @@ -1430,6 +1687,31 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.3.1", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "blst" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + [[package]] name = "bnum" version = "0.10.0" @@ -1441,6 +1723,10 @@ name = "bnum" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e31ea183f6ee62ac8b8a8cf7feddd766317adfb13ff469de57ce033efd6a790" +dependencies = [ + "num-integer", + "num-traits", +] [[package]] name = "borsh" @@ -1668,7 +1954,7 @@ dependencies = [ "cainome-rs", "cainome-rs-macro", "camino", - "clap 4.5.20", + "clap 4.5.41", "clap_complete", "convert_case 0.8.0", "serde", @@ -1800,12 +2086,27 @@ dependencies = [ "thiserror 1.0.63", ] +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 1.0.63", +] + [[package]] name = "cc" -version = "1.2.20" +version = "1.2.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" +checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -1820,6 +2121,21 @@ dependencies = [ "nom", ] +[[package]] +name = "cfb-mode" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330" +dependencies = [ + "cipher", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -1833,8 +2149,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] -name = "chrono" -version = "0.4.38" +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if 1.0.0", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrome-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c631c2cf4b95746cf065f732219ec0f2eb1497cd4c7fe07cb336ddf0d7c503" +dependencies = [ + "js-sys", + "thiserror 1.0.63", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "chrono" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ @@ -1855,6 +2207,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] @@ -1901,9 +2254,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.20" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" dependencies = [ "clap_builder", "clap_derive", @@ -1911,13 +2264,13 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" dependencies = [ "anstream", "anstyle", - "clap_lex 0.7.2", + "clap_lex 0.7.5", "strsim 0.11.1", ] @@ -1927,14 +2280,14 @@ version = "4.5.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bc73de94bc81e52f3bebec71bc4463e9748f7a59166663e32044669577b0e2" dependencies = [ - "clap 4.5.20", + "clap 4.5.41", ] [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ "heck 0.5.0", "proc-macro2 1.0.93", @@ -1953,9 +2306,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "cloudabi" @@ -1966,6 +2319,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "cobs" version = "0.2.3" @@ -2127,6 +2489,94 @@ dependencies = [ "unreachable", ] +[[package]] +name = "cometbft" +version = "0.1.0-alpha.2" +source = "git+https://github.com/hyperlane-xyz/cometbft-rs?tag=2025-08-07#9382da31488dd08063d5c5db854af42b401222fe" +dependencies = [ + "bytes", + "cometbft-proto", + "digest 0.10.7", + "ed25519 2.2.3", + "ed25519-consensus", + "flex-error", + "futures", + "k256 0.13.4", + "num-traits", + "once_cell", + "prost 0.13.4", + "ripemd", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.10.8", + "signature 2.2.0", + "subtle", + "subtle-encoding", + "time", + "zeroize", +] + +[[package]] +name = "cometbft-config" +version = "0.1.0-alpha.2" +source = "git+https://github.com/hyperlane-xyz/cometbft-rs?tag=2025-08-07#9382da31488dd08063d5c5db854af42b401222fe" +dependencies = [ + "cometbft", + "flex-error", + "serde", + "serde_json", + "toml 0.8.19", + "url", +] + +[[package]] +name = "cometbft-proto" +version = "0.1.0-alpha.2" +source = "git+https://github.com/hyperlane-xyz/cometbft-rs?tag=2025-08-07#9382da31488dd08063d5c5db854af42b401222fe" +dependencies = [ + "bytes", + "flex-error", + "prost 0.13.4", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "cometbft-rpc" +version = "0.1.0-alpha.2" +source = "git+https://github.com/hyperlane-xyz/cometbft-rs?tag=2025-08-07#9382da31488dd08063d5c5db854af42b401222fe" +dependencies = [ + "async-trait", + "bytes", + "cometbft", + "cometbft-config", + "cometbft-proto", + "flex-error", + "futures", + "getrandom 0.2.15", + "peg", + "pin-project", + "rand 0.8.5", + "reqwest 0.11.27", + "semver", + "serde", + "serde_bytes", + "serde_json", + "subtle", + "subtle-encoding", + "thiserror 1.0.63", + "time", + "tokio", + "tracing", + "url", + "uuid 1.11.0", + "walkdir", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -2211,7 +2661,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "wasm-bindgen", ] @@ -2237,18 +2687,36 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-sha1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8a42181e0652c2997ae4d217f25b63c5337a52fd2279736e97b832fa0a3cff" + [[package]] name = "constant_time_eq" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "convert_case" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" + [[package]] name = "convert_case" version = "0.6.0" @@ -2295,6 +2763,65 @@ dependencies = [ "url", ] +[[package]] +name = "core" +version = "0.2.0" +dependencies = [ + "bytes", + "clap 4.5.41", + "eyre", + "governor", + "hardcode", + "hex 0.4.3", + "kaspa-addresses", + "kaspa-consensus-client", + "kaspa-consensus-core", + "kaspa-core", + "kaspa-grpc-client", + "kaspa-hashes", + "kaspa-rpc-core", + "kaspa-txscript", + "kaspa-wallet-core", + "kaspa-wallet-keys", + "kaspa-wallet-pskt", + "kaspa-wrpc-client", + "log", + "openapi", + "reqwest 0.12.15", + "reqwest-middleware", + "reqwest-ratelimit", + "reqwest-retry", + "secp256k1 0.29.1", + "serde", + "serde-value", + "serde_json", + "tokio", + "tracing", + "url", + "workflow-core", +] + +[[package]] +name = "core-api-client" +version = "1.0.0" +source = "git+https://github.com/hyperlane-xyz/radix-apis-rust-clients.git?branch=hyperlane#e453efa17ebce976b22561fb32383da3e4b4e99e" +dependencies = [ + "core-api-client-async", +] + +[[package]] +name = "core-api-client-async" +version = "1.0.0" +source = "git+https://github.com/hyperlane-xyz/radix-apis-rust-clients.git?branch=hyperlane#e453efa17ebce976b22561fb32383da3e4b4e99e" +dependencies = [ + "reqwest 0.12.15", + "serde", + "serde_json", + "serde_with", + "url", + "uuid 1.11.0", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -2332,13 +2859,45 @@ dependencies = [ "tonic 0.12.3", ] +[[package]] +name = "cosmos-sdk-proto" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ac39be7373404accccaede7cc1ec942ccef14f0ca18d209967a756bf1dbb1f" +dependencies = [ + "prost 0.13.4", + "tendermint-proto", + "tonic 0.13.1", +] + [[package]] name = "cosmrs" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "210fbe6f98594963b46cc980f126a9ede5db9a3848ca65b71303bebdb01afcd9" dependencies = [ - "cosmos-sdk-proto", + "cosmos-sdk-proto 0.26.1", + "ecdsa 0.16.9", + "eyre", + "k256 0.13.4", + "rand_core 0.6.4", + "serde", + "serde_json", + "signature 2.2.0", + "subtle-encoding", + "tendermint", + "tendermint-rpc", + "thiserror 1.0.63", + "tokio", +] + +[[package]] +name = "cosmrs" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34e74fa7a22930fe0579bef560f2d64b78415d4c47b9dd976c0635136809471d" +dependencies = [ + "cosmos-sdk-proto 0.27.0", "ecdsa 0.16.9", "eyre", "k256 0.13.4", @@ -2566,7 +3125,7 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -2689,6 +3248,37 @@ dependencies = [ "subtle", ] +[[package]] +name = "crypto_box" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16182b4f39a82ec8a6851155cc4c0cda3065bb1db33651726a29e1951de0f009" +dependencies = [ + "aead", + "chacha20", + "crypto_secretbox", + "curve25519-dalek 4.1.3", + "salsa20", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto_secretbox" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" +dependencies = [ + "aead", + "chacha20", + "cipher", + "generic-array 0.14.7", + "poly1305", + "salsa20", + "subtle", + "zeroize", +] + [[package]] name = "ctr" version = "0.9.2" @@ -2727,7 +3317,7 @@ version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "curve25519-dalek-derive", "digest 0.10.7", @@ -2983,7 +3573,21 @@ version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core 0.9.10", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", "hashbrown 0.14.5", "lock_api", "once_cell", @@ -2996,6 +3600,18 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "delegate-display" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98a85201f233142ac819bbf6226e36d0b5e129a47bd325084674261c82d4cd66" +dependencies = [ + "macroific", + "proc-macro2 1.0.93", + "quote 1.0.37", + "syn 2.0.98", +] + [[package]] name = "der" version = "0.5.1" @@ -3084,7 +3700,16 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" dependencies = [ - "derive_builder_macro", + "derive_builder_macro 0.12.0", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro 0.20.2", ] [[package]] @@ -3099,16 +3724,38 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.10", + "proc-macro2 1.0.93", + "quote 1.0.37", + "syn 2.0.98", +] + [[package]] name = "derive_builder_macro" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" dependencies = [ - "derive_builder_core", + "derive_builder_core 0.12.0", "syn 1.0.109", ] +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core 0.20.2", + "syn 2.0.98", +] + [[package]] name = "derive_more" version = "0.99.18" @@ -3143,6 +3790,18 @@ dependencies = [ "unicode-xid 0.2.5", ] +[[package]] +name = "destructure_traitobject" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" + +[[package]] +name = "deunicode" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" + [[package]] name = "dhat" version = "0.3.3" @@ -3208,15 +3867,36 @@ dependencies = [ ] [[package]] -name = "dirs-next" -version = "2.0.0" +name = "dirs" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ - "cfg-if", + "dirs-sys", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if 1.0.0", "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -3280,18 +3960,124 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "downcast-rs" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea8a8b81cacc08888170eef4d13b775126db426d0b348bee9d18c2c1eaf123cf" + [[package]] name = "dtoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" +[[package]] +name = "duct" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ab5718d1224b63252cd0c6f74f6480f9ffeb117438a2e0f5cf6d9a4798929c" +dependencies = [ + "libc", + "once_cell", + "os_pipe", + "shared_child", +] + [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dym-kas-kms" +version = "0.1.0" +dependencies = [ + "aws-config", + "aws-sdk-kms", + "aws-sdk-secretsmanager", + "eyre", + "secp256k1 0.29.1", + "serde_json", + "tokio", + "tracing", +] + +[[package]] +name = "dymension-kaspa" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "axum 0.8.4", + "base64 0.21.7", + "bech32 0.11.0", + "bytes", + "core", + "cosmrs 0.21.0", + "crypto", + "derive-new", + "dym-kas-kms", + "ethers", + "eyre", + "futures", + "hardcode", + "hex 0.4.3", + "http 1.2.0", + "hyper 0.14.30", + "hyper-tls 0.5.0", + "hyperlane-core", + "hyperlane-cosmos", + "hyperlane-cosmos-dymension-rs", + "hyperlane-cosmwasm-interface", + "hyperlane-metric", + "hyperlane-operation-verifier", + "hyperlane-warp-route", + "kaspa-addresses", + "kaspa-bip32", + "kaspa-consensus-client", + "kaspa-consensus-core", + "kaspa-core", + "kaspa-grpc-client", + "kaspa-hashes", + "kaspa-rpc-core", + "kaspa-txscript", + "kaspa-utils-tower", + "kaspa-wallet-core", + "kaspa-wallet-keys", + "kaspa-wallet-pskt", + "kaspa-wrpc-client", + "openapi", + "prometheus", + "reqwest 0.11.27", + "rustls 0.23.19", + "serde", + "serde_json", + "sha2 0.10.8", + "sha256", + "sha3 0.10.8", + "tendermint", + "tendermint-rpc", + "thiserror 1.0.63", + "time", + "tokio", + "tonic 0.12.3", + "tower 0.5.2", + "tower-http 0.6.6", + "tracing", + "tracing-futures", + "url", + "vergen", + "workflow-core", +] + [[package]] name = "dyn-clone" version = "1.0.17" @@ -3394,8 +4180,10 @@ checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek 4.1.3", "ed25519 2.2.3", + "serde", "sha2 0.10.8", "subtle", + "zeroize", ] [[package]] @@ -3512,7 +4300,7 @@ version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -3598,12 +4386,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.9" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3612,7 +4400,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "home", "windows-sys 0.48.0", ] @@ -3772,7 +4560,7 @@ version = "1.0.2" source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2025-05-30#02d03c5f62cc5b34d44e34b0e2bc80c2bca83d6c" dependencies = [ "Inflector", - "cfg-if", + "cfg-if 1.0.0", "dunce", "ethers-core", "eyre", @@ -3811,7 +4599,7 @@ source = "git+https://github.com/hyperlane-xyz/ethers-rs?tag=2025-05-30#02d03c5f dependencies = [ "arrayvec", "bytes", - "cargo_metadata", + "cargo_metadata 0.15.4", "chrono", "convert_case 0.6.0", "elliptic-curve 0.12.3", @@ -3884,7 +4672,7 @@ dependencies = [ "abigen", "async-trait", "derive-new", - "derive_builder", + "derive_builder 0.12.0", "ethers", "ethers-core", "futures", @@ -4008,6 +4796,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "evpkdf" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb9671766b4540458f291944466ca7ce2be8f9c81e510b10b5064a8735998d9" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "eyre" version = "0.6.8" @@ -4024,6 +4821,27 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fancy_constructor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07b19d0e43eae2bfbafe4931b5e79c73fb1a849ca15cd41a761a7b8587f9a1a2" +dependencies = [ + "macroific", + "proc-macro2 1.0.93", + "quote 1.0.37", + "syn 2.0.98", +] + +[[package]] +name = "faster-hex" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" +dependencies = [ + "serde", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -4062,6 +4880,24 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + [[package]] name = "fixed-hash" version = "0.3.2" @@ -4098,6 +4934,21 @@ dependencies = [ "static_assertions 1.1.0", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fixedstr" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7fe1d85100f2405b683f73d7f0c2bdb09bcea9e817c6a0e07755a83c35be8a" +dependencies = [ + "serde", +] + [[package]] name = "flate2" version = "1.0.33" @@ -4186,6 +5037,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -4366,7 +5223,7 @@ dependencies = [ "lazy_static", "p256 0.13.2", "rand 0.8.5", - "secp256k1", + "secp256k1 0.26.0", "serde", "sha2 0.10.8", "zeroize", @@ -4637,9 +5494,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -4675,9 +5532,22 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] [[package]] name = "futures-locks" @@ -4691,9 +5561,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2 1.0.93", "quote 1.0.37", @@ -4702,15 +5572,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" @@ -4720,9 +5590,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -4745,6 +5615,27 @@ dependencies = [ "byteorder", ] +[[package]] +name = "gateway-api-client" +version = "1.0.0" +source = "git+https://github.com/hyperlane-xyz/radix-apis-rust-clients.git?branch=hyperlane#e453efa17ebce976b22561fb32383da3e4b4e99e" +dependencies = [ + "gateway-api-client-async", +] + +[[package]] +name = "gateway-api-client-async" +version = "1.0.0" +source = "git+https://github.com/hyperlane-xyz/radix-apis-rust-clients.git?branch=hyperlane#e453efa17ebce976b22561fb32383da3e4b4e99e" +dependencies = [ + "reqwest 0.12.15", + "serde", + "serde_json", + "serde_with", + "url", + "uuid 1.11.0", +] + [[package]] name = "generic-array" version = "0.12.4" @@ -4782,7 +5673,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", @@ -4795,7 +5686,7 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", @@ -4808,7 +5699,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "libc", "r-efi", @@ -4828,6 +5719,41 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "governor" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbe789d04bf14543f03c4b60cd494148aa79438c8440ae7d81a7778147745c3" +dependencies = [ + "cfg-if 1.0.0", + "dashmap 6.1.0", + "futures-sink", + "futures-timer", + "futures-util", + "getrandom 0.3.2", + "hashbrown 0.15.2", + "nonzero_ext", + "parking_lot 0.12.3", + "portable-atomic", + "quanta", + "rand 0.9.1", + "smallvec", + "spinning_top", + "web-time", +] + [[package]] name = "graphql-parser" version = "0.4.0" @@ -4898,6 +5824,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "hardcode" +version = "0.2.0" +dependencies = [ + "kaspa-addresses", + "kaspa-consensus-core", + "openapi", + "reqwest 0.12.15", + "reqwest-middleware", +] + [[package]] name = "hash32" version = "0.2.1" @@ -4907,6 +5844,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -5018,13 +5964,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" dependencies = [ "atomic-polyfill", - "hash32", + "hash32 0.2.1", "rustc_version", "serde", "spin 0.9.8", "stable_deref_trait", ] +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32 0.3.1", + "stable_deref_trait", +] + [[package]] name = "heapsize" version = "0.4.2" @@ -5061,6 +6017,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.1.0" @@ -5073,6 +6035,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hex-literal" @@ -5080,6 +6045,16 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "hexplay" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da1f4f846e8dcc1b5225caf702924816cabd855e4b46115c334ba09d5254a21" +dependencies = [ + "atty", + "termcolor", +] + [[package]] name = "histogram" version = "0.6.9" @@ -5300,6 +6275,7 @@ dependencies = [ "hyper 1.6.0", "hyper-util", "rustls 0.23.19", + "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", "tokio-rustls 0.26.1", @@ -5346,18 +6322,34 @@ dependencies = [ ] [[package]] -name = "hyper-util" -version = "0.1.10" +name = "hyper-tls" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", - "futures-channel", - "futures-util", - "http 1.2.0", - "http-body 1.0.1", + "http-body-util", "hyper 1.6.0", - "pin-project-lite", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "hyper 1.6.0", + "pin-project-lite", "socket2 0.5.7", "tokio", "tower-service", @@ -5390,9 +6382,11 @@ dependencies = [ "config", "console-subscriber", "convert_case 0.6.0", - "dashmap", + "core", + "dashmap 5.5.3", "derive-new", - "derive_builder", + "derive_builder 0.12.0", + "dymension-kaspa", "ed25519-dalek 1.0.1", "ethers", "ethers-prometheus", @@ -5400,22 +6394,29 @@ dependencies = [ "fuels", "futures", "futures-util", + "hardcode", + "humantime", "hyperlane-core", "hyperlane-cosmos", - "hyperlane-cosmos-native", "hyperlane-ethereum", "hyperlane-fuel", "hyperlane-metric", "hyperlane-operation-verifier", + "hyperlane-radix", "hyperlane-sealevel", "hyperlane-starknet", "hyperlane-test", - "itertools 0.12.1", + "itertools 0.13.0", + "kaspa-consensus-core", + "kaspa-core", + "kaspa-hashes", "maplit", "mockall", "moka", + "openapi", "paste", "prometheus", + "rand 0.8.5", "reqwest 0.11.27", "rocksdb", "rusoto_core", @@ -5448,6 +6449,7 @@ dependencies = [ "async-rwlock", "async-trait", "auto_impl 1.2.0", + "bech32 0.11.0", "bigdecimal", "borsh 0.9.3", "bs58 0.5.1", @@ -5456,6 +6458,7 @@ dependencies = [ "convert_case 0.6.0", "derive-new", "derive_more 0.99.18", + "downcast-rs 2.0.1", "ethers-contract", "ethers-core", "ethers-providers", @@ -5465,7 +6468,7 @@ dependencies = [ "getrandom 0.2.15", "hex 0.4.3", "hyperlane-application", - "itertools 0.12.1", + "itertools 0.13.0", "num 0.4.3", "num-derive 0.4.2", "num-traits", @@ -5493,7 +6496,9 @@ dependencies = [ "async-trait", "base64 0.21.7", "bech32 0.11.0", - "cosmrs", + "cometbft", + "cometbft-rpc", + "cosmrs 0.21.0", "cosmwasm-std 2.1.3", "crypto", "derive-new", @@ -5501,8 +6506,9 @@ dependencies = [ "hex 0.4.3", "http 1.2.0", "hyper 0.14.30", - "hyper-tls", + "hyper-tls 0.5.0", "hyperlane-core", + "hyperlane-cosmos-dymension-rs", "hyperlane-cosmwasm-interface", "hyperlane-metric", "hyperlane-operation-verifier", @@ -5510,47 +6516,7 @@ dependencies = [ "ibc-proto", "injective-protobuf", "injective-std", - "itertools 0.12.1", - "once_cell", - "pin-project", - "protobuf", - "ripemd", - "serde", - "serde_json", - "sha2 0.10.8", - "sha256", - "tendermint", - "tendermint-rpc", - "thiserror 1.0.63", - "time", - "tokio", - "tonic 0.12.3", - "tower 0.5.2", - "tracing", - "tracing-futures", - "url", -] - -[[package]] -name = "hyperlane-cosmos-native" -version = "0.1.0" -dependencies = [ - "async-trait", - "base64 0.21.7", - "bech32 0.11.0", - "cosmrs", - "crypto", - "derive-new", - "futures", - "hex 0.4.3", - "http 1.2.0", - "hyper 0.14.30", - "hyper-tls", - "hyperlane-core", - "hyperlane-cosmos-rs", - "hyperlane-cosmwasm-interface", - "hyperlane-metric", - "itertools 0.12.1", + "itertools 0.13.0", "once_cell", "pin-project", "protobuf", @@ -5559,8 +6525,6 @@ dependencies = [ "serde_json", "sha2 0.10.8", "sha256", - "tendermint", - "tendermint-rpc", "thiserror 1.0.63", "time", "tokio", @@ -5572,12 +6536,11 @@ dependencies = [ ] [[package]] -name = "hyperlane-cosmos-rs" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc98aea32657307552b9e58d53d2af8de83cda7f99ce8ea1daa8884221feba46" +name = "hyperlane-cosmos-dymension-rs" +version = "0.1.4-dymension-v3.2.1" +source = "git+https://github.com/dymensionxyz/hyperlane-cosmos-rs?rev=8fd5d6a#8fd5d6a08a74de2d6e2d81fba00ac51478fd47c3" dependencies = [ - "cosmrs", + "cosmrs 0.21.0", "prost 0.13.4", "serde", "tendermint-proto", @@ -5612,7 +6575,7 @@ version = "0.1.0" dependencies = [ "abigen", "async-trait", - "dashmap", + "dashmap 5.5.3", "derive-new", "ethers", "ethers-contract", @@ -5627,7 +6590,7 @@ dependencies = [ "hyperlane-metric", "hyperlane-operation-verifier", "hyperlane-warp-route", - "itertools 0.12.1", + "itertools 0.13.0", "num 0.4.3", "num-traits", "reqwest 0.11.27", @@ -5663,7 +6626,7 @@ version = "0.1.0" dependencies = [ "async-trait", "derive-new", - "derive_builder", + "derive_builder 0.12.0", "maplit", "prometheus", "serde", @@ -5681,6 +6644,51 @@ dependencies = [ "thiserror 1.0.63", ] +[[package]] +name = "hyperlane-radix" +version = "0.1.0" +dependencies = [ + "async-trait", + "bech32 0.11.0", + "chrono", + "core-api-client", + "crypto", + "derive-new", + "futures", + "gateway-api-client", + "hex 0.4.3", + "http 1.2.0", + "hyper 0.14.30", + "hyper-tls 0.5.0", + "hyperlane-core", + "hyperlane-metric", + "hyperlane-operation-verifier", + "hyperlane-warp-route", + "itertools 0.13.0", + "once_cell", + "radix-common", + "radix-engine-interface", + "radix-transactions", + "regex", + "reqwest 0.12.15", + "ripemd", + "sbor", + "scrypto", + "scrypto-derive", + "serde", + "serde_json", + "sha2 0.10.8", + "sha256", + "thiserror 1.0.63", + "time", + "tokio", + "tokio-metrics", + "tracing", + "tracing-futures", + "tracing-test", + "url", +] + [[package]] name = "hyperlane-sealevel" version = "0.1.0" @@ -5866,7 +6874,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -5886,7 +6894,7 @@ checksum = "9b70f517162e74e2d35875b8b94bf4d1e45f2c69ef3de452dc855944455d33ca" dependencies = [ "base64 0.22.1", "bytes", - "cosmos-sdk-proto", + "cosmos-sdk-proto 0.26.1", "flex-error", "ics23", "informalsystems-pbjson", @@ -6062,6 +7070,23 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "indexed_db_futures" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43315957678a70eb21fb0d2384fe86dde0d6c859a01e24ce127eb65a0143d28c" +dependencies = [ + "accessory", + "cfg-if 1.0.0", + "delegate-display", + "fancy_constructor", + "js-sys", + "uuid 1.11.0", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -6177,12 +7202,35 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "wasm-bindgen", "web-sys", ] +[[package]] +name = "intertrait" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00fc6ef7d878dfcf59d9e556ef1b368d7f55b9da5813ed481a3573eef485a01" +dependencies = [ + "intertrait-macros", + "linkme", + "once_cell", +] + +[[package]] +name = "intertrait-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d56984da2d4c9d6d7de8463892e65a9354f4238f641c246fe99176150e97bb8" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.37", + "syn 1.0.109", + "uuid 0.8.2", +] + [[package]] name = "inventory" version = "0.3.15" @@ -6210,6 +7258,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.12.1" @@ -6219,6 +7276,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -6277,7 +7343,7 @@ version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "ecdsa 0.14.8", "elliptic-curve 0.12.3", "sha2 0.10.8", @@ -6290,7 +7356,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "ecdsa 0.16.9", "elliptic-curve 0.13.8", "once_cell", @@ -6299,107 +7365,917 @@ dependencies = [ ] [[package]] -name = "keccak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +name = "kaspa-addresses" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" dependencies = [ - "cpufeatures", + "borsh 1.5.1", + "js-sys", + "serde", + "smallvec", + "thiserror 1.0.63", + "wasm-bindgen", + "workflow-log", + "workflow-wasm", ] [[package]] -name = "lambdaworks-crypto" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbc2a4da0d9e52ccfe6306801a112e81a8fc0c76aa3e4449fefeda7fef72bb34" +name = "kaspa-bip32" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" dependencies = [ - "lambdaworks-math", + "borsh 1.5.1", + "bs58 0.5.1", + "getrandom 0.2.15", + "hmac 0.12.1", + "js-sys", + "kaspa-consensus-core", + "kaspa-utils", + "once_cell", + "pbkdf2 0.12.2", + "rand 0.8.5", + "rand_core 0.6.4", + "ripemd", + "secp256k1 0.29.1", "serde", "sha2 0.10.8", - "sha3 0.10.8", + "subtle", + "thiserror 1.0.63", + "wasm-bindgen", + "workflow-wasm", + "zeroize", ] [[package]] -name = "lambdaworks-math" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1bd2632acbd9957afc5aeec07ad39f078ae38656654043bf16e046fa2730e23" +name = "kaspa-consensus-client" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" dependencies = [ + "ahash 0.8.11", + "cfg-if 1.0.0", + "faster-hex", + "hex 0.4.3", + "itertools 0.13.0", + "js-sys", + "kaspa-addresses", + "kaspa-consensus-core", + "kaspa-hashes", + "kaspa-math", + "kaspa-txscript", + "kaspa-utils", + "kaspa-wasm-core", + "rand 0.8.5", + "secp256k1 0.29.1", "serde", + "serde-wasm-bindgen", "serde_json", + "thiserror 1.0.63", + "wasm-bindgen", + "workflow-log", + "workflow-wasm", ] [[package]] -name = "lander" -version = "0.1.0" +name = "kaspa-consensus-core" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" dependencies = [ + "arc-swap", "async-trait", - "chrono", - "derive-new", - "ethers", - "ethers-core", - "eyre", + "borsh 1.5.1", + "cfg-if 1.0.0", + "faster-hex", "futures-util", - "hyperlane-base", - "hyperlane-core", - "hyperlane-ethereum", - "hyperlane-sealevel", - "itertools 0.12.1", - "mockall", - "prometheus", + "getrandom 0.2.15", + "itertools 0.13.0", + "js-sys", + "kaspa-addresses", + "kaspa-core", + "kaspa-hashes", + "kaspa-math", + "kaspa-merkle", + "kaspa-muhash", + "kaspa-txscript-errors", + "kaspa-utils", + "rand 0.8.5", + "secp256k1 0.29.1", "serde", + "serde-wasm-bindgen", "serde_json", - "solana-client", - "solana-sdk", - "solana-transaction-status", - "tempfile", + "smallvec", "thiserror 1.0.63", - "tokio", - "tokio-metrics", - "tracing", - "tracing-subscriber", - "tracing-test", - "uuid 1.11.0", + "wasm-bindgen", + "workflow-core", + "workflow-log", + "workflow-serializer", + "workflow-wasm", ] [[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +name = "kaspa-consensus-notify" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" dependencies = [ - "spin 0.9.8", + "async-channel 2.3.1", + "cfg-if 1.0.0", + "derive_more 0.99.18", + "futures", + "kaspa-consensus-core", + "kaspa-core", + "kaspa-hashes", + "kaspa-notify", + "kaspa-utils", + "log", + "paste", + "thiserror 1.0.63", + "triggered", ] [[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "libc" -version = "0.2.172" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +name = "kaspa-consensus-wasm" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" dependencies = [ - "cfg-if", - "winapi", + "cfg-if 1.0.0", + "faster-hex", + "js-sys", + "kaspa-addresses", + "kaspa-consensus-client", + "kaspa-consensus-core", + "kaspa-hashes", + "kaspa-txscript", + "kaspa-utils", + "rand 0.8.5", + "secp256k1 0.29.1", + "serde", + "serde-wasm-bindgen", + "serde_json", + "thiserror 1.0.63", + "wasm-bindgen", + "workflow-log", + "workflow-wasm", ] [[package]] -name = "libloading" +name = "kaspa-core" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "anyhow", + "cfg-if 1.0.0", + "ctrlc", + "futures-util", + "intertrait", + "log", + "log4rs", + "num_cpus", + "thiserror 1.0.63", + "tokio", + "triggered", + "wasm-bindgen", + "workflow-log", +] + +[[package]] +name = "kaspa-grpc-client" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "async-channel 2.3.1", + "async-stream", + "async-trait", + "faster-hex", + "futures", + "futures-util", + "h2 0.4.7", + "itertools 0.13.0", + "kaspa-core", + "kaspa-grpc-core", + "kaspa-notify", + "kaspa-rpc-core", + "kaspa-utils", + "kaspa-utils-tower", + "log", + "parking_lot 0.12.3", + "paste", + "prost 0.13.4", + "rand 0.8.5", + "regex", + "rustls 0.23.19", + "thiserror 1.0.63", + "tokio", + "tokio-stream", + "tonic 0.12.3", + "triggered", +] + +[[package]] +name = "kaspa-grpc-core" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "async-channel 2.3.1", + "async-stream", + "async-trait", + "faster-hex", + "futures", + "h2 0.4.7", + "kaspa-addresses", + "kaspa-consensus-core", + "kaspa-core", + "kaspa-notify", + "kaspa-rpc-core", + "kaspa-utils", + "log", + "paste", + "prost 0.13.4", + "rand 0.8.5", + "regex", + "thiserror 1.0.63", + "tokio", + "tokio-stream", + "tonic 0.12.3", + "tonic-build", + "triggered", + "workflow-core", +] + +[[package]] +name = "kaspa-hashes" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "blake2b_simd", + "borsh 1.5.1", + "cc", + "faster-hex", + "js-sys", + "kaspa-utils", + "keccak", + "once_cell", + "serde", + "sha2 0.10.8", + "wasm-bindgen", + "workflow-wasm", +] + +[[package]] +name = "kaspa-index-core" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "async-channel 2.3.1", + "async-trait", + "derive_more 0.99.18", + "futures", + "kaspa-consensus-core", + "kaspa-hashes", + "kaspa-notify", + "kaspa-utils", + "log", + "paste", + "serde", + "thiserror 1.0.63", + "triggered", +] + +[[package]] +name = "kaspa-math" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "borsh 1.5.1", + "faster-hex", + "js-sys", + "kaspa-utils", + "malachite-base", + "malachite-nz", + "serde", + "serde-wasm-bindgen", + "thiserror 1.0.63", + "wasm-bindgen", + "workflow-core", + "workflow-log", + "workflow-wasm", +] + +[[package]] +name = "kaspa-merkle" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "kaspa-hashes", +] + +[[package]] +name = "kaspa-metrics-core" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "async-trait", + "borsh 1.5.1", + "futures", + "kaspa-core", + "kaspa-rpc-core", + "separator", + "serde", + "thiserror 1.0.63", + "workflow-core", + "workflow-log", +] + +[[package]] +name = "kaspa-mining-errors" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "kaspa-consensus-core", + "thiserror 1.0.63", +] + +[[package]] +name = "kaspa-muhash" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "kaspa-hashes", + "kaspa-math", + "rand_chacha 0.3.1", + "serde", +] + +[[package]] +name = "kaspa-notify" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "async-channel 2.3.1", + "async-trait", + "borsh 1.5.1", + "derive_more 0.99.18", + "futures", + "futures-util", + "indexmap 2.9.0", + "itertools 0.13.0", + "kaspa-addresses", + "kaspa-consensus-core", + "kaspa-core", + "kaspa-hashes", + "kaspa-txscript", + "kaspa-txscript-errors", + "kaspa-utils", + "log", + "parking_lot 0.12.3", + "paste", + "rand 0.8.5", + "serde", + "thiserror 1.0.63", + "triggered", + "workflow-core", + "workflow-log", + "workflow-serializer", +] + +[[package]] +name = "kaspa-rpc-core" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "async-channel 2.3.1", + "async-trait", + "borsh 1.5.1", + "cfg-if 1.0.0", + "derive_more 0.99.18", + "downcast", + "faster-hex", + "hex 0.4.3", + "js-sys", + "kaspa-addresses", + "kaspa-consensus-client", + "kaspa-consensus-core", + "kaspa-consensus-notify", + "kaspa-consensus-wasm", + "kaspa-core", + "kaspa-hashes", + "kaspa-index-core", + "kaspa-math", + "kaspa-mining-errors", + "kaspa-notify", + "kaspa-rpc-macros", + "kaspa-txscript", + "kaspa-utils", + "log", + "paste", + "rand 0.8.5", + "serde", + "serde-wasm-bindgen", + "smallvec", + "thiserror 1.0.63", + "uuid 1.11.0", + "wasm-bindgen", + "workflow-core", + "workflow-serializer", + "workflow-wasm", +] + +[[package]] +name = "kaspa-rpc-macros" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "convert_case 0.6.0", + "proc-macro-error", + "proc-macro2 1.0.93", + "quote 1.0.37", + "regex", + "syn 1.0.109", +] + +[[package]] +name = "kaspa-tools" +version = "0.1.0" +dependencies = [ + "aws-config", + "aws-sdk-kms", + "aws-sdk-secretsmanager", + "aws-smithy-types", + "chrono", + "clap 4.5.41", + "cometbft", + "cometbft-rpc", + "core", + "cosmos-sdk-proto 0.26.1", + "cosmrs 0.22.0", + "dymension-kaspa", + "eyre", + "hardcode", + "hex 0.4.3", + "hyperlane-core", + "hyperlane-cosmos", + "hyperlane-cosmos-dymension-rs", + "hyperlane-metric", + "k256 0.13.4", + "kaspa-addresses", + "kaspa-consensus-core", + "kaspa-core", + "kaspa-hashes", + "kaspa-wallet-core", + "kaspa-wallet-keys", + "rand 0.8.5", + "rand 0.9.1", + "rand_core 0.6.4", + "rand_distr", + "rustls 0.23.19", + "secp256k1 0.29.1", + "serde", + "serde_json", + "thiserror 2.0.12", + "tokio", + "tokio-util", + "tracing", + "tracing-error", + "tracing-futures", + "tracing-subscriber", + "url", + "uuid 1.11.0", + "workflow-core", +] + +[[package]] +name = "kaspa-txscript" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "blake2b_simd", + "borsh 1.5.1", + "cfg-if 1.0.0", + "hexplay", + "indexmap 2.9.0", + "itertools 0.13.0", + "kaspa-addresses", + "kaspa-consensus-core", + "kaspa-hashes", + "kaspa-txscript-errors", + "kaspa-utils", + "kaspa-wasm-core", + "log", + "parking_lot 0.12.3", + "rand 0.8.5", + "secp256k1 0.29.1", + "serde", + "serde-wasm-bindgen", + "serde_json", + "sha2 0.10.8", + "smallvec", + "thiserror 1.0.63", + "wasm-bindgen", + "workflow-wasm", +] + +[[package]] +name = "kaspa-txscript-errors" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "secp256k1 0.29.1", + "thiserror 1.0.63", +] + +[[package]] +name = "kaspa-utils" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "arc-swap", + "async-channel 2.3.1", + "borsh 1.5.1", + "cfg-if 1.0.0", + "duct", + "event-listener 2.5.3", + "faster-hex", + "ipnet", + "itertools 0.13.0", + "log", + "mac_address", + "num_cpus", + "once_cell", + "parking_lot 0.12.3", + "rlimit", + "serde", + "sha2 0.10.8", + "smallvec", + "sysinfo", + "thiserror 1.0.63", + "triggered", + "uuid 1.11.0", + "wasm-bindgen", +] + +[[package]] +name = "kaspa-utils-tower" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "bytes", + "cfg-if 1.0.0", + "futures", + "http-body 1.0.1", + "http-body-util", + "log", + "pin-project-lite", + "tokio", + "tower 0.5.2", + "tower-http 0.5.2", +] + +[[package]] +name = "kaspa-wallet-core" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "aes", + "ahash 0.8.11", + "argon2", + "async-channel 2.3.1", + "async-std", + "async-trait", + "base64 0.22.1", + "borsh 1.5.1", + "cfb-mode", + "cfg-if 1.0.0", + "chacha20poly1305", + "convert_case 0.6.0", + "crypto_box", + "dashmap 6.1.0", + "derivative", + "downcast", + "evpkdf", + "faster-hex", + "fixedstr", + "futures", + "heapless 0.8.0", + "hmac 0.12.1", + "home", + "indexed_db_futures", + "itertools 0.13.0", + "js-sys", + "kaspa-addresses", + "kaspa-bip32", + "kaspa-consensus-client", + "kaspa-consensus-core", + "kaspa-consensus-wasm", + "kaspa-core", + "kaspa-hashes", + "kaspa-metrics-core", + "kaspa-notify", + "kaspa-rpc-core", + "kaspa-txscript", + "kaspa-txscript-errors", + "kaspa-utils", + "kaspa-wallet-keys", + "kaspa-wallet-macros", + "kaspa-wallet-pskt", + "kaspa-wasm-core", + "kaspa-wrpc-client", + "kaspa-wrpc-wasm", + "md-5 0.10.6", + "pad", + "pbkdf2 0.12.2", + "rand 0.8.5", + "regex", + "ripemd", + "secp256k1 0.29.1", + "separator", + "serde", + "serde-wasm-bindgen", + "serde_json", + "sha1", + "sha2 0.10.8", + "slugify-rs", + "sorted-insert", + "thiserror 1.0.63", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "workflow-core", + "workflow-log", + "workflow-node", + "workflow-rpc", + "workflow-store", + "workflow-wasm", + "xxhash-rust", + "zeroize", +] + +[[package]] +name = "kaspa-wallet-keys" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "async-trait", + "borsh 1.5.1", + "downcast", + "faster-hex", + "hmac 0.12.1", + "js-sys", + "kaspa-addresses", + "kaspa-bip32", + "kaspa-consensus-core", + "kaspa-txscript", + "kaspa-txscript-errors", + "kaspa-utils", + "kaspa-wasm-core", + "rand 0.8.5", + "ripemd", + "secp256k1 0.29.1", + "serde", + "serde-wasm-bindgen", + "serde_json", + "sha2 0.10.8", + "thiserror 1.0.63", + "wasm-bindgen", + "wasm-bindgen-futures", + "workflow-core", + "workflow-wasm", + "zeroize", +] + +[[package]] +name = "kaspa-wallet-macros" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "convert_case 0.5.0", + "proc-macro-error", + "proc-macro2 1.0.93", + "quote 1.0.37", + "regex", + "syn 1.0.109", + "xxhash-rust", +] + +[[package]] +name = "kaspa-wallet-pskt" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "bincode", + "derive_builder 0.20.2", + "futures", + "hex 0.4.3", + "js-sys", + "kaspa-addresses", + "kaspa-bip32", + "kaspa-consensus-client", + "kaspa-consensus-core", + "kaspa-txscript", + "kaspa-txscript-errors", + "kaspa-utils", + "secp256k1 0.29.1", + "separator", + "serde", + "serde-value", + "serde-wasm-bindgen", + "serde_json", + "serde_repr", + "thiserror 1.0.63", + "wasm-bindgen", + "workflow-wasm", +] + +[[package]] +name = "kaspa-wasm-core" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "faster-hex", + "hexplay", + "js-sys", + "wasm-bindgen", + "workflow-wasm", +] + +[[package]] +name = "kaspa-wrpc-client" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "async-std", + "async-trait", + "borsh 1.5.1", + "cfg-if 1.0.0", + "futures", + "js-sys", + "kaspa-addresses", + "kaspa-consensus-core", + "kaspa-consensus-wasm", + "kaspa-notify", + "kaspa-rpc-core", + "kaspa-rpc-macros", + "paste", + "rand 0.8.5", + "regex", + "rustls 0.23.19", + "serde", + "serde-wasm-bindgen", + "serde_json", + "thiserror 1.0.63", + "toml 0.8.19", + "wasm-bindgen", + "wasm-bindgen-futures", + "workflow-core", + "workflow-dom", + "workflow-http", + "workflow-log", + "workflow-rpc", + "workflow-serializer", + "workflow-wasm", +] + +[[package]] +name = "kaspa-wrpc-wasm" +version = "1.0.1" +source = "git+https://github.com/kaspanet/rusty-kaspa?rev=9ff5d0f#9ff5d0fb5059451107957972235ff2a4e6390808" +dependencies = [ + "ahash 0.8.11", + "async-std", + "cfg-if 1.0.0", + "futures", + "js-sys", + "kaspa-addresses", + "kaspa-consensus-client", + "kaspa-consensus-core", + "kaspa-consensus-wasm", + "kaspa-notify", + "kaspa-rpc-core", + "kaspa-rpc-macros", + "kaspa-wasm-core", + "kaspa-wrpc-client", + "ring 0.17.8", + "serde", + "serde-wasm-bindgen", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", + "workflow-core", + "workflow-log", + "workflow-rpc", + "workflow-wasm", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lambdaworks-crypto" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc2a4da0d9e52ccfe6306801a112e81a8fc0c76aa3e4449fefeda7fef72bb34" +dependencies = [ + "lambdaworks-math", + "serde", + "sha2 0.10.8", + "sha3 0.10.8", +] + +[[package]] +name = "lambdaworks-math" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1bd2632acbd9957afc5aeec07ad39f078ae38656654043bf16e046fa2730e23" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "lander" +version = "0.1.0" +dependencies = [ + "async-trait", + "chrono", + "derive-new", + "ethers", + "ethers-core", + "eyre", + "futures-util", + "gateway-api-client", + "hyperlane-base", + "hyperlane-core", + "hyperlane-ethereum", + "hyperlane-radix", + "hyperlane-sealevel", + "itertools 0.13.0", + "mockall", + "prometheus", + "serde", + "serde_json", + "solana-client", + "solana-sdk", + "solana-transaction-status", + "tempfile", + "thiserror 1.0.63", + "tokio", + "tokio-metrics", + "tracing", + "tracing-subscriber", + "tracing-test", + "uuid 1.11.0", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin 0.9.8", +] + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if 1.0.0", + "winapi", +] + +[[package]] +name = "libloading" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "windows-targets 0.52.6", ] @@ -6417,6 +8293,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", + "redox_syscall 0.5.3", ] [[package]] @@ -6425,7 +8302,7 @@ version = "0.11.0+8.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3386f101bcb4bd252d8e9d2fb41ec3b0862a15a62b478c355b2982efa469e3e" dependencies = [ - "bindgen", + "bindgen 0.65.1", "bzip2-sys", "cc", "glob", @@ -6497,18 +8374,38 @@ dependencies = [ name = "libz-sys" version = "1.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" +checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linkme" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd4ad156b9934dc21cad96fd17278a7cb6f30a5657a9d976cd7b71d6d49c02c" dependencies = [ - "cc", - "pkg-config", - "vcpkg", + "linkme-impl", ] [[package]] -name = "linked-hash-map" -version = "0.5.6" +name = "linkme-impl" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +checksum = "73fd9dc7072de7168cbdaba9125e8f742cd3a965aa12bde994b4611a174488d8" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.37", + "syn 1.0.109", +] [[package]] name = "linux-raw-sys" @@ -6516,6 +8413,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + [[package]] name = "lock_api" version = "0.4.12" @@ -6531,6 +8434,45 @@ name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +dependencies = [ + "serde", + "value-bag", +] + +[[package]] +name = "log-mdc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" + +[[package]] +name = "log4rs" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0816135ae15bd0391cf284eab37e6e3ee0a6ee63d2ceeb659862bd8d0a984ca6" +dependencies = [ + "anyhow", + "arc-swap", + "chrono", + "derivative", + "flate2", + "fnv", + "humantime", + "libc", + "log", + "log-mdc", + "once_cell", + "parking_lot 0.12.3", + "rand 0.8.5", + "serde", + "serde-value", + "serde_json", + "serde_yaml 0.9.34+deprecated", + "thiserror 1.0.63", + "thread-id", + "typemap-ors", + "winapi", +] [[package]] name = "lru" @@ -6557,6 +8499,16 @@ dependencies = [ "libc", ] +[[package]] +name = "mac_address" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" +dependencies = [ + "nix 0.29.0", + "winapi", +] + [[package]] name = "macro_rules_attribute" version = "0.2.0" @@ -6573,6 +8525,85 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dd856d451cc0da70e2ef2ce95a18e39a93b7558bedf10201ad28503f918568" +[[package]] +name = "macroific" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05c00ac596022625d01047c421a0d97d7f09a18e429187b341c201cb631b9dd" +dependencies = [ + "macroific_attr_parse", + "macroific_core", + "macroific_macro", +] + +[[package]] +name = "macroific_attr_parse" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd94d5da95b30ae6e10621ad02340909346ad91661f3f8c0f2b62345e46a2f67" +dependencies = [ + "cfg-if 1.0.0", + "proc-macro2 1.0.93", + "quote 1.0.37", + "syn 2.0.98", +] + +[[package]] +name = "macroific_core" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13198c120864097a565ccb3ff947672d969932b7975ebd4085732c9f09435e55" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.37", + "syn 2.0.98", +] + +[[package]] +name = "macroific_macro" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c9853143cbed7f1e41dc39fee95f9b361bec65c8dc2a01bf609be01b61f5ae" +dependencies = [ + "macroific_attr_parse", + "macroific_core", + "proc-macro2 1.0.93", + "quote 1.0.37", + "syn 2.0.98", +] + +[[package]] +name = "malachite-base" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea0ed76adf7defc1a92240b5c36d5368cfe9251640dcce5bd2d0b7c1fd87aeb" +dependencies = [ + "hashbrown 0.14.5", + "itertools 0.11.0", + "libm", + "ryu", +] + +[[package]] +name = "malachite-nz" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34a79feebb2bc9aa7762047c8e5495269a367da6b5a90a99882a0aeeac1841f7" +dependencies = [ + "itertools 0.11.0", + "libm", + "malachite-base", +] + +[[package]] +name = "manual_future" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943968aefb9b0fdf36cccc03f6cd9d6698b23574ab49eccc185ae6c5cb6ad43e" +dependencies = [ + "futures", +] + [[package]] name = "maplit" version = "1.0.2" @@ -6617,7 +8648,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "digest 0.10.7", ] @@ -6645,6 +8676,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "merlin" version = "3.0.0" @@ -6734,7 +8774,7 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c84490118f2ee2d74570d114f3d0493cbf02790df303d2707606c3e14e07c96" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "downcast", "fragile", "lazy_static", @@ -6749,7 +8789,7 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "proc-macro2 1.0.93", "quote 1.0.37", "syn 1.0.109", @@ -6797,6 +8837,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + [[package]] name = "multisig-ism" version = "0.1.0" @@ -6809,6 +8855,15 @@ dependencies = [ "thiserror 1.0.63", ] +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "native-tls" version = "0.2.12" @@ -6833,9 +8888,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ "bitflags 1.3.2", - "cfg-if", + "cfg-if 1.0.0", "libc", - "memoffset", + "memoffset 0.6.5", ] [[package]] @@ -6845,7 +8900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", - "cfg-if", + "cfg-if 1.0.0", "libc", ] @@ -6856,9 +8911,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.6.0", - "cfg-if", + "cfg-if 1.0.0", "cfg_aliases", "libc", + "memoffset 0.9.1", +] + +[[package]] +name = "node-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d75eee6e8b159228449706773f9f2af60720250308124e9c3d38bd8070844a" +dependencies = [ + "cfg-if 0.1.10", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -6871,12 +8940,27 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + [[package]] name = "normalize-line-endings" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -7205,6 +9289,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "openapi" +version = "0.9.9" +dependencies = [ + "reqwest 0.12.15", + "reqwest-middleware", + "serde", + "serde_json", + "serde_repr", + "url", +] + [[package]] name = "openssl" version = "0.10.66" @@ -7212,7 +9308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags 2.6.0", - "cfg-if", + "cfg-if 1.0.0", "foreign-types", "libc", "once_cell", @@ -7249,6 +9345,21 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-float" version = "4.6.0" @@ -7268,6 +9379,16 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "os_pipe" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "os_str_bytes" version = "6.6.1" @@ -7339,6 +9460,15 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "pad" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" +dependencies = [ + "unicode-width", +] + [[package]] name = "parity-scale-codec" version = "3.6.12" @@ -7398,7 +9528,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "instant", "libc", "redox_syscall 0.2.16", @@ -7412,13 +9542,33 @@ version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "redox_syscall 0.5.3", "smallvec", "windows-targets 0.52.6", ] +[[package]] +name = "parse-variants" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbdc211954226807e86041c663407640ff205e514bec8b25731653fd1d3bea82" +dependencies = [ + "parse-variants-derive", +] + +[[package]] +name = "parse-variants-derive" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27b3870ede76ad9fc5cf19aa770877ad429a8134168a55183bc588acc648ab2" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.37", + "syn 2.0.98", +] + [[package]] name = "password-hash" version = "0.4.2" @@ -7430,6 +9580,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" @@ -7459,7 +9620,7 @@ checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ "digest 0.10.7", "hmac 0.12.1", - "password-hash", + "password-hash 0.4.2", "sha2 0.10.8", ] @@ -7584,6 +9745,16 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.9.0", +] + [[package]] name = "pgvector" version = "0.4.0" @@ -7635,6 +9806,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkcs1" version = "0.7.5" @@ -7683,18 +9865,50 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "polling" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" +dependencies = [ + "cfg-if 1.0.0", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.0.7", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug 0.3.1", + "universal-hash", +] + [[package]] name = "polyval" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "opaque-debug 0.3.1", "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + [[package]] name = "portpicker" version = "0.1.1" @@ -7713,7 +9927,7 @@ dependencies = [ "cobs", "embedded-io 0.4.0", "embedded-io 0.6.1", - "heapless", + "heapless 0.7.17", "serde", ] @@ -7906,7 +10120,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "fnv", "lazy_static", "memchr", @@ -7952,10 +10166,30 @@ dependencies = [ name = "prost" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec" +checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec" +dependencies = [ + "bytes", + "prost-derive 0.13.4", +] + +[[package]] +name = "prost-build" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f3e5beed80eb580c68e2c600937ac2c4eedabdfd5ef1e5b7ea4f3fba84497b" dependencies = [ - "bytes", - "prost-derive 0.13.4", + "heck 0.5.0", + "itertools 0.13.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost 0.13.4", + "prost-types 0.13.4", + "regex", + "syn 2.0.98", + "tempfile", ] [[package]] @@ -7978,7 +10212,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.13.0", "proc-macro2 1.0.93", "quote 1.0.37", "syn 2.0.98", @@ -8234,6 +10468,126 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "radix-blueprint-schema-init" +version = "1.3.0" +source = "git+https://github.com/hyperlane-xyz/radixdlt-scrypto.git?branch=hyperlane#0ff82f3ba44cd848d178b5b3ec780ea43ea03bba" +dependencies = [ + "bitflags 1.3.2", + "radix-common", + "sbor", + "serde", +] + +[[package]] +name = "radix-common" +version = "1.3.0" +source = "git+https://github.com/hyperlane-xyz/radixdlt-scrypto.git?branch=hyperlane#0ff82f3ba44cd848d178b5b3ec780ea43ea03bba" +dependencies = [ + "bech32 0.9.1", + "blake2", + "blst", + "bnum 0.11.0", + "ed25519-dalek 2.1.1", + "hex 0.4.3", + "lazy_static", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "paste", + "radix-rust", + "radix-sbor-derive", + "sbor", + "secp256k1 0.28.2", + "serde", + "sha3 0.10.8", + "strum 0.24.1", + "zeroize", +] + +[[package]] +name = "radix-common-derive" +version = "1.3.0" +source = "git+https://github.com/hyperlane-xyz/radixdlt-scrypto.git?branch=hyperlane#0ff82f3ba44cd848d178b5b3ec780ea43ea03bba" +dependencies = [ + "paste", + "proc-macro2 1.0.93", + "quote 1.0.37", + "radix-common", + "syn 1.0.109", +] + +[[package]] +name = "radix-engine-interface" +version = "1.3.0" +source = "git+https://github.com/hyperlane-xyz/radixdlt-scrypto.git?branch=hyperlane#0ff82f3ba44cd848d178b5b3ec780ea43ea03bba" +dependencies = [ + "bitflags 1.3.2", + "const-sha1", + "hex 0.4.3", + "lazy_static", + "paste", + "radix-blueprint-schema-init", + "radix-common", + "radix-common-derive", + "radix-rust", + "regex", + "sbor", + "serde", + "serde_json", + "strum 0.24.1", +] + +[[package]] +name = "radix-rust" +version = "1.3.0" +source = "git+https://github.com/hyperlane-xyz/radixdlt-scrypto.git?branch=hyperlane#0ff82f3ba44cd848d178b5b3ec780ea43ea03bba" +dependencies = [ + "indexmap 2.9.0", + "serde", +] + +[[package]] +name = "radix-sbor-derive" +version = "1.3.0" +source = "git+https://github.com/hyperlane-xyz/radixdlt-scrypto.git?branch=hyperlane#0ff82f3ba44cd848d178b5b3ec780ea43ea03bba" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.37", + "sbor-derive-common", + "syn 1.0.109", +] + +[[package]] +name = "radix-substate-store-interface" +version = "1.3.0" +source = "git+https://github.com/hyperlane-xyz/radixdlt-scrypto.git?branch=hyperlane#0ff82f3ba44cd848d178b5b3ec780ea43ea03bba" +dependencies = [ + "hex 0.4.3", + "itertools 0.10.5", + "radix-common", + "radix-rust", + "sbor", +] + +[[package]] +name = "radix-transactions" +version = "1.3.0" +source = "git+https://github.com/hyperlane-xyz/radixdlt-scrypto.git?branch=hyperlane#0ff82f3ba44cd848d178b5b3ec780ea43ea03bba" +dependencies = [ + "annotate-snippets", + "bech32 0.9.1", + "hex 0.4.3", + "lazy_static", + "paste", + "radix-common", + "radix-engine-interface", + "radix-rust", + "radix-substate-store-interface", + "sbor", + "strum 0.24.1", +] + [[package]] name = "rand" version = "0.5.6" @@ -8353,6 +10707,16 @@ dependencies = [ "getrandom 0.3.2", ] +[[package]] +name = "rand_distr" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" +dependencies = [ + "num-traits", + "rand 0.9.1", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -8511,6 +10875,7 @@ dependencies = [ "derive-new", "derive_more 0.99.18", "dhat", + "dymension-kaspa", "ethers", "ethers-contract", "ethers-prometheus", @@ -8520,11 +10885,12 @@ dependencies = [ "http-body-util", "hyperlane-base", "hyperlane-core", + "hyperlane-cosmos", "hyperlane-ethereum", "hyperlane-metric", "hyperlane-operation-verifier", "hyperlane-test", - "itertools 0.12.1", + "itertools 0.13.0", "lander", "maplit", "mockall", @@ -8535,6 +10901,7 @@ dependencies = [ "rand 0.8.5", "regex", "reqwest 0.11.27", + "rustls 0.23.19", "serde", "serde_json", "sha3 0.10.8", @@ -8545,6 +10912,7 @@ dependencies = [ "tokio-metrics", "tokio-test", "tower 0.5.2", + "tower-http 0.6.6", "tracing", "tracing-futures", "tracing-subscriber", @@ -8581,7 +10949,7 @@ dependencies = [ "http-body 0.4.6", "hyper 0.14.30", "hyper-rustls 0.24.2", - "hyper-tls", + "hyper-tls 0.5.0", "ipnet", "js-sys", "log", @@ -8597,7 +10965,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper 0.1.2", - "system-configuration", + "system-configuration 0.5.1", "tokio", "tokio-native-tls", "tokio-rustls 0.24.1", @@ -8619,18 +10987,23 @@ checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", "futures-core", "futures-util", + "h2 0.4.7", "http 1.2.0", "http-body 1.0.1", "http-body-util", "hyper 1.6.0", "hyper-rustls 0.27.6", + "hyper-tls 0.6.0", "hyper-util", "ipnet", "js-sys", "log", "mime", + "mime_guess", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -8642,7 +11015,9 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper 1.0.2", + "system-configuration 0.6.1", "tokio", + "tokio-native-tls", "tokio-rustls 0.26.1", "tower 0.5.2", "tower-service", @@ -8654,6 +11029,64 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "reqwest-middleware" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57f17d28a6e6acfe1733fe24bcd30774d13bffa4b8a22535b4c8c98423088d4e" +dependencies = [ + "anyhow", + "async-trait", + "http 1.2.0", + "reqwest 0.12.15", + "serde", + "thiserror 1.0.63", + "tower-service", +] + +[[package]] +name = "reqwest-ratelimit" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8fff0d8036f23dcad6c27605ca3baa8ae3867438d0a8b34072f40f6c8bf628" +dependencies = [ + "async-trait", + "http 1.2.0", + "reqwest 0.12.15", + "reqwest-middleware", +] + +[[package]] +name = "reqwest-retry" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c73e4195a6bfbcb174b790d9b3407ab90646976c55de58a6515da25d851178" +dependencies = [ + "anyhow", + "async-trait", + "futures", + "getrandom 0.2.15", + "http 1.2.0", + "hyper 1.6.0", + "parking_lot 0.11.2", + "reqwest 0.12.15", + "reqwest-middleware", + "retry-policies", + "thiserror 1.0.63", + "tokio", + "tracing", + "wasm-timer", +] + +[[package]] +name = "retry-policies" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5875471e6cab2871bc150ecb8c727db5113c9338cc3354dc5ee3425b6aa40a1c" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "rfc6979" version = "0.3.1" @@ -8697,7 +11130,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", - "cfg-if", + "cfg-if 1.0.0", "getrandom 0.2.15", "libc", "spin 0.9.8", @@ -8743,6 +11176,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rlimit" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7043b63bd0cd1aaa628e476b80e6d4023a3b50eb32789f2728908107bd0c793a" +dependencies = [ + "libc", +] + [[package]] name = "rlp" version = "0.4.6" @@ -8843,7 +11285,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f168d99749d307be9de54d23fd226628d99768225ef08f6ffb52e0182a27746" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "glob", "proc-macro-crate 3.2.0", "proc-macro2 1.0.93", @@ -8870,7 +11312,6 @@ dependencies = [ "hyperlane-base", "hyperlane-core", "hyperlane-cosmos", - "hyperlane-cosmos-native", "hyperlane-cosmwasm-interface", "hyperlane-starknet", "jobserver", @@ -8908,7 +11349,7 @@ dependencies = [ "futures", "http 0.2.12", "hyper 0.14.30", - "hyper-tls", + "hyper-tls 0.5.0", "lazy_static", "log", "rusoto_credential", @@ -8999,7 +11440,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "ordered-multimap", ] @@ -9070,10 +11511,23 @@ dependencies = [ "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", +] + [[package]] name = "rustls" version = "0.20.9" @@ -9104,6 +11558,7 @@ version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring 0.17.8", @@ -9189,6 +11644,7 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ + "aws-lc-rs", "ring 0.17.8", "rustls-pki-types", "untrusted 0.9.0", @@ -9224,13 +11680,50 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "sbor" +version = "1.3.0" +source = "git+https://github.com/hyperlane-xyz/radixdlt-scrypto.git?branch=hyperlane#0ff82f3ba44cd848d178b5b3ec780ea43ea03bba" +dependencies = [ + "const-sha1", + "hex 0.4.3", + "lazy_static", + "paste", + "radix-rust", + "sbor-derive", + "serde", +] + +[[package]] +name = "sbor-derive" +version = "1.3.0" +source = "git+https://github.com/hyperlane-xyz/radixdlt-scrypto.git?branch=hyperlane#0ff82f3ba44cd848d178b5b3ec780ea43ea03bba" +dependencies = [ + "proc-macro2 1.0.93", + "sbor-derive-common", + "syn 1.0.109", +] + +[[package]] +name = "sbor-derive-common" +version = "1.3.0" +source = "git+https://github.com/hyperlane-xyz/radixdlt-scrypto.git?branch=hyperlane#0ff82f3ba44cd848d178b5b3ec780ea43ea03bba" +dependencies = [ + "const-sha1", + "indexmap 2.9.0", + "itertools 0.10.5", + "proc-macro2 1.0.93", + "quote 1.0.37", + "syn 1.0.109", +] + [[package]] name = "scale-info" version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "derive_more 0.99.18", "parity-scale-codec", "scale-info-derive", @@ -9335,7 +11828,7 @@ dependencies = [ "hyperlane-core", "hyperlane-ethereum", "hyperlane-test", - "itertools 0.12.1", + "itertools 0.13.0", "migration", "num-bigint 0.4.6", "num-traits", @@ -9365,6 +11858,43 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "scrypto" +version = "1.3.0" +source = "git+https://github.com/hyperlane-xyz/radixdlt-scrypto.git?branch=hyperlane#0ff82f3ba44cd848d178b5b3ec780ea43ea03bba" +dependencies = [ + "bech32 0.9.1", + "const-sha1", + "hex 0.4.3", + "num-bigint 0.4.6", + "num-traits", + "paste", + "radix-blueprint-schema-init", + "radix-common", + "radix-engine-interface", + "radix-rust", + "sbor", + "scrypto-derive", + "serde", + "strum 0.24.1", +] + +[[package]] +name = "scrypto-derive" +version = "1.3.0" +source = "git+https://github.com/hyperlane-xyz/radixdlt-scrypto.git?branch=hyperlane#0ff82f3ba44cd848d178b5b3ec780ea43ea03bba" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.37", + "radix-blueprint-schema-init", + "radix-common", + "regex", + "sbor", + "serde", + "serde_json", + "syn 1.0.109", +] + [[package]] name = "sct" version = "0.7.1" @@ -9424,7 +11954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f3d39b35e43a05998228f6c4abaa50b7fcaf001dea94fbdc04188a8be82a016" dependencies = [ "chrono", - "clap 4.5.20", + "clap 4.5.41", "dotenvy", "glob", "regex", @@ -9455,7 +11985,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae8b2af58a56c8f850e8d8a18a462255eb36a571f085f3405f6b83d9cf23e0f" dependencies = [ "async-trait", - "clap 4.5.20", + "clap 4.5.41", "dotenvy", "sea-orm", "sea-orm-cli", @@ -9473,7 +12003,7 @@ dependencies = [ "bigdecimal", "chrono", "inherent", - "ordered-float", + "ordered-float 4.6.0", "rust_decimal", "sea-query-derive", "serde_json", @@ -9575,14 +12105,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4124a35fe33ae14259c490fd70fa199a32b9ce9502f2ee6bc4f81ec06fa65894" dependencies = [ "rand 0.8.5", - "secp256k1-sys", + "secp256k1-sys 0.8.1", +] + +[[package]] +name = "secp256k1" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +dependencies = [ + "secp256k1-sys 0.9.2", +] + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "rand 0.8.5", + "secp256k1-sys 0.10.1", + "serde", ] [[package]] name = "secp256k1-sys" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" dependencies = [ "cc", ] @@ -9647,6 +12215,12 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +[[package]] +name = "separator" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" + [[package]] name = "serde" version = "1.0.219" @@ -9693,6 +12267,27 @@ dependencies = [ "serde", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float 2.10.1", + "serde", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_bytes" version = "0.11.15" @@ -9831,6 +12426,19 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.9.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "serializable-account-meta" version = "0.1.0" @@ -9845,7 +12453,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest 0.10.7", ] @@ -9856,7 +12464,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest 0.10.7", ] @@ -9880,7 +12488,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.1", @@ -9892,7 +12500,7 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest 0.10.7", ] @@ -9941,6 +12549,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shared_child" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e297bd52991bbe0686c086957bee142f13df85d1e79b0b21630a99d374ae9dc" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "shell-words" version = "1.1.0" @@ -10013,6 +12631,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slugify-rs" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c88cdb6ea794da1dde6f267c3a363b2373ce24386b136828d66402a97ebdbff3" +dependencies = [ + "deunicode", + "nanoid", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -10112,7 +12740,7 @@ dependencies = [ "lazy_static", "serde", "serde_derive", - "serde_yaml", + "serde_yaml 0.8.26", "solana-clap-utils", "solana-sdk", "url", @@ -10355,7 +12983,7 @@ dependencies = [ "libc", "libsecp256k1", "log", - "memoffset", + "memoffset 0.6.5", "num-derive 0.3.3", "num-traits", "parking_lot 0.12.3", @@ -10614,6 +13242,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "sorted-insert" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eec75fe132d95908f1c030f93630bc20b76f3ebaeca789a6180553b770ddcd39" + [[package]] name = "spin" version = "0.5.2" @@ -10629,6 +13263,15 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spinning_top" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +dependencies = [ + "lock_api", +] + [[package]] name = "spki" version = "0.5.4" @@ -11446,6 +14089,20 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "sysinfo" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -11454,7 +14111,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "system-configuration-sys", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.9.4", + "system-configuration-sys 0.6.0", ] [[package]] @@ -11467,6 +14135,16 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tagptr" version = "0.2.0" @@ -11512,10 +14190,10 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "fastrand", "once_cell", - "rustix", + "rustix 0.38.35", "windows-sys 0.59.0", ] @@ -11687,16 +14365,35 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" +[[package]] +name = "thread-id" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe8f25bbdd100db7e1d34acf7fd2dc59c4bf8f7483f505eaa7d4f12f76cc0ea" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "thread_local" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "time" version = "0.3.36" @@ -11927,11 +14624,27 @@ dependencies = [ "tungstenite 0.21.0", ] +[[package]] +name = "tokio-tungstenite" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +dependencies = [ + "futures-util", + "log", + "rustls 0.23.19", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.1", + "tungstenite 0.23.0", + "webpki-roots 0.26.11", +] + [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", @@ -12036,6 +14749,7 @@ dependencies = [ "axum 0.7.9", "base64 0.22.1", "bytes", + "flate2", "h2 0.4.7", "http 1.2.0", "http-body 1.0.1", @@ -12056,6 +14770,49 @@ dependencies = [ "tower-layer", "tower-service", "tracing", + "webpki-roots 0.26.11", +] + +[[package]] +name = "tonic" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" +dependencies = [ + "async-trait", + "base64 0.22.1", + "bytes", + "h2 0.4.7", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-timeout 0.5.2", + "hyper-util", + "percent-encoding", + "pin-project", + "prost 0.13.4", + "socket2 0.5.7", + "tokio", + "tokio-stream", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" +dependencies = [ + "prettyplease", + "proc-macro2 1.0.93", + "prost-build", + "prost-types 0.13.4", + "quote 1.0.37", + "syn 2.0.98", ] [[package]] @@ -12086,14 +14843,49 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", + "indexmap 2.9.0", "pin-project-lite", + "slab", "sync_wrapper 1.0.2", "tokio", + "tokio-util", "tower-layer", "tower-service", "tracing", ] +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -12222,6 +15014,12 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "triggered" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "593eddbc8a11f3e099e942c8c065fe376b9d1776741430888f2796682e08ab43" + [[package]] name = "triomphe" version = "0.1.11" @@ -12275,12 +15073,41 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.2.0", + "httparse", + "log", + "rand 0.8.5", + "rustls 0.23.19", + "rustls-pki-types", + "sha1", + "thiserror 1.0.63", + "utf-8", +] + [[package]] name = "typeid" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" +[[package]] +name = "typemap-ors" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68c24b707f02dd18f1e4ccceb9d49f2058c2fb86384ef9972592904d7a28867" +dependencies = [ + "unsafe-any-ors", +] + [[package]] name = "typenum" version = "1.17.0" @@ -12420,6 +15247,21 @@ dependencies = [ "void", ] +[[package]] +name = "unsafe-any-ors" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a303d30665362d9680d7d91d78b23f5f899504d4f08b3c4cf08d055d87c0ad" +dependencies = [ + "destructure_traitobject", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.7.1" @@ -12511,7 +15353,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom 0.2.15", + "rand 0.8.5", "serde", + "wasm-bindgen", ] [[package]] @@ -12526,6 +15370,7 @@ dependencies = [ "console-subscriber", "derive-new", "derive_more 0.99.18", + "dymension-kaspa", "ethers", "eyre", "futures", @@ -12536,12 +15381,16 @@ dependencies = [ "hyperlane-cosmos", "hyperlane-ethereum", "hyperlane-test", - "itertools 0.12.1", + "itertools 0.13.0", "k256 0.13.4", + "kaspa-grpc-client", + "kaspa-rpc-core", + "kaspa-utils-tower", "mockall", "prometheus", "reqwest 0.11.27", "rusoto_core", + "rustls 0.23.19", "serde", "serde_json", "tempfile", @@ -12561,6 +15410,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "value-bag" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5" + [[package]] name = "vcpkg" version = "0.2.15" @@ -12580,7 +15435,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2990d9ea5967266ea0ccf413a4aa5c42a93dbcfda9cb49a97de6931726b12566" dependencies = [ "anyhow", - "cfg-if", + "cargo_metadata 0.18.1", + "cfg-if 1.0.0", + "regex", + "rustc_version", "rustversion", "time", ] @@ -12684,9 +15542,11 @@ version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", "rustversion", + "serde", + "serde_json", "wasm-bindgen-macro", ] @@ -12710,7 +15570,7 @@ version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "wasm-bindgen", "web-sys", @@ -12835,7 +15695,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix", + "rustix 0.38.35", ] [[package]] @@ -12846,7 +15706,7 @@ checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" dependencies = [ "either", "home", - "rustix", + "rustix 0.38.35", "winsafe", ] @@ -12880,24 +15740,68 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" dependencies = [ - "windows-sys 0.59.0", + "windows-implement", + "windows-interface", + "windows-result 0.1.2", + "windows-targets 0.52.6", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-implement" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2 1.0.93", + "quote 1.0.37", + "syn 2.0.98", +] [[package]] -name = "windows-core" -version = "0.52.0" +name = "windows-interface" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ - "windows-targets 0.52.6", + "proc-macro2 1.0.93", + "quote 1.0.37", + "syn 2.0.98", ] [[package]] @@ -12912,11 +15816,20 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ - "windows-result", + "windows-result 0.3.4", "windows-strings", "windows-targets 0.53.0", ] +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -13171,7 +16084,7 @@ version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "windows-sys 0.48.0", ] @@ -13190,6 +16103,340 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "workflow-chrome" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0c0dfbc178cb7c3a47bd2aabf6902364d2db7e4c4f5b0dad57b75d78c6fe1f" +dependencies = [ + "cfg-if 1.0.0", + "chrome-sys", + "js-sys", + "thiserror 1.0.63", + "wasm-bindgen", + "workflow-core", + "workflow-log", +] + +[[package]] +name = "workflow-core" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d67bbe225ea90aa6979167f28935275506696ac867661e218893d3a42e1666" +dependencies = [ + "async-channel 2.3.1", + "async-std", + "borsh 1.5.1", + "bs58 0.5.1", + "cfg-if 1.0.0", + "chrono", + "dirs", + "faster-hex", + "futures", + "getrandom 0.2.15", + "instant", + "js-sys", + "rand 0.8.5", + "rlimit", + "serde", + "serde-wasm-bindgen", + "thiserror 1.0.63", + "tokio", + "triggered", + "vergen", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "workflow-core-macros", + "workflow-log", +] + +[[package]] +name = "workflow-core-macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65659ed208b0066a9344142218abda353eb6c6cc1fc3ae4808b750c560de004b" +dependencies = [ + "convert_case 0.6.0", + "parse-variants", + "proc-macro-error", + "proc-macro2 1.0.93", + "quote 1.0.37", + "regex", + "sha2 0.10.8", + "syn 1.0.109", + "workflow-macro-tools", +] + +[[package]] +name = "workflow-dom" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503bba85907753c960ddfd73b4e79bffadf521cc3c992ef2b2a29fd3af09a957" +dependencies = [ + "futures", + "js-sys", + "regex", + "thiserror 1.0.63", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "workflow-core", + "workflow-log", + "workflow-wasm", +] + +[[package]] +name = "workflow-http" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c654c7395e448001c658309377a44a8c3d7c28c7acb30e9babbaeacb575bb0" +dependencies = [ + "cfg-if 1.0.0", + "reqwest 0.12.15", + "serde", + "serde_json", + "thiserror 1.0.63", + "tokio", + "wasm-bindgen", + "workflow-core", +] + +[[package]] +name = "workflow-log" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64bf52c539193f219b7a79eb0c7c5f6c222ccf9b95c5e0bd59e924feb762256f" +dependencies = [ + "cfg-if 1.0.0", + "console", + "downcast", + "hexplay", + "lazy_static", + "log", + "termcolor", + "wasm-bindgen", +] + +[[package]] +name = "workflow-macro-tools" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "085d3045d5ca780fb589d230030e34fec962b3638d6c69806a72a7d7d1affea4" +dependencies = [ + "convert_case 0.6.0", + "parse-variants", + "proc-macro2 1.0.93", + "quote 1.0.37", + "syn 1.0.109", +] + +[[package]] +name = "workflow-node" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85c9add43b5da3bed3d0d6d92eb3a2c5986c0ae65c7c3f5189876c19648154" +dependencies = [ + "borsh 1.5.1", + "futures", + "js-sys", + "lazy_static", + "node-sys", + "serde", + "thiserror 1.0.63", + "wasm-bindgen", + "wasm-bindgen-futures", + "workflow-core", + "workflow-log", + "workflow-task", + "workflow-wasm", +] + +[[package]] +name = "workflow-panic-hook" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c76ca8b459e4f0c949f06ce2d45565a6769748e83ca7064d36671bbd67b4da" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "workflow-rpc" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4235eb167f0bef3bcbdf0c578823a0105ab5303115e3b2afb4d526e2498b08" +dependencies = [ + "ahash 0.8.11", + "async-std", + "async-trait", + "borsh 1.5.1", + "downcast-rs 1.2.1", + "futures", + "futures-util", + "getrandom 0.2.15", + "manual_future", + "rand 0.8.5", + "serde", + "serde_json", + "thiserror 1.0.63", + "tokio", + "tungstenite 0.23.0", + "wasm-bindgen", + "workflow-core", + "workflow-log", + "workflow-rpc-macros", + "workflow-task", + "workflow-wasm", + "workflow-websocket", +] + +[[package]] +name = "workflow-rpc-macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f048ca6b1c551f468c3c0c829f958e83dd15b20138b5466bb617ffde500e8cf4" +dependencies = [ + "parse-variants", + "proc-macro-error", + "proc-macro2 1.0.93", + "quote 1.0.37", + "syn 1.0.109", +] + +[[package]] +name = "workflow-serializer" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64679db6856852a472caff4ce869e3ecebe291fbccc9406e9643eb5951a0904a" +dependencies = [ + "ahash 0.8.11", + "borsh 1.5.1", + "serde", +] + +[[package]] +name = "workflow-store" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d161c4b844eee479f81306f2526266f9608a663e0a679d9fc0572ee15c144e06" +dependencies = [ + "async-std", + "base64 0.22.1", + "cfg-if 1.0.0", + "chrome-sys", + "faster-hex", + "filetime", + "home", + "js-sys", + "lazy_static", + "serde", + "serde_json", + "thiserror 1.0.63", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "workflow-chrome", + "workflow-core", + "workflow-log", + "workflow-node", + "workflow-wasm", +] + +[[package]] +name = "workflow-task" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1a90743bb4d3f68606cb4e9a78551a53399ebc35ddba981cbb56bf2b31940a" +dependencies = [ + "futures", + "thiserror 1.0.63", + "workflow-core", + "workflow-task-macros", +] + +[[package]] +name = "workflow-task-macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ecf6be36b52dc1e16d11b55f717d9ec2fec5804aff7f392af591933ba4af45e" +dependencies = [ + "convert_case 0.6.0", + "parse-variants", + "proc-macro-error", + "proc-macro2 1.0.93", + "quote 1.0.37", + "sha2 0.10.8", + "syn 1.0.109", + "workflow-macro-tools", +] + +[[package]] +name = "workflow-wasm" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799e5fbf266e0fffb5c24d6103735eb2b94bb31f93b664b91eaaf63b4f959804" +dependencies = [ + "cfg-if 1.0.0", + "faster-hex", + "futures", + "js-sys", + "serde", + "serde-wasm-bindgen", + "thiserror 1.0.63", + "wasm-bindgen", + "wasm-bindgen-futures", + "workflow-core", + "workflow-log", + "workflow-panic-hook", + "workflow-wasm-macros", +] + +[[package]] +name = "workflow-wasm-macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40237c65ecff78dbfedb13985e33f802a31f6f7de72dff12a6674fcdcf601822" +dependencies = [ + "js-sys", + "proc-macro-error", + "proc-macro2 1.0.93", + "quote 1.0.37", + "syn 1.0.109", + "wasm-bindgen", +] + +[[package]] +name = "workflow-websocket" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "515483a047477c91b5142e1090cce0afc21a0139d9c0c06ea42f0d3dbf3a6fcd" +dependencies = [ + "ahash 0.8.11", + "async-channel 2.3.1", + "async-std", + "async-trait", + "cfg-if 1.0.0", + "downcast-rs 1.2.1", + "futures", + "futures-util", + "js-sys", + "thiserror 1.0.63", + "tokio", + "tokio-tungstenite 0.23.1", + "triggered", + "tungstenite 0.23.0", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "workflow-core", + "workflow-log", + "workflow-task", + "workflow-wasm", +] + [[package]] name = "ws_stream_wasm" version = "0.7.4" @@ -13248,13 +16495,19 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + [[package]] name = "ya-gcp" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acaf2e321fc6f853572b372962fa253cba1b62a0025116bb463ce3c00b4394dc" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "futures", "http 0.2.12", "humantime-serde", diff --git a/rust/main/Cargo.toml b/rust/main/Cargo.toml index 63fadc547d9..b7f8b01a5b8 100644 --- a/rust/main/Cargo.toml +++ b/rust/main/Cargo.toml @@ -7,9 +7,10 @@ members = [ "applications/hyperlane-operation-verifier", "applications/hyperlane-warp-route", "chains/hyperlane-cosmos", - "chains/hyperlane-cosmos-native", + "chains/dymension-kaspa", "chains/hyperlane-ethereum", "chains/hyperlane-fuel", + "chains/hyperlane-radix", "chains/hyperlane-sealevel", "chains/hyperlane-starknet", "ethers-prometheus", @@ -22,6 +23,7 @@ members = [ "utils/backtrace-oneline", "utils/crypto", "utils/hex", + "utils/kaspa-tools", "utils/run-locally", ] resolver = "2" @@ -93,6 +95,7 @@ getrandom = { version = "0.2", features = ["js"] } hex = "0.4.3" hex-literal = "0.4.1" http = "1.2.0" +humantime = "2.1" http-body-util = "0.1" hyper = "0.14" hyper-tls = "0.5.0" @@ -141,6 +144,7 @@ sea-orm-migration = { version = "1.1.10", features = [ "sqlx-postgres", "runtime-tokio-native-tls", ] } +secp256k1 = { version = "0.29.0", features = ["global-context", "rand-std", "serde"] } semver = "1.0" serde = { version = "1.0", features = ["derive"] } serde_bytes = "0.11" @@ -169,7 +173,9 @@ tokio-metrics = { version = "0.4.0" } tokio-test = "0.4" toml_edit = "0.19.14" tonic = "0.12.3" +rustls = { version = "0.23", default-features = false, features = ["aws-lc-rs", "std"] } tower = "*" +tower-http = { version = "0.6", features = ["cors"] } tracing = { version = "0.1" } tracing-error = "0.2" tracing-futures = "0.2" @@ -184,6 +190,37 @@ walkdir = "2" warp = "0.3" which = "4.3" ya-gcp = { version = "0.11.3", features = ["storage"] } +downcast-rs = { version = "2.0.1", features = ["sync"] } +radix-common = { git = "https://github.com/hyperlane-xyz/radixdlt-scrypto.git", branch = "hyperlane" } +radix-transactions = { git = "https://github.com/hyperlane-xyz/radixdlt-scrypto.git", branch = "hyperlane" } +radix-engine-interface = { git = "https://github.com/hyperlane-xyz/radixdlt-scrypto.git", branch = "hyperlane" } +scrypto = { git = "https://github.com/hyperlane-xyz/radixdlt-scrypto.git", branch = "hyperlane", features = [ + "serde", +] } +core-api-client = { git = "https://github.com/hyperlane-xyz/radix-apis-rust-clients.git", branch = "hyperlane" } +gateway-api-client = { git = "https://github.com/hyperlane-xyz/radix-apis-rust-clients.git", branch = "hyperlane" } +scrypto-derive = { git = "https://github.com/hyperlane-xyz/radixdlt-scrypto.git", branch = "hyperlane" } +sbor = { git = "https://github.com/hyperlane-xyz/radixdlt-scrypto.git", branch = "hyperlane", features = [ + "serde", +] } +hyperlane-cosmos-rs = { package = "hyperlane-cosmos-dymension-rs", git = "https://github.com/dymensionxyz/hyperlane-cosmos-rs", rev = "8fd5d6a" } + +## Kaspa dependencies +kaspa-consensus-core = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-txscript = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-wallet-pskt = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-wrpc-client = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-addresses = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-wallet-core = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-wallet-keys = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-rpc-core = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-core = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-grpc-client = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-utils-tower = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-hashes = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-bip32 = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-consensus-client = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +workflow-core = { version = "0.18.0" } ## TODO: remove this cosmwasm-schema = "2.2.0" @@ -221,6 +258,13 @@ overflow-checks = true [profile.release.package.hyperlane-sealevel-validator-announce] overflow-checks = true +[workspace.dependencies.cometbft] +git = "https://github.com/hyperlane-xyz/cometbft-rs" +tag = "2025-08-07" + +[workspace.dependencies.cometbft-rpc] +git = "https://github.com/hyperlane-xyz/cometbft-rs" +tag = "2025-08-07" [workspace.dependencies.ethers] features = [] diff --git a/rust/main/agents/relayer/Cargo.toml b/rust/main/agents/relayer/Cargo.toml index d9d7db6081d..8ac489b4ac7 100644 --- a/rust/main/agents/relayer/Cargo.toml +++ b/rust/main/agents/relayer/Cargo.toml @@ -30,6 +30,7 @@ num-derive.workspace = true num-traits.workspace = true prometheus.workspace = true rand.workspace = true +rustls.workspace = true regex.workspace = true reqwest = { workspace = true, features = ["json"] } serde.workspace = true @@ -44,6 +45,7 @@ tokio = { workspace = true, features = [ "rt-multi-thread", ] } tokio-metrics.workspace = true +tower-http.workspace = true tracing-futures.workspace = true tracing.workspace = true typetag.workspace = true @@ -55,15 +57,18 @@ hyperlane-core = { path = "../../hyperlane-core", features = [ "async", ] } hyperlane-ethereum = { path = "../../chains/hyperlane-ethereum" } +dymension-kaspa = { path = "../../chains/dymension-kaspa" } +hyperlane-cosmos = { path = "../../chains/hyperlane-cosmos" } hyperlane-metric = { path = "../../hyperlane-metric" } hyperlane-operation-verifier = { path = "../../applications/hyperlane-operation-verifier" } lander = { path = "../../lander" } +mockall.workspace = true [dev-dependencies] axum = { workspace = true, features = ["macros"] } http-body-util.workspace = true once_cell.workspace = true -mockall.workspace = true +# mockall.workspace = true tokio-test.workspace = true tower.workspace = true tracing-test.workspace = true diff --git a/rust/main/agents/relayer/src/lib.rs b/rust/main/agents/relayer/src/lib.rs index bbabb721e10..c2da3b00b72 100644 --- a/rust/main/agents/relayer/src/lib.rs +++ b/rust/main/agents/relayer/src/lib.rs @@ -1,4 +1,5 @@ #![deny(clippy::unwrap_used, clippy::panic)] +#![deny(clippy::arithmetic_side_effects)] pub mod msg; diff --git a/rust/main/agents/relayer/src/main.rs b/rust/main/agents/relayer/src/main.rs index 47e1f9c2cac..6f8e6b259db 100644 --- a/rust/main/agents/relayer/src/main.rs +++ b/rust/main/agents/relayer/src/main.rs @@ -19,6 +19,11 @@ mod memory_profiler; #[tokio::main(flavor = "multi_thread", worker_threads = 8)] async fn main() -> Result<()> { + // Install rustls crypto provider (required for rustls 0.23+) + rustls::crypto::aws_lc_rs::default_provider() + .install_default() + .expect("Failed to install rustls crypto provider"); + // Logging is not initialised at this point, so, using `println!` println!("Relayer starting up..."); diff --git a/rust/main/agents/relayer/src/merkle_tree/builder.rs b/rust/main/agents/relayer/src/merkle_tree/builder.rs index f27465a0d08..39a2726b763 100644 --- a/rust/main/agents/relayer/src/merkle_tree/builder.rs +++ b/rust/main/agents/relayer/src/merkle_tree/builder.rs @@ -76,7 +76,7 @@ impl MerkleTreeBuilder { } } - #[instrument(err, skip(self), level="debug", fields(prover_latest_index=self.count()-1))] + #[instrument(err, skip(self), level="debug", fields(prover_latest_index=self.count().saturating_sub(1)))] pub fn get_proof( &self, leaf_index: u32, diff --git a/rust/main/agents/relayer/src/msg/gas_payment/mod.rs b/rust/main/agents/relayer/src/msg/gas_payment/mod.rs index 442302631d1..545164a9944 100644 --- a/rust/main/agents/relayer/src/msg/gas_payment/mod.rs +++ b/rust/main/agents/relayer/src/msg/gas_payment/mod.rs @@ -1,4 +1,4 @@ -use std::fmt::Debug; +use std::{fmt::Debug, ops::Mul}; use async_trait::async_trait; use eyre::Result; @@ -196,11 +196,12 @@ impl GasPaymentEnforcer { "{}", GAS_EXPENDITURE_LOG_MESSAGE, ); + + let tokens_used = FixedPointNumber::try_from(outcome.gas_used)?.mul(outcome.gas_price); self.db.process_gas_expenditure(InterchainGasExpenditure { message_id: message.id(), gas_used: outcome.gas_used, - tokens_used: (FixedPointNumber::try_from(outcome.gas_used)? * outcome.gas_price) - .try_into()?, + tokens_used: tokens_used.try_into()?, })?; Ok(()) } diff --git a/rust/main/agents/relayer/src/msg/gas_payment/policies/minimum.rs b/rust/main/agents/relayer/src/msg/gas_payment/policies/minimum.rs index 0485f025b24..6af77b518d6 100644 --- a/rust/main/agents/relayer/src/msg/gas_payment/policies/minimum.rs +++ b/rust/main/agents/relayer/src/msg/gas_payment/policies/minimum.rs @@ -21,7 +21,6 @@ impl GasPaymentPolicy for GasPaymentPolicyMinimum { /// This is different from not requiring message senders to make any payment at all to /// the configured IGP to get relayed. To relay regardless of the existence of a payment, /// the `None` IGP policy should be used. - async fn message_meets_gas_payment_requirement( &self, _message: &HyperlaneMessage, diff --git a/rust/main/agents/relayer/src/msg/gas_payment/policies/on_chain_fee_quoting.rs b/rust/main/agents/relayer/src/msg/gas_payment/policies/on_chain_fee_quoting.rs index d871f6f270e..50201ed4d71 100644 --- a/rust/main/agents/relayer/src/msg/gas_payment/policies/on_chain_fee_quoting.rs +++ b/rust/main/agents/relayer/src/msg/gas_payment/policies/on_chain_fee_quoting.rs @@ -41,7 +41,6 @@ impl GasPaymentPolicy for GasPaymentPolicyOnChainFeeQuoting { /// OnChainFeeQuoting requires the user to pay a specified fraction of the /// estimated gas. Like the Minimum policy, OnChainFeeQuoting requires a /// payment to exist on the IGP specified in the config. - async fn message_meets_gas_payment_requirement( &self, _message: &HyperlaneMessage, @@ -49,9 +48,11 @@ impl GasPaymentPolicy for GasPaymentPolicyOnChainFeeQuoting { current_expenditure: &InterchainGasExpenditure, tx_cost_estimate: &TxCostEstimate, ) -> Result> { - let fractional_gas_estimate = (tx_cost_estimate.enforceable_gas_limit() - * self.fractional_numerator) - / self.fractional_denominator; + let fractional_gas_estimate = tx_cost_estimate + .enforceable_gas_limit() + .saturating_mul(U256::from(self.fractional_numerator)) + .div_mod(U256::from(self.fractional_denominator)) + .0; let gas_amount = current_payment .gas_amount .saturating_sub(current_expenditure.gas_used); diff --git a/rust/main/agents/relayer/src/msg/message_processor.rs b/rust/main/agents/relayer/src/msg/message_processor.rs index 485add1eead..dcf79b60f0e 100644 --- a/rust/main/agents/relayer/src/msg/message_processor.rs +++ b/rust/main/agents/relayer/src/msg/message_processor.rs @@ -7,23 +7,28 @@ use std::time::Duration; use futures::future::join_all; use futures_util::future::try_join_all; +use itertools::Either; +use itertools::Itertools; use maplit::hashmap; use num_traits::Zero; use prometheus::{IntCounterVec, IntGaugeVec}; +use tokio::sync::MutexGuard; use tokio::sync::{broadcast::Sender, mpsc, Mutex}; use tokio::task::JoinHandle; use tokio::time::sleep; use tokio_metrics::TaskMonitor; use tracing::{debug, error, info, info_span, instrument, trace, warn, Instrument}; +use dymension_kaspa::is_kas; use hyperlane_base::db::{HyperlaneDb, HyperlaneRocksDB}; use hyperlane_base::CoreMetrics; use hyperlane_core::{ - ConfirmReason, HyperlaneDomain, HyperlaneDomainProtocol, PendingOperationResult, - PendingOperationStatus, QueueOperation, ReprepareReason, + ConfirmReason, HyperlaneDomain, HyperlaneDomainProtocol, PendingOperation, + PendingOperationResult, PendingOperationStatus, QueueOperation, ReprepareReason, }; use lander::{ DispatcherEntrypoint, Entrypoint, FullPayload, LanderError, PayloadStatus, PayloadUuid, + TransactionStatus, }; use crate::msg::pending_message::CONFIRM_DELAY; @@ -160,7 +165,10 @@ impl MessageProcessor { let entrypoint = self.payload_dispatcher_entrypoint.take().map(Arc::new); - let prepare_task = self.create_classic_prepare_task(); + let prepare_task = match &entrypoint { + None => self.create_classic_prepare_task(), + Some(entrypoint) => self.create_lander_prepare_task(entrypoint.clone()), + }; let submit_task = match &entrypoint { None => self.create_classic_submit_task(), @@ -182,6 +190,8 @@ impl MessageProcessor { domain=?self.domain.name(), "MessageProcessor task panicked for domain" ); + // The vanilla relayer silently continues if we error here, so add this extra panic which ensures a full crash + panic!("Dymension: message processor panic, exiting") } } @@ -424,16 +434,20 @@ async fn confirm_already_submitted_operations( batch: Vec, ) -> Vec { use ConfirmReason::AlreadySubmitted; - use PendingOperationStatus::Confirm; + use PendingOperationStatus::{Confirm, Retry}; let mut ops_to_prepare = vec![]; for op in batch.into_iter() { - if has_operation_been_submitted(entrypoint.clone(), db.clone(), &op).await { - let status = Some(Confirm(AlreadySubmitted)); - confirm_queue.push(op, status).await; - } else { + if let Retry(ReprepareReason::Manual) = op.status() { + ops_to_prepare.push(op); + continue; + } + if !has_operation_been_submitted(entrypoint.clone(), db.clone(), &op).await { ops_to_prepare.push(op); + continue; } + let status = Some(Confirm(AlreadySubmitted)); + confirm_queue.push(op, status).await; } ops_to_prepare } @@ -462,6 +476,7 @@ async fn has_operation_been_submitted( match status { Ok(PayloadStatus::Dropped(_)) => false, + Ok(PayloadStatus::InTransaction(TransactionStatus::Dropped(_))) => false, Ok(_) => true, Err(_) => false, } @@ -571,9 +586,29 @@ async fn submit_classic_task( metrics: MessageProcessorMetrics, ) { let recv_limit = max_batch_size as usize; + loop { let mut batch = submit_queue.pop_many(recv_limit).await; + if is_kas(&domain.clone()) && batch.len() > 0 { + /* + We do this here rather than in OperationBatch::submit because here we have better control over error handling. The regular batch flow + has some oddities like retrying all failed messages individually. + */ + submit_kaspa_batch( + &domain, + &mut prepare_queue, + &mut submit_queue, + &mut confirm_queue, + max_batch_size, + &metrics, + batch, + ) + .await; + + continue; + } + match batch.len().cmp(&1) { std::cmp::Ordering::Less => { // The queue is empty, so give some time before checking again to prevent burning CPU @@ -878,7 +913,7 @@ async fn confirm_lander_task( let status_results_len = status_results.len(); let successes = filter_status_results(status_results); - if status_results_len - successes.len() > 0 { + if status_results_len > successes.len() { warn!(?op, "Error retrieving payload status",); send_back_on_failed_submission( op, @@ -894,8 +929,8 @@ async fn confirm_lander_task( if finalized { { - let mut lock = confirmed_operations.lock().await; - *lock += 1; + let mut lock: MutexGuard = confirmed_operations.lock().await; + *lock = lock.saturating_add(1); } confirm_operation( op, @@ -1063,3 +1098,61 @@ impl MessageProcessorMetrics { } } } + +// NOTE: this code has a long history: see https://github.com/dymensionxyz/hyperlane-monorepo/blob/e53677d0aeca030a8fbe986dc15db952ab187ed5/rust/main/agents/relayer/src/msg/message_processor.rs#L1123-L1177 +// for old comments and explanations +async fn submit_kaspa_batch( + kas_domain: &HyperlaneDomain, + prepare_queue: &mut OpQueue, + submit_queue: &mut OpQueue, + confirm_queue: &mut OpQueue, + max_batch_size: u32, + metrics: &MessageProcessorMetrics, + batch: Vec>, // from the submit queue +) { + info!("Kaspa batch, submitting batch of size: {}", batch.len()); + // see https://github.com/dymensionxyz/hyperlane-monorepo/blob/8ca01f1ac17f28fb53df63ee2c9c17e59873af69/rust/main/agents/relayer/src/msg/op_batch.rs#L59-L70 + let Some(first_item) = batch.first() else { + error!("Kaspa batch, no first item"); + return; + }; + let Some(mailbox) = first_item.try_get_mailbox() else { + error!("Kaspa batch, no mailbox"); + return; + }; + if !mailbox.supports_batching() { + panic!("Kaspa must support batching") + } + let res = mailbox.process_batch(batch.iter().collect()).await; + match res { + Ok(batch_result) => { + let (_, excluded_ops): (Vec<_>, Vec<_>) = + batch.into_iter().enumerate().partition_map(|(i, op)| { + if !batch_result.failed_indexes.contains(&i) { + info!("Kaspa batch, successfully submitted op: {}", op.id()); + Either::Left(op) + } else { + info!("Kaspa batch, failed to submit op: {}", op.id()); + Either::Right(op) + } + }); + for op in excluded_ops { + send_back_on_failed_submission(op, prepare_queue.clone(), &metrics, None).await; + } + /* + We should do a second part here where we confirm the operations that were successful + For now we do nothing because the HL code is using 'mailbox.delivered' to check that + and in our case this is not reliable because kaspa mailbox delivered checks for the + completed confirmation phase agains the hub state, which takes a while/could take a while + and HL will think this means things have been reorged. + Needs some proper though to figure out what to do here! + */ + } + Err(e) => { + panic!( + "Dymension: kaspa mailbox process_batch error, should be impossible: {}", + e + ); + } + } +} diff --git a/rust/main/agents/relayer/src/msg/metadata/aggregation.rs b/rust/main/agents/relayer/src/msg/metadata/aggregation.rs index 8b4a0077803..f32a376d0c4 100644 --- a/rust/main/agents/relayer/src/msg/metadata/aggregation.rs +++ b/rust/main/agents/relayer/src/msg/metadata/aggregation.rs @@ -56,7 +56,9 @@ impl AggregationIsmMetadataBuilder { fn encode_byte_index(i: usize) -> [u8; 4] { (i as u32).to_be_bytes() } - let range_tuples_size = METADATA_RANGE_SIZE * 2 * ism_count; + let range_tuples_size = METADATA_RANGE_SIZE + .saturating_mul(2) + .saturating_mul(ism_count); // Format of metadata: // [????:????] Metadata start/end uint32 ranges, packed as uint64 // [????:????] ISM metadata, packed encoding @@ -70,10 +72,11 @@ impl AggregationIsmMetadataBuilder { // The new tuple starts at the end of the previous ones. // Also see: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/445da4fb0d8140a08c4b314e3051b7a934b0f968/solidity/contracts/libs/isms/AggregationIsmMetadata.sol#L49 - let encoded_range_start = METADATA_RANGE_SIZE * 2 * (*index); + let encoded_range_start = METADATA_RANGE_SIZE.saturating_mul(2).saturating_mul(*index); // Overwrite the 0-initialized buffer buffer.splice( - encoded_range_start..(encoded_range_start + METADATA_RANGE_SIZE * 2), + encoded_range_start + ..encoded_range_start.saturating_add(METADATA_RANGE_SIZE.saturating_mul(2)), [encode_byte_index(range_start), encode_byte_index(range_end)].concat(), ); } @@ -339,7 +342,7 @@ impl MetadataBuilder for AggregationIsmMetadataBuilder { // If any inner ISMs are refusing to build metadata, we propagate just the first refusal. for sub_module_res in sub_modules_and_metas.iter() { if let Err(MetadataBuildError::Refused(s)) = sub_module_res { - return Err(MetadataBuildError::Refused(s.to_string())); + return Err(MetadataBuildError::Refused(s.clone())); } } diff --git a/rust/main/agents/relayer/src/msg/metadata/base.rs b/rust/main/agents/relayer/src/msg/metadata/base.rs index ff72e12de36..59e59674830 100644 --- a/rust/main/agents/relayer/src/msg/metadata/base.rs +++ b/rust/main/agents/relayer/src/msg/metadata/base.rs @@ -44,6 +44,8 @@ pub enum MetadataBuildError { AggregationThresholdNotMet(u32), #[error("Fast path error ({0})")] FastPathError(String), + #[error("Merkle root mismatch ({root}, {canonical_root})")] + MerkleRootMismatch { root: H256, canonical_root: H256 }, } #[derive(Clone, Debug, new)] @@ -298,7 +300,7 @@ impl IsmCachePolicyClassifier { } IsmCacheSelector::AppContext { context: selector_app_context, - } => app_context.map_or(false, |app_context| app_context == selector_app_context), + } => app_context == Some(selector_app_context), }; if matches_module diff --git a/rust/main/agents/relayer/src/msg/metadata/base_builder.rs b/rust/main/agents/relayer/src/msg/metadata/base_builder.rs index 28c77cf02be..8415e7f2e85 100644 --- a/rust/main/agents/relayer/src/msg/metadata/base_builder.rs +++ b/rust/main/agents/relayer/src/msg/metadata/base_builder.rs @@ -4,7 +4,6 @@ use std::{collections::HashMap, fmt::Debug, str::FromStr, sync::Arc}; use derive_new::new; -use eyre::Context; use futures::{stream, StreamExt}; use hyperlane_ethereum::Signers; use maplit::hashmap; @@ -23,8 +22,8 @@ use hyperlane_core::{ ValidatorAnnounce, H160, H256, }; -use crate::merkle_tree::builder::MerkleTreeBuilder; use crate::msg::metadata::base_builder::validator_announced_storages::fetch_storage_locations_helper; +use crate::{merkle_tree::builder::MerkleTreeBuilder, msg::metadata::MetadataBuildError}; use super::{base::IsmCachePolicyClassifier, IsmAwareAppContextClassifier}; @@ -78,7 +77,11 @@ pub trait BuildsBaseMetadata: Send + Sync + Debug { fn update_ism_metric(&self, params: IsmBuildMetricsParams); - async fn get_proof(&self, leaf_index: u32, checkpoint: Checkpoint) -> eyre::Result; + async fn get_proof( + &self, + leaf_index: u32, + checkpoint: Checkpoint, + ) -> Result; async fn highest_known_leaf_index(&self) -> Option; async fn get_merkle_leaf_id_by_message_id(&self, message_id: H256) -> eyre::Result>; @@ -127,14 +130,17 @@ impl BuildsBaseMetadata for BaseMetadataBuilder { self.metrics.ism_build_count().with(&labels).inc(); } - async fn get_proof(&self, leaf_index: u32, checkpoint: Checkpoint) -> eyre::Result { - const CTX: &str = "When fetching message proof"; + async fn get_proof( + &self, + leaf_index: u32, + checkpoint: Checkpoint, + ) -> Result { let proof = self .origin_prover_sync .read() .await .get_proof(leaf_index, checkpoint.index) - .context(CTX)?; + .map_err(|err| MetadataBuildError::FailedToBuild(err.to_string()))?; if proof.root() != checkpoint.root { info!( @@ -142,6 +148,12 @@ impl BuildsBaseMetadata for BaseMetadataBuilder { canonical_root = ?proof.root(), "Could not fetch metadata: checkpoint root does not match canonical root from merkle proof" ); + self.metrics.set_merkle_root_mismatch(self.origin_domain()); + + return Err(MetadataBuildError::MerkleRootMismatch { + root: checkpoint.root, + canonical_root: proof.root(), + }); } Ok(proof) } @@ -198,7 +210,7 @@ impl BuildsBaseMetadata for BaseMetadataBuilder { ) -> Result { let storage_locations = self.fetch_storage_locations(validators).await?; - debug!( + info!( hyp_message=?message, ?validators, validators_len = ?validators.len(), @@ -225,7 +237,7 @@ impl BuildsBaseMetadata for BaseMetadataBuilder { // Reverse the order of storage locations to prefer the most recently announced for storage_location in validator_storage_locations.iter().rev() { let Ok(config) = CheckpointSyncerConf::from_str(storage_location) else { - debug!( + info!( ?validator, ?storage_location, "Could not parse checkpoint syncer config for validator" @@ -238,7 +250,7 @@ impl BuildsBaseMetadata for BaseMetadataBuilder { if !self.allow_local_checkpoint_syncers && matches!(config, CheckpointSyncerConf::LocalStorage { .. }) { - debug!( + info!( ?config, "Ignoring disallowed LocalStorage based checkpoint syncer" ); @@ -275,6 +287,13 @@ impl BuildsBaseMetadata for BaseMetadataBuilder { checkpoint_syncers.insert(validator.into(), checkpoint_syncer.into()); } + info!( + hyp_message=?message, + checkpoint_syncers_count = checkpoint_syncers.len(), + validators_count = validators.len(), + "Successfully built checkpoint syncers" + ); + Ok(MultisigCheckpointSyncer::new( checkpoint_syncers, app_context.map(|ctx| (self.metrics.clone(), ctx)), @@ -321,7 +340,7 @@ impl BaseMetadataBuilder { return Err(CheckpointSyncerBuildError::ReorgEvent(reorg_event)); } Err(err) => { - debug!( + info!( error=%err, ?config, ?validator, @@ -332,3 +351,89 @@ impl BaseMetadataBuilder { Ok(None) } } + +/// DYMENSION: it allow metadata creation even if this stuff is not used but it's needed for type construction +/// Base metadata builder with types used by higher level metadata builders. +#[allow(clippy::too_many_arguments)] +#[derive(new, Debug)] +pub struct DummyBuildsBaseMetadata; + +#[allow(unused_variables)] +#[async_trait::async_trait] +impl BuildsBaseMetadata for DummyBuildsBaseMetadata { + fn update_ism_metric(&self, params: IsmBuildMetricsParams) { + todo!() + } + + fn origin_domain(&self) -> &HyperlaneDomain { + todo!() + } + + fn destination_domain(&self) -> &HyperlaneDomain { + todo!() + } + + fn app_context_classifier(&self) -> &IsmAwareAppContextClassifier { + todo!() + } + + fn ism_cache_policy_classifier(&self) -> &IsmCachePolicyClassifier { + todo!() + } + + fn cache(&self) -> &OptionalCache> { + todo!() + } + + async fn get_proof( + &self, + leaf_index: u32, + checkpoint: Checkpoint, + ) -> Result { + todo!() + } + + async fn highest_known_leaf_index(&self) -> Option { + todo!() + } + + async fn get_merkle_leaf_id_by_message_id( + &self, + message_id: H256, + ) -> eyre::Result> { + todo!() + } + + async fn build_ism(&self, address: H256) -> eyre::Result> { + todo!() + } + + async fn build_routing_ism(&self, address: H256) -> eyre::Result> { + todo!() + } + + async fn build_multisig_ism(&self, address: H256) -> eyre::Result> { + todo!() + } + + async fn build_aggregation_ism(&self, address: H256) -> eyre::Result> { + todo!() + } + + async fn build_ccip_read_ism(&self, address: H256) -> eyre::Result> { + todo!() + } + + async fn build_checkpoint_syncer( + &self, + message: &HyperlaneMessage, + validators: &[H256], + app_context: Option, + ) -> Result { + todo!() + } + + fn get_signer(&self) -> Option<&Signers> { + todo!() + } +} diff --git a/rust/main/agents/relayer/src/msg/metadata/ccip_read/mod.rs b/rust/main/agents/relayer/src/msg/metadata/ccip_read/mod.rs index a0472cbba73..052094ff7e9 100644 --- a/rust/main/agents/relayer/src/msg/metadata/ccip_read/mod.rs +++ b/rust/main/agents/relayer/src/msg/metadata/ccip_read/mod.rs @@ -9,9 +9,8 @@ use derive_new::new; use ethers::{abi::AbiDecode, core::utils::hex::decode as hex_decode}; use hyperlane_base::cache::FunctionCallCache; use regex::{Regex, RegexSet, RegexSetBuilder}; -use reqwest::{header::CONTENT_TYPE, Client}; +use reqwest::{header::CONTENT_TYPE, Client, Method}; use serde::{Deserialize, Serialize}; -use serde_json::json; use sha3::{digest::Update, Digest, Keccak256}; use tracing::{info, instrument, warn}; @@ -33,6 +32,13 @@ mod cache_types; pub const DEFAULT_TIMEOUT: u64 = 30; +#[derive(Clone, Debug, Serialize)] +struct OffchainLookupRequestBody { + pub data: String, + pub sender: String, + pub signature: Option, +} + #[derive(Serialize, Deserialize)] struct OffchainResponse { data: String, @@ -227,76 +233,95 @@ async fn metadata_build( continue; } - // Compute relayer authentication signature via EIP-191 - let maybe_signature_hex = if let Some(signer) = ism_builder.base.base_builder().get_signer() - { - Some(CcipReadIsmMetadataBuilder::generate_signature_hex(signer, &info, url).await?) - } else { - None - }; - - // Need to explicitly convert the sender H160 the hex because the `ToString` implementation - // for `H160` truncates the output. (e.g. `0xc66a…7b6f` instead of returning - // the full address) - let sender_as_bytes = &bytes_to_hex(info.sender.as_bytes()); - let data_as_bytes = &info.call_data.to_string(); - let interpolated_url = url - .replace("{sender}", sender_as_bytes) - .replace("{data}", data_as_bytes); - let res = if !url.contains("{data}") { - let mut body = json!({ - "sender": sender_as_bytes, - "data": data_as_bytes - }); - if let Some(signature_hex) = &maybe_signature_hex { - body["signature"] = json!(signature_hex); + // if we fail, we want to try the other urls + match fetch_offchain_data(ism_builder, &info, url).await { + Ok(data) => return Ok(data), + Err(err) => { + tracing::warn!(?ism_address, url, ?err, "Failed to fetch offchain data"); + continue; } - Client::new() - .post(interpolated_url) - .header(CONTENT_TYPE, "application/json") - .timeout(Duration::from_secs(DEFAULT_TIMEOUT)) - .json(&body) - .send() - .await - .map_err(|err| { - let msg = format!( - "Failed to request offchain lookup server with post method: {}", - err - ); - MetadataBuildError::FailedToBuild(msg) - })? - } else { - reqwest::get(interpolated_url).await.map_err(|err| { + } + } + + // No metadata endpoints or endpoints down + Err(MetadataBuildError::CouldNotFetch) +} + +/// Fetch data from offchain lookup server +async fn fetch_offchain_data( + ism_builder: &CcipReadIsmMetadataBuilder, + info: &OffchainLookup, + url: &str, +) -> Result { + // Compute relayer authentication signature via EIP-191 + let maybe_signature_hex = if let Some(signer) = ism_builder.base.base_builder().get_signer() { + Some(CcipReadIsmMetadataBuilder::generate_signature_hex(signer, info, url).await?) + } else { + None + }; + + // Need to explicitly convert the sender H160 the hex because the `ToString` implementation + // for `H160` truncates the output. (e.g. `0xc66a…7b6f` instead of returning + // the full address) + let sender_as_bytes = bytes_to_hex(info.sender.as_bytes()); + let data_as_bytes = info.call_data.to_string(); + let interpolated_url = url + .replace("{sender}", &sender_as_bytes) + .replace("{data}", &data_as_bytes); + let res = if !url.contains("{data}") { + let body = OffchainLookupRequestBody { + sender: sender_as_bytes, + data: data_as_bytes, + signature: maybe_signature_hex, + }; + Client::new() + .request(Method::POST, interpolated_url) + .header(CONTENT_TYPE, "application/json") + .timeout(Duration::from_secs(DEFAULT_TIMEOUT)) + .json(&body) + .send() + .await + .map_err(|err| { + let msg = format!( + "Failed to request offchain lookup server with post method: {}", + err + ); + MetadataBuildError::FailedToBuild(msg) + })? + } else { + Client::new() + .request(Method::GET, interpolated_url) + .timeout(Duration::from_secs(DEFAULT_TIMEOUT)) + .send() + .await + .map_err(|err| { let msg = format!( "Failed to request offchain lookup server with get method: {}", err ); MetadataBuildError::FailedToBuild(msg) })? - }; + }; - let json: Result = res.json().await; - - match json { - Ok(result) => { - // remove leading 0x which hex_decode doesn't like - let metadata = hex_decode(&result.data[2..]).map_err(|err| { - let msg = format!( - "Failed to decode hex from offchain lookup server response: err: ({}), data: ({})", - err, result.data - ); - MetadataBuildError::FailedToBuild(msg) - })?; - return Ok(Metadata::new(metadata)); - } - Err(_err) => { - // try the next URL - } - } - } + let json: OffchainResponse = res.json().await.map_err(|err| { + let error_msg = format!( + "Failed to parse offchain lookup server json response: ({})", + err + ); + MetadataBuildError::FailedToBuild(error_msg) + })?; - // No metadata endpoints or endpoints down - Err(MetadataBuildError::CouldNotFetch) + // remove leading 0x which hex_decode doesn't like + let hex_data = &json.data[2..]; + + let metadata = hex_decode(hex_data).map_err(|err| { + let msg = format!( + "Failed to decode hex from offchain lookup server response: err: ({}), data: ({})", + err, json.data + ); + MetadataBuildError::FailedToBuild(msg) + })?; + Ok(Metadata::new(metadata)) } fn create_ccip_url_regex() -> RegexSet { diff --git a/rust/main/agents/relayer/src/msg/metadata/dymension_kaspa/mod.rs b/rust/main/agents/relayer/src/msg/metadata/dymension_kaspa/mod.rs new file mode 100644 index 00000000000..a7bec870c16 --- /dev/null +++ b/rust/main/agents/relayer/src/msg/metadata/dymension_kaspa/mod.rs @@ -0,0 +1,76 @@ +use super::{ + base::{MessageMetadataBuildParams, MetadataBuildError}, + message_builder::MessageMetadataBuilder, + Metadata, MetadataBuilder, +}; +use crate::msg::{ + metadata::multisig::{ + MessageIdMultisigMetadataBuilder, MultisigIsmMetadataBuilder, MultisigMetadata, + }, + metadata::DummyBuildsBaseMetadata, +}; +use async_trait::async_trait; +use hyperlane_base::kas_hack::logic_loop::MetadataConstructor; +use hyperlane_core::MultisigSignedCheckpoint; +use hyperlane_core::{HyperlaneMessage, H256}; +use std::sync::Arc; +use tracing::instrument; +pub struct KaspaMetadataBuilder; + +impl KaspaMetadataBuilder { + pub fn new(_message_builder: MessageMetadataBuilder) -> Self { + Self {} + } +} + +#[async_trait] +impl MetadataBuilder for KaspaMetadataBuilder { + #[instrument(err, skip(self, _message, _params))] + async fn build( + &self, + _ism_address: H256, + _message: &HyperlaneMessage, + _params: MessageMetadataBuildParams, + ) -> Result { + /* + Our Kaspa bridge design doesn't match perfectly with the Hyperlane relayer pattern. + The hyperlane relayer pattern gathers metadata (i.e. validator signatures) for each message individually, + and submits them one at a time to the destination chain. + There IS an optional way to submit messages in batches, but there is no way to gather gather metadata in a batch. + + We want to construct a batch of txs which contain possibly many hyperlane messages at once. + Therefore we return a dummy metadata, and then we ignore it later, and construct everything on the fly during submission. + */ + Ok(Metadata::new(vec![])) + } +} + +use eyre::Result; + +impl MetadataConstructor for PendingMessageMetadataGetter { + /// mimic https://github.com/dymensionxyz/hyperlane-monorepo/blob/f4836a2a7291864d0c1850dbbcecd6af54addce3/rust/main/agents/relayer/src/msg/metadata/multisig/base.rs#L226-L235 + fn metadata(&self, checkpoint: &MultisigSignedCheckpoint) -> Result> { + let d: MultisigMetadata = MultisigMetadata::new(checkpoint.clone(), 0, None); + let formatter = &self.builder as &dyn MultisigIsmMetadataBuilder; + formatter.format_metadata(d) + } +} + +/// A convenience way to properly format signature metadata, without requiring a huge amount of unused context objects +pub struct PendingMessageMetadataGetter { + builder: MessageIdMultisigMetadataBuilder, +} + +impl PendingMessageMetadataGetter { + pub fn new() -> Self { + Self { + builder: MessageIdMultisigMetadataBuilder::new(MessageMetadataBuilder { + base: Arc::new(DummyBuildsBaseMetadata), + app_context: None, + root_ism: H256::random(), + max_ism_depth: 0, + max_ism_count: 0, + }), + } + } +} diff --git a/rust/main/agents/relayer/src/msg/metadata/message_builder.rs b/rust/main/agents/relayer/src/msg/metadata/message_builder.rs index 771d239ea0d..9b2a039b314 100644 --- a/rust/main/agents/relayer/src/msg/metadata/message_builder.rs +++ b/rust/main/agents/relayer/src/msg/metadata/message_builder.rs @@ -21,6 +21,7 @@ use super::{ aggregation::AggregationIsmMetadataBuilder, base::{IsmWithMetadataAndType, MessageMetadataBuildParams, MetadataBuildError}, ccip_read::CcipReadIsmMetadataBuilder, + dymension_kaspa::KaspaMetadataBuilder, multisig::{MerkleRootMultisigMetadataBuilder, MessageIdMultisigMetadataBuilder}, null_metadata::NullMetadataBuilder, routing::RoutingIsmMetadataBuilder, @@ -200,7 +201,9 @@ pub async fn build_message_metadata( ModuleType::Routing => Box::new(RoutingIsmMetadataBuilder::new(message_builder)), ModuleType::Aggregation => Box::new(AggregationIsmMetadataBuilder::new(message_builder)), ModuleType::Null => Box::new(NullMetadataBuilder::new()), + ModuleType::Unused => Box::new(NullMetadataBuilder::new()), ModuleType::CcipRead => Box::new(CcipReadIsmMetadataBuilder::new(message_builder)), + ModuleType::KaspaMultisig => Box::new(KaspaMetadataBuilder::new(message_builder)), _ => return Err(MetadataBuildError::UnsupportedModuleType(module_type)), }; let metadata = metadata_builder.build(ism_address, message, params).await?; diff --git a/rust/main/agents/relayer/src/msg/metadata/mod.rs b/rust/main/agents/relayer/src/msg/metadata/mod.rs index f3515b590a0..f917803c2f0 100644 --- a/rust/main/agents/relayer/src/msg/metadata/mod.rs +++ b/rust/main/agents/relayer/src/msg/metadata/mod.rs @@ -2,8 +2,9 @@ mod aggregation; mod base; mod base_builder; mod ccip_read; +pub mod dymension_kaspa; mod message_builder; -mod multisig; +pub mod multisig; mod null_metadata; mod routing; @@ -13,5 +14,7 @@ pub(crate) use base::{ MetadataBuildError, MetadataBuilder, }; #[allow(unused_imports)] -pub(crate) use base_builder::{BaseMetadataBuilder, BuildsBaseMetadata, IsmBuildMetricsParams}; +pub(crate) use base_builder::{ + BaseMetadataBuilder, BuildsBaseMetadata, DummyBuildsBaseMetadata, IsmBuildMetricsParams, +}; pub(crate) use message_builder::MessageMetadataBuilder; diff --git a/rust/main/agents/relayer/src/msg/metadata/multisig/base.rs b/rust/main/agents/relayer/src/msg/metadata/multisig/base.rs index a2f8938f3df..befcf389153 100644 --- a/rust/main/agents/relayer/src/msg/metadata/multisig/base.rs +++ b/rust/main/agents/relayer/src/msg/metadata/multisig/base.rs @@ -5,7 +5,7 @@ use derive_more::{AsRef, Deref}; use derive_new::new; use ethers::abi::Token; -use eyre::{Context, Result}; +use eyre::Result; use hyperlane_base::cache::FunctionCallCache; use hyperlane_base::settings::CheckpointSyncerBuildError; use hyperlane_base::MultisigCheckpointSyncer; @@ -51,7 +51,7 @@ pub trait MultisigIsmMetadataBuilder: AsRef + Send + Syn threshold: u8, message: &HyperlaneMessage, checkpoint_syncer: &MultisigCheckpointSyncer, - ) -> Result>; + ) -> Result, MetadataBuildError>; fn token_layout(&self) -> Vec; @@ -195,7 +195,6 @@ async fn metadata_build( message: &HyperlaneMessage, _params: MessageMetadataBuildParams, ) -> Result { - const CTX: &str = "When fetching MultisigIsm metadata"; let multisig_ism = ism_builder .as_ref() .base_builder() @@ -249,22 +248,18 @@ async fn metadata_build( } }; - if let Some(metadata) = ism_builder + let metadata = ism_builder .fetch_metadata(&validators, threshold, message, &checkpoint_syncer) .await - .context(CTX) - .map_err(|_| MetadataBuildError::CouldNotFetch)? - { - debug!(hyp_message=?message, ?metadata.checkpoint, "Found checkpoint with quorum"); - let formatted = ism_builder - .format_metadata(metadata) - .map_err(|_| MetadataBuildError::CouldNotFetch)?; - Ok(Metadata::new(formatted)) - } else { - info!( - hyp_message=?message, ?validators, threshold, ism=%multisig_ism.address(), - "Could not fetch metadata: Unable to reach quorum" - ); - Err(MetadataBuildError::CouldNotFetch) - } + .map_err(|err| MetadataBuildError::FailedToBuild(format!("fetch_metadata: {}", err)))? + .ok_or_else(|| { + // Detailed error already logged by fetch_metadata + MetadataBuildError::CouldNotFetch + })?; + + debug!(hyp_message=?message, ?metadata.checkpoint, "Found checkpoint with quorum"); + let formatted = ism_builder.format_metadata(metadata).map_err(|err| { + MetadataBuildError::FailedToBuild(format!("format_metadata error: {}", err)) + })?; + Ok(Metadata::new(formatted)) } diff --git a/rust/main/agents/relayer/src/msg/metadata/multisig/merkle_root_multisig.rs b/rust/main/agents/relayer/src/msg/metadata/multisig/merkle_root_multisig.rs index ce679309a78..ede7d92b856 100644 --- a/rust/main/agents/relayer/src/msg/metadata/multisig/merkle_root_multisig.rs +++ b/rust/main/agents/relayer/src/msg/metadata/multisig/merkle_root_multisig.rs @@ -4,12 +4,12 @@ use async_trait::async_trait; use derive_more::{AsRef, Deref}; use derive_new::new; -use eyre::{Context, Result}; +use eyre::Result; use hyperlane_base::MultisigCheckpointSyncer; use hyperlane_core::{unwrap_or_none_result, HyperlaneMessage, ModuleType, H256}; -use tracing::debug; +use tracing::{error, info}; -use crate::msg::metadata::MessageMetadataBuilder; +use crate::msg::metadata::{MessageMetadataBuilder, MetadataBuildError}; use super::base::{MetadataToken, MultisigIsmMetadataBuilder, MultisigMetadata}; @@ -39,22 +39,53 @@ impl MultisigIsmMetadataBuilder for MerkleRootMultisigMetadataBuilder { threshold: u8, message: &HyperlaneMessage, checkpoint_syncer: &MultisigCheckpointSyncer, - ) -> Result> { - const CTX: &str = "When fetching MerkleRootMultisig metadata"; + ) -> Result, MetadataBuildError> { + let origin_domain = self.base_builder().origin_domain().name(); + let message_nonce = message.nonce; + let highest_leaf_index = unwrap_or_none_result!( self.base_builder().highest_known_leaf_index().await, - debug!("Couldn't get highest known leaf index") + error!( + hyp_message = ?message, + origin_domain, + message_nonce, + "Merkle tree not synced: tree is empty. Message nonce is {}, need to sync to at least that index. Origin: {}", + message_nonce, + origin_domain + ) ); + + if message_nonce > highest_leaf_index { + error!( + hyp_message = ?message, + origin_domain, + message_nonce, + highest_leaf_index, + "Merkle tree not synced: tree synced to index {}, but message nonce is {}. Need {} more. Origin: {}", + highest_leaf_index, + message_nonce, + message_nonce - highest_leaf_index, + origin_domain + ); + return Ok(None); + } + let leaf_index = unwrap_or_none_result!( self.base_builder() .get_merkle_leaf_id_by_message_id(message.id()) .await - .context(CTX)?, - debug!( - hyp_message=?message, - "No merkle leaf found for message id, must have not been enqueued in the tree" + .map_err(|err| MetadataBuildError::FailedToBuild(err.to_string()))?, + error!( + hyp_message = ?message, + origin_domain, + message_nonce, + highest_leaf_index, + "Unexpected: merkle tree synced to index {} which covers message nonce {}, but leaf not found by message id. Possible db issue.", + highest_leaf_index, + message_nonce ) ); + let quorum_checkpoint = unwrap_or_none_result!( checkpoint_syncer .fetch_checkpoint_in_range( @@ -66,17 +97,24 @@ impl MultisigIsmMetadataBuilder for MerkleRootMultisigMetadataBuilder { self.base_builder().destination_domain(), ) .await - .context(CTX)?, - debug!( + .map_err(|err| MetadataBuildError::FailedToBuild(err.to_string()))?, + info!( + origin_domain, + leaf_index, + highest_leaf_index, + threshold, + validators_count = validators.len(), + "Quorum not reached: validators have not signed checkpoints in range [{}, {}], or not enough validators ({}/{}) have announced storage locations", leaf_index, - highest_leaf_index, "Couldn't get checkpoint in range" + highest_leaf_index, + threshold, + validators.len() ) ); let proof = self .base_builder() .get_proof(leaf_index, quorum_checkpoint.checkpoint.checkpoint) - .await - .context(CTX)?; + .await?; Ok(Some(MultisigMetadata::new( quorum_checkpoint, leaf_index, diff --git a/rust/main/agents/relayer/src/msg/metadata/multisig/message_id_multisig.rs b/rust/main/agents/relayer/src/msg/metadata/multisig/message_id_multisig.rs index bc90fb82eb9..f87d450a4ff 100644 --- a/rust/main/agents/relayer/src/msg/metadata/multisig/message_id_multisig.rs +++ b/rust/main/agents/relayer/src/msg/metadata/multisig/message_id_multisig.rs @@ -4,13 +4,13 @@ use async_trait::async_trait; use derive_more::{AsRef, Deref}; use derive_new::new; -use eyre::{Context, Result}; +use eyre::Result; use hyperlane_base::MultisigCheckpointSyncer; use hyperlane_core::{unwrap_or_none_result, HyperlaneMessage, ModuleType, H256}; -use tracing::{debug, warn}; +use tracing::{debug, info, warn}; use super::base::{MetadataToken, MultisigIsmMetadataBuilder, MultisigMetadata}; -use crate::msg::metadata::MessageMetadataBuilder; +use crate::msg::metadata::{MessageMetadataBuilder, MetadataBuildError}; #[derive(Debug, Clone, Deref, new, AsRef)] pub struct MessageIdMultisigMetadataBuilder(MessageMetadataBuilder); @@ -36,14 +36,13 @@ impl MultisigIsmMetadataBuilder for MessageIdMultisigMetadataBuilder { threshold: u8, message: &HyperlaneMessage, checkpoint_syncer: &MultisigCheckpointSyncer, - ) -> Result> { + ) -> Result, MetadataBuildError> { let message_id = message.id(); - const CTX: &str = "When fetching MessageIdMultisig metadata"; let leaf_index = unwrap_or_none_result!( self.base_builder() .get_merkle_leaf_id_by_message_id(message_id) .await - .context(CTX)?, + .map_err(|err| MetadataBuildError::FailedToBuild(err.to_string()))?, debug!( hyp_message=?message, "No merkle leaf found for message id, must have not been enqueued in the tree" @@ -63,8 +62,13 @@ impl MultisigIsmMetadataBuilder for MessageIdMultisigMetadataBuilder { checkpoint_syncer .fetch_checkpoint(validators, threshold as usize, leaf_index) .await - .context(CTX)?, - debug!("No quorum checkpoint found") + .map_err(|err| MetadataBuildError::FailedToBuild(err.to_string()))?, + info!( + leaf_index, + threshold, + validators_count = validators.len(), + "No quorum checkpoint found for leaf_index" + ) ); if quorum_checkpoint.checkpoint.message_id != message_id { @@ -109,7 +113,8 @@ mod tests { MessageIdMultisigMetadataBuilder, MultisigIsmMetadataBuilder, MultisigMetadata, }; use crate::msg::metadata::{ - IsmBuildMetricsParams, MessageMetadataBuildParams, MessageMetadataBuilder, MetadataBuilder, + IsmBuildMetricsParams, MessageMetadataBuildParams, MessageMetadataBuilder, + MetadataBuildError, MetadataBuilder, }; use crate::test_utils::mock_base_builder::build_mock_base_builder; @@ -198,7 +203,9 @@ mod tests { .get_proof .lock() .unwrap() - .push_back(Err(eyre::eyre!("No Proof"))); + .push_back(Err(MetadataBuildError::FailedToBuild( + "No proof found".into(), + ))); let ism_address = H256::zero(); let message_builder = { @@ -277,7 +284,9 @@ mod tests { .get_proof .lock() .unwrap() - .push_back(Err(eyre::eyre!("No Proof"))); + .push_back(Err(MetadataBuildError::FailedToBuild( + "No proof found".into(), + ))); let multisig_syncer = MultisigCheckpointSyncer::new(syncers_dyn, None); @@ -387,7 +396,9 @@ mod tests { .get_proof .lock() .unwrap() - .push_back(Err(eyre::eyre!("No Proof"))); + .push_back(Err(MetadataBuildError::FailedToBuild( + "No proof found".into(), + ))); let multisig_syncer = MultisigCheckpointSyncer::new(syncers_dyn, None); diff --git a/rust/main/agents/relayer/src/msg/op_batch.rs b/rust/main/agents/relayer/src/msg/op_batch.rs index 679e1fc1352..5be754dd667 100644 --- a/rust/main/agents/relayer/src/msg/op_batch.rs +++ b/rust/main/agents/relayer/src/msg/op_batch.rs @@ -216,7 +216,7 @@ mod tests { #[tokio::test] async fn test_handle_batch_result_succeeds() { let mut mock_mailbox = MockMailboxContract::new(); - let dummy_domain: HyperlaneDomain = KnownHyperlaneDomain::Alfajores.into(); + let dummy_domain: HyperlaneDomain = KnownHyperlaneDomain::Sepolia.into(); mock_mailbox.expect_supports_batching().return_const(true); mock_mailbox.expect_process_batch().returning(move |_ops| { @@ -241,7 +241,7 @@ mod tests { #[tokio::test] async fn test_handle_batch_result_fails() { let mut mock_mailbox = MockMailboxContract::new(); - let dummy_domain: HyperlaneDomain = KnownHyperlaneDomain::Alfajores.into(); + let dummy_domain: HyperlaneDomain = KnownHyperlaneDomain::Sepolia.into(); mock_mailbox.expect_supports_batching().return_const(true); mock_mailbox @@ -265,7 +265,7 @@ mod tests { #[tokio::test] async fn test_handle_batch_succeeds_eventually() { let mut mock_mailbox = MockMailboxContract::new(); - let dummy_domain: HyperlaneDomain = KnownHyperlaneDomain::Alfajores.into(); + let dummy_domain: HyperlaneDomain = KnownHyperlaneDomain::Sepolia.into(); let mut counter = 0; mock_mailbox.expect_supports_batching().return_const(true); @@ -295,7 +295,7 @@ mod tests { #[tokio::test] async fn test_handle_batch_result_fails_if_not_supported() { let mut mock_mailbox = MockMailboxContract::new(); - let dummy_domain: HyperlaneDomain = KnownHyperlaneDomain::Alfajores.into(); + let dummy_domain: HyperlaneDomain = KnownHyperlaneDomain::Sepolia.into(); mock_mailbox.expect_supports_batching().return_const(false); mock_mailbox.expect_process_batch().returning(move |_ops| { @@ -413,7 +413,7 @@ mod tests { ))), transaction_gas_limit: Default::default(), metrics: dummy_submission_metrics(), - application_operation_verifier: Some(Arc::new(DummyApplicationOperationVerifier {})), + application_operation_verifier: Arc::new(DummyApplicationOperationVerifier {}), }); let attempts = 2; diff --git a/rust/main/agents/relayer/src/msg/op_queue.rs b/rust/main/agents/relayer/src/msg/op_queue.rs index 45746bba3d9..45a970ca18b 100644 --- a/rust/main/agents/relayer/src/msg/op_queue.rs +++ b/rust/main/agents/relayer/src/msg/op_queue.rs @@ -1,7 +1,7 @@ use std::{cmp::Reverse, collections::BinaryHeap, sync::Arc}; use derive_new::new; -use hyperlane_core::{PendingOperation, PendingOperationStatus, QueueOperation}; +use hyperlane_core::{PendingOperation, PendingOperationStatus, QueueOperation, ReprepareReason}; use prometheus::{IntGauge, IntGaugeVec}; use tokio::sync::{broadcast::Receiver, Mutex}; use tracing::{debug, instrument}; @@ -142,10 +142,11 @@ impl OpQueue { return; } // update retry metrics - retry_response.matched += 1; + retry_response.matched = retry_response.matched.saturating_add(1); matched = true; }); if matched { + op.set_status(PendingOperationStatus::Retry(ReprepareReason::Manual)); op.reset_attempts(); } Reverse(op) diff --git a/rust/main/agents/relayer/src/msg/pending_message.rs b/rust/main/agents/relayer/src/msg/pending_message.rs index a8f726a10ef..f34e2ea4ae7 100644 --- a/rust/main/agents/relayer/src/msg/pending_message.rs +++ b/rust/main/agents/relayer/src/msg/pending_message.rs @@ -45,7 +45,8 @@ pub const CONFIRM_DELAY: Duration = if cfg!(any(test, feature = "test-utils")) { Duration::from_secs(5) } else { // Wait 10 min after submitting the message before confirming in normal/production mode - Duration::from_secs(60 * 10) + // Duration::from_secs(60 * 10) + Duration::from_secs(10) }; pub const RETRIEVED_MESSAGE_LOG: &str = "Message status retrieved from db"; @@ -80,7 +81,7 @@ pub struct MessageContext { pub transaction_gas_limit: Option, pub metrics: MessageSubmissionMetrics, /// Application operation verifier - pub application_operation_verifier: Option>, + pub application_operation_verifier: Arc, } /// A message that is pending processing and submission. @@ -403,6 +404,12 @@ impl PendingOperation for PendingMessage { // We use the estimated gas limit from the prior call to // `process_estimate_costs` to avoid a second gas estimation. + debug!( + message_id = ?self.message.id(), + gas_limit = ?state.gas_limit, + destination = ?self.message.destination, + "Submitting message process transaction" + ); let tx_outcome = self .ctx .destination_mailbox @@ -410,11 +417,22 @@ impl PendingOperation for PendingMessage { .await; match tx_outcome { Ok(outcome) => { + info!( + message_id = ?self.message.id(), + tx_id = ?outcome.transaction_id, + gas_used = ?outcome.gas_used, + "Process transaction submitted successfully" + ); self.set_operation_outcome(outcome, state.gas_limit).await; PendingOperationResult::Confirm(ConfirmReason::SubmittedBySelf) } Err(e) => { - error!(error=?e, "Error when processing message"); + error!( + message_id = ?self.message.id(), + error = ?e, + gas_limit = ?state.gas_limit, + "Process transaction submission failed" + ); self.clear_metadata(); return PendingOperationResult::Reprepare(ReprepareReason::ErrorSubmitting); } @@ -434,13 +452,27 @@ impl PendingOperation for PendingMessage { return PendingOperationResult::NotReady; } + debug!( + message_id = ?self.message.id(), + submission_outcome = ?self.submission_outcome, + destination = ?self.message.destination, + "Checking message delivery status on destination chain" + ); + let is_delivered = match self .ctx .destination_mailbox .delivered(self.message.id()) .await { - Ok(is_delivered) => is_delivered, + Ok(is_delivered) => { + debug!( + message_id = ?self.message.id(), + is_delivered = is_delivered, + "Delivery status check result" + ); + is_delivered + } Err(err) => { return self.on_reconfirm(Some(err), "Error confirming message delivery"); } @@ -452,12 +484,20 @@ impl PendingOperation for PendingMessage { .on_reconfirm(Some(err), "Error when recording message process success"); } info!( - submission=?self.submission_outcome, + message_id = ?self.message.id(), + submission = ?self.submission_outcome, "Message successfully processed" ); PendingOperationResult::Success } else { - warn!(message_id = ?self.message.id(), tx_outcome=?self.submission_outcome, "Transaction attempting to process message either reverted or was reorged"); + warn!( + message_id = ?self.message.id(), + tx_outcome = ?self.submission_outcome, + has_tx_outcome = self.submission_outcome.is_some(), + destination = ?self.message.destination, + num_retries = self.num_retries, + "Transaction attempting to process message either reverted or was reorged" + ); let span = info_span!( "Error: Transaction attempting to process message either reverted or was reorged", tx_outcome=?self.submission_outcome, @@ -520,7 +560,7 @@ impl PendingOperation for PendingMessage { } fn set_next_attempt_after(&mut self, delay: Duration) { - self.next_attempt_after = Some(Instant::now() + delay); + self.next_attempt_after = Instant::now().checked_add(delay); } fn reset_attempts(&mut self) { @@ -597,7 +637,7 @@ impl PendingMessage { fn next_attempt_after(num_retries: u32, max_retries: u32) -> Option { PendingMessage::calculate_msg_backoff(num_retries, max_retries, None) - .map(|dur| Instant::now() + dur) + .and_then(|dur| Instant::now().checked_add(dur)) } fn get_retries_or_skip( @@ -887,14 +927,14 @@ impl PendingMessage { } fn inc_attempts(&mut self) { - self.set_retries(self.num_retries + 1); + self.set_retries(self.num_retries.saturating_add(1)); self.last_attempted_at = Instant::now(); self.next_attempt_after = PendingMessage::calculate_msg_backoff( self.num_retries, self.max_retries, Some(self.message.id()), ) - .map(|dur| self.last_attempted_at + dur); + .and_then(|dur| self.last_attempted_at.checked_add(dur)); } fn set_retries(&mut self, retries: u32) { @@ -915,34 +955,44 @@ impl PendingMessage { /// Get duration we should wait before re-attempting to deliver a message /// given the number of retries. /// `pub(crate)` for testing purposes + /// + /// Note: backoff is capped at 15 minutes to ensure stuck messages are retried promptly. pub(crate) fn calculate_msg_backoff( num_retries: u32, max_retries: u32, message_id: Option, ) -> Option { - Some(Duration::from_secs(match num_retries { + const MAX_BACKOFF_SECS: u64 = 15 * 60; // 15 minutes + + let backoff_secs = match num_retries { i if i < 1 => return None, 1 => 5, 2 => 10, 3 => 30, 4 => 60, - i if (5..25).contains(&i) => 60 * 3, + i if (5..25).contains(&i) => 60u64.saturating_mul(3), // linearly increase from 5min to ~25min, adding 1.5min for each additional attempt - i if (25..40).contains(&i) => 60 * 5 + (i as u64 - 25) * 90, + i if (25..40).contains(&i) => 60u64 + .saturating_mul(5) + .saturating_add((i as u64).saturating_sub(25).saturating_mul(90)), // wait 30min for the next 5 attempts - i if (40..45).contains(&i) => 60 * 30, + i if (40..45).contains(&i) => 60u64.saturating_mul(30), // wait 60min for the next 5 attempts - i if (45..50).contains(&i) => 60 * 60, + i if (45..50).contains(&i) => 60u64.saturating_mul(60), // linearly increase the backoff time, adding 1h for each additional attempt i if (50..max_retries).contains(&i) => { - let hour: u64 = 60 * 60; - let two_hours: u64 = hour * 2; + let hour: u64 = 60u64.saturating_mul(60); + let two_hours: u64 = hour.saturating_mul(2); // To be extra safe, `max` to make sure it's at least 2 hours. - let target = two_hours.max((num_retries - 49) as u64 * two_hours); + let target = two_hours + .max((num_retries.saturating_sub(49) as u64).saturating_mul(two_hours)); // Schedule it at some random point in the next 6 hours to // avoid scheduling messages with the same # of retries // at the exact same time and starve new messages. - target + (rand::random::() % (6 * hour)) + + let six_hours = hour.saturating_mul(6); + let random_val = rand::random::(); + target.saturating_add(random_val.overflowing_rem(six_hours).0) } // after `max_message_retries`, the message is considered undeliverable // and the backoff is set as far into the future as possible @@ -956,7 +1006,9 @@ impl PendingMessage { } chrono::Duration::weeks(10).num_seconds() as u64 } - })) + }; + + Some(Duration::from_secs(backoff_secs.min(MAX_BACKOFF_SECS))) } async fn clarify_reason(&self, reason: ReprepareReason) -> Option { @@ -965,7 +1017,6 @@ impl PendingMessage { match self .ctx .application_operation_verifier - .as_ref()? .verify(&self.app_context, &self.message) .await { @@ -1038,6 +1089,13 @@ impl PendingMessage { warn!(count, "Max validator count reached"); self.on_reprepare(Some(err), ReprepareReason::ErrorBuildingMetadata) } + MetadataBuildError::MerkleRootMismatch { + root, + canonical_root, + } => { + warn!(?root, ?canonical_root, "Merkle root mismatch"); + self.on_reprepare(Some(err), ReprepareReason::ErrorBuildingMetadata) + } }); let build_metadata_end = Instant::now(); diff --git a/rust/main/agents/relayer/src/prover.rs b/rust/main/agents/relayer/src/prover.rs index b5f41fc2776..ca6042cac2c 100644 --- a/rust/main/agents/relayer/src/prover.rs +++ b/rust/main/agents/relayer/src/prover.rs @@ -60,8 +60,8 @@ impl Prover { /// /// This will fail if the underlying tree is full. pub fn ingest(&mut self, element: H256) -> Result { - self.count += 1; self.tree.push_leaf(element, TREE_DEPTH)?; + self.count = self.count.saturating_add(1); Ok(self.tree.hash()) } diff --git a/rust/main/agents/relayer/src/relayer.rs b/rust/main/agents/relayer/src/relayer.rs index bebcf6d5423..2b3789a5ea5 100644 --- a/rust/main/agents/relayer/src/relayer.rs +++ b/rust/main/agents/relayer/src/relayer.rs @@ -9,49 +9,28 @@ use std::{ use async_trait::async_trait; use derive_more::AsRef; use eyre::Result; -use futures::future::join_all; use futures_util::future::try_join_all; use tokio::{ sync::{ broadcast::Sender as BroadcastSender, mpsc::{self, Receiver as MpscReceiver, UnboundedSender}, - RwLock, }, task::JoinHandle, }; use tokio_metrics::TaskMonitor; use tracing::{debug, error, info, info_span, warn, Instrument}; -use hyperlane_base::{ - broadcast::BroadcastMpscSender, - cache::{LocalCache, MeteredCache, MeteredCacheConfig, OptionalCache}, - cursors::Indexable, - db::{HyperlaneRocksDB, DB}, - metrics::{AgentMetrics, ChainSpecificMetricsUpdater}, - settings::{ChainConf, IndexSettings, SequenceIndexer, TryFromWithMetrics}, - AgentMetadata, BaseAgent, ChainMetrics, ContractSyncMetrics, ContractSyncer, CoreMetrics, - HyperlaneAgentCore, RuntimeMetrics, SyncOptions, -}; -use hyperlane_core::{ - rpc_clients::call_and_retry_n_times, ChainCommunicationError, ChainResult, ContractSyncCursor, - HyperlaneDomain, HyperlaneDomainProtocol, HyperlaneLogStore, HyperlaneMessage, - HyperlaneSequenceAwareIndexerStoreReader, HyperlaneWatermarkedLogStore, InterchainGasPayment, - Mailbox, MerkleTreeInsertion, QueueOperation, ValidatorAnnounce, H512, U256, -}; -use hyperlane_operation_verifier::ApplicationOperationVerifier; - -use crate::{db_loader::DbLoader, server::ENDPOINT_MESSAGES_QUEUE_SIZE}; +use super::msg::metadata::dymension_kaspa::PendingMessageMetadataGetter; +use crate::{db_loader::DbLoader, relayer::origin::Origin, server::ENDPOINT_MESSAGES_QUEUE_SIZE}; use crate::{ db_loader::DbLoaderExt, merkle_tree::db_loader::{MerkleTreeDbLoader, MerkleTreeDbLoaderMetrics}, }; use crate::{ - merkle_tree::builder::MerkleTreeBuilder, metrics::message_submission::MessageSubmissionMetrics, msg::{ blacklist::AddressBlacklist, db_loader::{MessageDbLoader, MessageDbLoaderMetrics}, - gas_payment::GasPaymentEnforcer, message_processor::{MessageProcessor, MessageProcessorMetrics}, metadata::{ BaseMetadataBuilder, DefaultIsmCache, IsmAwareAppContextClassifier, @@ -62,11 +41,30 @@ use crate::{ server::{self as relayer_server}, settings::{matching_list::MatchingList, RelayerSettings}, }; - -use destination::Destination; +use dymension_kaspa::{is_dym, is_kas, KaspaProvider}; +use hyperlane_base::kas_hack::logic_loop::Foo as KaspaBridgeFoo; +use hyperlane_base::kas_hack::{format_ad_hoc_signatures, run_migration_with_sync}; +use hyperlane_base::{ + broadcast::BroadcastMpscSender, + cache::{LocalCache, MeteredCache, MeteredCacheConfig, OptionalCache}, + db::{HyperlaneRocksDB, DB}, + metrics::{AgentMetrics, ChainSpecificMetricsUpdater}, + settings::IndexSettings, + AgentMetadata, BaseAgent, ChainMetrics, ContractSyncMetrics, ContractSyncer, CoreMetrics, + HyperlaneAgentCore, RuntimeMetrics, SyncOptions, +}; +use hyperlane_core::{ + rpc_clients::call_and_retry_n_times, ChainCommunicationError, ChainResult, ContractSyncCursor, + HyperlaneDomain, HyperlaneMessage, InterchainGasPayment, MerkleTreeInsertion, QueueOperation, + Signature, H512, U256, +}; +use hyperlane_cosmos::native::CosmosNativeMailbox; use lander::DispatcherMetrics; +use destination::{Destination, FactoryError}; + mod destination; +mod origin; const CURSOR_BUILDING_ERROR: &str = "Error building cursor for origin"; const CURSOR_INSTANTIATION_ATTEMPTS: usize = 10; @@ -82,18 +80,11 @@ struct ContextKey { #[derive(AsRef)] pub struct Relayer { origin_chains: HashSet, - destination_chains: HashMap, #[as_ref] core: HyperlaneAgentCore, - message_syncs: HashMap>>, - interchain_gas_payment_syncs: - Option>>>, /// Context data for each (origin, destination) chain pair a message can be /// sent between msg_ctxs: HashMap>, - prover_syncs: HashMap>>, - merkle_tree_hook_syncs: HashMap>>, - dbs: HashMap, /// The original reference to the relayer cache _cache: OptionalCache>, message_whitelist: Arc, @@ -112,6 +103,10 @@ pub struct Relayer { runtime_metrics: RuntimeMetrics, /// Tokio console server pub tokio_console_server: Option, + dymension_kaspa_args: Option, + + /// The origin chains and their associated structures + origins: HashMap, /// The destination chains and their associated structures destinations: HashMap, } @@ -122,7 +117,7 @@ impl Debug for Relayer { f, "Relayer {{ origin_chains: {:?}, destination_chains: {:?}, message_whitelist: {:?}, message_blacklist: {:?}, address_blacklist: {:?}, transaction_gas_limit: {:?}, skip_transaction_gas_limit_for: {:?}, allow_local_checkpoint_syncers: {:?} }}", self.origin_chains, - self.destination_chains, + self.destinations.values(), self.message_whitelist, self.message_blacklist, self.address_blacklist, @@ -176,34 +171,17 @@ impl BaseAgent for Relayer { let cache = OptionalCache::new(inner_cache); debug!(elapsed = ?start_entity_init.elapsed(), event = "initialized cache", "Relayer startup duration measurement"); - start_entity_init = Instant::now(); let db = DB::from_path(&settings.db)?; - let dbs = settings - .origin_chains - .iter() - .map(|origin| (origin.clone(), HyperlaneRocksDB::new(origin, db.clone()))) - .collect::>(); - debug!(elapsed = ?start_entity_init.elapsed(), event = "initialized databases", "Relayer startup duration measurement"); - - start_entity_init = Instant::now(); - let application_operation_verifiers = - Self::build_application_operation_verifiers(&settings, &core_metrics, &chain_metrics) - .await; - debug!(elapsed = ?start_entity_init.elapsed(), event = "initialized application operation verifiers", "Relayer startup duration measurement"); start_entity_init = Instant::now(); - let mailboxes = Self::build_mailboxes(&settings, &core_metrics, &chain_metrics).await; - debug!(elapsed = ?start_entity_init.elapsed(), event = "initialized mailbox", "Relayer startup duration measurement"); - - start_entity_init = Instant::now(); - let validator_announces = - Self::build_validator_announces(&settings, &core_metrics, &chain_metrics).await; - debug!(elapsed = ?start_entity_init.elapsed(), event = "initialized validator announces", "Relayer startup duration measurement"); + let origins = + Self::build_origins(&settings, db.clone(), core_metrics.clone(), &chain_metrics).await; + debug!(elapsed = ?start_entity_init.elapsed(), event = "initialized origin chains", "Relayer startup duration measurement"); start_entity_init = Instant::now(); let dispatcher_metrics = DispatcherMetrics::new(core_metrics.registry()) .expect("Creating dispatcher metrics is infallible"); - let destinations = Self::build_destinations( + let mut destinations = Self::build_destinations( &settings, db.clone(), core_metrics.clone(), @@ -213,52 +191,7 @@ impl BaseAgent for Relayer { .await; debug!(elapsed = ?start_entity_init.elapsed(), event = "initialized destination chains", "Relayer startup duration measurement"); - start_entity_init = Instant::now(); - let contract_sync_metrics = Arc::new(ContractSyncMetrics::new(&core_metrics)); - let stores: HashMap<_, _> = dbs - .iter() - .map(|(d, db)| (d.clone(), Arc::new(db.clone()))) - .collect(); - let message_syncs = Self::build_contract_syncs( - &settings, - &core_metrics, - &chain_metrics, - &contract_sync_metrics, - stores.clone(), - "message", - ) - .await; - debug!(elapsed = ?start_entity_init.elapsed(), event = "initialized message syncs", "Relayer startup duration measurement"); - - start_entity_init = Instant::now(); - let interchain_gas_payment_syncs = if settings.igp_indexing_enabled { - let igp_syncs = Self::build_contract_syncs( - &settings, - &core_metrics, - &chain_metrics, - &contract_sync_metrics, - stores.clone(), - "interchain gas payments", - ) - .await; - Some(igp_syncs) - } else { - None - }; - debug!(elapsed = ?start_entity_init.elapsed(), event = "initialized IGP syncs", "Relayer startup duration measurement"); - - start_entity_init = Instant::now(); - let merkle_tree_hook_syncs = Self::build_contract_syncs( - &settings, - &core_metrics, - &chain_metrics, - &contract_sync_metrics, - stores.clone(), - "merkle tree hook syncs", - ) - .await; - - debug!(elapsed = ?start_entity_init.elapsed(), event = "initialized merkle tree hook syncs", "Relayer startup duration measurement"); + let dymension_args = Self::get_dymension_kaspa_args(&mut destinations, &origins).await?; let message_whitelist = Arc::new(settings.whitelist); let message_blacklist = Arc::new(settings.blacklist); @@ -275,140 +208,35 @@ impl BaseAgent for Relayer { "Whitelist configuration" ); - // provers by origin chain - start_entity_init = Instant::now(); - let prover_syncs = settings - .origin_chains - .iter() - .map(|origin| { - ( - origin.clone(), - Arc::new(RwLock::new(MerkleTreeBuilder::new())), - ) - }) - .collect::>(); - debug!(elapsed = ?start_entity_init.elapsed(), event = "initialized prover syncs", "Relayer startup duration measurement"); - - info!(gas_enforcement_policies=?settings.gas_payment_enforcement, "Gas enforcement configuration"); - - // need one of these per origin chain due to the database scoping even though - // the config itself is the same - start_entity_init = Instant::now(); - let gas_payment_enforcers: HashMap<_, _> = settings - .origin_chains - .iter() - .filter_map(|domain| match dbs.get(domain) { - Some(db) => { - let gas_payment_enforcer = Arc::new(RwLock::new(GasPaymentEnforcer::new( - settings.gas_payment_enforcement.clone(), - db.clone(), - ))); - Some((domain.clone(), gas_payment_enforcer)) - } - None => { - tracing::error!(?domain, "Missing DB"); - None - } - }) - .collect(); - debug!(elapsed = ?start_entity_init.elapsed(), event = "initialized gas payment enforcers", "Relayer startup duration measurement"); - - // only iterate through destination chains that were successfully instantiated - start_entity_init = Instant::now(); - let mut ccip_signer_futures: Vec<_> = Vec::with_capacity(mailboxes.len()); - for destination in mailboxes.keys() { - let destination_chain_setup = match core.settings.chain_setup(destination) { - Ok(setup) => setup.clone(), - Err(err) => { - tracing::error!(?destination, ?err, "Destination chain setup failed"); - continue; - } - }; - let signer = destination_chain_setup.signer.clone(); - let future = async move { - if !matches!( - destination.domain_protocol(), - HyperlaneDomainProtocol::Ethereum - ) { - return (destination, None); - } - let signer = if let Some(builder) = signer { - match builder.build::().await { - Ok(signer) => Some(signer), - Err(err) => { - warn!(error = ?err, "Failed to build Ethereum signer for CCIP-read ISM. "); - None - } - } - } else { - None - }; - (destination, signer) - }; - ccip_signer_futures.push(future); - } - let ccip_signers = join_all(ccip_signer_futures) - .await - .into_iter() - .collect::>(); - debug!(elapsed = ?start_entity_init.elapsed(), event = "initialized ccip signers", "Relayer startup duration measurement"); - start_entity_init = Instant::now(); let mut msg_ctxs = HashMap::new(); - let mut destination_chains = HashMap::new(); - for (destination, dest_mailbox) in mailboxes.iter() { - let destination_chain_setup = match core.settings.chain_setup(destination) { - Ok(setup) => setup.clone(), - Err(err) => { - tracing::error!(?destination, ?err, "Destination chain setup failed"); - continue; - } - }; - destination_chains.insert(destination.clone(), destination_chain_setup.clone()); + for (destination_domain, destination) in destinations.iter() { + let application_operation_verifier = destination.application_operation_verifier.clone(); + let destination_chain_setup = destination.chain_conf.clone(); + let destination_mailbox = destination.mailbox.clone(); + let ccip_signer = destination.ccip_signer.clone(); + let transaction_gas_limit: Option = - if skip_transaction_gas_limit_for.contains(&destination.id()) { + if skip_transaction_gas_limit_for.contains(&destination_domain.id()) { None } else { transaction_gas_limit }; - let application_operation_verifier = application_operation_verifiers.get(destination); + let default_ism_getter = DefaultIsmCache::new(destination_mailbox.clone()); // only iterate through origin chains that were successfully instantiated - for (origin, validator_announce) in validator_announces.iter() { - let db = match dbs.get(origin) { - Some(db) => db.clone(), - None => { - tracing::error!(origin=?origin.name(), "DB missing"); - continue; - } - }; - let default_ism_getter = DefaultIsmCache::new(dest_mailbox.clone()); - let origin_chain_setup = match core.settings.chain_setup(origin) { - Ok(chain_setup) => chain_setup.clone(), - Err(err) => { - tracing::error!(origin=?origin.name(), ?err, "Origin chain setup failed"); - continue; - } - }; - let prover_sync = match prover_syncs.get(origin) { - Some(p) => p.clone(), - None => { - tracing::error!(origin = origin.name(), "Missing prover sync"); - continue; - } - }; - let origin_gas_payment_enforcer = match gas_payment_enforcers.get(origin) { - Some(p) => p.clone(), - None => { - tracing::error!(origin = origin.name(), "Missing gas payment enforcer"); - continue; - } - }; + for (origin_domain, origin) in origins.iter() { + let db = &origin.database; + + let origin_chain_setup = origin.chain_conf.clone(); + let prover_sync = origin.prover_sync.clone(); + let origin_gas_payment_enforcer = origin.gas_payment_enforcer.clone(); + let validator_announce = origin.validator_announce.clone(); // Extract optional Ethereum signer for CCIP-read authentication let metadata_builder = BaseMetadataBuilder::new( - origin.clone(), + origin_domain.clone(), destination_chain_setup.clone(), prover_sync, validator_announce.clone(), @@ -424,24 +252,28 @@ impl BaseAgent for Relayer { default_ism_getter.clone(), settings.ism_cache_configs.clone(), ), - ccip_signers.get(destination).cloned().flatten(), + ccip_signer.clone(), origin_chain_setup.ignore_reorg_reports, ); msg_ctxs.insert( ContextKey { - origin: origin.clone(), - destination: destination.clone(), + origin: origin_domain.clone(), + destination: destination_domain.clone(), }, Arc::new(MessageContext { - destination_mailbox: dest_mailbox.clone(), - origin_db: Arc::new(db), + destination_mailbox: destination_mailbox.clone(), + origin_db: Arc::new(db.clone()), cache: cache.clone(), metadata_builder: Arc::new(metadata_builder), origin_gas_payment_enforcer, transaction_gas_limit, - metrics: MessageSubmissionMetrics::new(&core_metrics, origin, destination), - application_operation_verifier: application_operation_verifier.cloned(), + metrics: MessageSubmissionMetrics::new( + &core_metrics, + origin_domain, + destination_domain, + ), + application_operation_verifier: application_operation_verifier.clone(), }), ); } @@ -451,16 +283,10 @@ impl BaseAgent for Relayer { debug!(elapsed = ?start.elapsed(), event = "fully initialized", "Relayer startup duration measurement"); Ok(Self { - dbs, _cache: cache, origin_chains: settings.origin_chains, - destination_chains, msg_ctxs, core, - message_syncs, - interchain_gas_payment_syncs, - prover_syncs, - merkle_tree_hook_syncs, message_whitelist, message_blacklist, address_blacklist, @@ -474,12 +300,31 @@ impl BaseAgent for Relayer { chain_metrics, runtime_metrics, tokio_console_server: Some(tokio_console_server), + dymension_kaspa_args: dymension_args, + origins, destinations, }) } #[allow(clippy::async_yields_async)] async fn run(mut self) { + // If migration mode is enabled, run migration and exit + if let Some(target) = self.get_migration_target() { + info!(target, "Migration mode: migrating escrow to new address"); + match self.run_escrow_migration(&target).await { + Ok(tx_ids) => { + info!(tx_count = tx_ids.len(), "Migration completed successfully"); + for (i, tx_id) in tx_ids.iter().enumerate() { + info!(index = i, tx_id = tx_id, "Migration transaction"); + } + } + Err(e) => { + error!(error = ?e, "Migration failed"); + } + } + return; + } + let start = Instant::now(); let mut start_entity_init = Instant::now(); @@ -506,10 +351,12 @@ impl BaseAgent for Relayer { let sender = BroadcastSender::new(ENDPOINT_MESSAGES_QUEUE_SIZE); // send channels by destination chain - let mut send_channels = HashMap::with_capacity(self.destination_chains.len()); - let mut prep_queues = HashMap::with_capacity(self.destination_chains.len()); + let mut send_channels = HashMap::with_capacity(self.destinations.len()); + let mut prep_queues = HashMap::with_capacity(self.destinations.len()); start_entity_init = Instant::now(); - for (dest_domain, dest_conf) in &self.destination_chains { + for (dest_domain, destination) in &self.destinations { + let dest_conf = &destination.chain_conf; + let (send_channel, receive_channel) = mpsc::unbounded_channel::(); send_channels.insert(dest_domain.id(), send_channel); @@ -518,8 +365,8 @@ impl BaseAgent for Relayer { .get(dest_domain) .and_then(|d| d.dispatcher_entrypoint.clone()); - let db = match self.dbs.get(dest_domain) { - Some(db) => db.clone(), + let db = match self.origins.get(dest_domain) { + Some(origin) => origin.database.clone(), None => { tracing::error!(domain=?dest_domain.name(), "DB missing"); continue; @@ -602,17 +449,24 @@ impl BaseAgent for Relayer { debug!(elapsed = ?start_entity_init.elapsed(), event = "started processors", "Relayer startup duration measurement"); start_entity_init = Instant::now(); - for origin in &self.origin_chains { - let maybe_broadcaster = self - .message_syncs - .get(origin) - .and_then(|sync| sync.get_broadcaster()); + for (origin_domain, origin) in self.origins.iter() { + if is_kas(&origin.domain) && self.dymension_kaspa_args.is_some() { + self.launch_dymension_kaspa_tasks( + origin, + &mut tasks, + task_monitor.clone(), + send_channels.clone(), + ) + .await; + continue; + } + let maybe_broadcaster = origin.message_sync.get_broadcaster(); let message_sync = match self.run_message_sync(origin, task_monitor.clone()).await { Ok(task) => task, Err(err) => { Self::record_critical_error( - origin, + origin_domain, &self.chain_metrics, &err, "Failed to run message sync", @@ -622,29 +476,26 @@ impl BaseAgent for Relayer { }; tasks.push(message_sync); - if let Some(interchain_gas_payment_syncs) = &self.interchain_gas_payment_syncs { - let interchain_gas_payment_sync = match self - .run_interchain_gas_payment_sync( - origin, - interchain_gas_payment_syncs, - BroadcastMpscSender::map_get_receiver(maybe_broadcaster.as_ref()).await, - task_monitor.clone(), - ) - .await - { - Ok(task) => task, - Err(err) => { - Self::record_critical_error( - origin, - &self.chain_metrics, - &err, - "Failed to run interchain gas payment sync", - ); - continue; - } - }; - tasks.push(interchain_gas_payment_sync); - } + let interchain_gas_payment_syncs = match self + .run_interchain_gas_payment_syncs( + origin, + BroadcastMpscSender::map_get_receiver(maybe_broadcaster.as_ref()).await, + task_monitor.clone(), + ) + .await + { + Ok(sync_tasks) => sync_tasks, + Err(err) => { + Self::record_critical_error( + &origin.domain, + &self.chain_metrics, + &err, + "Failed to run interchain gas payment syncs", + ); + continue; + } + }; + tasks.extend(interchain_gas_payment_syncs); let merkle_tree_hook_sync = match self .run_merkle_tree_hook_sync( @@ -657,7 +508,7 @@ impl BaseAgent for Relayer { Ok(task) => task, Err(err) => { Self::record_critical_error( - origin, + origin_domain, &self.chain_metrics, &err, "Failed to run merkle tree hook sync", @@ -675,7 +526,7 @@ impl BaseAgent for Relayer { Ok(task) => task, Err(err) => { Self::record_critical_error( - origin, + origin_domain, &self.chain_metrics, &err, "Failed to run message db loader", @@ -690,7 +541,7 @@ impl BaseAgent for Relayer { Ok(task) => task, Err(err) => { Self::record_critical_error( - origin, + origin_domain, &self.chain_metrics, &err, "Failed to run merkle tree db loader", @@ -706,19 +557,67 @@ impl BaseAgent for Relayer { start_entity_init = Instant::now(); // create a db mapping for server handlers - let dbs: HashMap = - self.dbs.iter().map(|(k, v)| (k.id(), v.clone())).collect(); + let dbs: HashMap = self + .origins + .iter() + .map(|(origin_domain, origin)| (origin_domain.id(), origin.database.clone())) + .chain( + self.destinations + .iter() + .map(|(dest_domain, dest)| (dest_domain.id(), dest.database.clone())), + ) + .collect(); let gas_enforcers: HashMap<_, _> = self .msg_ctxs .iter() .map(|(key, ctx)| (key.origin.clone(), ctx.origin_gas_payment_enforcer.clone())) .collect(); - let relayer_router = relayer_server::Server::new(self.destination_chains.len()) + + let msg_ctxs = self + .msg_ctxs + .iter() + .map(|(key, value)| ((key.origin.id(), key.destination.id()), value.clone())) + .collect(); + let prover_syncs: HashMap<_, _> = self + .origins + .iter() + .map(|(key, origin)| (key.id(), origin.prover_sync.clone())) + .collect(); + + // Build kaspa recovery config if available + let kaspa_recovery = if let Some(dym_args) = self.dymension_kaspa_args.as_ref() { + let sender_guard = dym_args.recovery_sender.blocking_read(); + sender_guard + .as_ref() + .map(|sender| relayer_server::KaspaRecoveryConfig { + sender: sender.clone(), + rest_api_url: dym_args + .kas_provider + .conf() + .kaspa_urls_rest + .first() + .map(|u| u.to_string()) + .unwrap_or_default(), + escrow_address: dym_args.kas_provider.escrow_address().to_string(), + }) + } else { + None + }; + + let relayer_router = relayer_server::Server::new(self.destinations.len()) .with_op_retry(sender.clone()) .with_message_queue(prep_queues) .with_dbs(dbs) .with_gas_enforcers(gas_enforcers) + .with_msg_ctxs(msg_ctxs) + .with_prover_sync(prover_syncs) + .with_kaspa_db( + self.dymension_kaspa_args + .as_ref() + .and_then(|dym_args| dym_args.kas_provider.kaspa_db().cloned()), + ) + .with_kaspa_recovery(kaspa_recovery) .router(); let server = self @@ -780,26 +679,28 @@ impl Relayer { async fn run_message_sync( &self, - origin: &HyperlaneDomain, + origin: &Origin, task_monitor: TaskMonitor, ) -> eyre::Result> { - let origin = origin.clone(); - let contract_sync = self - .message_syncs - .get(&origin) - .cloned() - .ok_or_else(|| eyre::eyre!("No message sync found"))?; - let index_settings = self.as_ref().settings.chains[&origin].index_settings(); + let origin_domain = origin.domain.clone(); + let contract_sync = origin.message_sync.clone(); + + let index_settings = origin.chain_conf.index_settings().clone(); let chain_metrics = self.chain_metrics.clone(); - let name = Self::contract_sync_task_name("message::", origin.name()); + let name = Self::contract_sync_task_name("message::", origin_domain.name()); Ok(tokio::task::Builder::new() .name(&name) .spawn(TaskMonitor::instrument( &task_monitor, async move { - Self::message_sync_task(&origin, contract_sync, index_settings, chain_metrics) - .await; + Self::message_sync_task( + &origin_domain, + contract_sync, + index_settings, + chain_metrics, + ) + .await; } .instrument(info_span!("MessageSync")), )) @@ -826,38 +727,86 @@ impl Relayer { info!(chain = origin.name(), label, "contract sync task exit"); } + async fn run_interchain_gas_payment_syncs( + &self, + origin: &Origin, + tx_id_receiver: Option>, + task_monitor: TaskMonitor, + ) -> eyre::Result>> { + let contract_syncs = &origin.interchain_gas_payment_syncs; + if contract_syncs.is_empty() { + return Ok(Vec::new()); + } + + let mut handles = Vec::with_capacity(contract_syncs.len()); + let mut tx_id_receiver = tx_id_receiver; + + for (i, contract_sync) in contract_syncs.iter().enumerate() { + let chain_metrics = self.chain_metrics.clone(); + let origin_domain = origin.domain.clone(); + let index_settings = origin.chain_conf.index_settings().clone(); + let contract_sync = contract_sync.clone(); + + let receiver = if i == 0 { tx_id_receiver.take() } else { None }; + + let suffix = if i == 0 { + String::new() + } else { + format!("_{}", i) + }; + let name = format!( + "{}{}", + Self::contract_sync_task_name("gas_payment::", origin_domain.name()), + suffix + ); + + let handle = tokio::task::Builder::new() + .name(&name) + .spawn(TaskMonitor::instrument( + &task_monitor, + async move { + Self::interchain_gas_payments_sync_task( + &origin_domain, + index_settings, + contract_sync, + chain_metrics, + receiver, + ) + .await; + } + .instrument(info_span!("IgpSync", igp_index = i)), + )) + .expect("spawning tokio task from Builder is infallible"); + handles.push(handle); + } + Ok(handles) + } + async fn run_interchain_gas_payment_sync( &self, - origin: &HyperlaneDomain, - interchain_gas_payment_syncs: &HashMap< - HyperlaneDomain, - Arc>, - >, + origin: &Origin, tx_id_receiver: Option>, task_monitor: TaskMonitor, - ) -> eyre::Result> { - let origin = origin.clone(); - let index_settings = self - .as_ref() - .settings - .chains - .get(&origin) - .map(|settings| settings.index_settings()) - .ok_or_else(|| eyre::eyre!("Error finding chain index settings"))?; - let contract_sync = interchain_gas_payment_syncs - .get(&origin) - .cloned() - .ok_or_else(|| eyre::eyre!("No interchain gas payment sync found"))?; + ) -> eyre::Result>> { + let contract_syncs = &origin.interchain_gas_payment_syncs; + if contract_syncs.is_empty() { + return Ok(None); + } + let contract_sync = contract_syncs[0].clone(); let chain_metrics = self.chain_metrics.clone(); - let name = Self::contract_sync_task_name("gas_payment::", origin.name()); - Ok(tokio::task::Builder::new() + let origin_domain = origin.domain.clone(); + let index_settings = origin.chain_conf.index_settings().clone(); + + let name = Self::contract_sync_task_name("gas_payment::", origin_domain.name()); + + let handle = tokio::task::Builder::new() .name(&name) .spawn(TaskMonitor::instrument( &task_monitor, async move { Self::interchain_gas_payments_sync_task( - &origin, + &origin_domain, index_settings, contract_sync, chain_metrics, @@ -867,7 +816,8 @@ impl Relayer { } .instrument(info_span!("IgpSync")), )) - .expect("spawning tokio task from Builder is infallible")) + .expect("spawning tokio task from Builder is infallible"); + Ok(Some(handle)) } async fn interchain_gas_payments_sync_task( @@ -877,10 +827,12 @@ impl Relayer { chain_metrics: ChainMetrics, tx_id_receiver: Option>, ) { - let cursor_instantiation_result = - Self::instantiate_cursor_with_retries(contract_sync.clone(), index_settings.clone()) - .await; - let cursor = match cursor_instantiation_result { + let cursor = match Self::instantiate_cursor_with_retries( + contract_sync.clone(), + index_settings.clone(), + ) + .await + { Ok(cursor) => cursor, Err(err) => { Self::record_critical_error(origin, &chain_metrics, &err, CURSOR_BUILDING_ERROR); @@ -897,35 +849,24 @@ impl Relayer { async fn run_merkle_tree_hook_sync( &self, - origin: &HyperlaneDomain, + origin: &Origin, tx_id_receiver: Option>, task_monitor: TaskMonitor, ) -> eyre::Result> { - let origin = origin.clone(); let chain_metrics = self.chain_metrics.clone(); - let index_settings = self - .as_ref() - .settings - .chains - .get(&origin) - .map(|settings| settings.index_settings()) - .ok_or_else(|| eyre::eyre!("Error finding chain index settings"))?; - let contract_sync = self - .merkle_tree_hook_syncs - .get(&origin) - .cloned() - .ok_or_else(|| eyre::eyre!("No merkle tree hook sync found"))?; - - let origin_name = origin.name().to_string(); - let name = Self::contract_sync_task_name("merkle_tree::", &origin_name); + let origin_domain = origin.domain.clone(); + let index_settings = origin.chain_conf.index_settings().clone(); + let contract_sync = origin.merkle_tree_hook_sync.clone(); + + let name = Self::contract_sync_task_name("merkle_tree::", origin_domain.name()); Ok(tokio::task::Builder::new() .name(&name) .spawn(TaskMonitor::instrument( &task_monitor, async move { Self::merkle_tree_hook_sync_task( - &origin, + &origin_domain, index_settings, contract_sync, chain_metrics, @@ -969,18 +910,21 @@ impl Relayer { fn run_message_db_loader( &self, - origin: &HyperlaneDomain, + origin: &Origin, send_channels: HashMap>, task_monitor: TaskMonitor, ) -> eyre::Result> { - let metrics = - MessageDbLoaderMetrics::new(&self.core.metrics, origin, self.destination_chains.keys()); + let metrics = MessageDbLoaderMetrics::new( + &self.core.metrics, + &origin.domain, + self.destinations.keys(), + ); let destination_ctxs: HashMap<_, _> = self - .destination_chains + .destinations .keys() .filter_map(|destination| { let key = ContextKey { - origin: origin.clone(), + origin: origin.domain.clone(), destination: destination.clone(), }; let context = self @@ -991,11 +935,11 @@ impl Relayer { if context.is_none() { let err_msg = format!( "No message context found for origin {} and destination {}", - origin.name(), + origin.domain.name(), destination.name() ); Self::record_critical_error( - origin, + &origin.domain, &self.chain_metrics, &ChainCommunicationError::CustomError(err_msg.clone()), &err_msg, @@ -1006,14 +950,8 @@ impl Relayer { }) .collect(); - let db = self - .dbs - .get(origin) - .cloned() - .ok_or_else(|| eyre::eyre!("Db not found"))?; - let message_db_loader = MessageDbLoader::new( - db, + origin.database.clone(), self.message_whitelist.clone(), self.message_blacklist.clone(), self.address_blacklist.clone(), @@ -1031,22 +969,13 @@ impl Relayer { fn run_merkle_tree_db_loader( &self, - origin: &HyperlaneDomain, + origin: &Origin, task_monitor: TaskMonitor, ) -> eyre::Result> { - let metrics = MerkleTreeDbLoaderMetrics::new(&self.core.metrics, origin); - let db = self - .dbs - .get(origin) - .cloned() - .ok_or_else(|| eyre::eyre!("Db not found"))?; - let prover_sync = self - .prover_syncs - .get(origin) - .cloned() - .ok_or_else(|| eyre::eyre!("No prover sync found"))?; - - let merkle_tree_db_loader = MerkleTreeDbLoader::new(db, metrics, prover_sync); + let metrics = MerkleTreeDbLoaderMetrics::new(&self.core.metrics, &origin.domain); + + let merkle_tree_db_loader = + MerkleTreeDbLoader::new(origin.database.clone(), metrics, origin.prover_sync.clone()); let span = info_span!("MerkleTreeDbLoader", origin=%merkle_tree_db_loader.domain()); let db_loader = DbLoader::new(Box::new(merkle_tree_db_loader), task_monitor.clone()); Ok(db_loader.spawn(span)) @@ -1082,32 +1011,71 @@ impl Relayer { .expect("spawning tokio task from Builder is infallible") } - /// Helper function to build and return a hashmap of mailboxes. - /// Any chains that fail to build mailbox will not be included - /// in the hashmap. Errors will be logged and chain metrics - /// will be updated for chains that fail to build mailbox. - pub async fn build_mailboxes( + pub async fn build_origins( settings: &RelayerSettings, - core_metrics: &CoreMetrics, + db: DB, + core_metrics: Arc, chain_metrics: &ChainMetrics, - ) -> HashMap> { - settings - .build_mailboxes(settings.destination_chains.iter(), core_metrics) - .await + ) -> HashMap { + use origin::Factory; + use origin::OriginFactory; + + let contract_sync_metrics = Arc::new(ContractSyncMetrics::new(&core_metrics)); + let factory = OriginFactory::new( + db, + core_metrics, + contract_sync_metrics, + ADVANCED_LOG_META, + settings.tx_id_indexing_enabled, + settings.igp_indexing_enabled, + ); + + let origin_futures: Vec<_> = settings + .chains + .iter() + .map(|(domain, chain)| async { + ( + domain.clone(), + factory + .create( + domain.clone(), + chain, + settings.gas_payment_enforcement.clone(), + ) + .await, + ) + }) + .collect(); + let results = futures::future::join_all(origin_futures).await; + let origins = results .into_iter() - .filter_map(|(origin, mailbox_res)| match mailbox_res { - Ok(mailbox) => Some((origin, mailbox)), + .filter_map(|(domain, result)| match result { + Ok(origin) => Some((domain, origin)), Err(err) => { Self::record_critical_error( - &origin, + &domain, chain_metrics, &err, - "Critical error when building mailbox", + "Critical error when building chain as origin", ); None } }) - .collect() + .collect::>(); + settings + .origin_chains + .iter() + .filter(|domain| !origins.contains_key(domain)) + .for_each(|domain| { + Self::record_critical_error( + domain, + chain_metrics, + &FactoryError::MissingConfiguration(domain.name().to_string()), + "Critical error when building chain as origin", + ); + }); + + origins } pub async fn build_destinations( @@ -1135,7 +1103,7 @@ impl Relayer { }) .collect(); let results = futures::future::join_all(destination_futures).await; - results + let destinations = results .into_iter() .filter_map(|(domain, result)| match result { Ok(destination) => Some((domain, destination)), @@ -1149,113 +1117,185 @@ impl Relayer { None } }) - .collect::>() - } + .collect::>(); - /// Helper function to build and return a hashmap of validator announces. - /// Any chains that fail to build validator announce will not be included - /// in the hashmap. Errors will be logged and chain metrics - /// will be updated for chains that fail to build validator announce. - pub async fn build_validator_announces( - settings: &RelayerSettings, - core_metrics: &CoreMetrics, - chain_metrics: &ChainMetrics, - ) -> HashMap> { settings - .build_validator_announces(settings.origin_chains.iter(), core_metrics) - .await - .into_iter() - .filter_map(|(origin, mailbox_res)| match mailbox_res { - Ok(mailbox) => Some((origin, mailbox)), - Err(err) => { - Self::record_critical_error( - &origin, - chain_metrics, - &err, - "Critical error when building validator announce", - ); - None - } - }) - .collect() + .destination_chains + .iter() + .filter(|domain| !destinations.contains_key(domain)) + .for_each(|domain| { + Self::record_critical_error( + domain, + chain_metrics, + &FactoryError::MissingConfiguration(domain.name().to_string()), + "Critical error when building chain as destination", + ); + }); + + destinations } - /// Helper function to build and return a hashmap of application operation verifiers. - /// Any chains that fail to build application operation verifier will not be included - /// in the hashmap. Errors will be logged and chain metrics - /// will be updated for chains that fail to build application operation verifier. - pub async fn build_application_operation_verifiers( - settings: &RelayerSettings, - core_metrics: &CoreMetrics, - chain_metrics: &ChainMetrics, - ) -> HashMap> { + fn reset_critical_errors(settings: &RelayerSettings, chain_metrics: &ChainMetrics) { settings - .build_application_operation_verifiers(settings.destination_chains.iter(), core_metrics) - .await - .into_iter() - .filter_map( - |(origin, app_context_verifier_res)| match app_context_verifier_res { - Ok(app_context_verifier) => Some((origin, app_context_verifier)), - Err(err) => { - Self::record_critical_error( - &origin, - chain_metrics, - &err, - "Critical error when building application operation verifier", - ); - None - } - }, - ) - .collect() + .origin_chains + .iter() + .for_each(|origin| chain_metrics.set_critical_error(origin.name(), false)); } +} - pub async fn build_contract_syncs( - settings: &RelayerSettings, - core_metrics: &CoreMetrics, - chain_metrics: &ChainMetrics, - contract_sync_metrics: &ContractSyncMetrics, - stores: HashMap>, - data_type: &str, - ) -> HashMap>> - where - T: Indexable + Debug + Send + Sync + Clone + Eq + Hash + 'static, - SequenceIndexer: TryFromWithMetrics, - S: HyperlaneLogStore - + HyperlaneSequenceAwareIndexerStoreReader - + HyperlaneWatermarkedLogStore - + 'static, - { - settings - .contract_syncs( - core_metrics, - contract_sync_metrics, - stores, - ADVANCED_LOG_META, - settings.tx_id_indexing_enabled, - ) - .await - .into_iter() - .filter_map(|(domain, sync)| match sync { - Ok(s) => Some((domain, s)), +#[derive(Clone)] +struct DymensionKaspaArgs { + kas_provider: Box, + dym_mailbox: Arc, + /// Sender for deposit recovery requests, populated when Foo is created + recovery_sender: + Arc>>, +} + +// Manual Debug since KaspaMailbox now has a trait object +impl std::fmt::Debug for DymensionKaspaArgs { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DymensionKaspaArgs") + .field("kas_provider", &self.kas_provider) + .field("kas_mailbox", &"KaspaMailbox") + .field("dym_mailbox", &self.dym_mailbox) + .field("recovery_sender", &"") + .finish() + } +} + +impl Relayer { + async fn get_dymension_kaspa_args( + dsts: &mut HashMap, + origins: &HashMap, + ) -> Result> { + use hyperlane_core::HyperlaneChain; + + let Some((kas_domain, kas_dst)) = dsts.iter().find(|(d, _)| is_kas(d)) else { + return Ok(None); + }; + let kas_domain = kas_domain.clone(); + let kas_mailbox_concrete = kas_dst + .mailbox + .clone() + .downcast_arc::() + .unwrap(); + let dym_mailbox = dsts + .iter() + .find(|(d, _)| is_dym(d)) + .unwrap() + .1 + .mailbox + .clone() + .downcast_arc::() + .unwrap(); + + let kas_mailbox_updated = + if let Some((domain, origin)) = origins.iter().find(|(d, _)| is_kas(d)) { + use hyperlane_base::db::DB; + use hyperlane_base::kas_hack::KaspaRocksDB; + let db: &DB = origin.database.as_ref(); + let kaspa_db = Arc::new(KaspaRocksDB::new(domain, db.clone())); + + let mut kas_provider = kas_mailbox_concrete + .provider() + .downcast::() + .unwrap(); + kas_provider.set_kaspa_db(kaspa_db as Arc); + info!("Set kaspa_db on kaspa provider"); + + let updated = Arc::try_unwrap(kas_mailbox_concrete) + .unwrap_or_else(|arc| (*arc).clone()) + .with_provider((*kas_provider).clone()); + info!("Updated kaspa mailbox with provider containing kaspa_db"); + Arc::new(updated) + } else { + kas_mailbox_concrete + }; + + dsts.get_mut(&kas_domain).unwrap().mailbox = kas_mailbox_updated.clone(); + let kas_provider = kas_mailbox_updated + .provider() + .downcast::() + .unwrap(); + + Ok(Some(DymensionKaspaArgs { + kas_provider, + dym_mailbox, + recovery_sender: Arc::new(tokio::sync::RwLock::new(None)), + })) + } + + async fn launch_dymension_kaspa_tasks( + &self, + origin: &Origin, + tasks: &mut Vec>, + task_monitor: TaskMonitor, + send_channels: HashMap>, + ) { + info!("Dymension: launching dymension kaspa tasks"); + + let args = self.dymension_kaspa_args.as_ref().unwrap(); + + let kas_provider = args.kas_provider.clone(); + let hub_mailbox = args.dym_mailbox.clone(); + + let metadata_getter = PendingMessageMetadataGetter::new(); + + let b = KaspaBridgeFoo::new(kas_provider.clone(), hub_mailbox.clone(), metadata_getter); + + // Store the recovery sender for use by the server endpoint + { + let mut sender_guard = args.recovery_sender.write().await; + *sender_guard = Some(b.recovery_sender()); + } + + // sync relayer before starting other tasks + b.sync_hub_if_needed().await.unwrap(); + + info!("Dymension: did sync_hub_if_needed, starting dymension kaspa task loops"); + + tasks.push(b.run_loops(task_monitor.clone())); + // it observes the local db and makes sure messages are eventually written to the destination chain + let message_db_loader = + match self.run_message_db_loader(origin, send_channels.clone(), task_monitor.clone()) { + Ok(task) => task, Err(err) => { Self::record_critical_error( - &domain, - chain_metrics, + &origin.domain, + &self.chain_metrics, &err, - &format!("Critical error when building {data_type} contract sync"), + "Failed to run message db loader", ); - None + return; } - }) - .collect() + }; + tasks.push(message_db_loader); } - fn reset_critical_errors(settings: &RelayerSettings, chain_metrics: &ChainMetrics) { - settings - .origin_chains - .iter() - .for_each(|origin| chain_metrics.set_critical_error(origin.name(), false)); + fn get_migration_target(&self) -> Option { + self.dymension_kaspa_args + .as_ref() + .and_then(|args| args.kas_provider.conf().migrate_escrow_to.clone()) + } + + async fn run_escrow_migration(&self, target_address: &str) -> Result> { + let args = self.dymension_kaspa_args.as_ref().unwrap(); + let min_sigs = args.kas_provider.validators().multisig_threshold_hub_ism() as usize; + + // Signature formatter using PendingMessageMetadataGetter + let metadata_getter = PendingMessageMetadataGetter::new(); + let format_sigs = |sigs: &mut Vec| -> ChainResult> { + format_ad_hoc_signatures(&metadata_getter, sigs, min_sigs) + }; + + run_migration_with_sync( + &args.kas_provider, + &args.dym_mailbox, + target_address, + format_sigs, + ) + .await } } diff --git a/rust/main/agents/relayer/src/relayer/destination.rs b/rust/main/agents/relayer/src/relayer/destination.rs index a19d7264903..9799de72760 100644 --- a/rust/main/agents/relayer/src/relayer/destination.rs +++ b/rust/main/agents/relayer/src/relayer/destination.rs @@ -1,26 +1,47 @@ +use std::fmt::Debug; use std::sync::Arc; use std::time::{Duration, Instant}; -use hyperlane_base::db::DB; -use hyperlane_base::settings::ChainConf; -use hyperlane_base::CoreMetrics; -use hyperlane_core::{HyperlaneDomain, SubmitterType}; +use tracing::warn; + +use hyperlane_base::db::HyperlaneRocksDB; +use hyperlane_base::{db::DB, settings::ChainConf, CoreMetrics}; +use hyperlane_core::{HyperlaneDomain, HyperlaneDomainProtocol, Mailbox, SubmitterType}; +use hyperlane_ethereum::Signers; +use hyperlane_operation_verifier::ApplicationOperationVerifier; use lander::{ DatabaseOrPath, Dispatcher, DispatcherEntrypoint, DispatcherMetrics, DispatcherSettings, }; pub struct Destination { pub domain: HyperlaneDomain, + pub application_operation_verifier: Arc, + pub chain_conf: ChainConf, + pub database: HyperlaneRocksDB, pub dispatcher_entrypoint: Option, pub dispatcher: Option, + pub mailbox: Arc, + pub ccip_signer: Option, +} + +impl Debug for Destination { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Destination {{ domain: {} }}", self.domain.name()) + } } #[derive(Debug, thiserror::Error)] pub enum FactoryError { + #[error("Failed to create application operation verifier for domain {0}: {1}")] + ApplicationOperationVerifierCreationFailed(String, String), #[error("Failed to create dispatcher for domain {0}: {1}")] DispatcherCreationFailed(String, String), #[error("Failed to create dispatcher entrypoint for domain {0}: {1}")] DispatcherEntrypointCreationFailed(String, String), + #[error("Failed to create mailbox for domain {0}: {1}")] + MailboxCreationFailed(String, String), + #[error("Failed to create destination for domain {0} due to missing configuration")] + MissingConfiguration(String), } pub trait Factory { @@ -50,14 +71,29 @@ impl Factory for DestinationFactory { chain_conf: ChainConf, dispatcher_metrics: DispatcherMetrics, ) -> Result { + let application_operation_verifier = self + .init_application_operation_verifier(&domain, &chain_conf) + .await?; + + let ccip_signer = self.init_ccip_signer(&domain, &chain_conf).await; + + let database = HyperlaneRocksDB::new(&domain, self.db.clone()); + let (dispatcher_entrypoint, dispatcher) = self - .init_dispatcher_and_entrypoint(&domain, chain_conf, dispatcher_metrics) + .init_dispatcher_and_entrypoint(&domain, chain_conf.clone(), dispatcher_metrics) .await?; + let mailbox = self.init_mailbox(&domain, &chain_conf).await?; + let destination = Destination { domain, + application_operation_verifier, + chain_conf, + database, dispatcher_entrypoint, dispatcher, + mailbox, + ccip_signer, }; Ok(destination) @@ -65,6 +101,58 @@ impl Factory for DestinationFactory { } impl DestinationFactory { + async fn init_application_operation_verifier( + &self, + domain: &HyperlaneDomain, + chain_conf: &ChainConf, + ) -> Result, FactoryError> { + let start_entity_init = Instant::now(); + let verifier = chain_conf + .build_application_operation_verifier(self.core_metrics.as_ref()) + .await + .map_err(|e| { + FactoryError::ApplicationOperationVerifierCreationFailed( + domain.to_string(), + e.to_string(), + ) + })? + .into(); + self.measure( + domain, + "application_operation_verifier", + start_entity_init.elapsed(), + ); + + Ok(verifier) + } + + async fn init_ccip_signer( + &self, + domain: &HyperlaneDomain, + chain_conf: &ChainConf, + ) -> Option { + let start_entity_init = Instant::now(); + + if !matches!(domain.domain_protocol(), HyperlaneDomainProtocol::Ethereum) { + return None; + } + + let signer_conf = chain_conf.signer.clone()?; + + let signer = signer_conf + .build::() + .await + .map_err(|e| { + warn!(error = ?e, "Failed to build Ethereum signer for CCIP-read ISM."); + e + }) + .ok(); + + self.measure(domain, "ccip_signers", start_entity_init.elapsed()); + + signer + } + async fn init_dispatcher_and_entrypoint( &self, domain: &HyperlaneDomain, @@ -107,6 +195,22 @@ impl DestinationFactory { Ok((Some(dispatcher_entrypoint), Some(dispatcher))) } + async fn init_mailbox( + &self, + domain: &HyperlaneDomain, + chain_conf: &ChainConf, + ) -> Result, FactoryError> { + let start_entity_init = Instant::now(); + let mailbox = chain_conf + .build_mailbox(self.core_metrics.as_ref()) + .await + .map_err(|e| FactoryError::MailboxCreationFailed(domain.to_string(), e.to_string()))? + .into(); + self.measure(domain, "mailbox", start_entity_init.elapsed()); + + Ok(mailbox) + } + fn measure(&self, domain: &HyperlaneDomain, entity: &str, latency: Duration) { let chain_init_latency_labels = [domain.name(), "destination", entity]; self.core_metrics diff --git a/rust/main/agents/relayer/src/relayer/origin.rs b/rust/main/agents/relayer/src/relayer/origin.rs new file mode 100644 index 00000000000..73c7b78897e --- /dev/null +++ b/rust/main/agents/relayer/src/relayer/origin.rs @@ -0,0 +1,398 @@ +use std::fmt::Debug; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use hyperlane_base::cursors::{CursorType, Indexable}; +use hyperlane_base::db::{HyperlaneRocksDB, DB}; +use hyperlane_base::settings::{ChainConf, SequenceIndexer, TryFromWithMetrics}; +use hyperlane_base::{ + ContractSync, ContractSyncMetrics, ContractSyncer, CoreMetrics, SequenceAwareLogStore, + SequencedDataContractSync, WatermarkContractSync, WatermarkLogStore, +}; +use hyperlane_core::{ + HyperlaneDomain, HyperlaneLogStore, HyperlaneMessage, HyperlaneSequenceAwareIndexerStoreReader, + HyperlaneWatermarkedLogStore, InterchainGasPayment, MerkleTreeInsertion, ValidatorAnnounce, +}; +use tokio::sync::RwLock; + +use crate::merkle_tree::builder::MerkleTreeBuilder; +use crate::msg::gas_payment::GasPaymentEnforcer; +use crate::settings::GasPaymentEnforcementConf; + +type MessageSync = Arc>; +type InterchainGasPaymentSync = Arc>; +type MerkleTreeHookSync = Arc>; + +pub struct Origin { + pub database: HyperlaneRocksDB, + pub domain: HyperlaneDomain, + pub chain_conf: ChainConf, + pub validator_announce: Arc, + pub gas_payment_enforcer: Arc>, + pub prover_sync: Arc>, + pub message_sync: MessageSync, + pub interchain_gas_payment_syncs: Vec, + pub merkle_tree_hook_sync: MerkleTreeHookSync, +} + +impl std::fmt::Debug for Origin { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Origin {{ domain: {} }}", self.domain.name()) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum FactoryError { + #[error("Failed to create validator announce for domain {0}: {1}")] + ValidatorAnnounce(String, String), + #[error("Failed to create message sync for domain {0}: {1}")] + MessageSync(String, String), + #[error("Failed to create igp sync for domain {0}: {1}")] + InterchainGasPaymentSync(String, String), + #[error("Failed to create merkle tree hook sync for domain {0}: {1}")] + MerkleTreeHookSync(String, String), +} + +pub trait Factory { + async fn create( + &self, + domain: HyperlaneDomain, + chain_conf: &ChainConf, + gas_payment_enforcement: Vec, + ) -> Result; +} + +#[derive(Clone, Debug)] +pub struct OriginFactory { + db: DB, + core_metrics: Arc, + sync_metrics: Arc, + advanced_log_meta: bool, + tx_id_indexing_enabled: bool, + igp_indexing_enabled: bool, +} + +impl OriginFactory { + pub fn new( + db: DB, + core_metrics: Arc, + sync_metrics: Arc, + advanced_log_meta: bool, + tx_id_indexing_enabled: bool, + igp_indexing_enabled: bool, + ) -> Self { + Self { + db, + core_metrics, + sync_metrics, + advanced_log_meta, + tx_id_indexing_enabled, + igp_indexing_enabled, + } + } +} + +impl Factory for OriginFactory { + async fn create( + &self, + domain: HyperlaneDomain, + chain_conf: &ChainConf, + gas_payment_enforcement: Vec, + ) -> Result { + let db = HyperlaneRocksDB::new(&domain, self.db.clone()); + + let validator_announce = { + let start_entity_init = Instant::now(); + let res = self.init_validator_announce(chain_conf, &domain).await?; + self.measure(&domain, "validator_announce", start_entity_init.elapsed()); + res + }; + + // need one of these per origin chain due to the database scoping even though + // the config itself is the same + // TODO: maybe use a global one moving forward? + let gas_payment_enforcer = { + let start_entity_init = Instant::now(); + let res = self + .init_gas_payment_enforcer(gas_payment_enforcement, db.clone()) + .await?; + self.measure(&domain, "gas_payment_enforcer", start_entity_init.elapsed()); + res + }; + + let prover_sync = { + let start_entity_init = Instant::now(); + let res = Self::init_prover_sync().await?; + self.measure(&domain, "prover_sync", start_entity_init.elapsed()); + res + }; + + let hyperlane_db = Arc::new(db.clone()); + let message_sync = { + let start_entity_init = Instant::now(); + let res = self + .init_message_sync(&domain, chain_conf, hyperlane_db.clone()) + .await?; + self.measure(&domain, "message_sync", start_entity_init.elapsed()); + res + }; + + let interchain_gas_payment_syncs = if self.igp_indexing_enabled { + let start_entity_init = Instant::now(); + let res = self + .init_igp_syncs(&domain, chain_conf, hyperlane_db.clone()) + .await?; + self.measure( + &domain, + "interchain_gas_payment_syncs", + start_entity_init.elapsed(), + ); + res + } else { + Vec::new() + }; + + let merkle_tree_hook_sync = { + let start_entity_init = Instant::now(); + let res = self + .init_merkle_tree_hook_sync(&domain, chain_conf, hyperlane_db.clone()) + .await?; + self.measure( + &domain, + "merkle_tree_hook_sync", + start_entity_init.elapsed(), + ); + res + }; + + let origin = Origin { + database: db, + domain, + chain_conf: chain_conf.clone(), + validator_announce, + gas_payment_enforcer: Arc::new(RwLock::new(gas_payment_enforcer)), + prover_sync: Arc::new(RwLock::new(prover_sync)), + message_sync, + interchain_gas_payment_syncs, + merkle_tree_hook_sync, + }; + Ok(origin) + } +} + +impl OriginFactory { + fn measure(&self, domain: &HyperlaneDomain, entity: &str, latency: Duration) { + let chain_init_latency_labels = [domain.name(), "origin", entity]; + self.core_metrics + .chain_init_latency() + .with_label_values(&chain_init_latency_labels) + .set(latency.as_millis() as i64); + } + + async fn init_validator_announce( + &self, + chain_conf: &ChainConf, + domain: &HyperlaneDomain, + ) -> Result, FactoryError> { + let validator_announce = chain_conf + .build_validator_announce(&self.core_metrics) + .await + .map_err(|err| FactoryError::ValidatorAnnounce(domain.to_string(), err.to_string()))?; + Ok(validator_announce.into()) + } + + async fn init_prover_sync() -> Result { + let prover_sync = MerkleTreeBuilder::new(); + Ok(prover_sync) + } + + async fn init_gas_payment_enforcer( + &self, + gas_payment_enforcement: Vec, + db: HyperlaneRocksDB, + ) -> Result { + Ok(GasPaymentEnforcer::new(gas_payment_enforcement, db)) + } + + async fn init_message_sync( + &self, + domain: &HyperlaneDomain, + chain_conf: &ChainConf, + db: Arc, + ) -> Result { + match HyperlaneMessage::indexing_cursor(domain.domain_protocol()) { + CursorType::SequenceAware => Self::build_sequenced_contract_sync( + domain, + chain_conf, + &self.core_metrics, + &self.sync_metrics, + db, + self.advanced_log_meta, + self.tx_id_indexing_enabled, + ) + .await + .map(|r| r as Arc>) + .map_err(|err| FactoryError::MessageSync(domain.to_string(), err.to_string())), + CursorType::RateLimited => Self::build_watermark_contract_sync( + domain, + chain_conf, + &self.core_metrics, + &self.sync_metrics, + db, + self.advanced_log_meta, + self.tx_id_indexing_enabled, + ) + .await + .map(|r| r as Arc>) + .map_err(|err| FactoryError::MessageSync(domain.to_string(), err.to_string())), + } + } + + async fn init_igp_syncs( + &self, + domain: &HyperlaneDomain, + chain_conf: &ChainConf, + db: Arc, + ) -> Result, FactoryError> { + let igp_addresses = &chain_conf.addresses.interchain_gas_paymasters; + if igp_addresses.is_empty() { + return Ok(Vec::new()); + } + + let mut syncs = Vec::with_capacity(igp_addresses.len()); + for igp_address in igp_addresses { + let mut modified_conf = chain_conf.clone(); + modified_conf.addresses.interchain_gas_paymasters = vec![*igp_address]; + let sync = self + .init_igp_sync(domain, &modified_conf, db.clone()) + .await?; + syncs.push(sync); + } + Ok(syncs) + } + + async fn init_igp_sync( + &self, + domain: &HyperlaneDomain, + chain_conf: &ChainConf, + db: Arc, + ) -> Result { + match InterchainGasPayment::indexing_cursor(domain.domain_protocol()) { + CursorType::SequenceAware => Self::build_sequenced_contract_sync( + domain, + chain_conf, + &self.core_metrics, + &self.sync_metrics, + db, + self.advanced_log_meta, + false, + ) + .await + .map(|r| r as Arc>) + .map_err(|err| { + FactoryError::InterchainGasPaymentSync(domain.to_string(), err.to_string()) + }), + CursorType::RateLimited => Self::build_watermark_contract_sync( + domain, + chain_conf, + &self.core_metrics, + &self.sync_metrics, + db, + self.advanced_log_meta, + false, + ) + .await + .map(|r| r as Arc>) + .map_err(|err| { + FactoryError::InterchainGasPaymentSync(domain.to_string(), err.to_string()) + }), + } + } + + async fn init_merkle_tree_hook_sync( + &self, + domain: &HyperlaneDomain, + chain_conf: &ChainConf, + db: Arc, + ) -> Result { + match MerkleTreeInsertion::indexing_cursor(domain.domain_protocol()) { + CursorType::SequenceAware => Self::build_sequenced_contract_sync( + domain, + chain_conf, + &self.core_metrics, + &self.sync_metrics, + db, + self.advanced_log_meta, + false, + ) + .await + .map(|r| r as Arc>) + .map_err(|err| FactoryError::MerkleTreeHookSync(domain.to_string(), err.to_string())), + CursorType::RateLimited => Self::build_watermark_contract_sync( + domain, + chain_conf, + &self.core_metrics, + &self.sync_metrics, + db, + self.advanced_log_meta, + false, + ) + .await + .map(|r| r as Arc>) + .map_err(|err| FactoryError::MerkleTreeHookSync(domain.to_string(), err.to_string())), + } + } + + async fn build_sequenced_contract_sync( + domain: &HyperlaneDomain, + chain_conf: &ChainConf, + metrics: &CoreMetrics, + sync_metrics: &ContractSyncMetrics, + store: Arc, + advanced_log_meta: bool, + broadcast_sender_enabled: bool, + ) -> eyre::Result>> + where + T: Indexable + Debug, + SequenceIndexer: TryFromWithMetrics, + S: HyperlaneLogStore + HyperlaneSequenceAwareIndexerStoreReader + 'static, + { + // Currently, all indexers are of the `SequenceIndexer` type + let indexer = + SequenceIndexer::::try_from_with_metrics(chain_conf, metrics, advanced_log_meta) + .await?; + Ok(Arc::new(ContractSync::new( + domain.clone(), + store.clone() as SequenceAwareLogStore<_>, + indexer, + sync_metrics.clone(), + broadcast_sender_enabled, + ))) + } + + async fn build_watermark_contract_sync( + domain: &HyperlaneDomain, + chain_conf: &ChainConf, + metrics: &CoreMetrics, + sync_metrics: &ContractSyncMetrics, + store: Arc, + advanced_log_meta: bool, + broadcast_sender_enabled: bool, + ) -> eyre::Result>> + where + T: Indexable + Debug, + SequenceIndexer: TryFromWithMetrics, + S: HyperlaneLogStore + HyperlaneWatermarkedLogStore + 'static, + { + let indexer = + SequenceIndexer::::try_from_with_metrics(chain_conf, metrics, advanced_log_meta) + .await?; + Ok(Arc::new(ContractSync::new( + domain.clone(), + store.clone() as WatermarkLogStore<_>, + indexer, + sync_metrics.clone(), + broadcast_sender_enabled, + ))) + } +} diff --git a/rust/main/agents/relayer/src/relayer/tests.rs b/rust/main/agents/relayer/src/relayer/tests.rs index 6acb40c3fef..7b216389e57 100644 --- a/rust/main/agents/relayer/src/relayer/tests.rs +++ b/rust/main/agents/relayer/src/relayer/tests.rs @@ -1,5 +1,6 @@ use std::collections::{HashMap, HashSet}; use std::path::Path; +use std::sync::Arc; use std::time::Duration; use ethers::utils::hex; @@ -9,6 +10,7 @@ use prometheus::{opts, IntGaugeVec, Registry}; use reqwest::Url; use tokio::time::error::Elapsed; +use hyperlane_base::db::DB; use hyperlane_base::settings::{ ChainConf, ChainConnectionConf, CoreContractAddresses, IndexSettings, Settings, SignerConf, TracingConfig, @@ -21,6 +23,7 @@ use hyperlane_core::{ config::OpSubmissionConfig, HyperlaneDomain, IndexMode, KnownHyperlaneDomain, ReorgPeriod, H256, }; use hyperlane_ethereum as h_eth; +use lander::DispatcherMetrics; use crate::settings::{matching_list::MatchingList, RelayerSettings}; @@ -33,11 +36,11 @@ fn generate_test_core_contract_addresses() -> CoreContractAddresses { .unwrap() .as_slice(), ), - interchain_gas_paymaster: H256::from_slice( + interchain_gas_paymasters: vec![H256::from_slice( hex::decode("000000000000000000000000c756cFc1b7d0d4646589EDf10eD54b201237F5e8") .unwrap() .as_slice(), - ), + )], validator_announce: H256::from_slice( hex::decode("0000000000000000000000001b33611fCc073aB0737011d5512EF673Bff74962") .unwrap() @@ -139,7 +142,7 @@ fn generate_test_relayer_settings( #[tokio::test] #[tracing_test::traced_test] -async fn test_failed_build_mailboxes() { +async fn test_failed_build_destinations() { let temp_dir = tempfile::tempdir().unwrap(); let db_path = temp_dir.path(); @@ -183,7 +186,7 @@ async fn test_failed_build_mailboxes() { ); let registry = Registry::new(); - let core_metrics = CoreMetrics::new("relayer", 4000, registry).unwrap(); + let core_metrics = Arc::new(CoreMetrics::new("relayer", 4000, registry).unwrap()); let chain_metrics = ChainMetrics { block_height: IntGaugeVec::new( opts!("block_height", BLOCK_HEIGHT_HELP), @@ -198,11 +201,23 @@ async fn test_failed_build_mailboxes() { .unwrap(), }; - let mailboxes = Relayer::build_mailboxes(&settings, &core_metrics, &chain_metrics).await; + let db = DB::from_path(db_path).unwrap(); + + let dispatcher_metrics = DispatcherMetrics::new(core_metrics.registry()) + .expect("Creating dispatcher metrics is infallible"); + + let destinations = Relayer::build_destinations( + &settings, + db, + core_metrics, + &chain_metrics, + dispatcher_metrics, + ) + .await; - assert_eq!(mailboxes.len(), 2); - assert!(mailboxes.contains_key(&HyperlaneDomain::Known(KnownHyperlaneDomain::Arbitrum))); - assert!(mailboxes.contains_key(&HyperlaneDomain::Known(KnownHyperlaneDomain::Ethereum))); + assert_eq!(destinations.len(), 2); + assert!(destinations.contains_key(&HyperlaneDomain::Known(KnownHyperlaneDomain::Arbitrum))); + assert!(destinations.contains_key(&HyperlaneDomain::Known(KnownHyperlaneDomain::Ethereum))); // Arbitrum chain should not have any errors because it's ChainConf exists let metric = chain_metrics @@ -211,7 +226,7 @@ async fn test_failed_build_mailboxes() { .unwrap(); assert_eq!(metric.get(), 0); - // Ethereum chain should error because it is missing ChainConf + // Ethereum chain should not have any errors because it's ChainConf exists let metric = chain_metrics .critical_error .get_metric_with_label_values(&["ethereum"]) @@ -226,9 +241,9 @@ async fn test_failed_build_mailboxes() { assert_eq!(metric.get(), 1); } -#[tokio::test] #[tracing_test::traced_test] -async fn test_failed_build_validator_announces() { +#[tokio::test] +async fn test_failed_build_origin() { let temp_dir = tempfile::tempdir().unwrap(); let db_path = temp_dir.path(); @@ -276,11 +291,12 @@ async fn test_failed_build_validator_announces() { .unwrap(), }; - let mailboxes = - Relayer::build_validator_announces(&settings, &core_metrics, &chain_metrics).await; + let db = DB::from_path(db_path).expect("Failed to initialize database"); + let origins = + Relayer::build_origins(&settings, db, Arc::new(core_metrics), &chain_metrics).await; - assert_eq!(mailboxes.len(), 1); - assert!(mailboxes.contains_key(&HyperlaneDomain::Known(KnownHyperlaneDomain::Arbitrum))); + assert_eq!(origins.len(), 1); + assert!(origins.contains_key(&HyperlaneDomain::Known(KnownHyperlaneDomain::Arbitrum))); // Arbitrum chain should not have any errors because it's ChainConf exists let metric = chain_metrics diff --git a/rust/main/agents/relayer/src/server/kaspa/list_deposits.rs b/rust/main/agents/relayer/src/server/kaspa/list_deposits.rs new file mode 100644 index 00000000000..76a395b4055 --- /dev/null +++ b/rust/main/agents/relayer/src/server/kaspa/list_deposits.rs @@ -0,0 +1,76 @@ +use axum::{ + extract::{Query, State}, + http::StatusCode, +}; +use serde::{Deserialize, Serialize}; + +use hyperlane_base::server::utils::{ + ServerErrorBody, ServerErrorResponse, ServerResult, ServerSuccessResponse, +}; +use hyperlane_core::{HyperlaneMessage, H256}; + +use crate::server::kaspa::ServerState; + +#[derive(Clone, Debug, Deserialize)] +pub struct QueryParams { + pub kaspa_tx: String, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct DepositResponse { + pub message_id: String, + pub message: HyperlaneMessage, + pub kaspa_tx: String, + pub hub_tx: Option, +} + +/// Fetch a Kaspa deposit by kaspa transaction hash +pub async fn handler( + State(state): State, + Query(query_params): Query, +) -> ServerResult> { + let kaspa_tx = query_params.kaspa_tx; + + tracing::debug!(%kaspa_tx, "Fetching Kaspa deposit by kaspa_tx"); + + let db = &state.kaspa_db; + + // Retrieve the deposit message directly by tx_hash + let message = match db.as_ref().retrieve_kaspa_deposit_by_tx_hash(&kaspa_tx) { + Ok(Some(message)) => message, + Ok(None) => { + return Err(ServerErrorResponse::new( + StatusCode::NOT_FOUND, + ServerErrorBody { + message: format!("No deposit found for kaspa_tx: {}", kaspa_tx), + }, + )); + } + Err(e) => { + tracing::error!(%kaspa_tx, error = ?e, "Error retrieving deposit from database"); + return Err(ServerErrorResponse::new( + StatusCode::INTERNAL_SERVER_ERROR, + ServerErrorBody { + message: format!("Database error: {}", e), + }, + )); + } + }; + + let message_id = message.id(); + + // Retrieve Hub transaction ID if available (indexed by kaspa_tx) + let hub_tx = db + .as_ref() + .retrieve_deposit_hub_tx(&kaspa_tx) + .unwrap_or(None); + + let response = DepositResponse { + message_id: format!("{:x}", message_id), + message, + kaspa_tx, + hub_tx, + }; + + Ok(ServerSuccessResponse::new(response)) +} diff --git a/rust/main/agents/relayer/src/server/kaspa/list_withdrawals.rs b/rust/main/agents/relayer/src/server/kaspa/list_withdrawals.rs new file mode 100644 index 00000000000..34970d0c082 --- /dev/null +++ b/rust/main/agents/relayer/src/server/kaspa/list_withdrawals.rs @@ -0,0 +1,91 @@ +use axum::{ + extract::{Query, State}, + http::StatusCode, +}; +use serde::{Deserialize, Serialize}; + +use hyperlane_base::server::utils::{ + ServerErrorBody, ServerErrorResponse, ServerResult, ServerSuccessResponse, +}; +use hyperlane_core::HyperlaneMessage; + +use crate::server::kaspa::ServerState; + +#[derive(Clone, Debug, Deserialize)] +pub struct QueryParams { + pub message_id: String, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct WithdrawalResponse { + pub message_id: String, + pub message: HyperlaneMessage, + pub kaspa_tx: Option, +} + +/// Fetch a Kaspa withdrawal by message_id +pub async fn handler( + State(state): State, + Query(query_params): Query, +) -> ServerResult> { + use hyperlane_core::H256; + + let message_id_str = query_params.message_id; + + tracing::debug!(%message_id_str, "Fetching Kaspa withdrawal by message_id"); + + let db = &state.kaspa_db; + + // Parse message_id from hex string + let message_id = match message_id_str.parse::() { + Ok(id) => id, + Err(e) => { + tracing::error!(%message_id_str, error = ?e, "Invalid message_id format"); + return Err(ServerErrorResponse::new( + StatusCode::BAD_REQUEST, + ServerErrorBody { + message: format!("Invalid message_id format: {}", e), + }, + )); + } + }; + + // Retrieve the withdrawal message directly by message_id + let message = match db + .as_ref() + .retrieve_kaspa_withdrawal_by_message_id(&message_id) + { + Ok(Some(message)) => message, + Ok(None) => { + return Err(ServerErrorResponse::new( + StatusCode::NOT_FOUND, + ServerErrorBody { + message: format!("No withdrawal found for message_id: {}", message_id_str), + }, + )); + } + Err(e) => { + tracing::error!(%message_id_str, error = ?e, "Error retrieving withdrawal from database"); + return Err(ServerErrorResponse::new( + StatusCode::INTERNAL_SERVER_ERROR, + ServerErrorBody { + message: format!("Database error: {}", e), + }, + )); + } + }; + + // Retrieve kaspa transaction ID if available + let kaspa_tx = db + .as_ref() + .retrieve_withdrawal_kaspa_tx(&message_id) + .unwrap_or(None); + + let response = WithdrawalResponse { + message_id: format!("{:x}", message.id()), + message, + kaspa_tx, + }; + + Ok(ServerSuccessResponse::new(response)) +} diff --git a/rust/main/agents/relayer/src/server/kaspa/mod.rs b/rust/main/agents/relayer/src/server/kaspa/mod.rs new file mode 100644 index 00000000000..968fd1a63a4 --- /dev/null +++ b/rust/main/agents/relayer/src/server/kaspa/mod.rs @@ -0,0 +1,73 @@ +use std::sync::Arc; + +use axum::{ + routing::{get, post}, + Router, +}; +use hyperlane_base::kas_hack::DepositRecoverySender; +use hyperlane_core::KaspaDb; +use tower_http::cors::{Any, CorsLayer}; + +pub mod list_deposits; +pub mod list_withdrawals; +pub mod recover_deposit; + +/// Server state for Kaspa endpoints +#[derive(Clone)] +pub struct ServerState { + pub kaspa_db: Arc, + /// Optional sender for deposit recovery requests + pub recovery_sender: Option, + /// Kaspa REST API URL for fetching transaction data + pub rest_api_url: Option, + /// Escrow address for validating deposits + pub escrow_address: Option, +} + +impl std::fmt::Debug for ServerState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ServerState") + .field("kaspa_db", &"") + .field("recovery_sender", &self.recovery_sender.is_some()) + .field("rest_api_url", &self.rest_api_url) + .field("escrow_address", &self.escrow_address) + .finish() + } +} + +impl ServerState { + pub fn new(kaspa_db: Arc) -> Self { + Self { + kaspa_db, + recovery_sender: None, + rest_api_url: None, + escrow_address: None, + } + } + + pub fn with_recovery( + mut self, + sender: DepositRecoverySender, + rest_api_url: String, + escrow_address: String, + ) -> Self { + self.recovery_sender = Some(sender); + self.rest_api_url = Some(rest_api_url); + self.escrow_address = Some(escrow_address); + self + } + + pub fn router(self) -> Router { + let cors = CorsLayer::new() + .allow_origin(Any) + .allow_methods(Any) + .allow_headers(Any); + + Router::new() + .route("/kaspa/deposit", get(list_deposits::handler)) + .route("/kaspa/withdrawal", get(list_withdrawals::handler)) + .route("/kaspa/deposit/recover", post(recover_deposit::handler)) + .layer(cors) + .with_state(self) + } +} diff --git a/rust/main/agents/relayer/src/server/kaspa/recover_deposit.rs b/rust/main/agents/relayer/src/server/kaspa/recover_deposit.rs new file mode 100644 index 00000000000..8fbaa567bc2 --- /dev/null +++ b/rust/main/agents/relayer/src/server/kaspa/recover_deposit.rs @@ -0,0 +1,127 @@ +use axum::{extract::State, http::StatusCode, Json}; +use dymension_kaspa::dym_kas_core::api::{ + base::RateLimitConfig, + client::{Deposit, HttpClient}, +}; +use serde::{Deserialize, Serialize}; + +use hyperlane_base::server::utils::{ + ServerErrorBody, ServerErrorResponse, ServerResult, ServerSuccessResponse, +}; + +use super::ServerState; + +#[derive(Clone, Debug, Deserialize)] +pub struct RequestBody { + /// The Kaspa transaction ID to recover + pub kaspa_tx: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct ResponseBody { + pub message: String, + pub deposit_id: String, +} + +/// Recover a Kaspa deposit by fetching it from the Kaspa REST API and submitting +/// it for processing. This is useful for deposits that fell outside the normal +/// lookback window due to relayer DB being wiped or other issues. +/// +/// POST /kaspa/deposit/recover +/// Body: { "kaspa_tx": "242b5987..." } +pub async fn handler( + State(state): State, + Json(body): Json, +) -> ServerResult> { + let kaspa_tx = body.kaspa_tx.clone(); + tracing::info!(%kaspa_tx, "Received deposit recovery request"); + + // Check if recovery is enabled + let (recovery_sender, rest_api_url, escrow_address) = match ( + &state.recovery_sender, + &state.rest_api_url, + &state.escrow_address, + ) { + (Some(sender), Some(url), Some(escrow)) => (sender, url, escrow), + _ => { + return Err(ServerErrorResponse::new( + StatusCode::SERVICE_UNAVAILABLE, + ServerErrorBody { + message: "Deposit recovery is not enabled on this relayer".to_string(), + }, + )); + } + }; + + // Fetch the transaction from Kaspa REST API + let client = HttpClient::new(rest_api_url.clone(), RateLimitConfig::default()); + let tx = client.get_tx_by_id(&kaspa_tx).await.map_err(|e| { + tracing::error!(%kaspa_tx, error = ?e, "Fetch transaction from Kaspa API"); + ServerErrorResponse::new( + StatusCode::NOT_FOUND, + ServerErrorBody { + message: format!("Transaction not found or API error: {}", e), + }, + ) + })?; + + // Validate it's a valid escrow transfer + if !is_valid_escrow_transfer(&tx, escrow_address) { + return Err(ServerErrorResponse::new( + StatusCode::BAD_REQUEST, + ServerErrorBody { + message: format!( + "Transaction {} is not a valid deposit to escrow {}", + kaspa_tx, escrow_address + ), + }, + )); + } + + // Convert to Deposit + let deposit: Deposit = tx.try_into().map_err(|e: eyre::Error| { + tracing::error!(%kaspa_tx, error = ?e, "Convert transaction to deposit"); + ServerErrorResponse::new( + StatusCode::BAD_REQUEST, + ServerErrorBody { + message: format!("Invalid deposit transaction: {}", e), + }, + ) + })?; + + let deposit_id = format!("{}", deposit.id); + + // Send to recovery channel + recovery_sender.send(deposit).await.map_err(|e| { + tracing::error!(%kaspa_tx, error = ?e, "Send deposit to recovery channel"); + ServerErrorResponse::new( + StatusCode::INTERNAL_SERVER_ERROR, + ServerErrorBody { + message: "Failed to queue deposit for recovery".to_string(), + }, + ) + })?; + + tracing::info!(%kaspa_tx, %deposit_id, "Deposit queued for recovery"); + + Ok(ServerSuccessResponse::new(ResponseBody { + message: "Deposit queued for recovery processing".to_string(), + deposit_id, + })) +} + +fn is_valid_escrow_transfer( + tx: &dymension_kaspa::dym_kas_api::models::TxModel, + escrow_address: &str, +) -> bool { + if let Some(outputs) = &tx.outputs { + for utxo in outputs { + if let Some(dest) = utxo.script_public_key_address.as_ref() { + if dest == escrow_address { + return true; + } + } + } + } + false +} diff --git a/rust/main/agents/relayer/src/server/merkle_tree_insertions/insert_merkle_tree_insertions.rs b/rust/main/agents/relayer/src/server/merkle_tree_insertions/insert_merkle_tree_insertions.rs index d59432c9a49..c9a1b48d9b6 100644 --- a/rust/main/agents/relayer/src/server/merkle_tree_insertions/insert_merkle_tree_insertions.rs +++ b/rust/main/agents/relayer/src/server/merkle_tree_insertions/insert_merkle_tree_insertions.rs @@ -58,7 +58,7 @@ pub async fn handler( }, ) })?; - insertion_count += 1; + insertion_count = insertion_count.saturating_add(1); } None => { tracing::debug!(?insertion, "No db found for chain"); diff --git a/rust/main/agents/relayer/src/server/merkle_tree_insertions/list_merkle_tree_insertions.rs b/rust/main/agents/relayer/src/server/merkle_tree_insertions/list_merkle_tree_insertions.rs index 1f5e5b0f390..75bafc176b3 100644 --- a/rust/main/agents/relayer/src/server/merkle_tree_insertions/list_merkle_tree_insertions.rs +++ b/rust/main/agents/relayer/src/server/merkle_tree_insertions/list_merkle_tree_insertions.rs @@ -141,6 +141,18 @@ mod tests { .expect("DB Error") } + fn insert_merkle_tree_insertion_block_number( + dbs: &HashMap, + domain: &HyperlaneDomain, + leaf_index: u32, + block_number: u64, + ) { + dbs.get(&domain.id()) + .expect("DB not found") + .store_merkle_tree_insertion_block_number_by_leaf_index(&leaf_index, &block_number) + .expect("DB Error") + } + #[tracing_test::traced_test] #[tokio::test] async fn test_list_merkle_tree_insertions_db_not_found() { @@ -195,6 +207,9 @@ mod tests { for insertion in insertions.iter() { insert_merkle_tree_insertion(&dbs, &domains[0], insertion.index(), insertion); } + for insertion in insertions.iter().take(2) { + insert_merkle_tree_insertion_block_number(&dbs, &domains[0], insertion.index(), 100); + } let leaf_index_start = 100; let leaf_index_end = 102; @@ -213,14 +228,17 @@ mod tests { let expected_list = [ TreeInsertion { + insertion_block_number: Some(100), leaf_index: 100, message_id: format!("{:?}", H256::from_low_u64_be(100)), }, TreeInsertion { + insertion_block_number: Some(100), leaf_index: 101, message_id: format!("{:?}", H256::from_low_u64_be(101)), }, TreeInsertion { + insertion_block_number: None, leaf_index: 102, message_id: format!("{:?}", H256::from_low_u64_be(102)), }, diff --git a/rust/main/agents/relayer/src/server/messages/insert_messages.rs b/rust/main/agents/relayer/src/server/messages/insert_messages.rs index 94ba7b08f6e..41e9769c2aa 100644 --- a/rust/main/agents/relayer/src/server/messages/insert_messages.rs +++ b/rust/main/agents/relayer/src/server/messages/insert_messages.rs @@ -1,16 +1,19 @@ +use std::str::FromStr; + use axum::{extract::State, http::StatusCode, Json}; use serde::{Deserialize, Serialize}; use hyperlane_base::server::utils::{ ServerErrorBody, ServerErrorResponse, ServerResult, ServerSuccessResponse, }; -use hyperlane_core::HyperlaneMessage; +use hyperlane_core::{HyperlaneMessage, H256}; use crate::server::messages::ServerState; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Message { pub message: HyperlaneMessage, + pub message_id: Option, pub dispatched_block_number: u64, } @@ -39,6 +42,33 @@ pub async fn handler( for message in messages { tracing::debug!(?message, "Manually inserting message"); + // if a message_id was provided, validate to make sure there are + // no human errors in the input. + if let Some(message_id) = message.message_id.as_ref() { + tracing::debug!("Verifying message id"); + let message_id_h256 = H256::from_str(message_id).map_err(|err| { + let error_msg = "Failed to parse message_id"; + tracing::debug!(message_id, ?err, "{error_msg}"); + ServerErrorResponse::new( + StatusCode::BAD_REQUEST, + ServerErrorBody { + message: error_msg.to_string(), + }, + ) + })?; + + if message_id_h256 != message.message.id() { + let error_msg = "Provided message does not match provided message_id"; + tracing::debug!(provided_message_id=message_id, generated_message_id=?message.message.id(), "{error_msg}"); + return Err(ServerErrorResponse::new( + StatusCode::BAD_REQUEST, + ServerErrorBody { + message: error_msg.to_string(), + }, + )); + } + } + let dispatched_block_number = message.dispatched_block_number; match state.dbs.get(&message.message.origin) { Some(db) => { @@ -53,7 +83,7 @@ pub async fn handler( }, ) })?; - insertion_count += 1; + insertion_count = insertion_count.saturating_add(1); } None => { tracing::debug!(?message, "No db found for origin"); @@ -144,30 +174,34 @@ mod tests { ]; let TestServerSetup { app, dbs } = setup_test_server(domains); + let message_1 = HyperlaneMessage { + version: 0, + nonce: 100, + origin: domains[0].id(), + sender: H256::from_low_u64_be(100), + destination: domains[1].id(), + recipient: H256::from_low_u64_be(200), + body: Vec::new(), + }; + let message_2 = HyperlaneMessage { + version: 0, + nonce: 100, + origin: domains[1].id(), + sender: H256::from_low_u64_be(100), + destination: domains[2].id(), + recipient: H256::from_low_u64_be(200), + body: Vec::new(), + }; let body = RequestBody { messages: vec![ Message { - message: HyperlaneMessage { - version: 0, - nonce: 100, - origin: domains[0].id(), - sender: H256::from_low_u64_be(100), - destination: domains[1].id(), - recipient: H256::from_low_u64_be(200), - body: Vec::new(), - }, + message: message_1.clone(), + message_id: Some(format!("{:x}", message_1.id())), dispatched_block_number: 1000, }, Message { - message: HyperlaneMessage { - version: 0, - nonce: 100, - origin: domains[1].id(), - sender: H256::from_low_u64_be(100), - destination: domains[2].id(), - recipient: H256::from_low_u64_be(200), - body: Vec::new(), - }, + message: message_2.clone(), + message_id: Some(format!("{:x}", message_2.id())), dispatched_block_number: 1000, }, ], @@ -198,31 +232,35 @@ mod tests { ]; let TestServerSetup { app, dbs } = setup_test_server(domains); + let message_1 = HyperlaneMessage { + version: 0, + nonce: 100, + origin: domains[0].id(), + sender: H256::from_low_u64_be(100), + destination: domains[1].id(), + recipient: H256::from_low_u64_be(200), + body: Vec::new(), + }; + let message_2 = HyperlaneMessage { + version: 0, + nonce: 100, + origin: domains[1].id(), + sender: H256::from_low_u64_be(100), + destination: domains[2].id(), + recipient: H256::from_low_u64_be(200), + body: Vec::new(), + }; // first insert some messages let body = RequestBody { messages: vec![ Message { - message: HyperlaneMessage { - version: 0, - nonce: 100, - origin: domains[0].id(), - sender: H256::from_low_u64_be(100), - destination: domains[1].id(), - recipient: H256::from_low_u64_be(200), - body: Vec::new(), - }, + message: message_1.clone(), + message_id: Some(format!("{:x}", message_1.id())), dispatched_block_number: 1000, }, Message { - message: HyperlaneMessage { - version: 0, - nonce: 100, - origin: domains[1].id(), - sender: H256::from_low_u64_be(100), - destination: domains[2].id(), - recipient: H256::from_low_u64_be(200), - body: Vec::new(), - }, + message: message_2.clone(), + message_id: Some(format!("{:x}", message_2.id())), dispatched_block_number: 1000, }, ], @@ -233,31 +271,35 @@ mod tests { assert_eq!(resp_status, StatusCode::OK); assert_eq!(resp_body.count, body.messages.len() as u64); + let message_1 = HyperlaneMessage { + version: 0, + nonce: 100, + origin: domains[0].id(), + sender: H256::from_low_u64_be(1000), + destination: domains[1].id(), + recipient: H256::from_low_u64_be(2000), + body: Vec::new(), + }; + let message_2 = HyperlaneMessage { + version: 0, + nonce: 100, + origin: domains[1].id(), + sender: H256::from_low_u64_be(1000), + destination: domains[2].id(), + recipient: H256::from_low_u64_be(2000), + body: Vec::new(), + }; // then insert some messages to overwrite previously inserted messages let body = RequestBody { messages: vec![ Message { - message: HyperlaneMessage { - version: 0, - nonce: 100, - origin: domains[0].id(), - sender: H256::from_low_u64_be(1000), - destination: domains[1].id(), - recipient: H256::from_low_u64_be(2000), - body: Vec::new(), - }, + message: message_1.clone(), + message_id: Some(format!("{:x}", message_1.id())), dispatched_block_number: 2000, }, Message { - message: HyperlaneMessage { - version: 0, - nonce: 100, - origin: domains[1].id(), - sender: H256::from_low_u64_be(1000), - destination: domains[2].id(), - recipient: H256::from_low_u64_be(2000), - body: Vec::new(), - }, + message: message_2.clone(), + message_id: Some(format!("{:x}", message_2.id())), dispatched_block_number: 2000, }, ], @@ -284,30 +326,34 @@ mod tests { let domains = &[HyperlaneDomain::Known(KnownHyperlaneDomain::Arbitrum)]; let TestServerSetup { app, dbs } = setup_test_server(domains); + let message_1 = HyperlaneMessage { + version: 0, + nonce: 100, + origin: domains[0].id(), + sender: H256::from_low_u64_be(100), + destination: 1000, + recipient: H256::from_low_u64_be(200), + body: Vec::new(), + }; + let message_2 = HyperlaneMessage { + version: 0, + nonce: 100, + origin: 1000, + sender: H256::from_low_u64_be(100), + destination: 2000, + recipient: H256::from_low_u64_be(200), + body: Vec::new(), + }; let body = RequestBody { messages: vec![ Message { - message: HyperlaneMessage { - version: 0, - nonce: 100, - origin: domains[0].id(), - sender: H256::from_low_u64_be(100), - destination: 1000, - recipient: H256::from_low_u64_be(200), - body: Vec::new(), - }, + message: message_1.clone(), + message_id: Some(format!("{:x}", message_1.id())), dispatched_block_number: 1000, }, Message { - message: HyperlaneMessage { - version: 0, - nonce: 100, - origin: 1000, - sender: H256::from_low_u64_be(100), - destination: 2000, - recipient: H256::from_low_u64_be(200), - body: Vec::new(), - }, + message: message_2.clone(), + message_id: Some(format!("{:x}", message_2.id())), dispatched_block_number: 1000, }, ], diff --git a/rust/main/agents/relayer/src/server/messages/list_messages.rs b/rust/main/agents/relayer/src/server/messages/list_messages.rs index 9a4fe731a74..afe735a9a11 100644 --- a/rust/main/agents/relayer/src/server/messages/list_messages.rs +++ b/rust/main/agents/relayer/src/server/messages/list_messages.rs @@ -19,9 +19,15 @@ pub struct QueryParams { pub nonce_end: u32, } +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct MessageResponse { + pub message_id: String, + pub message: HyperlaneMessage, +} + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ResponseBody { - pub messages: Vec, + pub messages: Vec, } /// Fetch merkle tree insertion into the database @@ -61,6 +67,14 @@ pub async fn handler( let messages = fetch_messages(db, nonce_start, nonce_end).await?; + let messages: Vec<_> = messages + .into_iter() + .map(|m| MessageResponse { + message_id: format!("{:x}", m.id()), + message: m, + }) + .collect(); + let resp = ResponseBody { messages }; Ok(ServerSuccessResponse::new(resp)) } @@ -229,7 +243,14 @@ mod tests { assert_eq!(resp_status, StatusCode::OK); - let expected_list: Vec<_> = insertions.into_iter().take(2).map(|(_, msg)| msg).collect(); + let expected_list: Vec<_> = insertions + .into_iter() + .take(2) + .map(|(_, msg)| MessageResponse { + message_id: format!("{:x}", msg.id()), + message: msg, + }) + .collect(); assert_eq!(resp_body.messages.len(), expected_list.len()); for (actual, expected) in resp_body.messages.iter().zip(expected_list.iter()) { diff --git a/rust/main/agents/relayer/src/server/mod.rs b/rust/main/agents/relayer/src/server/mod.rs index 8f90fcfdb3e..c61aa3a8b9c 100644 --- a/rust/main/agents/relayer/src/server/mod.rs +++ b/rust/main/agents/relayer/src/server/mod.rs @@ -4,23 +4,36 @@ use std::sync::Arc; use axum::Router; use derive_new::new; +use hyperlane_base::kas_hack::DepositRecoverySender; use hyperlane_core::HyperlaneDomain; use tokio::sync::broadcast::Sender; use hyperlane_base::db::HyperlaneRocksDB; +use hyperlane_core::KaspaDb; use tokio::sync::RwLock; +use crate::merkle_tree::builder::MerkleTreeBuilder; use crate::msg::gas_payment::GasPaymentEnforcer; use crate::msg::op_queue::OperationPriorityQueue; +use crate::msg::pending_message::MessageContext; use crate::server::environment_variable::EnvironmentVariableApi; pub const ENDPOINT_MESSAGES_QUEUE_SIZE: usize = 100; pub mod environment_variable; pub mod igp; +pub mod kaspa; pub mod merkle_tree_insertions; pub mod messages; pub mod operations; +pub mod proofs; + +/// Kaspa recovery configuration for the server +pub struct KaspaRecoveryConfig { + pub sender: DepositRecoverySender, + pub rest_api_url: String, + pub escrow_address: String, +} #[derive(new)] pub struct Server { @@ -33,6 +46,15 @@ pub struct Server { dbs: Option>, #[new(default)] gas_enforcers: Option>>>, + #[new(default)] + // (origin, destination) + msg_ctxs: HashMap<(u32, u32), Arc>, + #[new(default)] + prover_syncs: Option>>>, + #[new(default)] + kaspa_db: Option>, + #[new(default)] + kaspa_recovery: Option, } impl Server { @@ -62,6 +84,29 @@ impl Server { self } + pub fn with_msg_ctxs(mut self, msg_ctxs: HashMap<(u32, u32), Arc>) -> Self { + self.msg_ctxs = msg_ctxs; + self + } + + pub fn with_prover_sync( + mut self, + prover_syncs: HashMap>>, + ) -> Self { + self.prover_syncs = Some(prover_syncs); + self + } + + pub fn with_kaspa_db(mut self, kaspa_db: Option>) -> Self { + self.kaspa_db = kaspa_db; + self + } + + pub fn with_kaspa_recovery(mut self, recovery: Option) -> Self { + self.kaspa_recovery = recovery; + self + } + // return a custom router that can be used in combination with other routers pub fn router(self) -> Router { let mut router = Router::new(); @@ -72,20 +117,47 @@ impl Server { ) } if let Some(op_queues) = self.op_queues { - router = router.merge(operations::list_messages::ServerState::new(op_queues).router()); + router = router + .merge(operations::list_messages::ServerState::new(op_queues.clone()).router()); + if let Some(dbs) = self.dbs.as_ref() { + router = router.merge( + operations::reprocess_message::ServerState::new( + dbs.clone(), + op_queues.clone(), + self.msg_ctxs.clone(), + ) + .router(), + ); + } } - if let Some(dbs) = self.dbs { + if let Some(dbs) = self.dbs.as_ref() { router = router .merge(messages::ServerState::new(dbs.clone()).router()) - .merge(merkle_tree_insertions::ServerState::new(dbs.clone()).router()) + .merge(merkle_tree_insertions::ServerState::new(dbs.clone()).router()); } if let Some(gas_enforcers) = self.gas_enforcers { - router = router.merge(igp::ServerState::new(gas_enforcers.clone()).router()) + router = router.merge(igp::ServerState::new(gas_enforcers.clone()).router()); + } + if let Some(prover_syncs) = self.prover_syncs { + router = router.merge(proofs::ServerState::new(prover_syncs).router()); + } + if let Some(kaspa_db) = self.kaspa_db { + let kaspa_state = kaspa::ServerState::new(kaspa_db); + let kaspa_state = if let Some(recovery) = self.kaspa_recovery { + kaspa_state.with_recovery( + recovery.sender, + recovery.rest_api_url, + recovery.escrow_address, + ) + } else { + kaspa_state + }; + router = router.merge(kaspa_state.router()); } let expose_environment_variable_endpoint = env::var("HYPERLANE_RELAYER_ENVIRONMENT_VARIABLE_ENDPOINT_ENABLED") - .map_or(false, |v| v == "true"); + .is_ok_and(|v| v == "true"); if expose_environment_variable_endpoint { router = router.merge(EnvironmentVariableApi::new().router()); } diff --git a/rust/main/agents/relayer/src/server/operations/message_retry.rs b/rust/main/agents/relayer/src/server/operations/message_retry.rs index 3688980e5f0..39c286e2396 100644 --- a/rust/main/agents/relayer/src/server/operations/message_retry.rs +++ b/rust/main/agents/relayer/src/server/operations/message_retry.rs @@ -61,7 +61,7 @@ async fn handler( // message retry responses. // 3 queues for each chain (prepare, submit, confirm) let (transmitter, mut receiver) = - mpsc::channel(MESSAGE_PROCESSOR_QUEUE_COUNT * state.destination_chains); + mpsc::channel(MESSAGE_PROCESSOR_QUEUE_COUNT.saturating_mul(state.destination_chains)); state .retry_request_transmitter .send(MessageRetryRequest { @@ -89,8 +89,8 @@ async fn handler( matched = relayer_resp.matched, "Received relayer response" ); - resp.evaluated += relayer_resp.evaluated; - resp.matched += relayer_resp.matched; + resp.evaluated = resp.evaluated.saturating_add(relayer_resp.evaluated); + resp.matched = resp.matched.saturating_add(relayer_resp.matched); } Ok(Json(resp)) diff --git a/rust/main/agents/relayer/src/server/operations/mod.rs b/rust/main/agents/relayer/src/server/operations/mod.rs index adec5de5948..74636ecd0fb 100644 --- a/rust/main/agents/relayer/src/server/operations/mod.rs +++ b/rust/main/agents/relayer/src/server/operations/mod.rs @@ -1,2 +1,3 @@ pub mod list_messages; pub mod message_retry; +pub mod reprocess_message; diff --git a/rust/main/agents/relayer/src/server/operations/reprocess_message.rs b/rust/main/agents/relayer/src/server/operations/reprocess_message.rs new file mode 100644 index 00000000000..b5a3f4dd53b --- /dev/null +++ b/rust/main/agents/relayer/src/server/operations/reprocess_message.rs @@ -0,0 +1,350 @@ +use std::{cmp::Reverse, collections::HashMap, str::FromStr, sync::Arc}; + +use axum::{extract::State, http::StatusCode, routing, Json, Router}; +use derive_new::new; +use hyperlane_base::{ + db::{HyperlaneDb, HyperlaneRocksDB}, + server::utils::{ServerErrorBody, ServerErrorResponse, ServerResult, ServerSuccessResponse}, +}; +use hyperlane_core::{PendingOperationStatus, ReprepareReason, H256}; +use serde::{Deserialize, Serialize}; + +use crate::msg::{ + op_queue::OperationPriorityQueue, + pending_message::{MessageContext, PendingMessage, DEFAULT_MAX_MESSAGE_RETRIES}, +}; + +#[derive(Clone, new)] +pub struct ServerState { + pub dbs: HashMap, + pub op_queues: HashMap, + pub msg_ctxs: HashMap<(u32, u32), Arc>, +} + +impl ServerState { + pub fn router(self) -> Router { + Router::new() + .route("/reprocess_message", routing::post(handler)) + .with_state(self) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RequestBody { + pub origin_id: u32, + pub message_id: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ResponseBody { + pub pending_message: String, +} + +/// This endpoint will retrieve a message from the database and try +/// to process it again. +/// This endpoint is useful when the relayer believes a message has been delivered +/// but was later reorg-ed out. +/// +/// curl -X POST \ +/// 'localhost:9090/reprocess_message' \ +/// -H 'Content-type: application/json' \ +/// -d '{"domain_id": 1399811149, "message_id": "0x9484bd5c635b17b28cb382249d7a6fe5ca15debfd4f824247c68d47badc5b7de"}' +/// +/// Note: You may need to combine this with `POST /igp_rule` endpoint +/// to make sure interchain gas payments are not enforced. +/// ie. +/// curl -X POST \ +/// -H 'Content-type: application/json' \ +/// 'localhost:9090/igp_rules' \ +/// -d '{ "policy": "None", "matching_list": [{"messageid": "0x8ebdc20c6c728c5715412ee928599c7286151f76d9079c8bdee08a335c7d072f"}] }' +/// +/// This ensures the gas payment requirement is met, because the relayer will have recorded +/// the reorg-ed gas expenditures (in db) and believes the user did not pay enough gas. +async fn handler( + State(state): State, + Json(payload): Json, +) -> ServerResult> { + tracing::debug!(?payload, "Reprocessing message"); + let RequestBody { + origin_id: domain_id, + message_id, + } = payload; + + let message_id = H256::from_str(&message_id).map_err(|err| { + let error_msg = "Failed to parse message_id"; + tracing::debug!(message_id, ?err, "{error_msg}"); + ServerErrorResponse::new( + StatusCode::BAD_REQUEST, + ServerErrorBody { + message: error_msg.to_string(), + }, + ) + })?; + + let db = state.dbs.get(&domain_id).ok_or_else(|| { + let error_msg = "No db found for chain"; + tracing::debug!(domain_id, "{error_msg}"); + ServerErrorResponse::new( + StatusCode::NOT_FOUND, + ServerErrorBody { + message: error_msg.to_string(), + }, + ) + })?; + + // fetch message from db + let message = db + .retrieve_message_by_id(&message_id) + .map_err(|err| { + let error_msg = "Failed to fetch message"; + tracing::debug!(domain_id, ?message_id, ?err, "{error_msg}"); + ServerErrorResponse::new( + StatusCode::INTERNAL_SERVER_ERROR, + ServerErrorBody { + message: error_msg.to_string(), + }, + ) + })? + .ok_or_else(|| { + let error_msg = "Message not found"; + tracing::debug!(domain_id, ?message_id, "{error_msg}"); + ServerErrorResponse::new( + StatusCode::NOT_FOUND, + ServerErrorBody { + message: error_msg.to_string(), + }, + ) + })?; + + let app_context = state + .msg_ctxs + .get(&(message.origin, message.destination)) + .ok_or_else(|| { + let error_msg = "Message context not found"; + tracing::debug!(domain_id, ?message_id, "{error_msg}"); + ServerErrorResponse::new( + StatusCode::NOT_FOUND, + ServerErrorBody { + message: error_msg.to_string(), + }, + ) + })?; + + let destination = message.destination; + // create a pending message to push into prep queue + let pending_message = PendingMessage::new( + message, + app_context.clone(), + PendingOperationStatus::Retry(ReprepareReason::Manual), + // TODO: maybe include the app_context here in the future, for metrics + None, + DEFAULT_MAX_MESSAGE_RETRIES, + ); + + let prep_queue = state.op_queues.get(&destination).ok_or_else(|| { + let error_msg = "Queue not found"; + tracing::debug!(destination, ?message_id, "{error_msg}"); + ServerErrorResponse::new( + StatusCode::NOT_FOUND, + ServerErrorBody { + message: error_msg.to_string(), + }, + ) + })?; + + // just a debug to show what was inserted into the prepare queue + let message_str = format!("{:?}", pending_message); + + prep_queue + .lock() + .await + .push(Reverse(Box::new(pending_message))); + + let resp = ResponseBody { + pending_message: message_str, + }; + Ok(ServerSuccessResponse::new(resp)) +} + +#[cfg(test)] +mod tests { + use std::{collections::HashMap, sync::Arc}; + + use axum::{ + body::Body, + http::{header::CONTENT_TYPE, Method, Request, Response, StatusCode}, + Router, + }; + use tower::ServiceExt; + + use hyperlane_base::{ + cache::{LocalCache, MeteredCache, MeteredCacheConfig, OptionalCache}, + db::{HyperlaneRocksDB, DB}, + }; + use hyperlane_core::{HyperlaneDomain, HyperlaneMessage, KnownHyperlaneDomain}; + + use super::*; + use crate::{ + msg::db_loader::test::dummy_cache_metrics, + test_utils::dummy_data::{dummy_message_context, dummy_metadata_builder}, + }; + + #[derive(Debug)] + struct TestServerSetup { + pub app: Router, + pub dbs: HashMap, + pub op_queues: HashMap, + } + + fn setup_test_server(domains: &[HyperlaneDomain]) -> TestServerSetup { + let dbs: HashMap<_, _> = domains + .iter() + .map(|domain| { + let temp_dir = tempfile::tempdir().unwrap(); + let db = DB::from_path(temp_dir.path()).unwrap(); + let base_db = HyperlaneRocksDB::new(domain, db); + (domain.id(), base_db) + }) + .collect(); + + let op_queues: HashMap = domains + .iter() + .map(|domain| (domain.id(), OperationPriorityQueue::default())) + .collect(); + + let cache = OptionalCache::new(Some(MeteredCache::new( + LocalCache::new("test-cache"), + dummy_cache_metrics(), + MeteredCacheConfig { + cache_name: "test-cache".to_owned(), + }, + ))); + + let mut msg_ctxs = HashMap::new(); + + for origin_domain in domains.iter() { + let db = dbs.get(&origin_domain.id()).unwrap(); + for destination_domain in domains.iter() { + let base_metadata_builder = + dummy_metadata_builder(origin_domain, destination_domain, db, cache.clone()); + let msg_ctx = + dummy_message_context(Arc::new(base_metadata_builder), db, cache.clone()); + msg_ctxs.insert( + (origin_domain.id(), destination_domain.id()), + Arc::new(msg_ctx), + ); + } + } + + let server_state = ServerState::new(dbs.clone(), op_queues.clone(), msg_ctxs); + let app = server_state.router(); + + TestServerSetup { + app, + dbs, + op_queues, + } + } + + async fn send_request(app: Router, origin_id: u32, message_id: String) -> Response { + let api_url = "/reprocess_message"; + let body = RequestBody { + origin_id, + message_id, + }; + let request = Request::builder() + .uri(api_url) + .method(Method::POST) + .header(CONTENT_TYPE, "application/json") + .body(serde_json::to_string(&body).unwrap()) + .expect("Failed to build request"); + let response = app.oneshot(request).await.expect("Failed to send request"); + response + } + + fn insert_message( + dbs: &HashMap, + domain: &HyperlaneDomain, + message: &HyperlaneMessage, + dispatched_block_number: u64, + ) { + dbs.get(&domain.id()) + .expect("DB not found") + .store_message(&message, dispatched_block_number) + .expect("DB Error"); + } + + #[tracing_test::traced_test] + #[tokio::test] + async fn test_reprocess_message_happy_path() { + let domains = &[ + HyperlaneDomain::Known(KnownHyperlaneDomain::Arbitrum), + HyperlaneDomain::Known(KnownHyperlaneDomain::Ethereum), + HyperlaneDomain::Known(KnownHyperlaneDomain::Optimism), + ]; + let TestServerSetup { + app, + dbs, + op_queues, + } = setup_test_server(domains); + + let message = HyperlaneMessage { + version: 0, + nonce: 100, + origin: KnownHyperlaneDomain::Arbitrum as u32, + sender: H256::from_low_u64_be(100), + destination: KnownHyperlaneDomain::Ethereum as u32, + recipient: H256::from_low_u64_be(200), + body: Vec::new(), + }; + + insert_message(&dbs, &domains[0], &message, 1000); + + let message_id = format!("0x{:x}", message.id()); + let response = send_request(app, KnownHyperlaneDomain::Arbitrum as u32, message_id).await; + let resp_status = response.status(); + assert_eq!(resp_status, StatusCode::OK); + + let op_queue_len = op_queues + .get(&(KnownHyperlaneDomain::Ethereum as u32)) + .expect("Queue not found") + .lock() + .await + .len(); + assert_eq!(op_queue_len, 1); + } + + #[tracing_test::traced_test] + #[tokio::test] + async fn test_reprocess_message_not_found() { + let domains = &[ + HyperlaneDomain::Known(KnownHyperlaneDomain::Arbitrum), + HyperlaneDomain::Known(KnownHyperlaneDomain::Ethereum), + HyperlaneDomain::Known(KnownHyperlaneDomain::Optimism), + ]; + let TestServerSetup { app, op_queues, .. } = setup_test_server(domains); + + let message = HyperlaneMessage { + version: 0, + nonce: 100, + origin: KnownHyperlaneDomain::Arbitrum as u32, + sender: H256::from_low_u64_be(100), + destination: KnownHyperlaneDomain::Ethereum as u32, + recipient: H256::from_low_u64_be(200), + body: Vec::new(), + }; + + let message_id = format!("0x{:x}", message.id()); + let response = send_request(app, KnownHyperlaneDomain::Arbitrum as u32, message_id).await; + let resp_status = response.status(); + assert_eq!(resp_status, StatusCode::NOT_FOUND); + + let op_queue_len = op_queues + .get(&(KnownHyperlaneDomain::Arbitrum as u32)) + .expect("Queue not found") + .lock() + .await + .len(); + assert_eq!(op_queue_len, 0); + } +} diff --git a/rust/main/agents/relayer/src/server/proofs/mod.rs b/rust/main/agents/relayer/src/server/proofs/mod.rs new file mode 100644 index 00000000000..b3f940f2370 --- /dev/null +++ b/rust/main/agents/relayer/src/server/proofs/mod.rs @@ -0,0 +1,22 @@ +use std::{collections::HashMap, sync::Arc}; + +use axum::{routing::get, Router}; +use derive_new::new; +use tokio::sync::RwLock; + +use crate::merkle_tree::builder::MerkleTreeBuilder; + +pub mod prove_merkle_leaf; + +#[derive(Clone, Debug, new)] +pub struct ServerState { + pub origin_prover_syncs: HashMap>>, +} + +impl ServerState { + pub fn router(self) -> Router { + Router::new() + .route("/merkle_proofs", get(prove_merkle_leaf::handler)) + .with_state(self) + } +} diff --git a/rust/main/agents/relayer/src/server/proofs/prove_merkle_leaf.rs b/rust/main/agents/relayer/src/server/proofs/prove_merkle_leaf.rs new file mode 100644 index 00000000000..77cc8ca1504 --- /dev/null +++ b/rust/main/agents/relayer/src/server/proofs/prove_merkle_leaf.rs @@ -0,0 +1,189 @@ +use axum::{ + extract::{Query, State}, + http::StatusCode, +}; +use ethers::utils::hex; +use hyperlane_core::accumulator::merkle::Proof; +use serde::{Deserialize, Serialize}; + +use hyperlane_base::server::utils::{ + ServerErrorBody, ServerErrorResponse, ServerResult, ServerSuccessResponse, +}; + +use super::ServerState; + +#[derive(Clone, Debug, Deserialize)] +pub struct QueryParams { + pub domain_id: u32, + pub leaf_index: u32, + pub root_index: u32, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +pub struct ResponseBody { + pub proof: Proof, + pub root: String, +} + +/// Generate merkle proof +pub async fn handler( + State(state): State, + Query(query_params): Query, +) -> ServerResult> { + let QueryParams { + domain_id, + leaf_index, + root_index, + } = query_params; + + tracing::debug!( + domain_id, + leaf_index, + root_index, + "Calculating merkle proof", + ); + + let origin_prove_sync = state.origin_prover_syncs.get(&domain_id).ok_or_else(|| { + tracing::debug!(domain_id, "Domain does not exist"); + ServerErrorResponse::new( + StatusCode::NOT_FOUND, + ServerErrorBody { + message: format!("Domain {domain_id} does not exist"), + }, + ) + })?; + + let proof = origin_prove_sync + .read() + .await + .get_proof(leaf_index, root_index) + .map_err(|err| { + tracing::error!(leaf_index, root_index, ?err, "Failed to get proof"); + ServerErrorResponse::new( + StatusCode::INTERNAL_SERVER_ERROR, + ServerErrorBody { + message: err.to_string(), + }, + ) + })?; + + let root = hex::encode(proof.root()); + let resp = ResponseBody { proof, root }; + Ok(ServerSuccessResponse::new(resp)) +} + +#[cfg(test)] +mod tests { + use std::{collections::HashMap, sync::Arc}; + + use axum::{ + body::{self, Body}, + http::{header::CONTENT_TYPE, Request, Response, StatusCode}, + Router, + }; + use tokio::sync::RwLock; + use tower::ServiceExt; + + use hyperlane_core::{HyperlaneDomain, KnownHyperlaneDomain, H256}; + + use super::*; + use crate::{merkle_tree::builder::MerkleTreeBuilder, test_utils::request::parse_body_to_json}; + + #[derive(Debug)] + struct TestServerSetup { + pub app: Router, + pub origin_prover_syncs: HashMap>>, + } + + fn setup_test_server(domains: &[HyperlaneDomain]) -> TestServerSetup { + let origin_prover_syncs: HashMap>> = domains + .iter() + .map(|domain| { + let merkle_tree_builder = MerkleTreeBuilder::new(); + (domain.id(), Arc::new(RwLock::new(merkle_tree_builder))) + }) + .collect(); + + let server_state = ServerState::new(origin_prover_syncs.clone()); + let app = server_state.router(); + + TestServerSetup { + app, + origin_prover_syncs, + } + } + + async fn send_request( + app: Router, + domain_id: u32, + leaf_index: u32, + root_index: u32, + ) -> Response { + let api_url = format!( + "/merkle_proofs?domain_id={domain_id}&leaf_index={leaf_index}&root_index={root_index}" + ); + let request = Request::builder() + .uri(api_url) + .header(CONTENT_TYPE, "application/json") + .body(body::Body::empty()) + .expect("Failed to build request"); + let response = app.oneshot(request).await.expect("Failed to send request"); + response + } + + #[tracing_test::traced_test] + #[tokio::test] + async fn test_prove_merkle_leaf_happy_path() { + let domains = &[ + HyperlaneDomain::Known(KnownHyperlaneDomain::Arbitrum), + HyperlaneDomain::Known(KnownHyperlaneDomain::Ethereum), + HyperlaneDomain::Known(KnownHyperlaneDomain::Optimism), + ]; + let TestServerSetup { + app, + mut origin_prover_syncs, + } = setup_test_server(domains); + + { + let prover_sync = origin_prover_syncs + .get_mut(&(KnownHyperlaneDomain::Arbitrum as u32)) + .expect("Missing prover sync"); + + let mut prover_sync_write = prover_sync.write().await; + + let _ = prover_sync_write.ingest_message_id(H256::from_low_u64_be(1000)); + let _ = prover_sync_write.ingest_message_id(H256::from_low_u64_be(2000)); + let _ = prover_sync_write.ingest_message_id(H256::from_low_u64_be(3000)); + } + + let response = send_request(app, KnownHyperlaneDomain::Arbitrum as u32, 1, 1).await; + let resp_status = response.status(); + let resp_body: ResponseBody = parse_body_to_json(response.into_body()).await; + assert_eq!(resp_status, StatusCode::OK); + + let expected = ResponseBody { + proof: Proof { + leaf: H256::from_low_u64_be(2000), + index: 1, + path: resp_body.proof.path.clone(), + }, + root: "41fd56f0277eaba76a4ad043c1072239e32f0de80c6e2b6a546e73a4a1bafebd".into(), + }; + assert_eq!(resp_body, expected); + } + + #[tracing_test::traced_test] + #[tokio::test] + async fn test_prove_merkle_leaf_not_found() { + let domains = &[ + HyperlaneDomain::Known(KnownHyperlaneDomain::Arbitrum), + HyperlaneDomain::Known(KnownHyperlaneDomain::Ethereum), + HyperlaneDomain::Known(KnownHyperlaneDomain::Optimism), + ]; + let TestServerSetup { app, .. } = setup_test_server(domains); + + let response = send_request(app, KnownHyperlaneDomain::Arbitrum as u32, 1, 1).await; + let resp_status = response.status(); + assert_eq!(resp_status, StatusCode::INTERNAL_SERVER_ERROR); + } +} diff --git a/rust/main/agents/relayer/src/settings/matching_list.rs b/rust/main/agents/relayer/src/settings/matching_list.rs index 114fdc4d5a1..27bafc1ec67 100644 --- a/rust/main/agents/relayer/src/settings/matching_list.rs +++ b/rust/main/agents/relayer/src/settings/matching_list.rs @@ -11,7 +11,8 @@ use std::{ use derive_new::new; use ethers::utils::hex; use hyperlane_core::{ - config::StrOrInt, utils::hex_or_base58_to_h256, HyperlaneMessage, QueueOperation, H256, + config::StrOrInt, utils::hex_or_base58_or_bech32_to_h256, HyperlaneMessage, QueueOperation, + H256, }; use regex::Regex; use serde::{ @@ -198,7 +199,7 @@ impl<'de> Visitor<'de> for FilterVisitor { } } -impl<'de> Visitor<'de> for FilterVisitor { +impl Visitor<'_> for FilterVisitor { type Value = RegexWrapper; fn expecting(&self, fmt: &mut Formatter) -> fmt::Result { @@ -408,7 +409,7 @@ fn to_serde_err(e: IE) -> OE { } fn parse_addr(addr_str: &str) -> Result { - hex_or_base58_to_h256(addr_str).map_err(to_serde_err) + hex_or_base58_or_bech32_to_h256(addr_str).map_err(to_serde_err) } #[cfg(test)] diff --git a/rust/main/agents/relayer/src/settings/mod.rs b/rust/main/agents/relayer/src/settings/mod.rs index 30e596be78c..fc2f14d74e9 100644 --- a/rust/main/agents/relayer/src/settings/mod.rs +++ b/rust/main/agents/relayer/src/settings/mod.rs @@ -4,7 +4,7 @@ //! and validations it defines are not applied here, we should mirror them. //! ANY CHANGES HERE NEED TO BE REFLECTED IN THE TYPESCRIPT SDK. -use std::{collections::HashSet, path::PathBuf}; +use std::{collections::HashSet, ops::Add, path::PathBuf}; use convert_case::Case; use derive_more::{AsMut, AsRef, Deref, DerefMut}; @@ -159,18 +159,18 @@ impl FromRawConf for RelayerSettings { Ok(Some(parser)) => match parse_json_array(parser) { Some((path, value)) => (path, value, true), None => ( - &p.cwp + "gas_payment_enforcement", + (&p.cwp).add("gas_payment_enforcement"), Value::Array(vec![]), true, ), }, Ok(None) => ( - &p.cwp + "gas_payment_enforcement", + (&p.cwp).add("gas_payment_enforcement"), Value::Array(vec![]), false, ), Err(_) => ( - &p.cwp + "gas_payment_enforcement", + (&p.cwp).add("gas_payment_enforcement"), Value::Array(vec![]), false, ), @@ -190,7 +190,7 @@ impl FromRawConf for RelayerSettings { .unwrap_or(true) { Err::<(), eyre::Report>(eyre!("GASPAYMENTENFORCEMENT policy cannot be parsed")) - .take_err(&mut err, || cwp + "gas_payment_enforcement"); + .take_err(&mut err, || cwp.add("gas_payment_enforcement")); } let mut gas_payment_enforcement = gas_payment_enforcement_parser.into_array_iter().map(|itr| { @@ -214,24 +214,32 @@ impl FromRawConf for RelayerSettings { let (numerator, denominator) = gas_fraction .split_once('/') .ok_or_else(|| eyre!("Invalid `gas_fraction` for OnChainFeeQuoting gas payment enforcement policy; expected `numerator / denominator`")) - .take_err(&mut err, || &policy.cwp + "gas_fraction") + .take_err(&mut err, || (&policy.cwp).add("gas_fraction")) .unwrap_or(("1", "1")); + let gas_fraction_numerator = numerator + .parse() + .context("Error parsing gas fraction numerator") + .take_err(&mut err, || (&policy.cwp).add("gas_fraction")) + .unwrap_or(1); + let gas_fraction_denominator = denominator + .parse() + .context("Error parsing gas fraction denominator") + .take_err(&mut err, || (&policy.cwp).add("gas_fraction")) + .unwrap_or(1); + if gas_fraction_denominator == 0 { + err.push( + (&policy.cwp).add("gas_fraction"), + eyre!("gas_fraction denominator cannot be 0"), + ); + } Some(GasPaymentEnforcementPolicy::OnChainFeeQuoting { - gas_fraction_numerator: numerator - .parse() - .context("Error parsing gas fraction numerator") - .take_err(&mut err, || &policy.cwp + "gas_fraction") - .unwrap_or(1), - gas_fraction_denominator: denominator - .parse() - .context("Error parsing gas fraction denominator") - .take_err(&mut err, || &policy.cwp + "gas_fraction") - .unwrap_or(1), + gas_fraction_numerator, + gas_fraction_denominator, }) } Some(pt) => Err(eyre!("Unknown gas payment enforcement policy type `{pt}`")) - .take_err(&mut err, || cwp + "type"), + .take_err(&mut err, || cwp.add("type")), }.map(|policy| GasPaymentEnforcementConf { policy, matching_list, @@ -259,7 +267,7 @@ impl FromRawConf for RelayerSettings { .get_opt_key("addressBlacklist") .parse_string() .end() - .map(|str| parse_address_list(str, &mut err, || &p.cwp + "address_blacklist")) + .map(|str| parse_address_list(str, &mut err, || (&p.cwp).add("address_blacklist"))) .unwrap_or_default(); let transaction_gas_limit = p @@ -288,7 +296,7 @@ impl FromRawConf for RelayerSettings { .filter_map(|chain| { base.lookup_domain(chain) .context("Missing configuration for a chain in `skipTransactionGasLimitFor`") - .into_config_result(|| cwp + "skip_transaction_gas_limit_for") + .into_config_result(|| cwp.add("skip_transaction_gas_limit_for")) .take_config_err(&mut err) }) .map(|d| d.id()) @@ -300,7 +308,7 @@ impl FromRawConf for RelayerSettings { .filter_map(|chain| { base.lookup_domain(chain) .context("Missing configuration for a chain in `relayChains`") - .into_config_result(|| cwp + "relay_chains") + .into_config_result(|| cwp.add("relay_chains")) .take_config_err(&mut err) }) .collect(); @@ -309,7 +317,7 @@ impl FromRawConf for RelayerSettings { .get_opt_key("metricAppContexts") .take_config_err_flat(&mut err) .and_then(parse_json_array) - .unwrap_or_else(|| (&p.cwp + "metric_app_contexts", Value::Array(vec![]))); + .unwrap_or_else(|| ((&p.cwp).add("metric_app_contexts"), Value::Array(vec![]))); let metric_app_contexts_parser = ValueParser::new(raw_metric_app_contexts_path, &raw_metric_app_contexts); diff --git a/rust/main/agents/relayer/src/test_utils/dummy_data.rs b/rust/main/agents/relayer/src/test_utils/dummy_data.rs index e3965713970..e57ffe1ffed 100644 --- a/rust/main/agents/relayer/src/test_utils/dummy_data.rs +++ b/rust/main/agents/relayer/src/test_utils/dummy_data.rs @@ -123,6 +123,6 @@ pub fn dummy_message_context( origin_gas_payment_enforcer: Arc::new(RwLock::new(GasPaymentEnforcer::new([], db.clone()))), transaction_gas_limit: Default::default(), metrics: dummy_submission_metrics(), - application_operation_verifier: Some(Arc::new(DummyApplicationOperationVerifier {})), + application_operation_verifier: Arc::new(DummyApplicationOperationVerifier {}), } } diff --git a/rust/main/agents/relayer/src/test_utils/mock_base_builder.rs b/rust/main/agents/relayer/src/test_utils/mock_base_builder.rs index 9ff28d79250..4c83cfb3294 100644 --- a/rust/main/agents/relayer/src/test_utils/mock_base_builder.rs +++ b/rust/main/agents/relayer/src/test_utils/mock_base_builder.rs @@ -18,7 +18,7 @@ use hyperlane_test::mocks::MockMailboxContract; use crate::{ msg::metadata::{ BuildsBaseMetadata, DefaultIsmCache, IsmAwareAppContextClassifier, IsmBuildMetricsParams, - IsmCachePolicyClassifier, + IsmCachePolicyClassifier, MetadataBuildError, }, settings::matching_list::{Filter, ListElement, MatchingList}, }; @@ -33,7 +33,7 @@ pub struct MockBaseMetadataBuilderResponses { pub app_context_classifier: Option, pub ism_cache_policy_classifier: Option, pub cache: Option>>, - pub get_proof: ResponseList>, + pub get_proof: ResponseList>, pub highest_known_leaf_index: ResponseList>, pub get_merkle_leaf_id_by_message_id: ResponseList>>, /// build_ism uses a hashmap of VecDeque responses instead. @@ -160,7 +160,11 @@ impl BuildsBaseMetadata for MockBaseMetadataBuilder { .push(params); } - async fn get_proof(&self, _leaf_index: u32, _checkpoint: Checkpoint) -> eyre::Result { + async fn get_proof( + &self, + _leaf_index: u32, + _checkpoint: Checkpoint, + ) -> Result { self.responses .get_proof .lock() diff --git a/rust/main/agents/scraper/src/agent.rs b/rust/main/agents/scraper/src/agent.rs index e4e91748b1e..e09106989f4 100644 --- a/rust/main/agents/scraper/src/agent.rs +++ b/rust/main/agents/scraper/src/agent.rs @@ -5,7 +5,7 @@ use derive_more::AsRef; use futures::future::try_join_all; use hyperlane_core::{Delivery, HyperlaneDomain, HyperlaneMessage, InterchainGasPayment, H512}; use tokio::{sync::mpsc::Receiver as MpscReceiver, task::JoinHandle}; -use tracing::{info, info_span, trace, Instrument}; +use tracing::{info, info_span, instrument, trace, Instrument}; use hyperlane_base::{ broadcast::BroadcastMpscSender, metrics::AgentMetrics, settings::IndexSettings, AgentMetadata, @@ -148,6 +148,7 @@ impl BaseAgent for Scraper { impl Scraper { /// Sync contract data and other blockchain with the current chain state. /// This will spawn long-running contract sync tasks + #[instrument(fields(domain=%scraper.domain.name()), skip_all)] async fn scrape(&self, scraper: &ChainScraper) -> eyre::Result> { let store = scraper.store.clone(); let index_settings = scraper.index_settings.clone(); @@ -198,6 +199,7 @@ impl Scraper { )) } + #[instrument(fields(domain=%domain.name()), skip_all)] async fn build_chain_scraper( domain: &HyperlaneDomain, settings: &ScraperSettings, @@ -207,16 +209,18 @@ impl Scraper { info!(domain = domain.name(), "create chain scraper for domain"); let chain_setup = settings.chain_setup(domain)?; info!(domain = domain.name(), "create HyperlaneProvider"); - let provider = settings - .build_provider(domain, &metrics.clone()) - .await? - .into(); + let provider = chain_setup.build_provider(&metrics).await?.into(); info!(domain = domain.name(), "create HyperlaneDbStore"); let store = HyperlaneDbStore::new( scraper_db, domain.clone(), chain_setup.addresses.mailbox, - chain_setup.addresses.interchain_gas_paymaster, + chain_setup + .addresses + .interchain_gas_paymasters + .first() + .cloned() + .unwrap_or_default(), provider, &chain_setup.index.clone(), ) @@ -265,6 +269,7 @@ impl Scraper { store: HyperlaneDbStore, index_settings: IndexSettings, ) -> eyre::Result<(JoinHandle<()>, Option>)> { + let label = "message_dispatch"; let sync = self .as_ref() .settings @@ -278,18 +283,22 @@ impl Scraper { ) .await .map_err(|err| { - tracing::error!(?err, ?domain, "Error syncing sequenced contract"); + tracing::error!( + ?err, + domain = domain.name(), + label, + "Error syncing sequenced contract" + ); err })?; let cursor = sync.cursor(index_settings.clone()).await.map_err(|err| { - tracing::error!(?err, ?domain, "Error getting cursor"); + tracing::error!(?err, domain = domain.name(), label, "Error getting cursor"); err })?; let maybe_broadcaser = sync.get_broadcaster(); let task = tokio::spawn( - async move { sync.sync("message_dispatch", cursor.into()).await }.instrument( - info_span!("ChainContractSync", chain=%domain.name(), event="message_dispatch"), - ), + async move { sync.sync(label, cursor.into()).await } + .instrument(info_span!("ChainContractSync", chain=%domain.name(), event=label)), ); Ok((task, maybe_broadcaser)) } @@ -302,6 +311,7 @@ impl Scraper { store: HyperlaneDbStore, index_settings: IndexSettings, ) -> eyre::Result> { + let label = "message_delivery"; let sync = self .as_ref() .settings @@ -315,13 +325,16 @@ impl Scraper { ) .await .map_err(|err| { - tracing::error!(?err, ?domain, "Error syncing contract"); + tracing::error!( + ?err, + domain = domain.name(), + label, + "Error syncing contract" + ); err })?; - - let label = "message_delivery"; let cursor = sync.cursor(index_settings.clone()).await.map_err(|err| { - tracing::error!(?err, ?domain, "Error getting cursor"); + tracing::error!(?err, domain = domain.name(), label, "Error getting cursor"); err })?; // there is no txid receiver for delivery indexing, since delivery txs aren't batched with @@ -341,6 +354,7 @@ impl Scraper { index_settings: IndexSettings, tx_id_receiver: Option>, ) -> eyre::Result> { + let label = "gas_payment"; let sync = self .as_ref() .settings @@ -354,13 +368,16 @@ impl Scraper { ) .await .map_err(|err| { - tracing::error!(?err, ?domain, "Error syncing contract"); + tracing::error!( + ?err, + domain = domain.name(), + label, + "Error syncing contract" + ); err })?; - - let label = "gas_payment"; let cursor = sync.cursor(index_settings.clone()).await.map_err(|err| { - tracing::error!(?err, ?domain, "Error getting cursor"); + tracing::error!(?err, domain = domain.name(), label, "Error getting cursor"); err })?; Ok(tokio::spawn( @@ -414,13 +431,13 @@ mod test { .unwrap() .as_slice(), ), - interchain_gas_paymaster: H256::from_slice( + interchain_gas_paymasters: vec![H256::from_slice( hex::decode( "000000000000000000000000c756cFc1b7d0d4646589EDf10eD54b201237F5e8", ) .unwrap() .as_slice(), - ), + )], validator_announce: H256::from_slice( hex::decode( "0000000000000000000000001b33611fCc073aB0737011d5512EF673Bff74962", diff --git a/rust/main/agents/scraper/src/main.rs b/rust/main/agents/scraper/src/main.rs index 70b08617dcd..a2f3b284fe5 100644 --- a/rust/main/agents/scraper/src/main.rs +++ b/rust/main/agents/scraper/src/main.rs @@ -13,6 +13,7 @@ #![forbid(unsafe_code)] #![warn(missing_docs)] #![deny(clippy::unwrap_used, clippy::panic)] +#![deny(clippy::arithmetic_side_effects)] use agent::Scraper; use eyre::Result; diff --git a/rust/main/agents/scraper/src/settings.rs b/rust/main/agents/scraper/src/settings.rs index c95202051f2..6d58c5b9375 100644 --- a/rust/main/agents/scraper/src/settings.rs +++ b/rust/main/agents/scraper/src/settings.rs @@ -4,7 +4,7 @@ //! and validations it defines are not applied here, we should mirror them. //! ANY CHANGES HERE NEED TO BE REFLECTED IN THE TYPESCRIPT SDK. -use std::{collections::HashSet, default::Default}; +use std::{collections::HashSet, default::Default, ops::Add}; use derive_more::{AsMut, AsRef, Deref, DerefMut}; use eyre::Context; @@ -77,7 +77,7 @@ impl FromRawConf for ScraperSettings { .filter_map(|chain| { base.lookup_domain(chain) .context("Missing configuration for a chain in `chainsToScrape`") - .into_config_result(|| cwp + "chains_to_scrape") + .into_config_result(|| cwp.add("chains_to_scrape")) .take_config_err(&mut err) }) .collect() diff --git a/rust/main/agents/validator/Cargo.toml b/rust/main/agents/validator/Cargo.toml index 0bb4d1a18c1..41727a6d9ab 100644 --- a/rust/main/agents/validator/Cargo.toml +++ b/rust/main/agents/validator/Cargo.toml @@ -26,6 +26,7 @@ serde.workspace = true serde_json.workspace = true thiserror.workspace = true tokio = { workspace = true, features = ["rt", "macros", "parking_lot"] } +rustls.workspace = true tracing-futures.workspace = true tracing.workspace = true url.workspace = true @@ -37,6 +38,10 @@ hyperlane-core = { path = "../../hyperlane-core", features = [ hyperlane-base = { path = "../../hyperlane-base" } hyperlane-ethereum = { path = "../../chains/hyperlane-ethereum" } hyperlane-cosmos = { path = "../../chains/hyperlane-cosmos" } +dymension-kaspa = { path = "../../chains/dymension-kaspa" } +kaspa-grpc-client = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-rpc-core = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-utils-tower = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } # Dependency version is determined by ethers rusoto_core = '*' diff --git a/rust/main/agents/validator/src/main.rs b/rust/main/agents/validator/src/main.rs index 4f562181e2a..999a19f67fc 100644 --- a/rust/main/agents/validator/src/main.rs +++ b/rust/main/agents/validator/src/main.rs @@ -3,6 +3,7 @@ #![forbid(unsafe_code)] #![warn(missing_docs)] #![deny(clippy::unwrap_used)] +#![deny(clippy::arithmetic_side_effects)] use eyre::Result; @@ -18,6 +19,11 @@ mod validator; #[tokio::main(flavor = "multi_thread")] async fn main() -> Result<()> { + // Install rustls crypto provider (required for rustls 0.23+) + rustls::crypto::aws_lc_rs::default_provider() + .install_default() + .expect("Failed to install rustls crypto provider"); + // Logging is not initialised at this point, so, using `println!` println!("Validator starting up..."); diff --git a/rust/main/agents/validator/src/reorg_reporter.rs b/rust/main/agents/validator/src/reorg_reporter.rs index feddb9bfae1..8438136a28f 100644 --- a/rust/main/agents/validator/src/reorg_reporter.rs +++ b/rust/main/agents/validator/src/reorg_reporter.rs @@ -81,9 +81,8 @@ impl LatestCheckpointReorgReporter { let mut merkle_tree_hooks = HashMap::new(); for (url, settings) in Self::settings_with_single_rpc(settings, origin) { - let merkle_tree_hook = settings - .build_merkle_tree_hook(&settings.origin_chain, metrics) - .await?; + let chain_setup = settings.chain_setup(&settings.origin_chain)?; + let merkle_tree_hook = chain_setup.build_merkle_tree_hook(metrics).await?; merkle_tree_hooks.insert(url, merkle_tree_hook.into()); } @@ -97,7 +96,9 @@ impl LatestCheckpointReorgReporter { settings: &ValidatorSettings, origin: &HyperlaneDomain, ) -> Vec<(Url, ValidatorSettings)> { - use ChainConnectionConf::{Cosmos, CosmosNative, Ethereum, Fuel, Sealevel, Starknet}; + use ChainConnectionConf::{ + Cosmos, CosmosNative, Ethereum, Fuel, Kaspa, Radix, Sealevel, Starknet, + }; let chain_conf = settings .chains @@ -141,6 +142,19 @@ impl LatestCheckpointReorgReporter { Starknet(updated_conn) }) } + Kaspa(conn) => { + vec![( + Url::parse("http://localhost:16200").unwrap(), + ChainConnectionConf::Kaspa(conn), + )] + } + Radix(conn) => { + Self::map_urls_to_connections(conn.gateway.clone(), conn, |conn, url| { + let mut updated_conn = conn.clone(); + updated_conn.gateway = vec![url]; + Radix(updated_conn) + }) + } }; chain_conn_confs diff --git a/rust/main/agents/validator/src/server/merkle_tree_insertions/list_merkle_tree_insertions.rs b/rust/main/agents/validator/src/server/merkle_tree_insertions/list_merkle_tree_insertions.rs index df9dace5f71..43ecc2e9835 100644 --- a/rust/main/agents/validator/src/server/merkle_tree_insertions/list_merkle_tree_insertions.rs +++ b/rust/main/agents/validator/src/server/merkle_tree_insertions/list_merkle_tree_insertions.rs @@ -131,6 +131,15 @@ mod tests { .expect("DB Error") } + fn insert_merkle_tree_insertion_block_number( + db: &HyperlaneRocksDB, + leaf_index: u32, + block_number: u64, + ) { + db.store_merkle_tree_insertion_block_number_by_leaf_index(&leaf_index, &block_number) + .expect("DB Error") + } + #[tracing_test::traced_test] #[tokio::test] async fn test_list_merkle_tree_insertions_empty_db() { @@ -163,6 +172,9 @@ mod tests { for insertion in insertions.iter() { insert_merkle_tree_insertion(&db, insertion.index(), insertion); } + for insertion in insertions.iter().take(2) { + insert_merkle_tree_insertion_block_number(&db, insertion.index(), 100); + } let leaf_index_start = 100; let leaf_index_end = 102; @@ -175,14 +187,17 @@ mod tests { let expected_list = [ TreeInsertion { + insertion_block_number: Some(100), leaf_index: 100, message_id: format!("{:?}", H256::from_low_u64_be(100)), }, TreeInsertion { + insertion_block_number: Some(100), leaf_index: 101, message_id: format!("{:?}", H256::from_low_u64_be(101)), }, TreeInsertion { + insertion_block_number: None, leaf_index: 102, message_id: format!("{:?}", H256::from_low_u64_be(102)), }, diff --git a/rust/main/agents/validator/src/settings.rs b/rust/main/agents/validator/src/settings.rs index c6f0fc9a995..58b5643cf2e 100644 --- a/rust/main/agents/validator/src/settings.rs +++ b/rust/main/agents/validator/src/settings.rs @@ -4,7 +4,7 @@ //! and validations it defines are not applied here, we should mirror them. //! ANY CHANGES HERE NEED TO BE REFLECTED IN THE TYPESCRIPT SDK. -use std::{collections::HashSet, path::PathBuf, time::Duration}; +use std::{collections::HashSet, ops::Add, path::PathBuf, time::Duration}; use aws_config::Region; use derive_more::{AsMut, AsRef, Deref, DerefMut}; @@ -22,6 +22,7 @@ use hyperlane_core::{ use itertools::Itertools; use serde::Deserialize; use serde_json::Value; +use tracing::warn; /// Settings for RPCs #[derive(Debug, Clone)] @@ -88,6 +89,8 @@ impl FromRawConf for ValidatorSettings { .parse_string() .end(); + warn!("origin_chain_name: {:?}", origin_chain_name); + let allow_public_rpcs = p .chain(&mut err) .get_opt_key("allowPublicRpcs") @@ -108,7 +111,7 @@ impl FromRawConf for ValidatorSettings { { base.lookup_domain(origin_chain_name) .context("Missing configuration for the origin chain") - .take_err(&mut err, || cwp + "origin_chain_name") + .take_err(&mut err, || cwp.add("origin_chain_name")) } else { None }; @@ -334,9 +337,8 @@ fn parse_checkpoint_syncer(syncer: ValueParser) -> ConfigResult { - Err(eyre!("Unknown checkpoint syncer type")).into_config_result(|| &syncer.cwp + "type") - } + Some(_) => Err(eyre!("Unknown checkpoint syncer type")) + .into_config_result(|| (&syncer.cwp).add("type")), None => Err(err), } } diff --git a/rust/main/agents/validator/src/submit.rs b/rust/main/agents/validator/src/submit.rs index dce767c6b96..98c5fd73dd7 100644 --- a/rust/main/agents/validator/src/submit.rs +++ b/rust/main/agents/validator/src/submit.rs @@ -386,7 +386,10 @@ impl ValidatorSubmitter { /// Signs and submits any previously unsubmitted checkpoints. async fn sign_and_submit_checkpoints(&self, mut checkpoints: Vec) { // The checkpoints are ordered by index, so the last one is the highest index. - let last_checkpoint_index = checkpoints[checkpoints.len() - 1].index; + let last_checkpoint_index = match checkpoints.last() { + Some(c) => c.index, + None => return, + }; let arc_self = Arc::new(self.clone()); @@ -468,7 +471,7 @@ impl ValidatorSubmitter { fn tree_exceeds_checkpoint(checkpoint: &Checkpoint, tree: &IncrementalMerkle) -> bool { // tree.index() will panic if the tree is empty, so we use tree.count() instead // and convert the correctness_checkpoint.index to a count by adding 1. - checkpoint.index + 1 < tree.count() as u32 + checkpoint.index.saturating_add(1) < tree.count() as u32 } #[derive(Clone)] diff --git a/rust/main/agents/validator/src/validator.rs b/rust/main/agents/validator/src/validator.rs index 6159344de26..7db7d111413 100644 --- a/rust/main/agents/validator/src/validator.rs +++ b/rust/main/agents/validator/src/validator.rs @@ -1,4 +1,4 @@ -use std::{fmt::Debug, sync::Arc, time::Duration}; +use std::{fmt::Debug, path::PathBuf, sync::Arc, time::Duration}; use async_trait::async_trait; use axum::Router; @@ -11,6 +11,7 @@ use serde::Serialize; use tokio::{task::JoinHandle, time::sleep}; use tracing::{error, info, info_span, warn, Instrument}; +use dymension_kaspa; use hyperlane_base::{ db::{HyperlaneDb, HyperlaneRocksDB, DB}, git_sha, @@ -33,6 +34,7 @@ use crate::{ settings::ValidatorSettings, submit::{ValidatorSubmitter, ValidatorSubmitterMetrics}, }; +use dymension_kaspa::{is_kas, KaspaProvider}; /// A validator agent #[derive(Debug, AsRef)] @@ -42,6 +44,7 @@ pub struct Validator { #[as_ref] core: HyperlaneAgentCore, db: HyperlaneRocksDB, + db_path: PathBuf, merkle_tree_hook_sync: Arc>, mailbox: Arc, merkle_tree_hook: Arc, @@ -60,6 +63,7 @@ pub struct Validator { agent_metadata: ValidatorMetadata, max_sign_concurrency: usize, reorg_reporter: Arc, + dymension_kaspa_args: Option, } /// Metadata for `validator` @@ -144,9 +148,12 @@ impl BaseAgent for Validator { .expect("Failed to build checkpoint syncer") .into(); - let mailbox = settings - .build_mailbox(&settings.origin_chain, &metrics) - .await?; + let origin_chain_conf = core.settings.chain_setup(&settings.origin_chain)?.clone(); + + let mailbox = origin_chain_conf.build_mailbox(&metrics).await?; + + let dymension_args = + Self::get_dymension_kaspa_args(origin_chain_conf.clone(), &mailbox).await?; let merkle_tree_hook = settings .build_merkle_tree_hook(&settings.origin_chain, &metrics) @@ -156,8 +163,6 @@ impl BaseAgent for Validator { .build_validator_announce(&settings.origin_chain, &metrics) .await?; - let origin_chain_conf = core.settings.chain_setup(&settings.origin_chain)?.clone(); - let contract_sync_metrics = Arc::new(ContractSyncMetrics::new(&metrics)); let merkle_tree_hook_sync = settings @@ -176,6 +181,7 @@ impl BaseAgent for Validator { origin_chain_conf, core, db: msg_db, + db_path: settings.db.clone(), mailbox: mailbox.into(), merkle_tree_hook: merkle_tree_hook.into(), merkle_tree_hook_sync, @@ -193,6 +199,7 @@ impl BaseAgent for Validator { agent_metadata, max_sign_concurrency: settings.max_sign_concurrency, reorg_reporter, + dymension_kaspa_args: dymension_args, }) } @@ -201,7 +208,7 @@ impl BaseAgent for Validator { let mut tasks = vec![]; // run server - let router = Router::new() + let mut router = Router::new() .merge(validator_server::router( self.origin_chain.clone(), self.core.metrics.clone(), @@ -213,6 +220,43 @@ impl BaseAgent for Validator { .router(), ); + warn!("is kaspa: {}", is_kas(&self.origin_chain)); + + if is_kas(&self.origin_chain) { + let prov = self.dymension_kaspa_args.clone().unwrap().kas_provider; + + let is_migration = prov.conf().is_migration_mode(); + let migration_target = prov.conf().migrate_escrow_to.as_deref(); + info!( + is_migration_mode = is_migration, + migration_target = migration_target, + "Kaspa validator mode" + ); + + // Migration lock file logic + if is_migration { + // Write lock file with the migration target address + if let Some(target) = migration_target { + dymension_kaspa::write_migration_lock(&self.db_path, target) + .expect("Failed to write migration lock file"); + } + } else { + // Check lock file against configured escrow + let configured_escrow = prov.escrow_address().to_string(); + dymension_kaspa::check_migration_lock(&self.db_path, &configured_escrow) + .expect("Migration lock check failed"); + } + + let signing = dymension_kaspa::ValidatorISMSigningResources::new( + Arc::new(self.raw_signer.clone()), + self.signer.clone(), + ); + + router = router.merge(dymension_kaspa::router( + dymension_kaspa::ValidatorServerResources::new(signing, prov), + )) + } + let server = self .core .settings @@ -501,3 +545,22 @@ impl Validator { } } } + +#[derive(Debug, Clone)] +struct DymensionKaspaArgs { + kas_provider: Box, +} + +impl Validator { + async fn get_dymension_kaspa_args( + conf: ChainConf, + kas_mailbox: &Box, + ) -> Result> { + if !is_kas(&conf.domain) { + return Ok(None); + } + let kas_provider_trait = kas_mailbox.provider(); + let kas_provider = kas_provider_trait.downcast::().unwrap(); + Ok(Some(DymensionKaspaArgs { kas_provider })) + } +} diff --git a/rust/main/applications/hyperlane-application/src/lib.rs b/rust/main/applications/hyperlane-application/src/lib.rs index 365bd5ee2b9..0f65ba74368 100644 --- a/rust/main/applications/hyperlane-application/src/lib.rs +++ b/rust/main/applications/hyperlane-application/src/lib.rs @@ -1,3 +1,5 @@ +#![deny(clippy::arithmetic_side_effects)] + pub use application_report::ApplicationReport; mod application_report; diff --git a/rust/main/applications/hyperlane-operation-verifier/src/lib.rs b/rust/main/applications/hyperlane-operation-verifier/src/lib.rs index eebc50cf25a..f14de09d594 100644 --- a/rust/main/applications/hyperlane-operation-verifier/src/lib.rs +++ b/rust/main/applications/hyperlane-operation-verifier/src/lib.rs @@ -1,3 +1,5 @@ +#![deny(clippy::arithmetic_side_effects)] + pub use operation_verifier::ApplicationOperationVerifier; pub use operation_verifier::ApplicationOperationVerifierReport; diff --git a/rust/main/applications/hyperlane-warp-route/Cargo.toml b/rust/main/applications/hyperlane-warp-route/Cargo.toml index d1510170e24..6c4d575671d 100644 --- a/rust/main/applications/hyperlane-warp-route/Cargo.toml +++ b/rust/main/applications/hyperlane-warp-route/Cargo.toml @@ -1,4 +1,3 @@ -cargo-features = ["workspace-inheritance"] [package] name = "hyperlane-warp-route" diff --git a/rust/main/applications/hyperlane-warp-route/src/lib.rs b/rust/main/applications/hyperlane-warp-route/src/lib.rs index 381ddb75263..5b7e7436b50 100644 --- a/rust/main/applications/hyperlane-warp-route/src/lib.rs +++ b/rust/main/applications/hyperlane-warp-route/src/lib.rs @@ -1,3 +1,5 @@ +#![deny(clippy::arithmetic_side_effects)] + pub use token_message::TokenMessage; mod token_message; diff --git a/rust/main/applications/hyperlane-warp-route/src/token_message.rs b/rust/main/applications/hyperlane-warp-route/src/token_message.rs index a9633dd787e..618102cce49 100644 --- a/rust/main/applications/hyperlane-warp-route/src/token_message.rs +++ b/rust/main/applications/hyperlane-warp-route/src/token_message.rs @@ -24,7 +24,7 @@ impl Encode for TokenMessage { writer.write_all(&self.metadata)?; - Ok(32 + 32 + self.metadata.len()) + Ok(self.metadata.len().saturating_add(32).saturating_add(32)) } } diff --git a/rust/main/chains/dymension-kaspa/Cargo.toml b/rust/main/chains/dymension-kaspa/Cargo.toml new file mode 100644 index 00000000000..3b605fddf3e --- /dev/null +++ b/rust/main/chains/dymension-kaspa/Cargo.toml @@ -0,0 +1,85 @@ +[package] +name = "dymension-kaspa" +documentation = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +license-file = { workspace = true } +publish = { workspace = true } +version = { workspace = true } + +[dependencies] +async-trait = { workspace = true } +base64 = { workspace = true } +bech32 = { workspace = true } +cosmrs = { workspace = true, features = ["tokio", "grpc", "rpc"] } +crypto = { path = "../../utils/crypto" } +derive-new = { workspace = true } +futures = { workspace = true } +hex = { workspace = true } +http = { workspace = true } +hyper = { workspace = true } +hyper-tls = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +sha2 = { workspace = true } +sha3 = { workspace = true } +sha256 = { workspace = true } +tendermint = { workspace = true, features = ["rust-crypto", "secp256k1"] } +tendermint-rpc = { workspace = true } +time = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } + +tonic = { workspace = true, features = [ + "transport", + "tls", + "tls-roots", + "tls-native-roots", +] } +rustls = { workspace = true } +tower.workspace = true +tower-http = { workspace = true, features = ["limit"] } +tracing = { workspace = true } +tracing-futures = { workspace = true } +url = { workspace = true } +reqwest = { workspace = true } +axum = { workspace = true } +anyhow = { workspace = true } +ethers = { workspace = true } +dym-kas-core = { path = "../../../../dymension/libs/kaspa/lib/core" , package="core"} +dym-kas-hardcode = { path = "../../../../dymension/libs/kaspa/lib/hardcode", package="hardcode"} +dym-kas-api = { path = "../../../../dymension/libs/kaspa/lib/api" , package="openapi"} +dym-kas-kms = { path = "../../../../dymension/libs/kaspa/lib/kms", package="dym-kas-kms"} +eyre = { workspace = true } +bytes = { workspace = true } +prometheus = { workspace = true } + +hyperlane-metric = { path = "../../hyperlane-metric" } +hyperlane-core = { path = "../../hyperlane-core", features = ["async", "ethers"] } +hyperlane-cosmwasm-interface = { workspace = true } +hyperlane-operation-verifier = { path = "../../applications/hyperlane-operation-verifier" } +hyperlane-warp-route = { path = "../../applications/hyperlane-warp-route" } +hyperlane-cosmos = { path = "../hyperlane-cosmos" } +hyperlane-cosmos-rs = { package = "hyperlane-cosmos-dymension-rs", git = "https://github.com/dymensionxyz/hyperlane-cosmos-rs", rev = "8fd5d6a" } + +# Kaspa + +kaspa-consensus-core = { workspace = true } +kaspa-txscript = { workspace = true } +kaspa-wallet-pskt = { workspace = true } +kaspa-wrpc-client = { workspace = true } +kaspa-addresses = { workspace = true } +kaspa-wallet-core = { workspace = true } +kaspa-wallet-keys = { workspace = true } +kaspa-rpc-core = { workspace = true } +kaspa-core = { workspace = true } +kaspa-grpc-client = { workspace = true } +kaspa-utils-tower = { workspace = true } +kaspa-hashes = { workspace = true } +kaspa-bip32 = { workspace = true } +kaspa-consensus-client = { workspace = true } +workflow-core = { workspace = true } + +[build-dependencies] +anyhow = { workspace = true } +vergen = { version = "8.3.2", features = ["build", "git", "gitcl"] } \ No newline at end of file diff --git a/rust/main/chains/dymension-kaspa/README.md b/rust/main/chains/dymension-kaspa/README.md new file mode 100644 index 00000000000..8673c53ff29 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/README.md @@ -0,0 +1,3 @@ +## CONTRIBUTING + +The basis of this code was ../hyperlane-cosmos-native \ No newline at end of file diff --git a/rust/main/chains/dymension-kaspa/build.rs b/rust/main/chains/dymension-kaspa/build.rs new file mode 100644 index 00000000000..ca3892c1db3 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/build.rs @@ -0,0 +1,8 @@ +use anyhow::Result; +use vergen::EmitBuilder; + +fn main() -> Result<()> { + EmitBuilder::builder().git_sha(false).emit()?; + + Ok(()) +} diff --git a/rust/main/chains/dymension-kaspa/src/application.rs b/rust/main/chains/dymension-kaspa/src/application.rs new file mode 100644 index 00000000000..56565709ad2 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/application.rs @@ -0,0 +1,3 @@ +pub use operation_verifier::KaspaApplicationOperationVerifier; + +mod operation_verifier; diff --git a/rust/main/chains/dymension-kaspa/src/application/operation_verifier.rs b/rust/main/chains/dymension-kaspa/src/application/operation_verifier.rs new file mode 100644 index 00000000000..2a63a4f1910 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/application/operation_verifier.rs @@ -0,0 +1,21 @@ +use async_trait::async_trait; +use derive_new::new; + +use hyperlane_core::HyperlaneMessage; +use hyperlane_operation_verifier::{ + ApplicationOperationVerifier, ApplicationOperationVerifierReport, +}; + +#[derive(new)] +pub struct KaspaApplicationOperationVerifier {} + +#[async_trait] +impl ApplicationOperationVerifier for KaspaApplicationOperationVerifier { + async fn verify( + &self, + _app_context: &Option, + _message: &HyperlaneMessage, + ) -> Option { + return None; + } +} diff --git a/rust/main/chains/dymension-kaspa/src/conf.rs b/rust/main/chains/dymension-kaspa/src/conf.rs new file mode 100644 index 00000000000..8d777e1b292 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/conf.rs @@ -0,0 +1,296 @@ +use std::str::FromStr; + +use derive_new::new; +use serde::Deserialize; +use url::Url; + +use hyperlane_core::{ + config::OpSubmissionConfig, ChainCommunicationError, FixedPointNumber, H256, U256, +}; + +/// Escrow validator configuration for withdrawals and migrations. +/// +/// # Ordering Requirements +/// +/// The order of validators in the config array is significant: +/// The `escrow_pub` keys are extracted in config order to derive the Kaspa multisig escrow address. +/// Changing the order will produce a different escrow address. +/// The order must match the existing production escrow for backwards compatibility. +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct KaspaValidatorEscrow { + pub host: String, + #[serde(alias = "escrowpub")] + pub escrow_pub: String, +} + +/// ISM validator configuration for deposits and confirmations. +/// +/// # Ordering Requirements +/// +/// ISM signature ordering: Signatures for deposits and confirmations are sorted by ISM address +/// at runtime (lexicographic order of H160 bytes) before submission to the Hub ISM. +/// The relayer handles this sorting automatically. +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct KaspaValidatorIsm { + pub host: String, + #[serde(alias = "ismaddress")] + pub ism_address: String, +} + +#[derive(Debug, Clone)] +pub struct ConnectionConf { + // Used by both validator and relayer since easiest way to get WRPC client is through wallet + pub wallet_secret: String, + pub wallet_dir: Option, + + pub kaspa_urls_wrpc: Vec, // Direct connection to Kaspa DAG node, e.g. localhost:17210 + pub kaspa_urls_rest: Vec, // Connection to Kaspa indexer server, e.g. https://api.kaspa.org + + // Used by both agents to build escrow public object + pub validator_pub_keys: Vec, + + pub multisig_threshold_hub_ism: usize, // Could be queried from Dymension destination object instead + pub multisig_threshold_kaspa: usize, + + pub hub_grpc_urls: Vec, + pub op_submission_config: OpSubmissionConfig, + + pub min_deposit_sompi: U256, + pub validator_stuff: Option, + pub relayer_stuff: Option, + + /// If set, enables migration mode. + /// For validators: only migration TX signing and confirmation are allowed. + /// For relayers: run escrow key migration to this address and exit. + pub migrate_escrow_to: Option, +} + +#[derive(Debug, Clone)] +pub struct ValidatorStuff { + pub hub_domain: u32, + pub hub_token_id: H256, + pub kas_domain: u32, + pub kas_token_placeholder: H256, + pub hub_mailbox_id: String, + pub kas_escrow_key_source: KaspaEscrowKeySource, + pub kaspa_grpc_urls: Vec, + pub toggles: ValidationConf, +} + +#[derive(Debug, Clone)] +pub enum KaspaEscrowKeySource { + Direct(String), + Aws(dym_kas_kms::AwsKeyConfig), +} + +impl KaspaEscrowKeySource { + /// Load the Kaspa escrow keypair from the configured source. + pub async fn load_keypair(&self) -> eyre::Result { + match self { + KaspaEscrowKeySource::Direct(json_str) => serde_json::from_str(json_str) + .map_err(|e| eyre::eyre!("parse Kaspa keypair from JSON: {}", e)), + KaspaEscrowKeySource::Aws(aws_config) => { + dym_kas_kms::load_kaspa_keypair_from_aws(aws_config) + .await + .map_err(|e| eyre::eyre!("load Kaspa keypair from AWS: {}", e)) + } + } + } +} + +pub use dym_kas_kms::AwsKeyConfig; + +#[derive(Debug, Clone)] +pub struct RelayerStuff { + /// Escrow signers (for withdrawals and migration) + pub validators_escrow: Vec, + /// ISM signers (for deposits and confirmations) + pub validators_ism: Vec, + pub deposit_timings: RelayerDepositTimings, + pub tx_fee_multiplier: f64, + pub max_sweep_inputs: Option, + pub max_sweep_bundle_bytes: usize, + pub validator_request_timeout: std::time::Duration, +} + +#[derive(Debug, Clone)] +pub struct RelayerDepositTimings { + pub poll_interval: std::time::Duration, + pub retry_delay_base: std::time::Duration, + pub retry_delay_exponent: f64, + pub retry_delay_max: std::time::Duration, + pub deposit_look_back: std::time::Duration, + pub deposit_query_overlap: std::time::Duration, +} + +impl Default for RelayerDepositTimings { + fn default() -> Self { + Self { + poll_interval: std::time::Duration::from_secs(5), + retry_delay_base: std::time::Duration::from_secs(30), + retry_delay_exponent: 2.0, + retry_delay_max: std::time::Duration::from_secs(3600), + deposit_look_back: std::time::Duration::from_secs(0), + deposit_query_overlap: std::time::Duration::from_secs(60 * 5), + } + } +} + +#[derive(Debug, Clone)] +pub struct ValidationConf { + pub validate_deposits: bool, + pub validate_withdrawals: bool, + pub validate_confirmations: bool, +} + +impl Default for ValidationConf { + fn default() -> Self { + Self { + validate_deposits: true, + validate_withdrawals: true, + validate_confirmations: true, + } + } +} + +impl ConnectionConf { + #[allow(clippy::too_many_arguments)] + pub fn new( + wallet_secret: String, + wallet_dir: Option, + kaspa_urls_wrpc: Vec, + kaspa_urls_rest: Vec, + validators_escrow: Vec, + validators_ism: Vec, + kaspa_escrow_key_source: Option, + kaspa_urls_grpc: Vec, + multisig_threshold_hub_ism: usize, + multisig_threshold_kaspa_schnorr: usize, + hub_grpc_urls: Vec, + hub_mailbox_id: String, + op_submission_config: OpSubmissionConfig, + validation_conf: ValidationConf, + min_deposit_sompi: U256, + kaspa_time_config: Option, + + hub_domain: u32, + hub_token_id: H256, + + kas_domain: u32, + kas_token_placeholder: H256, + kas_tx_fee_multiplier: f64, + max_sweep_inputs: Option, + validator_request_timeout: std::time::Duration, + migrate_escrow_to: Option, + ) -> Self { + // Extract escrow pub keys from escrow validators (used by both validator and relayer) + let validator_pub_keys: Vec = validators_escrow + .iter() + .map(|v| v.escrow_pub.clone()) + .collect(); + + // Check if this is a relayer config (has validator hosts configured) + let has_relayer_config = validators_escrow.iter().any(|v| !v.host.is_empty()); + + let v = match kaspa_escrow_key_source { + Some(kas_escrow_key_source) => { + if hub_domain == 0 + || kas_domain == 0 + || hub_token_id == H256::default() + || kaspa_urls_grpc.is_empty() + { + panic!("Missing validator config: hub_domain: {}, kas_domain: {}, hub_token_id: {}, kas_token_placeholder: {}, kaspaUrlsGrpc: {:?}", hub_domain, kas_domain, hub_token_id, kas_token_placeholder, kaspa_urls_grpc) + } else { + Some(ValidatorStuff { + hub_domain, + hub_token_id, + kas_domain, + kas_token_placeholder, + hub_mailbox_id, + kas_escrow_key_source, + kaspa_grpc_urls: kaspa_urls_grpc, + toggles: validation_conf, + }) + } + } + None => None, + }; + + let r = if has_relayer_config { + let deposit_timings = kaspa_time_config.unwrap_or_default(); + Some(RelayerStuff { + validators_escrow, + validators_ism, + deposit_timings, + tx_fee_multiplier: kas_tx_fee_multiplier, + max_sweep_inputs, + max_sweep_bundle_bytes: 8 * 1024 * 1024, + validator_request_timeout, + }) + } else { + None + }; + + // Log early (before tracing is initialized) so migration mode is visible even if wallet connection fails + println!( + "Kaspa config: is_migration_mode={}, migration_target={:?}", + migrate_escrow_to.is_some(), + migrate_escrow_to.as_deref() + ); + + Self { + wallet_secret, + wallet_dir, + kaspa_urls_wrpc, + kaspa_urls_rest, + validator_stuff: v, + validator_pub_keys, + multisig_threshold_hub_ism, + multisig_threshold_kaspa: multisig_threshold_kaspa_schnorr, + hub_grpc_urls, + relayer_stuff: r, + op_submission_config, + min_deposit_sompi, + migrate_escrow_to, + } + } + + /// Returns true if migration mode is active. + pub fn is_migration_mode(&self) -> bool { + self.migrate_escrow_to.is_some() + } + + /// Returns the parsed migration target address if in migration mode. + pub fn parsed_migration_target(&self) -> Option { + self.migrate_escrow_to + .as_ref() + .and_then(|s| kaspa_addresses::Address::try_from(s.as_str()).ok()) + } +} + +#[derive(serde::Serialize, serde::Deserialize, new, Clone, Debug)] +pub struct RawKaspaAmount { + pub denom: String, + pub amount: String, +} + +#[derive(Clone, Debug)] +pub struct KaspaAmount { + pub denom: String, + pub amount: FixedPointNumber, +} + +impl TryFrom for KaspaAmount { + type Error = ChainCommunicationError; + fn try_from(raw: RawKaspaAmount) -> Result { + Ok(Self { + denom: raw.denom, + amount: FixedPointNumber::from_str(&raw.amount)?, + }) + } +} + +#[derive(thiserror::Error, Debug)] +pub enum ConnectionConfError {} diff --git a/rust/main/chains/dymension-kaspa/src/consts.rs b/rust/main/chains/dymension-kaspa/src/consts.rs new file mode 100644 index 00000000000..3f0865e0a08 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/consts.rs @@ -0,0 +1,53 @@ +//! Constants for Kaspa Hyperlane integration. +//! +//! Includes domain IDs and protocol constants. + +use hyperlane_core::H256; + +// ============================================================================ +// Domain IDs for Hyperlane protocol routing +// ============================================================================ + +pub const ALLOWED_HL_MESSAGE_VERSION: u8 = 3; + +pub const HL_DOMAIN_DYM_MAINNET: u32 = 1570310961; // NOTE: was patched to this value just before mainnet release. The old value did not do modulo on derivation. +pub const HL_DOMAIN_DYM_LOCAL: u32 = 587907060; +pub const HL_DOMAIN_DYM_TESTNET_BLUMBUS: u32 = 482195613; +pub const HL_DOMAIN_DYM_PLAYGROUND_202507: u32 = 180353102; +pub const HL_DOMAIN_DYM_PLAYGROUND_202507_LEGACY: u32 = 1260813472; +pub const HL_DOMAIN_DYM_PLAYGROUND_202509: u32 = 1260813473; +pub const HL_DOMAIN_KASPA_MAINNET: u32 = 1082673309; +pub const HL_DOMAIN_KASPA_TEST10: u32 = 897658017; +pub const HL_DOMAIN_KASPA_TEST10_LEGACY: u32 = 80808082; // deprecated + +// ============================================================================ +// Kaspa contract addresses (placeholder values) +// ============================================================================ + +// These are arbitrary, but MUST be unique and consistent. +// A good practice is to use a hash of a descriptive string. +// e.g., keccak256("kaspa_mailbox") + +#[allow(dead_code)] +pub const KASPA_MAILBOX_ADDRESS: H256 = H256([ + 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +]); + +#[allow(dead_code)] +pub const KASPA_VALIDATOR_ANNOUNCE_ADDRESS: H256 = H256([ + 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +]); + +#[allow(dead_code)] +pub const KASPA_IGP_ADDRESS: H256 = H256([ + 0x03, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +]); + +#[allow(dead_code)] +pub const KASPA_ISM_ADDRESS: H256 = H256([ + 0x04, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, +]); diff --git a/rust/main/chains/dymension-kaspa/src/endpoints.rs b/rust/main/chains/dymension-kaspa/src/endpoints.rs new file mode 100644 index 00000000000..08faadc1396 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/endpoints.rs @@ -0,0 +1,4 @@ +pub const ROUTE_VALIDATE_NEW_DEPOSITS: &str = "/validate_new_deposits"; +pub const ROUTE_SIGN_PSKTS: &str = "/sign_pskts"; +pub const ROUTE_VALIDATE_CONFIRMED_WITHDRAWALS: &str = "/validate_confirmed_withdrawals"; +pub const ROUTE_SIGN_MIGRATION: &str = "/sign_migration"; diff --git a/rust/main/chains/hyperlane-cosmos-native/src/error.rs b/rust/main/chains/dymension-kaspa/src/error.rs similarity index 52% rename from rust/main/chains/hyperlane-cosmos-native/src/error.rs rename to rust/main/chains/dymension-kaspa/src/error.rs index 16cd1ef3b9f..4b32267232d 100644 --- a/rust/main/chains/hyperlane-cosmos-native/src/error.rs +++ b/rust/main/chains/dymension-kaspa/src/error.rs @@ -5,82 +5,56 @@ use crypto::PublicKeyError; use hyperlane_core::ChainCommunicationError; -/// Errors from the crates specific to the hyperlane-cosmos -/// implementation. -/// This error can then be converted into the broader error type -/// in hyperlane-core using the `From` trait impl #[derive(Debug, thiserror::Error)] -pub enum HyperlaneCosmosError { - /// base64 error +pub enum HyperlaneKaspaError { #[error("{0}")] Base64(#[from] base64::DecodeError), - /// bech32 decode error #[error("{0}")] Bech32Decode(#[from] bech32::DecodeError), - /// bech32 encode error #[error("{0}")] Bech32Encode(#[from] bech32::EncodeError), - /// gRPC error #[error("{0}")] GrpcError(#[from] tonic::Status), - /// Cosmos error #[error("{0}")] - CosmosError(#[from] cosmrs::Error), - /// Cosmos error report + HyperlaneKaspaError(#[from] cosmrs::Error), #[error("{0}")] - CosmosErrorReport(#[from] cosmrs::ErrorReport), - /// Cosmrs Tendermint Error + KaspaErrorReport(#[from] cosmrs::ErrorReport), #[error("{0}")] CosmrsTendermintError(#[from] cosmrs::tendermint::Error), - /// Tonic error #[error("{0}")] Tonic(#[from] tonic::transport::Error), - /// Tonic codegen error #[error("{0}")] TonicGenError(#[from] tonic::codegen::StdError), - /// Tendermint RPC Error #[error(transparent)] TendermintRpcError(#[from] tendermint_rpc::error::Error), - /// Prost error #[error("{0}")] Prost(#[from] prost::DecodeError), - /// Protobuf error - #[error("{0}")] - Protobuf(#[from] protobuf::ProtobufError), - /// Fallback providers failed #[error("Fallback providers failed. (Errors: {0:?})")] - FallbackProvidersFailed(Vec), - /// Public key error + FallbackProvidersFailed(Vec), #[error("{0}")] PublicKeyError(String), - /// Address error #[error("{0}")] AddressError(String), - /// Signer info error #[error("{0}")] SignerInfoError(String), - /// Serde error #[error("{0}")] SerdeError(#[from] serde_json::Error), - /// Empty error #[error("{0}")] UnparsableEmptyField(String), - /// Parsing error #[error("{0}")] ParsingFailed(String), - /// Parsing attempt failed #[error("Parsing attempt failed. (Errors: {0:?})")] - ParsingAttemptsFailed(Vec), + ParsingAttemptsFailed(Vec), } -impl From for ChainCommunicationError { - fn from(value: HyperlaneCosmosError) -> Self { +impl From for ChainCommunicationError { + fn from(value: HyperlaneKaspaError) -> Self { ChainCommunicationError::from_other(value) } } -impl From for HyperlaneCosmosError { +impl From for HyperlaneKaspaError { fn from(value: PublicKeyError) -> Self { - HyperlaneCosmosError::PublicKeyError(value.to_string()) + HyperlaneKaspaError::PublicKeyError(value.to_string()) } } diff --git a/rust/main/chains/hyperlane-cosmos-native/src/indexers.rs b/rust/main/chains/dymension-kaspa/src/indexers.rs similarity index 100% rename from rust/main/chains/hyperlane-cosmos-native/src/indexers.rs rename to rust/main/chains/dymension-kaspa/src/indexers.rs diff --git a/rust/main/chains/dymension-kaspa/src/indexers/delivery.rs b/rust/main/chains/dymension-kaspa/src/indexers/delivery.rs new file mode 100644 index 00000000000..34269ee89f0 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/indexers/delivery.rs @@ -0,0 +1,64 @@ +use super::KaspaEventIndexer; +use crate::{KaspaProvider, RestProvider}; +use hyperlane_core::{ + ChainCommunicationError, ChainResult, ContractLocator, Indexed, Indexer, LogMeta, + SequenceAwareIndexer, H256, H512, +}; +use std::ops::RangeInclusive; +use tonic::async_trait; +use tracing::instrument; + +#[derive(Debug, Clone)] +pub struct KaspaDelivery { + provider: KaspaProvider, + address: H256, +} + +impl KaspaDelivery { + pub fn new(provider: KaspaProvider, locator: ContractLocator) -> ChainResult { + Ok(KaspaDelivery { + provider, + address: locator.address, + }) + } +} + +impl KaspaEventIndexer for KaspaDelivery { + fn provider(&self) -> &RestProvider { + self.provider.rest() + } + + fn address(&self) -> &H256 { + &self.address + } +} + +#[async_trait] +impl Indexer for KaspaDelivery { + #[instrument(err, skip(self))] + #[allow(clippy::blocks_in_conditions)] + async fn fetch_logs_in_range( + &self, + _range: RangeInclusive, + ) -> ChainResult, LogMeta)>> { + Err(ChainCommunicationError::from_other_str("not implemented")) + } + + async fn get_finalized_block_number(&self) -> ChainResult { + Err(ChainCommunicationError::from_other_str("not implemented")) + } + + async fn fetch_logs_by_tx_hash( + &self, + _tx_hash: H512, + ) -> ChainResult, LogMeta)>> { + Err(ChainCommunicationError::from_other_str("not implemented")) + } +} + +#[async_trait] +impl SequenceAwareIndexer for KaspaDelivery { + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { + Err(ChainCommunicationError::from_other_str("not implemented")) + } +} diff --git a/rust/main/chains/dymension-kaspa/src/indexers/dispatch.rs b/rust/main/chains/dymension-kaspa/src/indexers/dispatch.rs new file mode 100644 index 00000000000..97196100b7a --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/indexers/dispatch.rs @@ -0,0 +1,66 @@ +use super::KaspaEventIndexer; +use crate::{KaspaProvider, RestProvider}; +use hyperlane_core::{ + ChainCommunicationError, ChainResult, ContractLocator, HyperlaneMessage, Indexed, Indexer, + LogMeta, SequenceAwareIndexer, H256, H512, +}; +use std::ops::RangeInclusive; +use tonic::async_trait; +use tracing::instrument; + +#[derive(Debug, Clone)] +pub struct KaspaDispatch { + provider: KaspaProvider, + address: H256, +} + +impl KaspaDispatch { + pub fn new(provider: KaspaProvider, locator: ContractLocator) -> ChainResult { + Ok(KaspaDispatch { + provider, + address: locator.address, + }) + } +} + +impl KaspaEventIndexer for KaspaDispatch { + fn provider(&self) -> &RestProvider { + self.provider.rest() + } + + fn address(&self) -> &H256 { + &self.address + } +} + +#[async_trait] +impl Indexer for KaspaDispatch { + #[instrument(err, skip(self))] + #[allow(clippy::blocks_in_conditions)] + async fn fetch_logs_in_range( + &self, + _range: RangeInclusive, + ) -> ChainResult, LogMeta)>> { + Err(ChainCommunicationError::from_other_str("not implemented")) + } + + async fn get_finalized_block_number(&self) -> ChainResult { + Err(ChainCommunicationError::from_other_str("not implemented")) + } + + async fn fetch_logs_by_tx_hash( + &self, + _tx_hash: H512, + ) -> ChainResult, LogMeta)>> { + Err(ChainCommunicationError::from_other_str("not implemented")) + } +} + +#[async_trait] +impl SequenceAwareIndexer for KaspaDispatch { + #[instrument(err, skip(self), ret)] + #[allow(clippy::blocks_in_conditions)] + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { + Err(ChainCommunicationError::from_other_str("not implemented")) + } +} diff --git a/rust/main/chains/dymension-kaspa/src/indexers/indexer.rs b/rust/main/chains/dymension-kaspa/src/indexers/indexer.rs new file mode 100644 index 00000000000..ec806923dd5 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/indexers/indexer.rs @@ -0,0 +1,53 @@ +use crate::RestProvider; +use hyperlane_core::{ChainCommunicationError, ChainResult, Indexed, Indexer, LogMeta, H256, H512}; +use std::fmt::Debug; +use std::ops::RangeInclusive; +use tonic::async_trait; + +#[derive(Debug, Eq, PartialEq)] +pub struct ParsedEvent { + contract_address: H256, + event: T, +} + +impl ParsedEvent { + pub fn new(contract_address: H256, event: T) -> Self { + Self { + contract_address, + event, + } + } + + pub fn inner(self) -> T { + self.event + } +} + +#[async_trait] +pub trait KaspaEventIndexer: Indexer +where + Self: Clone + Send + Sync + 'static, + Indexed: From, +{ + fn provider(&self) -> &RestProvider; + + fn address(&self) -> &H256; + + async fn get_finalized_block_number(&self) -> ChainResult { + Err(ChainCommunicationError::from_other_str("not implemented")) + } + + async fn fetch_logs_by_tx_hash( + &self, + _tx_hash: H512, + ) -> ChainResult, LogMeta)>> { + Err(ChainCommunicationError::from_other_str("not implemented")) + } + + async fn fetch_logs_in_range( + &self, + _range: RangeInclusive, + ) -> ChainResult, LogMeta)>> { + Err(ChainCommunicationError::from_other_str("not implemented")) + } +} diff --git a/rust/main/chains/dymension-kaspa/src/indexers/interchain_gas.rs b/rust/main/chains/dymension-kaspa/src/indexers/interchain_gas.rs new file mode 100644 index 00000000000..522a7872e9b --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/indexers/interchain_gas.rs @@ -0,0 +1,90 @@ +use crate::{ConnectionConf, KaspaEventIndexer, KaspaProvider, RestProvider}; +use hyperlane_core::{ + ChainCommunicationError, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, + HyperlaneDomain, HyperlaneProvider, Indexed, Indexer, InterchainGasPaymaster, + InterchainGasPayment, LogMeta, SequenceAwareIndexer, H256, H512, +}; +use std::ops::RangeInclusive; +use tonic::async_trait; + +/// delivery indexer to check if a message was delivered +#[derive(Debug, Clone)] +pub struct KaspaGas { + address: H256, + domain: HyperlaneDomain, + provider: KaspaProvider, +} + +impl InterchainGasPaymaster for KaspaGas {} + +impl KaspaGas { + /// Gas Payment Indexer + pub fn new( + provider: KaspaProvider, + _conf: &ConnectionConf, + locator: ContractLocator, + ) -> ChainResult { + Ok(KaspaGas { + address: locator.address, + domain: locator.domain.clone(), + provider, + }) + } +} + +impl KaspaEventIndexer for KaspaGas { + fn provider(&self) -> &RestProvider { + self.provider.rest() + } + + fn address(&self) -> &H256 { + &self.address + } +} + +impl HyperlaneChain for KaspaGas { + // Return the domain + fn domain(&self) -> &HyperlaneDomain { + &self.domain + } + + // A provider for the chain + fn provider(&self) -> Box { + Box::new(self.provider.clone()) + } +} + +impl HyperlaneContract for KaspaGas { + // Return the address of this contract + fn address(&self) -> H256 { + self.address + } +} + +#[async_trait] +impl Indexer for KaspaGas { + async fn fetch_logs_in_range( + &self, + _range: RangeInclusive, + ) -> ChainResult, LogMeta)>> { + Err(ChainCommunicationError::from_other_str("not implemented")) + } + + async fn get_finalized_block_number(&self) -> ChainResult { + Err(ChainCommunicationError::from_other_str("not implemented")) + } + + async fn fetch_logs_by_tx_hash( + &self, + _tx_hash: H512, + ) -> ChainResult, LogMeta)>> { + Err(ChainCommunicationError::from_other_str("not implemented")) + } +} + +#[async_trait] +impl SequenceAwareIndexer for KaspaGas { + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { + Err(ChainCommunicationError::from_other_str("not implemented")) + } +} diff --git a/rust/main/chains/dymension-kaspa/src/indexers/merkle_tree_hook.rs b/rust/main/chains/dymension-kaspa/src/indexers/merkle_tree_hook.rs new file mode 100644 index 00000000000..db4574c9423 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/indexers/merkle_tree_hook.rs @@ -0,0 +1,118 @@ +use super::KaspaEventIndexer; +use crate::{KaspaProvider, RestProvider}; +use hyperlane_core::{ + ChainCommunicationError, ChainResult, CheckpointAtBlock, ContractLocator, HyperlaneChain, + HyperlaneContract, HyperlaneDomain, HyperlaneProvider, IncrementalMerkleAtBlock, Indexed, + Indexer, LogMeta, MerkleTreeHook, MerkleTreeInsertion, ReorgPeriod, SequenceAwareIndexer, H256, + H512, +}; +use std::ops::RangeInclusive; +use tonic::async_trait; +use tracing::instrument; + +/// delivery indexer to check if a message was delivered +#[derive(Debug, Clone)] +pub struct KaspaMerkle { + provider: KaspaProvider, + address: H256, +} + +impl KaspaMerkle { + /// New + pub fn new(provider: KaspaProvider, locator: ContractLocator) -> ChainResult { + Ok(Self { + provider, + address: locator.address, + }) + } +} + +impl HyperlaneChain for KaspaMerkle { + /// Return the domain + fn domain(&self) -> &HyperlaneDomain { + self.provider.domain() + } + + /// A provider for the chain + fn provider(&self) -> Box { + self.provider.provider() + } +} + +impl HyperlaneContract for KaspaMerkle { + /// Return the address of this contract."] + fn address(&self) -> H256 { + self.address + } +} + +#[async_trait] +impl MerkleTreeHook for KaspaMerkle { + /// Return the incremental merkle tree in storage + #[instrument(level = "debug", err, ret, skip(self))] + #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue + async fn tree(&self, _reorg_period: &ReorgPeriod) -> ChainResult { + Err(ChainCommunicationError::from_other_str("not implemented")) + } + + /// Gets the current leaf count of the merkle tree + async fn count(&self, _reorg_period: &ReorgPeriod) -> ChainResult { + Ok(0) + } + + #[instrument(level = "debug", err, ret, skip(self))] + #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue + async fn latest_checkpoint( + &self, + reorg_period: &ReorgPeriod, + ) -> ChainResult { + Err(ChainCommunicationError::from_other_str("not implemented")) + } + + #[instrument(level = "debug", err, ret, skip(self))] + #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue + async fn latest_checkpoint_at_block(&self, _height: u64) -> ChainResult { + Err(ChainCommunicationError::from_other_str("not implemented")) + } +} + +impl KaspaEventIndexer for KaspaMerkle { + fn provider(&self) -> &RestProvider { + self.provider.rest() + } + + fn address(&self) -> &H256 { + &self.address + } +} + +// It's for writing observed messages into the tree +#[async_trait] +impl Indexer for KaspaMerkle { + #[instrument(err, skip(self))] + #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue + async fn fetch_logs_in_range( + &self, + _range: RangeInclusive, + ) -> ChainResult, LogMeta)>> { + Err(ChainCommunicationError::from_other_str("not implemented")) + } + + async fn get_finalized_block_number(&self) -> ChainResult { + Err(ChainCommunicationError::from_other_str("not implemented")) + } + + async fn fetch_logs_by_tx_hash( + &self, + _tx_hash: H512, + ) -> ChainResult, LogMeta)>> { + Err(ChainCommunicationError::from_other_str("not implemented")) + } +} + +#[async_trait] +impl SequenceAwareIndexer for KaspaMerkle { + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { + Ok((None, 0)) + } +} diff --git a/rust/main/chains/dymension-kaspa/src/ism.rs b/rust/main/chains/dymension-kaspa/src/ism.rs new file mode 100644 index 00000000000..a4326ec4701 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/ism.rs @@ -0,0 +1,94 @@ +use super::consts::KASPA_ISM_ADDRESS; +use tonic::async_trait; + +use hyperlane_core::{ + ChainCommunicationError, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, + HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, InterchainSecurityModule, ModuleType, + MultisigIsm, RoutingIsm, H256, U256, +}; + +use crate::KaspaProvider; + +/// Kaspa Native ISM +#[derive(Debug)] +pub struct KaspaIsm { + /// The domain of the ISM contract. + domain: HyperlaneDomain, + /// The address of the ISM contract. + address: H256, + /// The provider for the ISM contract. + provider: Box, +} + +/// The Kaspa Interchain Security Module Implementation. +impl KaspaIsm { + /// Creates a new Kaspa Interchain Security Module. + pub fn new(provider: KaspaProvider, locator: ContractLocator) -> ChainResult { + Ok(Self { + domain: locator.domain.clone(), + address: locator.address, + provider: Box::new(provider), + }) + } +} + +impl HyperlaneContract for KaspaIsm { + /// Return the address of this contract + fn address(&self) -> H256 { + self.address + } +} + +impl HyperlaneChain for KaspaIsm { + /// Return the Domain + fn domain(&self) -> &HyperlaneDomain { + &self.domain + } + + /// A provider for the chain + fn provider(&self) -> Box { + self.provider.clone() + } +} + +/// Interface for the InterchainSecurityModule chain contract. Allows abstraction over +/// different chains +#[async_trait] +impl InterchainSecurityModule for KaspaIsm { + async fn module_type(&self) -> ChainResult { + Ok(ModuleType::KaspaMultisig) + } + + async fn dry_run_verify( + &self, + _message: &HyperlaneMessage, + _metadata: &[u8], + ) -> ChainResult> { + // NOTE: is only relevant for aggeration isms -> kaspa native does not support them yet + Ok(Some(0.into())) + } +} + +/// Interface for the MultisigIsm chain contract. Allows abstraction over +/// different chains +#[async_trait] +impl MultisigIsm for KaspaIsm { + /// Returns the validator and threshold needed to verify message + async fn validators_and_threshold( + &self, + _message: &HyperlaneMessage, + ) -> ChainResult<(Vec, u8)> { + Err(ChainCommunicationError::from_other_str("not implemented")) + } +} + +/// Interface for the RoutingIsm chain contract. Allows abstraction over +/// different chains +#[async_trait] +impl RoutingIsm for KaspaIsm { + /// Returns the ISM needed to verify message + async fn route(&self, _message: &HyperlaneMessage) -> ChainResult { + // We are only bridging Dymension to Kaspa + Ok(KASPA_ISM_ADDRESS) + } +} diff --git a/rust/main/chains/dymension-kaspa/src/lib.rs b/rust/main/chains/dymension-kaspa/src/lib.rs new file mode 100644 index 00000000000..091fa992ea9 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/lib.rs @@ -0,0 +1,42 @@ +//! Implementation of hyperlane for the native kaspa module. + +#![forbid(unsafe_code)] +// #![warn(missing_docs)] + +#[allow(missing_docs)] +pub mod application; +pub mod conf; +pub mod consts; +mod error; +mod indexers; +mod ism; +#[allow(missing_docs)] +mod mailbox; +mod providers; +mod validator_announce; + +mod endpoints; + +mod util; +mod withdrawal_utils; + +pub mod ops; +pub mod relayer; +pub mod validator; + +// Direct reexports of lib stuff: +pub use consts as hl_domains; +pub use dym_kas_api; +pub use dym_kas_core; +pub use kaspa_addresses::Address as KaspaAddress; + +// Re-export message module from ops as hl_message for semantic clarity +pub use ops::message as hl_message; + +pub use util::*; + +pub use { + self::conf::*, self::error::*, self::indexers::*, self::ism::*, self::mailbox::*, + self::providers::*, self::validator::server::*, self::validator::startup::*, + self::validator_announce::*, self::withdrawal_utils::*, +}; diff --git a/rust/main/chains/dymension-kaspa/src/mailbox.rs b/rust/main/chains/dymension-kaspa/src/mailbox.rs new file mode 100644 index 00000000000..b605320d2d4 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/mailbox.rs @@ -0,0 +1,223 @@ +use super::consts::*; +use crate::relayer::withdraw::minimum::is_small_value; +use crate::withdrawal_utils::{ + calculate_failed_indexes, record_withdrawal_batch_metrics, WithdrawalStage, +}; +use crate::KaspaProvider; +use hyperlane_core::{ + utils::bytes_to_hex, BatchResult, ChainResult, ContractLocator, Decode, FixedPointNumber, + HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, + Mailbox, QueueOperation, ReorgPeriod, TxCostEstimate, TxOutcome, H256, U256, +}; +use hyperlane_cosmos_rs::dymensionxyz::dymension::kas::{WithdrawalId, WithdrawalStatus}; +use hyperlane_warp_route::TokenMessage; +use tonic::async_trait; +use tracing::{error, info}; + +// pretends to be a mailbox +#[derive(Clone, Debug)] +pub struct KaspaMailbox { + provider: KaspaProvider, + domain: HyperlaneDomain, + address: H256, +} + +impl KaspaMailbox { + pub fn new(provider: KaspaProvider, locator: ContractLocator) -> ChainResult { + Ok(KaspaMailbox { + provider, + address: locator.address, + domain: locator.domain.clone(), + }) + } + + pub fn with_provider(&self, provider: KaspaProvider) -> Self { + Self { + provider, + domain: self.domain.clone(), + address: self.address, + } + } +} + +impl HyperlaneChain for KaspaMailbox { + fn domain(&self) -> &HyperlaneDomain { + &self.domain + } + + fn provider(&self) -> Box { + Box::new(self.provider.clone()) + } +} + +impl HyperlaneContract for KaspaMailbox { + fn address(&self) -> H256 { + self.address + } +} + +#[async_trait] +impl Mailbox for KaspaMailbox { + async fn count(&self, _reorg_period: &ReorgPeriod) -> ChainResult { + return Ok(0); + } + + // Not a precise answer since actually depends on subsequent confirmation step on Kaspa, + // so may often return false negative (says not delivered when it actually is) + async fn delivered(&self, id: H256) -> ChainResult { + info!(message_id = ?id, "kaspa mailbox: checking if message is delivered already, querying hub"); + let wid = WithdrawalId { + message_id: bytes_to_hex(id.as_ref()), + }; + let res = self + .provider + .hub_rpc() + .query() + .withdrawal_status(vec![wid], None) + .await?; + match res + .status + .first() + .map(|s| WithdrawalStatus::try_from(*s).ok()) + { + Some(Some(WithdrawalStatus::Processed)) => Ok(true), + _ => Ok(false), + } + } + + async fn default_ism(&self) -> ChainResult { + Ok(KASPA_ISM_ADDRESS) + } + + async fn recipient_ism(&self, _recipient: H256) -> ChainResult { + Ok(KASPA_ISM_ADDRESS) + } + + async fn process( + &self, + _message: &HyperlaneMessage, + _metadata: &[u8], + _tx_gas_limit: Option, + ) -> ChainResult { + unimplemented!("kas does not support single message processing") + } + + fn supports_batching(&self) -> bool { + true + } + + // Hijacks the batch processing flow since Kaspa uses different TX submission model than EVM chains. + // Instead of single mailbox.process() call, we build multiple Kaspa TXs that must execute in sequence. + async fn process_batch<'a>(&self, ops: Vec<&'a QueueOperation>) -> ChainResult { + info!( + batch_size = ops.len(), + "kaspa mailbox: processing/submitting kaspa batch" + ); + + let msgs: Vec = ops + .iter() + .map(|op| op.try_batch().map(|item| item.data)) + .collect::>>()?; + + // TODO: there's not need for this, withdrawals are already tracked by the relayer using vanilla hyperlane tech + // this is just a double storage and moreover, its not at the earliest time that the relayer actually observes the mailbox + // on the hub.. + record_withdrawal_batch_metrics(self.provider.metrics(), &msgs, WithdrawalStage::Initiated); + + self.provider.hack_store_withdrawals_for_query(&msgs); + + // Cannot process withdrawals while a confirmation is pending on the Hub. + // All operations marked failed and will be retried after confirmation completes. + if self.provider.has_pending_confirmation() { + return Ok(BatchResult { + failed_indexes: (0..ops.len()).collect(), + outcome: None, + }); + } + + let processed_messages = match self + .provider + .process_withdrawal_messages(msgs.clone()) + .await + { + Ok(results) => results.into_iter().map(|(msg, _)| msg).collect(), + Err(e) => { + error!(error = ?e, "kaspa mailbox: process withdrawals TXs"); + record_withdrawal_batch_metrics( + self.provider.metrics(), + &msgs, + WithdrawalStage::Failed, + ); + Vec::new() + } + }; + + info!("kaspa mailbox: processed withdrawals TXs"); + + let failed_idxs = calculate_failed_indexes( + &msgs, + &processed_messages, + self.provider.get_min_deposit_sompi(), + ); + + if !failed_idxs.is_empty() { + error!( + failed_indexes = ?failed_idxs, + "kaspa mailbox: processed batch with failed indexes" + ); + } + + Ok(BatchResult { + outcome: None, // outcome intentionally bogus, its not read anyway + failed_indexes: failed_idxs, + }) + } + + async fn process_estimate_costs( + &self, + msg: &HyperlaneMessage, + _metadata: &[u8], + ) -> ChainResult { + let token_msg = match TokenMessage::read_from(&mut msg.body.as_slice()) { + Ok(msg) => msg, + Err(_e) => { + return Ok(TxCostEstimate { + gas_limit: 0.into(), + gas_price: FixedPointNumber::from(0), + l2_gas_limit: None, + }); + } + }; + + if is_small_value( + token_msg.amount().as_u64(), + self.provider.get_min_deposit_sompi(), + ) { + Ok(TxCostEstimate { + gas_limit: U256::MAX, + gas_price: FixedPointNumber::from(u128::MAX), + l2_gas_limit: None, + }) + } else { + Ok(TxCostEstimate { + gas_limit: 0.into(), + gas_price: FixedPointNumber::from(0), + l2_gas_limit: None, + }) + } + } + + // Only used in 'lander' mode, not applicable for Kaspa bridge + async fn process_calldata( + &self, + _message: &HyperlaneMessage, + _metadata: &[u8], + ) -> ChainResult> { + todo!() + } + + // Only used in 'lander' mode, not applicable for Kaspa bridge + fn delivered_calldata(&self, _message_id: H256) -> ChainResult>> { + todo!() + } +} diff --git a/rust/main/chains/dymension-kaspa/src/ops/addr.rs b/rust/main/chains/dymension-kaspa/src/ops/addr.rs new file mode 100644 index 00000000000..59ecf7dff1b --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/ops/addr.rs @@ -0,0 +1,67 @@ +use hyperlane_core::H256; +use kaspa_addresses::{Address, Prefix, Version}; +use kaspa_consensus_core::tx::ScriptPublicKey; +use kaspa_txscript::pay_to_address_script; + +/// Converts a Kaspa address to an H256 hash. +/// +/// Kaspa PubKey addresses have a 32-byte payload that maps directly to H256. +/// +/// # Panics +/// Panics if the address payload is not exactly 32 bytes. +pub fn kaspa_address_to_h256(address: &Address) -> H256 { + let bytes_32: [u8; 32] = address.payload.as_slice().try_into().unwrap(); + H256::from_slice(&bytes_32) +} + +/// Converts a Kaspa address string to a hex recipient string for Hyperlane. +/// +/// The output is prefixed with "0x" for use in Hyperlane transfer recipient fields. +/// +/// # Panics +/// Panics if the address string is invalid or the payload is not 32 bytes. +pub fn kaspa_address_to_hex_recipient(kaspa_addr: &str) -> String { + let addr = Address::try_from(kaspa_addr).unwrap(); + let h256 = kaspa_address_to_h256(&addr); + format!("0x{}", hex::encode(h256.as_bytes())) +} + +/// Converts an H256 hash to a Kaspa address with the specified network prefix. +/// +/// Always creates a PubKey version address. +pub fn h256_to_kaspa_address(recipient: H256, prefix: Prefix) -> Address { + Address::new(prefix, Version::PubKey, recipient.as_bytes()) +} + +/// Converts an H256 hash to a Kaspa ScriptPublicKey. +/// +/// Creates the pay-to-address script for the corresponding Kaspa address. +pub fn h256_to_script_pubkey(recipient: H256, prefix: Prefix) -> ScriptPublicKey { + pay_to_address_script(&h256_to_kaspa_address(recipient, prefix)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_address_h256_roundtrip() { + let addr = Address::try_from( + "kaspatest:qq053k5up93kj5a3l08zens447s62ndstyrnuusserehq4laun7es8q29fwd4", + ) + .unwrap(); + let h256 = kaspa_address_to_h256(&addr); + let recovered = h256_to_kaspa_address(h256, Prefix::Testnet); + assert_eq!(addr, recovered); + } + + #[test] + fn test_hex_recipient_format() { + let hex = kaspa_address_to_hex_recipient( + "kaspatest:qq053k5up93kj5a3l08zens447s62ndstyrnuusserehq4laun7es8q29fwd4", + ); + assert!(hex.starts_with("0x")); + assert_eq!(hex.len(), 66); // "0x" + 64 hex chars + assert!(hex[2..].chars().all(|c| c.is_ascii_hexdigit())); + } +} diff --git a/rust/main/chains/dymension-kaspa/src/ops/confirmation.rs b/rust/main/chains/dymension-kaspa/src/ops/confirmation.rs new file mode 100644 index 00000000000..98fe98835f8 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/ops/confirmation.rs @@ -0,0 +1,169 @@ +use crate::ops::payload::MessageID; +use bytes::Bytes; +use eyre::Error as EyreError; +use hex::ToHex; +use hyperlane_core::H256; +use hyperlane_cosmos_rs::dymensionxyz::dymension::kas::{ + ProgressIndication, TransactionOutpoint as ProtoTransactionOutpoint, WithdrawalId, +}; +use hyperlane_cosmos_rs::dymensionxyz::hyperlane::kaspa::{ + ConfirmationFxg as ProtoConfirmationFXG, ConfirmationVersion::ConfirmationVersion1, +}; +use hyperlane_cosmos_rs::prost::Message; +use kaspa_consensus_core::tx::TransactionOutpoint; +use std::str::FromStr; + +#[derive(Debug, Clone)] +pub struct ConfirmationFXG { + pub progress_indication: ProgressIndication, + /// a sequence of chronological outpoints where the first is the old outpoint on the progres indication + /// and the last is the new one + pub outpoints: Vec, +} + +impl ConfirmationFXG { + pub fn new( + progress_indication: ProgressIndication, + outpoints: Vec, + ) -> Self { + Self { + progress_indication, + outpoints, + } + } + + pub fn from_msgs_outpoints(msgs: Vec, outpoints: Vec) -> Self { + let withdrawal_ids: Vec = msgs + .into_iter() + .map(|id| WithdrawalId { + message_id: id.0.encode_hex(), + }) + .collect(); + + // TODO: or is the list the other way around? + let old = outpoints[0]; + let new = outpoints[outpoints.len() - 1]; + + let new_outpoint_indication = + hyperlane_cosmos_rs::dymensionxyz::dymension::kas::TransactionOutpoint { + transaction_id: new.transaction_id.as_bytes().to_vec(), + index: new.index, + }; + + let anchor_outpoint_indication = + hyperlane_cosmos_rs::dymensionxyz::dymension::kas::TransactionOutpoint { + transaction_id: old.transaction_id.as_bytes().to_vec(), + index: old.index, + }; + + let progress_indication = ProgressIndication { + old_outpoint: Some(anchor_outpoint_indication), + new_outpoint: Some(new_outpoint_indication), + processed_withdrawals: withdrawal_ids, + }; + + Self::new(progress_indication, outpoints) + } + + pub fn msgs(&self) -> Vec { + self.progress_indication + .processed_withdrawals + .iter() + .map(|id| MessageID(H256::from_str(&id.message_id).unwrap())) + .collect() + } +} + +impl From<&ConfirmationFXG> for Bytes { + fn from(v: &ConfirmationFXG) -> Self { + let p = ProtoConfirmationFXG::from(v); + Bytes::from(p.encode_to_vec()) + } +} + +impl TryFrom for ConfirmationFXG { + type Error = EyreError; + + fn try_from(bytes: Bytes) -> Result { + let p = ProtoConfirmationFXG::decode(bytes) + .map_err(|e| eyre::eyre!("ConfirmationFXG deserialize: {}", e))?; + Ok(ConfirmationFXG::from(p)) + } +} + +impl From<&ConfirmationFXG> for ProtoConfirmationFXG { + fn from(v: &ConfirmationFXG) -> Self { + ProtoConfirmationFXG { + version: ConfirmationVersion1 as i32, + outpoints: v + .outpoints + .iter() + .map(|o| ProtoTransactionOutpoint { + transaction_id: o.transaction_id.as_bytes().to_vec(), + index: o.index, + }) + .collect(), + progress_indication: Some(v.progress_indication.clone()), + } + } +} + +impl From for ConfirmationFXG { + fn from(v: ProtoConfirmationFXG) -> Self { + ConfirmationFXG { + progress_indication: v.progress_indication.unwrap(), + outpoints: v + .outpoints + .iter() + .map(|o| TransactionOutpoint { + transaction_id: kaspa_hashes::Hash::from_slice(&o.transaction_id), + index: o.index, + }) + .collect(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bytes::Bytes; + use hyperlane_cosmos_rs::dymensionxyz::dymension::kas::{ + ProgressIndication, TransactionOutpoint as ProtoTransactionOutpoint, WithdrawalId, + }; + + #[test] + fn test_confirmationfxg_bytes_roundtrip() { + let old_outpoint = TransactionOutpoint::new(kaspa_hashes::Hash::default(), 5); + let new_outpoint = TransactionOutpoint::new(kaspa_hashes::Hash::default(), 15); + + let withdrawal_id = WithdrawalId { + message_id: "abc123".to_string(), + }; + + let progress_indication = ProgressIndication { + old_outpoint: Some(ProtoTransactionOutpoint { + transaction_id: old_outpoint.transaction_id.as_bytes().to_vec(), + index: old_outpoint.index, + }), + new_outpoint: Some(ProtoTransactionOutpoint { + transaction_id: new_outpoint.transaction_id.as_bytes().to_vec(), + index: new_outpoint.index, + }), + processed_withdrawals: vec![withdrawal_id], + }; + + let outpoints = vec![old_outpoint, new_outpoint]; + + let confirmation = ConfirmationFXG::new(progress_indication.clone(), outpoints.clone()); + + let bytes = Bytes::try_from(&confirmation).unwrap(); + let confirmation2 = ConfirmationFXG::try_from(bytes).unwrap(); + + assert_eq!(confirmation.outpoints, confirmation2.outpoints); + assert_eq!( + confirmation.progress_indication.processed_withdrawals, + confirmation2.progress_indication.processed_withdrawals + ); + } +} diff --git a/rust/main/chains/dymension-kaspa/src/ops/deposit.rs b/rust/main/chains/dymension-kaspa/src/ops/deposit.rs new file mode 100644 index 00000000000..4420ea56d54 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/ops/deposit.rs @@ -0,0 +1,212 @@ +use bytes::Bytes; +use eyre::Result; +use hyperlane_core::{Encode, HyperlaneMessage, U256}; +use hyperlane_cosmos_rs::dymensionxyz::hyperlane::kaspa::{ + DepositFxg as ProtoDepositFXG, DepositVersion, +}; +use hyperlane_cosmos_rs::prost::Message; +use kaspa_rpc_core::RpcHash; +use std::str::FromStr; + +#[derive(Debug, PartialEq, Clone)] +pub struct DepositFXG { + pub amount: U256, + pub tx_id: String, + pub utxo_index: usize, + pub accepting_block_hash: String, + pub hl_message: HyperlaneMessage, + pub containing_block_hash: String, +} + +impl Default for DepositFXG { + fn default() -> Self { + Self { + amount: U256::from(0), + tx_id: String::new(), + utxo_index: 0, + accepting_block_hash: String::new(), + hl_message: HyperlaneMessage::default(), + containing_block_hash: String::new(), + } + } +} + +impl DepositFXG { + pub fn accepting_block_hash_rpc(&self) -> Result { + RpcHash::from_str(&self.accepting_block_hash).map_err(|e| { + eyre::Report::new(e).wrap_err("Failed to convert accepting block hash to RpcHash") + }) + } + + pub fn tx_id_rpc(&self) -> Result { + RpcHash::from_str(&self.tx_id) + .map_err(|e| eyre::Report::new(e).wrap_err("Failed to convert tx hash to RpcHash")) + } + + pub fn containing_block_hash_rpc(&self) -> Result { + RpcHash::from_str(&self.containing_block_hash).map_err(|e| { + eyre::Report::new(e).wrap_err("Failed to convert containing block hash to RpcHash") + }) + } +} + +impl TryFrom for DepositFXG { + type Error = eyre::Report; + + fn try_from(bytes: Bytes) -> Result { + let protodeposit = ProtoDepositFXG::decode(bytes).map_err(|e| { + eyre::Report::new(e).wrap_err("Failed to deserialize proto DepositFXG from bytes") + })?; + + Ok(DepositFXG::from(protodeposit)) + } +} + +impl From<&DepositFXG> for Bytes { + fn from(deposit: &DepositFXG) -> Self { + let proto_deposit = ProtoDepositFXG::from(deposit); + Bytes::from(proto_deposit.encode_to_vec()) + } +} + +impl From<&DepositFXG> for ProtoDepositFXG { + fn from(deposit: &DepositFXG) -> Self { + ProtoDepositFXG { + version: DepositVersion::DepositVersion1 as i32, + amount: deposit.amount.to_vec(), // U256 -> Vec + tx_id: deposit.tx_id.clone(), + utxo_index: deposit.utxo_index as u32, // usize -> u32 + accepting_block_hash: deposit.accepting_block_hash.clone(), + hl_message: deposit.hl_message.to_vec(), + containing_block_hash: deposit.containing_block_hash.clone(), + } + } +} + +impl From for DepositFXG { + fn from(pb_deposit: ProtoDepositFXG) -> Self { + DepositFXG { + amount: U256::from_little_endian(&pb_deposit.amount.to_vec()), + tx_id: pb_deposit.tx_id, + utxo_index: pb_deposit.utxo_index as usize, + accepting_block_hash: pb_deposit.accepting_block_hash, + hl_message: HyperlaneMessage::from(pb_deposit.hl_message), + containing_block_hash: pb_deposit.containing_block_hash, + } + } +} + +// --- Test Module --- +#[cfg(test)] +mod tests { + use super::*; + use bytes::Bytes; + use eyre::Result as EyreResult; + use hyperlane_core::H256; + // --- Test Cases for DepositFXG Conversions --- + + #[tokio::test] + async fn test_deposit_fxg_serialization_deserialization_roundtrip() { + // Arrange: Create a sample DepositFXG instance + let original_deposit = DepositFXG { + tx_id: "test_transaction_id_123".to_string(), + utxo_index: 5, + amount: U256::from(100_000_000), + accepting_block_hash: "test_block_id_abc".to_string(), + containing_block_hash: "test_block_id_def".to_string(), + hl_message: HyperlaneMessage::default(), + }; + + // Act: Serialize to Bytes, then deserialize back + let encoded_bytes: Bytes = (&original_deposit).into(); // Using the `From<&DepositFXG> for Bytes` impl + let decoded_deposit: EyreResult = DepositFXG::try_from(encoded_bytes.clone()); // Using the `TryFrom for DepositFXG` impl + + // Assert: + // 1. Deserialization should be successful (Ok) + assert!( + decoded_deposit.is_ok(), + "Deserialization failed: {:?}", + decoded_deposit.unwrap_err() + ); + + // 2. The deserialized object should be identical to the original + let unwrapped_decoded_deposit = decoded_deposit.unwrap(); + assert_eq!( + unwrapped_decoded_deposit, original_deposit, + "Deserialized object does not match original" + ); + } + + #[tokio::test] + async fn test_deposit_fxg_deserialization_from_invalid_bytes_fails() { + // Arrange: Create some invalid bytes (e.g., truncated data, random garbage) + let invalid_bytes = Bytes::from(vec![0x01, 0x02, 0x03, 0x04]); // Too short or malformed for bincode + + // Act: Attempt to deserialize from invalid bytes + let decoded_deposit: EyreResult = DepositFXG::try_from(invalid_bytes.clone()); + + // Assert: Deserialization should fail (Err) + assert!( + decoded_deposit.is_err(), + "Expected deserialization to fail, but it succeeded" + ); + + // Optionally, check if the error message contains expected text + let error = decoded_deposit.unwrap_err(); + println!("Received error for invalid bytes: {:?}", error); + assert!( + error + .to_string() + .contains("Failed to deserialize proto DepositFXG from bytes"), + "Error message unexpected" + ); + } + + #[tokio::test] + async fn test_deposit_fxg_serialization_determinism() { + // Arrange: Create two identical DepositFXG instances + let deposit1 = DepositFXG { + tx_id: "deterministic_tx".to_string(), + utxo_index: 10, + containing_block_hash: "deterministic_block".to_string(), + accepting_block_hash: "deterministic_block".to_string(), + amount: U256::from(100_000_000), + hl_message: HyperlaneMessage { + version: 1, + nonce: 100, + origin: 1, + destination: 2, + sender: H256([2; 32]), + recipient: H256([3; 32]), + body: b"fixed_body".to_vec(), + }, + }; + + let deposit2 = DepositFXG { + tx_id: "deterministic_tx".to_string(), + utxo_index: 10, + containing_block_hash: "deterministic_block".to_string(), + accepting_block_hash: "deterministic_block".to_string(), + amount: U256::from(100_000_000), + hl_message: HyperlaneMessage { + version: 1, + nonce: 100, + origin: 1, + destination: 2, + sender: H256([2; 32]), + recipient: H256([3; 32]), + body: b"fixed_body".to_vec(), + }, + }; + + // Act: Serialize both + let encoded_bytes1: Bytes = (&deposit1).into(); + let encoded_bytes2: Bytes = (&deposit2).into(); + + // Assert: Encoded bytes should be identical + assert_eq!( + encoded_bytes1, encoded_bytes2, + "Serialization should be deterministic for identical objects" + ); + } +} diff --git a/rust/main/chains/dymension-kaspa/src/ops/message.rs b/rust/main/chains/dymension-kaspa/src/ops/message.rs new file mode 100644 index 00000000000..204f391a2ca --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/ops/message.rs @@ -0,0 +1,113 @@ +use eyre::Result; +use hyperlane_core::{Decode, Encode, HyperlaneMessage, RawHyperlaneMessage, U256}; +use hyperlane_cosmos_rs::dymensionxyz::dymension::forward::HlMetadata; +use hyperlane_cosmos_rs::dymensionxyz::dymension::kas::TransactionOutpoint; +use hyperlane_cosmos_rs::prost::Message; +use hyperlane_warp_route::TokenMessage; +pub use kaspa_bip32::secp256k1::Keypair as KaspaSecpKeypair; +use kaspa_hashes::Hash; +use std::io::Cursor; + +pub struct ParsedHL { + pub hl_message: HyperlaneMessage, + pub token_message: TokenMessage, +} + +impl ParsedHL { + pub fn parse_string(payload: &str) -> Result { + let raw = hex::decode(payload)?; + Self::parse_bytes(raw) + } + + pub fn parse_bytes(payload: Vec) -> Result { + let hl_message = parse_hyperlane_message(&payload)?; + let token_message = parse_hyperlane_metadata(&hl_message)?; + Ok(ParsedHL { + hl_message, + token_message, + }) + } +} + +pub fn parse_hyperlane_message(m: &RawHyperlaneMessage) -> Result { + const MIN_EXPECTED_LENGTH: usize = 77; + + if m.len() < MIN_EXPECTED_LENGTH { + return Err(eyre::eyre!("Value cannot be zero.")); + } + let message = HyperlaneMessage::from(m); + + Ok(message) +} + +pub fn parse_hyperlane_metadata(m: &HyperlaneMessage) -> Result { + // decode token message inside Hyperlane message + let mut reader = Cursor::new(m.body.as_slice()); + let token_message = TokenMessage::read_from(&mut reader) + .map_err(|e| eyre::eyre!("Failed to parse token message: {}", e))?; + + Ok(token_message) +} + +/// Parse withdrawal amount from HyperlaneMessage +pub fn parse_withdrawal_amount(msg: &HyperlaneMessage) -> Option { + match parse_hyperlane_metadata(msg) { + Ok(token_message) => { + let amount_u256 = token_message.amount(); + // Convert U256 to u64, handling overflow + if amount_u256 > U256::from(u64::MAX) { + tracing::info!("kaspa: withdrawal amount exceeded u64::MAX, using u64::MAX"); + Some(u64::MAX) + } else { + Some(amount_u256.as_u64()) + } + } + Err(e) => { + tracing::error!( + error = ?e, + "kaspa: failed to parse token message for withdrawal amount" + ); + None + } + } +} + +/// Calculate total withdrawal amount from messages +pub fn calculate_total_withdrawal_amount(msgs: &[HyperlaneMessage]) -> u64 { + msgs.iter().filter_map(parse_withdrawal_amount).sum() +} + +pub fn add_kaspa_metadata_hl_messsage( + parsed: ParsedHL, + transaction_id: Hash, + utxo_index: usize, +) -> Result { + let hl_message = parsed.hl_message; + let token_message: TokenMessage = parsed.token_message; + + let output = TransactionOutpoint { + transaction_id: transaction_id.as_bytes().to_vec(), + index: utxo_index as u32, + }; + + let output_bytes = output.encode_to_vec(); + + let mut metadata: HlMetadata; + if token_message.metadata().is_empty() { + metadata = HlMetadata::default(); + } else { + metadata = HlMetadata::decode(token_message.metadata())?; + } + metadata.kaspa = output_bytes; + + let token_message = TokenMessage::new( + token_message.recipient(), + token_message.amount(), + metadata.encode_to_vec(), + ); + + let mut hl_message: HyperlaneMessage = hl_message.clone(); + hl_message.body = token_message.to_vec(); + + Ok(hl_message) +} diff --git a/rust/main/chains/dymension-kaspa/src/ops/migration.rs b/rust/main/chains/dymension-kaspa/src/ops/migration.rs new file mode 100644 index 00000000000..b680f8adf5e --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/ops/migration.rs @@ -0,0 +1,86 @@ +use bytes::Bytes; +use eyre::Error as EyreError; +use hyperlane_cosmos_rs::dymensionxyz::hyperlane::kaspa::MigrationFxg as ProtoMigrationFxg; +use hyperlane_cosmos_rs::prost::Message; +use kaspa_wallet_pskt::prelude::Bundle; + +/// MigrationFXG represents a PSKT bundle for migrating funds from old escrow to new escrow. +/// The validator derives expected anchor and target address from local config. +#[derive(Debug)] +pub struct MigrationFXG { + pub bundle: Bundle, +} + +impl MigrationFXG { + pub fn new(bundle: Bundle) -> Self { + Self { bundle } + } +} + +impl TryFrom for MigrationFXG { + type Error = EyreError; + + fn try_from(bytes: Bytes) -> Result { + let p = ProtoMigrationFxg::decode(bytes) + .map_err(|e| eyre::eyre!("MigrationFXG deserialize: {}", e))?; + MigrationFXG::try_from(p) + } +} + +impl TryFrom<&MigrationFXG> for Bytes { + type Error = EyreError; + + fn try_from(x: &MigrationFXG) -> Result { + let p = ProtoMigrationFxg::try_from(x) + .map_err(|e| eyre::eyre!("MigrationFXG serialize: {}", e))?; + Ok(Bytes::from(p.encode_to_vec())) + } +} + +impl TryFrom for MigrationFXG { + type Error = EyreError; + + fn try_from(pb: ProtoMigrationFxg) -> Result { + Ok(MigrationFXG { + bundle: Bundle::try_from(pb.pskt_bundle) + .map_err(|e| eyre::eyre!("pskt deserialize: {}", e))?, + }) + } +} + +impl TryFrom<&MigrationFXG> for ProtoMigrationFxg { + type Error = EyreError; + + fn try_from(v: &MigrationFXG) -> Result { + Ok(ProtoMigrationFxg { + pskt_bundle: v + .bundle + .serialize() + .map_err(|e| eyre::eyre!("bundle serialize: {}", e))?, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use kaspa_wallet_pskt::prelude::{Version, PSKT}; + + #[test] + fn test_migrationfxg_bytes_roundtrip() { + let pskt = PSKT::::default() + .set_version(Version::One) + .constructor() + .no_more_outputs() + .no_more_inputs() + .signer(); + + let bundle = Bundle::from(pskt); + let fxg = MigrationFXG::new(bundle); + + let bytes = Bytes::try_from(&fxg).unwrap(); + let fxg2 = MigrationFXG::try_from(bytes).unwrap(); + + assert_eq!(fxg.bundle.0.len(), fxg2.bundle.0.len()); + } +} diff --git a/rust/main/chains/dymension-kaspa/src/ops/mod.rs b/rust/main/chains/dymension-kaspa/src/ops/mod.rs new file mode 100644 index 00000000000..3aa29d42efd --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/ops/mod.rs @@ -0,0 +1,8 @@ +pub mod addr; +pub mod confirmation; +pub mod deposit; +pub mod message; +pub mod migration; +pub mod payload; +pub mod user; +pub mod withdraw; diff --git a/rust/main/chains/dymension-kaspa/src/ops/payload.rs b/rust/main/chains/dymension-kaspa/src/ops/payload.rs new file mode 100644 index 00000000000..90e28ae6489 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/ops/payload.rs @@ -0,0 +1,179 @@ +use bytes::Bytes; +use eyre::Error as EyreError; +use hyperlane_core::{Encode, HyperlaneMessage, H256}; +use hyperlane_cosmos_rs::dymensionxyz::hyperlane::kaspa::MessageIDs as ProtoMessageIDs; +use hyperlane_cosmos_rs::prost::Message; + +#[derive(Debug, Clone, PartialEq, Eq, Copy)] +pub struct MessageID(pub H256); + +#[derive(Debug)] +pub struct MessageIDs(pub Vec); + +impl MessageIDs { + pub fn new(ids: Vec) -> Self { + Self(ids) + } + + pub fn to_bytes(&self) -> Vec { + Bytes::from(self).to_vec() + } + + pub fn from_bytes(bytes: Vec) -> Result { + Self::try_from(Bytes::from(bytes)) + } + + // Parse the payload string to extract the message ID + pub fn from_tx_payload(payload: &str) -> Result { + let unhexed_payload = + hex::decode(payload).map_err(|e| eyre::eyre!("Failed to decode payload: {}", e))?; + Self::try_from(Bytes::from(unhexed_payload)) + } +} + +impl TryFrom for MessageIDs { + type Error = EyreError; + + fn try_from(v: Bytes) -> Result { + let p = + ProtoMessageIDs::decode(v).map_err(|e| eyre::eyre!("MessageIDs deserialize: {}", e))?; + Ok(MessageIDs::from(p)) + } +} + +impl From<&MessageIDs> for Bytes { + fn from(v: &MessageIDs) -> Self { + let p = ProtoMessageIDs::from(v); + Bytes::from(p.encode_to_vec()) + } +} + +impl TryFrom> for MessageIDs { + type Error = EyreError; + + fn try_from(v: Vec) -> Result { + Self::try_from(Bytes::from(v)) + } +} + +impl From<&MessageIDs> for Vec { + fn from(v: &MessageIDs) -> Self { + Bytes::from(v).to_vec() + } +} + +impl From for MessageIDs { + fn from(v: ProtoMessageIDs) -> Self { + Self( + v.message_ids + .iter() + .map(|m| MessageID(H256::from_slice(m))) + .collect(), + ) + } +} + +impl From<&MessageIDs> for ProtoMessageIDs { + fn from(v: &MessageIDs) -> Self { + ProtoMessageIDs { + message_ids: v.0.iter().map(|id| id.0.to_vec()).collect(), + } + } +} + +impl From> for MessageIDs { + fn from(v: Vec) -> Self { + MessageIDs(v.into_iter().map(MessageID).collect()) + } +} + +impl From> for MessageIDs { + fn from(m: Vec) -> Self { + MessageIDs(m.into_iter().map(|w| MessageID(w.id())).collect()) + } +} + +impl From<&Vec> for MessageIDs { + fn from(m: &Vec) -> Self { + MessageIDs(m.iter().map(|w| MessageID(w.id())).collect()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_message_ids_bincode_serialization() { + let msg_id1 = MessageID(H256::from([1u8; 32])); + let msg_id2 = MessageID(H256::from([2u8; 32])); + let msg_id3 = MessageID(H256::from([255u8; 32])); + + let original_ids = vec![msg_id1, msg_id2, msg_id3]; + let message_ids = MessageIDs::new(original_ids.clone()); + + // Serialize to bytes using bincode + let serialized_bytes = message_ids.to_bytes(); + + // Deserialize back from bytes + let deserialized_message_ids = MessageIDs::try_from(serialized_bytes) + .expect("Failed to deserialize MessageIDs from bytes"); + + assert_eq!(deserialized_message_ids.0.len(), 3); + assert_eq!(deserialized_message_ids.0[0], msg_id1); + assert_eq!(deserialized_message_ids.0[1], msg_id2); + assert_eq!(deserialized_message_ids.0[2], msg_id3); + assert_eq!(deserialized_message_ids.0, original_ids); + } + + #[test] + fn test_empty_message_ids_bincode_serialization() { + let empty_message_ids = MessageIDs::new(vec![]); + + // Serialize empty vector to bytes + let serialized_bytes = empty_message_ids.to_bytes(); + + // Deserialize back from bytes + let deserialized_message_ids = MessageIDs::try_from(serialized_bytes) + .expect("Failed to deserialize empty MessageIDs from bytes"); + + assert!(deserialized_message_ids.0.is_empty()); + } + + #[test] + fn test_single_message_id_bincode_serialization() { + let single_id = vec![MessageID(H256::from([42u8; 32]))]; + let message_ids = MessageIDs::new(single_id.clone()); + + // Serialize single item to bytes + let serialized_bytes = message_ids.to_bytes(); + + // Deserialize back from bytes + let deserialized_message_ids = MessageIDs::try_from(serialized_bytes) + .expect("Failed to deserialize single MessageID from bytes"); + + assert_eq!(deserialized_message_ids.0.len(), 1); + assert_eq!( + deserialized_message_ids.0[0], + MessageID(H256::from([42u8; 32])) + ); + assert_eq!(deserialized_message_ids.0, single_id); + } + + #[test] + fn test_bincode_serialization_deterministic() { + // Test that serialization is deterministic (same input -> same output) + let msg_ids = vec![ + MessageID(H256::from([123u8; 32])), + MessageID(H256::from([234u8; 32])), + ]; + + let message_ids_1 = MessageIDs::new(msg_ids.clone()); + let message_ids_2 = MessageIDs::new(msg_ids); + + let bytes1 = message_ids_1.to_bytes(); + let bytes2 = message_ids_2.to_bytes(); + + assert_eq!(bytes1, bytes2, "Serialization should be deterministic"); + } +} diff --git a/rust/main/chains/dymension-kaspa/src/ops/user/deposit.rs b/rust/main/chains/dymension-kaspa/src/ops/user/deposit.rs new file mode 100644 index 00000000000..65eff65cd45 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/ops/user/deposit.rs @@ -0,0 +1,43 @@ +use eyre::Result; +use kaspa_addresses::Address; +use kaspa_wallet_core::error::Error as KaspaError; +use kaspa_wallet_core::prelude::*; +use kaspa_wallet_core::tx::Fees; +use std::sync::Arc; +use workflow_core::abortable::Abortable; + +pub async fn deposit_with_payload( + w: &Arc, + secret: &Secret, + address: Address, + amt: u64, + payload: Vec, +) -> Result { + let a = w.account()?; + + let dst = PaymentDestination::from(PaymentOutput::new(address, amt)); + let fees = Fees::from(0i64); + let payment_secret = None; + let abortable = Abortable::new(); + + // use account.send, because wallet.accounts_send(AccountsSendRequest{..}) is buggy + let (summary, _) = a + .send( + dst, + None, + fees, + match payload.len() { + 0 => None, + _ => Some(payload), + }, + secret.clone(), + payment_secret, + &abortable, + None, + ) + .await?; + + summary.final_transaction_id().ok_or_else(|| { + KaspaError::Custom("Deposit transaction failed to generate a transaction ID".to_string()) + }) +} diff --git a/rust/main/chains/dymension-kaspa/src/ops/user/mod.rs b/rust/main/chains/dymension-kaspa/src/ops/user/mod.rs new file mode 100644 index 00000000000..66e0efac813 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/ops/user/mod.rs @@ -0,0 +1,3 @@ +pub mod deposit; + +pub mod payload; diff --git a/rust/main/chains/dymension-kaspa/src/ops/user/payload.rs b/rust/main/chains/dymension-kaspa/src/ops/user/payload.rs new file mode 100644 index 00000000000..6467c8d5b57 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/ops/user/payload.rs @@ -0,0 +1,69 @@ +use hyperlane_core::{Encode, HyperlaneMessage, H256, U256}; +use hyperlane_cosmos::signers::Signer; +use hyperlane_cosmos_rs::dymensionxyz::dymension::forward::HlMetadata; +use hyperlane_cosmos_rs::prost::Message as _; +use hyperlane_warp_route::TokenMessage; + +/* +Need to make a hub priv key and address pair +Derive the HL user addr, which is just raw address bytes +https://github.com/dymensionxyz/dymension/blob/df472aefe2d022a075560160db678dddd4011f28/x/forward/cli/util.go#L685-L693 + */ +pub fn make_deposit_payload_easy( + domain_kas: u32, + token_kas_placeholder: H256, + domain_hub: u32, + token_hub: H256, + amt: u64, + signer: &Signer, +) -> Vec { + make_deposit_payload( + domain_kas, + token_kas_placeholder, + domain_hub, + token_hub, + amt, + signer.address_h256(), + ) +} + +pub fn make_deposit_payload( + domain_kas: u32, + token_kas_placeholder: H256, + domain_hub: u32, + token_hub: H256, + amt: u64, + hub_user_addr_hub: H256, +) -> Vec { + let meta = make_deposit_payload_meta(); + let token_message = TokenMessage::new(hub_user_addr_hub, U256::from(amt), meta); + let mut buf = vec![]; + token_message.write_to(&mut buf).unwrap(); + + let m = HyperlaneMessage { + origin: domain_kas, + sender: token_kas_placeholder, + destination: domain_hub, + recipient: token_hub, + body: buf, + ..Default::default() + }; + + let mut buf = vec![]; + m.write_to(&mut buf).unwrap(); + + buf +} + +fn make_deposit_payload_meta() -> Vec { + // Create an empty HlMetadata struct with all required fields + // The kaspa field will be populated later in the message flow + let metadata = HlMetadata { + kaspa: vec![], + hook_forward_to_hl: vec![], + hook_forward_to_ibc: vec![], + }; + + // Encode the metadata to protobuf bytes + metadata.encode_to_vec() +} diff --git a/rust/main/chains/dymension-kaspa/src/ops/withdraw.rs b/rust/main/chains/dymension-kaspa/src/ops/withdraw.rs new file mode 100644 index 00000000000..911bc85e2e7 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/ops/withdraw.rs @@ -0,0 +1,258 @@ +use crate::ops::payload::MessageID; +use bytes::Bytes; +use eyre::Error as EyreError; +use hex::ToHex; +use hyperlane_core::Encode; +use hyperlane_core::HyperlaneMessage; +use hyperlane_cosmos::native::ModuleQueryClient; +use hyperlane_cosmos_rs::dymensionxyz::dymension::kas::{ + TransactionOutpoint as ProtoTransactionOutpoint, WithdrawalId, WithdrawalStatus, +}; +use hyperlane_cosmos_rs::dymensionxyz::hyperlane::kaspa::{ + HyperlaneMessages as ProtoHyperlaneMessages, WithdrawFxg as ProtoWithdrawFXG, WithdrawalVersion, +}; +use hyperlane_cosmos_rs::prost::Message; +use kaspa_consensus_core::tx::TransactionOutpoint; +use kaspa_wallet_pskt::prelude::Bundle; + +/// WithdrawFXG represents a sequence of PSKT transactions for batch processing and transport as +/// a single serialized payload. A Bundle contains multiple PSKTs, where each PSKT is associated with +/// some Hyperlane messages. +/// +/// The structure maintains a strict correspondence between PSKTs and their messages: each PSKT at +/// index N in the bundle corresponds to the messages at index N in the messages array. For example, +/// if Bundle[0] contains PSKT1 and messages[0] contains {M1, M2}, then PSKT1 covers messages M1 and M2. +/// +#[derive(Debug)] +pub struct WithdrawFXG { + pub bundle: Bundle, + pub messages: Vec>, + + // used by relayer only + // the first element – the very first anchor (old hub) + // the last eleemnt – the very new anchor (new hub) + pub anchors: Vec, +} + +impl WithdrawFXG { + pub fn new( + bundle: Bundle, + messages: Vec>, + anchors: Vec, + ) -> Self { + Self { + bundle, + messages, + anchors, + } + } + + pub fn ids(&self) -> Vec { + self.messages + .iter() + .flat_map(|m| m.iter().map(|m| MessageID(m.id()))) + .collect() + } +} + +impl Default for WithdrawFXG { + fn default() -> Self { + Self { + bundle: Bundle::new(), + messages: vec![], + anchors: vec![], + } + } +} + +impl TryFrom for WithdrawFXG { + type Error = EyreError; + + fn try_from(bytes: Bytes) -> Result { + let p = ProtoWithdrawFXG::decode(bytes) + .map_err(|e| eyre::eyre!("WithdrawFXG deserialize: {}", e))?; + WithdrawFXG::try_from(p) + } +} + +impl TryFrom<&WithdrawFXG> for Bytes { + type Error = EyreError; + + fn try_from(x: &WithdrawFXG) -> Result { + let p = ProtoWithdrawFXG::try_from(x) + .map_err(|e| eyre::eyre!("WithdrawFXG serialize: {}", e))?; + Ok(Bytes::from(p.encode_to_vec())) + } +} + +impl TryFrom for WithdrawFXG { + type Error = EyreError; + + fn try_from(pb: ProtoWithdrawFXG) -> Result { + Ok(WithdrawFXG { + bundle: Bundle::try_from(pb.pskt_bundle) + .map_err(|e| eyre::eyre!("pskt deserialize: {}", e))?, + messages: pb + .messages + .into_iter() + .map(|inner_vec| { + inner_vec + .messages + .into_iter() + .map(HyperlaneMessage::from) + .collect() + }) + .collect(), + anchors: pb + .anchors + .into_iter() + .map(|a| TransactionOutpoint { + transaction_id: kaspa_hashes::Hash::from_slice(&a.transaction_id), + index: a.index, + }) + .collect(), + }) + } +} + +impl TryFrom<&WithdrawFXG> for ProtoWithdrawFXG { + type Error = EyreError; + + fn try_from(v: &WithdrawFXG) -> Result { + Ok(ProtoWithdrawFXG { + version: WithdrawalVersion::WithdrawalVersion1 as i32, + pskt_bundle: v + .bundle + .serialize() + .map_err(|e| eyre::eyre!("bundle serialize: {}", e))?, + messages: v + .messages + .iter() + .map(|inner_vec| ProtoHyperlaneMessages { + messages: inner_vec.iter().map(HyperlaneMessage::to_vec).collect(), + }) + .collect(), + anchors: v + .anchors + .iter() + .map(|a| ProtoTransactionOutpoint { + transaction_id: a.transaction_id.as_bytes().to_vec(), + index: a.index, + }) + .collect(), + }) + } +} + +/// Convert hub outpoint response to Kaspa TransactionOutpoint. +pub fn parse_hub_outpoint( + outpoint: hyperlane_cosmos_rs::dymensionxyz::dymension::kas::TransactionOutpoint, +) -> eyre::Result { + if outpoint.transaction_id.len() != 32 { + return Err(eyre::eyre!( + "Invalid transaction ID length: expected 32 bytes, got {}", + outpoint.transaction_id.len() + )); + } + + let kaspa_tx_id = kaspa_hashes::Hash::from_bytes( + outpoint + .transaction_id + .as_slice() + .try_into() + .map_err(|e| eyre::eyre!("Convert tx ID to Kaspa tx ID: {:}", e))?, + ); + + Ok(TransactionOutpoint { + transaction_id: kaspa_tx_id, + index: outpoint.index, + }) +} + +/// Query the current hub anchor (outpoint) without any withdrawal filtering. +pub async fn query_hub_anchor(cosmos: &ModuleQueryClient) -> eyre::Result { + let resp = cosmos + .withdrawal_status(vec![], None) + .await + .map_err(|e| eyre::eyre!("Query hub anchor: {}", e))?; + + let outpoint_data = resp + .outpoint + .ok_or_else(|| eyre::eyre!("No outpoint in hub response"))?; + + parse_hub_outpoint(outpoint_data) +} + +pub async fn filter_pending_withdrawals( + withdrawals: Vec, + cosmos: &ModuleQueryClient, +) -> eyre::Result<(TransactionOutpoint, Vec)> { + let withdrawal_ids: Vec<_> = withdrawals + .iter() + .map(|m| WithdrawalId { + message_id: m.id().encode_hex(), + }) + .collect(); + + let resp = cosmos + .withdrawal_status(withdrawal_ids, None) + .await + .map_err(|e| eyre::eyre!("Query outpoint from x/kas: {}", e))?; + + let outpoint_data = resp + .outpoint + .ok_or_else(|| eyre::eyre!("No outpoint data in response"))?; + + let anchor = parse_hub_outpoint(outpoint_data)?; + + let pending_withdrawals: Vec<_> = resp + .status + .into_iter() + .enumerate() + .filter_map(|(idx, status)| match status.try_into() { + Ok(WithdrawalStatus::Unprocessed) => Some(withdrawals[idx].clone()), + _ => None, + }) + .collect(); + + Ok((anchor, pending_withdrawals)) +} + +#[cfg(test)] +mod tests { + use super::*; + use bytes::Bytes; + use kaspa_wallet_pskt::prelude::{Version, PSKT}; + + #[test] + fn test_withdrawfxg_bytes_roundtrip() { + let msg = HyperlaneMessage::default(); + let messages = vec![ + vec![msg.clone()], + vec![msg.clone(), msg.clone()], + vec![msg.clone(), msg.clone(), msg.clone()], + ]; + + let pskt = PSKT::::default() + .set_version(Version::One) + .constructor() + .no_more_outputs() + .no_more_inputs() + .payload(Some(msg.clone().to_vec())) + .unwrap() + .signer(); + + let bundle = Bundle::from(pskt); + + let old = TransactionOutpoint::new(kaspa_hashes::Hash::default(), 10); + let new = TransactionOutpoint::new(kaspa_hashes::Hash::default(), 20); + + let fxg = WithdrawFXG::new(bundle, messages, vec![old, new]); + + let bytes = Bytes::try_from(&fxg).unwrap(); + let fxg2 = WithdrawFXG::try_from(bytes).unwrap(); + + assert_eq!(fxg.messages, fxg2.messages); + assert_eq!(fxg.anchors, fxg2.anchors); + } +} diff --git a/rust/main/chains/dymension-kaspa/src/providers/confirmation_queue.rs b/rust/main/chains/dymension-kaspa/src/providers/confirmation_queue.rs new file mode 100644 index 00000000000..38d2dd81b7a --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/providers/confirmation_queue.rs @@ -0,0 +1,41 @@ +use crate::ops::confirmation::ConfirmationFXG; +use std::sync::Mutex; + +#[derive(Debug)] +pub struct PendingConfirmation { + mutex: Mutex>, +} + +impl Default for PendingConfirmation { + fn default() -> Self { + Self::new() + } +} + +impl PendingConfirmation { + pub fn new() -> Self { + Self { + mutex: Mutex::new(None), + } + } + + pub fn consume(&self) -> Option { + let mut guard = self.mutex.lock().unwrap(); + std::mem::take(&mut *guard) + } + pub fn push(&self, fxg: ConfirmationFXG) { + let mut guard = self.mutex.lock().unwrap(); + *guard = Some(fxg); + } + /// has_pending checks if there's a pending ConfirmationFXG + pub fn has_pending(&self) -> bool { + let guard = self.mutex.lock().unwrap(); // Acquire lock + guard.is_some() // Check if the Option contains a value + } + + /// returns pending ConfirmationFXG without consuming + pub fn get_pending(&self) -> Option { + let guard = self.mutex.lock().unwrap(); + guard.as_ref().cloned() // Requires ConfirmationFXG to implement Clone + } +} diff --git a/rust/main/chains/dymension-kaspa/src/providers/mod.rs b/rust/main/chains/dymension-kaspa/src/providers/mod.rs new file mode 100644 index 00000000000..4fdddb96abf --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/providers/mod.rs @@ -0,0 +1,8 @@ +pub mod confirmation_queue; +mod provider; +mod rest; +mod validators; + +pub use provider::KaspaProvider; +pub use rest::*; +pub use validators::*; diff --git a/rust/main/chains/dymension-kaspa/src/providers/provider.rs b/rust/main/chains/dymension-kaspa/src/providers/provider.rs new file mode 100644 index 00000000000..97089d4046c --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/providers/provider.rs @@ -0,0 +1,633 @@ +use super::confirmation_queue::PendingConfirmation; +use super::validators::ValidatorsClient; +use super::RestProvider; +use crate::ops::confirmation::ConfirmationFXG; +use crate::relayer::withdraw::hub_to_kaspa::combine_bundles_with_fee; +use crate::relayer::withdraw::messages::on_new_withdrawals; +use crate::relayer::KaspaBridgeMetrics; +use crate::util::domain_to_kas_network; +use crate::withdrawal_utils::{record_withdrawal_batch_metrics, WithdrawalStage}; +use crate::ConnectionConf; +use crate::RelayerStuff; +use crate::ValidatorStuff; +use dym_kas_core::escrow::EscrowPublic; +use dym_kas_core::wallet::{EasyKaspaWallet, EasyKaspaWalletArgs}; +use eyre::Result; +use hyperlane_core::config::OpSubmissionConfig; +use hyperlane_core::NativeToken; +use hyperlane_core::{ + BlockInfo, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneMessage, + HyperlaneProvider, HyperlaneProviderError, TxnInfo, H256, H512, U256, +}; +use hyperlane_cosmos::ConnectionConf as HubConnectionConf; +use hyperlane_cosmos::RawCosmosAmount; +use hyperlane_cosmos::Signer as HyperlaneSigner; +use hyperlane_cosmos::{native::ModuleQueryClient, CosmosProvider}; +use hyperlane_metric::prometheus_metric::PrometheusClientMetrics; +use kaspa_addresses::Address; +use kaspa_rpc_core::model::{RpcTransaction, RpcTransactionId}; +use kaspa_rpc_core::notify::mode::NotificationMode; +use kaspa_wallet_core::prelude::DynRpcApi; +use prometheus::Registry; +use std::sync::Arc; +use tonic::async_trait; +use tracing::{error, info}; +use url::Url; + +struct ProcessedWithdrawals { + fxg: std::sync::Arc, + tx_ids: Vec, +} + +#[derive(Debug, Clone)] +pub struct KaspaProvider { + conf: ConnectionConf, + domain: HyperlaneDomain, + easy_wallet: EasyKaspaWallet, + rest: RestProvider, + validators: ValidatorsClient, + cosmos_rpc: CosmosProvider, + + // Kaspa escrow key source (Direct JSON or AWS KMS config) + kas_key_source: Option, + + // Optimistic hint for next confirmation needed on Hub. If out of date, relayer polls Kaspa to sync + pending_confirmation: Arc, + + metrics: KaspaBridgeMetrics, + + /// Kaspa database for tracking deposits/withdrawals purely for informational purposes (optional, set by relayer) + kaspa_db: Option>, + + /// gRPC client for validator operations (None for relayer) + grpc_client: Option, +} + +impl KaspaProvider { + pub async fn new( + cfg: &ConnectionConf, + domain: HyperlaneDomain, + signer: Option, + metrics: PrometheusClientMetrics, + chain: Option, + registry: Option<&Registry>, + ) -> ChainResult { + let rest = RestProvider::new(cfg.clone(), signer, metrics.clone(), chain.clone())?; + + let kaspa_metrics = if let Some(reg) = registry { + KaspaBridgeMetrics::new(reg).expect("create KaspaBridgeMetrics") + } else { + KaspaBridgeMetrics::new(prometheus::default_registry()) + .expect("create default KaspaBridgeMetrics") + }; + + let validators = ValidatorsClient::new( + cfg.clone(), + Some(kaspa_metrics.validator_request_duration.clone()), + )?; + + let easy_wallet = get_easy_wallet( + domain.clone(), + cfg.kaspa_urls_wrpc[0].clone(), + cfg.wallet_secret.clone(), + cfg.wallet_dir.clone(), + ) + .await + .map_err(|e| eyre::eyre!("create easy wallet: {}", e))?; + + let kas_key_source = cfg + .validator_stuff + .as_ref() + .map(|v| v.kas_escrow_key_source.clone()); + + // Create gRPC client for validator if configured + let grpc_client = if let Some(validator_stuff) = &cfg.validator_stuff { + let grpc_url = &validator_stuff.kaspa_grpc_urls[0]; // Use first URL, similar to wrpc + info!( + grpc_url = %grpc_url, + "kaspa provider: initializing gRPC client with reconnection support" + ); + Some( + kaspa_grpc_client::GrpcClient::connect_with_args( + NotificationMode::Direct, + grpc_url.clone(), + None, // subscription_context + true, // reconnect - enable automatic reconnection + None, // connection_event_sender + false, // override_handle_stop_notify + None, // timeout_duration - use default + Arc::new(kaspa_utils_tower::counters::TowerConnectionCounters::default()), + ) + .await + .expect("create Kaspa gRPC client"), + ) + } else { + None + }; + + let provider = KaspaProvider { + domain: domain.clone(), + conf: cfg.clone(), + easy_wallet, + rest, + validators, + cosmos_rpc: cosmos_grpc_client(cfg.hub_grpc_urls.clone()), + kas_key_source, + pending_confirmation: Arc::new(PendingConfirmation::new()), + metrics: kaspa_metrics, + kaspa_db: None, + grpc_client, + }; + + if let Err(e) = provider.update_balance_metrics().await { + tracing::error!(error=?e, "initialize balance metrics on startup"); + } + + // Set relayer change address metric on startup + if let Ok(change_addr) = provider.wallet().account().change_address() { + provider + .metrics() + .relayer_receive_address_info + .with_label_values(&[&change_addr.to_string()]) + .set(1.0); + } + + Ok(provider) + } + + pub fn kaspa_db(&self) -> Option<&Arc> { + self.kaspa_db.as_ref() + } + + pub fn set_kaspa_db(&mut self, kaspa_db: Arc) { + self.kaspa_db = Some(kaspa_db); + } + + pub fn hack_store_withdrawals_for_query(&self, withdrawals: &Vec) { + // Store withdrawal messages in kaspa_db before processing + if let Some(kaspa_db) = self.kaspa_db() { + for msg in withdrawals { + let message_id = format!("0x{:x}", msg.id()); + match kaspa_db.store_withdrawal_message(msg.clone()) { + Ok(()) => { + info!( + message_id = %message_id, + "Stored withdrawal message in kaspa_db" + ); + } + Err(e) => { + error!( + message_id = %message_id, + error = ?e, + "store withdrawal message in kaspa_db" + ); + } + } + } + } else { + error!("kaspa mailbox: no kaspa_db set, skipping storing withdrawal messages"); + } + } + + /// Store withdrawal messages and their kaspa transaction IDs in the database + pub fn hack_store_withdrawals_kaspa_tx_for_query( + &self, + withdrawals: &[(HyperlaneMessage, String)], + ) { + if let Some(kaspa_db) = &self.kaspa_db { + for (msg, kaspa_tx) in withdrawals { + if !kaspa_tx.is_empty() { + let message_id = msg.id(); + // Store kaspa_tx for the withdrawal + if let Err(e) = kaspa_db.store_withdrawal_kaspa_tx(&message_id, kaspa_tx) { + error!( + message_id = ?message_id, + kaspa_tx = %kaspa_tx, + error = ?e, + "store kaspa_tx for withdrawal" + ); + } else { + info!( + message_id = ?message_id, + kaspa_tx = %kaspa_tx, + "Stored withdrawal in kaspa_db" + ); + } + } + } + } + } + + /// Store a deposit message in the database with the corresponding kaspa tx as deposit id + pub fn store_deposit(&self, message: &hyperlane_core::HyperlaneMessage, kaspa_tx_id: &str) { + if let Some(db) = &self.kaspa_db { + let message_id = message.id(); + info!( + kaspa_tx_id = %kaspa_tx_id, + message_id = ?message_id, + nonce = message.nonce, + "Storing deposit message in database" + ); + match db.store_deposit_message(message.clone(), kaspa_tx_id.to_string()) { + Ok(()) => { + info!( + message_id = ?message_id, + kaspa_tx_id = %kaspa_tx_id, + "Successfully stored deposit message" + ); + } + Err(e) => { + error!( + error = ?e, + message_id = ?message_id, + kaspa_tx_id = %kaspa_tx_id, + "store deposit message in database" + ); + } + } + } else { + error!("no database available for storing deposit message"); + } + } + + /// Update a stored deposit with the new HyperlaneMessage and Hub transaction ID after successful submission + /// Stores the new message and hub_tx + pub fn update_processed_deposit( + &self, + kaspa_tx_id: &str, + new_message: hyperlane_core::HyperlaneMessage, + hub_tx: &H256, + ) { + if let Some(db) = &self.kaspa_db { + let new_message_id = new_message.id(); + info!( + kaspa_tx = %kaspa_tx_id, + new_message_id = ?new_message_id, + hub_tx = ?hub_tx, + nonce = new_message.nonce, + "Updating deposit with new message and Hub transaction ID" + ); + + match db.update_processed_deposit(kaspa_tx_id, new_message, hub_tx) { + Ok(()) => { + info!( + kaspa_tx = %kaspa_tx_id, + new_message_id = ?new_message_id, + hub_tx = ?hub_tx, + "Successfully updated deposit with new message and Hub transaction ID" + ); + } + Err(e) => { + error!( + error = ?e, + kaspa_tx = %kaspa_tx_id, + new_message_id = ?new_message_id, + hub_tx = ?hub_tx, + "update deposit" + ); + } + } + } else { + error!("no database available for updating deposit"); + } + } + + pub fn consume_pending_confirmation(&self) -> Option { + self.pending_confirmation.consume() + } + + pub fn has_pending_confirmation(&self) -> bool { + self.pending_confirmation.has_pending() + } + + pub async fn get_pending_confirmation(&self) -> Option { + self.pending_confirmation.get_pending() + } + + pub fn get_min_deposit_sompi(&self) -> U256 { + self.conf.min_deposit_sompi + } + + pub fn kas_key_source(&self) -> &crate::conf::KaspaEscrowKeySource { + self.kas_key_source + .as_ref() + .expect("Kaspa key source not configured") + } + + pub fn try_kas_key_source(&self) -> Option<&crate::conf::KaspaEscrowKeySource> { + self.kas_key_source.as_ref() + } + + pub fn rest(&self) -> &RestProvider { + &self.rest + } + + /// Execute RPC operation with automatic reconnection on WebSocket errors. + /// This delegates to the wallet's reconnection logic for a DRY implementation. + pub async fn rpc_with_reconnect(&self, op: F) -> eyre::Result + where + F: Fn(Arc) -> Fut, + Fut: std::future::Future>, + { + self.easy_wallet.rpc_with_reconnect(op).await + } + + pub fn validators(&self) -> &ValidatorsClient { + &self.validators + } + + pub fn hub_rpc(&self) -> &CosmosProvider { + &self.cosmos_rpc + } + + pub fn wallet(&self) -> &EasyKaspaWallet { + &self.easy_wallet + } + + pub fn must_validator_stuff(&self) -> &ValidatorStuff { + self.conf.validator_stuff.as_ref().unwrap() + } + + pub fn must_relayer_stuff(&self) -> &RelayerStuff { + self.conf.relayer_stuff.as_ref().unwrap() + } + + pub fn conf(&self) -> &ConnectionConf { + &self.conf + } + + pub fn grpc_client(&self) -> Option { + self.grpc_client.clone() + } + + // Process withdrawals from Hub to Kaspa by building and submitting Kaspa transactions. + // Returns the subset of messages that were successfully processed. + pub async fn process_withdrawal_messages( + &self, + msgs: Vec, + ) -> Result> { + match self.process_withdrawal_messages_inner(msgs.clone()).await { + Ok(Some(processed)) => { + let all_processed_msgs: Vec<_> = + processed.fxg.messages.iter().flatten().cloned().collect(); + + record_withdrawal_batch_metrics( + &self.metrics, + &all_processed_msgs, + WithdrawalStage::Processed, + ); + + if let Some(last_anchor) = processed.fxg.anchors.last() { + let current_ts = kaspa_core::time::unix_now(); + self.metrics.update_last_anchor_point( + &last_anchor.transaction_id.to_string(), + last_anchor.index as u64, + current_ts, + ); + } + + self.pending_confirmation + .push(ConfirmationFXG::from_msgs_outpoints( + processed.fxg.ids(), + processed.fxg.anchors.clone(), + )); + info!("kaspa provider: added to progress indication work queue"); + + let mut result = Vec::new(); + for (tx_id, msgs) in processed.tx_ids.iter().zip(processed.fxg.messages.iter()) { + let kaspa_tx = format!("{}", tx_id); + for msg in msgs { + result.push((msg.clone(), kaspa_tx.clone())); + } + } + + self.hack_store_withdrawals_kaspa_tx_for_query(&result); + + Ok(result) + } + Ok(None) => { + info!("on new withdrawals decided not to handle withdrawal messages"); + Ok(Vec::new()) + } + Err(error) => { + record_withdrawal_batch_metrics(&self.metrics, &msgs, WithdrawalStage::Failed); + Err(error) + } + } + } + + async fn process_withdrawal_messages_inner( + &self, + msgs: Vec, + ) -> Result> { + let fxg = match on_new_withdrawals( + msgs.clone(), + self.easy_wallet.clone(), + self.cosmos_rpc.clone(), + self.escrow(), + self.get_min_deposit_sompi(), + self.must_relayer_stuff().tx_fee_multiplier, + self.must_relayer_stuff().max_sweep_inputs, + self.must_relayer_stuff().max_sweep_bundle_bytes, + ) + .await? + { + Some(fxg) => fxg, + None => return Ok(None), + }; + + info!("kaspa provider: constructed withdrawal TXs, got withdrawal FXG, now gathering sigs and signing relayer fee"); + + let fxg_arc = std::sync::Arc::new(fxg); + let bundles_validators = self.validators().get_withdraw_sigs(fxg_arc.clone()).await?; + + let finalized = combine_bundles_with_fee( + bundles_validators, + fxg_arc.as_ref(), + self.conf.multisig_threshold_kaspa, + &self.escrow(), + &self.easy_wallet, + ) + .await?; + + let tx_ids = self.submit_txs(finalized.clone()).await?; + + info!("kaspa provider: submitted TXs, now indicating progress on the Hub"); + + Ok(Some(ProcessedWithdrawals { + fxg: fxg_arc, + tx_ids, + })) + } + + async fn submit_txs(&self, txs: Vec) -> Result> { + let mut tx_ids = Vec::new(); + for tx in txs { + // allow_orphan controls whether TX can be submitted without parent TX being in DAG. Set to false to ensure TX chain integrity + let allow_orphan = false; + let tx_id = self + .rpc_with_reconnect(|api| { + let tx_clone = tx.clone(); + async move { + api.submit_transaction(tx_clone, allow_orphan) + .await + .map_err(|e| eyre::eyre!("{}", e)) + } + }) + .await?; + tx_ids.push(tx_id); + } + + if let Err(e) = self.update_balance_metrics().await { + tracing::error!(error=?e, "update balance metrics"); + } + + Ok(tx_ids) + } + + pub async fn update_balance_metrics(&self) -> Result<()> { + let escrow_addr = self.escrow_address(); + let utxos = self + .rpc_with_reconnect(|api| { + let addr = escrow_addr.clone(); + async move { + api.get_utxos_by_addresses(vec![addr]) + .await + .map_err(|e| eyre::eyre!("get UTXOs for escrow address: {}", e)) + } + }) + .await?; + + let total_escrow_bal: u64 = utxos.iter().map(|utxo| utxo.utxo_entry.amount).sum(); + + self.metrics() + .update_funds_escrowed(total_escrow_bal as i64); + self.metrics().update_escrow_utxo_count(utxos.len() as i64); + + // Get change address balance + let change_addr = self.wallet().account().change_address()?; + let change_utxos = self + .rpc_with_reconnect(|api| { + let addr = change_addr.clone(); + async move { + api.get_utxos_by_addresses(vec![addr]) + .await + .map_err(|e| eyre::eyre!("get UTXOs for change address: {}", e)) + } + }) + .await?; + + let total_change_bal: u64 = change_utxos.iter().map(|utxo| utxo.utxo_entry.amount).sum(); + self.metrics().update_relayer_funds(total_change_bal as i64); + + Ok(()) + } + + pub fn escrow(&self) -> EscrowPublic { + EscrowPublic::from_strs( + self.conf.validator_pub_keys.clone(), + self.easy_wallet.net.address_prefix, + self.conf.multisig_threshold_kaspa as u8, + ) + } + + pub fn escrow_address(&self) -> Address { + self.escrow().addr + } + + pub fn metrics(&self) -> &KaspaBridgeMetrics { + &self.metrics + } +} + +impl HyperlaneChain for KaspaProvider { + fn domain(&self) -> &HyperlaneDomain { + &self.domain + } + + fn provider(&self) -> Box { + Box::new(self.clone()) + } +} + +// Only used by scraper, not implemented for Kaspa +#[async_trait] +impl HyperlaneProvider for KaspaProvider { + async fn get_block_by_height(&self, height: u64) -> ChainResult { + Err(HyperlaneProviderError::CouldNotFindBlockByHeight(height).into()) + } + + async fn get_txn_by_hash(&self, hash: &H512) -> ChainResult { + return Err(HyperlaneProviderError::CouldNotFindTransactionByHash(*hash).into()); + } + + async fn is_contract(&self, _address: &H256) -> ChainResult { + Ok(true) + } + + async fn get_balance(&self, _address: String) -> ChainResult { + Ok(0.into()) + } + + async fn get_chain_metrics(&self) -> ChainResult> { + return Ok(None); + } +} + +async fn get_easy_wallet( + domain: HyperlaneDomain, + rpc_url: String, + wallet_secret: String, + storage_dir: Option, +) -> Result { + let args = EasyKaspaWalletArgs { + wallet_secret, + wrpc_url: rpc_url, + net: domain_to_kas_network(&domain), + storage_folder: storage_dir, + }; + EasyKaspaWallet::try_new(args).await +} + +fn cosmos_grpc_client(urls: Vec) -> CosmosProvider { + let hub_cfg = HubConnectionConf::new( + urls.clone(), // grpc_urls + vec![], // rpc_urls + "".to_string(), + "".to_string(), + "".to_string(), + RawCosmosAmount { + denom: "".to_string(), + amount: "0".to_string(), + }, + 32, + OpSubmissionConfig::default(), + NativeToken::default(), + 1.0, + None, // compat_mode + ) + .expect("create Hub connection config with default values"); + let metrics = PrometheusClientMetrics::default(); + let chain = None; + let dummy_domain = hyperlane_core::HyperlaneDomain::new_test_domain("dummy"); + let loc = hyperlane_core::ContractLocator { + domain: &dummy_domain, + address: hyperlane_core::H256::zero(), + }; + CosmosProvider::::new(&hub_cfg, &loc, None, metrics, chain) + .expect("create CosmosProvider for query client") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_cosmos_grpc_client_playground() { + // Install rustls crypto provider (required for rustls 0.23+) + let _ = rustls::crypto::aws_lc_rs::default_provider().install_default(); + + let url = Url::parse("https://grpc-dymension-playground35.mzonder.com").expect("parse URL"); + let _client = cosmos_grpc_client(vec![url]); + } +} diff --git a/rust/main/chains/dymension-kaspa/src/providers/rest.rs b/rust/main/chains/dymension-kaspa/src/providers/rest.rs new file mode 100644 index 00000000000..e583b78c0e1 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/providers/rest.rs @@ -0,0 +1,156 @@ +use hyperlane_core::{ + rpc_clients::BlockNumberGetter, ChainCommunicationError, ChainResult, FixedPointNumber, +}; +use hyperlane_metric::prometheus_metric::{ + ClientConnectionType, PrometheusClientMetrics, PrometheusConfig, +}; +use tonic::async_trait; + +use dym_kas_api::apis::configuration::Configuration; +pub use dym_kas_core::api::base::{get_config, RateLimitConfig}; +pub use dym_kas_core::api::client::*; + +use crate::ConnectionConf; +use hyperlane_cosmos::Signer as CosmosSigner; + +#[derive(Debug)] +pub struct KaspaHttpClient { + pub client: HttpClient, + metrics: PrometheusClientMetrics, + metrics_config: PrometheusConfig, +} + +#[derive(Debug, Clone)] +pub struct RestProvider { + pub client: KaspaHttpClient, + cosmos_signer: Option, +} + +#[async_trait] +impl BlockNumberGetter for KaspaHttpClient { + async fn get_block_number(&self) -> Result { + return ChainResult::Err(ChainCommunicationError::from_other_str("not implemented")); + } +} + +impl KaspaHttpClient { + /// Create new `KaspaHttpClient` + pub fn new( + url: String, + metrics: PrometheusClientMetrics, + metrics_config: PrometheusConfig, + ) -> Self { + // increment provider metric count + let chain_name = PrometheusConfig::chain_name(&metrics_config.chain); + metrics.increment_provider_instance(chain_name); + + Self { + client: HttpClient::new(url, RateLimitConfig::default()), + metrics, + metrics_config, + } + } + + /// Creates a KaspaHttpClient from a url + pub fn from_url( + url: String, + metrics: PrometheusClientMetrics, + metrics_config: PrometheusConfig, + ) -> ChainResult { + Ok(Self::new(url, metrics, metrics_config)) + } +} + +impl Drop for KaspaHttpClient { + fn drop(&mut self) { + // decrement provider metric count + let chain_name = PrometheusConfig::chain_name(&self.metrics_config.chain); + self.metrics.decrement_provider_instance(chain_name); + } +} + +impl Clone for KaspaHttpClient { + fn clone(&self) -> Self { + Self::new( + self.client.url.clone(), + self.metrics.clone(), + self.metrics_config.clone(), + ) + } +} + +#[async_trait] +impl BlockNumberGetter for RestProvider { + async fn get_block_number(&self) -> ChainResult { + return ChainResult::Err(ChainCommunicationError::from_other_str("not implemented")); + } +} + +impl RestProvider { + /// Returns a new Rpc Provider + pub fn new( + cfg: ConnectionConf, + signer: Option, + metrics: PrometheusClientMetrics, + chain: Option, + ) -> ChainResult { + let clients = [cfg.kaspa_urls_rest[0].clone()] + .iter() + .map(|url| { + let metrics_cfg = + PrometheusConfig::from_url(url, ClientConnectionType::Rpc, chain.clone()); + KaspaHttpClient::from_url(url.to_string(), metrics.clone(), metrics_cfg) + }) + .collect::, _>>()?; + + Ok(RestProvider { + client: clients[0].clone(), + cosmos_signer: signer, + }) + } + + /// get the config used for the rest client + pub fn get_cfg(&self) -> Configuration { + self.client.client.get_config() + } + + pub fn get_signer(&self) -> ChainResult<&CosmosSigner> { + self.cosmos_signer + .as_ref() + .ok_or(ChainCommunicationError::SignerUnavailable) + } + + /// Get the gas price + pub fn gas_price(&self) -> FixedPointNumber { + FixedPointNumber::zero() + } + + /// dococo + pub async fn get_deposits( + &self, + escrow_addr: &str, + lower_bound_ts: Option, + domain_kas: u32, + ) -> ChainResult> { + let res = self + .client + .client + .get_deposits_by_address(lower_bound_ts, escrow_addr, domain_kas) + .await; + res.map_err(|e| ChainCommunicationError::from_other_str(&e.to_string())) + .map(|deposits| deposits.into_iter().collect()) + } +} + +#[cfg(test)] +mod tests { + use url::Url; + + #[test] + fn test_url_roundtrip() { + let start = "https://api-tn10.kaspa.org/"; + let url = Url::parse(start).unwrap(); + let end = url.as_str(); + assert_eq!(start, end); + } +} diff --git a/rust/main/chains/dymension-kaspa/src/providers/validators.rs b/rust/main/chains/dymension-kaspa/src/providers/validators.rs new file mode 100644 index 00000000000..09a7305d128 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/providers/validators.rs @@ -0,0 +1,630 @@ +use tonic::async_trait; + +use hyperlane_core::{ + rpc_clients::BlockNumberGetter, ChainCommunicationError, ChainResult, Signable, Signature, + SignedCheckpointWithMessageId, SignedType, H160, +}; + +use bytes::Bytes; +use eyre::Result; +use reqwest::StatusCode; +use std::str::FromStr; +use tracing::{error, info}; + +use crate::ConnectionConf; +use crate::SignableProgressIndication; +use futures::stream::{FuturesUnordered, StreamExt}; +use std::sync::Arc; +use std::time::Instant; + +use crate::endpoints::*; +use crate::ops::{ + confirmation::ConfirmationFXG, deposit::DepositFXG, migration::MigrationFXG, + withdraw::WithdrawFXG, +}; +use kaspa_wallet_pskt::prelude::Bundle; + +/// Verifies that a signature was produced by the expected ISM address. +/// Returns true if valid, false otherwise (with error logging). +fn verify_ism_signer( + index: usize, + host: &str, + expected_address: &str, + signed: &SignedType, +) -> bool { + let expected_h160 = match H160::from_str(expected_address) { + Ok(h) => h, + Err(e) => { + error!( + validator = ?host, + validator_index = index, + expected_address = ?expected_address, + error = ?e, + "kaspa: invalid ISM address format" + ); + return false; + } + }; + + match signed.recover() { + Ok(recovered_signer) => { + if recovered_signer != expected_h160 { + error!( + validator = ?host, + validator_index = index, + expected_signer = ?expected_h160, + actual_signer = ?recovered_signer, + "kaspa: signature verification failed - signer mismatch" + ); + false + } else { + true + } + } + Err(e) => { + error!( + validator = ?host, + validator_index = index, + error = ?e, + "kaspa: signature recovery failed" + ); + false + } + } +} + +#[derive(Clone)] +pub struct ValidatorsClient { + pub conf: ConnectionConf, + http_client: reqwest::Client, + metrics: Option, +} + +impl std::fmt::Debug for ValidatorsClient { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ValidatorsClient") + .field("conf", &self.conf) + .finish() + } +} + +#[async_trait] +impl BlockNumberGetter for ValidatorsClient { + async fn get_block_number(&self) -> Result { + ChainResult::Err(ChainCommunicationError::from_other_str("not implemented")) + } +} + +impl ValidatorsClient { + fn relayer_stuff(&self) -> &crate::RelayerStuff { + self.conf + .relayer_stuff + .as_ref() + .expect("ValidatorsClient methods require relayer config") + } + + fn validators_escrow(&self) -> &[crate::KaspaValidatorEscrow] { + &self.relayer_stuff().validators_escrow + } + + fn validators_ism(&self) -> &[crate::KaspaValidatorIsm] { + &self.relayer_stuff().validators_ism + } + + /// Returns (original_index, host) pairs for escrow validators with non-empty hosts. + /// Original indices are preserved for correct signature/address correlation. + fn hosts_escrow(&self) -> Vec<(usize, String)> { + self.validators_escrow() + .iter() + .enumerate() + .filter(|(_, v)| !v.host.is_empty()) + .map(|(i, v)| (i, v.host.clone())) + .collect() + } + + /// Returns (original_index, host) pairs for ISM validators with non-empty hosts. + /// Original indices are preserved for correct signature/address correlation. + fn hosts_ism(&self) -> Vec<(usize, String)> { + self.validators_ism() + .iter() + .enumerate() + .filter(|(_, v)| !v.host.is_empty()) + .map(|(i, v)| (i, v.host.clone())) + .collect() + } + + /// Collects responses from validators until threshold is met. + /// Takes (original_index, host) pairs to preserve index correlation after filtering empty hosts. + /// Returns (validator_index, response) pairs sorted by validator index. + async fn collect_with_threshold( + indexed_hosts: Vec<(usize, String)>, + metrics: Option, + request_type: &str, + threshold: usize, + request_fn: F, + validate_fn: Option, + ) -> ChainResult> + where + T: Send + 'static, + F: Fn(String) -> std::pin::Pin> + Send>> + + Send + + Sync + + 'static, + V: Fn(usize, &String, &T) -> bool + Send + Sync + 'static, + { + let mut futures: FuturesUnordered<_> = indexed_hosts + .iter() + .map(|(index, host)| { + let index = *index; + let host = host.clone(); + let request_type = request_type.to_string(); + let start = Instant::now(); + let fut = request_fn(host.clone()); + let metrics_clone = metrics.clone(); + + async move { + let result = fut.await; + let duration = start.elapsed(); + let status = if result.is_ok() { "success" } else { "failure" }; + + if let Some(metrics) = &metrics_clone { + metrics + .with_label_values(&[&host, &request_type, status]) + .observe(duration.as_secs_f64()); + } + + (index, host, result, duration) + } + }) + .collect(); + + let mut successes: Vec<(usize, T)> = Vec::new(); + + while let Some((index, host, result, duration)) = futures.next().await { + match result { + Ok(value) => { + let valid = match &validate_fn { + Some(validator) => validator(index, &host, &value), + None => true, + }; + + if valid { + info!( + validator = ?host, + validator_index = index, + duration_ms = duration.as_millis(), + request_type = request_type, + "kaspa: validator response success" + ); + successes.push((index, value)); + + if successes.len() >= threshold { + info!( + collected = successes.len(), + threshold = threshold, + remaining = futures.len(), + request_type = request_type, + "kaspa: reached threshold, returning early" + ); + + let request_type_owned = request_type.to_string(); + tokio::spawn(async move { + while let Some((_, host, result, _duration)) = futures.next().await + { + if let Err(e) = result { + error!( + validator = ?host, + error = ?e, + request_type = %request_type_owned, + "kaspa: background validator failed" + ); + } + } + }); + + successes.sort_by_key(|(idx, _)| *idx); + return Ok(successes); + } + } + } + Err(e) => { + error!( + validator = ?host, + validator_index = index, + error = ?e, + duration_ms = duration.as_millis(), + request_type = request_type, + "kaspa: validator response failed" + ); + } + } + } + + Err(ChainCommunicationError::from_other_str(&format!( + "collect {}: threshold={} but got only {} successes from {} validators (with non-empty hosts)", + request_type, + threshold, + successes.len(), + indexed_hosts.len() + ))) + } + + pub fn new( + cfg: ConnectionConf, + metrics: Option, + ) -> ChainResult { + let timeout = cfg + .relayer_stuff + .as_ref() + .map(|r| r.validator_request_timeout) + .unwrap_or(std::time::Duration::from_secs(15)); + + let http_client = reqwest::Client::builder() + .timeout(timeout) + .connect_timeout(std::time::Duration::from_secs(10)) + .build() + .map_err(|e| { + ChainCommunicationError::from_other_str(&format!("build HTTP client: {}", e)) + })?; + + Ok(ValidatorsClient { + conf: cfg, + http_client, + metrics, + }) + } + + pub async fn get_deposit_sigs( + &self, + fxg: &DepositFXG, + ) -> ChainResult> { + let threshold = self.multisig_threshold_hub_ism(); + let client = self.http_client.clone(); + let hosts = self.hosts_ism(); + let expected_addresses: Vec = self + .validators_ism() + .iter() + .map(|v| v.ism_address.clone()) + .collect(); + let metrics = self.metrics.clone(); + let fxg = fxg.clone(); + + let validator = + move |index: usize, + host: &String, + signed_checkpoint: &SignedCheckpointWithMessageId| { + expected_addresses + .get(index) + .map(|expected| verify_ism_signer(index, host, expected, signed_checkpoint)) + .unwrap_or(true) + }; + + let indexed_sigs = Self::collect_with_threshold( + hosts, + metrics, + "deposit", + threshold, + move |host| { + let client = client.clone(); + let fxg = fxg.clone(); + Box::pin(async move { request_validate_new_deposits(&client, host, &fxg).await }) + }, + Some(validator), + ) + .await?; + + // Sort by recovered signer address (lexicographic order required by Hub ISM) + let mut sigs: Vec<_> = indexed_sigs.into_iter().map(|(_, sig)| sig).collect(); + sigs.sort_by_cached_key(|sig| { + sig.recover() + .expect("signature recovery should succeed after validation") + .to_fixed_bytes() + }); + + Ok(sigs) + } + + pub async fn get_confirmation_sigs( + &self, + fxg: &ConfirmationFXG, + ) -> ChainResult> { + let threshold = self.multisig_threshold_hub_ism(); + let client = self.http_client.clone(); + let hosts = self.hosts_ism(); + let expected_addresses: Vec = self + .validators_ism() + .iter() + .map(|v| v.ism_address.clone()) + .collect(); + let metrics = self.metrics.clone(); + let fxg = fxg.clone(); + + // Capture progress_indication for signature verification + let progress_indication = fxg.progress_indication.clone(); + + let validator = move |index: usize, host: &String, signature: &Signature| { + expected_addresses.get(index).map_or(true, |expected| { + // Construct SignedType to enable signer recovery + let signable = SignableProgressIndication::new(progress_indication.clone()); + let signed = SignedType { + value: signable, + signature: *signature, + }; + verify_ism_signer(index, host, expected, &signed) + }) + }; + + let indexed_sigs = Self::collect_with_threshold( + hosts, + metrics, + "confirmation", + threshold, + move |host| { + let client = client.clone(); + let fxg = fxg.clone(); + Box::pin( + async move { request_validate_new_confirmation(&client, host, &fxg).await }, + ) + }, + Some(validator), + ) + .await?; + + // Get ISM addresses for sorting + let ism_addresses: Vec = self + .validators_ism() + .iter() + .map(|v| H160::from_str(&v.ism_address).expect("ISM address must be valid")) + .collect(); + + // Sort by ISM address (lexicographic order required by Hub ISM) + let mut sigs_with_addr: Vec<_> = indexed_sigs + .into_iter() + .map(|(idx, sig)| { + let addr = ism_addresses.get(idx).copied().unwrap_or_default(); + (addr, sig) + }) + .collect(); + sigs_with_addr.sort_by_key(|(addr, _)| addr.to_fixed_bytes()); + + Ok(sigs_with_addr.into_iter().map(|(_, sig)| sig).collect()) + } + + pub async fn get_withdraw_sigs(&self, fxg: Arc) -> ChainResult> { + let threshold = self.multisig_threshold_escrow(); + let hosts = self.hosts_escrow(); + let client = self.http_client.clone(); + let metrics = self.metrics.clone(); + + let indexed_bundles = Self::collect_with_threshold( + hosts, + metrics, + "withdrawal", + threshold, + move |host| { + let client = client.clone(); + let fxg = fxg.clone(); + Box::pin(async move { + request_sign_withdrawal_bundle(&client, host, fxg.as_ref()).await + }) + }, + None:: bool>, + ) + .await?; + + // Extract bundles (order doesn't matter for Kaspa Schnorr multisig) + Ok(indexed_bundles + .into_iter() + .map(|(_, bundle)| bundle) + .collect()) + } + + pub async fn get_migration_sigs(&self, fxg: Arc) -> ChainResult> { + let threshold = self.multisig_threshold_escrow(); + let hosts = self.hosts_escrow(); + let client = self.http_client.clone(); + let metrics = self.metrics.clone(); + + let indexed_bundles = Self::collect_with_threshold( + hosts, + metrics, + "migration", + threshold, + move |host| { + let client = client.clone(); + let fxg = fxg.clone(); + Box::pin( + async move { request_sign_migration_bundle(&client, host, fxg.as_ref()).await }, + ) + }, + None:: bool>, + ) + .await?; + + // Extract bundles (order doesn't matter for Kaspa Schnorr multisig) + Ok(indexed_bundles + .into_iter() + .map(|(_, bundle)| bundle) + .collect()) + } + + pub fn multisig_threshold_hub_ism(&self) -> usize { + self.conf.multisig_threshold_hub_ism + } + + pub fn multisig_threshold_escrow(&self) -> usize { + self.conf.multisig_threshold_kaspa + } +} + +// see https://github.com/dymensionxyz/hyperlane-monorepo/blob/fe1c79156f5ef6ead5bc60f26a373d0867848532/rust/main/hyperlane-base/src/types/local_storage.rs#L80 +pub async fn request_validate_new_deposits( + client: &reqwest::Client, + host: String, + deposits: &DepositFXG, +) -> Result { + info!( + validator = %host, + "dymension: requesting deposit sigs from validator" + ); + let bz = Bytes::from(deposits); + let res = client + // calls to https://github.com/dymensionxyz/hyperlane-monorepo/blob/1a603d65e0073037da896534fc52da4332a7a7b1/rust/main/chains/dymension-kaspa/src/router.rs#L40 + .post(format!("{}{}", host, ROUTE_VALIDATE_NEW_DEPOSITS)) + .body(bz) + .send() + .await?; + + let status = res.status(); + if status == StatusCode::OK { + let body = res.json::().await?; + Ok(body) + } else { + let err_msg = res.text().await.unwrap_or_else(|_| status.to_string()); + + let err = match status { + StatusCode::ACCEPTED => { + eyre::eyre!("DepositNotFinal: {}", err_msg) + } + StatusCode::UNPROCESSABLE_ENTITY => { + eyre::eyre!("TransactionRejected: {}", err_msg) + } + StatusCode::SERVICE_UNAVAILABLE => { + eyre::eyre!("ServiceUnavailable: {}", err_msg) + } + _ => { + eyre::eyre!("ValidationFailed: {} - {}", status, err_msg) + } + }; + + Err(err) + } +} + +pub async fn request_validate_new_confirmation( + client: &reqwest::Client, + host: String, + conf: &ConfirmationFXG, +) -> Result { + let bz = Bytes::from(conf); + let res = client + .post(format!("{}{}", host, ROUTE_VALIDATE_CONFIRMED_WITHDRAWALS)) + .body(bz) + .send() + .await?; + + let status = res.status(); + if status == StatusCode::OK { + let body = res.json::().await?; + Ok(body) + } else { + let err_msg = res.text().await.unwrap_or_else(|_| status.to_string()); + Err(eyre::eyre!( + "validate confirmation: {} - {}", + status, + err_msg + )) + } +} + +pub async fn request_sign_withdrawal_bundle( + client: &reqwest::Client, + host: String, + fxg: &WithdrawFXG, +) -> Result { + info!( + validator = %host, + "dymension: requesting withdrawal sigs from validator" + ); + let bz = Bytes::try_from(fxg)?; + let res = client + .post(format!("{}{}", host, ROUTE_SIGN_PSKTS)) + .body(bz) + .send() + .await?; + + let status = res.status(); + if status == StatusCode::OK { + let bundle = res.json::().await?; + Ok(bundle) + } else { + let err_msg = res.text().await.unwrap_or_else(|_| status.to_string()); + Err(eyre::eyre!( + "sign withdrawal bundle: {} - {}", + status, + err_msg + )) + } +} + +pub async fn request_sign_migration_bundle( + client: &reqwest::Client, + host: String, + fxg: &MigrationFXG, +) -> Result { + info!( + validator = %host, + "dymension: requesting migration sigs from validator" + ); + let bz = Bytes::try_from(fxg)?; + let res = client + .post(format!("{}{}", host, ROUTE_SIGN_MIGRATION)) + .body(bz) + .send() + .await?; + + let status = res.status(); + if status == StatusCode::OK { + let bundle = res.json::().await?; + Ok(bundle) + } else { + let err_msg = res.text().await.unwrap_or_else(|_| status.to_string()); + Err(eyre::eyre!( + "sign migration bundle: {} - {}", + status, + err_msg + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ops::deposit::DepositFXG; + use hyperlane_core::{Checkpoint, CheckpointWithMessageId, SignedType, H256, U256}; + + #[tokio::test] + #[ignore = "Requires real validator server"] + async fn test_server_smoke() { + let host = "http://localhost:9090"; + let deposits = DepositFXG::default(); + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(15)) + .build() + .unwrap(); + let res = request_validate_new_deposits(&client, host.to_string(), &deposits).await; + let _ = res; + println!("res: {:?}", res); + } + + #[tokio::test] + async fn test_body_json() { + let sig: SignedType = SignedType { + value: CheckpointWithMessageId { + checkpoint: Checkpoint { + merkle_tree_hook_address: H256::default(), + mailbox_domain: 0, + root: H256::default(), + index: 0, + }, + message_id: H256::default(), + }, + signature: Signature { + r: U256::default(), + s: U256::default(), + v: 0, + }, + }; + _ = sig; + } +} diff --git a/rust/main/chains/dymension-kaspa/src/relayer/confirm/confirmation.rs b/rust/main/chains/dymension-kaspa/src/relayer/confirm/confirmation.rs new file mode 100644 index 00000000000..87e896d77d2 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/relayer/confirm/confirmation.rs @@ -0,0 +1,145 @@ +use crate::ops::{confirmation::ConfirmationFXG, payload::MessageID}; +use dym_kas_core::api::client::HttpClient; +use dym_kas_core::hash::hex_to_kaspa_hash; +use eyre::Result; +use kaspa_consensus_core::tx::TransactionOutpoint; +use kaspa_wallet_core::error::Error; +use tracing::info; + +/// WARNING: ONLY FOR UNHAPPY PATH +/// +/// Traces transactions in reverse from a new UTXO to an old anchor UTXO, collecting +/// all withdrawal payloads that were processed in between. This follows the transaction +/// lineage of the escrow address to create a confirmation progress indication. +/// +/// # Arguments +/// * `client` - The HTTP client for querying Kaspa API transactions +/// * `src_escrow` - Source escrow address (where anchor/old UTXOs are) +/// * `dst_escrow` - Destination escrow address (where current UTXOs are) +/// * `out_new_candidate` - The new transaction outpoint to start tracing from +/// * `out_old` - The old anchor transaction outpoint to trace to +/// +/// In normal operation, src and dst are the same address. During post-migration sync, +/// src is the old escrow and dst is the new escrow, allowing the trace to cross the +/// migration boundary. +/// +/// # Returns +/// * `Result` - The confirmation FXG containing the progress indication +/// with old and new outpoints and a list of processed withdrawal message IDs +pub async fn expensive_trace_transactions( + client: &HttpClient, + src_escrow: &str, + dst_escrow: &str, + out_new_candidate: TransactionOutpoint, + out_old: TransactionOutpoint, +) -> Result { + let mut processed_withdrawals: Vec = Vec::new(); + let mut outpoint_sequence = Vec::new(); + + recursive_trace_transactions( + client, + src_escrow, + dst_escrow, + out_new_candidate, + out_old, + &mut outpoint_sequence, + &mut processed_withdrawals, + ) + .await?; + + info!( + new_anchor = ?out_new_candidate, + old_anchor = ?out_old, + utxos_count = outpoint_sequence.len(), + processed_withdrawals_count = processed_withdrawals.len(), + "kaspa relayer: traced transaction lineage" + ); + for o in outpoint_sequence.clone() { + info!(outpoint = ?o, "kaspa relayer: traced lineage outpoint"); + } + Ok(ConfirmationFXG::from_msgs_outpoints( + processed_withdrawals, + outpoint_sequence, + )) +} + +async fn recursive_trace_transactions( + client_rest: &HttpClient, + src_escrow: &str, + dst_escrow: &str, + out_curr: TransactionOutpoint, + out_old: TransactionOutpoint, + outpoint_sequence: &mut Vec, + processed_withdrawals: &mut Vec, +) -> Result<()> { + if out_curr == out_old { + // we reached the end, success! + outpoint_sequence.push(out_curr); + return Ok(()); + } + + info!(utxo = ?out_curr, "kaspa relayer: tracing lineage backwards from UTXO"); + + // tx that created the candidate + let tx = client_rest + .get_tx_by_id(&out_curr.transaction_id.to_string()) + .await?; + + info!(tx = ?tx, "kaspa relayer: queried transaction for lineage trace"); + + let inputs = tx + .inputs + .as_ref() + .ok_or(Error::Custom("Inputs not found".to_string()))?; + + // we skip inputs that are not from escrow addresses + // we do recursive call for inputs that are from escrow addresses + for input in inputs { + let spent_escrow_funds = { + let input_address = input + .previous_outpoint_address + .as_ref() + .ok_or(Error::Custom("Input address not found".to_string()))?; + + // Accept inputs from either src or dst escrow to handle migration boundary + input_address == src_escrow || input_address == dst_escrow + }; + if !spent_escrow_funds { + info!(input_index = ?input.index, "kaspa relayer: skipped input from non-escrow address"); + continue; + } + + // TODO: have ::From method to get utxo from TxInput + let out_input = TransactionOutpoint { + transaction_id: hex_to_kaspa_hash(&input.previous_outpoint_hash)?, + index: input.previous_outpoint_index.parse()?, + }; + + let res = Box::pin(recursive_trace_transactions( + client_rest, + src_escrow, + dst_escrow, + out_input, + out_old, + outpoint_sequence, + processed_withdrawals, + )) + .await; + + // this input is not part of the lineage, continue to other input + if res.is_err() { + continue; + } + + /* ------------ the input is part of the lineage! ------------ */ + if let Some(payload) = tx.payload.clone() { + let message_ids = crate::ops::payload::MessageIDs::from_tx_payload(&payload)?; + processed_withdrawals.extend(message_ids.0); + } + outpoint_sequence.push(out_curr); + return Ok(()); + } + + // if reached here, return error as we're not followed the lineage + Err(eyre::eyre!("No lineage UTXOs found in transaction inputs")) +} diff --git a/rust/main/chains/dymension-kaspa/src/relayer/confirm/confirmation_test.rs b/rust/main/chains/dymension-kaspa/src/relayer/confirm/confirmation_test.rs new file mode 100644 index 00000000000..a635353959e --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/relayer/confirm/confirmation_test.rs @@ -0,0 +1,81 @@ +#[cfg(test)] +mod tests { + use super::recursive_trace_transactions; + use dym_kas_core::api::base::RateLimitConfig; + use dym_kas_core::api::client::HttpClient; + use dym_kas_core::hash::hex_to_kaspa_hash; + use kaspa_consensus_core::tx::TransactionOutpoint; + + #[tokio::test] + #[ignore = "dont hit real api"] + // tested over https://explorer-tn10.kaspa.org/txs/1ffa672605af17906d99ba9506dd49406a2e8a3faa2969ab0c8929373aca51d1 + async fn test_trace_transactions() { + let mut lineage_utxos = Vec::new(); + let mut processed_withdrawals = Vec::new(); + + let client = HttpClient::new( + "https://api-tn10.kaspa.org/".to_string(), + RateLimitConfig::default(), + ); + + let escrow_address = + "kaspatest:pz2q7x7munf7p9zduvfed8dj7znkh7z4973mqd995cvrajk7qhkm57jdfl3l9".to_string(); + + // Define the anchor UTXO + let anchor_utxo = TransactionOutpoint { + transaction_id: hex_to_kaspa_hash( + "3e43fee61f7082a0fbbb9be7509219203e533e3f9cc8dd0aaa21ae4b81c5e9d5", + ) + .unwrap(), + index: 0, + }; + + let new_utxo = TransactionOutpoint { + transaction_id: hex_to_kaspa_hash( + "49601485182fa057b000d18993db7756fc5a58823c47b64495d5532add38d2ea", + ) + .unwrap(), + index: 0, + }; + + // Assert the result + let result = recursive_trace_transactions( + &client, + &escrow_address, + new_utxo, + anchor_utxo, + &mut lineage_utxos, + &mut processed_withdrawals, + ) + .await; + assert!(result.is_ok()); + assert_eq!(lineage_utxos.len(), 2); + assert_eq!(processed_withdrawals.len(), 1); + } + + #[test] + fn message_ids_from_payload() { + use hyperlane_core::H256; + + // Create a MessageID with a test H256 + let test_id = H256::from_slice(&[ + 0x27, 0xb2, 0x04, 0xce, 0x0d, 0xea, 0xb9, 0xf8, 0xcd, 0x60, 0x2b, 0x11, 0xe9, 0xb9, + 0x29, 0x8d, 0x9d, 0xfc, 0xd2, 0x80, 0x62, 0x72, 0x53, 0x52, 0x60, 0x97, 0x4f, 0x2a, + 0xc3, 0x56, 0x78, 0x2e, + ]); + + let message_ids = + crate::ops::payload::MessageIDs::new(vec![crate::ops::payload::MessageID(test_id)]); + + // Convert to bytes and back + let bytes = message_ids.to_bytes(); + let decoded = crate::ops::payload::MessageIDs::from_bytes(bytes).unwrap(); + + assert_eq!(decoded.0.len(), 1); + assert_eq!(decoded.0[0].0, test_id); + } +} + +// FIXME: test non lineage utxo +// FIXME: test multi hop lineage +// FIXME: test single TX with multiple message IDs diff --git a/rust/main/chains/dymension-kaspa/src/relayer/confirm/mod.rs b/rust/main/chains/dymension-kaspa/src/relayer/confirm/mod.rs new file mode 100644 index 00000000000..c05b8d1b64f --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/relayer/confirm/mod.rs @@ -0,0 +1,3 @@ +pub mod confirmation; +pub use confirmation::*; +mod confirmation_test; diff --git a/rust/main/chains/dymension-kaspa/src/relayer/deposit/deposit.rs b/rust/main/chains/dymension-kaspa/src/relayer/deposit/deposit.rs new file mode 100644 index 00000000000..dcc7349545e --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/relayer/deposit/deposit.rs @@ -0,0 +1,143 @@ +use crate::ops::deposit::DepositFXG; +use dym_kas_core::api::client::{Deposit, HttpClient}; +use dym_kas_core::finality::is_safe_against_reorg; +use eyre::Result; +use hyperlane_core::U256; +pub use kaspa_bip32::secp256k1::PublicKey; +use tracing::{debug, info}; + +/// Error type for deposit processing that includes retry timing information +#[derive(Debug)] +pub enum KaspaTxError { + NotFinalError { + confirmations: i64, + required_confirmations: i64, + retry_after_secs: f64, + }, + ProcessingError(eyre::Error), +} + +impl std::fmt::Display for KaspaTxError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + KaspaTxError::NotFinalError { + confirmations, + required_confirmations, + retry_after_secs, + } => { + write!( + f, + "Deposit not final enough: {}/{} confirmations. Retry in {:.1}s", + confirmations, required_confirmations, retry_after_secs + ) + } + KaspaTxError::ProcessingError(err) => { + write!(f, "Processing error: {}", err) + } + } + } +} + +impl std::error::Error for KaspaTxError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + KaspaTxError::NotFinalError { .. } => None, + KaspaTxError::ProcessingError(err) => Some(err.as_ref()), + } + } +} + +impl From for KaspaTxError { + fn from(err: eyre::Error) -> Self { + KaspaTxError::ProcessingError(err) + } +} + +pub async fn check_deposit_finality( + deposit: &Deposit, + rest_client: &HttpClient, +) -> Result<(), KaspaTxError> { + let finality_status = is_safe_against_reorg( + rest_client, + &deposit.id.to_string(), + Some(deposit.accepting_block_hash.clone()), + ) + .await?; + + if !finality_status.is_final() { + let pending_confirmations = + finality_status.required_confirmations - finality_status.confirmations; + let retry_after_secs = if pending_confirmations > 0 { + pending_confirmations as f64 * 0.1 + } else { + 10.0 + }; + debug!( + deposit_id = %deposit.id, + confirmations = finality_status.confirmations, + required_confirmations = finality_status.required_confirmations, + retry_after_secs = retry_after_secs, + "kaspa relayer: deposit not yet safe against reorg" + ); + + return Err(KaspaTxError::NotFinalError { + confirmations: finality_status.confirmations, + required_confirmations: finality_status.required_confirmations, + retry_after_secs, + }); + } + + info!( + deposit_id = %deposit.id, + confirmations = finality_status.confirmations, + "kaspa relayer: deposit safe against reorg" + ); + + if deposit.block_hashes.is_empty() { + return Err(eyre::eyre!("Deposit had no block hashes").into()); + } + + Ok(()) +} + +pub fn build_deposit_fxg( + hl_message: hyperlane_core::HyperlaneMessage, + amount: U256, + utxo_index: usize, + deposit: &Deposit, +) -> DepositFXG { + DepositFXG { + tx_id: deposit.id.to_string(), + utxo_index, + amount, + accepting_block_hash: deposit.accepting_block_hash.clone(), + containing_block_hash: deposit.block_hashes[0].clone(), + hl_message, + } +} + +#[cfg(test)] +mod tests { + + use crate::ops::message::ParsedHL; + + #[test] + fn test_parsed_hl_parse() { + let inputs = [ + "030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000029956d5fc7253fde73070a965c50051e03437fda8f657fdd8fb5926c402bf7520000000000000000000000000000000000000000000000000000000005f5e100", + "030000000004d10892ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff804b267ca0726f757465725f6170700000000000000000000000000002000000000000000000000000000000000000000089760f514dcfcccf1e4c5edc6bf6041931c4c18300000000000000000000000000000000000000000000000000000000000003e8", + ]; + for input in inputs { + let parsed = ParsedHL::parse_string(input); + match parsed { + Ok(parsed) => { + println!("hl_message: {:?}", parsed.hl_message); + println!("token_message: {:?}", parsed.token_message); + } + Err(e) => { + panic!("parse error: {:?}", e); + } + } + } + } +} diff --git a/rust/main/chains/dymension-kaspa/src/relayer/deposit/mod.rs b/rust/main/chains/dymension-kaspa/src/relayer/deposit/mod.rs new file mode 100644 index 00000000000..aba8c9a1e27 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/relayer/deposit/mod.rs @@ -0,0 +1,2 @@ +pub mod deposit; +pub use deposit::*; diff --git a/rust/main/chains/dymension-kaspa/src/relayer/metrics/metrics.rs b/rust/main/chains/dymension-kaspa/src/relayer/metrics/metrics.rs new file mode 100644 index 00000000000..1164e21c2bd --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/relayer/metrics/metrics.rs @@ -0,0 +1,665 @@ +use prometheus::{ + GaugeVec, Histogram, HistogramOpts, HistogramVec, IntCounter, IntGauge, Opts, Registry, +}; +use std::collections::{HashMap, HashSet}; +use std::sync::{Arc, Mutex, OnceLock, RwLock}; +use std::time::Instant; + +/// Singleton storage for KaspaBridgeMetrics instances per registry +static KASPA_METRICS_INSTANCES: OnceLock>>> = + OnceLock::new(); + +/// Kaspa relayer-specific metrics following Prometheus best practices +#[derive(Debug, Clone)] +pub struct KaspaBridgeMetrics { + // Balance gauges + /// Current balance of the relayer address in sompi + pub relayer_address_funds: IntGauge, + + /// Total funds currently held in escrow in sompi + pub funds_escrowed: IntGauge, + + /// Number of UTXOs in escrow address + pub escrow_utxo_count: IntGauge, + + // Deposit metrics + /// Distribution of deposit amounts in sompi + pub deposit_amount_sompi: Histogram, + + /// Distribution of deposit processing latency in seconds (from detection to Hub confirmation) + pub deposit_duration_seconds: Histogram, + + /// Cumulative amount of deposits processed in sompi + pub total_funds_deposited: IntCounter, + + /// Total number of deposits successfully processed + pub deposits_processed_total: IntCounter, + + // Withdrawal metrics + /// Distribution of withdrawal amounts in sompi + pub withdrawal_amount_sompi: Histogram, + + /// Distribution of withdrawal processing latency in seconds (from reception to Kaspa TX success) + pub withdrawal_duration_seconds: Histogram, + + /// Distribution of messages per withdrawal batch + pub withdrawal_batch_messages: Histogram, + + /// Cumulative amount of withdrawals processed in sompi + pub total_funds_withdrawn: IntCounter, + + /// Total number of withdrawal messages successfully processed + pub withdrawals_processed_total: IntCounter, + + // Failure tracking + /// Number of unique withdrawals currently in failed state + pub pending_failed_withdrawals: IntGauge, + + /// Number of unique deposits currently in failed state + pub pending_failed_deposits: IntGauge, + + /// Total amount in sompi currently in failed withdrawal state + pub failed_withdrawal_funds_sompi: IntGauge, + + /// Total amount in sompi currently in failed deposit state + pub failed_deposit_funds_sompi: IntGauge, + + // Confirmation metrics + /// Total number of confirmation failures + pub confirmations_failed: IntCounter, + + /// Number of confirmations currently pending + pub confirmations_pending: IntGauge, + + // Anchor point info + /// Hub anchor point information (info metric with transaction ID as label) + pub hub_anchor_point_info: GaugeVec, + + /// Last withdrawal anchor point information (info metric tracking last confirmed withdrawal) + pub last_anchor_point_info: GaugeVec, + + /// Relayer receive address information (info metric with receive address) + pub relayer_receive_address_info: GaugeVec, + + // Validator request metrics + /// Distribution of validator HTTP request latency in seconds + pub validator_request_duration: HistogramVec, + + // Internal tracking state (not exposed as metrics) + /// Track unique failed deposits to avoid double counting + failed_deposit_ids: Arc>>, + + /// Track amounts of failed deposits + failed_deposit_amounts: Arc>>, + + /// Track unique failed withdrawals to avoid double counting + failed_withdrawal_ids: Arc>>, + + /// Track amounts of failed withdrawals + failed_withdrawal_amounts: Arc>>, + + /// Track withdrawal start times for latency calculation (keyed by message ID) + withdrawal_start_times: Arc>>, +} + +impl KaspaBridgeMetrics { + pub fn new(registry: &Registry) -> prometheus::Result { + let registry_id = registry as *const Registry as usize; + + // Check if we already have an instance for this registry + let instances_map = KASPA_METRICS_INSTANCES.get_or_init(|| Mutex::new(HashMap::new())); + let mut instances = instances_map.lock().unwrap(); + + if let Some(existing_instance) = instances.get(®istry_id) { + return Ok((**existing_instance).clone()); + } + + // Create Kaspa relayer metrics using the provided registry + let relayer_address_funds = IntGauge::new( + "kaspa_relayer_balance_sompi", + "Current balance of the relayer address in sompi", + )?; + // Register the metric - if already exists, just continue + let _ = registry.register(Box::new(relayer_address_funds.clone())); + + let funds_escrowed = IntGauge::new( + "kaspa_funds_escrowed_sompi", + "Total funds currently held in escrow in sompi", + )?; + let _ = registry.register(Box::new(funds_escrowed.clone())); + + let total_funds_deposited = IntCounter::new( + "kaspa_total_funds_deposited_sompi", + "Cumulative amount of deposits processed in sompi", + )?; + let _ = registry.register(Box::new(total_funds_deposited.clone())); + + let total_funds_withdrawn = IntCounter::new( + "kaspa_total_funds_withdrawn_sompi", + "Cumulative amount of withdrawals processed in sompi", + )?; + let _ = registry.register(Box::new(total_funds_withdrawn.clone())); + + let pending_failed_withdrawals = IntGauge::new( + "kaspa_pending_failed_withdrawals", + "Number of unique withdrawals currently in failed state", + )?; + let _ = registry.register(Box::new(pending_failed_withdrawals.clone())); + + let pending_failed_deposits = IntGauge::new( + "kaspa_pending_failed_deposits", + "Number of unique deposits currently in failed state", + )?; + let _ = registry.register(Box::new(pending_failed_deposits.clone())); + + let failed_withdrawal_funds_sompi = IntGauge::new( + "kaspa_failed_withdrawal_funds_sompi", + "Total amount in sompi currently in failed withdrawal state", + )?; + let _ = registry.register(Box::new(failed_withdrawal_funds_sompi.clone())); + + let failed_deposit_funds_sompi = IntGauge::new( + "kaspa_failed_deposit_funds_sompi", + "Total amount in sompi currently in failed deposit state", + )?; + let _ = registry.register(Box::new(failed_deposit_funds_sompi.clone())); + + let confirmations_failed = IntCounter::new( + "kaspa_confirmations_failed_total", + "Total number of confirmation failures", + )?; + let _ = registry.register(Box::new(confirmations_failed.clone())); + + let confirmations_pending = IntGauge::new( + "kaspa_confirmations_pending", + "Number of confirmations currently pending", + )?; + let _ = registry.register(Box::new(confirmations_pending.clone())); + + let escrow_utxo_count = IntGauge::new( + "kaspa_escrow_utxo_count", + "Number of UTXOs in escrow address", + )?; + let _ = registry.register(Box::new(escrow_utxo_count.clone())); + + let deposits_processed_total = IntCounter::new( + "kaspa_deposits_processed_total", + "Total number of deposits successfully processed", + )?; + let _ = registry.register(Box::new(deposits_processed_total.clone())); + + let withdrawals_processed_total = IntCounter::new( + "kaspa_withdrawals_processed_total", + "Total number of withdrawal messages successfully processed", + )?; + let _ = registry.register(Box::new(withdrawals_processed_total.clone())); + + // Histogram for deposit amounts: 0.1 KAS, 1 KAS, 10 KAS, 100 KAS, 1000 KAS, 10k KAS, 100k KAS, 1M KAS + let deposit_amount_sompi = Histogram::with_opts( + HistogramOpts::new( + "kaspa_deposit_amount_sompi", + "Distribution of deposit amounts in sompi", + ) + .buckets(vec![ + 10_000_000.0, + 100_000_000.0, + 1_000_000_000.0, + 10_000_000_000.0, + 100_000_000_000.0, + 1_000_000_000_000.0, + 10_000_000_000_000.0, + 100_000_000_000_000.0, + ]), + )?; + let _ = registry.register(Box::new(deposit_amount_sompi.clone())); + + // Histogram for deposit durations: 10s, 30s, 1m, 2m, 5m, 10m, 30m, 1h, 2h, 6h, 12h, 24h + let deposit_duration_seconds = Histogram::with_opts( + HistogramOpts::new( + "kaspa_deposit_duration_seconds", + "Distribution of deposit processing latency in seconds", + ) + .buckets(vec![ + 10.0, 30.0, 60.0, 120.0, 300.0, 600.0, 1800.0, 3600.0, 7200.0, 21600.0, 43200.0, + 86400.0, + ]), + )?; + let _ = registry.register(Box::new(deposit_duration_seconds.clone())); + + // Histogram for withdrawal amounts: 0.1 KAS, 1 KAS, 10 KAS, 100 KAS, 1000 KAS, 10k KAS, 100k KAS, 1M KAS + let withdrawal_amount_sompi = Histogram::with_opts( + HistogramOpts::new( + "kaspa_withdrawal_amount_sompi", + "Distribution of withdrawal amounts in sompi", + ) + .buckets(vec![ + 10_000_000.0, + 100_000_000.0, + 1_000_000_000.0, + 10_000_000_000.0, + 100_000_000_000.0, + 1_000_000_000_000.0, + 10_000_000_000_000.0, + 100_000_000_000_000.0, + ]), + )?; + let _ = registry.register(Box::new(withdrawal_amount_sompi.clone())); + + // Histogram for withdrawal durations: 10s, 30s, 1m, 2m, 5m, 10m, 30m, 1h, 2h, 6h, 12h, 24h + let withdrawal_duration_seconds = Histogram::with_opts( + HistogramOpts::new( + "kaspa_withdrawal_duration_seconds", + "Distribution of withdrawal processing latency in seconds", + ) + .buckets(vec![ + 10.0, 30.0, 60.0, 120.0, 300.0, 600.0, 1800.0, 3600.0, 7200.0, 21600.0, 43200.0, + 86400.0, + ]), + )?; + let _ = registry.register(Box::new(withdrawal_duration_seconds.clone())); + + // Histogram for withdrawal batch message counts: 1, 2, 5, 10, 20, 50, 100 + let withdrawal_batch_messages = Histogram::with_opts( + HistogramOpts::new( + "kaspa_withdrawal_batch_messages", + "Distribution of messages per withdrawal batch", + ) + .buckets(vec![1.0, 2.0, 5.0, 10.0, 20.0, 50.0, 100.0]), + )?; + let _ = registry.register(Box::new(withdrawal_batch_messages.clone())); + + let hub_anchor_point_info = GaugeVec::new( + Opts::new( + "kaspa_hub_anchor_point_info", + "Current hub anchor point transaction ID and metadata", + ), + &["tx_id", "outpoint_index", "updated_at"], + )?; + let _ = registry.register(Box::new(hub_anchor_point_info.clone())); + + let last_anchor_point_info = GaugeVec::new( + Opts::new( + "kaspa_last_anchor_point_info", + "Last withdrawal anchor point transaction ID and metadata", + ), + &["tx_id", "outpoint_index", "updated_at"], + )?; + let _ = registry.register(Box::new(last_anchor_point_info.clone())); + + let relayer_receive_address_info = GaugeVec::new( + Opts::new( + "kaspa_relay_receive_address", + "Relayer wallet receive address", + ), + &["receive_address"], + )?; + let _ = registry.register(Box::new(relayer_receive_address_info.clone())); + + let validator_request_duration = HistogramVec::new( + HistogramOpts::new( + "kaspa_validator_request_duration_seconds", + "Distribution of validator HTTP request latency in seconds", + ) + .buckets(vec![ + 0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 15.0, 30.0, + ]), + &["validator_host", "request_type", "status"], + )?; + let _ = registry.register(Box::new(validator_request_duration.clone())); + + let new_instance = Self { + // Balances + relayer_address_funds, + funds_escrowed, + escrow_utxo_count, + // Deposits + deposit_amount_sompi, + deposit_duration_seconds, + total_funds_deposited, + deposits_processed_total, + // Withdrawals + withdrawal_amount_sompi, + withdrawal_duration_seconds, + withdrawal_batch_messages, + total_funds_withdrawn, + withdrawals_processed_total, + // Failures + pending_failed_withdrawals, + pending_failed_deposits, + failed_withdrawal_funds_sompi, + failed_deposit_funds_sompi, + // Confirmations + confirmations_failed, + confirmations_pending, + // Anchor points + hub_anchor_point_info, + last_anchor_point_info, + relayer_receive_address_info, + // Validators + validator_request_duration, + // Internal tracking + failed_deposit_ids: Arc::new(RwLock::new(HashSet::new())), + failed_deposit_amounts: Arc::new(RwLock::new(HashMap::new())), + failed_withdrawal_ids: Arc::new(RwLock::new(HashSet::new())), + failed_withdrawal_amounts: Arc::new(RwLock::new(HashMap::new())), + withdrawal_start_times: Arc::new(RwLock::new(HashMap::new())), + }; + + // Store the instance in our singleton map + let instance_arc = Arc::new(new_instance.clone()); + instances.insert(registry_id, instance_arc); + + Ok(new_instance) + } + + /// Update relayer address balance + pub fn update_relayer_funds(&self, balance_sompi: i64) { + self.relayer_address_funds.set(balance_sompi); + } + + /// Update escrow balance + pub fn update_funds_escrowed(&self, balance_sompi: i64) { + self.funds_escrowed.set(balance_sompi); + } + + /// Record successful deposit processing with amount, ID, and timing from creation + pub fn record_deposit_processed( + &self, + deposit_id: &str, + amount_sompi: u64, + created_at: std::time::Instant, + ) { + // Calculate and observe duration + let duration_secs = created_at.elapsed().as_secs_f64(); + self.deposit_duration_seconds.observe(duration_secs); + + // Observe amount + self.deposit_amount_sompi.observe(amount_sompi as f64); + + // Update counters + self.total_funds_deposited.inc_by(amount_sompi); + self.deposits_processed_total.inc(); + + // Remove from failed set if it was previously failed and decrement pending count and amount + let mut failed_ids = self.failed_deposit_ids.write().unwrap(); + let mut failed_amounts = self.failed_deposit_amounts.write().unwrap(); + if failed_ids.remove(deposit_id) { + self.pending_failed_deposits.dec(); + if let Some(failed_amount) = failed_amounts.remove(deposit_id) { + self.failed_deposit_funds_sompi.sub(failed_amount as i64); + } + } + } + + /// Record withdrawal message initiation - stores start time for the message + pub fn record_withdrawal_initiated(&self, message_id: &str, amount_sompi: u64) { + // Store start time for this message + let mut start_times = self.withdrawal_start_times.write().unwrap(); + start_times.insert(message_id.to_string(), Instant::now()); + + // Observe amount + self.withdrawal_amount_sompi.observe(amount_sompi as f64); + } + + /// Record withdrawal batch size + pub fn record_withdrawal_batch_size(&self, message_count: u64) { + self.withdrawal_batch_messages.observe(message_count as f64); + } + + /// Record successful withdrawal processing - calculates duration and updates counters + pub fn record_withdrawal_processed(&self, message_id: &str, amount_sompi: u64) { + // Calculate and observe duration if we have a start time + let mut start_times = self.withdrawal_start_times.write().unwrap(); + if let Some(start_time) = start_times.remove(message_id) { + let duration_secs = start_time.elapsed().as_secs_f64(); + self.withdrawal_duration_seconds.observe(duration_secs); + } else { + tracing::warn!( + message_id = %message_id, + "Withdrawal message completed but no start time found - latency metric will not be recorded" + ); + } + drop(start_times); + + // Update counters + self.total_funds_withdrawn.inc_by(amount_sompi); + self.withdrawals_processed_total.inc(); + + // Remove from failed set if it was previously failed + let mut failed_ids = self.failed_withdrawal_ids.write().unwrap(); + let mut failed_amounts = self.failed_withdrawal_amounts.write().unwrap(); + if failed_ids.remove(message_id) { + self.pending_failed_withdrawals.dec(); + if let Some(failed_amount) = failed_amounts.remove(message_id) { + self.failed_withdrawal_funds_sompi.sub(failed_amount as i64); + } + } + } + + /// Record failed deposit attempt with deduplication + /// Returns true if this is a new failure, false if it's a retry of an already-failed deposit + pub fn record_deposit_failed(&self, deposit_id: &str, amount_sompi: u64) -> bool { + let mut failed_ids = self.failed_deposit_ids.write().unwrap(); + let mut failed_amounts = self.failed_deposit_amounts.write().unwrap(); + + // Check if this deposit has already failed before + if failed_ids.insert(deposit_id.to_string()) { + // This is a new failure, increment pending failed deposits count and track amount + self.pending_failed_deposits.inc(); + failed_amounts.insert(deposit_id.to_string(), amount_sompi); + self.failed_deposit_funds_sompi.add(amount_sompi as i64); + true + } else { + // This deposit has already been counted as failed, no change to pending count + false + } + } + + /// Record failed withdrawal attempt with deduplication + /// Returns true if this is a new failure, false if it's a retry of an already-failed withdrawal + pub fn record_withdrawal_failed(&self, message_id: &str, amount_sompi: u64) -> bool { + let mut failed_ids = self.failed_withdrawal_ids.write().unwrap(); + let mut failed_amounts = self.failed_withdrawal_amounts.write().unwrap(); + + // Check if this message has already failed before + if failed_ids.insert(message_id.to_string()) { + // This is a new failure for this message + self.pending_failed_withdrawals.inc(); + failed_amounts.insert(message_id.to_string(), amount_sompi); + self.failed_withdrawal_funds_sompi.add(amount_sompi as i64); + true + } else { + // This withdrawal has already been counted as failed, no change to pending count + false + } + } + + /// Record confirmation failure + pub fn record_confirmation_failed(&self) { + self.confirmations_failed.inc(); + } + + /// Update pending confirmations count + pub fn update_confirmations_pending(&self, count: i64) { + self.confirmations_pending.set(count); + } + + /// Update the number of UTXOs in escrow address + pub fn update_escrow_utxo_count(&self, count: i64) { + self.escrow_utxo_count.set(count); + } + + /// Update hub anchor point information + pub fn update_hub_anchor_point(&self, tx_id: &str, outpoint_index: u64, timestamp: u64) { + // Reset all existing values first + self.hub_anchor_point_info.reset(); + + // Set new anchor point info + self.hub_anchor_point_info + .with_label_values(&[tx_id, &outpoint_index.to_string(), ×tamp.to_string()]) + .set(1.0); + } + + /// Update last withdrawal anchor point information + pub fn update_last_anchor_point(&self, tx_id: &str, outpoint_index: u64, timestamp: u64) { + // Reset all existing values first + self.last_anchor_point_info.reset(); + + // Set new last anchor point info + self.last_anchor_point_info + .with_label_values(&[tx_id, &outpoint_index.to_string(), ×tamp.to_string()]) + .set(1.0); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_metrics_creation() { + let registry = Registry::new(); + let metrics = KaspaBridgeMetrics::new(®istry).expect("Failed to create metrics"); + + // Test initial values for gauges + assert_eq!(metrics.relayer_address_funds.get(), 0); + assert_eq!(metrics.funds_escrowed.get(), 0); + assert_eq!(metrics.pending_failed_withdrawals.get(), 0); + assert_eq!(metrics.pending_failed_deposits.get(), 0); + assert_eq!(metrics.failed_withdrawal_funds_sompi.get(), 0); + assert_eq!(metrics.failed_deposit_funds_sompi.get(), 0); + assert_eq!(metrics.confirmations_pending.get(), 0); + assert_eq!(metrics.escrow_utxo_count.get(), 0); + + // Test initial values for counters + assert_eq!(metrics.deposits_processed_total.get(), 0); + assert_eq!(metrics.withdrawals_processed_total.get(), 0); + assert_eq!(metrics.total_funds_deposited.get(), 0); + assert_eq!(metrics.total_funds_withdrawn.get(), 0); + + // Histograms start with no observations, which we can verify by checking sample count + assert_eq!(metrics.deposit_amount_sompi.get_sample_count(), 0); + assert_eq!(metrics.deposit_duration_seconds.get_sample_count(), 0); + assert_eq!(metrics.withdrawal_amount_sompi.get_sample_count(), 0); + assert_eq!(metrics.withdrawal_duration_seconds.get_sample_count(), 0); + assert_eq!(metrics.withdrawal_batch_messages.get_sample_count(), 0); + } + + #[test] + fn test_metrics_operations() { + let registry = Registry::new(); + let metrics = KaspaBridgeMetrics::new(®istry).expect("Failed to create metrics"); + + // Test balance updates + metrics.update_relayer_funds(1000000); + assert_eq!(metrics.relayer_address_funds.get(), 1000000); + + metrics.update_funds_escrowed(500000); + assert_eq!(metrics.funds_escrowed.get(), 500000); + + // Test deposit processing with timing + let deposit_start = Instant::now(); + std::thread::sleep(std::time::Duration::from_millis(10)); // Small delay to ensure measurable duration + + let initial_total = metrics.total_funds_deposited.get(); + let initial_count = metrics.deposits_processed_total.get(); + metrics.record_deposit_processed("deposit_1", 100000, deposit_start); + + assert_eq!( + metrics.total_funds_deposited.get() as u64, + initial_total as u64 + 100000 + ); + assert_eq!(metrics.deposits_processed_total.get(), initial_count + 1); + assert_eq!(metrics.deposit_amount_sompi.get_sample_count(), 1); + assert_eq!(metrics.deposit_duration_seconds.get_sample_count(), 1); + + // Test withdrawal processing with timing + metrics.record_withdrawal_initiated("msg_1", 50000); + metrics.record_withdrawal_batch_size(1); + std::thread::sleep(std::time::Duration::from_millis(10)); // Small delay + + let initial_total = metrics.total_funds_withdrawn.get(); + let initial_count = metrics.withdrawals_processed_total.get(); + metrics.record_withdrawal_processed("msg_1", 50000); + + assert_eq!( + metrics.total_funds_withdrawn.get() as u64, + initial_total as u64 + 50000 + ); + assert_eq!(metrics.withdrawals_processed_total.get(), initial_count + 1); + assert_eq!(metrics.withdrawal_amount_sompi.get_sample_count(), 1); + assert_eq!(metrics.withdrawal_duration_seconds.get_sample_count(), 1); + assert_eq!(metrics.withdrawal_batch_messages.get_sample_count(), 1); + + // Test failure tracking + let is_new_failure = metrics.record_deposit_failed("deposit_2", 20000); + assert!(is_new_failure); + assert_eq!(metrics.pending_failed_deposits.get(), 1); + assert_eq!(metrics.failed_deposit_funds_sompi.get(), 20000); + + // Test duplicate failure tracking (retry of same deposit) + let is_new_failure = metrics.record_deposit_failed("deposit_2", 20000); + assert!(!is_new_failure); + assert_eq!(metrics.pending_failed_deposits.get(), 1); + assert_eq!(metrics.failed_deposit_funds_sompi.get(), 20000); + + let is_new_failure = metrics.record_withdrawal_failed("msg_2", 30000); + assert!(is_new_failure); + assert_eq!(metrics.pending_failed_withdrawals.get(), 1); + assert_eq!(metrics.failed_withdrawal_funds_sompi.get(), 30000); + + // Test failure removal on success + let deposit2_start = Instant::now(); + metrics.record_deposit_processed("deposit_2", 10000, deposit2_start); + assert_eq!(metrics.pending_failed_deposits.get(), 0); + assert_eq!(metrics.failed_deposit_funds_sompi.get(), 0); + + metrics.record_withdrawal_initiated("msg_3", 5000); + metrics.record_withdrawal_processed("msg_3", 5000); + + // Test confirmation metrics + metrics.record_confirmation_failed(); + assert_eq!(metrics.confirmations_failed.get() as u64, 1); + + metrics.update_confirmations_pending(5); + assert_eq!(metrics.confirmations_pending.get(), 5); + + // Test UTXO count + metrics.update_escrow_utxo_count(10); + assert_eq!(metrics.escrow_utxo_count.get(), 10); + + // Test histogram observations for withdrawal batches + metrics.record_withdrawal_batch_size(5); + metrics.record_withdrawal_initiated("batch_1_msg", 1000000); + + metrics.record_withdrawal_batch_size(10); + metrics.record_withdrawal_initiated("batch_2_msg1", 1000000); + metrics.record_withdrawal_initiated("batch_2_msg2", 1000000); + + metrics.record_withdrawal_batch_size(2); + metrics.record_withdrawal_initiated("batch_3_msg", 500000); + + // Verify we have 3 more batch observations (plus the 1 from earlier test) + assert_eq!(metrics.withdrawal_batch_messages.get_sample_count(), 4); + } + + #[test] + fn test_duplicate_metrics_creation() { + let registry = Registry::new(); + // Create first instance - should work fine + let metrics1 = + KaspaBridgeMetrics::new(®istry).expect("Failed to create first metrics instance"); + + // Create second instance - should handle duplicate registration gracefully + let metrics2 = + KaspaBridgeMetrics::new(®istry).expect("Failed to create second metrics instance"); + + // Test that both metrics instances are functional + metrics1.update_relayer_funds(1000000); + metrics2.update_funds_escrowed(500000); + + // Verify the values are accessible (they share the same underlying metrics) + assert_eq!(metrics1.relayer_address_funds.get(), 1000000); + assert_eq!(metrics2.funds_escrowed.get(), 500000); + } +} diff --git a/rust/main/chains/dymension-kaspa/src/relayer/metrics/mod.rs b/rust/main/chains/dymension-kaspa/src/relayer/metrics/mod.rs new file mode 100644 index 00000000000..836fd5b26d9 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/relayer/metrics/mod.rs @@ -0,0 +1,2 @@ +mod metrics; +pub use metrics::KaspaBridgeMetrics; diff --git a/rust/main/chains/dymension-kaspa/src/relayer/migration/flow.rs b/rust/main/chains/dymension-kaspa/src/relayer/migration/flow.rs new file mode 100644 index 00000000000..529aab7ae37 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/relayer/migration/flow.rs @@ -0,0 +1,271 @@ +use crate::ops::migration::MigrationFXG; +use crate::ops::payload::MessageIDs; +use crate::ops::withdraw::query_hub_anchor; +use crate::providers::KaspaProvider; +use crate::relayer::withdraw::hub_to_kaspa::{ + combine_all_bundles, create_pskt, fetch_input_utxos, finalize_txs, get_normal_bucket_feerate, + sign_relayer_fee, +}; +use dym_kas_core::pskt::{estimate_mass, PopulatedInput, PopulatedInputBuilder}; +use dym_kas_hardcode::tx::RELAYER_SWEEPING_PRIORITY_FEE; +use eyre::{eyre, Result}; +use kaspa_addresses::Address; +use kaspa_consensus_core::tx::TransactionOutput; +use kaspa_txscript::pay_to_address_script; +use kaspa_wallet_pskt::prelude::*; +use std::sync::Arc; +use tracing::info; + +/// Execute escrow key migration. +/// +/// This function: +/// 1. Queries hub for current anchor and fetches all escrow UTXOs +/// 2. Verifies hub anchor is among escrow UTXOs +/// 3. Fetches relayer UTXOs to pay transaction fees +/// 4. Builds a PSKT that transfers all escrow funds to the new escrow (relayer pays fee) +/// 5. Collects signatures from validators +/// 6. Combines signatures and broadcasts the transaction +pub async fn execute_migration( + provider: &KaspaProvider, + new_escrow_address: &Address, +) -> Result> { + let escrow = provider.escrow(); + let easy_wallet = provider.wallet(); + let validators_client = provider.validators(); + let hub_rpc = provider.hub_rpc().query(); + + // 1. Query hub for current anchor + let hub_anchor = query_hub_anchor(hub_rpc) + .await + .map_err(|e| eyre!("Query hub anchor: {}", e))?; + info!( + tx_id = %hub_anchor.transaction_id, + index = hub_anchor.index, + "Got hub anchor" + ); + + // 2. Fetch all escrow UTXOs + let escrow_addr = escrow.addr.clone(); + let escrow_utxos = easy_wallet + .rpc_with_reconnect(|api| { + let addr = escrow_addr.clone(); + async move { + api.get_utxos_by_addresses(vec![addr]) + .await + .map_err(|e| eyre!("Fetch escrow UTXOs: {}", e)) + } + }) + .await?; + + if escrow_utxos.is_empty() { + return Err(eyre!("No UTXOs found in escrow address")); + } + + // 3. Verify hub anchor is among escrow UTXOs + let hub_anchor_found = escrow_utxos.iter().any(|u| { + u.outpoint.transaction_id == hub_anchor.transaction_id + && u.outpoint.index == hub_anchor.index + }); + if !hub_anchor_found { + return Err(eyre!( + "Hub anchor {}:{} not found in escrow UTXOs - state may be stale", + hub_anchor.transaction_id, + hub_anchor.index + )); + } + + let escrow_sum: u64 = escrow_utxos.iter().map(|u| u.utxo_entry.amount).sum(); + info!( + utxo_count = escrow_utxos.len(), + escrow_sum, "Fetched escrow UTXOs for migration" + ); + + // 4. Build escrow inputs + let sig_op_count = escrow.n() as u8; + let escrow_inputs: Vec = escrow_utxos + .into_iter() + .map(|utxo| { + PopulatedInputBuilder::new( + utxo.outpoint.transaction_id, + utxo.outpoint.index, + utxo.utxo_entry.amount, + escrow.p2sh.clone(), + ) + .sig_op_count(sig_op_count) + .block_daa_score(utxo.utxo_entry.block_daa_score) + .redeem_script(Some(escrow.redeem_script.clone())) + .build() + }) + .collect(); + + // 5. Fetch relayer UTXOs for fee payment + let relayer_addr = easy_wallet.account().change_address()?; + let network_id = easy_wallet.net.network_id; + let relayer_addr_clone = relayer_addr.clone(); + let relayer_inputs = easy_wallet + .rpc_with_reconnect(|api| { + let addr = relayer_addr_clone.clone(); + async move { + fetch_input_utxos( + &api, &addr, None, // No redeem script for relayer inputs + 1, // sig_op_count for P2PK + network_id, + ) + .await + } + }) + .await + .map_err(|e| eyre!("Fetch relayer UTXOs: {}", e))?; + + if relayer_inputs.is_empty() { + return Err(eyre!( + "Relayer has no UTXOs to pay migration fee - fund relayer address first" + )); + } + + let relayer_sum: u64 = relayer_inputs + .iter() + .map(|(_, entry, _)| entry.amount) + .sum(); + info!( + relayer_utxo_count = relayer_inputs.len(), + relayer_sum, "Fetched relayer UTXOs for fee" + ); + + // 6. Build inputs: escrow + relayer + let mut inputs = escrow_inputs; + inputs.extend(relayer_inputs); + let num_inputs = inputs.len(); + + // 7. Two-pass fee estimation (storage mass = C / output_amount, so fee affects mass) + let new_escrow_script = pay_to_address_script(new_escrow_address); + let relayer_change_script = pay_to_address_script(&relayer_addr); + let empty_payload = MessageIDs::new(vec![]).to_bytes(); + + let feerate = easy_wallet + .rpc_with_reconnect(|api| async move { get_normal_bucket_feerate(&api).await }) + .await?; + + // Pass 1: estimate mass with full relayer balance as change output + let initial_outputs = vec![ + TransactionOutput::new(escrow_sum, new_escrow_script.clone()), + TransactionOutput::new(relayer_sum, relayer_change_script.clone()), + ]; + let initial_mass = estimate_mass( + inputs.clone(), + initial_outputs, + empty_payload.clone(), + easy_wallet.net.network_id, + escrow.m() as u16, + ) + .map_err(|e| eyre!("Estimate migration TX mass (pass 1): {}", e))?; + let initial_fee = (initial_mass as f64 * feerate).ceil() as u64 + RELAYER_SWEEPING_PRIORITY_FEE; + + // Pass 2: recalculate with fee-adjusted change output + let estimated_change = relayer_sum.saturating_sub(initial_fee); + if estimated_change == 0 { + return Err(eyre!( + "Relayer balance {} insufficient for fee {}", + relayer_sum, + initial_fee + )); + } + let final_outputs = vec![ + TransactionOutput::new(escrow_sum, new_escrow_script.clone()), + TransactionOutput::new(estimated_change, relayer_change_script.clone()), + ]; + let tx_mass = estimate_mass( + inputs.clone(), + final_outputs, + empty_payload.clone(), + easy_wallet.net.network_id, + escrow.m() as u16, + ) + .map_err(|e| eyre!("Estimate migration TX mass (pass 2): {}", e))?; + let tx_fee = (tx_mass as f64 * feerate).ceil() as u64 + RELAYER_SWEEPING_PRIORITY_FEE; + + if relayer_sum <= tx_fee { + return Err(eyre!( + "Relayer balance {} insufficient for fee {}", + relayer_sum, + tx_fee + )); + } + let relayer_change = relayer_sum - tx_fee; + + info!( + feerate, + tx_mass, tx_fee, relayer_change, "Calculated migration fee" + ); + + // 10. Build final outputs with correct relayer change + let num_outputs = 2; + let outputs = vec![ + TransactionOutput::new(escrow_sum, new_escrow_script), + TransactionOutput::new(relayer_change, relayer_change_script), + ]; + + // 11. Build PSKT + let pskt = create_pskt(inputs, outputs, empty_payload)?; + let bundle = Bundle::from(pskt); + let fxg = Arc::new(MigrationFXG::new(bundle)); + + info!( + num_inputs, + num_outputs, escrow_sum, "Built migration PSKT, collecting validator signatures" + ); + + // 12. Collect signatures from validators + let mut bundles = validators_client + .get_migration_sigs(fxg.clone()) + .await + .map_err(|e| eyre!("Collect migration signatures: {}", e))?; + + info!( + bundle_count = bundles.len(), + "Collected validator signatures" + ); + + // 13. Sign relayer fee inputs + let relayer_bundle = sign_relayer_fee(easy_wallet, &fxg.bundle).await?; + bundles.push(relayer_bundle); + info!("Signed relayer fee inputs"); + + // 14. Combine signatures and finalize + let combined = combine_all_bundles(bundles)?; + let finalized = finalize_txs( + combined, + &escrow, + easy_wallet.pub_key().await?, + easy_wallet.net.network_id, + )?; + + info!( + tx_count = finalized.len(), + "Finalized migration transactions" + ); + + // 15. Submit transactions + let mut tx_ids = Vec::new(); + for tx in finalized { + let tx_id = easy_wallet + .rpc_with_reconnect(|api| { + let tx = tx.clone(); + async move { + api.submit_transaction(tx, false) + .await + .map_err(|e| eyre!("Submit migration TX: {}", e)) + } + }) + .await?; + info!(tx_id = %tx_id, "Submitted migration transaction"); + tx_ids.push(tx_id); + } + + info!( + tx_count = tx_ids.len(), + "Migration complete, all transactions submitted" + ); + + Ok(tx_ids) +} diff --git a/rust/main/chains/dymension-kaspa/src/relayer/migration/mod.rs b/rust/main/chains/dymension-kaspa/src/relayer/migration/mod.rs new file mode 100644 index 00000000000..dd1fe9273ff --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/relayer/migration/mod.rs @@ -0,0 +1,3 @@ +pub mod flow; + +pub use flow::execute_migration; diff --git a/rust/main/chains/dymension-kaspa/src/relayer/mod.rs b/rust/main/chains/dymension-kaspa/src/relayer/mod.rs new file mode 100644 index 00000000000..218c0e19d88 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/relayer/mod.rs @@ -0,0 +1,14 @@ +pub mod confirm; +pub mod deposit; +pub mod metrics; +pub mod migration; +pub mod withdraw; + +// Re-export the main function for easier access +pub use migration::execute_migration; +pub use withdraw::messages::on_new_withdrawals; + +// Re-export metrics for easier access +pub use metrics::KaspaBridgeMetrics; + +pub use kaspa_bip32::secp256k1::PublicKey; diff --git a/rust/main/chains/dymension-kaspa/src/relayer/withdraw/hub_to_kaspa.rs b/rust/main/chains/dymension-kaspa/src/relayer/withdraw/hub_to_kaspa.rs new file mode 100644 index 00000000000..3ab2dbb0fbd --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/relayer/withdraw/hub_to_kaspa.rs @@ -0,0 +1,738 @@ +use super::minimum::{is_dust, is_small_value}; +use crate::ops::addr::h256_to_script_pubkey; +use crate::ops::message::parse_hyperlane_metadata; +use crate::ops::withdraw::WithdrawFXG; +use dym_kas_core::escrow::EscrowPublic; +use dym_kas_core::finality; +use dym_kas_core::pskt::{input_sighash_type, PopulatedInput, PopulatedInputBuilder}; +use dym_kas_core::wallet::EasyKaspaWallet; +use dym_kas_core::wallet::SigningResources; +use eyre::eyre; +use eyre::Result; +use hyperlane_core::HyperlaneMessage; +use hyperlane_core::U256; +use kaspa_addresses::Prefix; +use kaspa_consensus_core::config::params::Params; +use kaspa_consensus_core::network::NetworkId; +use kaspa_consensus_core::tx::UtxoEntry; +use kaspa_consensus_core::tx::{TransactionOutpoint, TransactionOutput}; +use kaspa_rpc_core::{RpcTransaction, RpcUtxosByAddressesEntry}; +use kaspa_txscript::standard::pay_to_address_script; +use kaspa_txscript::{opcodes::codes::OpData65, script_builder::ScriptBuilder}; +use kaspa_wallet_core::prelude::DynRpcApi; +use kaspa_wallet_pskt::prelude::Bundle; +use kaspa_wallet_pskt::prelude::*; +use kaspa_wallet_pskt::prelude::{Signer, PSKT}; +use std::sync::Arc; +use tracing::info; + +/// Fetches UTXOs and combines a list of all populated inputs +pub async fn fetch_input_utxos( + kaspa_rpc: &Arc, + address: &kaspa_addresses::Address, + redeem_script: Option>, + sig_op_count: u8, + network_id: NetworkId, +) -> Result> { + let utxos = get_utxo_to_spend(&address, kaspa_rpc, network_id).await?; + + // Create a vector of "populated" inputs: TransactionInput, UtxoEntry, and optional redeem_script. + Ok(utxos + .into_iter() + .map(|utxo| { + let outpoint = kaspa_consensus_core::tx::TransactionOutpoint::from(utxo.outpoint); + let entry = UtxoEntry::from(utxo.utxo_entry); + + PopulatedInputBuilder::new( + outpoint.transaction_id, + outpoint.index, + entry.amount, + entry.script_public_key, + ) + .sig_op_count(sig_op_count) + .block_daa_score(entry.block_daa_score) + .redeem_script(redeem_script.clone()) + .build() + }) + .collect()) +} + +pub async fn get_normal_bucket_feerate(kaspa_rpc: &Arc) -> Result { + let feerate = kaspa_rpc.get_fee_estimate().await?; + // Due to the documentation: + // > The first value of this vector is guaranteed to exist + Ok(feerate.normal_buckets.first().unwrap().feerate) +} + +/// Builds a single withdrawal PSKT. +/// +/// Example: +/// +/// The user sends 10 KAS. Multisig addr has 100 KAS. Due to the Hyperlane approach, the user +/// needs to get the whole amount they transferred, so they must get 10 KAS. However, there is +/// the transaction fee, which must be covered by the relayer. Let's say it's 1 KAS. +/// +/// For that, we fetch ALL UTXOs from the multisig address and them as inputs. This will also +/// work as automatic sweeping. The change is returned as an output which is also used as +/// a new anchor. +/// +/// The relayer fee is tricky. Relayer should provide some UTXOs to cover the fee. However, +/// each input increases the transaction fee, so we can't compute the concrete fee beforehand. +/// +/// We have two options: +/// +/// --- 1 --- +/// 1. Calculate the tx fee without relayer's UTXOs. +/// 2. Get the UTXOs that cover the fee. +/// 3. Add them as inputs. +/// 4. Calculate the fee again. +/// 5. Add additional UTXOs if needed and repeat 2-4. +/// +/// Pros: As low fee as possible. +/// Cons: The relayer account is fragmented (sweeping is needed); complex flow. +/// +/// --- 2 --- (Implemented) +/// Get ALL UTXOs and also use them as inputs. The change is returned as output. +/// +/// Pros: Simple to handle. +/// Cons: Potentially bigger fee because of the increased number of inputs. However, it's in +/// relayer's interest to pay min fees and thus keep its account with as few UTXOs as possible. +/// +/// CONTRACT: +/// Escrow change is always the last output. +pub fn build_withdrawal_pskt( + inputs: Vec, + mut outputs: Vec, + payload: Vec, + escrow: &EscrowPublic, + relayer_addr: &kaspa_addresses::Address, + min_deposit_sompi: U256, + feerate: f64, + tx_mass: u64, +) -> Result> { + ////////////////// + // Balances // + ////////////////// + + // TODO: Confirm if we can have an overflow here + // 1 KAS = 10^8 (dust denom). + // 10^19 < 2^26 < 10^20 + // This means the multisig must hold at most 10^19 (dust denom) => 10^11 KAS + // Given that 1 KAS = $10^-2, the max balance is $1B, but this might change + // in case of hyperinflation + + let (escrow_balance, relayer_balance) = + inputs + .iter() + .fold((0, 0), |mut acc, (_input, entry, redeem_script)| { + if redeem_script.is_none() { + // relayer has no redeem script + acc.1 += entry.amount; + } else { + // escrow has redeem script + acc.0 += entry.amount; + } + acc + }); + + let withdrawal_balance: u64 = outputs.iter().map(|w| w.value).sum(); + + if escrow_balance < withdrawal_balance { + return Err(eyre::eyre!( + "Insufficient funds in escrow: {} < {}", + escrow_balance, + withdrawal_balance + )); + } + + if is_small_value(escrow_balance, min_deposit_sompi) { + return Err(eyre::eyre!( + "Escrow balance is low: balance: {}, recommended: {}. Please deposit to escrow address to avoid high mass txs.", + escrow_balance, + min_deposit_sompi + )); + } + ////////////////// + // Fee // + ////////////////// + + // Apply TX mass multiplier and feerate + let tx_fee = (tx_mass as f64 * feerate).round() as u64; + + if relayer_balance < tx_fee { + return Err(eyre::eyre!( + "Insufficient relayer funds to cover tx fee: {} < {}", + relayer_balance, + tx_fee + )); + } + + if is_small_value(relayer_balance, min_deposit_sompi) { + return Err(eyre::eyre!( + "Relayer balance is low: balance: {}, recommended: {}. Please deposit to relayer address to avoid high mass txs.", + relayer_balance, + min_deposit_sompi + )); + } + + //////////////// + // Change // + //////////////// + + let relayer_change_amt = relayer_balance - tx_fee; + // check if relayer_change is dust + let relayer_change = TransactionOutput { + value: relayer_change_amt, + script_public_key: pay_to_address_script(relayer_addr), + }; + if is_dust(&relayer_change, min_deposit_sompi) { + return Err(eyre::eyre!( + "Insufficient relayer funds to cover tx fee: {} < {}, only leaves dust {}", + relayer_balance, + tx_fee, + relayer_change_amt + )); + } + + // escrow_balance - withdrawal_balance > 0 as checked above + let escrow_change_amt = escrow_balance - withdrawal_balance; + // check if relayer_change is dust + let escrow_change = TransactionOutput { + value: escrow_change_amt, + script_public_key: escrow.p2sh.clone(), + }; + if is_dust(&escrow_change, min_deposit_sompi) { + return Err(eyre::eyre!( + "Insufficient escrow funds to cover withdrawals and avoid dust change: {} < {}, only leaves dust {}, should never happen due to seed", + escrow_balance, + withdrawal_balance, + escrow_change_amt + )); + } + + // Escrow change (new anchor) is always the last element + outputs.extend(vec![relayer_change, escrow_change]); + + let inputs_num = inputs.len(); + let outputs_num = outputs.len(); + let payload_len = payload.len(); + + let pskt = create_pskt(inputs, outputs, payload)?; + + info!( + inputs_count = inputs_num, + outputs_count = outputs_num, + payload_len = payload_len, + tx_mass = tx_mass, + feerate = feerate, + tx_fee = tx_fee, + "kaspa relayer: prepared withdrawal transaction" + ); + + Ok(pskt) +} + +/// Create a PSKT from inputs and outputs with payload. +/// Used by both withdrawal and migration flows. +pub fn create_pskt( + inputs: Vec, + outputs: Vec, + payload: Vec, +) -> Result> { + let mut pskt = PSKT::::default() + .set_version(Version::One) + .constructor(); + + // Add inputs + for (input, entry, redeem_script) in inputs.into_iter() { + let mut b = InputBuilder::default(); + + b.utxo_entry(entry) + .previous_outpoint(input.previous_outpoint) + .sig_op_count(input.sig_op_count) + .sighash_type(input_sighash_type()); + + if let Some(script) = redeem_script { + b.redeem_script(script); + } + + pskt = pskt.input( + b.build() + .map_err(|e| eyre::eyre!("Build pskt input: {}", e))?, + ); + } + + // Add outputs + for output in outputs.into_iter() { + let b = OutputBuilder::default() + .amount(output.value) + .script_public_key(output.script_public_key) + .build() + .map_err(|e| eyre::eyre!("Build pskt output: {}", e))?; + + pskt = pskt.output(b); + } + + let pskt = pskt.no_more_inputs().no_more_outputs(); + Ok(pskt.payload(Some(payload))?.signer()) +} + +/// Return outputs generated based on the provided messages. Filter out messages +/// with dust amount. +pub fn get_outputs_from_msgs( + messages: Vec, + prefix: Prefix, + min_withdrawal_sompi: U256, +) -> (Vec, Vec) { + let mut hl_msgs: Vec = Vec::new(); + let mut outputs: Vec = Vec::new(); + for m in messages { + let tm = match parse_hyperlane_metadata(&m) { + Ok(tm) => tm, + Err(e) => { + info!( + error = %e, + "kaspa relayer: skipped message, failed to parse TokenMessage from HyperlaneMessage body" + ); + continue; + } + }; + + let recipient = h256_to_script_pubkey(tm.recipient(), prefix); + + let o = TransactionOutput::new(tm.amount().as_u64(), recipient); + + if is_dust(&o, min_withdrawal_sompi) { + info!( + amount = o.value, + message_id = ?m.id(), + "kaspa relayer: skipped withdrawal, amount below minimum withdrawal threshold" + ); + continue; + } + + outputs.push(o); + hl_msgs.push(m); + } + (hl_msgs, outputs) +} + +async fn get_utxo_to_spend( + addr: &kaspa_addresses::Address, + kaspa_rpc: &Arc, + network_id: NetworkId, +) -> Result> { + let mut utxos = kaspa_rpc + .get_utxos_by_addresses(vec![addr.clone()]) + .await + .map_err(|e| eyre::eyre!("Get escrow UTXOs: {}", e))?; + + let b = kaspa_rpc + .get_block_dag_info() + .await + .map_err(|e| eyre::eyre!("Get block DAG info: {}", e))?; + + // Descending order – older UTXOs first + utxos.sort_by_key(|u| std::cmp::Reverse(u.utxo_entry.block_daa_score)); + utxos.retain(|u| { + finality::is_mature( + u.utxo_entry.block_daa_score, + b.virtual_daa_score, + network_id, + ) + }); + + Ok(utxos) +} + +pub(crate) fn extract_current_anchor( + current_anchor: TransactionOutpoint, + mut escrow_inputs: Vec, +) -> Result<(PopulatedInput, Vec)> { + let anchor_index = escrow_inputs + .iter() + .position(|(input, _, _)| input.previous_outpoint == current_anchor) + .ok_or(eyre::eyre!( + "Current anchor not found in escrow UTXO set: {current_anchor:?}" + ))?; // Should always be found + + let anchor_input = escrow_inputs.swap_remove(anchor_index); + + Ok((anchor_input, escrow_inputs)) +} + +pub async fn combine_bundles_with_fee( + bundles_validators: Vec, + fxg: &WithdrawFXG, + multisig_threshold: usize, + escrow: &EscrowPublic, + easy_wallet: &EasyKaspaWallet, +) -> Result> { + info!("kaspa relayer: received withdrawal FXG, gathering validator signatures"); + + let mut bundles_validators = bundles_validators; + + let all_bundles = { + if bundles_validators.len() < multisig_threshold { + return Err(eyre!( + "Not enough validator bundles, required: {}, got: {}", + multisig_threshold, + bundles_validators.len() + )); + } + + let bundle_relayer = sign_relayer_fee(easy_wallet, &fxg.bundle).await?; + info!("kaspa relayer: signed relayer fee bundle"); + bundles_validators.push(bundle_relayer); + bundles_validators + }; + let txs_signed = combine_all_bundles(all_bundles)?; + let finalized = finalize_txs( + txs_signed, + escrow, + easy_wallet.pub_key().await?, + easy_wallet.net.network_id, + )?; + Ok(finalized) +} + +/// Sign relayer fee inputs in a PSKT bundle. +/// Used by both withdrawal and migration flows. +pub async fn sign_relayer_fee(easy_wallet: &EasyKaspaWallet, bundle: &Bundle) -> Result { + let resources = easy_wallet.signing_resources().await?; + let mut signed = Vec::new(); + for pskt in bundle.iter() { + let pskt = PSKT::::from(pskt.clone()); + signed.push(sign_pay_fee(pskt, &resources).await?); + } + Ok(Bundle::from(signed)) +} + +/// accepts bundle of signer +pub fn combine_all_bundles(bundles: Vec) -> Result>> { + // each bundle is from a different actor (validator or relayer), and is a vector of pskt + // therefore index i of each vector corresponds to the same TX i + + // make a list of lists, each top level element is a vector of pskt from a different actor + let actor_pskts = bundles + .iter() + .map(|b| { + b.iter() + .map(|inner| PSKT::::from(inner.clone())) + .collect::>>() + }) + .collect::>>>(); + + let n_txs = actor_pskts.first().unwrap().len(); + + // need to walk across each tx, and for each tx walk across each actor, and combine all for that tx, so all the sigs + // for each tx are grouped together in one vector + let mut tx_sigs: Vec>> = Vec::new(); + for tx_i in 0..n_txs { + let mut all_sigs_for_tx = Vec::new(); + for tx_sigs_from_actor_j in actor_pskts.iter() { + all_sigs_for_tx.push(tx_sigs_from_actor_j[tx_i].clone()); + } + tx_sigs.push(all_sigs_for_tx); + } + + // walk across each tx and combine all the sigs for that tx into one combiner + let mut ret = Vec::new(); + for (pskt_idx, all_actor_sigs_for_tx) in tx_sigs.iter().enumerate() { + let pskt = all_actor_sigs_for_tx.first().unwrap().clone(); + let tx_id = pskt.calculate_id(); + let mut combiner = pskt.combiner(); + + for (_sig_idx, tx_sig) in all_actor_sigs_for_tx.iter().skip(1).enumerate() { + combiner = (combiner + tx_sig.clone())?; + } + + info!( + pskt_idx = pskt_idx, + tx_id = %tx_id, + signatures_combined = all_actor_sigs_for_tx.len(), + "kaspa relayer: combined PSKT signatures" + ); + + ret.push(combiner); + } + Ok(ret) +} + +/// Finalize transactions by applying signatures. +/// Used by both withdrawal and migration flows. +pub fn finalize_txs( + txs_sigs: Vec>, + escrow: &EscrowPublic, + relayer_pub_key: kaspa_bip32::secp256k1::PublicKey, + network_id: NetworkId, +) -> Result> { + txs_sigs + .into_iter() + .map(|tx| finalize_pskt(tx, escrow, &relayer_pub_key, network_id)) + .collect() +} + +// used by multisig demo AND real code +pub fn finalize_pskt( + c: PSKT, + escrow: &EscrowPublic, + relayer_pub_key: &kaspa_bip32::secp256k1::PublicKey, + network_id: NetworkId, +) -> Result { + let finalized_pskt = c + .finalizer() + .finalize_sync(|inner: &Inner| -> Result>, String> { + Ok(inner + .inputs + .iter() + .map(|input| -> Vec { + match &input.redeem_script { + None => { + // relayer UTXO + + let sig = input + .partial_sigs + .iter() + .find(|(pk, _sig)| pk == &relayer_pub_key) + .unwrap() + .1 + .into_bytes(); + + std::iter::once(65u8) + .chain(sig) + .chain([input.sighash_type.to_u8()]) + .collect() + } + Some(redeem_script) => { + if redeem_script != &escrow.redeem_script { + panic!("Redeem script mismatch"); + } + // escrow UTXO + + // Return the full script + + // ORIGINAL COMMENT: todo actually required count can be retrieved from redeem_script, sigs can be taken from partial sigs according to required count + // ORIGINAL COMMENT: considering xpubs sorted order + let available_pubs = escrow + .pubs + .iter() + .filter(|kp| input.partial_sigs.contains_key(kp)) + .collect::>(); + + // For each escrow pubkey return and then concat these triples + let sigs: Vec<_> = available_pubs + .iter() + .take(escrow.m()) + .flat_map(|kp| { + let sig = input.partial_sigs.get(kp).unwrap().into_bytes(); + std::iter::once(OpData65) + .chain(sig) + .chain([input.sighash_type.to_u8()]) + }) + .collect(); + + // Then add the multisig redeem script to the end + sigs.into_iter() + .chain( + ScriptBuilder::new() + .add_data(input.redeem_script.as_ref().unwrap().as_slice()) + .unwrap() + .drain() + .iter() + .cloned(), + ) + .collect() + } + } + }) + .collect()) + }) + .unwrap(); + + let params = Params::from(network_id); + + let tx = finalized_pskt + .extractor() + .unwrap() + .extract_tx(¶ms) + .map_err(|e: ExtractError| eyre::eyre!("Extract kaspa tx: {:?}", e))?; + + let rpc_tx: RpcTransaction = (&tx.tx).into(); + Ok(rpc_tx) +} + +pub async fn sign_pay_fee(pskt: PSKT, r: &SigningResources) -> Result> { + dym_kas_core::pskt::sign_pskt( + pskt, + &r.key_pair, + Some(r.key_source.clone()), + None:: bool>, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use bytes::Bytes; + + use crate::ops::withdraw::WithdrawFXG; + use dym_kas_core::pskt::{estimate_mass, is_valid_sighash_type}; + use hyperlane_core::H256; + use kaspa_consensus_core::network::NetworkType::Devnet; + use kaspa_consensus_core::tx::ScriptPublicKey; + use std::str::FromStr; + + #[test] + fn test_kaspa_address_conversion() { + // Input is an address which is going to receive funds + let input = "kaspatest:qzgq29y4cwrchsre26tvyezk2lsyhm3k23ch9tv4nrpvyq7lyhs3sux404nt8"; + // First, we need to get its bytes representation + let input_kaspa = kaspa_addresses::Address::constructor(input); + // Input hex is what will be used in MsgRemoteTransfer + let input_hex = hex::encode(input_kaspa.payload); + // In x/warp, the input hex is converted to a byte vector + let output_bytes = hex::decode(input_hex).unwrap(); + // Put these bytes to a 32-byte array + let output_bytes_32: [u8; 32] = output_bytes.try_into().unwrap(); + // In the agent, the 32-byte array is converted to H256 + let output_h256 = H256::from_slice(&output_bytes_32); + // Construct Kaspa address + let output_kaspa = kaspa_addresses::Address::new( + kaspa_addresses::Prefix::Testnet, + kaspa_addresses::Version::PubKey, + output_h256.as_bytes(), + ); + + let output = output_kaspa.address_to_string(); + + assert_eq!(true, kaspa_addresses::Address::validate(output.as_str())); + assert_eq!(input, output.as_str()); + } + + #[test] + fn test_pskt_intput_sighash_type() -> Result<()> { + // Create PSKT signer with input + let input = kaspa_wallet_pskt::input::InputBuilder::default() + .sighash_type(input_sighash_type()) + .build() + .map_err(|e| eyre::eyre!("Failed to build input: {}", e))?; + + let pskt = PSKT::::default() + .constructor() + .input(input) + .no_more_inputs() + .no_more_outputs() + .signer(); + + // Verify sighash type + let sighash_type_1 = pskt.inputs.first().unwrap().sighash_type; + assert!(is_valid_sighash_type(sighash_type_1)); + + // Create WithdrawFXG + let bundle = Bundle::from(pskt); + + let withdraw_fxg = WithdrawFXG::new( + bundle, + vec![], + vec![ + TransactionOutpoint::default(), + TransactionOutpoint::default(), + ], + ); + + // Convert WithdrawFXG to Bytes + let serialized_bytes = Bytes::try_from(&withdraw_fxg) + .map_err(|e| eyre::eyre!("Failed to serialize WithdrawFXG to Bytes: {}", e))?; + + // Convert Bytes back to WithdrawFXG + let deserialized_withdraw_fxg = WithdrawFXG::try_from(serialized_bytes) + .map_err(|e| eyre::eyre!("Failed to deserialize WithdrawFXG from Bytes: {}", e))?; + + // Verify sighash type + let sighash_type_2 = deserialized_withdraw_fxg + .bundle + .iter() + .next() + .unwrap() + .inputs + .first() + .unwrap() + .sighash_type; + + assert!(is_valid_sighash_type(sighash_type_2)); + + Ok(()) + } + + #[test] + #[ignore] + fn test_estimate_fee_with_different_inputs() -> Result<()> { + // Skip this test. + // It can be used to play with the TX mass estimation. + + const MIN_OUTPUTS: u32 = 2; + const MIN_INPUTS: u32 = 2; + + const MAX_OUTPUTS: u32 = 15; + const MAX_INPUTS: u32 = 6; + + let spk = ScriptPublicKey::from_str( + "20bcff7587f574e249b549329291239682d6d3481ccbc5997c79770a607ab3ec98ac", + )?; + + let payload: Vec = + hex::decode("0a20f3b12fe3f4a43a7deb33be5f5a7a766ce22f76e9d8d6e1f77338e2f233db8e20") + .expect("Invalid hex payload"); + + let network_id = NetworkId::new(Devnet); + + let mut res: Vec> = Vec::new(); + + for input_count in MIN_INPUTS..=MAX_INPUTS { + let inputs: Vec = (0..input_count) + .map(|_i| { + let tx_id = "81b79b11b546e3769e91bebced62fc0ff7ce665258201fd501ea3c60d735ec7d".to_string().parse().unwrap(); + let sig_script = "41b31e2b858c19baef26cb352664b493cb9f7f3b3f94217a7ca857f740db5eb4cb1004c9a278449477e23fb1b09f141d1a939b7f8435c578af17549cd2ff79b7b4814129ab65d772387cfa300314597b0ab11d9900ffcbe2f072568cbb6cd76bdba057242e365d951d2f87ab98bb332527d6df07cf207164ab1be5a643a6d7edb9fde6814171641e6b8ce10fcf5b41e962cf3665020a5e295ddf35a6a07e791d619aa1580d5f43d37c7dfa3c115b648c25b92ad17868e61bd01ad782b04ead5177ce5f40958141bc5b0b3f6e3bbb468d8710aba0cb0c04e046371f1b0972aacf48121e9d0704233e7596685e9a25464b85857562427f4982ba6e84c3258e356d9bff67478bb4df81415177d6b9b39414dd75374089d98c38b145c332b7a960cf2cabeb9cdd397c090d7e81bc28619f0491b1a57483013adca9badff86df32d31598fee28fd4699ed15814123b9ae551e0201f106291923d294715800ffb47a7dc158f738e341f87f0656805b1d27f931bc1653d366ef7bf55f1a0be7c8c25ed510dbec3297fae0b51c96d4814d0b0156205461e2ab2584bc80435c2a3f51c4cf12285992b5e4fdec57f1f8b506134a90872018a9fcc6059c1995c70b8f31b2256ac3d4aeca5dffa331fb941a8c5d4bffdd7620d7a78be7d152498cfb9fb8a89b60723f011435303499e0de7c1bcbf88f87d1b920f02a8dc60f124b34e9a8800fb25cf25ac01a3bdcf5a6ea21d2e2569a173dd9b2208586f127129710cdac6ca1d86be1869bd8a8746db9a2339fde71278dff7fb4692014d0d6d828c2f3e5ce908978622c5677c1fc53372346a9cff60d1140c54b5e5e209035bddc82d62454b2d425e205533363d09dc5d9c0d0f74c1f937c2d211c15a120e4b95346367e49178c8571e8a649584981d8bd6f920c648e37bbe24f055baf9c58ae".as_bytes().to_vec(); + + // Create the populated input, then modify the signature script + let mut input = PopulatedInputBuilder::new(tx_id, 0, 4_000_000_000, spk.clone()) + .sig_op_count(8) + .build(); + + // Update the signature script in the TransactionInput + input.0.signature_script = sig_script; + input + }) + .collect(); + + let mut res_inner: Vec = Vec::new(); + for output_count in MIN_OUTPUTS..=MAX_OUTPUTS { + let outputs: Vec = (0..output_count) + .map(|_| TransactionOutput { + value: 4_000_000_000, + script_public_key: spk.clone(), + }) + .collect(); + + // Call the function under test + let v = estimate_mass( + inputs.clone(), + outputs.clone(), + payload.clone(), + network_id, + 8, + )?; + + res_inner.push(v); + + print!("{v},"); + } + + res.push(res_inner); + println!(); + } + + Ok(()) + } +} diff --git a/rust/main/chains/dymension-kaspa/src/relayer/withdraw/messages.rs b/rust/main/chains/dymension-kaspa/src/relayer/withdraw/messages.rs new file mode 100644 index 00000000000..47bc078ac47 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/relayer/withdraw/messages.rs @@ -0,0 +1,388 @@ +use super::hub_to_kaspa::{ + build_withdrawal_pskt, extract_current_anchor, fetch_input_utxos, get_normal_bucket_feerate, + get_outputs_from_msgs, +}; +use crate::ops::payload::MessageIDs; +use crate::ops::withdraw::{filter_pending_withdrawals, WithdrawFXG}; +use crate::relayer::withdraw::sweep::{create_inputs_from_sweeping_bundle, create_sweeping_bundle}; +use dym_kas_core::consts::RELAYER_SIG_OP_COUNT; +use dym_kas_core::escrow::EscrowPublic; +use dym_kas_core::pskt::{estimate_mass, PopulatedInput}; +use dym_kas_core::wallet::EasyKaspaWallet; +use dym_kas_hardcode::tx::{MAX_MASS_MARGIN, SWEEPING_THRESHOLD}; +use eyre::Result; +use hyperlane_core::HyperlaneMessage; +use hyperlane_core::U256; +use hyperlane_cosmos::{native::ModuleQueryClient, CosmosProvider}; +use kaspa_consensus_core::tx::TransactionOutpoint; +use kaspa_wallet_pskt::bundle::Bundle; +use tracing::{error, info}; + +/// Adjusts outputs and messages to fit within available funds from swept inputs +/// Returns (adjusted_outputs, adjusted_messages) +fn adjust_outputs_for_available_funds_swept( + inputs: &[PopulatedInput], + mut outputs: Vec, + mut messages: Vec, +) -> Result<( + Vec, + Vec, +)> { + // Calculate total available funds from escrow inputs only (excluding relayer inputs) + let total_available: u64 = inputs + .iter() + .filter(|(_, _, redeem_script)| redeem_script.is_some()) // escrow inputs have redeem script + .map(|(_, entry, _)| entry.amount) + .sum(); + + let mut total_requested: u64 = outputs.iter().map(|o| o.value).sum(); + + if total_requested <= total_available { + return Ok((outputs, messages)); + } + + // Remove outputs until total fits within available funds + while total_requested > total_available && !outputs.is_empty() { + let removed_output = outputs.pop(); + messages.pop(); + if let Some(out) = removed_output { + total_requested -= out.value; + } + } + + if outputs.is_empty() { + return Err(eyre::eyre!( + "Cannot process any withdrawals - available funds ({} sompi) insufficient even for smallest withdrawal", + total_available + )); + } + + Ok((outputs, messages)) +} + +/// Adjusts outputs and messages to fit within transaction mass limits +/// Returns (adjusted_outputs, adjusted_messages, final_mass) +fn adjust_outputs_for_mass_limit( + inputs: Vec, + mut outputs: Vec, + mut messages: Vec, + network_id: kaspa_consensus_core::network::NetworkId, + escrow_m: u16, +) -> Result<( + Vec, + Vec, + u64, +)> { + // Use MAX_MASS_MARGIN as safety margin for mass limit + // This ensures we stay under the limit even with estimation variance + let max_allowed_mass = + (kaspa_wallet_core::tx::MAXIMUM_STANDARD_TRANSACTION_MASS as f64 * MAX_MASS_MARGIN) as u64; + loop { + let tx_mass = estimate_mass( + inputs.clone(), + outputs.clone(), + MessageIDs::from(&messages).to_bytes(), + network_id, + escrow_m, + ) + .map_err(|e| eyre::eyre!("Estimate TX mass: {e}"))?; + if tx_mass <= max_allowed_mass { + return Ok((outputs, messages, tx_mass)); + } + if outputs.is_empty() { + return Err(eyre::eyre!( + "Cannot process any withdrawals - even a single withdrawal exceeds mass limit" + )); + } + outputs.pop(); + messages.pop(); + } +} + +/// Processes given messages and returns WithdrawFXG and the very first outpoint +/// (the one preceding all the given transfers; it should be used during process indication). +pub async fn on_new_withdrawals( + messages: Vec, + relayer: EasyKaspaWallet, + cosmos: CosmosProvider, + escrow_public: EscrowPublic, + min_withdrawal_sompi: U256, + tx_fee_multiplier: f64, + max_sweep_inputs: Option, + max_sweep_bundle_bytes: usize, +) -> Result> { + let (current_anchor, pending_msgs) = filter_pending_withdrawals(messages, cosmos.query()) + .await + .map_err(|e| eyre::eyre!("Get pending withdrawals: {}", e))?; + + info!("kaspa relayer: filtered pending withdrawals"); + + build_withdrawal_fxg( + pending_msgs, + current_anchor, + relayer, + escrow_public, + min_withdrawal_sompi, + tx_fee_multiplier, + max_sweep_inputs, + max_sweep_bundle_bytes, + ) + .await +} + +pub async fn build_withdrawal_fxg( + pending_msgs: Vec, + current_anchor: TransactionOutpoint, + relayer: EasyKaspaWallet, + escrow_public: EscrowPublic, + min_withdrawal_sompi: U256, + tx_fee_multiplier: f64, + max_sweep_inputs: Option, + max_sweep_bundle_bytes: usize, +) -> Result> { + // Filter out dust messages and create Kaspa outputs for the rest + let (valid_msgs, outputs) = get_outputs_from_msgs( + pending_msgs, + relayer.net.address_prefix, + min_withdrawal_sompi, + ); + + if outputs.is_empty() { + info!("kaspa relayer: no valid pending withdrawals found, all in batch already processed and confirmed on hub"); + return Ok(None); // nothing to process + } + + // Get all the UTXOs for the escrow and the relayer + let escrow_addr = escrow_public.addr.clone(); + let escrow_redeem = escrow_public.redeem_script.clone(); + let escrow_n = escrow_public.n() as u8; + let network_id = relayer.net.network_id; + let escrow_inputs = relayer + .rpc_with_reconnect(|api| { + let addr = escrow_addr.clone(); + let redeem = escrow_redeem.clone(); + async move { fetch_input_utxos(&api, &addr, Some(redeem), escrow_n, network_id).await } + }) + .await + .map_err(|e| eyre::eyre!("Fetch escrow UTXOs: {}", e))?; + + // Get relayer change address for the withdrawal PSKT change output + let relayer_address = relayer.account().change_address()?; + + // Fetch relayer UTXOs from change address + let relayer_addr_clone = relayer_address.clone(); + let relayer_inputs = + relayer + .rpc_with_reconnect(|api| { + let addr = relayer_addr_clone.clone(); + async move { + fetch_input_utxos(&api, &addr, None, RELAYER_SIG_OP_COUNT, network_id).await + } + }) + .await + .map_err(|e| eyre::eyre!("Fetch relayer change address UTXOs: {}", e))?; + + // Early validation of relayer funds available (otherwise it will panic later during PSKT building) + if relayer_inputs.is_empty() { + error!( + "Relayer has no UTXOs available. Cannot process withdrawals without relayer funds to pay transaction fees. \ + Please fund relayer address. All withdrawal operations will be marked as failed and retried later." + ); + return Ok(None); + } + + let (sweeping_bundle, inputs, adjusted_outputs, adjusted_msgs) = if escrow_inputs.len() + > SWEEPING_THRESHOLD + { + // Sweep + + // Extract the current anchor from the escrow UTXO set. + // All (and only) non-anchor UTXOs will be swept. + // Anchor UTXO will be used as the input for withdrawal PSKT. + let (anchor_input, escrow_inputs_to_sweep) = + extract_current_anchor(current_anchor, escrow_inputs) + .map_err(|e| eyre::eyre!("Extract current anchor: {}", e))?; + + let to_sweep_num = escrow_inputs_to_sweep.len(); + + // Calculate total withdrawal amount needed + let total_withdrawal_amount: u64 = outputs.iter().map(|o| o.value).sum(); + + // Get anchor amount (not swept but available for withdrawals) + let anchor_amount = anchor_input.1.amount; // anchor_input is (TransactionInput, UtxoEntry, Option>) + + let sweeping_bundle = create_sweeping_bundle( + &relayer, + &escrow_public, + escrow_inputs_to_sweep, + relayer_inputs, + total_withdrawal_amount, + anchor_amount, + max_sweep_inputs, + max_sweep_bundle_bytes, + ) + .await + .map_err(|e| eyre::eyre!("Create sweeping bundle: {}", e))?; + + // Use sweeping bundle's outputs to create inputs for withdrawal PSKT. + // Outputs contain escrow and relayer change. + let swept_outputs = create_inputs_from_sweeping_bundle(&sweeping_bundle, &escrow_public) + .map_err(|e| eyre::eyre!("Create input from sweeping bundle: {}", e))?; + + info!( + pskt_count = sweeping_bundle.0.len(), + escrow_inputs_swept = to_sweep_num, + "kaspa relayer: constructed sweeping bundle" + ); + + let mut inputs = Vec::with_capacity(swept_outputs.len() + 1); + inputs.push(anchor_input); + inputs.extend(swept_outputs); // use the swept outputs for the withdrawal inputs + + // Adjust outputs to fit within swept funds before checking mass limits + let (adjusted_outputs, adjusted_msgs) = + adjust_outputs_for_available_funds_swept(&inputs, outputs, valid_msgs)?; + + ( + Some(sweeping_bundle), + inputs, + adjusted_outputs, + adjusted_msgs, + ) + } else { + info!("kaspa relayer: no sweep needed, continuing to withdrawal"); + + let mut inputs = Vec::with_capacity(escrow_inputs.len() + relayer_inputs.len()); + inputs.extend(escrow_inputs); + inputs.extend(relayer_inputs); + + (None, inputs, outputs, valid_msgs) + }; + + // Estimate mass and remove outputs if necessary + let (final_outputs, final_msgs, tx_mass) = adjust_outputs_for_mass_limit( + inputs.clone(), + adjusted_outputs, + adjusted_msgs, + relayer.net.network_id, + escrow_public.m() as u16, + )?; + + let payload = MessageIDs::from(&final_msgs).to_bytes(); + + let feerate = relayer + .rpc_with_reconnect(|api| async move { get_normal_bucket_feerate(&api).await }) + .await + .map_err(|e| eyre::eyre!("Get normal bucket feerate: {e}"))?; + + let pskt = build_withdrawal_pskt( + inputs, + final_outputs.clone(), + payload, + &escrow_public, + &relayer_address, + min_withdrawal_sompi, + feerate * tx_fee_multiplier, + tx_mass, + ) + .map_err(|e| eyre::eyre!("Build withdrawal PSKT: {}", e))?; + + info!( + withdrawal_count = final_outputs.len(), + "kaspa relayer: built withdrawal PSKT" + ); + + // Contract: the last output of the withdrawal PSKT is the new anchor + let new_anchor = TransactionOutpoint::new(pskt.calculate_id(), (pskt.outputs.len() - 1) as u32); + + let messages = { + // Create a list of (list of) messages for teach TX + // The first N (if any) elements are empty since sweeping PSKTs don't have any HL messages. + // The last element is the withdrawal PSKT, so it should have all the HL messages. + let sweep_count = sweeping_bundle.as_ref().map_or(0, |b| b.0.len()); + let mut messages = Vec::with_capacity(sweep_count + final_msgs.len()); + messages.extend(vec![Vec::new(); sweep_count]); + messages.push(final_msgs); + messages + }; + + // Create a final bundle. It has the following structure: + // 1. Sweeping bundle (if any) – might contain multiple PSKTs. It sweeps all non-anchor UTXOs. + // 2. One withdrawal PSKT – contains the output UTXO from the sweeping bundle and the anchor input + let bundle = match sweeping_bundle { + Some(mut bundle) => { + bundle.add_pskt(pskt); + bundle + } + None => Bundle::from(pskt), + }; + + Ok(Some(WithdrawFXG::new( + bundle, + messages, + vec![current_anchor, new_anchor], + ))) +} + +#[cfg(test)] +mod tests { + use base64::{engine::general_purpose::STANDARD, Engine as _}; + use hyperlane_core::Decode; + use hyperlane_warp_route::TokenMessage; + + use std::io::Cursor; + + #[test] + fn test_transaction_id_conversion() { + // Test with valid 32-byte transaction ID + let b64 = "Xhz2eE568YCGdKJS60F9j6ADE1GQ3UFHyvmNhGOn5zo="; + let bytes = STANDARD.decode(b64).unwrap(); + let bz = bytes.as_slice().try_into().unwrap(); + let kaspa_tx_id = kaspa_hashes::Hash::from_bytes(bz); + println!("kaspa_tx_id: {:?}", kaspa_tx_id); + } + + #[test] + fn test_decode_token_message() { + let bytes_a: Vec> = vec![ + vec![ + 223, 45, 201, 23, 84, 12, 115, 128, 168, 110, 81, 250, 212, 184, 225, 16, 26, 14, + 250, 39, 71, 58, 92, 169, 185, 124, 235, 132, 108, 196, 2, 171, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 49, 45, 2, + ], + vec![ + 188, 255, 117, 135, 245, 116, 226, 73, 181, 73, 50, 146, 145, 35, 150, 130, 214, + 211, 72, 28, 203, 197, 153, 124, 121, 119, 10, 96, 122, 179, 236, 152, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 53, + 148, 0, + ], + vec![ + 188, 255, 117, 135, 245, 116, 226, 73, 181, 73, 50, 146, 145, 35, 150, 130, 214, + 211, 72, 28, 203, 197, 153, 124, 121, 119, 10, 96, 122, 179, 236, 152, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 154, + 202, 0, + ], + vec![ + 188, 255, 117, 135, 245, 116, 226, 73, 181, 73, 50, 146, 145, 35, 150, 130, 214, + 211, 72, 28, 203, 197, 153, 124, 121, 119, 10, 96, 122, 179, 236, 152, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 84, 11, + 228, 0, + ], + vec![ + 188, 255, 117, 135, 245, 116, 226, 73, 181, 73, 50, 146, 145, 35, 150, 130, 214, + 211, 72, 28, 203, 197, 153, 124, 121, 119, 10, 96, 122, 179, 236, 152, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 131, 33, + 86, 0, + ], + ]; + + for (_i, bytes) in bytes_a.iter().enumerate() { + // Create a Cursor around the byte array for the reader + let mut reader = Cursor::new(bytes); + + // Decode the byte array into a TokenMessage + let _token_message = + TokenMessage::read_from(&mut reader).expect("Failed to decode TokenMessage"); + } + } +} diff --git a/rust/main/chains/dymension-kaspa/src/relayer/withdraw/minimum.rs b/rust/main/chains/dymension-kaspa/src/relayer/withdraw/minimum.rs new file mode 100644 index 00000000000..d542df536ae --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/relayer/withdraw/minimum.rs @@ -0,0 +1,21 @@ +use dym_kas_hardcode::tx::DUST_AMOUNT; +use hyperlane_core::{HyperlaneMessage, U256}; +use kaspa_consensus_core::tx::TransactionOutput; +use kaspa_wallet_core::tx::is_transaction_output_dust; + +pub fn is_dust(tx_out: &TransactionOutput, min_sompi: U256) -> bool { + tx_out.value < DUST_AMOUNT + || is_transaction_output_dust(tx_out) + || is_small_value(tx_out.value, min_sompi) +} + +pub fn is_small_value(value: u64, min_sompi: U256) -> bool { + value < min_sompi.as_u64() +} + +/// Checks if a HyperlaneMessage contains a dust amount (below min_sompi threshold). +pub fn is_dust_message(msg: &HyperlaneMessage, min_sompi: U256) -> bool { + crate::hl_message::parse_withdrawal_amount(msg) + .map(|amount| is_small_value(amount, min_sompi)) + .unwrap_or(false) +} diff --git a/rust/main/chains/dymension-kaspa/src/relayer/withdraw/mod.rs b/rust/main/chains/dymension-kaspa/src/relayer/withdraw/mod.rs new file mode 100644 index 00000000000..71b1f18be65 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/relayer/withdraw/mod.rs @@ -0,0 +1,5 @@ +pub mod hub_to_kaspa; +pub mod messages; +pub mod minimum; +pub mod sweep; +pub mod tooling; diff --git a/rust/main/chains/dymension-kaspa/src/relayer/withdraw/sweep.rs b/rust/main/chains/dymension-kaspa/src/relayer/withdraw/sweep.rs new file mode 100644 index 00000000000..83cb379d5ba --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/relayer/withdraw/sweep.rs @@ -0,0 +1,559 @@ +use dym_kas_core::consts::RELAYER_SIG_OP_COUNT; +use dym_kas_core::escrow::EscrowPublic; +use dym_kas_core::pskt::{ + estimate_mass, input_sighash_type, PopulatedInput, PopulatedInputBuilder, +}; +use dym_kas_core::wallet::EasyKaspaWallet; +use dym_kas_hardcode::tx::{DUST_AMOUNT, RELAYER_SWEEPING_PRIORITY_FEE}; +use eyre::{eyre, Result}; +use kaspa_consensus_core::network::NetworkId; +use kaspa_consensus_core::tx::TransactionOutput; +use kaspa_txscript::standard::pay_to_address_script; +use kaspa_wallet_core::tx::MAXIMUM_STANDARD_TRANSACTION_MASS; +use kaspa_wallet_pskt::bundle::Bundle; +use kaspa_wallet_pskt::output::Output as PsktOutput; +use kaspa_wallet_pskt::prelude::{Creator, OutputBuilder, Signer, PSKT}; +use kaspa_wallet_pskt::pskt::InputBuilder; +use tracing::info; + +/// Output pair from a PSKT with exactly two outputs: escrow and relayer. +struct OutputPair<'a> { + relayer_idx: u32, + relayer_output: &'a PsktOutput, + escrow_idx: u32, + escrow_output: &'a PsktOutput, +} + +/// Extract escrow and relayer outputs from a PSKT that must have exactly two outputs. +fn extract_output_pair<'a>( + outputs: &'a [PsktOutput], + escrow: &EscrowPublic, +) -> Result> { + match outputs { + [o0, o1] if o0.script_public_key == escrow.p2sh => Ok(OutputPair { + relayer_idx: 1, + relayer_output: o1, + escrow_idx: 0, + escrow_output: o0, + }), + [o0, o1] if o1.script_public_key == escrow.p2sh => Ok(OutputPair { + relayer_idx: 0, + relayer_output: o0, + escrow_idx: 1, + escrow_output: o1, + }), + _ => Err(eyre!( + "PSKT must have exactly two outputs: escrow and relayer" + )), + } +} + +/// Helper function to create test outputs for mass estimation +fn create_test_outputs( + escrow_balance: u64, + relayer_balance: u64, + escrow: &EscrowPublic, + relayer_address: &kaspa_addresses::Address, +) -> Result> { + if escrow_balance == 0 { + return Err(eyre!("escrow_balance cannot be zero")); + } + if relayer_balance == 0 { + return Err(eyre!("relayer_balance cannot be zero")); + } + Ok(vec![ + TransactionOutput { + value: escrow_balance, + script_public_key: escrow.p2sh.clone(), + }, + TransactionOutput { + value: relayer_balance, + script_public_key: pay_to_address_script(relayer_address), + }, + ]) +} + +/// Calculate the maximum number of escrow inputs when sweeping that fit within mass limit using binary search +fn calculate_sweep_size( + escrow_inputs: &[PopulatedInput], + relayer_inputs: &[PopulatedInput], + escrow: &EscrowPublic, + relayer_address: &kaspa_addresses::Address, + network_id: NetworkId, +) -> Result { + let total_relayer_balance = relayer_inputs.iter().map(|(_, e, _)| e.amount).sum::(); + + // First try all escrow inputs + let total_escrow_balance = escrow_inputs.iter().map(|(_, e, _)| e.amount).sum::(); + + let test_outputs = create_test_outputs( + total_escrow_balance, + total_relayer_balance, + escrow, + relayer_address, + )?; + + let all_inputs: Vec<_> = escrow_inputs + .iter() + .cloned() + .chain(relayer_inputs.iter().cloned()) + .collect(); + + match estimate_mass( + all_inputs, + test_outputs, + vec![], + network_id, + escrow.m() as u16, + ) { + Ok(mass) if mass <= MAXIMUM_STANDARD_TRANSACTION_MASS => { + info!( + escrow_inputs_count = escrow_inputs.len(), + mass = mass, + "kaspa relayer sweeping: all escrow inputs fit within mass limit" + ); + return Ok(escrow_inputs.len()); + } + Ok(mass) => { + info!( + mass = mass, + "kaspa relayer sweeping: all inputs exceed mass limit, starting binary search" + ); + } + Err(e) => { + info!( + error = %e, + "kaspa relayer sweeping: mass calculation failed, starting binary search" + ); + } + } + + // Binary search for maximum batch size + let mut low = 1; + let mut high = escrow_inputs.len(); + let mut best_size = 1; + + while low <= high { + let mid = (low + high) / 2; + let test_escrow_batch = escrow_inputs.iter().take(mid).cloned().collect::>(); + let test_escrow_balance = test_escrow_batch + .iter() + .map(|(_, e, _)| e.amount) + .sum::(); + + let test_outputs = create_test_outputs( + test_escrow_balance, + total_relayer_balance, + escrow, + relayer_address, + )?; + + let test_inputs: Vec<_> = test_escrow_batch + .into_iter() + .chain(relayer_inputs.iter().cloned()) + .collect(); + + match estimate_mass( + test_inputs, + test_outputs, + vec![], + network_id, + escrow.m() as u16, + ) { + Ok(mass) if mass <= MAXIMUM_STANDARD_TRANSACTION_MASS => { + best_size = mid; + low = mid + 1; + info!( + batch_size = mid, + mass = mass, + "kaspa relayer sweeping: batch size fits within mass limit" + ); + } + Ok(mass) => { + high = mid - 1; + info!( + batch_size = mid, + mass = mass, + "kaspa relayer sweeping: batch size exceeds mass limit" + ); + } + Err(e) => { + high = mid - 1; + info!(batch_size = mid, error = %e, "kaspa relayer sweeping: mass calculation failed for batch size"); + } + } + } + + if best_size == 0 { + return Err(eyre!( + "Cannot create valid PSKT: even single escrow input exceeds mass limit" + )); + } + + info!( + best_size = best_size, + "kaspa relayer sweeping: determined optimal batch size" + ); + Ok(best_size) +} + +/// Calculate the relayer fee for a sweeping transaction +/// Returns (estimated_fee, relayer_output_amount) +fn calculate_relayer_fee( + batch_escrow_inputs: &[PopulatedInput], + relayer_inputs: &[PopulatedInput], + batch_escrow_balance: u64, + escrow: &EscrowPublic, + relayer_address: &kaspa_addresses::Address, + network_id: NetworkId, + feerate: f64, +) -> Result<(u64, u64)> { + let total_relayer_balance = relayer_inputs.iter().map(|(_, e, _)| e.amount).sum::(); + + // Initial mass calculation with total relayer balance as output + let initial_outputs = create_test_outputs( + batch_escrow_balance, + total_relayer_balance, + escrow, + relayer_address, + )?; + + let all_inputs: Vec<_> = batch_escrow_inputs + .iter() + .cloned() + .chain(relayer_inputs.iter().cloned()) + .collect(); + + let initial_mass = estimate_mass( + all_inputs.clone(), + initial_outputs, + vec![], + network_id, + escrow.m() as u16, + )?; + + // Calculate initial fee estimate + let initial_fee = (initial_mass as f64 * feerate).ceil() as u64 + RELAYER_SWEEPING_PRIORITY_FEE; + + // Second pass: recalculate mass with more accurate output (balance - fee) + let estimated_relayer_output = total_relayer_balance.saturating_sub(initial_fee); + + let final_outputs = create_test_outputs( + batch_escrow_balance, + estimated_relayer_output, + escrow, + relayer_address, + )?; + + let mass = estimate_mass( + all_inputs, + final_outputs, + vec![], + network_id, + escrow.m() as u16, + )?; + + let estimated_fee = (mass as f64 * feerate).ceil() as u64 + RELAYER_SWEEPING_PRIORITY_FEE; + + // Check if relayer has enough balance to cover fees and minimum dust output + if total_relayer_balance < estimated_fee + DUST_AMOUNT { + return Err(eyre!( + "Insufficient relayer balance: have {} sompi, need {} (fee) + {} (dust) = {} sompi", + total_relayer_balance, + estimated_fee, + DUST_AMOUNT, + estimated_fee + DUST_AMOUNT + )); + } + + let relayer_output_amount = total_relayer_balance - estimated_fee; + + Ok((estimated_fee, relayer_output_amount)) +} + +/// Creates inputs for the next PSKT iteration by using outputs from the current PSKT. +/// Returns updated relayer and escrow inputs for chaining. +fn prepare_next_iteration_inputs( + pskt_signer: &PSKT, + escrow: &EscrowPublic, + mut escrow_inputs: Vec, +) -> Result<(Vec, Vec)> { + // Get the actual transaction ID and output details from the PSKT + let sweep_tx = PSKT::::from(pskt_signer.clone()); + let tx_id = sweep_tx.calculate_id(); + + let pair = extract_output_pair(sweep_tx.outputs.as_slice(), escrow)?; + + // Create relayer input from previous PSKT's relayer output + let relayer_input = PopulatedInputBuilder::new( + tx_id, + pair.relayer_idx, + pair.relayer_output.amount, + pair.relayer_output.script_public_key.clone(), + ) + .build(); + + // Create escrow input from previous PSKT's escrow output + let escrow_input = PopulatedInputBuilder::new( + tx_id, + pair.escrow_idx, + pair.escrow_output.amount, + pair.escrow_output.script_public_key.clone(), + ) + .sig_op_count(escrow.n() as u8) + .redeem_script(Some(escrow.redeem_script.clone())) + .build(); + // Next iteration will use both outputs as inputs + let new_relayer_inputs = vec![relayer_input]; + // Add the escrow output from previous PSKT to the beginning of remaining escrow inputs + escrow_inputs.insert(0, escrow_input); + + info!( + escrow_idx = pair.escrow_idx, + escrow_amount = pair.escrow_output.amount, + relayer_idx = pair.relayer_idx, + relayer_amount = pair.relayer_output.amount, + "kaspa relayer sweeping: chained escrow and relayer outputs for next batch" + ); + + Ok((new_relayer_inputs, escrow_inputs)) +} + +/// Create a bundle that sweeps funds in the escrow address. +/// The function expects a set of inputs that are needed to be swept – [`escrow_inputs`]. +/// And a set of relayer inputs to cover the transaction fee – [`relayer_inputs`]. +/// Creates multiple PSKTs to respect mass limits. +/// Each PSKT includes all relayer inputs and consolidates escrow inputs. +/// Each PSKT has exactly 2 outputs: consolidated escrow and relayer change. +/// Sweeping will stop when enough inputs are consolidated to cover withdrawal amount and MAX_SWEEP_INPUTS is also reached, even if more inputs are available. +/// +/// # Parameters +/// * `anchor_amount` - The amount available in the anchor UTXO that will be used for withdrawals (not swept) +/// * `max_sweep_inputs` - Optional maximum number of inputs to sweep (if None, only bundle size limit applies) +/// * `max_sweep_bundle_bytes` - Maximum bundle size in bytes (to fit within validator body limit) +pub async fn create_sweeping_bundle( + relayer_wallet: &EasyKaspaWallet, + escrow: &EscrowPublic, + mut escrow_inputs: Vec, + mut relayer_inputs: Vec, + total_withdrawal_amount: u64, + anchor_amount: u64, + max_sweep_inputs: Option, + max_sweep_bundle_bytes: usize, +) -> Result { + use kaspa_txscript::standard::pay_to_address_script; + + if escrow_inputs.is_empty() { + return Err(eyre!("No escrow inputs to sweep")); + } + + // Sort escrow inputs by amount (largest first) for more efficient consolidation + escrow_inputs.sort_by(|a, b| b.1.amount.cmp(&a.1.amount)); + + let relayer_address = relayer_wallet.account().change_address()?; + let feerate = relayer_wallet + .rpc_with_reconnect(|api| async move { + api.get_fee_estimate() + .await + .map(|estimate| estimate.normal_buckets.first().unwrap().feerate) + .map_err(|e| eyre::eyre!("{}", e)) + }) + .await?; + + let mut bundle = Bundle::new(); + + info!( + escrow_inputs_count = escrow_inputs.len(), + relayer_inputs_count = relayer_inputs.len(), + total_withdrawal_amount = total_withdrawal_amount, + anchor_amount = anchor_amount, + "kaspa relayer sweeping: started" + ); + + let mut total_swept_amount = 0u64; + let mut total_inputs_swept = 0usize; + + // Calculate how much more we need to sweep considering the anchor amount + let withdrawal_amount_without_anchor = total_withdrawal_amount.saturating_sub(anchor_amount); + info!( + amount_to_sweep = withdrawal_amount_without_anchor, + total_withdrawals = total_withdrawal_amount, + anchor_amount = anchor_amount, + "kaspa relayer sweeping: calculated amount to sweep (sompi)" + ); + // Process escrow inputs recursively until: + // 1. All are consumed, OR + // 2. Reached the maximum bundle size (always enforced), OR + // 3. Reached the maximum number of inputs (if configured) + while !escrow_inputs.is_empty() { + // Check input count limit if configured + if let Some(max_inputs) = max_sweep_inputs { + if total_inputs_swept >= max_inputs { + info!( + total_swept_amount = total_swept_amount, + total_inputs_swept = total_inputs_swept, + max_inputs = max_inputs, + remaining_escrow_inputs = escrow_inputs.len(), + "kaspa relayer sweeping: stopped at configured max_sweep_inputs limit" + ); + break; + } + } + // Find batch size that fits within mass limit + let batch_size = calculate_sweep_size( + &escrow_inputs, + &relayer_inputs, + escrow, + &relayer_address, + relayer_wallet.net.network_id, + )?; + + // Take batch of escrow inputs + let batch_escrow_inputs: Vec<_> = escrow_inputs.drain(0..batch_size).collect(); + let batch_escrow_balance = batch_escrow_inputs + .iter() + .map(|(_, e, _)| e.amount) + .sum::(); + let batch_escrow_inputs_count = batch_escrow_inputs.len(); + total_swept_amount += batch_escrow_balance; + total_inputs_swept += batch_escrow_inputs_count; + + // Calculate relayer fee and output amount + let (estimated_fee, relayer_output_amount) = calculate_relayer_fee( + &batch_escrow_inputs, + &relayer_inputs, + batch_escrow_balance, + escrow, + &relayer_address, + relayer_wallet.net.network_id, + feerate, + )?; + + // Create PSKT + let mut pskt = PSKT::::default().constructor(); + + // Add escrow inputs + for (input, entry, _) in batch_escrow_inputs { + let mut b = InputBuilder::default(); + b.previous_outpoint(input.previous_outpoint) + .sig_op_count(escrow.n() as u8) + .sighash_type(input_sighash_type()) + .redeem_script(escrow.redeem_script.clone()) + .utxo_entry(entry); + + pskt = pskt.input(b.build().map_err(|e| eyre!("Build escrow input: {}", e))?); + } + + // Add all relayer inputs + for (input, entry, _) in &relayer_inputs { + let mut b = InputBuilder::default(); + b.previous_outpoint(input.previous_outpoint) + .sig_op_count(RELAYER_SIG_OP_COUNT) + .sighash_type(input_sighash_type()) + .utxo_entry(entry.clone()); + + pskt = pskt.input(b.build().map_err(|e| eyre!("Build relayer input: {}", e))?); + } + + // Add escrow output + let escrow_output_builder = OutputBuilder::default() + .amount(batch_escrow_balance) + .script_public_key(escrow.p2sh.clone()) + .build() + .map_err(|e| eyre!("Build escrow output: {}", e))?; + + pskt = pskt.output(escrow_output_builder); + + // Add relayer output + let relayer_output_builder = OutputBuilder::default() + .amount(relayer_output_amount) + .script_public_key(pay_to_address_script(&relayer_address)) + .build() + .map_err(|e| eyre!("Build relayer output: {}", e))?; + + pskt = pskt.output(relayer_output_builder); + + let pskt_signer = pskt.no_more_inputs().no_more_outputs().signer(); + let pskt_id = pskt_signer.calculate_id(); + + // Update inputs for next iteration (use outputs from current PSKT as inputs) + if !escrow_inputs.is_empty() { + let (new_relayer_inputs, updated_escrow_inputs) = + prepare_next_iteration_inputs(&pskt_signer, escrow, escrow_inputs)?; + relayer_inputs = new_relayer_inputs; + escrow_inputs = updated_escrow_inputs; + } + + bundle.add_pskt(pskt_signer); + + // Check bundle size limit + let bundle_bytes = bundle + .serialize() + .map_err(|e| eyre!("Serialize bundle to check size: {}", e))?; + let bundle_size = bundle_bytes.len(); + + if bundle_size >= max_sweep_bundle_bytes { + info!( + bundle_size_bytes = bundle_size, + max_bundle_bytes = max_sweep_bundle_bytes, + pskts_count = bundle.0.len(), + "kaspa relayer sweeping: reached max bundle size limit, stopping" + ); + break; + } + + info!( + pskt_id = %pskt_id, + batch_escrow_inputs_count = batch_escrow_inputs_count, + estimated_fee = estimated_fee, + relayer_output_amount = relayer_output_amount, + bundle_size_bytes = bundle_size, + "kaspa relayer sweeping: created PSKT" + ); + } + info!( + pskts_count = bundle.0.len(), + inputs_swept = total_inputs_swept, + swept_amount = total_swept_amount, + total_available = anchor_amount + total_swept_amount, + total_withdrawals = total_withdrawal_amount, + "kaspa relayer sweeping: completed" + ); + Ok(bundle) +} + +pub fn create_inputs_from_sweeping_bundle( + sweeping_bundle: &Bundle, + escrow: &EscrowPublic, +) -> Result> { + let last_pskt = sweeping_bundle + .iter() + .last() + .cloned() + .ok_or_else(|| eyre!("Empty sweeping bundle"))?; + + let sweep_tx = PSKT::::from(last_pskt); + let tx_id = sweep_tx.calculate_id(); + + let pair = extract_output_pair(sweep_tx.outputs.as_slice(), escrow)?; + + let relayer_input = PopulatedInputBuilder::new( + tx_id, + pair.relayer_idx, + pair.relayer_output.amount, + pair.relayer_output.script_public_key.clone(), + ) + .build(); + + let escrow_input = PopulatedInputBuilder::new( + tx_id, + pair.escrow_idx, + pair.escrow_output.amount, + escrow.p2sh.clone(), + ) + .sig_op_count(escrow.n() as u8) + .redeem_script(Some(escrow.redeem_script.clone())) + .build(); + + Ok(vec![relayer_input, escrow_input]) +} diff --git a/rust/main/chains/dymension-kaspa/src/relayer/withdraw/tooling.rs b/rust/main/chains/dymension-kaspa/src/relayer/withdraw/tooling.rs new file mode 100644 index 00000000000..381a67dac47 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/relayer/withdraw/tooling.rs @@ -0,0 +1,87 @@ +use dym_kas_core::escrow::*; +use dym_kas_core::pskt::input_sighash_type; +use eyre::Result; +use kaspa_addresses::Address; +use kaspa_consensus_core::tx::{TransactionOutpoint, UtxoEntry}; +use kaspa_rpc_core::api::rpc::RpcApi; +use kaspa_txscript::standard::pay_to_address_script; +use kaspa_wallet_core::error::Error; +use kaspa_wallet_core::prelude::*; +use kaspa_wallet_core::utxo::UtxoIterator; +use kaspa_wallet_pskt::prelude::*; +use std::sync::Arc; + +// used by multisig demo +pub async fn build_withdrawal_tx( + rpc: &T, + e: &EscrowPublic, + user_address: Address, + a_relayer: &Arc, + fee: u64, + amt: u64, +) -> Result, Error> { + let utxos_e = rpc.get_utxos_by_addresses(vec![e.addr.clone()]).await?; + let utxo_e_first = utxos_e + .into_iter() + .next() + .ok_or("No UTXO found at escrow address")?; + let utxo_e_entry = UtxoEntry::from(utxo_e_first.utxo_entry); + let utxo_e_out = TransactionOutpoint::from(utxo_e_first.outpoint); + + let utxo_r = UtxoIterator::new(a_relayer.utxo_context()) + .next() + .ok_or("Relayer has no UTXOs")?; + let utxo_r_entry: UtxoEntry = (utxo_r.utxo.as_ref()).into(); + let utxo_r_out = TransactionOutpoint::from(utxo_r.outpoint()); + + let input_e = InputBuilder::default() + .utxo_entry(utxo_e_entry.clone()) + .previous_outpoint(utxo_e_out) + .redeem_script(e.redeem_script.clone()) + .sig_op_count(e.n() as u8) // Total possible signers + .sighash_type(input_sighash_type()) + .build() + .map_err(|e| Error::Custom(format!("pskt input e: {e}")))?; + + let input_r = InputBuilder::default() + .utxo_entry(utxo_r_entry.clone()) + .previous_outpoint(utxo_r_out) + .sig_op_count(1) // TODO: needed if using p2pk? + .sighash_type(input_sighash_type()) + .build() + .map_err(|e| Error::Custom(format!("pskt input r: {e}")))?; + + let output_e_to_user = OutputBuilder::default() + .amount(amt) + .script_public_key(pay_to_address_script(&user_address)) + .build() + .map_err(|e| Error::Custom(format!("pskt output e_to_user: {e}")))?; + + let output_e_change = OutputBuilder::default() + .amount(utxo_e_entry.amount - amt) + .script_public_key(e.p2sh.clone()) + .build() + .map_err(|e| Error::Custom(format!("pskt output e_change: {e}")))?; + + _ = output_e_change; // TODO: fix + + let output_r_change = OutputBuilder::default() + .amount(utxo_r_entry.amount - fee) + .script_public_key(pay_to_address_script(&a_relayer.change_address()?)) + .build() + .map_err(|e| Error::Custom(format!("pskt output r_change: {e}")))?; + + let pskt = PSKT::::default() + .set_version(Version::One) + .constructor() + .input(input_e) + .input(input_r) + .output(output_e_to_user) + // .output(output_e_change) + .output(output_r_change) + .no_more_inputs() + .no_more_outputs() + .signer(); + + Ok(pskt) +} diff --git a/rust/main/chains/dymension-kaspa/src/util.rs b/rust/main/chains/dymension-kaspa/src/util.rs new file mode 100644 index 00000000000..e06da116a14 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/util.rs @@ -0,0 +1,80 @@ +use crate::validator::error::ValidationError; +use dym_kas_api::models::TxModel; +use hyperlane_core::{HyperlaneDomain, HyperlaneDomainProtocol}; + +use crate::consts::{ + HL_DOMAIN_DYM_LOCAL, HL_DOMAIN_DYM_MAINNET, HL_DOMAIN_DYM_PLAYGROUND_202507, + HL_DOMAIN_DYM_PLAYGROUND_202507_LEGACY, HL_DOMAIN_DYM_PLAYGROUND_202509, + HL_DOMAIN_DYM_TESTNET_BLUMBUS, HL_DOMAIN_KASPA_MAINNET, HL_DOMAIN_KASPA_TEST10, + HL_DOMAIN_KASPA_TEST10_LEGACY, +}; +use dym_kas_core::wallet::Network; + +/// is it a kaspa domain? +pub fn is_kas(d: &HyperlaneDomain) -> bool { + matches!( + d, + HyperlaneDomain::Unknown { + domain_protocol: HyperlaneDomainProtocol::Kaspa, + .. + } + ) +} + +/// is it a dym domain? +pub fn is_dym(d: &HyperlaneDomain) -> bool { + HUB_DOMAINS.contains(&d.id()) +} + +/// domain to kas network +pub fn domain_to_kas_network(d: &HyperlaneDomain) -> Network { + match d { + HyperlaneDomain::Unknown { + domain_protocol: HyperlaneDomainProtocol::Kaspa, + domain_id: HL_DOMAIN_KASPA_TEST10, + .. + } => Network::KaspaTest10, + HyperlaneDomain::Unknown { + domain_protocol: HyperlaneDomainProtocol::Kaspa, + domain_id: HL_DOMAIN_KASPA_TEST10_LEGACY, + .. + } => Network::KaspaTest10, + HyperlaneDomain::Unknown { + domain_protocol: HyperlaneDomainProtocol::Kaspa, + domain_id: HL_DOMAIN_KASPA_MAINNET, + .. + } => Network::KaspaMainnet, + + _ => todo!("only kaspa supported"), + } +} + +/// List of kas domain. +pub const KAS_DOMAINS: [u32; 2] = [HL_DOMAIN_KASPA_MAINNET, HL_DOMAIN_KASPA_TEST10]; + +/// List of dym domain. +pub const HUB_DOMAINS: [u32; 6] = [ + HL_DOMAIN_DYM_LOCAL, + HL_DOMAIN_DYM_MAINNET, + HL_DOMAIN_DYM_TESTNET_BLUMBUS, + HL_DOMAIN_DYM_PLAYGROUND_202507, + HL_DOMAIN_DYM_PLAYGROUND_202507_LEGACY, + HL_DOMAIN_DYM_PLAYGROUND_202509, +]; + +/// Extract the address string from a transaction output at the given index. +pub fn get_output_address(tx: &TxModel, index: usize) -> Result { + let outputs = tx + .outputs + .as_ref() + .ok_or(ValidationError::MissingTransactionOutputs)?; + + let output = outputs + .get(index) + .ok_or(ValidationError::UtxoNotFound { index })?; + + output + .script_public_key_address + .clone() + .ok_or(ValidationError::MissingScriptPubKeyAddress) +} diff --git a/rust/main/chains/dymension-kaspa/src/validator/confirmation.rs b/rust/main/chains/dymension-kaspa/src/validator/confirmation.rs new file mode 100644 index 00000000000..f0c99a5dd77 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/validator/confirmation.rs @@ -0,0 +1,228 @@ +use crate::ops::confirmation::ConfirmationFXG; +use crate::ops::payload::{MessageID, MessageIDs}; +use crate::util::get_output_address; +use crate::validator::error::ValidationError; +use dym_kas_api::models::TxModel; +use dym_kas_core::api::client::HttpClient; +use dym_kas_core::finality::is_safe_against_reorg; +use dym_kas_core::hash::hex_to_kaspa_hash; +use hyperlane_cosmos_rs::dymensionxyz::dymension::kas::ProgressIndication; +use hyperlane_cosmos_rs::dymensionxyz::dymension::kas::TransactionOutpoint as ProtoTransactionOutpoint; +use kaspa_addresses::Address; +use kaspa_consensus_core::tx::TransactionOutpoint; +use kaspa_hashes::Hash as KaspaHash; +use std::collections::HashSet; +use tracing::info; + +fn proto_to_outpoint( + proto: Option<&ProtoTransactionOutpoint>, +) -> Result { + let o = proto.ok_or(ValidationError::OutpointMissing { + description: "outpoint in progress indication".to_string(), + })?; + Ok(TransactionOutpoint { + transaction_id: KaspaHash::from_bytes(o.transaction_id.as_slice().try_into().map_err( + |e| ValidationError::InvalidOutpointData { + reason: format!("invalid transaction ID: {}", e), + }, + )?), + index: o.index, + }) +} + +/// Validator is given a progress indication to sign, and a cache of outpoints, +/// that should start from the current hub anchor and end with the new one. +/// The validator checks that indeed that set of outpoints is from a real withdrawal +/// sequence on Kaspa chain. +/// +/// `src_escrow` and `dst_escrow` define valid escrow addresses for the confirmation trace. +/// Normally these are the same address. During migration, `src_escrow` is the old escrow +/// and `dst_escrow` is the new escrow, allowing the trace to cross the migration boundary. +pub async fn validate_confirmed_withdrawals( + fxg: &ConfirmationFXG, + client_rest: &HttpClient, + src_escrow: &Address, + dst_escrow: &Address, +) -> Result<(), ValidationError> { + info!("Validator: Starting validation of withdrawals confirmation"); + + let untrusted_progress = &fxg.progress_indication; + + let proposed_hub_anchor_old = proto_to_outpoint(untrusted_progress.old_outpoint.as_ref())?; + let proposed_hub_anchor_new = proto_to_outpoint(untrusted_progress.new_outpoint.as_ref())?; + + // Validate the progress indication is correct according to the cache + let outpoint_sequence = &fxg.outpoints; + if outpoint_sequence.len() < 2 { + return Err(ValidationError::InsufficientOutpoints { + count: outpoint_sequence.len(), + }); + } + if outpoint_sequence[0] != proposed_hub_anchor_old { + return Err(ValidationError::AnchorMismatch { + o: proposed_hub_anchor_old, + }); + } + if outpoint_sequence[outpoint_sequence.len() - 1] != proposed_hub_anchor_new { + return Err(ValidationError::AnchorMismatch { + o: proposed_hub_anchor_new, + }); + } + + let mut observed_message_ids = Vec::new(); + + // Start from the next UTXO after the anchor_utxo (skip the first outpoint) + for (i, o) in outpoint_sequence.iter().enumerate().skip(1) { + info!( + "Validator: Processing outpoint {} of {}: {:?}", + i + 1, + outpoint_sequence.len(), + o + ); + + let tx_id = &o.transaction_id.to_string(); + + // Get the transaction that CREATED this UTXO + let tx = client_rest.get_tx_by_id(tx_id).await.map_err(|_| { + ValidationError::TransactionFetchError { + tx_id: o.transaction_id.to_string(), + } + })?; + + // Validate that this transaction spends the previous outpoint + let o_prev = &outpoint_sequence[i - 1]; // Previous outpoint in the chain + if !outpoint_in_inputs(&tx, o_prev)? { + return Err(ValidationError::PreviousTransactionNotFound); + } + + // Validate that this transaction creates the current outpoint + escrow_outpoint_in_outputs(&tx, o, src_escrow, dst_escrow)?; + + let message_ids = match tx.payload.clone() { + Some(p) => { + MessageIDs::from_tx_payload(&p).map_err(|e| ValidationError::PayloadParseError { + reason: format!("Failed to parse message IDs: {}", e), + })? + } + None => MessageIDs::new(vec![]), + }; + + // If the last TX in sequence is final then the others must be too + if i == outpoint_sequence.len() - 1 { + let hint = match tx.block_hash { + Some(block_hashes) => { + if !block_hashes.is_empty() { + Some(block_hashes[0].clone()) + } else { + None + } + } + None => None, + }; + let finality_status = is_safe_against_reorg(client_rest, tx_id, hint) + .await + .map_err(|e| ValidationError::FinalityCheckError { + tx_id: tx_id.to_string(), + reason: e.to_string(), + })?; + + if !finality_status.is_final() { + return Err(ValidationError::NotSafeAgainstReorg { + tx_id: tx_id.clone(), + confirmations: finality_status.confirmations, + required: finality_status.required_confirmations, + }); + } + } + + observed_message_ids.extend(message_ids.0); + } + + // Assert that the collected messageIds are the same as progress_indication.processed_withdrawals + validate_message_ids_exactly_equal(untrusted_progress, &observed_message_ids)?; + + info!("Validator: All validations passed successfully"); + Ok(()) +} + +/// Validate that the previous outpoint is referenced in the current transaction's inputs +fn outpoint_in_inputs( + transaction: &TxModel, + anchor: &TransactionOutpoint, +) -> Result { + let inputs = transaction + .inputs + .as_ref() + .ok_or(ValidationError::MissingTransactionInputs)?; + + for input in inputs { + let input_utxo = TransactionOutpoint { + transaction_id: hex_to_kaspa_hash(&input.previous_outpoint_hash).map_err(|e| { + ValidationError::InvalidOutpointData { + reason: e.to_string(), + } + })?, + index: input.previous_outpoint_index.parse().map_err(|e| { + ValidationError::InvalidOutpointData { + reason: format!("Failed to parse previous_outpoint_index: {}", e), + } + })?, + }; + + if input_utxo == *anchor { + return Ok(true); + } + } + + Ok(false) +} + +/// Validate that the anchor is referenced in the current transaction's outputs +/// and it is an escrow change (either src or dst escrow). +fn escrow_outpoint_in_outputs( + tx_trusted: &TxModel, + escrow_outpoint_untrusted: &TransactionOutpoint, + src_escrow: &Address, + dst_escrow: &Address, +) -> Result<(), ValidationError> { + // We already know this TX spends escrow funds, so it must be signed by validators. + // Validators only sign withdrawals containing exactly one change output back to escrow. + // We accept both src and dst because the trace may span the migration boundary: + // pre-migration TXs output to src, the migration TX outputs to dst. + let recipient_actual = + get_output_address(tx_trusted, escrow_outpoint_untrusted.index as usize)?; + + let src_str = src_escrow.address_to_string(); + let dst_str = dst_escrow.address_to_string(); + + if recipient_actual != src_str && recipient_actual != dst_str { + return Err(ValidationError::NonEscrowAnchor { + o: *escrow_outpoint_untrusted, + }); + } + + Ok(()) +} + +/// Validate that the collected message IDs match the progress indication +fn validate_message_ids_exactly_equal( + untrusted_progress: &ProgressIndication, + observed_message_ids: &[MessageID], +) -> Result<(), ValidationError> { + let expected_message_ids: HashSet = observed_message_ids + .iter() + .map(|id| hex::encode(id.0.as_bytes())) + .collect(); + + let untrusted_message_ids: HashSet = untrusted_progress + .processed_withdrawals + .iter() + .map(|w| w.message_id.clone()) + .collect(); + + if expected_message_ids != untrusted_message_ids { + return Err(ValidationError::MessageIdsMismatch); + } + + Ok(()) +} diff --git a/rust/main/chains/dymension-kaspa/src/validator/deposit.rs b/rust/main/chains/dymension-kaspa/src/validator/deposit.rs new file mode 100644 index 00000000000..043e6d61433 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/validator/deposit.rs @@ -0,0 +1,220 @@ +use crate::consts::ALLOWED_HL_MESSAGE_VERSION; +use crate::ops::deposit::DepositFXG; +use crate::ops::message::{add_kaspa_metadata_hl_messsage, ParsedHL}; +use crate::validator::error::{validate_hl_message_fields, ValidationError}; +use dym_kas_core::api::client::HttpClient; +use dym_kas_core::finality::is_safe_against_reorg; +use dym_kas_core::wallet::NetworkInfo; +use eyre::Result; +use hyperlane_core::HyperlaneMessage; +use hyperlane_core::H256; +use hyperlane_core::U256; +use hyperlane_cosmos::{native::ModuleQueryClient, CosmosProvider}; +use kaspa_addresses::Address; +use kaspa_grpc_client::GrpcClient; +use kaspa_rpc_core::api::rpc::RpcApi; +use kaspa_rpc_core::RpcBlock; +use kaspa_rpc_core::{RpcHash, RpcTransaction, RpcTransactionOutput}; +use kaspa_txscript::extract_script_pub_key_address; + +#[derive(Clone, Default)] +pub struct MustMatch { + partial_message: HyperlaneMessage, +} + +impl MustMatch { + pub fn new( + hub_domain: u32, + hub_token_id: H256, + kas_domain: u32, + kas_token_placeholder: H256, // a fake value, since Kaspa does not have a 'token' smart contract. Howevert his value must be consistent with hub config. + ) -> Self { + Self { + partial_message: HyperlaneMessage { + version: ALLOWED_HL_MESSAGE_VERSION, + nonce: 0, + origin: kas_domain, + sender: kas_token_placeholder, + destination: hub_domain, + recipient: hub_token_id, + body: vec![], + }, + } + } + + fn is_match(&self, other: &HyperlaneMessage) -> Result<(), ValidationError> { + validate_hl_message_fields(&self.partial_message, other) + } +} + +/// Deposit validation process +/// Executed by validators to check the deposit info relayed is equivalent to the original Kaspa tx to the escrow address +/// It validates that: +/// * The original escrow transaction exists in Kaspa network +/// * The HL message relayed is equivalent to the HL message included in the original Kaspa Tx (after recreating metadata injection to token message) +/// * The Kaspa transaction utxo destination is the escrowed address and the utxo value is enough to cover the tx. +/// * The utxo is mature +/// +/// Note: If the utxo value is higher of the amount the deposit is also accepted +/// +pub async fn validate_new_deposit( + client_rest: &HttpClient, + deposit: &DepositFXG, + net: &NetworkInfo, + escrow_address: &Address, + hub_client: &CosmosProvider, + must_match: MustMatch, + kaspa_grpc_client: GrpcClient, +) -> Result<(), ValidationError> { + let hub_bootstrapped = hub_client.query().hub_bootstrapped().await.map_err(|e| { + ValidationError::HubQueryError { + reason: e.to_string(), + } + })?; + validate_new_deposit_inner( + client_rest, + deposit, + net, + escrow_address, + hub_bootstrapped, + must_match, + kaspa_grpc_client, + ) + .await +} + +/// Deposit validation process +/// Executed by validators to check the deposit info relayed is equivalent to the original Kaspa tx to the escrow address +/// It validates that: +/// * The original escrow transaction exists in Kaspa network +/// * The HL message relayed is equivalent to the HL message included in the original Kaspa Tx (after recreating metadata injection to token message) +/// * The Kaspa transaction utxo destination is the escrowed address and the utxo value is enough to cover the tx. +/// * The utxo is mature +/// +/// Note: If the utxo value is higher of the amount the deposit is also accepted +/// +pub async fn validate_new_deposit_inner( + client_rest: &HttpClient, + d_untrusted: &DepositFXG, + net: &NetworkInfo, + escrow_address: &Address, + hub_bootstrapped: bool, + must_match: MustMatch, + grpc_client: GrpcClient, +) -> Result<(), ValidationError> { + if !hub_bootstrapped { + return Err(ValidationError::HubNotBootstrapped); + } + + if d_untrusted.tx_id_rpc().is_err() { + return Err(ValidationError::InvalidTransactionHash); + } + + let containing_block_hash = d_untrusted.containing_block_hash_rpc().map_err(|e| { + ValidationError::BlockHashConversionError { + reason: e.to_string(), + } + })?; + + let finality_status = is_safe_against_reorg( + client_rest, + &d_untrusted.tx_id, + Some(containing_block_hash.to_string()), + ) + .await + .map_err(|e| ValidationError::ExternalApiError { + reason: e.to_string(), + })?; + + if !finality_status.is_final() { + return Err(ValidationError::NotSafeAgainstReorg { + tx_id: d_untrusted.tx_id.clone(), + confirmations: finality_status.confirmations, + required: finality_status.required_confirmations, + }); + } + + let containing_block: RpcBlock = grpc_client + .get_block(containing_block_hash, true) + .await + .map_err(|e| ValidationError::KaspaNodeError { + reason: e.to_string(), + })?; + + let tx_id_rpc = + d_untrusted + .tx_id_rpc() + .map_err(|e| ValidationError::TransactionHashConversionError { + reason: e.to_string(), + })?; + + let actual_deposit = tx_by_id(&containing_block, &tx_id_rpc)?; + + // get utxo in the tx from index in deposit. + let actual_deposit_utxo: &RpcTransactionOutput = actual_deposit + .outputs + .get(d_untrusted.utxo_index) + .ok_or_else(|| ValidationError::UtxoNotFound { + index: d_untrusted.utxo_index, + })?; + + // get HLMessage and token message from Tx payload + let actual_hl_message = ParsedHL::parse_bytes(actual_deposit.payload).map_err(|e| { + ValidationError::PayloadParseError { + reason: e.to_string(), + } + })?; + + // deposit tx amount + let actual_hl_amt: U256 = actual_hl_message.token_message.amount(); + + // recreate the metadata injection to the token message done by the relayer + let actual_hl_message_with_injected_info = + add_kaspa_metadata_hl_messsage(actual_hl_message, tx_id_rpc, d_untrusted.utxo_index) + .map_err(|e| ValidationError::PayloadParseError { + reason: format!("Failed to add Kaspa metadata: {}", e), + })?; + + must_match.is_match(&actual_hl_message_with_injected_info)?; + + // validate the original HL message included in the Kaspa Tx its the same than the HL message relayed, after adding the metadata. + if d_untrusted.hl_message.id() != actual_hl_message_with_injected_info.id() { + return Err(ValidationError::HLMessageIdMismatch); + } + + // deposit covers HL message amount? + if U256::from(actual_deposit_utxo.value) < actual_hl_amt { + return Err(ValidationError::InsufficientDepositAmount { + required: actual_hl_amt.to_string(), + actual: U256::from(actual_deposit_utxo.value).to_string(), + }); + } + + let actual_utxo_addr = + extract_script_pub_key_address(&actual_deposit_utxo.script_public_key, net.address_prefix) + .map_err(|e| ValidationError::ScriptPubKeyExtractionError { + reason: e.to_string(), + })?; + if actual_utxo_addr != *escrow_address { + return Err(ValidationError::WrongDepositAddress { + expected: escrow_address.to_string(), + actual: actual_utxo_addr.to_string(), + }); + } + + Ok(()) +} + +/// takes block and tx id and returns the tx +fn tx_by_id(block: &RpcBlock, tx_id: &RpcHash) -> Result { + let tx_index_actual = block + .verbose_data + .as_ref() + .ok_or(ValidationError::TransactionDataNotFound)? + .transaction_ids + .iter() + .position(|id| id == tx_id) + .ok_or(ValidationError::TransactionDataNotFound)?; + + Ok(block.transactions[tx_index_actual].clone()) +} diff --git a/rust/main/chains/dymension-kaspa/src/validator/error.rs b/rust/main/chains/dymension-kaspa/src/validator/error.rs new file mode 100644 index 00000000000..df4fb6c1dc5 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/validator/error.rs @@ -0,0 +1,207 @@ +use hyperlane_core::HyperlaneMessage; +use kaspa_consensus_core::tx::TransactionOutpoint; + +#[derive(Debug, thiserror::Error)] +pub enum ValidationError { + #[error("Message is not dispatched: {message_id}")] + MessageNotDispatched { message_id: String }, + + #[error("The same message was relayed twice: {message_id}")] + DoubleSpending { message_id: String }, + + #[error("HL message field mismatch: field={field} expected={expected} actual={actual}")] + HLMessageFieldMismatch { + field: String, + expected: String, + actual: String, + }, + + #[error("Transaction is not safe against reorg: {tx_id} confirmations={confirmations} required={required}")] + NotSafeAgainstReorg { + tx_id: String, + confirmations: i64, + required: i64, + }, + + #[error("Hub is not bootstrapped")] + HubNotBootstrapped, + + #[error("Invalid transaction hash")] + InvalidTransactionHash, + + #[error("UTXO not found at index {index}")] + UtxoNotFound { index: usize }, + + #[error("Failed to parse payload: {reason}")] + PayloadParseError { reason: String }, + + #[error("Insufficient deposit amount: required={required} actual={actual}")] + InsufficientDepositAmount { required: String, actual: String }, + + #[error("Deposit not to escrow address: expected={expected} actual={actual}")] + WrongDepositAddress { expected: String, actual: String }, + + #[error("Transaction data not found in block")] + TransactionDataNotFound, + + #[error("Outpoint missing: {description}")] + OutpointMissing { description: String }, + + #[error("Invalid outpoint data: {reason}")] + InvalidOutpointData { reason: String }, + + #[error("Insufficient outpoints in cache: minimum_required=2 actual_count={count}")] + InsufficientOutpoints { count: usize }, + + #[error("Previous transaction not found in inputs")] + PreviousTransactionNotFound, + + #[error("Transaction inputs not found")] + MissingTransactionInputs, + + #[error("Transaction outputs not found")] + MissingTransactionOutputs, + + #[error("Script public key address not found in output")] + MissingScriptPubKeyAddress, + + #[error("Failed to extract script public key address: {reason}")] + ScriptPubKeyExtractionError { reason: String }, + + #[error("Message IDs do not match")] + MessageIdsMismatch, + + #[error("HL message ID mismatch after metadata injection")] + HLMessageIdMismatch, + + #[error("Failed general verification: {reason}")] + FailedGeneralVerification { reason: String }, + + #[error("Some of the messages are not in the unprocessed status on the Hub")] + MessagesNotUnprocessed, + + #[error("HL message used escrow address as withdrawal recipient")] + EscrowWithdrawalNotAllowed { message_id: String }, + + #[error("Anchor not found in PSKT inputs: outpoint={o:?}")] + AnchorNotFound { o: TransactionOutpoint }, + + #[error("Anchor shouldn't be spent in sweeping PSKT: outpoint={o:?}")] + AnchorSpent { o: TransactionOutpoint }, + + #[error("Anchor is not escrow change: outpoint={o:?}")] + NonEscrowAnchor { o: TransactionOutpoint }, + + #[error("No messages to validate")] + NoMessages, + + #[error("Hub anchor mismatch: hub_anchor={hub_anchor:?} relayer_anchor={relayer_anchor:?}")] + HubAnchorMismatch { + hub_anchor: TransactionOutpoint, + relayer_anchor: TransactionOutpoint, + }, + + #[error("Sighash type is not SIG_HASH_ALL | SIG_HASH_ANY_ONE_CAN_PAY")] + SigHashType, + + #[error("Next anchor not found in PSKT outputs")] + NextAnchorNotFound, + + #[error("More than one anchor candidate in PSKT outputs")] + MultipleAnchors, + + #[error("PSKT payload doesn't match inteded HL messages")] + PayloadMismatch, + + #[error("Outpoint not found in PSKT chain: outpoint={o:?}")] + AnchorMismatch { o: TransactionOutpoint }, + + #[error("Message cache length mismatch: expected={expected} actual={actual}")] + MessageCacheLengthMismatch { expected: usize, actual: usize }, + + #[error("Some HL messages do not have outputs")] + MissingOutputs, + + #[error("Escrow amount mismatch: input_amount={input_amount} output_amount={output_amount}")] + EscrowAmountMismatch { + input_amount: u64, + output_amount: u64, + }, + + #[error("Failed to get transaction: {tx_id}")] + TransactionFetchError { tx_id: String }, + + #[error("External API error: {reason}")] + ExternalApiError { reason: String }, + + #[error("Block hash conversion error: {reason}")] + BlockHashConversionError { reason: String }, + + #[error("Transaction hash conversion error: {reason}")] + TransactionHashConversionError { reason: String }, + + #[error("Hub query error: {reason}")] + HubQueryError { reason: String }, + + #[error("Kaspa node error: {reason}")] + KaspaNodeError { reason: String }, + + #[error("Finality check error: tx_id={tx_id} reason={reason}")] + FinalityCheckError { tx_id: String, reason: String }, + + #[error( + "Escrow configuration mismatch: Hub anchor is at address {hub_anchor_address}, \ + but validator is configured with escrow {configured_escrow}. \ + This likely means the validator config was not updated after an escrow migration. \ + Please update kaspaValidatorsEscrow configuration to match the current escrow." + )] + EscrowConfigMismatch { + hub_anchor_address: String, + configured_escrow: String, + }, +} + +/// Validate that a HyperlaneMessage matches expected static fields. +/// Checks version, origin, sender, destination, and recipient. +/// Nonce and body are not checked as they vary per message. +pub fn validate_hl_message_fields( + expected: &HyperlaneMessage, + actual: &HyperlaneMessage, +) -> Result<(), ValidationError> { + if expected.version != actual.version { + return Err(ValidationError::HLMessageFieldMismatch { + field: "version".to_string(), + expected: expected.version.to_string(), + actual: actual.version.to_string(), + }); + } + if expected.origin != actual.origin { + return Err(ValidationError::HLMessageFieldMismatch { + field: "origin".to_string(), + expected: expected.origin.to_string(), + actual: actual.origin.to_string(), + }); + } + if expected.sender != actual.sender { + return Err(ValidationError::HLMessageFieldMismatch { + field: "sender".to_string(), + expected: format!("{:?}", expected.sender), + actual: format!("{:?}", actual.sender), + }); + } + if expected.destination != actual.destination { + return Err(ValidationError::HLMessageFieldMismatch { + field: "destination".to_string(), + expected: expected.destination.to_string(), + actual: actual.destination.to_string(), + }); + } + if expected.recipient != actual.recipient { + return Err(ValidationError::HLMessageFieldMismatch { + field: "recipient".to_string(), + expected: format!("{:?}", expected.recipient), + actual: format!("{:?}", actual.recipient), + }); + } + Ok(()) +} diff --git a/rust/main/chains/dymension-kaspa/src/validator/migration.rs b/rust/main/chains/dymension-kaspa/src/validator/migration.rs new file mode 100644 index 00000000000..ada8978388e --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/validator/migration.rs @@ -0,0 +1,288 @@ +use crate::ops::migration::MigrationFXG; +use crate::ops::payload::MessageIDs; +use crate::ops::withdraw::query_hub_anchor; +use crate::validator::error::ValidationError; +use crate::validator::withdraw::{ + calculate_escrow_input_sum, escrow_input_selector, safe_bundle, sign_pskt_bundle, +}; +use dym_kas_core::escrow::EscrowPublic; +use dym_kas_core::pskt::is_valid_sighash_type; +use eyre::Result; +use hyperlane_cosmos::native::ModuleQueryClient; +use kaspa_addresses::Address; +use kaspa_bip32::secp256k1::Keypair as SecpKeypair; +use kaspa_consensus_core::tx::{ScriptPublicKey, TransactionOutpoint}; +use kaspa_rpc_core::api::rpc::RpcApi; +use kaspa_txscript::pay_to_address_script; +use kaspa_wallet_pskt::prelude::*; +use kaspa_wallet_pskt::pskt::{Signer, PSKT}; +use tracing::info; + +/// UTXO with outpoint and amount from Kaspa query. +#[derive(Debug, Clone)] +struct UtxoWithAmount { + outpoint: TransactionOutpoint, + amount: u64, +} + +/// Validate and sign a migration PSKT. +/// +/// Migration validation checks: +/// 1. Query hub for current anchor and verify PSKT spends it +/// 2. Query Kaspa for ALL escrow UTXOs and verify PSKT spends ALL of them with correct amounts +/// 3. Verify exactly ONE output goes to the configured migration target address +/// 4. Verify escrow funds are 100% preserved (escrow_input_sum == target_output) +/// 5. Verify payload is empty MessageIDs (no withdrawals processed) +/// 6. Allow relayer fee inputs (non-escrow inputs are permitted) +/// 7. Allow relayer change outputs (additional outputs beyond migration target) +pub async fn validate_sign_migration_fxg( + fxg: MigrationFXG, + escrow_public: EscrowPublic, + migration_target_address: &Address, + hub_rpc: &ModuleQueryClient, + kaspa_rpc: &R, + load_key: F, +) -> Result +where + F: FnOnce() -> Fut, + Fut: std::future::Future>, + R: RpcApi + ?Sized, +{ + let bundle = safe_bundle(&fxg.bundle) + .map_err(|e| eyre::eyre!("Safe bundle validation failed: {e:?}"))?; + + validate_migration_bundle( + &bundle, + &escrow_public, + migration_target_address, + hub_rpc, + kaspa_rpc, + ) + .await?; + + info!("Validator: migration PSKT is valid"); + + // Sign escrow inputs using the same selector as withdrawals + let signed = sign_pskt_bundle( + &bundle, + load_key, + Some(escrow_input_selector(&escrow_public)), + ) + .await + .map_err(|e| eyre::eyre!("Failed to sign migration: {e}"))?; + + Ok(signed) +} + +async fn validate_migration_bundle( + bundle: &Bundle, + escrow_public: &EscrowPublic, + migration_target_address: &Address, + hub_rpc: &ModuleQueryClient, + kaspa_rpc: &R, +) -> Result<(), ValidationError> +where + R: RpcApi + ?Sized, +{ + // Migration must be a single PSKT - no chaining needed for migration + if bundle.0.len() != 1 { + return Err(ValidationError::FailedGeneralVerification { + reason: format!( + "Migration bundle must contain exactly 1 PSKT, got {}", + bundle.0.len() + ), + }); + } + + // Query hub for current anchor (uses withdrawal_status with empty list) + let hub_anchor = + query_hub_anchor(hub_rpc) + .await + .map_err(|e| ValidationError::HubQueryError { + reason: e.to_string(), + })?; + info!( + tx_id = %hub_anchor.transaction_id, + index = hub_anchor.index, + "Migration: got hub anchor" + ); + + // Query Kaspa for ALL escrow UTXOs (with amounts for verification) + let escrow_utxos = query_escrow_utxos_with_amounts(kaspa_rpc, &escrow_public.addr).await?; + info!( + utxo_count = escrow_utxos.len(), + "Migration: got escrow UTXOs" + ); + + if escrow_utxos.is_empty() { + return Err(ValidationError::FailedGeneralVerification { + reason: "No UTXOs found at escrow address".to_string(), + }); + } + + let target_script = pay_to_address_script(migration_target_address); + let pskt_inner = + bundle + .iter() + .next() + .ok_or_else(|| ValidationError::FailedGeneralVerification { + reason: "Bundle was empty".to_string(), + })?; + let pskt = PSKT::::from(pskt_inner.clone()); + + validate_migration_pskt( + &pskt, + &hub_anchor, + &escrow_utxos, + escrow_public, + &target_script, + )?; + + Ok(()) +} + +async fn query_escrow_utxos_with_amounts( + kaspa_rpc: &R, + escrow_addr: &Address, +) -> Result, ValidationError> +where + R: RpcApi + ?Sized, +{ + let utxos = kaspa_rpc + .get_utxos_by_addresses(vec![escrow_addr.clone()]) + .await + .map_err(|e| ValidationError::FailedGeneralVerification { + reason: format!("Query Kaspa escrow UTXOs: {}", e), + })?; + + Ok(utxos + .into_iter() + .map(|u| UtxoWithAmount { + outpoint: TransactionOutpoint::from(u.outpoint), + amount: u.utxo_entry.amount, + }) + .collect()) +} + +fn validate_migration_pskt( + pskt: &PSKT, + hub_anchor: &TransactionOutpoint, + escrow_utxos: &[UtxoWithAmount], + escrow_public: &EscrowPublic, + target_script: &ScriptPublicKey, +) -> Result<(), ValidationError> { + // Check sighash types + if pskt + .inputs + .iter() + .any(|input| !is_valid_sighash_type(input.sighash_type)) + { + return Err(ValidationError::SigHashType); + } + + // Verify hub anchor is spent + let spends_hub_anchor = pskt + .inputs + .iter() + .any(|i| &i.previous_outpoint == hub_anchor); + + if !spends_hub_anchor { + return Err(ValidationError::AnchorNotFound { o: *hub_anchor }); + } + + // Verify ALL escrow UTXOs are spent with correct amounts + for expected in escrow_utxos { + let input = pskt + .inputs + .iter() + .find(|i| i.previous_outpoint == expected.outpoint) + .ok_or_else(|| ValidationError::FailedGeneralVerification { + reason: format!( + "Migration PSKT missing escrow UTXO: {}:{}", + expected.outpoint.transaction_id, expected.outpoint.index + ), + })?; + + // Verify UTXO entry exists (don't silently default to 0) + let utxo_entry = input.utxo_entry.as_ref().ok_or_else(|| { + ValidationError::FailedGeneralVerification { + reason: format!( + "Migration PSKT input {}:{} missing UTXO entry", + expected.outpoint.transaction_id, expected.outpoint.index + ), + } + })?; + + // Verify amount matches what Kaspa reports + if utxo_entry.amount != expected.amount { + return Err(ValidationError::FailedGeneralVerification { + reason: format!( + "Migration PSKT input {}:{} amount mismatch: PSKT={}, Kaspa={}", + expected.outpoint.transaction_id, + expected.outpoint.index, + utxo_entry.amount, + expected.amount + ), + }); + } + } + + // Relayer fee inputs are allowed - we only need to verify: + // 1. All escrow UTXOs are spent (checked above) + // 2. Hub anchor is spent (checked above) + // 3. Non-escrow inputs are permitted (relayer pays fees from their own UTXOs) + + // Verify payload is empty MessageIDs (migration TX processes no withdrawals) + let expected_payload = MessageIDs::new(vec![]).to_bytes(); + let actual_payload = pskt.global.payload.clone().unwrap_or_default(); + + if actual_payload != expected_payload { + return Err(ValidationError::FailedGeneralVerification { + reason: "Migration PSKT payload must be empty MessageIDs".to_string(), + }); + } + + // Calculate escrow input sum + let escrow_input_sum = calculate_escrow_input_sum(pskt, escrow_public); + + // Find exactly ONE output to migration target with correct amount + // Additional outputs are allowed (relayer change from fee inputs) + let target_outputs: Vec<_> = pskt + .outputs + .iter() + .filter(|o| &o.script_public_key == target_script) + .collect(); + + if target_outputs.len() != 1 { + return Err(ValidationError::FailedGeneralVerification { + reason: format!( + "Migration PSKT must have exactly 1 output to target address, got {}", + target_outputs.len() + ), + }); + } + + let target_output_amount = target_outputs[0].amount; + + // Verify escrow funds are 100% preserved: escrow_input_sum == target_output + // Relayer pays fees from their own inputs, so escrow funds should be fully transferred + if escrow_input_sum != target_output_amount { + return Err(ValidationError::EscrowAmountMismatch { + input_amount: escrow_input_sum, + output_amount: target_output_amount, + }); + } + + // Calculate total for logging (includes relayer fee inputs) + let total_inputs_sum: u64 = escrow_utxos.iter().map(|u| u.amount).sum(); + + info!( + escrow_input_sum, + target_output_amount, + total_inputs_sum, + num_outputs = pskt.outputs.len(), + "Migration PSKT validated: escrow funds fully preserved" + ); + + Ok(()) +} diff --git a/rust/main/chains/dymension-kaspa/src/validator/mod.rs b/rust/main/chains/dymension-kaspa/src/validator/mod.rs new file mode 100644 index 00000000000..99372bea2c9 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/validator/mod.rs @@ -0,0 +1,12 @@ +pub mod confirmation; +pub mod deposit; +pub mod error; +pub mod migration; +pub mod server; +pub mod signer; +pub mod startup; +pub mod withdraw; + +pub use kaspa_bip32::secp256k1::Keypair as KaspaSecpKeypair; +pub use server::*; +pub use startup::{check_migration_lock, write_migration_lock, MigrationLockError}; diff --git a/rust/main/chains/dymension-kaspa/src/validator/server.rs b/rust/main/chains/dymension-kaspa/src/validator/server.rs new file mode 100644 index 00000000000..260aba02cdb --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/validator/server.rs @@ -0,0 +1,617 @@ +use super::confirmation::validate_confirmed_withdrawals; +use super::deposit::{validate_new_deposit, MustMatch as DepositMustMatch}; +use super::migration::validate_sign_migration_fxg; +use super::withdraw::{validate_sign_withdrawal_fxg, MustMatch as WithdrawMustMatch}; +pub use super::KaspaSecpKeypair; +use crate::conf::ValidatorStuff; +use crate::endpoints::*; +use crate::ops::deposit::DepositFXG; +use crate::ops::migration::MigrationFXG; +use crate::ops::{confirmation::ConfirmationFXG, withdraw::WithdrawFXG}; +use crate::providers::KaspaProvider; +use axum::{ + body::Bytes, + extract::{DefaultBodyLimit, State}, + http::StatusCode, + response::{IntoResponse, Json, Response}, + routing::{get, post}, + Router, +}; +use dym_kas_core::api::client::HttpClient; +use dym_kas_core::escrow::EscrowPublic; +use dym_kas_core::wallet::EasyKaspaWallet; +use eyre::Report; +use hyperlane_core::{ + Checkpoint, CheckpointWithMessageId, HyperlaneSignerExt, Signable, + SignedCheckpointWithMessageId, H256, +}; +use hyperlane_core::{ + HyperlaneChain, HyperlaneDomain, HyperlaneSigner, Signature as HLCoreSignature, +}; +use hyperlane_cosmos::{native::ModuleQueryClient, CosmosProvider}; +use hyperlane_cosmos_rs::dymensionxyz::dymension::kas::ProgressIndication; +use hyperlane_cosmos_rs::prost::Message; +use kaspa_wallet_pskt::prelude::*; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use sha3::{digest::Update, Digest, Keccak256}; +use std::sync::Arc; +use tower_http::limit::RequestBodyLimitLayer; +use tracing::{error, info}; + +/// Returns latest git commit hash at the time when agent was built. +/// +/// If .git was not present at the time of build, +/// the variable defaults to "VERGEN_IDEMPOTENT_OUTPUT". +pub fn git_sha() -> String { + env!("VERGEN_GIT_SHA").to_string() +} + +#[derive(Serialize)] +struct VersionResponse { + git_sha: String, +} + +#[derive(Serialize)] +struct ValidatorInfoResponse { + #[serde(skip_serializing_if = "Option::is_none")] + ism_address: Option, + #[serde(skip_serializing_if = "Option::is_none")] + escrow_pub: Option, +} + +#[derive(Serialize)] +struct MigrationStatusResponse { + is_migration_mode: bool, + migration_target: Option, +} + +#[derive(Clone)] +pub struct ValidatorISMSigningResources< + S: HyperlaneSigner + HyperlaneSignerExt + Send + Sync + 'static, + H: HyperlaneSigner + HyperlaneSignerExt + Clone + Send + Sync + 'static, +> { + direct_signer: Arc, + singleton_signer: H, +} + +impl< + S: HyperlaneSigner + HyperlaneSignerExt + Send + Sync + 'static, + H: HyperlaneSigner + HyperlaneSignerExt + Clone + Send + Sync + 'static, + > ValidatorISMSigningResources +{ + pub fn new(direct_signer: Arc, singleton_signer: H) -> Self { + Self { + direct_signer, + singleton_signer, + } + } + + pub async fn sign_with_fallback( + &self, + signable: T, + ) -> Result, eyre::Report> { + const RETRIES: usize = 5; + const RETRY_DELAY_MS: u64 = 100; + + for attempt in 0..RETRIES { + match self.direct_signer.sign(signable.clone()).await { + Ok(signed) => { + tracing::debug!(attempt, "Signed with direct signer"); + return Ok(signed); + } + Err(_err) => { + tokio::time::sleep(tokio::time::Duration::from_millis(RETRY_DELAY_MS)).await; + } + } + } + + Ok(self.singleton_signer.sign(signable).await?) + } + + pub fn ism_address(&self) -> hyperlane_core::H160 { + self.singleton_signer.eth_address() + } +} + +struct AppError(eyre::Report); + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + let err_msg = self.0.to_string(); + eprintln!("Validator error: {}", err_msg); + + // HTTP status code differentiation enables retryable vs non-retryable error handling in clients + let (status_code, response_body) = if err_msg.contains("not safe against reorg") { + // Use 202 Accepted for non-final deposits (retryable) + ( + StatusCode::ACCEPTED, + format!("Deposit not final: {}", err_msg), + ) + } else if err_msg.contains("Hub is not bootstrapped") { + // Use 503 Service Unavailable for infrastructure issues (retryable) + ( + StatusCode::SERVICE_UNAVAILABLE, + format!("Service unavailable: {}", err_msg), + ) + } else { + // Default to 500 for other validation errors + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Validation failed: {}", err_msg), + ) + }; + + (status_code, response_body).into_response() + } +} + +type HandlerResult = Result; + +pub fn router< + S: HyperlaneSigner + HyperlaneSignerExt + Send + Sync + 'static, + H: HyperlaneSigner + HyperlaneSignerExt + Clone + Send + Sync + 'static, +>( + resources: ValidatorServerResources, +) -> Router { + const WITHDRAWAL_BODY_LIMIT: usize = 10 * 1024 * 1024; // 10 MB for large PSKT bundles + const DEFAULT_BODY_LIMIT: usize = 2 * 1024 * 1024; // 2 MB for other routes + + Router::new() + .route( + ROUTE_VALIDATE_NEW_DEPOSITS, + post(respond_validate_new_deposits::) + .layer(RequestBodyLimitLayer::new(DEFAULT_BODY_LIMIT)), + ) + .route( + ROUTE_VALIDATE_CONFIRMED_WITHDRAWALS, + post(respond_validate_confirmed_withdrawals::) + .layer(RequestBodyLimitLayer::new(DEFAULT_BODY_LIMIT)), + ) + .route( + ROUTE_SIGN_PSKTS, + post(respond_sign_pskts::) + .layer(RequestBodyLimitLayer::new(WITHDRAWAL_BODY_LIMIT)), + ) + .route( + ROUTE_SIGN_MIGRATION, + post(respond_sign_migration::) + .layer(RequestBodyLimitLayer::new(WITHDRAWAL_BODY_LIMIT)), + ) + .route( + "/kaspa-ping", + post(respond_kaspa_ping::).layer(RequestBodyLimitLayer::new(DEFAULT_BODY_LIMIT)), + ) + .route("/version", get(respond_version::)) + .route("/validator-info", get(respond_validator_info::)) + .route("/migration-status", get(respond_migration_status::)) + .layer(DefaultBodyLimit::disable()) + .with_state(Arc::new(resources)) +} + +async fn respond_kaspa_ping< + S: HyperlaneSigner + HyperlaneSignerExt + Send + Sync + 'static, + H: HyperlaneSigner + HyperlaneSignerExt + Clone + Send + Sync + 'static, +>( + State(_): State>>, + _body: Bytes, +) -> HandlerResult> { + error!("validator server: got kaspa ping"); + Ok(Json("pong".to_string())) +} + +async fn respond_version< + S: HyperlaneSigner + HyperlaneSignerExt + Send + Sync + 'static, + H: HyperlaneSigner + HyperlaneSignerExt + Clone + Send + Sync + 'static, +>( + State(_): State>>, +) -> HandlerResult> { + info!("validator: version requested"); + Ok(Json(VersionResponse { git_sha: git_sha() })) +} + +async fn respond_validator_info< + S: HyperlaneSigner + HyperlaneSignerExt + Send + Sync + 'static, + H: HyperlaneSigner + HyperlaneSignerExt + Clone + Send + Sync + 'static, +>( + State(res): State>>, +) -> HandlerResult> { + info!("validator: info requested"); + + let ism_address = res.try_signing().map(|s| format!("{:?}", s.ism_address())); + + let escrow_pub = match res.try_kas_key_source() { + Some(key_source) => match key_source.load_keypair().await { + Ok(keypair) => Some(hex::encode(keypair.public_key().serialize())), + Err(e) => { + tracing::warn!(error = %e, "validator-info: load escrow keypair"); + None + } + }, + None => None, + }; + + Ok(Json(ValidatorInfoResponse { + ism_address, + escrow_pub, + })) +} + +async fn respond_migration_status< + S: HyperlaneSigner + HyperlaneSignerExt + Send + Sync + 'static, + H: HyperlaneSigner + HyperlaneSignerExt + Clone + Send + Sync + 'static, +>( + State(res): State>>, +) -> HandlerResult> { + info!("validator: migration status requested"); + let is_migration_mode = res.conf().is_migration_mode(); + let migration_target = res.conf().migrate_escrow_to.clone(); + Ok(Json(MigrationStatusResponse { + is_migration_mode, + migration_target, + })) +} + +#[derive(Clone)] +pub struct ValidatorServerResources< + S: HyperlaneSigner + HyperlaneSignerExt + Send + Sync + 'static, + H: HyperlaneSigner + HyperlaneSignerExt + Clone + Send + Sync + 'static, +> { + signing: Option>, + kas_provider: Option>, +} + +impl< + S: HyperlaneSigner + HyperlaneSignerExt + Send + Sync + 'static, + H: HyperlaneSigner + HyperlaneSignerExt + Clone + Send + Sync + 'static, + > ValidatorServerResources +{ + pub fn new( + signing: ValidatorISMSigningResources, + kas_provider: Box, + ) -> Self { + Self { + signing: Some(signing), + kas_provider: Some(kas_provider), + } + } + fn must_signing(&self) -> &ValidatorISMSigningResources { + self.signing.as_ref().unwrap() + } + + fn try_signing(&self) -> Option<&ValidatorISMSigningResources> { + self.signing.as_ref() + } + + fn kas_key_source(&self) -> crate::conf::KaspaEscrowKeySource { + self.kas_provider.as_ref().unwrap().kas_key_source().clone() + } + + fn try_kas_key_source(&self) -> Option { + self.kas_provider + .as_ref() + .and_then(|p| p.try_kas_key_source().cloned()) + } + + fn must_escrow(&self) -> EscrowPublic { + self.kas_provider.as_ref().unwrap().escrow() + } + + fn must_wallet(&self) -> &EasyKaspaWallet { + self.kas_provider.as_ref().unwrap().wallet() + } + + fn must_hub_rpc(&self) -> &CosmosProvider { + self.kas_provider.as_ref().unwrap().hub_rpc() + } + + pub fn must_kas_domain(&self) -> &HyperlaneDomain { + self.kas_provider.as_ref().unwrap().domain() + } + + fn must_rest_client(&self) -> &HttpClient { + &self.kas_provider.as_ref().unwrap().rest().client.client + } + + fn must_val_stuff(&self) -> &ValidatorStuff { + self.kas_provider.as_ref().unwrap().must_validator_stuff() + } + + fn conf(&self) -> &crate::conf::ConnectionConf { + self.kas_provider.as_ref().unwrap().conf() + } + + fn must_kaspa_grpc_client(&self) -> kaspa_grpc_client::GrpcClient { + self.kas_provider + .as_ref() + .unwrap() + .grpc_client() + .expect("gRPC client required for validator") + } +} + +impl< + S: HyperlaneSigner + HyperlaneSignerExt + Send + Sync + 'static, + H: HyperlaneSigner + HyperlaneSignerExt + Clone + Send + Sync + 'static, + > Default for ValidatorServerResources +{ + fn default() -> Self { + Self { + signing: None, + kas_provider: None, + } + } +} + +async fn respond_validate_new_deposits< + S: HyperlaneSigner + HyperlaneSignerExt + Send + Sync + 'static, + H: HyperlaneSigner + HyperlaneSignerExt + Clone + Send + Sync + 'static, +>( + State(res): State>>, + body: Bytes, +) -> HandlerResult> { + info!("validator: checking new kaspa deposit"); + + // Migration mode: deposits are disabled + if res.conf().is_migration_mode() { + return Err(AppError(eyre::eyre!( + "Migration mode active: deposits disabled" + ))); + } + + let deposits: DepositFXG = body.try_into().map_err(|e: eyre::Report| AppError(e))?; + if res.must_val_stuff().toggles.validate_deposits { + validate_new_deposit( + res.must_rest_client(), + &deposits, + &res.must_wallet().net, + &res.must_escrow().addr, + res.must_hub_rpc(), + DepositMustMatch::new( + res.must_val_stuff().hub_domain, + res.must_val_stuff().hub_token_id, + res.must_val_stuff().kas_domain, + res.must_val_stuff().kas_token_placeholder, + ), + res.must_kaspa_grpc_client(), + ) + .await + .map_err(|e| { + eprintln!("Deposit validation failed: {:?}", e); + AppError(Report::from(e)) + })?; + } + info!( + message_id = ?deposits.hl_message.id(), + "validator: deposit is valid" + ); + + let msg_id = deposits.hl_message.id(); + let domain = deposits.hl_message.origin; + + let zero_array = [0u8; 32]; + let to_sign: CheckpointWithMessageId = CheckpointWithMessageId { + checkpoint: Checkpoint { + mailbox_domain: domain, + merkle_tree_hook_address: H256::from_slice(&zero_array), + root: H256::from_slice(&zero_array), + index: 0, + }, + message_id: msg_id, + }; + + let sig = res + .must_signing() + .sign_with_fallback(to_sign) + .await + .map_err(AppError)?; + info!("validator: signed deposit"); + + Ok(Json(sig)) +} + +async fn respond_sign_pskts< + S: HyperlaneSigner + HyperlaneSignerExt + Send + Sync + 'static, + H: HyperlaneSigner + HyperlaneSignerExt + Clone + Send + Sync + 'static, +>( + State(res): State>>, + body: Bytes, +) -> HandlerResult> { + info!("validator: signing pskts"); + + // Migration mode: withdrawals are disabled + if res.conf().is_migration_mode() { + return Err(AppError(eyre::eyre!( + "Migration mode active: withdrawals disabled" + ))); + } + + let fxg: WithdrawFXG = body.try_into().map_err(|e: Report| AppError(e))?; + let escrow = res.must_escrow(); + let val_stuff = res.must_val_stuff(); + + let kas_key_source = res.kas_key_source().clone(); + + let bundle = validate_sign_withdrawal_fxg( + fxg, + val_stuff.toggles.validate_withdrawals, + res.must_hub_rpc().query(), + escrow, + || async move { kas_key_source.load_keypair().await }, + WithdrawMustMatch::new( + res.must_wallet().net.address_prefix, + res.must_escrow(), + val_stuff.hub_domain, + val_stuff.hub_token_id, + val_stuff.kas_domain, + val_stuff.kas_token_placeholder, + val_stuff.hub_mailbox_id.clone(), + ), + ) + .await + .map_err(|e| { + eprintln!("Withdrawal validation and singing failed: {:?}", e); + AppError(e) + })?; + + Ok(Json(bundle)) +} + +#[derive(Clone)] +pub struct SignableProgressIndication { + progress_indication: ProgressIndication, +} + +impl SignableProgressIndication { + pub fn new(progress_indication: ProgressIndication) -> Self { + Self { + progress_indication, + } + } +} + +impl Serialize for SignableProgressIndication { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let encoded = self.progress_indication.encode_to_vec(); + serializer.serialize_bytes(&encoded) + } +} + +impl<'de> Deserialize<'de> for SignableProgressIndication { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes = Bytes::deserialize(deserializer)?; + let progress_indication = + ProgressIndication::decode(bytes.as_ref()).map_err(serde::de::Error::custom)?; + Ok(SignableProgressIndication { + progress_indication, + }) + } +} + +impl Signable for SignableProgressIndication { + fn signing_hash(&self) -> H256 { + // Byte derivation matches Hub code: https://github.com/dymensionxyz/dymension/blob/main/x/kas/types/signing.go + let mut bz = vec![]; + bz.extend( + self.progress_indication + .old_outpoint + .clone() + .unwrap() + .transaction_id, + ); + bz.extend( + self.progress_indication + .old_outpoint + .clone() + .unwrap() + .index + .to_be_bytes(), + ); + bz.extend( + self.progress_indication + .new_outpoint + .clone() + .unwrap() + .transaction_id, + ); + bz.extend( + self.progress_indication + .new_outpoint + .clone() + .unwrap() + .index + .to_be_bytes(), + ); + for w in self.progress_indication.processed_withdrawals.clone() { + bz.extend(w.message_id.as_bytes()); + } + H256::from_slice(Keccak256::new().chain(bz).finalize().as_slice()) + } +} + +async fn respond_validate_confirmed_withdrawals< + S: HyperlaneSigner + HyperlaneSignerExt + Send + Sync + 'static, + H: HyperlaneSigner + HyperlaneSignerExt + Clone + Send + Sync + 'static, +>( + State(res): State>>, + body: Bytes, +) -> HandlerResult> { + info!("validator: checking confirmed kaspa withdrawal"); + let conf_fxg: ConfirmationFXG = body.try_into().map_err(|e: eyre::Report| AppError(e))?; + + if res.must_val_stuff().toggles.validate_confirmations { + let src_escrow = &res.must_escrow().addr; + let migration_target = res.conf().parsed_migration_target(); + let dst_escrow = migration_target.as_ref().unwrap_or(src_escrow); + validate_confirmed_withdrawals(&conf_fxg, res.must_rest_client(), src_escrow, dst_escrow) + .await + .map_err(|e| { + eprintln!("Withdrawal confirmation validation failed: {:?}", e); + AppError(Report::from(e)) + })?; + info!("validator: confirmed withdrawal is valid"); + } + + let sig = res + .must_signing() + .sign_with_fallback(SignableProgressIndication::new( + conf_fxg.progress_indication.clone(), + )) + .await + .map_err(AppError)?; + + info!("validator: signed confirmed withdrawal"); + + Ok(Json(sig.signature)) +} + +async fn respond_sign_migration< + S: HyperlaneSigner + HyperlaneSignerExt + Send + Sync + 'static, + H: HyperlaneSigner + HyperlaneSignerExt + Clone + Send + Sync + 'static, +>( + State(res): State>>, + body: Bytes, +) -> HandlerResult> { + info!("validator: signing migration PSKT"); + + // Migration endpoint only works in migration mode + if !res.conf().is_migration_mode() { + return Err(AppError(eyre::eyre!( + "Migration signing requires migration mode to be active" + ))); + } + + let migration_target_addr = res.conf().parsed_migration_target().ok_or_else(|| { + AppError(eyre::eyre!( + "Migration target address not configured or invalid" + )) + })?; + + let fxg: MigrationFXG = body.try_into().map_err(|e: eyre::Report| AppError(e))?; + let escrow = res.must_escrow(); + let kas_key_source = res.kas_key_source().clone(); + let kaspa_grpc = res.must_kaspa_grpc_client(); + + let bundle = validate_sign_migration_fxg( + fxg, + escrow, + &migration_target_addr, + res.must_hub_rpc().query(), + &kaspa_grpc, + || async move { kas_key_source.load_keypair().await }, + ) + .await + .map_err(|e| { + eprintln!("Migration validation and signing failed: {:?}", e); + AppError(e) + })?; + + info!("validator: signed migration PSKT"); + + Ok(Json(bundle)) +} diff --git a/rust/main/chains/dymension-kaspa/src/validator/signer.rs b/rust/main/chains/dymension-kaspa/src/validator/signer.rs new file mode 100644 index 00000000000..768b9e6561c --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/validator/signer.rs @@ -0,0 +1,25 @@ +use ethers::signers::{LocalWallet, Signer}; +use ethers::utils::hex; +use kaspa_bip32::secp256k1::rand::thread_rng; + +pub struct EthereumStyleSigner { + pub address: String, + pub private_key: String, +} + +// Tested here: https://github.com/dymensionxyz/hyperlane-cosmos/blob/b0c2d20ccf5f8f02bfeab9ba9478e7c88d0ff91d/x/core/01_interchain_security/keeper/kas_test.go#L28-L30 +pub fn get_ethereum_style_signer() -> Result { + let wallet = LocalWallet::new(&mut thread_rng()); + + let private_key_bytes = wallet.signer().to_bytes(); + let private_key_hex = hex::encode(private_key_bytes).to_string(); + + let address = wallet.address(); + + let address_str = serde_json::to_string(&address).unwrap(); + + Ok(EthereumStyleSigner { + address: address_str, + private_key: private_key_hex, + }) +} diff --git a/rust/main/chains/dymension-kaspa/src/validator/startup.rs b/rust/main/chains/dymension-kaspa/src/validator/startup.rs new file mode 100644 index 00000000000..692ad1079a2 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/validator/startup.rs @@ -0,0 +1,95 @@ +//! Startup verification for Kaspa validators using local lock files. +//! +//! When a validator enters migration mode, it writes a lock file containing +//! the new escrow address. When it later starts in normal mode, it verifies +//! the configured escrow matches what was written, ensuring operators update +//! their config after migration. + +use std::fs; +use std::path::Path; +use tracing::info; + +const MIGRATION_LOCK_FILENAME: &str = "kaspa_migration.lock"; + +/// Write a migration lock file when entering migration mode. +/// +/// The lock file contains the new escrow address (migration target). +/// This file must be resolved before the validator can start in normal mode. +pub fn write_migration_lock(data_dir: &Path, new_escrow_address: &str) -> std::io::Result<()> { + let lock_path = data_dir.join(MIGRATION_LOCK_FILENAME); + fs::write(&lock_path, new_escrow_address)?; + info!( + lock_file = %lock_path.display(), + new_escrow = %new_escrow_address, + "Migration lock file written. After migration completes, update \ + kaspaValidatorsEscrow config to match this address and restart in normal mode." + ); + Ok(()) +} + +/// Check migration lock file when starting in normal mode. +/// +/// - If lock file exists and configured escrow matches: delete file, return Ok +/// - If lock file exists and configured escrow mismatches: return Err (should panic) +/// - If no lock file: return Ok (operator deleted it or never was in migration mode) +pub fn check_migration_lock( + data_dir: &Path, + configured_escrow: &str, +) -> Result<(), MigrationLockError> { + let lock_path = data_dir.join(MIGRATION_LOCK_FILENAME); + + if !lock_path.exists() { + // No lock file - either never migrated or operator manually cleared it + return Ok(()); + } + + let expected_escrow = + fs::read_to_string(&lock_path).map_err(|e| MigrationLockError::ReadError { + path: lock_path.display().to_string(), + reason: e.to_string(), + })?; + + let expected_escrow = expected_escrow.trim(); + + if configured_escrow == expected_escrow { + // Config matches - delete the lock file and continue + fs::remove_file(&lock_path).map_err(|e| MigrationLockError::DeleteError { + path: lock_path.display().to_string(), + reason: e.to_string(), + })?; + info!( + escrow = %configured_escrow, + "Migration lock file validated and removed. Escrow config is correct." + ); + Ok(()) + } else { + Err(MigrationLockError::EscrowMismatch { + lock_file: lock_path.display().to_string(), + expected: expected_escrow.to_string(), + configured: configured_escrow.to_string(), + }) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum MigrationLockError { + #[error( + "Escrow configuration mismatch after migration!\n\ + Lock file: {lock_file}\n\ + Expected escrow (from migration): {expected}\n\ + Configured escrow: {configured}\n\n\ + Please update kaspaValidatorsEscrow in your config to: {expected}\n\ + Or if you're sure your config is correct, delete the lock file manually." + )] + EscrowMismatch { + lock_file: String, + expected: String, + configured: String, + }, + + #[error("Failed to read migration lock file {path}: {reason}")] + ReadError { path: String, reason: String }, + + #[error("Failed to delete migration lock file {path}: {reason}")] + DeleteError { path: String, reason: String }, +} diff --git a/rust/main/chains/dymension-kaspa/src/validator/withdraw.rs b/rust/main/chains/dymension-kaspa/src/validator/withdraw.rs new file mode 100644 index 00000000000..4f3b0f52c48 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/validator/withdraw.rs @@ -0,0 +1,490 @@ +// We call the signers 'validators' + +use crate::consts::ALLOWED_HL_MESSAGE_VERSION; +use crate::ops::addr::h256_to_script_pubkey; +use crate::ops::payload::MessageIDs; +use crate::ops::withdraw::{filter_pending_withdrawals, WithdrawFXG}; +use crate::validator::error::{validate_hl_message_fields, ValidationError}; +use dym_kas_core::escrow::*; +use dym_kas_core::pskt::is_valid_sighash_type; +use eyre::Result; +use hex::ToHex; +use hyperlane_core::{Decode, HyperlaneMessage, H256}; +use hyperlane_cosmos::native::ModuleQueryClient; +use hyperlane_warp_route::TokenMessage; +use kaspa_addresses::Prefix as KaspaAddrPrefix; +use kaspa_bip32::secp256k1::Keypair as SecpKeypair; +use kaspa_consensus_core::tx::{ScriptPublicKey, TransactionOutpoint}; +use kaspa_wallet_pskt::prelude::*; +use kaspa_wallet_pskt::pskt::{Inner, Input, Signer, PSKT}; +use std::collections::hash_map::Entry; +use std::collections::{HashMap, HashSet}; +use std::io::Cursor; +use tracing::{debug, info}; + +/// Returns an input selector closure that matches escrow inputs by redeem script. +/// Used by both migration and withdrawal signing. +pub fn escrow_input_selector(escrow_public: &EscrowPublic) -> impl Fn(&Input) -> bool + '_ { + move |i: &Input| match i.redeem_script.as_ref() { + Some(rs) => rs == &escrow_public.redeem_script, + None => false, + } +} + +/// Calculate the sum of escrow inputs in a PSKT. +/// Used by both migration and withdrawal validation. +pub fn calculate_escrow_input_sum(pskt: &PSKT, escrow_public: &EscrowPublic) -> u64 { + pskt.inputs.iter().fold(0, |acc, i| { + let rs = i.redeem_script.clone().unwrap_or_default(); + if rs == escrow_public.redeem_script { + acc + i.utxo_entry.as_ref().map_or(0, |u| u.amount) + } else { + acc + } + }) +} + +#[derive(Clone)] +pub struct MustMatch { + address_prefix: KaspaAddrPrefix, + escrow_public: EscrowPublic, + partial_message: HyperlaneMessage, + hub_mailbox_id: String, +} + +impl MustMatch { + pub fn new( + address_prefix: KaspaAddrPrefix, + escrow_public: EscrowPublic, + hub_domain: u32, + hub_token_id: H256, + kas_domain: u32, + kas_token_placeholder: H256, // a fake value, since Kaspa does not have a 'token' smart contract. Howevert his value must be consistent with hub config. + hub_mailbox_id: String, + ) -> Self { + Self { + address_prefix, + escrow_public, + partial_message: HyperlaneMessage { + version: ALLOWED_HL_MESSAGE_VERSION, + nonce: 0, + origin: hub_domain, + sender: hub_token_id, + destination: kas_domain, + recipient: kas_token_placeholder, + body: vec![], + }, + hub_mailbox_id, + } + } + + fn is_match(&self, other: &HyperlaneMessage) -> Result<()> { + validate_hl_message_fields(&self.partial_message, other).map_err(|e| eyre::eyre!("{}", e)) + } +} + +pub async fn validate_sign_withdrawal_fxg( + fxg: WithdrawFXG, + validation_enabled: bool, + cosmos: &ModuleQueryClient, + escrow_public: EscrowPublic, + load_key: F, + must_match: MustMatch, +) -> Result +where + F: FnOnce() -> Fut, + Fut: std::future::Future>, +{ + // !! Safe bundle can be considered part of the validation, strictly speaking + let b = safe_bundle(&fxg.bundle) + .map_err(|e| eyre::eyre!("Safe bundle validation failed: {e:?}"))?; + + // Call to validator.G() + if validation_enabled { + validate_withdrawal_batch(&b, &fxg.messages, cosmos, must_match) + .await + .map_err(|e| eyre::eyre!("Withdrawal validation failed: {:?}", e))?; + + info!("Validator: pskts are valid"); + } + + // Only sign escrow inputs + let bundle = sign_pskt_bundle(&b, load_key, Some(escrow_input_selector(&escrow_public))) + .await + .map_err(|e| eyre::eyre!("Failed to sign withdrawal: {e}"))?; + + Ok(bundle) +} + +/// Validate WithdrawFXG received from the relayer against Kaspa and Hub. +/// It verifies that: +/// (0) All messages should have Kaspa domain. +/// (1) No double spending allowed. All messages must be unique. +/// (2) Each message is actually dispatched on the Hub. Achieved by `CosmosGrpcClient.delivered`. +/// Consequence: `delivered` ensures that the HL message hash in known on the Hub, +/// which verifies the correctness of all the HL message fields. +/// (3) The messages are not yet marked as processed on the Hub. +/// (4) The anchor UTXO provided by the relayer is actually still the anchor on the Hub. +/// (5) The Kaspa TXs are a linked sequence. The first PSKT contains Hub anchor in inputs. +/// (6) Check PSKT: +/// - The Kaspa TXs have corresponding message IDs in their payload (msg ID == msg hash). +/// Consequence: Each message actually hashes to the hash stored in the payload. +/// - Correct sighash type in inputs +/// - We validate agains the safe bundle (see `safe_bundle`), so assume that +/// the relayer must use no lock time and a default TX version +/// (7) TX UTXO spends actually correspond to the message content. +/// (8) No message use escrow as a recipient. +/// (9) Each PSKT has exactly one anchor. +/// +/// CONTRACT: the first anchor of `fxg.anchors` is the Hub anchor. +pub async fn validate_withdrawal_batch( + bundle: &Bundle, + messages: &[Vec], + cosmos: &ModuleQueryClient, + must_match: MustMatch, +) -> Result<(), ValidationError> { + let hub_anchor = validate_messages(messages, cosmos, &must_match).await?; + + // At this point we know + // - The set of messages is unique + // - All the messages are dispatched on the hub + // - None of the messages are already confirmed on the hub + + validate_pskts( + bundle, + messages, + hub_anchor, + must_match.escrow_public, + must_match.address_prefix, + )?; + + info!("Withdrawal validation completed successfully for withdrawals"); + + Ok(()) +} + +async fn validate_messages( + messages: &[Vec], + cosmos: &ModuleQueryClient, + must_match: &MustMatch, +) -> Result { + let messages: Vec = messages.iter().flatten().cloned().collect(); + let num_msgs = messages.len(); + debug!( + "Starting withdrawal validation for messages, num_msgs: {}", + num_msgs + ); + let msg_ids: Vec = messages.iter().map(|m| m.id()).collect(); + let mut seen = HashSet::new(); + if let Some(duplicate) = msg_ids.iter().find(|id| !seen.insert(*id)) { + let message_id = duplicate.encode_hex(); + return Err(ValidationError::DoubleSpending { message_id }); + } + for msg in messages.iter() { + if let Err(e) = must_match.is_match(msg) { + return Err(ValidationError::FailedGeneralVerification { + reason: e.to_string(), + }); + } + } + for id in msg_ids { + let res = cosmos + .delivered(must_match.hub_mailbox_id.clone(), id.encode_hex()) + .await + .map_err(|e| ValidationError::HubQueryError { + reason: e.to_string(), + })?; + + // Delivered is a confusing name. `delivered` is just the name of the network query. + let was_dispatched_on_hub = res.delivered; + if !was_dispatched_on_hub { + let message_id = id.encode_hex(); + return Err(ValidationError::MessageNotDispatched { message_id }); + } + } + debug!("All withdrawal fxg messages are dispatched on hub"); + let (hub_anchor, pending_messages) = filter_pending_withdrawals(messages, cosmos) + .await + .map_err(|e| ValidationError::HubQueryError { + reason: format!("Failed to get pending withdrawals: {}", e), + })?; + if num_msgs != pending_messages.len() { + return Err(ValidationError::MessagesNotUnprocessed); + } + debug!("All withdrawal fxg messages are unprocessed on hub"); + Ok(hub_anchor) +} + +pub fn validate_pskts( + bundle: &Bundle, + expected_messages: &[Vec], + hub_anchor: TransactionOutpoint, + escrow_public: EscrowPublic, + address_prefix: KaspaAddrPrefix, +) -> Result<(), ValidationError> { + if bundle.0.len() != expected_messages.len() { + return Err(ValidationError::MessageCacheLengthMismatch { + expected: bundle.0.len(), + actual: expected_messages.len(), + }); + } + + // PSKTs must be linked by anchor, starting with the current hub anchor + let mut anchor_to_spend = hub_anchor; + for (idx, pskt) in bundle.iter().enumerate() { + let messages = expected_messages.get(idx).unwrap(); + + anchor_to_spend = validate_pskt( + PSKT::::from(pskt.clone()), + anchor_to_spend, + messages, + &escrow_public, + address_prefix, + )?; + } + + Ok(()) +} + +pub fn validate_pskt( + pskt: PSKT, + must_spend: TransactionOutpoint, + expected_messages: &Vec, + escrow_public: &EscrowPublic, + address_prefix: KaspaAddrPrefix, +) -> Result { + validate_pskt_impl_details(&pskt)?; + + let tx_id = pskt.calculate_id(); + + // If there are no messages and payload is empty, then the PSKT + // is a sweeping tx which does not spend the anchor + let tx_type = if expected_messages.is_empty() { + info!("PSKT is a sweeping tx: {tx_id}"); + TxType::Sweeping + } else { + info!("PSKT is a withdrawal tx: {tx_id}"); + TxType::Withdrawal + }; + + let ix = validate_pskt_application_semantics( + &pskt, + must_spend, + tx_type, + expected_messages, + escrow_public, + address_prefix, + )?; + + match tx_type { + // In case of the sweeping tx, try to spend the anchor in the next PSKT + TxType::Sweeping => Ok(must_spend), + TxType::Withdrawal => Ok(TransactionOutpoint::new(pskt.calculate_id(), ix)), + } +} + +fn validate_pskt_impl_details(pskt: &PSKT) -> Result<(), ValidationError> { + if pskt + .inputs + .iter() + .any(|input| !is_valid_sighash_type(input.sighash_type)) + { + return Err(ValidationError::SigHashType); + } + + Ok(()) +} + +#[derive(Clone, Copy)] +enum TxType { + Sweeping, + Withdrawal, +} + +fn validate_pskt_application_semantics( + pskt: &PSKT, + current_anchor: TransactionOutpoint, + tx_type: TxType, + expected_messages: &Vec, + escrow_public: &EscrowPublic, + address_prefix: KaspaAddrPrefix, +) -> Result { + let anchor_found = pskt + .inputs + .iter() + .any(|input| input.previous_outpoint == current_anchor); + + // Check that the PSKT is a sweeping tx or a withdrawal tx. + // If it is a sweeping tx, then the anchor must not be spent. + // If it is a withdrawal tx, then the anchor must be spent. + match (tx_type, anchor_found) { + (TxType::Sweeping, true) => { + return Err(ValidationError::AnchorSpent { o: current_anchor }); + } + (TxType::Withdrawal, false) => { + return Err(ValidationError::AnchorNotFound { o: current_anchor }); + } + _ => {} + } + + let payload_expect = MessageIDs::from(expected_messages).to_bytes(); + + let payload_actual = pskt.global.payload.clone().unwrap_or_default(); + + if payload_actual != payload_expect { + return Err(ValidationError::PayloadMismatch); + } + + // Check that UTXO outputs align with withdrawals + let escrow_inputs_sum = calculate_escrow_input_sum(pskt, escrow_public); + + // Construct a multiset of expected outputs from HL messages. + // Key: recipiend + amount + // Value: number of entries + // + // Such structure accounts for cases where one address might send several transfers + // with the same amount. + let mut expected_outputs: HashMap<(u64, ScriptPublicKey), i32> = HashMap::new(); + + for m in expected_messages { + let tm = TokenMessage::read_from(&mut Cursor::new(&m.body)).map_err(|e| { + ValidationError::PayloadParseError { + reason: format!("Failed to parse TokenMessage from message body: {}", e), + } + })?; + + let recipient = h256_to_script_pubkey(tm.recipient(), address_prefix); + + // There are no withdrawals where escrow is set + // as recepient. It would drastically complicate the confirmation flow. + if recipient == escrow_public.p2sh { + let message_id = m.id().encode_hex(); + return Err(ValidationError::EscrowWithdrawalNotAllowed { message_id }); + } + + let key = (tm.amount().as_u64(), recipient); + *expected_outputs.entry(key).or_default() += 1; + } + + // Ensure that all HL messages have outputs. + // Also, calculate the total output amount of withdrawals + escrow change, + // it should match the input escrow amount. + let mut escrow_outputs_sum = 0; + let mut next_anchor_idx: Option = None; + for (idx, output) in pskt.outputs.iter().enumerate() { + let key = (output.amount, output.script_public_key.clone()); + + let e = expected_outputs.entry(key).and_modify(|v| *v -= 1); + if let Entry::Occupied(e) = e { + escrow_outputs_sum += output.amount; + if *e.get() == 0 { + e.remove(); + } + continue; + } + + // Check that output is an anchor + if output.script_public_key == escrow_public.p2sh { + // Abort if there is more than one anchor candidate + if next_anchor_idx.is_some() { + return Err(ValidationError::MultipleAnchors); + } + + escrow_outputs_sum += output.amount; + next_anchor_idx = Some(idx as u32); + } + } + + // expected_outputs contains the number of occurrences of (recipiend; amount) pairs. + // If it is empty, then all the occurrences are covered by the Kaspa TX. + if !expected_outputs.is_empty() { + return Err(ValidationError::MissingOutputs); + } + + // Verify that the input of escrow funds equals to the output of escrow funds: + // Input == output == escrow change + sum(withdrawals) + if escrow_inputs_sum != escrow_outputs_sum { + return Err(ValidationError::EscrowAmountMismatch { + input_amount: escrow_inputs_sum, + output_amount: escrow_outputs_sum, + }); + } + + // In case of the sweeping tx, next_anchor_idx is not an anchor, + // but a swept output. It shouldn't necessarily be spent on the next iteration. + // But it still should be present in the PSKT. + next_anchor_idx.ok_or(ValidationError::NextAnchorNotFound) +} + +/// Sign a PSKT bundle with an optional input filter. +/// Used by both withdrawal and migration signing. +pub async fn sign_pskt_bundle( + bundle: &Bundle, + load_key: F, + input_filter: Option bool>, +) -> Result +where + F: FnOnce() -> Fut, + Fut: std::future::Future>, +{ + let keypair = load_key() + .await + .map_err(|e| eyre::eyre!("load keypair for signing: {}", e))?; + + let mut signed = Vec::new(); + for pskt in bundle.iter() { + let pskt = PSKT::::from(pskt.clone()); + + let signed_pskt = + dym_kas_core::pskt::sign_pskt(pskt, &keypair, None, input_filter.as_ref())?; + + signed.push(signed_pskt); + } + info!("Validator: signed pskts"); + let bundle = Bundle::from(signed); + Ok(bundle) +} + +/// Load only the interesting fields of the PSKT which should be there +/// This means we don't have to validate all the other uninteresting fields one by one +/// The relayer should use no lock time and a default TX version +fn safe_pskt(unstrusted_inner: Inner) -> Result> { + let mut inner = Inner::default(); + inner.global.input_count = unstrusted_inner.inputs.len(); + inner.global.output_count = unstrusted_inner.outputs.len(); + inner.global.payload = unstrusted_inner.global.payload; + inner.global.version = unstrusted_inner.global.version; + + for input in unstrusted_inner.inputs.iter() { + let mut b = InputBuilder::default(); + if let Some(utxo_entry) = &input.utxo_entry { + b.utxo_entry(utxo_entry.clone()); + } + b.previous_outpoint(input.previous_outpoint); + if let Some(sig_op_count) = input.sig_op_count { + b.sig_op_count(sig_op_count); + } + b.sighash_type(input.sighash_type); + if let Some(redeem_script) = &input.redeem_script { + b.redeem_script(redeem_script.clone()); + } + inner.inputs.push(b.build()?); + } + + for output in unstrusted_inner.outputs.iter() { + let mut b = OutputBuilder::default(); + b.amount(output.amount); + b.script_public_key(output.script_public_key.clone()); + inner.outputs.push(b.build()?); + } + + Ok(PSKT::::from(inner)) +} + +pub fn safe_bundle(unstrusted_bundle: &Bundle) -> Result { + let mut items = Vec::new(); + for pskt in unstrusted_bundle.iter() { + items.push(safe_pskt(pskt.clone())?); + } + Ok(Bundle::from(items)) +} diff --git a/rust/main/chains/dymension-kaspa/src/validator_announce.rs b/rust/main/chains/dymension-kaspa/src/validator_announce.rs new file mode 100644 index 00000000000..846bd167e9e --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/validator_announce.rs @@ -0,0 +1,68 @@ +use async_trait::async_trait; +use hyperlane_core::{ + Announcement, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, + HyperlaneProvider, SignedType, TxOutcome, ValidatorAnnounce, H256, H512, U256, +}; + +use crate::KaspaProvider; + +#[derive(Debug)] +pub struct KaspaValidatorAnnounce { + domain: HyperlaneDomain, + address: H256, + provider: KaspaProvider, +} + +impl KaspaValidatorAnnounce { + pub fn new(provider: KaspaProvider, locator: ContractLocator) -> ChainResult { + Ok(Self { + domain: locator.domain.clone(), + address: locator.address, + provider, + }) + } +} + +impl HyperlaneContract for KaspaValidatorAnnounce { + fn address(&self) -> H256 { + self.address + } +} + +impl HyperlaneChain for KaspaValidatorAnnounce { + fn domain(&self) -> &HyperlaneDomain { + &self.domain + } + + fn provider(&self) -> Box { + Box::new(self.provider.clone()) + } +} + +#[async_trait] +impl ValidatorAnnounce for KaspaValidatorAnnounce { + async fn get_announced_storage_locations(&self, _: &[H256]) -> ChainResult>> { + // This value must match what was passed to the agent on CLI boot, it's not semantically used by the protocol + let c = "ARBITRARY_VALUE_FOOBAR"; + Ok(vec![vec![format!("file://{}", c)]]) + } + + async fn announce(&self, _: SignedType) -> ChainResult { + Ok(TxOutcome { + transaction_id: H512::from_slice( + b"0x0000000000000000000000000000000000000000000000000000000000000000", + ), + executed: true, + gas_used: 0.into(), + gas_price: 0.into(), + }) + } + + async fn announce_tokens_needed( + &self, + _announcement: SignedType, + _chain_signer: H256, + ) -> Option { + Some(0u64.into()) + } +} diff --git a/rust/main/chains/dymension-kaspa/src/withdrawal_utils.rs b/rust/main/chains/dymension-kaspa/src/withdrawal_utils.rs new file mode 100644 index 00000000000..2a351844207 --- /dev/null +++ b/rust/main/chains/dymension-kaspa/src/withdrawal_utils.rs @@ -0,0 +1,74 @@ +use crate::relayer::withdraw::minimum::is_dust_message; +use crate::relayer::KaspaBridgeMetrics; +use hyperlane_core::{HyperlaneMessage, U256}; +use std::collections::HashSet; +use tracing::info; + +pub enum WithdrawalStage { + Initiated, + Processed, + Failed, +} + +pub fn record_withdrawal_batch_metrics( + metrics: &KaspaBridgeMetrics, + messages: &[HyperlaneMessage], + stage: WithdrawalStage, +) { + match stage { + WithdrawalStage::Initiated => { + if !messages.is_empty() { + metrics.record_withdrawal_batch_size(messages.len() as u64); + } + for msg in messages { + if let Some(amount) = crate::hl_message::parse_withdrawal_amount(msg) { + let message_id = format!("{:?}", msg.id()); + metrics.record_withdrawal_initiated(&message_id, amount); + } + } + } + WithdrawalStage::Processed => { + for msg in messages { + if let Some(amount) = crate::hl_message::parse_withdrawal_amount(msg) { + let message_id = format!("{:?}", msg.id()); + metrics.record_withdrawal_processed(&message_id, amount); + } + } + } + WithdrawalStage::Failed => { + for msg in messages { + if let Some(amount) = crate::hl_message::parse_withdrawal_amount(msg) { + let message_id = format!("{:?}", msg.id()); + metrics.record_withdrawal_failed(&message_id, amount); + } + } + } + } +} + +pub fn calculate_failed_indexes( + all_msgs: &[HyperlaneMessage], + processed_msgs: &[HyperlaneMessage], + min_sompi: U256, +) -> Vec { + let processed_ids: HashSet<_> = processed_msgs.iter().map(|m| m.id()).collect(); + all_msgs + .iter() + .enumerate() + .filter_map(|(i, msg)| { + if processed_ids.contains(&msg.id()) { + return None; + } + // Exclude dust messages from failed indexes to prevent retry + if is_dust_message(msg, min_sompi) { + info!( + message_id = ?msg.id(), + min_sompi = min_sompi.as_u64(), + "kaspa mailbox: not retrying dust message" + ); + return None; + } + Some(i) + }) + .collect() +} diff --git a/rust/main/chains/hyperlane-cosmos-native/Cargo.toml b/rust/main/chains/hyperlane-cosmos-native/Cargo.toml deleted file mode 100644 index cbdb3ed6bc4..00000000000 --- a/rust/main/chains/hyperlane-cosmos-native/Cargo.toml +++ /dev/null @@ -1,50 +0,0 @@ -[package] -name = "hyperlane-cosmos-native" -documentation = { workspace = true } -edition = { workspace = true } -homepage = { workspace = true } -license-file = { workspace = true } -publish = { workspace = true } -version = { workspace = true } - -[dependencies] -async-trait = { workspace = true } -base64 = { workspace = true } -bech32 = { workspace = true } -cosmrs = { workspace = true, features = ["tokio", "grpc", "rpc"] } -crypto = { path = "../../utils/crypto" } -derive-new = { workspace = true } -futures = { workspace = true } -hex = { workspace = true } -http = { workspace = true } -hyper = { workspace = true } -hyper-tls = { workspace = true } -itertools = { workspace = true } -once_cell = { workspace = true } -protobuf = { workspace = true } -ripemd = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -sha2 = { workspace = true } -sha256 = { workspace = true } -tendermint = { workspace = true, features = ["rust-crypto", "secp256k1"] } -tendermint-rpc = { workspace = true } -time = { workspace = true } -thiserror = { workspace = true } -tokio = { workspace = true } -tonic = { workspace = true, features = [ - "transport", - "tls", - "tls-roots", - "tls-native-roots", -] } -tower.workspace = true -pin-project.workspace = true -tracing = { workspace = true } -tracing-futures = { workspace = true } -url = { workspace = true } -hyperlane-cosmos-rs = "0.1.4" - -hyperlane-metric = { path = "../../hyperlane-metric" } -hyperlane-core = { path = "../../hyperlane-core", features = ["async"] } -hyperlane-cosmwasm-interface = { workspace = true } diff --git a/rust/main/chains/hyperlane-cosmos-native/src/lib.rs b/rust/main/chains/hyperlane-cosmos-native/src/lib.rs deleted file mode 100644 index e73970599dc..00000000000 --- a/rust/main/chains/hyperlane-cosmos-native/src/lib.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! Implementation of hyperlane for the native cosmos module. - -#![forbid(unsafe_code)] -#![warn(missing_docs)] -#![deny(clippy::unwrap_used, clippy::panic)] - -mod error; -mod indexers; -mod ism; -mod libs; -mod mailbox; -mod prometheus; -mod providers; -mod signers; -mod trait_builder; -mod validator_announce; - -use self::libs::*; -pub use { - self::error::*, self::indexers::*, self::ism::*, self::mailbox::*, self::providers::*, - self::signers::*, self::trait_builder::*, self::validator_announce::*, -}; diff --git a/rust/main/chains/hyperlane-cosmos-native/src/libs/account.rs b/rust/main/chains/hyperlane-cosmos-native/src/libs/account.rs deleted file mode 100644 index f8d8c3453cd..00000000000 --- a/rust/main/chains/hyperlane-cosmos-native/src/libs/account.rs +++ /dev/null @@ -1,93 +0,0 @@ -use cosmrs::{crypto::PublicKey, AccountId}; -use hyperlane_cosmwasm_interface::types::keccak256_hash; -use tendermint::account::Id as TendermintAccountId; -use tendermint::public_key::PublicKey as TendermintPublicKey; - -use crypto::decompress_public_key; -use hyperlane_core::{AccountAddressType, ChainResult, H256}; - -use crate::HyperlaneCosmosError; - -pub(crate) struct CosmosAccountId<'a> { - account_id: &'a AccountId, -} - -impl<'a> CosmosAccountId<'a> { - pub fn new(account_id: &'a AccountId) -> Self { - Self { account_id } - } - - /// Calculate AccountId from public key depending on provided prefix - pub fn account_id_from_pubkey( - pub_key: PublicKey, - prefix: &str, - account_address_type: &AccountAddressType, - ) -> ChainResult { - match account_address_type { - AccountAddressType::Bitcoin => Self::bitcoin_style(pub_key, prefix), - AccountAddressType::Ethereum => Self::ethereum_style(pub_key, prefix), - } - } - - /// Returns a Bitcoin style address: RIPEMD160(SHA256(pubkey)) - /// Source: `` - fn bitcoin_style(pub_key: PublicKey, prefix: &str) -> ChainResult { - // Get the inner type - let tendermint_pub_key = TendermintPublicKey::from(pub_key); - // Get the RIPEMD160(SHA256(pub_key)) - let tendermint_id = TendermintAccountId::from(tendermint_pub_key); - // Bech32 encoding - let account_id = AccountId::new(prefix, tendermint_id.as_bytes()) - .map_err(Into::::into)?; - - Ok(account_id) - } - - /// Returns an Ethereum style address: KECCAK256(pubkey)[20] - /// Parameter `pub_key` is a compressed public key. - fn ethereum_style(pub_key: PublicKey, prefix: &str) -> ChainResult { - let decompressed_public_key = decompress_public_key(&pub_key.to_bytes()) - .map_err(Into::::into)?; - - let hash = keccak256_hash(&decompressed_public_key[1..]); - - let mut bytes = [0u8; 20]; - bytes.copy_from_slice(&hash.as_slice()[12..]); - - let account_id = - AccountId::new(prefix, bytes.as_slice()).map_err(Into::::into)?; - - Ok(account_id) - } -} - -impl TryFrom<&CosmosAccountId<'_>> for H256 { - type Error = HyperlaneCosmosError; - - /// Builds a H256 digest from a cosmos AccountId (Bech32 encoding) - fn try_from(account_id: &CosmosAccountId) -> Result { - let bytes = account_id.account_id.to_bytes(); - let h256_len = H256::len_bytes(); - let Some(start_point) = h256_len.checked_sub(bytes.len()) else { - // input is too large to fit in a H256 - let msg = "account address is too large to fit it a H256"; - return Err(HyperlaneCosmosError::AddressError(msg.to_owned())); - }; - let mut empty_hash = H256::default(); - let result = empty_hash.as_bytes_mut(); - result[start_point..].copy_from_slice(bytes.as_slice()); - Ok(H256::from_slice(result)) - } -} - -impl TryFrom> for H256 { - type Error = HyperlaneCosmosError; - - /// Builds a H256 digest from a cosmos AccountId (Bech32 encoding) - fn try_from(account_id: CosmosAccountId) -> Result { - (&account_id).try_into() - } -} - -#[cfg(test)] -mod tests; diff --git a/rust/main/chains/hyperlane-cosmos-native/src/libs/account/tests.rs b/rust/main/chains/hyperlane-cosmos-native/src/libs/account/tests.rs deleted file mode 100644 index c1e6c06e9d5..00000000000 --- a/rust/main/chains/hyperlane-cosmos-native/src/libs/account/tests.rs +++ /dev/null @@ -1,72 +0,0 @@ -use cosmrs::crypto::PublicKey; -use crypto::decompress_public_key; -use hyperlane_core::AccountAddressType; -use AccountAddressType::{Bitcoin, Ethereum}; - -use crate::CosmosAccountId; - -const COMPRESSED_PUBLIC_KEY: &str = - "02962d010010b6eec66846322704181570d89e28236796579c535d2e44d20931f4"; -const INJECTIVE_ADDRESS: &str = "inj1m6ada382hfuxvuke4h9p4uswhn2qcca7mlg0dr"; -const NEUTRON_ADDRESS: &str = "neutron1mydju5alsmhnfsawy0j4lyns70l7qukgdgy45w"; - -#[test] -fn test_account_id() { - // given - let pub_key = compressed_public_key(); - - // when - let neutron_account_id = - CosmosAccountId::account_id_from_pubkey(pub_key, "neutron", &Bitcoin).unwrap(); - let injective_account_id = - CosmosAccountId::account_id_from_pubkey(pub_key, "inj", &Ethereum).unwrap(); - - // then - assert_eq!(neutron_account_id.as_ref(), NEUTRON_ADDRESS); - assert_eq!(injective_account_id.as_ref(), INJECTIVE_ADDRESS); -} - -#[test] -fn test_bitcoin_style() { - // given - let compressed = compressed_public_key(); - let decompressed = decompressed_public_key(); - - // when - let from_compressed = CosmosAccountId::bitcoin_style(compressed, "neutron").unwrap(); - let from_decompressed = CosmosAccountId::bitcoin_style(decompressed, "neutron").unwrap(); - - // then - assert_eq!(from_compressed.as_ref(), NEUTRON_ADDRESS); - assert_eq!(from_decompressed.as_ref(), NEUTRON_ADDRESS); -} - -#[test] -fn test_ethereum_style() { - // given - let compressed = compressed_public_key(); - let decompressed = decompressed_public_key(); - - // when - let from_compressed = CosmosAccountId::ethereum_style(compressed, "inj").unwrap(); - let from_decompressed = CosmosAccountId::ethereum_style(decompressed, "inj").unwrap(); - - // then - assert_eq!(from_compressed.as_ref(), INJECTIVE_ADDRESS); - assert_eq!(from_decompressed.as_ref(), INJECTIVE_ADDRESS); -} - -fn compressed_public_key() -> PublicKey { - let hex = hex::decode(COMPRESSED_PUBLIC_KEY).unwrap(); - let tendermint = tendermint::PublicKey::from_raw_secp256k1(&hex).unwrap(); - - PublicKey::from(tendermint) -} - -fn decompressed_public_key() -> PublicKey { - let hex = hex::decode(COMPRESSED_PUBLIC_KEY).unwrap(); - let decompressed = decompress_public_key(&hex).unwrap(); - let tendermint = tendermint::PublicKey::from_raw_secp256k1(&decompressed).unwrap(); - - PublicKey::from(tendermint) -} diff --git a/rust/main/chains/hyperlane-cosmos-native/src/libs/address.rs b/rust/main/chains/hyperlane-cosmos-native/src/libs/address.rs deleted file mode 100644 index e538e1c3b07..00000000000 --- a/rust/main/chains/hyperlane-cosmos-native/src/libs/address.rs +++ /dev/null @@ -1,167 +0,0 @@ -use std::str::FromStr; - -use cosmrs::{ - crypto::{secp256k1::SigningKey, PublicKey}, - AccountId, -}; -use derive_new::new; -use hyperlane_core::{ - AccountAddressType, ChainCommunicationError, ChainResult, Error::Overflow, H256, -}; - -use crate::{CosmosAccountId, HyperlaneCosmosError}; - -/// Wrapper around the cosmrs AccountId type that abstracts bech32 encoding -#[derive(new, Debug, Clone)] -pub struct CosmosAddress { - /// Bech32 encoded cosmos account - account_id: AccountId, - /// Hex representation (digest) of cosmos account - digest: H256, -} - -impl CosmosAddress { - /// Creates a wrapper around a cosmrs AccountId from a private key byte array - pub fn from_privkey( - priv_key: &[u8], - prefix: &str, - account_address_type: &AccountAddressType, - ) -> ChainResult { - let pubkey = SigningKey::from_slice(priv_key) - .map_err(Into::::into)? - .public_key(); - Self::from_pubkey(pubkey, prefix, account_address_type) - } - - /// Returns an account address calculated from Bech32 encoding - pub fn from_account_id(account_id: AccountId) -> ChainResult { - // Hex digest - let digest = H256::try_from(&CosmosAccountId::new(&account_id))?; - Ok(CosmosAddress::new(account_id, digest)) - } - - /// Creates a wrapper around a cosmrs AccountId from a H256 digest - /// - /// - digest: H256 digest (hex representation of address) - /// - prefix: Bech32 prefix - /// - byte_count: Number of bytes to truncate the digest to. Cosmos addresses can sometimes - /// be less than 32 bytes, so this helps to serialize it in bech32 with the appropriate - /// length. - pub fn from_h256(digest: H256, prefix: &str, byte_count: usize) -> ChainResult { - // This is the hex-encoded version of the address - let untruncated_bytes = digest.as_bytes(); - - if byte_count > untruncated_bytes.len() { - return Err(Overflow.into()); - } - - let remainder_bytes_start = untruncated_bytes.len() - byte_count; - // Left-truncate the digest to the desired length - let bytes = &untruncated_bytes[remainder_bytes_start..]; - - // Bech32 encode it - let account_id = - AccountId::new(prefix, bytes).map_err(Into::::into)?; - Ok(CosmosAddress::new(account_id, digest)) - } - - /// String representation of a cosmos AccountId - pub fn address(&self) -> String { - self.account_id.to_string() - } - - /// H256 digest of the cosmos AccountId - pub fn digest(&self) -> H256 { - self.digest - } - - /// Calculates an account address depending on prefix and account address type - fn from_pubkey( - pubkey: PublicKey, - prefix: &str, - account_address_type: &AccountAddressType, - ) -> ChainResult { - let account_id = - CosmosAccountId::account_id_from_pubkey(pubkey, prefix, account_address_type)?; - Self::from_account_id(account_id) - } -} - -impl TryFrom<&CosmosAddress> for H256 { - type Error = ChainCommunicationError; - - fn try_from(cosmos_address: &CosmosAddress) -> Result { - H256::try_from(CosmosAccountId::new(&cosmos_address.account_id)) - .map_err(Into::::into) - } -} - -impl FromStr for CosmosAddress { - type Err = ChainCommunicationError; - - fn from_str(s: &str) -> Result { - let account_id = AccountId::from_str(s).map_err(Into::::into)?; - let digest = CosmosAccountId::new(&account_id).try_into()?; - Ok(Self::new(account_id, digest)) - } -} - -#[cfg(test)] -pub mod test { - use hyperlane_core::utils::hex_or_base58_to_h256; - - use super::*; - - #[test] - fn test_bech32_decode() { - let addr = "dual1pk99xge6q94qtu3568x3qhp68zzv0mx7za4ct008ks36qhx5tvss3qawfh"; - let cosmos_address = CosmosAddress::from_str(addr).unwrap(); - assert_eq!( - cosmos_address.digest, - H256::from_str("0d8a53233a016a05f234d1cd105c3a3884c7ecde176b85bde7b423a05cd45b21") - .unwrap() - ); - } - - #[test] - fn test_bech32_decode_from_cosmos_key() { - let hex_key = "0x5486418967eabc770b0fcb995f7ef6d9a72f7fc195531ef76c5109f44f51af26"; - let key = hex_or_base58_to_h256(hex_key).unwrap(); - let prefix = "neutron"; - let addr = - CosmosAddress::from_privkey(key.as_bytes(), prefix, &AccountAddressType::Bitcoin) - .expect("Cosmos address creation failed"); - assert_eq!( - addr.address(), - "neutron1kknekjxg0ear00dky5ykzs8wwp2gz62z9s6aaj" - ); - - // Create an address with the same digest & explicitly set the byte count to 20, - // which should have the same result as the above. - let digest = addr.digest(); - let addr2 = - CosmosAddress::from_h256(digest, prefix, 20).expect("Cosmos address creation failed"); - assert_eq!(addr.address(), addr2.address()); - } - - #[test] - fn test_bech32_encode_from_h256() { - let hex_key = "0x1b16866227825a5166eb44031cdcf6568b3e80b52f2806e01b89a34dc90ae616"; - let key = hex_or_base58_to_h256(hex_key).unwrap(); - let prefix = "dual"; - let addr = - CosmosAddress::from_h256(key, prefix, 32).expect("Cosmos address creation failed"); - assert_eq!( - addr.address(), - "dual1rvtgvc38sfd9zehtgsp3eh8k269naq949u5qdcqm3x35mjg2uctqfdn3yq" - ); - - // Last 20 bytes only, which is 0x1cdcf6568b3e80b52f2806e01b89a34dc90ae616 - let addr = - CosmosAddress::from_h256(key, prefix, 20).expect("Cosmos address creation failed"); - assert_eq!( - addr.address(), - "dual1rnw0v45t86qt2tegqmsphzdrfhys4esk9ktul7" - ); - } -} diff --git a/rust/main/chains/hyperlane-cosmos-native/src/libs/mod.rs b/rust/main/chains/hyperlane-cosmos-native/src/libs/mod.rs deleted file mode 100644 index 0ee55478e02..00000000000 --- a/rust/main/chains/hyperlane-cosmos-native/src/libs/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub(crate) use account::CosmosAccountId; -pub(crate) use address::CosmosAddress; - -/// This module contains conversions from Cosmos AccountId to H56 -mod account; - -/// This module contains all the verification variables the libraries used by the Hyperlane Cosmos chain. -mod address; diff --git a/rust/main/chains/hyperlane-cosmos-native/src/prometheus/mod.rs b/rust/main/chains/hyperlane-cosmos-native/src/prometheus/mod.rs deleted file mode 100644 index 106e8d89569..00000000000 --- a/rust/main/chains/hyperlane-cosmos-native/src/prometheus/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -/// TODO: copy pasted from `chains/hyperlane-cosmos/src/prometheus` -/// refactore shared logic -pub mod metrics_channel; -pub mod metrics_future; diff --git a/rust/main/chains/hyperlane-cosmos-native/src/providers.rs b/rust/main/chains/hyperlane-cosmos-native/src/providers.rs deleted file mode 100644 index 19599f4f64d..00000000000 --- a/rust/main/chains/hyperlane-cosmos-native/src/providers.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod cosmos; -mod grpc; -mod rpc; - -pub use cosmos::CosmosNativeProvider; -pub use grpc::*; -pub use rpc::*; diff --git a/rust/main/chains/hyperlane-cosmos-native/src/providers/cosmos.rs b/rust/main/chains/hyperlane-cosmos-native/src/providers/cosmos.rs deleted file mode 100644 index feb62b618c8..00000000000 --- a/rust/main/chains/hyperlane-cosmos-native/src/providers/cosmos.rs +++ /dev/null @@ -1,441 +0,0 @@ -use std::ops::Deref; - -use cosmrs::{ - crypto::PublicKey, - tx::{SequenceNumber, SignerInfo, SignerPublicKey}, - AccountId, Any, Coin, Tx, -}; -use derive_new::new; -use hyperlane_cosmos_rs::{ - hyperlane::{core::v1::MsgProcessMessage, warp::v1::MsgRemoteTransfer}, - prost::{Message, Name}, -}; -use itertools::Itertools; -use time::OffsetDateTime; -use tonic::async_trait; -use tracing::warn; - -use hyperlane_core::{ - h512_to_bytes, - rpc_clients::{BlockNumberGetter, FallbackProvider}, - utils::to_atto, - AccountAddressType, BlockInfo, ChainCommunicationError, ChainInfo, ChainResult, - ContractLocator, HyperlaneChain, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, - HyperlaneProviderError, RawHyperlaneMessage, ReorgPeriod, TxnInfo, TxnReceiptInfo, H256, H512, - U256, -}; -use hyperlane_metric::prometheus_metric::PrometheusClientMetrics; - -use crate::{ - ConnectionConf, CosmosAccountId, CosmosAddress, GrpcProvider, HyperlaneCosmosError, Signer, -}; - -use super::RpcProvider; - -/// Wrapper of `FallbackProvider` for use in `hyperlane-cosmos-native` -#[derive(new, Clone)] -pub(crate) struct CosmosFallbackProvider { - fallback_provider: FallbackProvider, -} - -impl Deref for CosmosFallbackProvider { - type Target = FallbackProvider; - - fn deref(&self) -> &Self::Target { - &self.fallback_provider - } -} - -impl std::fmt::Debug for CosmosFallbackProvider -where - C: std::fmt::Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.fallback_provider.fmt(f) - } -} - -/// Cosmos Native Provider -/// -/// implements the HyperlaneProvider trait -#[derive(Debug, Clone)] -pub struct CosmosNativeProvider { - conf: ConnectionConf, - rpc: RpcProvider, - domain: HyperlaneDomain, - grpc: GrpcProvider, -} - -impl CosmosNativeProvider { - /// Create a new Cosmos Provider instance - pub fn new( - conf: &ConnectionConf, - locator: &ContractLocator, - signer: Option, - metrics: PrometheusClientMetrics, - chain: Option, - ) -> ChainResult { - let rpc = RpcProvider::new(conf.clone(), signer, metrics.clone(), chain.clone())?; - let grpc = GrpcProvider::new(conf.clone(), metrics, chain)?; - - Ok(CosmosNativeProvider { - domain: locator.domain.clone(), - conf: conf.clone(), - rpc, - grpc, - }) - } - - /// RPC Provider - /// - /// This is used for general chain communication like getting the block number, block, transaction, etc. - pub fn rpc(&self) -> &RpcProvider { - &self.rpc - } - - /// gRPC Provider - /// - /// This is used for the Module Communication and querying the module state. Like mailboxes, isms etc. - pub fn grpc(&self) -> &GrpcProvider { - &self.grpc - } - - /// Get the block number according to the reorg period - pub async fn reorg_to_height(&self, reorg: &ReorgPeriod) -> ChainResult { - let height = self.rpc.get_block_number().await? as u32; - match reorg { - ReorgPeriod::None => Ok(height), - // height has to be at least 1 -> block 0 does not exist in cosmos - ReorgPeriod::Blocks(blocks) => Ok(height.checked_sub(blocks.get()).unwrap_or(1)), - ReorgPeriod::Tag(_) => Err(ChainCommunicationError::InvalidReorgPeriod(reorg.clone())), - } - } - - /// parses the message recipient if the transaction contains a MsgProcessMessage - fn parse_msg_process_recipient(tx: &Tx) -> ChainResult> { - // check for all messages processes - let processed_messages: Vec = tx - .body - .messages - .iter() - .filter(|a| a.type_url == MsgProcessMessage::type_url()) - .cloned() - .collect(); - - // right now one transaction can include max. one process - if processed_messages.len() > 1 { - let msg = "transaction contains multiple execution messages"; - Err(HyperlaneCosmosError::ParsingFailed(msg.to_owned()))? - } - - let msg = processed_messages.first(); - match msg { - Some(msg) => { - let result = MsgProcessMessage::decode(msg.value.as_slice()) - .map_err(HyperlaneCosmosError::from)?; - let message: RawHyperlaneMessage = hex::decode(result.message)?; - let message = HyperlaneMessage::from(message); - Ok(Some(message.recipient)) - } - None => Ok(None), - } - } - - /// parses the message recipient if the transaction contains a MsgRemoteTransfer - fn parse_msg_remote_transfer_recipient(tx: &Tx) -> ChainResult> { - // check for all remote transfers - let remote_transfers: Vec = tx - .body - .messages - .iter() - .filter(|a| a.type_url == MsgRemoteTransfer::type_url()) - .cloned() - .collect(); - - // right now one transaction can include max. one transfer - if remote_transfers.len() > 1 { - let msg = "transaction contains multiple execution messages"; - Err(HyperlaneCosmosError::ParsingFailed(msg.to_owned()))? - } - - let msg = remote_transfers.first().ok_or_else(|| { - ChainCommunicationError::from_other_str("tx does not contain any remote transfers") - })?; - let result = - MsgRemoteTransfer::decode(msg.value.as_slice()).map_err(HyperlaneCosmosError::from)?; - // the recipient is the token id of the transfer, which is the address that the user interacts with - let recipient: H256 = result.token_id.parse()?; - Ok(Some(recipient)) - } - - // extract the message recipient contract address from the tx - // the tx is either a MsgPorcessMessage on the destination or a MsgRemoteTransfer on the origin - // we check for both tx types, if both are missing or an error occurred while parsing we return the error - fn parse_tx_message_recipient(tx: &Tx) -> ChainResult { - // first check for the process message - if let Some(recipient) = Self::parse_msg_process_recipient(tx)? { - return Ok(recipient); - } - // if not found check for the remote transfer - if let Some(recipient) = Self::parse_msg_remote_transfer_recipient(tx)? { - return Ok(recipient); - } - // if both are missing we return an error - Err(HyperlaneCosmosError::ParsingFailed( - "transaction does not contain any process message or remote transfer".to_owned(), - ))? - } - - fn search_payer_in_signer_infos( - &self, - signer_infos: &[SignerInfo], - payer: &AccountId, - ) -> ChainResult<(AccountId, SequenceNumber)> { - signer_infos - .iter() - .map(|si| self.convert_signer_info_into_account_id_and_nonce(si)) - // After the following we have a single Ok entry and, possibly, many Err entries - .filter_ok(|(a, _)| payer == a) - // If we have Ok entry, use it since it is the payer, if not, use the first entry with error - .find_or_first(|r| match r { - Ok((a, _)) => payer == a, - Err(_) => false, - }) - // If there were not any signer info with non-empty public key or no signers for the transaction, - // we get None here - .unwrap_or_else(|| Err(ChainCommunicationError::from_other_str("no signer info"))) - } - - fn convert_signer_info_into_account_id_and_nonce( - &self, - signer_info: &SignerInfo, - ) -> ChainResult<(AccountId, SequenceNumber)> { - let signer_public_key = signer_info.public_key.clone().ok_or_else(|| { - HyperlaneCosmosError::PublicKeyError("no public key for default signer".to_owned()) - })?; - - let (key, account_address_type) = self.normalize_public_key(signer_public_key)?; - let public_key = PublicKey::try_from(key)?; - - let account_id = CosmosAccountId::account_id_from_pubkey( - public_key, - &self.conf.get_bech32_prefix(), - &account_address_type, - )?; - - Ok((account_id, signer_info.sequence)) - } - - fn normalize_public_key( - &self, - signer_public_key: SignerPublicKey, - ) -> ChainResult<(SignerPublicKey, AccountAddressType)> { - let public_key_and_account_address_type = match signer_public_key { - SignerPublicKey::Single(pk) => (SignerPublicKey::from(pk), AccountAddressType::Bitcoin), - SignerPublicKey::LegacyAminoMultisig(pk) => { - (SignerPublicKey::from(pk), AccountAddressType::Bitcoin) - } - SignerPublicKey::Any(pk) => { - if pk.type_url != PublicKey::ED25519_TYPE_URL - && pk.type_url != PublicKey::SECP256K1_TYPE_URL - { - let msg = format!( - "can only normalize public keys with a known TYPE_URL: {}, {}", - PublicKey::ED25519_TYPE_URL, - PublicKey::SECP256K1_TYPE_URL, - ); - warn!(pk.type_url, msg); - Err(HyperlaneCosmosError::PublicKeyError(msg.to_owned()))? - } - - let (pub_key, account_address_type) = - (PublicKey::try_from(pk)?, AccountAddressType::Bitcoin); - - (SignerPublicKey::Single(pub_key), account_address_type) - } - }; - - Ok(public_key_and_account_address_type) - } - - /// Calculates the sender and the nonce for the transaction. - /// We use `payer` of the fees as the sender of the transaction, and we search for `payer` - /// signature information to find the nonce. - /// If `payer` is not specified, we use the account which signed the transaction first, as - /// the sender. - pub fn sender_and_nonce(&self, tx: &Tx) -> ChainResult<(H256, SequenceNumber)> { - let (sender, nonce) = tx - .auth_info - .fee - .payer - .as_ref() - .map(|payer| self.search_payer_in_signer_infos(&tx.auth_info.signer_infos, payer)) - .map_or_else( - || { - #[allow(clippy::get_first)] // TODO: `rustc` 1.80.1 clippy issue - let signer_info = tx.auth_info.signer_infos.get(0).ok_or_else(|| { - HyperlaneCosmosError::SignerInfoError( - "no signer info in default signer".to_owned(), - ) - })?; - self.convert_signer_info_into_account_id_and_nonce(signer_info) - }, - |p| p, - ) - .map(|(a, n)| CosmosAddress::from_account_id(a).map(|a| (a.digest(), n)))??; - Ok((sender, nonce)) - } - - /// Reports if transaction contains fees expressed in unsupported denominations - /// The only denomination we support at the moment is the one we express gas minimum price - /// in the configuration of a chain. If fees contain an entry in a different denomination, - /// we report it in the logs. - fn report_unsupported_denominations(&self, tx: &Tx, tx_hash: &H256) -> ChainResult<()> { - let supported_denomination = self.conf.get_minimum_gas_price().denom; - let unsupported_denominations = tx - .auth_info - .fee - .amount - .iter() - .filter(|c| c.denom.as_ref() != supported_denomination) - .map(|c| c.denom.as_ref()) - .fold("".to_string(), |acc, denom| acc + ", " + denom); - - if !unsupported_denominations.is_empty() { - let msg = "transaction contains fees in unsupported denominations, manual intervention is required"; - warn!( - ?tx_hash, - ?supported_denomination, - ?unsupported_denominations, - msg, - ); - Err(ChainCommunicationError::CustomError(msg.to_owned()))? - } - - Ok(()) - } - - /// Converts fees to a common denomination if necessary. - /// - /// If fees are expressed in an unsupported denomination, they will be ignored. - fn convert_fee(&self, coin: &Coin) -> ChainResult { - let native_token = self.conf.get_native_token(); - - if coin.denom.as_ref() != native_token.denom { - return Ok(U256::zero()); - } - - let amount_in_native_denom = U256::from(coin.amount); - - to_atto(amount_in_native_denom, native_token.decimals).ok_or( - ChainCommunicationError::CustomError("Overflow in calculating fees".to_owned()), - ) - } - - fn calculate_gas_price(&self, hash: &H256, tx: &Tx) -> ChainResult { - let supported = self.report_unsupported_denominations(tx, hash); - if supported.is_err() { - return Ok(U256::max_value()); - } - - let gas_limit = U256::from(tx.auth_info.fee.gas_limit); - let fee = tx - .auth_info - .fee - .amount - .iter() - .map(|c| self.convert_fee(c)) - .fold_ok(U256::zero(), |acc, v| acc + v)?; - - if fee < gas_limit { - warn!(tx_hash = ?hash, ?fee, ?gas_limit, "calculated fee is less than gas limit. it will result in zero gas price"); - } - - Ok(fee / gas_limit) - } -} - -impl HyperlaneChain for CosmosNativeProvider { - /// Return the domain - fn domain(&self) -> &HyperlaneDomain { - &self.domain - } - - /// A provider for the chain - fn provider(&self) -> Box { - Box::new(self.clone()) - } -} - -#[async_trait] -impl HyperlaneProvider for CosmosNativeProvider { - async fn get_block_by_height(&self, height: u64) -> ChainResult { - let response = self.rpc.get_block(height as u32).await?; - let block = response.block; - let block_height = block.header.height.value(); - - if block_height != height { - Err(HyperlaneProviderError::IncorrectBlockByHeight( - height, - block_height, - ))? - } - - let hash = H256::from_slice(response.block_id.hash.as_bytes()); - let time: OffsetDateTime = block.header.time.into(); - - let block_info = BlockInfo { - hash: hash.to_owned(), - timestamp: time.unix_timestamp() as u64, - number: block_height, - }; - - Ok(block_info) - } - - async fn get_txn_by_hash(&self, hash: &H512) -> ChainResult { - if hash.is_zero() { - return Err(HyperlaneProviderError::CouldNotFindTransactionByHash(*hash).into()); - } - let response = self.rpc.get_tx(hash).await?; - let tx = Tx::from_bytes(&response.tx)?; - - let contract = Self::parse_tx_message_recipient(&tx)?; - let (sender, nonce) = self.sender_and_nonce(&tx)?; - - let hash: H256 = H256::from_slice(&h512_to_bytes(hash)); - let gas_price = self.calculate_gas_price(&hash, &tx)?; - - let tx_info = TxnInfo { - hash: hash.into(), - gas_limit: U256::from(response.tx_result.gas_wanted), - max_priority_fee_per_gas: None, - max_fee_per_gas: None, - gas_price: Some(gas_price), - nonce, - sender, - recipient: Some(contract), - receipt: Some(TxnReceiptInfo { - gas_used: response.tx_result.gas_used.into(), - cumulative_gas_used: response.tx_result.gas_used.into(), - effective_gas_price: Some(gas_price), - }), - raw_input_data: None, - }; - - Ok(tx_info) - } - - async fn is_contract(&self, _address: &H256) -> ChainResult { - // TODO: check if the address is a recipient - return Ok(true); - } - - async fn get_balance(&self, address: String) -> ChainResult { - self.rpc.get_balance(address).await - } - - async fn get_chain_metrics(&self) -> ChainResult> { - return Ok(None); - } -} diff --git a/rust/main/chains/hyperlane-cosmos-native/src/providers/grpc.rs b/rust/main/chains/hyperlane-cosmos-native/src/providers/grpc.rs deleted file mode 100644 index 49468aa051a..00000000000 --- a/rust/main/chains/hyperlane-cosmos-native/src/providers/grpc.rs +++ /dev/null @@ -1,258 +0,0 @@ -use std::time::Duration; - -use derive_new::new; -use hyperlane_cosmos_rs::cosmos::base::tendermint::v1beta1::service_client::ServiceClient; -use hyperlane_cosmos_rs::cosmos::base::tendermint::v1beta1::GetLatestBlockRequest; -use hyperlane_cosmos_rs::hyperlane::core::interchain_security::v1::{ - query_client::QueryClient as IsmQueryClient, QueryAnnouncedStorageLocationsRequest, - QueryAnnouncedStorageLocationsResponse, QueryIsmRequest, QueryIsmResponse, -}; -use hyperlane_cosmos_rs::hyperlane::core::post_dispatch::v1::{ - query_client::QueryClient as PostDispatchQueryClient, QueryMerkleTreeHookRequest, - QueryMerkleTreeHookResponse, -}; -use hyperlane_cosmos_rs::hyperlane::core::v1::query_client::QueryClient; -use hyperlane_cosmos_rs::hyperlane::core::v1::{ - QueryDeliveredRequest, QueryDeliveredResponse, QueryMailboxRequest, QueryMailboxResponse, - QueryRecipientIsmRequest, QueryRecipientIsmResponse, -}; -use tonic::async_trait; -use tonic::transport::{Channel, Endpoint}; - -use hyperlane_core::rpc_clients::{BlockNumberGetter, FallbackProvider}; -use hyperlane_core::{ChainCommunicationError, ChainResult}; -use hyperlane_metric::prometheus_metric::{ - ChainInfo, ClientConnectionType, PrometheusClientMetrics, PrometheusConfig, -}; - -use crate::prometheus::metrics_channel::MetricsChannel; -use crate::{ConnectionConf, HyperlaneCosmosError}; - -const REQUEST_TIMEOUT: u64 = 30; - -/// Grpc Provider -#[derive(Clone, Debug)] -pub struct GrpcProvider { - fallback: FallbackProvider, -} - -#[derive(Debug, Clone, new)] -struct CosmosGrpcClient { - channel: MetricsChannel, -} - -#[async_trait] -impl BlockNumberGetter for CosmosGrpcClient { - async fn get_block_number(&self) -> Result { - let mut client = ServiceClient::new(self.channel.clone()); - let mut request = tonic::Request::new(GetLatestBlockRequest {}); - request.set_timeout(Duration::from_secs(REQUEST_TIMEOUT)); - let response = client - .get_latest_block(request) - .await - .map_err(ChainCommunicationError::from_other)? - .into_inner(); - let height = response - .block - .ok_or_else(|| ChainCommunicationError::from_other_str("block not present"))? - .header - .ok_or_else(|| ChainCommunicationError::from_other_str("header not present"))? - .height; - - Ok(height as u64) - } -} -impl GrpcProvider { - /// New GrpcProvider - pub fn new( - conf: ConnectionConf, - metrics: PrometheusClientMetrics, - chain: Option, - ) -> ChainResult { - let clients = conf - .get_grpc_urls() - .iter() - .map(|url| { - let metrics_config = - PrometheusConfig::from_url(url, ClientConnectionType::Grpc, chain.clone()); - Endpoint::new(url.to_string()) - .map(|e| e.timeout(Duration::from_secs(REQUEST_TIMEOUT))) - .map(|e| e.connect_timeout(Duration::from_secs(REQUEST_TIMEOUT))) - .map(|e| MetricsChannel::new(e.connect_lazy(), metrics.clone(), metrics_config)) - .map(CosmosGrpcClient::new) - .map_err(Into::::into) - }) - .collect::, _>>() - .map_err(HyperlaneCosmosError::from)?; - - let fallback = FallbackProvider::new(clients); - Ok(Self { fallback }) - } - - fn request_at_height( - request: impl tonic::IntoRequest, - height: Option, - ) -> tonic::Request { - let mut request = request.into_request(); - request.set_timeout(Duration::from_secs(REQUEST_TIMEOUT)); - if let Some(height) = height { - request - .metadata_mut() - .insert("x-cosmos-block-height", height.into()); - } - request - } - - /// Mailbox struct at given height - pub async fn mailbox( - &self, - id: String, - height: Option, - ) -> ChainResult { - self.fallback - .call(|client| { - let id = id.clone(); - let future = async move { - let mut service = QueryClient::new(client.channel.clone()); - let result = service - .mailbox(Self::request_at_height(QueryMailboxRequest { id }, height)) - .await - .map_err(HyperlaneCosmosError::from)? - .into_inner(); - Ok(result) - }; - Box::pin(future) - }) - .await - } - - /// All of the storage locations of a validator - /// - /// Note: a validators storage locations are depended on the mailbox - pub async fn announced_storage_locations( - &self, - mailbox: String, - validator: String, - ) -> ChainResult { - self.fallback - .call(|client| { - let mailbox = mailbox.clone(); - let validator = validator.clone(); - let future = async move { - let mut service = IsmQueryClient::new(client.channel.clone()); - let result = service - .announced_storage_locations(QueryAnnouncedStorageLocationsRequest { - mailbox_id: mailbox, - validator_address: validator, - }) - .await - .map_err(HyperlaneCosmosError::from)? - .into_inner(); - Ok(result) - }; - Box::pin(future) - }) - .await - } - - /// ISM for a given recipient - /// - /// Recipient is a 32 byte long hex address - /// Mailbox independent query as one application (recipient) can only ever register on one mailbox - pub async fn recipient_ism(&self, recipient: String) -> ChainResult { - self.fallback - .call(|client| { - let recipient = recipient.clone(); - let future = async move { - let mut service = QueryClient::new(client.channel.clone()); - let result = service - .recipient_ism(QueryRecipientIsmRequest { recipient }) - .await - .map_err(HyperlaneCosmosError::from)? - .into_inner(); - Ok(result) - }; - Box::pin(future) - }) - .await - } - - /// merkle tree hook - /// - /// also contains the current root of the branches - pub async fn merkle_tree_hook( - &self, - id: String, - height: Option, - ) -> ChainResult { - self.fallback - .call(|client| { - let id = id.clone(); - let future = async move { - let mut service = PostDispatchQueryClient::new(client.channel.clone()); - let result = service - .merkle_tree_hook(Self::request_at_height( - QueryMerkleTreeHookRequest { id }, - height, - )) - .await - .map_err(HyperlaneCosmosError::from)? - .into_inner(); - Ok(result) - }; - Box::pin(future) - }) - .await - } - - /// checks if a message has been delivered to the given mailbox - pub async fn delivered( - &self, - mailbox_id: String, - message_id: String, - ) -> ChainResult { - self.fallback - .call(|client| { - let mailbox_id = mailbox_id.clone(); - let message_id = message_id.clone(); - let future = async move { - let mut service = QueryClient::new(client.channel.clone()); - let result = service - .delivered(QueryDeliveredRequest { - id: mailbox_id, - message_id, - }) - .await - .map_err(HyperlaneCosmosError::from)? - .into_inner(); - Ok(result) - }; - Box::pin(future) - }) - .await - } - - /// ism for a given id - /// - /// Note: this query will only ever work for the core ISMs that are directly supported by the cosmos module - /// because the cosmos module forces extensions to be stored in external keepers (Cosmos SDK specific). - /// As a result, extensions have to provide custom queries for their types, meaning if we want to support a custom ISM at some point - that is not provided by the default hyperlane cosmos module - - /// we'd have to query a custom endpoint for the ISMs as well. - pub async fn ism(&self, id: String) -> ChainResult { - self.fallback - .call(|client| { - let id = id.clone(); - let future = async move { - let mut service = IsmQueryClient::new(client.channel.clone()); - let result = service - .ism(QueryIsmRequest { id }) - .await - .map_err(HyperlaneCosmosError::from)? - .into_inner(); - Ok(result) - }; - Box::pin(future) - }) - .await - } -} diff --git a/rust/main/chains/hyperlane-cosmos-native/src/providers/rpc.rs b/rust/main/chains/hyperlane-cosmos-native/src/providers/rpc.rs deleted file mode 100644 index 806946cb132..00000000000 --- a/rust/main/chains/hyperlane-cosmos-native/src/providers/rpc.rs +++ /dev/null @@ -1,450 +0,0 @@ -use std::future::Future; -use std::time::Instant; - -use cosmrs::{ - proto::cosmos::{ - auth::v1beta1::{BaseAccount, QueryAccountRequest, QueryAccountResponse}, - bank::v1beta1::{QueryBalanceRequest, QueryBalanceResponse}, - tx::v1beta1::{SimulateRequest, SimulateResponse, TxRaw}, - }, - rpc::HttpClient, - tx::{self, Fee, MessageExt, SignDoc, SignerInfo}, - Any, Coin, -}; -use hyperlane_cosmos_rs::prost::Message; -use tendermint::{hash::Algorithm, Hash}; -use tendermint_rpc::{ - client::CompatMode, - endpoint::{ - block::Response as BlockResponse, block_results::Response as BlockResultsResponse, - broadcast::tx_commit, tx::Response as TxResponse, - }, - Client, Error, -}; -use tonic::async_trait; - -use hyperlane_core::{ - h512_to_bytes, - rpc_clients::{BlockNumberGetter, FallbackProvider}, - ChainCommunicationError, ChainResult, FixedPointNumber, H256, H512, U256, -}; -use hyperlane_metric::prometheus_metric::{ - ClientConnectionType, PrometheusClientMetrics, PrometheusConfig, -}; -use url::Url; - -use crate::{ConnectionConf, CosmosAmount, HyperlaneCosmosError, Signer}; - -use super::cosmos::CosmosFallbackProvider; - -#[derive(Debug)] -struct CosmosHttpClient { - client: HttpClient, - metrics: PrometheusClientMetrics, - metrics_config: PrometheusConfig, -} - -/// RPC Provider for Cosmos -/// -/// Responsible for chain communication -#[derive(Debug, Clone)] -pub struct RpcProvider { - provider: CosmosFallbackProvider, - conf: ConnectionConf, - signer: Option, - gas_price: CosmosAmount, -} - -#[async_trait] -impl BlockNumberGetter for CosmosHttpClient { - async fn get_block_number(&self) -> Result { - let block = self - .client - .latest_block() - .await - .map_err(HyperlaneCosmosError::from)?; - - Ok(block.block.header.height.value()) - } -} - -impl CosmosHttpClient { - /// Create new `CosmosHttpClient` - pub fn new( - client: HttpClient, - metrics: PrometheusClientMetrics, - metrics_config: PrometheusConfig, - ) -> Self { - // increment provider metric count - let chain_name = PrometheusConfig::chain_name(&metrics_config.chain); - metrics.increment_provider_instance(chain_name); - - Self { - client, - metrics, - metrics_config, - } - } - - /// Creates a CosmosHttpClient from a url - pub fn from_url( - url: &Url, - metrics: PrometheusClientMetrics, - metrics_config: PrometheusConfig, - ) -> ChainResult { - let tendermint_url = tendermint_rpc::Url::try_from(url.to_owned()) - .map_err(Box::new) - .map_err(ChainCommunicationError::from_other)?; - let url = tendermint_rpc::HttpClientUrl::try_from(tendermint_url) - .map_err(Box::new) - .map_err(ChainCommunicationError::from_other)?; - - let client = HttpClient::builder(url) - // Consider supporting different compatibility modes. - .compat_mode(CompatMode::latest()) - .build() - .map_err(Box::new) - .map_err(ChainCommunicationError::from_other)?; - - Ok(Self::new(client, metrics, metrics_config)) - } -} - -impl Drop for CosmosHttpClient { - fn drop(&mut self) { - // decrement provider metric count - let chain_name = PrometheusConfig::chain_name(&self.metrics_config.chain); - self.metrics.decrement_provider_instance(chain_name); - } -} - -impl Clone for CosmosHttpClient { - fn clone(&self) -> Self { - Self::new( - self.client.clone(), - self.metrics.clone(), - self.metrics_config.clone(), - ) - } -} - -impl RpcProvider { - /// Returns a new Rpc Provider - pub fn new( - conf: ConnectionConf, - signer: Option, - metrics: PrometheusClientMetrics, - chain: Option, - ) -> ChainResult { - let clients = conf - .get_rpc_urls() - .iter() - .map(|url| { - let metrics_config = - PrometheusConfig::from_url(url, ClientConnectionType::Rpc, chain.clone()); - CosmosHttpClient::from_url(url, metrics.clone(), metrics_config) - }) - .collect::, _>>()?; - - let provider = FallbackProvider::new(clients); - let provider = CosmosFallbackProvider::new(provider); - let gas_price = CosmosAmount::try_from(conf.get_minimum_gas_price().clone())?; - - Ok(RpcProvider { - provider, - conf, - signer, - gas_price, - }) - } - - // mostly copy pasted from `hyperlane-cosmos/src/providers/rpc/client.rs` - async fn track_metric_call( - client: &CosmosHttpClient, - method: &str, - call: F, - ) -> ChainResult - where - F: Fn() -> Fut, - Fut: Future>, - { - let start = Instant::now(); - let res = call().await; - - client - .metrics - .increment_metrics(&client.metrics_config, method, start, res.is_ok()); - - res.map_err(|e| ChainCommunicationError::from(HyperlaneCosmosError::from(e))) - } - - /// Get the transaction by hash - pub async fn get_tx(&self, hash: &H512) -> ChainResult { - let hash: H256 = H256::from_slice(&h512_to_bytes(hash)); - - let tendermint_hash = Hash::from_bytes(Algorithm::Sha256, hash.as_bytes()) - .expect("transaction hash should be of correct size"); - - let response = self - .provider - .call(|client| { - let future = async move { - Self::track_metric_call(&client, "get_tx", || { - client.client.tx(tendermint_hash, false) - }) - .await - }; - Box::pin(future) - }) - .await?; - - let received_hash = H256::from_slice(response.hash.as_bytes()); - if received_hash != hash { - return Err(ChainCommunicationError::from_other_str(&format!( - "received incorrect transaction, expected hash: {:?}, received hash: {:?}", - hash, received_hash, - ))); - } - - Ok(response) - } - - /// Get the block by height - pub async fn get_block(&self, height: u32) -> ChainResult { - self.provider - .call(|client| { - let future = async move { - Self::track_metric_call(&client, "get_block", || client.client.block(height)) - .await - }; - Box::pin(future) - }) - .await - } - - /// Get the block results by height - pub async fn get_block_results(&self, height: u32) -> ChainResult { - self.provider - .call(|client| { - let future = async move { - Self::track_metric_call(&client, "block_results", || { - client.client.block_results(height) - }) - .await - }; - Box::pin(future) - }) - .await - } - - /// abci query is low level function that only gets called by higher level ones like 'get_balance' - /// it performs raw state queries on the cosmos sdk - async fn abci_query(&self, path: &str, request: T) -> ChainResult - where - T: Message + hyperlane_cosmos_rs::prost::Name, - R: Message + std::default::Default, - { - let bytes = request.encode_to_vec(); - let response = self - .provider - .call(|client| { - let path = path.to_owned(); - let bytes = bytes.clone(); - let future = async move { - Self::track_metric_call(&client, T::NAME, || { - client - .client - .abci_query(Some(path.to_owned()), bytes.clone(), None, false) - }) - .await - }; - Box::pin(future) - }) - .await?; - - if response.code.is_err() { - return Err(ChainCommunicationError::from_other_str(&format!( - "ABCI query failed: path={}, code={}, log={}", - path, - response.code.value(), - response.log - ))); - } - - let response = R::decode(response.value.as_slice()).map_err(HyperlaneCosmosError::from)?; - Ok(response) - } - - /// Returns the denom balance of that address. Will use the denom specified as the canonical asset in the config - pub async fn get_balance(&self, address: String) -> ChainResult { - let response: QueryBalanceResponse = self - .abci_query( - "/cosmos.bank.v1beta1.Query/Balance", - QueryBalanceRequest { - address, - denom: self.conf.get_canonical_asset(), - }, - ) - .await?; - let balance = response - .balance - .ok_or_else(|| ChainCommunicationError::from_other_str("account not present"))?; - - Ok(U256::from_dec_str(&balance.amount)?) - } - - /// Gets a signer, or returns an error if one is not available. - pub fn get_signer(&self) -> ChainResult<&Signer> { - self.signer - .as_ref() - .ok_or(ChainCommunicationError::SignerUnavailable) - } - - async fn get_account(&self, address: String) -> ChainResult { - let response: QueryAccountResponse = self - .abci_query( - "/cosmos.auth.v1beta1.Query/Account", - QueryAccountRequest { address }, - ) - .await?; - let account = BaseAccount::decode( - response - .account - .ok_or_else(|| ChainCommunicationError::from_other_str("account not present"))? - .value - .as_slice(), - ) - .map_err(HyperlaneCosmosError::from)?; - Ok(account) - } - - /// Get the gas price - pub fn gas_price(&self) -> FixedPointNumber { - self.gas_price.amount.clone() - } - - /// Generates an unsigned SignDoc for a transaction and the Coin amount - /// required to pay for tx fees. - async fn generate_sign_doc( - &self, - msgs: Vec, - gas_limit: u64, - ) -> ChainResult { - // As this function is only used for estimating gas or sending transactions, - // we can reasonably expect to have a signer. - let signer = self.get_signer()?; - let account_info = self.get_account(signer.address_string.clone()).await?; - - // timeout height of zero means that we do not have a timeout height TODO: double check - let tx_body = tx::Body::new(msgs, String::default(), 0u32); - let signer_info = SignerInfo::single_direct(Some(signer.public_key), account_info.sequence); - - let amount: u128 = (FixedPointNumber::from(gas_limit) * self.gas_price()) - .ceil_to_integer() - .try_into()?; - let fee_coin = Coin::new( - // The fee to pay is the gas limit * the gas price - amount, - self.conf.get_canonical_asset().as_str(), - ) - .map_err(HyperlaneCosmosError::from)?; - - let auth_info = - signer_info.auth_info(Fee::from_amount_and_gas(fee_coin.clone(), gas_limit)); - - let chain_id = self - .conf - .get_chain_id() - .parse() - .map_err(HyperlaneCosmosError::from)?; - - Ok( - SignDoc::new(&tx_body, &auth_info, &chain_id, account_info.account_number) - .map_err(HyperlaneCosmosError::from)?, - ) - } - - /// Estimates the gas that will be used when a transaction with msgs is sent. - /// - /// Note: that simulated result will be multiplied by the gas multiplier in the gas config - pub async fn estimate_gas(&self, msgs: Vec) -> ChainResult { - // Get a sign doc with 0 gas, because we plan to simulate - let sign_doc = self.generate_sign_doc(msgs, 0).await?; - - let raw_tx = TxRaw { - body_bytes: sign_doc.body_bytes, - auth_info_bytes: sign_doc.auth_info_bytes, - signatures: vec![vec![]], - }; - let tx_bytes = raw_tx - .to_bytes() - .map_err(ChainCommunicationError::from_other)?; - - #[allow(deprecated)] - let response: SimulateResponse = self - .abci_query( - "/cosmos.tx.v1beta1.Service/Simulate", - SimulateRequest { tx_bytes, tx: None }, - ) - .await?; - - let gas_used = response - .gas_info - .ok_or(ChainCommunicationError::from_other_str( - "gas info not present", - ))? - .gas_used; - - let gas_estimate = (gas_used as f64 * self.conf.get_gas_multiplier()) as u64; - - Ok(gas_estimate) - } - - /// Sends a transaction and waits for confirmation - /// - /// gas_limit will be automatically set if None is passed - pub async fn send( - &self, - msgs: Vec, - gas_limit: Option, - ) -> ChainResult { - let gas_limit = match gas_limit { - Some(limit) => limit, - None => self.estimate_gas(msgs.clone()).await?, - }; - - let sign_doc = self.generate_sign_doc(msgs, gas_limit).await?; - let signer = self.get_signer()?; - - let signed_tx = sign_doc - .sign(&signer.signing_key()?) - .map_err(HyperlaneCosmosError::from)?; - let signed_tx = signed_tx.to_bytes()?; - - // broadcast tx commit blocks until the tx is included in a block - self.provider - .call(|client| { - let signed_tx = signed_tx.clone(); // TODO: this is very memory inefficient, we should actually be able to just reference the slice - let future = async move { - Self::track_metric_call(&client, "send", || { - client.client.broadcast_tx_commit(signed_tx.clone()) - }) - .await - }; - Box::pin(future) - }) - .await - } -} - -#[async_trait] -impl BlockNumberGetter for RpcProvider { - async fn get_block_number(&self) -> ChainResult { - self.provider - .call(|client| { - let future = async move { client.get_block_number().await }; - Box::pin(future) - }) - .await - } -} diff --git a/rust/main/chains/hyperlane-cosmos-native/src/signers.rs b/rust/main/chains/hyperlane-cosmos-native/src/signers.rs deleted file mode 100644 index 500082564c3..00000000000 --- a/rust/main/chains/hyperlane-cosmos-native/src/signers.rs +++ /dev/null @@ -1,62 +0,0 @@ -use cosmrs::crypto::{secp256k1::SigningKey, PublicKey}; - -use hyperlane_core::{AccountAddressType, ChainResult, H256}; - -use crate::{CosmosAddress, HyperlaneCosmosError}; - -#[derive(Clone, Debug)] -/// Signer for cosmos chain -pub struct Signer { - /// public key - pub public_key: PublicKey, - /// cosmos address - pub address: CosmosAddress, - /// precomputed address, because computing it is a fallible operation - /// and we want to avoid returning `Result` - pub address_string: String, - /// address prefix - pub prefix: String, - private_key: Vec, -} - -impl Signer { - /// create new signer - /// - /// # Arguments - /// * `private_key` - private key for signer - /// * `prefix` - prefix for signer address - /// * `account_address_type` - the type of account address used for signer - pub fn new( - private_key: Vec, - prefix: String, - account_address_type: &AccountAddressType, - ) -> ChainResult { - let address = CosmosAddress::from_privkey(&private_key, &prefix, account_address_type)?; - let address_string = address.address(); - let signing_key = Self::build_signing_key(&private_key)?; - let public_key = signing_key.public_key(); - Ok(Self { - public_key, - private_key, - address, - address_string, - prefix, - }) - } - - /// Build a SigningKey from a private key. This cannot be - /// precompiled and stored in `Signer`, because `SigningKey` is not `Sync`. - pub fn signing_key(&self) -> ChainResult { - Self::build_signing_key(&self.private_key) - } - - fn build_signing_key(private_key: &Vec) -> ChainResult { - Ok(SigningKey::from_slice(private_key.as_slice()) - .map_err(Into::::into)?) - } - - /// gets digest of the cosmos account - pub fn address_h256(&self) -> H256 { - self.address.digest() - } -} diff --git a/rust/main/chains/hyperlane-cosmos-native/src/trait_builder.rs b/rust/main/chains/hyperlane-cosmos-native/src/trait_builder.rs deleted file mode 100644 index 7cd9ee76a8a..00000000000 --- a/rust/main/chains/hyperlane-cosmos-native/src/trait_builder.rs +++ /dev/null @@ -1,160 +0,0 @@ -use std::str::FromStr; - -use derive_new::new; -use url::Url; - -use hyperlane_core::{ - config::OpSubmissionConfig, ChainCommunicationError, FixedPointNumber, NativeToken, -}; - -/// Cosmos connection configuration -#[derive(Debug, Clone)] -pub struct ConnectionConf { - /// gRPC urls to connect to - pub grpc_urls: Vec, - /// The RPC url to connect to - rpc_urls: Vec, - /// The chain ID - chain_id: String, - /// The human readable address prefix for the chains using bech32. - bech32_prefix: String, - /// Canonical Assets Denom - canonical_asset: String, - /// The gas price set by the cosmos-sdk validator. Note that this represents the - /// minimum price set by the validator. - /// More details here: https://docs.cosmos.network/main/learn/beginner/gas-fees#antehandler - gas_price: RawCosmosAmount, - /// The gas multiplier is used to estimate gas cost. The gas limit of the simulated transaction will be multiplied by this modifier. - gas_multiplier: f64, - /// The number of bytes used to represent a contract address. - /// Cosmos address lengths are sometimes less than 32 bytes, so this helps to serialize it in - /// bech32 with the appropriate length. - contract_address_bytes: usize, - /// Operation batching configuration - pub operation_batch: OpSubmissionConfig, - /// Native Token - native_token: NativeToken, -} - -/// Untyped cosmos amount -#[derive(serde::Serialize, serde::Deserialize, new, Clone, Debug)] -pub struct RawCosmosAmount { - /// Coin denom (e.g. `untrn`) - pub denom: String, - /// Amount in the given denom - pub amount: String, -} - -/// Typed cosmos amount -#[derive(Clone, Debug)] -pub struct CosmosAmount { - /// Coin denom (e.g. `untrn`) - pub denom: String, - /// Amount in the given denom - pub amount: FixedPointNumber, -} - -impl TryFrom for CosmosAmount { - type Error = ChainCommunicationError; - fn try_from(raw: RawCosmosAmount) -> Result { - Ok(Self { - denom: raw.denom, - amount: FixedPointNumber::from_str(&raw.amount)?, - }) - } -} - -/// An error type when parsing a connection configuration. -#[derive(thiserror::Error, Debug)] -pub enum ConnectionConfError { - /// Missing `rpc_url` for connection configuration - #[error("Missing `rpc_url` for connection configuration")] - MissingConnectionRpcUrl, - /// Missing `grpc_url` for connection configuration - #[error("Missing `grpc_url` for connection configuration")] - MissingConnectionGrpcUrl, - /// Missing `chainId` for connection configuration - #[error("Missing `chainId` for connection configuration")] - MissingChainId, - /// Missing `prefix` for connection configuration - #[error("Missing `prefix` for connection configuration")] - MissingPrefix, - /// Invalid `url` for connection configuration - #[error("Invalid `url` for connection configuration: `{0}` ({1})")] - InvalidConnectionUrl(String, url::ParseError), -} - -impl ConnectionConf { - /// Get the RPC urls - pub fn get_rpc_urls(&self) -> Vec { - self.rpc_urls.clone() - } - - /// Get the chain ID - pub fn get_chain_id(&self) -> String { - self.chain_id.clone() - } - - /// Get the bech32 prefix - pub fn get_bech32_prefix(&self) -> String { - self.bech32_prefix.clone() - } - - /// Get the asset - pub fn get_canonical_asset(&self) -> String { - self.canonical_asset.clone() - } - - /// Get the minimum gas price - pub fn get_minimum_gas_price(&self) -> RawCosmosAmount { - self.gas_price.clone() - } - - /// Get the native token - pub fn get_native_token(&self) -> &NativeToken { - &self.native_token - } - - /// Get the number of bytes used to represent a contract address - pub fn get_contract_address_bytes(&self) -> usize { - self.contract_address_bytes - } - - /// Get gRPC urls - pub fn get_grpc_urls(&self) -> Vec { - self.grpc_urls.clone() - } - - /// Returns the gas multiplier from the config. Used to estimate txn costs more reliable - pub fn get_gas_multiplier(&self) -> f64 { - self.gas_multiplier - } - - /// Create a new connection configuration - #[allow(clippy::too_many_arguments)] - pub fn new( - rpc_urls: Vec, - grpc_urls: Vec, - chain_id: String, - bech32_prefix: String, - canonical_asset: String, - minimum_gas_price: RawCosmosAmount, - gas_multiplier: f64, - contract_address_bytes: usize, - operation_batch: OpSubmissionConfig, - native_token: NativeToken, - ) -> Self { - Self { - grpc_urls, - rpc_urls, - chain_id, - bech32_prefix, - canonical_asset, - gas_price: minimum_gas_price, - contract_address_bytes, - operation_batch, - native_token, - gas_multiplier, - } - } -} diff --git a/rust/main/chains/hyperlane-cosmos/Cargo.toml b/rust/main/chains/hyperlane-cosmos/Cargo.toml index 2f2ff53da6d..ab3ef81ad23 100644 --- a/rust/main/chains/hyperlane-cosmos/Cargo.toml +++ b/rust/main/chains/hyperlane-cosmos/Cargo.toml @@ -12,6 +12,8 @@ version = { workspace = true } async-trait = { workspace = true } base64 = { workspace = true } bech32 = { workspace = true } +cometbft = { workspace = true, features = ["secp256k1"] } +cometbft-rpc = { workspace = true, features = ["http-client","secp256k1"] } cosmrs = { workspace = true, features = ["cosmwasm", "tokio", "grpc", "rpc"] } cosmwasm-std = { workspace = true } crypto = { path = "../../utils/crypto" } @@ -33,8 +35,6 @@ serde = { workspace = true } serde_json = { workspace = true } sha2 = { workspace = true } sha256 = { workspace = true } -tendermint = { workspace = true, features = ["rust-crypto", "secp256k1"] } -tendermint-rpc = { workspace = true } time = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } @@ -48,6 +48,7 @@ tower.workspace = true tracing = { workspace = true } tracing-futures = { workspace = true } url = { workspace = true } +hyperlane-cosmos-rs = {workspace = true} hyperlane-core = { path = "../../hyperlane-core", features = ["async"] } hyperlane-cosmwasm-interface.workspace = true diff --git a/rust/main/chains/hyperlane-cosmos/src/application/operation_verifier.rs b/rust/main/chains/hyperlane-cosmos/src/application/operation_verifier.rs index ad50e7f7558..cdaa7fd7aea 100644 --- a/rust/main/chains/hyperlane-cosmos/src/application/operation_verifier.rs +++ b/rust/main/chains/hyperlane-cosmos/src/application/operation_verifier.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use derive_new::new; use tracing::trace; -use hyperlane_core::{Decode, HyperlaneMessage, HyperlaneProvider, U256}; +use hyperlane_core::{Decode, HyperlaneMessage, U256}; use hyperlane_operation_verifier::{ ApplicationOperationVerifier, ApplicationOperationVerifierReport, }; diff --git a/rust/main/chains/hyperlane-cosmos/src/aggregation_ism.rs b/rust/main/chains/hyperlane-cosmos/src/cw/aggregation_ism.rs similarity index 79% rename from rust/main/chains/hyperlane-cosmos/src/aggregation_ism.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/aggregation_ism.rs index de2fc9432f6..630cb609ec3 100644 --- a/rust/main/chains/hyperlane-cosmos/src/aggregation_ism.rs +++ b/rust/main/chains/hyperlane-cosmos/src/cw/aggregation_ism.rs @@ -1,13 +1,10 @@ use std::str::FromStr; -use crate::{ - grpc::WasmProvider, - payloads::{ - ism_routes::QueryIsmGeneralRequest, - multisig_ism::{VerifyInfoRequest, VerifyInfoRequestInner, VerifyInfoResponse}, - }, - ConnectionConf, CosmosProvider, Signer, +use super::payloads::{ + ism_routes::QueryIsmGeneralRequest, + multisig_ism::{VerifyInfoRequest, VerifyInfoRequestInner, VerifyInfoResponse}, }; +use crate::{cw::CwQueryClient, CosmosProvider}; use async_trait::async_trait; use hyperlane_core::{ AggregationIsm, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, @@ -17,15 +14,18 @@ use tracing::instrument; /// A reference to an AggregationIsm contract on some Cosmos chain #[derive(Debug)] -pub struct CosmosAggregationIsm { +pub struct CwAggregationIsm { domain: HyperlaneDomain, address: H256, - provider: Box, + provider: Box>, } -impl CosmosAggregationIsm { +impl CwAggregationIsm { /// create new Cosmos AggregationIsm agent - pub fn new(provider: CosmosProvider, locator: ContractLocator) -> ChainResult { + pub fn new( + provider: CosmosProvider, + locator: ContractLocator, + ) -> ChainResult { Ok(Self { domain: locator.domain.clone(), address: locator.address, @@ -34,13 +34,13 @@ impl CosmosAggregationIsm { } } -impl HyperlaneContract for CosmosAggregationIsm { +impl HyperlaneContract for CwAggregationIsm { fn address(&self) -> H256 { self.address } } -impl HyperlaneChain for CosmosAggregationIsm { +impl HyperlaneChain for CwAggregationIsm { fn domain(&self) -> &HyperlaneDomain { &self.domain } @@ -51,7 +51,7 @@ impl HyperlaneChain for CosmosAggregationIsm { } #[async_trait] -impl AggregationIsm for CosmosAggregationIsm { +impl AggregationIsm for CwAggregationIsm { #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue #[instrument(err)] async fn modules_and_threshold( @@ -66,7 +66,7 @@ impl AggregationIsm for CosmosAggregationIsm { let data = self .provider - .grpc() + .query() .wasm_query(QueryIsmGeneralRequest { ism: payload }, None) .await?; let response: VerifyInfoResponse = serde_json::from_slice(&data)?; diff --git a/rust/main/chains/hyperlane-cosmos/src/cw/cw_query_client.rs b/rust/main/chains/hyperlane-cosmos/src/cw/cw_query_client.rs new file mode 100644 index 00000000000..e93ce737f32 --- /dev/null +++ b/rust/main/chains/hyperlane-cosmos/src/cw/cw_query_client.rs @@ -0,0 +1,328 @@ +use std::str::FromStr; + +use cosmrs::{ + cosmwasm::MsgExecuteContract as MsgExecuteContractRequest, + proto::{ + self, + cosmwasm::wasm::v1::{ + query_client::QueryClient as WasmQueryClient, ContractInfo, MsgExecuteContract, + QueryContractInfoRequest, QuerySmartContractStateRequest, + }, + }, + AccountId, Any, Tx, +}; +use serde::Serialize; +use tonic::async_trait; +use tracing::warn; + +use hyperlane_core::{ChainCommunicationError, ChainResult, H256, H512}; + +use crate::{ + BuildableQueryClient, CosmosAccountId, CosmosAddress, GrpcProvider, HyperlaneCosmosError, +}; + +use super::payloads::packet_data::PacketData; + +/// A client for querying a CosmWasm contract. +/// This client is used to query the state of a contract, and to encode messages to be sent to the contract. +#[derive(Clone, Debug)] +pub struct CwQueryClient { + /// grpc provider + grpc: GrpcProvider, + contract_address: CosmosAddress, + signer: Option, + prefix: String, + address_bytes: usize, +} + +#[async_trait] +impl BuildableQueryClient for CwQueryClient { + fn build_query_client( + grpc: GrpcProvider, + conf: &crate::ConnectionConf, + locator: &hyperlane_core::ContractLocator, + signer: Option, + ) -> hyperlane_core::ChainResult { + let contract_address = CosmosAddress::from_h256( + locator.address, + &conf.get_bech32_prefix(), + conf.get_contract_address_bytes(), + )?; + Ok(Self { + grpc, + contract_address, + signer, + prefix: conf.get_bech32_prefix(), + address_bytes: conf.get_contract_address_bytes(), + }) + } + + async fn is_contract(&self, address: &H256) -> ChainResult { + let cosmos_address = CosmosAddress::from_h256(*address, &self.prefix, self.address_bytes)?; + match self.wasm_contract_info(cosmos_address.address()).await { + Ok(_) => Ok(true), + Err(_) => Ok(false), + } + } + + // extract the message recipient contract address from the tx + // this is implementation specific + fn parse_tx_message_recipient(&self, tx: &Tx, tx_hash: &H512) -> ChainResult { + Self::contract(tx, tx_hash) + } + + /// Returns the Block height of the query client + async fn get_block_number(&self) -> ChainResult { + self.grpc.get_block_number().await + } +} + +impl CwQueryClient { + // Extract contract address from transaction. + fn contract(tx: &Tx, tx_hash: &H512) -> ChainResult { + // We merge two error messages together so that both of them are reported + match Self::contract_address_from_msg_execute_contract(tx) { + Ok(contract) => Ok(contract), + Err(msg_execute_contract_error) => { + match Self::contract_address_from_msg_recv_packet(tx) { + Ok(contract) => Ok(contract), + Err(msg_recv_packet_error) => { + let errors = vec![msg_execute_contract_error, msg_recv_packet_error]; + let error = HyperlaneCosmosError::ParsingAttemptsFailed(errors); + warn!(?tx_hash, ?error); + Err(ChainCommunicationError::from_other(error))? + } + } + } + } + } + + /// Assumes that there is only one `MsgExecuteContract` message in the transaction + fn contract_address_from_msg_execute_contract(tx: &Tx) -> Result { + let contract_execution_messages = tx + .body + .messages + .iter() + .filter(|a| a.type_url == "/cosmwasm.wasm.v1.MsgExecuteContract") + .cloned() + .collect::>(); + + let contract_execution_messages_len = contract_execution_messages.len(); + if contract_execution_messages_len > 1 { + let msg = "transaction contains multiple contract execution messages"; + Err(HyperlaneCosmosError::ParsingFailed(msg.to_owned()))? + } + + let any = contract_execution_messages.first().ok_or_else(|| { + let msg = "could not find contract execution message"; + HyperlaneCosmosError::ParsingFailed(msg.to_owned()) + })?; + let proto: proto::cosmwasm::wasm::v1::MsgExecuteContract = + any.to_msg().map_err(Into::::into)?; + let msg = MsgExecuteContractRequest::try_from(proto)?; + let contract = H256::try_from(CosmosAccountId::new(&msg.contract))?; + + Ok(contract) + } + + fn contract_address_from_msg_recv_packet(tx: &Tx) -> Result { + let packet_data = tx + .body + .messages + .iter() + .filter(|a| a.type_url == "/ibc.core.channel.v1.MsgRecvPacket") + .map(PacketData::try_from) + .flat_map(|r| r.ok()) + .next() + .ok_or_else(|| { + let msg = "could not find IBC receive packets message containing receiver address"; + HyperlaneCosmosError::ParsingFailed(msg.to_owned()) + })?; + + let account_id = AccountId::from_str(&packet_data.receiver).map_err(Box::new)?; + let address = H256::try_from(CosmosAccountId::new(&account_id))?; + + Ok(address) + } + + /// Gets a signer, or returns an error if one is not available. + fn get_signer(&self) -> ChainResult<&crate::Signer> { + self.signer + .as_ref() + .ok_or(ChainCommunicationError::SignerUnavailable) + } + + /// Gets the contract info for the configured address in the query client + /// Can check if the contract is a valid CosmWasm contract + pub async fn wasm_contract_info(&self, address: String) -> ChainResult { + let response = self + .grpc + .call(move |provider| { + let address = address.clone(); + let future = async move { + let address = address.clone(); + let mut client = WasmQueryClient::new(provider.channel()); + let request = tonic::Request::new(QueryContractInfoRequest { address }); + + let response = client + .contract_info(request) + .await + .map_err(ChainCommunicationError::from_other)? + .into_inner() + .contract_info + .ok_or(ChainCommunicationError::from_other_str( + "empty contract info", + ))?; + Ok(response) + }; + Box::pin(future) + }) + .await?; + + Ok(response) + } + + /// Encodes a contract payload to a message that can be sent to the chain + pub fn wasm_encode_msg(&self, payload: T) -> ChainResult + where + T: Serialize + Send + Sync, + { + // Estimating gas requires a signer, which we can reasonably expect to have + // since we need one to send a tx with the estimated gas anyways. + let signer = self.get_signer()?; + let contract_address = self.contract_address.address(); + let msg = MsgExecuteContract { + sender: signer.address_string.clone(), + contract: contract_address, + msg: serde_json::to_string(&payload)?.as_bytes().to_vec(), + funds: vec![], + }; + + Any::from_msg(&msg).map_err(ChainCommunicationError::from_other) + } + + /// Executes a state query on the contract + pub async fn wasm_query(&self, payload: T, block_height: Option) -> ChainResult> + where + T: Serialize + Send + Sync + Clone + std::fmt::Debug, + { + let contract_address = self.contract_address.address(); + let query_data = serde_json::to_string(&payload)?.as_bytes().to_vec(); + let response = self + .grpc + .call(move |provider| { + let to = contract_address.clone(); + let query_data = query_data.clone(); + let future = async move { + let mut client = WasmQueryClient::new(provider.channel()); + let mut request = tonic::Request::new(QuerySmartContractStateRequest { + address: to.clone(), + query_data: query_data.clone(), + }); + if let Some(block_height) = block_height { + request + .metadata_mut() + .insert("x-cosmos-block-height", block_height.into()); + } + let response = client + .smart_contract_state(request) + .await + .map_err(ChainCommunicationError::from_other); + Ok(response?.into_inner().data) + }; + Box::pin(future) + }) + .await?; + Ok(response) + } +} + +#[cfg(test)] + +mod test { + use std::str::FromStr; + + use cometbft_rpc::client::CompatMode; + use hyperlane_metric::prometheus_metric::PrometheusClientMetrics; + use url::Url; + + use hyperlane_core::{ + config::OpSubmissionConfig, ContractLocator, HyperlaneDomain, KnownHyperlaneDomain, + NativeToken, + }; + + use super::{BuildableQueryClient, CwQueryClient}; + use crate::{ConnectionConf, CosmosAddress, GrpcProvider, RawCosmosAmount}; + + #[ignore] + #[tokio::test] + async fn test_wasm_contract_info_success() { + // given + let provider = + provider("neutron1sjzzd4gwkggy6hrrs8kxxatexzcuz3jecsxm3wqgregkulzj8r7qlnuef4"); + + // when + let result = provider + .wasm_contract_info(provider.contract_address.address()) + .await; + // then + assert!(result.is_ok()); + + let contract_info = result.unwrap(); + + assert_eq!( + contract_info.creator, + "neutron1dwnrgwsf5c9vqjxsax04pdm0mx007yrre4yyvm", + ); + assert_eq!( + contract_info.admin, + "neutron1fqf5mprg3f5hytvzp3t7spmsum6rjrw80mq8zgkc0h6rxga0dtzqws3uu7", + ); + } + + #[ignore] + #[tokio::test] + async fn test_wasm_contract_info_no_contract() { + // given + let provider = provider("neutron1dwnrgwsf5c9vqjxsax04pdm0mx007yrre4yyvm"); + + // when + let result = provider + .wasm_contract_info(provider.contract_address.address()) + .await; + + // then + assert!(result.is_err()); + } + + fn provider(address: &str) -> CwQueryClient { + let domain = HyperlaneDomain::Known(KnownHyperlaneDomain::Neutron); + let address = CosmosAddress::from_str(address).unwrap(); + let locator = ContractLocator::new(&domain, address.digest()); + let conf = ConnectionConf::new( + vec![Url::parse("http://grpc-kralum.neutron-1.neutron.org:80").unwrap()], + vec![Url::parse("https://rpc-kralum.neutron-1.neutron.org").unwrap()], + "neutron-1".to_owned(), + "neutron".to_owned(), + "untrn".to_owned(), + RawCosmosAmount::new("untrn".to_owned(), "0".to_owned()), + 32, + OpSubmissionConfig { + batch_contract_address: None, + max_batch_size: 1, + ..Default::default() + }, + NativeToken { + decimals: 6, + denom: "untrn".to_owned(), + }, + 1.4f64, + None, + ) + .unwrap(); + + let grpc = GrpcProvider::new(&conf, PrometheusClientMetrics::default(), None).unwrap(); + + CwQueryClient::build_query_client(grpc, &conf, &locator, None).unwrap() + } +} diff --git a/rust/main/chains/hyperlane-cosmos/src/interchain_gas.rs b/rust/main/chains/hyperlane-cosmos/src/cw/interchain_gas.rs similarity index 78% rename from rust/main/chains/hyperlane-cosmos/src/interchain_gas.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/interchain_gas.rs index b8e8a6bbc6e..566dfffd238 100644 --- a/rust/main/chains/hyperlane-cosmos/src/interchain_gas.rs +++ b/rust/main/chains/hyperlane-cosmos/src/cw/interchain_gas.rs @@ -1,9 +1,10 @@ use std::ops::RangeInclusive; +use std::str::FromStr; use async_trait::async_trait; use base64::{engine::general_purpose::STANDARD as BASE64, Engine}; +use cometbft::abci::EventAttribute; use once_cell::sync::Lazy; -use tendermint::abci::EventAttribute; use tracing::instrument; use hyperlane_core::{ @@ -12,29 +13,25 @@ use hyperlane_core::{ InterchainGasPayment, LogMeta, SequenceAwareIndexer, H256, H512, U256, }; -use crate::rpc::{CosmosWasmRpcProvider, ParsedEvent, WasmRpcProvider}; -use crate::signers::Signer; -use crate::utils::{ - execute_and_parse_log_futures, parse_logs_in_range, parse_logs_in_tx, - CONTRACT_ADDRESS_ATTRIBUTE_KEY, CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64, -}; -use crate::{ConnectionConf, CosmosProvider, HyperlaneCosmosError}; +use crate::indexer::{CosmosEventIndexer, ParsedEvent}; +use crate::utils::{CONTRACT_ADDRESS_ATTRIBUTE_KEY, CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64}; +use crate::{cw::CwQueryClient, CosmosAddress, CosmosProvider, HyperlaneCosmosError, RpcProvider}; /// A reference to a InterchainGasPaymaster contract on some Cosmos chain #[derive(Debug)] -pub struct CosmosInterchainGasPaymaster { +pub struct CwInterchainGasPaymaster { domain: HyperlaneDomain, address: H256, - provider: CosmosProvider, + provider: CosmosProvider, } -impl HyperlaneContract for CosmosInterchainGasPaymaster { +impl HyperlaneContract for CwInterchainGasPaymaster { fn address(&self) -> H256 { self.address } } -impl HyperlaneChain for CosmosInterchainGasPaymaster { +impl HyperlaneChain for CwInterchainGasPaymaster { fn domain(&self) -> &HyperlaneDomain { &self.domain } @@ -44,11 +41,14 @@ impl HyperlaneChain for CosmosInterchainGasPaymaster { } } -impl InterchainGasPaymaster for CosmosInterchainGasPaymaster {} +impl InterchainGasPaymaster for CwInterchainGasPaymaster {} -impl CosmosInterchainGasPaymaster { +impl CwInterchainGasPaymaster { /// create new Cosmos InterchainGasPaymaster agent - pub fn new(provider: CosmosProvider, locator: ContractLocator) -> ChainResult { + pub fn new( + provider: CosmosProvider, + locator: ContractLocator, + ) -> ChainResult { Ok(Self { domain: locator.domain.clone(), address: locator.address, @@ -77,24 +77,25 @@ static DESTINATION_ATTRIBUTE_KEY_BASE64: Lazy = /// A reference to a InterchainGasPaymasterIndexer contract on some Cosmos chain #[derive(Debug, Clone)] -pub struct CosmosInterchainGasPaymasterIndexer { - provider: Box, +pub struct CwInterchainGasPaymasterIndexer { + provider: CosmosProvider, + address: H256, } -impl CosmosInterchainGasPaymasterIndexer { - /// The interchain gas payment event type from the CW contract. - pub const INTERCHAIN_GAS_PAYMENT_EVENT_TYPE: &'static str = "igp-core-pay-for-gas"; +impl CwInterchainGasPaymasterIndexer { + const INTERCHAIN_GAS_PAYMENT_EVENT_TYPE: &'static str = "wasm-igp-core-pay-for-gas"; - /// create new Cosmos InterchainGasPaymasterIndexer agent - pub fn new(provider: CosmosWasmRpcProvider) -> ChainResult { - Ok(Self { - provider: Box::new(provider), - }) + /// create new Cosmos InterchainGasPaymaster agent + pub fn new(provider: CosmosProvider, locator: &ContractLocator) -> Self { + Self { + provider, + address: locator.address, + } } - #[instrument(err)] + // TODO: this can be simplified heavily if we create a construct that autoamtically converts to base64 & ensures all keys are present fn interchain_gas_payment_parser( - attrs: &Vec, + attrs: &[EventAttribute], ) -> ChainResult> { let mut contract_address: Option = None; let mut gas_payment = IncompleteInterchainGasPayment::default(); @@ -176,7 +177,7 @@ impl CosmosInterchainGasPaymasterIndexer { } } - EventAttribute::V034(a) => { + EventAttribute::V034(_a) => { unimplemented!(); } } @@ -184,51 +185,61 @@ impl CosmosInterchainGasPaymasterIndexer { let contract_address = contract_address .ok_or_else(|| ChainCommunicationError::from_other_str("missing contract_address"))?; + Ok(ParsedEvent::new( + CosmosAddress::from_str(&contract_address)?.digest(), + gas_payment.try_into()?, + )) + } +} - Ok(ParsedEvent::new(contract_address, gas_payment.try_into()?)) +impl CosmosEventIndexer for CwInterchainGasPaymasterIndexer { + /// Target event to index + fn target_type() -> String { + Self::INTERCHAIN_GAS_PAYMENT_EVENT_TYPE.to_owned() + } + + /// Cosmos provider + fn provider(&self) -> &RpcProvider { + self.provider.rpc() + } + + /// parses the event attributes to the target type + fn parse(&self, attrs: &[EventAttribute]) -> ChainResult> { + Self::interchain_gas_payment_parser(attrs) + } + + /// address for the given module that will be indexed + fn address(&self) -> &H256 { + &self.address } } #[async_trait] -impl Indexer for CosmosInterchainGasPaymasterIndexer { +impl Indexer for CwInterchainGasPaymasterIndexer { async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { - let logs_futures = parse_logs_in_range( - range, - self.provider.clone(), - Self::interchain_gas_payment_parser, - "InterchainGasPaymentCursor", - ); - - execute_and_parse_log_futures(logs_futures).await + CosmosEventIndexer::fetch_logs_in_range(self, range).await } async fn get_finalized_block_number(&self) -> ChainResult { - self.provider.get_finalized_block_number().await + CosmosEventIndexer::get_finalized_block_number(self).await } async fn fetch_logs_by_tx_hash( &self, tx_hash: H512, ) -> ChainResult, LogMeta)>> { - parse_logs_in_tx( - &tx_hash.into(), - self.provider.clone(), - Self::interchain_gas_payment_parser, - "InterchainGasPaymentReceiver", - ) - .await - .map(|v| v.into_iter().map(|(m, l)| (m.into(), l)).collect()) + CosmosEventIndexer::fetch_logs_by_tx_hash(self, tx_hash).await } } #[async_trait] -impl SequenceAwareIndexer for CosmosInterchainGasPaymasterIndexer { +impl SequenceAwareIndexer for CwInterchainGasPaymasterIndexer { async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { // TODO: implement when cosmwasm scraper support is implemented - let tip = self.get_finalized_block_number().await?; + let tip = CosmosEventIndexer::get_finalized_block_number(self).await?; Ok((None, tip)) } } @@ -273,7 +284,7 @@ mod tests { use hyperlane_core::{InterchainGasPayment, H256, U256}; - use crate::providers::rpc::ParsedEvent; + use crate::indexer::ParsedEvent; use crate::utils::event_attributes_from_str; use super::*; @@ -281,9 +292,12 @@ mod tests { #[test] fn test_interchain_gas_payment_parser() { // Examples from https://rpc-kralum.neutron-1.neutron.org/tx_search?query=%22tx.height%20%3E=%204000000%20AND%20tx.height%20%3C=%204100000%20AND%20wasm-igp-core-pay-for-gas._contract_address%20=%20%27neutron12p8wntzra3vpfcqv05scdx5sa3ftaj6gjcmtm7ynkl0e6crtt4ns8cnrmx%27%22&prove=false&page=1&per_page=100 - + let contract_address = CosmosAddress::from_str( + "neutron12p8wntzra3vpfcqv05scdx5sa3ftaj6gjcmtm7ynkl0e6crtt4ns8cnrmx", + ); + assert!(contract_address.is_ok()); let expected = ParsedEvent::new( - "neutron12p8wntzra3vpfcqv05scdx5sa3ftaj6gjcmtm7ynkl0e6crtt4ns8cnrmx".into(), + contract_address.unwrap().digest(), InterchainGasPayment { message_id: H256::from_str( "5dcf6120f8adf4f267eb1a122a85c42eae257fbc872671e93929fbf63daed19b", @@ -297,7 +311,8 @@ mod tests { let assert_parsed_event = |attrs: &Vec| { let parsed_event = - CosmosInterchainGasPaymasterIndexer::interchain_gas_payment_parser(attrs).unwrap(); + CwInterchainGasPaymasterIndexer::interchain_gas_payment_parser(attrs.as_slice()) + .unwrap(); assert_eq!(parsed_event, expected); }; diff --git a/rust/main/chains/hyperlane-cosmos/src/interchain_security_module.rs b/rust/main/chains/hyperlane-cosmos/src/cw/interchain_security_module.rs similarity index 78% rename from rust/main/chains/hyperlane-cosmos/src/interchain_security_module.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/interchain_security_module.rs index 5294eea7253..918c8dc983e 100644 --- a/rust/main/chains/hyperlane-cosmos/src/interchain_security_module.rs +++ b/rust/main/chains/hyperlane-cosmos/src/cw/interchain_security_module.rs @@ -5,32 +5,33 @@ use hyperlane_core::{ H256, U256, }; -use crate::{ - grpc::WasmProvider, - payloads::{ - aggregate_ism::{VerifyRequest, VerifyRequestInner, VerifyResponse}, - general::EmptyStruct, - ism_routes::{QueryIsmGeneralRequest, QueryIsmModuleTypeRequest}, - }, - types::IsmType, - ConnectionConf, CosmosProvider, Signer, +use super::payloads::{ + aggregate_ism::{VerifyRequest, VerifyRequestInner, VerifyResponse}, + general::EmptyStruct, + ism_routes::{QueryIsmGeneralRequest, QueryIsmModuleTypeRequest}, }; +use super::types::IsmType; +use super::CwQueryClient; +use crate::CosmosProvider; #[derive(Debug)] /// The Cosmos Interchain Security Module. -pub struct CosmosInterchainSecurityModule { +pub struct CwInterchainSecurityModule { /// The domain of the ISM contract. domain: HyperlaneDomain, /// The address of the ISM contract. address: H256, /// The provider for the ISM contract. - provider: CosmosProvider, + provider: CosmosProvider, } /// The Cosmos Interchain Security Module Implementation. -impl CosmosInterchainSecurityModule { +impl CwInterchainSecurityModule { /// Creates a new Cosmos Interchain Security Module. - pub fn new(provider: CosmosProvider, locator: ContractLocator) -> ChainResult { + pub fn new( + provider: CosmosProvider, + locator: ContractLocator, + ) -> ChainResult { Ok(Self { domain: locator.domain.clone(), address: locator.address, @@ -39,13 +40,13 @@ impl CosmosInterchainSecurityModule { } } -impl HyperlaneContract for CosmosInterchainSecurityModule { +impl HyperlaneContract for CwInterchainSecurityModule { fn address(&self) -> H256 { self.address } } -impl HyperlaneChain for CosmosInterchainSecurityModule { +impl HyperlaneChain for CwInterchainSecurityModule { fn domain(&self) -> &HyperlaneDomain { &self.domain } @@ -56,7 +57,7 @@ impl HyperlaneChain for CosmosInterchainSecurityModule { } #[async_trait] -impl InterchainSecurityModule for CosmosInterchainSecurityModule { +impl InterchainSecurityModule for CwInterchainSecurityModule { /// Returns the module type of the ISM compliant with the corresponding /// metadata offchain fetching and onchain formatting standard. async fn module_type(&self) -> ChainResult { @@ -66,7 +67,7 @@ impl InterchainSecurityModule for CosmosInterchainSecurityModule { let data = self .provider - .grpc() + .query() .wasm_query(QueryIsmGeneralRequest { ism: query }, None) .await?; @@ -90,7 +91,7 @@ impl InterchainSecurityModule for CosmosInterchainSecurityModule { }; let data = self .provider - .grpc() + .query() .wasm_query(QueryIsmGeneralRequest { ism: payload }, None) .await?; let response: VerifyResponse = serde_json::from_slice(&data)?; diff --git a/rust/main/chains/hyperlane-cosmos/src/cw/mailbox.rs b/rust/main/chains/hyperlane-cosmos/src/cw/mailbox.rs new file mode 100644 index 00000000000..b6abe13bacc --- /dev/null +++ b/rust/main/chains/hyperlane-cosmos/src/cw/mailbox.rs @@ -0,0 +1,10 @@ +pub use contract::CwMailbox; +pub use delivery_indexer::CwMailboxDeliveryIndexer; +pub use dispatch_indexer::CwMailboxDispatchIndexer; + +mod contract; + +/// Cosmos Mailbox Delivery Indexer +pub mod delivery_indexer; +/// Cosmos Mailbox Dispatch Indexer +pub mod dispatch_indexer; diff --git a/rust/main/chains/hyperlane-cosmos/src/mailbox/contract.rs b/rust/main/chains/hyperlane-cosmos/src/cw/mailbox/contract.rs similarity index 83% rename from rust/main/chains/hyperlane-cosmos/src/mailbox/contract.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/mailbox/contract.rs index de13975603f..8af201ea0e5 100644 --- a/rust/main/chains/hyperlane-cosmos/src/mailbox/contract.rs +++ b/rust/main/chains/hyperlane-cosmos/src/cw/mailbox/contract.rs @@ -1,7 +1,6 @@ use std::str::FromStr; use async_trait::async_trait; -use cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse; use tracing::instrument; use hyperlane_core::{ @@ -10,29 +9,27 @@ use hyperlane_core::{ ReorgPeriod, TxCostEstimate, TxOutcome, H256, U256, }; -use crate::grpc::WasmProvider; -use crate::payloads::general; -use crate::payloads::mailbox::{ +use super::super::payloads; +use super::super::payloads::general; +use super::super::payloads::mailbox::{ GeneralMailboxQuery, ProcessMessageRequest, ProcessMessageRequestInner, }; -use crate::types::tx_response_to_outcome; -use crate::utils::get_block_height_for_reorg_period; -use crate::{payloads, ConnectionConf, CosmosAddress, CosmosProvider}; +use crate::{cw::CwQueryClient, utils, ConnectionConf, CosmosAddress, CosmosProvider}; #[derive(Clone, Debug)] /// A reference to a Mailbox contract on some Cosmos chain -pub struct CosmosMailbox { +pub struct CwMailbox { config: ConnectionConf, domain: HyperlaneDomain, address: H256, - provider: CosmosProvider, + provider: CosmosProvider, } -impl CosmosMailbox { +impl CwMailbox { /// Create a reference to a mailbox at a specific Cosmos address on some /// chain pub fn new( - provider: CosmosProvider, + provider: CosmosProvider, conf: ConnectionConf, locator: ContractLocator, ) -> ChainResult { @@ -54,13 +51,13 @@ impl CosmosMailbox { } } -impl HyperlaneContract for CosmosMailbox { +impl HyperlaneContract for CwMailbox { fn address(&self) -> H256 { self.address } } -impl HyperlaneChain for CosmosMailbox { +impl HyperlaneChain for CwMailbox { fn domain(&self) -> &HyperlaneDomain { &self.domain } @@ -71,12 +68,11 @@ impl HyperlaneChain for CosmosMailbox { } #[async_trait] -impl Mailbox for CosmosMailbox { +impl Mailbox for CwMailbox { #[instrument(level = "debug", err, ret, skip(self))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult { - let block_height = - get_block_height_for_reorg_period(self.provider.grpc(), reorg_period).await?; + let block_height = self.provider.reorg_to_height(reorg_period).await?; self.nonce_at_block(block_height).await } @@ -90,7 +86,7 @@ impl Mailbox for CosmosMailbox { let delivered = self .provider - .grpc() + .query() .wasm_query(GeneralMailboxQuery { mailbox: payload }, None) .await .map(|v| serde_json::from_slice::(&v))??; @@ -107,7 +103,7 @@ impl Mailbox for CosmosMailbox { let data = self .provider - .grpc() + .query() .wasm_query(GeneralMailboxQuery { mailbox: payload }, None) .await?; let response: payloads::mailbox::DefaultIsmResponse = serde_json::from_slice(&data)?; @@ -135,7 +131,7 @@ impl Mailbox for CosmosMailbox { let data = self .provider - .grpc() + .query() .wasm_query(GeneralMailboxQuery { mailbox: payload }, None) .await?; let response: payloads::mailbox::RecipientIsmResponse = serde_json::from_slice(&data)?; @@ -160,13 +156,18 @@ impl Mailbox for CosmosMailbox { }, }; - let response: TxResponse = self + let msg = self.provider.query().wasm_encode_msg(process_message)?; + + let response = self .provider - .grpc() - .wasm_send(process_message, tx_gas_limit) + .rpc() + .send(vec![msg], tx_gas_limit.map(|gas| gas.as_u64())) .await?; - Ok(tx_response_to_outcome(response)?) + Ok(utils::tx_response_to_outcome( + response, + self.provider.rpc().gas_price(), + )) } #[instrument(err, ret, skip(self), fields(hyp_message=%message, metadata=%bytes_to_hex(metadata)))] @@ -182,16 +183,12 @@ impl Mailbox for CosmosMailbox { metadata: hex::encode(metadata), }, }; - - let gas_limit = self - .provider - .grpc() - .wasm_estimate_gas(process_message) - .await?; + let msg = self.provider.query().wasm_encode_msg(process_message)?; + let gas_limit = self.provider.rpc().estimate_gas(vec![msg]).await?; let result = TxCostEstimate { gas_limit: gas_limit.into(), - gas_price: self.provider.grpc().gas_price(), + gas_price: self.provider.rpc().gas_price(), l2_gas_limit: None, }; @@ -206,12 +203,12 @@ impl Mailbox for CosmosMailbox { todo!() // not required } - fn delivered_calldata(&self, message_id: H256) -> ChainResult>> { + fn delivered_calldata(&self, _message_id: H256) -> ChainResult>> { todo!() } } -impl CosmosMailbox { +impl CwMailbox { #[instrument(level = "debug", err, ret, skip(self))] pub(crate) async fn nonce_at_block(&self, block_height: u64) -> ChainResult { let payload = payloads::mailbox::NonceRequest { @@ -220,7 +217,7 @@ impl CosmosMailbox { let data = self .provider - .grpc() + .query() .wasm_query(GeneralMailboxQuery { mailbox: payload }, Some(block_height)) .await?; diff --git a/rust/main/chains/hyperlane-cosmos/src/mailbox/delivery_indexer.rs b/rust/main/chains/hyperlane-cosmos/src/cw/mailbox/delivery_indexer.rs similarity index 64% rename from rust/main/chains/hyperlane-cosmos/src/mailbox/delivery_indexer.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/mailbox/delivery_indexer.rs index dd37baf8139..b609e3bc0cd 100644 --- a/rust/main/chains/hyperlane-cosmos/src/mailbox/delivery_indexer.rs +++ b/rust/main/chains/hyperlane-cosmos/src/cw/mailbox/delivery_indexer.rs @@ -1,49 +1,49 @@ use std::borrow::ToOwned; -use std::fmt::{Debug, Formatter}; +use std::fmt::Debug; use std::ops::RangeInclusive; +use std::str::FromStr; use async_trait::async_trait; use base64::{engine::general_purpose::STANDARD as BASE64, Engine}; +use cometbft::abci::EventAttribute; use once_cell::sync::Lazy; -use tendermint::abci::EventAttribute; use tracing::instrument; use hyperlane_core::{ - ChainCommunicationError, ChainResult, ContractLocator, Delivery, HyperlaneMessage, Indexed, - Indexer, LogMeta, SequenceAwareIndexer, H256, H512, + ChainCommunicationError, ChainResult, ContractLocator, Delivery, Indexed, Indexer, LogMeta, + SequenceAwareIndexer, H256, H512, }; -use crate::rpc::{CosmosWasmRpcProvider, ParsedEvent, WasmRpcProvider}; -use crate::utils::{ - execute_and_parse_log_futures, parse_logs_in_range, parse_logs_in_tx, - CONTRACT_ADDRESS_ATTRIBUTE_KEY, CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64, -}; -use crate::{ConnectionConf, HyperlaneCosmosError, Signer}; +use crate::cw::CwQueryClient; +use crate::indexer::{CosmosEventIndexer, ParsedEvent}; +use crate::utils::{CONTRACT_ADDRESS_ATTRIBUTE_KEY, CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64}; +use crate::{CosmosAddress, CosmosProvider, HyperlaneCosmosError, RpcProvider}; /// The message process event type from the CW contract. -pub const MESSAGE_DELIVERY_EVENT_TYPE: &str = "mailbox_process_id"; +pub const MESSAGE_DELIVERY_EVENT_TYPE: &str = "wasm-mailbox_process_id"; const MESSAGE_ID_ATTRIBUTE_KEY: &str = "message_id"; static MESSAGE_ID_ATTRIBUTE_KEY_BASE64: Lazy = Lazy::new(|| BASE64.encode(MESSAGE_ID_ATTRIBUTE_KEY)); /// Struct that retrieves delivery event data for a Cosmos Mailbox contract -pub struct CosmosMailboxDeliveryIndexer { - provider: Box, +#[derive(Debug, Clone)] +pub struct CwMailboxDeliveryIndexer { + provider: CosmosProvider, + address: H256, } -impl CosmosMailboxDeliveryIndexer { +impl CwMailboxDeliveryIndexer { /// Create a reference to a mailbox at a specific Cosmos address on some /// chain - pub fn new(wasm_provider: CosmosWasmRpcProvider) -> ChainResult { - Ok(Self { - provider: Box::new(wasm_provider), - }) + pub fn new(provider: CosmosProvider, locator: &ContractLocator) -> Self { + Self { + provider, + address: locator.address, + } } #[instrument(err)] - fn hyperlane_delivery_parser( - attrs: &Vec, - ) -> ChainResult> { + fn hyperlane_delivery_parser(attrs: &[EventAttribute]) -> ChainResult> { let mut contract_address: Option = None; let mut message_id: Option = None; @@ -81,7 +81,7 @@ impl CosmosMailboxDeliveryIndexer { } } - EventAttribute::V034(a) => { + EventAttribute::V034(_a) => { unimplemented!(); } } @@ -92,53 +92,54 @@ impl CosmosMailboxDeliveryIndexer { let message_id = message_id .ok_or_else(|| ChainCommunicationError::from_other_str("missing message_id"))?; - Ok(ParsedEvent::new(contract_address, message_id)) + Ok(ParsedEvent::new( + CosmosAddress::from_str(&contract_address)?.digest(), + message_id, + )) } } -impl Debug for CosmosMailboxDeliveryIndexer { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - todo!() +impl CosmosEventIndexer for CwMailboxDeliveryIndexer { + fn target_type() -> String { + MESSAGE_DELIVERY_EVENT_TYPE.to_owned() + } + + fn provider(&self) -> &RpcProvider { + self.provider.rpc() + } + + fn parse(&self, attrs: &[EventAttribute]) -> ChainResult> { + Self::hyperlane_delivery_parser(attrs) + } + + fn address(&self) -> &H256 { + &self.address } } #[async_trait] -impl Indexer for CosmosMailboxDeliveryIndexer { +impl Indexer for CwMailboxDeliveryIndexer { async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { - let logs_futures = parse_logs_in_range( - range, - self.provider.clone(), - Self::hyperlane_delivery_parser, - "DeliveryCursor", - ); - - execute_and_parse_log_futures(logs_futures).await + CosmosEventIndexer::fetch_logs_in_range(self, range).await } async fn get_finalized_block_number(&self) -> ChainResult { - self.provider.get_finalized_block_number().await + CosmosEventIndexer::get_finalized_block_number(self).await } async fn fetch_logs_by_tx_hash( &self, tx_hash: H512, ) -> ChainResult, LogMeta)>> { - parse_logs_in_tx( - &tx_hash.into(), - self.provider.clone(), - Self::hyperlane_delivery_parser, - "DeliveryReceiver", - ) - .await - .map(|v| v.into_iter().map(|(m, l)| (m.into(), l)).collect()) + CosmosEventIndexer::fetch_logs_by_tx_hash(self, tx_hash).await } } #[async_trait] -impl SequenceAwareIndexer for CosmosMailboxDeliveryIndexer { +impl SequenceAwareIndexer for CwMailboxDeliveryIndexer { async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { let tip = Indexer::::get_finalized_block_number(&self).await?; diff --git a/rust/main/chains/hyperlane-cosmos/src/mailbox/dispatch_indexer.rs b/rust/main/chains/hyperlane-cosmos/src/cw/mailbox/dispatch_indexer.rs similarity index 76% rename from rust/main/chains/hyperlane-cosmos/src/mailbox/dispatch_indexer.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/mailbox/dispatch_indexer.rs index 47b817889b3..93614e9fd8b 100644 --- a/rust/main/chains/hyperlane-cosmos/src/mailbox/dispatch_indexer.rs +++ b/rust/main/chains/hyperlane-cosmos/src/cw/mailbox/dispatch_indexer.rs @@ -1,50 +1,56 @@ use std::io::Cursor; use std::ops::RangeInclusive; +use std::str::FromStr; use async_trait::async_trait; use base64::{engine::general_purpose::STANDARD as BASE64, Engine}; +use cometbft::abci::EventAttribute; use once_cell::sync::Lazy; -use tendermint::abci::EventAttribute; use tracing::instrument; use hyperlane_core::{ ChainCommunicationError, ChainResult, ContractLocator, Decode, HyperlaneMessage, Indexed, - Indexer, LogMeta, SequenceAwareIndexer, H512, + Indexer, LogMeta, SequenceAwareIndexer, H256, H512, }; -use crate::rpc::{CosmosWasmRpcProvider, ParsedEvent, WasmRpcProvider}; -use crate::utils::{ - execute_and_parse_log_futures, parse_logs_in_range, parse_logs_in_tx, - CONTRACT_ADDRESS_ATTRIBUTE_KEY, CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64, -}; -use crate::{ConnectionConf, CosmosMailbox, HyperlaneCosmosError, Signer}; +use crate::indexer::{CosmosEventIndexer, ParsedEvent}; +use crate::utils::{CONTRACT_ADDRESS_ATTRIBUTE_KEY, CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64}; +use crate::{cw::CwQueryClient, CosmosAddress, CosmosProvider, HyperlaneCosmosError, RpcProvider}; + +use super::CwMailbox; /// The message dispatch event type from the CW contract. -pub const MESSAGE_DISPATCH_EVENT_TYPE: &str = "mailbox_dispatch"; +pub const MESSAGE_DISPATCH_EVENT_TYPE: &str = "wasm-mailbox_dispatch"; const MESSAGE_ATTRIBUTE_KEY: &str = "message"; static MESSAGE_ATTRIBUTE_KEY_BASE64: Lazy = Lazy::new(|| BASE64.encode(MESSAGE_ATTRIBUTE_KEY)); /// Struct that retrieves event data for a Cosmos Mailbox contract #[derive(Debug, Clone)] -pub struct CosmosMailboxDispatchIndexer { - mailbox: CosmosMailbox, - provider: Box, +pub struct CwMailboxDispatchIndexer { + mailbox: CwMailbox, + provider: CosmosProvider, + address: H256, } -impl CosmosMailboxDispatchIndexer { +impl CwMailboxDispatchIndexer { /// Create a reference to a mailbox at a specific Cosmos address on some /// chain - pub fn new(wasm_provider: CosmosWasmRpcProvider, mailbox: CosmosMailbox) -> ChainResult { + pub fn new( + provider: CosmosProvider, + mailbox: CwMailbox, + locator: &ContractLocator, + ) -> ChainResult { Ok(Self { mailbox, - provider: Box::new(wasm_provider), + provider, + address: locator.address, }) } #[instrument(err)] fn hyperlane_message_parser( - attrs: &Vec, + attrs: &[EventAttribute], ) -> ChainResult> { let mut contract_address: Option = None; let mut message: Option = None; @@ -88,7 +94,7 @@ impl CosmosMailboxDispatchIndexer { } } - EventAttribute::V034(a) => { + EventAttribute::V034(_a) => { unimplemented!(); } } @@ -99,47 +105,54 @@ impl CosmosMailboxDispatchIndexer { let message = message.ok_or_else(|| ChainCommunicationError::from_other_str("missing message"))?; - Ok(ParsedEvent::new(contract_address, message)) + Ok(ParsedEvent::new( + CosmosAddress::from_str(&contract_address)?.digest(), + message, + )) + } +} + +impl CosmosEventIndexer for CwMailboxDispatchIndexer { + fn target_type() -> String { + MESSAGE_DISPATCH_EVENT_TYPE.to_owned() + } + + fn provider(&self) -> &RpcProvider { + self.provider.rpc() + } + + fn parse(&self, attributes: &[EventAttribute]) -> ChainResult> { + Self::hyperlane_message_parser(attributes) + } + + fn address(&self) -> &H256 { + &self.address } } #[async_trait] -impl Indexer for CosmosMailboxDispatchIndexer { +impl Indexer for CwMailboxDispatchIndexer { async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { - let logs_futures = parse_logs_in_range( - range, - self.provider.clone(), - Self::hyperlane_message_parser, - "HyperlaneMessageCursor", - ); - - execute_and_parse_log_futures(logs_futures).await + CosmosEventIndexer::fetch_logs_in_range(self, range).await } async fn get_finalized_block_number(&self) -> ChainResult { - self.provider.get_finalized_block_number().await + CosmosEventIndexer::get_finalized_block_number(self).await } async fn fetch_logs_by_tx_hash( &self, tx_hash: H512, ) -> ChainResult, LogMeta)>> { - parse_logs_in_tx( - &tx_hash.into(), - self.provider.clone(), - Self::hyperlane_message_parser, - "HyperlaneMessageReceiver", - ) - .await - .map(|v| v.into_iter().map(|(m, l)| (m.into(), l)).collect()) + CosmosEventIndexer::fetch_logs_by_tx_hash(self, tx_hash).await } } #[async_trait] -impl SequenceAwareIndexer for CosmosMailboxDispatchIndexer { +impl SequenceAwareIndexer for CwMailboxDispatchIndexer { async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { let tip = Indexer::::get_finalized_block_number(&self).await?; @@ -153,7 +166,6 @@ impl SequenceAwareIndexer for CosmosMailboxDispatchIndexer { mod tests { use hyperlane_core::HyperlaneMessage; - use crate::providers::rpc::ParsedEvent; use crate::utils::event_attributes_from_str; use super::*; @@ -162,14 +174,17 @@ mod tests { fn test_hyperlane_message_parser() { // Examples from https://rpc-kralum.neutron-1.neutron.org/tx_search?query=%22tx.height%20%3E=%204000000%20AND%20tx.height%20%3C=%204100000%20AND%20wasm-mailbox_dispatch._contract_address%20=%20%27neutron1sjzzd4gwkggy6hrrs8kxxatexzcuz3jecsxm3wqgregkulzj8r7qlnuef4%27%22&prove=false&page=1&per_page=100 + let contract_address = CosmosAddress::from_str( + "neutron1sjzzd4gwkggy6hrrs8kxxatexzcuz3jecsxm3wqgregkulzj8r7qlnuef4", + ); + assert!(contract_address.is_ok()); let expected = ParsedEvent::new( - "neutron1sjzzd4gwkggy6hrrs8kxxatexzcuz3jecsxm3wqgregkulzj8r7qlnuef4".into(), + contract_address.unwrap().digest(), HyperlaneMessage::from(hex::decode("03000000006e74726e0000000000000000000000006ba6343a09a60ac048d0e99f50b76fd99eff1063000000a9000000000000000000000000281973b53c9aacec128ac964a6f750fea40912aa48656c6c6f2066726f6d204e657574726f6e204d61696e6e657420746f204d616e74612050616369666963206f63742032392c2031323a353520616d").unwrap()), ); let assert_parsed_event = |attrs: &Vec| { - let parsed_event = - CosmosMailboxDispatchIndexer::hyperlane_message_parser(attrs).unwrap(); + let parsed_event = CwMailboxDispatchIndexer::hyperlane_message_parser(attrs).unwrap(); assert_eq!(parsed_event, expected); }; diff --git a/rust/main/chains/hyperlane-cosmos/src/merkle_tree_hook.rs b/rust/main/chains/hyperlane-cosmos/src/cw/merkle_tree_hook.rs similarity index 75% rename from rust/main/chains/hyperlane-cosmos/src/merkle_tree_hook.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/merkle_tree_hook.rs index 38ae681d84b..78c5c50b0a1 100644 --- a/rust/main/chains/hyperlane-cosmos/src/merkle_tree_hook.rs +++ b/rust/main/chains/hyperlane-cosmos/src/cw/merkle_tree_hook.rs @@ -1,9 +1,9 @@ -use std::{fmt::Debug, num::NonZeroU64, ops::RangeInclusive, str::FromStr}; +use std::{fmt::Debug, ops::RangeInclusive, str::FromStr}; use async_trait::async_trait; use base64::{engine::general_purpose::STANDARD as BASE64, Engine}; +use cometbft::abci::EventAttribute; use once_cell::sync::Lazy; -use tendermint::abci::EventAttribute; use tracing::{debug, info, instrument}; use hyperlane_core::accumulator::incremental::IncrementalMerkle; @@ -14,73 +14,44 @@ use hyperlane_core::{ ReorgPeriod, SequenceAwareIndexer, H256, H512, }; -use crate::grpc::WasmProvider; -use crate::payloads::{general, merkle_tree_hook}; -use crate::rpc::{CosmosWasmRpcProvider, ParsedEvent, WasmRpcProvider}; -use crate::utils::{ - execute_and_parse_log_futures, get_block_height_for_reorg_period, parse_logs_in_range, - parse_logs_in_tx, CONTRACT_ADDRESS_ATTRIBUTE_KEY, CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64, -}; -use crate::{ConnectionConf, CosmosProvider, HyperlaneCosmosError, Signer}; +use super::payloads::{general, merkle_tree_hook}; +use super::CwQueryClient; +use crate::indexer::{CosmosEventIndexer, ParsedEvent}; +use crate::utils::{CONTRACT_ADDRESS_ATTRIBUTE_KEY, CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64}; +use crate::{CosmosAddress, CosmosProvider, HyperlaneCosmosError, RpcProvider}; #[derive(Debug, Clone)] /// A reference to a MerkleTreeHook contract on some Cosmos chain -pub struct CosmosMerkleTreeHook { +pub struct CwMerkleTreeHook { /// Domain domain: HyperlaneDomain, /// Contract address address: H256, /// Provider - provider: CosmosProvider, + provider: CosmosProvider, } -impl CosmosMerkleTreeHook { +impl CwMerkleTreeHook { /// create new Cosmos MerkleTreeHook agent - pub fn new(provider: CosmosProvider, locator: ContractLocator) -> ChainResult { + pub fn new( + provider: CosmosProvider, + locator: ContractLocator, + ) -> ChainResult { Ok(Self { domain: locator.domain.clone(), address: locator.address, provider, }) } - - async fn get_checkpoint_at_block(&self, block_height: u64) -> ChainResult { - let payload = merkle_tree_hook::CheckPointRequest { - check_point: general::EmptyStruct {}, - }; - - let data = self - .provider - .grpc() - .wasm_query( - merkle_tree_hook::MerkleTreeGenericRequest { - merkle_hook: payload, - }, - Some(block_height), - ) - .await?; - let response: merkle_tree_hook::CheckPointResponse = serde_json::from_slice(&data)?; - - let checkpoint = Checkpoint { - merkle_tree_hook_address: self.address, - mailbox_domain: self.domain.id(), - root: response.root.parse()?, - index: response.count, - }; - Ok(CheckpointAtBlock { - checkpoint, - block_height: Some(block_height), - }) - } } -impl HyperlaneContract for CosmosMerkleTreeHook { +impl HyperlaneContract for CwMerkleTreeHook { fn address(&self) -> H256 { self.address } } -impl HyperlaneChain for CosmosMerkleTreeHook { +impl HyperlaneChain for CwMerkleTreeHook { fn domain(&self) -> &HyperlaneDomain { &self.domain } @@ -91,7 +62,7 @@ impl HyperlaneChain for CosmosMerkleTreeHook { } #[async_trait] -impl MerkleTreeHook for CosmosMerkleTreeHook { +impl MerkleTreeHook for CwMerkleTreeHook { /// Return the incremental merkle tree in storage #[instrument(level = "debug", err, ret, skip(self))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue @@ -100,12 +71,11 @@ impl MerkleTreeHook for CosmosMerkleTreeHook { tree: general::EmptyStruct {}, }; - let block_height = - get_block_height_for_reorg_period(self.provider.grpc(), reorg_period).await?; + let block_height = self.provider.reorg_to_height(reorg_period).await?; let data = self .provider - .grpc() + .query() .wasm_query( merkle_tree_hook::MerkleTreeGenericRequest { merkle_hook: payload, @@ -135,12 +105,7 @@ impl MerkleTreeHook for CosmosMerkleTreeHook { /// Gets the current leaf count of the merkle tree async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult { - let payload = merkle_tree_hook::MerkleTreeCountRequest { - count: general::EmptyStruct {}, - }; - - let block_height = - get_block_height_for_reorg_period(self.provider.grpc(), reorg_period).await?; + let block_height = self.provider.reorg_to_height(reorg_period).await?; self.count_at_block(block_height).await } @@ -151,19 +116,40 @@ impl MerkleTreeHook for CosmosMerkleTreeHook { &self, reorg_period: &ReorgPeriod, ) -> ChainResult { - let block_height = - get_block_height_for_reorg_period(self.provider.grpc(), reorg_period).await?; - self.get_checkpoint_at_block(block_height).await + let block_height = self.provider.reorg_to_height(reorg_period).await?; + self.latest_checkpoint_at_block(block_height).await } - #[instrument(level = "debug", err, ret, skip(self))] - #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue + /// Get the latest checkpoint at a specific block height. async fn latest_checkpoint_at_block(&self, height: u64) -> ChainResult { - self.get_checkpoint_at_block(height).await + let payload = merkle_tree_hook::CheckPointRequest { + check_point: general::EmptyStruct {}, + }; + let data = self + .provider + .query() + .wasm_query( + merkle_tree_hook::MerkleTreeGenericRequest { + merkle_hook: payload, + }, + Some(height), + ) + .await?; + let response: merkle_tree_hook::CheckPointResponse = serde_json::from_slice(&data)?; + + Ok(CheckpointAtBlock { + checkpoint: Checkpoint { + merkle_tree_hook_address: self.address, + mailbox_domain: self.domain.id(), + root: response.root.parse()?, + index: response.count, + }, + block_height: Some(height), + }) } } -impl CosmosMerkleTreeHook { +impl CwMerkleTreeHook { #[instrument(level = "debug", err, ret, skip(self))] async fn count_at_block(&self, block_height: u64) -> ChainResult { let payload = merkle_tree_hook::MerkleTreeCountRequest { @@ -172,7 +158,7 @@ impl CosmosMerkleTreeHook { let data = self .provider - .grpc() + .query() .wasm_query( merkle_tree_hook::MerkleTreeGenericRequest { merkle_hook: payload, @@ -198,32 +184,35 @@ pub(crate) static MESSAGE_ID_ATTRIBUTE_KEY_BASE64: Lazy = #[derive(Debug, Clone)] /// A reference to a MerkleTreeHookIndexer contract on some Cosmos chain -pub struct CosmosMerkleTreeHookIndexer { +pub struct CwMerkleTreeHookIndexer { /// The CosmosMerkleTreeHook - merkle_tree_hook: CosmosMerkleTreeHook, + merkle_tree_hook: CwMerkleTreeHook, /// Cosmwasm RPC provider instance - provider: Box, + provider: CosmosProvider, + /// Address of the contract + address: H256, } -impl CosmosMerkleTreeHookIndexer { +impl CwMerkleTreeHookIndexer { /// The message dispatch event type from the CW contract. - pub const MERKLE_TREE_INSERTION_EVENT_TYPE: &'static str = "hpl_hook_merkle::post_dispatch"; + pub const MERKLE_TREE_INSERTION_EVENT_TYPE: &'static str = + "wasm-hpl_hook_merkle::post_dispatch"; /// create new Cosmos MerkleTreeHookIndexer agent pub fn new( - provider: CosmosProvider, - wasm_provider: CosmosWasmRpcProvider, + provider: CosmosProvider, locator: ContractLocator, ) -> ChainResult { Ok(Self { - merkle_tree_hook: CosmosMerkleTreeHook::new(provider, locator)?, - provider: Box::new(wasm_provider), + merkle_tree_hook: CwMerkleTreeHook::new(provider.clone(), locator.clone())?, + address: locator.address, + provider, }) } #[instrument(err)] fn merkle_tree_insertion_parser( - attrs: &Vec, + attrs: &[EventAttribute], ) -> ChainResult> { debug!( ?attrs, @@ -292,7 +281,7 @@ impl CosmosMerkleTreeHookIndexer { } } - EventAttribute::V034(a) => { + EventAttribute::V034(_a) => { unimplemented!(); } } @@ -307,7 +296,10 @@ impl CosmosMerkleTreeHookIndexer { "parsed contract address and insertion", ); - let event = ParsedEvent::new(contract_address, insertion.try_into()?); + let event = ParsedEvent::new( + CosmosAddress::from_str(&contract_address)?.digest(), + insertion.try_into()?, + ); info!(?event, "parsed event"); @@ -315,47 +307,54 @@ impl CosmosMerkleTreeHookIndexer { } } +impl CosmosEventIndexer for CwMerkleTreeHookIndexer { + fn target_type() -> String { + Self::MERKLE_TREE_INSERTION_EVENT_TYPE.to_owned() + } + + fn provider(&self) -> &RpcProvider { + self.provider.rpc() + } + + #[doc = " parses the event attributes to the target type"] + fn parse( + &self, + attributes: &[EventAttribute], + ) -> ChainResult> { + Self::merkle_tree_insertion_parser(attributes) + } + + #[doc = " address for the given module that will be indexed"] + fn address(&self) -> &H256 { + &self.address + } +} + #[async_trait] -impl Indexer for CosmosMerkleTreeHookIndexer { - /// Fetch list of logs between `range` of blocks +impl Indexer for CwMerkleTreeHookIndexer { async fn fetch_logs_in_range( &self, range: RangeInclusive, ) -> ChainResult, LogMeta)>> { - let logs_futures = parse_logs_in_range( - range, - self.provider.clone(), - Self::merkle_tree_insertion_parser, - "MerkleTreeInsertionCursor", - ); - - execute_and_parse_log_futures(logs_futures).await + CosmosEventIndexer::fetch_logs_in_range(self, range).await } - /// Get the chain's latest block number that has reached finality async fn get_finalized_block_number(&self) -> ChainResult { - self.provider.get_finalized_block_number().await + CosmosEventIndexer::get_finalized_block_number(self).await } async fn fetch_logs_by_tx_hash( &self, tx_hash: H512, ) -> ChainResult, LogMeta)>> { - parse_logs_in_tx( - &tx_hash.into(), - self.provider.clone(), - Self::merkle_tree_insertion_parser, - "MerkleTreeInsertionReceiver", - ) - .await - .map(|v| v.into_iter().map(|(m, l)| (m.into(), l)).collect()) + CosmosEventIndexer::fetch_logs_by_tx_hash(self, tx_hash).await } } #[async_trait] -impl SequenceAwareIndexer for CosmosMerkleTreeHookIndexer { +impl SequenceAwareIndexer for CwMerkleTreeHookIndexer { async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { - let tip = self.get_finalized_block_number().await?; + let tip = CosmosEventIndexer::get_finalized_block_number(self).await?; let sequence = self.merkle_tree_hook.count_at_block(tip.into()).await?; Ok((Some(sequence), tip)) @@ -389,17 +388,22 @@ mod tests { use hyperlane_core::H256; - use crate::providers::rpc::ParsedEvent; + use crate::indexer::ParsedEvent; use crate::utils::event_attributes_from_str; + use crate::CosmosAddress; use super::*; #[test] fn test_merkle_tree_insertion_parser() { // Examples from https://rpc-kralum.neutron-1.neutron.org/tx_search?query=%22tx.height%20%3E=%204000000%20AND%20tx.height%20%3C=%204100000%20AND%20wasm-hpl_hook_merkle::post_dispatch._contract_address%20=%20%27neutron1e5c2qqquc86rd3q77aj2wyht40z6z3q5pclaq040ue9f5f8yuf7qnpvkzk%27%22&prove=false&page=1&per_page=100 + let contract_address = CosmosAddress::from_str( + "neutron1e5c2qqquc86rd3q77aj2wyht40z6z3q5pclaq040ue9f5f8yuf7qnpvkzk", + ); + assert!(contract_address.is_ok()); let expected = ParsedEvent::new( - "neutron1e5c2qqquc86rd3q77aj2wyht40z6z3q5pclaq040ue9f5f8yuf7qnpvkzk".into(), + contract_address.unwrap().digest(), MerkleTreeInsertion::new( 4, H256::from_str("a21078beac8bc19770d532eed0b4ada5ef0b45992cde219979f07e3e49185384") @@ -409,7 +413,7 @@ mod tests { let assert_parsed_event = |attrs: &Vec| { let parsed_event = - CosmosMerkleTreeHookIndexer::merkle_tree_insertion_parser(attrs).unwrap(); + CwMerkleTreeHookIndexer::merkle_tree_insertion_parser(attrs).unwrap(); assert_eq!(parsed_event, expected); }; diff --git a/rust/main/chains/hyperlane-cosmos/src/cw/mod.rs b/rust/main/chains/hyperlane-cosmos/src/cw/mod.rs new file mode 100644 index 00000000000..f5f2ddcfc14 --- /dev/null +++ b/rust/main/chains/hyperlane-cosmos/src/cw/mod.rs @@ -0,0 +1,18 @@ +/// Hyperlane Cosmos Wasm Module +/// This module contains the implementation of the Hyperlane Cosmos Wasm module. +mod aggregation_ism; +mod cw_query_client; +mod interchain_gas; +mod interchain_security_module; +mod mailbox; +mod merkle_tree_hook; +mod multisig_ism; +pub(crate) mod payloads; +mod routing_ism; +pub(crate) mod types; +mod validator_announce; + +pub use { + aggregation_ism::*, cw_query_client::*, interchain_gas::*, interchain_security_module::*, + mailbox::*, merkle_tree_hook::*, multisig_ism::*, routing_ism::*, validator_announce::*, +}; diff --git a/rust/main/chains/hyperlane-cosmos/src/multisig_ism.rs b/rust/main/chains/hyperlane-cosmos/src/cw/multisig_ism.rs similarity index 77% rename from rust/main/chains/hyperlane-cosmos/src/multisig_ism.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/multisig_ism.rs index 8d618b6c6e0..fef1c41820c 100644 --- a/rust/main/chains/hyperlane-cosmos/src/multisig_ism.rs +++ b/rust/main/chains/hyperlane-cosmos/src/cw/multisig_ism.rs @@ -1,28 +1,30 @@ use std::str::FromStr; -use crate::{ - grpc::WasmProvider, payloads::ism_routes::QueryIsmGeneralRequest, signers::Signer, - ConnectionConf, CosmosProvider, -}; +use crate::CosmosProvider; use async_trait::async_trait; use hyperlane_core::{ ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, MultisigIsm, RawHyperlaneMessage, H160, H256, }; -use crate::payloads::multisig_ism::{self, VerifyInfoRequest, VerifyInfoRequestInner}; +use super::payloads::ism_routes::QueryIsmGeneralRequest; +use super::payloads::multisig_ism::{self, VerifyInfoRequest, VerifyInfoRequestInner}; +use super::CwQueryClient; /// A reference to a MultisigIsm contract on some Cosmos chain #[derive(Debug)] -pub struct CosmosMultisigIsm { +pub struct CwMultisigIsm { domain: HyperlaneDomain, address: H256, - provider: CosmosProvider, + provider: CosmosProvider, } -impl CosmosMultisigIsm { +impl CwMultisigIsm { /// create a new instance of CosmosMultisigIsm - pub fn new(provider: CosmosProvider, locator: ContractLocator) -> ChainResult { + pub fn new( + provider: CosmosProvider, + locator: ContractLocator, + ) -> ChainResult { Ok(Self { domain: locator.domain.clone(), address: locator.address, @@ -31,13 +33,13 @@ impl CosmosMultisigIsm { } } -impl HyperlaneContract for CosmosMultisigIsm { +impl HyperlaneContract for CwMultisigIsm { fn address(&self) -> H256 { self.address } } -impl HyperlaneChain for CosmosMultisigIsm { +impl HyperlaneChain for CwMultisigIsm { fn domain(&self) -> &HyperlaneDomain { &self.domain } @@ -48,7 +50,7 @@ impl HyperlaneChain for CosmosMultisigIsm { } #[async_trait] -impl MultisigIsm for CosmosMultisigIsm { +impl MultisigIsm for CwMultisigIsm { /// Returns the validator and threshold needed to verify message async fn validators_and_threshold( &self, @@ -62,7 +64,7 @@ impl MultisigIsm for CosmosMultisigIsm { let data = self .provider - .grpc() + .query() .wasm_query(QueryIsmGeneralRequest { ism: payload }, None) .await?; let response: multisig_ism::VerifyInfoResponse = serde_json::from_slice(&data)?; diff --git a/rust/main/chains/hyperlane-cosmos/src/payloads/aggregate_ism.rs b/rust/main/chains/hyperlane-cosmos/src/cw/payloads/aggregate_ism.rs similarity index 100% rename from rust/main/chains/hyperlane-cosmos/src/payloads/aggregate_ism.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/payloads/aggregate_ism.rs diff --git a/rust/main/chains/hyperlane-cosmos/src/payloads/general.rs b/rust/main/chains/hyperlane-cosmos/src/cw/payloads/general.rs similarity index 77% rename from rust/main/chains/hyperlane-cosmos/src/payloads/general.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/payloads/general.rs index 0652cd03406..a4a50d89e95 100644 --- a/rust/main/chains/hyperlane-cosmos/src/payloads/general.rs +++ b/rust/main/chains/hyperlane-cosmos/src/cw/payloads/general.rs @@ -1,6 +1,6 @@ +use cometbft::abci::v0_34; +use cometbft::abci::v0_37; use serde::{Deserialize, Serialize}; -use tendermint::abci::v0_34; -use tendermint::v0_37; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct EmptyStruct {} @@ -35,3 +35,9 @@ impl From for cosmrs::tendermint::abci::EventAttribute { cosmrs::tendermint::abci::EventAttribute::from((val.key, val.value, val.index)) } } + +impl From for cometbft::abci::EventAttribute { + fn from(val: EventAttribute) -> Self { + cometbft::abci::EventAttribute::from((val.key, val.value, val.index)) + } +} diff --git a/rust/main/chains/hyperlane-cosmos/src/payloads/ism_routes.rs b/rust/main/chains/hyperlane-cosmos/src/cw/payloads/ism_routes.rs similarity index 100% rename from rust/main/chains/hyperlane-cosmos/src/payloads/ism_routes.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/payloads/ism_routes.rs diff --git a/rust/main/chains/hyperlane-cosmos/src/payloads/mailbox.rs b/rust/main/chains/hyperlane-cosmos/src/cw/payloads/mailbox.rs similarity index 100% rename from rust/main/chains/hyperlane-cosmos/src/payloads/mailbox.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/payloads/mailbox.rs diff --git a/rust/main/chains/hyperlane-cosmos/src/payloads/merkle_tree_hook.rs b/rust/main/chains/hyperlane-cosmos/src/cw/payloads/merkle_tree_hook.rs similarity index 100% rename from rust/main/chains/hyperlane-cosmos/src/payloads/merkle_tree_hook.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/payloads/merkle_tree_hook.rs diff --git a/rust/main/chains/hyperlane-cosmos/src/cw/payloads/mod.rs b/rust/main/chains/hyperlane-cosmos/src/cw/payloads/mod.rs new file mode 100644 index 00000000000..08584c58970 --- /dev/null +++ b/rust/main/chains/hyperlane-cosmos/src/cw/payloads/mod.rs @@ -0,0 +1,8 @@ +pub(crate) mod aggregate_ism; +pub(crate) mod general; +pub(crate) mod ism_routes; +pub(crate) mod mailbox; +pub(crate) mod merkle_tree_hook; +pub(crate) mod multisig_ism; +pub(crate) mod packet_data; +pub(crate) mod validator_announce; diff --git a/rust/main/chains/hyperlane-cosmos/src/payloads/multisig_ism.rs b/rust/main/chains/hyperlane-cosmos/src/cw/payloads/multisig_ism.rs similarity index 100% rename from rust/main/chains/hyperlane-cosmos/src/payloads/multisig_ism.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/payloads/multisig_ism.rs diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider/parse.rs b/rust/main/chains/hyperlane-cosmos/src/cw/payloads/packet_data.rs similarity index 97% rename from rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider/parse.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/payloads/packet_data.rs index d648e716f91..c823fa9f703 100644 --- a/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider/parse.rs +++ b/rust/main/chains/hyperlane-cosmos/src/cw/payloads/packet_data.rs @@ -44,8 +44,7 @@ mod tests { use cosmrs::Any; use ibc_proto::ibc::core::channel::v1::{MsgRecvPacket, Packet}; - use crate::providers::cosmos::provider::parse::PacketData; - use crate::HyperlaneCosmosError; + use crate::{cw::payloads::packet_data::PacketData, HyperlaneCosmosError}; #[test] fn success() { diff --git a/rust/main/chains/hyperlane-cosmos/src/payloads/validator_announce.rs b/rust/main/chains/hyperlane-cosmos/src/cw/payloads/validator_announce.rs similarity index 100% rename from rust/main/chains/hyperlane-cosmos/src/payloads/validator_announce.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/payloads/validator_announce.rs diff --git a/rust/main/chains/hyperlane-cosmos/src/routing_ism.rs b/rust/main/chains/hyperlane-cosmos/src/cw/routing_ism.rs similarity index 72% rename from rust/main/chains/hyperlane-cosmos/src/routing_ism.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/routing_ism.rs index 07ae6b3d866..9e21937942a 100644 --- a/rust/main/chains/hyperlane-cosmos/src/routing_ism.rs +++ b/rust/main/chains/hyperlane-cosmos/src/cw/routing_ism.rs @@ -7,26 +7,27 @@ use hyperlane_core::{ HyperlaneMessage, HyperlaneProvider, RawHyperlaneMessage, RoutingIsm, H256, }; -use crate::{ - grpc::WasmProvider, - payloads::ism_routes::{ - IsmRouteRequest, IsmRouteRequestInner, IsmRouteRespnose, QueryRoutingIsmGeneralRequest, - }, - signers::Signer, - ConnectionConf, CosmosAddress, CosmosProvider, +use crate::{CosmosAddress, CosmosProvider}; + +use super::payloads::ism_routes::{ + IsmRouteRequest, IsmRouteRequestInner, IsmRouteRespnose, QueryRoutingIsmGeneralRequest, }; +use super::CwQueryClient; /// A reference to a RoutingIsm contract on some Cosmos chain #[derive(Debug)] -pub struct CosmosRoutingIsm { +pub struct CwRoutingIsm { domain: HyperlaneDomain, address: H256, - provider: CosmosProvider, + provider: CosmosProvider, } -impl CosmosRoutingIsm { +impl CwRoutingIsm { /// create a new instance of CosmosRoutingIsm - pub fn new(provider: CosmosProvider, locator: ContractLocator) -> ChainResult { + pub fn new( + provider: CosmosProvider, + locator: ContractLocator, + ) -> ChainResult { Ok(Self { domain: locator.domain.clone(), address: locator.address, @@ -35,13 +36,13 @@ impl CosmosRoutingIsm { } } -impl HyperlaneContract for CosmosRoutingIsm { +impl HyperlaneContract for CwRoutingIsm { fn address(&self) -> H256 { self.address } } -impl HyperlaneChain for CosmosRoutingIsm { +impl HyperlaneChain for CwRoutingIsm { fn domain(&self) -> &HyperlaneDomain { &self.domain } @@ -52,7 +53,7 @@ impl HyperlaneChain for CosmosRoutingIsm { } #[async_trait] -impl RoutingIsm for CosmosRoutingIsm { +impl RoutingIsm for CwRoutingIsm { async fn route(&self, message: &HyperlaneMessage) -> ChainResult { let payload = IsmRouteRequest { route: IsmRouteRequestInner { @@ -62,7 +63,7 @@ impl RoutingIsm for CosmosRoutingIsm { let data = self .provider - .grpc() + .query() .wasm_query( QueryRoutingIsmGeneralRequest { routing_ism: payload, diff --git a/rust/main/chains/hyperlane-cosmos/src/types.rs b/rust/main/chains/hyperlane-cosmos/src/cw/types.rs similarity index 71% rename from rust/main/chains/hyperlane-cosmos/src/types.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/types.rs index bba61fb0100..e9756dd1e24 100644 --- a/rust/main/chains/hyperlane-cosmos/src/types.rs +++ b/rust/main/chains/hyperlane-cosmos/src/cw/types.rs @@ -1,6 +1,4 @@ -use cosmrs::proto::{cosmos::base::abci::v1beta1::TxResponse, tendermint::Error}; -use hyperlane_core::{ChainResult, ModuleType, TxOutcome, H256, U256}; -use url::Url; +use hyperlane_core::ModuleType; pub struct IsmType(pub hyperlane_cosmwasm_interface::ism::IsmType); @@ -30,12 +28,3 @@ impl From for ModuleType { } } } - -pub fn tx_response_to_outcome(response: TxResponse) -> ChainResult { - Ok(TxOutcome { - transaction_id: H256::from_slice(hex::decode(response.txhash)?.as_slice()).into(), - executed: response.code == 0, - gas_used: U256::from(response.gas_used), - gas_price: U256::one().try_into()?, - }) -} diff --git a/rust/main/chains/hyperlane-cosmos/src/validator_announce.rs b/rust/main/chains/hyperlane-cosmos/src/cw/validator_announce.rs similarity index 64% rename from rust/main/chains/hyperlane-cosmos/src/validator_announce.rs rename to rust/main/chains/hyperlane-cosmos/src/cw/validator_announce.rs index 32a3daf5d0f..d71d9a2b5cc 100644 --- a/rust/main/chains/hyperlane-cosmos/src/validator_announce.rs +++ b/rust/main/chains/hyperlane-cosmos/src/cw/validator_announce.rs @@ -1,33 +1,32 @@ use async_trait::async_trait; -use cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse; use hyperlane_core::{ Announcement, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, SignedType, TxOutcome, ValidatorAnnounce, H160, H256, U256, }; -use crate::{ - grpc::WasmProvider, - payloads::validator_announce::{ - self, AnnouncementRequest, AnnouncementRequestInner, GetAnnounceStorageLocationsRequest, - GetAnnounceStorageLocationsRequestInner, - }, - signers::Signer, - types::tx_response_to_outcome, - ConnectionConf, CosmosProvider, +use crate::cw::payloads::validator_announce::{ + AnnouncementRequest, AnnouncementRequestInner, GetAnnounceStorageLocationsRequest, + GetAnnounceStorageLocationsRequestInner, GetAnnounceStorageLocationsResponse, }; +use crate::{utils, CosmosProvider}; + +use super::CwQueryClient; /// A reference to a ValidatorAnnounce contract on some Cosmos chain #[derive(Debug)] -pub struct CosmosValidatorAnnounce { +pub struct CwValidatorAnnounce { domain: HyperlaneDomain, address: H256, - provider: CosmosProvider, + provider: CosmosProvider, } -impl CosmosValidatorAnnounce { +impl CwValidatorAnnounce { /// create a new instance of CosmosValidatorAnnounce - pub fn new(provider: CosmosProvider, locator: ContractLocator) -> ChainResult { + pub fn new( + provider: CosmosProvider, + locator: ContractLocator, + ) -> ChainResult { Ok(Self { domain: locator.domain.clone(), address: locator.address, @@ -36,13 +35,13 @@ impl CosmosValidatorAnnounce { } } -impl HyperlaneContract for CosmosValidatorAnnounce { +impl HyperlaneContract for CwValidatorAnnounce { fn address(&self) -> H256 { self.address } } -impl HyperlaneChain for CosmosValidatorAnnounce { +impl HyperlaneChain for CwValidatorAnnounce { fn domain(&self) -> &HyperlaneDomain { &self.domain } @@ -53,7 +52,7 @@ impl HyperlaneChain for CosmosValidatorAnnounce { } #[async_trait] -impl ValidatorAnnounce for CosmosValidatorAnnounce { +impl ValidatorAnnounce for CwValidatorAnnounce { async fn get_announced_storage_locations( &self, validators: &[H256], @@ -70,9 +69,8 @@ impl ValidatorAnnounce for CosmosValidatorAnnounce { }, }; - let data: Vec = self.provider.grpc().wasm_query(payload, None).await?; - let response: validator_announce::GetAnnounceStorageLocationsResponse = - serde_json::from_slice(&data)?; + let data: Vec = self.provider.query().wasm_query(payload, None).await?; + let response: GetAnnounceStorageLocationsResponse = serde_json::from_slice(&data)?; Ok(response .storage_locations @@ -89,20 +87,18 @@ impl ValidatorAnnounce for CosmosValidatorAnnounce { signature: hex::encode(announcement.signature.to_vec()), }, }; + let payload = self.provider.query().wasm_encode_msg(announce_request)?; - let response: TxResponse = self - .provider - .grpc() - // TODO: consider transaction overrides for Cosmos. - .wasm_send(announce_request, None) - .await?; - - Ok(tx_response_to_outcome(response)?) + let response = self.provider.rpc().send(vec![payload], None).await?; + Ok(utils::tx_response_to_outcome( + response, + self.provider.rpc().gas_price(), + )) } async fn announce_tokens_needed( &self, - announcement: SignedType, + _announcement: SignedType, _chain_signer: H256, ) -> Option { // TODO: check user balance. For now, just try announcing and diff --git a/rust/main/chains/hyperlane-cosmos/src/error.rs b/rust/main/chains/hyperlane-cosmos/src/error.rs index 4a2c1f74063..4d3af79d3e9 100644 --- a/rust/main/chains/hyperlane-cosmos/src/error.rs +++ b/rust/main/chains/hyperlane-cosmos/src/error.rs @@ -29,8 +29,8 @@ pub enum HyperlaneCosmosError { /// Cosmos error report #[error("{0}")] CosmosErrorReport(#[from] Box), - #[error("{0}")] /// Cosmrs Tendermint Error + #[error("{0}")] CosmrsTendermintError(#[from] Box), #[error("{0}")] /// CosmWasm Error @@ -41,9 +41,12 @@ pub enum HyperlaneCosmosError { /// Tonic codegen error #[error("{0}")] TonicGenError(#[from] tonic::codegen::StdError), - /// Tendermint RPC Error + /// Cometbft Error + #[error(transparent)] + CometbftError(#[from] Box), + /// Cometbft RPC Error #[error(transparent)] - TendermintError(#[from] Box), + CometbftRpcError(#[from] Box), /// Prost error #[error("{0}")] Prost(#[from] prost::DecodeError), @@ -76,6 +79,30 @@ pub enum HyperlaneCosmosError { ParsingAttemptsFailed(Vec), } +impl From for HyperlaneCosmosError { + fn from(value: cosmrs::ErrorReport) -> Self { + HyperlaneCosmosError::CosmosErrorReport(Box::new(value)) + } +} + +impl From for HyperlaneCosmosError { + fn from(value: tonic::Status) -> Self { + HyperlaneCosmosError::GrpcError(Box::new(value)) + } +} + +impl From for HyperlaneCosmosError { + fn from(value: cosmrs::Error) -> Self { + HyperlaneCosmosError::CosmosError(Box::new(value)) + } +} + +impl From for HyperlaneCosmosError { + fn from(value: cosmwasm_std::StdError) -> Self { + HyperlaneCosmosError::CosmWasmError(Box::new(value)) + } +} + impl From for ChainCommunicationError { fn from(value: HyperlaneCosmosError) -> Self { ChainCommunicationError::from_other(value) @@ -87,3 +114,21 @@ impl From for HyperlaneCosmosError { HyperlaneCosmosError::PublicKeyError(value.to_string()) } } + +impl From for HyperlaneCosmosError { + fn from(value: cometbft_rpc::Error) -> Self { + HyperlaneCosmosError::CometbftRpcError(Box::new(value)) + } +} + +impl From for HyperlaneCosmosError { + fn from(value: cometbft::Error) -> Self { + HyperlaneCosmosError::CometbftError(Box::new(value)) + } +} + +impl From for HyperlaneCosmosError { + fn from(value: cosmrs::tendermint::Error) -> Self { + HyperlaneCosmosError::CosmrsTendermintError(Box::new(value)) + } +} diff --git a/rust/main/chains/hyperlane-cosmos-native/src/indexers/indexer.rs b/rust/main/chains/hyperlane-cosmos/src/indexer.rs similarity index 97% rename from rust/main/chains/hyperlane-cosmos-native/src/indexers/indexer.rs rename to rust/main/chains/hyperlane-cosmos/src/indexer.rs index 4990489a957..87deba57838 100644 --- a/rust/main/chains/hyperlane-cosmos-native/src/indexers/indexer.rs +++ b/rust/main/chains/hyperlane-cosmos/src/indexer.rs @@ -1,14 +1,14 @@ use std::fmt::Debug; use std::ops::RangeInclusive; -use futures::future; -use tendermint::abci::{Event, EventAttribute}; -use tendermint::hash::Algorithm; -use tendermint::Hash; -use tendermint_rpc::endpoint::tx; -use tendermint_rpc::endpoint::{ +use cometbft::abci::{Event, EventAttribute}; +use cometbft::hash::Algorithm; +use cometbft::Hash; +use cometbft_rpc::endpoint::tx; +use cometbft_rpc::endpoint::{ block::Response as BlockResponse, block_results::Response as BlockResultsResponse, }; +use futures::future; use tonic::async_trait; use tracing::{debug, trace, warn}; @@ -33,11 +33,6 @@ impl ParsedEvent { event, } } - - /// Get the inner event - pub fn inner(self) -> T { - self.event - } } #[async_trait] diff --git a/rust/main/chains/hyperlane-cosmos/src/lib.rs b/rust/main/chains/hyperlane-cosmos/src/lib.rs index a640d29be3a..038e75fe967 100644 --- a/rust/main/chains/hyperlane-cosmos/src/lib.rs +++ b/rust/main/chains/hyperlane-cosmos/src/lib.rs @@ -6,30 +6,22 @@ #![allow(unused_variables)] #![allow(unused_imports)] // TODO: `rustc` 1.80.1 clippy issue #![deny(clippy::unwrap_used, clippy::panic)] +#![deny(clippy::arithmetic_side_effects)] -mod aggregation_ism; /// Hyperlane Application specific functionality pub mod application; +/// CosmWasm specific modules +pub mod cw; mod error; -mod interchain_gas; -mod interchain_security_module; +mod indexer; mod libs; -mod mailbox; -mod merkle_tree_hook; -mod multisig_ism; -mod payloads; -mod prometheus; +/// CosmosModule/CosmosNative specific modules +pub mod native; mod providers; -mod routing_ism; -mod rpc_clients; -mod signers; +/// Cosmos signer implementations +pub mod signers; mod trait_builder; -mod types; mod utils; -mod validator_announce; -pub use self::{ - aggregation_ism::*, error::*, interchain_gas::*, interchain_security_module::*, libs::*, - mailbox::*, merkle_tree_hook::*, multisig_ism::*, providers::*, routing_ism::*, signers::*, - trait_builder::*, trait_builder::*, validator_announce::*, validator_announce::*, -}; +pub use self::{error::*, providers::*, signers::*, trait_builder::*}; +pub(crate) use libs::*; diff --git a/rust/main/chains/hyperlane-cosmos/src/libs/account.rs b/rust/main/chains/hyperlane-cosmos/src/libs/account.rs index df11d74d966..46a75690e43 100644 --- a/rust/main/chains/hyperlane-cosmos/src/libs/account.rs +++ b/rust/main/chains/hyperlane-cosmos/src/libs/account.rs @@ -1,10 +1,9 @@ +use cometbft::account::Id as TendermintAccountId; +use cometbft::public_key::PublicKey as TendermintPublicKey; use cosmrs::{crypto::PublicKey, AccountId}; use hyperlane_cosmwasm_interface::types::keccak256_hash; -use tendermint::account::Id as TendermintAccountId; -use tendermint::public_key::PublicKey as TendermintPublicKey; use crypto::decompress_public_key; -use hyperlane_core::Error::Overflow; use hyperlane_core::{AccountAddressType, ChainCommunicationError, ChainResult, H256}; use crate::HyperlaneCosmosError; @@ -34,11 +33,16 @@ impl<'a> CosmosAccountId<'a> { /// Source: `` fn bitcoin_style(pub_key: PublicKey, prefix: &str) -> ChainResult { // Get the inner type - let tendermint_pub_key = TendermintPublicKey::from(pub_key); + let pub_key = + cometbft::PublicKey::from_raw_secp256k1(&pub_key.to_bytes()).ok_or_else(|| { + ChainCommunicationError::ParseError { + msg: "Failed to parse to secp256k1 key".into(), + } + })?; // Get the RIPEMD160(SHA256(pub_key)) - let tendermint_id = TendermintAccountId::from(tendermint_pub_key); + let id_account = cometbft::account::Id::from(pub_key); // Bech32 encoding - let account_id = AccountId::new(prefix, tendermint_id.as_bytes()) + let account_id = AccountId::new(prefix, id_account.as_bytes()) .map_err(Box::new) .map_err(Into::::into)?; diff --git a/rust/main/chains/hyperlane-cosmos/src/libs/account/tests.rs b/rust/main/chains/hyperlane-cosmos/src/libs/account/tests.rs index 1dc7d30331c..0420d7ff4a2 100644 --- a/rust/main/chains/hyperlane-cosmos/src/libs/account/tests.rs +++ b/rust/main/chains/hyperlane-cosmos/src/libs/account/tests.rs @@ -1,11 +1,12 @@ +use std::str::FromStr; + use cosmrs::crypto::PublicKey; -use cosmwasm_std::HexBinary; use crypto::decompress_public_key; use hyperlane_core::AccountAddressType; use AccountAddressType::{Bitcoin, Ethereum}; -use crate::CosmosAccountId; +use crate::{utils::cometbft_pubkey_to_cosmrs_pubkey, CosmosAccountId}; const COMPRESSED_PUBLIC_KEY: &str = "02962d010010b6eec66846322704181570d89e28236796579c535d2e44d20931f4"; @@ -60,15 +61,17 @@ fn test_ethereum_style() { fn compressed_public_key() -> PublicKey { let hex = hex::decode(COMPRESSED_PUBLIC_KEY).unwrap(); - let tendermint = tendermint::PublicKey::from_raw_secp256k1(&hex).unwrap(); + let cometbft_key = cometbft::PublicKey::from_raw_secp256k1(&hex).unwrap(); - PublicKey::from(tendermint) + cometbft_pubkey_to_cosmrs_pubkey(&cometbft_key) + .expect("Failed to deserialize cosmrs::PublicKey") } fn decompressed_public_key() -> PublicKey { let hex = hex::decode(COMPRESSED_PUBLIC_KEY).unwrap(); let decompressed = decompress_public_key(&hex).unwrap(); - let tendermint = tendermint::PublicKey::from_raw_secp256k1(&decompressed).unwrap(); + let cometbft_key = cometbft::PublicKey::from_raw_secp256k1(&decompressed).unwrap(); - PublicKey::from(tendermint) + cometbft_pubkey_to_cosmrs_pubkey(&cometbft_key) + .expect("Failed to deserialize cosmrs::PublicKey") } diff --git a/rust/main/chains/hyperlane-cosmos/src/libs/address.rs b/rust/main/chains/hyperlane-cosmos/src/libs/address.rs index e45faa346b5..74f029beed0 100644 --- a/rust/main/chains/hyperlane-cosmos/src/libs/address.rs +++ b/rust/main/chains/hyperlane-cosmos/src/libs/address.rs @@ -46,8 +46,7 @@ impl CosmosAddress { /// - digest: H256 digest (hex representation of address) /// - prefix: Bech32 prefix /// - byte_count: Number of bytes to truncate the digest to. Cosmos addresses can sometimes - /// be less than 32 bytes, so this helps to serialize it in bech32 with the appropriate - /// length. + /// be less than 32 bytes, so this helps to serialize it in bech32 with the appropriate length. pub fn from_h256(digest: H256, prefix: &str, byte_count: usize) -> ChainResult { // This is the hex-encoded version of the address let untruncated_bytes = digest.as_bytes(); @@ -56,7 +55,7 @@ impl CosmosAddress { return Err(Overflow.into()); } - let remainder_bytes_start = untruncated_bytes.len() - byte_count; + let remainder_bytes_start = untruncated_bytes.len().saturating_sub(byte_count); // Left-truncate the digest to the desired length let bytes = &untruncated_bytes[remainder_bytes_start..]; @@ -112,7 +111,7 @@ impl FromStr for CosmosAddress { #[cfg(test)] pub mod test { - use hyperlane_core::utils::hex_or_base58_to_h256; + use hyperlane_core::utils::hex_or_base58_or_bech32_to_h256; use super::*; @@ -130,7 +129,7 @@ pub mod test { #[test] fn test_bech32_decode_from_cosmos_key() { let hex_key = "0x5486418967eabc770b0fcb995f7ef6d9a72f7fc195531ef76c5109f44f51af26"; - let key = hex_or_base58_to_h256(hex_key).unwrap(); + let key = hex_or_base58_or_bech32_to_h256(hex_key).unwrap(); let prefix = "neutron"; let addr = CosmosAddress::from_privkey(key.as_bytes(), prefix, &AccountAddressType::Bitcoin) @@ -151,7 +150,7 @@ pub mod test { #[test] fn test_bech32_encode_from_h256() { let hex_key = "0x1b16866227825a5166eb44031cdcf6568b3e80b52f2806e01b89a34dc90ae616"; - let key = hex_or_base58_to_h256(hex_key).unwrap(); + let key = hex_or_base58_or_bech32_to_h256(hex_key).unwrap(); let prefix = "dual"; let addr = CosmosAddress::from_h256(key, prefix, 32).expect("Cosmos address creation failed"); diff --git a/rust/main/chains/hyperlane-cosmos/src/mailbox.rs b/rust/main/chains/hyperlane-cosmos/src/mailbox.rs deleted file mode 100644 index 3043569e7bd..00000000000 --- a/rust/main/chains/hyperlane-cosmos/src/mailbox.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub use contract::CosmosMailbox; -pub use delivery_indexer::CosmosMailboxDeliveryIndexer; -pub use dispatch_indexer::CosmosMailboxDispatchIndexer; - -mod contract; - -/// Cosmos Mailbox Delivery Indexer -pub mod delivery_indexer; -/// Cosmos Mailbox Dispatch Indexer -pub mod dispatch_indexer; diff --git a/rust/main/chains/hyperlane-cosmos/src/native/indexers.rs b/rust/main/chains/hyperlane-cosmos/src/native/indexers.rs new file mode 100644 index 00000000000..2e234358f96 --- /dev/null +++ b/rust/main/chains/hyperlane-cosmos/src/native/indexers.rs @@ -0,0 +1,9 @@ +mod delivery; +mod dispatch; +mod interchain_gas; +mod merkle_tree_hook; + +pub use delivery::*; +pub use dispatch::*; +pub use interchain_gas::*; +pub use merkle_tree_hook::*; diff --git a/rust/main/chains/hyperlane-cosmos-native/src/indexers/delivery.rs b/rust/main/chains/hyperlane-cosmos/src/native/indexers/delivery.rs similarity index 86% rename from rust/main/chains/hyperlane-cosmos-native/src/indexers/delivery.rs rename to rust/main/chains/hyperlane-cosmos/src/native/indexers/delivery.rs index ca8ccd83b19..9584a4bb19a 100644 --- a/rust/main/chains/hyperlane-cosmos-native/src/indexers/delivery.rs +++ b/rust/main/chains/hyperlane-cosmos/src/native/indexers/delivery.rs @@ -1,7 +1,7 @@ use std::ops::RangeInclusive; +use cometbft::abci::EventAttribute; use hyperlane_cosmos_rs::{hyperlane::core::v1::EventProcess, prost::Name}; -use tendermint::abci::EventAttribute; use tonic::async_trait; use tracing::instrument; @@ -10,20 +10,26 @@ use hyperlane_core::{ SequenceAwareIndexer, H256, H512, }; -use crate::{CosmosNativeProvider, HyperlaneCosmosError, RpcProvider}; +use crate::{ + native::module_query_client::ModuleQueryClient, CosmosProvider, HyperlaneCosmosError, + RpcProvider, +}; -use super::{CosmosEventIndexer, ParsedEvent}; +use crate::indexer::{CosmosEventIndexer, ParsedEvent}; -/// delivery indexer to check if a message was delivered +/// Delivery indexer to check if a message was delivered #[derive(Debug, Clone)] pub struct CosmosNativeDeliveryIndexer { - provider: CosmosNativeProvider, + provider: CosmosProvider, address: H256, } impl CosmosNativeDeliveryIndexer { /// New Delivery Indexer - pub fn new(provider: CosmosNativeProvider, locator: ContractLocator) -> ChainResult { + pub fn new( + provider: CosmosProvider, + locator: ContractLocator, + ) -> ChainResult { Ok(CosmosNativeDeliveryIndexer { provider, address: locator.address, diff --git a/rust/main/chains/hyperlane-cosmos-native/src/indexers/dispatch.rs b/rust/main/chains/hyperlane-cosmos/src/native/indexers/dispatch.rs similarity index 88% rename from rust/main/chains/hyperlane-cosmos-native/src/indexers/dispatch.rs rename to rust/main/chains/hyperlane-cosmos/src/native/indexers/dispatch.rs index 6a2071f6f95..13009e40651 100644 --- a/rust/main/chains/hyperlane-cosmos-native/src/indexers/dispatch.rs +++ b/rust/main/chains/hyperlane-cosmos/src/native/indexers/dispatch.rs @@ -1,10 +1,10 @@ use std::io::Cursor; use std::ops::RangeInclusive; +use cometbft::abci::EventAttribute; use hex::ToHex; use hyperlane_cosmos_rs::hyperlane::core::v1::EventDispatch; use hyperlane_cosmos_rs::prost::Name; -use tendermint::abci::EventAttribute; use tonic::async_trait; use tracing::instrument; @@ -13,20 +13,26 @@ use hyperlane_core::{ Indexer, LogMeta, SequenceAwareIndexer, H256, H512, }; -use crate::{CosmosNativeProvider, HyperlaneCosmosError, RpcProvider}; +use crate::{ + native::module_query_client::ModuleQueryClient, CosmosProvider, HyperlaneCosmosError, + RpcProvider, +}; -use super::{CosmosEventIndexer, ParsedEvent}; +use crate::indexer::{CosmosEventIndexer, ParsedEvent}; /// Dispatch indexer to check if a new hyperlane message was dispatched #[derive(Debug, Clone)] pub struct CosmosNativeDispatchIndexer { - provider: CosmosNativeProvider, + provider: CosmosProvider, address: H256, } impl CosmosNativeDispatchIndexer { /// New Dispatch Indexer - pub fn new(provider: CosmosNativeProvider, locator: ContractLocator) -> ChainResult { + pub fn new( + provider: CosmosProvider, + locator: ContractLocator, + ) -> ChainResult { Ok(CosmosNativeDispatchIndexer { provider, address: locator.address, @@ -108,8 +114,8 @@ impl SequenceAwareIndexer for CosmosNativeDispatchIndexer { let tip = CosmosEventIndexer::get_finalized_block_number(self).await?; let mailbox = self .provider - .grpc() - .mailbox(self.address.encode_hex(), Some(tip)) + .query() + .mailbox(self.address.encode_hex(), Some(tip as u64)) .await?; match mailbox.mailbox { Some(mailbox) => Ok((Some(mailbox.message_sent), tip)), diff --git a/rust/main/chains/hyperlane-cosmos-native/src/indexers/interchain_gas.rs b/rust/main/chains/hyperlane-cosmos/src/native/indexers/interchain_gas.rs similarity index 82% rename from rust/main/chains/hyperlane-cosmos-native/src/indexers/interchain_gas.rs rename to rust/main/chains/hyperlane-cosmos/src/native/indexers/interchain_gas.rs index 3fef5ab69f1..d5c88b6b2cc 100644 --- a/rust/main/chains/hyperlane-cosmos-native/src/indexers/interchain_gas.rs +++ b/rust/main/chains/hyperlane-cosmos/src/native/indexers/interchain_gas.rs @@ -1,7 +1,7 @@ use std::ops::RangeInclusive; +use cometbft::abci::EventAttribute; use hyperlane_cosmos_rs::{hyperlane::core::post_dispatch::v1::EventGasPayment, prost::Name}; -use tendermint::abci::EventAttribute; use tonic::async_trait; use tracing::instrument; @@ -12,17 +12,18 @@ use hyperlane_core::{ }; use crate::{ - ConnectionConf, CosmosEventIndexer, CosmosNativeProvider, HyperlaneCosmosError, RpcProvider, + native::module_query_client::ModuleQueryClient, ConnectionConf, CosmosProvider, + HyperlaneCosmosError, RpcProvider, }; -use super::ParsedEvent; +use crate::indexer::{CosmosEventIndexer, ParsedEvent}; -/// delivery indexer to check if a message was delivered +/// Interchain Gas Payment Indexer #[derive(Debug, Clone)] pub struct CosmosNativeInterchainGas { address: H256, domain: HyperlaneDomain, - provider: CosmosNativeProvider, + provider: CosmosProvider, native_token: String, } @@ -31,7 +32,7 @@ impl InterchainGasPaymaster for CosmosNativeInterchainGas {} impl CosmosNativeInterchainGas { /// Gas Payment Indexer pub fn new( - provider: CosmosNativeProvider, + provider: CosmosProvider, conf: &ConnectionConf, locator: ContractLocator, ) -> ChainResult { @@ -43,23 +44,12 @@ impl CosmosNativeInterchainGas { }) } - /// parses a cosmos sdk.Coin in a string representation '{amoun}{denom}' - /// only returns the amount if it matches the native token in the config + /// Parses a cosmos sdk.Coin in string representation '{amount}{denom}'. + /// Extracts the amount, trusting the IGP contract to validate the denom. fn parse_gas_payment(&self, coin: &str) -> ChainResult { - // Convert the coin to a u256 by taking everything before the first non-numeric character + let _ = &self.native_token; // Field kept for API compatibility match coin.find(|c: char| !c.is_numeric()) { - Some(first_non_numeric) => { - let amount = U256::from_dec_str(&coin[..first_non_numeric])?; - let denom = &coin[first_non_numeric..]; - if denom == self.native_token { - Ok(amount) - } else { - Err(ChainCommunicationError::from_other_str(&format!( - "invalid gas payment: {coin} expected denom: {}", - self.native_token - ))) - } - } + Some(first_non_numeric) => Ok(U256::from_dec_str(&coin[..first_non_numeric])?), None => Err(ChainCommunicationError::from_other_str(&format!( "invalid coin: {coin}" ))), diff --git a/rust/main/chains/hyperlane-cosmos-native/src/indexers/merkle_tree_hook.rs b/rust/main/chains/hyperlane-cosmos/src/native/indexers/merkle_tree_hook.rs similarity index 92% rename from rust/main/chains/hyperlane-cosmos-native/src/indexers/merkle_tree_hook.rs rename to rust/main/chains/hyperlane-cosmos/src/native/indexers/merkle_tree_hook.rs index 73d8799a702..3b20fca6e6c 100644 --- a/rust/main/chains/hyperlane-cosmos-native/src/indexers/merkle_tree_hook.rs +++ b/rust/main/chains/hyperlane-cosmos/src/native/indexers/merkle_tree_hook.rs @@ -1,5 +1,6 @@ use std::ops::RangeInclusive; +use cometbft::abci::EventAttribute; use hex::ToHex; use hyperlane_cosmos_rs::{ hyperlane::core::post_dispatch::v1::{ @@ -8,7 +9,6 @@ use hyperlane_cosmos_rs::{ prost::Name, }; use itertools::Itertools; -use tendermint::abci::EventAttribute; use tonic::async_trait; use tracing::instrument; @@ -19,20 +19,26 @@ use hyperlane_core::{ MerkleTreeInsertion, ReorgPeriod, SequenceAwareIndexer, H256, H512, }; -use crate::{CosmosNativeProvider, HyperlaneCosmosError, RpcProvider}; +use crate::{ + native::module_query_client::ModuleQueryClient, CosmosProvider, HyperlaneCosmosError, + RpcProvider, +}; -use super::{CosmosEventIndexer, ParsedEvent}; +use crate::indexer::{CosmosEventIndexer, ParsedEvent}; -/// delivery indexer to check if a message was delivered +/// Merkle Tree Hook Indexer #[derive(Debug, Clone)] pub struct CosmosNativeMerkleTreeHook { - provider: CosmosNativeProvider, + provider: CosmosProvider, address: H256, } impl CosmosNativeMerkleTreeHook { /// New Tree Insertion Indexer - pub fn new(provider: CosmosNativeProvider, locator: ContractLocator) -> ChainResult { + pub fn new( + provider: CosmosProvider, + locator: ContractLocator, + ) -> ChainResult { Ok(CosmosNativeMerkleTreeHook { provider, address: locator.address, @@ -42,18 +48,18 @@ impl CosmosNativeMerkleTreeHook { async fn get_merkle_tree( &self, reorg_period: &ReorgPeriod, - ) -> ChainResult<(WrappedMerkleTreeHookResponse, TreeResponse, u32)> { + ) -> ChainResult<(WrappedMerkleTreeHookResponse, TreeResponse, u64)> { let height = self.provider.reorg_to_height(reorg_period).await?; self.get_merkle_tree_with_height(height).await } async fn get_merkle_tree_with_height( &self, - height: u32, - ) -> ChainResult<(WrappedMerkleTreeHookResponse, TreeResponse, u32)> { + height: u64, + ) -> ChainResult<(WrappedMerkleTreeHookResponse, TreeResponse, u64)> { let hook = self .provider - .grpc() + .query() .merkle_tree_hook(self.address.encode_hex(), Some(height)) .await?; let hook = hook @@ -69,10 +75,10 @@ impl CosmosNativeMerkleTreeHook { fn parse_checkpoint_from_tree_response( &self, tree: &TreeResponse, - height: u32, + height: u64, ) -> ChainResult { let root = H256::from_slice(&tree.root); - let index = if tree.count == 0 { 0 } else { tree.count - 1 }; + let index = tree.count.saturating_sub(1); let checkpoint = Checkpoint { merkle_tree_hook_address: self.address, @@ -83,7 +89,7 @@ impl CosmosNativeMerkleTreeHook { Ok(CheckpointAtBlock { checkpoint, - block_height: Some(height as u64), + block_height: Some(height), }) } } @@ -135,7 +141,7 @@ impl MerkleTreeHook for CosmosNativeMerkleTreeHook { }; Ok(IncrementalMerkleAtBlock { tree, - block_height: Some(height as u64), + block_height: Some(height), }) } @@ -158,7 +164,7 @@ impl MerkleTreeHook for CosmosNativeMerkleTreeHook { #[instrument(level = "debug", err, ret, skip(self))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue async fn latest_checkpoint_at_block(&self, height: u64) -> ChainResult { - let (_, tree, height) = self.get_merkle_tree_with_height(height as u32).await?; + let (_, tree, height) = self.get_merkle_tree_with_height(height).await?; self.parse_checkpoint_from_tree_response(&tree, height) } } @@ -240,8 +246,8 @@ impl SequenceAwareIndexer for CosmosNativeMerkleTreeHook { let tip = CosmosEventIndexer::get_finalized_block_number(self).await?; let merkle_tree = self .provider - .grpc() - .merkle_tree_hook(self.address.encode_hex(), Some(tip)) + .query() + .merkle_tree_hook(self.address.encode_hex(), Some(tip as u64)) .await?; let merkle_tree_count = merkle_tree diff --git a/rust/main/chains/hyperlane-cosmos-native/src/ism.rs b/rust/main/chains/hyperlane-cosmos/src/native/ism.rs similarity index 82% rename from rust/main/chains/hyperlane-cosmos-native/src/ism.rs rename to rust/main/chains/hyperlane-cosmos/src/native/ism.rs index b892026c421..14d681b03b3 100644 --- a/rust/main/chains/hyperlane-cosmos-native/src/ism.rs +++ b/rust/main/chains/hyperlane-cosmos/src/native/ism.rs @@ -4,7 +4,7 @@ use cosmrs::Any; use hex::ToHex; use hyperlane_cosmos_rs::{ hyperlane::core::interchain_security::v1::{ - MerkleRootMultisigIsm, NoopIsm, RoutingIsm as CosmosRoutingIsm, + MerkleRootMultisigIsm, MessageIdMultisigIsm, NoopIsm, RoutingIsm as CosmosRoutingIsm, }, prost::{Message, Name}, }; @@ -16,7 +16,9 @@ use hyperlane_core::{ MultisigIsm, RoutingIsm, H160, H256, U256, }; -use crate::{CosmosNativeProvider, HyperlaneCosmosError}; +use crate::{CosmosProvider, HyperlaneCosmosError}; + +use super::module_query_client::ModuleQueryClient; /// Cosmos Native ISM #[derive(Debug)] @@ -26,22 +28,25 @@ pub struct CosmosNativeIsm { /// The address of the ISM contract. address: H256, /// The provider for the ISM contract. - provider: Box, + provider: CosmosProvider, } /// The Cosmos Interchain Security Module Implementation. impl CosmosNativeIsm { /// Creates a new Cosmos Interchain Security Module. - pub fn new(provider: CosmosNativeProvider, locator: ContractLocator) -> ChainResult { + pub fn new( + provider: CosmosProvider, + locator: ContractLocator, + ) -> ChainResult { Ok(Self { domain: locator.domain.clone(), address: locator.address, - provider: Box::new(provider), + provider, }) } async fn get_ism(&self) -> ChainResult { - let ism = self.provider.grpc().ism(self.address.encode_hex()).await?; + let ism = self.provider.query().ism(self.address.encode_hex()).await?; ism.ism.ok_or_else(|| { ChainCommunicationError::from_other_str(&format!( "Empty ism response for: {:?}", @@ -66,7 +71,7 @@ impl HyperlaneChain for CosmosNativeIsm { /// A provider for the chain fn provider(&self) -> Box { - self.provider.clone() + Box::new(self.provider.clone()) } } @@ -79,6 +84,7 @@ impl InterchainSecurityModule for CosmosNativeIsm { async fn module_type(&self) -> ChainResult { let ism = self.get_ism().await?; match ism.type_url.as_str() { + t if t == MessageIdMultisigIsm::type_url() => Ok(ModuleType::MessageIdMultisig), t if t == MerkleRootMultisigIsm::type_url() => Ok(ModuleType::MerkleRootMultisig), t if t == CosmosRoutingIsm::type_url() => Ok(ModuleType::Routing), t if t == NoopIsm::type_url() => Ok(ModuleType::Null), @@ -122,6 +128,16 @@ impl MultisigIsm for CosmosNativeIsm { .collect::, _>>()?; Ok((validators, ism.threshold as u8)) } + t if t == MessageIdMultisigIsm::type_url() => { + let ism = MessageIdMultisigIsm::decode(ism.value.as_slice()) + .map_err(HyperlaneCosmosError::from)?; + let validators = ism + .validators + .iter() + .map(|v| H160::from_str(v).map(H256::from)) + .collect::, _>>()?; + Ok((validators, ism.threshold as u8)) + } _ => Err(ChainCommunicationError::from_other_str(&format!( "ISM {:?} not a multi sig ism", self.address diff --git a/rust/main/chains/hyperlane-cosmos-native/src/mailbox.rs b/rust/main/chains/hyperlane-cosmos/src/native/mailbox.rs similarity index 59% rename from rust/main/chains/hyperlane-cosmos-native/src/mailbox.rs rename to rust/main/chains/hyperlane-cosmos/src/native/mailbox.rs index 7c2fd9903ad..80cc2a60d61 100644 --- a/rust/main/chains/hyperlane-cosmos-native/src/mailbox.rs +++ b/rust/main/chains/hyperlane-cosmos/src/native/mailbox.rs @@ -1,21 +1,30 @@ +use std::ops::Mul; + +use cometbft::hash::Algorithm; +use cometbft::Hash; +use hyperlane_cosmos_rs::dymensionxyz::dymension::kas::{MsgIndicateProgress, ProgressIndication}; +use tonic::async_trait; +use tracing::info; + use cosmrs::Any; use hex::ToHex; use hyperlane_cosmos_rs::hyperlane::core::v1::MsgProcessMessage; use hyperlane_cosmos_rs::prost::{Message, Name}; -use tonic::async_trait; use hyperlane_core::{ ChainCommunicationError, ChainResult, ContractLocator, FixedPointNumber, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, Mailbox, - RawHyperlaneMessage, ReorgPeriod, TxCostEstimate, TxOutcome, H256, U256, + RawHyperlaneMessage, ReorgPeriod, TxCostEstimate, TxOutcome, H256, H512, U256, }; -use crate::CosmosNativeProvider; +use crate::{utils, CosmosProvider}; + +use super::module_query_client::ModuleQueryClient; /// Cosmos Native Mailbox #[derive(Debug, Clone)] pub struct CosmosNativeMailbox { - provider: CosmosNativeProvider, + provider: CosmosProvider, domain: HyperlaneDomain, address: H256, } @@ -23,7 +32,7 @@ pub struct CosmosNativeMailbox { impl CosmosNativeMailbox { /// new cosmos native mailbox instance pub fn new( - provider: CosmosNativeProvider, + provider: CosmosProvider, locator: ContractLocator, ) -> ChainResult { Ok(CosmosNativeMailbox { @@ -43,7 +52,7 @@ impl CosmosNativeMailbox { let metadata = hex::encode(metadata); let signer = self.provider.rpc().get_signer()?.address_string.clone(); let process = MsgProcessMessage { - mailbox_id: "0x".to_string() + &mailbox_id, + mailbox_id: format!("0x{mailbox_id}"), metadata, message, relayer: signer, @@ -76,7 +85,7 @@ impl HyperlaneContract for CosmosNativeMailbox { #[async_trait] impl Mailbox for CosmosNativeMailbox { - /// Gets the current leaf count of the merkle tree + /// Gets the current mailbox dispatch count /// /// - `reorg_period` is how far behind the current block to query, if not specified /// it will query at the latest block. @@ -84,7 +93,7 @@ impl Mailbox for CosmosNativeMailbox { let height = self.provider.reorg_to_height(reorg_period).await?; let mailbox = self .provider - .grpc() + .query() .mailbox(self.address.encode_hex(), Some(height)) .await?; Ok(mailbox.mailbox.map(|m| m.message_sent).unwrap_or(0)) @@ -94,7 +103,7 @@ impl Mailbox for CosmosNativeMailbox { async fn delivered(&self, id: H256) -> ChainResult { let delivered = self .provider - .grpc() + .query() .delivered(self.address.encode_hex(), id.encode_hex()) .await?; Ok(delivered.delivered) @@ -104,7 +113,7 @@ impl Mailbox for CosmosNativeMailbox { async fn default_ism(&self) -> ChainResult { let mailbox = self .provider - .grpc() + .query() .mailbox(self.address.encode_hex(), None) .await?; match mailbox.mailbox { @@ -120,7 +129,7 @@ impl Mailbox for CosmosNativeMailbox { async fn recipient_ism(&self, recipient: H256) -> ChainResult { let recipient = self .provider - .grpc() + .query() .recipient_ism(recipient.encode_hex()) .await?; let recipient: H256 = recipient.ism_id.parse()?; @@ -142,19 +151,11 @@ impl Mailbox for CosmosNativeMailbox { .rpc() .send(vec![any_encoded], gas_limit) .await?; - - // we assume that the underlying cosmos chain does not have gas refunds - // in that case the gas paid will always be: - // gas_wanted * gas_price - let gas_price = - FixedPointNumber::from(response.tx_result.gas_wanted) * self.provider.rpc().gas_price(); - - Ok(TxOutcome { - transaction_id: H256::from_slice(response.hash.as_bytes()).into(), - executed: response.tx_result.code.is_ok() && response.check_tx.code.is_ok(), - gas_used: response.tx_result.gas_used.into(), - gas_price, - }) + info!("Cosmos process response: {response:?}"); + Ok(utils::tx_response_to_outcome( + response, + self.provider.rpc().gas_price(), + )) } /// Estimate transaction costs to process a message. @@ -187,3 +188,91 @@ impl Mailbox for CosmosNativeMailbox { todo!() } } +/// DYMENSION: required for Kaspa bridge, a special indicate progress TX +/// https://github.com/dymensionxyz/dymension/blob/2ddaf251568713d45a6900c0abb8a30158efc9aa/x/kas/keeper/msg_server.go#L29 +impl CosmosNativeMailbox { + /// atomically update the hub with a new outpoint anchor and set of completed withdrawals + pub async fn indicate_progress( + &self, + metadata: &[u8], + u: &ProgressIndication, + ) -> ChainResult { + let msg = MsgIndicateProgress { + signer: self.provider.rpc().get_signer()?.address_string.clone(), + metadata: metadata.to_vec(), + payload: Some(u.clone()), + }; + let a = Any { + type_url: MsgIndicateProgress::type_url(), + value: msg.encode_to_vec(), + }; + let gas_limit = None; + let response = self.provider.rpc().send(vec![a], gas_limit).await?; + + // we assume that the underlying cosmos chain does not have gas refunds + // in that case the gas paid will always be: + // gas_wanted * gas_price + let gas_price = if response.tx_result.code.is_err() { + FixedPointNumber::try_from(U256::zero())? + } else { + FixedPointNumber::try_from(U256::from(response.tx_result.gas_wanted))? + .mul(self.provider.rpc().gas_price()) + }; + + let executed = response.tx_result.code.is_ok() && response.check_tx.code.is_ok(); + + // Logging here is a hack to get a reject reason. + // TxOutcome doesn't have a field for the reject reason. + // Cosmos doesn't save rejected TXs on-chain. + // Logging here is the easiest way to see what happened. + if !executed { + info!("Dymension, indicate progress is not executed on-chain: {response:?}"); + } + + Ok(TxOutcome { + transaction_id: H256::from_slice(response.hash.as_bytes()).into(), + executed, + gas_used: response.tx_result.gas_used.into(), + gas_price, + }) + } +} + +/// Convert H512 to Cosmos hash format (used by Kaspa bridge) +pub fn h512_to_cosmos_hash(h: H512) -> Hash { + let h_256: H256 = h.into(); + Hash::from_bytes(Algorithm::Sha256, h_256.as_bytes()) + .expect("H256 bytes are always valid SHA-256 hash") +} + +/// Extract the last 32 bytes of H512 to create H256 +/// Used for converting Cosmos transaction IDs to storage keys +pub fn h512_to_h256(h: H512) -> H256 { + let mut bytes = [0u8; 32]; + bytes.copy_from_slice(&h.as_bytes()[32..]); + H256::from(bytes) +} + +#[cfg(test)] +mod test { + use super::*; + use cometbft::{hash::Algorithm, Hash}; + + #[test] + fn test_hash() { + // From cosmos hex to HL transaction ID + let cosmos_hex = "5F3C6367A3AAC0B7E0B1F63CE25FEEDA3914F57FA9EAEC0F6A10CD84740BA010"; + let cosmos_hash = Hash::from_hex_upper(Algorithm::Sha256, cosmos_hex).unwrap(); + let cosmos_bytes = cosmos_hash.as_bytes(); + + let tx_id: H512 = H256::from_slice(cosmos_bytes).into(); + + // From HL transaction ID to cosmos hex + let tx_id_256: H256 = tx_id.into(); + let cosmos_bytes_1 = tx_id_256.as_bytes(); + let cosmos_hash_1 = Hash::from_bytes(Algorithm::Sha256, cosmos_bytes_1).unwrap(); + let cosmos_hex_1 = cosmos_hash_1.encode_hex_upper::(); + + assert_eq!(cosmos_hex, cosmos_hex_1.as_str()); + } +} diff --git a/rust/main/chains/hyperlane-cosmos/src/native/mod.rs b/rust/main/chains/hyperlane-cosmos/src/native/mod.rs new file mode 100644 index 00000000000..3ec43d40139 --- /dev/null +++ b/rust/main/chains/hyperlane-cosmos/src/native/mod.rs @@ -0,0 +1,11 @@ +/// Hyperlane Cosmos Module +/// This module contains the implementation of the Hyperlane Cosmos module. +/// The hyperlane cosmos module shares logic for chain communication with the Cw implementation, however, parsing of events and state queries are different. +/// The module itself is independent to the CW implementation. +mod indexers; +mod ism; +mod mailbox; +mod module_query_client; +mod validator_announce; + +pub use {indexers::*, ism::*, mailbox::*, module_query_client::*, validator_announce::*}; diff --git a/rust/main/chains/hyperlane-cosmos/src/native/module_query_client.rs b/rust/main/chains/hyperlane-cosmos/src/native/module_query_client.rs new file mode 100644 index 00000000000..fe89cfb6490 --- /dev/null +++ b/rust/main/chains/hyperlane-cosmos/src/native/module_query_client.rs @@ -0,0 +1,353 @@ +use cosmrs::{Any, Tx}; +use hyperlane_cosmos_rs::dymensionxyz::dymension::kas::{ + query_client::QueryClient as KasQueryClient, QueryOutpointRequest, QueryOutpointResponse, + QueryWithdrawalStatusRequest, QueryWithdrawalStatusResponse, WithdrawalId, +}; +use hyperlane_cosmos_rs::hyperlane::core::interchain_security::v1::{ + query_client::QueryClient as IsmQueryClient, QueryAnnouncedStorageLocationsRequest, + QueryAnnouncedStorageLocationsResponse, QueryIsmRequest, QueryIsmResponse, +}; +use hyperlane_cosmos_rs::hyperlane::core::post_dispatch::v1::{ + query_client::QueryClient as PostDispatchQueryClient, QueryMerkleTreeHookRequest, + QueryMerkleTreeHookResponse, +}; +use hyperlane_cosmos_rs::hyperlane::core::v1::query_client::QueryClient; +use hyperlane_cosmos_rs::hyperlane::core::v1::{ + MsgProcessMessage, QueryDeliveredRequest, QueryDeliveredResponse, QueryMailboxRequest, + QueryMailboxResponse, QueryRecipientIsmRequest, QueryRecipientIsmResponse, +}; +use hyperlane_cosmos_rs::hyperlane::warp::v1::MsgRemoteTransfer; +use hyperlane_cosmos_rs::prost::{Message, Name}; +use tonic::async_trait; + +use hyperlane_core::{ + ChainCommunicationError, ChainResult, HyperlaneMessage, RawHyperlaneMessage, H256, H512, +}; + +use crate::GrpcProvider; +use crate::{BuildableQueryClient, HyperlaneCosmosError}; + +/// Query Client for the Hyperlane Cosmos module +/// the client provides queries for the state of the Hyperlane application living on a Cosmos chain +#[derive(Clone, Debug)] +pub struct ModuleQueryClient { + /// grpc provider + grpc: GrpcProvider, +} + +#[async_trait] +impl BuildableQueryClient for ModuleQueryClient { + fn build_query_client( + grpc: GrpcProvider, + _conf: &crate::ConnectionConf, + _locator: &hyperlane_core::ContractLocator, + _signer: Option, + ) -> hyperlane_core::ChainResult { + Ok(Self { grpc }) + } + + // the tx is either a MsgPorcessMessage on the destination or a MsgRemoteTransfer on the origin + // we check for both tx types, if both are missing or an error occurred while parsing we return the error + fn parse_tx_message_recipient(&self, tx: &Tx, _hash: &H512) -> ChainResult { + // first check for the process message + if let Some(recipient) = Self::parse_msg_process_recipient(tx)? { + return Ok(recipient); + } + // if not found check for the remote transfer + if let Some(recipient) = Self::parse_msg_remote_transfer_recipient(tx)? { + return Ok(recipient); + } + // if both are missing we return an error + Err(HyperlaneCosmosError::ParsingFailed( + "transaction does not contain any process message or remote transfer".to_owned(), + ))? + } + + async fn is_contract(&self, _address: &H256) -> ChainResult { + return Ok(true); + } + + /// Returns the Block height of the query client + async fn get_block_number(&self) -> ChainResult { + self.grpc.get_block_number().await + } +} + +impl ModuleQueryClient { + /// parses the message recipient if the transaction contains a MsgProcessMessage + fn parse_msg_process_recipient(tx: &Tx) -> ChainResult> { + // check for all messages processes + let processed_messages: Vec = tx + .body + .messages + .iter() + .filter(|a| a.type_url == MsgProcessMessage::type_url()) + .cloned() + .collect(); + + // right now one transaction can include max. one process + if processed_messages.len() > 1 { + let msg = "transaction contains multiple execution messages"; + Err(HyperlaneCosmosError::ParsingFailed(msg.to_owned()))? + } + + let msg = processed_messages.first(); + match msg { + Some(msg) => { + let result = MsgProcessMessage::decode(msg.value.as_slice()) + .map_err(HyperlaneCosmosError::from)?; + let message: RawHyperlaneMessage = hex::decode(result.message)?; + let message = HyperlaneMessage::from(message); + Ok(Some(message.recipient)) + } + None => Ok(None), + } + } + + /// parses the message recipient if the transaction contains a MsgRemoteTransfer + fn parse_msg_remote_transfer_recipient(tx: &Tx) -> ChainResult> { + // check for all remote transfers + let remote_transfers: Vec = tx + .body + .messages + .iter() + .filter(|a| a.type_url == MsgRemoteTransfer::type_url()) + .cloned() + .collect(); + + // right now one transaction can include max. one transfer + if remote_transfers.len() > 1 { + let msg = "transaction contains multiple execution messages"; + Err(HyperlaneCosmosError::ParsingFailed(msg.to_owned()))? + } + + let msg = remote_transfers.first().ok_or_else(|| { + ChainCommunicationError::from_other_str("tx does not contain any remote transfers") + })?; + let result = + MsgRemoteTransfer::decode(msg.value.as_slice()).map_err(HyperlaneCosmosError::from)?; + // the recipient is the token id of the transfer, which is the address that the user interacts with + let recipient: H256 = result.token_id.parse()?; + Ok(Some(recipient)) + } + + fn request_at_height( + request: impl tonic::IntoRequest, + height: Option, + ) -> tonic::Request { + let mut request = request.into_request(); + if let Some(height) = height { + request + .metadata_mut() + .insert("x-cosmos-block-height", height.into()); + } + request + } + + /// Mailbox struct at given height + pub async fn mailbox( + &self, + id: String, + height: Option, + ) -> ChainResult { + self.grpc + .call(|client| { + let id = id.clone(); + let future = async move { + let mut service = QueryClient::new(client.channel()); + let result = service + .mailbox(Self::request_at_height(QueryMailboxRequest { id }, height)) + .await + .map_err(HyperlaneCosmosError::from)? + .into_inner(); + Ok(result) + }; + Box::pin(future) + }) + .await + } + + /// All of the storage locations of a validator + /// + /// Note: a validators storage locations are depended on the mailbox + pub async fn announced_storage_locations( + &self, + mailbox: String, + validator: String, + ) -> ChainResult { + self.grpc + .call(|client| { + let mailbox = mailbox.clone(); + let validator = validator.clone(); + let future = async move { + let mut service = IsmQueryClient::new(client.channel()); + let result = service + .announced_storage_locations(QueryAnnouncedStorageLocationsRequest { + mailbox_id: mailbox, + validator_address: validator, + }) + .await + .map_err(HyperlaneCosmosError::from)? + .into_inner(); + Ok(result) + }; + Box::pin(future) + }) + .await + } + + /// ISM for a given recipient + /// + /// Recipient is a 32 byte long hex address + /// Mailbox independent query as one application (recipient) can only ever register on one mailbox + pub async fn recipient_ism(&self, recipient: String) -> ChainResult { + self.grpc + .call(|client| { + let recipient = recipient.clone(); + let future = async move { + let mut service = QueryClient::new(client.channel()); + let result = service + .recipient_ism(QueryRecipientIsmRequest { recipient }) + .await + .map_err(HyperlaneCosmosError::from)? + .into_inner(); + Ok(result) + }; + Box::pin(future) + }) + .await + } + + /// merkle tree hook + /// + /// also contains the current root of the branches + pub async fn merkle_tree_hook( + &self, + id: String, + height: Option, + ) -> ChainResult { + self.grpc + .call(|client| { + let id = id.clone(); + let future = async move { + let mut service = PostDispatchQueryClient::new(client.channel()); + let result = service + .merkle_tree_hook(Self::request_at_height( + QueryMerkleTreeHookRequest { id }, + height, + )) + .await + .map_err(HyperlaneCosmosError::from)? + .into_inner(); + Ok(result) + }; + Box::pin(future) + }) + .await + } + + /// checks if a message has been delivered to the given mailbox + pub async fn delivered( + &self, + mailbox_id: String, + message_id: String, + ) -> ChainResult { + self.grpc + .call(|client| { + let mailbox_id = mailbox_id.clone(); + let message_id = message_id.clone(); + let future = async move { + let mut service = QueryClient::new(client.channel()); + let result = service + .delivered(QueryDeliveredRequest { + id: mailbox_id, + message_id, + }) + .await + .map_err(HyperlaneCosmosError::from)? + .into_inner(); + Ok(result) + }; + Box::pin(future) + }) + .await + } + + /// ism for a given id + /// + /// Note: this query will only ever work for the core ISMs that are directly supported by the cosmos module + /// because the cosmos module forces extensions to be stored in external keepers (Cosmos SDK specific). + /// As a result, extensions have to provide custom queries for their types, meaning if we want to support a custom ISM at some point - that is not provided by the default hyperlane cosmos module - + /// we'd have to query a custom endpoint for the ISMs as well. + pub async fn ism(&self, id: String) -> ChainResult { + self.grpc + .call(|client| { + let id = id.clone(); + let future = async move { + let mut service = IsmQueryClient::new(client.channel()); + let result = service + .ism(QueryIsmRequest { id }) + .await + .map_err(HyperlaneCosmosError::from)? + .into_inner(); + Ok(result) + }; + Box::pin(future) + }) + .await + } + + /// Query the current outpoint (anchor) for Kaspa bridge + pub async fn outpoint(&self, height: Option) -> ChainResult { + self.grpc + .call(|client| { + let future = async move { + let mut service = KasQueryClient::new(client.channel()); + let result = service + .outpoint(Self::request_at_height(QueryOutpointRequest {}, height)) + .await + .map_err(HyperlaneCosmosError::from)? + .into_inner(); + Ok(result) + }; + Box::pin(future) + }) + .await + } + + /// Query withdrawal status by withdrawal ID + pub async fn withdrawal_status( + &self, + withdrawal_id: Vec, + height: Option, + ) -> ChainResult { + self.grpc + .call(|client| { + let withdrawal_id = withdrawal_id.clone(); + let future = async move { + let mut service = KasQueryClient::new(client.channel()); + let result = service + .withdrawal_status(Self::request_at_height( + QueryWithdrawalStatusRequest { withdrawal_id }, + height, + )) + .await + .map_err(HyperlaneCosmosError::from)? + .into_inner(); + Ok(result) + }; + Box::pin(future) + }) + .await + } + + /// bit of a hack, we can see if the hub x/kas is bootstrapped by doing an empty withdrawal query + /// https://github.com/dymensionxyz/dymension/blob/55468f6494cc233d7478a658964881c465c46555/x/kas/keeper/grpc_query.go#L30 + pub async fn hub_bootstrapped(&self) -> ChainResult { + let ws_res = self.withdrawal_status(vec![], None).await; + match ws_res { + Ok(_) => Ok(true), + Err(e) => Err(e), + } + } +} diff --git a/rust/main/chains/hyperlane-cosmos-native/src/validator_announce.rs b/rust/main/chains/hyperlane-cosmos/src/native/validator_announce.rs similarity index 72% rename from rust/main/chains/hyperlane-cosmos-native/src/validator_announce.rs rename to rust/main/chains/hyperlane-cosmos/src/native/validator_announce.rs index 97c5fc57ce7..c136b6778e6 100644 --- a/rust/main/chains/hyperlane-cosmos-native/src/validator_announce.rs +++ b/rust/main/chains/hyperlane-cosmos/src/native/validator_announce.rs @@ -1,3 +1,5 @@ +use std::ops::Mul; + use async_trait::async_trait; use cosmrs::Any; use hex::ToHex; @@ -5,24 +7,28 @@ use hyperlane_cosmos_rs::hyperlane::core::interchain_security::v1::MsgAnnounceVa use hyperlane_cosmos_rs::prost::{Message, Name}; use hyperlane_core::{ - Announcement, ChainResult, ContractLocator, Encode, FixedPointNumber, HyperlaneChain, - HyperlaneContract, HyperlaneDomain, HyperlaneProvider, SignedType, TxOutcome, - ValidatorAnnounce, H160, H256, U256, + Announcement, ChainResult, ContractLocator, Encode, HyperlaneChain, HyperlaneContract, + HyperlaneDomain, HyperlaneProvider, SignedType, TxOutcome, ValidatorAnnounce, H160, H256, U256, }; -use crate::CosmosNativeProvider; +use crate::{utils, CosmosProvider}; + +use super::module_query_client::ModuleQueryClient; /// A reference to a ValidatorAnnounce contract on some Cosmos chain #[derive(Debug)] pub struct CosmosNativeValidatorAnnounce { domain: HyperlaneDomain, address: H256, - provider: CosmosNativeProvider, + provider: CosmosProvider, } impl CosmosNativeValidatorAnnounce { /// create a new instance of CosmosValidatorAnnounce - pub fn new(provider: CosmosNativeProvider, locator: ContractLocator) -> ChainResult { + pub fn new( + provider: CosmosProvider, + locator: ContractLocator, + ) -> ChainResult { Ok(Self { domain: locator.domain.clone(), address: locator.address, @@ -62,7 +68,7 @@ impl ValidatorAnnounce for CosmosNativeValidatorAnnounce { for validator in validators { let locations = self .provider - .grpc() + .query() .announced_storage_locations(self.address.encode_hex(), validator.clone()) .await; if let Ok(locations) = locations { @@ -76,11 +82,12 @@ impl ValidatorAnnounce for CosmosNativeValidatorAnnounce { async fn announce(&self, announcement: SignedType) -> ChainResult { let signer = self.provider.rpc().get_signer()?.address_string.to_owned(); + let mailbox_address_hex = hex::encode(announcement.value.mailbox_address.to_vec()); let announce = MsgAnnounceValidator { validator: announcement.value.validator.encode_hex(), storage_location: announcement.value.storage_location.clone(), signature: hex::encode(announcement.signature.to_vec()), - mailbox_id: "0x".to_owned() + &hex::encode(announcement.value.mailbox_address.to_vec()), // has to be prefixed with 0x + mailbox_id: format!("0x{mailbox_address_hex}"), creator: signer, }; @@ -91,18 +98,10 @@ impl ValidatorAnnounce for CosmosNativeValidatorAnnounce { let response = self.provider.rpc().send(vec![any_msg], None).await?; - // we assume that the underlying cosmos chain does not have gas refunds - // in that case the gas paid will always be: - // gas_wanted * gas_price - let gas_price = - FixedPointNumber::from(response.tx_result.gas_wanted) * self.provider.rpc().gas_price(); - - Ok(TxOutcome { - transaction_id: H256::from_slice(response.hash.as_bytes()).into(), - executed: response.check_tx.code.is_ok() && response.tx_result.code.is_ok(), - gas_used: response.tx_result.gas_used.into(), - gas_price, - }) + Ok(utils::tx_response_to_outcome( + response, + self.provider.rpc().gas_price(), + )) } async fn announce_tokens_needed( diff --git a/rust/main/chains/hyperlane-cosmos/src/payloads/mod.rs b/rust/main/chains/hyperlane-cosmos/src/payloads/mod.rs deleted file mode 100644 index 980e501a3ce..00000000000 --- a/rust/main/chains/hyperlane-cosmos/src/payloads/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod aggregate_ism; -pub mod general; -pub mod ism_routes; -pub mod mailbox; -pub mod merkle_tree_hook; -pub mod multisig_ism; -pub mod validator_announce; diff --git a/rust/main/chains/hyperlane-cosmos/src/prometheus/metrics_channel.rs b/rust/main/chains/hyperlane-cosmos/src/prometheus/metrics_channel.rs deleted file mode 100644 index 1a64e27da85..00000000000 --- a/rust/main/chains/hyperlane-cosmos/src/prometheus/metrics_channel.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::future::Future; -use std::num::NonZeroUsize; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::time::Instant; - -use derive_new::new; -use hyperlane_metric::prometheus_metric::{PrometheusClientMetrics, PrometheusConfig}; -use pin_project::pin_project; -use tonic::codegen::http::{Request, Response}; -use tonic::{Code, GrpcMethod}; -use tower::Service; - -use super::metrics_future::MetricsChannelFuture; - -#[derive(Debug)] -/// Wrapper for instrumenting a tonic client channel with gRPC metrics. -pub struct MetricsChannel { - metrics: PrometheusClientMetrics, - metrics_config: PrometheusConfig, - inner: T, -} - -impl MetricsChannel { - /// Wrap a channel so that sending RPCs over it increments gRPC client - /// Prometeus metrics. - pub fn new( - inner: T, - metrics: PrometheusClientMetrics, - metrics_config: PrometheusConfig, - ) -> Self { - // increment provider metric count - let chain_name = PrometheusConfig::chain_name(&metrics_config.chain); - metrics.increment_provider_instance(chain_name); - - Self { - inner, - metrics, - metrics_config, - } - } -} - -impl Drop for MetricsChannel { - fn drop(&mut self) { - // decrement provider metric count - let chain_name = PrometheusConfig::chain_name(&self.metrics_config.chain); - self.metrics.decrement_provider_instance(chain_name); - } -} - -impl Clone for MetricsChannel { - fn clone(&self) -> Self { - Self::new( - self.inner.clone(), - self.metrics.clone(), - self.metrics_config.clone(), - ) - } -} - -impl Service> for MetricsChannel -where - T: Service, Response = Response>, - T::Future: Future>, -{ - type Response = T::Response; - type Error = T::Error; - type Future = MetricsChannelFuture; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.inner.poll_ready(cx) - } - - fn call(&mut self, req: Request) -> Self::Future { - let (service, method) = req - .extensions() - .get::() - .map_or(("", ""), |gm| (gm.service(), gm.method())); - MetricsChannelFuture::new( - method.into(), - self.metrics.clone(), - self.metrics_config.clone(), - self.inner.call(req), - ) - } -} diff --git a/rust/main/chains/hyperlane-cosmos/src/prometheus/metrics_future.rs b/rust/main/chains/hyperlane-cosmos/src/prometheus/metrics_future.rs deleted file mode 100644 index 85e9e8593b7..00000000000 --- a/rust/main/chains/hyperlane-cosmos/src/prometheus/metrics_future.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::future::Future; -use std::num::NonZeroUsize; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::time::Instant; - -use derive_new::new; -use hyperlane_metric::prometheus_metric::{PrometheusClientMetrics, PrometheusConfig}; -use pin_project::pin_project; -use tonic::codegen::http::{request, response}; -use tonic::Code; - -/// This is only needed to capture the result of the future -#[pin_project] -pub struct MetricsChannelFuture { - method: String, - metrics: PrometheusClientMetrics, - metrics_config: PrometheusConfig, - started_at: Option, - #[pin] - inner: F, -} - -impl MetricsChannelFuture { - pub fn new( - method: String, - metrics: PrometheusClientMetrics, - metrics_config: PrometheusConfig, - inner: F, - ) -> Self { - Self { - started_at: None, - method, - metrics, - metrics_config, - inner, - } - } -} - -impl Future for MetricsChannelFuture -where - F: Future, E>>, -{ - type Output = F::Output; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - - let started_at = this.started_at.get_or_insert_with(Instant::now); - - if let Poll::Ready(v) = this.inner.poll(cx) { - let code = v.as_ref().map_or(Code::Unknown, |resp| { - resp.headers() - .get("grpc-status") - .map(|s| Code::from_bytes(s.as_bytes())) - .unwrap_or(Code::Ok) - }); - this.metrics.increment_metrics( - this.metrics_config, - this.method, - *started_at, - code == Code::Ok, - ); - - Poll::Ready(v) - } else { - Poll::Pending - } - } -} diff --git a/rust/main/chains/hyperlane-cosmos/src/prometheus/mod.rs b/rust/main/chains/hyperlane-cosmos/src/prometheus/mod.rs deleted file mode 100644 index 414eef7d446..00000000000 --- a/rust/main/chains/hyperlane-cosmos/src/prometheus/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod metrics_channel; -pub mod metrics_future; diff --git a/rust/main/chains/hyperlane-cosmos/src/providers.rs b/rust/main/chains/hyperlane-cosmos/src/providers.rs deleted file mode 100644 index 3c3a79fd807..00000000000 --- a/rust/main/chains/hyperlane-cosmos/src/providers.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub use cosmos::CosmosProvider; - -/// cosmos provider -mod cosmos; -/// cosmos grpc provider -pub mod grpc; -/// cosmos rpc provider -pub mod rpc; diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/cosmos.rs b/rust/main/chains/hyperlane-cosmos/src/providers/cosmos.rs index 9fea74725dc..c84e8a552c3 100644 --- a/rust/main/chains/hyperlane-cosmos/src/providers/cosmos.rs +++ b/rust/main/chains/hyperlane-cosmos/src/providers/cosmos.rs @@ -1,3 +1,334 @@ -pub use provider::CosmosProvider; +use cosmrs::{ + crypto::PublicKey, + tx::{SequenceNumber, SignerInfo}, + AccountId, Coin, Tx, +}; +use itertools::Itertools; +use time::OffsetDateTime; +use tonic::async_trait; +use tracing::warn; -mod provider; +use hyperlane_core::{ + h512_to_bytes, rpc_clients::BlockNumberGetter, utils::to_atto, BlockInfo, + ChainCommunicationError, ChainInfo, ChainResult, ContractLocator, HyperlaneChain, + HyperlaneDomain, HyperlaneProvider, HyperlaneProviderError, ReorgPeriod, TxnInfo, + TxnReceiptInfo, H256, H512, U256, +}; +use hyperlane_metric::prometheus_metric::PrometheusClientMetrics; + +use crate::{utils, ConnectionConf, CosmosAccountId, CosmosAddress, HyperlaneCosmosError, Signer}; + +use super::{grpc::GrpcProvider, rpc::RpcProvider}; + +/// Trait for the QueryClient to be used in the CosmosProvider +#[async_trait] +pub trait BuildableQueryClient: Sized + std::fmt::Debug + Sync + Send + 'static + Clone { + /// Build the QueryClient + fn build_query_client( + grpc: GrpcProvider, + conf: &ConnectionConf, + locator: &ContractLocator, + signer: Option, + ) -> ChainResult; + + /// Whether or not the given address is a contract + async fn is_contract(&self, address: &H256) -> ChainResult; + + /// Extract the message recipient contract address from the tx + /// this is implementation specific + fn parse_tx_message_recipient(&self, tx: &Tx, hash: &H512) -> ChainResult; + + /// Returns the Block height of the query client + async fn get_block_number(&self) -> ChainResult; +} + +/// Cosmos Provider +/// +/// implements the HyperlaneProvider trait +#[derive(Debug, Clone)] +pub struct CosmosProvider { + conf: ConnectionConf, + /// RPC provider for Cosmos chain + pub rpc: RpcProvider, + domain: HyperlaneDomain, + query: QueryClient, +} + +impl CosmosProvider { + /// Create a new Cosmos Provider instance + pub fn new( + conf: &ConnectionConf, + locator: &ContractLocator, + signer: Option, + metrics: PrometheusClientMetrics, + chain: Option, + ) -> ChainResult { + let rpc = RpcProvider::new(conf.clone(), signer.clone(), metrics.clone(), chain.clone())?; + let grpc = GrpcProvider::new(conf, metrics, chain)?; + let query = QueryClient::build_query_client(grpc.clone(), conf, locator, signer)?; + + Ok(CosmosProvider { + domain: locator.domain.clone(), + conf: conf.clone(), + rpc, + query, + }) + } + + /// RPC Provider + /// + /// This is used for general chain communication like getting the block number, block, transaction, etc. + pub fn rpc(&self) -> &RpcProvider { + &self.rpc + } + + /// gRPC Provider + /// + /// This is used for the Module Communication and querying the module state. Like mailboxes, isms etc. + pub fn query(&self) -> &QueryClient { + &self.query + } + + /// Get the block number according to the reorg period + pub async fn reorg_to_height(&self, reorg: &ReorgPeriod) -> ChainResult { + // use the query client's block number as they tend to lag behind + let height = self.query().get_block_number().await?; + match reorg { + ReorgPeriod::None => Ok(height), + // height has to be at least 1 -> block 0 does not exist in cosmos + ReorgPeriod::Blocks(blocks) => Ok(height.saturating_sub(blocks.get() as u64).max(1)), + ReorgPeriod::Tag(_) => Err(ChainCommunicationError::InvalidReorgPeriod(reorg.clone())), + } + } + + fn search_payer_in_signer_infos( + &self, + signer_infos: &[SignerInfo], + payer: &AccountId, + ) -> ChainResult<(AccountId, SequenceNumber)> { + signer_infos + .iter() + .map(|si| self.convert_signer_info_into_account_id_and_nonce(si)) + // After the following we have a single Ok entry and, possibly, many Err entries + .filter_ok(|(a, _)| payer == a) + // If we have Ok entry, use it since it is the payer, if not, use the first entry with error + .find_or_first(|r| match r { + Ok((a, _)) => payer == a, + Err(_) => false, + }) + // If there were not any signer info with non-empty public key or no signers for the transaction, + // we get None here + .unwrap_or_else(|| Err(ChainCommunicationError::from_other_str("no signer info"))) + } + + fn convert_signer_info_into_account_id_and_nonce( + &self, + signer_info: &SignerInfo, + ) -> ChainResult<(AccountId, SequenceNumber)> { + let signer_public_key = signer_info.public_key.clone().ok_or_else(|| { + HyperlaneCosmosError::PublicKeyError("no public key for default signer".to_owned()) + })?; + + let (key, account_address_type) = utils::normalize_public_key(signer_public_key)?; + let public_key = PublicKey::try_from(key)?; + + let account_id = CosmosAccountId::account_id_from_pubkey( + public_key, + &self.conf.get_bech32_prefix(), + &account_address_type, + )?; + + Ok((account_id, signer_info.sequence)) + } + + /// Calculates the sender and the nonce for the transaction. + /// We use `payer` of the fees as the sender of the transaction, and we search for `payer` + /// signature information to find the nonce. + /// If `payer` is not specified, we use the account which signed the transaction first, as + /// the sender. + pub fn sender_and_nonce(&self, tx: &Tx) -> ChainResult<(H256, SequenceNumber)> { + let (sender, nonce) = tx + .auth_info + .fee + .payer + .as_ref() + .map(|payer| self.search_payer_in_signer_infos(&tx.auth_info.signer_infos, payer)) + .map_or_else( + || { + #[allow(clippy::get_first)] // TODO: `rustc` 1.80.1 clippy issue + let signer_info = tx.auth_info.signer_infos.get(0).ok_or_else(|| { + HyperlaneCosmosError::SignerInfoError( + "no signer info in default signer".to_owned(), + ) + })?; + self.convert_signer_info_into_account_id_and_nonce(signer_info) + }, + |p| p, + ) + .map(|(a, n)| CosmosAddress::from_account_id(a).map(|a| (a.digest(), n)))??; + Ok((sender, nonce)) + } + + /// Reports if transaction contains fees expressed in unsupported denominations + /// The only denomination we support at the moment is the one we express gas minimum price + /// in the configuration of a chain. If fees contain an entry in a different denomination, + /// we report it in the logs. + fn report_unsupported_denominations(&self, tx: &Tx, tx_hash: &H256) -> ChainResult<()> { + let supported_denomination = self.conf.get_minimum_gas_price().denom; + let unsupported_denominations = tx + .auth_info + .fee + .amount + .iter() + .filter(|c| c.denom.as_ref() != supported_denomination) + .map(|c| c.denom.as_ref()) + .fold("".to_string(), |acc, denom| acc + ", " + denom); + + if !unsupported_denominations.is_empty() { + let msg = "transaction contains fees in unsupported denominations, manual intervention is required"; + warn!( + ?tx_hash, + ?supported_denomination, + ?unsupported_denominations, + msg, + ); + Err(ChainCommunicationError::CustomError(msg.to_owned()))? + } + + Ok(()) + } + + /// Converts fees to a common denomination if necessary. + /// + /// If fees are expressed in an unsupported denomination, they will be ignored. + fn convert_fee(&self, coin: &Coin) -> ChainResult { + let native_token = self.conf.get_native_token(); + + if coin.denom.as_ref() != native_token.denom { + return Ok(U256::zero()); + } + + let amount_in_native_denom = U256::from(coin.amount); + + to_atto(amount_in_native_denom, native_token.decimals).ok_or( + ChainCommunicationError::CustomError("Overflow in calculating fees".to_owned()), + ) + } + + fn calculate_gas_price(&self, hash: &H256, tx: &Tx) -> ChainResult { + let supported = self.report_unsupported_denominations(tx, hash); + if supported.is_err() { + return Ok(U256::max_value()); + } + + let gas_limit = U256::from(tx.auth_info.fee.gas_limit); + let fee = tx + .auth_info + .fee + .amount + .iter() + .map(|c| self.convert_fee(c)) + .fold_ok(U256::zero(), |acc, v| acc.saturating_add(v))?; + + if fee < gas_limit { + warn!(tx_hash = ?hash, ?fee, ?gas_limit, "calculated fee is less than gas limit. it will result in zero gas price"); + } + + Ok(fee.checked_div(gas_limit).unwrap_or_default()) + } +} + +impl HyperlaneChain for CosmosProvider { + /// Return the domain + fn domain(&self) -> &HyperlaneDomain { + &self.domain + } + + /// A provider for the chain + fn provider(&self) -> Box { + Box::new(self.clone()) + } +} + +#[async_trait] +impl HyperlaneProvider for CosmosProvider { + async fn get_block_by_height(&self, height: u64) -> ChainResult { + let response = self.rpc.get_block(height as u32).await?; + let block = response.block; + let block_height = block.header.height.value(); + + if block_height != height { + Err(HyperlaneProviderError::IncorrectBlockByHeight( + height, + block_height, + ))? + } + + let hash = H256::from_slice(response.block_id.hash.as_bytes()); + let time: OffsetDateTime = block.header.time.into(); + + let block_info = BlockInfo { + hash: hash.to_owned(), + timestamp: time.unix_timestamp() as u64, + number: block_height, + }; + + Ok(block_info) + } + + async fn get_txn_by_hash(&self, hash: &H512) -> ChainResult { + if hash.is_zero() { + return Err(HyperlaneProviderError::CouldNotFindTransactionByHash(*hash).into()); + } + let response = self.rpc.get_tx(hash).await?; + let tx = Tx::from_bytes(&response.tx)?; + + let contract = self.query.parse_tx_message_recipient(&tx, hash)?; + let (sender, nonce) = self.sender_and_nonce(&tx)?; + + let hash: H256 = H256::from_slice(&h512_to_bytes(hash)); + let gas_price = self.calculate_gas_price(&hash, &tx)?; + + let tx_info = TxnInfo { + hash: hash.into(), + gas_limit: U256::from(response.tx_result.gas_wanted), + max_priority_fee_per_gas: None, + max_fee_per_gas: None, + gas_price: Some(gas_price), + nonce, + sender, + recipient: Some(contract), + receipt: Some(TxnReceiptInfo { + gas_used: response.tx_result.gas_used.into(), + cumulative_gas_used: response.tx_result.gas_used.into(), + effective_gas_price: Some(gas_price), + }), + raw_input_data: None, + }; + + Ok(tx_info) + } + + async fn is_contract(&self, address: &H256) -> ChainResult { + self.query.is_contract(address).await + } + + async fn get_balance(&self, address: String) -> ChainResult { + self.rpc.get_balance(address).await + } + + async fn get_chain_metrics(&self) -> ChainResult> { + let height = self.rpc().get_block_number().await?; + let response = self.rpc().get_block(height as u32).await?; + let hash = response.block.header.hash(); + let block_info = BlockInfo { + hash: H256::from_slice(hash.as_bytes()), + timestamp: response.block.header.time.unix_timestamp() as u64, + number: height, + }; + Ok(Some(ChainInfo { + latest_block: block_info, + min_gas_price: None, + })) + } +} diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs b/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs deleted file mode 100644 index ade5eb402df..00000000000 --- a/rust/main/chains/hyperlane-cosmos/src/providers/cosmos/provider.rs +++ /dev/null @@ -1,488 +0,0 @@ -use std::str::FromStr; - -use async_trait::async_trait; -use cosmrs::cosmwasm::MsgExecuteContract; -use cosmrs::crypto::PublicKey; -use cosmrs::proto::traits::Message; -use cosmrs::tx::{MessageExt, SequenceNumber, SignerInfo, SignerPublicKey}; -use cosmrs::{proto, AccountId, Any, Coin, Tx}; - -use itertools::{any, cloned, Itertools}; -use once_cell::sync::Lazy; -use serde::{Deserialize, Serialize}; -use tendermint::hash::Algorithm; -use tendermint::Hash; -use tendermint_rpc::{client::CompatMode, Client, HttpClient}; -use time::OffsetDateTime; -use tracing::{error, warn}; - -use crypto::decompress_public_key; - -use hyperlane_core::rpc_clients::FallbackProvider; -use hyperlane_core::{ - bytes_to_h512, h512_to_bytes, utils::to_atto, AccountAddressType, BlockInfo, - ChainCommunicationError, ChainInfo, ChainResult, ContractLocator, HyperlaneChain, - HyperlaneDomain, HyperlaneProvider, HyperlaneProviderError, TxnInfo, TxnReceiptInfo, H256, - H512, U256, -}; -use hyperlane_metric::prometheus_metric::{ - ClientConnectionType, NodeInfo, PrometheusClientMetrics, PrometheusConfig, -}; -use hyperlane_metric::utils::url_to_host_info; - -use crate::grpc::{WasmGrpcProvider, WasmProvider}; -use crate::providers::cosmos::provider::parse::PacketData; -use crate::providers::rpc::CosmosRpcClient; -use crate::rpc_clients::CosmosFallbackProvider; -use crate::{ - ConnectionConf, CosmosAccountId, CosmosAddress, CosmosAmount, HyperlaneCosmosError, Signer, -}; - -mod parse; - -/// Injective public key type URL for protobuf Any -const INJECTIVE_PUBLIC_KEY_TYPE_URL: &str = "/injective.crypto.v1beta1.ethsecp256k1.PubKey"; - -/// Abstraction over a connection to a Cosmos chain -#[derive(Debug, Clone)] -pub struct CosmosProvider { - domain: HyperlaneDomain, - connection_conf: ConnectionConf, - grpc_provider: WasmGrpcProvider, - rpc_client: CosmosFallbackProvider, -} - -impl CosmosProvider { - /// Create a reference to a Cosmos chain - pub fn new( - domain: HyperlaneDomain, - conf: ConnectionConf, - locator: &ContractLocator, - signer: Option, - metrics: PrometheusClientMetrics, - chain: Option, - ) -> ChainResult { - let gas_price = CosmosAmount::try_from(conf.get_minimum_gas_price().clone())?; - let grpc_provider = WasmGrpcProvider::new( - domain.clone(), - conf.clone(), - gas_price.clone(), - locator, - signer, - metrics.clone(), - chain.clone(), - )?; - - let providers = conf - .get_rpc_urls() - .iter() - .map(|url| { - let metrics_config = - PrometheusConfig::from_url(url, ClientConnectionType::Rpc, chain.clone()); - CosmosRpcClient::from_url(url, metrics.clone(), metrics_config) - }) - .collect::, _>>()?; - let provider = CosmosFallbackProvider::new( - FallbackProvider::builder().add_providers(providers).build(), - ); - - Ok(Self { - domain, - connection_conf: conf, - grpc_provider, - rpc_client: provider, - }) - } - - /// Get a grpc client - pub fn grpc(&self) -> &WasmGrpcProvider { - &self.grpc_provider - } - - fn search_payer_in_signer_infos( - &self, - signer_infos: &[SignerInfo], - payer: &AccountId, - ) -> ChainResult<(AccountId, SequenceNumber)> { - signer_infos - .iter() - .map(|si| self.convert_signer_info_into_account_id_and_nonce(si)) - // After the following we have a single Ok entry and, possibly, many Err entries - .filter_ok(|(a, s)| payer == a) - // If we have Ok entry, use it since it is the payer, if not, use the first entry with error - .find_or_first(|r| match r { - Ok((a, s)) => payer == a, - Err(e) => false, - }) - // If there were not any signer info with non-empty public key or no signers for the transaction, - // we get None here - .unwrap_or_else(|| Err(ChainCommunicationError::from_other_str("no signer info"))) - } - - fn convert_signer_info_into_account_id_and_nonce( - &self, - signer_info: &SignerInfo, - ) -> ChainResult<(AccountId, SequenceNumber)> { - let signer_public_key = signer_info.public_key.clone().ok_or_else(|| { - HyperlaneCosmosError::PublicKeyError("no public key for default signer".to_owned()) - })?; - - let (key, account_address_type) = self.normalize_public_key(signer_public_key)?; - let public_key = PublicKey::try_from(key)?; - - let account_id = CosmosAccountId::account_id_from_pubkey( - public_key, - &self.connection_conf.get_bech32_prefix(), - &account_address_type, - )?; - - Ok((account_id, signer_info.sequence)) - } - - fn normalize_public_key( - &self, - signer_public_key: SignerPublicKey, - ) -> ChainResult<(SignerPublicKey, AccountAddressType)> { - let public_key_and_account_address_type = match signer_public_key { - SignerPublicKey::Single(pk) => (SignerPublicKey::from(pk), AccountAddressType::Bitcoin), - SignerPublicKey::LegacyAminoMultisig(pk) => { - (SignerPublicKey::from(pk), AccountAddressType::Bitcoin) - } - SignerPublicKey::Any(pk) => { - if pk.type_url != PublicKey::ED25519_TYPE_URL - && pk.type_url != PublicKey::SECP256K1_TYPE_URL - && pk.type_url != INJECTIVE_PUBLIC_KEY_TYPE_URL - { - let msg = format!( - "can only normalize public keys with a known TYPE_URL: {}, {}, {}", - PublicKey::ED25519_TYPE_URL, - PublicKey::SECP256K1_TYPE_URL, - INJECTIVE_PUBLIC_KEY_TYPE_URL - ); - warn!(pk.type_url, msg); - Err(HyperlaneCosmosError::PublicKeyError(msg.to_owned()))? - } - - let (pub_key, account_address_type) = - if pk.type_url == INJECTIVE_PUBLIC_KEY_TYPE_URL { - let any = Any { - type_url: PublicKey::SECP256K1_TYPE_URL.to_owned(), - value: pk.value, - }; - - let proto: proto::cosmos::crypto::secp256k1::PubKey = - any.to_msg().map_err(Into::::into)?; - - let decompressed = decompress_public_key(&proto.key) - .map_err(|e| HyperlaneCosmosError::PublicKeyError(e.to_string()))?; - - let tendermint = tendermint::PublicKey::from_raw_secp256k1(&decompressed) - .ok_or_else(|| { - HyperlaneCosmosError::PublicKeyError( - "cannot create tendermint public key".to_owned(), - ) - })?; - - (PublicKey::from(tendermint), AccountAddressType::Ethereum) - } else { - (PublicKey::try_from(pk)?, AccountAddressType::Bitcoin) - }; - - (SignerPublicKey::Single(pub_key), account_address_type) - } - }; - - Ok(public_key_and_account_address_type) - } - - /// Calculates the sender and the nonce for the transaction. - /// We use `payer` of the fees as the sender of the transaction, and we search for `payer` - /// signature information to find the nonce. - /// If `payer` is not specified, we use the account which signed the transaction first, as - /// the sender. - fn sender_and_nonce(&self, tx: &Tx) -> ChainResult<(H256, SequenceNumber)> { - let (sender, nonce) = tx - .auth_info - .fee - .payer - .as_ref() - .map(|payer| self.search_payer_in_signer_infos(&tx.auth_info.signer_infos, payer)) - .map_or_else( - || { - #[allow(clippy::get_first)] // TODO: `rustc` 1.80.1 clippy issue - let signer_info = tx.auth_info.signer_infos.get(0).ok_or_else(|| { - HyperlaneCosmosError::SignerInfoError( - "no signer info in default signer".to_owned(), - ) - })?; - self.convert_signer_info_into_account_id_and_nonce(signer_info) - }, - |p| p, - ) - .map(|(a, n)| CosmosAddress::from_account_id(a).map(|a| (a.digest(), n)))??; - Ok((sender, nonce)) - } - - /// Extract contract address from transaction. - fn contract(tx: &Tx, tx_hash: &H256) -> ChainResult { - // We merge two error messages together so that both of them are reported - match Self::contract_address_from_msg_execute_contract(tx) { - Ok(contract) => Ok(contract), - Err(msg_execute_contract_error) => { - match Self::contract_address_from_msg_recv_packet(tx) { - Ok(contract) => Ok(contract), - Err(msg_recv_packet_error) => { - let errors = vec![msg_execute_contract_error, msg_recv_packet_error]; - let error = HyperlaneCosmosError::ParsingAttemptsFailed(errors); - warn!(?tx_hash, ?error); - Err(ChainCommunicationError::from_other(error))? - } - } - } - } - } - - /// Assumes that there is only one `MsgExecuteContract` message in the transaction - fn contract_address_from_msg_execute_contract(tx: &Tx) -> Result { - use cosmrs::proto::cosmwasm::wasm::v1::MsgExecuteContract as ProtoMsgExecuteContract; - - let contract_execution_messages = tx - .body - .messages - .iter() - .filter(|a| a.type_url == "/cosmwasm.wasm.v1.MsgExecuteContract") - .cloned() - .collect::>(); - - let contract_execution_messages_len = contract_execution_messages.len(); - if contract_execution_messages_len > 1 { - let msg = "transaction contains multiple contract execution messages"; - Err(HyperlaneCosmosError::ParsingFailed(msg.to_owned()))? - } - - let any = contract_execution_messages.first().ok_or_else(|| { - let msg = "could not find contract execution message"; - HyperlaneCosmosError::ParsingFailed(msg.to_owned()) - })?; - let proto: proto::cosmwasm::wasm::v1::MsgExecuteContract = - any.to_msg().map_err(Into::::into)?; - let msg = MsgExecuteContract::try_from(proto).map_err(Box::new)?; - let contract = H256::try_from(CosmosAccountId::new(&msg.contract))?; - - Ok(contract) - } - - fn contract_address_from_msg_recv_packet(tx: &Tx) -> Result { - let packet_data = tx - .body - .messages - .iter() - .filter(|a| a.type_url == "/ibc.core.channel.v1.MsgRecvPacket") - .map(PacketData::try_from) - .flat_map(|r| r.ok()) - .next() - .ok_or_else(|| { - let msg = "could not find IBC receive packets message containing receiver address"; - HyperlaneCosmosError::ParsingFailed(msg.to_owned()) - })?; - - let account_id = AccountId::from_str(&packet_data.receiver).map_err(Box::new)?; - let address = H256::try_from(CosmosAccountId::new(&account_id))?; - - Ok(address) - } - - /// Reports if transaction contains fees expressed in unsupported denominations - /// The only denomination we support at the moment is the one we express gas minimum price - /// in the configuration of a chain. If fees contain an entry in a different denomination, - /// we report it in the logs. - fn report_unsupported_denominations(&self, tx: &Tx, tx_hash: &H256) -> ChainResult<()> { - let supported_denomination = self.connection_conf.get_minimum_gas_price().denom; - let unsupported_denominations = tx - .auth_info - .fee - .amount - .iter() - .filter(|c| c.denom.as_ref() != supported_denomination) - .map(|c| c.denom.as_ref()) - .fold("".to_string(), |acc, denom| acc + ", " + denom); - - if !unsupported_denominations.is_empty() { - let msg = "transaction contains fees in unsupported denominations, manual intervention is required"; - warn!( - ?tx_hash, - ?supported_denomination, - ?unsupported_denominations, - msg, - ); - Err(ChainCommunicationError::CustomError(msg.to_owned()))? - } - - Ok(()) - } - - /// Converts fees to a common denomination if necessary. - /// - /// Currently, we support Injective, Neutron and Osmosis. Fees in Injective are usually - /// expressed in `inj` which is 10^-18 of `INJ`, while fees in Neutron and Osmosis are - /// usually expressed in `untrn` and `uosmo`, respectively, which are 10^-6 of corresponding - /// `NTRN` and `OSMO`. - /// - /// This function will convert fees expressed in `untrn` and `uosmo` to 10^-18 of `NTRN` and - /// `OSMO` and it will keep fees expressed in `inj` as is. - /// - /// If fees are expressed in an unsupported denomination, they will be ignored. - fn convert_fee(&self, coin: &Coin) -> ChainResult { - let native_token = self.connection_conf.get_native_token(); - - if coin.denom.as_ref() != native_token.denom { - return Ok(U256::zero()); - } - - let amount_in_native_denom = U256::from(coin.amount); - - to_atto(amount_in_native_denom, native_token.decimals).ok_or( - ChainCommunicationError::CustomError("Overflow in calculating fees".to_owned()), - ) - } - - fn calculate_gas_price(&self, hash: &H256, tx: &Tx) -> ChainResult { - // TODO support multiple denominations for amount - let supported = self.report_unsupported_denominations(tx, hash); - if supported.is_err() { - return Ok(U256::max_value()); - } - - let gas_limit = U256::from(tx.auth_info.fee.gas_limit); - let fee = tx - .auth_info - .fee - .amount - .iter() - .map(|c| self.convert_fee(c)) - .fold_ok(U256::zero(), |acc, v| acc + v)?; - - if fee < gas_limit { - warn!(tx_hash = ?hash, ?fee, ?gas_limit, "calculated fee is less than gas limit. it will result in zero gas price"); - } - - Ok(fee / gas_limit) - } - - async fn block_info_by_height(&self, height: u64) -> ChainResult { - let response = self - .rpc_client - .call(|provider| Box::pin(async move { provider.get_block(height as u32).await })) - .await?; - - let block = response.block; - let block_height = block.header.height.value(); - - if block_height != height { - Err(HyperlaneProviderError::IncorrectBlockByHeight( - height, - block_height, - ))? - } - - let hash = H256::from_slice(response.block_id.hash.as_bytes()); - let time: OffsetDateTime = block.header.time.into(); - - let block_info = BlockInfo { - hash: hash.to_owned(), - timestamp: time.unix_timestamp() as u64, - number: block_height, - }; - Ok(block_info) - } -} - -impl HyperlaneChain for CosmosProvider { - fn domain(&self) -> &HyperlaneDomain { - &self.domain - } - - fn provider(&self) -> Box { - Box::new(self.clone()) - } -} - -#[async_trait] -impl HyperlaneProvider for CosmosProvider { - async fn get_block_by_height(&self, height: u64) -> ChainResult { - let block_info = self.block_info_by_height(height).await?; - Ok(block_info) - } - - async fn get_txn_by_hash(&self, hash: &H512) -> ChainResult { - let hash: H256 = H256::from_slice(&h512_to_bytes(hash)); - - let tendermint_hash = Hash::from_bytes(Algorithm::Sha256, hash.as_bytes()) - .expect("transaction hash should be of correct size"); - - let response = self - .rpc_client - .call(|provider| { - Box::pin(async move { provider.get_tx_by_hash(tendermint_hash).await }) - }) - .await?; - - let received_hash = H256::from_slice(response.hash.as_bytes()); - - if received_hash != hash { - return Err(ChainCommunicationError::from_other_str(&format!( - "received incorrect transaction, expected hash: {:?}, received hash: {:?}", - hash, received_hash, - ))); - } - - let tx = Tx::from_bytes(&response.tx)?; - - let contract = Self::contract(&tx, &hash)?; - let (sender, nonce) = self.sender_and_nonce(&tx)?; - let gas_price = self.calculate_gas_price(&hash, &tx)?; - - let tx_info = TxnInfo { - hash: hash.into(), - gas_limit: U256::from(response.tx_result.gas_wanted), - max_priority_fee_per_gas: None, - max_fee_per_gas: None, - gas_price: Some(gas_price), - nonce, - sender, - recipient: Some(contract), - receipt: Some(TxnReceiptInfo { - gas_used: U256::from(response.tx_result.gas_used), - cumulative_gas_used: U256::from(response.tx_result.gas_used), - effective_gas_price: Some(gas_price), - }), - raw_input_data: None, - }; - - Ok(tx_info) - } - - async fn is_contract(&self, address: &H256) -> ChainResult { - match self.grpc_provider.wasm_contract_info().await { - Ok(c) => Ok(true), - Err(e) => Ok(false), - } - } - - async fn get_balance(&self, address: String) -> ChainResult { - Ok(self - .grpc_provider - .get_balance(address, self.connection_conf.get_canonical_asset()) - .await?) - } - - async fn get_chain_metrics(&self) -> ChainResult> { - let height = self.grpc_provider.latest_block_height().await?; - let latest_block = self.block_info_by_height(height).await?; - let chain_info = ChainInfo { - latest_block, - min_gas_price: None, - }; - Ok(Some(chain_info)) - } -} diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/grpc.rs b/rust/main/chains/hyperlane-cosmos/src/providers/grpc.rs index a585cc3c2e1..63a5296b19b 100644 --- a/rust/main/chains/hyperlane-cosmos/src/providers/grpc.rs +++ b/rust/main/chains/hyperlane-cosmos/src/providers/grpc.rs @@ -1,247 +1,50 @@ -use std::fmt::Debug; -use std::future::Future; -use std::time::{Duration, Instant}; +use std::ops::Deref; +use std::time::Duration; -use async_trait::async_trait; -use cosmrs::{ - proto::{ - cosmos::{ - auth::v1beta1::{ - query_client::QueryClient as QueryAccountClient, BaseAccount, QueryAccountRequest, - }, - bank::v1beta1::{query_client::QueryClient as QueryBalanceClient, QueryBalanceRequest}, - base::{ - abci::v1beta1::TxResponse, - tendermint::v1beta1::{service_client::ServiceClient, GetLatestBlockRequest}, - }, - tx::v1beta1::{ - service_client::ServiceClient as TxServiceClient, BroadcastMode, - BroadcastTxRequest, SimulateRequest, TxRaw, - }, - }, - cosmwasm::wasm::v1::{ - query_client::QueryClient as WasmQueryClient, ContractInfo, MsgExecuteContract, - QueryContractInfoRequest, QuerySmartContractStateRequest, - }, - traits::Message, - }, - tx::{self, Fee, MessageExt, SignDoc, SignerInfo}, - Any, Coin, -}; use derive_new::new; +use tonic::async_trait; +use tonic::transport::{Channel, Endpoint}; + +use hyperlane_core::rpc_clients::{BlockNumberGetter, FallbackProvider}; +use hyperlane_core::{ChainCommunicationError, ChainResult, ReorgPeriod}; +use hyperlane_cosmos_rs::dymensionxyz::dymension::kas::{ + query_client::QueryClient as KasQueryClient, QueryOutpointRequest, QueryOutpointResponse, + QueryWithdrawalStatusRequest, QueryWithdrawalStatusResponse, WithdrawalId, +}; use hyperlane_metric::prometheus_metric::{ ChainInfo, ClientConnectionType, PrometheusClientMetrics, PrometheusConfig, }; -use ibc_proto::cosmos::{ - auth::v1beta1::QueryAccountResponse, bank::v1beta1::QueryBalanceResponse, - base::tendermint::v1beta1::GetLatestBlockResponse, -}; -use protobuf::Message as _; -use serde::Serialize; -use tonic::{ - transport::{Channel, Endpoint}, - GrpcMethod, IntoRequest, -}; -use tracing::{debug, instrument}; + +use cosmrs::proto::cosmos::base::tendermint::v1beta1::service_client::ServiceClient; +use cosmrs::proto::cosmos::base::tendermint::v1beta1::GetLatestBlockRequest; use url::Url; -use hyperlane_core::{ - rpc_clients::{BlockNumberGetter, FallbackProvider}, - ChainCommunicationError, ChainResult, ContractLocator, FixedPointNumber, HyperlaneDomain, U256, -}; +use crate::{ConnectionConf, HyperlaneCosmosError, MetricsChannel}; -use crate::{ - prometheus::metrics_channel::MetricsChannel, rpc_clients::CosmosFallbackProvider, - HyperlaneCosmosError, -}; -use crate::{signers::Signer, ConnectionConf}; -use crate::{CosmosAddress, CosmosAmount}; +/// Grpc Provider +#[derive(Clone, Debug)] +pub struct GrpcProvider { + fallback: FallbackProvider, +} -/// A multiplier applied to a simulated transaction's gas usage to -/// calculate the estimated gas. -const GAS_ESTIMATE_MULTIPLIER: f64 = 1.25; -/// The number of blocks in the future in which a transaction will -/// be valid for. -const TIMEOUT_BLOCKS: u64 = 1000; /// gRPC request timeout -const REQUEST_TIMEOUT: u64 = 30; +const REQUEST_TIMEOUT: Duration = Duration::from_secs(30); +/// GrpcChannel is a wrapper used by the FallbackProvider +/// implements BlockNumberGetter #[derive(Debug, Clone, new)] -struct CosmosChannel { +pub struct GrpcChannel { channel: MetricsChannel, /// The url that this channel is connected to. /// Not explicitly used, but useful for debugging. _url: Url, } -impl CosmosChannel { - async fn latest_block_height(&self) -> ChainResult { - let mut client = ServiceClient::new(self.channel.clone()); - let mut request = tonic::Request::new(GetLatestBlockRequest {}); - request.set_timeout(Duration::from_secs(REQUEST_TIMEOUT)); - let response = client - .get_latest_block(request) - .await - .map_err(ChainCommunicationError::from_other)? - .into_inner(); - Ok(response) - } - - async fn estimate_gas(&self, payload: Vec) -> ChainResult { - let tx_bytes = payload.to_vec(); - let mut client = TxServiceClient::new(self.channel.clone()); - #[allow(deprecated)] - let mut sim_req = tonic::Request::new(SimulateRequest { tx: None, tx_bytes }); - sim_req.set_timeout(Duration::from_secs(REQUEST_TIMEOUT)); - let response = client - .simulate(sim_req) - .await - .map_err(ChainCommunicationError::from_other)? - .into_inner() - .gas_info - .ok_or_else(|| ChainCommunicationError::from_other_str("gas info not present")); - Ok(response?.gas_used) - } - - async fn account_query(&self, account: String) -> ChainResult { - let address = account.clone(); - let mut client = QueryAccountClient::new(self.channel.clone()); - let mut request = tonic::Request::new(QueryAccountRequest { address }); - request.set_timeout(Duration::from_secs(REQUEST_TIMEOUT)); - let response = client - .account(request) - .await - .map_err(ChainCommunicationError::from_other)? - .into_inner(); - Ok(response) - } - - async fn account_query_injective( - &self, - account: String, - ) -> ChainResult { - let address = account.clone(); - let mut request = tonic::Request::new( - injective_std::types::cosmos::auth::v1beta1::QueryAccountRequest { address }, - ); - request.set_timeout(Duration::from_secs(REQUEST_TIMEOUT)); - - // Borrowed from the logic of `QueryAccountClient` in `cosmrs`, but using injective types. - let mut grpc_client = tonic::client::Grpc::new(self.channel.clone()); - grpc_client - .ready() - .await - .map_err(Into::::into)?; - - let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/cosmos.auth.v1beta1.Query/Account"); - let mut req: tonic::Request< - injective_std::types::cosmos::auth::v1beta1::QueryAccountRequest, - > = request.into_request(); - req.extensions_mut() - .insert(GrpcMethod::new("cosmos.auth.v1beta1.Query", "Account")); - - let response: tonic::Response< - injective_std::types::cosmos::auth::v1beta1::QueryAccountResponse, - > = grpc_client - .unary(req, path, codec) - .await - .map_err(Box::new) - .map_err(Into::::into)?; - Ok(response.into_inner()) - } - - async fn get_balance( - &self, - address: String, - denom: String, - ) -> ChainResult { - let address = address.clone(); - let denom = denom.clone(); - - let mut client = QueryBalanceClient::new(self.channel.clone()); - let mut balance_request = tonic::Request::new(QueryBalanceRequest { address, denom }); - balance_request.set_timeout(Duration::from_secs(REQUEST_TIMEOUT)); - let response = client - .balance(balance_request) - .await - .map_err(ChainCommunicationError::from_other)? - .into_inner(); - Ok(response) - } - - async fn wasm_query( - &self, - contract_address: String, - payload: Vec, - block_height: Option, - ) -> ChainResult> { - let to = contract_address.clone(); - let mut client = WasmQueryClient::new(self.channel.clone()); - let mut request = tonic::Request::new(QuerySmartContractStateRequest { - address: to, - query_data: payload.clone(), - }); - request.set_timeout(Duration::from_secs(REQUEST_TIMEOUT)); - if let Some(block_height) = block_height { - request - .metadata_mut() - .insert("x-cosmos-block-height", block_height.into()); - } - let response = client - .smart_contract_state(request) - .await - .map_err(ChainCommunicationError::from_other); - Ok(response?.into_inner().data) - } - - async fn wasm_contract_info(&self, contract_address: String) -> ChainResult { - let to = contract_address.clone(); - let mut client = WasmQueryClient::new(self.channel.clone()); - - let mut request = tonic::Request::new(QueryContractInfoRequest { address: to }); - request.set_timeout(Duration::from_secs(REQUEST_TIMEOUT)); - - let response = client - .contract_info(request) - .await - .map_err(ChainCommunicationError::from_other)? - .into_inner() - .contract_info - .ok_or(ChainCommunicationError::from_other_str( - "empty contract info", - ))?; - Ok(response) - } - - async fn wasm_send(&self, payload: Vec) -> ChainResult { - let tx_bytes = payload.to_vec(); - let mut client = TxServiceClient::new(self.channel.clone()); - // We often use U256s to represent gas limits, but Cosmos expects u64s. Try to convert, - // and if it fails, just fallback to None which will result in gas estimation. - let tx_req = BroadcastTxRequest { - tx_bytes, - mode: BroadcastMode::Sync as i32, - }; - let response = client - .broadcast_tx(tx_req) - .await - .map_err(Box::new) - .map_err(Into::::into)? - .into_inner() - .tx_response - .ok_or_else(|| ChainCommunicationError::from_other_str("Empty tx_response"))?; - Ok(response) - } -} - #[async_trait] -impl BlockNumberGetter for CosmosChannel { - async fn get_block_number(&self) -> ChainResult { +impl BlockNumberGetter for GrpcChannel { + async fn get_block_number(&self) -> Result { let mut client = ServiceClient::new(self.channel.clone()); - let mut request = tonic::Request::new(GetLatestBlockRequest {}); - request.set_timeout(Duration::from_secs(REQUEST_TIMEOUT)); - + let request = tonic::Request::new(GetLatestBlockRequest {}); let response = client .get_latest_block(request) .await @@ -258,450 +61,52 @@ impl BlockNumberGetter for CosmosChannel { } } -#[async_trait] -/// Cosmwasm GRPC Provider -pub trait WasmProvider: Send + Sync { - /// Get latest block height. - /// Note that in Tendermint, validators come to consensus on a block - /// before they execute the transactions in that block. This means that - /// we may not be able to make state queries against this block until - /// the next one is committed! - async fn latest_block_height(&self) -> ChainResult; - - /// Perform a wasm query against the stored contract address. - async fn wasm_query( - &self, - payload: T, - block_height: Option, - ) -> ChainResult>; - - /// Request contract info from the stored contract address. - async fn wasm_contract_info(&self) -> ChainResult; - - /// Send a wasm tx. - async fn wasm_send( - &self, - payload: T, - gas_limit: Option, - ) -> ChainResult; - - /// Estimate gas for a wasm tx. - async fn wasm_estimate_gas( - &self, - payload: T, - ) -> ChainResult; -} - -#[derive(Debug, Clone)] -/// CosmWasm GRPC provider. -pub struct WasmGrpcProvider { - /// Hyperlane domain, used for special cases depending on the chain. - domain: HyperlaneDomain, - /// Connection configuration. - conf: ConnectionConf, - /// A contract address that can be used as the default - /// for queries / sends / estimates. - contract_address: CosmosAddress, - /// Signer for transactions. - signer: Option, - /// GRPC Channel that can be cheaply cloned. - /// See `` - provider: CosmosFallbackProvider, - gas_price: CosmosAmount, +impl GrpcChannel { + /// Get the channel + pub fn channel(&self) -> MetricsChannel { + self.channel.clone() + } } -impl WasmGrpcProvider { - /// Create new CosmWasm GRPC Provider. +impl GrpcProvider { + /// New GrpcProvider pub fn new( - domain: HyperlaneDomain, - conf: ConnectionConf, - gas_price: CosmosAmount, - locator: &ContractLocator, - signer: Option, + conf: &ConnectionConf, metrics: PrometheusClientMetrics, chain: Option, ) -> ChainResult { - // get all the configured grpc urls and convert them to a Vec - let channels: Result, _> = conf + let clients = conf .get_grpc_urls() .into_iter() .map(|url| { let metrics_config = PrometheusConfig::from_url(&url, ClientConnectionType::Grpc, chain.clone()); Endpoint::new(url.to_string()) - .map(|e| e.timeout(Duration::from_secs(REQUEST_TIMEOUT))) - .map(|e| e.connect_timeout(Duration::from_secs(REQUEST_TIMEOUT))) + .map(|e| e.timeout(REQUEST_TIMEOUT)) + .map(|e| e.connect_timeout(REQUEST_TIMEOUT)) .map(|e| MetricsChannel::new(e.connect_lazy(), metrics.clone(), metrics_config)) - .map(|m| CosmosChannel::new(m, url)) + .map(|m| GrpcChannel::new(m, url)) .map_err(Into::::into) }) - .collect(); - let mut builder = FallbackProvider::builder(); - builder = builder.add_providers(channels?); - let fallback_provider = builder.build(); - let provider = CosmosFallbackProvider::new(fallback_provider); - - let contract_address = CosmosAddress::from_h256( - locator.address, - &conf.get_bech32_prefix(), - conf.get_contract_address_bytes(), - )?; - - Ok(Self { - domain, - conf, - contract_address, - signer, - provider, - gas_price, - }) - } - - /// Gets a signer, or returns an error if one is not available. - fn get_signer(&self) -> ChainResult<&Signer> { - self.signer - .as_ref() - .ok_or(ChainCommunicationError::SignerUnavailable) - } - - /// Get the gas price - pub fn gas_price(&self) -> FixedPointNumber { - self.gas_price.amount.clone() - } - - /// Generates an unsigned SignDoc for a transaction and the Coin amount - /// required to pay for tx fees. - async fn generate_unsigned_sign_doc_and_fee( - &self, - msgs: Vec, - gas_limit: u64, - ) -> ChainResult<(SignDoc, Coin)> { - // As this function is only used for estimating gas or sending transactions, - // we can reasonably expect to have a signer. - let signer = self.get_signer()?; - let account_info = self.account_query(signer.address_string.clone()).await?; - let current_height = self.latest_block_height().await?; - let timeout_height = current_height + TIMEOUT_BLOCKS; - - let tx_body = tx::Body::new( - msgs, - String::default(), - TryInto::::try_into(timeout_height) - .map_err(ChainCommunicationError::from_other)?, - ); - let signer_info = SignerInfo::single_direct(Some(signer.public_key), account_info.sequence); - - let amount: u128 = (FixedPointNumber::from(gas_limit) * self.gas_price()) - .ceil_to_integer() - .try_into()?; - let fee_coin = Coin::new( - // The fee to pay is the gas limit * the gas price - amount, - self.conf.get_canonical_asset().as_str(), - ) - .map_err(Box::new) - .map_err(Into::::into)?; - let auth_info = - signer_info.auth_info(Fee::from_amount_and_gas(fee_coin.clone(), gas_limit)); - - let chain_id = self - .conf - .get_chain_id() - .parse() - .map_err(Box::new) - .map_err(Into::::into)?; - - Ok(( - SignDoc::new(&tx_body, &auth_info, &chain_id, account_info.account_number) - .map_err(Box::new) - .map_err(Into::::into)?, - fee_coin, - )) - } - - /// Generates a raw signed transaction including `msgs`, estimating gas if a limit is not provided, - /// and the Coin amount required to pay for tx fees. - async fn generate_raw_signed_tx_and_fee( - &self, - msgs: Vec, - gas_limit: Option, - ) -> ChainResult<(Vec, Coin)> { - let gas_limit = if let Some(l) = gas_limit { - l - } else { - self.estimate_gas(msgs.clone()).await? - }; - - let (sign_doc, fee) = self - .generate_unsigned_sign_doc_and_fee(msgs, gas_limit) - .await?; - - let signer = self.get_signer()?; - let tx_signed = sign_doc - .sign(&signer.signing_key()?) - .map_err(Box::new) - .map_err(Into::::into)?; - Ok(( - tx_signed - .to_bytes() - .map_err(Box::new) - .map_err(Into::::into)?, - fee, - )) - } - - /// Estimates gas for a transaction containing `msgs`. - async fn estimate_gas(&self, msgs: Vec) -> ChainResult { - // Get a sign doc with 0 gas, because we plan to simulate - let (sign_doc, _) = self.generate_unsigned_sign_doc_and_fee(msgs, 0).await?; - - let raw_tx = TxRaw { - body_bytes: sign_doc.body_bytes, - auth_info_bytes: sign_doc.auth_info_bytes, - // The poorly documented trick to simulating a tx without a valid signature is to just pass - // in a single empty signature. Taken from cosmjs: - // https://github.com/cosmos/cosmjs/blob/44893af824f0712d1f406a8daa9fcae335422235/packages/stargate/src/modules/tx/queries.ts#L67 - signatures: vec![vec![]], - }; - let tx_bytes = raw_tx - .to_bytes() - .map_err(ChainCommunicationError::from_other)?; - let gas_used = self - .provider - .call(move |provider| { - let tx_bytes = tx_bytes.clone(); - let future = async move { provider.estimate_gas(tx_bytes).await }; - Box::pin(future) - }) - .await?; - - let gas_estimate = (gas_used as f64 * GAS_ESTIMATE_MULTIPLIER) as u64; + .collect::, _>>()?; - Ok(gas_estimate) + let fallback = FallbackProvider::new(clients); + Ok(Self { fallback }) } - /// Fetches balance for a given `address` and `denom` - pub async fn get_balance(&self, address: String, denom: String) -> ChainResult { - let response = self - .provider - .call(move |provider| { - let address = address.clone(); - let denom = denom.clone(); - let future = async move { provider.get_balance(address, denom).await }; - Box::pin(future) - }) - .await?; - - let balance = response - .balance - .ok_or_else(|| ChainCommunicationError::from_other_str("account not present"))?; - - Ok(U256::from_dec_str(&balance.amount)?) - } - - /// Queries an account. - pub async fn account_query(&self, account: String) -> ChainResult { - // Injective is a special case where their account query requires - // the use of different protobuf types. - if self.domain.is_injective() { - return self.account_query_injective(account).await; - } - - let response = self - .provider - .call(move |provider| { - let address = account.clone(); - let future = async move { provider.account_query(address).await }; - Box::pin(future) - }) - .await?; - - let account = BaseAccount::decode( - response - .account - .ok_or_else(|| ChainCommunicationError::from_other_str("account not present"))? - .value - .as_slice(), - ) - .map_err(Into::::into)?; - Ok(account) - } - - /// Injective-specific logic for querying an account. - async fn account_query_injective(&self, account: String) -> ChainResult { - let response = self - .provider - .call(move |provider| { - let address = account.clone(); - let future = async move { provider.account_query_injective(address).await }; - Box::pin(future) - }) - .await?; - - let mut eth_account = injective_protobuf::proto::account::EthAccount::parse_from_bytes( - response - .account - .ok_or_else(|| ChainCommunicationError::from_other_str("account not present"))? - .value - .as_slice(), - ) - .map_err(Into::::into)?; - - let base_account = eth_account.take_base_account(); - let pub_key = base_account.pub_key.into_option(); - - Ok(BaseAccount { - address: base_account.address, - pub_key: pub_key.map(|pub_key| Any { - type_url: pub_key.type_url, - value: pub_key.value, - }), - account_number: base_account.account_number, - sequence: base_account.sequence, + /// Get the block number according to the reorg period + pub async fn get_block_number(&self) -> ChainResult { + self.call(|provider| { + let future = async move { provider.get_block_number().await }; + Box::pin(future) }) - } - - fn get_contract_address(&self) -> &CosmosAddress { - &self.contract_address - } -} - -#[async_trait] -impl WasmProvider for WasmGrpcProvider { - async fn latest_block_height(&self) -> ChainResult { - let response = self - .provider - .call(move |provider| { - let start = Instant::now(); - let future = async move { provider.latest_block_height().await }; - Box::pin(future) - }) - .await?; - - let height = response - .block - .ok_or_else(|| ChainCommunicationError::from_other_str("block not present"))? - .header - .ok_or_else(|| ChainCommunicationError::from_other_str("header not present"))? - .height; - - Ok(height as u64) - } - - async fn wasm_query(&self, payload: T, block_height: Option) -> ChainResult> - where - T: Serialize + Send + Sync + Clone + Debug, - { - let contract_address = self.get_contract_address(); - let query_data = serde_json::to_string(&payload)?.as_bytes().to_vec(); - let response = self - .provider - .call(move |provider| { - let to = contract_address.address().clone(); - let query_data = query_data.clone(); - let future = async move { provider.wasm_query(to, query_data, block_height).await }; - Box::pin(future) - }) - .await?; - Ok(response) - } - - async fn wasm_contract_info(&self) -> ChainResult { - let contract_address = self.get_contract_address(); - let response = self - .provider - .call(move |provider| { - let to = contract_address.address().clone(); - let future = async move { provider.wasm_contract_info(to).await }; - Box::pin(future) - }) - .await?; - - Ok(response) - } - - #[instrument(skip(self))] - async fn wasm_send(&self, payload: T, gas_limit: Option) -> ChainResult - where - T: Serialize + Send + Sync + Clone + Debug, - { - let signer = self.get_signer()?; - let contract_address = self.get_contract_address(); - let msg = MsgExecuteContract { - sender: signer.address_string.clone(), - contract: contract_address.address(), - msg: serde_json::to_string(&payload)?.as_bytes().to_vec(), - funds: vec![], - }; - let msgs = vec![Any::from_msg(&msg).map_err(ChainCommunicationError::from_other)?]; - let gas_limit: Option = gas_limit.and_then(|limit| match limit.try_into() { - Ok(limit) => Some(limit), - Err(err) => { - tracing::warn!( - ?err, - "failed to convert gas_limit to u64, falling back to estimation" - ); - None - } - }); - let (tx_bytes, fee) = self.generate_raw_signed_tx_and_fee(msgs, gas_limit).await?; - - // Check if the signer has enough funds to pay for the fee so we can get - // a more informative error. - let signer_balance = self - .get_balance(signer.address_string.clone(), fee.denom.to_string()) - .await?; - let fee_amount: U256 = fee.amount.into(); - if signer_balance < fee_amount { - return Err(ChainCommunicationError::InsufficientFunds { - required: Box::new(fee_amount), - available: Box::new(signer_balance), - }); - } - - let tx_res = self - .provider - .call(move |provider| { - let tx_bytes = tx_bytes.clone(); - let future = async move { provider.wasm_send(tx_bytes).await }; - Box::pin(future) - }) - .await?; - debug!(tx_result=?tx_res, domain=?self.domain.name(), ?payload, "Wasm transaction sent"); - Ok(tx_res) - } - - async fn wasm_estimate_gas(&self, payload: T) -> ChainResult - where - T: Serialize + Send + Sync, - { - // Estimating gas requires a signer, which we can reasonably expect to have - // since we need one to send a tx with the estimated gas anyways. - let signer = self.get_signer()?; - let contract_address = self.get_contract_address(); - let msg = MsgExecuteContract { - sender: signer.address_string.clone(), - contract: contract_address.address(), - msg: serde_json::to_string(&payload)?.as_bytes().to_vec(), - funds: vec![], - }; - - let response = self - .estimate_gas(vec![ - Any::from_msg(&msg).map_err(ChainCommunicationError::from_other)? - ]) - .await?; - - Ok(response) + .await } } -#[async_trait] -impl BlockNumberGetter for WasmGrpcProvider { - async fn get_block_number(&self) -> ChainResult { - self.latest_block_height().await +impl Deref for GrpcProvider { + type Target = FallbackProvider; + fn deref(&self) -> &Self::Target { + &self.fallback } } - -#[cfg(test)] -mod tests; diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/grpc/tests.rs b/rust/main/chains/hyperlane-cosmos/src/providers/grpc/tests.rs deleted file mode 100644 index 5c7926ee687..00000000000 --- a/rust/main/chains/hyperlane-cosmos/src/providers/grpc/tests.rs +++ /dev/null @@ -1,84 +0,0 @@ -use std::str::FromStr; - -use hyperlane_metric::prometheus_metric::PrometheusClientMetrics; -use url::Url; - -use hyperlane_core::config::OpSubmissionConfig; -use hyperlane_core::{ContractLocator, HyperlaneDomain, KnownHyperlaneDomain, NativeToken}; - -use crate::grpc::{WasmGrpcProvider, WasmProvider}; -use crate::{ConnectionConf, CosmosAddress, CosmosAmount, RawCosmosAmount}; - -#[ignore] -#[tokio::test] -async fn test_wasm_contract_info_success() { - // given - let provider = provider("neutron1sjzzd4gwkggy6hrrs8kxxatexzcuz3jecsxm3wqgregkulzj8r7qlnuef4"); - - // when - let result = provider.wasm_contract_info().await; - - // then - assert!(result.is_ok()); - - let contract_info = result.unwrap(); - - assert_eq!( - contract_info.creator, - "neutron1dwnrgwsf5c9vqjxsax04pdm0mx007yrre4yyvm", - ); - assert_eq!( - contract_info.admin, - "neutron1fqf5mprg3f5hytvzp3t7spmsum6rjrw80mq8zgkc0h6rxga0dtzqws3uu7", - ); -} - -#[ignore] -#[tokio::test] -async fn test_wasm_contract_info_no_contract() { - // given - let provider = provider("neutron1dwnrgwsf5c9vqjxsax04pdm0mx007yrre4yyvm"); - - // when - let result = provider.wasm_contract_info().await; - - // then - assert!(result.is_err()); -} - -fn provider(address: &str) -> WasmGrpcProvider { - let domain = HyperlaneDomain::Known(KnownHyperlaneDomain::Neutron); - let address = CosmosAddress::from_str(address).unwrap(); - let locator = ContractLocator::new(&domain, address.digest()); - - WasmGrpcProvider::new( - domain.clone(), - ConnectionConf::new( - vec![Url::parse("http://grpc-kralum.neutron-1.neutron.org:80").unwrap()], - vec![Url::parse("https://rpc-kralum.neutron-1.neutron.org").unwrap()], - "neutron-1".to_owned(), - "neutron".to_owned(), - "untrn".to_owned(), - RawCosmosAmount::new("untrn".to_owned(), "0".to_owned()), - 32, - OpSubmissionConfig { - batch_contract_address: None, - max_batch_size: 1, - ..Default::default() - }, - NativeToken { - decimals: 6, - denom: "untrn".to_owned(), - }, - ), - CosmosAmount { - denom: "untrn".to_owned(), - amount: Default::default(), - }, - &locator, - None, - PrometheusClientMetrics::default(), - None, - ) - .unwrap() -} diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/mod.rs b/rust/main/chains/hyperlane-cosmos/src/providers/mod.rs new file mode 100644 index 00000000000..7a814fb208f --- /dev/null +++ b/rust/main/chains/hyperlane-cosmos/src/providers/mod.rs @@ -0,0 +1,12 @@ +mod cosmos; +mod grpc; +mod prometheus; +mod rpc; + +#[cfg(test)] +mod tests; + +pub use cosmos::*; +pub use grpc::*; +pub use prometheus::*; +pub use rpc::*; diff --git a/rust/main/chains/hyperlane-cosmos-native/src/prometheus/metrics_channel.rs b/rust/main/chains/hyperlane-cosmos/src/providers/prometheus/metrics_channel.rs similarity index 96% rename from rust/main/chains/hyperlane-cosmos-native/src/prometheus/metrics_channel.rs rename to rust/main/chains/hyperlane-cosmos/src/providers/prometheus/metrics_channel.rs index 371d0dcb6df..4876a34687e 100644 --- a/rust/main/chains/hyperlane-cosmos-native/src/prometheus/metrics_channel.rs +++ b/rust/main/chains/hyperlane-cosmos/src/providers/prometheus/metrics_channel.rs @@ -1,5 +1,3 @@ -/// TODO: copy pasted from `chains/hyperlane-cosmos/src/prometheus` -/// refactore shared logic use std::future::Future; use std::task::{Context, Poll}; diff --git a/rust/main/chains/hyperlane-cosmos-native/src/prometheus/metrics_future.rs b/rust/main/chains/hyperlane-cosmos/src/providers/prometheus/metrics_future.rs similarity index 94% rename from rust/main/chains/hyperlane-cosmos-native/src/prometheus/metrics_future.rs rename to rust/main/chains/hyperlane-cosmos/src/providers/prometheus/metrics_future.rs index e2a5b57706e..0a2156306fd 100644 --- a/rust/main/chains/hyperlane-cosmos-native/src/prometheus/metrics_future.rs +++ b/rust/main/chains/hyperlane-cosmos/src/providers/prometheus/metrics_future.rs @@ -1,5 +1,3 @@ -/// TODO: copy pasted from `chains/hyperlane-cosmos/src/prometheus` -/// refactore shared logic use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; @@ -22,6 +20,8 @@ pub struct MetricsChannelFuture { } impl MetricsChannelFuture { + /// Future for the MetricChannel + /// Will increment the metrics when the future is ready pub fn new( method: String, metrics: PrometheusClientMetrics, diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/prometheus/mod.rs b/rust/main/chains/hyperlane-cosmos/src/providers/prometheus/mod.rs new file mode 100644 index 00000000000..fc7de344034 --- /dev/null +++ b/rust/main/chains/hyperlane-cosmos/src/providers/prometheus/mod.rs @@ -0,0 +1,5 @@ +mod metrics_channel; +mod metrics_future; + +pub use metrics_channel::MetricsChannel; +pub use metrics_future::MetricsChannelFuture; diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/rpc.rs b/rust/main/chains/hyperlane-cosmos/src/providers/rpc.rs index 08864b26c88..9754d32f76f 100644 --- a/rust/main/chains/hyperlane-cosmos/src/providers/rpc.rs +++ b/rust/main/chains/hyperlane-cosmos/src/providers/rpc.rs @@ -1,5 +1,537 @@ -pub use client::CosmosRpcClient; -pub use provider::{CosmosWasmRpcProvider, ParsedEvent, WasmRpcProvider}; +use std::future::Future; +use std::ops::Mul; +use std::time::Instant; -mod client; -mod provider; +use cometbft::{hash::Algorithm, Hash}; +use cometbft_rpc::{ + client::CompatMode, + endpoint::{ + block::Response as BlockResponse, block_results::Response as BlockResultsResponse, + broadcast::tx_commit, tx::Response as TxResponse, + }, + Client, Error, HttpClient, +}; +use cosmrs::{ + proto::{ + cosmos::{ + auth::v1beta1::{BaseAccount, QueryAccountRequest, QueryAccountResponse}, + bank::v1beta1::{QueryBalanceRequest, QueryBalanceResponse}, + tx::v1beta1::{SimulateRequest, SimulateResponse, TxRaw}, + }, + prost::{self, Message}, + }, + tx::{self, Fee, MessageExt, SignDoc, SignerInfo}, + Any, Coin, +}; +use protobuf::Message as _; +use tonic::async_trait; +use url::Url; + +use hyperlane_core::{ + h512_to_bytes, + rpc_clients::{BlockNumberGetter, FallbackProvider}, + ChainCommunicationError, ChainResult, FixedPointNumber, H256, H512, U256, +}; +use hyperlane_metric::prometheus_metric::{ + ClientConnectionType, PrometheusClientMetrics, PrometheusConfig, +}; + +use crate::{ConnectionConf, CosmosAmount, HyperlaneCosmosError, Signer}; + +const TX_TIMEOUT_BLOCKS: u32 = 100; + +#[derive(Debug)] +pub(crate) struct CosmosHttpClient { + client: HttpClient, + metrics: PrometheusClientMetrics, + metrics_config: PrometheusConfig, +} + +/// RPC Provider for Cosmos +/// +/// Responsible for chain communication +#[derive(Debug, Clone)] +pub struct RpcProvider { + provider: FallbackProvider, + conf: ConnectionConf, + signer: Option, + gas_price: CosmosAmount, +} + +#[async_trait] +impl BlockNumberGetter for CosmosHttpClient { + async fn get_block_number(&self) -> Result { + let block = self + .client + .latest_block() + .await + .map_err(HyperlaneCosmosError::from)?; + + Ok(block.block.header.height.value()) + } +} + +impl CosmosHttpClient { + /// Create new `CosmosHttpClient` + pub fn new( + client: HttpClient, + metrics: PrometheusClientMetrics, + metrics_config: PrometheusConfig, + ) -> Self { + // increment provider metric count + let chain_name = PrometheusConfig::chain_name(&metrics_config.chain); + metrics.increment_provider_instance(chain_name); + + Self { + client, + metrics, + metrics_config, + } + } + + /// Creates a CosmosHttpClient from a url + pub fn from_url( + url: &Url, + metrics: PrometheusClientMetrics, + metrics_config: PrometheusConfig, + compat_mode: CompatMode, + ) -> ChainResult { + let tendermint_url = cometbft_rpc::Url::try_from(url.to_owned()) + .map_err(Box::new) + .map_err(ChainCommunicationError::from_other)?; + let url = cometbft_rpc::HttpClientUrl::try_from(tendermint_url) + .map_err(Box::new) + .map_err(ChainCommunicationError::from_other)?; + + let client = HttpClient::builder(url) + .compat_mode(compat_mode) + .build() + .map_err(Box::new) + .map_err(ChainCommunicationError::from_other)?; + + Ok(Self::new(client, metrics, metrics_config)) + } +} + +impl Clone for CosmosHttpClient { + fn clone(&self) -> Self { + // increment provider metric count + let chain_name = PrometheusConfig::chain_name(&self.metrics_config.chain); + self.metrics.increment_provider_instance(chain_name); + + Self { + client: self.client.clone(), + metrics: self.metrics.clone(), + metrics_config: self.metrics_config.clone(), + } + } +} + +impl Drop for CosmosHttpClient { + fn drop(&mut self) { + // decrement provider metric count + let chain_name = PrometheusConfig::chain_name(&self.metrics_config.chain); + self.metrics.decrement_provider_instance(chain_name); + } +} + +impl RpcProvider { + /// Returns a new Rpc Provider + pub fn new( + conf: ConnectionConf, + signer: Option, + metrics: PrometheusClientMetrics, + chain: Option, + ) -> ChainResult { + let clients = conf + .get_rpc_urls() + .iter() + .map(|url| { + let metrics_config = + PrometheusConfig::from_url(url, ClientConnectionType::Rpc, chain.clone()); + CosmosHttpClient::from_url(url, metrics.clone(), metrics_config, conf.compat_mode) + }) + .collect::, _>>()?; + + let provider = FallbackProvider::new(clients); + let gas_price = CosmosAmount::try_from(conf.get_minimum_gas_price().clone())?; + + Ok(RpcProvider { + provider, + conf, + signer, + gas_price, + }) + } + + #[cfg(test)] + pub(crate) fn from_providers( + provider: Vec, + conf: ConnectionConf, + signer: Option, + gas_price: CosmosAmount, + ) -> Self { + let provider = FallbackProvider::new(provider); + RpcProvider { + provider, + conf, + signer, + gas_price, + } + } + + async fn track_metric_call( + client: &CosmosHttpClient, + method: &str, + call: F, + ) -> ChainResult + where + F: Fn() -> Fut, + Fut: Future>, + { + let start = Instant::now(); + let res = call().await; + + client + .metrics + .increment_metrics(&client.metrics_config, method, start, res.is_ok()); + + res.map_err(|e| ChainCommunicationError::from(HyperlaneCosmosError::from(e))) + } + + /// Get the transaction by hash + pub async fn get_tx(&self, hash: &H512) -> ChainResult { + let hash: H256 = H256::from_slice(&h512_to_bytes(hash)); + + let tendermint_hash = Hash::from_bytes(Algorithm::Sha256, hash.as_bytes()) + .expect("transaction hash should be of correct size"); + + let response = self + .provider + .call(|client| { + let future = async move { + Self::track_metric_call(&client, "get_tx", || { + client.client.tx(tendermint_hash, false) + }) + .await + }; + Box::pin(future) + }) + .await?; + + let received_hash = H256::from_slice(response.hash.as_bytes()); + if received_hash != hash { + return Err(ChainCommunicationError::from_other_str(&format!( + "received incorrect transaction, expected hash: {:?}, received hash: {:?}", + hash, received_hash, + ))); + } + + Ok(response) + } + + /// Get the block by height + pub async fn get_block(&self, height: u32) -> ChainResult { + self.provider + .call(|client| { + let future = async move { + Self::track_metric_call(&client, "get_block", || client.client.block(height)) + .await + }; + Box::pin(future) + }) + .await + } + + /// Get the block results by height + pub async fn get_block_results(&self, height: u32) -> ChainResult { + self.provider + .call(|client| { + let future = async move { + Self::track_metric_call(&client, "block_results", || { + client.client.block_results(height) + }) + .await + }; + Box::pin(future) + }) + .await + } + + /// abci query is low level function that only gets called by higher level ones like 'get_balance' + /// it performs raw state queries on the cosmos sdk + async fn abci_query(&self, path: &str, request: T) -> ChainResult + where + T: Message + prost::Name, + R: Message + std::default::Default, + { + let bytes = request.encode_to_vec(); + let response = self + .provider + .call(|client| { + let path = path.to_owned(); + let bytes = bytes.clone(); + let future = async move { + Self::track_metric_call(&client, T::NAME, || { + client + .client + .abci_query(Some(path.to_owned()), bytes.clone(), None, false) + }) + .await + }; + Box::pin(future) + }) + .await?; + + if response.code.is_err() { + return Err(ChainCommunicationError::from_other_str(&format!( + "ABCI query failed: path={}, code={}, log={}", + path, + response.code.value(), + response.log + ))); + } + + let response = R::decode(response.value.as_slice()).map_err(HyperlaneCosmosError::from)?; + Ok(response) + } + + /// Returns the denom balance of that address. Will use the denom specified as the canonical asset in the config + pub async fn get_balance(&self, address: String) -> ChainResult { + let response: QueryBalanceResponse = self + .abci_query( + "/cosmos.bank.v1beta1.Query/Balance", + QueryBalanceRequest { + address, + denom: self.conf.get_canonical_asset(), + }, + ) + .await?; + let balance = response + .balance + .ok_or_else(|| ChainCommunicationError::from_other_str("account not present"))?; + + Ok(U256::from_dec_str(&balance.amount)?) + } + + /// Returns the denom balance of that address. Will use the denom specified as the canonical asset in the config + pub async fn get_balance_denom(&self, address: String, denom: String) -> ChainResult { + let response: QueryBalanceResponse = self + .abci_query( + "/cosmos.bank.v1beta1.Query/Balance", + QueryBalanceRequest { address, denom }, + ) + .await?; + let balance = response + .balance + .ok_or_else(|| ChainCommunicationError::from_other_str("account not present"))?; + + Ok(U256::from_dec_str(&balance.amount)?) + } + + /// Gets a signer, or returns an error if one is not available. + pub fn get_signer(&self) -> ChainResult<&Signer> { + self.signer + .as_ref() + .ok_or(ChainCommunicationError::SignerUnavailable) + } + + /// ... + pub fn with_signer(&self, signer: Signer) -> Self { + let mut new = self.clone(); + new.signer = Some(signer); + new + } + + /// Injective uses custom proto type for account info. + /// Decode the BaseAccount with the injective proto - which is different to the default one + async fn get_injective_account( + &self, + response: QueryAccountResponse, + ) -> ChainResult { + // Injective uses custom proto type for account info. The account must exist in auth module + let mut eth_account = injective_protobuf::proto::account::EthAccount::parse_from_bytes( + response + .account + .ok_or_else(|| ChainCommunicationError::from_other_str("account not present"))? + .value + .as_slice(), + ) + .map_err(Into::::into)?; + + let base_account = eth_account.take_base_account(); + let pub_key = base_account.pub_key.into_option(); + + Ok(BaseAccount { + address: base_account.address, + pub_key: pub_key.map(|pub_key| Any { + type_url: pub_key.type_url, + value: pub_key.value, + }), + account_number: base_account.account_number, + sequence: base_account.sequence, + }) + } + + async fn get_account(&self, address: String) -> ChainResult { + let response: QueryAccountResponse = self + .abci_query( + "/cosmos.auth.v1beta1.Query/Account", + QueryAccountRequest { address }, + ) + .await?; + + // Try to decode as a standard base account, and if that fails, try Injective-specific format + let account = BaseAccount::decode( + response + .clone() + .account + .ok_or_else(|| ChainCommunicationError::from_other_str("account not present"))? + .value + .as_slice(), + ) + .map_err(HyperlaneCosmosError::from); + + // If standard decoding fails, try Injective format + if account.is_err() { + return self.get_injective_account(response).await; + } + + Ok(account?) + } + + /// Get the gas price + pub fn gas_price(&self) -> FixedPointNumber { + self.gas_price.amount.clone() + } + + /// Generates an unsigned SignDoc for a transaction and the Coin amount + /// required to pay for tx fees. + async fn generate_sign_doc( + &self, + msgs: Vec, + gas_limit: u64, + ) -> ChainResult { + // As this function is only used for estimating gas or sending transactions, + // we can reasonably expect to have a signer. + let signer = self.get_signer()?; + let account_info = self.get_account(signer.address_string.clone()).await?; + + let current_height = self.get_block_number().await? as u32; + + let tx_body = tx::Body::new( + msgs, + String::default(), + current_height.saturating_add(TX_TIMEOUT_BLOCKS), + ); + let signer_info = SignerInfo::single_direct(Some(signer.public_key), account_info.sequence); + + let amount: u128 = (FixedPointNumber::from(gas_limit).mul(self.gas_price())) + .ceil_to_integer() + .try_into()?; + let fee_coin = Coin::new( + // The fee to pay is the gas limit * the gas price + amount, + self.conf.get_canonical_asset().as_str(), + ) + .map_err(HyperlaneCosmosError::from)?; + + let auth_info = + signer_info.auth_info(Fee::from_amount_and_gas(fee_coin.clone(), gas_limit)); + + let chain_id = self + .conf + .get_chain_id() + .parse() + .map_err(HyperlaneCosmosError::from)?; + + Ok( + SignDoc::new(&tx_body, &auth_info, &chain_id, account_info.account_number) + .map_err(HyperlaneCosmosError::from)?, + ) + } + + /// Estimates the gas that will be used when a transaction with msgs is sent. + /// + /// Note: that simulated result will be multiplied by the gas multiplier in the gas config + pub async fn estimate_gas(&self, msgs: Vec) -> ChainResult { + // Get a sign doc with 0 gas, because we plan to simulate + let sign_doc = self.generate_sign_doc(msgs, 0).await?; + + let raw_tx = TxRaw { + body_bytes: sign_doc.body_bytes, + auth_info_bytes: sign_doc.auth_info_bytes, + signatures: vec![vec![]], + }; + let tx_bytes = raw_tx + .to_bytes() + .map_err(ChainCommunicationError::from_other)?; + + #[allow(deprecated)] + let response: SimulateResponse = self + .abci_query( + "/cosmos.tx.v1beta1.Service/Simulate", + SimulateRequest { tx_bytes, tx: None }, + ) + .await?; + + let gas_used = response + .gas_info + .ok_or(ChainCommunicationError::from_other_str( + "gas info not present", + ))? + .gas_used; + + let gas_estimate = (gas_used as f64 * self.conf.get_gas_multiplier()) as u64; + + Ok(gas_estimate) + } + + /// Sends a transaction and waits for confirmation + /// + /// gas_limit will be automatically set if None is passed + pub async fn send( + &self, + msgs: Vec, + gas_limit: Option, + ) -> ChainResult { + let gas_limit = match gas_limit { + Some(limit) => limit, + None => self.estimate_gas(msgs.clone()).await?, + }; + + let sign_doc = self.generate_sign_doc(msgs, gas_limit).await?; + let signer = self.get_signer()?; + + let signed_tx = sign_doc + .sign(&signer.signing_key()?) + .map_err(HyperlaneCosmosError::from)?; + let signed_tx = signed_tx.to_bytes()?; + + // broadcast tx commit blocks until the tx is included in a block + self.provider + .call(|client| { + let signed_tx = signed_tx.clone(); // TODO: this is very memory inefficient, we should actually be able to just reference the slice + let future = async move { + Self::track_metric_call(&client, "send", || { + client.client.broadcast_tx_commit(signed_tx.clone()) + }) + .await + }; + Box::pin(future) + }) + .await + } +} + +#[async_trait] +impl BlockNumberGetter for RpcProvider { + async fn get_block_number(&self) -> Result { + self.provider + .call(|client| { + let future = async move { client.get_block_number().await }; + Box::pin(future) + }) + .await + } +} diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/rpc/client.rs b/rust/main/chains/hyperlane-cosmos/src/providers/rpc/client.rs deleted file mode 100644 index efb1bf38a32..00000000000 --- a/rust/main/chains/hyperlane-cosmos/src/providers/rpc/client.rs +++ /dev/null @@ -1,176 +0,0 @@ -use std::future::Future; -use std::time::Instant; - -use cosmrs::proto::tendermint::blocksync::BlockResponse; -use hyperlane_core::rpc_clients::BlockNumberGetter; -use hyperlane_metric::prometheus_metric::{ - PrometheusClientMetrics, PrometheusConfig, PrometheusConfigExt, -}; -use tendermint::Hash; -use tendermint_rpc::client::CompatMode; -use tendermint_rpc::endpoint::{block, block_by_hash, block_results, tx}; -use tendermint_rpc::{Client, HttpClient, HttpClientUrl, Url as TendermintUrl}; - -use hyperlane_core::{ChainCommunicationError, ChainResult}; -use tonic::async_trait; -use url::Url; - -use crate::{ConnectionConf, HyperlaneCosmosError}; - -/// Thin wrapper around Cosmos RPC client with error mapping -#[derive(Debug)] -pub struct CosmosRpcClient { - client: HttpClient, - metrics: PrometheusClientMetrics, - metrics_config: PrometheusConfig, -} - -impl CosmosRpcClient { - /// Create new `CosmosRpcClient` - pub fn new( - client: HttpClient, - metrics: PrometheusClientMetrics, - metrics_config: PrometheusConfig, - ) -> Self { - // increment provider metric count - let chain_name = PrometheusConfig::chain_name(&metrics_config.chain); - metrics.increment_provider_instance(chain_name); - - Self { - client, - metrics, - metrics_config, - } - } - - /// Creates a CosmosRpcClient from a url - pub fn from_url( - url: &Url, - metrics: PrometheusClientMetrics, - metrics_config: PrometheusConfig, - ) -> ChainResult { - let tendermint_url = tendermint_rpc::Url::try_from(url.to_owned()) - .map_err(Box::new) - .map_err(Into::::into)?; - let url = tendermint_rpc::HttpClientUrl::try_from(tendermint_url) - .map_err(Box::new) - .map_err(Into::::into)?; - - let client = HttpClient::builder(url) - // Consider supporting different compatibility modes. - .compat_mode(CompatMode::V0_37) - .build() - .map_err(Box::new) - .map_err(Into::::into)?; - - Ok(Self::new(client, metrics, metrics_config)) - } - - /// Request block by block height - pub async fn get_block(&self, height: u32) -> ChainResult { - self.track_metric_call("get_block", || async { - Ok(self - .client - .block(height) - .await - .map_err(Box::new) - .map_err(Into::::into)?) - }) - .await - } - - /// Request block results by block height - pub async fn get_block_results(&self, height: u32) -> ChainResult { - self.track_metric_call("get_block_results", || async { - Ok(self - .client - .block_results(height) - .await - .map_err(Box::new) - .map_err(Into::::into)?) - }) - .await - } - - /// Request block by block hash - pub async fn get_block_by_hash(&self, hash: Hash) -> ChainResult { - self.track_metric_call("get_block_by_hash", || async { - Ok(self - .client - .block_by_hash(hash) - .await - .map_err(Box::new) - .map_err(Into::::into)?) - }) - .await - } - - /// Request the latest block - pub async fn get_latest_block(&self) -> ChainResult { - self.track_metric_call("get_latest_block", || async { - Ok(self - .client - .latest_block() - .await - .map_err(Box::new) - .map_err(Into::::into)?) - }) - .await - } - - /// Request transaction by transaction hash - pub async fn get_tx_by_hash(&self, hash: Hash) -> ChainResult { - self.track_metric_call("get_tx_by_hash", || async { - Ok(self - .client - .tx(hash, false) - .await - .map_err(Box::new) - .map_err(Into::::into)?) - }) - .await - } - - async fn track_metric_call(&self, method: &str, rpc_call: F) -> ChainResult - where - F: Fn() -> Fut, - Fut: Future>, - { - let start = Instant::now(); - let res = rpc_call().await; - - self.metrics - .increment_metrics(&self.metrics_config, method, start, res.is_ok()); - res - } -} - -impl Drop for CosmosRpcClient { - fn drop(&mut self) { - // decrement provider metric count - let chain_name = PrometheusConfig::chain_name(&self.metrics_config.chain); - self.metrics.decrement_provider_instance(chain_name); - } -} - -impl Clone for CosmosRpcClient { - fn clone(&self) -> Self { - Self::new( - self.client.clone(), - self.metrics.clone(), - self.metrics_config.clone(), - ) - } -} - -#[async_trait] -impl BlockNumberGetter for CosmosRpcClient { - async fn get_block_number(&self) -> ChainResult { - self.get_latest_block() - .await - .map(|block| block.block.header.height.value()) - } -} - -#[cfg(test)] -mod tests; diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/rpc/provider.rs b/rust/main/chains/hyperlane-cosmos/src/providers/rpc/provider.rs deleted file mode 100644 index ac1ab419190..00000000000 --- a/rust/main/chains/hyperlane-cosmos/src/providers/rpc/provider.rs +++ /dev/null @@ -1,314 +0,0 @@ -use std::fmt::Debug; - -use async_trait::async_trait; -use cosmrs::cosmwasm::MsgExecuteContract; -use cosmrs::rpc::client::Client; -use futures::StreamExt; -use hyperlane_core::rpc_clients::{BlockNumberGetter, FallbackProvider}; -use hyperlane_metric::prometheus_metric::{ - ChainInfo, ClientConnectionType, PrometheusClientMetrics, PrometheusConfig, -}; -use sha256::digest; -use tendermint::abci::{Event, EventAttribute}; -use tendermint::hash::Algorithm; -use tendermint::Hash; -use tendermint_rpc::client::CompatMode; -use tendermint_rpc::endpoint::block::Response as BlockResponse; -use tendermint_rpc::endpoint::block_results::{self, Response as BlockResultsResponse}; -use tendermint_rpc::endpoint::tx; -use tendermint_rpc::HttpClient; -use time::OffsetDateTime; -use tracing::{debug, info, instrument, trace}; - -use hyperlane_core::{ - ChainCommunicationError, ChainResult, ContractLocator, HyperlaneDomain, LogMeta, H256, U256, -}; - -use crate::rpc::CosmosRpcClient; -use crate::rpc_clients::CosmosFallbackProvider; -use crate::{ConnectionConf, CosmosAddress, CosmosProvider, HyperlaneCosmosError}; - -#[async_trait] -/// Trait for wasm indexer. Use rpc provider -pub trait WasmRpcProvider: Send + Sync { - /// Get the finalized block height. - async fn get_finalized_block_number(&self) -> ChainResult; - - /// Get logs for the given block using the given parser. - async fn get_logs_in_block( - &self, - block_number: u32, - parser: for<'a> fn(&'a Vec) -> ChainResult>, - cursor_label: &'static str, - ) -> ChainResult> - where - T: Send + Sync + PartialEq + Debug + 'static; - - /// Get logs for the given transaction using the given parser. - async fn get_logs_in_tx( - &self, - tx_hash: Hash, - parser: for<'a> fn(&'a Vec) -> ChainResult>, - cursor_label: &'static str, - ) -> ChainResult> - where - T: Send + Sync + PartialEq + Debug + 'static; -} - -#[derive(Debug, Eq, PartialEq)] -/// An event parsed from the RPC response. -pub struct ParsedEvent { - contract_address: String, - event: T, -} - -impl ParsedEvent { - /// Create a new ParsedEvent. - pub fn new(contract_address: String, event: T) -> Self { - Self { - contract_address, - event, - } - } - - /// Get the inner event - pub fn inner(self) -> T { - self.event - } -} - -#[derive(Debug, Clone)] -/// Cosmwasm RPC Provider -pub struct CosmosWasmRpcProvider { - domain: HyperlaneDomain, - contract_address: CosmosAddress, - target_event_kind: String, - reorg_period: u32, - rpc_client: CosmosFallbackProvider, -} - -impl CosmosWasmRpcProvider { - const WASM_TYPE: &'static str = "wasm"; - - /// create new Cosmwasm RPC Provider - pub fn new( - conf: &ConnectionConf, - locator: &ContractLocator, - event_type: String, - reorg_period: u32, - metrics: PrometheusClientMetrics, - chain: Option, - ) -> ChainResult { - let providers = conf - .get_rpc_urls() - .iter() - .map(|url| { - let metrics_config = - PrometheusConfig::from_url(url, ClientConnectionType::Rpc, chain.clone()); - CosmosRpcClient::from_url(url, metrics.clone(), metrics_config) - }) - .collect::, _>>()?; - let mut builder = FallbackProvider::builder(); - builder = builder.add_providers(providers); - let fallback_provider = builder.build(); - let provider = CosmosFallbackProvider::new(fallback_provider); - - Ok(Self { - domain: locator.domain.clone(), - contract_address: CosmosAddress::from_h256( - locator.address, - conf.get_bech32_prefix().as_str(), - conf.get_contract_address_bytes(), - )?, - target_event_kind: format!("{}-{}", Self::WASM_TYPE, event_type), - reorg_period, - rpc_client: provider, - }) - } - - async fn get_block(&self, height: u32) -> ChainResult { - self.rpc_client - .call(|provider| Box::pin(async move { provider.get_block(height).await })) - .await - } -} - -impl CosmosWasmRpcProvider { - // Iterate through all txs, filter out failed txs, find target events - // in successful txs, and parse them. - fn handle_txs( - &self, - block: BlockResponse, - block_results: BlockResultsResponse, - parser: for<'a> fn(&'a Vec) -> ChainResult>, - cursor_label: &'static str, - ) -> Vec<(T, LogMeta)> - where - T: PartialEq + Debug + 'static, - { - let Some(tx_results) = block_results.txs_results else { - return vec![]; - }; - - let tx_hashes: Vec = block - .clone() - .block - .data - .into_iter() - .filter_map(|tx| hex::decode(digest(tx.as_slice())).ok()) - .filter_map(|hash| Hash::from_bytes(Algorithm::Sha256, hash.as_slice()).ok()) - .collect(); - - tx_results - .into_iter() - .enumerate() - .filter_map(move |(idx, tx)| { - let Some(tx_hash) = tx_hashes.get(idx) else { - debug!(?tx, "No tx hash found for tx"); - return None; - }; - if tx.code.is_err() { - debug!(?tx_hash, "Not indexing failed transaction"); - return None; - } - - // We construct a simplified structure `tx::Response` here so that we can - // reuse `handle_tx` method below. - let tx_response = tx::Response { - hash: *tx_hash, - height: block_results.height, - index: idx as u32, - tx_result: tx, - tx: vec![], - proof: None, - }; - - let block_hash = H256::from_slice(block.block_id.hash.as_bytes()); - - Some(self.handle_tx(tx_response, block_hash, parser)) - }) - .flatten() - .collect() - } - - // Iter through all events in the tx, looking for any target events - // made by the contract we are indexing. - fn handle_tx( - &self, - tx: tx::Response, - block_hash: H256, - parser: for<'a> fn(&'a Vec) -> ChainResult>, - ) -> impl Iterator + '_ - where - T: PartialEq + 'static, - { - let tx_events = tx.tx_result.events; - let tx_hash = tx.hash; - let tx_index = tx.index; - let block_height = tx.height; - - tx_events.into_iter().enumerate().filter_map(move |(log_idx, event)| { - if event.kind.as_str() != self.target_event_kind { - return None; - } - - parser(&event.attributes) - .map_err(|err| { - // This can happen if we attempt to parse an event that just happens - // to have the same name but a different structure. - trace!(?err, tx_hash=?tx_hash, log_idx, ?event, "Failed to parse event attributes"); - }) - .ok() - .and_then(|parsed_event| { - // This is crucial! We need to make sure that the contract address - // in the event matches the contract address we are indexing. - // Otherwise, we might index events from other contracts that happen - // to have the same target event name. - if parsed_event.contract_address != self.contract_address.address() { - trace!(tx_hash=?tx_hash, log_idx, ?event, "Event contract address does not match indexer contract address"); - return None; - } - - Some((parsed_event.event, LogMeta { - address: self.contract_address.digest(), - block_number: block_height.value(), - block_hash, - transaction_id: H256::from_slice(tx_hash.as_bytes()).into(), - transaction_index: tx_index as u64, - log_index: U256::from(log_idx), - })) - }) - }) - } -} - -#[async_trait] -impl WasmRpcProvider for CosmosWasmRpcProvider { - #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue - async fn get_finalized_block_number(&self) -> ChainResult { - let latest_block = self - .rpc_client - .call(|provider| Box::pin(async move { provider.get_latest_block().await })) - .await?; - let latest_height: u32 = latest_block - .block - .header - .height - .value() - .try_into() - .map_err(ChainCommunicationError::from_other)?; - Ok(latest_height.saturating_sub(self.reorg_period)) - } - - #[instrument(err, fields(domain = self.domain.name()), skip(self, parser))] - #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue - async fn get_logs_in_block( - &self, - block_number: u32, - parser: for<'a> fn(&'a Vec) -> ChainResult>, - cursor_label: &'static str, - ) -> ChainResult> - where - T: Send + Sync + PartialEq + Debug + 'static, - { - // The two calls below could be made in parallel, but on cosmos rate limiting is a bigger problem - // than indexing latency, so we do them sequentially. - let block = self.get_block(block_number).await?; - debug!(?block_number, block_hash = ?block.block_id.hash, cursor_label, domain=?self.domain.name(), "Getting logs in block with hash"); - let block_results = self - .rpc_client - .call(|provider| { - Box::pin(async move { provider.get_block_results(block_number).await }) - }) - .await?; - - Ok(self.handle_txs(block, block_results, parser, cursor_label)) - } - - #[instrument(err, skip(self, parser))] - #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue - async fn get_logs_in_tx( - &self, - hash: Hash, - parser: for<'a> fn(&'a Vec) -> ChainResult>, - cursor_label: &'static str, - ) -> ChainResult> - where - T: Send + Sync + PartialEq + Debug + 'static, - { - let tx = self - .rpc_client - .call(|provider| Box::pin(async move { provider.get_tx_by_hash(hash).await })) - .await?; - let block_number = tx.height.value() as u32; - let block = self.get_block(block_number).await?; - let block_hash = H256::from_slice(block.block_id.hash.as_bytes()); - - debug!(?block_number, block_hash = ?block.block_id.hash, cursor_label, domain=?self.domain.name(), "Getting logs in transaction: block info"); - - Ok(self.handle_tx(tx, block_hash, parser).collect()) - } -} - -#[cfg(test)] -mod tests; diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/rpc/provider/tests.rs b/rust/main/chains/hyperlane-cosmos/src/providers/rpc/provider/tests.rs deleted file mode 100644 index 09d6b775a49..00000000000 --- a/rust/main/chains/hyperlane-cosmos/src/providers/rpc/provider/tests.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::str::FromStr; - -use tendermint_rpc::client::CompatMode; -use tendermint_rpc::HttpClient; -use url::Url; - -use hyperlane_core::rpc_clients::FallbackProvider; -use hyperlane_metric::prometheus_metric::{ - ClientConnectionType, PrometheusClientMetrics, PrometheusConfig, -}; - -use crate::rpc::CosmosRpcClient; -use crate::rpc_clients::CosmosFallbackProvider; - -/// This test passes when HttpClient is initialised with `CompactMode::V0_37` (done in prod code). -/// This test fails when `CompactMode::V0_38` is used with Neutron url and block height. -/// This test passes with Osmosis url and block height and any compact mode. -#[tokio::test] -#[ignore] -async fn test_fallback_provider() { - use tendermint_rpc::Client; - - // Neutron - let url = ""; - let height = 22488720u32; - - // Osmosis - // let url = ""; - // let height = 15317185u32; - - let url = Url::from_str(url).unwrap(); - - let metrics = PrometheusClientMetrics::default(); - - let metrics_config = PrometheusConfig { - connection_type: ClientConnectionType::Rpc, - node: None, - chain: None, - }; - let rpc_client = CosmosRpcClient::from_url(&url, metrics.clone(), metrics_config).unwrap(); - let providers = [rpc_client]; - - let mut builder = FallbackProvider::builder(); - builder = builder.add_providers(providers); - let fallback_provider = builder.build(); - let provider = CosmosFallbackProvider::new(fallback_provider); - - let response = provider - .call(|provider| Box::pin(async move { provider.get_block(height).await })) - .await - .unwrap(); - - println!("{:?}", response); -} diff --git a/rust/main/chains/hyperlane-cosmos/src/providers/rpc/client/tests.rs b/rust/main/chains/hyperlane-cosmos/src/providers/tests.rs similarity index 98% rename from rust/main/chains/hyperlane-cosmos/src/providers/rpc/client/tests.rs rename to rust/main/chains/hyperlane-cosmos/src/providers/tests.rs index 34f1475192d..73c86ce9792 100644 --- a/rust/main/chains/hyperlane-cosmos/src/providers/rpc/client/tests.rs +++ b/rust/main/chains/hyperlane-cosmos/src/providers/tests.rs @@ -1,8 +1,13 @@ -use crate::HyperlaneCosmosError; use std::str::FromStr; -use tendermint_rpc::client::CompatMode; -use tendermint_rpc::endpoint::block::Response; -use tendermint_rpc::{HttpClient, Url}; + +use cometbft_rpc::{client::CompatMode, HttpClient, Url}; +use hyperlane_core::{config::OpSubmissionConfig, FixedPointNumber, NativeToken}; +use hyperlane_metric::prometheus_metric::{ + ClientConnectionType, PrometheusClientMetrics, PrometheusConfig, +}; + +use crate::{ConnectionConf, CosmosAmount, CosmosHttpClient, RawCosmosAmount, RpcProvider}; +use cometbft_rpc::endpoint::block::Response; #[test] fn test_deserialize_neutron_block_22488720() { @@ -1273,22 +1278,22 @@ fn test_deserialize_osmosis_block_15317185() { #[tokio::test] #[ignore] async fn test_http_client() { - use tendermint_rpc::Client; + use cometbft_rpc::Client; // Neutron let url = ""; - let height = 22488720u32; + let height: u32 = 22488720; // Osmosis // let url = ""; // let height = 15317185u32; let url = Url::from_str(url).unwrap(); - let tendermint_url = tendermint_rpc::Url::try_from(url).unwrap(); - let url = tendermint_rpc::HttpClientUrl::try_from(tendermint_url).unwrap(); + let rpc_url = cometbft_rpc::Url::try_from(url).unwrap(); + let url = cometbft_rpc::HttpClientUrl::try_from(rpc_url).unwrap(); let client = HttpClient::builder(url) - .compat_mode(CompatMode::V0_37) + .compat_mode(CompatMode::latest()) .build() .unwrap(); @@ -1296,3 +1301,64 @@ async fn test_http_client() { println!("Response: {:?}", response); } + +/// This test passes when HttpClient is initialised with `CompactMode::V0_37` (done in prod code). +/// This test fails when `CompactMode::V0_38` is used with Neutron url and block height. +/// This test passes with Osmosis url and block height and any compact mode. +#[tokio::test] +#[ignore] +async fn test_fallback_provider() { + use url::Url as UUrl; + + // Neutron + let url = ""; + let height = 22488720u32; + + // Osmosis + // let url = ""; + // let height = 15317185u32; + + let url = UUrl::from_str(url).unwrap(); + + let metrics = PrometheusClientMetrics::default(); + + let metrics_config = PrometheusConfig { + connection_type: ClientConnectionType::Rpc, + node: None, + chain: None, + }; + let rpc_client = + CosmosHttpClient::from_url(&url, metrics.clone(), metrics_config, CompatMode::latest()) + .unwrap(); + let providers = [rpc_client]; + + let provider = RpcProvider::from_providers( + providers.to_vec(), + ConnectionConf::new( + vec![], + vec![], + "".into(), + "".into(), + "".into(), + RawCosmosAmount { + denom: "".into(), + amount: "".into(), + }, + 32usize, + OpSubmissionConfig::default(), + NativeToken::default(), + 1.0f64, + None, + ) + .unwrap(), + None, + CosmosAmount { + denom: "".into(), + amount: FixedPointNumber::zero(), + }, + ); + + let response = provider.get_block(height).await.unwrap(); + + println!("{:?}", response); +} diff --git a/rust/main/chains/hyperlane-cosmos/src/rpc_clients/fallback.rs b/rust/main/chains/hyperlane-cosmos/src/rpc_clients/fallback.rs deleted file mode 100644 index 95456cc2c1c..00000000000 --- a/rust/main/chains/hyperlane-cosmos/src/rpc_clients/fallback.rs +++ /dev/null @@ -1,134 +0,0 @@ -use std::{ - fmt::{Debug, Formatter}, - ops::Deref, -}; - -use derive_new::new; -use hyperlane_core::rpc_clients::FallbackProvider; - -/// Wrapper of `FallbackProvider` for use in `hyperlane-cosmos` -#[derive(new, Clone)] -pub struct CosmosFallbackProvider { - fallback_provider: FallbackProvider, -} - -impl Deref for CosmosFallbackProvider { - type Target = FallbackProvider; - - fn deref(&self) -> &Self::Target { - &self.fallback_provider - } -} - -impl Debug for CosmosFallbackProvider -where - C: Debug, -{ - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - self.fallback_provider.fmt(f) - } -} - -#[cfg(test)] -mod tests { - use std::time::Duration; - - use async_trait::async_trait; - use hyperlane_core::rpc_clients::test::ProviderMock; - use hyperlane_core::rpc_clients::{BlockNumberGetter, FallbackProviderBuilder}; - use hyperlane_core::ChainResult; - use tokio::time::sleep; - - use super::*; - - #[derive(Debug, Clone, Default)] - struct CosmosProviderMock(ProviderMock); - - impl Deref for CosmosProviderMock { - type Target = ProviderMock; - - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - impl CosmosProviderMock { - fn new(request_sleep: Option) -> Self { - Self(ProviderMock::new(request_sleep)) - } - } - - #[async_trait] - impl BlockNumberGetter for CosmosProviderMock { - async fn get_block_number(&self) -> ChainResult { - Ok(0) - } - } - - impl From for Box { - fn from(val: CosmosProviderMock) -> Self { - Box::new(val) - } - } - - impl CosmosFallbackProvider { - async fn low_level_test_call(&mut self) -> ChainResult { - self.call(|provider| { - provider.push("GET", "http://localhost:1234"); - let future = async move { - let body = tonic::body::BoxBody::default(); - let response = http::Response::builder().status(200).body(body).unwrap(); - if let Some(sleep_duration) = provider.request_sleep() { - sleep(sleep_duration).await; - } - Ok(response) - }; - Box::pin(future) - }) - .await?; - Ok(()) - } - } - - #[tokio::test] - async fn test_first_provider_is_attempted() { - let fallback_provider_builder = FallbackProviderBuilder::default(); - let providers = vec![ - CosmosProviderMock::default(), - CosmosProviderMock::default(), - CosmosProviderMock::default(), - ]; - let fallback_provider = fallback_provider_builder.add_providers(providers).build(); - let mut cosmos_fallback_provider = CosmosFallbackProvider::new(fallback_provider); - cosmos_fallback_provider - .low_level_test_call() - .await - .unwrap(); - let provider_call_count: Vec<_> = - ProviderMock::get_call_counts(&cosmos_fallback_provider).await; - assert_eq!(provider_call_count, vec![1, 0, 0]); - } - - #[tokio::test] - async fn test_one_stalled_provider() { - let fallback_provider_builder = FallbackProviderBuilder::default(); - let providers = vec![ - CosmosProviderMock::new(Some(Duration::from_millis(10))), - CosmosProviderMock::default(), - CosmosProviderMock::default(), - ]; - let fallback_provider = fallback_provider_builder - .add_providers(providers) - .with_max_block_time(Duration::from_secs(0)) - .build(); - let mut cosmos_fallback_provider = CosmosFallbackProvider::new(fallback_provider); - cosmos_fallback_provider - .low_level_test_call() - .await - .unwrap(); - - let provider_call_count: Vec<_> = - ProviderMock::get_call_counts(&cosmos_fallback_provider).await; - assert_eq!(provider_call_count, vec![0, 0, 1]); - } -} diff --git a/rust/main/chains/hyperlane-cosmos/src/rpc_clients/mod.rs b/rust/main/chains/hyperlane-cosmos/src/rpc_clients/mod.rs deleted file mode 100644 index 536845688d5..00000000000 --- a/rust/main/chains/hyperlane-cosmos/src/rpc_clients/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub use self::fallback::*; - -mod fallback; diff --git a/rust/main/chains/hyperlane-cosmos/src/trait_builder.rs b/rust/main/chains/hyperlane-cosmos/src/trait_builder.rs index 997c5049da3..58c5387c6d0 100644 --- a/rust/main/chains/hyperlane-cosmos/src/trait_builder.rs +++ b/rust/main/chains/hyperlane-cosmos/src/trait_builder.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use cometbft_rpc::client::CompatMode; use derive_new::new; use url::Url; @@ -32,6 +33,11 @@ pub struct ConnectionConf { pub op_submission_config: OpSubmissionConfig, /// Native Token native_token: NativeToken, + /// Gas Multiplier + gas_multiplier: f64, + /// RPC Compatibility Mode + /// This is useful to support different tendermin/cometbft spec versions + pub compat_mode: CompatMode, } /// Untyped cosmos amount @@ -123,6 +129,11 @@ impl ConnectionConf { self.contract_address_bytes } + /// Get the gas multiplier which might be different for each chain + pub fn get_gas_multiplier(&self) -> f64 { + self.gas_multiplier + } + /// Create a new connection configuration #[allow(clippy::too_many_arguments)] pub fn new( @@ -135,8 +146,14 @@ impl ConnectionConf { contract_address_bytes: usize, op_submission_config: OpSubmissionConfig, native_token: NativeToken, - ) -> Self { - Self { + gas_multiplier: f64, + compat_mode: Option<&str>, + ) -> Result { + let compat_mode = compat_mode + .map(|s| CompatMode::from_str(s).map_err(|e| e.to_string())) + .transpose()? + .unwrap_or_default(); + Ok(Self { grpc_urls, rpc_urls, chain_id, @@ -146,6 +163,8 @@ impl ConnectionConf { contract_address_bytes, op_submission_config, native_token, - } + gas_multiplier, + compat_mode, + }) } } diff --git a/rust/main/chains/hyperlane-cosmos/src/utils.rs b/rust/main/chains/hyperlane-cosmos/src/utils.rs index cd1d42c4621..2eca758c040 100644 --- a/rust/main/chains/hyperlane-cosmos/src/utils.rs +++ b/rust/main/chains/hyperlane-cosmos/src/utils.rs @@ -1,22 +1,7 @@ -use std::fmt::Debug; -use std::num::NonZeroU64; -use std::ops::RangeInclusive; - -use base64::{engine::general_purpose::STANDARD as BASE64, Engine}; -use futures::future; +use base64::{ + engine::general_purpose::STANDARD as BASE64, prelude::BASE64_STANDARD_NO_PAD, Engine, +}; use once_cell::sync::Lazy; -use tendermint::abci::EventAttribute; -use tendermint::hash::Algorithm; -use tendermint::Hash; -use tokio::task::JoinHandle; -use tracing::warn; - -use hyperlane_core::{ChainCommunicationError, ChainResult, Indexed, LogMeta, ReorgPeriod, H256}; - -use crate::grpc::{WasmGrpcProvider, WasmProvider}; -use crate::rpc::{CosmosWasmRpcProvider, ParsedEvent, WasmRpcProvider}; - -type FutureChainResults = Vec>, u32)>>; /// The event attribute key for the contract address. pub(crate) const CONTRACT_ADDRESS_ATTRIBUTE_KEY: &str = "_contract_address"; @@ -24,97 +9,126 @@ pub(crate) const CONTRACT_ADDRESS_ATTRIBUTE_KEY: &str = "_contract_address"; pub(crate) static CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64: Lazy = Lazy::new(|| BASE64.encode(CONTRACT_ADDRESS_ATTRIBUTE_KEY)); -/// Given a `reorg_period`, returns the block height at the moment. -/// If the `reorg_period` is None, a block height of None is given, -/// indicating that the tip directly can be used. -pub(crate) async fn get_block_height_for_reorg_period( - provider: &WasmGrpcProvider, - reorg_period: &ReorgPeriod, -) -> ChainResult { - let period = match reorg_period { - ReorgPeriod::Blocks(blocks) => blocks.get() as u64, - ReorgPeriod::None => 0, - ReorgPeriod::Tag(_) => { - return Err(ChainCommunicationError::InvalidReorgPeriod( - reorg_period.clone(), - )) +#[cfg(test)] +/// Helper function to create a Vec from a JSON string - +/// crate::payloads::general::EventAttribute has a Deserialize impl while +/// cosmrs::tendermint::abci::EventAttribute does not. +pub(crate) fn event_attributes_from_str(attrs_str: &str) -> Vec { + serde_json::from_str::>(attrs_str) + .unwrap() + .into_iter() + .map(|attr| attr.into()) + .collect() +} + +use cometbft_rpc::endpoint::broadcast::tx_commit::Response; +use cosmrs::{crypto::PublicKey, proto, tx::SignerPublicKey, Any}; +use crypto::decompress_public_key; +use serde::{Deserialize, Serialize}; +use tracing::warn; + +use hyperlane_core::{AccountAddressType, ChainResult, FixedPointNumber, TxOutcome, H256}; + +const INJECTIVE_PUBLIC_KEY_TYPE_URL: &str = "/injective.crypto.v1beta1.ethsecp256k1.PubKey"; + +use crate::HyperlaneCosmosError; + +#[derive(Clone, Debug, Deserialize, Serialize)] +struct CosmosKeyJsonFormat { + #[serde(rename = "@type")] + pub key_type: &'static str, + pub key: String, +} + +pub fn normalize_public_key( + signer_public_key: SignerPublicKey, +) -> ChainResult<(SignerPublicKey, AccountAddressType)> { + let public_key_and_account_address_type = match signer_public_key { + SignerPublicKey::Single(pk) => (SignerPublicKey::from(pk), AccountAddressType::Bitcoin), + SignerPublicKey::LegacyAminoMultisig(pk) => { + (SignerPublicKey::from(pk), AccountAddressType::Bitcoin) + } + SignerPublicKey::Any(pk) => { + if pk.type_url != PublicKey::ED25519_TYPE_URL + && pk.type_url != PublicKey::SECP256K1_TYPE_URL + && pk.type_url != INJECTIVE_PUBLIC_KEY_TYPE_URL + { + let msg = format!( + "can only normalize public keys with a known TYPE_URL: {}, {}, {}", + PublicKey::ED25519_TYPE_URL, + PublicKey::SECP256K1_TYPE_URL, + INJECTIVE_PUBLIC_KEY_TYPE_URL + ); + warn!(pk.type_url, msg); + Err(HyperlaneCosmosError::PublicKeyError(msg.to_owned()))? + } + + let (pub_key, account_address_type) = if pk.type_url == INJECTIVE_PUBLIC_KEY_TYPE_URL { + let any = Any { + type_url: PublicKey::SECP256K1_TYPE_URL.to_owned(), + value: pk.value, + }; + + let proto: proto::cosmos::crypto::secp256k1::PubKey = + any.to_msg().map_err(Into::::into)?; + + let decompressed = decompress_public_key(&proto.key) + .map_err(|e| HyperlaneCosmosError::PublicKeyError(e.to_string()))?; + + let cometbft_key = cometbft::PublicKey::from_raw_secp256k1(&decompressed) + .ok_or_else(|| { + HyperlaneCosmosError::PublicKeyError( + "cannot create cometbft public key".to_owned(), + ) + })?; + + let cosm_key = cometbft_pubkey_to_cosmrs_pubkey(&cometbft_key)?; + (cosm_key, AccountAddressType::Ethereum) + } else { + (PublicKey::try_from(pk)?, AccountAddressType::Bitcoin) + }; + + (SignerPublicKey::Single(pub_key), account_address_type) } }; - let tip = provider.latest_block_height().await?; - let block_height = tip - period; - Ok(block_height) + Ok(public_key_and_account_address_type) } -pub(crate) fn parse_logs_in_range( - range: RangeInclusive, - provider: Box, - parser: for<'a> fn(&'a Vec) -> ChainResult>, - label: &'static str, -) -> FutureChainResults { - range - .map(|block_number| { - let provider = provider.clone(); - tokio::spawn(async move { - let logs = provider - .get_logs_in_block(block_number, parser, label) - .await; - (logs, block_number) - }) - }) - .collect() -} +pub fn cometbft_pubkey_to_cosmrs_pubkey( + cometbft_key: &cometbft::PublicKey, +) -> ChainResult { + let cometbft_key_json = serde_json::to_string(&cometbft_key) + .map_err(|e| HyperlaneCosmosError::PublicKeyError(e.to_string()))?; -pub(crate) async fn parse_logs_in_tx( - hash: &H256, - provider: Box, - parser: for<'a> fn(&'a Vec) -> ChainResult>, - label: &'static str, -) -> ChainResult> { - let tendermint_hash = Hash::from_bytes(Algorithm::Sha256, hash.as_bytes()) - .expect("transaction hash should be of correct size"); - - provider - .get_logs_in_tx(tendermint_hash, parser, label) - .await -} + let cosmos_key_json = match cometbft_key { + cometbft::PublicKey::Ed25519(key) => CosmosKeyJsonFormat { + key_type: cosmrs::crypto::PublicKey::ED25519_TYPE_URL, + key: BASE64_STANDARD_NO_PAD.encode(key.as_bytes()), + }, + cometbft::PublicKey::Secp256k1(key) => CosmosKeyJsonFormat { + key_type: cosmrs::crypto::PublicKey::SECP256K1_TYPE_URL, + key: BASE64_STANDARD_NO_PAD.encode(key.to_sec1_bytes()), + }, + // not sure why it requires me to have this extra arm. But + // we should never reach this + _ => { + return Err(HyperlaneCosmosError::PublicKeyError("Invalid key".into()).into()); + } + }; -#[allow(clippy::type_complexity)] -pub(crate) async fn execute_and_parse_log_futures>>( - logs_futures: Vec, ChainCommunicationError>, u32)>>, -) -> ChainResult, LogMeta)>> { - // TODO: this can be refactored when we rework indexing, to be part of the block-by-block indexing - let result = future::join_all(logs_futures) - .await - .into_iter() - .flatten() - .map(|(logs, block_number)| { - if let Err(err) = &logs { - warn!(?err, ?block_number, "Failed to fetch logs for block"); - } - logs - }) - // Propagate errors from any of the queries. This will cause the entire range to be retried, - // including successful ones, but we don't have a way to handle partial failures in a range for now. - // This is also why cosmos indexing should be run with small chunks (currently set to 5). - .collect::, _>>()? - .into_iter() - .flatten() - .map(|(log, meta)| (log.into(), meta)) - .collect(); - Ok(result) + let json_val = serde_json::to_string(&cosmos_key_json) + .map_err(|e| HyperlaneCosmosError::PublicKeyError(e.to_string()))?; + let cosm_key = PublicKey::from_json(&json_val) + .map_err(|e| HyperlaneCosmosError::PublicKeyError(e.to_string()))?; + Ok(cosm_key) } -#[cfg(test)] -/// Helper function to create a Vec from a JSON string - -/// crate::payloads::general::EventAttribute has a Deserialize impl while -/// cosmrs::tendermint::abci::EventAttribute does not. -pub(crate) fn event_attributes_from_str( - attrs_str: &str, -) -> Vec { - serde_json::from_str::>(attrs_str) - .unwrap() - .into_iter() - .map(|attr| attr.into()) - .collect() +pub(crate) fn tx_response_to_outcome(response: Response, gas_price: FixedPointNumber) -> TxOutcome { + TxOutcome { + transaction_id: H256::from_slice(response.hash.as_bytes()).into(), + executed: response.check_tx.code.is_ok() && response.tx_result.code.is_ok(), + gas_used: response.tx_result.gas_used.into(), + gas_price, + } } diff --git a/rust/main/chains/hyperlane-ethereum/src/config.rs b/rust/main/chains/hyperlane-ethereum/src/config.rs index bd8f59abc4d..bffc7f5abe8 100644 --- a/rust/main/chains/hyperlane-ethereum/src/config.rs +++ b/rust/main/chains/hyperlane-ethereum/src/config.rs @@ -3,8 +3,8 @@ use ethers_core::types::{BlockId, BlockNumber}; use url::Url; use hyperlane_core::{ - config::OpSubmissionConfig, utils::hex_or_base58_to_h256, ChainCommunicationError, ChainResult, - ReorgPeriod, H256, U256, + config::OpSubmissionConfig, utils::hex_or_base58_or_bech32_to_h256, ChainCommunicationError, + ChainResult, ReorgPeriod, H256, U256, }; static BATCH_CONTRACT_ADDRESS_DEFAULT: &str = "0xcA11bde05977b3631167028862bE2a173976CA11"; @@ -69,7 +69,7 @@ impl ConnectionConf { self.op_submission_config .batch_contract_address .unwrap_or_else(|| { - hex_or_base58_to_h256(BATCH_CONTRACT_ADDRESS_DEFAULT) + hex_or_base58_or_bech32_to_h256(BATCH_CONTRACT_ADDRESS_DEFAULT) .expect("Invalid default batch contract address") }) } @@ -97,18 +97,18 @@ pub struct TransactionOverrides { /// Min priority fee per gas to use for EIP-1559 transactions. pub min_priority_fee_per_gas: Option, - /// Gas limit multiplier denominator to use for transactions, eg 110 - pub gas_limit_multiplier_denominator: Option, - /// Gas limit multiplier numerator to use for transactions, eg 100 - pub gas_limit_multiplier_numerator: Option, - /// Gas price multiplier denominator to use for transactions, eg 110 pub gas_price_multiplier_denominator: Option, /// Gas price multiplier numerator to use for transactions, eg 100 pub gas_price_multiplier_numerator: Option, + /// Gas price cap multiplier to bound escalated prices by newly estimated prices. + /// Default value is 3 if not specified. + pub gas_price_cap_multiplier: Option, /// Gas price cap, in wei. pub gas_price_cap: Option, + /// Gas limit cap, in wei. + pub gas_limit_cap: Option, } /// Ethereum reorg period diff --git a/rust/main/chains/hyperlane-ethereum/src/contracts/mailbox.rs b/rust/main/chains/hyperlane-ethereum/src/contracts/mailbox.rs index d568be44217..84232dcbe35 100644 --- a/rust/main/chains/hyperlane-ethereum/src/contracts/mailbox.rs +++ b/rust/main/chains/hyperlane-ethereum/src/contracts/mailbox.rs @@ -773,13 +773,13 @@ mod test { // order, so we start with the final RPCs and work toward the first // RPCs - // RPC 4: eth_gasPrice by process_estimate_costs + // RPC 9: eth_gasPrice by process_estimate_costs // Return 15 gwei let gas_price: U256 = EthersU256::from(ethers::utils::parse_units("15", "gwei").unwrap()).into(); mock_provider.push(gas_price).unwrap(); - // RPC 6: eth_estimateGas to the ArbitrumNodeInterface's estimateRetryableTicket function by process_estimate_costs + // RPC 8: eth_estimateGas to the ArbitrumNodeInterface's estimateRetryableTicket function by process_estimate_costs let l2_gas_limit = U256::from(200000); // 200k gas mock_provider.push(l2_gas_limit).unwrap(); @@ -790,18 +790,23 @@ mod test { reward: vec![vec![]], }; - // RPC 5: eth_feeHistory from the estimate_eip1559_fees_default - mock_provider.push(fee_history).unwrap(); + // RPC 5-7: eth_feeHistory from the estimate_eip1559_fees_default with different percentiles + mock_provider.push(fee_history.clone()).unwrap(); + mock_provider.push(fee_history.clone()).unwrap(); + mock_provider.push(fee_history.clone()).unwrap(); + + // RPC 4: eth_feeHistory from the estimate_eip1559_fees_default + mock_provider.push(fee_history.clone()).unwrap(); let latest_block: Block = Block { gas_limit: ethers::types::U256::MAX, ..Block::::default() }; - // RPC 4: eth_getBlockByNumber from the estimate_eip1559_fees_default + // RPC 3: eth_getBlockByNumber from the estimate_eip1559_fees_default mock_provider.push(latest_block.clone()).unwrap(); - // RPC 3: eth_getBlockByNumber from the fill_tx_gas_params call in process_contract_call + // RPC 2: eth_getBlockByNumber from the fill_tx_gas_params call in process_contract_call // to get the latest block gas limit and for eip 1559 fee estimation mock_provider.push(latest_block).unwrap(); @@ -837,7 +842,7 @@ mod test { // order, so we start with the final RPCs and work toward the first // RPCs - // RPC 6: eth_gasPrice by process_estimate_costs + // RPC 8: eth_gasPrice by process_estimate_costs // Return 15 gwei let gas_price: U256 = EthersU256::from(ethers::utils::parse_units("15", "gwei").unwrap()).into(); @@ -850,7 +855,12 @@ mod test { reward: vec![vec![]], }; - // RPC 5: eth_feeHistory from the estimate_eip1559_fees_default + // RPC 5-7: eth_feeHistory from the estimate_eip1559_fees_default with different percentiles + mock_provider.push(fee_history.clone()).unwrap(); + mock_provider.push(fee_history.clone()).unwrap(); + mock_provider.push(fee_history.clone()).unwrap(); + + // RPC 4: eth_feeHistory from the estimate_eip1559_fees_default mock_provider.push(fee_history).unwrap(); let latest_block_gas_limit = U256::from(12345u32); @@ -859,10 +869,10 @@ mod test { ..Block::::default() }; - // RPC 4: eth_getBlockByNumber from the estimate_eip1559_fees_default + // RPC 3: eth_getBlockByNumber from the estimate_eip1559_fees_default mock_provider.push(latest_block.clone()).unwrap(); - // RPC 3: eth_getBlockByNumber from the fill_tx_gas_params call in process_contract_call + // RPC 2: eth_getBlockByNumber from the fill_tx_gas_params call in process_contract_call // to get the latest block gas limit and for eip 1559 fee estimation mock_provider.push(latest_block).unwrap(); diff --git a/rust/main/chains/hyperlane-ethereum/src/lib.rs b/rust/main/chains/hyperlane-ethereum/src/lib.rs index 1637f03fd6d..1dfb7f5aeb5 100644 --- a/rust/main/chains/hyperlane-ethereum/src/lib.rs +++ b/rust/main/chains/hyperlane-ethereum/src/lib.rs @@ -3,6 +3,7 @@ #![forbid(unsafe_code)] #![warn(missing_docs)] #![deny(clippy::unwrap_used, clippy::panic)] +#![deny(clippy::arithmetic_side_effects)] use std::collections::HashMap; @@ -21,7 +22,7 @@ mod rpc_clients; mod signer; mod tx; -#[allow(clippy::unwrap_used)] +#[allow(clippy::arithmetic_side_effects, clippy::unwrap_used)] mod contracts; #[allow(clippy::unwrap_used)] mod interfaces; diff --git a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/error.rs b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/error.rs index 0975e975cc1..b109bef415d 100644 --- a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/error.rs +++ b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/error.rs @@ -14,10 +14,7 @@ pub fn decode_revert_reason(return_data: &Bytes) -> Option { // Manually decode the ABI-encoded string // Equivalent to ethers.js: ethers.utils.defaultAbiCoder.decode(['string'], data) - match ::decode(data) { - Ok(reason) => Some(reason), - Err(_) => None, - } + ::decode(data).ok() } else { None } diff --git a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/fallback.rs b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/fallback.rs index 908112e5c75..67c1e47a6fa 100644 --- a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/fallback.rs +++ b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/fallback.rs @@ -24,9 +24,10 @@ const METHOD_SEND_RAW_TRANSACTION: &str = "eth_sendRawTransaction"; /// Wrapper of `FallbackProvider` for use in `hyperlane-ethereum` /// The wrapper uses two distinct strategies to place requests to chains: /// 1. multicast - the request will be sent to all the providers simultaneously and the first -/// successful response will be used. +/// successful response will be used. +/// /// 2. fallback - the request will be sent to each provider one by one according to their -/// priority and the priority will be updated depending on success/failure. +/// priority and the priority will be updated depending on success/failure. /// /// Multicast strategy is used to submit transactions into the chain, namely with RPC method /// `eth_sendRawTransaction` while fallback strategy is used for all the other RPC methods. @@ -145,8 +146,8 @@ where // future which visits all providers as they fulfill their requests let mut unordered = self.populate_unordered_future(method, ¶ms); - while let Some(resp) = unordered.next().await { - let value = match categorize_client_response(method, resp) { + while let Some((provider_host, resp)) = unordered.next().await { + let value = match categorize_client_response(provider_host.as_str(), method, resp) { IsOk(v) => serde_json::from_value(v)?, RetryableErr(e) | RateLimitErr(e) => { retryable_errors.push(e.into()); @@ -161,7 +162,10 @@ where // if we are here, it means one of the providers returned a successful result if !retryable_errors.is_empty() || !non_retryable_errors.is_empty() { // we log a warning if we got errors from failed providers - warn!(errors_count=?(retryable_errors.len() + non_retryable_errors.len()), ?retryable_errors, ?non_retryable_errors, providers=?self.inner.providers, "multicast_request"); + let errors_count = retryable_errors + .len() + .saturating_add(non_retryable_errors.len()); + warn!(errors_count, ?retryable_errors, ?non_retryable_errors, providers=?self.inner.providers, "multicast_request"); } return Ok(value); @@ -198,7 +202,7 @@ where for (idx, priority) in priorities_snapshot.iter().enumerate() { let provider = &self.inner.providers[priority.index]; let fut = Self::provider_request(provider, method, ¶ms); - let resp = fut.await; + let (provider_host, resp) = fut.await; self.handle_stalled_provider(priority, provider).await; if resp.is_err() { self.handle_failed_provider(priority).await; @@ -206,10 +210,12 @@ where tracing::debug!( fallback_count = idx, provider_index = priority.index, + provider_host = provider_host.as_str(), + method, "fallback_request" ); - match categorize_client_response(method, resp) { + match categorize_client_response(provider_host.as_str(), method, resp) { IsOk(v) => return Ok(serde_json::from_value(v)?), RetryableErr(e) | RateLimitErr(e) => errors.push(e.into()), NonRetryableErr(e) => return Err(e.into()), @@ -224,18 +230,22 @@ where provider: &'a C, method: &'a str, params: &'a Value, - ) -> Result { - match params { + ) -> (String, Result) { + let provider_host = provider.node_host().to_owned(); + let result = match params { Value::Null => provider.request(method, ()).await, _ => provider.request(method, params).await, - } + }; + + (provider_host, result) } fn populate_unordered_future<'a>( &'a self, method: &'a str, params: &'a Value, - ) -> FuturesUnordered> + Sized + '_> { + ) -> FuturesUnordered)> + Sized + 'a> + { let unordered = FuturesUnordered::new(); self.inner .providers diff --git a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/fallback/tests.rs b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/fallback/tests.rs index eea08be7385..693bf04cb61 100644 --- a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/fallback/tests.rs +++ b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/fallback/tests.rs @@ -74,7 +74,7 @@ impl JsonRpcClient for EthereumProviderMock { impl PrometheusConfigExt for EthereumProviderMock { fn node_host(&self) -> &str { - todo!() + "test_provider_host" } fn chain_name(&self) -> &str { diff --git a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/mod.rs b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/mod.rs index 1863d841a7e..e57a48624d7 100644 --- a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/mod.rs +++ b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/mod.rs @@ -1,5 +1,5 @@ use ethers::providers::HttpClientError; -use tracing::{error, info, trace, warn}; +use tracing::{debug, error, trace, warn}; pub use self::{fallback::*, provider::*, retrying::*, trait_builder::*}; pub use error::decode_revert_reason; @@ -38,6 +38,7 @@ const METHODS_TO_NOT_RETRY_ON_INSUFFICIENT_FUNDS: &[&str] = /// /// Caller is responsible for adding a log span with additional context. fn categorize_client_response( + provider_host: &str, method: &str, resp: Result, ) -> CategorizedResponse { @@ -45,19 +46,19 @@ fn categorize_client_response( use HttpClientError::*; match resp { Ok(res) => { - trace!("Received Ok response from http client"); + trace!(provider_host, "Received Ok response from http client"); IsOk(res) } Err(ReqwestError(e)) => { - warn!(error=%e, "ReqwestError in http provider"); + warn!(provider_host, error=%e, "ReqwestError in http provider"); RetryableErr(ReqwestError(e)) } Err(SerdeJson { err, text }) => { if text.contains("429") { - warn!(error=%err, text, "Received rate limit request SerdeJson error in http provider"); + debug!(provider_host, error=%err, text, "Received rate limit request SerdeJson error in http provider"); RateLimitErr(SerdeJson { err, text }) } else { - warn!(error=%err, text, "SerdeJson error in http provider"); + warn!(provider_host, error=%err, text, "SerdeJson error in http provider"); RetryableErr(SerdeJson { err, text }) } } @@ -68,7 +69,7 @@ fn categorize_client_response( || msg.contains("rate limit") || msg.contains("too many requests") { - info!(error=%e, "Received rate limit request JsonRpcError in http provider"); + debug!(provider_host, error=%e, "Received rate limit request JsonRpcError in http provider"); RateLimitErr(JsonRpcError(e)) } else if METHODS_TO_NOT_RETRY.contains(&method) || (METHOD_TO_NOT_RETRY_WHEN_NOT_SUPPORTED.contains(&method) @@ -88,12 +89,12 @@ fn categorize_client_response( // We don't want to retry errors that are probably not going to work if we keep // retrying them or that indicate an error in higher-order logic and not // transient provider (connection or other) errors. - error!(error=%e, "Non-retryable JsonRpcError in http provider"); + error!(provider_host, error=%e, "Non-retryable JsonRpcError in http provider"); NonRetryableErr(JsonRpcError(e)) } else { // the assumption is this is not a "provider error" but rather an invalid // request, e.g. nonce too low, not enough gas, ... - warn!(error=%e, "Retryable JsonRpcError in http provider"); + warn!(provider_host, error=%e, "Retryable JsonRpcError in http provider"); RetryableErr(JsonRpcError(e)) } } diff --git a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/provider.rs b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/provider.rs index 8f785da0255..43ed954726e 100644 --- a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/provider.rs +++ b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/provider.rs @@ -275,6 +275,7 @@ where .map(Into::into) } + #[instrument(skip(self), ret)] async fn fee_history( &self, block_count: U256, diff --git a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/retrying.rs b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/retrying.rs index cf8b59cba65..058e5ca4fd5 100644 --- a/rust/main/chains/hyperlane-ethereum/src/rpc_clients/retrying.rs +++ b/rust/main/chains/hyperlane-ethereum/src/rpc_clients/retrying.rs @@ -89,10 +89,12 @@ where let params = serde_json::to_value(params).expect("valid"); let mut last_err = None; - let mut i = 1; + let mut i: u32 = 1; loop { let mut rate_limited = false; - let backoff_ms = self.base_retry_ms * 2u64.pow(i - 1); + let backoff_ms = self + .base_retry_ms + .saturating_mul(2u64.saturating_pow(i.saturating_sub(1))); if let Some(ref last_err) = last_err { // `last_err` is always expected to be `Some` if `i > 1` warn!(attempt = i, ?last_err, "Dispatching request"); @@ -120,7 +122,7 @@ where } } - i += 1; + i = i.saturating_add(1); if i <= self.max_requests { let backoff_ms = if rate_limited { backoff_ms.max(20 * 1000) // 20s timeout @@ -187,7 +189,8 @@ impl JsonRpcClient for RetryingProvider> { ) .entered(); - match categorize_client_response(method, res) { + let provider_host = self.inner.node_host(); + match categorize_client_response(provider_host, method, res) { IsOk(res) => Accept(res), RetryableErr(e) => Retry(e), RateLimitErr(e) => RateLimitedRetry(e), diff --git a/rust/main/chains/hyperlane-ethereum/src/signer/mod.rs b/rust/main/chains/hyperlane-ethereum/src/signer/mod.rs index 242d95f7685..985d7d8116d 100644 --- a/rust/main/chains/hyperlane-ethereum/src/signer/mod.rs +++ b/rust/main/chains/hyperlane-ethereum/src/signer/mod.rs @@ -95,7 +95,7 @@ impl HyperlaneSigner for Signers { let mut signature = Signer::sign_message(self, hash) .await .map_err(|err| HyperlaneSignerError::from(Box::new(err) as Box<_>))?; - signature.v = 28 - (signature.v % 2); + signature.v = 28u64.saturating_sub(signature.v.wrapping_rem(2)); Ok(signature.into()) } } diff --git a/rust/main/chains/hyperlane-ethereum/src/signer/singleton.rs b/rust/main/chains/hyperlane-ethereum/src/signer/singleton.rs index 7925d1ec9c7..ad265e19f2f 100644 --- a/rust/main/chains/hyperlane-ethereum/src/signer/singleton.rs +++ b/rust/main/chains/hyperlane-ethereum/src/signer/singleton.rs @@ -103,7 +103,7 @@ impl SingletonSigner { if retries == 0 { break Err(err); } - retries -= 1; + retries = retries.saturating_sub(1); } } }; diff --git a/rust/main/chains/hyperlane-ethereum/src/tx.rs b/rust/main/chains/hyperlane-ethereum/src/tx.rs index 32c4ccc08a7..c4e39519768 100644 --- a/rust/main/chains/hyperlane-ethereum/src/tx.rs +++ b/rust/main/chains/hyperlane-ethereum/src/tx.rs @@ -2,6 +2,7 @@ use std::str::FromStr; use std::sync::Arc; use std::time::Duration; +use ethers::contract::Lazy; use ethers::types::transaction::eip2718::TypedTransaction; use ethers::{ abi::Detokenize, @@ -10,7 +11,7 @@ use ethers::{ types::{Block, Eip1559TransactionRequest, TxHash}, }; use ethers_contract::builders::ContractCall; -use ethers_core::types::H160; +use ethers_core::types::{FeeHistory, H160}; use ethers_core::{ types::{BlockNumber, U256 as EthersU256}, utils::{ @@ -18,6 +19,7 @@ use ethers_core::{ EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE, }, }; +use futures_util::future::join_all; use tokio::sync::Mutex; use tokio::try_join; use tracing::{debug, error, info, instrument, warn}; @@ -37,6 +39,14 @@ pub const DEFAULT_GAS_LIMIT_MULTIPLIER_DENOMINATOR: u32 = 10; pub const PENDING_TX_TIMEOUT_SECS: u64 = 90; +// We have 2 to 4 multiples of the default percentile, and we limit it to 100% percentile. +static PERCENTILES: Lazy> = Lazy::new(|| { + (2..5) + .map(|m| EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE * m as f64) + .filter(|p| *p <= 100.0) + .collect::>() +}); + pub fn apply_gas_estimate_buffer(gas: U256, domain: &HyperlaneDomain) -> ChainResult { // Arbitrum Nitro chains use 2d fees are especially prone to costs increasing // by the time the transaction lands on chain, requiring a higher gas limit. @@ -401,6 +411,8 @@ where let (latest_block, fee_history) = try_join!(latest_block, fee_history)?; + let fee_history = ensure_non_empty_rewards(provider.clone(), fee_history).await?; + let base_fee_per_gas = latest_block .base_fee_per_gas .ok_or_else(|| ProviderError::CustomError("EIP-1559 not activated".into()))?; @@ -418,6 +430,71 @@ where )) } +async fn ensure_non_empty_rewards( + provider: Arc, + default_fee_history: FeeHistory, +) -> ChainResult +where + M: Middleware + 'static, +{ + if has_rewards(&default_fee_history) { + debug!(?default_fee_history, "default rewards non zero"); + return Ok(default_fee_history); + } + + let fee_history_futures = PERCENTILES + .clone() + .into_iter() + .map(|p| { + let provider = provider.clone(); + async move { + provider + .fee_history( + EIP1559_FEE_ESTIMATION_PAST_BLOCKS, + BlockNumber::Latest, + &[p], + ) + .await + .map_err(ChainCommunicationError::from_other) + } + }) + .collect::>(); + + // Results will be ordered by percentile, so we can just take the first non-empty one. + let fee_histories = join_all(fee_history_futures).await; + + debug!( + ?fee_histories, + ?PERCENTILES, + "fee history for each percentile" + ); + + // We return the first non-empty fee history. + // If all are empty, we return the default fee history which is empty at this point. + let mut chosen_fee_history = default_fee_history; + for fee_history in fee_histories { + let Ok(fee_history) = fee_history else { + continue; + }; + if has_rewards(&fee_history) { + chosen_fee_history = fee_history; + break; + } + } + + debug!(?chosen_fee_history, "chosen fee history"); + + Ok(chosen_fee_history) +} + +fn has_rewards(fee_history: &FeeHistory) -> bool { + fee_history + .reward + .iter() + .filter_map(|r| r.first()) + .any(|r| !r.is_zero()) +} + pub(crate) async fn call_with_reorg_period( call: ContractCall, provider: &M, @@ -435,8 +512,10 @@ where #[cfg(test)] mod test { + use std::str::FromStr; use std::sync::Arc; + use ethers::prelude::U256; use ethers::{ providers::{Http, Provider}, types::{ @@ -444,11 +523,33 @@ mod test { NameOrAddress, }, }; - use std::str::FromStr; + use ethers_core::types::FeeHistory; use url::Url; use crate::tx::zksync_estimate_fee; + #[test] + fn test_is_rewards_non_zero_all_zero() { + let fee_history = FeeHistory { + reward: vec![vec![U256::zero()], vec![U256::zero()]], + oldest_block: U256::zero(), + base_fee_per_gas: vec![], + gas_used_ratio: vec![], + }; + assert!(!super::has_rewards(&fee_history)); + } + + #[test] + fn test_is_rewards_non_zero_some_non_zero() { + let fee_history = FeeHistory { + reward: vec![vec![U256::zero()], vec![U256::from(1)]], + oldest_block: U256::zero(), + base_fee_per_gas: vec![], + gas_used_ratio: vec![], + }; + assert!(super::has_rewards(&fee_history)); + } + #[ignore = "Not running a flaky test requiring network"] #[tokio::test] async fn test_zksync_estimate_fees() { diff --git a/rust/main/chains/hyperlane-fuel/src/lib.rs b/rust/main/chains/hyperlane-fuel/src/lib.rs index 949387870aa..5de61580326 100644 --- a/rust/main/chains/hyperlane-fuel/src/lib.rs +++ b/rust/main/chains/hyperlane-fuel/src/lib.rs @@ -4,6 +4,7 @@ #![warn(missing_docs)] // TODO: Remove once we start filling things in #![allow(unused_variables)] +#![deny(clippy::arithmetic_side_effects)] pub use self::{ interchain_gas::*, mailbox::*, multisig_ism::*, provider::*, routing_ism::*, trait_builder::*, diff --git a/rust/main/chains/hyperlane-fuel/src/provider.rs b/rust/main/chains/hyperlane-fuel/src/provider.rs index f4c6ee1345c..a5e7a341dbc 100644 --- a/rust/main/chains/hyperlane-fuel/src/provider.rs +++ b/rust/main/chains/hyperlane-fuel/src/provider.rs @@ -123,7 +123,7 @@ impl FuelProvider { &self, range: std::ops::RangeInclusive, ) -> ChainResult<(Vec, HashMap)> { - let result_amount = range.end() - range.start() + 1; + let result_amount = range.end().saturating_add(1).saturating_sub(*range.start()); let req = PaginationRequest { cursor: Some(range.start().to_string()), results: i32::try_from(result_amount).expect("Invalid range"), diff --git a/rust/main/chains/hyperlane-radix/Cargo.toml b/rust/main/chains/hyperlane-radix/Cargo.toml new file mode 100644 index 00000000000..ea035868392 --- /dev/null +++ b/rust/main/chains/hyperlane-radix/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "hyperlane-radix" +version = "0.1.0" +edition = "2021" + +[dependencies] +async-trait = { workspace = true } +bech32 = { workspace = true } +chrono = { workspace = true } +core-api-client = { workspace = true } +crypto = { path = "../../utils/crypto" } +derive-new = { workspace = true } +futures = { workspace = true } +gateway-api-client = { workspace = true } +hex = { workspace = true } +http = { workspace = true } +hyperlane-core = { path = "../../hyperlane-core", features = ["async"] } +hyperlane-metric = { path = "../../hyperlane-metric" } +hyperlane-operation-verifier = { path = "../../applications/hyperlane-operation-verifier" } +hyperlane-warp-route = { path = "../../applications/hyperlane-warp-route" } +hyper = { workspace = true } +hyper-tls = { workspace = true } +itertools = { workspace = true } +once_cell = { workspace = true } +radix-common = { workspace = true } +radix-engine-interface = { workspace = true } +radix-transactions = { workspace = true } +regex = { workspace = true } +reqwest = "0.12.15" +ripemd = { workspace = true } +sbor = { workspace = true, features = ["serde"] } +scrypto = { workspace = true, features = ["serde"] } +scrypto-derive = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +sha2 = { workspace = true } +sha256 = { workspace = true } +thiserror = { workspace = true } +time = { workspace = true } +tokio = { workspace = true, features = [ + "rt", + "macros", + "parking_lot", + "rt-multi-thread", +] } +tokio-metrics.workspace = true +tracing = { workspace = true } +tracing-futures = { workspace = true } +url = { workspace = true } + +[dev-dependencies] +tracing-test.workspace = true diff --git a/rust/main/chains/hyperlane-radix/src/application.rs b/rust/main/chains/hyperlane-radix/src/application.rs new file mode 100644 index 00000000000..4ecdfbb306c --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/application.rs @@ -0,0 +1,3 @@ +pub use operation_verifier::RadixApplicationOperationVerifier; + +mod operation_verifier; diff --git a/rust/main/chains/hyperlane-radix/src/application/operation_verifier.rs b/rust/main/chains/hyperlane-radix/src/application/operation_verifier.rs new file mode 100644 index 00000000000..6b04512f277 --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/application/operation_verifier.rs @@ -0,0 +1,122 @@ +use std::io::Cursor; + +use async_trait::async_trait; +use derive_new::new; +use scrypto::math::Decimal; +use tracing::trace; + +use hyperlane_core::{Decode, HyperlaneMessage}; +use hyperlane_operation_verifier::{ + ApplicationOperationVerifier, ApplicationOperationVerifierReport, +}; +use hyperlane_warp_route::TokenMessage; +use ApplicationOperationVerifierReport::MalformedMessage; + +use crate::decimal_to_u256; + +const WARP_ROUTE_MARKER: &str = "/"; + +/// Application operation verifier for Radix +#[derive(new)] +pub struct RadixApplicationOperationVerifier {} + +#[async_trait] +impl ApplicationOperationVerifier for RadixApplicationOperationVerifier { + async fn verify( + &self, + app_context: &Option, + message: &HyperlaneMessage, + ) -> Option { + trace!( + ?app_context, + ?message, + "Radix application operation verifier", + ); + + Self::verify_message(app_context, message) + } +} + +impl RadixApplicationOperationVerifier { + fn verify_message( + app_context: &Option, + message: &HyperlaneMessage, + ) -> Option { + let context = match app_context { + Some(c) => c, + None => return None, + }; + + if !context.contains(WARP_ROUTE_MARKER) { + return None; + } + + // Starting from this point we assume that we are in a warp route context + + let mut reader = Cursor::new(message.body.as_slice()); + let token_message = match TokenMessage::read_from(&mut reader) { + Ok(m) => m, + Err(_) => return Some(MalformedMessage(message.clone())), + }; + + if token_message.amount() > decimal_to_u256(Decimal::MAX) { + return Some(MalformedMessage(message.clone())); + } + + None + } +} + +#[cfg(test)] +mod test { + use std::ops::Add; + + use super::*; + use hyperlane_core::Encode; + use hyperlane_core::H256; + + fn encode(token_message: TokenMessage) -> Vec { + let mut encoded = vec![]; + token_message.write_to(&mut encoded).unwrap(); + encoded + } + + #[test] + fn test_amount_not_too_big() { + let app_context = Some("H/warp-route".to_string()); + let amount = decimal_to_u256(Decimal::MAX); + + let token_message = TokenMessage::new(H256::zero(), amount, vec![]); + let encoded = encode(token_message); + let message = HyperlaneMessage { + body: encoded, + ..Default::default() + }; + + // when + let report = RadixApplicationOperationVerifier::verify_message(&app_context, &message); + + // then + assert_eq!(report, None); + } + + #[test] + fn test_amount_too_big() { + let app_context = Some("H/warp-route".to_string()); + + let amount = decimal_to_u256(Decimal::MAX).add(1); + + let token_message = TokenMessage::new(H256::zero(), amount, vec![]); + let encoded = encode(token_message); + let message = HyperlaneMessage { + body: encoded, + ..Default::default() + }; + + // when + let report = RadixApplicationOperationVerifier::verify_message(&app_context, &message); + + // then + assert_eq!(report.unwrap(), MalformedMessage(message)); + } +} diff --git a/rust/main/chains/hyperlane-radix/src/config.rs b/rust/main/chains/hyperlane-radix/src/config.rs new file mode 100644 index 00000000000..8d7f87c7171 --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/config.rs @@ -0,0 +1,39 @@ +use std::{collections::HashMap, str::FromStr}; + +use scrypto::network::NetworkDefinition; +use url::Url; + +/// Radix connection config +#[derive(Clone, Debug)] +pub struct ConnectionConf { + /// Core API endpoint + pub core: Vec, + /// Gateway API endpoint + pub gateway: Vec, + /// Network definitions + pub network: NetworkDefinition, + /// Core Headers + pub core_header: Vec>, + /// Gateway Headers + pub gateway_header: Vec>, +} + +impl ConnectionConf { + /// Returns a new Connection Config + pub fn new( + core: Vec, + gateway: Vec, + network: String, + core_header: Vec>, + gateway_header: Vec>, + ) -> Self { + let network = NetworkDefinition::from_str(&network).unwrap_or(NetworkDefinition::mainnet()); + Self { + core, + core_header, + gateway, + gateway_header, + network, + } + } +} diff --git a/rust/main/chains/hyperlane-radix/src/error.rs b/rust/main/chains/hyperlane-radix/src/error.rs new file mode 100644 index 00000000000..dd593dc7f1c --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/error.rs @@ -0,0 +1,102 @@ +use bech32::{DecodeError, EncodeError}; +use chrono::ParseError; +use core_api_client::apis::Error as CoreError; +use gateway_api_client::apis::Error as GatewayError; +use scrypto::math::ParseDecimalError; + +use hyperlane_core::ChainCommunicationError; + +use crate::EventParseError; + +/// Errors from the crates specific to the hyperlane-radix +#[derive(Debug, thiserror::Error)] +pub enum HyperlaneRadixError { + /// Reqwest Error + #[error("Reqwest error: {0}")] + ReqwestError(#[from] reqwest::Error), + /// Serde Error + #[error("Serde error: {0}")] + Serde(#[from] serde_json::Error), + /// IO Error + #[error("Io error: {0}")] + Io(#[from] std::io::Error), + /// Response Error + #[error("Response error: {0}")] + ResponseError(String), + /// Parsing Error + #[error("Parsing error: {0}")] + ParsingError(String), + /// Bech32 error + #[error("Decode error: {0}")] + DecodeError(#[from] DecodeError), + /// Bech32 error + #[error("Decode error: {0}")] + EncodeError(#[from] EncodeError), + /// Event Parse error + #[error("Event parse error: {0}")] + EventParseError(#[from] EventParseError), + /// Call Method failed + #[error("Sbor call method error: {0}")] + SborCallMethod(String), + /// SborDecode failed + #[error("Sbor decode error: {0}")] + SborDecode(String), + /// SborEncode failed + #[error("Sbor encode error: {0}")] + SborEncode(String), + /// Signer missing + #[error("Signer missing")] + SignerMissing, + /// Bech32Encode failed + #[error("Bech32 error: {0}")] + Bech32Error(String), + /// Date time error + #[error("DateTime error: {0}")] + DateTime(#[from] ParseError), + /// parse float error + #[error("Parse Decimal error: {0}")] + ParseDecimal(#[from] ParseDecimalError), + /// Other errors + #[error("{0}")] + Other(String), +} + +impl From for ChainCommunicationError { + fn from(value: HyperlaneRadixError) -> Self { + ChainCommunicationError::from_other(value) + } +} + +impl From> for HyperlaneRadixError { + fn from(value: CoreError) -> Self { + match value { + CoreError::Reqwest(err) => Self::ReqwestError(err), + CoreError::Serde(err) => Self::Serde(err), + CoreError::Io(err) => Self::Io(err), + CoreError::ResponseError(response) => Self::ResponseError(format!("{:?}", response)), + } + } +} + +impl From> for HyperlaneRadixError { + fn from(value: GatewayError) -> Self { + match value { + GatewayError::Reqwest(err) => Self::ReqwestError(err), + GatewayError::Serde(err) => Self::Serde(err), + GatewayError::Io(err) => Self::Io(err), + GatewayError::ResponseError(response) => Self::ResponseError(format!("{:?}", response)), + } + } +} + +impl From for HyperlaneRadixError { + fn from(err: sbor::DecodeError) -> Self { + Self::SborDecode(format!("{:?}", err)) + } +} + +impl From for HyperlaneRadixError { + fn from(err: sbor::EncodeError) -> Self { + Self::SborEncode(format!("{:?}", err)) + } +} diff --git a/rust/main/chains/hyperlane-radix/src/events/mod.rs b/rust/main/chains/hyperlane-radix/src/events/mod.rs new file mode 100644 index 00000000000..cdb87988aac --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/events/mod.rs @@ -0,0 +1,156 @@ +use hyperlane_core::{InterchainGasPayment, H160, H256}; +use radix_common::prelude::*; + +use crate::decimal_to_u256; + +/// EthAddress representation used by the radix contracts +#[derive(Debug, Clone, PartialEq, Eq, Copy, Sbor, Hash, PartialOrd, Ord)] +#[sbor(transparent)] +pub struct EthAddress([u8; 20]); + +impl From for H160 { + fn from(value: EthAddress) -> Self { + H160(value.0) + } +} + +impl From<&H256> for EthAddress { + fn from(value: &H256) -> Self { + let bytes = H160::from(*value); + EthAddress(bytes.0) + } +} + +impl From for EthAddress { + fn from(value: H160) -> Self { + EthAddress(value.0) + } +} + +/// Bytes32 representation used by the radix contracts +#[derive(Clone, Eq, PartialEq, Hash, Sbor, ScryptoEvent, PartialOrd, Ord, Copy, Default, Debug)] +#[sbor(transparent)] +pub struct Bytes32(pub [u8; 32]); + +impl From for H256 { + fn from(value: Bytes32) -> Self { + H256(value.0) + } +} + +impl From for Bytes32 { + fn from(value: H256) -> Self { + Bytes32(value.0) + } +} + +impl Bytes32 { + /// returns the raw slice + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } +} + +impl From<[u8; 32]> for Bytes32 { + fn from(bytes: [u8; 32]) -> Self { + Bytes32(bytes) + } +} + +/// Radix dispatch event +#[derive(ScryptoSbor, ScryptoEvent)] +pub struct DispatchEvent { + /// domain + pub destination: u32, + /// encoded recipient + pub recipient: Bytes32, + /// raw message + pub message: Vec, + /// sequence of the dispatch count, this is equal to the nonce + pub sequence: u32, +} + +/// Process event +#[derive(ScryptoSbor, ScryptoEvent)] +pub struct ProcessIdEvent { + /// encoded message id that has been processed + pub message_id: Bytes32, + /// sequence of the process + pub sequence: u32, +} + +/// Inserted into tree event +#[derive(ScryptoSbor, ScryptoEvent, Debug)] +pub struct InsertedIntoTreeEvent { + /// the message id encoded + pub id: Bytes32, + /// index of the leaf that has been inserted + pub index: u32, +} + +/// Hash representation used by the radix implementation +#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Sbor)] +#[sbor(transparent)] +pub struct Hash(pub [u8; 32]); + +/// MerkleTree that is used by the radix implementation +#[derive(ScryptoSbor, ScryptoEvent)] +pub struct MerkleTree { + /// all the branches of the tree + pub branch: [Hash; 32], + /// current amount of ingested leafs + pub count: usize, +} + +/// IsmTypes for the radix implementation +/// equal to the relayer implementation, just needed for the sbor encoding +#[derive(ScryptoSbor, ScryptoEvent)] +pub enum IsmTypes { + /// INVALID ISM + Unused, + /// Routing ISM (defers to another ISM) + Routing, + /// Aggregation ISM (aggregates multiple ISMs) + Aggregation, + /// Legacy ISM (DEPRECATED) + LegacyMultisig, + /// Merkle Proof ISM (batching and censorship resistance) + MerkleRootMultisig, + /// Message ID ISM (cheapest multisig with no batching) + MessageIdMultisig, + /// No metadata ISM (no metadata) + Null, + /// Ccip Read ISM (accepts offchain signature information) + CcipRead, +} + +/// Gas payment event +#[derive(ScryptoSbor, ScryptoEvent)] +pub struct GasPayment { + /// encoded message id + pub message_id: Bytes32, + /// destination domain + pub destination_domain: u32, + /// Gas amount in a decimal + pub gas_amount: Decimal, + /// resource payment + pub payment: Decimal, + /// resource addresses that was paid in + pub resource_address: String, + /// Sequence of the event + pub sequence: u32, +} + +impl From for InterchainGasPayment { + fn from(value: GasPayment) -> Self { + // Convert Decimal gas_amount (in attos) to whole gas units by dividing by 10^18. + // decimal_to_u256(Decimal::ONE) == 10^18, so types stay U256 / U256. + let gas_amount = decimal_to_u256(value.gas_amount) / decimal_to_u256(Decimal::ONE); + Self { + message_id: value.message_id.into(), + destination: value.destination_domain, + payment: decimal_to_u256(value.payment), + gas_amount, + } + } +} diff --git a/rust/main/chains/hyperlane-radix/src/indexer/delivery.rs b/rust/main/chains/hyperlane-radix/src/indexer/delivery.rs new file mode 100644 index 00000000000..3785d8ce156 --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/indexer/delivery.rs @@ -0,0 +1,85 @@ +use std::ops::RangeInclusive; + +use async_trait::async_trait; + +use hyperlane_core::{ + ChainResult, ContractLocator, Indexed, Indexer, LogMeta, SequenceAwareIndexer, H256, H512, +}; + +use crate::{encode_component_address, parse_process_id_event, ConnectionConf, RadixProvider}; + +/// Radix Delivery Indexer +#[derive(Debug)] +pub struct RadixDeliveryIndexer { + provider: RadixProvider, + address: String, +} + +impl RadixDeliveryIndexer { + /// New Delivery indexer instance + pub fn new( + provider: RadixProvider, + locator: &ContractLocator, + conf: &ConnectionConf, + ) -> ChainResult { + let address = encode_component_address(&conf.network, locator.address)?; + Ok(Self { address, provider }) + } +} + +#[async_trait] +impl Indexer for RadixDeliveryIndexer { + #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue + async fn fetch_logs_in_range( + &self, + range: RangeInclusive, + ) -> ChainResult, LogMeta)>> { + let events = self + .provider + .fetch_logs_in_range(&self.address, range, parse_process_id_event) + .await?; + let result = events + .into_iter() + .map(|(event, meta)| { + let id: H256 = event.message_id.into(); + let sequence = event.sequence; + (Indexed::new(id).with_sequence(sequence), meta) + }) + .collect(); + Ok(result) + } + + async fn get_finalized_block_number(&self) -> ChainResult { + Ok(self.provider.get_state_version(None).await?.try_into()?) + } + + async fn fetch_logs_by_tx_hash( + &self, + tx_hash: H512, + ) -> ChainResult, LogMeta)>> { + let events = self + .provider + .fetch_logs_by_hash(&self.address, &tx_hash, parse_process_id_event) + .await?; + let result = events + .into_iter() + .map(|(event, meta)| { + let id: H256 = event.message_id.into(); + let sequence = event.sequence; + (Indexed::new(id).with_sequence(sequence), meta) + }) + .collect(); + Ok(result) + } +} + +#[async_trait] +impl SequenceAwareIndexer for RadixDeliveryIndexer { + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { + let (sequence, state_version): (u32, u64) = self + .provider + .call_method(&self.address, "processed", None, Vec::new()) + .await?; + Ok((Some(sequence), state_version.try_into()?)) + } +} diff --git a/rust/main/chains/hyperlane-radix/src/indexer/dispatch.rs b/rust/main/chains/hyperlane-radix/src/indexer/dispatch.rs new file mode 100644 index 00000000000..ca72a954f5e --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/indexer/dispatch.rs @@ -0,0 +1,86 @@ +use std::ops::RangeInclusive; + +use async_trait::async_trait; + +use hyperlane_core::{ + ChainResult, ContractLocator, HyperlaneMessage, Indexed, Indexer, LogMeta, + SequenceAwareIndexer, H512, +}; + +use crate::{encode_component_address, parse_dispatch_event, ConnectionConf, RadixProvider}; + +/// Radix Dispatch Indexer +#[derive(Debug)] +pub struct RadixDispatchIndexer { + provider: RadixProvider, + address: String, +} + +impl RadixDispatchIndexer { + /// New Dispatch indexer instance + pub fn new( + provider: RadixProvider, + locator: &ContractLocator, + conf: &ConnectionConf, + ) -> ChainResult { + let address = encode_component_address(&conf.network, locator.address)?; + Ok(Self { address, provider }) + } +} + +#[async_trait] +impl Indexer for RadixDispatchIndexer { + #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue + async fn fetch_logs_in_range( + &self, + range: RangeInclusive, + ) -> ChainResult, LogMeta)>> { + let events = self + .provider + .fetch_logs_in_range(&self.address, range, parse_dispatch_event) + .await?; + let result = events + .into_iter() + .map(|(event, meta)| { + let message: HyperlaneMessage = event.message.into(); + let sequence = event.sequence; + (Indexed::new(message).with_sequence(sequence), meta) + }) + .collect(); + Ok(result) + } + + async fn get_finalized_block_number(&self) -> ChainResult { + Ok(self.provider.get_state_version(None).await?.try_into()?) + } + + async fn fetch_logs_by_tx_hash( + &self, + tx_hash: H512, + ) -> ChainResult, LogMeta)>> { + let events = self + .provider + .fetch_logs_by_hash(&self.address, &tx_hash, parse_dispatch_event) + .await?; + let result = events + .into_iter() + .map(|(event, meta)| { + let message: HyperlaneMessage = event.message.into(); + let sequence = event.sequence; + (Indexed::new(message).with_sequence(sequence), meta) + }) + .collect(); + Ok(result) + } +} + +#[async_trait] +impl SequenceAwareIndexer for RadixDispatchIndexer { + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { + let (sequence, state_version): (u32, u64) = self + .provider + .call_method(&self.address, "nonce", None, Vec::new()) + .await?; + Ok((Some(sequence), state_version.try_into()?)) + } +} diff --git a/rust/main/chains/hyperlane-radix/src/indexer/interchain_gas.rs b/rust/main/chains/hyperlane-radix/src/indexer/interchain_gas.rs new file mode 100644 index 00000000000..07a0d785c00 --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/indexer/interchain_gas.rs @@ -0,0 +1,112 @@ +use async_trait::async_trait; + +use hyperlane_core::{ + ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, + HyperlaneProvider, Indexed, Indexer, InterchainGasPaymaster, InterchainGasPayment, LogMeta, + SequenceAwareIndexer, H256, H512, +}; + +use crate::{encode_component_address, parse_gas_payment_event, ConnectionConf, RadixProvider}; + +/// Radix Interchain Gas Indexer +#[derive(Debug)] +pub struct RadixInterchainGasIndexer { + provider: RadixProvider, + address: String, + address_256: H256, + domain: HyperlaneDomain, +} + +impl RadixInterchainGasIndexer { + /// New Interchain Gas indexer instance + pub fn new( + provider: RadixProvider, + locator: &ContractLocator, + conf: &ConnectionConf, + ) -> ChainResult { + let address = encode_component_address(&conf.network, locator.address)?; + Ok(Self { + address, + address_256: locator.address, + provider, + domain: locator.domain.clone(), + }) + } +} + +impl HyperlaneChain for RadixInterchainGasIndexer { + fn domain(&self) -> &HyperlaneDomain { + &self.domain + } + + fn provider(&self) -> Box { + Box::new(self.provider.clone()) + } +} + +impl HyperlaneContract for RadixInterchainGasIndexer { + fn address(&self) -> H256 { + self.address_256 + } +} + +#[async_trait] +impl InterchainGasPaymaster for RadixInterchainGasIndexer {} + +#[async_trait] +impl Indexer for RadixInterchainGasIndexer { + async fn fetch_logs_in_range( + &self, + range: std::ops::RangeInclusive, + ) -> ChainResult, LogMeta)>> { + let events = self + .provider + .fetch_logs_in_range(&self.address, range, parse_gas_payment_event) + .await?; + let result = events + .into_iter() + .map(|(event, meta)| { + let sequence = event.sequence; + let gas_payment: InterchainGasPayment = event.into(); + (Indexed::new(gas_payment).with_sequence(sequence), meta) + }) + .collect(); + + Ok(result) + } + + async fn get_finalized_block_number(&self) -> ChainResult { + Ok(self.provider.get_state_version(None).await?.try_into()?) + } + + /// Fetch list of logs emitted in a transaction with the given hash. + async fn fetch_logs_by_tx_hash( + &self, + tx_hash: H512, + ) -> ChainResult, LogMeta)>> { + let events = self + .provider + .fetch_logs_by_hash(&self.address, &tx_hash, parse_gas_payment_event) + .await?; + let result = events + .into_iter() + .map(|(event, meta)| { + let sequence = event.sequence; + let gas_payment: InterchainGasPayment = event.into(); + (Indexed::new(gas_payment).with_sequence(sequence), meta) + }) + .collect(); + Ok(result) + } +} + +#[async_trait] +impl SequenceAwareIndexer for RadixInterchainGasIndexer { + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { + let (sequence, state_version): (u32, u64) = self + .provider + .call_method(&self.address, "sequence", None, Vec::new()) + .await?; + Ok((Some(sequence), state_version.try_into()?)) + } +} diff --git a/rust/main/chains/hyperlane-radix/src/indexer/merkle_tree_hook.rs b/rust/main/chains/hyperlane-radix/src/indexer/merkle_tree_hook.rs new file mode 100644 index 00000000000..281e1123b67 --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/indexer/merkle_tree_hook.rs @@ -0,0 +1,205 @@ +use std::ops::RangeInclusive; + +use async_trait::async_trait; + +use hyperlane_core::{ + accumulator::incremental::IncrementalMerkle, ChainResult, Checkpoint, CheckpointAtBlock, + ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneProvider, + IncrementalMerkleAtBlock, Indexed, Indexer, LogMeta, MerkleTreeHook, MerkleTreeInsertion, + ReorgPeriod, SequenceAwareIndexer, H256, H512, +}; + +use crate::{ + encode_component_address, parse_inserted_into_tree_event, ConnectionConf, MerkleTree, + RadixProvider, +}; + +/// Radix Merkle Tree Indexer +#[derive(Debug)] +pub struct RadixMerkleTreeIndexer { + provider: RadixProvider, + encoded_address: String, + address: H256, + domain: HyperlaneDomain, +} + +impl RadixMerkleTreeIndexer { + /// New MerkleTree indexer instance + pub fn new( + provider: RadixProvider, + locator: &ContractLocator, + conf: &ConnectionConf, + ) -> ChainResult { + let address = encode_component_address(&conf.network, locator.address)?; + Ok(Self { + address: locator.address, + encoded_address: address, + domain: locator.domain.clone(), + provider, + }) + } +} + +impl HyperlaneChain for RadixMerkleTreeIndexer { + fn domain(&self) -> &HyperlaneDomain { + &self.domain + } + + fn provider(&self) -> Box { + Box::new(self.provider.clone()) + } +} + +impl HyperlaneContract for RadixMerkleTreeIndexer { + fn address(&self) -> H256 { + self.address + } +} + +#[async_trait] +impl MerkleTreeHook for RadixMerkleTreeIndexer { + /// Return the incremental merkle tree in storage + async fn tree(&self, reorg_period: &ReorgPeriod) -> ChainResult { + let (tree, state_version) = self + .provider + .call_method::( + &self.encoded_address, + "tree", + Some(reorg_period), + Vec::new(), + ) + .await?; + + let branch = tree.branch.map(|x| H256::from_slice(&x.0)); + + let tree: IncrementalMerkle = IncrementalMerkle { + branch, + count: tree.count, + }; + + Ok(IncrementalMerkleAtBlock { + tree, + block_height: Some(state_version), + }) + } + + /// Gets the current leaf count of the merkle tree + async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult { + let (count, _) = self + .provider + .call_method::( + &self.encoded_address, + "count", + Some(reorg_period), + Vec::new(), + ) + .await?; + Ok(count) + } + + async fn latest_checkpoint( + &self, + reorg_period: &ReorgPeriod, + ) -> ChainResult { + let state_version = self.provider.get_state_version(Some(reorg_period)).await?; + self.latest_checkpoint_at_block(state_version).await + } + + async fn latest_checkpoint_at_block( + &self, + state_version: u64, + ) -> ChainResult { + let ((root, index), _) = self + .provider + .call_method_at_state::<(crate::Hash, u32)>( + &self.encoded_address, + "latest_checkpoint", + Some(state_version), + Vec::new(), + ) + .await?; + + let (domain, _): (u32, u64) = self + .provider + .call_method_at_state( + &self.encoded_address, + "local_domain", + Some(state_version), + Vec::new(), + ) + .await?; + + Ok(CheckpointAtBlock { + checkpoint: Checkpoint { + merkle_tree_hook_address: self.address, + mailbox_domain: domain, + root: H256::from_slice(&root.0), + index, + }, + block_height: Some(state_version), + }) + } +} + +#[async_trait] +impl Indexer for RadixMerkleTreeIndexer { + #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue + async fn fetch_logs_in_range( + &self, + range: RangeInclusive, + ) -> ChainResult, LogMeta)>> { + let events = self + .provider + .fetch_logs_in_range(&self.encoded_address, range, parse_inserted_into_tree_event) + .await?; + let result = events + .into_iter() + .map(|(event, meta)| { + let message: MerkleTreeInsertion = + MerkleTreeInsertion::new(event.index, event.id.into()); + let sequence = event.index; + (Indexed::new(message).with_sequence(sequence), meta) + }) + .collect(); + Ok(result) + } + + async fn get_finalized_block_number(&self) -> ChainResult { + Ok(self.provider.get_state_version(None).await?.try_into()?) + } + + async fn fetch_logs_by_tx_hash( + &self, + tx_hash: H512, + ) -> ChainResult, LogMeta)>> { + let events = self + .provider + .fetch_logs_by_hash( + &self.encoded_address, + &tx_hash, + parse_inserted_into_tree_event, + ) + .await?; + let result = events + .into_iter() + .map(|(event, meta)| { + let message: MerkleTreeInsertion = + MerkleTreeInsertion::new(event.index, event.id.into()); + let sequence = event.index; + (Indexed::new(message).with_sequence(sequence), meta) + }) + .collect(); + Ok(result) + } +} + +#[async_trait] +impl SequenceAwareIndexer for RadixMerkleTreeIndexer { + async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option, u32)> { + let (sequence, state_version): (u32, u64) = self + .provider + .call_method(&self.encoded_address, "count", None, Vec::new()) + .await?; + Ok((Some(sequence), state_version.try_into()?)) + } +} diff --git a/rust/main/chains/hyperlane-radix/src/indexer/mod.rs b/rust/main/chains/hyperlane-radix/src/indexer/mod.rs new file mode 100644 index 00000000000..58b0959f791 --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/indexer/mod.rs @@ -0,0 +1,9 @@ +mod delivery; +mod dispatch; +mod interchain_gas; +mod merkle_tree_hook; + +pub use { + delivery::RadixDeliveryIndexer, dispatch::RadixDispatchIndexer, + interchain_gas::RadixInterchainGasIndexer, merkle_tree_hook::RadixMerkleTreeIndexer, +}; diff --git a/rust/main/chains/hyperlane-radix/src/ism.rs b/rust/main/chains/hyperlane-radix/src/ism.rs new file mode 100644 index 00000000000..0425485894f --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/ism.rs @@ -0,0 +1,124 @@ +use async_trait::async_trait; +use scrypto::types::ComponentAddress; + +use hyperlane_core::{ + ChainResult, ContractLocator, Encode, HyperlaneChain, HyperlaneContract, HyperlaneDomain, + HyperlaneMessage, HyperlaneProvider, InterchainSecurityModule, ModuleType, MultisigIsm, + RoutingIsm, H160, H256, U256, +}; + +use crate::{ + address_to_h256, encode_component_address, ConnectionConf, EthAddress, IsmTypes, RadixProvider, +}; + +/// Radix ISM +#[derive(Debug)] +pub struct RadixIsm { + provider: RadixProvider, + encoded_address: String, + address_256: H256, +} + +impl RadixIsm { + /// New ISM instance + pub fn new( + provider: RadixProvider, + locator: &ContractLocator, + conf: &ConnectionConf, + ) -> ChainResult { + let encoded_address = encode_component_address(&conf.network, locator.address)?; + Ok(Self { + encoded_address, + provider, + address_256: locator.address, + }) + } +} + +impl HyperlaneContract for RadixIsm { + fn address(&self) -> H256 { + self.address_256 + } +} + +impl HyperlaneChain for RadixIsm { + fn domain(&self) -> &HyperlaneDomain { + self.provider.domain() + } + + fn provider(&self) -> Box { + Box::new(self.provider.clone()) + } +} + +#[async_trait] +impl InterchainSecurityModule for RadixIsm { + /// Returns the module type of the ISM compliant with the corresponding + /// metadata offchain fetching and onchain formatting standard. + async fn module_type(&self) -> ChainResult { + let (types, _) = self + .provider + .call_method::(&self.encoded_address, "module_type", None, Vec::new()) + .await?; + + let result = match types { + IsmTypes::Unused => ModuleType::Unused, + IsmTypes::Routing => ModuleType::Routing, + IsmTypes::Aggregation => ModuleType::Aggregation, + IsmTypes::LegacyMultisig => ModuleType::LegacyMultisig, + IsmTypes::MerkleRootMultisig => ModuleType::MerkleRootMultisig, + IsmTypes::MessageIdMultisig => ModuleType::MessageIdMultisig, + IsmTypes::Null => ModuleType::Null, + IsmTypes::CcipRead => ModuleType::CcipRead, + }; + Ok(result) + } + + /// Dry runs the `verify()` ISM call and returns `Some(gas_estimate)` if the call + /// succeeds. + async fn dry_run_verify( + &self, + _message: &HyperlaneMessage, + _metadata: &[u8], + ) -> ChainResult> { + Ok(Some(U256::one())) // NOTE: we don't need to implement this, as there is no aggregation ism and we can't save any costs + } +} + +#[async_trait] +impl MultisigIsm for RadixIsm { + /// Returns the validator and threshold needed to verify message + async fn validators_and_threshold( + &self, + message: &HyperlaneMessage, + ) -> ChainResult<(Vec, u8)> { + let message = message.to_vec(); + + let (validators, threshold): (Vec, usize) = self + .provider + .call_method_with_arg(&self.encoded_address, "validators_and_threshold", &message) + .await?; + + Ok(( + validators + .into_iter() + .map(|x| Into::::into(x).into()) // convert EthAddress -> H160 -> H256 + .collect(), + threshold as u8, + )) + } +} + +#[async_trait] +impl RoutingIsm for RadixIsm { + /// Returns the ISM needed to verify message + async fn route(&self, message: &HyperlaneMessage) -> ChainResult { + let message = message.to_vec(); + let route: ComponentAddress = self + .provider + .call_method_with_arg(&self.encoded_address, "route", &message) + .await?; + + Ok(address_to_h256(route)) + } +} diff --git a/rust/main/chains/hyperlane-radix/src/lib.rs b/rust/main/chains/hyperlane-radix/src/lib.rs new file mode 100644 index 00000000000..cb3debddf7e --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/lib.rs @@ -0,0 +1,31 @@ +//! Interacts with Radix Chains + +#![forbid(unsafe_code)] +#![warn(missing_docs)] +/// Hyperlane Application specific functionality +pub mod application; +mod config; +mod error; +mod events; +/// Hyperlane Radix indexer +pub mod indexer; +mod ism; +mod mailbox; +mod manifest; +mod parse; +mod provider; +mod signer; +mod utils; +mod validator_announce; + +pub(crate) use {events::*, parse::*, provider::*, utils::*}; + +pub use { + config::ConnectionConf, + error::HyperlaneRadixError, + ism::RadixIsm, + mailbox::{DeliveredCalldata, RadixMailbox}, + provider::{RadixProvider, RadixProviderForLander}, + signer::RadixSigner, + validator_announce::RadixValidatorAnnounce, +}; diff --git a/rust/main/chains/hyperlane-radix/src/mailbox.rs b/rust/main/chains/hyperlane-radix/src/mailbox.rs new file mode 100644 index 00000000000..8aefa444c02 --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/mailbox.rs @@ -0,0 +1,271 @@ +use std::str::FromStr; + +use async_trait::async_trait; +use core_api_client::models::{FeeSummary, TransactionStatus}; +use radix_common::manifest_args; +use radix_common::prelude::ManifestArgs; +use regex::Regex; +use scrypto::{ + address::AddressBech32Decoder, network::NetworkDefinition, prelude::manifest_encode, + types::ComponentAddress, +}; + +use hyperlane_core::{ + ChainCommunicationError, ChainResult, ContractLocator, Encode, FixedPointNumber, + HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, + Mailbox, ReorgPeriod, TxCostEstimate, TxOutcome, H256, U256, +}; +use serde::{Deserialize, Serialize}; + +use crate::{ + address_from_h256, address_to_h256, encode_component_address, Bytes32, ConnectionConf, + HyperlaneRadixError, RadixProvider, +}; + +// the number of simulate calls we do to get the necessary addresses +const NODE_DEPTH: usize = 5; + +/// Radix mailbox +#[derive(Debug)] +pub struct RadixMailbox { + provider: RadixProvider, + encoded_address: String, + address: ComponentAddress, + address_256: H256, + network: NetworkDefinition, + component_regex: Regex, +} + +impl RadixMailbox { + /// New mailbox instance + pub fn new( + provider: RadixProvider, + locator: &ContractLocator, + conf: &ConnectionConf, + ) -> ChainResult { + let encoded_address = encode_component_address(&conf.network, locator.address)?; + let address = address_from_h256(locator.address); + let component_regex = + regex::Regex::new(&format!(r"\w+_{}([a-zA-Z0-9]+)", conf.network.hrp_suffix)) + .map_err(ChainCommunicationError::from_other)?; + + Ok(Self { + address, + component_regex, + network: conf.network.clone(), + encoded_address, + provider, + address_256: locator.address, + }) + } + + async fn visible_components( + &self, + message: &[u8], + metadata: &[u8], + ) -> ChainResult<(Vec, FeeSummary)> { + let decoder = AddressBech32Decoder::new(&self.network); + let mut visible_components = Vec::new(); + let mut fee_summary = FeeSummary::default(); + + // in radix all addresses/node have to visible for a transaction to be valid + // we simulate the tx first to get the necessary addresses + for _ in 0..NODE_DEPTH { + // we need to simulate the tx multiple times to get all the necessary addresses + let result = self + .provider + .simulate_tx(|builder| { + builder.call_method( + self.address, + "process", + manifest_args!(&metadata, &message, visible_components.clone()), + ) + }) + .await?; + fee_summary = result.fee_summary; + if result.status == TransactionStatus::Succeeded { + break; + } + + // luckily there is a fixed error message if a node is not visible + // we match against that error message and extract the invisible component + let error_message = result.error_message.unwrap_or_default(); + if let Some(matched) = self.component_regex.find(&error_message) { + if let Some(component_address) = + ComponentAddress::try_from_bech32(&decoder, matched.as_str()) + { + visible_components.push(component_address); + } + } else { + // early return if the error message is caused by something else than an invisible node + return Ok((visible_components, fee_summary)); + } + } + + Ok((visible_components, fee_summary)) + } +} + +impl HyperlaneContract for RadixMailbox { + fn address(&self) -> H256 { + self.address_256 + } +} + +impl HyperlaneChain for RadixMailbox { + fn domain(&self) -> &HyperlaneDomain { + self.provider.domain() + } + + fn provider(&self) -> Box { + Box::new(self.provider.clone()) + } +} + +#[async_trait] +impl Mailbox for RadixMailbox { + /// Gets the current number of dispatched messages + /// + /// - `reorg_period` is how far behind the current block to query, if not specified + /// it will query at the latest block. + async fn count(&self, reorg_period: &ReorgPeriod) -> ChainResult { + Ok(self + .provider + .call_method::( + &self.encoded_address, + "count", + Some(reorg_period), + Vec::new(), + ) + .await? + .0) + } + + /// Fetch the status of a message + async fn delivered(&self, id: H256) -> ChainResult { + let id: Bytes32 = id.into(); + self.provider + .call_method_with_arg(&self.encoded_address, "delivered", &id) + .await + } + + /// Fetch the current default interchain security module value + async fn default_ism(&self) -> ChainResult { + let (default_ism, _) = self + .provider + .call_method::>( + &self.encoded_address, + "default_ism", + None, + Vec::new(), + ) + .await?; + match default_ism { + Some(ism) => Ok(address_to_h256(ism)), + None => Err(HyperlaneRadixError::Other("no default ism present".to_owned()).into()), + } + } + + /// Get the recipient ism address + async fn recipient_ism(&self, recipient: H256) -> ChainResult { + let recipient = address_from_h256(recipient); + + let recipient_ism: Option = self + .provider + .call_method_with_arg(&self.encoded_address, "recipient_ism", &recipient) + .await?; + match recipient_ism { + Some(ism) => Ok(address_to_h256(ism)), + None => Err(HyperlaneRadixError::Other("no recipient ism present".to_owned()).into()), + } + } + + /// Process a message with a proof against the provided signed checkpoint + async fn process( + &self, + message: &HyperlaneMessage, + metadata: &[u8], + _tx_gas_limit: Option, + ) -> ChainResult { + let message = message.to_vec(); + let metadata = metadata.to_vec(); + let (visible_components, fee_summary) = + self.visible_components(&message, &metadata).await?; + self.provider + .send_tx( + |builder| { + builder.call_method( + self.address, + "process", + manifest_args!(&metadata, &message, &visible_components), + ) + }, + Some(fee_summary), + ) + .await + } + + /// Estimate transaction costs to process a message. + async fn process_estimate_costs( + &self, + message: &HyperlaneMessage, + metadata: &[u8], + ) -> ChainResult { + let message = message.to_vec(); + let metadata = metadata.to_vec(); + let (_, summary) = self.visible_components(&message, &metadata).await?; + let total_units = + summary.execution_cost_units_consumed + summary.finalization_cost_units_consumed; + + let paid = RadixProvider::total_fee(summary)?; + let paid = if total_units == 0 { + paid + } else { + paid / total_units + }; + + // TODO: + Ok(TxCostEstimate { + gas_limit: total_units.into(), + gas_price: FixedPointNumber::from_str(&paid.to_string())?, + l2_gas_limit: None, + }) + } + + /// Get the calldata for a transaction to process a message with a proof + /// against the provided signed checkpoint + async fn process_calldata( + &self, + _message: &HyperlaneMessage, + _metadata: &[u8], + ) -> ChainResult> { + todo!() // we dont need this for now + } + + /// Data required to make a TransactionCallPreviewRequest to + /// check if a message was delivered or not on-chain. + fn delivered_calldata(&self, message_id: H256) -> ChainResult>> { + let id: Bytes32 = message_id.into(); + let encoded_arguments = manifest_encode(&id).map_err(HyperlaneRadixError::from)?; + + let calldata = DeliveredCalldata { + component_address: self.encoded_address.clone(), + method_name: "delivered".into(), + encoded_arguments, + }; + let json_val = + serde_json::to_vec(&calldata).map_err(ChainCommunicationError::JsonParseError)?; + Ok(Some(json_val)) + } +} + +/// Data required to check if a message was delivered on-chain for Radix chain +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct DeliveredCalldata { + /// Address of mailbox (already encoded) + pub component_address: String, + /// Method to call on mailbox + pub method_name: String, + /// parameters required to call method + pub encoded_arguments: Vec, +} diff --git a/rust/main/chains/hyperlane-radix/src/manifest/manifest.rtm b/rust/main/chains/hyperlane-radix/src/manifest/manifest.rtm new file mode 100644 index 00000000000..f76c7eab9d3 --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/manifest/manifest.rtm @@ -0,0 +1,49 @@ +CALL_METHOD + Address("account_rdx168nr5dwmll4k2x5apegw5dhrpejf3xac7khjhgjqyg4qddj9tg9v4d") + "lock_fee" + Decimal("0.58434484845") +; +CALL_METHOD + Address("account_rdx168nr5dwmll4k2x5apegw5dhrpejf3xac7khjhgjqyg4qddj9tg9v4d") + "create_proof_of_amount" + Address("resource_rdx1th3yr5dlydnhw0lfp6r22x5l2fj9lv3t8f0enkp7j5ttnx3e09rhna") + Decimal("1") +; +CALL_METHOD + Address("component_rdx1cr3psyfptwkktqusfg8ngtupr4wwfg32kz2xvh9tqh4c7pwkvlk2kn") + "set_price_batch" + Map( + Tuple( + Address("resource_rdx1thrvr3xfs2tarm2dl9emvs26vjqxu6mqvfgvqjne940jv0lnrrg7rw"), + Address("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + ) => Decimal("191.67045094145536"), + Tuple( + Address("resource_rdx1th88qcj5syl9ghka2g9l7tw497vy5x6zaatyvgfkwcfe8n9jt2npww"), + Address("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + ) => Decimal("887709.7375331281"), + Tuple( + Address("resource_rdx1t4upr78guuapv5ept7d7ptekk9mqhy605zgms33mcszen8l9fac8vf"), + Address("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + ) => Decimal("191.66817958469463"), + Tuple( + Address("resource_rdx1t580qxc7upat7lww4l2c4jckacafjeudxj5wpjrrct0p3e82sq4y75"), + Address("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + ) => Decimal("21385378.506144695"), + Tuple( + Address("resource_rdx1thksg5ng70g9mmy9ne7wz0sc7auzrrwy7fmgcxzel2gvp8pj0xxfmf"), + Address("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + ) => Decimal("1.148292541506036065"), + Tuple( + Address("resource_rdx1t5kmyj54jt85malva7fxdrnpvgfgs623yt7ywdaval25vrdlmnwe97"), + Address("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + ) => Decimal("0.000395318920575434"), + Tuple( + Address("resource_rdx1t5pyvlaas0ljxy0wytm5gvyamyv896m69njqdmm2stukr3xexc2up9"), + Address("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + ) => Decimal("115824.78567336412"), + Tuple( + Address("resource_rdx1t5ywq4c6nd2lxkemkv4uzt8v7x7smjcguzq5sgafwtasa6luq7fclq"), + Address("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + ) => Decimal("1.0692337686438784") + ) +; \ No newline at end of file diff --git a/rust/main/chains/hyperlane-radix/src/manifest/manifest_with_blob.rtm b/rust/main/chains/hyperlane-radix/src/manifest/manifest_with_blob.rtm new file mode 100644 index 00000000000..e767c184359 --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/manifest/manifest_with_blob.rtm @@ -0,0 +1,12 @@ +CALL_METHOD + Address("account_tdx_2_129gthzx2r70rdylg5qxpkx7lpmaqaqtex6xca2a9cfnqzpgn5av5z8") + "lock_fee" + Decimal("169.618337617485") +; +PUBLISH_PACKAGE_ADVANCED + Enum<0u8>() + None + Blob("404926490ca92c49cf024b66f0a17c4c05d222fa3800b404bca32af0b9308016") + Map() + Enum<0u8>() +; diff --git a/rust/main/chains/hyperlane-radix/src/manifest/manifest_with_withdraw.rtm b/rust/main/chains/hyperlane-radix/src/manifest/manifest_with_withdraw.rtm new file mode 100644 index 00000000000..44a776f5c3a --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/manifest/manifest_with_withdraw.rtm @@ -0,0 +1,44 @@ +CALL_METHOD + Address("account_rdx16xuf7teqapv5gzpsxf2l2yd5xnc84988qd7k5ezp3ws3qh2z4c6rp4") + "lock_fee_and_withdraw" + Decimal("10") + Address("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + Decimal("164.25883931284386108") +; +TAKE_ALL_FROM_WORKTOP + Address("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + Bucket("bucket1") +; +CALL_METHOD + Address("component_rdx1cz79xc57dpuhzd3wylnc88m3pyvfk7c5e03me2qv7x8wh9t6c3aw4g") + "swap" + Bucket("bucket1") +; +TAKE_ALL_FROM_WORKTOP + Address("resource_rdx1thrvr3xfs2tarm2dl9emvs26vjqxu6mqvfgvqjne940jv0lnrrg7rw") + Bucket("bucket2") +; +CALL_METHOD + Address("component_rdx1cqm7wcyaeuv7hj2maec65rtscfj4hzkc20kalge89xfmwus2ag4rgs") + "swap" + Bucket("bucket2") +; +TAKE_ALL_FROM_WORKTOP + Address("resource_rdx1tkff46jkeu98jgl8naxpzfkn0m0hytysxzex3l3a8m7qps49f7m45c") + Bucket("bucket3") +; +CALL_METHOD + Address("component_rdx1cz9w9kpuj5q0r3hyzl9q065q54ytwgxp22ckvfmp9g2xvjrshmg5mk") + "swap" + Bucket("bucket3") +; +ASSERT_WORKTOP_CONTAINS + Address("resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd") + Decimal("164.25883931284386108") +; +CALL_METHOD + Address("account_rdx16xuf7teqapv5gzpsxf2l2yd5xnc84988qd7k5ezp3ws3qh2z4c6rp4") + "try_deposit_batch_or_abort" + Expression("ENTIRE_WORKTOP") + Enum<0u8>() +; \ No newline at end of file diff --git a/rust/main/chains/hyperlane-radix/src/manifest/mod.rs b/rust/main/chains/hyperlane-radix/src/manifest/mod.rs new file mode 100644 index 00000000000..a1aa71cbff7 --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/manifest/mod.rs @@ -0,0 +1,152 @@ +use hyperlane_core::H256; +use radix_transactions::manifest::{ + ast::{Instruction, Value}, + lexer, parser, +}; +use scrypto::{address::AddressBech32Decoder, network::NetworkDefinition}; + +use crate::radix_address_bytes_to_h256; + +pub fn find_fee_payer_from_manifest(s: &str, network: &NetworkDefinition) -> Option { + let address_bech32_decoder = AddressBech32Decoder::new(network); + + let tokens = match lexer::tokenize(s) { + Ok(tokens) => tokens, + Err(err) => { + tracing::error!(?err, "Failed to tokenize manifest"); + return None; + } + }; + let mut instructions = match parser::Parser::new(tokens, parser::PARSER_MAX_DEPTH) { + Ok(inst) => inst, + Err(err) => { + tracing::error!(?err, "Failed to parse manifest tokens"); + return None; + } + }; + + while !instructions.is_eof() { + let instruction = match instructions.parse_instruction() { + Ok(inst) => inst, + Err(err) => { + tracing::warn!(?err, "Failed to parse instruction"); + continue; + } + }; + let Instruction::CallMethod { + address, + method_name, + .. + } = instruction.instruction + else { + continue; + }; + + // deconstruct method_name + let Value::String(method_name) = method_name.value else { + continue; + }; + + // https://docs.radixdlt.com/docs/account + // could be any of: + // - lock_fee + // - lock_fee_and_withdraw + // - lock_fee_and_withdraw_non_fungibles + // - lock_fee_from_faucet + if !method_name.starts_with("lock_fee") { + continue; + } + + // deconstruct address + let Value::Address(boxed_value) = address.value else { + continue; + }; + let Value::String(address) = boxed_value.value else { + continue; + }; + + let (_, address_bytes) = match address_bech32_decoder.validate_and_decode(&address) { + Ok(v) => v, + Err(err) => { + tracing::error!(?err, "Failed to decode address"); + continue; + } + }; + + // For some reason, radix addresses are 30 bytes instead of 32. + let address_h256 = radix_address_bytes_to_h256(&address_bytes); + return Some(address_h256); + } + None +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use crate::encode_module_address; + + use super::*; + + const TEST_MANIFEST: &str = include_str!("./manifest.rtm"); + const TEST_MANIFEST_WITH_WITHDRAW: &str = include_str!("./manifest_with_withdraw.rtm"); + const TEST_MANIFEST_WITH_BLOB: &str = include_str!("./manifest_with_blob.rtm"); + + #[tracing_test::traced_test] + #[test] + fn test_decode_manifest_lock_fee() { + let network = NetworkDefinition::mainnet(); + let fee_payer = + find_fee_payer_from_manifest(TEST_MANIFEST, &network).expect("Fee payer not found"); + + let expected_address = + H256::from_str("0000d1e63a35dbffeb651a9d0e50ea36e30e64989bb8f5af2ba240222a06b645") + .unwrap(); + assert_eq!(fee_payer, expected_address); + + let radix_address = encode_module_address("account", &network.hrp_suffix, expected_address) + .expect("Failed to encode radix address"); + assert_eq!( + radix_address, + "account_rdx168nr5dwmll4k2x5apegw5dhrpejf3xac7khjhgjqyg4qddj9tg9v4d" + ); + } + + #[test] + fn test_decode_manifest_lock_fee_and_withdraw() { + let network = NetworkDefinition::mainnet(); + let fee_payer = find_fee_payer_from_manifest(TEST_MANIFEST_WITH_WITHDRAW, &network) + .expect("Fee payer not found"); + + let expected_address = + H256::from_str("0000d1b89f2f20e8594408303255f511b434f07a94e7037d6a64418ba1105d42") + .unwrap(); + assert_eq!(fee_payer, expected_address); + + let radix_address = encode_module_address("account", &network.hrp_suffix, expected_address) + .expect("Failed to encode radix address"); + assert_eq!( + radix_address, + "account_rdx16xuf7teqapv5gzpsxf2l2yd5xnc84988qd7k5ezp3ws3qh2z4c6rp4" + ); + } + + #[test] + fn test_decode_manifest_lock_fee_with_blob() { + let network = NetworkDefinition::stokenet(); + let fee_payer = find_fee_payer_from_manifest(TEST_MANIFEST_WITH_BLOB, &network) + .expect("Fee payer not found"); + + let expected_address = + H256::from_str("00005150bb88ca1f9e3693e8a00c1b1bdf0efa0e8179368d8eaba5c266010513") + .unwrap(); + assert_eq!(fee_payer, expected_address); + + let radix_address = encode_module_address("account", &network.hrp_suffix, expected_address) + .expect("Failed to encode radix address"); + assert_eq!( + radix_address, + "account_tdx_2_129gthzx2r70rdylg5qxpkx7lpmaqaqtex6xca2a9cfnqzpgn5av5z8" + ); + } +} diff --git a/rust/main/chains/hyperlane-radix/src/parse.rs b/rust/main/chains/hyperlane-radix/src/parse.rs new file mode 100644 index 00000000000..79f1a9d3536 --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/parse.rs @@ -0,0 +1,582 @@ +use std::error::Error; +use std::fmt; + +use gateway_api_client::models::ProgrammaticScryptoSborValue; +use hex::FromHex; +use scrypto::math::Decimal; + +use hyperlane_core::ChainResult; + +use crate::events::Bytes32; +use crate::events::{DispatchEvent, InsertedIntoTreeEvent, ProcessIdEvent}; +use crate::{GasPayment, HyperlaneRadixError}; + +/// Radix event parse errors +#[derive(Debug)] +pub enum EventParseError { + /// Invalid event type + InvalidEventType(&'static str), + /// Missing field + MissingField(&'static str), + /// Missing field type + InvalidFieldType(&'static str), + /// Hex decoding error + HexDecodeError(hex::FromHexError), + /// Others + Other(String), +} + +impl fmt::Display for EventParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidEventType(expected) => { + write!(f, "Invalid event type, expected {}", expected) + } + Self::MissingField(field) => write!(f, "Missing field: {}", field), + Self::InvalidFieldType(field) => write!(f, "Invalid field type for: {}", field), + Self::HexDecodeError(e) => write!(f, "Hex decode error: {}", e), + Self::Other(msg) => write!(f, "{}", msg), + } + } +} + +impl Error for EventParseError {} + +impl From for EventParseError { + fn from(err: hex::FromHexError) -> Self { + EventParseError::HexDecodeError(err) + } +} + +/// parses an inserted into tree event from the sbor value to a usable type +pub fn parse_inserted_into_tree_event( + value: ProgrammaticScryptoSborValue, +) -> ChainResult { + match value { + ProgrammaticScryptoSborValue::Tuple(tuple) => { + // Verify the tuple represents an InsertedIntoTreeEvent + if let Some(Some(type_name)) = tuple.type_name.as_ref() { + if type_name != "InsertedIntoTreeEvent" { + return Err(Into::::into( + EventParseError::InvalidEventType("InsertedIntoTreeEvent"), + ) + .into()); + } + } + + // Extract the fields + if tuple.fields.len() != 2 { + return Err( + Into::::into(EventParseError::Other(format!( + "Expected 2 fields, got {}", + tuple.fields.len() + ))) + .into(), + ); + } + + // Parse the id field (Bytes32) + let id = match &tuple.fields[0] { + ProgrammaticScryptoSborValue::Bytes(bytes) => { + // Verify field name + if let Some(Some(field_name)) = bytes.field_name.as_ref() { + if field_name != "id" { + return Err(Into::::into( + EventParseError::InvalidFieldType("id"), + ) + .into()); + } + } + + // Decode hex string to bytes + let bytes_array = <[u8; 32]>::from_hex(&bytes.hex) + .map_err(|e| Into::::into(EventParseError::from(e)))?; + Bytes32(bytes_array) + } + _ => { + return Err(Into::::into( + EventParseError::InvalidFieldType("id"), + ) + .into()) + } + }; + + // Parse the index field (u32) + let index = match &tuple.fields[1] { + ProgrammaticScryptoSborValue::U32(u32_val) => { + // Verify field name + if let Some(Some(field_name)) = u32_val.field_name.as_ref() { + if field_name != "index" { + return Err(Into::::into( + EventParseError::InvalidFieldType("index"), + ) + .into()); + } + } + + // Parse the string value to u32 + u32_val.value.parse::().map_err(|_| { + Into::::into(EventParseError::Other(format!( + "Failed to parse index value: {}", + u32_val.value + ))) + })? + } + _ => { + return Err(Into::::into( + EventParseError::InvalidFieldType("index"), + ) + .into()) + } + }; + + Ok(InsertedIntoTreeEvent { id, index }) + } + _ => Err( + Into::::into(EventParseError::InvalidEventType("Tuple")).into(), + ), + } +} + +/// parses an dispatch event from the sbor value to a usable type +pub fn parse_dispatch_event(value: ProgrammaticScryptoSborValue) -> ChainResult { + match value { + ProgrammaticScryptoSborValue::Tuple(tuple) => { + // Verify the tuple represents a DispatchEvent + if let Some(Some(type_name)) = tuple.type_name.as_ref() { + if type_name != "DispatchEvent" { + return Err(Into::::into( + EventParseError::InvalidEventType("DispatchEvent"), + ) + .into()); + } + } + + // Extract the fields + if tuple.fields.len() != 4 { + return Err( + Into::::into(EventParseError::Other(format!( + "Expected 4 fields, got {}", + tuple.fields.len() + ))) + .into(), + ); + } + + // Parse the destination field (u32) + let destination = match &tuple.fields[0] { + ProgrammaticScryptoSborValue::U32(u32_val) => { + // Verify field name + if let Some(Some(field_name)) = u32_val.field_name.as_ref() { + if field_name != "destination" { + return Err(Into::::into( + EventParseError::InvalidFieldType("destination"), + ) + .into()); + } + } + + // Parse the string value to u32 + u32_val.value.parse::().map_err(|_| { + Into::::into(EventParseError::Other(format!( + "Failed to parse destination value: {}", + u32_val.value + ))) + })? + } + _ => { + return Err(Into::::into( + EventParseError::InvalidFieldType("destination"), + ) + .into()) + } + }; + + // Parse the recipient field (Bytes32) + let recipient = match &tuple.fields[1] { + ProgrammaticScryptoSborValue::Bytes(bytes) => { + // Verify field name + if let Some(Some(field_name)) = bytes.field_name.as_ref() { + if field_name != "recipient" { + return Err(Into::::into( + EventParseError::InvalidFieldType("recipient"), + ) + .into()); + } + } + + // Decode hex string to bytes + let bytes_array = <[u8; 32]>::from_hex(&bytes.hex) + .map_err(|e| Into::::into(EventParseError::from(e)))?; + Bytes32(bytes_array) + } + _ => { + return Err(Into::::into( + EventParseError::InvalidFieldType("recipient"), + ) + .into()) + } + }; + + // Parse the message field (Vec) + let message = match &tuple.fields[2] { + ProgrammaticScryptoSborValue::Bytes(bytes) => { + // Verify field name + if let Some(Some(field_name)) = bytes.field_name.as_ref() { + if field_name != "message" { + return Err(Into::::into( + EventParseError::InvalidFieldType("message"), + ) + .into()); + } + } + + // Decode hex string to Vec + Vec::from_hex(&bytes.hex) + .map_err(|e| Into::::into(EventParseError::from(e)))? + } + _ => { + return Err(Into::::into( + EventParseError::InvalidFieldType("message"), + ) + .into()) + } + }; + + // Parse the sequence field (u32) + let sequence = match &tuple.fields[3] { + ProgrammaticScryptoSborValue::U32(u32_val) => { + // Verify field name + if let Some(Some(field_name)) = u32_val.field_name.as_ref() { + if field_name != "sequence" { + return Err(Into::::into( + EventParseError::InvalidFieldType("sequence"), + ) + .into()); + } + } + + // Parse the string value to u32 + u32_val.value.parse::().map_err(|_| { + Into::::into(EventParseError::Other(format!( + "Failed to parse sequence value: {}", + u32_val.value + ))) + })? + } + _ => { + return Err(Into::::into( + EventParseError::InvalidFieldType("sequence"), + ) + .into()) + } + }; + + Ok(DispatchEvent { + destination, + recipient, + message, + sequence, + }) + } + _ => Err( + Into::::into(EventParseError::InvalidEventType("Tuple")).into(), + ), + } +} + +/// parses an process event from the sbor value to a usable type +pub fn parse_process_id_event(value: ProgrammaticScryptoSborValue) -> ChainResult { + match value { + ProgrammaticScryptoSborValue::Tuple(tuple) => { + // Verify the tuple represents a ProcessIdEvent + if let Some(Some(type_name)) = tuple.type_name.as_ref() { + if type_name != "ProcessIdEvent" { + return Err(Into::::into( + EventParseError::InvalidEventType("ProcessIdEvent"), + ) + .into()); + } + } + + // Extract the fields + if tuple.fields.len() != 2 { + return Err( + Into::::into(EventParseError::Other(format!( + "Expected 2 fields, got {}", + tuple.fields.len() + ))) + .into(), + ); + } + + // Parse the message_id field (Bytes32) + let message_id = match &tuple.fields[0] { + ProgrammaticScryptoSborValue::Bytes(bytes) => { + // Verify field name + if let Some(Some(field_name)) = bytes.field_name.as_ref() { + if field_name != "message_id" { + return Err(Into::::into( + EventParseError::InvalidFieldType("message_id"), + ) + .into()); + } + } + + // Decode hex string to bytes + let bytes_array = <[u8; 32]>::from_hex(&bytes.hex) + .map_err(|e| Into::::into(EventParseError::from(e)))?; + Bytes32(bytes_array) + } + _ => { + return Err(Into::::into( + EventParseError::InvalidFieldType("message_id"), + ) + .into()) + } + }; + + // Parse the sequence field (u32) + let sequence = match &tuple.fields[1] { + ProgrammaticScryptoSborValue::U32(u32_val) => { + // Verify field name + if let Some(Some(field_name)) = u32_val.field_name.as_ref() { + if field_name != "sequence" { + return Err(Into::::into( + EventParseError::InvalidFieldType("sequence"), + ) + .into()); + } + } + + // Parse the string value to u32 + u32_val.value.parse::().map_err(|_| { + Into::::into(EventParseError::Other(format!( + "Failed to parse sequence value: {}", + u32_val.value + ))) + })? + } + _ => { + return Err(Into::::into( + EventParseError::InvalidFieldType("sequence"), + ) + .into()) + } + }; + + Ok(ProcessIdEvent { + message_id, + sequence, + }) + } + _ => Err( + Into::::into(EventParseError::InvalidEventType("Tuple")).into(), + ), + } +} + +/// parses a gas payment event from the sbor value to a usable type +pub fn parse_gas_payment_event(value: ProgrammaticScryptoSborValue) -> ChainResult { + match value { + ProgrammaticScryptoSborValue::Tuple(tuple) => { + // Verify the tuple represents a GasPayment + if let Some(Some(type_name)) = tuple.type_name.as_ref() { + if type_name != "GasPayment" { + return Err(Into::::into( + EventParseError::InvalidEventType("GasPayment"), + ) + .into()); + } + } + + // Extract the fields + if tuple.fields.len() != 6 { + return Err( + Into::::into(EventParseError::Other(format!( + "Expected 6 fields, got {}", + tuple.fields.len() + ))) + .into(), + ); + } + + // Parse the message_id field (Bytes32) + let message_id = match &tuple.fields[0] { + ProgrammaticScryptoSborValue::Bytes(bytes) => { + // Verify field name + if let Some(Some(field_name)) = bytes.field_name.as_ref() { + if field_name != "message_id" { + return Err(Into::::into( + EventParseError::InvalidFieldType("message_id"), + ) + .into()); + } + } + + // Decode hex string to bytes + let bytes_array = <[u8; 32]>::from_hex(&bytes.hex) + .map_err(|e| Into::::into(EventParseError::from(e)))?; + Bytes32(bytes_array) + } + _ => { + return Err(Into::::into( + EventParseError::InvalidFieldType("message_id"), + ) + .into()) + } + }; + + // Parse the destination_domain field (u32) + let destination_domain = match &tuple.fields[1] { + ProgrammaticScryptoSborValue::U32(u32_val) => { + // Verify field name + if let Some(Some(field_name)) = u32_val.field_name.as_ref() { + if field_name != "destination_domain" { + return Err(Into::::into( + EventParseError::InvalidFieldType("destination_domain"), + ) + .into()); + } + } + + // Parse the string value to u32 + u32_val.value.parse::().map_err(|_| { + Into::::into(EventParseError::Other(format!( + "Failed to parse destination_domain value: {}", + u32_val.value + ))) + })? + } + _ => { + return Err(Into::::into( + EventParseError::InvalidFieldType("destination_domain"), + ) + .into()) + } + }; + + // Parse the gas_amount field (Decimal) + let gas_amount = match &tuple.fields[2] { + ProgrammaticScryptoSborValue::Decimal(decimal_val) => { + // Verify field name + if let Some(Some(field_name)) = decimal_val.field_name.as_ref() { + if field_name != "gas_amount" { + return Err(Into::::into( + EventParseError::InvalidFieldType("gas_amount"), + ) + .into()); + } + } + + // Parse the string value to Decimal + decimal_val.value.parse::().map_err(|_| { + Into::::into(EventParseError::Other(format!( + "Failed to parse gas_amount value: {}", + decimal_val.value + ))) + })? + } + _ => { + return Err(Into::::into( + EventParseError::InvalidFieldType("gas_amount"), + ) + .into()) + } + }; + + // Parse the payment field (Decimal) + let payment = match &tuple.fields[3] { + ProgrammaticScryptoSborValue::Decimal(decimal_val) => { + // Verify field name + if let Some(Some(field_name)) = decimal_val.field_name.as_ref() { + if field_name != "payment" { + return Err(Into::::into( + EventParseError::InvalidFieldType("payment"), + ) + .into()); + } + } + + // Parse the string value to Decimal + decimal_val.value.parse::().map_err(|_| { + Into::::into(EventParseError::Other(format!( + "Failed to parse payment value: {}", + decimal_val.value + ))) + })? + } + _ => { + return Err(Into::::into( + EventParseError::InvalidFieldType("payment"), + ) + .into()) + } + }; + + // Parse the resource_address field (ResourceAddress) + let resource_address = match &tuple.fields[4] { + ProgrammaticScryptoSborValue::Reference(custom_val) => { + // Verify field name + if let Some(Some(field_name)) = custom_val.field_name.as_ref() { + if field_name != "resource_address" { + return Err(Into::::into( + EventParseError::InvalidFieldType("resource_address"), + ) + .into()); + } + } + + custom_val.value.clone() + } + _ => { + return Err(Into::::into( + EventParseError::InvalidFieldType("resource_address"), + ) + .into()) + } + }; + + // Parse the sequence field (u32) + let sequence = match &tuple.fields[5] { + ProgrammaticScryptoSborValue::U32(u32_val) => { + // Verify field name + if let Some(Some(field_name)) = u32_val.field_name.as_ref() { + if field_name != "sequence" { + return Err(Into::::into( + EventParseError::InvalidFieldType("sequence"), + ) + .into()); + } + } + + // Parse the string value to u32 + u32_val.value.parse::().map_err(|_| { + Into::::into(EventParseError::Other(format!( + "Failed to parse sequence value: {}", + u32_val.value + ))) + })? + } + _ => { + return Err(Into::::into( + EventParseError::InvalidFieldType("sequence"), + ) + .into()) + } + }; + + Ok(GasPayment { + message_id, + destination_domain, + gas_amount, + payment, + resource_address, + sequence, + }) + } + _ => Err( + Into::::into(EventParseError::InvalidEventType("Tuple")).into(), + ), + } +} diff --git a/rust/main/chains/hyperlane-radix/src/provider/fallback.rs b/rust/main/chains/hyperlane-radix/src/provider/fallback.rs new file mode 100644 index 00000000000..73b7088361c --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/provider/fallback.rs @@ -0,0 +1,130 @@ +use async_trait::async_trait; +use core_api_client::models::{ + NetworkStatusResponse, TransactionCallPreviewRequest, TransactionCallPreviewResponse, +}; +use derive_new::new; +use gateway_api_client::models::{ + CommittedTransactionInfo, GatewayStatusResponse, StateEntityDetailsRequest, + StateEntityDetailsResponse, StreamTransactionsRequest, StreamTransactionsResponse, + TransactionCommittedDetailsRequest, TransactionPreviewV2Request, TransactionPreviewV2Response, + TransactionStatusResponse, TransactionSubmitResponse, +}; + +use hyperlane_core::{rpc_clients::FallbackProvider, ChainResult}; + +use crate::{ + RadixBaseCoreProvider, RadixBaseGatewayProvider, RadixCoreProvider, RadixGatewayProvider, +}; + +/// Radix fallback provider +#[derive(new, Debug, Clone)] +pub struct RadixFallbackProvider { + core: FallbackProvider, + gateway: FallbackProvider, +} + +#[async_trait] +impl RadixGatewayProvider for RadixFallbackProvider { + async fn gateway_status(&self) -> ChainResult { + self.gateway + .call(|client| { + let future = async move { client.gateway_status().await }; + Box::pin(future) + }) + .await + } + async fn transaction_committed( + &self, + tx_intent: TransactionCommittedDetailsRequest, + ) -> ChainResult { + self.gateway + .call(|client| { + let tx_intent = tx_intent.clone(); + let future = async move { client.transaction_committed(tx_intent).await }; + Box::pin(future) + }) + .await + } + async fn submit_transaction(&self, tx: Vec) -> ChainResult { + self.gateway + .call(|client| { + let tx = tx.clone(); + let future = async move { client.submit_transaction(tx).await }; + Box::pin(future) + }) + .await + } + async fn transaction_preview( + &self, + request: TransactionPreviewV2Request, + ) -> ChainResult { + self.gateway + .call(|client| { + let request = request.clone(); + let future = async move { client.transaction_preview(request).await }; + Box::pin(future) + }) + .await + } + async fn stream_txs( + &self, + request: StreamTransactionsRequest, + ) -> ChainResult { + self.gateway + .call(|client| { + let request = request.clone(); + let future = async move { client.stream_txs(request).await }; + Box::pin(future) + }) + .await + } + async fn transaction_status( + &self, + intent_hash: String, + ) -> ChainResult { + self.gateway + .call(|client| { + let intent_hash = intent_hash.clone(); + let future = async move { client.transaction_status(intent_hash).await }; + Box::pin(future) + }) + .await + } + async fn entity_details( + &self, + request: StateEntityDetailsRequest, + ) -> ChainResult { + self.gateway + .call(|client| { + let request = request.clone(); + let future = async move { client.entity_details(request).await }; + Box::pin(future) + }) + .await + } +} + +#[async_trait] +impl RadixCoreProvider for RadixFallbackProvider { + async fn core_status(&self) -> ChainResult { + self.core + .call(|client| { + let future = async move { client.core_status().await }; + Box::pin(future) + }) + .await + } + + async fn call_preview( + &self, + request: TransactionCallPreviewRequest, + ) -> ChainResult { + self.core + .call(|client| { + let request = request.clone(); + let future = async move { client.call_preview(request).await }; + Box::pin(future) + }) + .await + } +} diff --git a/rust/main/chains/hyperlane-radix/src/provider/lander.rs b/rust/main/chains/hyperlane-radix/src/provider/lander.rs new file mode 100644 index 00000000000..1bc32100bdd --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/provider/lander.rs @@ -0,0 +1,31 @@ +use gateway_api_client::models::TransactionStatusResponse; +use hyperlane_core::{ChainResult, H512}; + +use crate::{DeliveredCalldata, RadixProvider}; + +/// Trait used by lander +#[async_trait::async_trait] +pub trait RadixProviderForLander: Send + Sync { + /// Get the status of a radix transaction + async fn get_tx_hash_status(&self, hash: H512) -> ChainResult; + /// Check preview call + async fn check_preview(&self, params: &DeliveredCalldata) -> ChainResult; +} + +#[async_trait::async_trait] +impl RadixProviderForLander for RadixProvider { + async fn get_tx_hash_status(&self, hash: H512) -> ChainResult { + self.get_tx_status(hash).await + } + async fn check_preview(&self, params: &DeliveredCalldata) -> ChainResult { + let resp = self + .call_method::( + ¶ms.component_address, + ¶ms.method_name, + None, + vec![params.encoded_arguments.clone()], + ) + .await?; + Ok(resp.0) + } +} diff --git a/rust/main/chains/hyperlane-radix/src/provider/mod.rs b/rust/main/chains/hyperlane-radix/src/provider/mod.rs new file mode 100644 index 00000000000..09b37d8095d --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/provider/mod.rs @@ -0,0 +1,231 @@ +use async_trait::async_trait; +use core_api_client::apis::configuration::Configuration as CoreConfig; +use core_api_client::models::{ + NetworkStatusRequest, NetworkStatusResponse, TransactionCallPreviewRequest, + TransactionCallPreviewResponse, +}; +use derive_new::new; +use gateway_api_client::apis::configuration::Configuration as GatewayConfig; +use gateway_api_client::models::{ + CommittedTransactionInfo, GatewayStatusResponse, StateEntityDetailsRequest, + StateEntityDetailsResponse, StreamTransactionsRequest, StreamTransactionsResponse, + TransactionCommittedDetailsRequest, TransactionPreviewV2Request, TransactionPreviewV2Response, + TransactionStatusRequest, TransactionStatusResponse, TransactionSubmitRequest, + TransactionSubmitResponse, +}; +use scrypto::network::NetworkDefinition; + +use hyperlane_core::rpc_clients::BlockNumberGetter; +use hyperlane_core::ChainResult; + +use crate::HyperlaneRadixError; + +mod fallback; +mod lander; +mod radix; + +pub use {fallback::RadixFallbackProvider, lander::RadixProviderForLander, radix::RadixProvider}; + +/// Base Raidx provider +/// defined the most basic methods the provider has to implement. +/// Provides abstraction over the radix providers, that allows to implement features like Fallback behaviour +#[async_trait] +pub trait RadixGatewayProvider { + /// Gateway status + async fn gateway_status(&self) -> ChainResult; + /// Get committed transaction details + async fn transaction_committed( + &self, + tx_intent: TransactionCommittedDetailsRequest, + ) -> ChainResult; + /// Submits a tx to the gateway + async fn submit_transaction(&self, tx: Vec) -> ChainResult; + /// Get a preview of the tx + async fn transaction_preview( + &self, + request: TransactionPreviewV2Request, + ) -> ChainResult; + /// Get committed tx by various filters + async fn stream_txs( + &self, + request: StreamTransactionsRequest, + ) -> ChainResult; + /// Get tx status + async fn transaction_status( + &self, + intent_hash: String, + ) -> ChainResult; + /// Get entity details + async fn entity_details( + &self, + request: StateEntityDetailsRequest, + ) -> ChainResult; +} + +#[async_trait] +/// Radix core provider +pub trait RadixCoreProvider { + /// Core status + async fn core_status(&self) -> ChainResult; + /// Call preview a contract method + async fn call_preview( + &self, + request: TransactionCallPreviewRequest, + ) -> ChainResult; +} + +/// Base Radix provider +#[derive(new, Debug, Clone)] +pub struct RadixBaseGatewayProvider { + gateway: GatewayConfig, +} + +/// Base Radix provider +#[derive(new, Debug, Clone)] +pub struct RadixBaseCoreProvider { + core: CoreConfig, + network: NetworkDefinition, +} + +#[async_trait] +impl BlockNumberGetter for RadixBaseGatewayProvider { + /// Latest block number getter + async fn get_block_number(&self) -> ChainResult { + let state = self.gateway_status().await?; + Ok(state.ledger_state.state_version as u64) + } +} + +#[async_trait] +impl BlockNumberGetter for RadixBaseCoreProvider { + /// Latest block number getter + async fn get_block_number(&self) -> ChainResult { + let state = core_api_client::apis::status_api::status_network_status_post( + &self.core, + NetworkStatusRequest { + network: self.network.logical_name.to_string(), + }, + ) + .await + .map_err(HyperlaneRadixError::from)?; + Ok(state.current_state_identifier.state_version) + } +} + +#[async_trait] +impl RadixGatewayProvider for RadixBaseGatewayProvider { + async fn gateway_status(&self) -> ChainResult { + Ok( + gateway_api_client::apis::status_api::gateway_status(&self.gateway) + .await + .map_err(HyperlaneRadixError::from)?, + ) + } + + async fn transaction_committed( + &self, + request: TransactionCommittedDetailsRequest, + ) -> ChainResult { + Ok( + gateway_api_client::apis::transaction_api::transaction_committed_details( + &self.gateway, + request, + ) + .await + .map_err(HyperlaneRadixError::from)? + .transaction, + ) + } + + async fn submit_transaction(&self, tx: Vec) -> ChainResult { + Ok( + gateway_api_client::apis::transaction_api::transaction_submit( + &self.gateway, + TransactionSubmitRequest { + notarized_transaction_hex: hex::encode(tx), + }, + ) + .await + .map_err(HyperlaneRadixError::from)?, + ) + } + + async fn transaction_preview( + &self, + request: TransactionPreviewV2Request, + ) -> ChainResult { + Ok( + gateway_api_client::apis::transaction_api::transaction_preview_v2( + &self.gateway, + request, + ) + .await + .map_err(HyperlaneRadixError::from)?, + ) + } + + async fn transaction_status( + &self, + intent_hash: String, + ) -> ChainResult { + Ok( + gateway_api_client::apis::transaction_api::transaction_status( + &self.gateway, + TransactionStatusRequest { intent_hash }, + ) + .await + .map_err(HyperlaneRadixError::from)?, + ) + } + + async fn stream_txs( + &self, + request: StreamTransactionsRequest, + ) -> ChainResult { + Ok( + gateway_api_client::apis::stream_api::stream_transactions(&self.gateway, request) + .await + .map_err(HyperlaneRadixError::from)?, + ) + } + + async fn entity_details( + &self, + request: StateEntityDetailsRequest, + ) -> ChainResult { + Ok( + gateway_api_client::apis::state_api::state_entity_details(&self.gateway, request) + .await + .map_err(HyperlaneRadixError::from)?, + ) + } +} + +#[async_trait] +impl RadixCoreProvider for RadixBaseCoreProvider { + async fn core_status(&self) -> ChainResult { + Ok( + core_api_client::apis::status_api::status_network_status_post( + &self.core, + NetworkStatusRequest { + network: self.network.logical_name.to_string(), + }, + ) + .await + .map_err(HyperlaneRadixError::from)?, + ) + } + + async fn call_preview( + &self, + request: TransactionCallPreviewRequest, + ) -> ChainResult { + Ok( + core_api_client::apis::transaction_api::transaction_call_preview_post( + &self.core, request, + ) + .await + .map_err(HyperlaneRadixError::from)?, + ) + } +} diff --git a/rust/main/chains/hyperlane-radix/src/provider/radix.rs b/rust/main/chains/hyperlane-radix/src/provider/radix.rs new file mode 100644 index 00000000000..911417c0dd2 --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/provider/radix.rs @@ -0,0 +1,814 @@ +use std::{ + ops::{Deref, RangeInclusive}, + str::FromStr, + time::Duration, +}; + +use async_trait::async_trait; +use chrono::{DateTime, Utc}; +use core_api_client::{ + apis::configuration::Configuration as CoreConfig, + models::{ + ComponentMethodTargetIdentifier, EventEmitterIdentifier, FeeSummary, TargetIdentifier, + TransactionCallPreviewRequest, TransactionReceipt, TransactionStatus, + VersionLedgerStateSelector, + }, +}; +use gateway_api_client::{ + apis::configuration::Configuration as GatewayConfig, + models::{ + self, CommittedTransactionInfo, CompiledPreviewTransaction, LedgerStateSelector, + ProgrammaticScryptoSborValue, StateEntityDetailsRequest, StreamTransactionsRequest, + TransactionCommittedDetailsRequest, TransactionDetailsOptIns, TransactionPreviewV2Request, + TransactionStatusResponse, + }, +}; +use radix_common::traits::ScryptoEvent; +use radix_transactions::{ + builder::{ + ManifestBuilder, TransactionBuilder, TransactionManifestV2Builder, TransactionV2Builder, + }, + model::{IntentHeaderV2, TransactionHeaderV2, TransactionPayload}, + signing::PrivateKey, +}; +use reqwest::ClientBuilder; +use scrypto::{ + address::AddressBech32Decoder, + constants::XRD, + crypto::IsHash, + data::{ + manifest::{manifest_encode, ManifestEncode}, + scrypto::{scrypto_decode, ScryptoSbor}, + }, + math::Decimal, + network::NetworkDefinition, + types::Epoch, +}; + +use hyperlane_core::{ + rpc_clients::FallbackProvider, BlockInfo, ChainCommunicationError, ChainInfo, ChainResult, + ContractLocator, Encode, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, LogMeta, + ReorgPeriod, TxOutcome, TxnInfo, TxnReceiptInfo, H256, H512, U256, +}; + +use crate::{ + decimal_to_u256, decode_bech32, encode_tx, manifest::find_fee_payer_from_manifest, + provider::RadixGatewayProvider, radix_address_bytes_to_h256, signer::RadixSigner, + ConnectionConf, HyperlaneRadixError, RadixBaseCoreProvider, RadixBaseGatewayProvider, + RadixCoreProvider, RadixFallbackProvider, +}; + +/// Radix provider +#[derive(Debug, Clone)] +pub struct RadixProvider { + provider: RadixFallbackProvider, + signer: Option, + conf: ConnectionConf, + domain: HyperlaneDomain, + reorg: ReorgPeriod, +} + +impl Deref for RadixProvider { + type Target = RadixFallbackProvider; + + fn deref(&self) -> &Self::Target { + &self.provider + } +} + +impl RadixProvider { + fn build_fallback_provider(conf: &ConnectionConf) -> ChainResult { + let mut gateway_provider = Vec::with_capacity(conf.gateway.len()); + for (index, url) in conf.gateway.iter().enumerate() { + let map = conf.gateway_header.get(index).cloned().unwrap_or_default(); + let header = reqwest::header::HeaderMap::try_from(&map) + .map_err(ChainCommunicationError::from_other)?; + + let client = ClientBuilder::new() + .default_headers(header) + .build() + .map_err(ChainCommunicationError::from_other)?; + + let provider = RadixBaseGatewayProvider::new(GatewayConfig { + client, + base_path: url.to_string().trim_end_matches('/').to_string(), + ..Default::default() + }); + gateway_provider.push(provider); + } + + let mut core_provider = Vec::with_capacity(conf.core.len()); + for (index, url) in conf.core.iter().enumerate() { + let map = conf.core_header.get(index).cloned().unwrap_or_default(); + let header = reqwest::header::HeaderMap::try_from(&map) + .map_err(ChainCommunicationError::from_other)?; + + let client = ClientBuilder::new() + .default_headers(header) + .build() + .map_err(ChainCommunicationError::from_other)?; + + let provider = RadixBaseCoreProvider::new( + CoreConfig { + client, + base_path: url.to_string().trim_end_matches('/').to_string(), + ..Default::default() + }, + conf.network.clone(), + ); + core_provider.push(provider); + } + Ok(RadixFallbackProvider::new( + FallbackProvider::new(core_provider), + FallbackProvider::new(gateway_provider), + )) + } + + /// Create a new Radix Provider + pub fn new( + signer: Option, + conf: &ConnectionConf, + locator: &ContractLocator, + reorg: &ReorgPeriod, + ) -> ChainResult { + Ok(Self { + domain: locator.domain.clone(), + signer, + reorg: reorg.clone(), + provider: Self::build_fallback_provider(conf)?, + conf: conf.clone(), + }) + } + + fn get_signer(&self) -> ChainResult<&RadixSigner> { + let signer = self + .signer + .as_ref() + .ok_or(HyperlaneRadixError::SignerMissing)?; + Ok(signer) + } + + /// Calls a method on a component at a specific block + pub async fn call_method_at_state( + &self, + component: &str, + method: &str, + state_version: Option, + raw_args: Vec>, + ) -> ChainResult<(T, u64)> { + let selector = state_version.map(|state_version| { + core_api_client::models::LedgerStateSelector::ByStateVersion( + VersionLedgerStateSelector { state_version }, + ) + }); + + let args = raw_args.into_iter().map(hex::encode).collect(); + + let result = self + .provider + .call_preview(TransactionCallPreviewRequest { + arguments: args, + at_ledger_state: selector, + target: TargetIdentifier::Method(ComponentMethodTargetIdentifier { + component_address: component.to_owned(), + method_name: method.to_owned(), + }), + network: self.conf.network.logical_name.to_string(), + }) + .await?; + match result.status { + TransactionStatus::Succeeded => { + if let Some(output) = result.output { + let Some(data) = output.hex else { + return Err( + HyperlaneRadixError::SborCallMethod("no output found".into()).into(), + ); + }; + let data = hex::decode(data)?; + return Ok(( + scrypto_decode::(&data).map_err(HyperlaneRadixError::from)?, + result.at_ledger_state.state_version, + )); + } + Err(HyperlaneRadixError::SborCallMethod("no output found".into()).into()) + } + _ => Err(HyperlaneRadixError::SborCallMethod(format!( + "status: {} error: {:?}", + result.status, result.error_message + )) + .into()), + } + } + + /// Calls a method on a component + pub async fn call_method( + &self, + component: &str, + method: &str, + reorg: Option<&ReorgPeriod>, + raw_args: Vec>, + ) -> ChainResult<(T, u64)> { + let state_version = match reorg { + Some(ReorgPeriod::None) => None, + Some(reorg) => Some(self.get_state_version(Some(reorg)).await?), + None => None, + }; + + self.call_method_at_state(component, method, state_version, raw_args) + .await + } + + /// Calls a method with arguments on a component + pub async fn call_method_with_arg( + &self, + component: &str, + method: &str, + argument: &A, + ) -> ChainResult { + let arguments = manifest_encode(argument).map_err(HyperlaneRadixError::from)?; + + Ok(self + .call_method::(component, method, None, vec![arguments]) + .await? + .0) + } + + /// Returns the latest ledger state of the chain + pub async fn get_state_version(&self, reorg: Option<&ReorgPeriod>) -> ChainResult { + let status = self.core_status().await?; + let state = status.current_state_identifier.state_version; + let reorg = reorg.unwrap_or(&self.reorg); + let offset = match reorg { + ReorgPeriod::None => 0, + ReorgPeriod::Blocks(blocks) => blocks.get(), + ReorgPeriod::Tag(_) => { + return Err(HyperlaneRadixError::Other( + "radix only supports blocks as reorg periods".to_owned(), + ) + .into()) + } + }; + Ok(state.saturating_sub(offset as u64)) + } + + fn filter_parsed_logs( + contract: &str, + txs: Vec, + parse: fn(ProgrammaticScryptoSborValue) -> ChainResult, + ) -> ChainResult> { + let mut events = vec![]; + + for tx in txs { + let Some(receipt) = tx.receipt else { + return Err(HyperlaneRadixError::ParsingError("receipt".to_owned()).into()); + }; + + // filter out failed transactions + if receipt.status != Some(models::TransactionStatus::CommittedSuccess) { + continue; + } + + let Some(hash) = tx.intent_hash else { + return Err(HyperlaneRadixError::ParsingError( + "failed to parse intent hash".to_owned(), + ) + .into()); + }; + let (_, hash) = bech32::decode(&hash).map_err(HyperlaneRadixError::from)?; + let hash = H256::from_slice(&hash); + let Some(raw_events) = receipt.events else { + return Err( + HyperlaneRadixError::ParsingError("events not present".to_owned()).into(), + ); + }; + + for (event_index, event) in raw_events.iter().enumerate() { + // make sure to return only events that match the params + if event.name != T::EVENT_NAME { + continue; + } + let emitter: EventEmitterIdentifier = serde_json::from_value(event.emitter.clone()) + .map_err(HyperlaneRadixError::from)?; + match emitter { + EventEmitterIdentifier::Method(method) + if method.entity.entity_address == contract => + { + let address = decode_bech32(&method.entity.entity_address)?; + + // Pad address to 32 bytes with zeros + let mut padded_address = [0u8; 32]; + let len = std::cmp::min(address.len(), 32); + padded_address[32 - len..].copy_from_slice(&address[..len]); + let address: H256 = padded_address.into(); + + let height = U256::from(tx.state_version).to_vec(); + + let meta = LogMeta { + address, + block_number: tx.state_version.try_into()?, + block_hash: H256::from_slice(&height), + transaction_id: hash.into(), + transaction_index: tx.state_version.try_into()?, // the state version is the absolute identifier for a transaction + log_index: event_index.into(), + }; + + events.push((event.data.clone(), meta)) + } + _ => continue, + } + } + } + + events + .into_iter() + .map(|(event, meta)| parse(event).map(|x| (x, meta))) + .collect::, _>>() + } + + /// Fetches events for a tx that were emitted from the given contract + pub async fn fetch_logs_by_hash( + &self, + contract: &str, + hash: &H512, + parse: fn(ProgrammaticScryptoSborValue) -> ChainResult, + ) -> ChainResult> { + let tx = self.get_tx_by_hash(hash).await?; + Self::filter_parsed_logs(contract, vec![tx], parse) + } + + /// Fetches events for a range of state versions that were emitted from the given contract + pub async fn fetch_logs_in_range( + &self, + contract: &str, + range: RangeInclusive, + parse: fn(ProgrammaticScryptoSborValue) -> ChainResult, + ) -> ChainResult> { + let txs = self + .get_raw_txs(*range.start() as u64, *range.end() as u64, Some(contract)) + .await?; + + Self::filter_parsed_logs(contract, txs, parse) + } + + /// Returns a raw radix transaction + /// instead of fetching the txs for each individual state_version + /// we start a search at the beginning state version with the corresponding filters + /// this improves the performance as there are probably very little to no relevant transactions in the given sv range + pub async fn get_tx_by_hash(&self, hash: &H512) -> ChainResult { + let hash: H256 = (*hash).into(); + let hash = encode_tx(&self.conf.network, hash)?; + let response = self + .transaction_committed(TransactionCommittedDetailsRequest { + intent_hash: hash, + opt_ins: Some(TransactionDetailsOptIns { + affected_global_entities: Some(true), + manifest_instructions: Some(true), + receipt_events: Some(true), + receipt_fee_summary: Some(true), + ..Default::default() + }), + ..Default::default() + }) + .await?; + Ok(response) + } + + /// Returns a raw radix transaction + /// instead of fetching the txs for each individual state_version + /// we start a search at the beginning state version with the corresponding filters + /// this improves the performance as there are probably very little to no relevant transactions in the given sv range + pub async fn get_raw_txs( + &self, + from_state_version: u64, + end_state_version: u64, + emitter: Option<&str>, + ) -> ChainResult> { + let selector = LedgerStateSelector { + state_version: Some(Some(from_state_version)), + ..Default::default() + }; + + let mut request = StreamTransactionsRequest::new(); + request.from_ledger_state = Some(Some(selector)); + request.event_global_emitters_filter = emitter.map(|emitter| vec![emitter.to_owned()]); + request.order = Some(gateway_api_client::models::stream_transactions_request::Order::Asc); + request.opt_ins = Some(TransactionDetailsOptIns { + receipt_events: Some(true), + ..Default::default() + }); + + let mut cursor = None; + let mut txs = vec![]; + + loop { + let response = self + .stream_txs(StreamTransactionsRequest { + cursor, + ..request.clone() + }) + .await?; + + for item in response.items { + // the cursor is open end and will go up to the most recent state version + // dismiss all the txs that are not in the specified state version range + if item.state_version as u64 > end_state_version { + return Ok(txs); + } + txs.push(item) + } + + match response.next_cursor { + Some(c) => cursor = Some(c), + None => break, + } + } + + Ok(txs) + } + + /// Returns a tx builder with header information already filled in + pub async fn get_tx_builder( + &self, + ) -> ChainResult<(TransactionV2Builder, &RadixSigner, PrivateKey)> { + let signer = self.get_signer()?; + let private_key = signer.get_signer()?; + + let epoch = self.provider.gateway_status().await?.ledger_state.epoch as u64; + let tx = TransactionBuilder::new_v2() + .transaction_header(TransactionHeaderV2 { + notary_public_key: private_key.public_key(), + notary_is_signatory: false, + tip_basis_points: 0u32, // TODO: what should we set this to? + }) + .intent_header(IntentHeaderV2 { + network_id: self.conf.network.id, + start_epoch_inclusive: Epoch::of(epoch), + end_epoch_exclusive: Epoch::of(epoch + 2), // ~5 minutes per epoch -> 10min timeout + intent_discriminator: 0u64, // TODO: do we want this to happen? This is used like a nonce + min_proposer_timestamp_inclusive: None, // TODO: discuss whether or not we want to have a time limit + max_proposer_timestamp_exclusive: None, + }); + Ok((tx, signer, private_key)) + } + + /// Returns the total Fee that was paid + pub fn total_fee(fee_summary: FeeSummary) -> ChainResult { + let execution = Decimal::try_from(fee_summary.xrd_total_execution_cost) + .map_err(HyperlaneRadixError::from)?; + let finaliztaion = Decimal::try_from(fee_summary.xrd_total_finalization_cost) + .map_err(HyperlaneRadixError::from)?; + let royalty = Decimal::try_from(fee_summary.xrd_total_royalty_cost) + .map_err(HyperlaneRadixError::from)?; + let storage_cost = Decimal::try_from(fee_summary.xrd_total_storage_cost) + .map_err(HyperlaneRadixError::from)?; + + Ok(execution + finaliztaion + royalty + storage_cost) + } + + /// Sends a tx to the gateway + /// NOTE: does not wait for inclusion + pub async fn send_tx( + &self, + build_manifest: impl Fn(TransactionManifestV2Builder) -> TransactionManifestV2Builder, + fee: Option, + ) -> ChainResult { + let (tx_builder, signer, private_key) = self.get_tx_builder().await?; + + let manifest = build_manifest(ManifestBuilder::new_v2()).build(); + let simulation = tx_builder + .clone() + .manifest(manifest) + .build_preview_transaction(vec![]) + .to_raw() + .map_err(HyperlaneRadixError::from)?; + + let simulation = match fee { + Some(summary) => summary, + None => self.simulate_raw_tx(simulation.to_vec()).await?.fee_summary, + }; + let simulated_xrd = Self::total_fee(simulation)? + * Decimal::from_str("1.5").map_err(HyperlaneRadixError::from)?; + + let tx = tx_builder + .manifest_builder(|builder| { + build_manifest(builder.lock_fee(signer.address, simulated_xrd)) + }) + .sign(&private_key) + .notarize(&private_key) + .build(); + + self.submit_transaction(tx.raw.to_vec()).await?; + + let tx_hash: H512 = + H256::from_slice(tx.transaction_hashes.transaction_intent_hash.0.as_bytes()).into(); + + // Polling delay is the total amount of seconds to wait before we call a timeout + const TIMEOUT_DELAY: u64 = 60; + const POLLING_INTERVAL: u64 = 2; + const N: usize = (TIMEOUT_DELAY / POLLING_INTERVAL) as usize; + let hash = encode_tx(&self.conf.network, tx_hash.into())?; + let mut attempt = 0; + + let status = loop { + let tx_status = self.get_tx_status(tx_hash).await?; + + match tx_status.status { + models::TransactionStatus::CommittedSuccess + | models::TransactionStatus::CommittedFailure => { + break Ok(tx_status.status); + } + models::TransactionStatus::Rejected => { + break Err(HyperlaneRadixError::Other(format!( + "Transacstion rejected: {:?}", + tx_status.error_message + ))); + } + _ => { + tracing::debug!( + current_attempt = attempt, + total_wait_seconds = attempt as u64 * POLLING_INTERVAL, + status = ?tx_status.status, + hash = hash, + "Transaction still pending, continuing to poll" + ); + // Transaction is still pending, continue polling + attempt += 1; + if attempt >= N { + return Err(HyperlaneRadixError::Other(format!( + "Transaction timed out after {} seconds", + TIMEOUT_DELAY + )) + .into()); + } + tokio::time::sleep(Duration::from_secs(POLLING_INTERVAL)).await; + continue; + } + } + }?; + + let details = self.get_txn_by_hash(&tx_hash).await?; + let gas_price = details.gas_price.unwrap_or_default().try_into()?; + + Ok(TxOutcome { + transaction_id: tx_hash, + executed: status == models::TransactionStatus::CommittedSuccess, + gas_used: details.gas_limit, + gas_price, + }) + } + + /// Sends a tx to the gateway + /// NOTE: does not wait for inclusion + pub async fn simulate_tx( + &self, + build_manifest: impl Fn(TransactionManifestV2Builder) -> TransactionManifestV2Builder, + ) -> ChainResult { + let (tx, _, _) = self.get_tx_builder().await?; + let manifest = build_manifest(ManifestBuilder::new_v2()).build(); + let tx = tx + .manifest(manifest) + .build_preview_transaction(vec![]) + .to_raw() + .map_err(HyperlaneRadixError::from)?; + + self.simulate_raw_tx(tx.to_vec()).await + } + + /// Simulates a raw tx to the gateway to be included + pub async fn simulate_raw_tx(&self, tx: Vec) -> ChainResult { + let response = self + .transaction_preview(TransactionPreviewV2Request { + flags: Some(gateway_api_client::models::PreviewFlags { + use_free_credit: Some(true), + ..Default::default() + }), + preview_transaction: gateway_api_client::models::PreviewTransaction::Compiled( + CompiledPreviewTransaction { + preview_transaction_hex: hex::encode(tx), + }, + ), + opt_ins: Some(gateway_api_client::models::TransactionPreviewV2OptIns { + core_api_receipt: Some(true), + ..Default::default() + }), + }) + .await?; + + let Some(receipt) = response.receipt else { + return Err(HyperlaneRadixError::ParsingError("receipt".to_owned()).into()); + }; + let receipt: TransactionReceipt = + serde_json::from_value(receipt).map_err(HyperlaneRadixError::from)?; + Ok(receipt) + } + + /// Returns the status of a send tx + pub async fn get_tx_status(&self, hash: H512) -> ChainResult { + let hash: H256 = hash.into(); + let hash = encode_tx(&self.conf.network, hash)?; + let response = self.transaction_status(hash).await?; + Ok(response) + } + + fn find_first_component_address( + hash: &H512, + network: &NetworkDefinition, + addresses: &[String], + ) -> Option { + let address_bech32_decoder = AddressBech32Decoder::new(network); + addresses + .iter() + .filter(|addr| addr.starts_with("component_")) + .filter_map( + |addr| match address_bech32_decoder.validate_and_decode(addr) { + Ok(s) => Some(s), + Err(err) => { + tracing::warn!(?err, ?hash, "Failed to decode component address"); + None + } + }, + ) + .map(|addr| radix_address_bytes_to_h256(&addr.1)) + .next() + } +} + +impl HyperlaneChain for RadixProvider { + /// Return the domain + fn domain(&self) -> &HyperlaneDomain { + &self.domain + } + + /// A provider for the chain + fn provider(&self) -> Box { + Box::new(self.clone()) + } +} + +#[async_trait] +impl HyperlaneProvider for RadixProvider { + /// Get block info for a given block height + async fn get_block_by_height(&self, height: u64) -> ChainResult { + // Radix doesn't have any blocks + // we will fetch TXs at the given height instead and return the resulting information from them + let tx = self + .stream_txs(StreamTransactionsRequest { + at_ledger_state: Some(Some(LedgerStateSelector { + state_version: Some(Some(height)), + ..Default::default() + })), + limit_per_page: Some(Some(1)), + ..Default::default() + }) + .await?; + + if tx.items.is_empty() { + return Err(HyperlaneRadixError::Other(format!( + "Expected at least one tx for state version: {}", + height + )) + .into()); + } + + let datetime = DateTime::parse_from_rfc3339(&tx.ledger_state.proposer_round_timestamp) + .map_err(HyperlaneRadixError::from)?; + let timestamp = datetime.with_timezone(&Utc).timestamp() as u64; + + let height_bytes = U256::from(tx.ledger_state.state_version).to_vec(); + + Ok(BlockInfo { + hash: H256::from_slice(&height_bytes), + timestamp, + number: height, + }) + } + + /// Get txn info for a given txn hash + async fn get_txn_by_hash(&self, hash: &H512) -> ChainResult { + let tx = self.get_tx_by_hash(hash).await?; + let Some(receipt) = tx.receipt else { + return Err(HyperlaneRadixError::ParsingError("receipt".to_owned()).into()); + }; + + let Some(tx_manifest) = tx.manifest_instructions else { + return Err( + HyperlaneRadixError::ParsingError("manifest_instructions".to_owned()).into(), + ); + }; + + let affected_global_entities = tx.affected_global_entities.unwrap_or_default(); + + // Radix doesn't have the concept of a single "primary" recipient of a transaction + // so its hard to who/what the "primary" entity each transaction is for. + // Instead, we just use the first component address in a transaction + let first_component_address = + Self::find_first_component_address(hash, &self.conf.network, &affected_global_entities); + + // We assume the account that locked up XRD to pay for fees is the sender of the transaction. + // If we can't find fee payer, then default to H256::zero() + let fee_payer = + find_fee_payer_from_manifest(&tx_manifest, &self.conf.network).unwrap_or_default(); + + let Some(fee_summary) = receipt.fee_summary else { + return Err( + HyperlaneRadixError::ParsingError("expected fee summary".to_owned()).into(), + ); + }; + + let fee_summary: FeeSummary = + serde_json::from_value(fee_summary).map_err(HyperlaneRadixError::from)?; + + let Some(fee_paid) = tx.fee_paid else { + return Err( + HyperlaneRadixError::ParsingError("expected fee_paid in tx".to_owned()).into(), + ); + }; + + let fee_paid = Decimal::try_from(fee_paid).map_err(HyperlaneRadixError::from)?; + let gas_limit = fee_summary.execution_cost_units_consumed + + fee_summary.finalization_cost_units_consumed; + let gas_price: Decimal = if gas_limit == 0 { + Decimal::zero() + } else { + fee_paid / gas_limit + }; + + let gas_price = decimal_to_u256(gas_price); + + Ok(TxnInfo { + hash: *hash, + gas_limit: gas_limit.into(), + max_priority_fee_per_gas: None, + max_fee_per_gas: None, + gas_price: Some(gas_price), + // TODO: double check if we need a nonce, there are no nonces in radix, we might want to use the discriminator instead + nonce: 0, + sender: fee_payer, + recipient: first_component_address, + receipt: Some(TxnReceiptInfo { + gas_used: U256::from(gas_limit), + cumulative_gas_used: gas_price, + effective_gas_price: Some(gas_price), + }), + raw_input_data: None, + }) + } + + /// Returns whether a contract exists at the provided address + async fn is_contract(&self, _address: &H256) -> ChainResult { + Ok(true) // TODO: check if the given address is a global component + } + + /// Fetch the balance of the wallet address associated with the chain provider. + async fn get_balance(&self, address: String) -> ChainResult { + let details = self + .entity_details(StateEntityDetailsRequest { + addresses: vec![address], + opt_ins: Some(models::StateEntityDetailsOptIns { + native_resource_details: Some(true), + ..Default::default() + }), + ..Default::default() + }) + .await?; + + for d in details.items { + if let Some(resources) = d.fungible_resources { + for i in resources.items { + let (address, amount) = match i { + models::FungibleResourcesCollectionItem::Global(x) => { + let amount = + Decimal::try_from(x.amount).map_err(HyperlaneRadixError::from)?; + (x.resource_address, amount) + } + models::FungibleResourcesCollectionItem::Vault(v) => { + // aggregate all the vaults amounts + let amount = v + .vaults + .items + .into_iter() + .map(|x| Decimal::try_from(x.amount)) + .collect::, _>>() + .map_err(HyperlaneRadixError::from)? + .into_iter() + .reduce(|a, b| a + b) + .unwrap_or_default(); + (v.resource_address, amount) + } + }; + let address = decode_bech32(&address)?; + if address == XRD.to_vec() { + return Ok(decimal_to_u256(amount)); + } + } + } + } + + Ok(U256::zero()) + } + + /// Fetch metrics related to this chain + async fn get_chain_metrics(&self) -> ChainResult> { + return Ok(None); + } +} diff --git a/rust/main/chains/hyperlane-radix/src/signer.rs b/rust/main/chains/hyperlane-radix/src/signer.rs new file mode 100644 index 00000000000..58a843afdef --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/signer.rs @@ -0,0 +1,46 @@ +use radix_transactions::signing::PrivateKey; +use scrypto::{crypto::Ed25519PrivateKey, types::ComponentAddress}; + +use hyperlane_core::{ChainResult, H256}; + +use crate::{address_to_h256, encode_module_address, HyperlaneRadixError}; + +#[derive(Clone, Debug)] +/// Signer for radix chain +pub struct RadixSigner { + private_key: Vec, + /// encoded address as bech32 and network prefix + pub encoded_address: String, + /// H256 address encoding + pub address_256: H256, + /// Radix struct representation + pub address: ComponentAddress, +} + +impl RadixSigner { + /// create new signer + pub fn new(private_key_bytes: Vec, suffix: String) -> ChainResult { + let private_key = Ed25519PrivateKey::from_bytes(&private_key_bytes) + .map_err(|()| HyperlaneRadixError::Other("failed to build private key".to_owned()))?; + let signer = PrivateKey::Ed25519(private_key); + let public_key = signer.public_key(); + let address = ComponentAddress::preallocated_account_from_public_key(&public_key); + let address_256 = address_to_h256(address); + let encoded_address = encode_module_address("account", &suffix, address_256)?; // TODO: there has to be a constant in radix that defines this + + Ok(Self { + private_key: private_key_bytes, + address, + encoded_address, + address_256, + }) + } + + /// Returns a radix private key primitive type that can be used to sign tx + pub fn get_signer(&self) -> ChainResult { + let private_key = Ed25519PrivateKey::from_bytes(&self.private_key) + .map_err(|()| HyperlaneRadixError::Other("failed to build private key".to_owned()))?; + let signer = PrivateKey::Ed25519(private_key); + Ok(signer) + } +} diff --git a/rust/main/chains/hyperlane-radix/src/utils.rs b/rust/main/chains/hyperlane-radix/src/utils.rs new file mode 100644 index 00000000000..0f3b1eabcfe --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/utils.rs @@ -0,0 +1,212 @@ +use bech32::{Bech32m, Hrp}; +use scrypto::{ + math::{Decimal, I192}, + network::NetworkDefinition, + types::{ComponentAddress, NodeId}, +}; + +use hyperlane_core::{ChainResult, H256, U256}; + +use crate::HyperlaneRadixError; + +/// Encodes a bytes array into a bech32 radix component address +pub fn encode_component_address(network: &NetworkDefinition, address: H256) -> ChainResult { + encode_module_address("component", &network.hrp_suffix, address) // TODO: there has to be a constant in radix that defines this +} + +/// Encodes a bytes array into a bech32 radix address +/// radix bech32 addresses always follow a certain schema: {module}_{network_prefix}_xxxxxxxxxxx +pub fn encode_module_address(module: &str, suffix: &str, address: H256) -> ChainResult { + let slice: &[u8; 32] = address.as_fixed_bytes(); + + // Take only the last 30 bytes as required for Radix component addresses + let bytes = slice[32 - NodeId::LENGTH..].to_vec(); + + let hrp = format!("{}_{}", module, suffix); + let hrp = Hrp::parse(&hrp).map_err(|e| HyperlaneRadixError::Bech32Error(format!("{}", e)))?; + let encoded = bech32::encode::(hrp, &bytes) + .map_err(|e| HyperlaneRadixError::Bech32Error(format!("{}", e)))?; + Ok(encoded) +} + +/// Encodes a bytes array into a bech32 radix address +/// radix bech32 addresses always follow a certain schema: txid_{network_prefix}_xxxxxxxxxxx +pub fn encode_tx(network: &NetworkDefinition, address: H256) -> ChainResult { + let slice: &[u8; 32] = address.as_fixed_bytes(); + + let hrp = format!("txid_{}", network.hrp_suffix); + let hrp = Hrp::parse(&hrp).map_err(|e| HyperlaneRadixError::Bech32Error(format!("{}", e)))?; + let encoded = bech32::encode::(hrp, slice) + .map_err(|e| HyperlaneRadixError::Bech32Error(format!("{}", e)))?; + Ok(encoded) +} + +/// decodes a bech32 encoded address into bytes +pub fn decode_bech32(bech32_address: &str) -> ChainResult> { + let (_, value) = bech32::decode(bech32_address).map_err(HyperlaneRadixError::from)?; + Ok(value) +} + +/// converts an internal radix address to a H256 +/// first two bytes are set to 0, as the radix address is 30 bytes long +pub fn address_to_h256(component_address: ComponentAddress) -> H256 { + let mut bytes = [0u8; 32]; + let node_bytes = component_address.as_bytes(); + bytes[2..].copy_from_slice(node_bytes); + H256::from(bytes) +} + +/// converts an internal radix address to a H256 +/// first two bytes are set to 0, as the radix address is 30 bytes long +pub fn radix_address_bytes_to_h256(value: &[u8]) -> H256 { + let mut bytes = [0u8; 32]; + bytes[2..].copy_from_slice(value); + H256::from(bytes) +} + +/// converts a H256 address to a radix component address +pub fn address_from_h256(address: H256) -> ComponentAddress { + let bytes: &[u8; 32] = address.as_fixed_bytes(); + + let mut component_bytes = [0u8; NodeId::LENGTH]; + component_bytes.copy_from_slice(&bytes[2..NodeId::LENGTH + 2]); + + ComponentAddress::new_or_panic(component_bytes) +} + +/// converts a radix decimal to a u256 +pub fn decimal_to_u256(decimal: Decimal) -> U256 { + let decimal: I192 = decimal.attos(); + if decimal.is_negative() { + U256::zero() + } else { + U256::from_little_endian(&decimal.abs().to_le_bytes()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use scrypto::constants::FAUCET; + use scrypto::math::Decimal; + use scrypto::network::NetworkDefinition; + + fn get_test_network() -> NetworkDefinition { + NetworkDefinition::mainnet() + } + + #[test] + fn test_encode_component_address() { + let network = get_test_network(); + let test_address = H256::from([0u8; 32]); + + let result = encode_component_address(&network, test_address); + assert!(result.is_ok()); + + let encoded = result.unwrap(); + assert_eq!( + encoded, + "component_rdx1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqaeq3h6" + ); + } + + #[test] + fn test_encode_module_address() { + let test_address = H256::from([0u8; 32]); + let result = encode_module_address("test", "rdx", test_address); + + assert!(result.is_ok()); + let encoded = result.unwrap(); + assert_eq!( + encoded, + "test_rdx1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqct33l3" + ) + } + + #[test] + fn test_encode_tx() { + let network = get_test_network(); + let test_address = H256::from([0u8; 32]); + + let result = encode_tx(&network, test_address); + assert!(result.is_ok()); + + let encoded = result.unwrap(); + assert_eq!( + encoded, + "txid_rdx1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq7euex0" + ) + } + + #[test] + fn test_decode_bech32_valid() { + // First encode something, then decode it + let test_address = H256::from([0u8; 32]); + let encoded = encode_module_address("test", "rdx", test_address).unwrap(); + + let result = decode_bech32(&encoded); + assert!(result.is_ok()); + + let decoded = result.unwrap(); + assert_eq!(decoded.len(), 30); // Should be 30 bytes for component address + + assert!(decoded.iter().all(|x| *x == 0)); + } + + #[test] + fn test_decode_bech32_invalid() { + let result = decode_bech32("invalid_bech32_string"); + assert!(result.is_err()); + } + + #[test] + fn test_address_to_h256() { + let component_address = FAUCET; + let h256_result = address_to_h256(component_address); + + let expected: [u8; 32] = [ + 0, 0, 192, 86, 99, 24, 198, 49, 140, 100, 247, 152, 202, 204, 99, 24, 198, 49, 140, + 247, 190, 138, 247, 138, 120, 248, 166, 49, 140, 99, 24, 198, + ]; + + assert_eq!(h256_result.0, expected); + } + + #[test] + fn test_address_from_h256() { + let test_h256 = H256::from([ + 0, 0, 192, 86, 99, 24, 198, 49, 140, 100, 247, 152, 202, 204, 99, 24, 198, 49, 140, + 247, 190, 138, 247, 138, 120, 248, 166, 49, 140, 99, 24, 198, + ]); + + let component_address = address_from_h256(test_h256); + let component_bytes = component_address.as_bytes(); + + assert_eq!(component_bytes, FAUCET.as_bytes()) + } + + #[test] + fn test_decimal_to_u256_positive() { + let decimal = Decimal::from(42); + let result = decimal_to_u256(decimal); + + assert_eq!(result, U256::from(42000000000000000000u128)) // 42 * 1e18 + } + + #[test] + fn test_decimal_to_u256_zero() { + let decimal = Decimal::ZERO; + let result = decimal_to_u256(decimal); + + assert_eq!(result, U256::zero()); + } + + #[test] + fn test_decimal_to_u256_negative() { + let decimal = Decimal::from(-42); + let result = decimal_to_u256(decimal); + + // Negative decimals should return zero + assert_eq!(result, U256::zero()); + } +} diff --git a/rust/main/chains/hyperlane-radix/src/validator_announce.rs b/rust/main/chains/hyperlane-radix/src/validator_announce.rs new file mode 100644 index 00000000000..34109854ce9 --- /dev/null +++ b/rust/main/chains/hyperlane-radix/src/validator_announce.rs @@ -0,0 +1,104 @@ +use async_trait::async_trait; +use radix_common::manifest_args; +use scrypto::data::manifest::ManifestArgs; +use scrypto::types::ComponentAddress; + +use hyperlane_core::{ + Announcement, ChainResult, ContractLocator, HyperlaneChain, HyperlaneContract, HyperlaneDomain, + HyperlaneProvider, SignedType, TxOutcome, ValidatorAnnounce, H256, U256, +}; + +use crate::{ + address_from_h256, encode_component_address, ConnectionConf, EthAddress, RadixProvider, +}; + +/// Radix Validator Announce +#[derive(Debug)] +pub struct RadixValidatorAnnounce { + provider: RadixProvider, + encoded_address: String, + address: ComponentAddress, + address_256: H256, +} + +impl RadixValidatorAnnounce { + /// New validator announce instance + pub fn new( + provider: RadixProvider, + locator: &ContractLocator, + conf: &ConnectionConf, + ) -> ChainResult { + let encoded_address = encode_component_address(&conf.network, locator.address)?; + let address = address_from_h256(locator.address); + Ok(Self { + address, + encoded_address, + provider, + address_256: locator.address, + }) + } +} + +impl HyperlaneContract for RadixValidatorAnnounce { + fn address(&self) -> H256 { + self.address_256 + } +} + +impl HyperlaneChain for RadixValidatorAnnounce { + fn domain(&self) -> &HyperlaneDomain { + self.provider.domain() + } + + fn provider(&self) -> Box { + Box::new(self.provider.clone()) + } +} + +#[async_trait] +impl ValidatorAnnounce for RadixValidatorAnnounce { + async fn get_announced_storage_locations( + &self, + validators: &[H256], + ) -> ChainResult>> { + let eth_addresses: Vec = validators.iter().map(EthAddress::from).collect(); + let storage_locations: Vec> = self + .provider + .call_method_with_arg( + &self.encoded_address, + "get_announced_storage_locations", + ð_addresses, + ) + .await?; + + Ok(storage_locations) + } + + async fn announce(&self, announcement: SignedType) -> ChainResult { + let address: EthAddress = announcement.value.validator.into(); + let location = announcement.value.storage_location; + let signature = announcement.signature.to_vec(); + self.provider + .send_tx( + |builder| { + builder.call_method( + self.address, + "announce", + manifest_args!(address, &location, &signature), + ) + }, + None, + ) + .await + } + + async fn announce_tokens_needed( + &self, + _announcement: SignedType, + _chain_signer: H256, + ) -> Option { + // TODO: check user balance. For now, just try announcing and + // allow the announce attempt to fail if there are not enough tokens. + Some(0u64.into()) + } +} diff --git a/rust/main/chains/hyperlane-sealevel/src/application/operation_verifier.rs b/rust/main/chains/hyperlane-sealevel/src/application/operation_verifier.rs index dfa8935c2eb..705e27c5b53 100644 --- a/rust/main/chains/hyperlane-sealevel/src/application/operation_verifier.rs +++ b/rust/main/chains/hyperlane-sealevel/src/application/operation_verifier.rs @@ -9,7 +9,7 @@ use solana_sdk::pubkey::Pubkey; use tracing::trace; use hyperlane_core::{ - utils::hex_or_base58_to_h256, ChainResult, Decode, HyperlaneMessage, H256, U256, + utils::hex_or_base58_or_bech32_to_h256, ChainResult, Decode, HyperlaneMessage, H256, U256, }; use hyperlane_operation_verifier::{ ApplicationOperationVerifier, ApplicationOperationVerifierReport, @@ -24,17 +24,15 @@ const NATIVE_WARP_ROUTE_PREFIX: &str = "SOL/"; lazy_static! { static ref NATIVE_WARP_ROUTES: HashSet = { HashSet::from([ - hex_or_base58_to_h256("8DtAGQpcMuD5sG3KdxDy49ydqXUggR1LQtebh2TECbAc") + hex_or_base58_or_bech32_to_h256("8DtAGQpcMuD5sG3KdxDy49ydqXUggR1LQtebh2TECbAc") .expect("Invalid sealevel address"), - hex_or_base58_to_h256("BXKDfnNkgUNVT5uCfk36sv2GDtK6RwAt9SLbGiKzZkih") + hex_or_base58_or_bech32_to_h256("BXKDfnNkgUNVT5uCfk36sv2GDtK6RwAt9SLbGiKzZkih") .expect("Invalid sealevel address"), - hex_or_base58_to_h256("7KD647mgysBeEt6PSrv2XYktkSNLzear124oaMENp8SY") + hex_or_base58_or_bech32_to_h256("7KD647mgysBeEt6PSrv2XYktkSNLzear124oaMENp8SY") .expect("Invalid sealevel address"), - hex_or_base58_to_h256("GPFwRQ5Cw6dTWnmappUKJt76DD8yawxPx28QugfCaGaA") + hex_or_base58_or_bech32_to_h256("GPFwRQ5Cw6dTWnmappUKJt76DD8yawxPx28QugfCaGaA") .expect("Invalid sealevel address"), - hex_or_base58_to_h256("4CMbJtieJ7EboZZGSbXTQjW5i2sL638jFvE3dWTYG3SK") - .expect("Invalid sealevel address"), - hex_or_base58_to_h256("EfSiyLayPpfZ7uGetjmjxYh6r3LjoJjmHkkxRJmDpgaT") + hex_or_base58_or_bech32_to_h256("4CMbJtieJ7EboZZGSbXTQjW5i2sL638jFvE3dWTYG3SK") .expect("Invalid sealevel address"), ]) }; diff --git a/rust/main/chains/hyperlane-sealevel/src/application/operation_verifier/tests.rs b/rust/main/chains/hyperlane-sealevel/src/application/operation_verifier/tests.rs index f10924df49a..df1859d109f 100644 --- a/rust/main/chains/hyperlane-sealevel/src/application/operation_verifier/tests.rs +++ b/rust/main/chains/hyperlane-sealevel/src/application/operation_verifier/tests.rs @@ -1,4 +1,6 @@ -use hyperlane_core::{utils::hex_or_base58_to_h256, Encode, HyperlaneMessage, H256, U256}; +use hyperlane_core::{ + utils::hex_or_base58_or_bech32_to_h256, Encode, HyperlaneMessage, H256, U256, +}; use hyperlane_operation_verifier::ApplicationOperationVerifierReport::{ AmountBelowMinimum, MalformedMessage, }; @@ -68,7 +70,8 @@ async fn test_message_not_native_warp_route_recipient() { // given let app_context = Some("SOL/warp-route".to_string()); let message = HyperlaneMessage { - recipient: hex_or_base58_to_h256("5dDyfdy9fannAdHEkYghgQpiPZrQPHadxBLa1WsGHPFi").unwrap(), + recipient: hex_or_base58_or_bech32_to_h256("5dDyfdy9fannAdHEkYghgQpiPZrQPHadxBLa1WsGHPFi") + .unwrap(), ..Default::default() }; let check_account_does_not_exist_and_get_minimum = |_: H256| async move { None }; @@ -90,7 +93,8 @@ async fn test_message_is_not_token_message() { // given let app_context = Some("SOL/warp-route".to_string()); let message = HyperlaneMessage { - recipient: hex_or_base58_to_h256("8DtAGQpcMuD5sG3KdxDy49ydqXUggR1LQtebh2TECbAc").unwrap(), + recipient: hex_or_base58_or_bech32_to_h256("8DtAGQpcMuD5sG3KdxDy49ydqXUggR1LQtebh2TECbAc") + .unwrap(), ..Default::default() }; let check_account_does_not_exist_and_get_minimum = |_: H256| async move { None }; @@ -113,7 +117,8 @@ async fn test_token_recipient_exists_or_communication_error() { let app_context = Some("SOL/warp-route".to_string()); let token_message = TokenMessage::new(H256::zero(), U256::one(), vec![]); let message = HyperlaneMessage { - recipient: hex_or_base58_to_h256("8DtAGQpcMuD5sG3KdxDy49ydqXUggR1LQtebh2TECbAc").unwrap(), + recipient: hex_or_base58_or_bech32_to_h256("8DtAGQpcMuD5sG3KdxDy49ydqXUggR1LQtebh2TECbAc") + .unwrap(), body: encode(token_message), ..Default::default() }; @@ -139,7 +144,8 @@ async fn test_below_minimum() { let minimum = U256::one() * 2; let token_message = TokenMessage::new(H256::zero(), amount, vec![]); let message = HyperlaneMessage { - recipient: hex_or_base58_to_h256("8DtAGQpcMuD5sG3KdxDy49ydqXUggR1LQtebh2TECbAc").unwrap(), + recipient: hex_or_base58_or_bech32_to_h256("8DtAGQpcMuD5sG3KdxDy49ydqXUggR1LQtebh2TECbAc") + .unwrap(), body: encode(token_message), ..Default::default() }; @@ -171,7 +177,8 @@ async fn test_above_minimum() { let minimum = U256::one(); let token_message = TokenMessage::new(H256::zero(), amount, vec![]); let message = HyperlaneMessage { - recipient: hex_or_base58_to_h256("8DtAGQpcMuD5sG3KdxDy49ydqXUggR1LQtebh2TECbAc").unwrap(), + recipient: hex_or_base58_or_bech32_to_h256("8DtAGQpcMuD5sG3KdxDy49ydqXUggR1LQtebh2TECbAc") + .unwrap(), body: encode(token_message), ..Default::default() }; diff --git a/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs b/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs index c2d9ed545f7..8f256eed18c 100644 --- a/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs +++ b/rust/main/chains/hyperlane-sealevel/src/interchain_gas.rs @@ -162,7 +162,11 @@ impl SealevelInterchainGasPaymasterIndexer { .map_err(ChainCommunicationError::from_other)? .into_inner(); - tracing::debug!(gas_payment_account=?gas_payment_account, "Found gas payment account"); + tracing::debug!( + gas_payment_account=?gas_payment_account, + payment_pda_pubkey=?valid_payment_pda_pubkey, + "Found gas payment account", + ); let igp_payment = InterchainGasPayment { message_id: gas_payment_account.message_id, diff --git a/rust/main/chains/hyperlane-sealevel/src/lib.rs b/rust/main/chains/hyperlane-sealevel/src/lib.rs index 04664982f53..19af9560d88 100644 --- a/rust/main/chains/hyperlane-sealevel/src/lib.rs +++ b/rust/main/chains/hyperlane-sealevel/src/lib.rs @@ -4,6 +4,7 @@ #![warn(missing_docs)] #![deny(warnings)] #![deny(clippy::unwrap_used, clippy::panic)] +#![deny(clippy::arithmetic_side_effects)] pub use crate::multisig_ism::*; pub use interchain_gas::*; diff --git a/rust/main/chains/hyperlane-sealevel/src/log_meta_composer.rs b/rust/main/chains/hyperlane-sealevel/src/log_meta_composer.rs index 8c0c232305c..5ae5db10aa7 100644 --- a/rust/main/chains/hyperlane-sealevel/src/log_meta_composer.rs +++ b/rust/main/chains/hyperlane-sealevel/src/log_meta_composer.rs @@ -138,7 +138,7 @@ pub fn is_interchain_payment_instruction(instruction_data: &[u8]) -> bool { /// * `program_id` - Identifier of program for which we are searching transactions for. /// * `pda_pubkey` - Identifier for PDA the relevant transaction should operate upon. /// * `is_specified_instruction` - Function which returns `true` for instruction which should be -/// included into the relevant transaction. +/// included into the relevant transaction. fn search_transactions( transactions: Vec, program_id: &Pubkey, diff --git a/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/dispatch_message_block_interchain_payment_search_solaxy.json b/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/dispatch_message_block_interchain_payment_search_solaxy.json new file mode 100644 index 00000000000..182f6de3778 --- /dev/null +++ b/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/dispatch_message_block_interchain_payment_search_solaxy.json @@ -0,0 +1,209 @@ +{ + "previousBlockhash": "8q7dmPPtf6G8T2Z6ueQV9HDtz7fUSW3qJZL8dqQPmHi8", + "blockhash": "8rM2RmUDMESwb74oJtEPsimpUnxmQaZ3YxKCdaGHcJs1", + "parentSlot": 6841764, + "transactions": [ + { + "transaction": { + "signatures": [ + "3wVK8GxSe6U1T9SE33Rh6GG52wRJZ1fQwRaJp7iCvkJ7MGVQVYLxpG51K4RwduYRPm4Tt7Lh81M5ArVutToNxNwq", + "58NU56RNWtD3CCEm3QcAvyJe3uRWAUFUqLEHEWacqyA7xXXLT9QFHceXwzLtvWvRyHfGcoXwBu9b81GBKD81PBbS" + ], + "message": { + "header": { + "numRequiredSignatures": 2, + "numReadonlySignedAccounts": 1, + "numReadonlyUnsignedAccounts": 9 + }, + "accountKeys": [ + "9L2x42tZvzGxDe873TrZmzMTNoaCx3eutqPcn4b5sSNc", + "DMN3vzngh773JnADzp4BgJGa8jHwJwQc9rQNZdgnJ6j6", + "3SDcBbfZUF9ykR3yudYN198ja3KJSq17Q3rKw7JKfJR6", + "4hWzwVjSd2Mi9kKxJuYGEL9j4dPnTtLSSBp3txR1egPM", + "Cff2q8sR9Bg3nST6Cvs8GusQ7qXEHMm27CCx8eXXHfyY", + "GASNEHuCf7AJiHLyEPMTkPwRuCs1wdNpUbFicJ1vg8Ji", + "m6AVVyf77phzZSSnimDtpdDTrub1njFW4YdnT8vrTMf", + "u1mzVeduYv56wpfLtspSsVhRVY5bVWmJFJeqPsdQLuc", + "11111111111111111111111111111111", + "5qPDSWuKi2TuZ6cEAV3kPMCmHUHFWYCbPmcmSnEBqWER", + "6VdnmP8MfYkPNMwyU2ZY9S3ickYyewF58LPC38y9FAVU", + "7HZ5A89rQyd3gpSLCFCsAzpap4SZJx3eC2AUMXKJBnfh", + "7z1QjgLLxowA9RA9QBuiC72Tc6aC1XjtA5zdH9LJHm1a", + "AAo7mnH9nihP9xCSQvTwmwumYhTA7LLJMJh5UntuSv87", + "ComputeBudget111111111111111111111111111111", + "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV", + "VG7YDF5Am2hrgyydE2ufdusdtw5DjgzXJLFxn9p8ehU" + ], + "recentBlockhash": "HapPbT13MA6x7sNyQ27ALYSegoVRwGLQ2PpJ2agSZNXL", + "instructions": [ + { + "programIdIndex": 14, + "accounts": [], + "data": "FjL4FH", + "stackHeight": null + }, + { + "programIdIndex": 14, + "accounts": [], + "data": "3DTZbgwsozUF", + "stackHeight": null + }, + { + "programIdIndex": 11, + "accounts": [ + 8, + 15, + 13, + 10, + 6, + 9, + 0, + 1, + 5, + 16, + 4, + 3, + 12, + 2, + 8, + 7 + ], + "data": "RpjV6TtUSvsfow1RrLDKrzEPFJRUqo55BRjKGBHkX4EN3SzDFtuoNVm8M3AbFABZvXAAYCrcNoGcGKxRp1R4aLbyB2B65gBSzvfY6orw", + "stackHeight": null + } + ] + } + }, + "meta": { + "err": null, + "status": { + "Ok": null + }, + "fee": 10000, + "preBalances": [], + "postBalances": [], + "innerInstructions": [ + { + "index": 2, + "instructions": [ + { + "programIdIndex": 8, + "accounts": [ + 0, + 7 + ], + "data": "3Bxs411zuWswhZBV", + "stackHeight": 2 + }, + { + "programIdIndex": 10, + "accounts": [ + 6, + 9, + 8, + 15, + 0, + 1, + 5 + ], + "data": "2TR5zF8eRXb24CNcXxCd67QuwuTpCSk4BxdiBfVo5HXcTYqAPP24yojKDFBqheuBu44jBNVA1ZedHHU1nvJuSkfvVn5fJgf23iSq37vNbTLSYDcY8ZmsXxQ1YkjayYUYMG9QaVCXv4cD7kd8oXGhXqK7zt5uH1UY43FT3C4Xmma19cv7YZZcCtX3bT5", + "stackHeight": 2 + }, + { + "programIdIndex": 8, + "accounts": [ + 0, + 6 + ], + "data": "3Bxs3zrfFUZbEPqZ", + "stackHeight": 3 + }, + { + "programIdIndex": 8, + "accounts": [ + 0, + 5 + ], + "data": "11114XfZCGKrze4PNou1GXiYCJZ8grdJvdbqoUCRFqQAjex9xQnTLuyNectFVdVcGdgon6", + "stackHeight": 3 + }, + { + "programIdIndex": 16, + "accounts": [ + 8, + 0, + 4, + 1, + 3, + 2, + 12 + ], + "data": "5ReTbgk9wsHBGZ2WNBf22PaTU9Wnr3khg9qQ2uvipb4LgHZNXRtLUE51uGqEw", + "stackHeight": 2 + }, + { + "programIdIndex": 8, + "accounts": [ + 0, + 2 + ], + "data": "3Bxs4V7uPMfG5Tv7", + "stackHeight": 3 + }, + { + "programIdIndex": 8, + "accounts": [ + 0, + 3 + ], + "data": "111158VjdPaAaGVkCbPZoXJqkn7K4jo8kgeEZHhKnGgmmcAtv1UJUsctqKqimhn2mXLg3Y", + "stackHeight": 3 + } + ] + } + ], + "logMessages": [ + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program ComputeBudget111111111111111111111111111111 invoke [1]", + "Program ComputeBudget111111111111111111111111111111 success", + "Program 7HZ5A89rQyd3gpSLCFCsAzpap4SZJx3eC2AUMXKJBnfh invoke [1]", + "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 success", + "Program 6VdnmP8MfYkPNMwyU2ZY9S3ickYyewF58LPC38y9FAVU invoke [2]", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Protocol fee of 0 paid from 9L2x42tZvzGxDe873TrZmzMTNoaCx3eutqPcn4b5sSNc to m6AVVyf77phzZSSnimDtpdDTrub1njFW4YdnT8vrTMf", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Dispatched message to 1, ID 0x15588a76d580172ca6399e4197603bffbb474896a4d552933515cf3482bbb05a", + "Program 6VdnmP8MfYkPNMwyU2ZY9S3ickYyewF58LPC38y9FAVU consumed 95319 of 1375094 compute units", + "Program return: 6VdnmP8MfYkPNMwyU2ZY9S3ickYyewF58LPC38y9FAVU FViKdtWAFyymOZ5Bl2A7/7tHSJak1VKTNRXPNIK7sFo=", + "Program 6VdnmP8MfYkPNMwyU2ZY9S3ickYyewF58LPC38y9FAVU success", + "Program VG7YDF5Am2hrgyydE2ufdusdtw5DjgzXJLFxn9p8ehU invoke [2]", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program log: Paid IGP 3SDcBbfZUF9ykR3yudYN198ja3KJSq17Q3rKw7JKfJR6 for 227337 gas for message 0x1558…b05a to 1", + "Program VG7YDF5Am2hrgyydE2ufdusdtw5DjgzXJLFxn9p8ehU consumed 41051 of 1276754 compute units", + "Program VG7YDF5Am2hrgyydE2ufdusdtw5DjgzXJLFxn9p8ehU success", + "Program log: Warp route transfer completed to destination: 1, recipient: 0x0000…a4c8, remote_amount: 95000000000000000000000", + "Program 7HZ5A89rQyd3gpSLCFCsAzpap4SZJx3eC2AUMXKJBnfh consumed 214463 of 1399700 compute units", + "Program 7HZ5A89rQyd3gpSLCFCsAzpap4SZJx3eC2AUMXKJBnfh success" + ], + "preTokenBalances": null, + "postTokenBalances": null, + "rewards": null, + "loadedAddresses": { + "writable": [], + "readonly": [] + }, + "computeUnitsConsumed": 214763 + }, + "version": "legacy" + } + ], + "blockTime": 1757413932, + "blockHeight": 6841765 +} diff --git a/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/tests.rs b/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/tests.rs index f7657d1e488..0bdc34ca5a7 100644 --- a/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/tests.rs +++ b/rust/main/chains/hyperlane-sealevel/src/log_meta_composer/tests.rs @@ -187,6 +187,48 @@ fn test_log_meta_block_with_multiple_txs_only_one_successful() { }); } +#[test] +fn test_log_meta_block_with_txn_interchain_payment_search_solaxy() { + // This test case uses an example of a block where a message was dispatched from the Solaxy chain. + // We should be able to find the interchain payment details in the block. + // Transaction in the block - https://explorer.solaxy.io/tx/3wVK8GxSe6U1T9SE33Rh6GG52wRJZ1fQwRaJp7iCvkJ7MGVQVYLxpG51K4RwduYRPm4Tt7Lh81M5ArVutToNxNwq + + // given + let interchain_payment_program_id = + decode_pubkey("VG7YDF5Am2hrgyydE2ufdusdtw5DjgzXJLFxn9p8ehU").unwrap(); + let composer = LogMetaComposer::new( + interchain_payment_program_id, + "interchain gas payment".to_owned(), + is_interchain_payment_instruction, + ); + + let payment_pda_account = + decode_pubkey("4hWzwVjSd2Mi9kKxJuYGEL9j4dPnTtLSSBp3txR1egPM").unwrap(); + let block = serde_json::from_str::(&read_json( + "dispatch_message_block_interchain_payment_search_solaxy.json", + )) + .unwrap(); + let log_index = U256::zero(); + let pda_slot = block.block_height.unwrap(); + let blockhash = decode_h256(&block.blockhash).unwrap(); + + // when + let log_meta = composer + .log_meta(block, log_index, &payment_pda_account, &pda_slot) + .unwrap(); + + // then + assert_eq!(log_meta, LogMeta { + address: interchain_payment_program_id.to_bytes().into(), + block_number: pda_slot, + block_hash: blockhash, + // The successful transaction and its index in the block + transaction_id: decode_h512("3wVK8GxSe6U1T9SE33Rh6GG52wRJZ1fQwRaJp7iCvkJ7MGVQVYLxpG51K4RwduYRPm4Tt7Lh81M5ArVutToNxNwq").unwrap(), + transaction_index: 0, + log_index, + }); +} + fn read_json(path: &str) -> String { let relative = PathBuf::new().join("src/log_meta_composer/").join(path); let absolute = fs::canonicalize(relative).expect("cannot find path"); diff --git a/rust/main/chains/hyperlane-sealevel/src/mailbox_indexer.rs b/rust/main/chains/hyperlane-sealevel/src/mailbox_indexer.rs index e79dd066178..6295918b6e5 100644 --- a/rust/main/chains/hyperlane-sealevel/src/mailbox_indexer.rs +++ b/rust/main/chains/hyperlane-sealevel/src/mailbox_indexer.rs @@ -107,6 +107,8 @@ impl SealevelMailboxIndexer { let hyperlane_message = HyperlaneMessage::read_from(&mut &dispatched_message_account.encoded_message[..])?; + info!("foo encoded_message: {:?}", hyperlane_message); + let log_meta = if self.advanced_log_meta { self.dispatch_message_log_meta( U256::from(nonce), diff --git a/rust/main/chains/hyperlane-sealevel/src/provider.rs b/rust/main/chains/hyperlane-sealevel/src/provider.rs index 9e24b267f82..c3f3e960548 100644 --- a/rust/main/chains/hyperlane-sealevel/src/provider.rs +++ b/rust/main/chains/hyperlane-sealevel/src/provider.rs @@ -212,8 +212,9 @@ impl SealevelProviderForLander for SealevelProvider { // Bump the compute units to be conservative let simulation_compute_units = MAX_COMPUTE_UNITS.min( - (simulation_compute_units * COMPUTE_UNIT_MULTIPLIER_NUMERATOR) - / COMPUTE_UNIT_MULTIPLIER_DENOMINATOR, + simulation_compute_units + .saturating_mul(COMPUTE_UNIT_MULTIPLIER_NUMERATOR) + .saturating_div(COMPUTE_UNIT_MULTIPLIER_DENOMINATOR), ); let mut priority_fee = priority_fee_oracle.get_priority_fee(&simulation_tx).await?; @@ -236,8 +237,9 @@ impl SealevelProviderForLander for SealevelProvider { .unwrap_or(PRIORITY_FEE_MULTIPLIER_NUMERATOR); // Bump the priority fee to be conservative - let priority_fee = - (priority_fee * priority_fee_numerator) / PRIORITY_FEE_MULTIPLIER_DENOMINATOR; + let priority_fee = priority_fee + .saturating_mul(priority_fee_numerator) + .saturating_div(PRIORITY_FEE_MULTIPLIER_DENOMINATOR); Ok(SealevelTxCostEstimate { compute_units: simulation_compute_units, @@ -560,7 +562,7 @@ impl HyperlaneProvider for SealevelProvider { warn!(tx_hash = ?hash, ?fee, ?gas_used, "calculated fee is less than gas used. it will result in zero gas price"); } - let gas_price = Some(fee / gas_used); + let gas_price = fee.checked_div(gas_used); let receipt = TxnReceiptInfo { gas_used, diff --git a/rust/main/chains/hyperlane-sealevel/src/tx_submitter.rs b/rust/main/chains/hyperlane-sealevel/src/tx_submitter.rs index 3674aa6196f..16452983840 100644 --- a/rust/main/chains/hyperlane-sealevel/src/tx_submitter.rs +++ b/rust/main/chains/hyperlane-sealevel/src/tx_submitter.rs @@ -116,7 +116,9 @@ impl TransactionSubmitter for JitoTransactionSubmitter { payer: &Pubkey, ) -> Instruction { // Divide by 1_000_000 to convert from microlamports to lamports. - let tip_lamports = (compute_units * compute_unit_price_micro_lamports) / 1_000_000; + let tip_lamports = compute_units + .saturating_mul(compute_unit_price_micro_lamports) + .saturating_div(1_000_000); let tip_lamports = tip_lamports.max(Self::MINIMUM_TIP_LAMPORTS); // The tip is a standalone transfer to a Jito fee account. diff --git a/rust/main/chains/hyperlane-starknet/src/indexer.rs b/rust/main/chains/hyperlane-starknet/src/indexer.rs index 2515b09ea7f..63ce1b9ed89 100644 --- a/rust/main/chains/hyperlane-starknet/src/indexer.rs +++ b/rust/main/chains/hyperlane-starknet/src/indexer.rs @@ -212,8 +212,8 @@ impl SequenceAwareIndexer for StarknetMerkleTreeHookIndexer } } -/// TODO: This is a placeholder for the Interchain Gas Paymaster indexer. -/// Interchain Gas Paymaster +// TODO: This is a placeholder for the Interchain Gas Paymaster indexer. +// Interchain Gas Paymaster /// A reference to a InterchainGasPaymasterIndexer contract on some Starknet chain #[derive(Debug, Clone)] diff --git a/rust/main/chains/hyperlane-starknet/src/lib.rs b/rust/main/chains/hyperlane-starknet/src/lib.rs index 8e79375b96a..31f3b2fd52d 100644 --- a/rust/main/chains/hyperlane-starknet/src/lib.rs +++ b/rust/main/chains/hyperlane-starknet/src/lib.rs @@ -3,6 +3,7 @@ #![forbid(unsafe_code)] #![warn(missing_docs)] #![deny(warnings)] +#![deny(clippy::arithmetic_side_effects)] pub use error::*; pub use indexer::*; @@ -16,8 +17,9 @@ pub use trait_builder::*; pub use utils::*; pub use validator_announce::*; -#[allow(clippy::all)] #[rustfmt::skip] +#[allow(clippy::all)] +#[allow(clippy::arithmetic_side_effects)] pub mod contracts; /// Application specific functionality diff --git a/rust/main/chains/hyperlane-starknet/src/utils.rs b/rust/main/chains/hyperlane-starknet/src/utils.rs index 5030c94603d..46a5320ca3d 100644 --- a/rust/main/chains/hyperlane-starknet/src/utils.rs +++ b/rust/main/chains/hyperlane-starknet/src/utils.rs @@ -1,3 +1,4 @@ +use std::ops::Rem; use std::time::Duration; use cainome::cairo_serde::CairoSerde; @@ -247,13 +248,13 @@ message_converter!(routing_ism::Message); /// Convert a byte slice to a starknet bytes by padding the bytes to 16 bytes chunks pub fn to_packed_bytes(bytes: &[u8]) -> Vec { // Calculate the required padding - let padding = (16 - (bytes.len() % 16)) % 16; - let total_len = bytes.len() + padding; + let padding = 16usize.saturating_sub(bytes.len().rem(16)).rem(16); + let total_len = bytes.len().saturating_add(padding); // Create a new byte vector with the necessary padding let mut padded_bytes = Vec::with_capacity(total_len); padded_bytes.extend_from_slice(bytes); - padded_bytes.extend(std::iter::repeat(0).take(padding)); + padded_bytes.extend(std::iter::repeat_n(0, padding)); let mut result = Vec::with_capacity(total_len / 16); for chunk in padded_bytes.chunks_exact(16) { @@ -274,10 +275,10 @@ pub fn string_to_cairo_long_string(s: &str) -> Result, CairoShortStrin let mut start = 0; while start < s.len() { - let end = std::cmp::min(start + chunk_size, s.len()); + let end = std::cmp::min(start.saturating_add(chunk_size), s.len()); let chunk = s[start..end].to_string(); chunks.push(cairo_short_string_to_felt(&chunk)?); - start += chunk_size; + start = start.saturating_add(chunk_size); } Ok(chunks) @@ -296,7 +297,7 @@ pub(crate) async fn get_block_height_for_reorg_period( .block_number() .await .map_err(Into::::into)?; - tip - blocks.get() as u64 + tip.saturating_sub(blocks.get() as u64) } ReorgPeriod::None => provider .block_number() diff --git a/rust/main/config/mainnet_config.json b/rust/main/config/mainnet_config.json index 9cdf18d9271..49fb7fabdd4 100644 --- a/rust/main/config/mainnet_config.json +++ b/rust/main/config/mainnet_config.json @@ -31,10 +31,9 @@ "index": { "from": 2507127 }, - "interchainAccountIsm": "0xd766e7C7517f2d0D92754b2fe4aE7AdEf7bDEC3e", - "interchainAccountRouter": "0x25C87e735021F72d8728438C2130b02E3141f2cb", + "interchainAccountRouter": "0x23C6D8145DDb71a24965AAAdf4CA4B095b4eC85F", "interchainGasPaymaster": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", - "interchainSecurityModule": "0xdDf6f710bb9E2afFC9BA2e47566568BBfacB4995", + "interchainSecurityModule": "0x23875eBEBf22F924D0Ee28F94a9160Fa5CBD1D76", "isTestnet": false, "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x811808Dd29ba8B0FC6C0ec0b5537035E59745162", @@ -71,8 +70,8 @@ "aggregationHook": "0xe0cb37cFc47296f1c4eD77EFf92Aed478644d10c", "blockExplorers": [ { - "apiKey": "QAI5SWBNHJSFAN6KMS9RC5JGFKV2IYD2Z5", - "apiUrl": "https://api.arbiscan.io/api", + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=42161", "family": "etherscan", "name": "Arbiscan", "url": "https://arbiscan.io" @@ -98,10 +97,9 @@ "index": { "from": 143649797 }, - "interchainAccountIsm": "0x2A7574358Ec53522CE2452887661AB4c86F7d400", - "interchainAccountRouter": "0x91874Dbed74925dFe6059B90385EEb90DdE0B2E6", + "interchainAccountRouter": "0xF90A3d406C6F8321fe118861A357F4D7107760D7", "interchainGasPaymaster": "0x3b6044acd6767f017e99318AA6Ef93b7B06A5a22", - "interchainSecurityModule": "0xc9E547dc93b5A6075eD844816a94CBa3e52DfC26", + "interchainSecurityModule": "0x12d289D009BaeFecb02b0efD7C0b6b23a3877135", "mailbox": "0x979Ca5202784112f4738403dBec5D0F3B9daabB9", "merkleTreeHook": "0x748040afB89B8FdBb992799808215419d36A0930", "name": "arbitrum", @@ -171,10 +169,9 @@ "index": { "from": 36874693 }, - "interchainAccountIsm": "0x27a3233c05C1Df7c163123301D14bE9349E3Cb48", - "interchainAccountRouter": "0xa82a0227e6d6db53AF4B264A852bfF91C6504a51", + "interchainAccountRouter": "0x2c58687fFfCD5b7043a5bF256B196216a98a6587", "interchainGasPaymaster": "0x95519ba800BBd0d34eeAE026fEc620AD978176C0", - "interchainSecurityModule": "0x591E01c79a8F83c532A95E915fEBDE0C769263dE", + "interchainSecurityModule": "0xA21F66A66AB32076Cf53989c0Cd9Ef2aC3BE6E1c", "mailbox": "0xFf06aFcaABaDDd1fb08371f9ccA15D73D51FeBD6", "merkleTreeHook": "0x84eea61D679F42D92145fA052C89900CBAccE95A", "name": "avalanche", @@ -224,11 +221,23 @@ "aggregationHook": "0x13f3d4B0Ee0a713430fded9E18f7fb6c91A6E41F", "blockExplorers": [ { - "apiKey": "R8CVTG7HDJYD5JDV2GSD5TGQH3J2KJDSSY", - "apiUrl": "https://api.basescan.org/api", + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=8453", "family": "etherscan", "name": "BaseScan", "url": "https://basescan.org" + }, + { + "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/8453/etherscan/api", + "family": "routescan", + "name": "Base Explorer", + "url": "https://base.thesuperscan.io" + }, + { + "apiUrl": "https://base.blockscout.com/api", + "family": "blockscout", + "name": "Base Mainnet explorer", + "url": "https://base.blockscout.com" } ], "blocks": { @@ -251,10 +260,9 @@ "index": { "from": 5695475 }, - "interchainAccountIsm": "0x223F7D3f27E6272266AE4B5B91Fd5C7A2d798cD8", - "interchainAccountRouter": "0x4767D22117bBeeb295413000B620B93FD8522d53", + "interchainAccountRouter": "0x44647Cd983E80558793780f9a0c7C2aa9F384D07", "interchainGasPaymaster": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", - "interchainSecurityModule": "0xBa05388aE47866c677bE665DDF762295e7fC513A", + "interchainSecurityModule": "0x57Ff1CE1319c5f234F8a8Fcf19fb56dC4468b320", "mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "merkleTreeHook": "0x19dc38aeae620380430C200a6E990D5Af5480117", "name": "base", @@ -270,7 +278,7 @@ "proxyAdmin": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "rpcUrls": [ { - "http": "https://base.publicnode.com/" + "http": "https://base.publicnode.com" }, { "http": "https://mainnet.base.org" @@ -311,6 +319,13 @@ "blast": { "aggregationHook": "0x012278333Ce0A845AE9bD7302867a59Bd5D3635d", "blockExplorers": [ + { + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=81457", + "family": "etherscan", + "name": "BlastScan", + "url": "https://blastscan.io/" + }, { "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/81457/etherscan/api", "family": "routescan", @@ -338,10 +353,9 @@ "index": { "from": 2496427 }, - "interchainAccountIsm": "0xe93f2f409ad8B5000431D234472973fe848dcBEC", - "interchainAccountRouter": "0x2f4Eb04189e11Af642237Da62d163Ab714614498", + "interchainAccountRouter": "0x7d58D7F052792e54eeEe91B2467c2A17a163227e", "interchainGasPaymaster": "0xB3fCcD379ad66CED0c91028520C64226611A48c9", - "interchainSecurityModule": "0xBb44E588cc72010eE6bd301D6e2be4e517cB349F", + "interchainSecurityModule": "0x58fDb7fB427f6ED3187aAE18E9244B711199b981", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", "name": "blast", @@ -406,10 +420,9 @@ "index": { "from": 3225119 }, - "interchainAccountIsm": "0x451dF8AB0936D85526D816f0b4dCaDD934A034A4", - "interchainAccountRouter": "0x5C02157068a52cEcfc98EDb6115DE6134EcB4764", + "interchainAccountRouter": "0xA6f0A37DFDe9C2c8F46F010989C47d9edB3a9FA8", "interchainGasPaymaster": "0x62B7592C1B6D1E43f4630B8e37f4377097840C05", - "interchainSecurityModule": "0x882d47B822bA30E78332A0Da859abD0e01069266", + "interchainSecurityModule": "0x3Db3C45a51AF0F6b673404Dee1bcfe303B6e39aE", "mailbox": "0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147", "merkleTreeHook": "0x781bE492F1232E66990d83a9D3AC3Ec26f56DAfB", "name": "bob", @@ -445,8 +458,8 @@ "aggregationHook": "0x402Fc106576462a892355d69ACF03D46A888ae88", "blockExplorers": [ { - "apiKey": "NXSXUUUAUDD5SYGQUIVS9TZTBCEG6X5THY", - "apiUrl": "https://api.bscscan.com/api", + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=56", "family": "etherscan", "name": "BscScan", "url": "https://bscscan.com" @@ -473,10 +486,9 @@ "index": { "from": 32893043 }, - "interchainAccountIsm": "0x9e22945bE593946618383B108CC5bce09eBA4C26", - "interchainAccountRouter": "0x32A07c1B7a7fe8D4A0e44B0181873aB9d64C16c1", + "interchainAccountRouter": "0xf453B589F0166b90e050691EAc281C01a8959897", "interchainGasPaymaster": "0x78E25e7f84416e69b9339B0A6336EB6EFfF6b451", - "interchainSecurityModule": "0x50D3a6a22abB1FB1a2C2d91Dd490893b16049494", + "interchainSecurityModule": "0xf54C53f0c2b5502A900dEA459F564B6d805a41a7", "mailbox": "0x2971b9Aec44bE4eb673DF1B88cDB57b96eefe8a4", "merkleTreeHook": "0xFDb9Cd5f9daAA2E4474019405A328a88E7484f26", "name": "bsc", @@ -528,8 +540,8 @@ "aggregationHook": "0xc65890329066FB20c339Bc5C22f1756e9D3a4fF5", "blockExplorers": [ { - "apiKey": "IC1UCY8JWIYFWGPCEEE58HKFIFEVN73PVV", - "apiUrl": "https://api.celoscan.io/api", + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=42220", "family": "etherscan", "name": "CeloScan", "url": "https://celoscan.io" @@ -561,10 +573,9 @@ "index": { "from": 22102340 }, - "interchainAccountIsm": "0xB732c83aeE29596E3163Da2260710eAB67Bc0B29", - "interchainAccountRouter": "0x27a6cAe33378bB6A6663b382070427A01fc9cB37", + "interchainAccountRouter": "0x1eA7aC243c398671194B7e2C51d76d1a1D312953", "interchainGasPaymaster": "0x571f1435613381208477ac5d6974310d88AC7cB7", - "interchainSecurityModule": "0xEa711d25755886Fe144cDFa0c58d790d98eA59f8", + "interchainSecurityModule": "0x7D4D158e94eC90440B3BAae58Ea3c5A6aCB394D0", "mailbox": "0x50da3B3907A08a24fe4999F4Dcf337E8dC7954bb", "merkleTreeHook": "0x04dB778f05854f26E67e0a66b740BBbE9070D366", "name": "celo", @@ -632,10 +643,9 @@ "index": { "from": 50650 }, - "interchainAccountIsm": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", - "interchainAccountRouter": "0xEF9A332Ec1fD233Bf9344A58be56ff9E104B4f60", + "interchainAccountRouter": "0xBF4f96006d98780120f950b36505bC317aC6b6a4", "interchainGasPaymaster": "0x7E27456a839BFF31CA642c060a2b68414Cb6e503", - "interchainSecurityModule": "0xE9c74f1d4CC59f21D8c7b20B3BeeC16Dc59bd2b2", + "interchainSecurityModule": "0x518095961529D7762200F4769743719D20D2B0A3", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x0054D19613f20dD72721A146ED408971a2CCA9BD", "name": "cheesechain", @@ -698,7 +708,7 @@ "from": 4842212 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0xc15D1D8B17565Cacb21B2DE43872086ED9DD9fCf", + "interchainSecurityModule": "0x16b86FA6b925B852F1B74f8DE13AA1Ee29E57f55", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "cyber", @@ -730,8 +740,7 @@ "validatorAnnounce": "0x062200d92df6bb7ba89ce4d6800110450f94784e", "staticMerkleRootWeightedMultisigIsmFactory": "0x989B7307d266151BE763935C856493D968b2affF", "staticMessageIdWeightedMultisigIsmFactory": "0x71388C9E25BE7b229B5d17Df7D4DB3F7DA7C962d", - "interchainAccountIsm": "0x67F36550b73B731e5b2FC44E4F8f250d89c87bD6", - "interchainAccountRouter": "0x7B032cBB00AD7438E802A66D8b64761A06E5df22", + "interchainAccountRouter": "0x1B947F6246ACe28abAf073FF11c098F31ce4f899", "timelockController": "0x0000000000000000000000000000000000000000", "technicalStack": "opstack" }, @@ -765,7 +774,7 @@ "from": 23783929 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0xBAa0d295590882AAC304E12E691e281eE70E24da", + "interchainSecurityModule": "0xC06976ec301Fd71D6193Ae74e6Ce27159A449b42", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "degenchain", @@ -795,8 +804,7 @@ "validatorAnnounce": "0x062200d92dF6bB7bA89Ce4D6800110450f94784e", "staticMerkleRootWeightedMultisigIsmFactory": "0x71388C9E25BE7b229B5d17Df7D4DB3F7DA7C962d", "staticMessageIdWeightedMultisigIsmFactory": "0x3E969bA938E6A993eeCD6F65b0dd8712B07dFe59", - "interchainAccountIsm": "0xD8aF449f8fEFbA2064863DCE5aC248F8B232635F", - "interchainAccountRouter": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94", + "interchainAccountRouter": "0xAb65C41a1BC580a52f0b166879122EFdce0cB868", "timelockController": "0x0000000000000000000000000000000000000000" }, "eclipsemainnet": { @@ -875,10 +883,9 @@ "index": { "from": 952917 }, - "interchainAccountIsm": "0xCeafc098e5c3c7768b9229Be2FEC275862A81Abd", - "interchainAccountRouter": "0xed9a722c543883FB7e07E78F3879762DE09eA7D5", + "interchainAccountRouter": "0xB5CA647Fd7b28cb8Ec80144f2C6BAEE2Dfc12E03", "interchainGasPaymaster": "0xB30EAB08aa87138D57168D0e236850A530f49921", - "interchainSecurityModule": "0x9163dD495CD4c4F2E5d4D0EE3D4dF901F1c5B457", + "interchainSecurityModule": "0x1CB90f1620D338dC7e2107D9Dc7296058fD6cA97", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xC831271c1fB212012811a91Dd43e5926C1020563", "name": "endurance", @@ -914,8 +921,8 @@ "aggregationHook": "0xb87AC8EA4533AE017604E44470F7c1E550AC6F10", "blockExplorers": [ { - "apiKey": "CYUPN3Q66JIMRGQWYUDXJKQH4SX8YIYZMW", - "apiUrl": "https://api.etherscan.io/api", + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=1", "family": "etherscan", "name": "Etherscan", "url": "https://etherscan.io" @@ -925,6 +932,12 @@ "family": "blockscout", "name": "Blockscout", "url": "https://blockscout.com/eth/mainnet" + }, + { + "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/1/etherscan/api", + "family": "routescan", + "name": "Ethereum Explorer", + "url": "https://1.routescan.io" } ], "blocks": { @@ -947,10 +960,9 @@ "index": { "from": 18422581 }, - "interchainAccountIsm": "0x292C614ED53DaaDBf971521bc2C652d1ca51cB47", - "interchainAccountRouter": "0x5E532F7B610618eE73C2B462978e94CB1F7995Ce", + "interchainAccountRouter": "0xC00b94c115742f711a6F9EA90373c33e9B72A4A9", "interchainGasPaymaster": "0x9e6B1022bE9BBF5aFd152483DAD9b88911bC8611", - "interchainSecurityModule": "0x427Af5b61CdEbf660123C1c3fA9CAa83756E1B70", + "interchainSecurityModule": "0x9e7D21eC266Ffb43B7A7770557002002Cf52B128", "mailbox": "0xc005dc82818d67AF737725bD4bf75435d065D239", "merkleTreeHook": "0x48e6c30B97748d1e2e03bf3e9FbE3890ca5f8CCA", "name": "ethereum", @@ -1002,11 +1014,17 @@ "aggregationHook": "0xD7ff06cDd83642D648baF0d36f77e79349120dA4", "blockExplorers": [ { - "apiKey": "X3G6FVJU5VEZXVNQFRX52EQ5FEVP8IPR6F", - "apiUrl": "https://api.fraxscan.com/api", + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=252", "family": "etherscan", "name": "Fraxscan", "url": "https://fraxscan.com" + }, + { + "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/252/etherscan/api", + "family": "routescan", + "name": "Fraxtal Explorer", + "url": "https://252.thesuperscan.io" } ], "blocks": { @@ -1029,10 +1047,9 @@ "index": { "from": 5350807 }, - "interchainAccountIsm": "0x7C012DCA02C42cfA3Fd7Da3B0ED7234B52AE68eF", - "interchainAccountRouter": "0xbed53B5C5BCE9433f25A2A702e6df13E22d84Ae9", + "interchainAccountRouter": "0xD59a200cCEc5b3b1bF544dD7439De452D718f594", "interchainGasPaymaster": "0x2Fca7f6eC3d4A0408900f2BB30004d4616eE985E", - "interchainSecurityModule": "0x7f15524887A5809dE1Fe36dfCC8e48E0c1353537", + "interchainSecurityModule": "0x7EB270e62d9B4001154da7407A231f63479416C6", "mailbox": "0x2f9DB5616fa3fAd1aB06cB2C906830BA63d135e3", "merkleTreeHook": "0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147", "name": "fraxtal", @@ -1097,10 +1114,9 @@ "index": { "from": 30585739 }, - "interchainAccountIsm": "0x9629c28990F11c31735765A6FD59E1E1bC197DbD", - "interchainAccountRouter": "0x2351FBe24C1212F253b7a300ff0cBCFd97952a19", + "interchainAccountRouter": "0xc57fe3d144434d0aBaF8D3698E3103a4ddFD777A", "interchainGasPaymaster": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", - "interchainSecurityModule": "0xF39521d01fD37F5EeC8e454B1eF5382a59Cc7b33", + "interchainSecurityModule": "0x53724837AfDbf898aDf28ef58C6Fa391F7cad8ee", "mailbox": "0x3071D4DA6020C956Fe15Bfd0a9Ca8D4574f16696", "merkleTreeHook": "0xfBc08389224d23b79cb21cDc16c5d42F0ad0F57f", "name": "fusemainnet", @@ -1145,8 +1161,8 @@ "aggregationHook": "0xdD1FA1C12496474c1dDC67a658Ba81437F818861", "blockExplorers": [ { - "apiKey": "98A32SWJE876CHG95TMFRC5H5SBBX9GHAG", - "apiUrl": "https://api.gnosisscan.io/api", + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=100", "family": "etherscan", "name": "GnosisScan", "url": "https://gnosisscan.io" @@ -1172,10 +1188,9 @@ "index": { "from": 30620793 }, - "interchainAccountIsm": "0x07E2062A1bC66a2C1d05cb5C3870a4AF86e0056E", - "interchainAccountRouter": "0xBE70Ab882D1F7E37e04a70CDd9Ec23b37a234064", + "interchainAccountRouter": "0xef0Adeb4103A7A1AcE86371867202f2171126362", "interchainGasPaymaster": "0xDd260B99d302f0A3fF885728c086f729c06f227f", - "interchainSecurityModule": "0xbd604F98665B360CB93bea4769F611C297F5EE14", + "interchainSecurityModule": "0xCC37772b431D1deFe40481544d9bb5dC0009aB9c", "mailbox": "0xaD09d78f4c6b9dA2Ae82b1D34107802d380Bb74f", "merkleTreeHook": "0x2684C6F89E901987E1FdB7649dC5Be0c57C61645", "name": "gnosis", @@ -1243,10 +1258,9 @@ "index": { "from": 37 }, - "interchainAccountIsm": "0x708E002637792FDC031E6B62f23DD60014AC976a", - "interchainAccountRouter": "0xfB8cea1c7F45608Da30655b50bbF355D123A4358", + "interchainAccountRouter": "0xd2884F7042d0a819F5276A69770F70d9C89Bf823", "interchainGasPaymaster": "0x19dc38aeae620380430C200a6E990D5Af5480117", - "interchainSecurityModule": "0x4D34adDEFf297871C31eDC91B28E19167bf27558", + "interchainSecurityModule": "0x7d74C7c58c492072a7EF3f1E9dF6322c33d04328", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x0972954923a1e2b2aAb04Fa0c4a0797e5989Cd65", "name": "inevm", @@ -1351,11 +1365,17 @@ "aggregationHook": "0x43fF73dF1E170D076D9Ed30d4C6922A9D34322dE", "blockExplorers": [ { - "apiKey": "ZCA9836XUX1XPAACHK4BMMYTIFMHIGU8VN", - "apiUrl": "https://api.lineascan.build/api", + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=59144", "family": "etherscan", "name": "LineaScan", "url": "https://lineascan.build" + }, + { + "apiUrl": "https://api-explorer.linea.build/api", + "family": "blockscout", + "name": "Linea Explorer", + "url": "https://explorer.linea.build" } ], "blocks": { @@ -1378,10 +1398,9 @@ "index": { "from": 5154574 }, - "interchainAccountIsm": "0xdcA646C56E7768DD11654956adE24bfFf9Ba4893", - "interchainAccountRouter": "0xD59dA396F162Ed93a41252Cebb8d5DD4F093238C", + "interchainAccountRouter": "0xBfC8DCEf3eFabC064f5afff4Ac875a82D2Dc9E55", "interchainGasPaymaster": "0x8105a095368f1a184CceA86cCe21318B5Ee5BE28", - "interchainSecurityModule": "0x367f9049285f55b7cBF1F294f3770f42B1E7F9c2", + "interchainSecurityModule": "0x9aac5E3fbeab6cd2C1147a4C0c06668E1bd29214", "mailbox": "0x02d16BC51af6BfD153d67CA61754cF912E82C4d9", "merkleTreeHook": "0xC077A0Cc408173349b1c9870C667B40FE3C01dd7", "name": "linea", @@ -1427,6 +1446,12 @@ "family": "blockscout", "name": "Lisk Explorer", "url": "https://blockscout.lisk.com" + }, + { + "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/1135/etherscan/api", + "family": "routescan", + "name": "Lisk Explorer", + "url": "https://1135.thesuperscan.io" } ], "blocks": { @@ -1449,7 +1474,7 @@ "from": 4195553 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0x966252AA7751DEe702970b09591311e43c670d4a", + "interchainSecurityModule": "0x48831E46D9dBbc59D2bd85019ABcE9b74c82c8F2", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "lisk", @@ -1478,8 +1503,7 @@ "validatorAnnounce": "0x062200d92dF6bB7bA89Ce4D6800110450f94784e", "staticMerkleRootWeightedMultisigIsmFactory": "0x71388C9E25BE7b229B5d17Df7D4DB3F7DA7C962d", "staticMessageIdWeightedMultisigIsmFactory": "0x3E969bA938E6A993eeCD6F65b0dd8712B07dFe59", - "interchainAccountIsm": "0xD8aF449f8fEFbA2064863DCE5aC248F8B232635F", - "interchainAccountRouter": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94", + "interchainAccountRouter": "0xE59592a179c4f436d5d2e4caA6e2750beA4E3166", "timelockController": "0x0000000000000000000000000000000000000000", "technicalStack": "opstack", "gnosisSafeTransactionServiceUrl": "https://transaction-lisk.safe.optimism.io" @@ -1514,7 +1538,7 @@ "from": 3088760 }, "interchainGasPaymaster": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", - "interchainSecurityModule": "0x503728e24aEEf69f543bC2c489E7B76cF9AA001e", + "interchainSecurityModule": "0x8EDC8ecE66c0F7EB525b19C48eAD2f2e769E768C", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x062200d92dF6bB7bA89Ce4D6800110450f94784e", "name": "lukso", @@ -1549,8 +1573,7 @@ "validatorAnnounce": "0x3C2b535a49c6827DF0b8e94467e6922c99E3c092", "staticMerkleRootWeightedMultisigIsmFactory": "0x148CF67B8A242c1360bb2C93fCe203EC4d4f9B56", "staticMessageIdWeightedMultisigIsmFactory": "0xcd849e612Aaa138f03698C3Edb42a34117BFF631", - "interchainAccountIsm": "0xd64d126941EaC2Cf53e0E4E8146cC70449b60D73", - "interchainAccountRouter": "0x1A4F09A615aA4a35E5a146DC2fa19975bebF21A5", + "interchainAccountRouter": "0x7e0956bfEE5C4dEAd8Ced283C934299998100362", "timelockController": "0x0000000000000000000000000000000000000000", "technicalStack": "other" }, @@ -1585,10 +1608,9 @@ "index": { "from": 437300 }, - "interchainAccountIsm": "0x8Ea50255C282F89d1A14ad3F159437EE5EF0507f", - "interchainAccountRouter": "0x693A4cE39d99e46B04cb562329e3F0141cA17331", + "interchainAccountRouter": "0x620ffeEB3359649dbE48278d3Cffd00CC36976EA", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "interchainSecurityModule": "0x3B2316F4feb1CA2f002e823EF379CBEE01fb0FCe", + "interchainSecurityModule": "0x9eFa90AEEffd3a17c25ed8a562F6b0913a37A12F", "isTestnet": false, "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", @@ -1633,6 +1655,12 @@ "family": "blockscout", "name": "Mantle Mainnet Explorer", "url": "https://explorer.mantle.xyz" + }, + { + "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/5000/etherscan/api", + "family": "routescan", + "name": "Routescan", + "url": "https://routescan.io" } ], "blocks": { @@ -1655,10 +1683,9 @@ "index": { "from": 65590958 }, - "interchainAccountIsm": "0xe039DA3A0071BEd087A12660D7b03cf669c7776E", - "interchainAccountRouter": "0x45285463352c53a481e882cD5E2AF2E25BBdAd0D", + "interchainAccountRouter": "0x31e81982E98F5D321F839E82789b628AedB15751", "interchainGasPaymaster": "0x8105a095368f1a184CceA86cCe21318B5Ee5BE28", - "interchainSecurityModule": "0x96a589081b478CBa4A6628475b8C5E61E56dAeb4", + "interchainSecurityModule": "0xFDB2c59e1Ed78Be8000fD35586AAfb69AaF58fa2", "mailbox": "0x398633D19f4371e1DB5a8EFE90468eB70B1176AA", "merkleTreeHook": "0x5332D1AC0A626D265298c14ff681c0A8D28dB86d", "name": "mantle", @@ -1720,7 +1747,7 @@ "from": 13523607 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0x02f1756168608A5c1A9F685b84AA8Fa565D1Ffb9", + "interchainSecurityModule": "0x66ae04E1E1fd46022697e70F056D7826C8318DD8", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "merlin", @@ -1752,8 +1779,7 @@ "validatorAnnounce": "0xd21192429df453021e896f2897Dc8B1167DD61E5", "staticMerkleRootWeightedMultisigIsmFactory": "0xF15D70941dE2Bf95A23d6488eBCbedE0a444137f", "staticMessageIdWeightedMultisigIsmFactory": "0xD7EcB0396406682a27E87F7946c25Ac531140959", - "interchainAccountIsm": "0xA8A311B69f688c1D9928259D872C31ca0d473642", - "interchainAccountRouter": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7", + "interchainAccountRouter": "0xFCfE7344d7a769C89B3A22c596fE83a1bF8458Da", "timelockController": "0x0000000000000000000000000000000000000000", "technicalStack": "polygoncdk" }, @@ -1765,6 +1791,12 @@ "family": "blockscout", "name": "Metis Andromeda Explorer", "url": "https://andromeda-explorer.metis.io" + }, + { + "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/1088/etherscan/api", + "family": "routescan", + "name": "Metis Explorer", + "url": "https://explorer.metis.io" } ], "blocks": { @@ -1787,7 +1819,7 @@ "from": 17966274 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0x379A49cE147230925889769Dd1925d12Ed4f5738", + "interchainSecurityModule": "0xE93dE6EB22B146B7478c463B0C0B200DA938cAb4", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "metis", @@ -1817,8 +1849,7 @@ "validatorAnnounce": "0x062200d92dF6bB7bA89Ce4D6800110450f94784e", "staticMerkleRootWeightedMultisigIsmFactory": "0xcd849e612Aaa138f03698C3Edb42a34117BFF631", "staticMessageIdWeightedMultisigIsmFactory": "0xb129828B9EDa48192D0B2db35D0E40dCF51B3594", - "interchainAccountIsm": "0x1A4F09A615aA4a35E5a146DC2fa19975bebF21A5", - "interchainAccountRouter": "0xb2674E213019972f937CCFc5e23BF963D915809e", + "interchainAccountRouter": "0x04Bd82Ba84a165BE5D555549ebB9890Bb327336E", "timelockController": "0x0000000000000000000000000000000000000000" }, "mint": { @@ -1852,7 +1883,7 @@ "from": 3752032 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0x053D04356ddeBC45076763545E6C4e7f46b1a00C", + "interchainSecurityModule": "0x630407c50288394DF0547A72646F365d267ceF03", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "mint", @@ -1881,8 +1912,7 @@ "validatorAnnounce": "0x062200d92dF6bB7bA89Ce4D6800110450f94784e", "staticMerkleRootWeightedMultisigIsmFactory": "0xcd849e612Aaa138f03698C3Edb42a34117BFF631", "staticMessageIdWeightedMultisigIsmFactory": "0xb129828B9EDa48192D0B2db35D0E40dCF51B3594", - "interchainAccountIsm": "0x1A4F09A615aA4a35E5a146DC2fa19975bebF21A5", - "interchainAccountRouter": "0xb2674E213019972f937CCFc5e23BF963D915809e", + "interchainAccountRouter": "0x511C21cF98AB0D07a6fB9Fb65E9e66DD483375B5", "timelockController": "0x0000000000000000000000000000000000000000", "technicalStack": "opstack" }, @@ -1894,6 +1924,12 @@ "family": "blockscout", "name": "Mode Explorer", "url": "https://explorer.mode.network" + }, + { + "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/34443/etherscan/api", + "family": "routescan", + "name": "Mode Explorer", + "url": "https://34443.thesuperscan.io" } ], "blocks": { @@ -1916,10 +1952,9 @@ "index": { "from": 6817759 }, - "interchainAccountIsm": "0xa377b8269e0A47cdd2fD5AAeAe860b45623c6d82", - "interchainAccountRouter": "0x6e1B9f776bd415d7cC3C7458A5f0d801016918f8", + "interchainAccountRouter": "0x860ec58b115930EcbC53EDb8585C1B16AFFF3c50", "interchainGasPaymaster": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "interchainSecurityModule": "0xAF3ac97016D27EDe371d30D6ae48ee070a730830", + "interchainSecurityModule": "0x8b5f2C93eEB46938179959703F282a456249Ff2C", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xE2ee936bEa8e42671c400aC96dE198E06F2bA2A6", "name": "mode", @@ -1990,10 +2025,9 @@ "chunk": 1024, "from": 4719713 }, - "interchainAccountIsm": "0x79b3730CE3685f65802aF1771319992bA960EB9D", - "interchainAccountRouter": "0xc4482f66191754a8629D35289043C4EB0285F10E", + "interchainAccountRouter": "0x24b900De85479A586aC8568b471AAC1CEeD6370c", "interchainGasPaymaster": "0x14760E32C0746094cF14D97124865BC7F0F7368F", - "interchainSecurityModule": "0xf1716996A1638EE16d0cCAd24BcdC15fbE8c3e70", + "interchainSecurityModule": "0x6c5244dC5fC1dab96350C1F4C51D3FD3d3066E99", "mailbox": "0x094d03E751f49908080EFf000Dd6FD177fd44CC3", "merkleTreeHook": "0x87403b85f6f316e7ba91ba1fa6C3Fb7dD4095547", "name": "moonbeam", @@ -2106,11 +2140,23 @@ "aggregationHook": "0x4ccC6d8eB79f2a1EC9bcb0f211fef7907631F91f", "blockExplorers": [ { - "apiKey": "JMYR3W6HHVPQ1HH8T6W8HSZVG88IH3MHRU", - "apiUrl": "https://api-optimistic.etherscan.io/api", + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=10", "family": "etherscan", "name": "Etherscan", "url": "https://optimistic.etherscan.io" + }, + { + "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/10/etherscan/api", + "family": "routescan", + "name": "Optimism Explorer", + "url": "https://mainnet.thesuperscan.io" + }, + { + "apiUrl": "https://optimism.blockscout.com/api", + "family": "blockscout", + "name": "OP Mainnet explorer", + "url": "https://optimism.blockscout.com" } ], "blocks": { @@ -2133,10 +2179,9 @@ "index": { "from": 111290758 }, - "interchainAccountIsm": "0x2c46BF14641d00549ECa4779BF5CBf91602C1DEd", - "interchainAccountRouter": "0x03D6cC17d45E9EA27ED757A8214d1F07F7D901aD", + "interchainAccountRouter": "0x3E343D07D024E657ECF1f8Ae8bb7a12f08652E75", "interchainGasPaymaster": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", - "interchainSecurityModule": "0x262264DCb9A0e952368b207a67c0082cbC4a3B66", + "interchainSecurityModule": "0x8530508F8E6fc9Eb20c21b72445C7002b62a4a5a", "mailbox": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D", "merkleTreeHook": "0x68eE9bec9B4dbB61f69D9D293Ae26a5AACb2e28f", "name": "optimism", @@ -2208,7 +2253,7 @@ "domainId": 875, "gasCurrencyCoinGeckoId": "osmosis", "gasPrice": { - "amount": "0.025", + "amount": "0.1", "denom": "uosmo" }, "grpcUrls": [ @@ -2255,8 +2300,8 @@ "aggregationHook": "0x34dAb05650Cf590088bA18aF9d597f3e081bCc47", "blockExplorers": [ { - "apiKey": "ZPAHJ5A73MC45WJED98YJX4MKJE1UGN8D1", - "apiUrl": "https://api.polygonscan.com/api", + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=137", "family": "etherscan", "name": "PolygonScan", "url": "https://polygonscan.com" @@ -2282,10 +2327,9 @@ "index": { "from": 49108065 }, - "interchainAccountIsm": "0xBAC4529cdfE7CCe9E858BF706e41F8Ed096C1BAd", - "interchainAccountRouter": "0xF163949AD9F88977ebF649D0461398Ca752E64B9", + "interchainAccountRouter": "0xd8B641FEb587844854aeC97544ccEA426DFF04a3", "interchainGasPaymaster": "0x0071740Bf129b05C4684abfbBeD248D80971cce2", - "interchainSecurityModule": "0x43d9404c51E0DAC02a38c0023bF8803E39C2169e", + "interchainSecurityModule": "0x55ad2cA66D4189b0357972547206DE119c59f4bA", "mailbox": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB", "merkleTreeHook": "0x73FbD25c3e817DC4B4Cd9d00eff6D83dcde2DfF6", "name": "polygon", @@ -2331,8 +2375,8 @@ "aggregationHook": "0x8464aF853363B8d6844070F68b0AB34Cb6523d0F", "blockExplorers": [ { - "apiKey": "P8765WMUM4KAM9NPNAY49EACATYC4927HK", - "apiUrl": "https://api-zkevm.polygonscan.com/api", + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=1101", "family": "etherscan", "name": "PolygonScan", "url": "https://zkevm.polygonscan.com" @@ -2360,10 +2404,9 @@ "chunk": 999, "from": 6577743 }, - "interchainAccountIsm": "0xc1198e241DAe48BF5AEDE5DCE49Fe4A6064cF7a7", - "interchainAccountRouter": "0x20a0A32a110362920597F72974E1E0d7e25cA20a", + "interchainAccountRouter": "0x0b6C22e18fDcA681049A7ce003372DFfb3C71214", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "interchainSecurityModule": "0xC41E23eaF8Ce58f8aac0683B74AB07bF6c1bcB51", + "interchainSecurityModule": "0x8502633706b396160b5c3a646Cf6B52427980379", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", "name": "polygonzkevm", @@ -2431,7 +2474,7 @@ "from": 32018468 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0xcA61B64621D8A14034835ebbEfDbEbd2957aab2b", + "interchainSecurityModule": "0xd41F61871A8bcB3be821690C27c3D333C084f33b", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "proofofplay", @@ -2461,8 +2504,7 @@ "validatorAnnounce": "0x062200d92dF6bB7bA89Ce4D6800110450f94784e", "staticMerkleRootWeightedMultisigIsmFactory": "0xcd849e612Aaa138f03698C3Edb42a34117BFF631", "staticMessageIdWeightedMultisigIsmFactory": "0xb129828B9EDa48192D0B2db35D0E40dCF51B3594", - "interchainAccountIsm": "0x1A4F09A615aA4a35E5a146DC2fa19975bebF21A5", - "interchainAccountRouter": "0xb2674E213019972f937CCFc5e23BF963D915809e", + "interchainAccountRouter": "0xbED975874FD8b5Be7358988Bb856d9FD77C2Df01", "timelockController": "0x0000000000000000000000000000000000000000" }, "redstone": { @@ -2495,10 +2537,9 @@ "index": { "from": 1797579 }, - "interchainAccountIsm": "0x5DA60220C5dDe35b7aE91c042ff5979047FA0785", - "interchainAccountRouter": "0x7a4d31a686A36285d68e14EDD53631417eB19603", + "interchainAccountRouter": "0x27e88AeB8EA4B159d81df06355Ea3d20bEB1de38", "interchainGasPaymaster": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", - "interchainSecurityModule": "0x30e3173CbE5e075CD8d030087D6186242Ce154C4", + "interchainSecurityModule": "0x1Ef02F4505e6B6581cEfbC28AC0bF2f5B94378ab", "mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "merkleTreeHook": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", "name": "redstone", @@ -2530,76 +2571,12 @@ "staticMessageIdWeightedMultisigIsmFactory": "0x7B8AA8f23Ab6B0757eC6FC71894211376D9335b0", "technicalStack": "opstack" }, - "sanko": { - "aggregationHook": "0xF6C1769d5390Be0f77080eF7791fBbA7eF4D5659", - "blockExplorers": [ - { - "apiUrl": "https://explorer.sanko.xyz/api/eth-rpc", - "family": "blockscout", - "name": "Sanko Explorer", - "url": "https://explorer.sanko.xyz" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 15, - "reorgPeriod": 1 - }, - "chainId": 1996, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Sanko", - "domainId": 1996, - "domainRoutingIsm": "0xaDc0cB48E8DB81855A930C0C1165ea3dCe4Ba5C7", - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "fallbackRoutingHook": "0xd21192429df453021e896f2897Dc8B1167DD61E5", - "gasCurrencyCoinGeckoId": "dream-machine-token", - "index": { - "from": 937117 - }, - "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0xfEc0c5265EF15317AFda1B952cd351B1Ad904ce7", - "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", - "name": "sanko", - "nativeToken": { - "decimals": 18, - "name": "Dream Machine Token", - "symbol": "DMT" - }, - "pausableHook": "0x61594D2cA900C44ab51d07776465397FefC643C6", - "pausableIsm": "0x92cdbF0Ccdf8E93467FA858fb986fa650A02f2A8", - "protocol": "ethereum", - "protocolFee": "0xDab56C5A1EffFdd23f6BD1243E457B1575984Bc6", - "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "rpcUrls": [ - { - "http": "https://mainnet.sanko.xyz" - } - ], - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsm": "0x203B8cEe54c0875d7b3384722636B5Ef4A4D81f7", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "storageGasOracle": "0x7b2e996742fA42d223652A344252B725D1bC428C", - "technicalStack": "arbitrumnitro", - "testRecipient": "0x2c61Cda929e4e2174cb10cd8e2724A9ceaD62E67", - "validatorAnnounce": "0x062200d92dF6bB7bA89Ce4D6800110450f94784e", - "staticMerkleRootWeightedMultisigIsmFactory": "0x749848D7b783A328638C3ea74AcFcfb73c977CbE", - "staticMessageIdWeightedMultisigIsmFactory": "0x148CF67B8A242c1360bb2C93fCe203EC4d4f9B56", - "interchainAccountIsm": "0x5DA60220C5dDe35b7aE91c042ff5979047FA0785", - "interchainAccountRouter": "0x7a4d31a686A36285d68e14EDD53631417eB19603", - "timelockController": "0x0000000000000000000000000000000000000000" - }, "scroll": { "aggregationHook": "0x9Bc0FAf446E128a618A88a2F28960Fb2Ca169faE", "blockExplorers": [ { - "apiKey": "8MU9QJVN429TEYSCMB68NC5MG4VM542CFW", - "apiUrl": "https://api.scrollscan.com/api", + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=534352", "family": "etherscan", "name": "Scroll Explorer", "url": "https://scrollscan.com/" @@ -2626,10 +2603,9 @@ "chunk": 999, "from": 271840 }, - "interchainAccountIsm": "0x32af5Df81fEd5E26119F6640FBB13f3d63a94CDe", - "interchainAccountRouter": "0x0B48a744698ba8dFa514742dFEB6728f52fD66f7", + "interchainAccountRouter": "0x7E4a3CdF715650A2EF407C86186ec8Fd2d1fb46c", "interchainGasPaymaster": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", - "interchainSecurityModule": "0xF7B78b4Bca8a4D693dA8ACF4D8954348fa398E8c", + "interchainSecurityModule": "0x8Ae63403153Ae64819949C18af9Ca42024108155", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", "name": "scroll", @@ -2708,10 +2684,9 @@ "from": 80809403, "chunk": 1000 }, - "interchainAccountIsm": "0xf35dc7B9eE4Ebf0cd3546Bd6EE3b403dE2b9F5D6", - "interchainAccountRouter": "0xBcaedE97a98573A88242B3b0CB0A255F3f90d4d5", + "interchainAccountRouter": "0xA70482D7359816809988AC4053d83F0C8C98D292", "interchainGasPaymaster": "0xFC62DeF1f08793aBf0E67f69257c6be258194F72", - "interchainSecurityModule": "0xD81c581ee3678536ef2BCAcF36796c6A0aC4f089", + "interchainSecurityModule": "0x46093f5E403835a80E8bB383A995137535A8D31a", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xca1b69fA4c4a7c7fD839bC50867c589592bcfe49", "name": "sei", @@ -2790,15 +2765,15 @@ } ], "validatorAnnounce": "pRgs5vN4Pj7WvFbxf6QDHizo2njq2uksqEUbaSghVA8", - "interchainSecurityModule": "EpAuVN1oc5GccKAk41VMBHTgzJFtB5bftvi92SywQdbS", + "interchainSecurityModule": "Da6Lp9syj8hLRiqjZLTLbZEC1NPhPMPd1JJ3HQRN4NyJ", "technicalStack": "other" }, "taiko": { "aggregationHook": "0x1175A31f66C5e3d0ce0ca3B7F80Abe72c6FcE272", "blockExplorers": [ { - "apiKey": "1CDVSH9KFUVPSFGD1EUFNHD18FUH484IEK", - "apiUrl": "https://api.taikoscan.io/api", + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=167000", "family": "etherscan", "name": "Taikoscan", "url": "https://taikoscan.io" @@ -2824,10 +2799,9 @@ "index": { "from": 98997 }, - "interchainAccountIsm": "0xAE557e108b3336130370aC74836f1356B4b30Cf2", - "interchainAccountRouter": "0x1F8CF09F060A2AE962c0Bb1F92e209a1E7b0E10B", + "interchainAccountRouter": "0xEE47aD8f6582CDcBF4B8581A1c3482E72E4DeaBf", "interchainGasPaymaster": "0x273Bc6b01D9E88c064b6E5e409BdF998246AEF42", - "interchainSecurityModule": "0x0deC2cff54beDA85465433EfBb0A9E668C1a00b4", + "interchainSecurityModule": "0x7136c4441485A5bD5Ed6EEABBA82CfE5303D1425", "mailbox": "0x28EFBCadA00A7ed6772b3666F3898d276e88CAe3", "merkleTreeHook": "0x6A55822cf11f9fcBc4c75BC2638AfE8Eb942cAdd", "name": "taiko", @@ -2889,7 +2863,7 @@ "from": 1678063 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0x5c7EB7c71e38b43d3Ca61866865FEFac37AEC0b8", + "interchainSecurityModule": "0xd2e76F1F22587Bd54bE5600d71c42F6033989D2F", "isTestnet": false, "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", @@ -2919,8 +2893,7 @@ "validatorAnnounce": "0x062200d92dF6bB7bA89Ce4D6800110450f94784e", "staticMerkleRootWeightedMultisigIsmFactory": "0x148CF67B8A242c1360bb2C93fCe203EC4d4f9B56", "staticMessageIdWeightedMultisigIsmFactory": "0xcd849e612Aaa138f03698C3Edb42a34117BFF631", - "interchainAccountIsm": "0x45285463352c53a481e882cD5E2AF2E25BBdAd0D", - "interchainAccountRouter": "0x67F36550b73B731e5b2FC44E4F8f250d89c87bD6", + "interchainAccountRouter": "0xf63Eb8e72Cbc59eC1185620c44050aF266d3eC19", "timelockController": "0x0000000000000000000000000000000000000000", "technicalStack": "polkadotsubstrate" }, @@ -2954,7 +2927,6 @@ "chunk": 999, "from": 73573878 }, - "interchainAccountIsm": "0x551BbEc45FD665a8C95ca8731CbC32b7653Bc59B", "interchainAccountRouter": "0xc11f8Cf2343d3788405582F65B8af6A4F7a6FfC8", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", "interchainSecurityModule": "0x875a04AAdE245e9509731d33CDffCCB4184babcb", @@ -3000,11 +2972,17 @@ "aggregationHook": "0x8007d1e60991fB9BE1be26f70A7cE284fdE7da97", "blockExplorers": [ { - "apiKey": "BA89EI7MB9Z88UBURAYPRRRUKCQJB96KAE", - "apiUrl": "https://api.worldscan.org/api", + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=480", "family": "etherscan", "name": "Worldscan", "url": "https://worldscan.org" + }, + { + "apiUrl": "https://worldchain-mainnet.explorer.alchemy.com/api", + "family": "blockscout", + "name": "World Chain Explorer", + "url": "https://worldchain-mainnet.explorer.alchemy.com" } ], "blocks": { @@ -3026,10 +3004,9 @@ "index": { "from": 1328243 }, - "interchainAccountIsm": "0xCB9f90EE5d83Ea52ABd922BD70898f0155D54798", - "interchainAccountRouter": "0x473884010F0C1742DA8Ad01E7E295624B931076b", + "interchainAccountRouter": "0xd55bFDfb3486fE49a0b2E2Af324453452329051F", "interchainGasPaymaster": "0x7E27456a839BFF31CA642c060a2b68414Cb6e503", - "interchainSecurityModule": "0x861D017b4b0f26e16695D4A38820CD2C58b20C0A", + "interchainSecurityModule": "0x93EC0f146456868721B17630a2e57cc8e486811D", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x0054D19613f20dD72721A146ED408971a2CCA9BD", "name": "worldchain", @@ -3098,7 +3075,7 @@ "from": 24395308 }, "interchainGasPaymaster": "0x9844aFFaBE17c37F791ff99ABa58B0FbB75e22AF", - "interchainSecurityModule": "0x4fa8C55477f58b2AC71c6d599b1baF7a7EFBA43d", + "interchainSecurityModule": "0x834f36e14Afc641302d2e212f80Df55FD279E3af", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "name": "xai", @@ -3128,8 +3105,7 @@ "validatorAnnounce": "0x062200d92dF6bB7bA89Ce4D6800110450f94784e", "staticMerkleRootWeightedMultisigIsmFactory": "0x71388C9E25BE7b229B5d17Df7D4DB3F7DA7C962d", "staticMessageIdWeightedMultisigIsmFactory": "0x3E969bA938E6A993eeCD6F65b0dd8712B07dFe59", - "interchainAccountIsm": "0xD8aF449f8fEFbA2064863DCE5aC248F8B232635F", - "interchainAccountRouter": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94", + "interchainAccountRouter": "0xA8aB763aB8ab133236afc7b31aFC606F268048f5", "timelockController": "0x0000000000000000000000000000000000000000" }, "xlayer": { @@ -3164,10 +3140,9 @@ "chunk": 999, "from": 3387690 }, - "interchainAccountIsm": "0x29B37088724B745C0ABcE591449Cf042772160C2", - "interchainAccountRouter": "0x03cF708E42C89623bd83B281A56935cB562b9258", + "interchainAccountRouter": "0x39d3c2Cf646447ee302178EDBe5a15E13B6F33aC", "interchainGasPaymaster": "0x7E27456a839BFF31CA642c060a2b68414Cb6e503", - "interchainSecurityModule": "0x8aBCCB4F714B563bDD8e77112Be4E6f18F8b3b2F", + "interchainSecurityModule": "0x4c57ad0305F01E114870Db842dDB22D49c38088a", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0x0054D19613f20dD72721A146ED408971a2CCA9BD", "name": "xlayer", @@ -3232,10 +3207,9 @@ "index": { "from": 3068132 }, - "interchainAccountIsm": "0x2b6d3F7d28B5EC8C3C028fBCAdcf774D9709Dd29", - "interchainAccountRouter": "0x3AdCBc94ab8C48EC52D06dc65Bb787fD1981E3d5", + "interchainAccountRouter": "0x6783dC9f1Bf88DC554c8716c4C42C5bf640dDcc8", "interchainGasPaymaster": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "interchainSecurityModule": "0x2B015ec6e8aFbb2Ec145a2D65938Fdb9Aa89B64e", + "interchainSecurityModule": "0x93804c4b2AecD2EdD1aCa618E6E2EfF426922a3B", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "merkleTreeHook": "0xE2ee936bEa8e42671c400aC96dE198E06F2bA2A6", "name": "zetachain", @@ -3283,7 +3257,7 @@ "blocks": { "confirmations": 1, "estimateBlockTime": 2, - "reorgPeriod": 5 + "reorgPeriod": 300 }, "chainId": 48900, "deployer": { @@ -3301,7 +3275,7 @@ "from": 1511458 }, "interchainGasPaymaster": "0x03cF708E42C89623bd83B281A56935cB562b9258", - "interchainSecurityModule": "0xA2559AD085B13236C27CE14a8405b613020bd69a", + "interchainSecurityModule": "0x2EaaB11C74a49A346a17EbE8EAC1280167472F9C", "mailbox": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", "merkleTreeHook": "0x4C97D35c668EE5194a13c8DE8Afc18cce40C9F28", "name": "zircuit", @@ -3336,8 +3310,7 @@ "validatorAnnounce": "0x5366362c41e34869BDa231061603E4356D66079D", "staticMerkleRootWeightedMultisigIsmFactory": "0x13E83ac41e696856B6996263501fB3225AD5E6F5", "staticMessageIdWeightedMultisigIsmFactory": "0x61374178e45F65fF9D6252d017Cd580FC60B7654", - "interchainAccountIsm": "0xd386Bb418B61E296e1689C95AfE94A2E321a6eaD", - "interchainAccountRouter": "0x51545389E04c2Ac07d98A40b85d29B480a2AF6ce", + "interchainAccountRouter": "0xf20414268e76d0e943533aFa1F2b99DBfb4e0F71", "timelockController": "0x0000000000000000000000000000000000000000", "technicalStack": "opstack" }, @@ -3371,10 +3344,9 @@ "index": { "from": 17180113 }, - "interchainAccountIsm": "0xb2674E213019972f937CCFc5e23BF963D915809e", - "interchainAccountRouter": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", + "interchainAccountRouter": "0x9e6B1022bE9BBF5aFd152483DAD9b88911bC8611", "interchainGasPaymaster": "0x18B0688990720103dB63559a3563f7E8d0f63EDb", - "interchainSecurityModule": "0x2548561D15bCD44795b7477C83a00a76F3CB8ff7", + "interchainSecurityModule": "0x2c34688447c091351a8BC3345b56375C1F8060dF", "mailbox": "0xF5da68b2577EF5C0A0D98aA2a58483a68C2f232a", "merkleTreeHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", "name": "zoramainnet", @@ -3445,7 +3417,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0xAfa1266a1745ae8d01956dCBf87C65F82F3AB89D", + "interchainSecurityModule": "0xCf7BE9231b1E14b5Eeeed1cd3A750a1649fB3d6c", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3465,8 +3437,7 @@ "index": { "from": 6898609 }, - "interchainAccountIsm": "0xd01A3E167d59FF98c983E83BAa5da0C3e0ADe726", - "interchainAccountRouter": "0x5090dF2FBDa7127c7aDa41f60B79F5c55D380Dd8", + "interchainAccountRouter": "0xC3d9e724c6Bf3c4456EB8572Be05AA52f8acC9Ae", "timelockController": "0x0000000000000000000000000000000000000000", "technicalStack": "polkadotsubstrate" }, @@ -3515,7 +3486,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0xC8f05b14ef93302922bbe384C1bAd799d07aE71F", + "interchainSecurityModule": "0x262dBc6776Fb199010F40223a90d9d095706EF7C", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3535,8 +3506,7 @@ "index": { "from": 4162791 }, - "interchainAccountIsm": "0xa97ec3E58cBd60199dcFDd6396431BE85c2E363e", - "interchainAccountRouter": "0xA0a44cB8Bc0f7EDe788b0Cd29524A5b14fED7b45", + "interchainAccountRouter": "0xE0208ddBe76c703eb3Cd758a76e2c8c1Ff9472fD", "timelockController": "0x0000000000000000000000000000000000000000", "technicalStack": "other", "gnosisSafeTransactionServiceUrl": "https://multisign.bitlayer.org/txs/" @@ -3586,7 +3556,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0xDD64719b0EA7Bf80fcE3d104c6d2554cEF518e9b", + "interchainSecurityModule": "0x81F6f0db136A13Ea7a08DAfc4B5164dde601Ee8f", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3606,8 +3576,7 @@ "index": { "from": 17154537 }, - "interchainAccountIsm": "0x87bDFaBbCC36D8B1aEdA871Cd54b2e86C7a4d597", - "interchainAccountRouter": "0xd01A3E167d59FF98c983E83BAa5da0C3e0ADe726", + "interchainAccountRouter": "0xB8736c87da7DEc750fA0226e3bdE1Ac35B88f43d", "timelockController": "0x0000000000000000000000000000000000000000", "technicalStack": "other" }, @@ -3650,7 +3619,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0x68F852665236495abF8daf87DfDE7e7395bB07d3", + "interchainSecurityModule": "0xEd55A99Cd3851415B30d6B6685AaC44828c66DE7", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3670,8 +3639,7 @@ "index": { "from": 31903424 }, - "interchainAccountIsm": "0x87bDFaBbCC36D8B1aEdA871Cd54b2e86C7a4d597", - "interchainAccountRouter": "0xd01A3E167d59FF98c983E83BAa5da0C3e0ADe726", + "interchainAccountRouter": "0xe05f59ec3AE5B475050a735522Def832F602152f", "timelockController": "0x0000000000000000000000000000000000000000", "technicalStack": "polygoncdk" }, @@ -3726,7 +3694,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0x59a2246f8fA908F3710f4A723F34189450b0D585", + "interchainSecurityModule": "0xe13F1Ec344B36c56619c8151cB2496bA57560647", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3744,11 +3712,10 @@ "testRecipient": "0xbB22547D1dc681fe925f568f637Ff67aC06c20fc", "validatorAnnounce": "0xb4fc9B5fD57499Ef6FfF3995728a55F7A618ef86", "index": { - "chunk": 30, + "chunk": 29, "from": 28949565 }, - "interchainAccountIsm": "0xc2Da384799488B4e1E773d70a83346529145085B", - "interchainAccountRouter": "0x87bDFaBbCC36D8B1aEdA871Cd54b2e86C7a4d597", + "interchainAccountRouter": "0xC1272CCea251c85b7D11eDeD1204a88DEde90f46", "timelockController": "0x0000000000000000000000000000000000000000", "technicalStack": "other" }, @@ -3795,7 +3762,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0x551Fd08520D96ae454734ea1fA5484654Daf14cB", + "interchainSecurityModule": "0xddfc625BA9DdC42554ADdEB631F6DED5aA3E119c", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3812,8 +3779,7 @@ "storageGasOracle": "0xA38D1D7F217A52A27b0e6BF50E0a9ddAD05798C0", "testRecipient": "0xbB22547D1dc681fe925f568f637Ff67aC06c20fc", "validatorAnnounce": "0xb4fc9B5fD57499Ef6FfF3995728a55F7A618ef86", - "interchainAccountIsm": "0x87bDFaBbCC36D8B1aEdA871Cd54b2e86C7a4d597", - "interchainAccountRouter": "0xd01A3E167d59FF98c983E83BAa5da0C3e0ADe726", + "interchainAccountRouter": "0xCf42106b85fC72c43Ac4976f20fA2aD7D9592c31", "timelockController": "0x0000000000000000000000000000000000000000" }, "shibarium": { @@ -3858,7 +3824,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xc401e251CCa7A364114504A994D6fC7cb1c243AB", "interchainGasPaymaster": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", - "interchainSecurityModule": "0xdd2d11423800F8b7f064f4d448B2560df4fA86E1", + "interchainSecurityModule": "0x219c497188C7edC88FaCBbbef21969A3F090715f", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x441a01Fca2eD731C0Fc4633998332f9FEDB17575", "pausableHook": "0x5Ed813B8b41f25c8002B01A72bbDBe6A0232Fe27", @@ -3878,12 +3844,12 @@ "index": { "from": 6519337 }, - "interchainAccountIsm": "0xA0a44cB8Bc0f7EDe788b0Cd29524A5b14fED7b45", - "interchainAccountRouter": "0xf3dFf6747E7FC74B431C943961054B7BF6309d8a", + "interchainAccountRouter": "0xa4fc7C90a4D4ae2A11637D04A6c5286E00B4bAA0", "timelockController": "0x0000000000000000000000000000000000000000", "technicalStack": "other" }, "everclear": { + "batchContractAddress": "0xeA7576da1b5f66559d7c84A57E39EdcDB1CD27C2", "blockExplorers": [ { "apiUrl": "https://scan.everclear.org/api", @@ -3925,10 +3891,9 @@ "domainRoutingIsm": "0xDEed16fe4b1c9b2a93483EDFf34C77A9b57D31Ff", "domainRoutingIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "fallbackRoutingHook": "0x3C2b535a49c6827DF0b8e94467e6922c99E3c092", - "interchainAccountIsm": "0xcd9D3744512F07AE844c40E27912092d7c503565", - "interchainAccountRouter": "0x92cdbF0Ccdf8E93467FA858fb986fa650A02f2A8", + "interchainAccountRouter": "0x6FD739221F53F8dc1565F3aF830Cb687cfe5932D", "interchainGasPaymaster": "0xb58257cc81E47EC72fD38aE16297048de23163b4", - "interchainSecurityModule": "0xB5203CBd5404163ccaDD8BB68cb167B62Eb14986", + "interchainSecurityModule": "0x54d71e2E49BD459ef049dBcAc142cf696c7b3d14", "mailbox": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", "merkleTreeHook": "0xCC3D1659D50461d27a2F025dDb2c9B06B584B7e1", "pausableHook": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", @@ -3985,10 +3950,9 @@ "domainRoutingIsm": "0x4101B9B755FC58FcEA156e70B42a38CFF8A46F77", "domainRoutingIsmFactory": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "fallbackRoutingHook": "0x168DFF0Ad2b180F3801883Fe5Ae56d7E7d91D5f4", - "interchainAccountIsm": "0xc23BaF5Eb5848D19701BbE7f139645e6bd58a319", - "interchainAccountRouter": "0x7c58Cadcc2b60ACF794eE1843488d6f5703f76BE", + "interchainAccountRouter": "0x6c3b61e60Ff510E35Ba51D25bb2E0F90B0307E7D", "interchainGasPaymaster": "0xb4fc9B5fD57499Ef6FfF3995728a55F7A618ef86", - "interchainSecurityModule": "0xe040e626EE3a0a2D91C7fba2e8C5C793189Bf9ea", + "interchainSecurityModule": "0x084f046a4df81BC12fE34ef625395EA1472D6B2b", "mailbox": "0xb129828B9EDa48192D0B2db35D0E40dCF51B3594", "merkleTreeHook": "0x3E969bA938E6A993eeCD6F65b0dd8712B07dFe59", "pausableHook": "0x6Fb36672365C7c797028C400A61c58c0ECc53cD2", @@ -4116,10 +4080,9 @@ "domainRoutingIsm": "0xDEed16fe4b1c9b2a93483EDFf34C77A9b57D31Ff", "domainRoutingIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "fallbackRoutingHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", - "interchainAccountIsm": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1", - "interchainAccountRouter": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", + "interchainAccountRouter": "0xb347c2cbfc32e0bdf365183635352e0C38c97147", "interchainGasPaymaster": "0x18B0688990720103dB63559a3563f7E8d0f63EDb", - "interchainSecurityModule": "0x94D90fd47D3AdcFC386022C34196B43228A686b3", + "interchainSecurityModule": "0xBE5196cde6c9fdecBf30F5bc50988414bCd5D267", "mailbox": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", "merkleTreeHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", "pausableHook": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", @@ -4180,10 +4143,9 @@ "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "fallbackRoutingHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", - "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", - "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "interchainAccountRouter": "0xf2F83b26d56f0e9B9Bd81efAb9e0ECB9ba5708be", "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "interchainSecurityModule": "0x437f4A02596f7930A811a8f826FA4d0B7BDA52eB", + "interchainSecurityModule": "0x753C49F4c7eEaaE2e36B8cC5D82eC208fa0BB5a8", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", @@ -4241,10 +4203,9 @@ "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "fallbackRoutingHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", - "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", - "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "interchainAccountRouter": "0x0fC7b3518C03BfA5e01995285b1eF3c4B55c8922", "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "interchainSecurityModule": "0xD4bE57Fe566ca02C7Fb813f766d63FeB4347508f", + "interchainSecurityModule": "0xB628a55eD61F41869984c6798F2dCd2872201d72", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", @@ -4320,10 +4281,9 @@ "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "fallbackRoutingHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", - "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", - "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "interchainAccountRouter": "0x01016c0A5118dBD87E34a50fF1a5D8D9306aAa2e", "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "interchainSecurityModule": "0xbb33f36ab018A3dc6ADd9CbC84916fE5bAE213DD", + "interchainSecurityModule": "0xc45702AE2C5aC25693A228892D768B3b9Bcd691c", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", @@ -4387,10 +4347,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", - "interchainAccountIsm": "0x783EC5e105234a570eB90f314284E5dBe53bdd90", - "interchainAccountRouter": "0xc5D6aCaafBCcEC6D7fD7d92F4509befce641c563", + "interchainAccountRouter": "0x335593971F655220a760837b64fbeABd09dE6dD9", "interchainGasPaymaster": "0xf3dFf6747E7FC74B431C943961054B7BF6309d8a", - "interchainSecurityModule": "0x5Df7c22dD0f7e1Bd1558f5789f4eCbBB399261b1", + "interchainSecurityModule": "0xC318bf7bfA5579A50B3589D6a1fC107010A29C13", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x5090dF2FBDa7127c7aDa41f60B79F5c55D380Dd8", "pausableHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", @@ -4455,10 +4414,9 @@ "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "fallbackRoutingHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", - "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", - "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "interchainAccountRouter": "0xFCfE7344d7a769C89B3A22c596fE83a1bF8458Da", "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "interchainSecurityModule": "0x26E1d60362332B5805D787526E9e9dA83A68Beea", + "interchainSecurityModule": "0xA361BCa32dd89197ec82b61B76642B7887852580", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", @@ -4520,10 +4478,9 @@ "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "fallbackRoutingHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", - "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", - "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "interchainAccountRouter": "0xcfe6dBaD47c3B8cf4fecbb28B53Df4617F8538A7", "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "interchainSecurityModule": "0xf911F62809A0007bffDB4954893B31A57314C3Fb", + "interchainSecurityModule": "0xa4cDCbe5743BB9B6946332a9E8CF358640cdA44f", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", @@ -4584,10 +4541,9 @@ "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "fallbackRoutingHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", - "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", - "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "interchainAccountRouter": "0x36E437699E3658396Bf6229ddDaE54884cf28779", "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "interchainSecurityModule": "0x94FfE47A1536cFaCC1C53463E8f77569FC9eaDaE", + "interchainSecurityModule": "0x994008262d7eE8c2A166100eD1f9ab37fFC961C0", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", @@ -4651,10 +4607,9 @@ "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "fallbackRoutingHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", - "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", - "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "interchainAccountRouter": "0x9121E58Cb02890cEEF1a21EF4B80420eC2b8B61C", "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "interchainSecurityModule": "0x393BDD9D730647ba8a0Fc209dD304B01f6AeaD35", + "interchainSecurityModule": "0x7a9265aE4AFb44A9b317B2E6c70700fef38C1F6e", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", @@ -4715,10 +4670,9 @@ "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "fallbackRoutingHook": "0x886BB0f329781b98f98FDeb1ce7a8957F2d43B9F", - "interchainAccountIsm": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", - "interchainAccountRouter": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", + "interchainAccountRouter": "0x9121E58Cb02890cEEF1a21EF4B80420eC2b8B61C", "interchainGasPaymaster": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "interchainSecurityModule": "0x076055e4ce4eCEa4D9d37739773dFAa6eA93A34D", + "interchainSecurityModule": "0x7a9265aE4AFb44A9b317B2E6c70700fef38C1F6e", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x6963480b05EB58f4d624B014ab92e9aD4d21df6D", "pausableHook": "0xD0dca420feFda68537695A8D887080eeF4030AF7", @@ -4780,7 +4734,7 @@ "fallbackDomainRoutingHook": "0x671836d35BB15E21ECc92c4936F0e3131efe12B4", "fallbackRoutingHook": "0x671836d35BB15E21ECc92c4936F0e3131efe12B4", "interchainGasPaymaster": "0x318FbdB17d4e743aBF3183658a4730777101B75C", - "interchainSecurityModule": "0x17e89240A20eB14B902f91EB15b16303BEe6C3EE", + "interchainSecurityModule": "0xAe51C9EcF4A111e478e9f8AB04a9005B5E385d1e", "mailbox": "0xd7b351D2dE3495eA259DD10ab4b9300A378Afbf3", "merkleTreeHook": "0x55379421409961Ef129738c24261379ef8A547Df", "proxyAdmin": "0x72e2A678442Edc65f14476A0E4c94312C0469f4A", @@ -4838,7 +4792,7 @@ "fallbackDomainRoutingHook": "0xe4e98Cc5D0318aBFD2adA8A3C6817b727063F500", "fallbackRoutingHook": "0xe4e98Cc5D0318aBFD2adA8A3C6817b727063F500", "interchainGasPaymaster": "0xf44AdA86a1f765A938d404699B8070Dd47bD2431", - "interchainSecurityModule": "0x3f49308B3d4861cFA7c7f075524e7144d0Fbc9d2", + "interchainSecurityModule": "0xf55CB3c8C4b20276AF9F979B0D94Bd7ae5487fa2", "mailbox": "0x6bD0A2214797Bc81e0b006F7B74d6221BcD8cb6E", "merkleTreeHook": "0x823500D69D77A52212DC93f8836E9c08581487eE", "proxyAdmin": "0xD01274DC164D32F8595bE707F221375E68cE300C", @@ -4902,10 +4856,9 @@ "domainRoutingIsm": "0x9b0759e8Aec446f77ec4b7Ec20B1883d785139A4", "domainRoutingIsmFactory": "0xD127D4549cb4A5B2781303a4fE99a10EAd13263A", "fallbackRoutingHook": "0xE7487b4DF583c63D6841997ab56324D0a825e7F4", - "interchainAccountIsm": "0x4d264424905535E97396Db83bd553D0d73A4EF9d", - "interchainAccountRouter": "0x26A29486480BD74f9B830a9B8dB33cb43C40f496", + "interchainAccountRouter": "0xAab1D11E2063Bae5EB01fa946cA8d2FDe3db05D5", "interchainGasPaymaster": "0x9c2214467Daf9e2e1F45b36d08ce0b9C65BFeA88", - "interchainSecurityModule": "0x5f205659F3d697b64615B289a6c1858729954084", + "interchainSecurityModule": "0x55A3Dac7240C22422AF0948eA6432f47dfB806B4", "mailbox": "0x5bdADEAD721Eb4C4038fF7c989E3C7BbBA302435", "merkleTreeHook": "0x2684C6F89E901987E1FdB7649dC5Be0c57C61645", "pausableHook": "0xC8E323036AAFB4B4201e7B640E79C4Db285A3FC8", @@ -4966,10 +4919,9 @@ "domainRoutingIsm": "0xdAEe23c103419600F3B96f4DbA3858d4a1c3eaaf", "domainRoutingIsmFactory": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", "fallbackRoutingHook": "0x3CBCa631A797fd4c6332Cd4435C92065AFb57ef8", - "interchainAccountIsm": "0x545E289B88c6d97b74eC0B96e308cae46Bf5f832", - "interchainAccountRouter": "0x4ef363Da5bb09CC6aeA16973786963d0C8820778", + "interchainAccountRouter": "0x246BBe3983C22553362A42aa4B06320E2fB4E880", "interchainGasPaymaster": "0x561BcA8D862536CD9C88f332C1A1Da0fC8F96e40", - "interchainSecurityModule": "0x45E614f9Cb1b244fB97248593BF194d1b17a3281", + "interchainSecurityModule": "0x4920d447a81fE7201BbDd342ec34bc73e381cfaF", "mailbox": "0x248aDe14C0489E20C9a7Fea5F86DBfC3702208eF", "merkleTreeHook": "0x9c2214467Daf9e2e1F45b36d08ce0b9C65BFeA88", "pausableHook": "0x2f536FB7a37bd817Af644072a904Ddc02Dae429f", @@ -4994,70 +4946,6 @@ "maxPriorityFeePerGas": 1000000000 } }, - "flame": { - "blockExplorers": [ - { - "apiUrl": "https://explorer.flame.astria.org/api", - "family": "blockscout", - "name": "Astria Flame Explorer", - "url": "https://explorer.flame.astria.org" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 2, - "reorgPeriod": 5 - }, - "chainId": 253368190, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Flame", - "domainId": 253368190, - "gasCurrencyCoinGeckoId": "celestia", - "name": "flame", - "nativeToken": { - "decimals": 18, - "name": "Celestia", - "symbol": "TIA" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://rpc.flame.astria.org" - } - ], - "technicalStack": "other", - "aggregationHook": "0xf6D7BE913f90A4fB02a248837409ee208e10C647", - "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "fallbackRoutingHook": "0xf3dFf6747E7FC74B431C943961054B7BF6309d8a", - "interchainAccountIsm": "0x60bB6D060393D3C206719A7bD61844cC82891cfB", - "interchainAccountRouter": "0x9534122Aae7978dB8f5f10dF4432233c53e820A1", - "interchainGasPaymaster": "0x61374178e45F65fF9D6252d017Cd580FC60B7654", - "interchainSecurityModule": "0x05CEf1BDa2de71F0b830f372Af9a73276374353b", - "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "merkleTreeHook": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256", - "pausableHook": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "pausableIsm": "0x2F619Ac5122689180AeBB930ADccdae215d538a9", - "protocolFee": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", - "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsm": "0x7c6A51101Ba76EC1f358C11d349fbEd9486B62Ee", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "storageGasOracle": "0x18B0688990720103dB63559a3563f7E8d0f63EDb", - "testRecipient": "0xeE8C0E1EeBfFCC451a013336386eA53E42a44451", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0xc5D6aCaafBCcEC6D7fD7d92F4509befce641c563", - "index": { - "from": 395313 - } - }, "flowmainnet": { "blockExplorers": [ { @@ -5098,10 +4986,9 @@ "domainRoutingIsm": "0xa2d4E4dF4972d5196c45bD7f9287827a712Dd27a", "domainRoutingIsmFactory": "0x693A4cE39d99e46B04cb562329e3F0141cA17331", "fallbackRoutingHook": "0x5CF4ce6Be17CaB6d86d724804e2f1f5040205594", - "interchainAccountIsm": "0xcdc31BA959DE8C035A03167ebAE1961208CDf172", - "interchainAccountRouter": "0x349831a180eE4265008C5FFB9465Ff97c1CF0028", + "interchainAccountRouter": "0x1D43Eb638ABF43B4147B7985402a4FfbDd89D4ac", "interchainGasPaymaster": "0x6AA10748a036a49Cb290C0e12B77319b76792D5E", - "interchainSecurityModule": "0xbF029BC5E4781B3a29bCFA19fc8475039D1C8d58", + "interchainSecurityModule": "0x655ded46e79cCA93A9017F7eaD2CE53FBD5d1E7b", "mailbox": "0xd9Cc2e652A162bb93173d1c44d46cd2c0bbDA59D", "merkleTreeHook": "0x2783D98CC073dbcDa90241C804d16982D3d75821", "pausableHook": "0x3bb2D0a828f7dD91bA786091F421f6d7cF376445", @@ -5168,10 +5055,9 @@ "domainRoutingIsm": "0x34397CaA9e6c1A5EF6440E82C46a83D1ef779dcF", "domainRoutingIsmFactory": "0xbed53B5C5BCE9433f25A2A702e6df13E22d84Ae9", "fallbackRoutingHook": "0x2b79328DA089E89A9E9c08732b56dd31F01011Db", - "interchainAccountIsm": "0x545E289B88c6d97b74eC0B96e308cae46Bf5f832", - "interchainAccountRouter": "0x4ef363Da5bb09CC6aeA16973786963d0C8820778", + "interchainAccountRouter": "0xE2cBbc708411eAf2DfbaA31DaA531d4FF089d7b0", "interchainGasPaymaster": "0xc6835e52C1b976F1ebC71Bc8919738E02849FdA9", - "interchainSecurityModule": "0x2c0Fb19eEE5bFE1aB56F04385a7B77e95b4D47DD", + "interchainSecurityModule": "0x746679B069BB74A76f184ecf6f78d9128a3A2cC6", "mailbox": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", "merkleTreeHook": "0xdAa1B65547fB969c9ff5678956AB2FF9771B883D", "pausableHook": "0xA0e0829DA397CcF55d5B779C31728f21Cb8219DF", @@ -5197,50 +5083,6 @@ "maxPriorityFeePerGas": 100000000000 } }, - "lumia": { - "blockExplorers": [ - { - "apiUrl": "https://explorer.lumia.org/api/eth-rpc", - "family": "blockscout", - "name": "Lumia Prism Explorer", - "url": "https://explorer.lumia.org" - } - ], - "blocks": { - "confirmations": 3, - "estimateBlockTime": 4, - "reorgPeriod": 5 - }, - "chainId": 994873017, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Lumia Prism", - "domainId": 994873017, - "gasCurrencyCoinGeckoId": "orion-protocol", - "name": "lumia", - "nativeToken": { - "decimals": 18, - "name": "Lumia", - "symbol": "LUMIA" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://mainnet-rpc.lumia.org" - } - ], - "technicalStack": "polygoncdk", - "index": { - "from": 1923136, - "chunk": 1000 - }, - "interchainGasPaymaster": "0x9024A3902B542C87a5C4A2b3e15d60B2f087Dc3E", - "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", - "merkleTreeHook": "0x9c44E6b8F0dB517C2c3a0478caaC5349b614F912", - "validatorAnnounce": "0x989B7307d266151BE763935C856493D968b2affF" - }, "metal": { "blockExplorers": [ { @@ -5281,10 +5123,9 @@ "domainRoutingIsm": "0x3F525c2bf9e2Ffc54D64B883FD960FD04874D2D1", "domainRoutingIsmFactory": "0x34b67302583A8Faa7c490A2D85463838D3045ce6", "fallbackRoutingHook": "0x50141343265C6f84315E5B27D64f1229AC86645F", - "interchainAccountIsm": "0x8c794a781327b819416E7b67908f1D22397f1E67", - "interchainAccountRouter": "0x16625230dD6cFe1B2bec3eCaEc7d43bA3A902CD6", + "interchainAccountRouter": "0x0b2d429acccAA411b867d57703F88Ed208eC35E4", "interchainGasPaymaster": "0x2b79328DA089E89A9E9c08732b56dd31F01011Db", - "interchainSecurityModule": "0x3905e12e040971b2D9757d132252D255364ca2fc", + "interchainSecurityModule": "0xd9cf772C2c5d5111e266396b6edfAE7369D9BdC4", "mailbox": "0x730f8a4128Fa8c53C777B62Baa1abeF94cAd34a9", "merkleTreeHook": "0x9c64f327F0140DeBd430aab3E2F1d6cbcA921227", "pausableHook": "0x2684C6F89E901987E1FdB7649dC5Be0c57C61645", @@ -5346,10 +5187,9 @@ "domainRoutingIsm": "0x8A82DE5b4EC1A4f1FAf86c74031c2D38B07e1E80", "domainRoutingIsmFactory": "0xc441521bA37EaCd9af4f319CcdA27E9D48f74281", "fallbackRoutingHook": "0xDd260B99d302f0A3fF885728c086f729c06f227f", - "interchainAccountIsm": "0xE67Dc24970B482579923551Ede52BD35a2858989", - "interchainAccountRouter": "0xDDE46032Baf4da13fDD79BF9dfbaA2749615C409", + "interchainAccountRouter": "0x1B947F6246ACe28abAf073FF11c098F31ce4f899", "interchainGasPaymaster": "0x2f536FB7a37bd817Af644072a904Ddc02Dae429f", - "interchainSecurityModule": "0xC48b9E93c08775A0cf41a83355Faac2883Dd1C2f", + "interchainSecurityModule": "0x472Bd0FDDD4d6f2DB4F45E9D3420050c641Ad3Ea", "mailbox": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", "merkleTreeHook": "0xC8E323036AAFB4B4201e7B640E79C4Db285A3FC8", "pausableHook": "0xdAa1B65547fB969c9ff5678956AB2FF9771B883D", @@ -5410,10 +5250,9 @@ "domainRoutingIsm": "0xF6C3854e195467625D3a9e50103751629b767cE5", "domainRoutingIsmFactory": "0xbb0AE51BCa526cF313b6a95BfaB020794af6C394", "fallbackRoutingHook": "0x5C2D2fB7F948895cf57f4723167DDEe016A462D1", - "interchainAccountIsm": "0x20a0A32a110362920597F72974E1E0d7e25cA20a", - "interchainAccountRouter": "0x5b3EeADcc0E2d4284eA6816e2E503c24d30a9E54", + "interchainAccountRouter": "0xd5D8c2d9A3F4974E89396928FEb7829d9C5e0788", "interchainGasPaymaster": "0x282629Af1A2f9b8e2c5Cbc54C35C7989f21950c6", - "interchainSecurityModule": "0xe6bfcB095175eF34525B4148C80b150CcF40086F", + "interchainSecurityModule": "0x9F6e4A2dBdF9CC417AFD257e52320b2c988Eee53", "mailbox": "0x5C02157068a52cEcfc98EDb6115DE6134EcB4764", "merkleTreeHook": "0xf147bBD944C610F86DaE6C7668497D22932C1E4A", "pausableHook": "0x872Bd98057931c8809927c6dE2ef39738a80Eb0C", @@ -5477,10 +5316,9 @@ "domainRoutingIsm": "0x43fEfD9323a9e71Df4cd9f32f1015efa4c906d10", "domainRoutingIsmFactory": "0x5DdFCA27f9a308c1429A010C4daB291b5534a297", "fallbackRoutingHook": "0x01EBa6D613DC09Cb899aF1e8E8a747416d7250ad", - "interchainAccountIsm": "0xf40eE9FF75Fa34910b7C4C8d68d4850B3bD184D3", - "interchainAccountRouter": "0xf6fB78dc009C1A4286c0E7d90C10c9E8906a62Ea", + "interchainAccountRouter": "0xecA217aB573506eaB6E51CDD1c3a84B626CDf7b4", "interchainGasPaymaster": "0xDDE46032Baf4da13fDD79BF9dfbaA2749615C409", - "interchainSecurityModule": "0xa8EaCD9b82E796D1AE8F33F9ae1584dA6d81d2B3", + "interchainSecurityModule": "0x4f341F99758ee243AE7f843f26dc8105e944F2C4", "mailbox": "0x65dCf8F6b3f6a0ECEdf3d0bdCB036AEa47A1d615", "merkleTreeHook": "0x8c794a781327b819416E7b67908f1D22397f1E67", "pausableHook": "0x4d264424905535E97396Db83bd553D0d73A4EF9d", @@ -5499,73 +5337,6 @@ "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x38D361861d321B8B05de200c61B8F18740Daf4D8" }, - "rootstockmainnet": { - "blockExplorers": [ - { - "apiUrl": "https://rootstock.blockscout.com/api", - "family": "blockscout", - "name": "Blockscout", - "url": "https://rootstock.blockscout.com" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 30, - "reorgPeriod": 4 - }, - "chainId": 30, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Rootstock", - "domainId": 1000000030, - "gasCurrencyCoinGeckoId": "rootstock", - "name": "rootstockmainnet", - "nativeToken": { - "decimals": 18, - "name": "Rootstock Smart Bitcoin", - "symbol": "RBTC" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://rpc.mainnet.rootstock.io/kXhXHf6TnnfW1POvr4UT0YUvujmuju-M" - } - ], - "technicalStack": "other", - "aggregationHook": "0x97efE0C88EBEBB8382473233a4975fC6b23DCe25", - "domainRoutingIsm": "0xB6F0f1267B01C27326F61a4B4fe2c73751802685", - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "fallbackRoutingHook": "0x282629Af1A2f9b8e2c5Cbc54C35C7989f21950c6", - "interchainAccountIsm": "0xd9Cc2e652A162bb93173d1c44d46cd2c0bbDA59D", - "interchainAccountRouter": "0x7279B1e11142078b8dC9e69620200f4C84FB8aaa", - "interchainGasPaymaster": "0x5ae1ECA065aC8ee92Ce98E584fc3CE43070020e7", - "interchainSecurityModule": "0xD26C7fdcCfe740aCE2CA76591600922f2460C6CF", - "mailbox": "0x96D51cc3f7500d501bAeB1A2a62BB96fa03532F8", - "merkleTreeHook": "0x086c3947F71BE98A0bDf4AB7239955e7542b0CbA", - "pausableHook": "0x9C6e8d989ea7F212e679191BEb44139d83ac927a", - "pausableIsm": "0x086A21A16eF1133BF58a09A77E2A9e07d3b95c1c", - "protocolFee": "0x8Ea50255C282F89d1A14ad3F159437EE5EF0507f", - "proxyAdmin": "0xe93f2f409ad8B5000431D234472973fe848dcBEC", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsm": "0xE5bdba0070d0665F5f61847277b201bD29113347", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "storageGasOracle": "0xa36172F79a248C24693a208b0CEF0e7D6FB995F0", - "testRecipient": "0x693A4cE39d99e46B04cb562329e3F0141cA17331", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0x20a0A32a110362920597F72974E1E0d7e25cA20a", - "index": { - "from": 6878186 - }, - "transactionOverrides": { - "gasPrice": 70000000 - } - }, "superpositionmainnet": { "blockExplorers": [ { @@ -5608,10 +5379,9 @@ "domainRoutingIsm": "0xab8178D8D0502Bef80177909a50F48b7c9269fde", "domainRoutingIsmFactory": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", "fallbackRoutingHook": "0x87ED6926abc9E38b9C7C19f835B41943b622663c", - "interchainAccountIsm": "0x8a733038eF4BbC314eE0F7595257D8d3799B6aA9", - "interchainAccountRouter": "0xCE8260c1b5cF2fAD15bb4B6542716b050Fdf35c9", + "interchainAccountRouter": "0x02b833b8a0fB7680e2d233176B54538c81505131", "interchainGasPaymaster": "0xa1c3884EbE24Cccb120B2E98a55f85140563aa4C", - "interchainSecurityModule": "0x701e426A0946D4c1EF688f67d52D5Db092F7C55e", + "interchainSecurityModule": "0xC7D031c95d152f3319248482916146578CBF9c06", "mailbox": "0x5e8a0fCc0D1DF583322943e01F02cB243e5300f6", "merkleTreeHook": "0x2f536FB7a37bd817Af644072a904Ddc02Dae429f", "pausableHook": "0xc6835e52C1b976F1ebC71Bc8919738E02849FdA9", @@ -5669,10 +5439,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x0D3bD9F1bcDA82bD1682b2C895a907d7aaE45849", - "interchainAccountIsm": "0x25EAC2007b0D40E3f0AF112FD346412321038719", - "interchainAccountRouter": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", + "interchainAccountRouter": "0x625324ebE9Fe13fEDD8ac3761F153b90aa35B404", "interchainGasPaymaster": "0x9534122Aae7978dB8f5f10dF4432233c53e820A1", - "interchainSecurityModule": "0xD45fabe41b63D222f0b1C2E200c32D89A01Ba0eb", + "interchainSecurityModule": "0x7738134B5fd65D583B5cBb0Be715B358E177624B", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", "pausableHook": "0x9eb56085DdbDA60aDf7d2B533AFeD90e38fC9666", @@ -5695,73 +5464,6 @@ }, "gnosisSafeTransactionServiceUrl": "https://transaction.safe.boba.network" }, - "duckchain": { - "blockExplorers": [ - { - "apiUrl": "https://scan.duckchain.io/api", - "family": "blockscout", - "name": "DuckChain Explorer", - "url": "https://scan.duckchain.io" - } - ], - "blocks": { - "confirmations": 3, - "estimateBlockTime": 1, - "reorgPeriod": 5 - }, - "chainId": 5545, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "DuckChain", - "domainId": 5545, - "gasCurrencyCoinGeckoId": "the-open-network", - "index": { - "from": 1149918 - }, - "name": "duckchain", - "nativeToken": { - "decimals": 18, - "name": "Toncoin", - "symbol": "TON" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://rpc.duckchain.io" - }, - { - "http": "https://rpc-hk.duckchain.io" - } - ], - "technicalStack": "arbitrumnitro", - "aggregationHook": "0x4533B9ff84bE4f4009409414aF8b8e9c3f4F52a8", - "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "fallbackRoutingHook": "0x0D3bD9F1bcDA82bD1682b2C895a907d7aaE45849", - "interchainAccountIsm": "0x25EAC2007b0D40E3f0AF112FD346412321038719", - "interchainAccountRouter": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", - "interchainGasPaymaster": "0x9534122Aae7978dB8f5f10dF4432233c53e820A1", - "interchainSecurityModule": "0x772C863c6AFd1F70E19D3B9054C48a1914a1c275", - "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "merkleTreeHook": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", - "pausableHook": "0x9eb56085DdbDA60aDf7d2B533AFeD90e38fC9666", - "pausableIsm": "0xc5D6aCaafBCcEC6D7fD7d92F4509befce641c563", - "protocolFee": "0x99fEFc1119E86Ee0153eb887cF8E8ab2d92A16e8", - "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsm": "0x46De8b87577624b9ce63201238982b95ad0d7Ea4", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "storageGasOracle": "0xbb0AE51BCa526cF313b6a95BfaB020794af6C394", - "testRecipient": "0xbB88a31E4b709b645c06825c0E0b5CAC906d97DE", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0xD743801ABB6c7664B623D8534C0f5AF8cD2F1C5e" - }, "superseed": { "blockExplorers": [ { @@ -5769,6 +5471,12 @@ "family": "blockscout", "name": "Superseed Explorer", "url": "https://explorer.superseed.xyz" + }, + { + "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/5330/etherscan/api", + "family": "routescan", + "name": "Superseed Explorer", + "url": "https://5330.thesuperscan.io" } ], "blocks": { @@ -5801,10 +5509,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x0D3bD9F1bcDA82bD1682b2C895a907d7aaE45849", - "interchainAccountIsm": "0x25EAC2007b0D40E3f0AF112FD346412321038719", - "interchainAccountRouter": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", + "interchainAccountRouter": "0x3CA0e8AEfC14F962B13B40c6c4b9CEE3e4927Ae3", "interchainGasPaymaster": "0x9534122Aae7978dB8f5f10dF4432233c53e820A1", - "interchainSecurityModule": "0xd63B75ED94d80EFF1d54aC47CCAb379636A37DA0", + "interchainSecurityModule": "0x5b24A2189eA9Be78508Dd45d7d965Fc147bd4e31", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", "pausableHook": "0x9eb56085DdbDA60aDf7d2B533AFeD90e38fC9666", @@ -5830,11 +5537,17 @@ "unichain": { "blockExplorers": [ { - "apiKey": "MUPHYQXB8A6GEEKKYA95N7WENSHYTU1UQQ", - "apiUrl": "https://api.uniscan.xyz/api", + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=130", "family": "etherscan", "name": "Unichain Explorer", "url": "https://uniscan.xyz" + }, + { + "apiUrl": "https://unichain.blockscout.com/api", + "family": "blockscout", + "name": "Unichain Blockscout", + "url": "https://unichain.blockscout.com" } ], "blocks": { @@ -5867,10 +5580,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x0D3bD9F1bcDA82bD1682b2C895a907d7aaE45849", - "interchainAccountIsm": "0x25EAC2007b0D40E3f0AF112FD346412321038719", - "interchainAccountRouter": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", + "interchainAccountRouter": "0x43320f6B410322Bf5ca326a0DeAaa6a2FC5A021B", "interchainGasPaymaster": "0x9534122Aae7978dB8f5f10dF4432233c53e820A1", - "interchainSecurityModule": "0x8A6a92c690953bB1F3AB2432c8CFd2b6C6771092", + "interchainSecurityModule": "0x7257C394D266c05FCe7154FAD900FBbC7fdb83f3", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", "pausableHook": "0x9eb56085DdbDA60aDf7d2B533AFeD90e38fC9666", @@ -5932,10 +5644,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x0D3bD9F1bcDA82bD1682b2C895a907d7aaE45849", - "interchainAccountIsm": "0x25EAC2007b0D40E3f0AF112FD346412321038719", - "interchainAccountRouter": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", + "interchainAccountRouter": "0x3adf8f4219BdCcd4B727B9dD67E277C58799b57C", "interchainGasPaymaster": "0x9534122Aae7978dB8f5f10dF4432233c53e820A1", - "interchainSecurityModule": "0x9a355e19B5A32122D6ACcB81BB597C53a4317BD8", + "interchainSecurityModule": "0xa2193Cac8EF19aB06712D812D671EC088560355c", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", "pausableHook": "0x9eb56085DdbDA60aDf7d2B533AFeD90e38fC9666", @@ -6005,10 +5716,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x60bB6D060393D3C206719A7bD61844cC82891cfB", - "interchainAccountIsm": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", - "interchainAccountRouter": "0x4D50044335dc1d4D26c343AdeDf6E47808475Deb", + "interchainAccountRouter": "0x9f4012ba9368FBb95F56c2Fc2D956df803D8779e", "interchainGasPaymaster": "0x70EbA87Cd15616f32C736B3f3BdCfaeD0713a82B", - "interchainSecurityModule": "0xeB7812b0EC587b05e5C38d097aa20b937a5aC52A", + "interchainSecurityModule": "0xE02f4F46c10C680A42AC6a5c2BEAe787639758Be", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xbb0AE51BCa526cF313b6a95BfaB020794af6C394", "pausableHook": "0x83475ca5bEB2Eaa59A2FF48a0544ebaa4a32c2de", @@ -6070,10 +5780,9 @@ "domainRoutingIsm": "0x30f96591A3A57D76ad4c071fD857d4D1022e886b", "domainRoutingIsmFactory": "0x8eC5f0239C77295452Ed899FDB851e785cA5FC31", "fallbackRoutingHook": "0x850615dF99FEc6edffC77Ff246f604a6f7Df6a78", - "interchainAccountIsm": "0x31Bb27f6007C33acD1be83ACEd3164C60f8F7b13", - "interchainAccountRouter": "0xEeb5a99a75585fe137c83E7b62b74f87264A5481", + "interchainAccountRouter": "0x3C330D4A2e2b8443AFaB8E326E64ab4251B7Eae0", "interchainGasPaymaster": "0xb7C9307fE90B9AB093c6D3EdeE3259f5378D5f03", - "interchainSecurityModule": "0x2526F60eB02F97Bdd839dE4B3cF8f3971eaB3D52", + "interchainSecurityModule": "0xc960b6DaBC4B5089BDac4820FE4c721585abF392", "mailbox": "0x0dF25A2d59F03F039b56E90EdC5B89679Ace28Bc", "merkleTreeHook": "0xC88636fFdFAc7cb87b7A76310B7a62AF0A000595", "pausableHook": "0x2AF32cF8e3Cf42d221eDa0c843818fA5ee129E27", @@ -6135,10 +5844,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x70EbA87Cd15616f32C736B3f3BdCfaeD0713a82B", - "interchainAccountIsm": "0x28291a7062afA569104bEd52F7AcCA3dD2FafD11", - "interchainAccountRouter": "0xe9E3444DDD80c50276c0Fcf316026f6d7fEc2c47", + "interchainAccountRouter": "0x95Fb6Ca1BBF441386b119ad097edcAca3b1C35B7", "interchainGasPaymaster": "0x25EAC2007b0D40E3f0AF112FD346412321038719", - "interchainSecurityModule": "0xAdb276dfEBcCa7ebE468F581bC8436D0EaA0f9C5", + "interchainSecurityModule": "0xf588B8aE4a9691ea33f224478a51887f0707D9e1", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x5C02157068a52cEcfc98EDb6115DE6134EcB4764", "pausableHook": "0x99fEFc1119E86Ee0153eb887cF8E8ab2d92A16e8", @@ -6162,6 +5870,7 @@ "gnosisSafeTransactionServiceUrl": "https://trx-swell.safe.protofire.io" }, "appchain": { + "batchContractAddress": "0xEB92D3f697F0d7DCbC025912e671d2A979FBFf78", "blockExplorers": [ { "apiUrl": "https://explorer.appchain.xyz/api", @@ -6203,10 +5912,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", - "interchainAccountIsm": "0x027eFD1695941969435AA640542B690044dF7E06", - "interchainAccountRouter": "0x65F1343AC23D4fF48bf6c7E0c55872d245397567", + "interchainAccountRouter": "0x24f89395e932961C27167F42DB928Ec92047B695", "interchainGasPaymaster": "0x28291a7062afA569104bEd52F7AcCA3dD2FafD11", - "interchainSecurityModule": "0x6f25149845ee2FCA5be5097295d0961bcda86458", + "interchainSecurityModule": "0xb492dc635a954Fc40f22Dd787EeBf16f7d1813a0", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xcd90D49b046772F710250b9119117169CB2e4D8b", "pausableHook": "0x7CE76f5f0C469bBB4cd7Ea6EbabB54437A093127", @@ -6225,65 +5933,6 @@ "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x1196055C61af3e3DA6f8458B07b255a72b64Bcf7" }, - "zklink": { - "blockExplorers": [ - { - "apiUrl": "https://explorer.zklink.io/contract_verification", - "family": "etherscan", - "name": "zkLink Nova Block Explorer", - "url": "https://explorer.zklink.io" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 1, - "reorgPeriod": 0 - }, - "chainId": 810180, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "zkLink Nova", - "displayNameShort": "zkLink", - "domainId": 810180, - "gasCurrencyCoinGeckoId": "ethereum", - "name": "zklink", - "nativeToken": { - "decimals": 18, - "name": "Ethereum", - "symbol": "ETH" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://rpc.zklink.io" - } - ], - "technicalStack": "zksync", - "domainRoutingIsm": "0xc364cfedefE854c1275B0f4088EaFA9695e1FC56", - "domainRoutingIsmFactory": "0x0000000000000000000000000000000000000000", - "fallbackDomainRoutingHook": "0x388289cd5862e17AAfD6ffF7F46A9Ec48a969bCd", - "fallbackRoutingHook": "0x388289cd5862e17AAfD6ffF7F46A9Ec48a969bCd", - "interchainGasPaymaster": "0xB35eCb9714e8f48332Af22B48C18ca21E2607438", - "interchainSecurityModule": "0x434f75095093D323263A195Cf0fe48eD642B9640", - "mailbox": "0x9BbDf86b272d224323136E15594fdCe487F40ce7", - "merkleTreeHook": "0xA1ADFCa9666Bcd68b7b5C8b55e3ecC465DcDfE65", - "proxyAdmin": "0x038F9F4e93e88Af2C688da265222FdE80e455aA4", - "staticAggregationHookFactory": "0x0000000000000000000000000000000000000000", - "staticAggregationIsmFactory": "0x0000000000000000000000000000000000000000", - "staticMerkleRootMultisigIsmFactory": "0x0000000000000000000000000000000000000000", - "staticMerkleRootWeightedMultisigIsmFactory": "0x0000000000000000000000000000000000000000", - "staticMessageIdMultisigIsmFactory": "0x0000000000000000000000000000000000000000", - "staticMessageIdWeightedMultisigIsmFactory": "0x0000000000000000000000000000000000000000", - "storageGasOracle": "0x73a82061Cd258d02BEa145fe183120456e718c2A", - "testRecipient": "0x58e8699b8e0a2A3ba40b380038f64F0B3ef4f2e5", - "validatorAnnounce": "0xf5626c0f33Ca102eb3ca1633A410cd8aa92909e4", - "index": { - "from": 7286869 - }, - "gnosisSafeTransactionServiceUrl": "https://transaction.safe.zklink.io" - }, "aurora": { "blockExplorers": [ { @@ -6326,10 +5975,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", - "interchainAccountIsm": "0xF457D831d9F55e87B2F0b35AD6D033fd6b4181Ed", - "interchainAccountRouter": "0x021D2810a758c833080DEc2F1Fa8F571Aae97D45", + "interchainAccountRouter": "0x0FdAa7296D06bB5E2365c25b2bF3BB8f188Ecf4F", "interchainGasPaymaster": "0xc0C2dB448fC2c84213394Fcb93a3C467e50ECa9E", - "interchainSecurityModule": "0xe92c977B87be86f11a02cd9f8F2fcb116b0e4C36", + "interchainSecurityModule": "0xa5A5A4aF931649FDc33c4CcA57121DfF128CC716", "mailbox": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", "merkleTreeHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", "pausableHook": "0x48C427782Bc1e9ecE406b3e277481b28ABcBdf03", @@ -6351,141 +5999,6 @@ "from": 135117550 } }, - "conflux": { - "blockExplorers": [ - { - "apiUrl": "https://evmapi.confluxscan.net/api", - "family": "other", - "name": "ConfluxScan eSpace", - "url": "https://evm.confluxscan.net" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 2, - "reorgPeriod": 10 - }, - "chainId": 1030, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Conflux eSpace", - "domainId": 1030, - "gasCurrencyCoinGeckoId": "conflux-token", - "name": "conflux", - "nativeToken": { - "decimals": 18, - "name": "Ethereum", - "symbol": "ETH" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://evm.confluxrpc.com" - }, - { - "http": "https://conflux-espace-public.unifra.io" - }, - { - "http": "https://conflux-espace.blockpi.network/v1/rpc/public" - } - ], - "technicalStack": "other", - "aggregationHook": "0x2A3E42635E7acAc8Bb7851963Bc9d29B1dAAd409", - "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "fallbackRoutingHook": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", - "interchainAccountIsm": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df", - "interchainAccountRouter": "0xc2466492C451E1AE49d8C874bB9f89293Aaad59b", - "interchainGasPaymaster": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "interchainSecurityModule": "0x4AA74dF2955529e8DA600F8d923551382cdCe987", - "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "merkleTreeHook": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", - "pausableHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", - "pausableIsm": "0xbed53B5C5BCE9433f25A2A702e6df13E22d84Ae9", - "protocolFee": "0x5DdFCA27f9a308c1429A010C4daB291b5534a297", - "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsm": "0x95cE3183137f906753463933C5dfDe777D2E7fE2", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "storageGasOracle": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", - "testRecipient": "0x4Ee9dEBB3046139661b51E17bdfD54Fd63211de7", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0x84444cE490233CFa76E3F1029bc166aa8c266907", - "index": { - "chunk": 999, - "from": 111730015 - } - }, - "conwai": { - "blockExplorers": [ - { - "apiUrl": "https://conwai.calderaexplorer.xyz/api", - "family": "blockscout", - "name": "Conwai Explorer", - "url": "https://conwai.calderaexplorer.xyz" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 2, - "reorgPeriod": 0 - }, - "chainId": 668668, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Conwai", - "domainId": 668668, - "gasCurrencyCoinGeckoId": "conwai", - "index": { - "from": 73 - }, - "name": "conwai", - "nativeToken": { - "decimals": 18, - "name": "Conwai", - "symbol": "CNW" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://conwai.calderachain.xyz/http" - } - ], - "technicalStack": "arbitrumnitro", - "aggregationHook": "0x2A3E42635E7acAc8Bb7851963Bc9d29B1dAAd409", - "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "fallbackRoutingHook": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", - "interchainAccountIsm": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", - "interchainAccountRouter": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df", - "interchainGasPaymaster": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "interchainSecurityModule": "0xB1E6076B3381E444723DE1b696D7a9940Bc759D3", - "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "merkleTreeHook": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", - "pausableHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", - "pausableIsm": "0xbed53B5C5BCE9433f25A2A702e6df13E22d84Ae9", - "protocolFee": "0x5DdFCA27f9a308c1429A010C4daB291b5534a297", - "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsm": "0x95cE3183137f906753463933C5dfDe777D2E7fE2", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "storageGasOracle": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", - "testRecipient": "0x4Ee9dEBB3046139661b51E17bdfD54Fd63211de7", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0x84444cE490233CFa76E3F1029bc166aa8c266907" - }, "corn": { "blockExplorers": [ { @@ -6534,7 +6047,6 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", - "interchainAccountIsm": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", "interchainAccountRouter": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df", "interchainGasPaymaster": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", "interchainSecurityModule": "0x23f963Ee7e1883917C9166079c86839a19e1f5ee", @@ -6556,79 +6068,6 @@ "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x84444cE490233CFa76E3F1029bc166aa8c266907" }, - "evmos": { - "blockExplorers": [ - { - "apiUrl": "https://www.mintscan.io/evmos/api", - "family": "other", - "name": "Mintscan", - "url": "https://www.mintscan.io/evmos" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 2, - "reorgPeriod": 5 - }, - "chainId": 9001, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Evmos EVM", - "domainId": 9001, - "gasCurrencyCoinGeckoId": "evmos", - "name": "evmos", - "nativeToken": { - "decimals": 18, - "name": "Evmos", - "symbol": "EVMOS" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://evmos.lava.build" - }, - { - "http": "https://evmos-json-rpc.stakely.io" - }, - { - "http": "https://rpc-evm.evmos.dragonstake.io" - }, - { - "http": "https://evmos.drpc.org" - } - ], - "technicalStack": "other", - "aggregationHook": "0x2A3E42635E7acAc8Bb7851963Bc9d29B1dAAd409", - "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "fallbackRoutingHook": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", - "interchainAccountIsm": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", - "interchainAccountRouter": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df", - "interchainGasPaymaster": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "interchainSecurityModule": "0xf561973Cc39fa7D23c50307769eca87770502C5e", - "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "merkleTreeHook": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", - "pausableHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", - "pausableIsm": "0xbed53B5C5BCE9433f25A2A702e6df13E22d84Ae9", - "protocolFee": "0x5DdFCA27f9a308c1429A010C4daB291b5534a297", - "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsm": "0x95cE3183137f906753463933C5dfDe777D2E7fE2", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "storageGasOracle": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", - "testRecipient": "0x4Ee9dEBB3046139661b51E17bdfD54Fd63211de7", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0x84444cE490233CFa76E3F1029bc166aa8c266907", - "index": { - "from": 25566476 - } - }, "form": { "blockExplorers": [ { @@ -6668,10 +6107,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", - "interchainAccountIsm": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", - "interchainAccountRouter": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df", + "interchainAccountRouter": "0xab6Ce17c7E323A8962E1BD445097D07C5693fF98", "interchainGasPaymaster": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "interchainSecurityModule": "0x223DAe7C9bbd61261439Db4D5068847C5b3915ac", + "interchainSecurityModule": "0x9A69E2F2Da6778f80D04dA7539BFDD2A5D3055D5", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", "pausableHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", @@ -6701,6 +6139,12 @@ "family": "blockscout", "name": "Ink Explorer", "url": "https://explorer.inkonchain.com" + }, + { + "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/57073/etherscan/api", + "family": "routescan", + "name": "Ink Explorer", + "url": "https://57073.routescan.io" } ], "blocks": { @@ -6733,10 +6177,9 @@ "domainRoutingIsm": "0xDEed16fe4b1c9b2a93483EDFf34C77A9b57D31Ff", "domainRoutingIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "fallbackRoutingHook": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", - "interchainAccountIsm": "0x60515f328B2c55Df63f456D9D839a0082892dEf8", - "interchainAccountRouter": "0xF457D831d9F55e87B2F0b35AD6D033fd6b4181Ed", + "interchainAccountRouter": "0x55Ba00F1Bac2a47e0A73584d7c900087642F9aE3", "interchainGasPaymaster": "0xc0C2dB448fC2c84213394Fcb93a3C467e50ECa9E", - "interchainSecurityModule": "0x4dE1f438707c7097d656Aaf0DdCc43c4d1859500", + "interchainSecurityModule": "0x2c714dFA62cE769c2C123833ac46cF0390B0dB38", "mailbox": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", "merkleTreeHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", "pausableHook": "0x48C427782Bc1e9ecE406b3e277481b28ABcBdf03", @@ -6759,70 +6202,6 @@ }, "gnosisSafeTransactionServiceUrl": "https://safe-transaction-ink.safe.global/" }, - "rivalz": { - "blockExplorers": [ - { - "apiUrl": "https://rivalz.calderaexplorer.xyz/api", - "family": "blockscout", - "name": "Rivalz Explorer", - "url": "https://rivalz.calderaexplorer.xyz" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 2, - "reorgPeriod": 0 - }, - "chainId": 753, - "displayName": "Rivalz", - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "domainId": 753, - "gasCurrencyCoinGeckoId": "ethereum", - "index": { - "from": 21 - }, - "name": "rivalz", - "nativeToken": { - "decimals": 18, - "name": "Ether", - "symbol": "ETH" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://rivalz.calderachain.xyz/http" - } - ], - "technicalStack": "arbitrumnitro", - "aggregationHook": "0xfe94Ea7DA6C45849D395e3d03973aa924553b937", - "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "fallbackRoutingHook": "0x48C427782Bc1e9ecE406b3e277481b28ABcBdf03", - "interchainAccountIsm": "0xd64d126941EaC2Cf53e0E4E8146cC70449b60D73", - "interchainAccountRouter": "0x1A4F09A615aA4a35E5a146DC2fa19975bebF21A5", - "interchainGasPaymaster": "0x3cECBa60A580dE20CC57D87528953a00f4ED99EA", - "interchainSecurityModule": "0xE19217AC9F93483dEF87a646408c0142595A58BB", - "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "merkleTreeHook": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", - "pausableHook": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7", - "pausableIsm": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", - "protocolFee": "0x4Ee9dEBB3046139661b51E17bdfD54Fd63211de7", - "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsm": "0x8f1953EFbd2C720223faD02d6CB5CD25f97D7fC9", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "storageGasOracle": "0x248aDe14C0489E20C9a7Fea5F86DBfC3702208eF", - "testRecipient": "0x92249B8ed35C2980e58666a3EBF4a075DDD2895f", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0x65dCf8F6b3f6a0ECEdf3d0bdCB036AEa47A1d615" - }, "soneium": { "blockExplorers": [ { @@ -6862,10 +6241,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", - "interchainAccountIsm": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", - "interchainAccountRouter": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df", + "interchainAccountRouter": "0xc08C1451979e9958458dA3387E92c9Feb1571f9C", "interchainGasPaymaster": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "interchainSecurityModule": "0x7E31881D08f13CAE762E3dF199EBdf1357F6bc3e", + "interchainSecurityModule": "0xeB200c20AC222aC199aBc8D470A5B23D367992Cd", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", "pausableHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", @@ -6891,8 +6269,8 @@ "sonic": { "blockExplorers": [ { - "apiKey": "V9PP7AFQF5Q6GJSQQ5YS2UBN7GI8QCA443", - "apiUrl": "https://api.sonicscan.org/api", + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=146", "family": "etherscan", "name": "Sonic Explorer", "url": "https://sonicscan.org" @@ -6928,10 +6306,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", - "interchainAccountIsm": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", - "interchainAccountRouter": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df", + "interchainAccountRouter": "0xEfad3f079048bE2765b6bCfAa3E9d99e9A2C3Df6", "interchainGasPaymaster": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "interchainSecurityModule": "0x48dD630E7bDD47d00Ca2eE94305d7E43bf0a8E70", + "interchainSecurityModule": "0xd8CBF2c76475FadBB46914f6A98667C01F937f6f", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", "pausableHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", @@ -7007,7 +6384,6 @@ }, "technicalStack": "other", "domainRoutingIsmFactory": "0x0000000000000000000000000000000000000000000000000000000000000000", - "interchainAccountIsm": "0x0000000000000000000000000000000000000000000000000000000000000000", "interchainAccountRouter": "0x0000000000000000000000000000000000000000000000000000000000000000", "proxyAdmin": "0x0000000000000000000000000000000000000000000000000000000000000000", "staticAggregationHookFactory": "0x0000000000000000000000000000000000000000000000000000000000000000", @@ -7022,74 +6398,7 @@ "address": "0x0000000000000000000000000000000000000000000000000000000000000000" } }, - "telos": { - "blockExplorers": [ - { - "apiUrl": "https://www.teloscan.io/api", - "family": "other", - "name": "Teloscan", - "url": "https://www.teloscan.io" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 1, - "reorgPeriod": "finalized" - }, - "chainId": 40, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Telos EVM", - "domainId": 40, - "gasCurrencyCoinGeckoId": "telos", - "name": "telos", - "nativeToken": { - "decimals": 18, - "name": "Telos", - "symbol": "TLOS" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://rpc.telos.net" - }, - { - "http": "https://telos.drpc.org" - } - ], - "technicalStack": "other", - "aggregationHook": "0x2A3E42635E7acAc8Bb7851963Bc9d29B1dAAd409", - "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "fallbackRoutingHook": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", - "interchainAccountIsm": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", - "interchainAccountRouter": "0xF457D831d9F55e87B2F0b35AD6D033fd6b4181Ed", - "interchainGasPaymaster": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "interchainSecurityModule": "0x748799aCCD153662AeAa836424D48AD60B74E826", - "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "merkleTreeHook": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", - "pausableHook": "0xA8A311B69f688c1D9928259D872C31ca0d473642", - "pausableIsm": "0xbed53B5C5BCE9433f25A2A702e6df13E22d84Ae9", - "protocolFee": "0x5DdFCA27f9a308c1429A010C4daB291b5534a297", - "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsm": "0x95cE3183137f906753463933C5dfDe777D2E7fE2", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "storageGasOracle": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", - "testRecipient": "0x4Ee9dEBB3046139661b51E17bdfD54Fd63211de7", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0x84444cE490233CFa76E3F1029bc166aa8c266907", - "index": { - "from": 378761707 - } - }, - "soon": { + "soon": { "blockExplorers": [ { "apiUrl": "https://explorer.soo.network/", @@ -7138,7 +6447,7 @@ "torus": { "blockExplorers": [ { - "apiUrl": "https://api.blockscout.torus.network/api", + "apiUrl": "https://blockscout.torus.network/api", "family": "blockscout", "name": "Torus Explorer", "url": "https://blockscout.torus.network" @@ -7177,10 +6486,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x48C427782Bc1e9ecE406b3e277481b28ABcBdf03", - "interchainAccountIsm": "0xd64d126941EaC2Cf53e0E4E8146cC70449b60D73", - "interchainAccountRouter": "0x1A4F09A615aA4a35E5a146DC2fa19975bebF21A5", + "interchainAccountRouter": "0xCCceDFFAA987F47D0D3A26430c3d3f3270fE6369", "interchainGasPaymaster": "0x3cECBa60A580dE20CC57D87528953a00f4ED99EA", - "interchainSecurityModule": "0x7Be5786937504435aE3Ae3D6E9BBCe574196d861", + "interchainSecurityModule": "0x0431C48Ea064621a289916Da0F0a2ec55c887962", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", "pausableHook": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7", @@ -7244,10 +6552,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", - "interchainAccountIsm": "0x2351FBe24C1212F253b7a300ff0cBCFd97952a19", - "interchainAccountRouter": "0xC5f2c60073DCAA9D157C45d5B017D639dF9C5CeB", + "interchainAccountRouter": "0x3A220676CFD4e21726cbF20E8F5df4F138364f69", "interchainGasPaymaster": "0xc2466492C451E1AE49d8C874bB9f89293Aaad59b", - "interchainSecurityModule": "0xFD3B0328B34de461833d0a9c1a96DA1ad078B576", + "interchainSecurityModule": "0x7E2A4EEBa391b38182Df58079b8ef97CC2A446A5", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x7B032cBB00AD7438E802A66D8b64761A06E5df22", "pausableHook": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94", @@ -7308,7 +6615,6 @@ "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "fallbackRoutingHook": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94", - "interchainAccountIsm": "0x8F23872dAb3B166cef411EeB6C391Ff6Ce419532", "interchainAccountRouter": "0xb201817dFdd822B75Fa9b595457E6Ee466a7C187", "interchainGasPaymaster": "0xA9D06082F4AA449D95b49D85F27fdC0cFb491d4b", "interchainSecurityModule": "0x317b64Fd6cCFA833c3E51239600C30A50d50ba26", @@ -7372,10 +6678,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", - "interchainAccountIsm": "0x7937CB2886f01F38210506491A69B0D107Ea0ad9", - "interchainAccountRouter": "0xc31B1E6c8E706cF40842C3d728985Cd2f85413eD", + "interchainAccountRouter": "0x1604d2D3DaFba7D302F86BD7e79B3931414E4625", "interchainGasPaymaster": "0xc2466492C451E1AE49d8C874bB9f89293Aaad59b", - "interchainSecurityModule": "0xda28D0451d88828eF6ca65ECEC9b6b7D438a8BB1", + "interchainSecurityModule": "0x39838e638eF83D98A2c3eAD2Be6b68e7018c584F", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x7B032cBB00AD7438E802A66D8b64761A06E5df22", "pausableHook": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94", @@ -7397,139 +6702,6 @@ "from": 881513 } }, - "nero": { - "blockExplorers": [ - { - "apiUrl": "https://api.neroscan.io/api", - "family": "etherscan", - "name": "Neroscan", - "url": "https://www.neroscan.io" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 3, - "reorgPeriod": 5 - }, - "chainId": 1689, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Nero", - "domainId": 1689, - "gasCurrencyCoinGeckoId": "nerochain", - "gnosisSafeTransactionServiceUrl": "https://multisign.nerochain.io/txs/", - "name": "nero", - "nativeToken": { - "decimals": 18, - "name": "Nero", - "symbol": "NERO" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://rpc.nerochain.io" - } - ], - "technicalStack": "other", - "aggregationHook": "0x79B1c9E49396A62AFf9B072A0DebD010D4b80455", - "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "fallbackRoutingHook": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", - "interchainAccountIsm": "0x9629c28990F11c31735765A6FD59E1E1bC197DbD", - "interchainAccountRouter": "0x2351FBe24C1212F253b7a300ff0cBCFd97952a19", - "interchainGasPaymaster": "0xc2466492C451E1AE49d8C874bB9f89293Aaad59b", - "interchainSecurityModule": "0xd2d3d1990793417c6097a5EDB0d00a3A3B5fc016", - "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "merkleTreeHook": "0x7B032cBB00AD7438E802A66D8b64761A06E5df22", - "pausableHook": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94", - "pausableIsm": "0xb2674E213019972f937CCFc5e23BF963D915809e", - "protocolFee": "0x46008F5971eFb16e6c354Ef993EA021B489bc055", - "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsm": "0x9eAc9956855640c6a58AF8f91537A3E9ccd757a9", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "storageGasOracle": "0x60515f328B2c55Df63f456D9D839a0082892dEf8", - "testRecipient": "0xa2401b57A8CCBF6AbD9b7e62e28811b2b523AB2B", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0xb89c6ED617f5F46175E41551350725A09110bbCE", - "index": { - "from": 1623141 - }, - "transactionOverrides": { - "maxFeePerGas": 10000000000, - "maxPriorityFeePerGas": 1000000000 - } - }, - "xpla": { - "blockExplorers": [ - { - "apiUrl": "https://explorer.xpla.io/mainnet/api", - "family": "other", - "name": "XPLA Explorer", - "url": "https://explorer.xpla.io/mainnet" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 6, - "reorgPeriod": 5 - }, - "chainId": 37, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "XPLA", - "domainId": 37, - "gasCurrencyCoinGeckoId": "xpla", - "name": "xpla", - "nativeToken": { - "decimals": 18, - "name": "XPLA", - "symbol": "XPLA" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://dimension-evm-rpc.xpla.dev" - } - ], - "technicalStack": "other", - "aggregationHook": "0xC4d5F57ac4C1D076E13D5fE299AB26c8f5698454", - "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "fallbackRoutingHook": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", - "interchainAccountIsm": "0xE350143242a2F7962F23D71ee9Dd98f6e86D1772", - "interchainAccountRouter": "0x5B24EE24049582fF74c1d311d72c70bA5B76a554", - "interchainGasPaymaster": "0x8F23872dAb3B166cef411EeB6C391Ff6Ce419532", - "interchainSecurityModule": "0x996264e73e66b3011a1727366Bc5954E6b512897", - "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "merkleTreeHook": "0x7B032cBB00AD7438E802A66D8b64761A06E5df22", - "pausableHook": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94", - "pausableIsm": "0xb2674E213019972f937CCFc5e23BF963D915809e", - "protocolFee": "0xc31B1E6c8E706cF40842C3d728985Cd2f85413eD", - "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsm": "0x9eAc9956855640c6a58AF8f91537A3E9ccd757a9", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "storageGasOracle": "0xc2466492C451E1AE49d8C874bB9f89293Aaad59b", - "testRecipient": "0x7Ce3a48cd9FD80004d95b088760bD05bA86C1f7b", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0xFa6fDABA1d0688675f05cE1B9DE17461247Bce9e", - "index": { - "from": 12446115 - } - }, "abstract": { "blockExplorers": [ { @@ -7573,7 +6745,7 @@ "fallbackDomainRoutingHook": "0x72f747270ED9C92c7026b222c5767561Be281DaF", "fallbackRoutingHook": "0x72f747270ED9C92c7026b222c5767561Be281DaF", "interchainGasPaymaster": "0x874AaCa847B365592B2b9dB7235517c5F3a5c689", - "interchainSecurityModule": "0xb82A1AC8516610FB139DBee6Cc92841E9f8518b2", + "interchainSecurityModule": "0xA4B4cE629b9959495Acc3923308FdCB92f8B9412", "mailbox": "0x9BbDf86b272d224323136E15594fdCe487F40ce7", "merkleTreeHook": "0x11b69aB33AD8a550dcF9B4A041AA1121255F08A5", "proxyAdmin": "0x038F9F4e93e88Af2C688da265222FdE80e455aA4", @@ -7586,7 +6758,7 @@ "storageGasOracle": "0x1cE4d0E16570C362feef85Ce2713555fCbd3dBC7", "testRecipient": "0xCDfE1782fDC9E74810D3B69E971d752bC4b4D6E6", "validatorAnnounce": "0x2235662a9a8ED39AE489aafb2feE13Db26f72044", - "gnosisSafeTransactionServiceUrl": "https://transaction.abstract-safe.protofire.io" + "gnosisSafeTransactionServiceUrl": "https://transaction.safe.abs.xyz" }, "glue": { "blockExplorers": [ @@ -7627,7 +6799,6 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x46008F5971eFb16e6c354Ef993EA021B489bc055", - "interchainAccountIsm": "0xE350143242a2F7962F23D71ee9Dd98f6e86D1772", "interchainAccountRouter": "0x5B24EE24049582fF74c1d311d72c70bA5B76a554", "interchainGasPaymaster": "0x8F23872dAb3B166cef411EeB6C391Ff6Ce419532", "interchainSecurityModule": "0x95499c936f16E27396FCd6451Ceb74545Bd52ddb", @@ -7691,10 +6862,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xf0F937943Cd6D2a5D02b7f96C9Dd9e04AB633813", - "interchainAccountIsm": "0xD84981Ecd4c411A86E1Ccda77F944c8f3D9737ab", - "interchainAccountRouter": "0xF16E63B42Df7f2676B373979120BBf7e6298F473", + "interchainAccountRouter": "0xcb98BD947B58445Fc4815f10285F44De42129918", "interchainGasPaymaster": "0x9629c28990F11c31735765A6FD59E1E1bC197DbD", - "interchainSecurityModule": "0x4ac3afD7d4715310419AeF99303d3ECA754ECb77", + "interchainSecurityModule": "0x1f3226346DA515AeA240c84d2AFF37adD39EdDFb", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x021D2810a758c833080DEc2F1Fa8F571Aae97D45", "pausableHook": "0x46008F5971eFb16e6c354Ef993EA021B489bc055", @@ -7717,70 +6887,6 @@ "chunk": 999 } }, - "unitzero": { - "blockExplorers": [ - { - "apiUrl": "https://explorer.unit0.dev/api", - "family": "blockscout", - "name": "Unit Zero Explorer", - "url": "https://explorer.unit0.dev" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 7, - "reorgPeriod": 5 - }, - "chainId": 88811, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Unit Zero", - "domainId": 88811, - "gasCurrencyCoinGeckoId": "unit0", - "name": "unitzero", - "nativeToken": { - "decimals": 18, - "name": "Unit Zero", - "symbol": "UNIT0" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://rpc.unit0.dev" - } - ], - "technicalStack": "other", - "aggregationHook": "0x57f5F4c801912C332fddCEfa22433728EcFD9595", - "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "fallbackRoutingHook": "0xf0F937943Cd6D2a5D02b7f96C9Dd9e04AB633813", - "interchainAccountIsm": "0xD84981Ecd4c411A86E1Ccda77F944c8f3D9737ab", - "interchainAccountRouter": "0xF16E63B42Df7f2676B373979120BBf7e6298F473", - "interchainGasPaymaster": "0x9629c28990F11c31735765A6FD59E1E1bC197DbD", - "interchainSecurityModule": "0x2Bf186806018aFf7c1e2dD203987994b2765d94c", - "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "merkleTreeHook": "0x021D2810a758c833080DEc2F1Fa8F571Aae97D45", - "pausableHook": "0x46008F5971eFb16e6c354Ef993EA021B489bc055", - "pausableIsm": "0xc2466492C451E1AE49d8C874bB9f89293Aaad59b", - "protocolFee": "0xb201817dFdd822B75Fa9b595457E6Ee466a7C187", - "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsm": "0x2BAb551CFDFbd6CC45a3B73424aD6228F8A81B61", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "storageGasOracle": "0xff72A726Ce261846f2dF6F32113e514b5Ddb0E37", - "testRecipient": "0xFa6fDABA1d0688675f05cE1B9DE17461247Bce9e", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0xC5f2c60073DCAA9D157C45d5B017D639dF9C5CeB", - "index": { - "from": 938445 - } - }, "sonicsvm": { "blockExplorers": [ { @@ -7861,10 +6967,9 @@ "domainRoutingIsm": "0xDEed16fe4b1c9b2a93483EDFf34C77A9b57D31Ff", "domainRoutingIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "fallbackRoutingHook": "0x7937CB2886f01F38210506491A69B0D107Ea0ad9", - "interchainAccountIsm": "0x30a539E2E2d09FB4e68661B1EDD70D266211602a", - "interchainAccountRouter": "0xB2b0A80b2fa3fC9aB1564A4FaF013d4D6084B325", + "interchainAccountRouter": "0x84Fcd67D2B723416e2aFDd61484BD19bd9C32f27", "interchainGasPaymaster": "0x7Ce3a48cd9FD80004d95b088760bD05bA86C1f7b", - "interchainSecurityModule": "0x1EB4375802ebED3F0ac58eBa35566Fde97e1b39b", + "interchainSecurityModule": "0x34e302141769f6531E020d721c80577Fa43FFb62", "mailbox": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", "merkleTreeHook": "0x8F23872dAb3B166cef411EeB6C391Ff6Ce419532", "pausableHook": "0x2351FBe24C1212F253b7a300ff0cBCFd97952a19", @@ -7926,8 +7031,7 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x7927B6fE8FA061c32CE3771d11076E6161DE5f52", - "interchainAccountIsm": "0xc261Bd2BD995d3D0026e918cBFD44b0Cc5416a57", - "interchainAccountRouter": "0xf4035357EB3e3B48E498FA6e1207892f615A2c2f", + "interchainAccountRouter": "0x87ED6926abc9E38b9C7C19f835B41943b622663c", "interchainGasPaymaster": "0x30a539E2E2d09FB4e68661B1EDD70D266211602a", "interchainSecurityModule": "0x2cC6e45de3c4C824A80F367507EAe885e125d765", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", @@ -7951,71 +7055,6 @@ "from": 773 } }, - "bouncebit": { - "blockExplorers": [ - { - "apiUrl": "https://bbscan.io/api", - "family": "other", - "name": "BBScan", - "url": "https://bbscan.io" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 4, - "reorgPeriod": 5 - }, - "chainId": 6001, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "BounceBit", - "domainId": 6001, - "gasCurrencyCoinGeckoId": "bouncebit", - "name": "bouncebit", - "nativeToken": { - "decimals": 18, - "name": "BounceBit", - "symbol": "BB" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://fullnode-mainnet.bouncebitapi.com" - } - ], - "technicalStack": "other", - "aggregationHook": "0x162aF576D9fe7ab73fB45d0402F99CF55c128d45", - "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "fallbackRoutingHook": "0x82540c4C1C6956FC4815E583DDc6d88A782E0F3e", - "interchainAccountIsm": "0xcDD89f19b2d00DCB9510BB3fBd5eCeCa761fe5Ab", - "interchainAccountRouter": "0x7947b7Fe737B4bd1D3387153f32148974066E591", - "interchainGasPaymaster": "0x5cD695ADCB156589cde501822C314bFD74398cA1", - "interchainSecurityModule": "0x7902b52956FD67762766fEC63A67DEe52C03f68f", - "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "merkleTreeHook": "0xa377b8269e0A47cdd2fD5AAeAe860b45623c6d82", - "pausableHook": "0x3E57E4908FB4Ac05e9928feE2Ffd78f59692317C", - "pausableIsm": "0xF16E63B42Df7f2676B373979120BBf7e6298F473", - "protocolFee": "0xd386Bb418B61E296e1689C95AfE94A2E321a6eaD", - "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsm": "0x2ac85c56B1FCB06951a6457918E29fB2ab30722b", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "storageGasOracle": "0x6e1B9f776bd415d7cC3C7458A5f0d801016918f8", - "testRecipient": "0x51545389E04c2Ac07d98A40b85d29B480a2AF6ce", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0xB2b0A80b2fa3fC9aB1564A4FaF013d4D6084B325", - "index": { - "chunk": 100, - "from": 7598788 - } - }, "ronin": { "blockExplorers": [ { @@ -8058,10 +7097,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x82540c4C1C6956FC4815E583DDc6d88A782E0F3e", - "interchainAccountIsm": "0xcDD89f19b2d00DCB9510BB3fBd5eCeCa761fe5Ab", - "interchainAccountRouter": "0x7947b7Fe737B4bd1D3387153f32148974066E591", + "interchainAccountRouter": "0xd6b12ecC223b483427ea66B029b4EEfcC1af86DC", "interchainGasPaymaster": "0x5cD695ADCB156589cde501822C314bFD74398cA1", - "interchainSecurityModule": "0x8521df62a5AEd0B0e6c9A274B5826C1DF06Ec28d", + "interchainSecurityModule": "0xf5Eac8BD0134b247ccC73Eac00958489462b4611", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xa377b8269e0A47cdd2fD5AAeAe860b45623c6d82", "pausableHook": "0x3E57E4908FB4Ac05e9928feE2Ffd78f59692317C", @@ -8127,7 +7165,7 @@ "fallbackDomainRoutingHook": "0xc364cfedefE854c1275B0f4088EaFA9695e1FC56", "fallbackRoutingHook": "0xc364cfedefE854c1275B0f4088EaFA9695e1FC56", "interchainGasPaymaster": "0x73a82061Cd258d02BEa145fe183120456e718c2A", - "interchainSecurityModule": "0x7b50a8264aE3054a8Ed5d08Eb3E64311EaA391da", + "interchainSecurityModule": "0x309D19dc1AdaAcD7Ff8BaAE9737e7d2314A4651E", "mailbox": "0x9BbDf86b272d224323136E15594fdCe487F40ce7", "merkleTreeHook": "0x697a90753B7dCf6512189c239E612fC12baaE500", "proxyAdmin": "0x038F9F4e93e88Af2C688da265222FdE80e455aA4", @@ -8184,10 +7222,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x82540c4C1C6956FC4815E583DDc6d88A782E0F3e", - "interchainAccountIsm": "0xcDD89f19b2d00DCB9510BB3fBd5eCeCa761fe5Ab", - "interchainAccountRouter": "0x7947b7Fe737B4bd1D3387153f32148974066E591", + "interchainAccountRouter": "0x4ef363Da5bb09CC6aeA16973786963d0C8820778", "interchainGasPaymaster": "0x5cD695ADCB156589cde501822C314bFD74398cA1", - "interchainSecurityModule": "0x86e8477D1916F3dE36bC4d0E8D9d89f57c0e03F7", + "interchainSecurityModule": "0xd04b9327C519074ac673A8971335b36a3447B6d9", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xa377b8269e0A47cdd2fD5AAeAe860b45623c6d82", "pausableHook": "0x3E57E4908FB4Ac05e9928feE2Ffd78f59692317C", @@ -8248,10 +7285,9 @@ "domainRoutingIsm": "0x3A19eaBd25764c857b49FD350736E2b52Ec713a0", "domainRoutingIsmFactory": "0xF7af65596A16740b16CF755F3A43206C96285da0", "fallbackRoutingHook": "0x51545389E04c2Ac07d98A40b85d29B480a2AF6ce", - "interchainAccountIsm": "0x72246331d057741008751AB3976a8297Ce7267Bc", - "interchainAccountRouter": "0xf303B04d9ad21dAe2658Cf302478A424e0B45368", + "interchainAccountRouter": "0x9097869cb719335f45A069D41dEFAFA2858af676", "interchainGasPaymaster": "0x8d7E604460E1133ebB91513a6D1024f3A3ca17F9", - "interchainSecurityModule": "0x40Ce0130600A10104AC0FD9718c641786F7Ab8B4", + "interchainSecurityModule": "0x5F65b6863277bcC653A3c57f89E4638d20c86052", "mailbox": "0xF767D698c510FE5E53b46BA6Fd1174F5271e390A", "merkleTreeHook": "0xB2b0A80b2fa3fC9aB1564A4FaF013d4D6084B325", "pausableHook": "0x1e4dE25C3b07c8DF66D4c193693d8B5f3b431d51", @@ -8312,10 +7348,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x5cD695ADCB156589cde501822C314bFD74398cA1", - "interchainAccountIsm": "0xbE58F200ffca4e1cE4D2F4541E94Ae18370fC405", - "interchainAccountRouter": "0xdf4aA3905e0391C7763e33CB6A08fFa97221D49B", + "interchainAccountRouter": "0x1CF975C9bF2DF76c43a14405066007f8393142E9", "interchainGasPaymaster": "0xEa2Bcee14eA30bbBe3018E5E7829F963230F71C3", - "interchainSecurityModule": "0xCB5e7b21Ee4e41A423d0618916d447Aa6C3c51cD", + "interchainSecurityModule": "0xA5938E5EDb3826FA4E9f1DEa2c1E8A79823C909f", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x3862A9B1aCd89245a59002C2a08658EC1d5690E3", "pausableHook": "0x8da1aE5A1fA3883c1c12b46270989EAC0EE7BA78", @@ -8381,10 +7416,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xB2b0A80b2fa3fC9aB1564A4FaF013d4D6084B325", - "interchainAccountIsm": "0x72246331d057741008751AB3976a8297Ce7267Bc", - "interchainAccountRouter": "0xf303B04d9ad21dAe2658Cf302478A424e0B45368", + "interchainAccountRouter": "0xd9Cc2e652A162bb93173d1c44d46cd2c0bbDA59D", "interchainGasPaymaster": "0xc261Bd2BD995d3D0026e918cBFD44b0Cc5416a57", - "interchainSecurityModule": "0xB97b4253591e49161Ced74863c5E7A1e70040C38", + "interchainSecurityModule": "0x3b944DB4B53F0dB464D5d4dd7fC9F2471EA07814", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x25C87e735021F72d8728438C2130b02E3141f2cb", "pausableHook": "0x51545389E04c2Ac07d98A40b85d29B480a2AF6ce", @@ -8495,10 +7529,9 @@ "domainRoutingIsm": "0x93B21c39F9C4cFdD8779d01A5fdC7Fe881708Fcb", "domainRoutingIsmFactory": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "fallbackRoutingHook": "0x9fE454AA2B01fc7A2a777AE561bc58Ce560CD5a9", - "interchainAccountIsm": "0xA9Adb480F10547f10202173a49b7F52116304476", - "interchainAccountRouter": "0xA697222b77cDe62A8C47E627d5A1c49A87018236", + "interchainAccountRouter": "0x7D5a79539d7B1c9aE5e54d18EEE188840f1Fe4CC", "interchainGasPaymaster": "0x8452363d5c78bf95538614441Dc8B465e03A89ca", - "interchainSecurityModule": "0xCb2608ae751954F32F779b3dBAad10614790937d", + "interchainSecurityModule": "0x202bdAdee75dbfB70F1C3000208ea260265eECB2", "mailbox": "0x398633D19f4371e1DB5a8EFE90468eB70B1176AA", "merkleTreeHook": "0x6D48135b7584E8Bf828B6e23110Bc0Da4252704f", "pausableHook": "0x2FF6cf2651fec512D0618E33c9d1374aaCd8b310", @@ -8520,77 +7553,13 @@ "from": 66586 } }, - "deepbrainchain": { + "nibiru": { "blockExplorers": [ { - "apiUrl": "https://www.dbcscan.io/api", - "family": "blockscout", - "name": "dbcscan", - "url": "https://www.dbcscan.io" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 6, - "reorgPeriod": "finalized" - }, - "chainId": 19880818, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Deep Brain Chain", - "domainId": 19880818, - "gasCurrencyCoinGeckoId": "deepbrain-chain", - "name": "deepbrainchain", - "nativeToken": { - "decimals": 18, - "name": "DBC", - "symbol": "DBC" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://rpc.dbcwallet.io" - } - ], - "technicalStack": "polkadotsubstrate", - "aggregationHook": "0x5f65691fCF01CF258Aa5A5f416902C0524402395", - "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "fallbackRoutingHook": "0xdf4aA3905e0391C7763e33CB6A08fFa97221D49B", - "interchainAccountIsm": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", - "interchainAccountRouter": "0xBCD18636e5876DFd7AAb5F2B2a5Eb5ca168BA1d8", - "interchainGasPaymaster": "0xE911e75CECe0eF01df3ceD96BCd362941AE474D4", - "interchainSecurityModule": "0x9FcDFc2AdCe497C1d966C483e9DE0614Cd7DDb37", - "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "merkleTreeHook": "0xcDD89f19b2d00DCB9510BB3fBd5eCeCa761fe5Ab", - "pausableHook": "0x72246331d057741008751AB3976a8297Ce7267Bc", - "pausableIsm": "0xbE58F200ffca4e1cE4D2F4541E94Ae18370fC405", - "protocolFee": "0x6D48135b7584E8Bf828B6e23110Bc0Da4252704f", - "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsm": "0x0Cb57b789C6CF954F422F043E351cc6Ba8447aE1", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "storageGasOracle": "0x7947b7Fe737B4bd1D3387153f32148974066E591", - "testRecipient": "0x76F2cC245882ceFf209A61d75b9F0f1A3b7285fB", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0x9fE454AA2B01fc7A2a777AE561bc58Ce560CD5a9", - "index": { - "from": 1489864 - } - }, - "nibiru": { - "blockExplorers": [ - { - "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/6900/etherscan/api", - "family": "routescan", - "name": "nibiscan", - "url": "https://nibiscan.io" + "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/6900/etherscan/api", + "family": "routescan", + "name": "nibiscan", + "url": "https://nibiscan.io" } ], "blocks": { @@ -8623,10 +7592,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x7947b7Fe737B4bd1D3387153f32148974066E591", - "interchainAccountIsm": "0x284226F651eb5cbd696365BC27d333028FCc5D54", - "interchainAccountRouter": "0x64F077915B41479901a23Aa798B30701589C5063", + "interchainAccountRouter": "0xd5e0859Cf2e9C790bE6ec4499A39d75Cb84836Dc", "interchainGasPaymaster": "0x5887BDA66EC9e854b0da6BFFe423511e69d327DC", - "interchainSecurityModule": "0x55b2F3e6e0ff0F0D8b63B42906117E97aE58B370", + "interchainSecurityModule": "0xB93B69eC574543E7213048DC1c577404Ccb3c4c9", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x72246331d057741008751AB3976a8297Ce7267Bc", "pausableHook": "0x1A41a365A693b6A7aED1a46316097d290f569F22", @@ -8651,8 +7619,8 @@ "opbnb": { "blockExplorers": [ { - "apiKey": "GK47JUUDX17PHP8XQ947IYTTPHP6FTAQMK", - "apiUrl": "https://api-opbnb.bscscan.com/api", + "apiKey": "GP69JEAP2W7YFJT9ZJTEPGQT6Y6KMW44ZN", + "apiUrl": "https://api.etherscan.io/v2/api?chainid=204", "family": "etherscan", "name": "opBNB Scan", "url": "https://opbnb.bscscan.com" @@ -8697,10 +7665,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xdf4aA3905e0391C7763e33CB6A08fFa97221D49B", - "interchainAccountIsm": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", - "interchainAccountRouter": "0xBCD18636e5876DFd7AAb5F2B2a5Eb5ca168BA1d8", + "interchainAccountRouter": "0x8847A94861C299e6AD408923A604dEe057baB5dC", "interchainGasPaymaster": "0xE911e75CECe0eF01df3ceD96BCd362941AE474D4", - "interchainSecurityModule": "0x4f398068d3876CBA4Aa8Cd6649BF11D4582FE1D7", + "interchainSecurityModule": "0x28c6107ec45d4F756b6cA9679F33209eA6aF01F0", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xcDD89f19b2d00DCB9510BB3fBd5eCeCa761fe5Ab", "pausableHook": "0x72246331d057741008751AB3976a8297Ce7267Bc", @@ -8761,10 +7728,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0xdf4aA3905e0391C7763e33CB6A08fFa97221D49B", - "interchainAccountIsm": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", - "interchainAccountRouter": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143", + "interchainAccountRouter": "0x2A532fc8cF9a72142eA8753a0d2AB68098C19585", "interchainGasPaymaster": "0xE911e75CECe0eF01df3ceD96BCd362941AE474D4", - "interchainSecurityModule": "0xF30bdef09039a53AbD27f2444C4107dD1F54db2F", + "interchainSecurityModule": "0x9D4d62907fE2E9fdc33B35d29475544857E96B88", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0xcDD89f19b2d00DCB9510BB3fBd5eCeCa761fe5Ab", "pausableHook": "0x72246331d057741008751AB3976a8297Ce7267Bc", @@ -8886,10 +7852,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", - "interchainAccountIsm": "0xD233433AeC23F8382DAd87D808F60557Ea35399f", - "interchainAccountRouter": "0x5CBD4c5f9CD55747285652f815Cc7b9A2Ef6c586", + "interchainAccountRouter": "0x1504Dff2ab3196a41FC0565E41B64247dc405022", "interchainGasPaymaster": "0x0DbB60c348DF645c295Fd0ce26F87bB850710185", - "interchainSecurityModule": "0xA1eB35F8e357D01F94081f6a1Df17676318c25AC", + "interchainSecurityModule": "0xeEc0955AF9BB3545E8a7f1775020fB6751B68206", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x2FF6cf2651fec512D0618E33c9d1374aaCd8b310", "pausableHook": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", @@ -8954,10 +7919,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", - "interchainAccountIsm": "0xD233433AeC23F8382DAd87D808F60557Ea35399f", - "interchainAccountRouter": "0x5CBD4c5f9CD55747285652f815Cc7b9A2Ef6c586", + "interchainAccountRouter": "0x1504Dff2ab3196a41FC0565E41B64247dc405022", "interchainGasPaymaster": "0x0DbB60c348DF645c295Fd0ce26F87bB850710185", - "interchainSecurityModule": "0xdE91041D2Cf011c1fD2bdF9770cABeC762fa6B4D", + "interchainSecurityModule": "0x21bED542E4e53c2323cbcd2Af6240421c44bfc92", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x2FF6cf2651fec512D0618E33c9d1374aaCd8b310", "pausableHook": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", @@ -9016,10 +7980,9 @@ "domainRoutingIsm": "0x494415e823236A05c608D6b777bC80082cED6A2E", "domainRoutingIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "fallbackRoutingHook": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", - "interchainAccountIsm": "0x2ed6030D204745aC0Cd6be8301C3a63bf14D97Cc", - "interchainAccountRouter": "0xA9Adb480F10547f10202173a49b7F52116304476", + "interchainAccountRouter": "0xD79A14EA21db52F130A57Ea6e2af55949B00086E", "interchainGasPaymaster": "0x8452363d5c78bf95538614441Dc8B465e03A89ca", - "interchainSecurityModule": "0xa084D1332BDcF0659D24a48dD027882bA32A94D1", + "interchainSecurityModule": "0xF5C204D28e72Bf41f9d91a9efb35274245537a20", "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "merkleTreeHook": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", "pausableHook": "0x4A91738390a3D55CB27c2863e8950c9cD1b89d0e", @@ -9076,8 +8039,7 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", - "interchainAccountIsm": "0xD233433AeC23F8382DAd87D808F60557Ea35399f", - "interchainAccountRouter": "0x5CBD4c5f9CD55747285652f815Cc7b9A2Ef6c586", + "interchainAccountRouter": "0xF192f901e3204317aC9Eb27E838a25E4d073944B", "interchainGasPaymaster": "0x0DbB60c348DF645c295Fd0ce26F87bB850710185", "interchainSecurityModule": "0x67260F14dFa0B10463f5ED669B65437839E7398d", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", @@ -9138,7 +8100,6 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x3E12271EbD523d0886D0D51A4FF8D8e046CF2E1D", - "interchainAccountIsm": "0x8BdD5bf519714515083801448A99F84882A8F61E", "interchainAccountRouter": "0x718f11e349374481Be8c8B7589eC4B4316ddDCc2", "interchainGasPaymaster": "0x11EF91d17c5ad3330DbCa709a8841743d3Af6819", "interchainSecurityModule": "0x7cF1Dc23162280dfc01c324589d0DB3018527C20", @@ -9203,10 +8164,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", - "interchainAccountIsm": "0xD233433AeC23F8382DAd87D808F60557Ea35399f", - "interchainAccountRouter": "0x5CBD4c5f9CD55747285652f815Cc7b9A2Ef6c586", + "interchainAccountRouter": "0xdcA646C56E7768DD11654956adE24bfFf9Ba4893", "interchainGasPaymaster": "0x0DbB60c348DF645c295Fd0ce26F87bB850710185", - "interchainSecurityModule": "0xe15477DedeBB3bFE171A7354D2515aE34c5E0f62", + "interchainSecurityModule": "0xB84E6A5d8D0c001c57A302875C57911D196E310f", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x2FF6cf2651fec512D0618E33c9d1374aaCd8b310", "pausableHook": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", @@ -9380,10 +8340,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x0dc95Af5156fb0cC34a8c9BD646B748B9989A956", - "interchainAccountIsm": "0x23cc88CF424d48fDB05b4f0A8Ff6099aa4D56D8e", - "interchainAccountRouter": "0x40Ca4155c0334F7e0F6d7F80536B59EF8831c9fb", + "interchainAccountRouter": "0x38D361861d321B8B05de200c61B8F18740Daf4D8", "interchainGasPaymaster": "0xD233433AeC23F8382DAd87D808F60557Ea35399f", - "interchainSecurityModule": "0xDF944257c16ab3b4aEE4609411c7E9B353181905", + "interchainSecurityModule": "0x02Ab816B10b7cdb7e30547417005Bf564Ae0D5B5", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x64F077915B41479901a23Aa798B30701589C5063", "pausableHook": "0xF6CC9B10c607afB777380bF71F272E4D7037C3A9", @@ -9441,10 +8400,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x5CBD4c5f9CD55747285652f815Cc7b9A2Ef6c586", - "interchainAccountIsm": "0x1fbcCdc677c10671eE50b46C61F0f7d135112450", - "interchainAccountRouter": "0x96D51cc3f7500d501bAeB1A2a62BB96fa03532F8", + "interchainAccountRouter": "0x21b5a2fA1f53e94cF4871201aeD30C6ad5E405f2", "interchainGasPaymaster": "0x23cc88CF424d48fDB05b4f0A8Ff6099aa4D56D8e", - "interchainSecurityModule": "0x4575196FA48C834FD5eFe07a4E9d28B58DBf3C42", + "interchainSecurityModule": "0x1902BdaC1f351C7115111e428B7919A6438AC1EC", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x2ed6030D204745aC0Cd6be8301C3a63bf14D97Cc", "pausableHook": "0x946E9f4540E032a9fAc038AE58187eFcad9DE952", @@ -9505,10 +8463,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x5CBD4c5f9CD55747285652f815Cc7b9A2Ef6c586", - "interchainAccountIsm": "0x1fbcCdc677c10671eE50b46C61F0f7d135112450", - "interchainAccountRouter": "0x96D51cc3f7500d501bAeB1A2a62BB96fa03532F8", + "interchainAccountRouter": "0xbF2D3b1a37D54ce86d0e1455884dA875a97C87a8", "interchainGasPaymaster": "0x23cc88CF424d48fDB05b4f0A8Ff6099aa4D56D8e", - "interchainSecurityModule": "0x3C6454dB28304A8728DF0f61BC540A5A7bE233bc", + "interchainSecurityModule": "0xD005c1a08F23076a6ceD8746E804087FaC2ea0B7", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x2ed6030D204745aC0Cd6be8301C3a63bf14D97Cc", "pausableHook": "0x946E9f4540E032a9fAc038AE58187eFcad9DE952", @@ -9566,7 +8523,6 @@ ], "technicalStack": "other", "domainRoutingIsmFactory": "0x8F3F34DE0ffd1C3135a60005C4aEACdAc1917e30", - "interchainAccountIsm": "0xcC7671Eb52594741e65522f62AB153559d2fd0Ec", "interchainAccountRouter": "0xd75a7b112de0baEBcC7C26c9B6590f4Ac0209d49", "interchainGasPaymaster": "0x0000000000000000000000000000000000000000", "mailbox": "0xef07FAE1a6912C8d333F72E01A03CE3bb18E12a1", @@ -9625,7 +8581,6 @@ "technicalStack": "other", "defaultIsm": "0x015328cb0dd4707ffcf985f5549a69d496cb9e82fbfd8fc5b1a84a6cda0db680", "domainRoutingIsmFactory": "0x0000000000000000000000000000000000000000000000000000000000000000", - "interchainAccountIsm": "0x0000000000000000000000000000000000000000000000000000000000000000", "interchainAccountRouter": "0x0000000000000000000000000000000000000000000000000000000000000000", "interchainGasPaymaster": "0x0000000000000000000000000000000000000000000000000000000000000000", "mailbox": "0x0375a37caebba07e57cb57690cfe681efcd0641b5cdb310622d776069528d2a8", @@ -9728,10 +8683,9 @@ "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x23cc88CF424d48fDB05b4f0A8Ff6099aa4D56D8e", - "interchainAccountIsm": "0xFb7D175d6F53800D68D32C3Fe1416807A394cC24", - "interchainAccountRouter": "0xBa9E76d893f88B42d90b954921118C269D203a2C", + "interchainAccountRouter": "0x829AcBc15a66F6B32a189CFB6451B2Ee583706BA", "interchainGasPaymaster": "0x466b330C2e360c0214A9Da2428415298f720883E", - "interchainSecurityModule": "0x4A0210Ff7fdE6DA93314e72A1D035570bc66a293", + "interchainSecurityModule": "0x7165BdF04eA70808D685064faB50f1304B848cE9", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x58CA3F0D65FFd40727cF6Eb2f323151853Ad3D44", "pausableHook": "0xA697222b77cDe62A8C47E627d5A1c49A87018236", @@ -9797,7 +8751,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x0dc95Af5156fb0cC34a8c9BD646B748B9989A956", "interchainGasPaymaster": "0xD233433AeC23F8382DAd87D808F60557Ea35399f", - "interchainSecurityModule": "0x1C241771Dbf491e57eE9bEf97b861B6f5cf3627d", + "interchainSecurityModule": "0x0A5A1985d2b35381A9368c300516B22AB4050b87", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x64F077915B41479901a23Aa798B30701589C5063", "pausableHook": "0xF6CC9B10c607afB777380bF71F272E4D7037C3A9", @@ -9813,7 +8767,8 @@ "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "storageGasOracle": "0x794Fe7970EE45945b0ad2667f99A5bBc9ddfB5d7", "testRecipient": "0xc8826EA18D9884A1A335b2Cd7d5f44B159084301", - "validatorAnnounce": "0x5b1A451c85e5bcCd79A56eb638aBd9998fA215f9" + "validatorAnnounce": "0x5b1A451c85e5bcCd79A56eb638aBd9998fA215f9", + "interchainAccountRouter": "0x1fbcCdc677c10671eE50b46C61F0f7d135112450" }, "xrplevm": { "blockExplorers": [ @@ -9855,7 +8810,7 @@ "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "fallbackRoutingHook": "0x0dc95Af5156fb0cC34a8c9BD646B748B9989A956", "interchainGasPaymaster": "0xD233433AeC23F8382DAd87D808F60557Ea35399f", - "interchainSecurityModule": "0x44AE7f69Ca8c6EE978015f8b2C9008ee265bD12a", + "interchainSecurityModule": "0x8aE45Dd0d61EB480E4F71CB165517b311bF3d09C", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "merkleTreeHook": "0x64F077915B41479901a23Aa798B30701589C5063", "pausableHook": "0xF6CC9B10c607afB777380bF71F272E4D7037C3A9", @@ -9874,7 +8829,8 @@ "validatorAnnounce": "0x5b1A451c85e5bcCd79A56eb638aBd9998fA215f9", "index": { "from": 1005847 - } + }, + "interchainAccountRouter": "0xe6fC77B08b457A29747682aB1dBfb32AF4A1A999" }, "noble": { "bech32Prefix": "noble", @@ -9953,6 +8909,589 @@ "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", "merkleTreeHook": "0x726f757465725f706f73745f6469737061746368000000030000000000000000", "validatorAnnounce": "0x68797065726c616e650000000000000000000000000000000000000000000000" + }, + "celestia": { + "bech32Prefix": "celestia", + "blockExplorers": [ + { + "apiUrl": "https://celenium.io", + "family": "other", + "name": "Celenium", + "url": "https://celenium.io" + }, + { + "apiUrl": "https://www.mintscan.io/celestia", + "family": "other", + "name": "Mintscan", + "url": "https://www.mintscan.io/celestia" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 6, + "reorgPeriod": 1 + }, + "canonicalAsset": "utia", + "chainId": "celestia", + "contractAddressBytes": 32, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Celestia", + "domainId": 1128614981, + "gasCurrencyCoinGeckoId": "celestia", + "gasPrice": { + "denom": "utia", + "amount": "0.02" + }, + "grpcUrls": [ + { + "http": "https://celestia-grpc.publicnode.com:443" + } + ], + "index": { + "chunk": 10, + "from": 6680339 + }, + "name": "celestia", + "nativeToken": { + "decimals": 6, + "denom": "utia", + "name": "Celestia", + "symbol": "TIA" + }, + "protocol": "cosmosnative", + "restUrls": [ + { + "http": "https://celestia-rest.publicnode.com" + } + ], + "rpcUrls": [ + { + "http": "https://celestia-rpc.publicnode.com:443" + } + ], + "signer": { + "key": "0x5486418967eabc770b0fcb995f7ef6d9a72f7fc195531ef76c5109f44f51af26", + "prefix": "celestia", + "type": "cosmosKey" + }, + "slip44": 118, + "technicalStack": "other", + "interchainGasPaymaster": "0x726f757465725f706f73745f6469737061746368000000040000000000000001", + "interchainSecurityModule": "0x726f757465725f69736d00000000000000000000000000010000000000000000", + "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "merkleTreeHook": "0x726f757465725f706f73745f6469737061746368000000030000000000000000", + "validatorAnnounce": "0x68797065726c616e650000000000000000000000000000000000000000000000" + }, + "mitosis": { + "blocks": { + "confirmations": 1, + "estimateBlockTime": 2, + "reorgPeriod": 5 + }, + "chainId": 124816, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Mitosis", + "domainId": 124816, + "gasCurrencyCoinGeckoId": "mitosis", + "name": "mitosis", + "nativeToken": { + "decimals": 18, + "name": "MITO", + "symbol": "MITO" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.mitosis.org" + } + ], + "technicalStack": "other", + "aggregationHook": "0x5DF904906e0736b7F56894027AEE0bdF6963629E", + "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", + "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "fallbackRoutingHook": "0xEa2Bcee14eA30bbBe3018E5E7829F963230F71C3", + "interchainAccountRouter": "0x1A41a365A693b6A7aED1a46316097d290f569F22", + "interchainGasPaymaster": "0xf4035357EB3e3B48E498FA6e1207892f615A2c2f", + "interchainSecurityModule": "0x53e75279c691a796B25b3581ECd1cDB36DB5d5f9", + "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "merkleTreeHook": "0x1e4dE25C3b07c8DF66D4c193693d8B5f3b431d51", + "pausableHook": "0x2D374F85AE2B80147CffEb34d294ce02d1afd4D8", + "pausableIsm": "0xB2b0A80b2fa3fC9aB1564A4FaF013d4D6084B325", + "protocolFee": "0x8D8979F2C29bA49FAb259A826D0271c43F70288c", + "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticAggregationIsm": "0x56E7a2Ef2f95374C54c70d3Ca74E8A404A59C90c", + "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "storageGasOracle": "0xf6092016a7bef22F7aC9C982C9E96D968F709C05", + "testRecipient": "0x72246331d057741008751AB3976a8297Ce7267Bc", + "validatorAnnounce": "0xdf4aA3905e0391C7763e33CB6A08fFa97221D49B", + "index": { + "chunk": 1000, + "from": 127654 + }, + "blockExplorers": [ + { + "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/124816/etherscan/api", + "family": "routescan", + "name": "Mitosis Explorer", + "url": "https://mitoscan.io/" + } + ] + }, + "radix": { + "bech32Prefix": "account_rdx", + "blockExplorers": [ + { + "apiUrl": "https://dashboard.radixdlt.com", + "family": "radixdashboard", + "name": "Radix Dashboard", + "url": "https://dashboard.radixdlt.com" + } + ], + "blocks": { + "confirmations": 0, + "estimateBlockTime": 5, + "reorgPeriod": 0 + }, + "chainId": 1, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "networkName": "mainnet", + "displayName": "Radix", + "domainId": 1633970780, + "gasCurrencyCoinGeckoId": "radix", + "gatewayUrls": [ + { + "http": "https://mainnet.radixdlt.com" + } + ], + "name": "radix", + "nativeToken": { + "decimals": 18, + "denom": "resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd", + "name": "Radix", + "symbol": "XRD" + }, + "protocol": "radix", + "rpcUrls": [ + { + "http": "https://radix.rpc.grove.city/v1/326002fc" + } + ], + "interchainGasPaymaster": "component_rdx1cznxpn5m3kutzr6jrhgnvv0x7uhcs0rf8fl2w59hkclm6m7axzlqgu", + "interchainSecurityModule": "component_rdx1crzkj7lujcdazgc4hpuvzlkmaddwnzh6d39ln5hrpxk6wllehqcdcf", + "mailbox": "component_rdx1cpcq2wcs8zmpjanjf5ek76y4wttdxswnyfcuhynz4zmhjfjxqfsg9z", + "merkleTreeHook": "component_rdx1cz4c0upfeezhr7nxft5x3dg7w4gmhddy62d5a730lurazwkk830r4g", + "validatorAnnounce": "component_rdx1cr0hsv0qg58ud3pv3prd4kv2m4dl4vd4ma2yd0et64987qukxp9n94", + "signer": { + "key": "0x1bf2d9eb6d9b2bd8ba49d3064375eaf72fb585aecc8873a20188740abd069646", + "type": "radixKey", + "suffix": "rdx" + }, + "technicalStack": "other" + }, + "pulsechain": { + "blockExplorers": [ + { + "apiUrl": "https://scan.pulsechain.box/api", + "family": "blockscout", + "name": "Pulsechain Explorer", + "url": "https://scan.pulsechain.box" + }, + { + "apiUrl": "https://api.scan.pulsechain.com/api", + "family": "blockscout", + "name": "Pulsechain Explorer", + "url": "https://scan.pulsechainfoundation.org/#/" + }, + { + "apiUrl": "https://api.scan.pulsechain.com/api", + "family": "other", + "name": "Otterscan", + "url": "https://otter.pulsechain.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 10, + "reorgPeriod": 10 + }, + "chainId": 369, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "PulseChain", + "domainId": 369, + "gasCurrencyCoinGeckoId": "pulsechain", + "name": "pulsechain", + "nativeToken": { + "decimals": 18, + "name": "PulseChain", + "symbol": "PLS" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.pulsechain.com" + }, + { + "http": "https://pulsechain-rpc.publicnode.com" + } + ], + "technicalStack": "other", + "aggregationHook": "0xcb8738c0e8ea5CCD603C8D593720FB1C16396CC5", + "domainRoutingIsm": "0xceC94cB0C1d128B36e2142bDd070e047f0E4B1CC", + "domainRoutingIsmFactory": "0x175113a9E1B81DB97482edF427C1Ac079eFD9b2a", + "fallbackRoutingHook": "0x4ECc899c97A5a70273C291cB30740CdF1244a931", + "interchainAccountRouter": "0x7823Ce52c254F71321a70b2b87EcC63a516008a1", + "interchainGasPaymaster": "0xc996F4D7d7F39189921A08F3DaAf1b9ff0b20006", + "interchainSecurityModule": "0x0F9BdcC84905D1610eb0d19fB37C0bd3759d8BaF", + "mailbox": "0x56176C7Fb66FdD70ef962Ae53a46A226c7F6a2Cc", + "merkleTreeHook": "0x9DaC51dF95298453C7fb5b43233818CfA4604daC", + "pausableHook": "0x7528697bdc7300fFA9470EfbDb6007Ea34e56c38", + "pausableIsm": "0x5adA3443361585A1c651DA5E53AEeF5Eec364b56", + "protocolFee": "0xbadC9416567503f01A737477f88f64d41f817Cba", + "proxyAdmin": "0xEe3147deeDFC0c6DDD7c65D4e9ff9a48632BE645", + "staticAggregationHookFactory": "0x651178ADa5378d82401D2E6543fc00f8CDcf3aaf", + "staticAggregationIsm": "0xaF2eb8E23728e3f3d547E05fee3832Bee1d4Fb39", + "staticAggregationIsmFactory": "0xD6ba15FF7191b4b823d4e212EDA4530a76506D46", + "staticMerkleRootMultisigIsmFactory": "0x7aed6B857796806C890D969B40325F5bc87e0313", + "staticMerkleRootWeightedMultisigIsmFactory": "0x99652737deCa1924F9D267bD7eFE784f28E282C8", + "staticMessageIdMultisigIsmFactory": "0x55e1e72051872F571C2B9e7e738276F59ffe3148", + "staticMessageIdWeightedMultisigIsmFactory": "0xD9B8aCD833452ab6021290719CeF5759DDbF2E53", + "storageGasOracle": "0xe974B5F169a5770DbfFB20F669e662Bd721c9Cc4", + "testRecipient": "0x08462526f57DADfc371bc0c0cb3227eBb4576a7c", + "validatorAnnounce": "0xB08bd33086CF244D3757843DdD771c3263e1C02c", + "index": { + "from": 24358784 + } + }, + "electroneum": { + "blockExplorers": [ + { + "apiUrl": "https://blockexplorer.electroneum.com/api", + "family": "blockscout", + "name": "Electroneum Explorer", + "url": "https://blockexplorer.electroneum.com" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 5, + "reorgPeriod": 10 + }, + "chainId": 52014, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Electroneum", + "domainId": 52014, + "gasCurrencyCoinGeckoId": "electroneum", + "name": "electroneum", + "nativeToken": { + "decimals": 18, + "name": "ETN", + "symbol": "ETN" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.ankr.com/electroneum" + } + ], + "technicalStack": "other", + "aggregationHook": "0xC1F1EC06556512CCBCA8493F25dE4D438C825f7A", + "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", + "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "fallbackRoutingHook": "0xbE58F200ffca4e1cE4D2F4541E94Ae18370fC405", + "interchainAccountRouter": "0x9fE454AA2B01fc7A2a777AE561bc58Ce560CD5a9", + "interchainGasPaymaster": "0x1A41a365A693b6A7aED1a46316097d290f569F22", + "interchainSecurityModule": "0xCB1c58B88CAcf9BF2251a61B3ceDED4fb336E478", + "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "merkleTreeHook": "0xA7d42B7a7603bEb87f84a1f3D5C97a033DFd2Cc9", + "pausableHook": "0x8D8979F2C29bA49FAb259A826D0271c43F70288c", + "pausableIsm": "0xf4035357EB3e3B48E498FA6e1207892f615A2c2f", + "protocolFee": "0x238b3Fc6f3D32102AA655984059A051647DA98e2", + "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticAggregationIsm": "0x3505b58BD49C5261aC33a8Dec01756FEb66B9ee1", + "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "storageGasOracle": "0xcDD89f19b2d00DCB9510BB3fBd5eCeCa761fe5Ab", + "testRecipient": "0x6D48135b7584E8Bf828B6e23110Bc0Da4252704f", + "validatorAnnounce": "0x75719C858e0c73e07128F95B2C466d142490e933", + "index": { + "from": 9403258 + } + }, + "plasma": { + "blockExplorers": [ + { + "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/9745/etherscan/api", + "family": "routescan", + "name": "Plasma Explorer", + "url": "https://plasmascan.to" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 1, + "reorgPeriod": 10 + }, + "chainId": 9745, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Plasma", + "domainId": 9745, + "gasCurrencyCoinGeckoId": "plasma", + "name": "plasma", + "nativeToken": { + "decimals": 18, + "name": "XPL", + "symbol": "XPL" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.plasma.to" + } + ], + "technicalStack": "other", + "aggregationHook": "0xC1F1EC06556512CCBCA8493F25dE4D438C825f7A", + "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", + "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "fallbackRoutingHook": "0xbE58F200ffca4e1cE4D2F4541E94Ae18370fC405", + "interchainAccountRouter": "0x9fE454AA2B01fc7A2a777AE561bc58Ce560CD5a9", + "interchainGasPaymaster": "0x1A41a365A693b6A7aED1a46316097d290f569F22", + "interchainSecurityModule": "0xCB1c58B88CAcf9BF2251a61B3ceDED4fb336E478", + "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "merkleTreeHook": "0xA7d42B7a7603bEb87f84a1f3D5C97a033DFd2Cc9", + "pausableHook": "0x8D8979F2C29bA49FAb259A826D0271c43F70288c", + "pausableIsm": "0xf4035357EB3e3B48E498FA6e1207892f615A2c2f", + "protocolFee": "0x238b3Fc6f3D32102AA655984059A051647DA98e2", + "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticAggregationIsm": "0x3505b58BD49C5261aC33a8Dec01756FEb66B9ee1", + "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "storageGasOracle": "0xcDD89f19b2d00DCB9510BB3fBd5eCeCa761fe5Ab", + "testRecipient": "0x6D48135b7584E8Bf828B6e23110Bc0Da4252704f", + "validatorAnnounce": "0x75719C858e0c73e07128F95B2C466d142490e933", + "index": { + "from": 89474 + } + }, + "sova": { + "blockExplorers": [ + { + "apiUrl": "https://explorer.sova.io/api", + "family": "blockscout", + "name": "Sova Explorer", + "url": "https://explorer.sova.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 2, + "reorgPeriod": 5 + }, + "chainId": 100021, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Sova", + "domainId": 100021, + "gasCurrencyCoinGeckoId": "ethereum", + "name": "sova", + "nativeToken": { + "decimals": 18, + "name": "ETH", + "symbol": "ETH" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc.sova.io" + } + ], + "technicalStack": "opstack", + "aggregationHook": "0x461EABb3719b8901810b3Ab91144Fe3245ad11b1", + "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", + "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "fallbackRoutingHook": "0x1A41a365A693b6A7aED1a46316097d290f569F22", + "interchainAccountRouter": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143", + "interchainGasPaymaster": "0x75719C858e0c73e07128F95B2C466d142490e933", + "interchainSecurityModule": "0x776FeEbaC2bDdb6902Baa34f22cACFd9f556Ba1F", + "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "merkleTreeHook": "0x7947b7Fe737B4bd1D3387153f32148974066E591", + "pausableHook": "0xf303B04d9ad21dAe2658Cf302478A424e0B45368", + "pausableIsm": "0xdf4aA3905e0391C7763e33CB6A08fFa97221D49B", + "protocolFee": "0x76F2cC245882ceFf209A61d75b9F0f1A3b7285fB", + "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticAggregationIsm": "0x1a81bEA3FecDaCa6a0eE119840Fb7Eb215CE54de", + "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "storageGasOracle": "0xC4F464C89184c7870858A8B12ca822daB6ed04F3", + "testRecipient": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", + "validatorAnnounce": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", + "index": { + "from": 162860 + } + }, + "zerogravity": { + "blockExplorers": [ + { + "apiUrl": "https://chainscan.0g.ai/open/api", + "family": "other", + "name": "0G Explorer", + "url": "https://chainscan.0g.ai" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 0.4, + "reorgPeriod": 10 + }, + "chainId": 16661, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "0G", + "domainId": 16661, + "gasCurrencyCoinGeckoId": "zero-gravity", + "name": "zerogravity", + "nativeToken": { + "decimals": 18, + "name": "0G", + "symbol": "0G" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://evmrpc.0g.ai" + } + ], + "technicalStack": "other", + "aggregationHook": "0x273d450bdB633b7787A6b220AcAF5b616eD17CCB", + "domainRoutingIsm": "0xe785522199eb01B39d5E6b934DbE5bfcbb9A29A4", + "domainRoutingIsmFactory": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "fallbackRoutingHook": "0x75719C858e0c73e07128F95B2C466d142490e933", + "interchainAccountRouter": "0x23cc88CF424d48fDB05b4f0A8Ff6099aa4D56D8e", + "interchainGasPaymaster": "0x7B8AA8f23Ab6B0757eC6FC71894211376D9335b0", + "interchainSecurityModule": "0x43Cf9D502731A8F643869a7D005948422e5Cda7a", + "mailbox": "0x8428a1a7E97Fc75Fb7Ba5c4aec31B55e52bbe9D6", + "merkleTreeHook": "0x5887BDA66EC9e854b0da6BFFe423511e69d327DC", + "pausableHook": "0x6D48135b7584E8Bf828B6e23110Bc0Da4252704f", + "pausableIsm": "0xE911e75CECe0eF01df3ceD96BCd362941AE474D4", + "protocolFee": "0x946E9f4540E032a9fAc038AE58187eFcad9DE952", + "proxyAdmin": "0x02d16BC51af6BfD153d67CA61754cF912E82C4d9", + "staticAggregationHookFactory": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "staticAggregationIsm": "0x5440cEd0D21e0C29E716bE887a7A57bF1BeEC1cB", + "staticAggregationIsmFactory": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "staticMerkleRootMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "staticMerkleRootWeightedMultisigIsmFactory": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", + "staticMessageIdMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "staticMessageIdWeightedMultisigIsmFactory": "0x2f9DB5616fa3fAd1aB06cB2C906830BA63d135e3", + "storageGasOracle": "0x74B2b1fC57B28e11A5bAf32a758bbC98FA7837da", + "testRecipient": "0xc8826EA18D9884A1A335b2Cd7d5f44B159084301", + "validatorAnnounce": "0x5b1A451c85e5bcCd79A56eb638aBd9998fA215f9", + "index": { + "from": 5797421 + }, + "transactionOverrides": { + "minFeePerGas": 2000000000, + "minPriorityFeePerGas": 2000000000 + } + }, + "mantra": { + "blockExplorers": [ + { + "apiUrl": "https://blockscout.mantrascan.io/api", + "family": "blockscout", + "name": "Mantrascan", + "url": "https://blockscout.mantrascan.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 3, + "reorgPeriod": 5 + }, + "chainId": 5888, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Mantra", + "domainId": 5888, + "gasCurrencyCoinGeckoId": "mantra-dao", + "name": "mantra", + "nativeToken": { + "decimals": 18, + "name": "OM", + "symbol": "OM" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://evm.mantrachain.io" + } + ], + "technicalStack": "other", + "aggregationHook": "0x60A7854c34b73Bf8056Ddec3F0B1bA1e9f6508F4", + "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", + "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "fallbackRoutingHook": "0xE911e75CECe0eF01df3ceD96BCd362941AE474D4", + "interchainAccountRouter": "0x0DbB60c348DF645c295Fd0ce26F87bB850710185", + "interchainGasPaymaster": "0x9fE454AA2B01fc7A2a777AE561bc58Ce560CD5a9", + "interchainSecurityModule": "0xd3fA473AC307cED4A454c20626EA39D88d3fDE41", + "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "merkleTreeHook": "0xC4F464C89184c7870858A8B12ca822daB6ed04F3", + "pausableHook": "0x238b3Fc6f3D32102AA655984059A051647DA98e2", + "pausableIsm": "0x1A41a365A693b6A7aED1a46316097d290f569F22", + "protocolFee": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", + "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "staticAggregationIsm": "0xd3fA473AC307cED4A454c20626EA39D88d3fDE41", + "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "staticMerkleRootWeightedMultisigIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "staticMessageIdWeightedMultisigIsmFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "storageGasOracle": "0x5887BDA66EC9e854b0da6BFFe423511e69d327DC", + "testRecipient": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143", + "validatorAnnounce": "0xBCD18636e5876DFd7AAb5F2B2a5Eb5ca168BA1d8", + "index": { + "from": 8730936 + } } } } diff --git a/rust/main/config/testnet_config.json b/rust/main/config/testnet_config.json index 93287f8fe52..748e66e4326 100644 --- a/rust/main/config/testnet_config.json +++ b/rust/main/config/testnet_config.json @@ -1,70 +1,5 @@ { "chains": { - "alfajores": { - "aggregationHook": "0xdBabD76358897E68E4964647C1fb8Bf524f5EFdB", - "blockExplorers": [ - { - "apiUrl": "https://explorer.celo.org/alfajores/api", - "family": "blockscout", - "name": "Blockscout", - "url": "https://explorer.celo.org/alfajores" - } - ], - "blocks": { - "confirmations": 3, - "estimateBlockTime": 5, - "reorgPeriod": 0 - }, - "chainId": 44787, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Alfajores", - "domainId": 44787, - "domainRoutingIsm": "0xD1DCBe1546bb911f2570E939a231A28F14C29638", - "domainRoutingIsmFactory": "0x30d9A03762431F8A917a0C469E7A62Bf55092Ca6", - "fallbackRoutingHook": "0x3528B1aeF3a3d29E0eae90ad777A2b4A6a48aC3F", - "index": { - "from": 20231908 - }, - "interchainAccountIsm": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E", - "interchainAccountRouter": "0xEbA64c8a9b4a61a9210d5fe7E4375380999C821b", - "interchainGasPaymaster": "0x44769b0f4a6f01339e131a691cc2eebbb519d297", - "interchainSecurityModule": "0x9a61371af2b381e9786172fa5205039c3Ed52880", - "isTestnet": true, - "mailbox": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", - "merkleTreeHook": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", - "name": "alfajores", - "nativeToken": { - "decimals": 18, - "name": "CELO", - "symbol": "CELO" - }, - "pausableHook": "0x4a2902395A40Ecf0B57CaB362b59bAffba9BB4aE", - "pausableIsm": "0xA4caB1565083D33899A6eE69B174cC7729b3EaDF", - "protocol": "ethereum", - "protocolFee": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", - "proxyAdmin": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", - "rpcUrls": [ - { - "http": "https://alfajores-forno.celo-testnet.org" - } - ], - "staticAggregationHookFactory": "0x71bB34Ee833467443628CEdFAA188B2387827Cee", - "staticAggregationIsm": "0x8B29157852340cC5d3d0E289be3B0344E8812173", - "staticAggregationIsmFactory": "0x4bE8AC22f506B1504C93C3A5b1579C5e7c550D9C", - "staticMerkleRootMultisigIsmFactory": "0xa9C7e306C0941896CA1fd528aA59089571D8D67E", - "staticMessageIdMultisigIsmFactory": "0xC1b8c0e56D6a34940Ee2B86172450B54AFd633A7", - "storageGasOracle": "0x8356113754C7aCa297Db3089b89F87CC125499fb", - "testRecipient": "0x6489d13AcAd3B8dce4c5B31f375DE4f9451E7b38", - "testTokenRecipient": "0x92dC0a76452a9D9358D2d2dEd8CddA209DF67c45", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", - "staticMerkleRootWeightedMultisigIsmFactory": "0x374961678da5911083599314974B94094513F95c", - "staticMessageIdWeightedMultisigIsmFactory": "0x1Fa22d908f5a5E7F5429D9146E5a3740D8AC10d7", - "gasCurrencyCoinGeckoId": "celo" - }, "arbitrumsepolia": { "aggregationHook": "0xD2670EedcD21116c6F0B331Ce391eA4B3Bf1aB19", "blockExplorers": [ @@ -95,7 +30,7 @@ "from": 49690504 }, "interchainGasPaymaster": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", - "interchainSecurityModule": "0x77261C0517a16B86c80608aA711Bb993b2aA1111", + "interchainSecurityModule": "0x8B00D4DD2716B6A1eed0c5CB0BdF2bD66B451b3a", "isTestnet": true, "mailbox": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8", "merkleTreeHook": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", @@ -167,7 +102,7 @@ "from": 13851043 }, "interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "interchainSecurityModule": "0xC7Ee6061c213555033f414Ff1841c63e9fB0aFED", + "interchainSecurityModule": "0x9DAF056AbE167098Dc8eDE9C7C0A142834f95Be6", "isTestnet": true, "mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039", "merkleTreeHook": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", @@ -237,7 +172,7 @@ "interchainAccountIsm": "0xa9D8Ec959F34272B1a56D09AF00eeee58970d3AE", "interchainAccountRouter": "0x6d2B3e304E58c2a19f1492E7cf15CaF63Ce6e0d2", "interchainGasPaymaster": "0x0dD20e410bdB95404f71c5a4e7Fa67B892A5f949", - "interchainSecurityModule": "0xc1d01Ae6698C06cfd0B3cd84BcBE9371b6F65B5a", + "interchainSecurityModule": "0x0eea66145A96Ea14881E95f430987a7fDc877FE0", "isTestnet": true, "mailbox": "0xF9F6F5646F478d5ab4e20B0F910C92F1CCC9Cc6D", "merkleTreeHook": "0xc6cbF39A747f5E28d1bDc8D9dfDAb2960Abd5A8f", @@ -277,71 +212,6 @@ "gasPrice": 1000000000 } }, - "connextsepolia": { - "aggregationHook": "0x331eb40963dc11F5BB271308c42d97ac6e41F124", - "blockExplorers": [ - { - "apiUrl": "https://scan.testnet.everclear.org/api", - "family": "blockscout", - "name": "Everclear Testnet Explorer", - "url": "https://scan.testnet.everclear.org/" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 10, - "reorgPeriod": 0 - }, - "chainId": 6398, - "deployer": { - "name": "Everclear", - "url": "https://everclear.org" - }, - "displayName": "Everclear Sepolia", - "domainId": 6398, - "domainRoutingIsm": "0x4ac19e0bafc2aF6B98094F0a1B817dF196551219", - "domainRoutingIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "fallbackRoutingHook": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE", - "index": { - "from": 4950 - }, - "interchainGasPaymaster": "0xeC7eb4196Bd601DEa7585A744FbFB4CF11278450", - "interchainSecurityModule": "0x45825A82012CCc2e6Da3713c70f14a55AF8eE8d6", - "isTestnet": true, - "mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "merkleTreeHook": "0x4926a10788306D84202A2aDbd290b7743146Cc17", - "name": "connextsepolia", - "nativeToken": { - "decimals": 18, - "name": "Ether", - "symbol": "ETH" - }, - "pausableHook": "0x07009DA2249c388aD0f416a235AfE90D784e1aAc", - "pausableIsm": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "protocol": "ethereum", - "protocolFee": "0x863E8c26621c52ACa1849C53500606e73BA272F0", - "proxyAdmin": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "rpcUrls": [ - { - "http": "https://rpc.connext-sepolia.gelato.digital" - } - ], - "staticAggregationHookFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "staticAggregationIsm": "0x5e6Fe18eC7D4b159bDC5Fa0C32bB1996277B3ddF", - "staticAggregationIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "staticMerkleRootMultisigIsmFactory": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", - "staticMessageIdMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "storageGasOracle": "0xF7561c34f17A32D5620583A3397C304e7038a7F6", - "technicalStack": "arbitrumnitro", - "testRecipient": "0xAb9B273366D794B7F80B4378bc8Aaca75C6178E2", - "validatorAnnounce": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", - "staticMerkleRootWeightedMultisigIsmFactory": "0x8584590ad637C61C7cDF72eFF3381Ee1c3D1bC8E", - "staticMessageIdWeightedMultisigIsmFactory": "0xcCB305B1f21e5FbC85D1DD7Be5cd8d5bf5B7f863", - "gasCurrencyCoinGeckoId": "ethereum", - "interchainAccountIsm": "0xA30b2CbC14b97aa55bBC947f4AC6c4254971aFD1", - "interchainAccountRouter": "0xc9ab470A61571ac0c39B7E0923fbEaDdB58d98FE", - "timelockController": "0x0000000000000000000000000000000000000000" - }, "eclipsetestnet": { "blocks": { "confirmations": 1, @@ -378,70 +248,6 @@ "validatorAnnounce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3", "gasCurrencyCoinGeckoId": "ethereum" }, - "ecotestnet": { - "aggregationHook": "0xccA408a6A9A6dc405C3278647421eb4317466943", - "blockExplorers": [ - { - "apiUrl": "https://eco-testnet.explorer.caldera.xyz/api", - "family": "blockscout", - "name": "ECO Testnet explorer", - "url": "https://eco-testnet.explorer.caldera.xyz/" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 1, - "reorgPeriod": 0 - }, - "chainId": 471923, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Eco Testnet", - "domainId": 471923, - "domainRoutingIsm": "0x4ac19e0bafc2aF6B98094F0a1B817dF196551219", - "domainRoutingIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "fallbackRoutingHook": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75", - "index": { - "from": 1606754 - }, - "interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "interchainSecurityModule": "0x58f2Dc9bC238ad30B744af2AB573b611ec2adaAE", - "isTestnet": true, - "mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "merkleTreeHook": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", - "name": "ecotestnet", - "nativeToken": { - "decimals": 18, - "name": "Ether", - "symbol": "ETH" - }, - "pausableHook": "0x19Be55D859368e02d7b9C00803Eb677BDC1359Bd", - "pausableIsm": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", - "protocol": "ethereum", - "protocolFee": "0x1b33611fCc073aB0737011d5512EF673Bff74962", - "proxyAdmin": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "rpcUrls": [ - { - "http": "https://eco-testnet.rpc.caldera.xyz/http" - } - ], - "staticAggregationHookFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "staticAggregationIsm": "0x815a9642497Ee1E9F061f8b828C85Eb7193DecfC", - "staticAggregationIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "staticMerkleRootMultisigIsmFactory": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", - "staticMessageIdMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "storageGasOracle": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "testRecipient": "0x783c4a0bB6663359281aD4a637D5af68F83ae213", - "validatorAnnounce": "0x20c44b1E3BeaDA1e9826CFd48BeEDABeE9871cE9", - "staticMerkleRootWeightedMultisigIsmFactory": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8", - "staticMessageIdWeightedMultisigIsmFactory": "0x628BC518ED1e0E8C6cbcD574EbA0ee29e7F6943E", - "gasCurrencyCoinGeckoId": "ethereum", - "interchainAccountIsm": "0x0De2F539569Fb1e2e3C1d233f7A63a18B9A17110", - "interchainAccountRouter": "0x2C6dD6768E669EDB7b53f26067C1C4534862c3de", - "timelockController": "0x0000000000000000000000000000000000000000" - }, "fuji": { "aggregationHook": "0x8E9b4006171c6B75111823e7545Ee5400CEce0B3", "blockExplorers": [ @@ -473,7 +279,7 @@ "interchainAccountIsm": "0xfaB4815BDC5c60c6bD625459C8577aFdD79D9311", "interchainAccountRouter": "0xeEF6933122894fF217a7dd07510b3D64b747e29b", "interchainGasPaymaster": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E", - "interchainSecurityModule": "0x447712120C92661FBE2Ee1f062DA17E892e52945", + "interchainSecurityModule": "0x39a3A416a48bC11737320C259058148A87ccbAf3", "isTestnet": true, "mailbox": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", "merkleTreeHook": "0x9ff6ac3dAf63103620BBf76136eA1AFf43c2F612", @@ -539,7 +345,7 @@ "from": 1543015 }, "interchainGasPaymaster": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", - "interchainSecurityModule": "0x4Bb06Cd554724f4816Ea589e86a842b6B6268758", + "interchainSecurityModule": "0xA13182528feA6cD3F650c6E503c341e2AA1c65f2", "isTestnet": true, "mailbox": "0x46f7C5D896bbeC89bE1B19e4485e59b4Be49e9Cc", "merkleTreeHook": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE", @@ -605,7 +411,7 @@ "from": 15833917 }, "interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "interchainSecurityModule": "0x169CEC9A408791b83C654BFD19c798BA846FB8Dd", + "interchainSecurityModule": "0x426539Fda024e1Ae9CaB738D6EAb721F9eA6F2B3", "isTestnet": true, "mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039", "merkleTreeHook": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", @@ -733,7 +539,7 @@ "from": 10634605 }, "interchainGasPaymaster": "0x6c13643B3927C57DB92c790E4E3E7Ee81e13f78C", - "interchainSecurityModule": "0x3044380054b7d1a83bC7F779D3E61cD9D4FCa00B", + "interchainSecurityModule": "0x701121eF50aD3FD04Fea01ba4cc836439200A231", "isTestnet": true, "mailbox": "0x54148470292C24345fb828B003461a9444414517", "merkleTreeHook": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75", @@ -808,7 +614,7 @@ "interchainAccountIsm": "0xE023239c8dfc172FF008D8087E7442d3eBEd9350", "interchainAccountRouter": "0xe17c37212d785760E8331D4A4395B17b34Ba8cDF", "interchainGasPaymaster": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", - "interchainSecurityModule": "0x04856430675C96af0e09FFa7B1f6f529Ea55A08E", + "interchainSecurityModule": "0xe6927f8f2aFB1A3A65ED6631BABcc939411042Bd", "isTestnet": true, "mailbox": "0x3C5154a193D6e2955650f9305c8d80c18C814A68", "merkleTreeHook": "0x863E8c26621c52ACa1849C53500606e73BA272F0", @@ -884,7 +690,7 @@ "interchainAccountIsm": "0x83a3068B719F764d413625dA77468ED74789ae02", "interchainAccountRouter": "0x8e131c8aE5BF1Ed38D05a00892b6001a7d37739d", "interchainGasPaymaster": "0x6f2756380FD49228ae25Aa7F2817993cB74Ecc56", - "interchainSecurityModule": "0x4998C54633C45AC907F3465d8579ACB80E27AF1A", + "interchainSecurityModule": "0x64530546989259BaF402B6DE4922aE745A10faBc", "isTestnet": true, "mailbox": "0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766", "merkleTreeHook": "0x4917a9746A7B6E0A57159cCb7F5a6744247f2d0d", @@ -1023,71 +829,6 @@ "url": "https://www.hyperlane.xyz" } }, - "superpositiontestnet": { - "aggregationHook": "0x331eb40963dc11F5BB271308c42d97ac6e41F124", - "blockExplorers": [ - { - "apiUrl": "https://testnet-explorer.superposition.so/api", - "family": "blockscout", - "name": "Superposition Testnet Explorer", - "url": "https://testnet-explorer.superposition.so" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 1, - "reorgPeriod": 1 - }, - "chainId": 98985, - "displayName": "Superposition Testnet", - "domainId": 98985, - "domainRoutingIsm": "0x4ac19e0bafc2aF6B98094F0a1B817dF196551219", - "domainRoutingIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "fallbackRoutingHook": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE", - "index": { - "from": 3111622 - }, - "interchainGasPaymaster": "0xeC7eb4196Bd601DEa7585A744FbFB4CF11278450", - "interchainSecurityModule": "0x31202fb0C4D5680c9fC474262aeFAD1fef974B73", - "isTestnet": true, - "mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "merkleTreeHook": "0x4926a10788306D84202A2aDbd290b7743146Cc17", - "name": "superpositiontestnet", - "nativeToken": { - "decimals": 18, - "name": "Superposition", - "symbol": "SPN" - }, - "pausableHook": "0x07009DA2249c388aD0f416a235AfE90D784e1aAc", - "pausableIsm": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "protocol": "ethereum", - "protocolFee": "0x863E8c26621c52ACa1849C53500606e73BA272F0", - "proxyAdmin": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "rpcUrls": [ - { - "http": "https://testnet-rpc.superposition.so" - } - ], - "staticAggregationHookFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "staticAggregationIsm": "0x5e6Fe18eC7D4b159bDC5Fa0C32bB1996277B3ddF", - "staticAggregationIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "staticMerkleRootMultisigIsmFactory": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", - "staticMessageIdMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "storageGasOracle": "0xF7561c34f17A32D5620583A3397C304e7038a7F6", - "technicalStack": "arbitrumnitro", - "testRecipient": "0xAb9B273366D794B7F80B4378bc8Aaca75C6178E2", - "validatorAnnounce": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", - "staticMerkleRootWeightedMultisigIsmFactory": "0xE67CfA164cDa449Ae38a0a09391eF6bCDf8e4e2c", - "staticMessageIdWeightedMultisigIsmFactory": "0x867f2089D09903f208AeCac84E599B90E5a4A821", - "gasCurrencyCoinGeckoId": "superposition", - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "interchainAccountIsm": "0xd09D08a19C6609a1B51e1ca6a055861E7e7A4400", - "interchainAccountRouter": "0x3572a9d808738922194921b275B2A55414BcDA57", - "timelockController": "0x0000000000000000000000000000000000000000" - }, "citreatestnet": { "blockExplorers": [ { @@ -1130,7 +871,7 @@ "interchainAccountIsm": "0xFfa913705484C9BAea32Ffe9945BeA099A1DFF72", "interchainAccountRouter": "0xB5fB1F5410a2c2b7deD462d018541383968cB01c", "interchainGasPaymaster": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A", - "interchainSecurityModule": "0x4F3581793605f076f76ADccaa8808aE86972ADc1", + "interchainSecurityModule": "0x5a543693377be020c10AA6cbaD8F093440954cfB", "mailbox": "0xB08d78F439e55D02C398519eef61606A5926245F", "merkleTreeHook": "0x783c4a0bB6663359281aD4a637D5af68F83ae213", "pausableHook": "0x66b71A4e18FbE09a6977A6520B47fEDdffA82a1c", @@ -1153,70 +894,6 @@ "from": 334706 } }, - "formtestnet": { - "blockExplorers": [ - { - "apiUrl": "https://sepolia-explorer.form.network/api", - "family": "blockscout", - "name": "Form Testnet Explorer", - "url": "https://sepolia-explorer.form.network" - } - ], - "blocks": { - "confirmations": 3, - "estimateBlockTime": 2, - "reorgPeriod": 1 - }, - "chainId": 132902, - "displayName": "Form Testnet", - "domainId": 132902, - "gasCurrencyCoinGeckoId": "ethereum", - "isTestnet": true, - "name": "formtestnet", - "nativeToken": { - "decimals": 18, - "name": "Ether", - "symbol": "ETH" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://testnet-rpc.form.network/http" - } - ], - "aggregationHook": "0xb97D172479E9EC2501524E02703B42247559A1bD", - "domainRoutingIsm": "0x2a2F4AAaf726abb4B969c2804D38e188555683b5", - "domainRoutingIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "fallbackRoutingHook": "0xb94F96D398eA5BAB5CA528EE9Fdc19afaA825818", - "interchainAccountIsm": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", - "interchainAccountRouter": "0x867f2089D09903f208AeCac84E599B90E5a4A821", - "interchainGasPaymaster": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8", - "interchainSecurityModule": "0x1914E770E09e15165D6e573814dB03592538e287", - "mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "merkleTreeHook": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A", - "pausableHook": "0x51A0a100e7BC63Ea7821A3a023B6F17fb94FF011", - "pausableIsm": "0x04438ef7622f5412f82915F59caD4f704C61eA48", - "protocolFee": "0xc76E477437065093D353b7d56c81ff54D167B0Ab", - "proxyAdmin": "0x54148470292C24345fb828B003461a9444414517", - "staticAggregationHookFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "staticAggregationIsm": "0x77d4B4090B666d84b4451C7425682B8F51Dbd827", - "staticAggregationIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "staticMerkleRootMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "staticMerkleRootWeightedMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "staticMessageIdMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "staticMessageIdWeightedMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "storageGasOracle": "0x086E902d2f99BcCEAa28B31747eC6Dc5fd43B1bE", - "testRecipient": "0x7483faD0Bc297667664A43A064bA7c9911659f57", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0xEa7e618Bee8927fBb2fA20Bc41eE8DEA51838aAD", - "index": { - "from": 12137144 - }, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - } - }, "hyperliquidevmtestnet": { "blockExplorers": [ { @@ -1282,70 +959,6 @@ "from": 7032169 } }, - "soneiumtestnet": { - "blockExplorers": [ - { - "apiUrl": "https://explorer-testnet.soneium.org/api", - "family": "blockscout", - "name": "Soneium Minato Testnet Explorer", - "url": "https://explorer-testnet.soneium.org" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 2, - "reorgPeriod": 1 - }, - "chainId": 1946, - "displayName": "Soneium Minato Testnet", - "domainId": 1946, - "gasCurrencyCoinGeckoId": "ethereum", - "isTestnet": true, - "name": "soneiumtestnet", - "nativeToken": { - "decimals": 18, - "name": "Ether", - "symbol": "ETH" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://rpc.minato.soneium.org" - } - ], - "aggregationHook": "0xb97D172479E9EC2501524E02703B42247559A1bD", - "domainRoutingIsm": "0x2a2F4AAaf726abb4B969c2804D38e188555683b5", - "domainRoutingIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "fallbackRoutingHook": "0xb94F96D398eA5BAB5CA528EE9Fdc19afaA825818", - "interchainAccountIsm": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", - "interchainAccountRouter": "0x867f2089D09903f208AeCac84E599B90E5a4A821", - "interchainGasPaymaster": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8", - "interchainSecurityModule": "0x53aDE37b0861aF359053b02709f9aA69C96275e3", - "mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "merkleTreeHook": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A", - "pausableHook": "0x51A0a100e7BC63Ea7821A3a023B6F17fb94FF011", - "pausableIsm": "0x04438ef7622f5412f82915F59caD4f704C61eA48", - "protocolFee": "0xc76E477437065093D353b7d56c81ff54D167B0Ab", - "proxyAdmin": "0x54148470292C24345fb828B003461a9444414517", - "staticAggregationHookFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "staticAggregationIsm": "0x77d4B4090B666d84b4451C7425682B8F51Dbd827", - "staticAggregationIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "staticMerkleRootMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "staticMerkleRootWeightedMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "staticMessageIdMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "staticMessageIdWeightedMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "storageGasOracle": "0x086E902d2f99BcCEAa28B31747eC6Dc5fd43B1bE", - "testRecipient": "0x7483faD0Bc297667664A43A064bA7c9911659f57", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0xEa7e618Bee8927fBb2fA20Bc41eE8DEA51838aAD", - "index": { - "from": 2054457 - }, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - } - }, "test1": { "blockExplorers": [ { @@ -1571,7 +1184,7 @@ "interchainAccountIsm": "0x39c85C84876479694A2470c0E8075e9d68049aFc", "interchainAccountRouter": "0x80fE4Cb8c70fc60B745d4ffD4403c27a8cBC9e02", "interchainGasPaymaster": "0xfBeaF07855181f8476B235Cf746A7DF3F9e386Fb", - "interchainSecurityModule": "0x2107E78296DC6E8cb75C55f08cB5C875cf3643Cc", + "interchainSecurityModule": "0x4c6261781F1cEDb83d5Ee1589F2997b1887C3780", "mailbox": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", "merkleTreeHook": "0xEa7e618Bee8927fBb2fA20Bc41eE8DEA51838aAD", "pausableHook": "0x4fE19d49F45854Da50b6009258929613EC92C147", @@ -1597,323 +1210,12 @@ "url": "https://www.hyperlane.xyz" } }, - "unichaintestnet": { - "blockExplorers": [ - { - "apiUrl": "https://unichain-sepolia.blockscout.com/api", - "family": "blockscout", - "name": "Unichain Sepolia Testnet Explorer", - "url": "https://unichain-sepolia.blockscout.com" - } - ], + "kyvealpha": { + "bech32Prefix": "kyve", + "blockExplorers": [], "blocks": { - "confirmations": 3, - "estimateBlockTime": 1, - "reorgPeriod": 1 - }, - "chainId": 1301, - "displayName": "Unichain Testnet", - "domainId": 1301, - "isTestnet": true, - "name": "unichaintestnet", - "nativeToken": { - "decimals": 18, - "name": "Ether", - "symbol": "ETH" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://sepolia.unichain.org" - } - ], - "aggregationHook": "0x04B8A7B7BF29b269428c4976D6408BAf6fd42922", - "domainRoutingIsm": "0x2a2F4AAaf726abb4B969c2804D38e188555683b5", - "domainRoutingIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "fallbackRoutingHook": "0xB057Fb841027a8554521DcCdeC3c3474CaC99AB5", - "interchainAccountIsm": "0x3ca332A585FDB9d4FF51f2FA8999eA32184D3606", - "interchainAccountRouter": "0x4eC139a771eBdD3b0a0b67bb7E08960210882d44", - "interchainGasPaymaster": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA", - "interchainSecurityModule": "0x9b6D46ABeBd5eCEFd8188dc99c676a11E7000DA7", - "mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "merkleTreeHook": "0x086E902d2f99BcCEAa28B31747eC6Dc5fd43B1bE", - "pausableHook": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F", - "pausableIsm": "0xb94F96D398eA5BAB5CA528EE9Fdc19afaA825818", - "protocolFee": "0x7483faD0Bc297667664A43A064bA7c9911659f57", - "proxyAdmin": "0x54148470292C24345fb828B003461a9444414517", - "staticAggregationHookFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "staticAggregationIsm": "0xf0F828278DEfB0c703EE78E620D20BA72CD56D82", - "staticAggregationIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "staticMerkleRootMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "staticMerkleRootWeightedMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "staticMessageIdMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "staticMessageIdWeightedMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "storageGasOracle": "0x7c5B5bdA7F1d1F70A6678ABb4d894612Fc76498F", - "testRecipient": "0x01812D60958798695391dacF092BAc4a715B1718", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", - "index": { - "from": 1721192 - }, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - } - }, - "odysseytestnet": { - "blockExplorers": [ - { - "apiUrl": "https://odyssey-explorer.ithaca.xyz/api", - "family": "blockscout", - "name": "Odyssey Explorer", - "url": "https://odyssey-explorer.ithaca.xyz" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 1, - "reorgPeriod": 1 - }, - "chainId": 911867, - "displayName": "Odyssey Testnet", - "domainId": 911867, - "isTestnet": true, - "name": "odysseytestnet", - "nativeToken": { - "decimals": 18, - "name": "Ether", - "symbol": "ETH" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://odyssey.ithaca.xyz" - } - ], - "aggregationHook": "0xf96cF73BB4e57F90479cD8f74bb4C1f6a0c3da50", - "domainRoutingIsm": "0x2a2F4AAaf726abb4B969c2804D38e188555683b5", - "domainRoutingIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "fallbackRoutingHook": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA", - "interchainAccountIsm": "0xBF2C366530C1269d531707154948494D3fF4AcA7", - "interchainAccountRouter": "0xBdf49bE2201A1c4B13023F0a407196C6Adb32680", - "interchainGasPaymaster": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", - "interchainSecurityModule": "0x93d8aa69369c2b32B8A4177e798Cb30c4f682D57", - "mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "merkleTreeHook": "0xFfa913705484C9BAea32Ffe9945BeA099A1DFF72", - "pausableHook": "0xc76E477437065093D353b7d56c81ff54D167B0Ab", - "pausableIsm": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8", - "protocolFee": "0xfBeaF07855181f8476B235Cf746A7DF3F9e386Fb", - "proxyAdmin": "0x54148470292C24345fb828B003461a9444414517", - "staticAggregationHookFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "staticAggregationIsm": "0x9e71cC1A91E48CfFA2F7D2956eB5c3b730bD8605", - "staticAggregationIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "staticMerkleRootMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "staticMerkleRootWeightedMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "staticMessageIdMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "staticMessageIdWeightedMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "storageGasOracle": "0xB5fB1F5410a2c2b7deD462d018541383968cB01c", - "testRecipient": "0x5e65279Fb7293a058776e37587398fcc3E9184b1", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0x54Bd02f0f20677e9846F8E9FdB1Abc7315C49C38", - "index": { - "from": 67925 - }, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - } - }, - "alephzeroevmtestnet": { - "blockExplorers": [ - { - "apiUrl": "https://evm-explorer-testnet.alephzero.org/api", - "family": "blockscout", - "name": "Aleph Zero EVM Testnet Explorer", - "url": "https://evm-explorer-testnet.alephzero.org" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 3, - "reorgPeriod": 5 - }, - "chainId": 2039, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Aleph Zero EVM Testnet", - "domainId": 2039, - "index": { - "from": 1380870 - }, - "isTestnet": true, - "name": "alephzeroevmtestnet", - "nativeToken": { - "decimals": 18, - "name": "Testnet AZERO", - "symbol": "TZERO" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://rpc.alephzero-testnet.gelato.digital" - } - ], - "technicalStack": "arbitrumnitro", - "aggregationHook": "0xf63f12A71d730794F8de247c65a40E5BF8fA590A", - "domainRoutingIsm": "0x2a2F4AAaf726abb4B969c2804D38e188555683b5", - "domainRoutingIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "fallbackRoutingHook": "0xEa7e618Bee8927fBb2fA20Bc41eE8DEA51838aAD", - "interchainAccountIsm": "0x342B5630Ba1C1e4d3048E51Dad208201aF52692c", - "interchainAccountRouter": "0xe036768e48Cb0D42811d2bF0748806FCcBfCd670", - "interchainGasPaymaster": "0x867f2089D09903f208AeCac84E599B90E5a4A821", - "interchainSecurityModule": "0x2f2f17cC7E77b332A81e95f4D1497BF6387a3352", - "mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "merkleTreeHook": "0xB5fB1F5410a2c2b7deD462d018541383968cB01c", - "pausableHook": "0x7483faD0Bc297667664A43A064bA7c9911659f57", - "pausableIsm": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA", - "protocolFee": "0x5e65279Fb7293a058776e37587398fcc3E9184b1", - "proxyAdmin": "0x54148470292C24345fb828B003461a9444414517", - "staticAggregationHookFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "staticAggregationIsm": "0x1897d03A682C0AA04e2C018B8Edc33A379bf9610", - "staticAggregationIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "staticMerkleRootMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "staticMerkleRootWeightedMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "staticMessageIdMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "staticMessageIdWeightedMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "storageGasOracle": "0x4fE19d49F45854Da50b6009258929613EC92C147", - "testRecipient": "0x843908541D24d9F6Fa30C8Bb1c39038C947D08fC", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0xBF2C366530C1269d531707154948494D3fF4AcA7" - }, - "inksepolia": { - "blockExplorers": [ - { - "apiUrl": "https://explorer-sepolia.inkonchain.com/api", - "family": "blockscout", - "name": "https://explorer-sepolia.inkonchain.com", - "url": "https://explorer-sepolia.inkonchain.com" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 1, - "reorgPeriod": 1 - }, - "chainId": 763373, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Ink Sepolia", - "domainId": 763373, - "isTestnet": true, - "name": "inksepolia", - "nativeToken": { - "decimals": 18, - "name": "Ether", - "symbol": "ETH" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://rpc-qnd-sepolia.inkonchain.com" - } - ], - "technicalStack": "opstack", - "aggregationHook": "0xDd77EFE606DD4e9601D8E13CF3caAcCcacD6bb3c", - "domainRoutingIsm": "0x2a2F4AAaf726abb4B969c2804D38e188555683b5", - "domainRoutingIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "fallbackRoutingHook": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", - "interchainGasPaymaster": "0x54Bd02f0f20677e9846F8E9FdB1Abc7315C49C38", - "interchainSecurityModule": "0xcA6B51a2a6A074DC88e713505b28102bc0C15aE0", - "mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "merkleTreeHook": "0x4fE19d49F45854Da50b6009258929613EC92C147", - "pausableHook": "0x01812D60958798695391dacF092BAc4a715B1718", - "pausableIsm": "0xEa7e618Bee8927fBb2fA20Bc41eE8DEA51838aAD", - "protocolFee": "0x843908541D24d9F6Fa30C8Bb1c39038C947D08fC", - "proxyAdmin": "0x54148470292C24345fb828B003461a9444414517", - "staticAggregationHookFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "staticAggregationIsm": "0x16977B194B3d61aA30F70A5521ac6bbfaa4CF460", - "staticAggregationIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "staticMerkleRootMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "staticMerkleRootWeightedMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "staticMessageIdMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "staticMessageIdWeightedMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "storageGasOracle": "0xE67CfA164cDa449Ae38a0a09391eF6bCDf8e4e2c", - "testRecipient": "0x0e91088824Fa6E2675b2a53DA3491a9B098bD868", - "validatorAnnounce": "0xBdf49bE2201A1c4B13023F0a407196C6Adb32680", - "index": { - "from": 1915290 - }, - "interchainAccountIsm": "0xB7697612fbfb4ad02a11dCa16e9711eCB6Da4ceA", - "interchainAccountRouter": "0xE0745955baF7e614707E22c90EA14d329C38941D", - "timelockController": "0x0000000000000000000000000000000000000000" - }, - "abstracttestnet": { - "blockExplorers": [ - { - "apiUrl": "https://api-explorer-verify.testnet.abs.xyz/contract_verification", - "family": "etherscan", - "name": "Abstract Block Explorer", - "url": "https://explorer.testnet.abs.xyz" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 1, - "reorgPeriod": 0 - }, - "chainId": 11124, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Abstract Testnet", - "domainId": 11124, - "isTestnet": true, - "name": "abstracttestnet", - "nativeToken": { - "decimals": 18, - "name": "Ethereum", - "symbol": "ETH" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://api.testnet.abs.xyz" - } - ], - "technicalStack": "zksync", - "domainRoutingIsm": "0x7ca1b3fa385F3585f8ab58c0bC90A421689141B8", - "domainRoutingIsmFactory": "0x0000000000000000000000000000000000000000", - "fallbackDomainRoutingHook": "0x623f284257f133E8bE7c74f6D4D684B61FE8923a", - "fallbackRoutingHook": "0x623f284257f133E8bE7c74f6D4D684B61FE8923a", - "interchainGasPaymaster": "0xbAaE1B4e953190b05C757F69B2F6C46b9548fa4f", - "interchainSecurityModule": "0x7ca1b3fa385F3585f8ab58c0bC90A421689141B8", - "mailbox": "0x28f448885bEaaF662f8A9A6c9aF20fAd17A5a1DC", - "merkleTreeHook": "0x7fa6009b59F139813eA710dB5496976eE8D80E64", - "proxyAdmin": "0xfbA0c57A6BA24B5440D3e2089222099b4663B98B", - "staticAggregationHookFactory": "0x0000000000000000000000000000000000000000", - "staticAggregationIsmFactory": "0x0000000000000000000000000000000000000000", - "staticMerkleRootMultisigIsmFactory": "0x0000000000000000000000000000000000000000", - "staticMerkleRootWeightedMultisigIsmFactory": "0x0000000000000000000000000000000000000000", - "staticMessageIdMultisigIsmFactory": "0x0000000000000000000000000000000000000000", - "staticMessageIdWeightedMultisigIsmFactory": "0x0000000000000000000000000000000000000000", - "storageGasOracle": "0x5F1bADC7e28B9b4C98f58dB4e5841e5bf63A7A52", - "testRecipient": "0x9EC79CA89DeF61BFa2f38cD4fCC137b9e49d60dD", - "validatorAnnounce": "0xfE9a467831a28Ec3D54deCCf0A2A41fa77dDD1D7", - "index": { - "from": 964305 - } - }, - "kyvealpha": { - "bech32Prefix": "kyve", - "blockExplorers": [], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 5, + "confirmations": 1, + "estimateBlockTime": 5, "reorgPeriod": 1 }, "canonicalAsset": "ukyve", @@ -1992,140 +1294,6 @@ "interchainGasPaymaster": "FSy4hQ92ZTPJVG2UmiWiymoogpwEDBcucKnLzLnbrBDt", "interchainSecurityModule": "DgLicFznJQbnapc9cLSTB2DxN1FnrFnZo5SwD55iTycA" }, - "flametestnet": { - "blockExplorers": [ - { - "apiUrl": "https://explorer.flame.dawn-1.astria.org/api", - "family": "blockscout", - "name": "Astria Flame Explorer", - "url": "https://explorer.flame.dawn-1.astria.org" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 2, - "reorgPeriod": 1 - }, - "chainId": 16604737732183, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Flame Dawn-1 Testnet", - "displayNameShort": "Flame Testnet", - "domainId": 1660473773, - "gasCurrencyCoinGeckoId": "celestia", - "isTestnet": true, - "name": "flametestnet", - "nativeToken": { - "decimals": 18, - "name": "Celestia", - "symbol": "TIA" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://rpc.flame.dawn-1.astria.org" - } - ], - "technicalStack": "other", - "aggregationHook": "0xBfCd91A2d11578056f89e0610D10468f07a16898", - "domainRoutingIsm": "0x4ac19e0bafc2aF6B98094F0a1B817dF196551219", - "domainRoutingIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "fallbackRoutingHook": "0xCB3c489a2FB67a7Cd555D47B3a9A0E654784eD16", - "interchainGasPaymaster": "0x39c85C84876479694A2470c0E8075e9d68049aFc", - "interchainSecurityModule": "0x94e54c5688Dff777Ca881543311812930f3b45B0", - "mailbox": "0x589C201a07c26b4725A4A829d772f24423da480B", - "merkleTreeHook": "0x843908541D24d9F6Fa30C8Bb1c39038C947D08fC", - "pausableHook": "0xBdf49bE2201A1c4B13023F0a407196C6Adb32680", - "pausableIsm": "0xA0aB1750b4F68AE5E8C42d936fa78871eae52643", - "protocolFee": "0x4Ece7b15ba5dCA2708dCE2812016683193102b9F", - "proxyAdmin": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "staticAggregationHookFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "staticAggregationIsm": "0x3b2761e8674551BBC6711E59a8fd9Dc7bE65b32d", - "staticAggregationIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "staticMerkleRootMultisigIsmFactory": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", - "staticMerkleRootWeightedMultisigIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "staticMessageIdMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "staticMessageIdWeightedMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "storageGasOracle": "0x0e91088824Fa6E2675b2a53DA3491a9B098bD868", - "testRecipient": "0x8d4f112cffa338D3c3Ef2Cf443179C5a48E678e4", - "validatorAnnounce": "0xB589407cf6bEA5CD81AD0946b9F1467933ede74c", - "index": { - "from": 3500775 - }, - "interchainAccountIsm": "0x919Af376D02751bFCaD9CBAD6bad0c3089dAE33f", - "interchainAccountRouter": "0xF64c33DcC5F9Dd8CE9A7bc61A4DabeB1Ac6CCE27", - "timelockController": "0x0000000000000000000000000000000000000000" - }, - "sonicblaze": { - "blockExplorers": [ - { - "apiKey": "V9PP7AFQF5Q6GJSQQ5YS2UBN7GI8QCA443", - "apiUrl": "https://api-testnet.sonicscan.org/api", - "family": "etherscan", - "name": "Sonic Blaze Testnet Explorer", - "url": "https://testnet.sonicscan.org" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 1, - "reorgPeriod": 1 - }, - "chainId": 57054, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Sonic Blaze Testnet", - "displayNameShort": "Sonic Blaze", - "domainId": 57054, - "isTestnet": true, - "name": "sonicblaze", - "nativeToken": { - "decimals": 18, - "name": "Sonic", - "symbol": "S" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://rpc.blaze.soniclabs.com" - }, - { - "http": "https://rpc.ankr.com/sonic_blaze_testnet" - } - ], - "aggregationHook": "0xBfCd91A2d11578056f89e0610D10468f07a16898", - "domainRoutingIsm": "0x4ac19e0bafc2aF6B98094F0a1B817dF196551219", - "domainRoutingIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "fallbackRoutingHook": "0xCB3c489a2FB67a7Cd555D47B3a9A0E654784eD16", - "interchainAccountIsm": "0x507C18fa4e3b0ce6beBD494488D62d1ed0fB0555", - "interchainAccountRouter": "0x8584590ad637C61C7cDF72eFF3381Ee1c3D1bC8E", - "interchainGasPaymaster": "0x39c85C84876479694A2470c0E8075e9d68049aFc", - "interchainSecurityModule": "0x239CeB1686cD07Fa28CCbfe82047Ad49f0D6c90B", - "mailbox": "0x589C201a07c26b4725A4A829d772f24423da480B", - "merkleTreeHook": "0x843908541D24d9F6Fa30C8Bb1c39038C947D08fC", - "pausableHook": "0xBdf49bE2201A1c4B13023F0a407196C6Adb32680", - "pausableIsm": "0xA0aB1750b4F68AE5E8C42d936fa78871eae52643", - "protocolFee": "0x4Ece7b15ba5dCA2708dCE2812016683193102b9F", - "proxyAdmin": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "staticAggregationHookFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "staticAggregationIsm": "0x3b2761e8674551BBC6711E59a8fd9Dc7bE65b32d", - "staticAggregationIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "staticMerkleRootMultisigIsmFactory": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", - "staticMerkleRootWeightedMultisigIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "staticMessageIdMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "staticMessageIdWeightedMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "storageGasOracle": "0x0e91088824Fa6E2675b2a53DA3491a9B098bD868", - "testRecipient": "0x8d4f112cffa338D3c3Ef2Cf443179C5a48E678e4", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0xB589407cf6bEA5CD81AD0946b9F1467933ede74c", - "index": { - "from": 13357661 - } - }, "paradexsepolia": { "chainId": "0x505249564154455f534e5f504f54435f5345504f4c4941", "blocks": { @@ -2173,71 +1341,7 @@ "deployer": { "name": "Abacus Works", "url": "https://www.hyperlane.xyz" - } - }, - "chronicleyellowstone": { - "blockExplorers": [ - { - "apiUrl": "https://yellowstone-explorer.litprotocol.com/api", - "family": "blockscout", - "name": "Yellowstone Explorer", - "url": "https://yellowstone-explorer.litprotocol.com" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 1, - "reorgPeriod": 0 - }, - "chainId": 175188, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Chronicle Yellowstone", - "domainId": 175188, - "index": { - "from": 2339059 - }, - "isTestnet": true, - "name": "chronicleyellowstone", - "nativeToken": { - "decimals": 18, - "name": "Test LPX", - "symbol": "tstLPX" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://yellowstone-rpc.litprotocol.com" - } - ], - "technicalStack": "arbitrumnitro", - "aggregationHook": "0xEB951d31bEc3ddD3391a87f1f216d97993576953", - "domainRoutingIsm": "0x4ac19e0bafc2aF6B98094F0a1B817dF196551219", - "domainRoutingIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "fallbackRoutingHook": "0x39c85C84876479694A2470c0E8075e9d68049aFc", - "interchainGasPaymaster": "0xB589407cf6bEA5CD81AD0946b9F1467933ede74c", - "interchainSecurityModule": "0xBd5Da6e55cA8a39BD64c30320b323f85527f2d86", - "mailbox": "0x589C201a07c26b4725A4A829d772f24423da480B", - "merkleTreeHook": "0x342B5630Ba1C1e4d3048E51Dad208201aF52692c", - "pausableHook": "0x4eC139a771eBdD3b0a0b67bb7E08960210882d44", - "pausableIsm": "0xc08675806BA844467E559E45E4bB59e66778bDcd", - "protocolFee": "0x2bD9aF503B9F608beAD63D4ACC328Abf9796b576", - "proxyAdmin": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "staticAggregationHookFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "staticAggregationIsm": "0x87cfdDeF8a8f3a8E3c095aFe7A171D5Dd1bCaA64", - "staticAggregationIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "staticMerkleRootMultisigIsmFactory": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", - "staticMerkleRootWeightedMultisigIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "staticMessageIdMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "staticMessageIdWeightedMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "storageGasOracle": "0xe036768e48Cb0D42811d2bF0748806FCcBfCd670", - "testRecipient": "0xcCB305B1f21e5FbC85D1DD7Be5cd8d5bf5B7f863", - "validatorAnnounce": "0x8584590ad637C61C7cDF72eFF3381Ee1c3D1bC8E", - "interchainAccountIsm": "0xc0Ce04851bF6Ea149fA06bf7a4808c9db81af189", - "interchainAccountRouter": "0xB261C52241E133f957630AeeFEd48a82963AC33e", - "timelockController": "0x0000000000000000000000000000000000000000" + } }, "subtensortestnet": { "blocks": { @@ -2272,7 +1376,7 @@ "domainRoutingIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", "fallbackRoutingHook": "0x39c85C84876479694A2470c0E8075e9d68049aFc", "interchainGasPaymaster": "0xB589407cf6bEA5CD81AD0946b9F1467933ede74c", - "interchainSecurityModule": "0x5da101D85e19Fb7b3fE91118991808715e08a356", + "interchainSecurityModule": "0x880f3Fa0B3B3B0A30EAd20B790474F05D018539a", "mailbox": "0x589C201a07c26b4725A4A829d772f24423da480B", "merkleTreeHook": "0x342B5630Ba1C1e4d3048E51Dad208201aF52692c", "pausableHook": "0x4eC139a771eBdD3b0a0b67bb7E08960210882d44", @@ -2338,7 +1442,7 @@ "interchainAccountIsm": "0x4da6f7E710137657008D5BCeF26151aac5c9884f", "interchainAccountRouter": "0x919Af376D02751bFCaD9CBAD6bad0c3089dAE33f", "interchainGasPaymaster": "0x8584590ad637C61C7cDF72eFF3381Ee1c3D1bC8E", - "interchainSecurityModule": "0x53dDC314d8A4AB8c42da8FC75c589B4830A8e7E5", + "interchainSecurityModule": "0x0965C57Ea0e576B079e9c4754a8a5f49116d8627", "mailbox": "0x589C201a07c26b4725A4A829d772f24423da480B", "merkleTreeHook": "0xE1CCB130389f687bf745Dd6dc05E50da17d9ea96", "pausableHook": "0x8d4f112cffa338D3c3Ef2Cf443179C5a48E678e4", @@ -2361,70 +1465,6 @@ "from": 2721280 } }, - "weavevmtestnet": { - "blockExplorers": [ - { - "apiUrl": "https://explorer.load.network/api", - "family": "blockscout", - "name": "Load Network Explorer", - "url": "https://explorer.load.network" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 2, - "reorgPeriod": 1 - }, - "chainId": 9496, - "displayName": "Load Network Alphanet", - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "domainId": 9496, - "isTestnet": true, - "name": "weavevmtestnet", - "nativeToken": { - "decimals": 18, - "name": "tLOAD", - "symbol": "tLOAD" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://alphanet.load.network" - } - ], - "technicalStack": "other", - "aggregationHook": "0xCe66bbBC89f4eb5Be35FC9D7D96a4204cb00B291", - "domainRoutingIsm": "0x4ac19e0bafc2aF6B98094F0a1B817dF196551219", - "domainRoutingIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "fallbackRoutingHook": "0xB589407cf6bEA5CD81AD0946b9F1467933ede74c", - "interchainAccountIsm": "0x4da6f7E710137657008D5BCeF26151aac5c9884f", - "interchainAccountRouter": "0x919Af376D02751bFCaD9CBAD6bad0c3089dAE33f", - "interchainGasPaymaster": "0x8584590ad637C61C7cDF72eFF3381Ee1c3D1bC8E", - "interchainSecurityModule": "0xdca680152fCf4D3Fe43d7805ba164c0b1175f581", - "mailbox": "0x589C201a07c26b4725A4A829d772f24423da480B", - "merkleTreeHook": "0xE1CCB130389f687bf745Dd6dc05E50da17d9ea96", - "pausableHook": "0x8d4f112cffa338D3c3Ef2Cf443179C5a48E678e4", - "pausableIsm": "0x80fE4Cb8c70fc60B745d4ffD4403c27a8cBC9e02", - "protocolFee": "0x9D19a337f4d11738b8D25A0E3FB06c342783f2ce", - "proxyAdmin": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "staticAggregationHookFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "staticAggregationIsm": "0x913A5f0AfCCE650923c32C7c1247436fc3a9DAd6", - "staticAggregationIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "staticMerkleRootMultisigIsmFactory": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", - "staticMerkleRootWeightedMultisigIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "staticMessageIdMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "staticMessageIdWeightedMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "storageGasOracle": "0xb3D796584fDeBE2321894eeF31e0C3ec52169C61", - "testRecipient": "0xCCC126d96efcc342BF2781A7d224D3AB1F25B19C", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0xA9999B4abC373FF2BB95B8725FABC96CA883d811", - "index": { - "from": 7876567 - } - }, "carrchaintestnet": { "blockExplorers": [ { @@ -2531,7 +1571,7 @@ "interchainAccountIsm": "0x740bEd6E4eEc7c57a2818177Fba3f9E896D5DE1c", "interchainAccountRouter": "0xD5B70f7Da85F98A5197E55114A38f3eDcDCf020e", "interchainGasPaymaster": "0x919Af376D02751bFCaD9CBAD6bad0c3089dAE33f", - "interchainSecurityModule": "0x4db51e93fEd57D0D623Eaf126702197b0cF812fE", + "interchainSecurityModule": "0xBE8df85F4a40D25D56E000815786c833B67De883", "mailbox": "0x7d498740A4572f2B5c6b0A1Ba9d1d9DbE207e89E", "merkleTreeHook": "0x7d811da36c48cfDc7C445F14252F102bF06E3Fd7", "pausableHook": "0xCCC126d96efcc342BF2781A7d224D3AB1F25B19C", @@ -2653,7 +1693,7 @@ "interchainAccountIsm": "0x890eB21B76DCB165A1807cBE279f883716eA47D4", "interchainAccountRouter": "0xB7697612fbfb4ad02a11dCa16e9711eCB6Da4ceA", "interchainGasPaymaster": "0xA9425D5cBcD2c83EB2a5BF453EAA18968db3ef77", - "interchainSecurityModule": "0xf41f097e9894CB1F280999ad9e06673dDde9e09e", + "interchainSecurityModule": "0x3cd5572a845edBe1411d3027bef0eFeDb957e854", "mailbox": "0x7FE7EA170cf08A25C2ff315814D96D93C311E692", "merkleTreeHook": "0x413c74F3D034dB54A1ecfFbd0Ad74Cb25E59f579", "pausableHook": "0x86abe4c3493A1eE0Aa42f0231b5594D42aBdA36e", @@ -2717,7 +1757,7 @@ "interchainAccountIsm": "0x6C3132f78260EdD1cE88Ea4FeEB8C2D6309ecc75", "interchainAccountRouter": "0x1681cc382e08a72d4b64A123080896e30f96B740", "interchainGasPaymaster": "0xB261C52241E133f957630AeeFEd48a82963AC33e", - "interchainSecurityModule": "0x183eA9fA55982736Ea855367a2b74CC622c6d3F3", + "interchainSecurityModule": "0x86Dab24806e966Fbdbc50C8aC9A8D14989C1d099", "mailbox": "0x589C201a07c26b4725A4A829d772f24423da480B", "merkleTreeHook": "0xf83416bA0491C8BC80Dad259Fc7C007bC57Bd766", "pausableHook": "0x6cB503d97D1c900316583C8D55997A1f17b1ABd1", @@ -2739,71 +1779,6 @@ "from": 27652999 } }, - "plumetestnet2": { - "blockExplorers": [ - { - "apiUrl": "https://testnet-explorer.plume.org/api", - "family": "blockscout", - "name": "Plume Explorer", - "url": "https://testnet-explorer.plume.org" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 2, - "reorgPeriod": 0 - }, - "chainId": 98867, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Plume Testnet", - "domainId": 98867, - "gasCurrencyCoinGeckoId": "plume", - "index": { - "from": 126532 - }, - "isTestnet": true, - "name": "plumetestnet2", - "nativeToken": { - "decimals": 18, - "name": "Plume", - "symbol": "PLUME" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://testnet-rpc.plume.org" - } - ], - "technicalStack": "arbitrumnitro", - "aggregationHook": "0xD12494d89078F5349413eeD97Ce673274764500A", - "domainRoutingIsm": "0x2a2F4AAaf726abb4B969c2804D38e188555683b5", - "domainRoutingIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "fallbackRoutingHook": "0x6cB503d97D1c900316583C8D55997A1f17b1ABd1", - "interchainAccountIsm": "0x9667EfF1556A9D092fdbeC09244CB99b677E9D1E", - "interchainAccountRouter": "0x2A9E9188C7e76f3345e91fD4650aC654A9FE355C", - "interchainGasPaymaster": "0xD5B70f7Da85F98A5197E55114A38f3eDcDCf020e", - "interchainSecurityModule": "0x49Eb22588AdCB2e514E2f3e1F1c82b80d0DDc9e5", - "mailbox": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "merkleTreeHook": "0x0b9A4A46f50f91f353B8Aa0F3Ca80E35E253bDd8", - "pausableHook": "0x9450181a7719dAb93483d43a45473Ac2373E25B0", - "pausableIsm": "0x17866ebE0e503784a9461d3e753dEeD0d3F61153", - "protocolFee": "0xE6C0740b6aB7C060e197a7bd2952A27bB219c62A", - "proxyAdmin": "0x54148470292C24345fb828B003461a9444414517", - "staticAggregationHookFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "staticAggregationIsm": "0x322C3eC90341F6DbD0339b418dc3A09072C5D50b", - "staticAggregationIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "staticMerkleRootMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "staticMerkleRootWeightedMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "staticMessageIdMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "staticMessageIdWeightedMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "storageGasOracle": "0xc0Ce04851bF6Ea149fA06bf7a4808c9db81af189", - "testRecipient": "0x86abe4c3493A1eE0Aa42f0231b5594D42aBdA36e", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0xF8E6c1222049AAb68E410E43242449994Cb64996" - }, "kyvetestnet": { "bech32Prefix": "kyve", "blockExplorers": [ @@ -2914,7 +1889,7 @@ "domainRoutingIsmFactory": "0x783c4a0bB6663359281aD4a637D5af68F83ae213", "fallbackRoutingHook": "0xE3D93F9296FA3dF262E1a54f0de02F71E845af6b", "interchainGasPaymaster": "0xF61322936D80cd87B49df48F3DE24fD5c02dE9D1", - "interchainSecurityModule": "0xFebaD2f0ae5c7BC1d082C095c3Ac7d8D0a876e15", + "interchainSecurityModule": "0xF2b64B2F0980decf1BCcfb6Ef0E14cf31Ff69850", "mailbox": "0x04438ef7622f5412f82915F59caD4f704C61eA48", "merkleTreeHook": "0x6d6a9bDDea1456673062633b7a4823dB13bDB9fb", "pausableHook": "0x48c94311789194215Fe19002C2D33681A76d63dF", @@ -3033,7 +2008,7 @@ "domainRoutingIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", "fallbackRoutingHook": "0x6916690816a1aE1F6D9163dA9e691124737B5f5b", "interchainGasPaymaster": "0xF78deCe5Cf97e1bd61C202A5ba1af33b87454878", - "interchainSecurityModule": "0x1D15208703CA3f592b3dBEF21Ae6CC1FC01415C4", + "interchainSecurityModule": "0x0b617cB3B0D97c8313AE78b142bBccA0142508B2", "mailbox": "0x589C201a07c26b4725A4A829d772f24423da480B", "merkleTreeHook": "0x86abe4c3493A1eE0Aa42f0231b5594D42aBdA36e", "pausableHook": "0x9667EfF1556A9D092fdbeC09244CB99b677E9D1E", @@ -3057,71 +2032,6 @@ "interchainAccountRouter": "0x638A831b4d11Be6a72AcB97d1aE79DA05Ae9B1D3", "timelockController": "0x0000000000000000000000000000000000000000" }, - "bepolia": { - "blockExplorers": [ - { - "apiUrl": "https://api.routescan.io/v2/network/testnet/evm/80069/etherscan/api", - "family": "routescan", - "name": "Bepolia Explorer", - "url": "https://bepolia.beratrail.io" - } - ], - "blocks": { - "confirmations": 1, - "estimateBlockTime": 2, - "reorgPeriod": 5 - }, - "chainId": 80069, - "deployer": { - "name": "Abacus Works", - "url": "https://www.hyperlane.xyz" - }, - "displayName": "Berachain Bepolia", - "displayNameShort": "Bepolia", - "domainId": 80069, - "isTestnet": true, - "name": "bepolia", - "nativeToken": { - "decimals": 18, - "name": "BERA", - "symbol": "BERA" - }, - "protocol": "ethereum", - "rpcUrls": [ - { - "http": "https://bepolia.rpc.berachain.com" - } - ], - "technicalStack": "other", - "aggregationHook": "0x08cB5638d0B4b8D1E313E9317171515Eb84AfEA3", - "domainRoutingIsm": "0x4ac19e0bafc2aF6B98094F0a1B817dF196551219", - "domainRoutingIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "fallbackRoutingHook": "0x6916690816a1aE1F6D9163dA9e691124737B5f5b", - "interchainGasPaymaster": "0xF78deCe5Cf97e1bd61C202A5ba1af33b87454878", - "interchainSecurityModule": "0x265a84f1d4D82db60b5f79370206aB245A44c2Dc", - "mailbox": "0x589C201a07c26b4725A4A829d772f24423da480B", - "merkleTreeHook": "0x86abe4c3493A1eE0Aa42f0231b5594D42aBdA36e", - "pausableHook": "0x9667EfF1556A9D092fdbeC09244CB99b677E9D1E", - "pausableIsm": "0x6AD4DEBA8A147d000C09de6465267a9047d1c217", - "protocolFee": "0x0E18b28D98C2efDb59252c021320F203305b1B66", - "proxyAdmin": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "staticAggregationHookFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "staticAggregationIsm": "0x5978eB2951fEA157408e2BEA93E95AF92DbF5D19", - "staticAggregationIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "staticMerkleRootMultisigIsmFactory": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", - "staticMerkleRootWeightedMultisigIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "staticMessageIdMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "staticMessageIdWeightedMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "storageGasOracle": "0x36502C6e24C51ba4839c4c4A070aeB52E1adB672", - "testRecipient": "0xB6a4129c305056d80fFfea96DdbDCf1F58BC8240", - "validatorAnnounce": "0xe63844Ca06Ce8E6D4097Cb33E9b3d62704122307", - "index": { - "from": 3449778 - }, - "interchainAccountIsm": "0x1fe349d93078B26C8c7350A401e2B60f100952F5", - "interchainAccountRouter": "0xB7697612fbfb4ad02a11dCa16e9711eCB6Da4ceA", - "timelockController": "0x0000000000000000000000000000000000000000" - }, "megaethtestnet": { "blockExplorers": [ { @@ -3162,7 +2072,7 @@ "domainRoutingIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", "fallbackRoutingHook": "0x890eB21B76DCB165A1807cBE279f883716eA47D4", "interchainGasPaymaster": "0x638A831b4d11Be6a72AcB97d1aE79DA05Ae9B1D3", - "interchainSecurityModule": "0x063C5EEC31912D4e92345E98ceEc887847645A3F", + "interchainSecurityModule": "0x5AfCC382E9db8048d902B2130Ec99EF1EEb9475b", "mailbox": "0xF78deCe5Cf97e1bd61C202A5ba1af33b87454878", "merkleTreeHook": "0x0C16e730090cb681460516428c3b2D1BBCB1b647", "pausableHook": "0xBA3f3A84F149842529993bc38A7b4cF8131c17c2", @@ -3298,7 +2208,7 @@ "interchainAccountIsm": "0xcE4D149Db78729E8b52a9D79A36E64C5429dD39a", "interchainAccountRouter": "0x08911d73b443309081d66A878227E20d6DF0f64C", "interchainGasPaymaster": "0xFb55597F07417b08195Ba674f4dd58aeC9B89FBB", - "interchainSecurityModule": "0x0457f873cA532E59A0bDAbDb2F20B6584036812c", + "interchainSecurityModule": "0x3ffDd7045382476e91563c6d955fC6741e090F50", "mailbox": "0x589C201a07c26b4725A4A829d772f24423da480B", "merkleTreeHook": "0x36502C6e24C51ba4839c4c4A070aeB52E1adB672", "pausableHook": "0x2A9E9188C7e76f3345e91fD4650aC654A9FE355C", @@ -3375,6 +2285,176 @@ "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", "merkleTreeHook": "0x726f757465725f706f73745f6469737061746368000000030000000000000000", "validatorAnnounce": "0x68797065726c616e650000000000000000000000000000000000000000000000" + }, + "celosepolia": { + "blockExplorers": [ + { + "apiUrl": "https://celo-sepolia.blockscout.com/api", + "family": "blockscout", + "name": "Celo Sepolia Explorer", + "url": "https://celo-sepolia.blockscout.com" + } + ], + "blocks": { + "confirmations": 3, + "estimateBlockTime": 1, + "reorgPeriod": 1 + }, + "chainId": 11142220, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Celo Sepolia", + "domainId": 11142220, + "isTestnet": true, + "name": "celosepolia", + "nativeToken": { + "decimals": 18, + "name": "CELO", + "symbol": "CELO" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://forno.celo-sepolia.celo-testnet.org" + } + ], + "technicalStack": "opstack", + "aggregationHook": "0x545eF8941960CfD5f46e1172949FC23809D97ADC", + "domainRoutingIsm": "0xD81476357A464D1577b03A5220d966DBf234F079", + "domainRoutingIsmFactory": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75", + "fallbackRoutingHook": "0xaA3022A7e114D4eaF9BBcA552B850346C55E5550", + "interchainGasPaymaster": "0xB2A79c5e63A3e949e4b3982052958E3eEbD3AA83", + "interchainSecurityModule": "0x7806bC19aB3F3aA639477e7531C84ea952058667", + "mailbox": "0xD0680F80F4f947968206806C2598Cbc5b6FE5b03", + "merkleTreeHook": "0x1b0d4c88288258D57998F0bdb30489007A42B834", + "pausableHook": "0xd09D08a19C6609a1B51e1ca6a055861E7e7A4400", + "pausableIsm": "0x4a7ef067A9A7c82321378D66B648453F67f7b065", + "protocolFee": "0x4D40f433F2f89cBa4BB9D994FD18D8302C378D26", + "proxyAdmin": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "staticAggregationHookFactory": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", + "staticAggregationIsm": "0x3EBA6280514CCC2637CedF17Fb672528383ce152", + "staticAggregationIsmFactory": "0xAb9B273366D794B7F80B4378bc8Aaca75C6178E2", + "staticMerkleRootMultisigIsmFactory": "0x6b1bb4ce664Bb4164AEB4d3D2E7DE7450DD8084C", + "staticMerkleRootWeightedMultisigIsmFactory": "0x19Be55D859368e02d7b9C00803Eb677BDC1359Bd", + "staticMessageIdMultisigIsmFactory": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", + "staticMessageIdWeightedMultisigIsmFactory": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", + "storageGasOracle": "0x56c09458cC7863fff1Cc6Bcb6652Dcc3412FcA86", + "testRecipient": "0xdB1a04C9e08cd96c170bFD8C2f1D0F7fAE8aB0f9", + "validatorAnnounce": "0x555B5d0AD2C1f5010e39dA82342A2E85402E4637", + "index": { + "from": 2847992 + } + }, + "incentivtestnet": { + "blockExplorers": [ + { + "apiUrl": "https://explorer-testnet.incentiv.io/api", + "family": "blockscout", + "name": "Incentiv Testnet Explorer", + "url": "https://explorer-testnet.incentiv.io" + } + ], + "blocks": { + "confirmations": 1, + "estimateBlockTime": 10, + "reorgPeriod": 1 + }, + "chainId": 28802, + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Incentiv Testnet v2", + "domainId": 28802, + "isTestnet": true, + "name": "incentivtestnet", + "nativeToken": { + "decimals": 18, + "name": "TCENT", + "symbol": "TCENT" + }, + "protocol": "ethereum", + "rpcUrls": [ + { + "http": "https://rpc2.testnet.incentiv.io" + } + ], + "technicalStack": "other", + "aggregationHook": "0x08E249D6781015f1b158D897Ca39B04DCb1AA82f", + "domainRoutingIsm": "0xfb4555A14C9BAEB0f39B22131515D2267a5681e3", + "domainRoutingIsmFactory": "0x589C201a07c26b4725A4A829d772f24423da480B", + "fallbackRoutingHook": "0xa318ffca299412ce76B7cb980850ceDB15584E7e", + "interchainGasPaymaster": "0xFfA20C4c8e3b2A2C1220134684FEe23EEB8872d0", + "interchainSecurityModule": "0xC156F23ef5Bf6F761815f6C51cFA8De8aE2811bE", + "mailbox": "0xB7697612fbfb4ad02a11dCa16e9711eCB6Da4ceA", + "merkleTreeHook": "0x7740342863717e26E3d5B828639834ADfce8a061", + "pausableHook": "0xE023239c8dfc172FF008D8087E7442d3eBEd9350", + "pausableIsm": "0xe403E16db1f5997bC62Dc611A8d42836364A7f01", + "protocolFee": "0x1D8742741d87d886F72dC0379541Cd4188DFd46E", + "proxyAdmin": "0xD221ffdAa65518BF56c8623e04eA0380CE1BfaE2", + "staticAggregationHookFactory": "0x54148470292C24345fb828B003461a9444414517", + "staticAggregationIsm": "0x29C7d9607AcD639d32B9b8F074F08DBe66279250", + "staticAggregationIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039", + "staticMerkleRootMultisigIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", + "staticMerkleRootWeightedMultisigIsmFactory": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", + "staticMessageIdMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "staticMessageIdWeightedMultisigIsmFactory": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", + "storageGasOracle": "0xf3e31239257b300Daa566131aA6b9e45Ef2A4266", + "testRecipient": "0x48c94311789194215Fe19002C2D33681A76d63dF", + "validatorAnnounce": "0xE3D93F9296FA3dF262E1a54f0de02F71E845af6b", + "index": { + "from": 227612 + } + }, + "radixtestnet": { + "bech32Prefix": "account_tdx_2_", + "blockExplorers": [ + { + "apiUrl": "https://stokenet-dashboard.radixdlt.com", + "family": "radixdashboard", + "name": "Radix Dashboard", + "url": "https://stokenet-dashboard.radixdlt.com" + } + ], + "blocks": { + "confirmations": 0, + "estimateBlockTime": 5, + "reorgPeriod": 0 + }, + "chainId": 2, + "networkName": "stokenet", + "deployer": { + "name": "Abacus Works", + "url": "https://www.hyperlane.xyz" + }, + "displayName": "Radix Stokenet", + "domainId": 1280787160, + "gatewayUrls": [ + { + "http": "https://stokenet.radixdlt.com" + } + ], + "isTestnet": true, + "name": "radixtestnet", + "nativeToken": { + "decimals": 18, + "denom": "resource_tdx_2_1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxtfd2jc", + "name": "Radix", + "symbol": "XRD" + }, + "protocol": "radix", + "rpcUrls": [ + { + "http": "https://stokenet.radixdlt.com" + } + ], + "interchainGasPaymaster": "component_tdx_2_1cpxu7z3y32wdyp48kc7vpwy62t6m4wu03c7capemnclcwn9y6cxvkc", + "interchainSecurityModule": "component_tdx_2_1czd03w0hf3gq75pyv2hqvlhek269njz2wt6x8w3pqej6mykp9fsrqc", + "mailbox": "component_tdx_2_1czr6pvkk72u9ecs8y830d42c3qcshqsjypltdhxa3xyujlzeq5clm8", + "merkleTreeHook": "component_tdx_2_1cp6xk70fyx7e57g2p87tqcsd0lt8ldvajdl63ff4td8fpmhkk8hhfj", + "validatorAnnounce": "component_tdx_2_1cq3wn6y87j7nw8hnqk545246mrg4q4amsmnm03hk4gxky2tsx0jy93" } } } diff --git a/rust/main/ethers-prometheus/src/lib.rs b/rust/main/ethers-prometheus/src/lib.rs index d22c421d7a1..73a4787c43f 100644 --- a/rust/main/ethers-prometheus/src/lib.rs +++ b/rust/main/ethers-prometheus/src/lib.rs @@ -3,6 +3,7 @@ #![forbid(unsafe_code)] #![warn(missing_docs)] #![deny(clippy::unwrap_used, clippy::panic)] +#![deny(clippy::arithmetic_side_effects)] #[allow(clippy::unwrap_used)] mod contracts; diff --git a/rust/main/ethers-prometheus/src/middleware/mod.rs b/rust/main/ethers-prometheus/src/middleware/mod.rs index 5ea70097e79..6283b4bbfe7 100644 --- a/rust/main/ethers-prometheus/src/middleware/mod.rs +++ b/rust/main/ethers-prometheus/src/middleware/mod.rs @@ -255,7 +255,7 @@ impl Middleware for PrometheusMiddleware { let result = self.inner.send_transaction(tx, block).await; if let Some(m) = &self.metrics.transaction_send_duration_seconds { - let duration = (Instant::now() - start).as_secs_f64(); + let duration = (Instant::now().saturating_duration_since(start)).as_secs_f64(); m.with(&hashmap! { "chain" => chain.as_str(), "address_from" => addr_from.as_str(), @@ -329,8 +329,11 @@ impl Middleware for PrometheusMiddleware { m.with(&labels).inc(); } if let Some(m) = &self.metrics.contract_call_duration_seconds { - m.with(&labels) - .inc_by((Instant::now() - start).as_secs_f64()); + m.with(&labels).inc_by( + Instant::now() + .saturating_duration_since(start) + .as_secs_f64(), + ); } } Ok(result?) @@ -352,7 +355,7 @@ impl Middleware for PrometheusMiddleware { }; let to_csv_str = |mut acc: String, i: String| { acc.push(','); - acc += &i; + acc.push_str(&i); acc }; let chain = chain_name(&data.chain); @@ -413,8 +416,11 @@ impl Middleware for PrometheusMiddleware { m.with(&labels).inc(); } if let Some(m) = &self.metrics.logs_query_duration_seconds { - m.with(&labels) - .inc_by((Instant::now() - start).as_secs_f64()); + m.with(&labels).inc_by( + Instant::now() + .saturating_duration_since(start) + .as_secs_f64(), + ); } } diff --git a/rust/main/helm/hyperlane-agent/templates/relayer-external-secret.yaml b/rust/main/helm/hyperlane-agent/templates/relayer-external-secret.yaml index cb2e5b162bf..cf2a8024c19 100644 --- a/rust/main/helm/hyperlane-agent/templates/relayer-external-secret.yaml +++ b/rust/main/helm/hyperlane-agent/templates/relayer-external-secret.yaml @@ -22,7 +22,7 @@ spec: {{- include "agent-common.labels" . | nindent 10 }} data: {{- range .Values.hyperlane.relayerChains }} - {{- if or (eq .signer.type "hexKey") (eq .signer.type "cosmosKey") (eq .signer.type "starkKey") }} + {{- if or (eq .signer.type "hexKey") (eq .signer.type "cosmosKey") (eq .signer.type "starkKey") (eq .signer.type "radixKey") }} HYP_CHAINS_{{ .name | upper }}_SIGNER_KEY: {{ printf "'{{ .%s_signer_key | toString }}'" .name }} {{- include "agent-common.config-env-vars" (dict "config" .signer "format" "config_map" "key_name_prefix" (printf "CHAINS_%s_SIGNER_" (.name | upper))) | nindent 8 }} {{- end }} @@ -30,6 +30,9 @@ spec: HYP_CHAINS_{{ .name | upper }}_SIGNER_LEGACY: {{ printf "'{{ .%s_signer_legacy | toString }}'" .name }} HYP_CHAINS_{{ .name | upper }}_SIGNER_ADDRESS: {{ printf "'{{ .%s_signer_address | toString }}'" .name }} {{- end }} + {{- if eq .signer.type "radixKey" }} + HYP_CHAINS_{{ .name | upper }}_SIGNER_SUFFIX: {{ printf "'{{ .%s_signer_suffix | toString }}'" .name }} + {{- end }} {{- if and .signer (eq .signer.type "aws") $.Values.hyperlane.relayer.aws }} HYP_CHAINS_{{ .name | upper }}_SIGNER_TYPE: aws HYP_CHAINS_{{ .name | upper }}_SIGNER_ID: {{ .signer.id }} @@ -45,10 +48,10 @@ spec: {{- end }} data: {{- range .Values.hyperlane.relayerChains }} - {{- if or (eq .signer.type "hexKey") (eq .signer.type "cosmosKey") (eq .signer.type "starkKey") }} + {{- if or (eq .signer.type "hexKey") (eq .signer.type "cosmosKey") (eq .signer.type "starkKey") (eq .signer.type "radixKey") }} - secretKey: {{ printf "%s_signer_key" .name }} remoteRef: - {{- if and (ne .signer.type "starkKey") $.Values.hyperlane.relayer.usingDefaultSignerKey }} + {{- if and (ne .signer.type "starkKey") (ne .signer.type "radixKey") $.Values.hyperlane.relayer.usingDefaultSignerKey }} key: {{ printf "%s-%s-key-relayer" $.Values.hyperlane.context $.Values.hyperlane.runEnv }} {{- else }} key: {{ printf "%s-%s-key-%s-relayer" $.Values.hyperlane.context $.Values.hyperlane.runEnv .name }} @@ -65,6 +68,12 @@ spec: key: {{ printf "%s-%s-key-%s-relayer" $.Values.hyperlane.context $.Values.hyperlane.runEnv .name }} property: is_legacy {{- end }} + {{- if eq .signer.type "radixKey" }} + - secretKey: {{ printf "%s_signer_suffix" .name }} + remoteRef: + key: {{ printf "%s-%s-key-%s-relayer" $.Values.hyperlane.context $.Values.hyperlane.runEnv .name }} + property: suffix + {{- end }} {{- end }} {{- if .Values.hyperlane.relayer.aws }} - secretKey: aws_access_key_id diff --git a/rust/main/helm/hyperlane-agent/values.yaml b/rust/main/helm/hyperlane-agent/values.yaml index 3462b145059..e870bb1a19b 100644 --- a/rust/main/helm/hyperlane-agent/values.yaml +++ b/rust/main/helm/hyperlane-agent/values.yaml @@ -128,7 +128,7 @@ hyperlane: object_targz: '' relayerChains: - - name: 'alfajores' + - name: 'ethereum' signer: type: # "aws" diff --git a/rust/main/hyperlane-base/Cargo.toml b/rust/main/hyperlane-base/Cargo.toml index ad1fe42637a..5f6f898b084 100644 --- a/rust/main/hyperlane-base/Cargo.toml +++ b/rust/main/hyperlane-base/Cargo.toml @@ -8,6 +8,7 @@ publish.workspace = true version.workspace = true [dependencies] +anyhow = { workspace = true } async-trait.workspace = true axum.workspace = true aws-config.workspace = true @@ -26,11 +27,13 @@ eyre.workspace = true fuels.workspace = true futures.workspace = true futures-util.workspace = true +humantime.workspace = true itertools.workspace = true maplit.workspace = true mockall.workspace = true paste.workspace = true prometheus.workspace = true +rand.workspace = true rocksdb.workspace = true serde.workspace = true serde_json.workspace = true @@ -38,7 +41,7 @@ solana-sdk.workspace = true static_assertions.workspace = true tempfile = { workspace = true, optional = true } thiserror.workspace = true -tokio = { workspace = true, features = ["rt", "macros", "parking_lot"] } +tokio = { workspace = true, features = ["rt", "macros", "parking_lot", "tracing"] } tokio-metrics.workspace = true tracing-error.workspace = true tracing-futures.workspace = true @@ -63,8 +66,17 @@ hyperlane-ethereum = { path = "../chains/hyperlane-ethereum" } hyperlane-fuel = { path = "../chains/hyperlane-fuel" } hyperlane-cosmos = { path = "../chains/hyperlane-cosmos" } hyperlane-starknet = { path = "../chains/hyperlane-starknet" } -hyperlane-cosmos-native = { path = "../chains/hyperlane-cosmos-native" } +dymension-kaspa = { path = "../chains/dymension-kaspa" } hyperlane-sealevel = { path = "../chains/hyperlane-sealevel" } +hyperlane-radix = { path = "../chains/hyperlane-radix" } + +dym_kas_core = { path = "../../../dymension/libs/kaspa/lib/core" , package="core"} +dym_kas_hardcode = { path = "../../../dymension/libs/kaspa/lib/hardcode" , package="hardcode"} +api-rs = { path = "../../../dymension/libs/kaspa/lib/api", package = "openapi" } + +kaspa-consensus-core = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-hashes ={ git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } +kaspa-core = { git = "https://github.com/kaspanet/rusty-kaspa", rev = "9ff5d0f" } # dependency version is determined by etheres rusoto_core = "*" diff --git a/rust/main/hyperlane-base/src/cache/moka/dynamic_expiry.rs b/rust/main/hyperlane-base/src/cache/moka/dynamic_expiry.rs index 0cd73178155..6be513b745e 100644 --- a/rust/main/hyperlane-base/src/cache/moka/dynamic_expiry.rs +++ b/rust/main/hyperlane-base/src/cache/moka/dynamic_expiry.rs @@ -60,13 +60,10 @@ impl Expiration { pub fn as_duration(&self) -> Option { match self.variant { ExpirationType::AfterDuration(duration) => Some(duration), - ExpirationType::AfterTimestamp(timestamp) => { - let target_time = UNIX_EPOCH + Duration::from_secs(timestamp); - target_time - .duration_since(SystemTime::now()) - .ok() - .or(Some(Duration::ZERO)) - } + ExpirationType::AfterTimestamp(timestamp) => UNIX_EPOCH + .checked_add(Duration::from_secs(timestamp)) + .and_then(|t| t.duration_since(SystemTime::now()).ok()) + .or(Some(Duration::ZERO)), ExpirationType::Never => None, ExpirationType::Default => Some(default_expiration()), } diff --git a/rust/main/hyperlane-base/src/contract_sync/cursors/mod.rs b/rust/main/hyperlane-base/src/contract_sync/cursors/mod.rs index a2a209541f6..f0dc0bd1261 100644 --- a/rust/main/hyperlane-base/src/contract_sync/cursors/mod.rs +++ b/rust/main/hyperlane-base/src/contract_sync/cursors/mod.rs @@ -44,6 +44,8 @@ impl Indexable for HyperlaneMessage { HyperlaneDomainProtocol::Cosmos => CursorType::SequenceAware, HyperlaneDomainProtocol::Starknet => CursorType::SequenceAware, HyperlaneDomainProtocol::CosmosNative => CursorType::SequenceAware, + HyperlaneDomainProtocol::Kaspa => CursorType::SequenceAware, + HyperlaneDomainProtocol::Radix => CursorType::SequenceAware, } } @@ -66,6 +68,8 @@ impl Indexable for InterchainGasPayment { HyperlaneDomainProtocol::Cosmos => CursorType::RateLimited, HyperlaneDomainProtocol::Starknet => CursorType::RateLimited, HyperlaneDomainProtocol::CosmosNative => CursorType::RateLimited, + HyperlaneDomainProtocol::Kaspa => CursorType::RateLimited, + HyperlaneDomainProtocol::Radix => CursorType::SequenceAware, } } @@ -83,6 +87,8 @@ impl Indexable for MerkleTreeInsertion { HyperlaneDomainProtocol::Cosmos => CursorType::SequenceAware, HyperlaneDomainProtocol::Starknet => CursorType::SequenceAware, HyperlaneDomainProtocol::CosmosNative => CursorType::SequenceAware, + HyperlaneDomainProtocol::Kaspa => CursorType::SequenceAware, + HyperlaneDomainProtocol::Radix => CursorType::SequenceAware, } } @@ -100,6 +106,8 @@ impl Indexable for Delivery { HyperlaneDomainProtocol::Cosmos => CursorType::RateLimited, HyperlaneDomainProtocol::Starknet => CursorType::RateLimited, HyperlaneDomainProtocol::CosmosNative => CursorType::RateLimited, + HyperlaneDomainProtocol::Kaspa => CursorType::RateLimited, + HyperlaneDomainProtocol::Radix => CursorType::SequenceAware, } } diff --git a/rust/main/hyperlane-base/src/contract_sync/cursors/rate_limited.rs b/rust/main/hyperlane-base/src/contract_sync/cursors/rate_limited.rs index 805439bfa4d..bc92810d485 100644 --- a/rust/main/hyperlane-base/src/contract_sync/cursors/rate_limited.rs +++ b/rust/main/hyperlane-base/src/contract_sync/cursors/rate_limited.rs @@ -45,7 +45,7 @@ impl SyncState { let (from, to) = match self.direction { SyncDirection::Forward => { let from = self.next_block; - let mut to = from + self.chunk_size; + let mut to = from.saturating_add(self.chunk_size); to = u32::min(to, tip); (from, to) } @@ -61,7 +61,7 @@ impl SyncState { fn update_range(&mut self, range: RangeInclusive) { match self.direction { SyncDirection::Forward => { - self.next_block = *range.end() + 1; + self.next_block = range.end().saturating_add(1); } SyncDirection::Backward => { self.next_block = range.start().saturating_sub(1); @@ -146,7 +146,12 @@ impl RateLimitedContractSyncCursor /// Wait based on how close we are to the tip and update the tip, /// i.e. the highest block we may scrape. async fn get_rate_limit(&self) -> Result> { - if self.sync_state.next_block + self.sync_state.chunk_size < self.tip { + if self + .sync_state + .next_block + .saturating_add(self.sync_state.chunk_size) + < self.tip + { // If doing the full chunk wouldn't exceed the already known tip we do not need to rate limit. return Ok(None); } @@ -180,7 +185,10 @@ impl RateLimitedContractSyncCursor fn sync_eta(&mut self) -> Duration { let sync_end = self.sync_end(); - let to = u32::min(sync_end, self.sync_position() + self.sync_step()); + let to = u32::min( + sync_end, + self.sync_position().saturating_add(self.sync_step()), + ); let from = self.sync_position(); if to < sync_end { self.eta_calculator.calculate(from, sync_end) diff --git a/rust/main/hyperlane-base/src/contract_sync/cursors/sequence_aware/backward.rs b/rust/main/hyperlane-base/src/contract_sync/cursors/sequence_aware/backward.rs index cd90e6a44a8..230d651f992 100644 --- a/rust/main/hyperlane-base/src/contract_sync/cursors/sequence_aware/backward.rs +++ b/rust/main/hyperlane-base/src/contract_sync/cursors/sequence_aware/backward.rs @@ -226,6 +226,10 @@ impl BackwardSequenceAware Some(low..=current_indexing_snapshot.sequence) } + /// Get the lowest block height or sequence. + /// If the configured lowest block height/sequence is positive, just use that value. + /// If the configured value is negative, then fetch the current latest sequence + /// and calculate a relative lowest block height or sequence. async fn get_lowest_block_height_or_sequence(&self) -> Option { if self.lowest_block_height_or_sequence >= 0 { return Some(self.lowest_block_height_or_sequence as u32); @@ -246,15 +250,16 @@ impl BackwardSequenceAware Some(lowest_block_height as u32) } } - IndexMode::Sequence => sequence_count.map(|seq_count| { + IndexMode::Sequence => { + let seq_count = sequence_count?; let lowest_sequence_count = (seq_count as i64).saturating_add(self.lowest_block_height_or_sequence); if lowest_sequence_count < 0 { - 0 + Some(0) } else { - lowest_sequence_count as u32 + Some(lowest_sequence_count as u32) } - }), + } } } @@ -337,9 +342,11 @@ impl BackwardSequenceAware ) -> Result<()> { // We require no sequence gaps and to build upon the last snapshot. // A non-inclusive range is used to allow updates without any logs. - let expected_sequences = ((current_indexing_snapshot.sequence + 1) + let expected_sequences = (current_indexing_snapshot + .sequence + .saturating_add(1) .saturating_sub(logs.len() as u32) - ..(current_indexing_snapshot.sequence + 1)) + ..(current_indexing_snapshot.sequence.saturating_add(1))) .collect::>(); if all_log_sequences != &expected_sequences { // If there are any missing sequences, rewind to just before the last indexed snapshot. @@ -519,7 +526,7 @@ impl ContractSyncCursor /// ## logs /// The logs to ingest. If any logs are duplicated or their sequence is higher than the current indexing snapshot, /// they are filtered out. - #[instrument(err, ret, skip(logs), fields(range=?range, logs=?logs.iter().map(|(log, _)| log.sequence).collect::>()))] + #[instrument(err, skip(logs), fields(range=?range, logs=?logs.iter().map(|(log, _)| log.sequence).collect::>()))] #[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue async fn update( &mut self, @@ -1454,57 +1461,155 @@ mod test { #[tracing_test::traced_test] #[tokio::test] - async fn test_negative_block_height() { + async fn test_get_next_range_negative_block_height() { let chunk_size = 50; - let lowest_sequence = -10; + let lowest_block_height_or_sequence = -1000; - let mut cursor = get_test_backward_sequence_aware_sync_cursor( - INDEX_MODE, + let latest_sequence_count = 3000; + let latest_tip = 4000; + + let latest_sequence_querier = Arc::new(MockLatestSequenceQuerier { + latest_sequence_count: Some(latest_sequence_count), + tip: latest_tip, + }); + + let db = Arc::new(MockHyperlaneSequenceAwareIndexerStore { logs: vec![] }); + + let metrics_data = MetricsData { + domain: HyperlaneDomain::new_test_domain("test"), + metrics: Arc::new(mock_cursor_metrics()), + }; + let params = BackwardSequenceAwareSyncCursorParams { chunk_size, - lowest_sequence, - ) - .await; + latest_sequence_querier, + lowest_block_height_or_sequence, + store: db, + current_sequence_count: latest_sequence_count, + start_block: latest_tip, + index_mode: IndexMode::Block, + metrics_data, + }; + let mut cursor = BackwardSequenceAwareSyncCursor::new(params); + + let expected_current_snapshot = TargetSnapshot { + sequence: 2999, + at_block: 4000, + }; + // Skip any already indexed logs and sanity check we start at the correct spot. + cursor.skip_indexed().await.unwrap(); + assert_eq!( + cursor.current_indexing_snapshot, + Some(expected_current_snapshot.clone()), + ); + assert_eq!( + cursor.last_indexed_snapshot, + LastIndexedSnapshot { + sequence: Some(3000), + at_block: 4000, + } + ); // Expect the range to be: // (current - chunk_size, current) // since cursor does not reach the lowest sequence at this round let range = cursor.get_next_range().await.unwrap().unwrap(); - let expected_range = (90)..=99; + let expected_range = expected_current_snapshot + .at_block + .saturating_sub(chunk_size) + ..=expected_current_snapshot.at_block; assert_eq!(range, expected_range); - // Update the with all the missing logs. + let range = 2900u32..=3000u32; cursor .update( range - .map(|i| { - ( - MockSequencedData::new(i).into(), - log_meta_with_block(900 + i as u64), - ) - }) + .clone() + .map(|i| (MockSequencedData::new(i).into(), log_meta_with_block(3000))) .collect(), - expected_range, + range, ) .await .unwrap(); - // Expect the cursor to indicate that synced up to the latest sequence it could + // even though, we only were able to find sequences between 3000 and 2900, we stop + // indexing because we've reached the block relative height of 3000 (4000 - 1000) + let range = cursor.get_next_range().await.unwrap(); + assert_eq!(range, None); + } + + #[tracing_test::traced_test] + #[tokio::test] + async fn test_get_next_range_negative_sequence() { + let chunk_size = 50; + let lowest_block_height_or_sequence = -1000; + + let latest_sequence_count = 3000; + let latest_tip = 4000; + + let latest_sequence_querier = Arc::new(MockLatestSequenceQuerier { + latest_sequence_count: Some(latest_sequence_count), + tip: latest_tip, + }); + + let db = Arc::new(MockHyperlaneSequenceAwareIndexerStore { logs: vec![] }); + + let metrics_data = MetricsData { + domain: HyperlaneDomain::new_test_domain("test"), + metrics: Arc::new(mock_cursor_metrics()), + }; + let params = BackwardSequenceAwareSyncCursorParams { + chunk_size, + latest_sequence_querier, + lowest_block_height_or_sequence, + store: db, + current_sequence_count: latest_sequence_count, + start_block: latest_tip, + index_mode: IndexMode::Sequence, + metrics_data, + }; + let mut cursor = BackwardSequenceAwareSyncCursor::new(params); + + let expected_current_snapshot = TargetSnapshot { + sequence: 2999, + at_block: 4000, + }; + // Skip any already indexed logs and sanity check we start at the correct spot. + cursor.skip_indexed().await.unwrap(); assert_eq!( cursor.current_indexing_snapshot, - Some(TargetSnapshot { - sequence: 89, - at_block: 990 - }) + Some(expected_current_snapshot.clone()), ); assert_eq!( cursor.last_indexed_snapshot, LastIndexedSnapshot { - sequence: Some(90), - at_block: 990, + sequence: Some(3000), + at_block: 4000, } ); - // should be no more ranges because we've indexed everything + // Expect the range to be: + // (current - chunk_size, current) + // since cursor does not reach the lowest sequence at this round + let range = cursor.get_next_range().await.unwrap().unwrap(); + let expected_range = expected_current_snapshot + .sequence + .saturating_sub(chunk_size) + ..=expected_current_snapshot.sequence; + assert_eq!(range, expected_range); + + let range = 2000u32..=2999u32; + cursor + .update( + range + .clone() + .map(|i| (MockSequencedData::new(i).into(), log_meta_with_block(3000))) + .collect(), + range, + ) + .await + .unwrap(); + + // We were able to find all sequences >= 2000, so we should stop indexing now let range = cursor.get_next_range().await.unwrap(); assert_eq!(range, None); } diff --git a/rust/main/hyperlane-base/src/contract_sync/cursors/sequence_aware/forward.rs b/rust/main/hyperlane-base/src/contract_sync/cursors/sequence_aware/forward.rs index 13ae406f52c..ec79f6f1c1c 100644 --- a/rust/main/hyperlane-base/src/contract_sync/cursors/sequence_aware/forward.rs +++ b/rust/main/hyperlane-base/src/contract_sync/cursors/sequence_aware/forward.rs @@ -195,7 +195,9 @@ impl ForwardSequenceAwareS Some( self.current_indexing_snapshot.at_block ..=u32::min( - self.current_indexing_snapshot.at_block + self.chunk_size, + self.current_indexing_snapshot + .at_block + .saturating_add(self.chunk_size), tip, ), ) @@ -209,7 +211,11 @@ impl ForwardSequenceAwareS target_sequence: u32, ) -> RangeInclusive { // Query the sequence range starting from the cursor count. - current_sequence..=u32::min(target_sequence, current_sequence + self.chunk_size) + current_sequence + ..=u32::min( + target_sequence, + current_sequence.saturating_add(self.chunk_size), + ) } /// Reads the DB to check if the current indexing sequence has already been indexed, @@ -275,7 +281,10 @@ impl ForwardSequenceAwareS // We require no sequence gaps and to build upon the last snapshot. // A non-inclusive range is used to allow updates without any logs. let expected_sequences = (self.current_indexing_snapshot.sequence - ..(self.current_indexing_snapshot.sequence + logs.len() as u32)) + ..(self + .current_indexing_snapshot + .sequence + .saturating_add(logs.len() as u32))) .collect::>(); if all_log_sequences != &expected_sequences { // If there are any missing sequences, rewind to just after the last snapshot. @@ -285,7 +294,10 @@ impl ForwardSequenceAwareS // Update the current indexing snapshot forward. self.current_indexing_snapshot = TargetSnapshot { - sequence: self.current_indexing_snapshot.sequence + logs.len() as u32, + sequence: self + .current_indexing_snapshot + .sequence + .saturating_add(logs.len() as u32), at_block: *range.end(), }; diff --git a/rust/main/hyperlane-base/src/contract_sync/cursors/sequence_aware/mod.rs b/rust/main/hyperlane-base/src/contract_sync/cursors/sequence_aware/mod.rs index 05868c6da6f..02f01a5307a 100644 --- a/rust/main/hyperlane-base/src/contract_sync/cursors/sequence_aware/mod.rs +++ b/rust/main/hyperlane-base/src/contract_sync/cursors/sequence_aware/mod.rs @@ -42,7 +42,7 @@ impl LastIndexedSnapshot { fn next_target(&self) -> TargetSnapshot { TargetSnapshot { // If we haven't indexed anything yet, we start at 0, otherwise we increment. - sequence: self.sequence.map(|s| s + 1).unwrap_or(0), + sequence: self.sequence.map(|s| s.saturating_add(1)).unwrap_or(0), at_block: self.at_block, } } diff --git a/rust/main/hyperlane-base/src/contract_sync/eta_calculator.rs b/rust/main/hyperlane-base/src/contract_sync/eta_calculator.rs index 3b0664d89f3..acda7c42643 100644 --- a/rust/main/hyperlane-base/src/contract_sync/eta_calculator.rs +++ b/rust/main/hyperlane-base/src/contract_sync/eta_calculator.rs @@ -1,4 +1,7 @@ -use std::time::{Duration, Instant}; +use std::{ + ops::Div, + time::{Duration, Instant}, +}; use derive_new::new; use tracing::warn; @@ -65,8 +68,8 @@ impl SyncerEtaCalculator { // max out at 1yr if we are behind default_duration } else { - match Duration::try_from_secs_f64((current_tip - current_block) as f64 / effective_rate) - { + let secs = (current_tip.saturating_sub(current_block) as f64).div(effective_rate); + match Duration::try_from_secs_f64(secs) { Ok(eta) => eta, Err(e) => { warn!(error=?e, tip=?current_tip, block=?current_block, rate=?effective_rate, "Failed to calculate the eta"); diff --git a/rust/main/hyperlane-base/src/db/rocks/hyperlane_db.rs b/rust/main/hyperlane-base/src/db/rocks/hyperlane_db.rs index d6d05c900e9..be016e218c1 100644 --- a/rust/main/hyperlane-base/src/db/rocks/hyperlane_db.rs +++ b/rust/main/hyperlane-base/src/db/rocks/hyperlane_db.rs @@ -1,3 +1,5 @@ +use std::ops::Add; + use async_trait::async_trait; use eyre::{bail, Result}; use tracing::{debug, instrument, trace}; @@ -255,7 +257,7 @@ impl HyperlaneRocksDB { Some(payment) => payment, None => InterchainGasPayment::from_gas_payment_key(gas_payment_key), }; - let total = existing_payment + event; + let total = existing_payment.add(event); debug!(?event, new_total_gas_payment=?total, "Storing gas payment"); self.store_interchain_gas_payment_data_by_gas_payment_key(&gas_payment_key, &total.into())?; @@ -269,7 +271,7 @@ impl HyperlaneRocksDB { event: InterchainGasExpenditure, ) -> DbResult<()> { let existing_expenditure = self.retrieve_gas_expenditure_by_message_id(event.message_id)?; - let total = existing_expenditure + event; + let total = existing_expenditure.add(event); debug!(?event, new_total_gas_expenditure=?total, "Storing gas expenditure"); self.store_interchain_gas_expenditure_data_by_message_id( @@ -311,11 +313,11 @@ impl HyperlaneLogStore for HyperlaneRocksDB { /// Store a list of dispatched messages and their associated metadata. #[instrument(skip_all)] async fn store_logs(&self, messages: &[(Indexed, LogMeta)]) -> Result { - let mut stored = 0; + let mut stored: u32 = 0; for (message, meta) in messages { let stored_message = self.store_message(message.inner(), meta.block_number)?; if stored_message { - stored += 1; + stored = stored.saturating_add(1); } } if stored > 0 { @@ -331,10 +333,10 @@ async fn store_and_count_new( log_type: &str, process: impl Fn(&HyperlaneRocksDB, T, &LogMeta) -> DbResult, ) -> Result { - let mut new_logs = 0; + let mut new_logs: u32 = 0; for (log, meta) in logs { if process(store, *log, meta)? { - new_logs += 1; + new_logs = new_logs.saturating_add(1); } } if new_logs > 0 { @@ -366,10 +368,10 @@ impl HyperlaneLogStore for HyperlaneRocksDB { /// Store every tree insertion event #[instrument(skip_all)] async fn store_logs(&self, leaves: &[(Indexed, LogMeta)]) -> Result { - let mut insertions = 0; + let mut insertions: u32 = 0; for (insertion, meta) in leaves { if self.process_tree_insertion(insertion.inner(), meta.block_number)? { - insertions += 1; + insertions = insertions.saturating_add(1); } } Ok(insertions) diff --git a/rust/main/hyperlane-base/src/db/rocks/iterator.rs b/rust/main/hyperlane-base/src/db/rocks/iterator.rs index 8eaa03fd3dd..c59f551a9e9 100644 --- a/rust/main/hyperlane-base/src/db/rocks/iterator.rs +++ b/rust/main/hyperlane-base/src/db/rocks/iterator.rs @@ -11,7 +11,7 @@ pub struct PrefixIterator<'a, V> { _phantom: PhantomData<*const V>, } -impl<'a, V> Iterator for PrefixIterator<'a, V> +impl Iterator for PrefixIterator<'_, V> where V: Encode + Decode, { diff --git a/rust/main/hyperlane-base/src/db/storage_types.rs b/rust/main/hyperlane-base/src/db/storage_types.rs index bde1eb7739a..0518fa5e0ce 100644 --- a/rust/main/hyperlane-base/src/db/storage_types.rs +++ b/rust/main/hyperlane-base/src/db/storage_types.rs @@ -59,7 +59,11 @@ impl Encode for InterchainGasPaymentData { where W: Write, { - Ok(self.payment.write_to(writer)? + self.gas_amount.write_to(writer)?) + let written = self + .payment + .write_to(writer)? + .saturating_add(self.gas_amount.write_to(writer)?); + Ok(written) } } @@ -110,7 +114,11 @@ impl Encode for InterchainGasExpenditureData { where W: Write, { - Ok(self.tokens_used.write_to(writer)? + self.gas_used.write_to(writer)?) + let written = self + .tokens_used + .write_to(writer)? + .saturating_add(self.gas_used.write_to(writer)?); + Ok(written) } } diff --git a/rust/main/hyperlane-base/src/kas_hack/README.md b/rust/main/hyperlane-base/src/kas_hack/README.md new file mode 100644 index 00000000000..ae56032f00c --- /dev/null +++ b/rust/main/hyperlane-base/src/kas_hack/README.md @@ -0,0 +1,14 @@ +## What? + +A temporary workaround to be able to call a Kas provider and logic loop without wiring through all the existing HL interface restrictions + +It needs to launch a task which +1. Polls the escrow address for recent deposits +2. Dedupes them +3. Call relayer F() returning evidence X for validator +4. Call validator G(X) to get bool +5. If OK, then sign the HL message ID using the custom ISM +6. Gather all of these over http, and send direct to hub using cosmos-rs + + +TODO: finish \ No newline at end of file diff --git a/rust/main/hyperlane-base/src/kas_hack/deposit_operation.rs b/rust/main/hyperlane-base/src/kas_hack/deposit_operation.rs new file mode 100644 index 00000000000..d50e23d6078 --- /dev/null +++ b/rust/main/hyperlane-base/src/kas_hack/deposit_operation.rs @@ -0,0 +1,133 @@ +use dymension_kaspa::{conf::RelayerDepositTimings, Deposit}; +use rand::Rng; +use std::cmp::Ordering; +use std::time::{Duration, Instant}; +use tracing::error; + +#[derive(Debug, Clone)] +pub struct DepositOperation { + pub deposit: Deposit, + pub escrow_address: String, + pub retry_count: u32, + pub next_attempt_after: Instant, + pub created_at: Instant, +} + +impl PartialEq for DepositOperation { + fn eq(&self, other: &Self) -> bool { + self.deposit.id == other.deposit.id + } +} + +impl Eq for DepositOperation {} + +impl PartialOrd for DepositOperation { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for DepositOperation { + fn cmp(&self, other: &Self) -> Ordering { + match self + .next_attempt_after + .cmp(&other.next_attempt_after) + .reverse() + { + Ordering::Equal => self.deposit.id.cmp(&other.deposit.id), + other => other, + } + } +} + +impl DepositOperation { + pub fn new(deposit: Deposit, escrow_address: String) -> Self { + let now = Instant::now(); + Self { + deposit, + escrow_address, + retry_count: 0, + next_attempt_after: now, + created_at: now, + } + } + + pub fn is_ready(&self) -> bool { + Instant::now() >= self.next_attempt_after + } + + pub fn mark_failed(&mut self, cfg: &RelayerDepositTimings, custom_delay: Option) { + self.retry_count += 1; + + let delay = match custom_delay { + Some(d) => d, + None => { + let base_delay = if self.retry_count == 1 { + cfg.retry_delay_base + } else { + let base_secs = cfg.retry_delay_base.as_secs_f64(); + let exponential_delay = + base_secs * cfg.retry_delay_exponent.powi((self.retry_count - 1) as i32); + let max_secs = cfg.retry_delay_max.as_secs_f64(); + Duration::from_secs_f64(exponential_delay.min(max_secs)) + }; + + // Add jitter: random multiplier between 0.75 and 1.25 + let mut rng = rand::thread_rng(); + let jitter = rng.gen_range(0.75..=1.25); + let delay_secs = base_delay.as_secs_f64() * jitter; + Duration::from_secs_f64(delay_secs) + } + }; + + self.next_attempt_after = Instant::now() + delay; + error!( + deposit_id = %self.deposit.id, + retry_count = self.retry_count, + retry_after_secs = delay.as_secs_f64(), + "Deposit operation failed" + ); + } +} + +#[derive(Debug)] +pub struct DepositTracker { + seen: std::collections::HashSet, + pending: std::collections::BinaryHeap, +} + +impl DepositTracker { + pub fn new() -> Self { + Self { + seen: std::collections::HashSet::new(), + pending: std::collections::BinaryHeap::new(), + } + } + + pub fn has_seen(&self, deposit: &Deposit) -> bool { + self.seen.contains(&deposit.id) + } + + pub fn track(&mut self, deposit: Deposit, escrow_address: String) -> bool { + if self.seen.insert(deposit.id) { + let op = DepositOperation::new(deposit, escrow_address); + self.pending.push(op); + true + } else { + false + } + } + + pub fn pop_ready(&mut self) -> Option { + if let Some(op) = self.pending.peek() { + if op.is_ready() { + return self.pending.pop(); + } + } + None + } + + pub fn requeue(&mut self, op: DepositOperation) { + self.pending.push(op); + } +} diff --git a/rust/main/hyperlane-base/src/kas_hack/error.rs b/rust/main/hyperlane-base/src/kas_hack/error.rs new file mode 100644 index 00000000000..affdbd424fe --- /dev/null +++ b/rust/main/hyperlane-base/src/kas_hack/error.rs @@ -0,0 +1,52 @@ +use dymension_kaspa::relayer::deposit::KaspaTxError; +use hyperlane_core::ChainCommunicationError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum KaspaDepositError { + #[error("Deposit not final enough: need {needed} confirmations, have {current}")] + NotFinalError { needed: i64, current: i64 }, + + #[error("Processing error: {0}")] + ProcessingError(String), +} + +impl KaspaDepositError { + pub fn is_retryable(&self) -> bool { + true + } + + // Returns suggested retry delay based on missing confirmations. + // Kaspa produces ~10 blocks/sec, so 0.1 sec per confirmation. + pub fn retry_delay_hint(&self) -> Option { + match self { + Self::NotFinalError { needed, current } => { + let missing = needed.saturating_sub(*current); + Some(missing as f64 * 0.1) + } + _ => None, + } + } +} + +impl From for KaspaDepositError { + fn from(err: KaspaTxError) -> Self { + match err { + KaspaTxError::NotFinalError { + confirmations, + required_confirmations, + .. + } => KaspaDepositError::NotFinalError { + needed: required_confirmations, + current: confirmations, + }, + KaspaTxError::ProcessingError(e) => KaspaDepositError::ProcessingError(e.to_string()), + } + } +} + +impl From for ChainCommunicationError { + fn from(err: KaspaDepositError) -> Self { + ChainCommunicationError::CustomError(err.to_string()) + } +} diff --git a/rust/main/hyperlane-base/src/kas_hack/kaspa_db.rs b/rust/main/hyperlane-base/src/kas_hack/kaspa_db.rs new file mode 100644 index 00000000000..90f1d661a74 --- /dev/null +++ b/rust/main/hyperlane-base/src/kas_hack/kaspa_db.rs @@ -0,0 +1,192 @@ +use eyre::Result; +use tracing::debug; + +use hyperlane_core::{Decode, Encode, HyperlaneDomain, HyperlaneMessage, H256}; + +use crate::db::{DbError, TypedDB, DB}; + +const KASPA_WITHDRAWAL_MESSAGE: &str = "kaspa_withdrawal_message_"; +const KASPA_WITHDRAWAL_KASPA_TX: &str = "kaspa_withdrawal_kaspa_tx_"; +const KASPA_DEPOSIT_MESSAGE: &str = "kaspa_deposit_message_"; +const KASPA_DEPOSIT_MESSAGE_ID_BY_TX_HASH: &str = "kaspa_deposit_message_id_by_tx_hash_"; +const KASPA_DEPOSIT_HUB_TX: &str = "kaspa_deposit_hub_tx_"; + +/// Rocks DB result type +pub type DbResult = std::result::Result; + +/// DB handle for storing Kaspa-related data. +#[derive(Debug, Clone)] +pub struct KaspaRocksDB(TypedDB); + +impl std::ops::Deref for KaspaRocksDB { + type Target = TypedDB; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef for KaspaRocksDB { + fn as_ref(&self) -> &TypedDB { + &self.0 + } +} + +impl AsRef for KaspaRocksDB { + fn as_ref(&self) -> &DB { + self.0.as_ref() + } +} + +impl KaspaRocksDB { + /// Instantiated new `KaspaRocksDB` + pub fn new(domain: &HyperlaneDomain, db: DB) -> Self { + Self(TypedDB::new(domain, db)) + } + + /// Store a value by key + pub fn store_value_by_key( + &self, + prefix: impl AsRef<[u8]>, + key: &K, + value: &V, + ) -> DbResult<()> { + self.store_encodable(prefix, key.to_vec(), value) + } + + /// Retrieve a value by key + pub fn retrieve_value_by_key( + &self, + prefix: impl AsRef<[u8]>, + key: &K, + ) -> DbResult> { + self.retrieve_decodable(prefix, key.to_vec()) + } +} + +// Implement the KaspaDb trait from hyperlane-core to allow dymension-kaspa +// to access kaspa_db functionality without creating circular dependencies +impl hyperlane_core::KaspaDb for KaspaRocksDB { + fn store_withdrawal_message(&self, message: HyperlaneMessage) -> Result<()> { + let id = message.id(); + debug!( + message_id = ?id, + nonce = message.nonce, + "Storing Kaspa withdrawal" + ); + self.store_value_by_key(KASPA_WITHDRAWAL_MESSAGE, &id, &message)?; + Ok(()) + } + + fn retrieve_kaspa_withdrawal_by_message_id( + &self, + message_id: &H256, + ) -> Result> { + Ok(self.retrieve_value_by_key(KASPA_WITHDRAWAL_MESSAGE, message_id)?) + } + + fn store_deposit_message(&self, message: HyperlaneMessage, kaspa_tx_id: String) -> Result<()> { + let id = message.id(); + debug!( + message_id = ?id, + kaspa_tx_id = %kaspa_tx_id, + nonce = message.nonce, + "Storing Kaspa deposit" + ); + // Store deposit message by message_id + self.store_value_by_key(KASPA_DEPOSIT_MESSAGE, &id, &message)?; + // Store mapping from tx_hash to message_id for retrieval by tx_hash + self.store_encodable( + KASPA_DEPOSIT_MESSAGE_ID_BY_TX_HASH, + kaspa_tx_id.as_bytes(), + &id, + )?; + Ok(()) + } + + fn retrieve_kaspa_deposit_by_message_id( + &self, + message_id: &H256, + ) -> Result> { + Ok(self.retrieve_value_by_key(KASPA_DEPOSIT_MESSAGE, message_id)?) + } + + fn retrieve_kaspa_deposit_by_tx_hash( + &self, + hub_tx_id: &str, + ) -> Result> { + // First get the message_id from tx_hash (stored as bytes) + let message_id: Option = + self.retrieve_decodable(KASPA_DEPOSIT_MESSAGE_ID_BY_TX_HASH, hub_tx_id.as_bytes())?; + + match message_id { + Some(id) => Ok(self.retrieve_value_by_key(KASPA_DEPOSIT_MESSAGE, &id)?), + None => Ok(None), + } + } + + fn store_deposit_hub_tx(&self, kaspa_tx: &str, hub_tx: &H256) -> Result<()> { + debug!( + kaspa_tx = %kaspa_tx, + hub_tx = %hub_tx, + "Storing deposit Hub transaction ID" + ); + self.store_encodable(KASPA_DEPOSIT_HUB_TX, kaspa_tx.as_bytes(), hub_tx)?; + Ok(()) + } + + fn retrieve_deposit_hub_tx(&self, kaspa_tx_id: &str) -> Result> { + Ok(self.retrieve_decodable(KASPA_DEPOSIT_HUB_TX, kaspa_tx_id.as_bytes())?) + } + + fn store_withdrawal_kaspa_tx(&self, message_id: &H256, kaspa_tx_id: &str) -> Result<()> { + debug!( + message_id = ?message_id, + kaspa_tx = %kaspa_tx_id, + "Storing withdrawal Kaspa transaction ID" + ); + // Parse kaspa_tx as H256 and store + let kaspa_tx_h256: H256 = kaspa_tx_id + .parse() + .map_err(|e| eyre::eyre!("Invalid kaspa_tx format: {}", e))?; + self.store_value_by_key(KASPA_WITHDRAWAL_KASPA_TX, message_id, &kaspa_tx_h256)?; + Ok(()) + } + + fn retrieve_withdrawal_kaspa_tx(&self, message_id: &H256) -> Result> { + let kaspa_tx_h256: Option = + self.retrieve_value_by_key(KASPA_WITHDRAWAL_KASPA_TX, message_id)?; + Ok(kaspa_tx_h256.map(|h| format!("{:x}", h))) + } + + fn update_processed_deposit( + &self, + kaspa_tx_id: &str, + new_message: HyperlaneMessage, + hub_tx: &H256, + ) -> Result<()> { + let new_message_id = new_message.id(); + debug!( + new_message_id = ?new_message_id, + kaspa_tx_id = %kaspa_tx_id, + hub_tx = ?hub_tx, + nonce = new_message.nonce, + "Updating Kaspa deposit with new message and hub_tx" + ); + + // Store new deposit message by new message_id + self.store_value_by_key(KASPA_DEPOSIT_MESSAGE, &new_message_id, &new_message)?; + + // Update mapping from kaspa_tx to new message_id (overwrites old mapping) + self.store_encodable( + KASPA_DEPOSIT_MESSAGE_ID_BY_TX_HASH, + kaspa_tx_id.as_bytes(), + &new_message_id, + )?; + + // Store hub transaction ID + self.store_encodable(KASPA_DEPOSIT_HUB_TX, kaspa_tx_id.as_bytes(), hub_tx)?; + + Ok(()) + } +} diff --git a/rust/main/hyperlane-base/src/kas_hack/logic_loop.rs b/rust/main/hyperlane-base/src/kas_hack/logic_loop.rs new file mode 100644 index 00000000000..d43b1a69b4b --- /dev/null +++ b/rust/main/hyperlane-base/src/kas_hack/logic_loop.rs @@ -0,0 +1,757 @@ +use crate::contract_sync::cursors::Indexable; +use dym_kas_core::finality::is_safe_against_reorg; +use dymension_kaspa::hl_message::add_kaspa_metadata_hl_messsage; +use dymension_kaspa::ops::{confirmation::ConfirmationFXG, deposit::DepositFXG}; +use dymension_kaspa::relayer::deposit::{build_deposit_fxg, check_deposit_finality, KaspaTxError}; +use dymension_kaspa::{Deposit, KaspaProvider}; +use ethers::utils::hex::ToHex; +use eyre::Result; +use hyperlane_core::{ + ChainCommunicationError, ChainResult, Checkpoint, CheckpointWithMessageId, HyperlaneChain, + HyperlaneLogStore, Indexed, LogMeta, Mailbox, MultisigSignedCheckpoint, Signature, + SignedCheckpointWithMessageId, TxOutcome, H256, U256, +}; +use hyperlane_cosmos::native::{h512_to_cosmos_hash, CosmosNativeMailbox}; +use std::{collections::HashSet, fmt::Debug, hash::Hash, sync::Arc, time::Duration}; +use tokio::{ + sync::{mpsc, Mutex}, + task::JoinHandle, + time, +}; +use tokio_metrics::TaskMonitor; +use tracing::{debug, error, info, info_span, Instrument}; + +/// Channel for submitting deposits to be recovered/reprocessed +pub type DepositRecoverySender = mpsc::Sender; +pub type DepositRecoveryReceiver = mpsc::Receiver; + +use super::{ + deposit_operation::{DepositOperation, DepositTracker}, + error::KaspaDepositError, +}; +use dymension_kaspa::conf::RelayerDepositTimings; + +enum DepositRelayResult { + Success { + deposit_id: String, + amount: u64, + hub_tx_hash: H256, + }, + AlreadyDelivered { + deposit_id: String, + }, + Retryable { + deposit_id: String, + amount: u64, + error: eyre::Error, + custom_delay: Option, + }, + NonRetryable { + deposit_id: String, + amount: u64, + error: eyre::Error, + }, +} + +pub struct Foo { + provider: Box, + hub_mailbox: Arc, + metadata_constructor: C, + deposit_tracker: Mutex, + config: RelayerDepositTimings, + recovery_sender: DepositRecoverySender, + recovery_receiver: Mutex, +} + +impl Foo +where + C: Send + Sync + 'static, +{ + pub fn new( + provider: Box, + hub_mailbox: Arc, + metadata_constructor: C, + ) -> Self { + // Get config from provider, or use defaults if not available + let config = provider.must_relayer_stuff().deposit_timings.clone(); + // Channel for deposit recovery requests (buffer of 100 should be plenty) + let (recovery_sender, recovery_receiver) = mpsc::channel(100); + Self { + provider, + hub_mailbox, + metadata_constructor, + deposit_tracker: Mutex::new(DepositTracker::new()), + config, + recovery_sender, + recovery_receiver: Mutex::new(recovery_receiver), + } + } + + /// Get a sender for submitting deposits to be recovered/reprocessed. + /// This can be used by the server to submit old deposits that fell outside the lookback window. + pub fn recovery_sender(&self) -> DepositRecoverySender { + self.recovery_sender.clone() + } + + /// Run deposit and progress indication loops + pub fn run_loops(self, task_monitor: TaskMonitor) -> JoinHandle<()> { + let foo = Arc::new(self); + + { + let foo_clone = foo.clone(); + let name = "dymension_kaspa_deposit_loop"; + tokio::task::Builder::new() + .name(name) + .spawn(TaskMonitor::instrument( + &task_monitor, + async move { + foo_clone.deposit_loop().await; + } + .instrument(info_span!("Kaspa Monitor")), + )) + .expect("Failed to spawn kaspa monitor task"); + } + + { + let foo_clone = foo.clone(); + let name = "dymension_kaspa_progress_indication_loop"; + tokio::task::Builder::new() + .name(name) + .spawn(TaskMonitor::instrument( + &task_monitor, + async move { + foo_clone.progress_indication_loop().await; + } + .instrument(info_span!("Kaspa Monitor")), + )) + .expect("Failed to spawn kaspa progress indication task") + } + } + + // https://github.com/dymensionxyz/hyperlane-monorepo/blob/20b9e669afcfb7728e66b5932e85c0f7fcbd50c1/dymension/libs/kaspa/lib/relayer/note.md#L102-L119 + async fn deposit_loop(&self) { + info!("Dymension, starting deposit loop with queue"); + + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("System time before Unix epoch") + .as_millis() as i64; + + let mut from_time = Some(now - self.config.deposit_look_back.as_millis() as i64); + let mut last_query_time = 0i64; + + loop { + // Process any recovery requests first + self.process_recovery_requests().await; + + self.process_deposit_queue().await; + + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("System time before Unix epoch") + .as_millis() as i64; + + let elapsed = now - last_query_time; + let poll_interval_ms = self.config.poll_interval.as_millis() as i64; + let to_sleep = poll_interval_ms.saturating_sub(elapsed); + + if to_sleep > 0 { + time::sleep(Duration::from_millis(to_sleep as u64)).await; + } + + last_query_time = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("System time before Unix epoch") + .as_millis() as i64; + + match self + .provider + .rest() + .get_deposits( + &self.provider.escrow_address().to_string(), + from_time, + self.provider.domain().id(), + ) + .await + { + Ok(deposits) => { + self.queue_new_deposits(deposits).await; + } + Err(e) => { + error!(error = ?e, "Dymension, query new Kaspa deposits failed"); + } + } + + from_time = + Some(last_query_time - self.config.deposit_query_overlap.as_millis() as i64); + } + } + + /// Process any deposits submitted via the recovery channel + async fn process_recovery_requests(&self) { + let mut receiver = self.recovery_receiver.lock().await; + while let Ok(deposit) = receiver.try_recv() { + info!( + deposit_id = %deposit.id, + "Processing deposit recovery request" + ); + self.queue_new_deposits(vec![deposit]).await; + } + } + + async fn queue_new_deposits(&self, deposits: Vec) { + let escrow_address = self.provider.escrow_address().to_string(); + let mut tracker = self.deposit_tracker.lock().await; + let mut new_count = 0; + + for dep in deposits { + if tracker.track(dep.clone(), escrow_address.clone()) { + info!(deposit = ?dep, "Dymension, new deposit seen"); + new_count += 1; + } + } + + drop(tracker); + + if new_count > 0 { + info!( + deposit_count = new_count, + "Dymension, queried new kaspa deposits" + ); + if let Err(e) = self.provider.update_balance_metrics().await { + error!("Failed to update balance metrics: {:?}", e); + } + } + } + + /// Process the retry queue for failed deposit operations + async fn process_deposit_queue(&self) { + loop { + let op = { + let mut tracker = self.deposit_tracker.lock().await; + tracker.pop_ready() + }; + + match op { + Some(operation) => { + self.try_relay_deposit(operation).await; + } + None => break, + } + } + } + + /// Decode deposit payload into Hyperlane message with Kaspa metadata + fn decode_and_add_kaspa_metadata( + &self, + deposit: &Deposit, + escrow_address: &str, + ) -> Result<(hyperlane_core::HyperlaneMessage, u64, usize), eyre::Error> { + use dymension_kaspa::hl_message::ParsedHL; + + let payload = deposit + .payload + .as_ref() + .ok_or_else(|| eyre::eyre!("Deposit has no payload"))?; + + let parsed_hl = ParsedHL::parse_string(payload)?; + let amt_hl = parsed_hl.token_message.amount(); + + // Find the index of the UTXO that satisfies the transfer amount in HL message + let utxo_index = deposit + .outputs + .iter() + .position(|utxo| { + U256::from(utxo.amount) >= amt_hl + && utxo + .script_public_key_address + .as_ref() + .map(|addr| addr == escrow_address) + .unwrap_or(false) + }) + .ok_or_else(|| { + eyre::eyre!( + "kaspa deposit {} had insufficient sompi amount or no matching escrow output", + deposit.id + ) + })?; + + // Add Kaspa metadata to the Hyperlane message + let hl_message_with_metadata = + add_kaspa_metadata_hl_messsage(parsed_hl, deposit.id, utxo_index)?; + let amount = amt_hl.low_u64(); + + Ok((hl_message_with_metadata, amount, utxo_index)) + } + + async fn try_relay_deposit(&self, mut op: DepositOperation) { + info!(deposit_id = %op.deposit.id, "Processing deposit operation"); + + match self.try_relay_deposit_inner(&op).await { + DepositRelayResult::Success { + deposit_id, + amount, + hub_tx_hash, + } => { + // Metrics are recorded inside try_relay_deposit_inner with timing info + info!( + deposit_id = %deposit_id, + hub_tx_hash = ?hub_tx_hash, + amount = %amount, + "Deposit successfully processed and relayed to hub" + ); + } + DepositRelayResult::AlreadyDelivered { deposit_id } => { + info!( + deposit_id = %deposit_id, + "Deposit already delivered, skipping" + ); + } + DepositRelayResult::Retryable { + deposit_id, + amount, + error, + custom_delay, + } => { + self.provider + .metrics() + .record_deposit_failed(&deposit_id, amount); + op.mark_failed(&self.config, custom_delay); + self.deposit_tracker.lock().await.requeue(op); + error!( + deposit_id = %deposit_id, + error = ?error, + "Deposit processing error (retryable), requeued" + ); + } + DepositRelayResult::NonRetryable { + deposit_id, + amount, + error, + } => { + self.provider + .metrics() + .record_deposit_failed(&deposit_id, amount); + error!( + deposit_id = %deposit_id, + error = ?error, + "Deposit processing error (non-retryable), dropping" + ); + } + } + } + + async fn try_relay_deposit_inner(&self, op: &DepositOperation) -> DepositRelayResult { + let deposit_id = format!("{:?}", op.deposit.id); + + // Step 1: Get the HL message and add kaspa metadata + let (hl_message, amount, utxo_index) = + match self.decode_and_add_kaspa_metadata(&op.deposit, &op.escrow_address) { + Ok(v) => v, + Err(error) => { + return DepositRelayResult::NonRetryable { + deposit_id, + amount: 0, + error, + } + } + }; + + // Step 2: Check if already delivered (before expensive finality check) + match self.hub_mailbox.delivered(hl_message.id()).await { + Ok(true) => { + return DepositRelayResult::AlreadyDelivered { deposit_id }; + } + Err(e) => { + return DepositRelayResult::Retryable { + deposit_id, + amount, + error: eyre::eyre!("Check if deposit is delivered: {}", e), + custom_delay: None, + }; + } + _ => {} + } + + // Step 3: Save to DB + self.provider.store_deposit(&hl_message, &deposit_id); + + // Step 4: Check finality + if let Err(e) = + check_deposit_finality(&op.deposit, &self.provider.rest().client.client).await + { + let kaspa_err = KaspaDepositError::from(e); + let custom_delay = kaspa_err + .retry_delay_hint() + .map(|secs| Duration::from_secs_f64(secs)); + return DepositRelayResult::Retryable { + deposit_id, + amount, + error: eyre::eyre!("{}", kaspa_err), + custom_delay, + }; + } + + // Step 5: Build FXG for validators + let fxg = build_deposit_fxg(hl_message, U256::from(amount), utxo_index, &op.deposit); + info!(fxg = ?fxg, "Built deposit FXG"); + + // Step 6: Get signatures and relay + let outcome = match self.get_deposit_validator_sigs_and_send_to_hub(&fxg).await { + Ok(outcome) => outcome, + Err(e) => { + let kaspa_err = self.chain_error_to_kaspa_error(&e); + return if kaspa_err.is_retryable() { + DepositRelayResult::Retryable { + deposit_id, + amount, + error: eyre::eyre!("Gather sigs and send deposit to hub: {}", e), + custom_delay: None, + } + } else { + DepositRelayResult::NonRetryable { + deposit_id, + amount, + error: eyre::eyre!("Gather sigs and send deposit to hub: {}", e), + } + }; + } + }; + + if !outcome.executed { + let tx_hash = hyperlane_cosmos::native::h512_to_cosmos_hash(outcome.transaction_id) + .encode_hex_upper::(); + return DepositRelayResult::Retryable { + deposit_id, + amount, + error: eyre::eyre!( + "TX was not executed on-chain, tx hash: {}, gas used: {}", + tx_hash, + outcome.gas_used + ), + custom_delay: None, + }; + } + + // Step 7: Save hub tx to DB and record metrics + let hub_tx_hash = hyperlane_cosmos::native::h512_to_h256(outcome.transaction_id); + self.provider + .update_processed_deposit(&deposit_id, fxg.hl_message, &hub_tx_hash); + + // Record deposit metrics with timing from operation creation + self.provider + .metrics() + .record_deposit_processed(&deposit_id, amount, op.created_at); + + DepositRelayResult::Success { + deposit_id, + amount, + hub_tx_hash, + } + } + + async fn progress_indication_loop(&self) { + // Confirmation list structure before IndicateProgress is called on Hub: + // prev: 100, next: 101 + // prev: 100, next: 102 + // prev: 100, next: 103 + // All prev_outpoint are same since Hub last outpoint doesn't change. + // Process only the last confirmation. If Hub outpoint != prev_outpoint, + // Hub moved forward - clear confirmation list and get new ones next iteration. + loop { + let conf = self.provider.get_pending_confirmation().await; + + match conf { + Some(conf) => { + let result = self.confirm_withdrawal_on_hub(conf.clone()).await; + match result { + Ok(_) => { + info!(confirmation = ?conf, "Dymension, confirmed withdrawal on hub"); + self.provider.metrics().update_confirmations_pending(0); + self.provider.consume_pending_confirmation(); + + if let Err(e) = self.update_hub_anchor_point_metric().await { + error!(error = ?e, "Failed to update hub anchor point metric after successful confirmation"); + } + } + Err(KaspaTxError::NotFinalError { + retry_after_secs, .. + }) => { + info!( + retry_after_secs = retry_after_secs, + "Dymension, withdrawal not final yet, sleeping before retry" + ); + self.provider.metrics().update_confirmations_pending(1); + time::sleep(Duration::from_secs_f64(retry_after_secs)).await; + continue; + } + Err(e) => { + error!("Dymension, confirm withdrawal on hub: {:?}", e); + self.provider.metrics().record_confirmation_failed(); + } + } + } + None => { + info!("Dymension, no pending confirmation found."); + } + } + + time::sleep(self.config.poll_interval).await; + } + } + + async fn get_deposit_validator_sigs_and_send_to_hub( + &self, + fxg: &DepositFXG, + ) -> ChainResult { + let mut sigs = self.provider.validators().get_deposit_sigs(fxg).await?; + info!( + "Dymension, got deposit sigs: number of sigs: {:?}", + sigs.len() + ); + + let formatted_sigs = self.format_checkpoint_signatures( + &mut sigs, + self.provider.validators().multisig_threshold_hub_ism() as usize, + )?; + + self.hub_mailbox + .process(&fxg.hl_message, &formatted_sigs, None) + .await + } + + fn chain_error_to_kaspa_error(&self, err: &ChainCommunicationError) -> KaspaDepositError { + KaspaDepositError::ProcessingError(err.to_string()) + } + + async fn _deposits_to_logs(&self, _deposits: Vec) -> Vec<(Indexed, LogMeta)> + where + T: Indexable + Debug + Send + Sync + Clone + Eq + Hash + 'static, + { + unimplemented!() + } + + // Unused - Kaspa bridge bypasses normal DB management for deposits/withdrawals + async fn _dedupe_and_store_logs( + &self, + s: &S, + logs: Vec<(Indexed, LogMeta)>, + ) -> Vec<(Indexed, LogMeta)> + where + T: Indexable + Debug + Send + Sync + Clone + Eq + Hash + 'static, + S: HyperlaneLogStore + Clone + 'static, + { + let deduped = HashSet::<_>::from_iter(logs); + let logs = Vec::from_iter(deduped); + + if let Err(e) = s.store_logs(&logs).await { + debug!(error = ?e, "Error storing logs in db"); + } + + logs + } + + // Check if Hub's committed outpoint is already spent on Kaspa chain. + // If not synced, prepare progress indication and submit to Hub. + pub async fn sync_hub_if_needed(&self) -> Result<()> { + let escrow_str = self.provider.escrow_address().to_string(); + let min_sigs = self.provider.validators().multisig_threshold_hub_ism() as usize; + + // Create signature formatter closure + let format_sigs = |sigs: &mut Vec| -> ChainResult> { + self.format_ad_hoc_signatures(sigs, min_sigs) + }; + + super::sync::ensure_hub_synced( + &self.provider, + &self.hub_mailbox, + &escrow_str, + &escrow_str, + format_sigs, + ) + .await?; + + if let Err(e) = self.update_hub_anchor_point_metric().await { + error!(error = ?e, "Failed to update hub anchor point metric after syncing"); + } + + Ok(()) + } + + async fn update_hub_anchor_point_metric(&self) -> Result<()> { + use hyperlane_cosmos::{native::ModuleQueryClient, CosmosProvider}; + let prov = self.hub_mailbox.provider(); + let cosmos_prov = prov + .as_any() + .downcast_ref::>() + .expect("Hub mailbox provider must be CosmosProvider"); + let resp = cosmos_prov.query().outpoint(None).await?; + + if let Some(op) = resp.outpoint { + let tx_id = kaspa_hashes::Hash::from_bytes( + op.transaction_id + .as_slice() + .try_into() + .map_err(|e| eyre::eyre!("Invalid transaction ID bytes: {:?}", e))?, + ); + let ts = kaspa_core::time::unix_now(); + + self.provider.metrics().update_hub_anchor_point( + &tx_id.to_string(), + op.index as u64, + ts, + ); + + info!( + tx_id = %tx_id, + outpoint_index = op.index, + "Updated hub anchor point metric" + ); + } else { + error!("No anchor point found in hub response"); + } + + Ok(()) + } + + // Needs to satisfy Hub validation: + // - https://github.com/dymensionxyz/dymension/blob/2ddaf251568713d45a6900c0abb8a30158efc9aa/x/kas/keeper/msg_server.go#L42-L48 + // - https://github.com/dymensionxyz/dymension/blob/2ddaf251568713d45a6900c0abb8a30158efc9aa/x/kas/types/d.go#L76-L84 + async fn confirm_withdrawal_on_hub(&self, fxg: ConfirmationFXG) -> Result<(), KaspaTxError> { + // Use the last outpoint (new anchor) from the withdrawal sequence + let anchor_new = fxg.outpoints.last().ok_or_else(|| { + KaspaTxError::ProcessingError(eyre::eyre!("No outpoints in confirmation FXG")) + })?; + + let finality = is_safe_against_reorg( + &self.provider.rest().client.client, + &anchor_new.transaction_id.to_string(), + None, + ) + .await + .map_err(|e| KaspaTxError::ProcessingError(e))?; + + if !finality.is_final() { + return Err(KaspaTxError::NotFinalError { + confirmations: finality.confirmations, + required_confirmations: finality.required_confirmations, + retry_after_secs: (finality.required_confirmations - finality.confirmations) as f64 + * 0.1, + }); + } + + info!( + confirmations = finality.confirmations, + required = finality.required_confirmations, + "Finality check passed for withdrawal confirmation" + ); + + let mut sigs = self + .provider + .validators() + .get_confirmation_sigs(&fxg) + .await + .map_err(|e| { + KaspaTxError::ProcessingError(eyre::eyre!("Failed to get confirmation sigs: {}", e)) + })?; + + info!(sig_count = sigs.len(), "Dymension, got confirmation sigs"); + let formatted = self + .format_ad_hoc_signatures( + &mut sigs, + self.provider.validators().multisig_threshold_hub_ism() as usize, + ) + .map_err(|e| { + KaspaTxError::ProcessingError(eyre::eyre!("Failed to format signatures: {}", e)) + })?; + + info!("Dymension, formatted confirmation sigs: {:?}", formatted); + + let outcome = self + .hub_mailbox + .indicate_progress(&formatted, &fxg.progress_indication) + .await + .map_err(|e| { + KaspaTxError::ProcessingError(eyre::eyre!("Indicate progress failed: {}", e)) + })?; + + let tx_hash = h512_to_cosmos_hash(outcome.transaction_id).encode_hex_upper::(); + + if !outcome.executed { + return Err(KaspaTxError::ProcessingError(eyre::eyre!( + "Indicate progress failed, TX was not executed on-chain, tx hash: {tx_hash}" + ))); + } + + info!( + "Dymension, indicated progress on hub: {:?}, outcome: {:?}, tx hash: {:?}", + fxg.progress_indication, outcome, tx_hash, + ); + + Ok(()) + } + + // for deposits + fn format_checkpoint_signatures( + &self, + sigs: &mut Vec, + min: usize, + ) -> ChainResult> { + if sigs.len() < min { + return Err(ChainCommunicationError::InvalidRequest { + msg: format!( + "insufficient validator signatures: got {}, need {}", + sigs.len(), + min + ), + }); + } + + let ckpt = MultisigSignedCheckpoint::try_from(sigs).map_err(|_| { + ChainCommunicationError::InvalidRequest { + msg: "to convert sigs to checkpoint".to_string(), + } + })?; + let meta = self.metadata_constructor.metadata(&ckpt)?; + Ok(meta.to_vec()) + } + + // for withdrawal confirmations + fn format_ad_hoc_signatures( + &self, + sigs: &mut Vec, + min: usize, + ) -> ChainResult> { + if sigs.len() < min { + return Err(ChainCommunicationError::InvalidRequest { + msg: format!( + "insufficient validator signatures: got {}, need {}", + sigs.len(), + min + ), + }); + } + + // Checkpoint struct not actually used in metadata formatting, only signatures matter. + // Create directly without needing real checkpoint data. + let ckpt = MultisigSignedCheckpoint { + checkpoint: CheckpointWithMessageId { + checkpoint: Checkpoint { + merkle_tree_hook_address: H256::default(), + mailbox_domain: 0, + root: H256::default(), + index: 0, + }, + message_id: H256::default(), + }, + signatures: sigs.clone(), + }; + + let meta = self.metadata_constructor.metadata(&ckpt)?; + Ok(meta.to_vec()) + } +} + +pub trait MetadataConstructor { + fn metadata(&self, ckpt: &MultisigSignedCheckpoint) -> Result>; +} diff --git a/rust/main/hyperlane-base/src/kas_hack/migration.rs b/rust/main/hyperlane-base/src/kas_hack/migration.rs new file mode 100644 index 00000000000..4b7ef86b932 --- /dev/null +++ b/rust/main/hyperlane-base/src/kas_hack/migration.rs @@ -0,0 +1,148 @@ +use dymension_kaspa::relayer::execute_migration; +use dymension_kaspa::KaspaProvider; +use eyre::Result; +use hyperlane_core::{ChainResult, Signature}; +use hyperlane_cosmos::native::CosmosNativeMailbox; +use std::time::Duration; +use tracing::{error, info}; + +use super::ensure_hub_synced; + +const SYNC_DELAY_SECS: u64 = 10; +const RETRY_DELAY_SECS: u64 = 60; + +/// Execute escrow key migration with retry loop and hub sync. +/// +/// Handles two scenarios: +/// 1. Fresh migration: executes TX, waits for confirmation, syncs hub +/// 2. Resumed after prior migration: detects new escrow has funds, skips TX, syncs hub +/// +/// Retries indefinitely until success. +pub async fn run_migration_with_sync( + provider: &KaspaProvider, + hub_mailbox: &CosmosNativeMailbox, + new_escrow_address: &str, + format_signatures: F, +) -> Result> +where + F: Fn(&mut Vec) -> ChainResult>, +{ + let target_addr: dymension_kaspa::KaspaAddress = new_escrow_address + .try_into() + .map_err(|e| eyre::eyre!("Invalid target address '{}': {}", new_escrow_address, e))?; + + let old_escrow = provider.escrow_address().to_string(); + let new_escrow = new_escrow_address.to_string(); + + // Step 1: Attempt migration (may be skipped if already done) + let tx_ids = execute_or_detect_migration(provider, &target_addr, &new_escrow).await; + + // Step 2: Wait for TX confirmation then sync hub + info!( + delay_secs = SYNC_DELAY_SECS, + "Waiting for TX confirmation before hub sync" + ); + tokio::time::sleep(Duration::from_secs(SYNC_DELAY_SECS)).await; + + sync_hub( + provider, + hub_mailbox, + &old_escrow, + &new_escrow, + &format_signatures, + ) + .await; + + Ok(tx_ids) +} + +/// Attempts migration. If old escrow is empty but new escrow has funds, +/// concludes migration already happened and returns success. +/// Retries indefinitely. +async fn execute_or_detect_migration( + provider: &KaspaProvider, + target_addr: &dymension_kaspa::KaspaAddress, + new_escrow: &str, +) -> Vec { + let mut attempt: u64 = 0; + loop { + attempt += 1; + info!(attempt, "Migration attempt"); + + match execute_migration(provider, target_addr).await { + Ok(tx_ids) => { + info!(tx_count = tx_ids.len(), "Migration transactions submitted"); + return tx_ids.into_iter().map(|h| h.to_string()).collect(); + } + Err(e) => { + if new_escrow_has_funds(provider, new_escrow).await { + info!("Migration already completed (new escrow has funds), proceeding to sync"); + return vec![]; + } + + error!(error = ?e, attempt, "Migration failed, will retry"); + tokio::time::sleep(Duration::from_secs(RETRY_DELAY_SECS)).await; + } + } + } +} + +/// Retries hub sync indefinitely until success. +async fn sync_hub( + provider: &KaspaProvider, + hub_mailbox: &CosmosNativeMailbox, + old_escrow: &str, + new_escrow: &str, + format_signatures: &F, +) where + F: Fn(&mut Vec) -> ChainResult>, +{ + let mut attempt: u64 = 0; + loop { + attempt += 1; + match ensure_hub_synced( + provider, + hub_mailbox, + old_escrow, + new_escrow, + format_signatures, + ) + .await + { + Ok(_) => { + info!("Post-migration hub sync completed"); + return; + } + Err(e) => { + error!(error = ?e, attempt, "Post-migration sync failed"); + info!(delay_secs = RETRY_DELAY_SECS, "Waiting before sync retry"); + tokio::time::sleep(Duration::from_secs(RETRY_DELAY_SECS)).await; + } + } + } +} + +/// Check if the new escrow address has any UTXOs (indicating migration already happened). +async fn new_escrow_has_funds(provider: &KaspaProvider, new_escrow: &str) -> bool { + let addr: dymension_kaspa::KaspaAddress = match new_escrow.try_into() { + Ok(a) => a, + Err(_) => return false, + }; + + let result = provider + .wallet() + .rpc_with_reconnect(|api| { + let addr = addr.clone(); + async move { + api.get_utxos_by_addresses(vec![addr]) + .await + .map_err(|e| eyre::eyre!("check new escrow UTXOs: {}", e)) + } + }) + .await; + + match result { + Ok(utxos) => !utxos.is_empty(), + Err(_) => false, + } +} diff --git a/rust/main/hyperlane-base/src/kas_hack/mod.rs b/rust/main/hyperlane-base/src/kas_hack/mod.rs new file mode 100644 index 00000000000..74e1d3ef23a --- /dev/null +++ b/rust/main/hyperlane-base/src/kas_hack/mod.rs @@ -0,0 +1,11 @@ +pub mod deposit_operation; +pub mod error; +pub mod kaspa_db; +pub mod logic_loop; +pub mod migration; +pub mod sync; + +pub use kaspa_db::KaspaRocksDB; +pub use logic_loop::DepositRecoverySender; +pub use migration::run_migration_with_sync; +pub use sync::{ensure_hub_synced, format_ad_hoc_signatures}; diff --git a/rust/main/hyperlane-base/src/kas_hack/sync.rs b/rust/main/hyperlane-base/src/kas_hack/sync.rs new file mode 100644 index 00000000000..2c5ea2c0886 --- /dev/null +++ b/rust/main/hyperlane-base/src/kas_hack/sync.rs @@ -0,0 +1,244 @@ +use dymension_kaspa::ops::confirmation::ConfirmationFXG; +use dymension_kaspa::relayer::confirm::expensive_trace_transactions; +use dymension_kaspa::KaspaProvider; +use eyre::Result; +use hyperlane_core::{ + ChainCommunicationError, ChainResult, Checkpoint, CheckpointWithMessageId, HyperlaneChain, + MultisigSignedCheckpoint, Signature, H256, +}; +use hyperlane_cosmos::native::{h512_to_cosmos_hash, CosmosNativeMailbox, ModuleQueryClient}; +use hyperlane_cosmos::CosmosProvider; +use kaspa_consensus_core::tx::TransactionOutpoint; +use tracing::{error, info}; + +use super::logic_loop::MetadataConstructor; + +/// Ensures the hub anchor is in sync with the Kaspa escrow state. +/// +/// This function checks if the hub's committed anchor outpoint exists in the current +/// escrow UTXOs. If not, it traces the transaction lineage and submits a confirmation +/// to update the hub anchor. +/// +/// # Arguments +/// * `provider` - Kaspa provider for RPC and escrow info +/// * `hub_mailbox` - Hub mailbox for querying anchor and submitting confirmations +/// * `src_escrow` - Source escrow address (where anchor/old UTXOs are) +/// * `dst_escrow` - Destination escrow address (where current UTXOs are) +/// * `format_signatures` - Function to format signatures for hub submission +/// +/// In normal operation, src and dst are the same. For post-migration sync, +/// src is the old escrow and dst is the new escrow. +pub async fn ensure_hub_synced( + provider: &KaspaProvider, + hub_mailbox: &CosmosNativeMailbox, + src_escrow: &str, + dst_escrow: &str, + format_signatures: F, +) -> Result<()> +where + F: Fn(&mut Vec) -> ChainResult>, +{ + // Query hub for current anchor + let provider_box = hub_mailbox.provider(); + let cosmos_prov = provider_box + .as_any() + .downcast_ref::>() + .expect("Hub mailbox provider must be CosmosProvider"); + let resp = cosmos_prov.query().outpoint(None).await?; + let anchor_old = resp + .outpoint + .map(|o| TransactionOutpoint { + transaction_id: kaspa_hashes::Hash::from_bytes( + o.transaction_id.as_slice().try_into().unwrap(), + ), + index: o.index, + }) + .ok_or_else(|| eyre::eyre!("No outpoint found on hub"))?; + + info!( + anchor_tx = %anchor_old.transaction_id, + anchor_index = anchor_old.index, + "Got hub anchor" + ); + + // Fetch UTXOs from destination escrow (current escrow) + let dst_addr: dymension_kaspa::KaspaAddress = dst_escrow + .try_into() + .map_err(|e| eyre::eyre!("Invalid dst escrow address: {}", e))?; + + let utxos = provider + .rpc_with_reconnect(|api| { + let addr = dst_addr.clone(); + async move { + api.get_utxos_by_addresses(vec![addr]) + .await + .map_err(|e| eyre::eyre!("Fetch UTXOs: {}", e)) + } + }) + .await?; + + info!(utxo_count = utxos.len(), "Fetched destination escrow UTXOs"); + + // Check if anchor is in current UTXOs - if yes, already synced + let synced = utxos.iter().any(|utxo| { + utxo.outpoint.transaction_id == anchor_old.transaction_id + && utxo.outpoint.index == anchor_old.index + }); + + if synced { + info!("Hub anchor found in escrow UTXOs, already synced"); + return Ok(()); + } + + info!("Hub anchor not in escrow UTXOs, syncing by tracing and confirming"); + + // Try to trace from each UTXO back to the anchor + let mut found = false; + for utxo in utxos { + let candidate = TransactionOutpoint::from(utxo.outpoint); + + let trace_result = expensive_trace_transactions( + &provider.rest().client.client, + src_escrow, + dst_escrow, + candidate.clone(), + anchor_old, + ) + .await; + + match trace_result { + Ok(fxg) => { + info!("Traced transaction lineage from UTXO to anchor"); + confirm_withdrawal_on_hub(provider, hub_mailbox, fxg, &format_signatures).await?; + found = true; + break; + } + Err(e) => { + error!( + error = ?e, + candidate_tx = %candidate.transaction_id, + "Trace failed for candidate" + ); + continue; + } + } + } + + if !found { + return Err(eyre::eyre!( + "No valid trace found from any UTXO to hub anchor" + )); + } + + info!("Hub synced successfully"); + Ok(()) +} + +/// Submit a confirmation to the hub to update the anchor. +async fn confirm_withdrawal_on_hub( + provider: &KaspaProvider, + hub_mailbox: &CosmosNativeMailbox, + fxg: ConfirmationFXG, + format_signatures: &F, +) -> Result<()> +where + F: Fn(&mut Vec) -> ChainResult>, +{ + use dym_kas_core::finality::is_safe_against_reorg; + + // Check finality of the new anchor + let anchor_new = fxg + .outpoints + .last() + .ok_or_else(|| eyre::eyre!("No outpoints in confirmation FXG"))?; + + let finality = is_safe_against_reorg( + &provider.rest().client.client, + &anchor_new.transaction_id.to_string(), + None, + ) + .await + .map_err(|e| eyre::eyre!("Finality check failed: {}", e))?; + + if !finality.is_final() { + return Err(eyre::eyre!( + "New anchor not final: {}/{} confirmations", + finality.confirmations, + finality.required_confirmations + )); + } + + info!( + confirmations = finality.confirmations, + "Finality check passed" + ); + + // Get confirmation signatures from validators + let mut sigs = provider + .validators() + .get_confirmation_sigs(&fxg) + .await + .map_err(|e| eyre::eyre!("Get confirmation sigs: {}", e))?; + + info!(sig_count = sigs.len(), "Got confirmation signatures"); + + // Format signatures + let formatted = + format_signatures(&mut sigs).map_err(|e| eyre::eyre!("Format signatures: {}", e))?; + + // Submit to hub + let outcome = hub_mailbox + .indicate_progress(&formatted, &fxg.progress_indication) + .await + .map_err(|e| eyre::eyre!("Indicate progress: {}", e))?; + + let tx_hash = h512_to_cosmos_hash(outcome.transaction_id); + + if !outcome.executed { + return Err(eyre::eyre!( + "Confirmation TX not executed, hash: {:?}", + tx_hash + )); + } + + info!(tx_hash = ?tx_hash, "Confirmation submitted to hub"); + Ok(()) +} + +/// Format signatures for hub submission using a metadata constructor. +pub fn format_ad_hoc_signatures( + metadata_constructor: &C, + sigs: &mut Vec, + min: usize, +) -> ChainResult> { + if sigs.len() < min { + return Err(ChainCommunicationError::InvalidRequest { + msg: format!( + "insufficient validator signatures: got {}, need {}", + sigs.len(), + min + ), + }); + } + + // Checkpoint struct not used in metadata formatting, only signatures matter. + let ckpt = MultisigSignedCheckpoint { + checkpoint: CheckpointWithMessageId { + checkpoint: Checkpoint { + merkle_tree_hook_address: H256::default(), + mailbox_domain: 0, + root: H256::default(), + index: 0, + }, + message_id: H256::default(), + }, + signatures: sigs.clone(), + }; + + let meta = metadata_constructor.metadata(&ckpt).map_err(|e| { + ChainCommunicationError::InvalidRequest { + msg: format!("metadata formatting: {}", e), + } + })?; + Ok(meta.to_vec()) +} diff --git a/rust/main/hyperlane-base/src/lib.rs b/rust/main/hyperlane-base/src/lib.rs index d3a07a0d55f..7143b59b891 100644 --- a/rust/main/hyperlane-base/src/lib.rs +++ b/rust/main/hyperlane-base/src/lib.rs @@ -6,6 +6,7 @@ #![cfg_attr(not(test), forbid(unsafe_code))] #![warn(missing_docs)] #![deny(clippy::unwrap_used, clippy::panic)] +#![deny(clippy::arithmetic_side_effects)] pub mod settings; @@ -38,5 +39,7 @@ pub use types::*; #[cfg(feature = "oneline-eyre")] pub mod oneline_eyre; +#[allow(missing_docs)] +pub mod kas_hack; /// code related to testing pub mod tests; diff --git a/rust/main/hyperlane-base/src/metrics/core.rs b/rust/main/hyperlane-base/src/metrics/core.rs index 3bd748b6200..5fc715104c3 100644 --- a/rust/main/hyperlane-base/src/metrics/core.rs +++ b/rust/main/hyperlane-base/src/metrics/core.rs @@ -4,6 +4,7 @@ use std::sync::OnceLock; use std::time; use eyre::Result; +use maplit::hashmap; use prometheus::{ histogram_opts, labels, opts, register_counter_vec_with_registry, register_gauge_vec_with_registry, register_histogram_vec_with_registry, @@ -52,6 +53,7 @@ pub struct CoreMetrics { operations_processed_count: IntCounterVec, messages_processed_count: IntCounterVec, + merkle_root_mismatch: IntGaugeVec, latest_checkpoint: IntGaugeVec, @@ -272,6 +274,16 @@ impl CoreMetrics { registry )?; + let merkle_root_mismatch = register_int_gauge_vec_with_registry!( + opts!( + namespaced!("merkle_root_mismatch"), + "Merkle root mismatch", + const_labels_ref + ), + &["origin"], + registry + )?; + let metadata_build_count = register_int_counter_vec_with_registry!( opts!( namespaced!("metadata_build_count"), @@ -333,6 +345,7 @@ impl CoreMetrics { operations_processed_count, messages_processed_count, + merkle_root_mismatch, latest_checkpoint, @@ -362,6 +375,11 @@ impl CoreMetrics { self.registry.clone() } + /// Get a reference to the registry (for metrics that need to register with the same instance) + pub fn registry_ref(&self) -> &Registry { + &self.registry + } + /// Create the provider metrics attached to this core metrics instance. pub fn provider_metrics(&self) -> MiddlewareMetrics { self.provider_metrics @@ -626,6 +644,24 @@ impl CoreMetrics { self.messages_processed_count.clone() } + /// Indicate when a merkle root mismatch occurs. + /// + /// Labels: + /// - `app_context`: Context + /// - `origin`: Chain the merkle root is for. + pub fn merkle_root_mismatch(&self) -> IntGaugeVec { + self.merkle_root_mismatch.clone() + } + + /// Set merkle root mismatch + pub fn set_merkle_root_mismatch(&self, origin: &HyperlaneDomain) { + self.merkle_root_mismatch + .with(&hashmap! { + "origin" => origin.name(), + }) + .set(1); + } + /// Measure of span durations provided by tracing. /// /// Labels: @@ -739,7 +775,7 @@ impl CoreMetrics { .latest_checkpoint() .with_label_values(&["validator_processed", origin_chain.name()]) .get(); - observed_checkpoint - signed_checkpoint + observed_checkpoint.saturating_sub(signed_checkpoint) } } diff --git a/rust/main/hyperlane-base/src/server/base_server.rs b/rust/main/hyperlane-base/src/server/base_server.rs index b84d93d3be4..c6683555b51 100644 --- a/rust/main/hyperlane-base/src/server/base_server.rs +++ b/rust/main/hyperlane-base/src/server/base_server.rs @@ -22,7 +22,7 @@ impl Server { /// /// routes: /// - metrics - serving OpenMetrics format reports on `/metrics` - /// (this is compatible with Prometheus, which ought to be configured to scrape this endpoint) + /// (this is compatible with Prometheus, which ought to be configured to scrape this endpoint) /// - custom_routes - additional routes to be served by the server as per the specific agent pub fn run_with_custom_router(self: Arc, router: Router) -> JoinHandle<()> { let port = self.listen_port; diff --git a/rust/main/hyperlane-base/src/server/merkle_tree_insertions.rs b/rust/main/hyperlane-base/src/server/merkle_tree_insertions.rs index 65572a59236..04367dc332c 100644 --- a/rust/main/hyperlane-base/src/server/merkle_tree_insertions.rs +++ b/rust/main/hyperlane-base/src/server/merkle_tree_insertions.rs @@ -29,6 +29,8 @@ pub struct ResponseBody { /// Merkle tree insertion #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub struct TreeInsertion { + /// insertion block number + pub insertion_block_number: Option, /// index of the merkle insertion pub leaf_index: u32, /// id of the message @@ -42,9 +44,24 @@ pub async fn fetch_merkle_tree_insertions( leaf_index_start: u32, leaf_index_end: u32, ) -> ServerResult> { - let mut merkle_tree_insertions = - Vec::with_capacity((leaf_index_end + 1).saturating_sub(leaf_index_start) as usize); - for leaf_index in leaf_index_start..(leaf_index_end + 1) { + let capacity = leaf_index_end + .saturating_add(1) + .saturating_sub(leaf_index_start) as usize; + let mut merkle_tree_insertions = Vec::with_capacity(capacity); + for leaf_index in leaf_index_start..=leaf_index_end { + let block_number_res = db + .retrieve_merkle_tree_insertion_block_number_by_leaf_index(&leaf_index) + .map_err(|err| { + let error_msg = "Failed to fetch merkle tree insertion block number"; + tracing::debug!(leaf_index, ?err, "{error_msg}"); + ServerErrorResponse::new( + StatusCode::INTERNAL_SERVER_ERROR, + ServerErrorBody { + message: error_msg.to_string(), + }, + ) + })?; + let retrieve_res = db .retrieve_merkle_tree_insertion_by_leaf_index(&leaf_index) .map_err(|err| { @@ -59,6 +76,7 @@ pub async fn fetch_merkle_tree_insertions( })?; if let Some(insertion) = retrieve_res { let tree_insertion = TreeInsertion { + insertion_block_number: block_number_res, leaf_index: insertion.index(), message_id: format!("{:?}", insertion.message_id()), }; diff --git a/rust/main/hyperlane-base/src/server/messages.rs b/rust/main/hyperlane-base/src/server/messages.rs index 88229e4668f..0dfa237657f 100644 --- a/rust/main/hyperlane-base/src/server/messages.rs +++ b/rust/main/hyperlane-base/src/server/messages.rs @@ -15,8 +15,9 @@ pub async fn fetch_messages( nonce_start: u32, nonce_end: u32, ) -> ServerResult> { - let mut messages = Vec::with_capacity((nonce_end + 1).saturating_sub(nonce_start) as usize); - for nonce in nonce_start..(nonce_end + 1) { + let mut messages = + Vec::with_capacity(nonce_end.saturating_add(1).saturating_sub(nonce_start) as usize); + for nonce in nonce_start..nonce_end.saturating_add(1) { let retrieve_res = db.retrieve_message_by_nonce(nonce).map_err(|err| { let error_msg = "Failed to fetch message"; tracing::debug!(nonce, ?err, "{error_msg}"); diff --git a/rust/main/hyperlane-base/src/settings/aws_credentials.rs b/rust/main/hyperlane-base/src/settings/aws_credentials.rs index 8a35aa942e3..56b737a60b2 100644 --- a/rust/main/hyperlane-base/src/settings/aws_credentials.rs +++ b/rust/main/hyperlane-base/src/settings/aws_credentials.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use rusoto_core::credential::{ AutoRefreshingProvider, AwsCredentials, CredentialsError, EnvironmentProvider, - ProvideAwsCredentials, + InstanceMetadataProvider, ProvideAwsCredentials, }; use rusoto_sts::WebIdentityProvider; @@ -17,9 +17,12 @@ use rusoto_sts::WebIdentityProvider; /// The primary use case is running Hyperlane agents in AWS Kubernetes cluster (EKS) configured /// with [IAM Roles for Service Accounts (IRSA)](https://aws.amazon.com/blogs/containers/diving-into-iam-roles-for-service-accounts/). /// The IRSA approach follows security best practices and allows for key rotation. +/// 3) `InstanceMetadataProvider`: retrieves credentials from EC2 instance metadata service (IMDSv2). +/// This allows EC2 instances with attached IAM instance profiles to authenticate without static credentials. pub(crate) struct AwsChainCredentialsProvider { environment_provider: EnvironmentProvider, web_identity_provider: AutoRefreshingProvider, + instance_metadata_provider: AutoRefreshingProvider, } impl AwsChainCredentialsProvider { @@ -30,9 +33,17 @@ impl AwsChainCredentialsProvider { let auto_refreshing_provider = AutoRefreshingProvider::new(WebIdentityProvider::from_k8s_env()) .expect("Always returns Ok(...)"); + + // Wrap the `InstanceMetadataProvider` to a caching `AutoRefreshingProvider`. + // This enables automatic credential refresh for EC2 instance profiles. + let instance_metadata_auto_refreshing = + AutoRefreshingProvider::new(InstanceMetadataProvider::new()) + .expect("Always returns Ok(...)"); + AwsChainCredentialsProvider { environment_provider: EnvironmentProvider::default(), web_identity_provider: auto_refreshing_provider, + instance_metadata_provider: instance_metadata_auto_refreshing, } } } @@ -43,8 +54,32 @@ impl ProvideAwsCredentials for AwsChainCredentialsProvider { if let Ok(creds) = self.environment_provider.credentials().await { Ok(creds) } else { - // Propagate errors from the 'WebIdentityProvider'. - self.web_identity_provider.credentials().await + match self.web_identity_provider.credentials().await { + Ok(creds) => { + tracing::debug!("Using AWS credentials from web identity provider (K8s IRSA)"); + return Ok(creds); + } + Err(e) => { + tracing::debug!("Web identity provider failed: {:?}", e); + } + } + + // 3. EC2 Instance Metadata (for EC2 instances with IAM instance profiles) + match self.instance_metadata_provider.credentials().await { + Ok(creds) => { + tracing::info!( + "Using AWS credentials from EC2 instance metadata (IAM instance profile)" + ); + Ok(creds) + } + Err(e) => { + tracing::error!( + "All AWS credential providers failed. Instance metadata error: {:?}", + e + ); + Err(e) + } + } } } } diff --git a/rust/main/hyperlane-base/src/settings/base.rs b/rust/main/hyperlane-base/src/settings/base.rs index 97ef9948af5..3c788676b5c 100644 --- a/rust/main/hyperlane-base/src/settings/base.rs +++ b/rust/main/hyperlane-base/src/settings/base.rs @@ -6,9 +6,8 @@ use futures_util::future::join_all; use hyperlane_core::{ HyperlaneDomain, HyperlaneLogStore, HyperlaneProvider, HyperlaneSequenceAwareIndexerStoreReader, HyperlaneWatermarkedLogStore, InterchainGasPaymaster, - Mailbox, MerkleTreeHook, MultisigIsm, SequenceAwareIndexer, ValidatorAnnounce, H256, + MerkleTreeHook, MultisigIsm, SequenceAwareIndexer, ValidatorAnnounce, H256, }; -use hyperlane_operation_verifier::ApplicationOperationVerifier; use crate::{ cursors::{CursorType, Indexable}, @@ -151,9 +150,7 @@ macro_rules! build_chain_conf_fns { pub type SequenceIndexer = Arc>; impl Settings { - build_chain_conf_fns!(build_application_operation_verifier, build_application_operation_verifiers -> dyn ApplicationOperationVerifier); build_chain_conf_fns!(build_interchain_gas_paymaster, build_interchain_gas_paymasters -> dyn InterchainGasPaymaster); - build_chain_conf_fns!(build_mailbox, build_mailboxes -> dyn Mailbox); build_chain_conf_fns!(build_merkle_tree_hook, build_merkle_tree_hooks -> dyn MerkleTreeHook); build_chain_conf_fns!(build_provider, build_providers -> dyn HyperlaneProvider); build_chain_conf_fns!(build_validator_announce, build_validator_announces -> dyn ValidatorAnnounce); diff --git a/rust/main/hyperlane-base/src/settings/chains.rs b/rust/main/hyperlane-base/src/settings/chains.rs index 15d2151fe8b..e71ecd87564 100644 --- a/rust/main/hyperlane-base/src/settings/chains.rs +++ b/rust/main/hyperlane-base/src/settings/chains.rs @@ -4,10 +4,11 @@ use std::time::Duration; use async_trait::async_trait; use ethers::prelude::Selector; +use ethers_prometheus::middleware::{ContractInfo, PrometheusMiddlewareConf}; use eyre::{eyre, Context, Report, Result}; use serde_json::Value; +use tracing::instrument; -use ethers_prometheus::middleware::{ContractInfo, PrometheusMiddlewareConf}; use hyperlane_core::{ config::OpSubmissionConfig, AggregationIsm, CcipReadIsm, ChainResult, ContractLocator, HyperlaneAbi, HyperlaneDomain, HyperlaneDomainProtocol, HyperlaneMessage, HyperlaneProvider, @@ -18,16 +19,16 @@ use hyperlane_core::{ use hyperlane_metric::prometheus_metric::ChainInfo; use hyperlane_operation_verifier::ApplicationOperationVerifier; +use dymension_kaspa::{self as dym_kaspa, KaspaMerkle, KaspaProvider}; use hyperlane_cosmos::{ - self as h_cosmos, delivery_indexer, dispatch_indexer, rpc::CosmosWasmRpcProvider, - CosmosProvider, Signer, + self as h_cosmos, cw::CwQueryClient, native::ModuleQueryClient, CosmosProvider, }; -use hyperlane_cosmos_native::{self as h_cosmos_native, CosmosNativeProvider}; use hyperlane_ethereum::{ self as h_eth, BuildableWithProvider, EthereumInterchainGasPaymasterAbi, EthereumMailboxAbi, EthereumReorgPeriod, EthereumValidatorAnnounceAbi, }; use hyperlane_fuel as h_fuel; +use hyperlane_radix::{self as h_radix, RadixProvider}; use hyperlane_sealevel::{ self as h_sealevel, fallback::SealevelFallbackRpcClient, SealevelProvider, TransactionSubmitter, }; @@ -174,7 +175,11 @@ pub enum ChainConnectionConf { /// Starknet configuration. Starknet(h_starknet::ConnectionConf), /// Cosmos native configuration - CosmosNative(h_cosmos_native::ConnectionConf), + CosmosNative(h_cosmos::ConnectionConf), + /// Kaspa configuration + Kaspa(dym_kaspa::ConnectionConf), + /// Radix configuration + Radix(h_radix::ConnectionConf), } impl ChainConnectionConf { @@ -187,6 +192,8 @@ impl ChainConnectionConf { Self::Cosmos(_) => HyperlaneDomainProtocol::Cosmos, Self::Starknet(_) => HyperlaneDomainProtocol::Starknet, Self::CosmosNative(_) => HyperlaneDomainProtocol::CosmosNative, + Self::Kaspa(_) => HyperlaneDomainProtocol::Kaspa, + Self::Radix(_) => HyperlaneDomainProtocol::Radix, } } @@ -196,6 +203,7 @@ impl ChainConnectionConf { Self::Ethereum(conf) => Some(&conf.op_submission_config), Self::Cosmos(conf) => Some(&conf.op_submission_config), Self::Sealevel(conf) => Some(&conf.op_submission_config), + Self::Kaspa(conf) => Some(&conf.op_submission_config), Self::Starknet(config) => Some(&config.op_submission_config), _ => None, } @@ -207,8 +215,8 @@ impl ChainConnectionConf { pub struct CoreContractAddresses { /// Address of the mailbox contract pub mailbox: H256, - /// Address of the InterchainGasPaymaster contract - pub interchain_gas_paymaster: H256, + /// Addresses of the InterchainGasPaymaster contracts + pub interchain_gas_paymasters: Vec, /// Address of the ValidatorAnnounce contract pub validator_announce: H256, /// Address of the MerkleTreeHook contract @@ -266,6 +274,14 @@ impl ChainConf { h_cosmos::application::CosmosApplicationOperationVerifier::new(), ) as Box), + ChainConnectionConf::Kaspa(_) => Ok(Box::new( + dym_kaspa::application::KaspaApplicationOperationVerifier::new(), + ) + as Box), + ChainConnectionConf::Radix(_) => Ok(Box::new( + h_radix::application::RadixApplicationOperationVerifier::new(), + ) + as Box), }; result.context(ctx) @@ -285,20 +301,14 @@ impl ChainConf { } ChainConnectionConf::Fuel(_) => todo!(), ChainConnectionConf::Sealevel(conf) => { - let provider = build_sealevel_provider( - self, - &locator, - &[ - self.addresses.mailbox, - self.addresses.interchain_gas_paymaster, - ], - conf, - metrics, - ); + let mut contract_addresses = vec![self.addresses.mailbox]; + contract_addresses.extend(&self.addresses.interchain_gas_paymasters); + let provider = + build_sealevel_provider(self, &locator, &contract_addresses, conf, metrics); Ok(Box::new(provider) as Box) } ChainConnectionConf::Cosmos(conf) => { - let provider = build_cosmos_provider(self, conf, metrics, &locator, None)?; + let provider = build_cosmos_wasm_provider(self, conf, metrics, &locator, None)?; Ok(Box::new(provider) as Box) } ChainConnectionConf::Starknet(conf) => { @@ -309,6 +319,14 @@ impl ChainConf { let provider = build_cosmos_native_provider(self, conf, metrics, &locator, None)?; Ok(Box::new(provider) as Box) } + ChainConnectionConf::Kaspa(conf) => { + let provider = build_kaspa_provider(self, conf, metrics, &locator, None).await?; + Ok(Box::new(provider) as Box) + } + ChainConnectionConf::Radix(conf) => { + let provider = build_radix_provider(self, conf, metrics, &locator, None)?; + Ok(Box::new(provider) as Box) + } } .context(ctx) } @@ -350,9 +368,9 @@ impl ChainConf { } ChainConnectionConf::Cosmos(conf) => { let signer = self.cosmos_signer().await.context(ctx)?; - let provider = build_cosmos_provider(self, conf, metrics, &locator, signer)?; + let provider = build_cosmos_wasm_provider(self, conf, metrics, &locator, signer)?; - h_cosmos::CosmosMailbox::new(provider, conf.clone(), locator.clone()) + h_cosmos::cw::CwMailbox::new(provider, conf.clone(), locator.clone()) .map(|m| Box::new(m) as Box) .map_err(Into::into) } @@ -364,12 +382,25 @@ impl ChainConf { .map_err(Into::into) } ChainConnectionConf::CosmosNative(conf) => { - let signer = self.cosmos_native_signer().await.context(ctx)?; + let signer = self.cosmos_signer().await.context(ctx)?; let provider = build_cosmos_native_provider(self, conf, metrics, &locator, signer)?; - h_cosmos_native::CosmosNativeMailbox::new(provider, locator.clone()) + h_cosmos::native::CosmosNativeMailbox::new(provider, locator.clone()) + .map(|m| Box::new(m) as Box) + .map_err(Into::into) + } + ChainConnectionConf::Kaspa(conf) => { + _ = self.kaspa_signer().await.context(ctx)?; + let provider = build_kaspa_provider(self, conf, metrics, &locator, None).await?; // intentionally pass None signer, not needed! + dym_kaspa::KaspaMailbox::new(provider, locator.clone()) .map(|m| Box::new(m) as Box) .map_err(Into::into) } + ChainConnectionConf::Radix(conf) => { + let signer = self.radix_signer().await?; + let provider = build_radix_provider(self, conf, metrics, &locator, signer)?; + let mailbox = h_radix::RadixMailbox::new(provider, &locator, conf)?; + Ok(Box::new(mailbox) as Box) + } } .context(ctx) } @@ -402,8 +433,8 @@ impl ChainConf { } ChainConnectionConf::Cosmos(conf) => { let signer = self.cosmos_signer().await.context(ctx)?; - let provider = build_cosmos_provider(self, conf, metrics, &locator, signer)?; - let hook = h_cosmos::CosmosMerkleTreeHook::new(provider, locator.clone())?; + let provider = build_cosmos_wasm_provider(self, conf, metrics, &locator, signer)?; + let hook = h_cosmos::cw::CwMerkleTreeHook::new(provider, locator.clone())?; Ok(Box::new(hook) as Box) } @@ -414,10 +445,21 @@ impl ChainConf { ChainConnectionConf::CosmosNative(conf) => { let provider = build_cosmos_native_provider(self, conf, metrics, &locator, None)?; let hook = - h_cosmos_native::CosmosNativeMerkleTreeHook::new(provider, locator.clone())?; + h_cosmos::native::CosmosNativeMerkleTreeHook::new(provider, locator.clone())?; + + Ok(Box::new(hook) as Box) + } + ChainConnectionConf::Radix(conf) => { + let provider = build_radix_provider(self, conf, metrics, &locator, None)?; + let hook = h_radix::indexer::RadixMerkleTreeIndexer::new(provider, &locator, conf)?; Ok(Box::new(hook) as Box) } + ChainConnectionConf::Kaspa(conf) => { + let provider = build_kaspa_provider(self, conf, metrics, &locator, None).await?; + let h = KaspaMerkle::new(provider, locator.clone())?; + Ok(Box::new(h) as Box) + } } .context(ctx) } @@ -460,26 +502,13 @@ impl ChainConf { Ok(indexer as Box>) } ChainConnectionConf::Cosmos(conf) => { - let signer = self.cosmos_signer().await.context(ctx)?; - let reorg_period = self.reorg_period.as_blocks().context(ctx)?; - - let provider = - build_cosmos_provider(self, conf, metrics, &locator, signer.clone())?; - let wasm_provider = build_cosmos_wasm_provider( - self, - conf, - &locator, - metrics, - reorg_period, - dispatch_indexer::MESSAGE_DISPATCH_EVENT_TYPE.into(), - )?; + let provider = build_cosmos_wasm_provider(self, conf, metrics, &locator, None)?; let mailbox = - h_cosmos::CosmosMailbox::new(provider, conf.clone(), locator.clone())?; + h_cosmos::cw::CwMailbox::new(provider.clone(), conf.clone(), locator.clone())?; let indexer = Box::new( - h_cosmos::dispatch_indexer::CosmosMailboxDispatchIndexer::new( - wasm_provider, - mailbox, + h_cosmos::cw::dispatch_indexer::CwMailboxDispatchIndexer::new( + provider, mailbox, &locator, )?, ); Ok(indexer as Box>) @@ -494,11 +523,23 @@ impl ChainConf { } ChainConnectionConf::CosmosNative(conf) => { let provider = build_cosmos_native_provider(self, conf, metrics, &locator, None)?; - let indexer = Box::new(h_cosmos_native::CosmosNativeDispatchIndexer::new( + let indexer = Box::new(h_cosmos::native::CosmosNativeDispatchIndexer::new( provider, locator, )?); Ok(indexer as Box>) } + ChainConnectionConf::Kaspa(conf) => { + let provider = build_kaspa_provider(self, conf, metrics, &locator, None).await?; + let indexer = Box::new(dym_kaspa::KaspaDispatch::new(provider, locator)?); + Ok(indexer as Box>) + } + ChainConnectionConf::Radix(conf) => { + let provider = build_radix_provider(self, conf, metrics, &locator, None)?; + let indexer = + h_radix::indexer::RadixDispatchIndexer::new(provider, &locator, conf)?; + + Ok(Box::new(indexer) as Box>) + } } .context(ctx) } @@ -540,18 +581,11 @@ impl ChainConf { Ok(indexer as Box>) } ChainConnectionConf::Cosmos(conf) => { - let reorg_period = self.reorg_period.as_blocks().context(ctx)?; - let wasm_provider = build_cosmos_wasm_provider( - self, - conf, - &locator, - metrics, - reorg_period, - delivery_indexer::MESSAGE_DELIVERY_EVENT_TYPE.into(), - )?; - + let provider = build_cosmos_wasm_provider(self, conf, metrics, &locator, None)?; let indexer = Box::new( - h_cosmos::delivery_indexer::CosmosMailboxDeliveryIndexer::new(wasm_provider)?, + h_cosmos::cw::delivery_indexer::CwMailboxDeliveryIndexer::new( + provider, &locator, + ), ); Ok(indexer as Box>) } @@ -565,11 +599,23 @@ impl ChainConf { } ChainConnectionConf::CosmosNative(conf) => { let provider = build_cosmos_native_provider(self, conf, metrics, &locator, None)?; - let indexer = Box::new(h_cosmos_native::CosmosNativeDeliveryIndexer::new( + let indexer = Box::new(h_cosmos::native::CosmosNativeDeliveryIndexer::new( provider, locator, )?); Ok(indexer as Box>) } + ChainConnectionConf::Kaspa(conf) => { + let provider = build_kaspa_provider(self, conf, metrics, &locator, None).await?; + let indexer = Box::new(dym_kaspa::KaspaDelivery::new(provider, locator)?); + Ok(indexer as Box>) + } + ChainConnectionConf::Radix(conf) => { + let provider = build_radix_provider(self, conf, metrics, &locator, None)?; + let indexer = + h_radix::indexer::RadixDeliveryIndexer::new(provider, &locator, conf)?; + + Ok(Box::new(indexer) as Box>) + } } .context(ctx) } @@ -581,7 +627,13 @@ impl ChainConf { metrics: &CoreMetrics, ) -> Result> { let ctx = "Building IGP"; - let locator = self.locator(self.addresses.interchain_gas_paymaster); + let igp_address = self + .addresses + .interchain_gas_paymasters + .first() + .copied() + .ok_or_else(|| eyre!("No IGP address configured"))?; + let locator = self.locator(igp_address); match &self.connection { ChainConnectionConf::Ethereum(conf) => { @@ -604,9 +656,9 @@ impl ChainConf { } ChainConnectionConf::Cosmos(conf) => { let signer = self.cosmos_signer().await.context(ctx)?; - let provider = build_cosmos_provider(self, conf, metrics, &locator, signer)?; + let provider = build_cosmos_wasm_provider(self, conf, metrics, &locator, signer)?; - let paymaster = Box::new(h_cosmos::CosmosInterchainGasPaymaster::new( + let paymaster = Box::new(h_cosmos::cw::CwInterchainGasPaymaster::new( provider, locator.clone(), )?); @@ -620,11 +672,23 @@ impl ChainConf { } ChainConnectionConf::CosmosNative(conf) => { let provider = build_cosmos_native_provider(self, conf, metrics, &locator, None)?; - let indexer = Box::new(h_cosmos_native::CosmosNativeInterchainGas::new( + let indexer = Box::new(h_cosmos::native::CosmosNativeInterchainGas::new( provider, conf, locator, )?); Ok(indexer as Box) } + ChainConnectionConf::Kaspa(conf) => { + let provider = build_kaspa_provider(self, conf, metrics, &locator, None).await?; + let paymaster = Box::new(dym_kaspa::KaspaGas::new(provider, conf, locator)?); + Ok(paymaster as Box) + } + ChainConnectionConf::Radix(conf) => { + let provider = build_radix_provider(self, conf, metrics, &locator, None)?; + let indexer = Box::new(h_radix::indexer::RadixInterchainGasIndexer::new( + provider, &locator, conf, + )?); + Ok(indexer as Box) + } } .context(ctx) } @@ -636,7 +700,13 @@ impl ChainConf { advanced_log_meta: bool, ) -> Result>> { let ctx = "Building IGP indexer"; - let locator = self.locator(self.addresses.interchain_gas_paymaster); + let igp_address = self + .addresses + .interchain_gas_paymasters + .first() + .copied() + .ok_or_else(|| eyre!("No IGP address configured"))?; + let locator = self.locator(igp_address); match &self.connection { ChainConnectionConf::Ethereum(conf) => { @@ -655,7 +725,8 @@ impl ChainConf { } ChainConnectionConf::Fuel(_) => todo!(), ChainConnectionConf::Sealevel(conf) => { - let provider = Arc::new(build_sealevel_provider(self, &locator, &[], conf, metrics)); + let provider = + Arc::new(build_sealevel_provider(self, &locator, &[], conf, metrics)); let indexer = Box::new( h_sealevel::SealevelInterchainGasPaymasterIndexer::new( provider, @@ -667,19 +738,104 @@ impl ChainConf { Ok(indexer as Box>) } ChainConnectionConf::Cosmos(conf) => { - let reorg_period = self.reorg_period.as_blocks().context(ctx)?; - let wasm_provider = build_cosmos_wasm_provider( - self, + let provider = build_cosmos_wasm_provider(self, conf, metrics, &locator, None)?; + + let indexer = Box::new(h_cosmos::cw::CwInterchainGasPaymasterIndexer::new( + provider, &locator, + )); + Ok(indexer as Box>) + } + ChainConnectionConf::Starknet(_) => { + let indexer = Box::new(h_starknet::StarknetInterchainGasPaymasterIndexer {}); + Ok(indexer as Box>) + } + ChainConnectionConf::CosmosNative(conf) => { + let provider = build_cosmos_native_provider(self, conf, metrics, &locator, None)?; + let indexer = Box::new(h_cosmos::native::CosmosNativeInterchainGas::new( + provider, conf, locator, + )?); + Ok(indexer as Box>) + } + ChainConnectionConf::Radix(conf) => { + let provider = build_radix_provider(self, conf, metrics, &locator, None)?; + let indexer = Box::new(h_radix::indexer::RadixInterchainGasIndexer::new( + provider, &locator, conf, + )?); + Ok(indexer as Box>) + } + ChainConnectionConf::Kaspa(conf) => { + let provider = build_kaspa_provider(self, conf, metrics, &locator, None).await?; + let indexer = Box::new(dym_kaspa::KaspaGas::new(provider, conf, locator)?); + Ok(indexer as Box>) + } + } + .context(ctx) + } + + /// Try to convert the chain settings into multiple gas payment indexers + pub async fn build_interchain_gas_payment_indexers( + &self, + metrics: &CoreMetrics, + advanced_log_meta: bool, + ) -> Result>>> { + let mut indexers = Vec::new(); + for igp_address in &self.addresses.interchain_gas_paymasters { + let indexer = self + .build_interchain_gas_payment_indexer_for_address( + *igp_address, + metrics, + advanced_log_meta, + ) + .await?; + indexers.push(indexer); + } + Ok(indexers) + } + + async fn build_interchain_gas_payment_indexer_for_address( + &self, + igp_address: H256, + metrics: &CoreMetrics, + advanced_log_meta: bool, + ) -> Result>> { + let ctx = "Building IGP indexer"; + let locator = self.locator(igp_address); + + match &self.connection { + ChainConnectionConf::Ethereum(conf) => { + let reorg_period = + EthereumReorgPeriod::try_from(&self.reorg_period).context(ctx)?; + self.build_ethereum( conf, &locator, metrics, - reorg_period, - h_cosmos::CosmosInterchainGasPaymasterIndexer::INTERCHAIN_GAS_PAYMENT_EVENT_TYPE.into(), - )?; + h_eth::InterchainGasPaymasterIndexerBuilder { + mailbox_address: self.addresses.mailbox.into(), + reorg_period, + }, + ) + .await + } + ChainConnectionConf::Fuel(_) => todo!(), + ChainConnectionConf::Sealevel(conf) => { + let provider = + Arc::new(build_sealevel_provider(self, &locator, &[], conf, metrics)); + let indexer = Box::new( + h_sealevel::SealevelInterchainGasPaymasterIndexer::new( + provider, + locator, + advanced_log_meta, + ) + .await?, + ); + Ok(indexer as Box>) + } + ChainConnectionConf::Cosmos(conf) => { + let provider = build_cosmos_wasm_provider(self, conf, metrics, &locator, None)?; - let indexer = Box::new(h_cosmos::CosmosInterchainGasPaymasterIndexer::new( - wasm_provider, - )?); + let indexer = Box::new(h_cosmos::cw::CwInterchainGasPaymasterIndexer::new( + provider, &locator, + )); Ok(indexer as Box>) } ChainConnectionConf::Starknet(_) => { @@ -688,13 +844,23 @@ impl ChainConf { } ChainConnectionConf::CosmosNative(conf) => { let provider = build_cosmos_native_provider(self, conf, metrics, &locator, None)?; - let indexer = Box::new(h_cosmos_native::CosmosNativeInterchainGas::new( - provider, - conf, - locator, + let indexer = Box::new(h_cosmos::native::CosmosNativeInterchainGas::new( + provider, conf, locator, )?); Ok(indexer as Box>) } + ChainConnectionConf::Radix(conf) => { + let provider = build_radix_provider(self, conf, metrics, &locator, None)?; + let indexer = Box::new(h_radix::indexer::RadixInterchainGasIndexer::new( + provider, &locator, conf, + )?); + Ok(indexer as Box>) + } + ChainConnectionConf::Kaspa(conf) => { + let provider = build_kaspa_provider(self, conf, metrics, &locator, None).await?; + let indexer = Box::new(dym_kaspa::KaspaGas::new(provider, conf, locator)?); + Ok(indexer as Box>) + } } .context(ctx) } @@ -741,22 +907,10 @@ impl ChainConf { } ChainConnectionConf::Cosmos(conf) => { let signer = self.cosmos_signer().await.context(ctx)?; - let reorg_period = self.reorg_period.as_blocks().context(ctx)?; - - let provider = build_cosmos_provider(self, conf, metrics, &locator, signer)?; - let wasm_provider = build_cosmos_wasm_provider( - self, - conf, - &locator, - metrics, - reorg_period, - h_cosmos::CosmosMerkleTreeHookIndexer::MERKLE_TREE_INSERTION_EVENT_TYPE.into(), - )?; - let indexer = Box::new(h_cosmos::CosmosMerkleTreeHookIndexer::new( - provider, - wasm_provider, - locator, + let provider = build_cosmos_wasm_provider(self, conf, metrics, &locator, signer)?; + let indexer = Box::new(h_cosmos::cw::CwMerkleTreeHookIndexer::new( + provider, locator, )?); Ok(indexer as Box>) } @@ -770,11 +924,23 @@ impl ChainConf { } ChainConnectionConf::CosmosNative(conf) => { let provider = build_cosmos_native_provider(self, conf, metrics, &locator, None)?; - let indexer = Box::new(h_cosmos_native::CosmosNativeMerkleTreeHook::new( + let indexer = Box::new(h_cosmos::native::CosmosNativeMerkleTreeHook::new( provider, locator, )?); Ok(indexer as Box>) } + ChainConnectionConf::Kaspa(conf) => { + let provider = build_kaspa_provider(self, conf, metrics, &locator, None).await?; + let indexer = Box::new(dym_kaspa::KaspaMerkle::new(provider, locator)?); + Ok(indexer as Box>) + } + ChainConnectionConf::Radix(conf) => { + let provider = build_radix_provider(self, conf, metrics, &locator, None)?; + let indexer = Box::new(h_radix::indexer::RadixMerkleTreeIndexer::new( + provider, &locator, conf, + )?); + Ok(indexer as Box>) + } } .context(ctx) } @@ -802,9 +968,9 @@ impl ChainConf { } ChainConnectionConf::Cosmos(conf) => { let signer = self.cosmos_signer().await.context(ctx)?; - let provider = build_cosmos_provider(self, conf, metrics, &locator, signer)?; + let provider = build_cosmos_wasm_provider(self, conf, metrics, &locator, signer)?; - let va = Box::new(h_cosmos::CosmosValidatorAnnounce::new( + let va = Box::new(h_cosmos::cw::CwValidatorAnnounce::new( provider, locator.clone(), )?); @@ -820,15 +986,27 @@ impl ChainConf { Ok(va as Box) } ChainConnectionConf::CosmosNative(conf) => { - let signer = self.cosmos_native_signer().await.context(ctx)?; + let signer = self.cosmos_signer().await.context(ctx)?; let provider = build_cosmos_native_provider(self, conf, metrics, &locator, signer)?; - let va = Box::new(h_cosmos_native::CosmosNativeValidatorAnnounce::new( + let va = Box::new(h_cosmos::native::CosmosNativeValidatorAnnounce::new( provider, locator.clone(), )?); Ok(va as Box) } + ChainConnectionConf::Kaspa(conf) => { + let provider = build_kaspa_provider(self, conf, metrics, &locator, None).await?; + let va = Box::new(dym_kaspa::KaspaValidatorAnnounce::new(provider, locator)?); + Ok(va as Box) + } + ChainConnectionConf::Radix(conf) => { + let signer = self.radix_signer().await?; + let provider = build_radix_provider(self, conf, metrics, &locator, signer)?; + let validator_announce = + h_radix::RadixValidatorAnnounce::new(provider, &locator, conf)?; + Ok(Box::new(validator_announce) as Box) + } } .context("Building ValidatorAnnounce") } @@ -867,9 +1045,9 @@ impl ChainConf { } ChainConnectionConf::Cosmos(conf) => { let signer = self.cosmos_signer().await.context(ctx)?; - let provider = build_cosmos_provider(self, conf, metrics, &locator, signer)?; + let provider = build_cosmos_wasm_provider(self, conf, metrics, &locator, signer)?; - let ism = Box::new(h_cosmos::CosmosInterchainSecurityModule::new( + let ism = Box::new(h_cosmos::cw::CwInterchainSecurityModule::new( provider, locator, )?); Ok(ism as Box) @@ -882,9 +1060,19 @@ impl ChainConf { } ChainConnectionConf::CosmosNative(conf) => { let provider = build_cosmos_native_provider(self, conf, metrics, &locator, None)?; - let ism = Box::new(h_cosmos_native::CosmosNativeIsm::new(provider, locator)?); + let ism = Box::new(h_cosmos::native::CosmosNativeIsm::new(provider, locator)?); + Ok(ism as Box) + } + ChainConnectionConf::Kaspa(conf) => { + let provider = build_kaspa_provider(self, conf, metrics, &locator, None).await?; + let ism = Box::new(dym_kaspa::KaspaIsm::new(provider, locator)?); Ok(ism as Box) } + ChainConnectionConf::Radix(conf) => { + let provider = build_radix_provider(self, conf, metrics, &locator, None)?; + let ism = h_radix::RadixIsm::new(provider, &locator, conf)?; + Ok(Box::new(ism) as Box) + } } .context(ctx) } @@ -918,9 +1106,9 @@ impl ChainConf { } ChainConnectionConf::Cosmos(conf) => { let signer = self.cosmos_signer().await.context(ctx)?; - let provider = build_cosmos_provider(self, conf, metrics, &locator, signer)?; + let provider = build_cosmos_wasm_provider(self, conf, metrics, &locator, signer)?; - let ism = Box::new(h_cosmos::CosmosMultisigIsm::new(provider, locator.clone())?); + let ism = Box::new(h_cosmos::cw::CwMultisigIsm::new(provider, locator.clone())?); Ok(ism as Box) } ChainConnectionConf::Starknet(conf) => { @@ -929,10 +1117,20 @@ impl ChainConf { } ChainConnectionConf::CosmosNative(conf) => { let provider = build_cosmos_native_provider(self, conf, metrics, &locator, None)?; - let ism: Box = - Box::new(h_cosmos_native::CosmosNativeIsm::new(provider, locator)?); + let ism = Box::new(h_cosmos::native::CosmosNativeIsm::new(provider, locator)?); + Ok(ism as Box) + } + ChainConnectionConf::Kaspa(conf) => { + let provider = build_kaspa_provider(self, conf, metrics, &locator, None).await?; + let ism: Box = + Box::new(dym_kaspa::KaspaIsm::new(provider, locator)?); Ok(ism as Box) } + ChainConnectionConf::Radix(conf) => { + let provider = build_radix_provider(self, conf, metrics, &locator, None)?; + let ism = h_radix::RadixIsm::new(provider, &locator, conf)?; + Ok(Box::new(ism) as Box) + } } .context(ctx) } @@ -960,9 +1158,9 @@ impl ChainConf { } ChainConnectionConf::Cosmos(conf) => { let signer = self.cosmos_signer().await.context(ctx)?; - let provider = build_cosmos_provider(self, conf, metrics, &locator, signer)?; + let provider = build_cosmos_wasm_provider(self, conf, metrics, &locator, signer)?; - let ism = Box::new(h_cosmos::CosmosRoutingIsm::new(provider, locator.clone())?); + let ism = Box::new(h_cosmos::cw::CwRoutingIsm::new(provider, locator.clone())?); Ok(ism as Box) } ChainConnectionConf::Starknet(conf) => { @@ -971,10 +1169,20 @@ impl ChainConf { } ChainConnectionConf::CosmosNative(conf) => { let provider = build_cosmos_native_provider(self, conf, metrics, &locator, None)?; - let ism: Box = - Box::new(h_cosmos_native::CosmosNativeIsm::new(provider, locator)?); + let ism = Box::new(h_cosmos::native::CosmosNativeIsm::new(provider, locator)?); + Ok(ism as Box) + } + ChainConnectionConf::Kaspa(conf) => { + let provider = build_kaspa_provider(self, conf, metrics, &locator, None).await?; + let ism: Box = + Box::new(dym_kaspa::KaspaIsm::new(provider, locator)?); Ok(ism as Box) } + ChainConnectionConf::Radix(conf) => { + let provider = build_radix_provider(self, conf, metrics, &locator, None)?; + let ism = h_radix::RadixIsm::new(provider, &locator, conf)?; + Ok(Box::new(ism) as Box) + } } .context(ctx) } @@ -1002,8 +1210,8 @@ impl ChainConf { } ChainConnectionConf::Cosmos(conf) => { let signer = self.cosmos_signer().await.context(ctx)?; - let provider = build_cosmos_provider(self, conf, metrics, &locator, signer)?; - let ism = Box::new(h_cosmos::CosmosAggregationIsm::new( + let provider = build_cosmos_wasm_provider(self, conf, metrics, &locator, signer)?; + let ism = Box::new(h_cosmos::cw::CwAggregationIsm::new( provider, locator.clone(), )?); @@ -1018,6 +1226,12 @@ impl ChainConf { ChainConnectionConf::CosmosNative(_) => { Err(eyre!("Cosmos Native does not support aggregation ISM yet")).context(ctx) } + ChainConnectionConf::Kaspa(_) => { + Err(eyre!("Kaspa does not support aggregation ISM yet")).context(ctx) + } + ChainConnectionConf::Radix(_) => { + todo!("Radix aggregation ISM not yet implemented") + } } .context(ctx) } @@ -1052,10 +1266,17 @@ impl ChainConf { ChainConnectionConf::CosmosNative(_) => { Err(eyre!("Cosmos Native does not support CCIP read ISM yet")).context(ctx) } + ChainConnectionConf::Kaspa(_) => { + Err(eyre!("Kaspa does not support CCIP read ISM yet")).context(ctx) + } + ChainConnectionConf::Radix(_) => { + Err(eyre!("Radix does not support CCIP read ISM yet")).context(ctx) + } } .context(ctx) } + #[instrument(skip_all, fields(domain=%self.domain.name()))] async fn signer(&self) -> Result> { if let Some(conf) = &self.signer { Ok(Some(conf.build::().await?)) @@ -1075,12 +1296,17 @@ impl ChainConf { ChainConnectionConf::Sealevel(_) => { Box::new(conf.build::().await?) } - ChainConnectionConf::Cosmos(_) => Box::new(conf.build::().await?), + ChainConnectionConf::Cosmos(_) | ChainConnectionConf::CosmosNative(_) => { + Box::new(conf.build::().await?) + } ChainConnectionConf::Starknet(_) => { Box::new(conf.build::().await?) } - ChainConnectionConf::CosmosNative(_) => { - Box::new(conf.build::().await?) + ChainConnectionConf::Radix(_) => { + Box::new(conf.build::().await?) + } + ChainConnectionConf::Kaspa(_) => { + Box::new(conf.build::().await?) } }; Ok(Some(chain_signer)) @@ -1111,10 +1337,15 @@ impl ChainConf { self.signer().await } - async fn cosmos_native_signer(&self) -> Result> { + async fn radix_signer(&self) -> Result> { self.signer().await } + async fn kaspa_signer(&self) -> Result> { + // We implemented our own ad hoc signing for Kaspa TXs, which are parsed and loaded from their own new config fields + Result::Ok(None) + } + /// Try to build an agent metrics configuration from the chain config pub async fn agent_metrics_conf(&self, agent_name: String) -> Result { let chain_signer_address = self.chain_signer().await?.map(|s| s.address_string()); @@ -1158,11 +1389,18 @@ impl ChainConf { self.addresses.validator_announce, EthereumValidatorAnnounceAbi::fn_map_owned(), ); - register_contract( - "igp", - self.addresses.interchain_gas_paymaster, - EthereumInterchainGasPaymasterAbi::fn_map_owned(), - ); + for (i, igp_address) in self.addresses.interchain_gas_paymasters.iter().enumerate() { + let name = if i == 0 { + "igp".to_string() + } else { + format!("igp_{}", i) + }; + register_contract( + &name, + *igp_address, + EthereumInterchainGasPaymasterAbi::fn_map_owned(), + ); + } register_contract( "merkle_tree_hook", self.addresses.merkle_tree_hook, @@ -1242,61 +1480,69 @@ fn build_sealevel_tx_submitter( ) } -fn build_cosmos_provider( +fn build_cosmos_wasm_provider( chain_conf: &ChainConf, connection_conf: &h_cosmos::ConnectionConf, metrics: &CoreMetrics, locator: &ContractLocator, - signer: Option, -) -> ChainResult { + signer: Option, +) -> ChainResult> { let middleware_metrics = chain_conf.metrics_conf(); - let client_metrics = metrics.client_metrics(); - + let metrics = metrics.client_metrics(); CosmosProvider::new( - locator.domain.clone(), - connection_conf.clone(), + connection_conf, locator, signer, - client_metrics, + metrics, middleware_metrics.chain.clone(), ) } -fn build_cosmos_wasm_provider( +fn build_cosmos_native_provider( chain_conf: &ChainConf, connection_conf: &h_cosmos::ConnectionConf, - locator: &ContractLocator, metrics: &CoreMetrics, - reorg_period: u32, - event_type: String, -) -> ChainResult { + locator: &ContractLocator, + signer: Option, +) -> ChainResult> { let middleware_metrics = chain_conf.metrics_conf(); - let client_metrics = metrics.client_metrics(); - - CosmosWasmRpcProvider::new( + let metrics = metrics.client_metrics(); + CosmosProvider::new( connection_conf, locator, - event_type, - reorg_period, - client_metrics, + signer, + metrics, middleware_metrics.chain.clone(), ) } -fn build_cosmos_native_provider( +/// doc ... +pub async fn build_kaspa_provider<'a>( chain_conf: &ChainConf, - connection_conf: &h_cosmos_native::ConnectionConf, + connection_conf: &dym_kaspa::ConnectionConf, metrics: &CoreMetrics, - locator: &ContractLocator, - signer: Option, -) -> ChainResult { + locator: &ContractLocator<'a>, + signer: Option, +) -> ChainResult { let middleware_metrics = chain_conf.metrics_conf(); - let metrics = metrics.client_metrics(); - CosmosNativeProvider::new( + let client_metrics = metrics.client_metrics(); + KaspaProvider::new( connection_conf, - locator, + locator.domain.clone(), signer, - metrics, + client_metrics, middleware_metrics.chain.clone(), + Some(metrics.registry_ref()), ) + .await +} + +fn build_radix_provider( + chain_conf: &ChainConf, + connection_conf: &h_radix::ConnectionConf, + _metrics: &CoreMetrics, + locator: &ContractLocator, + signer: Option, +) -> ChainResult { + RadixProvider::new(signer, connection_conf, locator, &chain_conf.reorg_period) } diff --git a/rust/main/hyperlane-base/src/settings/loader/arguments.rs b/rust/main/hyperlane-base/src/settings/loader/arguments.rs index 0f72d68eb40..b392b48cf3f 100644 --- a/rust/main/hyperlane-base/src/settings/loader/arguments.rs +++ b/rust/main/hyperlane-base/src/settings/loader/arguments.rs @@ -78,6 +78,7 @@ impl Source for CommandLineArguments { } let key = key.split(separator).join("."); + m.insert(key, Value::new(Some(&uri), ValueKind::String(value))); } @@ -142,7 +143,7 @@ impl Iterator for ArgumentParser { self.0.remove(idx); } PairKind::TwoArguments => { - self.0.remove(idx + 1); + self.0.remove(idx.saturating_add(1)); self.0.remove(idx); } } @@ -172,13 +173,13 @@ impl ArgumentParser { // A closing quote must be the same as an opening one. return Err(Error::UnmatchedQuote(key)); } - &value[1..value.len() - 1] + &value[1..value.len().saturating_sub(1)] } else if starts_with(value, b'\'') { if !ends_with(value, b'\'') { // A closing quote must be the same as an opening one. return Err(Error::UnmatchedQuote(key)); } - &value[1..value.len() - 1] + &value[1..value.len().saturating_sub(1)] } else { value }; @@ -189,7 +190,7 @@ impl ArgumentParser { let key = term.to_owned(); let value = self .0 - .get(idx + 1) + .get(idx.saturating_add(1)) .map(|v| os_to_str(v)) .transpose()? .unwrap_or(""); @@ -229,11 +230,11 @@ fn starts_with(text: &str, c: u8) -> bool { #[inline] fn ends_with(text: &str, c: u8) -> bool { - if text.is_empty() { - false - } else { - text.as_bytes()[text.len() - 1] == c - } + text.as_bytes() + .iter() + .last() + .map(|v| *v == c) + .unwrap_or(false) } #[inline] diff --git a/rust/main/hyperlane-base/src/settings/loader/mod.rs b/rust/main/hyperlane-base/src/settings/loader/mod.rs index ac47d4200a7..318c689201f 100644 --- a/rust/main/hyperlane-base/src/settings/loader/mod.rs +++ b/rust/main/hyperlane-base/src/settings/loader/mod.rs @@ -1,6 +1,6 @@ //! Load a settings object from the config locations. -use std::{env, error::Error, fmt::Debug, path::PathBuf}; +use std::{env, error::Error, fmt::Debug, ops::Add, path::PathBuf}; use config::{Config, File}; use convert_case::Case; @@ -38,7 +38,7 @@ where { let entry = entry.map_err(|err| { let mut config_err = ConfigParsingError::default(); - let config_path = ConfigPath::default() + "./config"; + let config_path = ConfigPath::default().add("./config"); config_err.push(config_path, eyre::eyre!(err.to_string())); config_err })?; diff --git a/rust/main/hyperlane-base/src/settings/mod.rs b/rust/main/hyperlane-base/src/settings/mod.rs index 6d24549333e..f6fc5dc6ebc 100644 --- a/rust/main/hyperlane-base/src/settings/mod.rs +++ b/rust/main/hyperlane-base/src/settings/mod.rs @@ -69,8 +69,8 @@ pub use signers::*; pub use trace::*; mod envs { + pub use dymension_kaspa as h_kaspa; pub use hyperlane_cosmos as h_cosmos; - pub use hyperlane_cosmos_native as h_cosmos_native; pub use hyperlane_ethereum as h_eth; pub use hyperlane_fuel as h_fuel; pub use hyperlane_sealevel as h_sealevel; @@ -81,6 +81,7 @@ pub(crate) mod aws_credentials; mod base; /// Chain configuration mod chains; +pub use chains::build_kaspa_provider; pub mod loader; /// Signer configuration mod signers; diff --git a/rust/main/hyperlane-base/src/settings/parser/connection_parser.rs b/rust/main/hyperlane-base/src/settings/parser/connection_parser.rs index 6a0c5d91cf2..403bfa633cf 100644 --- a/rust/main/hyperlane-base/src/settings/parser/connection_parser.rs +++ b/rust/main/hyperlane-base/src/settings/parser/connection_parser.rs @@ -1,18 +1,25 @@ -use eyre::eyre; +use std::collections::HashMap; +use std::ops::Add; + +use convert_case::Case; +use eyre::{eyre, Context}; use hyperlane_sealevel::{ HeliusPriorityFeeLevel, HeliusPriorityFeeOracleConfig, PriorityFeeOracleConfig, }; +use serde_json::Value; use url::Url; use h_eth::TransactionOverrides; use hyperlane_core::config::{ConfigErrResultExt, OpSubmissionConfig}; -use hyperlane_core::{config::ConfigParsingError, HyperlaneDomainProtocol, NativeToken}; +use hyperlane_core::{config::ConfigParsingError, HyperlaneDomainProtocol, NativeToken, H256}; use hyperlane_starknet as h_starknet; use crate::settings::envs::*; +use crate::settings::parser::recase_json_value; use crate::settings::ChainConnectionConf; +use std::str::FromStr; use super::{parse_base_and_override_urls, parse_cosmos_gas_price, ValueParser}; @@ -42,12 +49,12 @@ pub fn build_ethereum_connection_conf( urls: rpcs.to_owned().clone(), }), ty => Err(eyre!("unknown rpc consensus type `{ty}`")) - .take_err(err, || &chain.cwp + "rpc_consensus_type"), + .take_err(err, || (&chain.cwp).add("rpc_consensus_type")), }; let transaction_overrides = chain .get_opt_key("transactionOverrides") - .take_err(err, || &chain.cwp + "transaction_overrides") + .take_err(err, || (&chain.cwp).add("transaction_overrides")) .flatten() .map(|value_parser| TransactionOverrides { gas_price: value_parser @@ -87,17 +94,6 @@ pub fn build_ethereum_connection_conf( .parse_u256() .end(), - gas_limit_multiplier_denominator: value_parser - .chain(err) - .get_opt_key("gasLimitMultiplierDenominator") - .parse_u256() - .end(), - gas_limit_multiplier_numerator: value_parser - .chain(err) - .get_opt_key("gasLimitMultiplierNumerator") - .parse_u256() - .end(), - gas_price_multiplier_denominator: value_parser .chain(err) .get_opt_key("gasPriceMultiplierDenominator") @@ -108,12 +104,22 @@ pub fn build_ethereum_connection_conf( .get_opt_key("gasPriceMultiplierNumerator") .parse_u256() .end(), + gas_price_cap_multiplier: value_parser + .chain(err) + .get_opt_key("gasPriceCapMultiplier") + .parse_u256() + .end(), gas_price_cap: value_parser .chain(err) .get_opt_key("gasPriceCap") .parse_u256() .end(), + gas_limit_cap: value_parser + .chain(err) + .get_opt_key("gasLimitCap") + .parse_u256() + .end(), }) .unwrap_or_default(); @@ -129,6 +135,7 @@ pub fn build_cosmos_connection_conf( chain: &ValueParser, err: &mut ConfigParsingError, operation_batch: OpSubmissionConfig, + protocol: HyperlaneDomainProtocol, ) -> Option { let mut local_err = ConfigParsingError::default(); let grpcs = @@ -140,7 +147,10 @@ pub fn build_cosmos_connection_conf( .parse_string() .end() .or_else(|| { - local_err.push(&chain.cwp + "chain_id", eyre!("Missing chain id for chain")); + local_err.push( + (&chain.cwp).add("chain_id"), + eyre!("Missing chain id for chain"), + ); None }); @@ -151,7 +161,7 @@ pub fn build_cosmos_connection_conf( .end() .or_else(|| { local_err.push( - &chain.cwp + "bech32Prefix", + (&chain.cwp).add("bech32Prefix"), eyre!("Missing bech32 prefix for chain"), ); None @@ -171,6 +181,19 @@ pub fn build_cosmos_connection_conf( let native_token = parse_native_token(chain, err, 18); + let gas_multiplier = chain + .chain(err) + .get_opt_key("gasMultiplier") + .parse_f64() + .end() + .unwrap_or(1.35); + + let compat_mode = chain + .chain(err) + .get_opt_key("compatMode") + .parse_string() + .end(); + if !local_err.is_ok() { err.merge(local_err); return None; @@ -190,8 +213,7 @@ pub fn build_cosmos_connection_conf( Some(asset) => asset.to_string(), None => format!("u{}", prefix), }; - - Some(ChainConnectionConf::Cosmos(h_cosmos::ConnectionConf::new( + let config = h_cosmos::ConnectionConf::new( grpcs, rpcs.to_owned(), chain_id.to_string(), @@ -201,128 +223,358 @@ pub fn build_cosmos_connection_conf( contract_address_bytes as usize, operation_batch, native_token, - ))) + gas_multiplier, + compat_mode, + ); + + match config { + Err(e) => { + err.push((&chain.cwp).add("compatMode"), eyre!(e)); + None + } + Ok(config) => match protocol { + HyperlaneDomainProtocol::Cosmos => Some(ChainConnectionConf::Cosmos(config)), + HyperlaneDomainProtocol::CosmosNative => { + Some(ChainConnectionConf::CosmosNative(config)) + } + _ => None, + }, + } } -pub fn build_cosmos_native_connection_conf( - rpcs: &[Url], +fn build_starknet_connection_conf( + urls: &[Url], chain: &ValueParser, err: &mut ConfigParsingError, operation_batch: OpSubmissionConfig, ) -> Option { - let mut local_err = ConfigParsingError::default(); - let grpcs = - parse_base_and_override_urls(chain, "grpcUrls", "customGrpcUrls", "http", &mut local_err); + let native_token_address = chain + .chain(err) + .get_key("nativeToken") + .get_key("denom") + .parse_address_hash() + .end(); - let chain_id = chain - .chain(&mut local_err) - .get_key("chainId") + let Some(native_token_address) = native_token_address else { + err.push( + (&chain.cwp).add("nativeToken.denom"), + eyre!("nativeToken denom required"), + ); + return None; + }; + + Some(ChainConnectionConf::Starknet(h_starknet::ConnectionConf { + urls: urls.to_vec(), + native_token_address, + op_submission_config: operation_batch, + })) +} + +pub fn build_kaspa_connection_conf( + _rpcs: &[Url], // we dont use it because it does not support raw ip addresses and such + chain: &ValueParser, + err: &mut ConfigParsingError, + operation_batch: OpSubmissionConfig, +) -> Option { + let wallet_secret = chain + .chain(err) + .get_opt_key("walletSecret") .parse_string() - .end() - .or_else(|| { - local_err.push(&chain.cwp + "chain_id", eyre!("Missing chain id for chain")); + .end()?; + + let wallet_dir = { + chain + .chain(err) + .get_opt_key("walletDir") + .parse_string() + .end() + .map(|s| s.to_string()) + }; + + let wrpc_urls: Vec = chain + .chain(err) + .get_key("kaspaUrlsWrpc") + .parse_string() + .end()? + .split(',') + .map(|s| s.trim().to_string()) + .collect(); + + let rest_urls: Vec = { + chain + .chain(err) + .get_key("kaspaUrlsRest") + .parse_string() + .end()? + .split(',') + .map(|s| s.trim().to_string()) + .map(|s| Url::parse(&s).unwrap()) + .collect() + }; + + // Parse kaspaValidatorsEscrow and kaspaValidatorsIsm as arrays of validator objects + let validators_escrow: Vec = parse_kaspa_validators( + chain, + err, + "kaspaValidatorsEscrow", + "failed to parse kaspaValidatorsEscrow array", + )?; + let validators_ism: Vec = parse_kaspa_validators( + chain, + err, + "kaspaValidatorsIsm", + "failed to parse kaspaValidatorsIsm array", + )?; + + let kaspa_escrow_key_source = + if let Some(key_config) = chain.chain(err).get_opt_key("kaspaKey").end() { + let key_type = key_config + .chain(err) + .get_opt_key("type") + .parse_string() + .end(); + + match key_type { + Some("aws") => { + let secret_id = key_config + .chain(err) + .get_key("secretId") + .parse_string() + .end()?; + let kms_key_id = key_config + .chain(err) + .get_key("kmsKeyId") + .parse_string() + .end()?; + let region = key_config + .chain(err) + .get_key("region") + .parse_string() + .end()?; + + Some(dymension_kaspa::KaspaEscrowKeySource::Aws( + dymension_kaspa::AwsKeyConfig { + secret_id: secret_id.to_string(), + kms_key_id: kms_key_id.to_string(), + region: region.to_string(), + }, + )) + } + _ => None, + } + } else if let Some(kaspa_escrow_private_key_s) = chain + .chain(err) + .get_opt_key("kaspaEscrowPrivateKey") + .parse_string() + .end() + { + Some(dymension_kaspa::KaspaEscrowKeySource::Direct( + kaspa_escrow_private_key_s.to_string(), + )) + } else { None - }); + }; - let prefix = chain + let grpc_urls: Vec = chain .chain(err) - .get_key("bech32Prefix") + .get_opt_key("kaspaUrlsGrpc") .parse_string() .end() - .or_else(|| { - local_err.push( - &chain.cwp + "bech32Prefix", - eyre!("Missing bech32 prefix for chain"), - ); - None - }); + .map(|s| s.split(',').map(|s| s.trim().to_string()).collect()) + .unwrap_or_default(); - let gas_price = chain + let threshold_ism = chain .chain(err) - .get_opt_key("gasPrice") - .and_then(parse_cosmos_gas_price) - .end(); + .get_key("kaspaMultisigThresholdHubIsm") + .parse_u32() + .end()?; - let gas_multiplier = chain + let threshold_escrow = chain .chain(err) - .get_opt_key("gasMultiplier") - .parse_f64() - .end() - .unwrap_or(1.8); + .get_key("kaspaMultisigThresholdEscrow") + .parse_u32() + .end()?; - let contract_address_bytes = chain + let kaspa_min_deposit_sompi = chain .chain(err) - .get_opt_key("contractAddressBytes") - .parse_u64() - .end(); + .get_key("kaspaMinDepositSompi") + .parse_u256() + .end()?; - let native_token = parse_native_token(chain, err, 18); + let mut local_err = ConfigParsingError::default(); + let grpcs = + parse_base_and_override_urls(chain, "grpcUrls", "customGrpcUrls", "http", &mut local_err); - if !local_err.is_ok() { - err.merge(local_err); - return None; - } - let gas_price = gas_price?; - let gas_price = h_cosmos_native::RawCosmosAmount { - denom: gas_price.denom, - amount: gas_price.amount, + let hub_mailbox_id = chain + .chain(err) + .get_key("hubMailboxId") + .parse_string() + .end() + .unwrap(); + + let validation_conf = { + let mut conf = dymension_kaspa::ValidationConf::default(); + conf.validate_deposits = chain + .chain(err) + .get_opt_key("validateDeposit") + .parse_bool() + .end() + .unwrap_or(conf.validate_deposits); + conf.validate_withdrawals = chain + .chain(err) + .get_opt_key("validateWithdrawal") + .parse_bool() + .end() + .unwrap_or(conf.validate_withdrawals); + conf.validate_confirmations = chain + .chain(err) + .get_opt_key("validateWithdrawalConfirmation") + .parse_bool() + .end() + .unwrap_or(conf.validate_confirmations); + conf }; - let contract_address_bytes: usize = contract_address_bytes.and_then(|v| v.try_into().ok())?; - let chain_id = chain_id?; - let prefix = prefix?; - let canonical_asset = match chain + let hub_domain = chain .chain(err) - .get_opt_key("canonicalAsset") + .get_opt_key("hubDomain") + .parse_u32() + .end() + .unwrap_or(0); + + let hub_token_id = match chain + .chain(err) + .get_opt_key("hubTokenId") .parse_string() .end() { - Some(asset) => asset.to_string(), - None => format!("u{}", prefix), + Some(s) => { + let ss: &String = &s.to_owned(); + H256::from_str(ss).unwrap() + } + None => H256::default(), }; - Some(ChainConnectionConf::CosmosNative( - h_cosmos_native::ConnectionConf::new( - rpcs.to_owned(), - grpcs, - chain_id.to_string(), - prefix.to_string(), - canonical_asset, - gas_price, - gas_multiplier, - contract_address_bytes, - operation_batch, - native_token, - ), - )) -} + let kas_domain = chain + .chain(err) + .get_opt_key("kasDomain") + .parse_u32() + .end() + .unwrap_or(0); -fn build_starknet_connection_conf( - urls: &[Url], - chain: &ValueParser, - err: &mut ConfigParsingError, - operation_batch: OpSubmissionConfig, -) -> Option { - let native_token_address = chain + let kas_token_placeholder = match chain .chain(err) - .get_key("nativeToken") - .get_key("denom") - .parse_address_hash() - .end(); + .get_opt_key("kasTokenId") + .parse_string() + .end() + { + Some(s) => { + let ss: &String = &s.to_owned(); + H256::from_str(ss).unwrap() + } + None => H256::default(), + }; - let Some(native_token_address) = native_token_address else { - err.push( - &chain.cwp + "nativeToken.denom", - eyre!("nativeToken denom required"), - ); - return None; + let kaspa_tx_fee_multiplier = chain + .chain(err) + .get_opt_key("kaspaFeeMultiplier") + .parse_f64() + .end() + .unwrap_or(1.5); + + let max_sweep_inputs = chain + .chain(err) + .get_opt_key("maxSweepInputs") + .parse_u64() + .end() + .map(|v| v as usize); + + // Parse KaspaTimeConfig if provided (only for relayer configs with validators) + let kaspa_time_config = if !validators_escrow.is_empty() { + Some(dymension_kaspa::RelayerDepositTimings { + poll_interval: chain + .chain(err) + .get_opt_key("depositPollInterval") + .parse_duration() + .end() + .unwrap_or(std::time::Duration::from_secs(5)), + retry_delay_base: chain + .chain(err) + .get_opt_key("depositRetryDelayBase") + .parse_duration() + .end() + .unwrap_or(std::time::Duration::from_secs(30)), + retry_delay_exponent: chain + .chain(err) + .get_opt_key("depositRetryDelayExponent") + .parse_f64() + .end() + .unwrap_or(2.0), + retry_delay_max: chain + .chain(err) + .get_opt_key("depositRetryDelayMax") + .parse_duration() + .end() + .unwrap_or(std::time::Duration::from_secs(60 * 60)), + deposit_look_back: chain + .chain(err) + .get_opt_key("depositLookBack") + .parse_duration() + .end() + .unwrap_or(std::time::Duration::from_secs(0)), + deposit_query_overlap: chain + .chain(err) + .get_opt_key("depositQueryOverlap") + .parse_duration() + .end() + .unwrap_or(std::time::Duration::from_secs(60 * 5)), + }) + } else { + None }; - Some(ChainConnectionConf::Starknet(h_starknet::ConnectionConf { - urls: urls.to_vec(), - native_token_address, - op_submission_config: operation_batch, - })) + let validator_request_timeout = chain + .chain(err) + .get_opt_key("signRequestTimeout") + .parse_duration() + .end() + .unwrap_or(std::time::Duration::from_secs(15)); + + let migrate_escrow_to = chain + .chain(err) + .get_opt_key("migrateEscrowTo") + .parse_string() + .end() + .map(|s| s.to_string()); + + let conf = dymension_kaspa::ConnectionConf::new( + wallet_secret.to_owned(), + wallet_dir, + wrpc_urls, + rest_urls, + validators_escrow, + validators_ism, + kaspa_escrow_key_source, + grpc_urls, + threshold_ism as usize, + threshold_escrow as usize, + grpcs, + hub_mailbox_id.to_owned(), + operation_batch, + validation_conf, + kaspa_min_deposit_sompi, + kaspa_time_config, + hub_domain, + hub_token_id, + kas_domain, + kas_token_placeholder, + kaspa_tx_fee_multiplier, + max_sweep_inputs, + validator_request_timeout, + migrate_escrow_to, + ); + + Some(ChainConnectionConf::Kaspa(conf)) } fn build_sealevel_connection_conf( @@ -392,7 +644,7 @@ fn parse_sealevel_priority_fee_oracle_config( .end() .or_else(|| { err.push( - &value_parser.cwp + "type", + (&value_parser.cwp).add("type"), eyre!("Missing priority fee oracle type"), ); None @@ -425,7 +677,7 @@ fn parse_sealevel_priority_fee_oracle_config( } _ => { err.push( - &value_parser.cwp + "type", + (&value_parser.cwp).add("type"), eyre!("Unknown priority fee oracle type"), ); None @@ -460,7 +712,7 @@ fn parse_helius_priority_fee_level( "unsafemax" => Some(HeliusPriorityFeeLevel::UnsafeMax), _ => { err.push( - &value_parser.cwp + "fee_level", + (&value_parser.cwp).add("fee_level"), eyre!("Unknown priority fee level"), ); None @@ -507,7 +759,7 @@ fn parse_transaction_submitter_config( } _ => { err.push( - &chain.cwp + "transaction_submitter.type", + (&chain.cwp).add("transaction_submitter.type"), eyre!("Unknown transaction submitter type"), ); None @@ -519,6 +771,129 @@ fn parse_transaction_submitter_config( } } +fn parse_header( + name: &str, + chain: &ValueParser, + err: &mut ConfigParsingError, +) -> Vec> { + let mut local_err = ConfigParsingError::default(); + let header = chain.chain(&mut local_err).get_opt_key(name).end(); + + let Some(header) = header else { + return Vec::new(); + }; + let result = match header { + ValueParser { + val: Value::String(array_str), + cwp, + } => { + let Some(value) = serde_json::from_str::(array_str) + .context("Expected JSON string") + .take_err(&mut local_err, || cwp.clone()) + .map(|v| recase_json_value(v, Case::Flat)) + else { + if !local_err.is_ok() { + err.merge(local_err); + } + return Vec::new(); + }; + ValueParser::new(cwp.clone(), &value) + .chain(&mut local_err) + .parse_value::>>("failed to parse header") + .unwrap_or_default() + } + ValueParser { + val: value @ Value::Array(_), + .. + } => ValueParser::new(header.cwp.clone(), value) + .chain(&mut local_err) + .parse_value::>>("failed to parse header") + .unwrap_or_default(), + _ => { + err.push( + (&chain.cwp).add(name), + eyre!("Expected JSON array or stringified JSON"), + ); + return vec![]; + } + }; + + if !local_err.is_ok() { + err.merge(local_err); + } + + result +} + +pub fn build_radix_connection_conf( + rpcs: &[Url], + chain: &ValueParser, + err: &mut ConfigParsingError, + _operation_batch: OpSubmissionConfig, +) -> Option { + let mut local_err = ConfigParsingError::default(); + let gateway_urls = parse_base_and_override_urls( + chain, + "gatewayUrls", + "customGatewayUrls", + "http", + &mut local_err, + ); + + let network_name = chain + .chain(&mut local_err) + .get_key("networkName") + .parse_string() + .end() + .or_else(|| { + local_err.push( + (&chain.cwp).add("network_name"), + eyre!("Missing network name for chain"), + ); + None + }); + + let gateway_header = parse_header("gatewayHeader", chain, &mut local_err); + let core_header = parse_header("coreHeader", chain, &mut local_err); + + if !local_err.is_ok() { + err.merge(local_err); + None + } else { + Some(ChainConnectionConf::Radix( + hyperlane_radix::ConnectionConf::new( + rpcs.to_vec(), + gateway_urls, + network_name?.to_string(), + core_header, + gateway_header, + ), + )) + } +} + +/// Parse a kaspa validators array from config. +/// Returns empty vec if the field is missing (valid for validator-only configs). +fn parse_kaspa_validators( + chain: &ValueParser, + err: &mut ConfigParsingError, + key: &str, + err_msg: &'static str, +) -> Option> +where + T: serde::de::DeserializeOwned, +{ + let validators_opt = chain.chain(err).get_opt_key(key).end(); + + match validators_opt { + Some(value_parser) => { + let validators: Vec = value_parser.chain(err).parse_value(err_msg).end()?; + Some(validators) + } + None => Some(Vec::new()), + } +} + pub fn build_connection_conf( domain_protocol: HyperlaneDomainProtocol, rpcs: &[Url], @@ -543,14 +918,18 @@ pub fn build_connection_conf( let urls = rpcs.to_vec(); build_sealevel_connection_conf(&urls, chain, err, operation_batch) } - HyperlaneDomainProtocol::Cosmos => { - build_cosmos_connection_conf(rpcs, chain, err, operation_batch) + HyperlaneDomainProtocol::Cosmos | HyperlaneDomainProtocol::CosmosNative => { + build_cosmos_connection_conf(rpcs, chain, err, operation_batch, domain_protocol) } HyperlaneDomainProtocol::Starknet => { build_starknet_connection_conf(rpcs, chain, err, operation_batch) } - HyperlaneDomainProtocol::CosmosNative => { - build_cosmos_native_connection_conf(rpcs, chain, err, operation_batch) + // TODO: adjust the connection config + HyperlaneDomainProtocol::Radix => { + build_radix_connection_conf(rpcs, chain, err, operation_batch) + } + HyperlaneDomainProtocol::Kaspa => { + build_kaspa_connection_conf(rpcs, chain, err, operation_batch) } } } diff --git a/rust/main/hyperlane-base/src/settings/parser/json_value_parser.rs b/rust/main/hyperlane-base/src/settings/parser/json_value_parser.rs index c523537a376..aa8fbf1ff50 100644 --- a/rust/main/hyperlane-base/src/settings/parser/json_value_parser.rs +++ b/rust/main/hyperlane-base/src/settings/parser/json_value_parser.rs @@ -1,9 +1,9 @@ -use std::{fmt::Debug, str::FromStr}; +use std::{fmt::Debug, ops::Add, str::FromStr, time::Duration}; use convert_case::{Case, Casing}; use derive_new::new; use eyre::{eyre, Context}; -use hyperlane_core::{config::*, utils::hex_or_base58_to_h256, H256, U256}; +use hyperlane_core::{config::*, utils::hex_or_base58_or_bech32_to_h256, H256, U256}; use itertools::Itertools; use serde::de::{DeserializeOwned, StdError}; use serde_json::Value; @@ -30,12 +30,12 @@ impl<'v> ValueParser<'v> { pub fn get_key(&self, key: &str) -> ConfigResult> { self.get_opt_key(&key.to_case(Case::Flat))? .ok_or_else(|| eyre!("Expected key `{key}` to be defined")) - .into_config_result(|| &self.cwp + key.to_case(Case::Snake)) + .into_config_result(|| (&self.cwp).add(key.to_case(Case::Snake))) } /// Get a value at the given key allowing for it to not be set. pub fn get_opt_key(&self, key: &str) -> ConfigResult>> { - let cwp = &self.cwp + key.to_case(Case::Snake); + let cwp = (&self.cwp).add(key.to_case(Case::Snake)); match self.val { Value::Object(obj) => Ok(obj.get(&key.to_case(Case::Flat)).map(|val| Self { val, @@ -58,7 +58,7 @@ impl<'v> ValueParser<'v> { k.clone(), Self { val: v, - cwp: &cwp + k.to_case(Case::Snake), + cwp: (&cwp).add(k.to_case(Case::Snake)), }, ) })), @@ -74,7 +74,7 @@ impl<'v> ValueParser<'v> { match self.val { Value::Array(arr) => Ok(arr.iter().enumerate().map(move |(i, v)| Self { val: v, - cwp: &cwp + i.to_string(), + cwp: (&cwp).add(i.to_string()), })) .map(|itr| Box::new(itr) as Box>>), Value::Object(obj) => obj @@ -100,7 +100,7 @@ impl<'v> ValueParser<'v> { .map(|itr| { itr.map(move |(i, v)| Self { val: v, - cwp: &cwp + i.to_string(), + cwp: (&cwp).add(i.to_string()), }) }) .map(|itr| Box::new(itr) as Box>>), @@ -192,6 +192,22 @@ impl<'v> ValueParser<'v> { .into_config_result(|| self.cwp.clone()) } + /// Parse a duration value from a string like "5s", "30s", "15m", "1h" + pub fn parse_duration(&self) -> ConfigResult { + match self.val.as_str() { + Some(s) => humantime::parse_duration(s) + .with_context(|| { + format!("Expected a duration string like '5s', '30s', '15m', got `{s}`") + }) + .into_config_result(|| self.cwp.clone()), + None => Err(eyre!( + "Expected a duration string like '5s', '30s', '15m', got `{:?}`", + self.val + )) + .into_config_result(|| self.cwp.clone()), + } + } + /// Parse a u256 value allowing for it to be represented as string or number. pub fn parse_u256(&self) -> ConfigResult { match self.val { @@ -234,12 +250,11 @@ impl<'v> ValueParser<'v> { .into_config_result(|| self.cwp.clone()) } - /// Parse an address hash allowing for it to be represented as a hex or base58 string. + /// Parse an address hash allowing for it to be represented as a hex, bech32 or base58 string. pub fn parse_address_hash(&self) -> ConfigResult { match self.val { - Value::String(s) => { - hex_or_base58_to_h256(s).context("Expected a valid address hash in hex or base58") - } + Value::String(s) => hex_or_base58_or_bech32_to_h256(s) + .context("Expected a valid address hash in hex, base58 or bech32"), _ => Err(eyre!("Expected an address string, got `{:?}`", self.val)), } .into_config_result(|| self.cwp.clone()) @@ -248,9 +263,8 @@ impl<'v> ValueParser<'v> { /// Parse a private key allowing for it to be represented as a hex or base58 string. pub fn parse_private_key(&self) -> ConfigResult { match self.val { - Value::String(s) => { - hex_or_base58_to_h256(s).context("Expected a valid private key in hex or base58") - } + Value::String(s) => hex_or_base58_or_bech32_to_h256(s) + .context("Expected a valid private key in hex, base58 or bech32"), _ => Err(eyre!("Expected a private key string")), } .into_config_result(|| self.cwp.clone()) @@ -317,6 +331,7 @@ define_basic_parse!( parse_u32: u32, parse_u16: u16, parse_i32: i32, + parse_duration: Duration, parse_u256: U256, parse_bool: bool, parse_string: &'v str, @@ -403,7 +418,7 @@ impl<'e, T> ParseChain<'e, T> { } } -impl<'e, T: Default> ParseChain<'e, T> { +impl ParseChain<'_, T> { pub fn unwrap_or_default(self) -> T { self.0.unwrap_or_default() } diff --git a/rust/main/hyperlane-base/src/settings/parser/mod.rs b/rust/main/hyperlane-base/src/settings/parser/mod.rs index dce67cc45ed..a55eb80c2e8 100644 --- a/rust/main/hyperlane-base/src/settings/parser/mod.rs +++ b/rust/main/hyperlane-base/src/settings/parser/mod.rs @@ -7,6 +7,7 @@ use std::{ collections::{HashMap, HashSet}, default::Default, + ops::Add, time::Duration, }; @@ -195,11 +196,41 @@ fn parse_chain( .get_key("mailbox") .parse_address_hash() .end(); - let interchain_gas_paymaster = chain + let interchain_gas_paymaster_single = chain .chain(&mut err) - .get_key("interchainGasPaymaster") + .get_opt_key("interchainGasPaymaster") .parse_address_hash() .end(); + let interchain_gas_paymaster_array = chain + .chain(&mut err) + .get_opt_key("interchainGasPaymasters") + .into_array_iter() + .map(|arr| { + arr.filter_map(|v| v.chain(&mut err).parse_address_hash().end()) + .collect() + }); + + let interchain_gas_paymasters = match ( + interchain_gas_paymaster_single, + interchain_gas_paymaster_array, + ) { + (Some(single), None) => vec![single], + (None, Some(array)) => array, + (Some(_), Some(_)) => { + err.push( + (&chain.cwp).add("interchainGasPaymaster"), + eyre!("Both interchainGasPaymaster and interchainGasPaymasters are specified"), + ); + vec![] + } + (None, None) => { + err.push( + (&chain.cwp).add("interchainGasPaymaster"), + eyre!("Either interchainGasPaymaster or interchainGasPaymasters must be specified"), + ); + vec![] + } + }; let validator_announce = chain .chain(&mut err) .get_key("validatorAnnounce") @@ -256,7 +287,7 @@ fn parse_chain( }, ); - cfg_unwrap_all!(&chain.cwp, err: [connection, mailbox, interchain_gas_paymaster, validator_announce, merkle_tree_hook]); + cfg_unwrap_all!(&chain.cwp, err: [connection, mailbox, validator_announce, merkle_tree_hook]); let submitter = chain .chain(&mut err) @@ -282,7 +313,7 @@ fn parse_chain( reorg_period, addresses: CoreContractAddresses { mailbox, - interchain_gas_paymaster, + interchain_gas_paymasters, validator_announce, merkle_tree_hook, }, @@ -313,7 +344,7 @@ fn parse_domain(chain: ValueParser, name: &str) -> ConfigResult } else { Err(eyre!("missing chain name, the config may be corrupted")) } - .take_err(&mut err, || &chain.cwp + "name"); + .take_err(&mut err, || (&chain.cwp).add("name")); let domain_id = chain .chain(&mut err) @@ -427,6 +458,22 @@ fn parse_signer(signer: ValueParser) -> ConfigResult { is_legacy, }) }}; + (radixKey) => {{ + let key = signer + .chain(&mut err) + .get_opt_key("key") + .parse_private_key() + .unwrap_or_default(); + let suffix = signer + .chain(&mut err) + .get_opt_key("suffix") + .parse_string() + .unwrap_or_default(); + err.into_result(SignerConf::RadixKey { + key, + suffix: suffix.to_owned(), + }) + }}; } match signer_type { @@ -434,8 +481,9 @@ fn parse_signer(signer: ValueParser) -> ConfigResult { Some("aws") => parse_signer!(aws), Some("cosmosKey") => parse_signer!(cosmosKey), Some("starkKey") => parse_signer!(starkKey), + Some("radixKey") => parse_signer!(radixKey), Some(t) => { - Err(eyre!("Unknown signer type `{t}`")).into_config_result(|| &signer.cwp + "type") + Err(eyre!("Unknown signer type `{t}`")).into_config_result(|| (&signer.cwp).add("type")) } None if key_is_some => parse_signer!(hexKey), None if id_is_some | region_is_some => parse_signer!(aws), @@ -538,7 +586,7 @@ fn parse_custom_urls( .end() .map(|urls| { urls.split(',') - .filter_map(|url| url.parse().take_err(err, || &chain.cwp + key)) + .filter_map(|url| url.parse().take_err(err, || (&chain.cwp).add(key))) .collect_vec() }) } @@ -555,12 +603,14 @@ fn parse_base_and_override_urls( let combined = overrides.unwrap_or(base); if combined.is_empty() { + let config_path = (&chain.cwp).add(base_key.to_ascii_lowercase()); err.push( - &chain.cwp + base_key.to_ascii_lowercase(), + config_path, eyre!("Missing base {} definitions for chain", base_key), ); + let config_path = (&chain.cwp).add(override_key.to_lowercase()); err.push( - &chain.cwp + override_key.to_lowercase(), + config_path, eyre!("Also missing {} overrides for chain", base_key), ); } diff --git a/rust/main/hyperlane-base/src/settings/signers.rs b/rust/main/hyperlane-base/src/settings/signers.rs index 8c31e995c51..45f55d6f743 100644 --- a/rust/main/hyperlane-base/src/settings/signers.rs +++ b/rust/main/hyperlane-base/src/settings/signers.rs @@ -40,6 +40,13 @@ pub enum SignerConf { /// Account address type for cosmos address account_address_type: AccountAddressType, }, + /// Radix Specific key + RadixKey { + /// private key + key: H256, + /// suffix for address formatting + suffix: String, + }, /// Starknet Specific key StarkKey { /// Private key value @@ -49,6 +56,15 @@ pub enum SignerConf { /// Whether the Starknet signer is legacy is_legacy: bool, }, + /// Kaspa Specific key + KaspaKey { + /// Private key value + key: H256, + /// Prefix for kaspa address + prefix: String, + /// Account address type for kaspa address + account_address_type: AccountAddressType, + }, /// Assume node will sign on RPC calls #[default] Node, @@ -94,7 +110,6 @@ impl BuildableWithSignerConf for hyperlane_ethereum::Signers { rusoto_core::Client::new_with(AwsChainCredentialsProvider::new(), http_client), region.clone(), ); - let signer = AwsSigner::new(client, id, 0, Some(AWS_SIGNER_TIMEOUT)).await?; hyperlane_ethereum::Signers::Aws(signer) } @@ -105,6 +120,12 @@ impl BuildableWithSignerConf for hyperlane_ethereum::Signers { bail!("starkKey signer is not supported by Ethereum") } SignerConf::Node => bail!("Node signer"), + SignerConf::KaspaKey { .. } => { + bail!("kaspaKey signer is not supported by Ethereum") + } + SignerConf::RadixKey { .. } => { + bail!("radixKey signer is not supported by Ethereum") + } }) } } @@ -207,26 +228,6 @@ impl BuildableWithSignerConf for hyperlane_starknet::Signer { } } -#[async_trait] -impl BuildableWithSignerConf for hyperlane_cosmos_native::Signer { - async fn build(conf: &SignerConf) -> Result { - if let SignerConf::CosmosKey { - key, - prefix, - account_address_type, - } = conf - { - Ok(hyperlane_cosmos_native::Signer::new( - key.as_bytes().to_vec(), - prefix.clone(), - account_address_type, - )?) - } else { - bail!(format!("{conf:?} key is not supported by cosmos")); - } - } -} - impl ChainSigner for hyperlane_starknet::Signer { fn address_string(&self) -> String { self.address.to_string() @@ -237,12 +238,27 @@ impl ChainSigner for hyperlane_starknet::Signer { } } -impl ChainSigner for hyperlane_cosmos_native::Signer { +#[async_trait] +impl BuildableWithSignerConf for hyperlane_radix::RadixSigner { + async fn build(conf: &SignerConf) -> Result { + if let SignerConf::RadixKey { key, suffix } = conf { + Ok(hyperlane_radix::RadixSigner::new( + key.as_bytes().to_vec(), + suffix.to_string(), + )?) + } else { + bail!(format!("{conf:?} key is not supported by radix")); + } + } +} + +impl ChainSigner for hyperlane_radix::RadixSigner { fn address_string(&self) -> String { - self.address_string.clone() + self.encoded_address.clone() } + fn address_h256(&self) -> H256 { - self.address_h256() + self.address_256 } } @@ -342,30 +358,4 @@ mod tests { ); assert_eq!(chain_signer.address_h256(), address_h256); } - - #[test] - fn address_h256_cosmosnative() { - const PRIVATE_KEY: &str = - "5486418967eabc770b0fcb995f7ef6d9a72f7fc195531ef76c5109f44f51af26"; - const ADDRESS: &str = "000000000000000000000000b5a79b48c87e7a37bdb625096140ee7054816942"; - - let key = H256::from_slice( - hex::decode(PRIVATE_KEY) - .expect("Failed to decode public key") - .as_slice(), - ); - let chain_signer = hyperlane_cosmos_native::Signer::new( - key.to_vec(), - "neutron".to_string(), - &AccountAddressType::Bitcoin, - ) - .expect("Failed to create cosmos signer"); - - let address_h256 = H256::from_slice( - hex::decode(ADDRESS) - .expect("Failed to decode public key") - .as_slice(), - ); - assert_eq!(chain_signer.address_h256(), address_h256); - } } diff --git a/rust/main/hyperlane-base/src/settings/trace/mod.rs b/rust/main/hyperlane-base/src/settings/trace/mod.rs index e00bb46f07f..d9eeaf9a3cd 100644 --- a/rust/main/hyperlane-base/src/settings/trace/mod.rs +++ b/rust/main/hyperlane-base/src/settings/trace/mod.rs @@ -66,7 +66,10 @@ impl TracingConfig { if self.level < Level::DependencyTrace { // Reduce log noise from trusted libraries that we can reasonably assume are working correctly target_layer = target_layer + .with_target("cometbft", Level::Warn) + .with_target("cometbft_rpc", Level::Warn) .with_target("hyper::", Level::Info) + .with_target("hyper_util", Level::Warn) .with_target("rusoto_core", Level::Info) .with_target("rustls", Level::Info) .with_target("reqwest", Level::Info) diff --git a/rust/main/hyperlane-base/src/settings/trace/span_metrics.rs b/rust/main/hyperlane-base/src/settings/trace/span_metrics.rs index fd60ed4745e..3e0a4c057ec 100644 --- a/rust/main/hyperlane-base/src/settings/trace/span_metrics.rs +++ b/rust/main/hyperlane-base/src/settings/trace/span_metrics.rs @@ -61,8 +61,8 @@ where .expect("bug: didn't insert SpanTiming"); let tags = [span.name(), span.metadata().target()]; self.count.with_label_values(&tags).inc(); - self.duration - .with_label_values(&tags) - .inc_by((now - timing.start).as_secs_f64()); + + let diff = now.saturating_duration_since(timing.start).as_secs_f64(); + self.duration.with_label_values(&tags).inc_by(diff); } } diff --git a/rust/main/hyperlane-base/src/types/multisig.rs b/rust/main/hyperlane-base/src/types/multisig.rs index c61cf959c9c..e5b1e525de8 100644 --- a/rust/main/hyperlane-base/src/types/multisig.rs +++ b/rust/main/hyperlane-base/src/types/multisig.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use derive_new::new; use eyre::Result; use futures::StreamExt; -use tracing::{debug, instrument, warn}; +use tracing::{debug, info, instrument, warn}; use hyperlane_core::{ HyperlaneDomain, MultisigSignedCheckpoint, SignedCheckpointWithMessageId, H160, H256, @@ -68,12 +68,12 @@ impl MultisigCheckpointSyncer { debug!(?validator, ?index, "Validator returned latest index"); latest_indices.insert(*validator, Some(index)); } - result => { - debug!( - ?validator, - ?result, - "Failed to get latest index from validator" - ); + Ok(None) => { + info!(?validator, "Validator returned no latest index"); + latest_indices.insert(*validator, None); + } + Err(err) => { + info!(?validator, %err, "Error fetching latest index from validator"); latest_indices.insert(*validator, None); } } @@ -130,7 +130,10 @@ impl MultisigCheckpointSyncer { ); if latest_indices.is_empty() { - debug!("No validators returned a latest index"); + info!( + validators_count = validators.len(), + "Quorum unreachable: no validators returned a latest index" + ); return Ok(None); } @@ -138,24 +141,48 @@ impl MultisigCheckpointSyncer { // the highest index for which we (supposedly) have (n+1) signed checkpoints latest_indices.sort_by(|a, b| b.1.cmp(&a.1)); - if let Some(&(_, highest_quorum_index)) = latest_indices.get(threshold - 1) { + if let Some(&(_, highest_quorum_index)) = latest_indices.get(threshold.saturating_sub(1)) { // The highest viable checkpoint index is the minimum of the highest index // we (supposedly) have a quorum for, and the maximum index for which we can // generate a proof. let start_index = highest_quorum_index.min(maximum_index); if minimum_index > start_index { - debug!(%start_index, %highest_quorum_index, "Highest quorum index is below the minimum index"); + info!( + %minimum_index, + %start_index, + %highest_quorum_index, + validators_with_indices = latest_indices.len(), + threshold, + "Quorum unreachable: highest quorum index is below minimum required" + ); return Ok(None); } + info!( + %minimum_index, + %start_index, + %highest_quorum_index, + validators_with_indices = latest_indices.len(), + threshold, + ?latest_indices, + "Searching for checkpoint in range" + ); + for index in (minimum_index..=start_index).rev() { let checkpoint_res = self.fetch_checkpoint(validators, threshold, index).await; if let Ok(Some(checkpoint)) = checkpoint_res { return Ok(Some(checkpoint)); } } + info!( + %minimum_index, + %start_index, + validators_with_indices = latest_indices.len(), + threshold, + ?latest_indices, + "Quorum unreachable: no valid checkpoint found in searched range" + ); } - debug!("No checkpoint found in range"); Ok(None) } @@ -207,67 +234,88 @@ impl MultisigCheckpointSyncer { // Gracefully ignore an error fetching the checkpoint from a validator's // checkpoint syncer, which can happen if the validator has not // signed the checkpoint at `index`. - if let Ok(Some(signed_checkpoint)) = checkpoint { - // If the signed checkpoint is for a different index, ignore it - if signed_checkpoint.value.index != index { + let signed_checkpoint = match checkpoint { + Ok(Some(c)) => c, + Ok(None) => { debug!( validator = format!("{:#x}", validator), index = index, - checkpoint_index = signed_checkpoint.value.index, - "Checkpoint index mismatch" + "Checkpoint not found for validator" ); continue; } - - // Ensure that the signature is actually by the validator - let signer = signed_checkpoint.recover()?; - - if H256::from(signer) != *validator { - debug!( + Err(err) => { + info!( validator = format!("{:#x}", validator), index = index, - "Checkpoint signature mismatch" + %err, + "Error fetching checkpoint from validator" ); continue; } + }; - // Push the signed checkpoint into the hashmap - let root = signed_checkpoint.value.root; - let signed_checkpoints = signed_checkpoints_per_root.entry(root).or_default(); - signed_checkpoints.push(signed_checkpoint); - - // Count the number of signatures for this signed checkpoint - let signature_count = signed_checkpoints.len(); + // If the signed checkpoint is for a different index, ignore it + if signed_checkpoint.value.index != index { debug!( validator = format!("{:#x}", validator), index = index, - root = format!("{:#x}", root), - signature_count = signature_count, - "Found signed checkpoint" + checkpoint_index = signed_checkpoint.value.index, + "Checkpoint index mismatch" ); + continue; + } - // If we've hit a quorum, create a MultisigSignedCheckpoint - if signature_count >= threshold { - let checkpoint: MultisigSignedCheckpoint = signed_checkpoints.try_into()?; - debug!(checkpoint=?checkpoint, "Fetched multisig checkpoint"); - return Ok(Some(checkpoint)); - } - } else { + // Ensure that the signature is actually by the validator + let signer = signed_checkpoint.recover()?; + + if H256::from(signer) != *validator { debug!( validator = format!("{:#x}", validator), index = index, - "Unable to find signed checkpoint" + "Checkpoint signature mismatch" ); + continue; + } + + // Push the signed checkpoint into the hashmap + let root = signed_checkpoint.value.root; + let signed_checkpoints = signed_checkpoints_per_root.entry(root).or_default(); + signed_checkpoints.push(signed_checkpoint); + + // Count the number of signatures for this signed checkpoint + let signature_count = signed_checkpoints.len(); + debug!( + validator = format!("{:#x}", validator), + index = index, + root = format!("{:#x}", root), + signature_count = signature_count, + "Found signed checkpoint" + ); + + // If we've hit a quorum, create a MultisigSignedCheckpoint + if signature_count >= threshold { + let checkpoint: MultisigSignedCheckpoint = signed_checkpoints.try_into()?; + debug!(checkpoint=?checkpoint, "Fetched multisig checkpoint"); + return Ok(Some(checkpoint)); } } } - debug!("No quorum checkpoint found for message"); + + info!( + index, + threshold, + validators_queried = validators.len(), + roots_found = signed_checkpoints_per_root.len(), + signatures_per_root = ?signed_checkpoints_per_root.iter().map(|(root, sigs)| (format!("{:#x}", root), sigs.len())).collect::>(), + "No quorum checkpoint found: insufficient signatures for any root" + ); Ok(None) } } #[cfg(test)] -pub mod test { +mod test { use std::str::FromStr; use aws_config::Region; @@ -586,7 +634,7 @@ pub mod test { let result = multisig_syncer .fetch_checkpoint(validator_addresses.as_slice(), threshold, index) .await - .unwrap(); + .expect("Failed to fetch checkpoint"); let expected = Some(generate_multisig_signed_checkpoint(&validators, checkpoint).await); assert_eq!(result, expected); diff --git a/rust/main/hyperlane-core/Cargo.toml b/rust/main/hyperlane-core/Cargo.toml index be715f38e15..e60b0c49ae0 100644 --- a/rust/main/hyperlane-core/Cargo.toml +++ b/rust/main/hyperlane-core/Cargo.toml @@ -1,4 +1,3 @@ -cargo-features = ["workspace-inheritance"] [package] name = "hyperlane-core" @@ -16,6 +15,7 @@ auto_impl.workspace = true bigdecimal.workspace = true borsh.workspace = true bs58.workspace = true +bech32.workspace = true bytes = { workspace = true, features = ["serde"] } config = { workspace = true, optional = true } convert_case.workspace = true @@ -48,6 +48,7 @@ solana-sdk = { workspace = true, optional = true } tiny-keccak = { workspace = true, features = ["keccak"] } uint.workspace = true uuid = { workspace = true, features = ["v4", "serde"] } +downcast-rs = { workspace = true } hyperlane-application = { path = "../applications/hyperlane-application" } diff --git a/rust/main/hyperlane-core/src/accumulator/incremental.rs b/rust/main/hyperlane-core/src/accumulator/incremental.rs index e71b9cbbe17..28203020ec3 100644 --- a/rust/main/hyperlane-core/src/accumulator/incremental.rs +++ b/rust/main/hyperlane-core/src/accumulator/incremental.rs @@ -32,7 +32,7 @@ impl IncrementalMerkle { pub fn ingest(&mut self, element: H256) { let mut node = element; assert!(self.count < u32::MAX as usize); - self.count += 1; + self.count = self.count.saturating_add(1); let mut size = self.count; for i in 0..TREE_DEPTH { if (size & 1) == 1 { @@ -69,7 +69,7 @@ impl IncrementalMerkle { /// Get the index pub fn index(&self) -> u32 { assert!(self.count > 0, "index is invalid when tree is empty"); - self.count as u32 - 1 + (self.count as u32).saturating_sub(1) } /// Get the leading-edge branch. diff --git a/rust/main/hyperlane-core/src/accumulator/merkle.rs b/rust/main/hyperlane-core/src/accumulator/merkle.rs index 0adc6603a54..092d5b3b7b7 100644 --- a/rust/main/hyperlane-core/src/accumulator/merkle.rs +++ b/rust/main/hyperlane-core/src/accumulator/merkle.rs @@ -168,15 +168,15 @@ impl MerkleTree { } _ => { // Split leaves into left and right subtrees - let subtree_capacity = 2usize.pow(depth as u32 - 1); + let subtree_capacity = 2usize.pow((depth as u32).saturating_sub(1)); let (left_leaves, right_leaves) = if leaves.len() <= subtree_capacity { (leaves, EMPTY_SLICE) } else { leaves.split_at(subtree_capacity) }; - let left_subtree = MerkleTree::create(left_leaves, depth - 1); - let right_subtree = MerkleTree::create(right_leaves, depth - 1); + let left_subtree = MerkleTree::create(left_leaves, depth.saturating_sub(1)); + let right_subtree = MerkleTree::create(right_leaves, depth.saturating_sub(1)); let hash = hash_concat(left_subtree.hash(), right_subtree.hash()); Node(hash, Box::new(left_subtree), Box::new(right_subtree)) @@ -201,27 +201,29 @@ impl MerkleTree { Node(ref mut hash, ref mut left, ref mut right) => { let left: &mut MerkleTree = &mut *left; let right: &mut MerkleTree = &mut *right; + + let depth_minus_1 = depth.saturating_sub(1); match (&*left, &*right) { // Tree is full (Leaf(_), Leaf(_)) => return Err(MerkleTreeError::MerkleTreeFull), // There is a right node so insert in right node - (Node(_, _, _), Node(_, _, _)) => right.push_leaf(elem, depth - 1)?, + (Node(_, _, _), Node(_, _, _)) => right.push_leaf(elem, depth_minus_1)?, // Both branches are zero, insert in left one (Zero(_), Zero(_)) => { - *left = MerkleTree::create(&[elem], depth - 1); + *left = MerkleTree::create(&[elem], depth_minus_1); } // Leaf on left branch and zero on right branch, insert on right side (Leaf(_), Zero(_)) => { - *right = MerkleTree::create(&[elem], depth - 1); + *right = MerkleTree::create(&[elem], depth_minus_1); } // Try inserting on the left node -> if it fails because it is full, insert in // right side. (Node(_, _, _), Zero(_)) => { - match left.push_leaf(elem, depth - 1) { + match left.push_leaf(elem, depth_minus_1) { Ok(_) => (), // Left node is full, insert in right node Err(MerkleTreeError::MerkleTreeFull) => { - *right = MerkleTree::create(&[elem], depth - 1); + *right = MerkleTree::create(&[elem], depth_minus_1); } Err(e) => return Err(e), }; @@ -240,7 +242,11 @@ impl MerkleTree { match *self { MerkleTree::Leaf(_) | MerkleTree::Zero(0) => None, MerkleTree::Node(_, ref l, ref r) => Some((l, r)), - MerkleTree::Zero(depth) => Some((&ZERO_NODES[depth - 1], &ZERO_NODES[depth - 1])), + MerkleTree::Zero(depth) => { + let depth_minus_1 = depth.saturating_sub(1); + + Some((&ZERO_NODES[depth_minus_1], &ZERO_NODES[depth_minus_1])) + } } } @@ -258,7 +264,7 @@ impl MerkleTree { let mut current_node = self; let mut current_depth = depth; while current_depth > 0 { - let ith_bit = (index >> (current_depth - 1)) & 0x01; + let ith_bit = (index >> (current_depth.saturating_sub(1))) & 0x01; // Note: unwrap is safe because leaves are only ever constructed at depth == 0. let (left, right) = current_node .left_and_right_branches() @@ -272,7 +278,7 @@ impl MerkleTree { proof.push(right.hash()); current_node = left; } - current_depth -= 1; + current_depth = current_depth.saturating_sub(1); } debug_assert_eq!(proof.len(), depth); diff --git a/rust/main/hyperlane-core/src/chain.rs b/rust/main/hyperlane-core/src/chain.rs index c8e244e7407..aacb0c166ff 100644 --- a/rust/main/hyperlane-core/src/chain.rs +++ b/rust/main/hyperlane-core/src/chain.rs @@ -29,7 +29,7 @@ pub struct ContractLocator<'a> { } #[cfg(feature = "strum")] -impl<'a> std::fmt::Display for ContractLocator<'a> { +impl std::fmt::Display for ContractLocator<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, @@ -91,7 +91,7 @@ impl<'de> Deserialize<'de> for ReorgPeriod { struct ReorgPeriodVisitor; - impl<'de> de::Visitor<'de> for ReorgPeriodVisitor { + impl de::Visitor<'_> for ReorgPeriodVisitor { type Value = ReorgPeriod; fn expecting(&self, f: &mut Formatter) -> std::fmt::Result { @@ -147,30 +147,23 @@ pub enum KnownHyperlaneDomain { Bob = 60808, Boba = 288, Botanix = 3637, - BounceBit = 6001, BSquared = 223, B3 = 8333, Celo = 42220, Cheesechain = 383353, ChilizMainnet = 1000088888, - Conflux = 1030, - Conwai = 668668, CoreDao = 1116, Corn = 21000000, Coti = 2632500, Cyber = 7560, DegenChain = 666666666, - DeepbrainChain = 19880818, DogeChain = 2000, - DuckChain = 5545, EclipseMainnet = 1408864445, EdgenChain = 4207, Everclear = 25327, - EvmOs = 9001, Endurance = 648, Ethereum = 1, Fantom = 250, - Flame = 253368190, Flare = 14, FlowMainnet = 1000000747, Fluence = 9999999, @@ -200,7 +193,6 @@ pub enum KnownHyperlaneDomain { Linea = 59144, Lisk = 1135, Lukso = 42, - Lumia = 994873017, LumiaPrism = 1000073017, MantaPacific = 169, Mantle = 5000, @@ -214,7 +206,6 @@ pub enum KnownHyperlaneDomain { Molten = 360, Moonbeam = 1284, Morph = 2818, - Nero = 1689, Neutron = 1853125230, Nibiru = 6900, Noble = 1313817164, @@ -232,12 +223,9 @@ pub enum KnownHyperlaneDomain { Prom = 227, ProofOfPlay = 70700, Rarichain = 1000012617, - Rivalz = 753, Ronin = 2020, - RootstockMainnet = 1000000030, Reactive = 1597, Redstone = 690, - Sanko = 1996, Sei = 1329, Scroll = 534352, Shibarium = 109, @@ -260,55 +248,40 @@ pub enum KnownHyperlaneDomain { Tac = 239, Taiko = 167000, Tangle = 5845, - Telos = 40, Torus = 21000, Treasure = 61166, - UnitZero = 88811, Unichain = 130, Vana = 1480, Viction = 88, - WeavevmTestnet = 9496, Worldchain = 480, StarknetMainnet = 23448592, Xai = 660279, Xlayer = 196, XrplEvm = 1440000, - Xpla = 37, Zetachain = 7000, Zeronetwork = 543210, - Zklink = 810180, Zksync = 324, Zircuit = 48900, ZoraMainnet = 7777777, // -- Test chains -- // - AbstractTestnet = 11124, - AlephZeroEvmTestnet = 2039, - Alfajores = 44787, ArbitrumSepolia = 421614, ArcadiaTestnet2 = 1098411886, AuroraTestnet = 1313161555, BasecampTestnet = 1000001114, BaseSepolia = 84532, - Bepolia = 80069, #[cfg_attr(feature = "strum", strum(serialize = "bsctestnet"))] BinanceSmartChainTestnet = 97, CarrchainTestnet = 76672, CelestiaTestnet = 1297040200, Chiado = 10200, - ChronicleYellowstone = 175188, CitreaTestnet = 5115, - ConnextSepolia = 6398, CotiTestnet = 7082400, EclipseTestnet = 239092742, - EcoTestnet = 471923, - FlameTestnet = 1660473773, - FormTestnet = 132902, Holesky = 17000, HyperLiquidEvmTestnet = 998, InfinityVmMonza = 96025, - InkSepolia = 763373, KyveTestnet = 1262571342, Matchain = 698, MegaEthTestnet = 6342, @@ -319,11 +292,9 @@ pub enum KnownHyperlaneDomain { NeuraTestnet = 267, NobleTestnet = 1196573006, KyveAlpha = 75898669, - OdysseyTestnet = 911867, OptimismSepolia = 11155420, ParadexSepolia = 12263410, PlumeTestnet = 161221135, - Plumetestnet2 = 98867, Polygonamoy = 80002, PolynomialFi = 1000008008, PragmaDevnet = 6363709, @@ -331,13 +302,9 @@ pub enum KnownHyperlaneDomain { Sepolia = 11155111, SolanaTestnet = 1399811150, SomniaTestnet = 50312, - SoneiumTestnet = 1946, SonicSvmTestnet = 15153042, - SonicBlaze = 57054, StarknetSepolia = 23448591, SubtensorTestnet = 945, - SuperpositionTestnet = 98985, - UnichainTestnet = 1301, // -- Local chains -- // @@ -368,7 +335,6 @@ pub enum HyperlaneDomain { }, } -#[cfg(any(test, feature = "test-utils"))] impl HyperlaneDomain { pub fn new_test_domain(name: &str) -> Self { Self::Unknown { @@ -425,6 +391,10 @@ pub enum HyperlaneDomainProtocol { Starknet, /// A Cosmos based chain with uses a module instead of a contract. CosmosNative, + /// A Kaspa-based chain type which uses hyperlane-kaspa. + Kaspa, + /// A Raidx based chain + Radix, } impl HyperlaneDomainProtocol { @@ -468,32 +438,21 @@ impl KnownHyperlaneDomain { use self::KnownHyperlaneDomain::*; match self { - AbstractTestnet - | AlephZeroEvmTestnet - | Alfajores - | ArbitrumSepolia + ArbitrumSepolia | ArcadiaTestnet2 | AuroraTestnet | BasecampTestnet | BaseSepolia - | Bepolia | BinanceSmartChainTestnet | CarrchainTestnet | CelestiaTestnet | Chiado - | ChronicleYellowstone | CitreaTestnet - | ConnextSepolia | CotiTestnet | EclipseTestnet - | EcoTestnet - | FlameTestnet - | FormTestnet - | Fuji | Holesky | HyperLiquidEvmTestnet | InfinityVmMonza - | InkSepolia | KyveTestnet | MegaEthTestnet | MilkywayTestnet @@ -502,28 +461,22 @@ impl KnownHyperlaneDomain { | MoonbaseAlpha | NeuraTestnet | NobleTestnet - | OdysseyTestnet | OptimismSepolia | ParadexSepolia | PlumeTestnet - | Plumetestnet2 | Polygonamoy | PragmaDevnet | ScrollSepolia | Sepolia | SolanaTestnet | SomniaTestnet - | SoneiumTestnet - | SonicBlaze | SonicSvmTestnet | StarknetSepolia | SubtensorTestnet - | SuperpositionTestnet - | UnichainTestnet - | WeavevmTestnet => HyperlaneDomainType::Testnet, + | KyveAlpha => HyperlaneDomainType::Testnet, Test1 | Test2 | Test3 | Test4 | FuelTest1 | SealevelTest1 | SealevelTest2 | CosmosTest99990 | CosmosTest99991 | CosmosTestNative1 | CosmosTestNative2 - | KyveAlpha | StarknetTest23448593 => HyperlaneDomainType::LocalTestChain, + | StarknetTest23448593 | StarknetTest23448594 => HyperlaneDomainType::LocalTestChain, _ => HyperlaneDomainType::Mainnet, } } @@ -577,29 +530,28 @@ impl KnownHyperlaneDomain { pub const fn domain_technical_stack(self) -> HyperlaneDomainTechnicalStack { use KnownHyperlaneDomain::*; match self { - AlephZeroEvmMainnet | AlephZeroEvmTestnet | ApeChain | AppChain | Arbitrum - | ArbitrumNova | ArbitrumSepolia | CarrchainTestnet | Cheesechain - | ChronicleYellowstone | ConnextSepolia | Conwai | Corn | DuckChain | Everclear - | Fluence | DegenChain | Galactica | Game7 | Gravity | InEvm | MiracleChain - | Molten | Plume | PlumeTestnet | Plumetestnet2 | ProofOfPlay | Rarichain | Rivalz - | Sanko | SuperpositionMainnet | SuperpositionTestnet | Xai => { + AlephZeroEvmMainnet | ApeChain | AppChain | Arbitrum | ArbitrumNova + | ArbitrumSepolia | CarrchainTestnet | Cheesechain | Corn | Everclear | Fluence + | DegenChain | Galactica | Game7 | Gravity | InEvm | MiracleChain | Molten | Plume + | PlumeTestnet | ProofOfPlay | Rarichain | SuperpositionMainnet | Xai => { HyperlaneDomainTechnicalStack::ArbitrumNitro } Ancient8 | Base | Blast | Bob | Boba | B3 | Celo | Cyber | Form | Fraxtal | Guru - | Ink | InkSepolia | Lisk | MantaPacific | Mantle | Matchain | Metal | Metis | Mint - | Mode | ModeTestnet | OpBnb | Optimism | Orderly | PolynomialFi | Redstone - | SnaxChain | Soneium | Superseed | Swell | Unichain | Worldchain | Zircuit - | ZoraMainnet => HyperlaneDomainTechnicalStack::OpStack, - DogeChain | Lumia | LumiaPrism | Katana | Merlin | PolygonZkEvm | Prom | Xlayer => { + | Ink | Lisk | MantaPacific | Mantle | Matchain | Metal | Metis | Mint | Mode + | ModeTestnet | OpBnb | Optimism | Orderly | PolynomialFi | Redstone | SnaxChain + | Soneium | Superseed | Swell | Unichain | Worldchain | Zircuit | ZoraMainnet => { + HyperlaneDomainTechnicalStack::OpStack + } + DogeChain | LumiaPrism | Katana | Merlin | PolygonZkEvm | Prom | Xlayer => { HyperlaneDomainTechnicalStack::PolygonCDK } - Astar | DeepbrainChain | Moonbeam | Peaq | Tangle | Torus => { + Astar | Moonbeam | Peaq | Tangle | Torus => { HyperlaneDomainTechnicalStack::PolkadotSubstrate } StarknetMainnet | StarknetTest23448593 | StarknetTest23448594 | PragmaDevnet => { HyperlaneDomainTechnicalStack::Starknet } - Abstract | AbstractTestnet | Sophon | Treasure | Zeronetwork | Zklink | Zksync => { + Abstract | Sophon | Treasure | Zeronetwork | Zksync => { HyperlaneDomainTechnicalStack::ZkSync } _ => HyperlaneDomainTechnicalStack::Other, @@ -789,8 +741,8 @@ impl HyperlaneDomain { use HyperlaneDomainProtocol::*; let protocol = self.domain_protocol(); match protocol { - Ethereum | Cosmos | CosmosNative | Starknet => IndexMode::Block, - Fuel | Sealevel => IndexMode::Sequence, + Ethereum | Cosmos | CosmosNative | Starknet | Kaspa => IndexMode::Block, + Fuel | Sealevel | Radix => IndexMode::Sequence, } } } diff --git a/rust/main/hyperlane-core/src/error.rs b/rust/main/hyperlane-core/src/error.rs index 89009dc8d00..91d92ae03d3 100644 --- a/rust/main/hyperlane-core/src/error.rs +++ b/rust/main/hyperlane-core/src/error.rs @@ -1,6 +1,7 @@ use std::any::Any; use std::error::Error as StdError; use std::fmt::{Debug, Display, Formatter}; +use std::num::TryFromIntError; use std::ops::Deref; use bigdecimal::ParseBigDecimalError; @@ -159,6 +160,9 @@ pub enum ChainCommunicationError { /// Invalid reorg period #[error("Invalid reorg period: {0:?}")] InvalidReorgPeriod(ReorgPeriod), + /// Convert Integer Error + #[error("{0}")] + TryFromIntError(#[from] TryFromIntError), } impl ChainCommunicationError { diff --git a/rust/main/hyperlane-core/src/lib.rs b/rust/main/hyperlane-core/src/lib.rs index b92b1b48794..a37af4bdd1b 100644 --- a/rust/main/hyperlane-core/src/lib.rs +++ b/rust/main/hyperlane-core/src/lib.rs @@ -4,8 +4,8 @@ #![warn(missing_docs)] #![deny(unsafe_code)] #![allow(unknown_lints)] // TODO: `rustc` 1.80.1 clippy issue -#![forbid(where_clauses_object_safety)] #![deny(clippy::unwrap_used, clippy::panic)] +#![deny(clippy::arithmetic_side_effects)] extern crate core; diff --git a/rust/main/hyperlane-core/src/rpc_clients/fallback.rs b/rust/main/hyperlane-core/src/rpc_clients/fallback.rs index ec809a7396a..2f1ac4515b8 100644 --- a/rust/main/hyperlane-core/src/rpc_clients/fallback.rs +++ b/rust/main/hyperlane-core/src/rpc_clients/fallback.rs @@ -195,7 +195,7 @@ where pub async fn handle_failed_provider(&self, priority: &PrioritizedProviderInner) { self.increment_failed_count(priority.index).await; - if priority.last_failed_count + 1 >= FAILED_REQUEST_THRESHOLD { + if priority.last_failed_count.saturating_add(1) >= FAILED_REQUEST_THRESHOLD { let new_priority = priority.reset_failed_count(); self.deprioritize_provider(new_priority).await; info!( @@ -210,7 +210,7 @@ where let mut priorities = self.inner.priorities.write().await; if let Some(priority) = priorities.iter_mut().find(|p| p.index == index) { - priority.last_failed_count += 1; + priority.last_failed_count = priority.last_failed_count.saturating_add(1); } } @@ -396,7 +396,7 @@ pub mod test { } #[tokio::test] - pub async fn test_deprioritization_by_failed_count() { + async fn test_deprioritization_by_failed_count() { let provider1 = ProviderMock::new(None); let provider2 = ProviderMock::new(None); let provider3 = ProviderMock::new(None); diff --git a/rust/main/hyperlane-core/src/rpc_clients/retry.rs b/rust/main/hyperlane-core/src/rpc_clients/retry.rs index fd5f407e073..abc729e41ed 100644 --- a/rust/main/hyperlane-core/src/rpc_clients/retry.rs +++ b/rust/main/hyperlane-core/src/rpc_clients/retry.rs @@ -1,7 +1,7 @@ use futures::Future; use std::{pin::Pin, time::Duration}; use tokio::time::sleep; -use tracing::{instrument, warn}; +use tracing::{debug, instrument}; use crate::{ChainCommunicationError, ChainResult}; @@ -23,7 +23,7 @@ pub async fn call_and_retry_n_times( match f().await { Ok(res) => return Ok(res), Err(err) => { - warn!(retries=retry_number, error=?err, "Retrying call"); + debug!(retries=retry_number, error=?err, "Retrying call"); sleep(rpc_retry_sleep_duration.unwrap_or(RPC_RETRY_SLEEP_DURATION)).await; } } diff --git a/rust/main/hyperlane-core/src/traits/db.rs b/rust/main/hyperlane-core/src/traits/db.rs index 6bef9781d9c..ea09fa40d07 100644 --- a/rust/main/hyperlane-core/src/traits/db.rs +++ b/rust/main/hyperlane-core/src/traits/db.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use auto_impl::auto_impl; use eyre::Result; -use crate::{Indexed, LogMeta}; +use crate::{Indexed, LogMeta, H256}; /// Interface for a HyperlaneLogStore that ingests logs. #[async_trait] @@ -57,3 +57,58 @@ pub trait HyperlaneWatermarkedLogStore: HyperlaneLogStore { /// Stores the block number high watermark async fn store_high_watermark(&self, block_number: u32) -> Result<()>; } + +/// Trait for Kaspa-specific database operations (deposits/withdrawals tracking) +/// This trait is defined in hyperlane-core to avoid circular dependencies between +/// dymension-kaspa and hyperlane-base. +#[auto_impl(&, Box, Arc)] +pub trait KaspaDb: Send + Sync + Debug { + /// Store a withdrawal message indexed by message_id + fn store_withdrawal_message(&self, message: crate::HyperlaneMessage) -> Result<()>; + + /// Retrieve a withdrawal message by message_id + fn retrieve_kaspa_withdrawal_by_message_id( + &self, + message_id: &H256, + ) -> Result>; + + /// Store a deposit message indexed by both message_id and tx_hash + fn store_deposit_message( + &self, + message: crate::HyperlaneMessage, + kaspa_tx_id: String, + ) -> Result<()>; + + /// Retrieve a deposit message by message_id + fn retrieve_kaspa_deposit_by_message_id( + &self, + message_id: &H256, + ) -> Result>; + + /// Retrieve a deposit message by kaspa transaction hash + fn retrieve_kaspa_deposit_by_tx_hash( + &self, + hub_tx_id: &str, + ) -> Result>; + + /// Store Hub transaction ID for a deposit indexed by kaspa_tx + fn store_deposit_hub_tx(&self, kaspa_tx_id: &str, hub_tx: &H256) -> Result<()>; + + /// Retrieve Hub transaction ID for a deposit by kaspa_tx + fn retrieve_deposit_hub_tx(&self, kaspa_tx_id: &str) -> Result>; + + /// Store Kaspa transaction ID for a withdrawal indexed by message_id + fn store_withdrawal_kaspa_tx(&self, message_id: &H256, kaspa_tx: &str) -> Result<()>; + + /// Retrieve Kaspa transaction ID for a withdrawal by message_id + fn retrieve_withdrawal_kaspa_tx(&self, message_id: &H256) -> Result>; + + /// Update a deposit by storing the HyperlaneMessage and hub_tx + /// This is called after we successfully submit the deposit to the Hub + fn update_processed_deposit( + &self, + kaspa_tx_id: &str, + message: crate::HyperlaneMessage, + hub_tx: &H256, + ) -> Result<()>; +} diff --git a/rust/main/hyperlane-core/src/traits/encode.rs b/rust/main/hyperlane-core/src/traits/encode.rs index bc922c4273f..f35ae8cde5b 100644 --- a/rust/main/hyperlane-core/src/traits/encode.rs +++ b/rust/main/hyperlane-core/src/traits/encode.rs @@ -213,9 +213,9 @@ impl Encode for GasPaymentKey { where W: std::io::Write, { - let mut written = 0; - written += self.message_id.write_to(writer)?; - written += self.destination.write_to(writer)?; + let mut written: usize = 0; + written = written.saturating_add(self.message_id.write_to(writer)?); + written = written.saturating_add(self.destination.write_to(writer)?); Ok(written) } } @@ -238,11 +238,11 @@ impl Encode for InterchainGasPayment { where W: std::io::Write, { - let mut written = 0; - written += self.message_id.write_to(writer)?; - written += self.destination.write_to(writer)?; - written += self.payment.write_to(writer)?; - written += self.gas_amount.write_to(writer)?; + let mut written: usize = 0; + written = written.saturating_add(self.message_id.write_to(writer)?); + written = written.saturating_add(self.destination.write_to(writer)?); + written = written.saturating_add(self.payment.write_to(writer)?); + written = written.saturating_add(self.gas_amount.write_to(writer)?); Ok(written) } } @@ -269,17 +269,17 @@ impl Encode for Indexed { where W: std::io::Write, { - let mut written = 0; - written += self.inner().write_to(writer)?; + let mut written: usize = 0; + written = written.saturating_add(self.inner().write_to(writer)?); match self.sequence { Some(sequence) => { let sequence_is_defined = true; - written += sequence_is_defined.write_to(writer)?; - written += sequence.write_to(writer)?; + written = written.saturating_add(sequence_is_defined.write_to(writer)?); + written = written.saturating_add(sequence.write_to(writer)?); } None => { let sequence_is_defined = false; - written += sequence_is_defined.write_to(writer)?; + written = written.saturating_add(sequence_is_defined.write_to(writer)?); } } Ok(written) @@ -308,14 +308,14 @@ impl Encode for Vec { where W: std::io::Write, { - let mut written = 0; + let mut written: usize = 0; // Write the length of the vector as a u32 - written += (self.len() as u64).write_to(writer)?; + written = written.saturating_add((self.len() as u64).write_to(writer)?); // Write each `T` in the vector using its `Encode` implementation - written += self.iter().try_fold(0, |acc, item| { - item.write_to(writer).map(|bytes| acc + bytes) - })?; + written = written.saturating_add(self.iter().try_fold(0usize, |acc, item| { + item.write_to(writer).map(|bytes| acc.saturating_add(bytes)) + })?); Ok(written) } } diff --git a/rust/main/hyperlane-core/src/traits/interchain_security_module.rs b/rust/main/hyperlane-core/src/traits/interchain_security_module.rs index dcf21d7b51d..06e20150785 100644 --- a/rust/main/hyperlane-core/src/traits/interchain_security_module.rs +++ b/rust/main/hyperlane-core/src/traits/interchain_security_module.rs @@ -42,6 +42,8 @@ pub enum ModuleType { Null, /// Ccip Read ISM (accepts offchain signature information) CcipRead, + /// Kaspa ISM (used for kaspa) + KaspaMultisig, } impl ModuleType { @@ -56,6 +58,7 @@ impl ModuleType { Self::MessageIdMultisig => "message_id_multisig", Self::Null => "null", Self::CcipRead => "ccip_read", + Self::KaspaMultisig => "kaspa_multisig", } } } diff --git a/rust/main/hyperlane-core/src/traits/mailbox.rs b/rust/main/hyperlane-core/src/traits/mailbox.rs index afffc69f988..d017538be03 100644 --- a/rust/main/hyperlane-core/src/traits/mailbox.rs +++ b/rust/main/hyperlane-core/src/traits/mailbox.rs @@ -2,6 +2,7 @@ use std::fmt::Debug; use async_trait::async_trait; use derive_new::new; +use downcast_rs::{impl_downcast, DowncastSync}; use crate::{ traits::TxOutcome, utils::domain_hash, ChainCommunicationError, ChainResult, HyperlaneContract, @@ -11,7 +12,7 @@ use crate::{ /// Interface for the Mailbox chain contract. Allows abstraction over different /// chains #[async_trait] -pub trait Mailbox: HyperlaneContract + Send + Sync + Debug { +pub trait Mailbox: HyperlaneContract + Send + Sync + Debug + DowncastSync { /// Return the domain hash fn domain_hash(&self) -> H256 { domain_hash(self.address(), self.domain().id()) @@ -76,6 +77,8 @@ pub trait Mailbox: HyperlaneContract + Send + Sync + Debug { fn delivered_calldata(&self, message_id: H256) -> ChainResult>>; } +impl_downcast!(sync Mailbox); + /// The result of processing a batch of messages #[derive(new, Debug)] pub struct BatchResult { diff --git a/rust/main/hyperlane-core/src/traits/mod.rs b/rust/main/hyperlane-core/src/traits/mod.rs index d5faa10aaea..ed699fc03bf 100644 --- a/rust/main/hyperlane-core/src/traits/mod.rs +++ b/rust/main/hyperlane-core/src/traits/mod.rs @@ -61,11 +61,11 @@ impl From for TxOutcome { .status .map(|status| status.low_u32() == 1) .unwrap_or(false), - gas_used: t.gas_used.map(Into::into).unwrap_or(U256::zero()), + gas_used: t.gas_used.map(Into::into).unwrap_or_default(), gas_price: t .effective_gas_price .and_then(|price| U256::from(price).try_into().ok()) - .unwrap_or(FixedPointNumber::zero()), + .unwrap_or_default(), } } } diff --git a/rust/main/hyperlane-core/src/traits/pending_operation.rs b/rust/main/hyperlane-core/src/traits/pending_operation.rs index 0fdace9c4b4..9fd2ca6e500 100644 --- a/rust/main/hyperlane-core/src/traits/pending_operation.rs +++ b/rust/main/hyperlane-core/src/traits/pending_operation.rs @@ -3,6 +3,7 @@ use std::{ env, fmt::{Debug, Display}, io::Write, + ops::Mul, sync::Arc, time::{Duration, Instant}, }; @@ -207,8 +208,8 @@ impl Encode for PendingOperationStatus { W: Write, { // Serialize to JSON and write to the writer, to avoid having to implement the encoding manually - let serialized = serde_json::to_vec(self) - .map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "Failed to serialize"))?; + let serialized = + serde_json::to_vec(self).map_err(|_| std::io::Error::other("Failed to serialize"))?; writer.write(&serialized) } } @@ -221,10 +222,10 @@ impl Decode for PendingOperationStatus { { // Deserialize from JSON and read from the reader, to avoid having to implement the encoding / decoding manually serde_json::from_reader(reader).map_err(|err| { - HyperlaneProtocolError::IoError(std::io::Error::new( - std::io::ErrorKind::Other, - format!("Failed to deserialize. Error: {}", err), - )) + HyperlaneProtocolError::IoError(std::io::Error::other(format!( + "Failed to deserialize. Error: {}", + err + ))) }) } } @@ -234,6 +235,9 @@ impl Decode for PendingOperationStatus { /// WARNING: This enum is serialized to JSON and stored in the database, so to keep backwards compatibility, we shouldn't remove or rename any variants. /// Adding new variants is fine. pub enum ReprepareReason { + #[strum(to_string = "Manual retry")] + /// Failed to create payload success criteria + Manual, #[strum(to_string = "Error checking message delivery status")] /// Error checking message delivery status ErrorCheckingDeliveryStatus, @@ -336,7 +340,8 @@ pub fn gas_used_by_operation( let gas_used_by_tx = FixedPointNumber::try_from(tx_outcome.gas_used)?; let operation_gas_estimate = FixedPointNumber::try_from(operation_estimated_cost)?; let tx_gas_estimate = FixedPointNumber::try_from(tx_estimated_cost)?; - let gas_used_by_operation = (gas_used_by_tx * operation_gas_estimate) + let gas_used_by_operation = gas_used_by_tx + .mul(operation_gas_estimate) .checked_div(&tx_gas_estimate) .ok_or(eyre::eyre!("Division by zero"))?; gas_used_by_operation.try_into() @@ -369,6 +374,7 @@ impl PartialEq for QueueOperation { impl Eq for QueueOperation {} +#[allow(clippy::unnecessary_map_or)] // can't use `.is_ok_and` because it still unstables in `sbf` impl Ord for QueueOperation { fn cmp(&self, other: &Self) -> Ordering { use Ordering::*; diff --git a/rust/main/hyperlane-core/src/traits/provider.rs b/rust/main/hyperlane-core/src/traits/provider.rs index 63b4d01dd9f..6f7ff329e05 100644 --- a/rust/main/hyperlane-core/src/traits/provider.rs +++ b/rust/main/hyperlane-core/src/traits/provider.rs @@ -4,6 +4,8 @@ use async_trait::async_trait; use auto_impl::auto_impl; use thiserror::Error; +use downcast_rs::{impl_downcast, DowncastSync}; + use crate::{BlockInfo, ChainInfo, ChainResult, HyperlaneChain, TxnInfo, H256, H512, U256}; /// Interface for a provider. Allows abstraction over different provider types @@ -15,7 +17,7 @@ use crate::{BlockInfo, ChainInfo, ChainResult, HyperlaneChain, TxnInfo, H256, H5 /// the context of a contract. #[async_trait] #[auto_impl(&, Box, Arc)] -pub trait HyperlaneProvider: HyperlaneChain + Send + Sync + Debug { +pub trait HyperlaneProvider: HyperlaneChain + Send + Sync + Debug + DowncastSync { /// Get block info for a given block height async fn get_block_by_height(&self, height: u64) -> ChainResult; @@ -32,6 +34,8 @@ pub trait HyperlaneProvider: HyperlaneChain + Send + Sync + Debug { async fn get_chain_metrics(&self) -> ChainResult>; } +impl_downcast!(sync HyperlaneProvider); + /// Errors when querying for provider information. #[derive(Error, Debug)] pub enum HyperlaneProviderError { diff --git a/rust/main/hyperlane-core/src/types/conversions.rs b/rust/main/hyperlane-core/src/types/conversions.rs index 5336e0e2bf2..22ec16acf6b 100644 --- a/rust/main/hyperlane-core/src/types/conversions.rs +++ b/rust/main/hyperlane-core/src/types/conversions.rs @@ -50,7 +50,7 @@ pub fn bytes_to_h512(data: &[u8]) -> H512 { } let mut buf = [0; 64]; - buf[64 - data.len()..64].copy_from_slice(data); + buf[64usize.saturating_sub(data.len())..64].copy_from_slice(data); H512::from_slice(&buf) } diff --git a/rust/main/hyperlane-core/src/types/merkle_tree.rs b/rust/main/hyperlane-core/src/types/merkle_tree.rs index c64c533a843..c1fee8f365e 100644 --- a/rust/main/hyperlane-core/src/types/merkle_tree.rs +++ b/rust/main/hyperlane-core/src/types/merkle_tree.rs @@ -27,7 +27,11 @@ impl Encode for MerkleTreeInsertion { where W: Write, { - Ok(self.leaf_index.write_to(writer)? + self.message_id.write_to(writer)?) + let written: usize = self + .leaf_index + .write_to(writer)? + .saturating_add(self.message_id.write_to(writer)?); + Ok(written) } } diff --git a/rust/main/hyperlane-core/src/types/message.rs b/rust/main/hyperlane-core/src/types/message.rs index 7f9ed35d15d..741011eacbb 100644 --- a/rust/main/hyperlane-core/src/types/message.rs +++ b/rust/main/hyperlane-core/src/types/message.rs @@ -118,7 +118,7 @@ impl Encode for HyperlaneMessage { writer.write_all(&self.destination.to_be_bytes())?; writer.write_all(self.recipient.as_ref())?; writer.write_all(&self.body)?; - Ok(HYPERLANE_MESSAGE_PREFIX_LEN + self.body.len()) + Ok(HYPERLANE_MESSAGE_PREFIX_LEN.saturating_add(self.body.len())) } } @@ -166,3 +166,28 @@ impl HyperlaneMessage { H256::from_slice(Keccak256::new().chain(self.to_vec()).finalize().as_slice()) } } + +#[cfg(test)] +mod tests { + use super::HyperlaneMessage; + + #[ignore] + #[test] + fn test_decode_from_raw_body() { + let raw = "0x03000005C50000044D0000000000000000000000007DAC480D20F322D2EF108A59A465CCB5749371C40000A86A0000000000000000000000007DAC480D20F322D2EF108A59A465CCB5749371C40000000000000000000000007566176716A55DAD1B4E83D0E2273FB95049483E0000000000000000000000000000000000000000000000000000000008F0D3EC"; + + let raw_bytes = hex::decode(&raw[2..]).unwrap(); + let msg = HyperlaneMessage::try_from(raw_bytes).unwrap(); + + eprintln!( + r#"Message ID: {:x} +Message Version: {} +Message: {} +Message Body: {:?}"#, + msg.id(), + msg.version, + msg, + msg.body + ); + } +} diff --git a/rust/main/hyperlane-core/src/types/mod.rs b/rust/main/hyperlane-core/src/types/mod.rs index 39689c3f270..9117d405672 100644 --- a/rust/main/hyperlane-core/src/types/mod.rs +++ b/rust/main/hyperlane-core/src/types/mod.rs @@ -42,6 +42,7 @@ mod transaction; /// Unified 32-byte identifier with convenience tooling for handling /// 20-byte ids (e.g ethereum addresses) pub mod identifiers; +#[allow(clippy::arithmetic_side_effects)] mod primitive_types; // Copied from https://github.com/hyperlane-xyz/ethers-rs/blob/hyperlane/ethers-core/src/types/signature.rs#L54 @@ -190,8 +191,8 @@ impl Add for InterchainGasPayment { Self { message_id: self.message_id, destination: self.destination, - payment: self.payment + rhs.payment, - gas_amount: self.gas_amount + rhs.gas_amount, + payment: self.payment.saturating_add(rhs.payment), + gas_amount: self.gas_amount.saturating_add(rhs.gas_amount), } } } @@ -206,8 +207,8 @@ impl Add for InterchainGasExpenditure { ); Self { message_id: self.message_id, - tokens_used: self.tokens_used + rhs.tokens_used, - gas_used: self.gas_used + rhs.gas_used, + tokens_used: self.tokens_used.saturating_add(rhs.tokens_used), + gas_used: self.gas_used.saturating_add(rhs.gas_used), } } } @@ -226,9 +227,9 @@ impl Encode for InterchainGasPaymentMeta { where W: Write, { - let mut written = 0; - written += self.transaction_id.write_to(writer)?; - written += self.log_index.write_to(writer)?; + let mut written: usize = 0; + written = written.saturating_add(self.transaction_id.write_to(writer)?); + written = written.saturating_add(self.log_index.write_to(writer)?); Ok(written) } } diff --git a/rust/main/hyperlane-core/src/types/primitive_types.rs b/rust/main/hyperlane-core/src/types/primitive_types.rs index 648119cddc6..32686c86e42 100644 --- a/rust/main/hyperlane-core/src/types/primitive_types.rs +++ b/rust/main/hyperlane-core/src/types/primitive_types.rs @@ -2,6 +2,7 @@ #![allow(clippy::assign_op_pattern)] #![allow(clippy::reversed_empty_ranges)] +#![allow(clippy::manual_div_ceil)] use std::{ ops::{Div, Mul}, @@ -33,7 +34,7 @@ construct_uint! { construct_uint! { /// 256-bit unsigned integer. #[derive(BorshSerialize, BorshDeserialize)] - pub struct U256(4); + pub struct U256(4); } construct_uint! { @@ -45,6 +46,7 @@ construct_uint! { mod fixed_hashes { // we can't change how they made the macro, so ignore the lint #![allow(clippy::non_canonical_clone_impl)] + #![allow(unexpected_cfgs)] use borsh::{BorshDeserialize, BorshSerialize}; use fixed_hash::construct_fixed_hash; diff --git a/rust/main/hyperlane-core/src/types/serialize.rs b/rust/main/hyperlane-core/src/types/serialize.rs index 73558e10ca8..c7d26463e3d 100644 --- a/rust/main/hyperlane-core/src/types/serialize.rs +++ b/rust/main/hyperlane-core/src/types/serialize.rs @@ -37,12 +37,13 @@ pub fn to_hex(bytes: &[u8], skip_leading_zero: bool) -> String { bytes }; - let mut slice = vec![0u8; (bytes.len() + 1) * 2]; + let slice_len = bytes.len().saturating_add(1).saturating_mul(2); + let mut slice = vec![0u8; slice_len]; to_hex_raw(&mut slice, bytes, skip_leading_zero).into() } fn to_hex_raw<'a>(v: &'a mut [u8], bytes: &[u8], skip_leading_zero: bool) -> &'a str { - assert!(v.len() > 1 + bytes.len() * 2); + assert!(v.len() > bytes.len().saturating_mul(2).saturating_add(1)); v[0] = b'0'; v[1] = b'x'; @@ -51,15 +52,15 @@ fn to_hex_raw<'a>(v: &'a mut [u8], bytes: &[u8], skip_leading_zero: bool) -> &'a let first_nibble = bytes[0] >> 4; if first_nibble != 0 || !skip_leading_zero { v[idx] = CHARS[first_nibble as usize]; - idx += 1; + idx = idx.saturating_add(1); } v[idx] = CHARS[(bytes[0] & 0xf) as usize]; - idx += 1; + idx = idx.saturating_add(1); for &byte in bytes.iter().skip(1) { v[idx] = CHARS[(byte >> 4) as usize]; - v[idx + 1] = CHARS[(byte & 0xf) as usize]; - idx += 2; + v[idx.saturating_add(1)] = CHARS[(byte & 0xf) as usize]; + idx = idx.saturating_add(2); } // SAFETY: all characters come either from CHARS or "0x", therefore valid UTF8 @@ -102,10 +103,12 @@ impl fmt::Display for FromHexError { /// Decode given (both 0x-prefixed or not) hex string into a vector of bytes. /// /// Returns an error if non-hex characters are present. +#[allow(clippy::manual_div_ceil)] // can't use `.div_ceil` because it still unstables in `sbf` pub fn from_hex(v: &str) -> Result, FromHexError> { let (v, stripped) = v.strip_prefix("0x").map_or((v, false), |v| (v, true)); - let mut bytes = vec![0u8; (v.len() + 1) / 2]; + let bytes_len = v.len().saturating_add(1).saturating_div(2); + let mut bytes = vec![0u8; bytes_len]; from_hex_raw(v, &mut bytes, stripped)?; Ok(bytes) } @@ -114,6 +117,7 @@ pub fn from_hex(v: &str) -> Result, FromHexError> { /// Used internally by `from_hex` and `deserialize_check_len`. /// /// The method will panic if `bytes` have incorrect length (make sure to allocate enough beforehand). +#[allow(clippy::arithmetic_side_effects)] fn from_hex_raw(v: &str, bytes: &mut [u8], stripped: bool) -> Result { let bytes_len = v.len(); let mut modulus = bytes_len % 2; @@ -134,16 +138,16 @@ fn from_hex_raw(v: &str, bytes: &mut [u8], stripped: bool) -> Result(bytes: &[u8], serializer: S) -> Result where S: Serializer, { - let mut slice = vec![0u8; (bytes.len() + 1) * 2]; + let slice_len = bytes.len().saturating_add(1).saturating_mul(2); + let mut slice = vec![0u8; slice_len]; serialize_raw(&mut slice, bytes, serializer) } @@ -196,7 +201,7 @@ pub enum ExpectedLen<'a> { Between(usize, &'a mut [u8]), } -impl<'a> fmt::Display for ExpectedLen<'a> { +impl fmt::Display for ExpectedLen<'_> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match *self { ExpectedLen::Exact(ref v) => write!(fmt, "{} bytes", v.len()), @@ -271,7 +276,7 @@ where len: ExpectedLen<'a>, } - impl<'a, 'b> de::Visitor<'b> for Visitor<'a> { + impl<'b> de::Visitor<'b> for Visitor<'_> { type Value = usize; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -287,8 +292,10 @@ where let len = v.len(); let is_len_valid = match self.len { - ExpectedLen::Exact(ref slice) => len == 2 * slice.len(), - ExpectedLen::Between(min, ref slice) => len <= 2 * slice.len() && len > 2 * min, + ExpectedLen::Exact(ref slice) => len == slice.len().saturating_mul(2), + ExpectedLen::Between(min, ref slice) => { + len <= slice.len().saturating_mul(2) && len > min.saturating_mul(2) + } }; if !is_len_valid { diff --git a/rust/main/hyperlane-core/src/utils.rs b/rust/main/hyperlane-core/src/utils.rs index 84142103511..67882d44cb2 100644 --- a/rust/main/hyperlane-core/src/utils.rs +++ b/rust/main/hyperlane-core/src/utils.rs @@ -8,7 +8,7 @@ use std::time::Duration; use crate::{KnownHyperlaneDomain, H160, H256, U256}; /// Converts a hex or base58 string to an H256. -pub fn hex_or_base58_to_h256(string: &str) -> Result { +pub fn hex_or_base58_or_bech32_to_h256(string: &str) -> Result { let h256 = if string.starts_with("0x") { match string.len() { 66 => H256::from_str(string)?, @@ -16,11 +16,24 @@ pub fn hex_or_base58_to_h256(string: &str) -> Result { _ => eyre::bail!("Invalid hex string"), } } else { - let bytes = bs58::decode(string).into_vec()?; - if bytes.len() != 32 { - eyre::bail!("Invalid length of base58 string") + let bytes = bech32::decode(string); + if let Ok((_, bytes)) = bytes { + if bytes.len() > 32 { + eyre::bail!("Invalid length of bech32 string") + } + // We pad bech32 address to be 32 bytes long + let padded: Vec = (0..32usize.saturating_sub(bytes.len())) + .map(|_| 0u8) + .chain(bytes.iter().cloned()) + .collect(); + H256::from_slice(padded.as_slice()) + } else { + let bytes = bs58::decode(string).into_vec()?; + if bytes.len() != 32 { + eyre::bail!("Invalid length of base58 string") + } + H256::from_slice(bytes.as_slice()) } - H256::from_slice(bytes.as_slice()) }; Ok(h256) @@ -68,7 +81,7 @@ const ATTO_EXPONENT: u32 = 18; /// Converts `value` expressed with `decimals` into `atto` (`10^-18`) decimals. pub fn to_atto(value: U256, decimals: u32) -> Option { assert!(decimals <= ATTO_EXPONENT); - let exponent = ATTO_EXPONENT - decimals; + let exponent = ATTO_EXPONENT.saturating_sub(decimals); let coefficient = U256::from(10u128.pow(exponent)); value.checked_mul(coefficient) } diff --git a/rust/main/hyperlane-metric/src/lib.rs b/rust/main/hyperlane-metric/src/lib.rs index d8ef401b619..775027ed4b6 100644 --- a/rust/main/hyperlane-metric/src/lib.rs +++ b/rust/main/hyperlane-metric/src/lib.rs @@ -2,6 +2,7 @@ #![warn(missing_docs)] #![deny(clippy::unwrap_used, clippy::panic)] +#![deny(clippy::arithmetic_side_effects)] /// Prometheus metric related code pub mod prometheus_metric; diff --git a/rust/main/hyperlane-metric/src/prometheus_metric.rs b/rust/main/hyperlane-metric/src/prometheus_metric.rs index e1e434b9f63..fdfc53be452 100644 --- a/rust/main/hyperlane-metric/src/prometheus_metric.rs +++ b/rust/main/hyperlane-metric/src/prometheus_metric.rs @@ -113,7 +113,7 @@ impl PrometheusClientMetrics { if let Some(counter) = &self.request_duration_seconds { counter .with(&labels) - .inc_by((Instant::now() - start).as_secs_f64()) + .inc_by((Instant::now().saturating_duration_since(start)).as_secs_f64()) }; } } diff --git a/rust/main/hyperlane-test/src/lib.rs b/rust/main/hyperlane-test/src/lib.rs index aa76ffae5c0..b0f78d53572 100644 --- a/rust/main/hyperlane-test/src/lib.rs +++ b/rust/main/hyperlane-test/src/lib.rs @@ -3,7 +3,7 @@ #![forbid(unsafe_code)] #![cfg_attr(test, warn(missing_docs))] #![allow(unknown_lints)] // TODO: `rustc` 1.80.1 clippy issue -#![forbid(where_clauses_object_safety)] +#![deny(clippy::arithmetic_side_effects)] /// Mock contracts pub mod mocks; diff --git a/rust/main/hyperlane-test/src/mocks/mailbox.rs b/rust/main/hyperlane-test/src/mocks/mailbox.rs index 50a775eeffe..8985abd917a 100644 --- a/rust/main/hyperlane-test/src/mocks/mailbox.rs +++ b/rust/main/hyperlane-test/src/mocks/mailbox.rs @@ -5,63 +5,71 @@ use mockall::*; use hyperlane_core::{accumulator::incremental::IncrementalMerkle, *}; -mock! { - pub MailboxContract { - // Mailbox - pub fn _address(&self) -> H256 {} +pub use mock_mailbox_contract::MockMailboxContract; - pub fn _domain(&self) -> &HyperlaneDomain {} +mod mock_mailbox_contract { - pub fn _provider(&self) -> Box {} + #![allow(missing_docs)] + use super::*; - pub fn _domain_hash(&self) -> H256 {} + mock! { + pub MailboxContract { + // Mailbox + pub fn _address(&self) -> H256 {} - pub fn _raw_message_by_id( - &self, - leaf: H256, - ) -> ChainResult> {} + pub fn _domain(&self) -> &HyperlaneDomain {} - pub fn _id_by_nonce( - &self, - nonce: usize, - ) -> ChainResult> {} + pub fn _provider(&self) -> Box {} - pub fn _tree(&self, reorg_period: &ReorgPeriod) -> ChainResult {} + pub fn _domain_hash(&self) -> H256 {} - pub fn _count(&self, reorg_period: &ReorgPeriod) -> ChainResult {} + pub fn _raw_message_by_id( + &self, + leaf: H256, + ) -> ChainResult> {} - pub fn _latest_checkpoint(&self, reorg_period: &ReorgPeriod) -> ChainResult {} + pub fn _id_by_nonce( + &self, + nonce: usize, + ) -> ChainResult> {} - pub fn _default_ism(&self) -> ChainResult {} - pub fn _recipient_ism(&self, recipient: H256) -> ChainResult {} + pub fn _tree(&self, reorg_period: &ReorgPeriod) -> ChainResult {} - pub fn _delivered(&self, id: H256) -> ChainResult {} + pub fn _count(&self, reorg_period: &ReorgPeriod) -> ChainResult {} - pub fn process( - &self, - message: &HyperlaneMessage, - metadata: &[u8], - tx_gas_limit: Option, - ) -> ChainResult {} + pub fn _latest_checkpoint(&self, reorg_period: &ReorgPeriod) -> ChainResult {} - pub fn process_estimate_costs( - &self, - message: &HyperlaneMessage, - metadata: &[u8], - ) -> ChainResult {} + pub fn _default_ism(&self) -> ChainResult {} + pub fn _recipient_ism(&self, recipient: H256) -> ChainResult {} - pub fn process_calldata( - &self, - message: &HyperlaneMessage, - metadata: &[u8], - ) -> Vec {} + pub fn _delivered(&self, id: H256) -> ChainResult {} - pub fn process_batch<'a>( - &self, - ops: Vec<&'a QueueOperation>, - ) -> ChainResult {} + pub fn process( + &self, + message: &HyperlaneMessage, + metadata: &[u8], + tx_gas_limit: Option, + ) -> ChainResult {} - pub fn supports_batching(&self) -> bool { + pub fn process_estimate_costs( + &self, + message: &HyperlaneMessage, + metadata: &[u8], + ) -> ChainResult {} + + pub fn process_calldata( + &self, + message: &HyperlaneMessage, + metadata: &[u8], + ) -> Vec {} + + pub fn process_batch<'a>( + &self, + ops: Vec<&'a QueueOperation>, + ) -> ChainResult {} + + pub fn supports_batching(&self) -> bool { + } } } } @@ -145,6 +153,7 @@ impl HyperlaneContract for MockMailboxContract { } impl MockMailboxContract { + /// Create a new mock mailbox contract with a default ISM pub fn new_with_default_ism(default_ism: H256) -> Self { let mut mock = Self::new(); mock.expect__default_ism() diff --git a/rust/main/hyperlane-test/src/mocks/mod.rs b/rust/main/hyperlane-test/src/mocks/mod.rs index 930ee0059f7..a1177e6d180 100644 --- a/rust/main/hyperlane-test/src/mocks/mod.rs +++ b/rust/main/hyperlane-test/src/mocks/mod.rs @@ -1,5 +1,6 @@ /// Mock mailbox contract pub mod mailbox; +/// Mock validator announce contract pub mod validator_announce; pub use mailbox::MockMailboxContract; diff --git a/rust/main/lander/Cargo.toml b/rust/main/lander/Cargo.toml index 747eec32df4..fd2eaca87ef 100644 --- a/rust/main/lander/Cargo.toml +++ b/rust/main/lander/Cargo.toml @@ -11,6 +11,7 @@ version.workspace = true hyperlane-base = { path = "../hyperlane-base", features = ["test-utils"] } hyperlane-core = { path = "../hyperlane-core" } hyperlane-ethereum = { path = "../chains/hyperlane-ethereum" } +hyperlane-radix = { path = "../chains/hyperlane-radix" } hyperlane-sealevel = { path = "../chains/hyperlane-sealevel" } async-trait.workspace = true @@ -33,6 +34,9 @@ tokio-metrics.workspace = true tracing.workspace = true uuid = { workspace = true, features = ["v4", "serde"] } +# Radix dependencies +gateway-api-client = { workspace = true } + [dev-dependencies] tracing-subscriber.workspace = true tracing-test.workspace = true diff --git a/rust/main/lander/src/adapter/chains.rs b/rust/main/lander/src/adapter/chains.rs index f7dd166b654..f4e4b2874fc 100644 --- a/rust/main/lander/src/adapter/chains.rs +++ b/rust/main/lander/src/adapter/chains.rs @@ -7,4 +7,5 @@ mod factory; // chains modules below mod cosmos; pub mod ethereum; +pub mod radix; pub mod sealevel; diff --git a/rust/main/lander/src/adapter/chains/ethereum/adapter.rs b/rust/main/lander/src/adapter/chains/ethereum/adapter.rs index 742c1abe570..963372bbfd9 100644 --- a/rust/main/lander/src/adapter/chains/ethereum/adapter.rs +++ b/rust/main/lander/src/adapter/chains/ethereum/adapter.rs @@ -1,3 +1,4 @@ +use std::ops::Sub; use std::{marker::PhantomData, sync::Arc, time::Duration}; use async_trait::async_trait; @@ -23,7 +24,10 @@ use hyperlane_base::{ }, CoreMetrics, }; -use hyperlane_core::{config::OpSubmissionConfig, ContractLocator, HyperlaneDomain, H256, U256}; +use hyperlane_core::{ + config::OpSubmissionConfig, ChainCommunicationError, ContractLocator, HyperlaneDomain, H256, + U256, +}; use hyperlane_ethereum::multicall::BatchCache; use hyperlane_ethereum::{ multicall, EthereumReorgPeriod, EvmProviderForLander, LanderProviderBuilder, @@ -49,6 +53,7 @@ mod gas_price; mod tx_status_checker; const NONCE_TOO_LOW_ERROR: &str = "nonce too low"; +const DEFAULT_MINIMUM_TIME_BETWEEN_RESUBMISSIONS: Duration = Duration::from_secs(1); pub struct EthereumAdapter { pub estimated_block_time: Duration, @@ -62,6 +67,7 @@ pub struct EthereumAdapter { pub batch_contract_address: H256, pub payload_db: Arc, pub signer: H160, + pub minimum_time_between_resubmissions: Duration, } impl EthereumAdapter { @@ -114,12 +120,13 @@ impl EthereumAdapter { batch_contract_address: connection_conf.batch_contract_address(), payload_db, signer, + minimum_time_between_resubmissions: DEFAULT_MINIMUM_TIME_BETWEEN_RESUBMISSIONS, }; Ok(adapter) } - async fn calculate_nonce(&self, tx: &Transaction) -> Result, LanderError> { + async fn calculate_nonce(&self, tx: &Transaction) -> Result { self.nonce_manager.calculate_next_nonce(tx).await } @@ -140,8 +147,11 @@ impl EthereumAdapter { .await; // then, compare the estimated gas price with `current * escalation_multiplier` - let escalated_gas_price = - gas_price::escalate_gas_price_if_needed(&old_gas_price, &estimated_gas_price); + let escalated_gas_price = gas_price::escalate_gas_price_if_needed( + &old_gas_price, + &estimated_gas_price, + &self.transaction_overrides, + ); let new_gas_price = match escalated_gas_price { GasPrice::None => estimated_gas_price, @@ -152,7 +162,7 @@ impl EthereumAdapter { Ok(new_gas_price) } - fn update_tx(&self, tx: &mut Transaction, nonce: Option, gas_price: GasPrice) { + fn update_tx(&self, tx: &mut Transaction, nonce: U256, gas_price: GasPrice) { let precursor = tx.precursor_mut(); if let GasPrice::Eip1559 { @@ -200,13 +210,7 @@ impl EthereumAdapter { } GasPrice::Eip1559 { .. } => {} } - - match nonce { - None => {} - Some(nonce) => { - precursor.tx.set_nonce(nonce); - } - } + precursor.tx.set_nonce(nonce); info!(?tx, "updated transaction with nonce and gas price"); } @@ -317,9 +321,12 @@ impl EthereumAdapter { .await .map(|(tx, f)| EthereumTxPrecursor::new(tx, f)); - let Ok(multi_precursor) = multi_precursor else { - error!("Failed to batch payloads"); - return vec![]; + let multi_precursor = match multi_precursor { + Ok(precursor) => precursor, + Err(e) => { + error!(error = ?e, "Failed to batch payloads"); + return vec![]; + } }; let transaction = TransactionFactory::build(multi_precursor, payload_details.clone()); @@ -344,11 +351,13 @@ impl AdaptsChain for EthereumAdapter { async fn tx_ready_for_resubmission(&self, tx: &Transaction) -> bool { let estimated_block_time = self.estimated_block_time(); + let ready_time = estimated_block_time.max(&self.minimum_time_between_resubmissions); + let Some(last_submission_time) = tx.last_submission_attempt else { // If the transaction has never been submitted, it is ready for resubmission return true; }; - let elapsed = chrono::Utc::now() - last_submission_time; + let elapsed = chrono::Utc::now().sub(last_submission_time); let elapsed = match elapsed.to_std() { Ok(duration) => duration, Err(err) => { @@ -360,7 +369,7 @@ impl AdaptsChain for EthereumAdapter { return true; } }; - elapsed > *estimated_block_time + elapsed > *ready_time } /// Builds a transaction for the given payloads. @@ -644,7 +653,7 @@ impl AdaptsChain for EthereumAdapter { } #[cfg(test)] -pub use gas_limit_estimator::apply_estimate_buffer_to_ethers; +pub use gas_limit_estimator::tests::apply_estimate_buffer_to_ethers; #[cfg(test)] mod tests; diff --git a/rust/main/lander/src/adapter/chains/ethereum/adapter/gas_limit_estimator.rs b/rust/main/lander/src/adapter/chains/ethereum/adapter/gas_limit_estimator.rs index 81c86ff2405..1fd200b0b6b 100644 --- a/rust/main/lander/src/adapter/chains/ethereum/adapter/gas_limit_estimator.rs +++ b/rust/main/lander/src/adapter/chains/ethereum/adapter/gas_limit_estimator.rs @@ -34,6 +34,8 @@ pub async fn estimate_gas_limit( estimated_gas_limit = estimated_gas_limit.max(gas_limit) } } + + estimated_gas_limit = apply_gas_limit_cap(transaction_overrides, estimated_gas_limit); let gas_limit = estimated_gas_limit; // Cap the gas limit to the block gas limit @@ -78,11 +80,129 @@ pub fn apply_gas_estimate_buffer(gas: U256, domain: &HyperlaneDomain) -> ChainRe Ok(gas.saturating_add(GAS_LIMIT_BUFFER.into())) } +pub fn apply_gas_limit_cap(transaction_overrides: &TransactionOverrides, gas_limit: U256) -> U256 { + if let Some(gas_limit_cap) = transaction_overrides.gas_limit_cap { + if gas_limit > gas_limit_cap { + warn!( + ?gas_limit, + ?gas_limit_cap, + "Gas limit for transaction is higher than the gas limit cap. Capping it to the gas limit cap." + ); + return gas_limit_cap; + } + } + gas_limit +} // Used for testing #[cfg(test)] -pub fn apply_estimate_buffer_to_ethers( - gas: EthersU256, - domain: &HyperlaneDomain, -) -> ChainResult { - apply_gas_estimate_buffer(gas.into(), domain).map(Into::into) +pub mod tests { + use ethers::{ + abi::Function, + types::{transaction::eip2718::TypedTransaction, Block, Eip1559TransactionRequest}, + }; + + use crate::adapter::chains::ethereum::tests::MockEvmProvider; + + use super::*; + + pub fn apply_estimate_buffer_to_ethers( + gas: EthersU256, + domain: &HyperlaneDomain, + ) -> ChainResult { + apply_gas_estimate_buffer(gas.into(), domain).map(Into::into) + } + + #[tokio::test] + async fn test_gas_limit_cap_applies() { + #[allow(deprecated)] + let mut tx_precursor = EthereumTxPrecursor { + tx: TypedTransaction::Eip1559(Eip1559TransactionRequest::default()), + function: Function { + name: "test".into(), + inputs: Vec::new(), + outputs: Vec::new(), + constant: None, + state_mutability: ethers::abi::StateMutability::Payable, + }, + }; + let mut provider = MockEvmProvider::new(); + + provider + .expect_estimate_gas_limit() + .returning(|_, _| Ok(U256::from(10_000))); + provider.expect_get_block().returning(|_| { + Ok(Some(Block { + gas_limit: EthersU256::from(100_000), + ..Default::default() + })) + }); + + let transaction_overrides = TransactionOverrides { + gas_limit_cap: Some(U256::from(100)), + ..Default::default() + }; + let domain = HyperlaneDomain::Known(hyperlane_core::KnownHyperlaneDomain::Ethereum); + let with_gas_limit_overrides = false; + + estimate_gas_limit( + Arc::new(provider), + &mut tx_precursor, + &transaction_overrides, + &domain, + with_gas_limit_overrides, + ) + .await + .expect("Failed to estimate gas limit"); + + let expected_gas = EthersU256::from(100); + let expected = Some(&expected_gas); + assert_eq!(tx_precursor.tx.gas(), expected); + } + + #[tokio::test] + async fn test_gas_limit_cap_applies_with_gas_limit_override() { + #[allow(deprecated)] + let mut tx_precursor = EthereumTxPrecursor { + tx: TypedTransaction::Eip1559(Eip1559TransactionRequest::default()), + function: Function { + name: "test".into(), + inputs: Vec::new(), + outputs: Vec::new(), + constant: None, + state_mutability: ethers::abi::StateMutability::Payable, + }, + }; + let mut provider = MockEvmProvider::new(); + + provider + .expect_estimate_gas_limit() + .returning(|_, _| Ok(U256::from(10_000))); + provider.expect_get_block().returning(|_| { + Ok(Some(Block { + gas_limit: EthersU256::from(100_000), + ..Default::default() + })) + }); + + let transaction_overrides = TransactionOverrides { + gas_limit_cap: Some(U256::from(100)), + ..Default::default() + }; + let domain = HyperlaneDomain::Known(hyperlane_core::KnownHyperlaneDomain::Ethereum); + let with_gas_limit_overrides = true; + + estimate_gas_limit( + Arc::new(provider), + &mut tx_precursor, + &transaction_overrides, + &domain, + with_gas_limit_overrides, + ) + .await + .expect("Failed to estimate gas limit"); + + let expected_gas = EthersU256::from(100); + let expected = Some(&expected_gas); + assert_eq!(tx_precursor.tx.gas(), expected); + } } diff --git a/rust/main/lander/src/adapter/chains/ethereum/adapter/gas_price/escalator.rs b/rust/main/lander/src/adapter/chains/ethereum/adapter/gas_price/escalator.rs index 45a4d81d156..32e824e2f65 100644 --- a/rust/main/lander/src/adapter/chains/ethereum/adapter/gas_price/escalator.rs +++ b/rust/main/lander/src/adapter/chains/ethereum/adapter/gas_price/escalator.rs @@ -3,6 +3,7 @@ use ethers_core::types::transaction::eip2718::TypedTransaction; use tracing::{debug, error, info, warn}; use hyperlane_core::U256; +use hyperlane_ethereum::TransactionOverrides; use crate::adapter::EthereumTxPrecursor; @@ -10,11 +11,19 @@ use super::price::GasPrice; const ESCALATION_MULTIPLIER_NUMERATOR: u32 = 110; const ESCALATION_MULTIPLIER_DENOMINATOR: u32 = 100; +const GAS_PRICE_CAP_MULTIPLIER: u32 = 3; -/// Sets the max between the newly estimated gas price and 1.1x the old gas price. +/// Escalates gas price using a 4-step process: +/// 1. Escalate old price by 10% to help stuck transactions +/// 2. Use max of escalated and newly estimated price (market responsiveness) +/// 3. Cap at gas_price_cap_multiplier × new_estimated_price (prevent runaway costs) +/// 4. Ensure result ≥ old price (RBF compatibility) +/// +/// Formula: Max(Min(Max(Escalate(oldGasPrice), newEstimatedGasPrice), cap_multiplier × newEstimatedGasPrice), oldGasPrice) pub fn escalate_gas_price_if_needed( old_gas_price: &GasPrice, estimated_gas_price: &GasPrice, + transaction_overrides: &TransactionOverrides, ) -> GasPrice { // assumes the old and new txs have the same type match (old_gas_price, estimated_gas_price) { @@ -44,11 +53,15 @@ pub fn escalate_gas_price_if_needed( gas_price: estimated_gas_price, }, ) => { - let escalated_gas_price = - get_escalated_price_from_old_and_new(old_gas_price, estimated_gas_price); + let escalated_gas_price = get_escalated_price_from_old_and_new( + old_gas_price, + estimated_gas_price, + transaction_overrides, + ); debug!( tx_type = "Legacy or Eip2930", ?old_gas_price, + ?estimated_gas_price, ?escalated_gas_price, "Escalation attempt outcome" ); @@ -63,21 +76,29 @@ pub fn escalate_gas_price_if_needed( max_priority_fee: old_max_priority_fee, }, GasPrice::Eip1559 { - max_fee: new_max_fee, - max_priority_fee: new_max_priority_fee, + max_fee: estimated_max_fee, + max_priority_fee: estimated_max_priority_fee, }, ) => { - let escalated_max_fee_per_gas = - get_escalated_price_from_old_and_new(old_max_fee, new_max_fee); + let escalated_max_fee_per_gas = get_escalated_price_from_old_and_new( + old_max_fee, + estimated_max_fee, + transaction_overrides, + ); - let escalated_max_priority_fee_per_gas = - get_escalated_price_from_old_and_new(old_max_priority_fee, new_max_priority_fee); + let escalated_max_priority_fee_per_gas = get_escalated_price_from_old_and_new( + old_max_priority_fee, + estimated_max_priority_fee, + transaction_overrides, + ); debug!( tx_type = "Eip1559", old_max_fee_per_gas = ?old_max_fee, + estimated_max_fee = ?estimated_max_fee, escalated_max_fee_per_gas = ?escalated_max_fee_per_gas, old_max_priority_fee_per_gas = ?old_max_priority_fee, + estimated_max_priority_fee = ?estimated_max_priority_fee, escalated_max_priority_fee_per_gas = ?escalated_max_priority_fee_per_gas, "Escalation attempt outcome" ); @@ -94,13 +115,306 @@ pub fn escalate_gas_price_if_needed( } } -fn get_escalated_price_from_old_and_new(old_gas_price: &U256, new_gas_price: &U256) -> U256 { - let escalated_gas_price = apply_escalation_multiplier(old_gas_price); - escalated_gas_price.max(*new_gas_price) +fn get_escalated_price_from_old_and_new( + old_gas_price: &U256, + new_gas_price: &U256, + transaction_overrides: &TransactionOverrides, +) -> U256 { + // Step 1: Calculate escalated price (old price * 1.1) + // This provides a 10% increase to help stuck transactions get through + let escalated_price = apply_escalation_multiplier(old_gas_price); + + // Step 2: Take max of escalated and newly estimated price + // This ensures we use current market conditions if they're higher than our escalation + let competitive_price = escalated_price.max(*new_gas_price); + + // Step 3: Apply cap to prevent indefinite escalation + // Cap = new_estimated_price * multiplier (default 3x) + // This prevents runaway costs when network estimates drop significantly + let multiplier = transaction_overrides + .gas_price_cap_multiplier + .unwrap_or_else(|| U256::from(GAS_PRICE_CAP_MULTIPLIER)); + let escalation_cap = new_gas_price.saturating_mul(multiplier); + let capped_price = competitive_price.min(escalation_cap); + + // Step 4: Ensure price never goes backwards (RBF compatibility) + // Replace-by-Fee requires each replacement to have higher fees than the previous + // This prevents transaction rejections when cap is lower than old price + capped_price.max(*old_gas_price) } fn apply_escalation_multiplier(gas_price: &U256) -> U256 { let numerator = U256::from(ESCALATION_MULTIPLIER_NUMERATOR); let denominator = U256::from(ESCALATION_MULTIPLIER_DENOMINATOR); - gas_price * numerator / denominator + gas_price.saturating_mul(numerator).div_mod(denominator).0 +} + +#[cfg(test)] +mod tests { + use hyperlane_core::U256; + + use crate::adapter::chains::ethereum::adapter::gas_price::GasPrice; + use hyperlane_ethereum::TransactionOverrides; + + use super::*; + + fn default_transaction_overrides() -> TransactionOverrides { + TransactionOverrides { + gas_price_cap_multiplier: Some(U256::from(3)), + ..Default::default() + } + } + + #[test] + fn test_gas_price_does_not_overflow() { + let old_gas_price = GasPrice::Eip1559 { + max_fee: U256::MAX, + max_priority_fee: U256::MAX, + }; + let estimated_gas_price = GasPrice::Eip1559 { + max_fee: U256::MAX, + max_priority_fee: U256::MAX, + }; + + // should not overflow and panic + let res = escalate_gas_price_if_needed( + &old_gas_price, + &estimated_gas_price, + &default_transaction_overrides(), + ); + let expected = GasPrice::Eip1559 { + max_fee: U256::MAX, + max_priority_fee: U256::MAX, + }; + + assert_eq!(res, expected); + } + + #[test] + fn test_gas_price_cap_applied_for_legacy_tx() { + // Test that escalated price is capped by 3x the new estimated price + let old_gas_price = GasPrice::NonEip1559 { + gas_price: U256::from(1000), // High old price + }; + let estimated_gas_price = GasPrice::NonEip1559 { + gas_price: U256::from(100), // Low new estimated price + }; + + let res = escalate_gas_price_if_needed( + &old_gas_price, + &estimated_gas_price, + &default_transaction_overrides(), + ); + + // Escalated would be 1000 * 1.1 = 1100 + // Cap is 100 * 3 = 300 + // Capped would be min(max(1100, 100), 300) = 300 + // But final result must be max(300, 1000) = 1000 (can't go backwards) + let expected = GasPrice::NonEip1559 { + gas_price: U256::from(1000), // Can't go below old price + }; + + assert_eq!(res, expected); + } + + #[test] + fn test_gas_price_cap_applied_for_eip1559_tx() { + // Test that escalated price is capped by 3x the new estimated price for EIP-1559 + let old_gas_price = GasPrice::Eip1559 { + max_fee: U256::from(2000), + max_priority_fee: U256::from(1000), + }; + let estimated_gas_price = GasPrice::Eip1559 { + max_fee: U256::from(200), + max_priority_fee: U256::from(100), + }; + + let res = escalate_gas_price_if_needed( + &old_gas_price, + &estimated_gas_price, + &default_transaction_overrides(), + ); + + // Escalated max_fee would be 2000 * 1.1 = 2200 + // Cap for max_fee is 200 * 3 = 600 + // Capped would be min(max(2200, 200), 600) = 600 + // But final max_fee must be max(600, 2000) = 2000 (can't go backwards) + + // Escalated max_priority_fee would be 1000 * 1.1 = 1100 + // Cap for max_priority_fee is 100 * 3 = 300 + // Capped would be min(max(1100, 100), 300) = 300 + // But final max_priority_fee must be max(300, 1000) = 1000 (can't go backwards) + let expected = GasPrice::Eip1559 { + max_fee: U256::from(2000), // Can't go below old price + max_priority_fee: U256::from(1000), // Can't go below old price + }; + + assert_eq!(res, expected); + } + + #[test] + fn test_gas_price_cap_not_applied_when_escalated_lower_than_cap() { + // Test that cap is not applied when escalated price is already lower + let old_gas_price = GasPrice::NonEip1559 { + gas_price: U256::from(100), + }; + let estimated_gas_price = GasPrice::NonEip1559 { + gas_price: U256::from(200), // Higher new estimated price + }; + + let res = escalate_gas_price_if_needed( + &old_gas_price, + &estimated_gas_price, + &default_transaction_overrides(), + ); + + // Escalated would be 100 * 1.1 = 110 + // Cap is 200 * 3 = 600 + // Result should be min(max(110, 200), 600) = 200 (new estimated price is higher) + let expected = GasPrice::NonEip1559 { + gas_price: U256::from(200), + }; + + assert_eq!(res, expected); + } + + #[test] + fn test_gas_price_escalation_without_cap() { + // Test normal escalation when cap doesn't apply + let old_gas_price = GasPrice::NonEip1559 { + gas_price: U256::from(100), + }; + let estimated_gas_price = GasPrice::NonEip1559 { + gas_price: U256::from(50), // Lower new estimated price + }; + + let res = escalate_gas_price_if_needed( + &old_gas_price, + &estimated_gas_price, + &default_transaction_overrides(), + ); + + // Escalated would be 100 * 1.1 = 110 + // Cap is 50 * 3 = 150 + // Result should be min(max(110, 50), 150) = 110 (escalated price) + let expected = GasPrice::NonEip1559 { + gas_price: U256::from(110), + }; + + assert_eq!(res, expected); + } + + #[test] + fn test_configurable_gas_price_cap_multiplier() { + // Test that custom gas price cap multiplier is used + let old_gas_price = GasPrice::NonEip1559 { + gas_price: U256::from(1000), // High old price + }; + let estimated_gas_price = GasPrice::NonEip1559 { + gas_price: U256::from(100), // Low new estimated price + }; + + // Use custom multiplier of 5 instead of default 3 + let custom_overrides = TransactionOverrides { + gas_price_cap_multiplier: Some(U256::from(5)), + ..Default::default() + }; + + let res = + escalate_gas_price_if_needed(&old_gas_price, &estimated_gas_price, &custom_overrides); + + // Escalated would be 1000 * 1.1 = 1100 + // Cap with multiplier 5 is 100 * 5 = 500 + // Capped would be min(max(1100, 100), 500) = 500 + // But final result must be max(500, 1000) = 1000 (can't go backwards) + let expected = GasPrice::NonEip1559 { + gas_price: U256::from(1000), // Can't go below old price + }; + + assert_eq!(res, expected); + } + + #[test] + fn test_default_gas_price_cap_multiplier_when_none() { + // Test that default multiplier (3) is used when not specified + let old_gas_price = GasPrice::NonEip1559 { + gas_price: U256::from(1000), // High old price + }; + let estimated_gas_price = GasPrice::NonEip1559 { + gas_price: U256::from(100), // Low new estimated price + }; + + // Use overrides with no gas_price_cap_multiplier set + let default_overrides = TransactionOverrides { + gas_price_cap_multiplier: None, + ..Default::default() + }; + + let res = + escalate_gas_price_if_needed(&old_gas_price, &estimated_gas_price, &default_overrides); + + // Escalated would be 1000 * 1.1 = 1100 + // Cap with default multiplier 3 is 100 * 3 = 300 + // Capped would be min(max(1100, 100), 300) = 300 + // But final result must be max(300, 1000) = 1000 (can't go backwards) + let expected = GasPrice::NonEip1559 { + gas_price: U256::from(1000), // Can't go below old price + }; + + assert_eq!(res, expected); + } + + #[test] + fn test_rbf_protection_prevents_backwards_price() { + // Test that RBF protection prevents gas price from going backwards + let old_gas_price = GasPrice::NonEip1559 { + gas_price: U256::from(1000), // High old price + }; + let estimated_gas_price = GasPrice::NonEip1559 { + gas_price: U256::from(10), // Very low new estimated price + }; + + let res = escalate_gas_price_if_needed( + &old_gas_price, + &estimated_gas_price, + &default_transaction_overrides(), + ); + + // Escalated would be 1000 * 1.1 = 1100 + // Cap is 10 * 3 = 30 + // Capped would be min(max(1100, 10), 30) = 30 + // RBF protection ensures final result is max(30, 1000) = 1000 + let expected = GasPrice::NonEip1559 { + gas_price: U256::from(1000), // Maintains old price due to RBF protection + }; + + assert_eq!(res, expected); + } + + #[test] + fn test_zero_estimated_price_protection() { + // Test protection against zero estimated prices + let old_gas_price = GasPrice::NonEip1559 { + gas_price: U256::from(100), + }; + let estimated_gas_price = GasPrice::NonEip1559 { + gas_price: U256::from(0), // Zero price + }; + + let res = escalate_gas_price_if_needed( + &old_gas_price, + &estimated_gas_price, + &default_transaction_overrides(), + ); + + // Escalated would be 100 * 1.1 = 110 + // Cap is 0 * 3 = 0 + // Capped would be min(max(110, 0), 0) = 0 + // RBF protection ensures final result is max(0, 100) = 100 + let expected = GasPrice::NonEip1559 { + gas_price: U256::from(100), // Maintains old price, prevents zeroing + }; + + assert_eq!(res, expected); + } } diff --git a/rust/main/lander/src/adapter/chains/ethereum/adapter/gas_price/estimator.rs b/rust/main/lander/src/adapter/chains/ethereum/adapter/gas_price/estimator.rs index bed3753c85d..1e50b17efee 100644 --- a/rust/main/lander/src/adapter/chains/ethereum/adapter/gas_price/estimator.rs +++ b/rust/main/lander/src/adapter/chains/ethereum/adapter/gas_price/estimator.rs @@ -1,5 +1,6 @@ use std::{str::FromStr, sync::Arc}; +use ethers::contract::Lazy; use ethers::{ abi::Detokenize, contract::builders::ContractCall, @@ -14,6 +15,8 @@ use hyperlane_core::{ChainCommunicationError, ChainResult, HyperlaneDomain, U256 use hyperlane_ethereum::{EvmProviderForLander, TransactionOverrides, ZksyncEstimateFeeResponse}; use tracing::{debug, warn}; +use crate::{adapter::EthereumTxPrecursor, LanderError}; +use ethers_core::types::FeeHistory; use ethers_core::{ types::{BlockNumber, U256 as EthersU256}, utils::{ @@ -21,8 +24,7 @@ use ethers_core::{ EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE, }, }; - -use crate::{adapter::EthereumTxPrecursor, LanderError}; +use futures_util::future::join_all; use super::price::GasPrice; @@ -30,6 +32,14 @@ type FeeEstimator = fn(EthersU256, Vec>) -> (EthersU256, EthersU const EVM_RELAYER_ADDRESS: &str = "0x74cae0ecc47b02ed9b9d32e000fd70b9417970c5"; +// We have 2 to 4 multiples of the default percentile, and we limit it to 100% percentile. +static PERCENTILES: Lazy> = Lazy::new(|| { + (2..5) + .map(|m| EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE * m as f64) + .filter(|p| *p <= 100.0) + .collect::>() +}); + pub type Eip1559Fee = ( EthersU256, // base fee EthersU256, // max fee @@ -222,6 +232,8 @@ async fn estimate_eip1559_fees_default( let (latest_block, fee_history) = try_join!(latest_block, fee_history)?; + let fee_history = ensure_non_empty_rewards(provider, fee_history).await?; + let base_fee_per_gas = latest_block .base_fee_per_gas .ok_or_else(|| ProviderError::CustomError("EIP-1559 not activated".into()))?; @@ -247,3 +259,65 @@ async fn latest_block(provider: &Arc) -> ChainResult, + default_fee_history: FeeHistory, +) -> ChainResult { + if has_rewards(&default_fee_history) { + debug!(?default_fee_history, "default rewards non zero"); + return Ok(default_fee_history); + } + + let fee_history_futures = PERCENTILES + .clone() + .into_iter() + .map(|p| { + let provider = provider.clone(); + async move { + provider + .fee_history( + EIP1559_FEE_ESTIMATION_PAST_BLOCKS.into(), + BlockNumber::Latest, + &[p], + ) + .await + .map_err(ChainCommunicationError::from_other) + } + }) + .collect::>(); + + // Results will be ordered by percentile, so we can just take the first non-empty one. + let fee_histories = join_all(fee_history_futures).await; + + debug!( + ?fee_histories, + ?PERCENTILES, + "fee history for each percentile" + ); + + // We return the first non-empty fee history. + // If all are empty, we return the default fee history which is empty at this point. + let mut chosen_fee_history = default_fee_history; + for fee_history in fee_histories { + let Ok(fee_history) = fee_history else { + continue; + }; + if has_rewards(&fee_history) { + chosen_fee_history = fee_history; + break; + } + } + + debug!(?chosen_fee_history, "chosen fee history"); + + Ok(chosen_fee_history) +} + +fn has_rewards(fee_history: &FeeHistory) -> bool { + fee_history + .reward + .iter() + .filter_map(|r| r.first()) + .any(|r| !r.is_zero()) +} diff --git a/rust/main/lander/src/adapter/chains/ethereum/adapter/gas_price/price.rs b/rust/main/lander/src/adapter/chains/ethereum/adapter/gas_price/price.rs index 435555b946d..dd6a9abeaff 100644 --- a/rust/main/lander/src/adapter/chains/ethereum/adapter/gas_price/price.rs +++ b/rust/main/lander/src/adapter/chains/ethereum/adapter/gas_price/price.rs @@ -4,7 +4,7 @@ use hyperlane_core::U256; use crate::adapter::EthereumTxPrecursor; -#[derive(Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum GasPrice { None, NonEip1559 { diff --git a/rust/main/lander/src/adapter/chains/ethereum/nonce/db.rs b/rust/main/lander/src/adapter/chains/ethereum/nonce/db.rs index fc2e667bd5c..9de102f24e8 100644 --- a/rust/main/lander/src/adapter/chains/ethereum/nonce/db.rs +++ b/rust/main/lander/src/adapter/chains/ethereum/nonce/db.rs @@ -15,6 +15,7 @@ const FINALIZED_NONCE_BY_SIGNER_ADDRESS_STORAGE_PREFIX: &str = "finalized_nonce_ const UPPER_NONCE_BY_SIGNER_ADDRESS_STORAGE_PREFIX: &str = "upper_nonce_by_signer_address_"; const TRANSACTION_UUID_BY_NONCE_AND_SIGNER_ADDRESS_STORAGE_PREFIX: &str = "transaction_uuid_by_nonce_and_signer_address_"; +const EVM_NONCE_BY_TRANSACTION_UUID_PREFIX: &str = "evm_nonce_by_transaction_uuid_"; #[async_trait] pub trait NonceDb: Send + Sync { @@ -52,6 +53,19 @@ pub trait NonceDb: Send + Sync { signer_address: &Address, tx_uuid: &TransactionUuid, ) -> DbResult<()>; + + async fn retrieve_nonce_by_transaction_uuid( + &self, + signer_address: &Address, + tx_uuid: &TransactionUuid, + ) -> DbResult>; + + async fn store_nonce_by_transaction_uuid( + &self, + signer_address: &Address, + tx_uuid: &TransactionUuid, + nonce: &U256, + ) -> DbResult<()>; } #[async_trait] @@ -123,6 +137,30 @@ impl NonceDb for HyperlaneRocksDB { tx_uuid, ) } + + async fn retrieve_nonce_by_transaction_uuid( + &self, + signer_address: &Address, + tx_uuid: &TransactionUuid, + ) -> DbResult> { + self.retrieve_value_by_key( + EVM_NONCE_BY_TRANSACTION_UUID_PREFIX, + &SignerAddressAndTransactionUuid(*signer_address, tx_uuid.clone()), + ) + } + + async fn store_nonce_by_transaction_uuid( + &self, + signer_address: &Address, + tx_uuid: &TransactionUuid, + nonce: &U256, + ) -> DbResult<()> { + self.store_value_by_key( + EVM_NONCE_BY_TRANSACTION_UUID_PREFIX, + &SignerAddressAndTransactionUuid(*signer_address, tx_uuid.clone()), + nonce, + ) + } } struct SignerAddress(Address); @@ -147,7 +185,21 @@ impl Encode for NonceAndSignerAddress { let (nonce, address) = (self.0, SignerAddress(self.1)); let mut written = nonce.write_to(writer)?; - written += address.write_to(writer)?; + written = written.saturating_add(address.write_to(writer)?); + Ok(written) + } +} + +struct SignerAddressAndTransactionUuid(Address, TransactionUuid); +impl Encode for SignerAddressAndTransactionUuid { + fn write_to(&self, writer: &mut W) -> std::io::Result + where + W: Write, + { + let (address, tx_uuid) = (SignerAddress(self.0), &self.1); + + let mut written = address.write_to(writer)?; + written = written.saturating_add(tx_uuid.write_to(writer)?); Ok(written) } } diff --git a/rust/main/lander/src/adapter/chains/ethereum/nonce/error.rs b/rust/main/lander/src/adapter/chains/ethereum/nonce/error.rs index 2d7a0af4323..c3791800c05 100644 --- a/rust/main/lander/src/adapter/chains/ethereum/nonce/error.rs +++ b/rust/main/lander/src/adapter/chains/ethereum/nonce/error.rs @@ -1,7 +1,7 @@ use hyperlane_base::db::DbError; use hyperlane_core::{ChainCommunicationError, U256}; -use crate::transaction::TransactionUuid; +use crate::{transaction::TransactionUuid, LanderError}; pub(crate) type NonceResult = Result; @@ -20,3 +20,12 @@ impl From for NonceError { NonceError::DatabaseError(error) } } + +impl From for LanderError { + fn from(value: NonceError) -> Self { + match value { + NonceError::DatabaseError(err) => LanderError::DbError(err), + NonceError::ProviderError(err) => LanderError::ChainCommunicationError(err), + } + } +} diff --git a/rust/main/lander/src/adapter/chains/ethereum/nonce/manager.rs b/rust/main/lander/src/adapter/chains/ethereum/nonce/manager.rs index 54f0d16ea5b..f63636ff238 100644 --- a/rust/main/lander/src/adapter/chains/ethereum/nonce/manager.rs +++ b/rust/main/lander/src/adapter/chains/ethereum/nonce/manager.rs @@ -56,9 +56,7 @@ impl NonceManager { pub(crate) async fn calculate_next_nonce( &self, tx: &Transaction, - ) -> eyre::Result, LanderError> { - use NonceAction::Noop; - + ) -> eyre::Result { let tx_uuid = tx.uuid.clone(); let precursor = tx.precursor(); @@ -77,29 +75,29 @@ impl NonceManager { .await .map_err(|e| eyre::eyre!("Failed to update boundary nonces: {}", e))?; - let (action, nonce) = self + let nonce_action = self .state .validate_assigned_nonce(tx) .await .map_err(|e| eyre::eyre!("Failed to validate assigned nonce: {}", e))?; - if matches!(action, Noop) { - return Ok(None); + match nonce_action { + NonceAction::Assign { nonce } => Ok(nonce), + NonceAction::AssignNext { old_nonce } => { + let next_nonce = self + .state + .assign_next_nonce(&tx_uuid, &old_nonce) + .await + .map_err(|e| { + eyre::eyre!( + "Failed to assign next nonce for transaction {}: {}", + tx_uuid, + e + ) + })?; + Ok(next_nonce) + } } - - let next_nonce = self - .state - .assign_next_nonce(&tx_uuid, &nonce) - .await - .map_err(|e| { - eyre::eyre!( - "Failed to assign next nonce for transaction {}: {}", - tx_uuid, - e - ) - })?; - - Ok(Some(next_nonce)) } async fn address(chain_conf: &ChainConf) -> eyre::Result
{ diff --git a/rust/main/lander/src/adapter/chains/ethereum/nonce/manager/tests.rs b/rust/main/lander/src/adapter/chains/ethereum/nonce/manager/tests.rs index d4661235632..b8cc21e73a0 100644 --- a/rust/main/lander/src/adapter/chains/ethereum/nonce/manager/tests.rs +++ b/rust/main/lander/src/adapter/chains/ethereum/nonce/manager/tests.rs @@ -55,11 +55,7 @@ async fn test_assign_nonce_sets_nonce_when_none_present() { ); // Should assign nonce 1, since mock provider returns 1 - let nonce = manager - .calculate_next_nonce(&mut tx) - .await - .unwrap() - .unwrap(); + let nonce = manager.calculate_next_nonce(&mut tx).await.unwrap(); assert_eq!(nonce, U256::one()); } diff --git a/rust/main/lander/src/adapter/chains/ethereum/nonce/state/assign.rs b/rust/main/lander/src/adapter/chains/ethereum/nonce/state/assign.rs index 8c4379cd4cb..dd5b80d77e8 100644 --- a/rust/main/lander/src/adapter/chains/ethereum/nonce/state/assign.rs +++ b/rust/main/lander/src/adapter/chains/ethereum/nonce/state/assign.rs @@ -12,9 +12,9 @@ impl NonceManagerState { pub(crate) async fn assign_next_nonce( &self, tx_uuid: &TransactionUuid, - nonce: &Option, + old_nonce: &Option, ) -> NonceResult { - if let Some(nonce) = nonce { + if let Some(nonce) = old_nonce { // If the different nonce was assigned to the transaction, // we clear the tracked nonce for the transaction first. warn!( @@ -22,6 +22,7 @@ impl NonceManagerState { "Reassigning nonce to transaction, clearing currently tracked nonce" ); self.clear_tracked_tx_uuid(nonce).await?; + self.clear_tracked_tx_nonce(tx_uuid).await?; } let (finalized_nonce, upper_nonce) = self.get_boundary_nonces().await?; @@ -38,7 +39,8 @@ impl NonceManagerState { if next_nonce == upper_nonce { // If we reached the upper nonce, we need to update it. - self.set_upper_nonce(&(next_nonce + 1)).await?; + self.set_upper_nonce(&(next_nonce.saturating_add(U256::one()))) + .await?; } self.set_tracked_tx_uuid(&next_nonce, tx_uuid).await?; @@ -61,7 +63,7 @@ impl NonceManagerState { let mut next_nonce = finalized_nonce; while next_nonce < upper_nonce { - next_nonce += U256::one(); + next_nonce = next_nonce.saturating_add(U256::one()); let tracked_tx_uuid = self.get_tracked_tx_uuid(&next_nonce).await?; diff --git a/rust/main/lander/src/adapter/chains/ethereum/nonce/state/boundary.rs b/rust/main/lander/src/adapter/chains/ethereum/nonce/state/boundary.rs index 6000dde6961..a3a1c14ec40 100644 --- a/rust/main/lander/src/adapter/chains/ethereum/nonce/state/boundary.rs +++ b/rust/main/lander/src/adapter/chains/ethereum/nonce/state/boundary.rs @@ -21,7 +21,7 @@ impl NonceManagerState { // If the finalized nonce is greater than or equal to the upper nonce, it means that // some transactions were finalized by a service different from Lander. // And we need to update the upper nonce. - upper_nonce = finalized_nonce + U256::one(); + upper_nonce = finalized_nonce.saturating_add(U256::one()); self.set_upper_nonce(&upper_nonce).await?; } diff --git a/rust/main/lander/src/adapter/chains/ethereum/nonce/state/db.rs b/rust/main/lander/src/adapter/chains/ethereum/nonce/state/db.rs index b0b4f8bd132..136c44579ad 100644 --- a/rust/main/lander/src/adapter/chains/ethereum/nonce/state/db.rs +++ b/rust/main/lander/src/adapter/chains/ethereum/nonce/state/db.rs @@ -40,6 +40,9 @@ impl NonceManagerState { self.nonce_db .store_transaction_uuid_by_nonce_and_signer_address(nonce, &self.address, tx_uuid) .await?; + self.nonce_db + .store_nonce_by_transaction_uuid(&self.address, tx_uuid, nonce) + .await?; Ok(()) } @@ -88,6 +91,22 @@ impl NonceManagerState { Ok(nonce) } + + pub(super) async fn get_tx_nonce(&self, uuid: &TransactionUuid) -> NonceResult> { + let nonce = self + .nonce_db + .retrieve_nonce_by_transaction_uuid(&self.address, uuid) + .await?; + Ok(nonce) + } + + pub(super) async fn clear_tracked_tx_nonce(&self, uuid: &TransactionUuid) -> NonceResult<()> { + self.nonce_db + .store_nonce_by_transaction_uuid(&self.address, uuid, &U256::MAX) + .await?; + + Ok(()) + } } #[cfg(test)] diff --git a/rust/main/lander/src/adapter/chains/ethereum/nonce/state/validate.rs b/rust/main/lander/src/adapter/chains/ethereum/nonce/state/validate.rs index 5cd5e080d56..21e45520507 100644 --- a/rust/main/lander/src/adapter/chains/ethereum/nonce/state/validate.rs +++ b/rust/main/lander/src/adapter/chains/ethereum/nonce/state/validate.rs @@ -9,24 +9,41 @@ use super::NonceManagerState; use crate::transaction::{Transaction, TransactionUuid}; use crate::TransactionStatus; +/// What action to take given a tx #[derive(Clone, Debug, Eq, PartialEq)] pub(crate) enum NonceAction { - Noop, - Assign, + // Assign the provided nonce + Assign { nonce: U256 }, + // Assign the next available nonce, old_nonce will be unassigned if any. + AssignNext { old_nonce: Option }, } impl NonceManagerState { pub(crate) async fn validate_assigned_nonce( &self, tx: &Transaction, - ) -> NonceResult<(NonceAction, Option)> { - use NonceAction::{Assign, Noop}; + ) -> NonceResult { use NonceStatus::{Committed, Freed, Taken}; let tx_uuid = tx.uuid.clone(); let tx_status = tx.status.clone(); - let Some(nonce): Option = tx.precursor().tx.nonce().map(Into::into) else { - return Ok((Assign, None)); + + let nonce = match tx.precursor().tx.nonce().map(Into::into) { + Some(nonce) => nonce, + // if tx has no nonce assigned, check if it has one assigned in the + // db. + None => match self.get_tx_nonce(&tx_uuid).await? { + Some(nonce) => { + if nonce == U256::MAX { + return Ok(NonceAction::AssignNext { old_nonce: None }); + } else { + nonce + } + } + None => { + return Ok(NonceAction::AssignNext { old_nonce: None }); + } + }, }; let nonce_status = NonceStatus::calculate_nonce_status(tx_uuid.clone(), &tx_status); @@ -37,7 +54,9 @@ impl NonceManagerState { // If the nonce, which currently assigned to the transaction, is not tracked, // we should assign the new nonce. warn!(?nonce, "Nonce is not tracked, assigning new nonce"); - return Ok((Assign, Some(nonce))); + return Ok(NonceAction::AssignNext { + old_nonce: Some(nonce), + }); }; if tracked_tx_uuid != tx_uuid { @@ -51,7 +70,9 @@ impl NonceManagerState { ?nonce_status, "Nonce is assigned to a different transaction, assigning new nonce" ); - return Ok((Assign, Some(nonce))); + return Ok(NonceAction::AssignNext { + old_nonce: Some(nonce), + }); } let finalized_nonce = self.get_finalized_nonce().await?; @@ -62,7 +83,9 @@ impl NonceManagerState { // then the transaction was dropped. But we are validating the nonce just before // we submit the transaction again. It means that we should assign the new nonce. warn!(?nonce, ?nonce_status, "Nonce is freed, assigning new nonce"); - Ok((Assign, Some(nonce))) + Ok(NonceAction::AssignNext { + old_nonce: Some(nonce), + }) } (Taken(_), Some(finalized_nonce)) if nonce <= finalized_nonce => { // If the nonce is taken, but it is below or equal to the finalized nonce, @@ -73,7 +96,9 @@ impl NonceManagerState { ?nonce_status, "Nonce is taken by the transaction but below or equal to the finalized nonce, assigning new nonce" ); - Ok((Assign, Some(nonce))) + Ok(NonceAction::AssignNext { + old_nonce: Some(nonce), + }) } (Taken(_), _) => { // If the nonce is taken or committed, we don't need to do anything. @@ -82,7 +107,7 @@ impl NonceManagerState { ?nonce_status, "Nonce is already assigned to the transaction, no action needed" ); - Ok((Noop, Some(nonce))) + Ok(NonceAction::Assign { nonce }) } (Committed(_), _) => { // If the nonce is taken or committed, we don't need to do anything. @@ -92,7 +117,7 @@ impl NonceManagerState { "Nonce is already assigned to the transaction, no action needed, \ but transaction should not be committed at this point" ); - Ok((Noop, Some(nonce))) + Ok(NonceAction::Assign { nonce }) } } } diff --git a/rust/main/lander/src/adapter/chains/ethereum/nonce/state/validate/tests.rs b/rust/main/lander/src/adapter/chains/ethereum/nonce/state/validate/tests.rs index 758080eeda9..2750c6357e8 100644 --- a/rust/main/lander/src/adapter/chains/ethereum/nonce/state/validate/tests.rs +++ b/rust/main/lander/src/adapter/chains/ethereum/nonce/state/validate/tests.rs @@ -28,9 +28,8 @@ async fn test_validate_assigned_nonce_none_nonce() { Some(address), ); - let (action, nonce) = state.validate_assigned_nonce(&tx).await.unwrap(); - assert!(matches!(action, NonceAction::Assign)); - assert_eq!(nonce, None); + let action = state.validate_assigned_nonce(&tx).await.unwrap(); + assert_eq!(action, NonceAction::AssignNext { old_nonce: None }); } #[tokio::test] @@ -49,9 +48,13 @@ async fn test_validate_assigned_nonce_not_tracked() { Some(address), ); - let (action, nonce) = state.validate_assigned_nonce(&tx).await.unwrap(); - assert!(matches!(action, NonceAction::Assign)); - assert_eq!(nonce, Some(nonce_val)); + let action = state.validate_assigned_nonce(&tx).await.unwrap(); + assert_eq!( + action, + NonceAction::AssignNext { + old_nonce: Some(nonce_val) + } + ); } #[tokio::test] @@ -74,9 +77,13 @@ async fn test_validate_assigned_nonce_tracked_different_tx_uuid() { Some(nonce_val), Some(address), ); - let (action, nonce) = state.validate_assigned_nonce(&tx).await.unwrap(); - assert!(matches!(action, NonceAction::Assign)); - assert_eq!(nonce, Some(nonce_val)); + let action = state.validate_assigned_nonce(&tx).await.unwrap(); + assert_eq!( + action, + NonceAction::AssignNext { + old_nonce: Some(nonce_val) + } + ); } #[tokio::test] @@ -99,9 +106,13 @@ async fn test_validate_assigned_nonce_freed_status() { Some(nonce_val), Some(address), ); - let (action, nonce) = state.validate_assigned_nonce(&tx).await.unwrap(); - assert!(matches!(action, NonceAction::Assign)); - assert_eq!(nonce, Some(nonce_val)); + let action = state.validate_assigned_nonce(&tx).await.unwrap(); + assert_eq!( + action, + NonceAction::AssignNext { + old_nonce: Some(nonce_val) + } + ); } #[tokio::test] @@ -126,9 +137,13 @@ async fn test_validate_assigned_nonce_taken_status_below_finalized() { Some(nonce_val), Some(address), ); - let (action, nonce) = state.validate_assigned_nonce(&tx).await.unwrap(); - assert!(matches!(action, NonceAction::Assign)); - assert_eq!(nonce, Some(nonce_val)); + let action = state.validate_assigned_nonce(&tx).await.unwrap(); + assert_eq!( + action, + NonceAction::AssignNext { + old_nonce: Some(nonce_val) + } + ); } #[tokio::test] @@ -153,9 +168,13 @@ async fn test_validate_assigned_nonce_taken_status_equal_finalized() { Some(nonce_val), Some(address), ); - let (action, nonce) = state.validate_assigned_nonce(&tx).await.unwrap(); - assert!(matches!(action, NonceAction::Assign)); - assert_eq!(nonce, Some(nonce_val)); + let action = state.validate_assigned_nonce(&tx).await.unwrap(); + assert_eq!( + action, + NonceAction::AssignNext { + old_nonce: Some(nonce_val) + } + ); } #[tokio::test] @@ -180,9 +199,8 @@ async fn test_validate_assigned_nonce_taken_status_above_finalized() { Some(nonce_val), Some(address), ); - let (action, nonce) = state.validate_assigned_nonce(&tx).await.unwrap(); - assert!(matches!(action, NonceAction::Noop)); - assert_eq!(nonce, Some(nonce_val)); + let action = state.validate_assigned_nonce(&tx).await.unwrap(); + assert_eq!(action, NonceAction::Assign { nonce: nonce_val }); } #[tokio::test] @@ -204,7 +222,34 @@ async fn test_validate_assigned_nonce_committed_status() { Some(nonce_val), Some(address), ); - let (action, nonce) = state.validate_assigned_nonce(&tx).await.unwrap(); - assert!(matches!(action, NonceAction::Noop)); - assert_eq!(nonce, Some(nonce_val)); + let action = state.validate_assigned_nonce(&tx).await.unwrap(); + assert_eq!(action, NonceAction::Assign { nonce: nonce_val }); +} + +#[tokio::test] +async fn test_validate_assigned_nonce_with_db() { + let (_, tx_db, nonce_db) = tmp_dbs(); + let address = Address::random(); + let metrics = EthereumAdapterMetrics::dummy_instance(); + + let uuid = TransactionUuid::random(); + let nonce_val = U256::from(7); + nonce_db + .store_nonce_by_transaction_uuid(&address, &uuid, &nonce_val) + .await + .expect("Failed to store nonce"); + + let state = Arc::new(NonceManagerState::new(nonce_db, tx_db, address, metrics)); + + // Set tracked_tx_uuid to uuid + state.set_tracked_tx_uuid(&nonce_val, &uuid).await.unwrap(); + + let tx = make_tx( + uuid, + TransactionStatus::PendingInclusion, + None, + Some(address), + ); + let action = state.validate_assigned_nonce(&tx).await.unwrap(); + assert_eq!(action, NonceAction::Assign { nonce: nonce_val }); } diff --git a/rust/main/lander/src/adapter/chains/ethereum/nonce/tests.rs b/rust/main/lander/src/adapter/chains/ethereum/nonce/tests.rs index 9d0aecb6976..82f26f3348f 100644 --- a/rust/main/lander/src/adapter/chains/ethereum/nonce/tests.rs +++ b/rust/main/lander/src/adapter/chains/ethereum/nonce/tests.rs @@ -39,5 +39,6 @@ pub fn make_tx( submission_attempts: 0, creation_timestamp: Default::default(), last_submission_attempt: None, + last_status_check: None, } } diff --git a/rust/main/lander/src/adapter/chains/ethereum/tests.rs b/rust/main/lander/src/adapter/chains/ethereum/tests.rs index 281d94a7078..24cc478d28c 100644 --- a/rust/main/lander/src/adapter/chains/ethereum/tests.rs +++ b/rust/main/lander/src/adapter/chains/ethereum/tests.rs @@ -133,6 +133,7 @@ pub fn dummy_evm_tx( submission_attempts: 0, creation_timestamp: chrono::Utc::now(), last_submission_attempt: None, + last_status_check: None, } } diff --git a/rust/main/lander/src/adapter/chains/ethereum/transaction/factory.rs b/rust/main/lander/src/adapter/chains/ethereum/transaction/factory.rs index 7b84b2cee4d..38c6df75edc 100644 --- a/rust/main/lander/src/adapter/chains/ethereum/transaction/factory.rs +++ b/rust/main/lander/src/adapter/chains/ethereum/transaction/factory.rs @@ -18,6 +18,7 @@ impl TransactionFactory { submission_attempts: 0, creation_timestamp: chrono::Utc::now(), last_submission_attempt: None, + last_status_check: None, } } } diff --git a/rust/main/lander/src/adapter/chains/factory.rs b/rust/main/lander/src/adapter/chains/factory.rs index 08f0bcae34b..24f826ee10d 100644 --- a/rust/main/lander/src/adapter/chains/factory.rs +++ b/rust/main/lander/src/adapter/chains/factory.rs @@ -50,6 +50,8 @@ impl AdapterFactory { } ChainConnectionConf::Starknet(_) => todo!(), ChainConnectionConf::CosmosNative(_) => todo!(), + ChainConnectionConf::Kaspa(_) => todo!(), + ChainConnectionConf::Radix(_) => todo!(), }; Ok(adapter) diff --git a/rust/main/lander/src/adapter/chains/radix.rs b/rust/main/lander/src/adapter/chains/radix.rs new file mode 100644 index 00000000000..b3638f5e402 --- /dev/null +++ b/rust/main/lander/src/adapter/chains/radix.rs @@ -0,0 +1,2 @@ +pub mod adapter; +pub mod precursor; diff --git a/rust/main/lander/src/adapter/chains/radix/adapter.rs b/rust/main/lander/src/adapter/chains/radix/adapter.rs new file mode 100644 index 00000000000..78393ad3f95 --- /dev/null +++ b/rust/main/lander/src/adapter/chains/radix/adapter.rs @@ -0,0 +1,272 @@ +use std::{sync::Arc, time::Duration}; + +use hyperlane_core::H512; +use hyperlane_radix::{DeliveredCalldata, RadixProviderForLander}; + +use crate::{ + adapter::{AdaptsChain, GasLimit, TxBuildingResult}, + payload::PayloadDetails, + transaction::Transaction, + DispatcherMetrics, FullPayload, LanderError, TransactionDropReason, TransactionStatus, +}; + +#[derive(Clone)] +pub struct RadixAdapter { + pub provider: Arc, +} + +#[async_trait::async_trait] +impl AdaptsChain for RadixAdapter { + async fn estimate_gas_limit( + &self, + _payload: &FullPayload, + ) -> Result, LanderError> { + todo!() + } + + async fn build_transactions(&self, _payloads: &[FullPayload]) -> Vec { + todo!() + } + + async fn simulate_tx(&self, _tx: &mut Transaction) -> Result, LanderError> { + todo!() + } + + async fn estimate_tx(&self, _tx: &mut Transaction) -> Result<(), LanderError> { + todo!() + } + + async fn submit(&self, _tx: &mut Transaction) -> Result<(), LanderError> { + todo!() + } + + async fn get_tx_hash_status(&self, hash: H512) -> Result { + let resp = self + .provider + .get_tx_hash_status(hash) + .await + .map_err(LanderError::ChainCommunicationError)?; + + match resp.status { + gateway_api_client::models::TransactionStatus::Unknown => { + Err(LanderError::TxHashNotFound(format!("{:x}", hash))) + } + gateway_api_client::models::TransactionStatus::Pending => { + Ok(TransactionStatus::Mempool) + } + gateway_api_client::models::TransactionStatus::Rejected => Ok( + TransactionStatus::Dropped(TransactionDropReason::DroppedByChain), + ), + gateway_api_client::models::TransactionStatus::CommittedFailure => Ok( + TransactionStatus::Dropped(TransactionDropReason::FailedSimulation), + ), + gateway_api_client::models::TransactionStatus::CommittedSuccess => { + Ok(TransactionStatus::Finalized) + } + } + } + + async fn tx_ready_for_resubmission(&self, _tx: &Transaction) -> bool { + true + } + + async fn reverted_payloads( + &self, + tx: &Transaction, + ) -> Result, LanderError> { + let delivered_calldata_list: Vec<(DeliveredCalldata, &PayloadDetails)> = tx + .payload_details + .iter() + .filter_map(|d| { + let calldata = d + .success_criteria + .as_ref() + .and_then(|s| serde_json::from_slice(s).ok())?; + Some((calldata, d)) + }) + .collect(); + + let mut reverted = Vec::new(); + for (delivered_calldata, payload_details) in delivered_calldata_list { + let success = self + .provider + .check_preview(&delivered_calldata) + .await + .unwrap_or(false); + if !success { + reverted.push(payload_details.clone()); + } + } + Ok(reverted) + } + + fn estimated_block_time(&self) -> &Duration { + todo!() + } + + fn max_batch_size(&self) -> u32 { + todo!() + } + + fn update_vm_specific_metrics(&self, _tx: &Transaction, _metrics: &DispatcherMetrics) { + todo!() + } + + async fn nonce_gap_exists(&self) -> bool { + todo!() + } + + async fn replace_tx(&self, _tx: &Transaction) -> Result<(), LanderError> { + todo!() + } +} + +#[cfg(test)] +mod tests { + use gateway_api_client::models::TransactionStatusResponse; + use hyperlane_core::ChainResult; + + use super::*; + + mockall::mock! { + pub MockRadixProviderForLander { + + } + + #[async_trait::async_trait] + impl RadixProviderForLander for MockRadixProviderForLander { + async fn get_tx_hash_status(&self, hash: H512) -> ChainResult; + async fn check_preview(&self, params: &DeliveredCalldata) -> ChainResult; + } + } + + #[tokio::test] + async fn get_tx_hash_status_pending() { + let mut provider = MockMockRadixProviderForLander::new(); + + provider.expect_get_tx_hash_status().returning(|_| { + Ok(TransactionStatusResponse { + status: gateway_api_client::models::TransactionStatus::Pending, + ..Default::default() + }) + }); + + let adapter = RadixAdapter { + provider: Arc::new(provider), + }; + + let hash = H512::zero(); + let tx_status = adapter + .get_tx_hash_status(hash) + .await + .expect("Failed to get tx hash status"); + + assert_eq!(tx_status, TransactionStatus::Mempool); + } + + #[tokio::test] + async fn get_tx_hash_status_rejected() { + let mut provider = MockMockRadixProviderForLander::new(); + + provider.expect_get_tx_hash_status().returning(|_| { + Ok(TransactionStatusResponse { + status: gateway_api_client::models::TransactionStatus::Rejected, + ..Default::default() + }) + }); + + let adapter = RadixAdapter { + provider: Arc::new(provider), + }; + + let hash = H512::zero(); + let tx_status = adapter + .get_tx_hash_status(hash) + .await + .expect("Failed to get tx hash status"); + + assert_eq!( + tx_status, + TransactionStatus::Dropped(TransactionDropReason::DroppedByChain) + ); + } + + #[tokio::test] + async fn get_tx_hash_status_unknown() { + let mut provider = MockMockRadixProviderForLander::new(); + + provider.expect_get_tx_hash_status().returning(|_| { + Ok(TransactionStatusResponse { + status: gateway_api_client::models::TransactionStatus::Unknown, + ..Default::default() + }) + }); + + let adapter = RadixAdapter { + provider: Arc::new(provider), + }; + + let hash = H512::zero(); + let tx_status = adapter.get_tx_hash_status(hash.clone()).await; + + match tx_status { + Err(LanderError::TxHashNotFound(tx_hash)) => { + assert_eq!(tx_hash, format!("{:x}", hash)); + } + val => { + panic!("Incorrect status {:?}", val); + } + } + } + + #[tokio::test] + async fn get_tx_hash_status_committed_failure() { + let mut provider = MockMockRadixProviderForLander::new(); + + provider.expect_get_tx_hash_status().returning(|_| { + Ok(TransactionStatusResponse { + status: gateway_api_client::models::TransactionStatus::CommittedFailure, + ..Default::default() + }) + }); + + let adapter = RadixAdapter { + provider: Arc::new(provider), + }; + + let hash = H512::zero(); + let tx_status = adapter + .get_tx_hash_status(hash.clone()) + .await + .expect("Failed to get tx hash status"); + + assert_eq!( + tx_status, + TransactionStatus::Dropped(TransactionDropReason::FailedSimulation) + ); + } + + #[tokio::test] + async fn get_tx_hash_status_committed_success() { + let mut provider = MockMockRadixProviderForLander::new(); + + provider.expect_get_tx_hash_status().returning(|_| { + Ok(TransactionStatusResponse { + status: gateway_api_client::models::TransactionStatus::CommittedSuccess, + ..Default::default() + }) + }); + + let adapter = RadixAdapter { + provider: Arc::new(provider), + }; + + let hash = H512::zero(); + let tx_status = adapter + .get_tx_hash_status(hash.clone()) + .await + .expect("Failed to get tx hash status"); + + assert_eq!(tx_status, TransactionStatus::Finalized); + } +} diff --git a/rust/main/lander/src/adapter/chains/radix/precursor.rs b/rust/main/lander/src/adapter/chains/radix/precursor.rs new file mode 100644 index 00000000000..41c4fab97ae --- /dev/null +++ b/rust/main/lander/src/adapter/chains/radix/precursor.rs @@ -0,0 +1,4 @@ +/// Radix Tx Precursor +#[allow(dead_code)] +#[derive(Clone, Debug)] +pub struct RadixTxPrecursor {} diff --git a/rust/main/lander/src/adapter/chains/sealevel/adapter.rs b/rust/main/lander/src/adapter/chains/sealevel/adapter.rs index 8e20c2201ea..5f37dff2711 100644 --- a/rust/main/lander/src/adapter/chains/sealevel/adapter.rs +++ b/rust/main/lander/src/adapter/chains/sealevel/adapter.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, sync::Arc, time::Duration}; +use std::{collections::HashMap, ops::Sub, sync::Arc, time::Duration}; use async_trait::async_trait; use chrono::Utc; @@ -250,25 +250,47 @@ impl SealevelAdapter { // query the tx hash from most to least finalized to learn what level of finality it has // the calls below can be parallelized if needed, but for now avoid rate limiting - if self + let tx_resp = self .client .get_transaction_with_commitment(signature, CommitmentConfig::finalized()) - .await - .is_ok() - { - info!("transaction finalized"); - return Ok(TransactionStatus::Finalized); + .await; + + if let Ok(tx) = tx_resp { + if let Some(meta) = tx.transaction.meta { + // It is possible for a failed transaction to be finalized. + // In this case, we need to re-submit. + if meta.err.is_some() { + warn!(?signature, ?tx_hash, "Transaction finalized, but failed"); + return Ok(TransactionStatus::Dropped( + TransactionDropReason::DroppedByChain, + )); + } else { + info!(?tx_hash, "transaction finalized"); + return Ok(TransactionStatus::Finalized); + } + } } - // the "confirmed" commitment is equivalent to being "included" in a block on evm - if self + let tx_resp = self .client .get_transaction_with_commitment(signature, CommitmentConfig::confirmed()) - .await - .is_ok() - { - info!("transaction included"); - return Ok(TransactionStatus::Included); + .await; + + // the "confirmed" commitment is equivalent to being "included" in a block on evm + if let Ok(tx) = tx_resp { + if let Some(meta) = tx.transaction.meta { + // It is possible for a failed transaction to be confirmed. + // In this case, we need to re-submit. + if meta.err.is_some() { + warn!(?signature, ?tx_hash, "Transaction included, but failed"); + return Ok(TransactionStatus::Dropped( + TransactionDropReason::DroppedByChain, + )); + } else { + info!(?tx_hash, "transaction included"); + return Ok(TransactionStatus::Included); + } + } } match self @@ -405,7 +427,7 @@ impl AdaptsChain for SealevelAdapter { let time_before_resubmission = self.time_before_resubmission(); if let Some(ref last_submission_time) = tx.last_submission_attempt { let seconds_since_last_submission = - (Utc::now() - last_submission_time).num_milliseconds() as u64; + Utc::now().sub(last_submission_time).num_milliseconds() as u64; return seconds_since_last_submission >= time_before_resubmission.as_millis() as u64; } true diff --git a/rust/main/lander/src/adapter/chains/sealevel/adapter/tests/tests_common.rs b/rust/main/lander/src/adapter/chains/sealevel/adapter/tests/tests_common.rs index 18744b86c80..613cb032262 100644 --- a/rust/main/lander/src/adapter/chains/sealevel/adapter/tests/tests_common.rs +++ b/rust/main/lander/src/adapter/chains/sealevel/adapter/tests/tests_common.rs @@ -13,8 +13,9 @@ use solana_sdk::{ transaction::Transaction as SealevelTransaction, }; use solana_transaction_status::{ - EncodedConfirmedTransactionWithStatusMeta, EncodedTransaction, - EncodedTransactionWithStatusMeta, UiConfirmedBlock, + option_serializer::OptionSerializer, EncodedConfirmedTransactionWithStatusMeta, + EncodedTransaction, EncodedTransactionWithStatusMeta, UiConfirmedBlock, + UiTransactionStatusMeta, }; use hyperlane_base::settings::{ChainConf, RawChainConf}; @@ -262,7 +263,21 @@ pub fn encoded_svm_transaction() -> EncodedConfirmedTransactionWithStatusMeta { slot: 43, transaction: EncodedTransactionWithStatusMeta { transaction: EncodedTransaction::LegacyBinary("binary".to_string()), - meta: None, + meta: Some(UiTransactionStatusMeta { + err: None, + status: Ok(()), + fee: 0, + pre_balances: Vec::new(), + post_balances: Vec::new(), + inner_instructions: OptionSerializer::None, + log_messages: OptionSerializer::None, + pre_token_balances: OptionSerializer::None, + post_token_balances: OptionSerializer::None, + rewards: OptionSerializer::None, + loaded_addresses: OptionSerializer::None, + return_data: OptionSerializer::None, + compute_units_consumed: OptionSerializer::None, + }), version: None, }, block_time: None, diff --git a/rust/main/lander/src/adapter/chains/sealevel/transaction/factory.rs b/rust/main/lander/src/adapter/chains/sealevel/transaction/factory.rs index 5589c32b7d4..a9e6aa6060d 100644 --- a/rust/main/lander/src/adapter/chains/sealevel/transaction/factory.rs +++ b/rust/main/lander/src/adapter/chains/sealevel/transaction/factory.rs @@ -19,6 +19,7 @@ impl TransactionFactory { submission_attempts: 0, creation_timestamp: chrono::Utc::now(), last_submission_attempt: None, + last_status_check: None, } } } diff --git a/rust/main/lander/src/dispatcher/db/loader.rs b/rust/main/lander/src/dispatcher/db/loader.rs index 17b0ad13df1..6466f8ddf4e 100644 --- a/rust/main/lander/src/dispatcher/db/loader.rs +++ b/rust/main/lander/src/dispatcher/db/loader.rs @@ -121,7 +121,7 @@ impl DbIterator { pub async fn load_from_db(&mut self, metrics: DispatcherMetrics) -> Result<(), LanderError> { let mut last_time_no_items_reported = Instant::now(); - let mut iteration_count = 0; + let mut iteration_count: usize = 0; loop { metrics.update_liveness_metric( format!("{}DbLoader", self.iterated_item_name,).as_str(), @@ -144,7 +144,7 @@ impl DbIterator { // sleep to wait for new items to be added sleep(Duration::from_millis(10)).await; - iteration_count += 1; + iteration_count = iteration_count.saturating_add(1); } else { debug!(?self, "Loaded item"); last_time_no_items_reported = Instant::now(); diff --git a/rust/main/lander/src/dispatcher/db/payload/payload_db.rs b/rust/main/lander/src/dispatcher/db/payload/payload_db.rs index d04c041151b..6dc8c7e23f5 100644 --- a/rust/main/lander/src/dispatcher/db/payload/payload_db.rs +++ b/rust/main/lander/src/dispatcher/db/payload/payload_db.rs @@ -116,7 +116,9 @@ impl PayloadDb for HyperlaneRocksDB { .is_none() { let highest_index = self.retrieve_highest_payload_index().await?; - let payload_index = highest_index + 1; + let payload_index = highest_index.checked_add(1).ok_or_else(|| { + DbError::Other("Highest payload index exhausted (u32::MAX)".into()) + })?; self.store_highest_payload_index(payload_index).await?; self.store_payload_index_by_uuid(payload_index, payload.uuid()) .await?; diff --git a/rust/main/lander/src/dispatcher/db/transaction/transaction_db.rs b/rust/main/lander/src/dispatcher/db/transaction/transaction_db.rs index a3b86166be7..ac52485f608 100644 --- a/rust/main/lander/src/dispatcher/db/transaction/transaction_db.rs +++ b/rust/main/lander/src/dispatcher/db/transaction/transaction_db.rs @@ -85,7 +85,7 @@ impl TransactionDb for HyperlaneRocksDB { .is_none() { let highest_index = self.retrieve_highest_transaction_index().await?; - let tx_index = highest_index + 1; + let tx_index = highest_index.saturating_add(1); self.store_highest_transaction_index(tx_index).await?; self.store_transaction_index_by_uuid(tx_index, &tx.uuid) .await?; diff --git a/rust/main/lander/src/dispatcher/stages/building_stage/building.rs b/rust/main/lander/src/dispatcher/stages/building_stage/building.rs index 71da926143f..4e22d33a028 100644 --- a/rust/main/lander/src/dispatcher/stages/building_stage/building.rs +++ b/rust/main/lander/src/dispatcher/stages/building_stage/building.rs @@ -28,7 +28,7 @@ pub struct BuildingStage { } impl BuildingStage { - #[instrument(skip(self), name = "BuildingStage::run")] + #[instrument(skip(self), name = "BuildingStage::run", fields(domain=%self.domain))] pub async fn run(&self) { loop { // event-driven by the Building queue diff --git a/rust/main/lander/src/dispatcher/stages/finality_stage.rs b/rust/main/lander/src/dispatcher/stages/finality_stage.rs index fa1462360f9..943f359a4e7 100644 --- a/rust/main/lander/src/dispatcher/stages/finality_stage.rs +++ b/rust/main/lander/src/dispatcher/stages/finality_stage.rs @@ -82,6 +82,7 @@ impl FinalityStage { } } + #[instrument(skip_all, fields(domain))] async fn receive_txs( mut tx_receiver: mpsc::Receiver, pool: FinalityStagePool, @@ -102,6 +103,7 @@ impl FinalityStage { } } + #[instrument(skip_all, fields(domain))] async fn process_txs( pool: FinalityStagePool, building_stage_queue: BuildingStageQueue, diff --git a/rust/main/lander/src/dispatcher/stages/inclusion_stage.rs b/rust/main/lander/src/dispatcher/stages/inclusion_stage.rs index 0feb7a9a463..45dd275e725 100644 --- a/rust/main/lander/src/dispatcher/stages/inclusion_stage.rs +++ b/rust/main/lander/src/dispatcher/stages/inclusion_stage.rs @@ -1,8 +1,10 @@ +use std::cmp::max; use std::collections::{HashMap, VecDeque}; use std::future::Future; use std::sync::Arc; use std::time::Duration; +use chrono::{DateTime, Utc}; use derive_new::new; use eyre::{eyre, Result}; use futures_util::future::try_join_all; @@ -27,6 +29,8 @@ pub type InclusionStagePool = Arc>>; pub const STAGE_NAME: &str = "InclusionStage"; +const MIN_TX_STATUS_CHECK_DELAY: Duration = Duration::from_millis(100); + pub struct InclusionStage { pub(crate) pool: InclusionStagePool, tx_receiver: mpsc::Receiver, @@ -77,6 +81,7 @@ impl InclusionStage { } } + #[instrument(skip_all, fields(domain))] pub async fn receive_txs( mut building_stage_receiver: mpsc::Receiver, pool: InclusionStagePool, @@ -105,16 +110,23 @@ impl InclusionStage { } } + #[instrument(skip_all, fields(domain))] async fn process_txs( pool: InclusionStagePool, finality_stage_sender: mpsc::Sender, state: DispatcherState, domain: String, ) -> Result<(), LanderError> { - loop { - // evaluate the pool every block - sleep(Duration::from_millis(10)).await; + let base_interval = *state.adapter.estimated_block_time(); + // Use adaptive polling interval based on block time, but never faster than 100ms + // for small block time chains, and never slower than 1/4 block time for responsiveness + let polling_interval = max( + base_interval.div_f64(4.0), // Never slower than 1/4 block time for responsiveness + MIN_TX_STATUS_CHECK_DELAY, // Never faster than 100ms to avoid excessive RPC calls + ); + loop { + sleep(polling_interval).await; Self::process_txs_step(&pool, &finality_stage_sender, &state, &domain).await?; } } @@ -143,7 +155,21 @@ impl InclusionStage { return Ok(()); } info!(pool_size=?pool_snapshot.len() , "Processing transactions in inclusion pool"); + + let base_interval = *state.adapter.estimated_block_time(); + let now = chrono::Utc::now(); + for (_, mut tx) in pool_snapshot { + // Update liveness metric on every tx as well. + // This prevents alert misfires when there are many txs to process. + state + .metrics + .update_liveness_metric(format!("{}::process_txs", STAGE_NAME).as_str(), domain); + + if !Self::tx_ready_for_processing(base_interval, now, &tx) { + continue; + } + if let Err(err) = Self::try_process_tx(tx.clone(), finality_stage_sender, state, pool).await { @@ -154,18 +180,67 @@ impl InclusionStage { Ok(()) } + fn tx_ready_for_processing( + base_interval: Duration, + now: DateTime, + tx: &Transaction, + ) -> bool { + // Implement per-transaction backoff: don't check transactions too frequently + if let Some(last_check) = tx.last_status_check { + let time_since_last_check = now.signed_duration_since(last_check); + + // Calculate the backoff interval based on how long the transaction has been pending + let tx_age = now.signed_duration_since(tx.creation_timestamp); + let backoff_interval = if tx_age.num_seconds() < 30 { + // New transactions: check every quarter of block time (responsive) + // But for very new transactions (< 1 second), allow immediate recheck for testing + if tx_age.num_seconds() < 1 { + Duration::ZERO // Immediate recheck for tests + } else { + max( + base_interval.div_f64(4.0), + MIN_TX_STATUS_CHECK_DELAY.div_f64(4.0), + ) + } + } else if tx_age.num_seconds() < 300 { + // Medium age transactions: check every half of block time + max( + base_interval.div_f64(2.0), + MIN_TX_STATUS_CHECK_DELAY.div_f64(2.0), + ) + } else { + // Old transactions: check every full block time + max(base_interval, MIN_TX_STATUS_CHECK_DELAY) + }; + + // Skip this transaction if we checked it too recently + if time_since_last_check + .to_std() + .unwrap_or(Duration::from_secs(0)) + < backoff_interval + { + return false; + } + } + true + } + #[instrument( skip_all, name = "InclusionStage::try_process_tx", fields(tx_uuid = ?tx.uuid, tx_status = ?tx.status, payloads = ?tx.payload_details) )] async fn try_process_tx( - tx: Transaction, + mut tx: Transaction, finality_stage_sender: &mpsc::Sender, state: &DispatcherState, pool: &InclusionStagePool, ) -> Result<()> { info!(?tx, "Processing inclusion stage transaction"); + + // Update the last status check timestamp before querying + tx.last_status_check = Some(chrono::Utc::now()); + let tx_status = call_until_success_or_nonretryable_error( || state.adapter.tx_status(&tx), "Querying transaction status", @@ -174,6 +249,12 @@ impl InclusionStage { .await?; info!(?tx, next_tx_status = ?tx_status, "Transaction status"); + // Update the transaction in the pool with the new timestamp + { + let mut pool_lock = pool.lock().await; + pool_lock.insert(tx.uuid.clone(), tx.clone()); + } + Self::try_process_tx_with_next_status(tx, tx_status, finality_stage_sender, state, pool) .await } @@ -227,7 +308,7 @@ impl InclusionStage { info!(?tx, "Processing pending transaction"); // update tx submission attempts - tx.submission_attempts += 1; + tx.submission_attempts = tx.submission_attempts.saturating_add(1); tx.last_submission_attempt = Some(chrono::Utc::now()); // Simulating transaction if it has never been submitted before diff --git a/rust/main/lander/src/dispatcher/stages/inclusion_stage/tests.rs b/rust/main/lander/src/dispatcher/stages/inclusion_stage/tests.rs index ed60edb9aff..39abe7c6cb6 100644 --- a/rust/main/lander/src/dispatcher/stages/inclusion_stage/tests.rs +++ b/rust/main/lander/src/dispatcher/stages/inclusion_stage/tests.rs @@ -22,7 +22,7 @@ async fn test_processing_included_txs() { let mut mock_adapter = MockAdapter::new(); mock_adapter .expect_estimated_block_time() - .return_const(Duration::from_millis(10)); + .return_const(Duration::from_millis(400)); mock_adapter .expect_tx_status() @@ -49,7 +49,7 @@ async fn test_unincluded_txs_reach_mempool() { let mut mock_adapter = MockAdapter::new(); mock_adapter .expect_estimated_block_time() - .return_const(Duration::from_millis(10)); + .return_const(Duration::from_millis(400)); mock_adapter .expect_tx_ready_for_resubmission() @@ -90,7 +90,7 @@ async fn test_failed_simulation() { let mut mock_adapter = MockAdapter::new(); mock_adapter .expect_estimated_block_time() - .return_const(Duration::from_millis(10)); + .return_const(Duration::from_millis(400)); mock_adapter .expect_tx_ready_for_resubmission() @@ -127,7 +127,7 @@ async fn test_failed_estimation() { let mut mock_adapter = MockAdapter::new(); mock_adapter .expect_estimated_block_time() - .return_const(Duration::from_millis(10)); + .return_const(Duration::from_millis(400)); mock_adapter .expect_tx_status() @@ -200,7 +200,7 @@ async fn test_transaction_status_dropped() { let mut mock_adapter = MockAdapter::new(); mock_adapter .expect_estimated_block_time() - .return_const(Duration::from_millis(10)); + .return_const(Duration::from_millis(400)); mock_adapter .expect_tx_status() .returning(|_| Ok(TransactionStatus::Dropped(TxDropReason::FailedSimulation))); @@ -224,7 +224,7 @@ async fn test_transaction_not_ready_for_resubmission() { let mut mock_adapter = MockAdapter::new(); mock_adapter .expect_estimated_block_time() - .return_const(Duration::from_millis(10)); + .return_const(Duration::from_millis(400)); mock_adapter .expect_tx_status() .returning(|_| Ok(TransactionStatus::PendingInclusion)); @@ -251,7 +251,7 @@ async fn test_failed_submission_after_simulation_and_estimation() { let mut mock_adapter = MockAdapter::new(); mock_adapter .expect_estimated_block_time() - .return_const(Duration::from_millis(10)); + .return_const(Duration::from_millis(400)); mock_adapter .expect_tx_status() .returning(|_| Ok(TransactionStatus::PendingInclusion)); @@ -285,7 +285,7 @@ async fn test_transaction_included_immediately() { let mut mock_adapter = MockAdapter::new(); mock_adapter .expect_estimated_block_time() - .return_const(Duration::from_millis(10)); + .return_const(Duration::from_millis(400)); mock_adapter .expect_tx_status() .returning(|_| Ok(TransactionStatus::Included)); @@ -309,7 +309,7 @@ async fn test_transaction_pending_then_included() { let mut mock_adapter = MockAdapter::new(); mock_adapter .expect_estimated_block_time() - .return_const(Duration::from_millis(10)); + .return_const(Duration::from_millis(400)); let mut call_count = 0; mock_adapter.expect_tx_status().returning(move |_| { call_count += 1; @@ -409,7 +409,8 @@ async fn run_stage( let stage = tokio::spawn(async move { stage.run().await }); - // give the inclusion stage 100ms to send the transaction(s) to the receiver + // give the inclusion stage more time to send the transaction(s) to the receiver + // with adaptive polling, we need at least 2 polling cycles (200ms minimum) let _ = tokio::select! { // this arm runs indefinitely res = stage => res, @@ -417,8 +418,8 @@ async fn run_stage( received = receiving_closure => { return received; }, - // this arm is the timeout - _ = sleep(Duration::from_millis(100)) => { + // this arm is the timeout - increased to accommodate adaptive polling + _ = sleep(Duration::from_millis(500)) => { return vec![] }, }; @@ -453,3 +454,107 @@ async fn assert_tx_status( } } } + +#[tokio::test] +async fn test_reasonable_receipt_query_frequency() { + // This test enforces reasonable eth_getTransactionReceipt query frequency + // It will FAIL with the current 10ms polling implementation + // and PASS once we implement adaptive polling based on block time + + use std::sync::atomic::{AtomicU32, Ordering}; + use std::sync::Arc as StdArc; + + let call_counter = StdArc::new(AtomicU32::new(0)); + let call_counter_clone = call_counter.clone(); + + let mut mock_adapter = MockAdapter::new(); + mock_adapter + .expect_estimated_block_time() + .return_const(Duration::from_millis(12000)); // Ethereum block time ~12s + + // Track how many times tx_status is called (which triggers get_transaction_receipt) + mock_adapter.expect_tx_status().returning(move |_| { + call_counter_clone.fetch_add(1, Ordering::SeqCst); + Ok(TransactionStatus::PendingInclusion) // Always pending to keep it in the pool + }); + + mock_adapter + .expect_tx_ready_for_resubmission() + .returning(|_| false); // Don't resubmit to avoid extra complexity + + let (payload_db, tx_db, _) = tmp_dbs(); + let (building_stage_sender, building_stage_receiver) = mpsc::channel(5); + let (finality_stage_sender, _finality_stage_receiver) = mpsc::channel(5); + + let state = DispatcherState::new( + payload_db.clone(), + tx_db.clone(), + Arc::new(mock_adapter), + DispatcherMetrics::dummy_instance(), + "test".to_string(), + ); + + let inclusion_stage = InclusionStage::new( + building_stage_receiver, + finality_stage_sender, + state, + "test".to_string(), + ); + + // Create 3 transactions that will stay pending + const NUM_TXS: usize = 3; + let txs_created = create_random_txs_and_store_them( + NUM_TXS, + &payload_db, + &tx_db, + TransactionStatus::PendingInclusion, + ) + .await; + + // Send transactions to inclusion stage + for tx in txs_created.iter() { + building_stage_sender.send(tx.clone()).await.unwrap(); + } + + // Let the inclusion stage run for 1 second to get a good sample + let stage_handle = tokio::spawn(async move { inclusion_stage.run().await }); + + sleep(Duration::from_millis(1000)).await; + stage_handle.abort(); + + let total_calls = call_counter.load(Ordering::SeqCst); + let queries_per_second = total_calls as f64 / 1.0; + let queries_per_second_per_tx = queries_per_second / NUM_TXS as f64; + + println!( + "Total tx_status calls (receipt queries) in 1 second: {}", + total_calls + ); + println!( + "Queries per second per transaction: {:.2}", + queries_per_second_per_tx + ); + + // REASONABLE EXPECTATIONS FOR ETHEREUM (12s block time): + // - New transactions: Check every 3s (1/4 block time) = 0.33 queries/sec/tx + // - Older transactions: Exponential backoff, average ~0.1 queries/sec/tx + // - For 3 transactions: Maximum ~1 query/sec total + + // This test will FAIL with current 10ms implementation (making ~80 queries/sec/tx) + // but PASS with adaptive polling (making ~0.1-0.3 queries/sec/tx) + + assert!( + queries_per_second <= 5.0, + "Too many receipt queries! Expected ≤5 queries/sec total, got {:.1}. \ + Current implementation makes {:.1} queries/sec/tx but should make ≤0.5 queries/sec/tx", + queries_per_second, + queries_per_second_per_tx + ); + + assert!( + queries_per_second_per_tx <= 1.0, + "Receipt queries per transaction too high! Expected ≤1.0 queries/sec/tx, got {:.2}. \ + With 12s Ethereum blocks, should check at most every 3s (0.33 queries/sec/tx)", + queries_per_second_per_tx + ); +} diff --git a/rust/main/lander/src/error.rs b/rust/main/lander/src/error.rs index 0b375722562..eb1b93d4de6 100644 --- a/rust/main/lander/src/error.rs +++ b/rust/main/lander/src/error.rs @@ -71,6 +71,9 @@ const EVM_GAS_UNDERPRICED_ERRORS: [&str; 4] = [ const SVM_BLOCKHASH_NOT_FOUND_ERROR: &str = "Blockhash not found"; +// Add constant for block gas limit error +const EVM_EXCEEDS_BLOCK_GAS_LIMIT_ERROR: &str = "exceeds block gas limit"; + // this error is returned randomly by the `TestTokenRecipient`, // to simulate delivery errors const SIMULATED_DELIVERY_FAILURE_ERROR: &str = "block hash ends in 0"; @@ -90,17 +93,21 @@ impl IsRetryable for LanderError { false } ChainCommunicationError(err) => { - if err.to_string().contains(SIMULATED_DELIVERY_FAILURE_ERROR) { + let err_str = err.to_string(); + // If error contains block gas limit, always non-retryable + if err_str.contains(EVM_EXCEEDS_BLOCK_GAS_LIMIT_ERROR) { + return false; + } + if err_str.contains(SIMULATED_DELIVERY_FAILURE_ERROR) { return true; } if EVM_GAS_UNDERPRICED_ERRORS .iter() - .any(|&e| err.to_string().contains(e)) + .any(|&e| err_str.contains(e)) { return true; } - - if err.to_string().contains(SVM_BLOCKHASH_NOT_FOUND_ERROR) { + if err_str.contains(SVM_BLOCKHASH_NOT_FOUND_ERROR) { return true; } // TODO: add logic to classify based on the error message @@ -125,3 +132,6 @@ impl IsRetryable for LanderError { } } } + +#[cfg(test)] +mod tests; diff --git a/rust/main/lander/src/error/tests.rs b/rust/main/lander/src/error/tests.rs new file mode 100644 index 00000000000..7b9ccb518bd --- /dev/null +++ b/rust/main/lander/src/error/tests.rs @@ -0,0 +1,34 @@ +use hyperlane_core::ChainCommunicationError; + +use crate::error::{IsRetryable, LanderError}; + +fn make_chain_comm_err(msg: &str) -> LanderError { + LanderError::ChainCommunicationError(ChainCommunicationError::from(eyre::eyre!(msg.to_owned()))) +} + +#[test] +fn test_exceeds_block_gas_limit_non_retryable() { + let err = make_chain_comm_err("exceeds block gas limit"); + assert!( + !err.is_retryable(), + "Should not be retryable if exceeds block gas limit" + ); +} + +#[test] +fn test_exceeds_block_gas_limit_with_retryable_string_non_retryable() { + let err = make_chain_comm_err("already known; exceeds block gas limit"); + assert!( + !err.is_retryable(), + "Should not be retryable if exceeds block gas limit is present, even with retryable string" + ); +} + +#[test] +fn test_retryable_string_only() { + let err = make_chain_comm_err("already known"); + assert!( + err.is_retryable(), + "Should be retryable if only retryable string is present" + ); +} diff --git a/rust/main/lander/src/lib.rs b/rust/main/lander/src/lib.rs index f59beb5dd60..cb6cda50ecf 100644 --- a/rust/main/lander/src/lib.rs +++ b/rust/main/lander/src/lib.rs @@ -1,4 +1,5 @@ #![deny(clippy::unwrap_used, clippy::panic)] +#![deny(clippy::arithmetic_side_effects)] pub use dispatcher::entrypoint::{DispatcherEntrypoint, Entrypoint}; pub use dispatcher::{DatabaseOrPath, Dispatcher, DispatcherMetrics, DispatcherSettings}; diff --git a/rust/main/lander/src/tests/evm/tests_inclusion_stage.rs b/rust/main/lander/src/tests/evm/tests_inclusion_stage.rs index 44c92566187..e8178329cfc 100644 --- a/rust/main/lander/src/tests/evm/tests_inclusion_stage.rs +++ b/rust/main/lander/src/tests/evm/tests_inclusion_stage.rs @@ -28,6 +28,7 @@ use crate::{DispatcherMetrics, FullPayload, PayloadStatus, TransactionStatus}; /// This is block time for unit tests which assume that we are ready to re-submit every time, /// so, it is set to 0 nanoseconds so that we can test the inclusion stage without waiting const TEST_BLOCK_TIME: Duration = Duration::from_nanos(0); +const TEST_MINIMUM_TIME_BETWEEN_RESUBMISSIONS: Duration = Duration::from_nanos(0); const TEST_DOMAIN: KnownHyperlaneDomain = KnownHyperlaneDomain::Arbitrum; static TEST_GAS_LIMIT: LazyLock = LazyLock::new(|| { apply_estimate_buffer_to_ethers(EthersU256::from(21000), &TEST_DOMAIN.into()).unwrap() @@ -77,6 +78,7 @@ async fn test_inclusion_happy_path() { expected_tx_states, mock_evm_provider, block_time, + TEST_MINIMUM_TIME_BETWEEN_RESUBMISSIONS, ) .await; } @@ -213,6 +215,7 @@ async fn test_inclusion_gas_spike() { expected_tx_states, mock_evm_provider, block_time, + TEST_MINIMUM_TIME_BETWEEN_RESUBMISSIONS, ) .await; } @@ -306,6 +309,7 @@ async fn test_inclusion_gas_underpriced() { expected_tx_states, mock_evm_provider, block_time, + TEST_MINIMUM_TIME_BETWEEN_RESUBMISSIONS, ) .await; } @@ -443,6 +447,7 @@ async fn test_tx_which_fails_simulation_after_submission_is_delivered() { expected_tx_states, mock_evm_provider, block_time, + TEST_MINIMUM_TIME_BETWEEN_RESUBMISSIONS, ) .await; } @@ -536,6 +541,7 @@ async fn test_inclusion_escalate_but_old_hash_finalized() { expected_tx_states, mock_evm_provider, block_time, + TEST_MINIMUM_TIME_BETWEEN_RESUBMISSIONS, ) .await; } @@ -632,6 +638,7 @@ async fn test_escalate_gas_and_upgrade_legacy_to_eip1559() { expected_tx_states, mock_evm_provider, block_time, + TEST_MINIMUM_TIME_BETWEEN_RESUBMISSIONS, ) .await; } @@ -657,8 +664,12 @@ async fn test_inclusion_estimate_gas_limit_error_drops_tx_and_payload() { }); let signer = H160::random(); - let dispatcher_state = - mock_dispatcher_state_with_provider(mock_evm_provider, signer, block_time); + let dispatcher_state = mock_dispatcher_state_with_provider( + mock_evm_provider, + signer, + block_time, + TEST_MINIMUM_TIME_BETWEEN_RESUBMISSIONS, + ); let (finality_stage_sender, mut finality_stage_receiver) = mpsc::channel(100); let inclusion_stage_pool = Arc::new(tokio::sync::Mutex::new(HashMap::new())); @@ -765,8 +776,12 @@ async fn test_inclusion_stage_nonce_too_low_error_does_not_drop_tx() { .returning(move |_| Ok(None)); let signer = H160::random(); - let dispatcher_state = - mock_dispatcher_state_with_provider(mock_evm_provider, signer, block_time); + let dispatcher_state = mock_dispatcher_state_with_provider( + mock_evm_provider, + signer, + block_time, + TEST_MINIMUM_TIME_BETWEEN_RESUBMISSIONS, + ); let (finality_stage_sender, mut finality_stage_receiver) = mpsc::channel(100); let inclusion_stage_pool = Arc::new(tokio::sync::Mutex::new(HashMap::new())); @@ -831,14 +846,18 @@ async fn test_inclusion_stage_nonce_too_low_error_does_not_drop_tx() { #[tokio::test] #[traced_test] -async fn test_tx_ready_for_resubmission() { +async fn test_tx_ready_for_resubmission_block_time() { let block_time = Duration::from_millis(20); let mut mock_evm_provider = MockEvmProvider::new(); mock_finalized_block_number(&mut mock_evm_provider); let signer = H160::random(); - let dispatcher_state = - mock_dispatcher_state_with_provider(mock_evm_provider, signer, block_time); + let dispatcher_state = mock_dispatcher_state_with_provider( + mock_evm_provider, + signer, + block_time, + TEST_MINIMUM_TIME_BETWEEN_RESUBMISSIONS, + ); let mut created_txs = mock_evm_txs( 1, @@ -880,15 +899,87 @@ async fn test_tx_ready_for_resubmission() { ); } +#[tokio::test] +#[traced_test] +async fn test_tx_ready_for_resubmission_minimum_time_between_submissions() { + let block_time = Duration::from_millis(20); + let minimum_time_between_resubmissions = block_time * 2; + let mut mock_evm_provider = MockEvmProvider::new(); + mock_finalized_block_number(&mut mock_evm_provider); + + let signer = H160::random(); + let dispatcher_state = mock_dispatcher_state_with_provider( + mock_evm_provider, + signer, + block_time, + minimum_time_between_resubmissions, + ); + + let mut created_txs = mock_evm_txs( + 1, + &dispatcher_state.payload_db, + &dispatcher_state.tx_db, + TransactionStatus::PendingInclusion, + signer, + ExpectedTxType::Eip1559, + ) + .await; + let mut tx = created_txs.remove(0); + let duration_since_epoch = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap(); + #[allow(deprecated)] + let mock_last_submission_attempt = Utc.timestamp( + duration_since_epoch.as_secs() as i64, + duration_since_epoch.subsec_nanos(), + ); + tx.last_submission_attempt = Some(mock_last_submission_attempt); + + // Ensure the transaction is not ready for resubmission immediately + assert!( + !dispatcher_state + .adapter + .tx_ready_for_resubmission(&tx) + .await + ); + + // Simulate block time passing + tokio::time::sleep(block_time).await; + + // Ensure the transaction is not ready for resubmission after block time + assert!( + !dispatcher_state + .adapter + .tx_ready_for_resubmission(&tx) + .await + ); + + // Simulate another block time passing + tokio::time::sleep(block_time).await; + + // Ensure the transaction is now ready for resubmission + assert!( + dispatcher_state + .adapter + .tx_ready_for_resubmission(&tx) + .await + ); +} + async fn run_and_expect_successful_inclusion( initial_tx_type: ExpectedTxType, mut expected_tx_states: Vec, mock_evm_provider: MockEvmProvider, block_time: Duration, + minimum_time_between_resubmissions: Duration, ) { let signer = H160::random(); - let dispatcher_state = - mock_dispatcher_state_with_provider(mock_evm_provider, signer, block_time); + let dispatcher_state = mock_dispatcher_state_with_provider( + mock_evm_provider, + signer, + block_time, + minimum_time_between_resubmissions, + ); let (finality_stage_sender, mut finality_stage_receiver) = mpsc::channel(100); let inclusion_stage_pool = Arc::new(tokio::sync::Mutex::new(HashMap::new())); @@ -1057,6 +1148,7 @@ pub fn mock_dispatcher_state_with_provider( provider: MockEvmProvider, signer: H160, block_time: Duration, + minimum_time_between_resubmissions: Duration, ) -> DispatcherState { let (payload_db, tx_db, nonce_db) = tmp_dbs(); let adapter = mock_ethereum_adapter( @@ -1066,6 +1158,7 @@ pub fn mock_dispatcher_state_with_provider( nonce_db, signer, block_time, + minimum_time_between_resubmissions, ); DispatcherState::new( payload_db, @@ -1083,6 +1176,7 @@ fn mock_ethereum_adapter( nonce_db: Arc, signer: H160, block_time: Duration, + minimum_time_between_resubmissions: Duration, ) -> EthereumAdapter { let domain: HyperlaneDomain = TEST_DOMAIN.into(); let provider = Arc::new(provider); @@ -1121,6 +1215,7 @@ fn mock_ethereum_adapter( batch_contract_address, payload_db, signer, + minimum_time_between_resubmissions, } } diff --git a/rust/main/lander/src/tests/svm/tests_inclusion_stage.rs b/rust/main/lander/src/tests/svm/tests_inclusion_stage.rs index c4ca42c1af9..d06b5324bfa 100644 --- a/rust/main/lander/src/tests/svm/tests_inclusion_stage.rs +++ b/rust/main/lander/src/tests/svm/tests_inclusion_stage.rs @@ -32,7 +32,7 @@ use crate::{ TransactionStatus, }; -const TEST_BLOCK_TIME: Duration = Duration::from_millis(50); +const TEST_BLOCK_TIME: Duration = Duration::from_millis(400); const TEST_DOMAIN: KnownHyperlaneDomain = KnownHyperlaneDomain::SolanaMainnet; #[tokio::test] diff --git a/rust/main/lander/src/tests/test_utils.rs b/rust/main/lander/src/tests/test_utils.rs index 4e06a14a9f6..bd1340b2084 100644 --- a/rust/main/lander/src/tests/test_utils.rs +++ b/rust/main/lander/src/tests/test_utils.rs @@ -64,6 +64,7 @@ pub(crate) fn dummy_tx(payloads: Vec, status: TransactionStatus) -> submission_attempts: 0, creation_timestamp: chrono::Utc::now(), last_submission_attempt: None, + last_status_check: None, } } diff --git a/rust/main/lander/src/transaction/types.rs b/rust/main/lander/src/transaction/types.rs index 66c629c0eb4..ec97c6f880e 100644 --- a/rust/main/lander/src/transaction/types.rs +++ b/rust/main/lander/src/transaction/types.rs @@ -1,7 +1,7 @@ // TODO: re-enable clippy warnings #![allow(dead_code)] -use std::{collections::HashMap, ops::Deref}; +use std::{collections::HashMap, fmt, ops::Deref}; use chrono::{DateTime, Utc}; @@ -17,7 +17,7 @@ pub type TransactionUuid = UniqueIdentifier; pub type SignerAddress = H256; /// Full details about a transaction -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, PartialEq, Eq)] +#[derive(Clone, serde::Deserialize, serde::Serialize, PartialEq, Eq)] pub struct Transaction { /// unique tx identifier. Used as primary key in the db. pub uuid: TransactionUuid, @@ -36,6 +36,19 @@ pub struct Transaction { pub creation_timestamp: DateTime, /// the date and time the transaction was last submitted pub last_submission_attempt: Option>, + /// the date and time the transaction status was last checked + pub last_status_check: Option>, +} + +impl fmt::Debug for Transaction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Transaction") + .field("uuid", &self.uuid) + .field("tx_hashes_count", &self.tx_hashes.len()) + .field("status", &self.status) + .field("submission_attempts", &self.submission_attempts) + .finish_non_exhaustive() + } } #[derive(Default, Debug, Clone, serde::Deserialize, serde::Serialize, PartialEq, Eq, Hash)] @@ -61,7 +74,8 @@ impl TransactionStatus { // count the occurrences of each successfully queried hash status for status in statuses.iter().flatten() { - *status_counts.entry(status.clone()).or_insert(0) += 1; + let entry = status_counts.entry(status.clone()).or_insert(0); + *entry = entry.saturating_add(1); } let finalized_count = status_counts diff --git a/rust/main/rust-toolchain b/rust/main/rust-toolchain index bbf217f21b9..4bd1ff459ff 100644 --- a/rust/main/rust-toolchain +++ b/rust/main/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "1.81.0" +channel = "1.86.0" profile = "default" diff --git a/rust/main/utils/abigen/src/lib.rs b/rust/main/utils/abigen/src/lib.rs index 59792424047..201318a40c0 100644 --- a/rust/main/utils/abigen/src/lib.rs +++ b/rust/main/utils/abigen/src/lib.rs @@ -184,25 +184,15 @@ fn fmt_file(path: &Path) { /// Get the rustfmt binary path. #[cfg(feature = "fmt")] fn rustfmt_path() -> &'static Path { - use std::path::PathBuf; + use std::{path::PathBuf, sync::LazyLock}; - // lazy static var - static mut PATH: Option = None; - - if let Some(path) = unsafe { PATH.as_ref() } { - return path; - } - - if let Ok(path) = std::env::var("RUSTFMT") { - unsafe { - PATH = Some(PathBuf::from(path)); - PATH.as_ref().unwrap() + static PATH: LazyLock = LazyLock::new(|| { + if let Ok(path) = std::env::var("RUSTFMT") { + PathBuf::from(path) + } else { + which::which("rustfmt").unwrap_or_else(|_| "rustfmt".into()) } - } else { - // assume it is in PATH - unsafe { - PATH = Some(which::which("rustmft").unwrap_or_else(|_| "rustfmt".into())); - PATH.as_ref().unwrap() - } - } + }); + + PATH.as_path() } diff --git a/rust/main/utils/kaspa-tools/Cargo.toml b/rust/main/utils/kaspa-tools/Cargo.toml new file mode 100644 index 00000000000..fd02224d11a --- /dev/null +++ b/rust/main/utils/kaspa-tools/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "kaspa-tools" +version = "0.1.0" +edition = "2021" + +[dependencies] +kaspa-core = { workspace = true } +dym_kas_core = { path = "../../../../dymension/libs/kaspa/lib/core", package = "core" } +dym_kas_hardcode = { path = "../../../../dymension/libs/kaspa/lib/hardcode", package = "hardcode" } +dymension-kaspa = { path = "../../chains/dymension-kaspa" } +secp256k1 = { workspace = true } +serde = { version = "^1.0", features = ["derive"] } +serde_json = { workspace = true } +hex = "0.4.3" +rustls = {workspace = true} +clap = { workspace = true, features = ["derive"] } +tokio = { workspace = true, features = ["full"] } +tokio-util = { version = "0.7.15" } +eyre = { workspace = true } +kaspa-wallet-core = { workspace = true } +kaspa-wallet-keys = { workspace = true } +kaspa-consensus-core = { workspace = true } +kaspa-addresses = { workspace = true } +kaspa-hashes = { workspace = true } +workflow-core = { workspace = true } +uuid = { version = "1.11", features = ["v4"] } +hyperlane-core = { path = "../../hyperlane-core", features = ["ethers"] } +hyperlane-cosmos = { path = "../../chains/hyperlane-cosmos" } +hyperlane-metric = { path = "../../hyperlane-metric" } +rand = { version = "0.9" } +rand_08 = { version = "0.8.5", package = "rand" } +rand_distr = "0.5" +rand_core = { version = "0.6.4" } +tracing = "0.1" +tracing-subscriber = { version = "0.3", default-features = false } +tracing-error = "0.2" +tracing-futures = "0.2" +chrono = { version = "0.4" } +hyperlane-cosmos-rs = { package = "hyperlane-cosmos-dymension-rs", git = "https://github.com/dymensionxyz/hyperlane-cosmos-rs", rev = "8fd5d6a" } +cosmrs = { version = "0.22.0", default-features = false, features = [ + "cosmwasm", + "rpc", + "tokio", + "grpc", + "getrandom", +] } +k256 = { version = "0.13.4", features = ["arithmetic", "std", "ecdsa"] } +url = "2.3" +thiserror = "2.0" +cosmos-sdk-proto = { version = "0.26.1" } +cometbft = { workspace = true, features = ["secp256k1"] } +cometbft-rpc = { workspace = true, features = ["http-client","secp256k1"] } +aws-config = { version = "1.5", features = ["behavior-version-latest"] } +aws-sdk-secretsmanager = "1.55" +aws-sdk-kms = "1.48" +aws-smithy-types = "1.2" diff --git a/rust/main/utils/kaspa-tools/src/main.rs b/rust/main/utils/kaspa-tools/src/main.rs new file mode 100644 index 00000000000..d4b472f4c57 --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/main.rs @@ -0,0 +1,96 @@ +use clap::Parser; +use x::args::{Cli, Commands, SimulateTrafficCli, ValidatorAction, ValidatorBackend}; + +mod sim; +use sim::{SimulateTrafficArgs, TrafficSim}; +mod x; + +async fn run_sim(args: SimulateTrafficCli) -> eyre::Result<()> { + let sim = SimulateTrafficArgs::try_from(args)?; + let sim = TrafficSim::new(sim).await?; + sim.run().await +} + +async fn run(cli: Cli) { + tracing_subscriber::fmt::init(); + rustls::crypto::aws_lc_rs::default_provider() + .install_default() + .expect("Failed to install rustls crypto provider"); + match cli.command { + Commands::Recipient(args) => { + let converted = + dymension_kaspa::ops::addr::kaspa_address_to_hex_recipient(&args.address); + println!("{converted}",); + } + Commands::Deposit(args) => { + let res = x::deposit::do_deposit(args.to_deposit_args()).await; + if let Err(e) = res { + eprintln!("Error: {e}"); + } + } + Commands::Escrow(args) => { + let pub_keys = args + .pub_keys + .split(",") + .map(|s| s.trim()) + .collect::>(); + let e = x::escrow::get_escrow_address(pub_keys, args.required_signatures, &args.env); + println!("Escrow address: {e}"); + } + Commands::Validator { action } => match action { + ValidatorAction::Create { backend } => match backend { + ValidatorBackend::Local(args) => { + if let Err(e) = x::validator::handle_local_backend(args) { + eprintln!("Error: {}", e); + std::process::exit(1); + } + } + ValidatorBackend::Aws(args) => { + if let Err(e) = x::validator::handle_aws_backend(args).await { + eprintln!("Error: {}", e); + std::process::exit(1); + } + } + }, + }, + Commands::Relayer => { + let signer = x::relayer::create_relayer(); + println!("Relayer address: {}", signer.address); + println!("Relayer private key: {}", signer.private_key); + } + Commands::Sim(args) => { + if let Err(e) = run_sim(args).await { + eprintln!("error: {e}"); + std::process::exit(1); + } + } + Commands::Roundtrip(args) => { + if let Err(e) = sim::roundtrip::do_roundtrip(args).await { + eprintln!("error: {e}"); + std::process::exit(1); + } + } + Commands::DecodePayload(args) => { + if let Err(e) = x::decode_payload::decode_payload(&args.payload) { + eprintln!("decode payload: {e}"); + std::process::exit(1); + } + } + Commands::ComputeDepositId(args) => { + if let Err(e) = x::compute_deposit_id::compute_deposit_id( + &args.payload, + &args.tx_id, + args.utxo_index, + ) { + eprintln!("compute deposit id: {e}"); + std::process::exit(1); + } + } + } +} + +#[tokio::main] +async fn main() { + let cli = Cli::parse(); + run(cli).await; +} diff --git a/rust/main/utils/kaspa-tools/src/sim/hub_whale_pool.rs b/rust/main/utils/kaspa-tools/src/sim/hub_whale_pool.rs new file mode 100644 index 00000000000..35df7a4bdf1 --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/sim/hub_whale_pool.rs @@ -0,0 +1,132 @@ +use super::key_cosmos::EasyHubKey; +use super::util::create_cosmos_provider; +use eyre::Result; +use hyperlane_cosmos::{native::ModuleQueryClient, CosmosProvider}; +use std::sync::{Arc, Mutex}; +use std::time::Instant; +use tokio::sync::Mutex as AsyncMutex; +use tracing::{debug, info}; + +pub struct HubWhale { + pub provider: CosmosProvider, + pub last_used: Mutex, + pub id: usize, + pub tx_lock: AsyncMutex<()>, +} + +impl HubWhale { + fn mark_used(&self) { + *self.last_used.lock().unwrap() = Instant::now(); + } + + fn last_used(&self) -> Instant { + *self.last_used.lock().unwrap() + } + + pub async fn lock_for_tx(&self) -> tokio::sync::MutexGuard<'_, ()> { + self.tx_lock.lock().await + } +} + +pub struct HubWhalePool { + whales: Vec>, +} + +impl HubWhalePool { + pub async fn new( + priv_keys: Vec, + rpc_url: String, + grpc_url: String, + chain_id: String, + prefix: String, + denom: String, + decimals: u32, + ) -> Result { + if priv_keys.is_empty() { + return Err(eyre::eyre!("hub whale private keys list cannot be empty")); + } + + info!("Initializing {} Hub whales", priv_keys.len()); + + let mut whales = Vec::new(); + let base_time = Instant::now(); + + for (id, priv_key_hex) in priv_keys.into_iter().enumerate() { + let key = EasyHubKey::from_hex(&priv_key_hex); + let provider = create_cosmos_provider( + &key, &rpc_url, &grpc_url, &chain_id, &prefix, &denom, decimals, + ) + .await?; + + debug!( + "Hub whale initialized: id={} address={}", + id, + key.signer().address_string + ); + + let whale = Arc::new(HubWhale { + provider, + last_used: Mutex::new(base_time - std::time::Duration::from_secs(id as u64)), + id, + tx_lock: AsyncMutex::new(()), + }); + + whales.push(whale); + + info!("Initialized Hub whale: id={}", id); + } + + info!("All {} Hub whales initialized", whales.len()); + + Ok(Self { whales }) + } + + pub fn select_whale(&self) -> Arc { + let selected = self + .whales + .iter() + .min_by_key(|w| w.last_used()) + .expect("whale pool cannot be empty") + .clone(); + + selected.mark_used(); + selected + } + + pub fn count(&self) -> usize { + self.whales.len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_lru_tracking() { + let base = Instant::now(); + + let w1 = Arc::new(HubWhale { + provider: unsafe { std::mem::zeroed() }, + last_used: Mutex::new(base - std::time::Duration::from_secs(10)), + id: 1, + tx_lock: AsyncMutex::new(()), + }); + let w2 = Arc::new(HubWhale { + provider: unsafe { std::mem::zeroed() }, + last_used: Mutex::new(base - std::time::Duration::from_secs(5)), + id: 2, + tx_lock: AsyncMutex::new(()), + }); + + let pool = HubWhalePool { + whales: vec![w1.clone(), w2.clone()], + }; + + let selected = pool.select_whale(); + assert_eq!(selected.id, 1); + + let selected = pool.select_whale(); + assert_eq!(selected.id, 2); + } +} diff --git a/rust/main/utils/kaspa-tools/src/sim/kaspa_whale_pool.rs b/rust/main/utils/kaspa-tools/src/sim/kaspa_whale_pool.rs new file mode 100644 index 00000000000..02c8d4022b9 --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/sim/kaspa_whale_pool.rs @@ -0,0 +1,147 @@ +use dym_kas_core::wallet::{EasyKaspaWallet, EasyKaspaWalletArgs, Network}; +use eyre::Result; +use kaspa_addresses::Address; +use kaspa_consensus_core::tx::TransactionId; +use kaspa_wallet_core::prelude::Secret; +use std::sync::{Arc, Mutex}; +use std::time::Instant; +use tracing::info; + +pub struct KaspaWhale { + pub wallet: EasyKaspaWallet, + pub secret: Secret, + pub last_used: Mutex, + pub id: usize, +} + +impl KaspaWhale { + pub async fn deposit_with_payload( + &self, + address: Address, + amt: u64, + payload: Vec, + ) -> Result { + dymension_kaspa::ops::user::deposit::deposit_with_payload( + &self.wallet.wallet, + &self.secret, + address, + amt, + payload, + ) + .await + .map_err(Into::into) + } + + fn mark_used(&self) { + *self.last_used.lock().unwrap() = Instant::now(); + } + + fn last_used(&self) -> Instant { + *self.last_used.lock().unwrap() + } +} + +pub struct KaspaWhalePool { + whales: Vec>, +} + +impl KaspaWhalePool { + pub async fn new( + secrets: Vec, + wrpc_url: String, + net: Network, + wallet_dir_prefix: Option, + ) -> Result { + if secrets.is_empty() { + return Err(eyre::eyre!("kaspa whale secrets list cannot be empty")); + } + + info!("Initializing {} Kaspa whales", secrets.len()); + + let mut whales = Vec::new(); + let base_time = Instant::now(); + + for (id, secret_str) in secrets.into_iter().enumerate() { + let storage_folder = wallet_dir_prefix + .as_ref() + .map(|prefix| format!("{}/{}", prefix, id)); + + if let Some(ref folder) = storage_folder { + std::fs::create_dir_all(folder)?; + } + + let wallet = EasyKaspaWallet::try_new(EasyKaspaWalletArgs { + wallet_secret: secret_str.clone(), + wrpc_url: wrpc_url.clone(), + net: net.clone(), + storage_folder, + }) + .await?; + + let secret = Secret::from(secret_str); + + let whale = Arc::new(KaspaWhale { + wallet, + secret, + last_used: Mutex::new(base_time - std::time::Duration::from_secs(id as u64)), + id, + }); + + whales.push(whale); + + info!("Initialized Kaspa whale: id={}", id); + } + + info!("All {} Kaspa whales initialized", whales.len()); + + Ok(Self { whales }) + } + + pub fn select_whale(&self) -> Arc { + let selected = self + .whales + .iter() + .min_by_key(|w| w.last_used()) + .expect("whale pool cannot be empty") + .clone(); + + selected.mark_used(); + selected + } + + pub fn count(&self) -> usize { + self.whales.len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_lru_tracking() { + let base = Instant::now(); + let w1 = Arc::new(KaspaWhale { + wallet: unsafe { std::mem::zeroed() }, + secret: Secret::from("test1".to_string()), + last_used: Mutex::new(base - std::time::Duration::from_secs(10)), + id: 1, + }); + let w2 = Arc::new(KaspaWhale { + wallet: unsafe { std::mem::zeroed() }, + secret: Secret::from("test2".to_string()), + last_used: Mutex::new(base - std::time::Duration::from_secs(5)), + id: 2, + }); + + let pool = KaspaWhalePool { + whales: vec![w1.clone(), w2.clone()], + }; + + let selected = pool.select_whale(); + assert_eq!(selected.id, 1); + + let selected = pool.select_whale(); + assert_eq!(selected.id, 2); + } +} diff --git a/rust/main/utils/kaspa-tools/src/sim/key_cosmos.rs b/rust/main/utils/kaspa-tools/src/sim/key_cosmos.rs new file mode 100644 index 00000000000..3f98d2ffdd4 --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/sim/key_cosmos.rs @@ -0,0 +1,54 @@ +use hyperlane_core::AccountAddressType; +use hyperlane_cosmos::signers::Signer; +use k256::ecdsa::SigningKey as K256SigningKey; +use rand_core::OsRng; + +#[derive(Clone)] +pub struct EasyHubKey { + pub private: K256SigningKey, +} + +impl EasyHubKey { + pub fn new() -> Self { + let hub_k = K256SigningKey::random(&mut OsRng); + Self { private: hub_k } + } + pub fn signer(&self) -> Signer { + let priv_k = self.private.to_bytes().to_vec(); + Signer::new(priv_k, "dym".to_string(), &AccountAddressType::Bitcoin).unwrap() + } + pub fn from_hex(hex: &str) -> Self { + let priv_k = hex::decode(hex).unwrap(); + let hub_k = K256SigningKey::from_slice(&priv_k).unwrap(); + Self { private: hub_k } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_hub_key() { + let hub_key = EasyHubKey::new(); + let signer = hub_key.signer(); + let addr = signer.address_string; + let priv_k = hub_key.private.to_bytes().to_vec(); + let priv_k_hex = hex::encode(priv_k); + println!("priv_k_hex: {}", priv_k_hex); + println!("addr: {}", addr); + } + + #[tokio::test] + async fn test_round_trip_generate_hex_unhex() { + let hub_key_0 = EasyHubKey::new(); + let priv_k = hub_key_0.private.to_bytes().to_vec(); + let priv_k_hex = hex::encode(priv_k); + let hub_key_1 = EasyHubKey::from_hex(&priv_k_hex); + assert_eq!(hub_key_0.private.to_bytes(), hub_key_1.private.to_bytes()); + assert_eq!( + hub_key_0.signer().address_string, + hub_key_1.signer().address_string + ); + } +} diff --git a/rust/main/utils/kaspa-tools/src/sim/key_kaspa.rs b/rust/main/utils/kaspa-tools/src/sim/key_kaspa.rs new file mode 100644 index 00000000000..086e7ea2dc1 --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/sim/key_kaspa.rs @@ -0,0 +1,30 @@ +use kaspa_addresses::{Address, Prefix, Version}; +use secp256k1::{Keypair, Secp256k1, SecretKey}; + +pub struct EasyKaspaKey { + pub address: Address, + pub private_key: SecretKey, +} + +pub fn get_kaspa_keypair(prefix: Prefix) -> EasyKaspaKey { + let secp = Secp256k1::new(); + let mut rng = rand_08::thread_rng(); + let keypair = Keypair::new(&secp, &mut rng); + let pub_key = keypair.public_key().x_only_public_key().0; + let address = Address::new(prefix, Version::PubKey, &pub_key.serialize()); + EasyKaspaKey { + address, + private_key: keypair.secret_key(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_kaspa_keypair_print_address() { + let key = get_kaspa_keypair(Prefix::Testnet); + println!("{}", key.address); + } +} diff --git a/rust/main/utils/kaspa-tools/src/sim/mod.rs b/rust/main/utils/kaspa-tools/src/sim/mod.rs new file mode 100644 index 00000000000..7c506751bfe --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/sim/mod.rs @@ -0,0 +1,10 @@ +mod hub_whale_pool; +mod kaspa_whale_pool; +mod key_cosmos; +mod key_kaspa; +mod round_trip; +pub mod roundtrip; +mod sim; +mod stats; +mod util; +pub use sim::*; diff --git a/rust/main/utils/kaspa-tools/src/sim/round_trip.rs b/rust/main/utils/kaspa-tools/src/sim/round_trip.rs new file mode 100644 index 00000000000..c927b353afa --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/sim/round_trip.rs @@ -0,0 +1,569 @@ +use super::hub_whale_pool::HubWhale; +use super::kaspa_whale_pool::KaspaWhale; +use super::key_cosmos::EasyHubKey; +use super::key_kaspa::get_kaspa_keypair; +use super::stats::RoundTripStats; +use super::util::now_millis; +use cometbft_rpc::endpoint::broadcast::tx_commit::Response as HubResponse; +use cosmos_sdk_proto::cosmos::bank::v1beta1::MsgSend; +use cosmos_sdk_proto::cosmos::base::v1beta1::Coin; +use cosmrs::Any; +use dym_kas_core::api::client::HttpClient; +use dym_kas_core::wallet::Network; +use dymension_kaspa::ops::user::payload::make_deposit_payload_easy; +use eyre::Result; +use hex::ToHex; +use hyperlane_core::H256; +use hyperlane_core::U256; +use hyperlane_cosmos::{native::ModuleQueryClient, CosmosProvider}; +use hyperlane_cosmos_rs::hyperlane::warp::v1::MsgRemoteTransfer; +use hyperlane_cosmos_rs::prost::{Message, Name}; +use kaspa_addresses::Address; +use kaspa_consensus_core::tx::TransactionId; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::mpsc; +use tokio_util::sync::CancellationToken; +use tracing::debug; +use tracing::error; +use tracing::info; +use tracing::warn; + +const MAX_RETRIES: usize = 3; +const RETRY_DELAY_MS: u64 = 2000; +const HUB_FUND_AMOUNT: u64 = 50_000_000_000_000_000; // 0.05 dym to pay gas + +fn is_retryable(error: &eyre::Error) -> bool { + let err_str = error.to_string().to_lowercase(); + err_str.contains("account sequence mismatch") +} + +async fn retry_with_backoff(operation_name: &str, task_id: u64, mut f: F) -> Result +where + F: FnMut() -> Fut, + Fut: std::future::Future>, +{ + let mut last_error = None; + + for attempt in 1..=MAX_RETRIES { + match f().await { + Ok(result) => { + if attempt > 1 { + info!( + "{}: succeeded after {} attempts: task_id={}", + operation_name, attempt, task_id + ); + } + return Ok(result); + } + Err(e) => { + if is_retryable(&e) && attempt < MAX_RETRIES { + warn!( + "{} failed (attempt {}/{}): task_id={} error={:?}", + operation_name, attempt, MAX_RETRIES, task_id, e + ); + tokio::time::sleep(Duration::from_millis(RETRY_DELAY_MS)).await; + last_error = Some(e); + } else { + return Err(e); + } + } + } + } + + Err(last_error + .unwrap_or_else(|| eyre::eyre!("{} failed after {} retries", operation_name, MAX_RETRIES))) +} + +async fn fund_hub_addr(hub_key: &EasyHubKey, hub_whale: &HubWhale, amount: u64) -> Result<()> { + let _lock = hub_whale.lock_for_tx().await; + let hub_addr = hub_key.signer().address_string.clone(); + debug!("funding hub address: addr={} amount={}", hub_addr, amount); + let rpc = hub_whale.provider.rpc(); + let msg = MsgSend { + from_address: rpc.get_signer()?.address_string.clone(), + to_address: hub_addr.clone(), + amount: vec![Coin { + amount: amount.to_string(), + denom: "adym".to_string(), + }], + }; + let a = Any { + type_url: "/cosmos.bank.v1beta1.MsgSend".to_string(), + value: msg.encode_to_vec(), + }; + let gas_limit = None; + let response = rpc.send(vec![a], gas_limit).await?; + if !response.tx_result.code.is_ok() || !response.check_tx.code.is_ok() { + return Err(eyre::eyre!( + "funding failed: tx_result_code={:?} check_tx_code={:?}", + response.tx_result.code, + response.check_tx.code + )); + } + info!("hub address funded: addr={} amount={}", hub_addr, amount); + Ok(()) +} + +#[derive(Debug, Clone)] +pub struct TaskResources { + pub hub: CosmosProvider, + pub args: TaskArgs, + pub kas_rest: HttpClient, + pub kaspa_network: Network, +} + +#[derive(Debug, Clone)] +pub struct TaskArgs { + pub domain_kas: u32, + pub token_kas_placeholder: H256, + pub domain_hub: u32, + pub token_hub: H256, + pub escrow_address: Address, + pub deposit_amount: u64, + pub withdrawal_fee_pct: f64, +} + +impl TaskArgs { + pub fn token_hub_str(&self) -> String { + format!("0x{}", hex::encode(self.token_hub.as_bytes())) + } + pub fn hub_denom(&self) -> String { + format!("hyperlane/{}", self.token_hub_str()) + } + /// Net amount to withdraw such that withdrawal + fee < deposit_amount + pub fn net_withdrawal_amount(&self) -> u64 { + (self.deposit_amount as f64 / (1.0 + self.withdrawal_fee_pct)) as u64 - 1 + } +} + +/* +Stages + 1. Deposit using whale, to new hub user + 2. Poll for hub user balance to be credited + 3. Withdraw from hub user to a kaspa user + 4. Poll for kaspa user balance to be credited + + Measure the time gaps, and record failures + */ +pub async fn do_round_trip( + res: TaskResources, + kaspa_whale: Arc, + hub_whale: Arc, + tx: &mpsc::Sender, + task_id: u64, + cancel_token: CancellationToken, +) { + let mut rt = RoundTrip::new( + res, + kaspa_whale.clone(), + hub_whale.clone(), + task_id, + cancel_token, + tx, + ); + do_round_trip_inner(&mut rt).await; +} + +async fn do_round_trip_inner(rt: &mut RoundTrip<'_>) { + let hub_user_key = EasyHubKey::new(); + info!("hub_user_key: {:?}", hub_user_key.private.to_bytes()); + let hub_user_addr = hub_user_key.signer().address_string.clone(); + + rt.stats.deposit_addr_hub = Some(hub_user_addr.clone()); + rt.stats.kaspa_whale_id = Some(rt.kaspa_whale.id); + rt.stats.hub_whale_id = Some(rt.hub_whale.id); + + debug!( + "round trip started: task_id={} kaspa_whale_id={} hub_whale_id={} hub_user_addr={}", + rt.task_id, rt.kaspa_whale.id, rt.hub_whale.id, hub_user_addr + ); + + match fund_hub_addr(&hub_user_key, &rt.hub_whale, HUB_FUND_AMOUNT).await { + Ok(()) => { + info!( + "hub user funded: task_id={} hub_whale_id={} hub_user_addr={}", + rt.task_id, rt.hub_whale.id, hub_user_addr + ); + } + Err(e) => { + error!( + "hub funding error: task_id={} hub_whale_id={} error={:?}", + rt.task_id, rt.hub_whale.id, e + ); + rt.send_stats().await; + return; + } + }; + + rt.hub_user_key = Some(hub_user_key.clone()); + + match rt.deposit(&hub_user_key).await { + Ok((tx_id, deposit_time_millis)) => { + rt.stats.kaspa_deposit_tx_id = Some(tx_id); + rt.stats.kaspa_deposit_tx_time_millis = Some(deposit_time_millis); + info!( + "deposit completed: task_id={} kaspa_whale_id={} hub_whale_id={} hub_user_addr={} tx_id={:?}", + rt.task_id, + rt.kaspa_whale.id, + rt.hub_whale.id, + hub_user_addr, + tx_id + ); + rt.send_stats().await; + } + Err(e) => { + rt.stats.deposit_error = Some(e.to_string()); + error!( + "deposit error: task_id={} kaspa_whale_id={} error={:?}", + rt.task_id, rt.kaspa_whale.id, e + ); + rt.send_stats().await; + return; + } + }; + match rt.await_hub_credit(&hub_user_key).await { + Ok(()) => { + rt.stats.deposit_credit_time_millis = Some(now_millis()); + info!( + "hub credit received: task_id={} hub_whale_id={} hub_user_addr={}", + rt.task_id, rt.hub_whale.id, hub_user_addr + ); + rt.send_stats().await; + } + Err(e) => { + rt.stats.deposit_credit_error = Some(e.to_string()); + error!( + "hub credit error: task_id={} hub_whale_id={} error={}", + rt.task_id, rt.hub_whale.id, e + ); + rt.send_stats().await; + return; + } + }; + + let withdraw_res = rt.withdraw(&hub_user_key).await; + if !withdraw_res.is_ok() { + let e = withdraw_res.err().unwrap(); + rt.stats.withdrawal_error = Some(e.to_string()); + error!( + "withdrawal error: task_id={} hub_whale_id={} hub_user_addr={} error={:?}", + rt.task_id, rt.hub_whale.id, hub_user_addr, e + ); + rt.send_stats().await; + return; + } + let (kaspa_addr, tx_id, withdrawal_time_millis) = withdraw_res.unwrap(); + rt.stats.hub_withdraw_tx_id = Some(tx_id.clone()); + rt.stats.hub_withdraw_tx_time_millis = Some(withdrawal_time_millis); + rt.stats.withdraw_addr_kaspa = Some(kaspa_addr.clone()); + rt.send_stats().await; + + match rt.await_kaspa_credit(kaspa_addr.clone()).await { + Ok(()) => { + rt.stats.withdraw_credit_time_millis = Some(now_millis()); + info!( + "kaspa credit received: task_id={} hub_whale_id={} hub_user_addr={} kaspa_addr={}", + rt.task_id, rt.hub_whale.id, hub_user_addr, kaspa_addr + ); + rt.send_stats().await; + } + Err(e) => { + rt.stats.withdraw_credit_error = Some(e.to_string()); + error!( + "kaspa credit error: task_id={} hub_whale_id={} error={}", + rt.task_id, rt.hub_whale.id, e + ); + rt.send_stats().await; + return; + } + }; +} + +struct RoundTrip<'a> { + res: TaskResources, + kaspa_whale: Arc, + hub_whale: Arc, + task_id: u64, + stats: RoundTripStats, + cancel: CancellationToken, + tx: &'a mpsc::Sender, + hub_user_key: Option, +} + +impl<'a> RoundTrip<'a> { + pub fn new( + res: TaskResources, + kaspa_whale: Arc, + hub_whale: Arc, + task_id: u64, + cancel_token: CancellationToken, + tx: &'a mpsc::Sender, + ) -> Self { + Self { + res, + kaspa_whale, + hub_whale, + stats: RoundTripStats::new(task_id), + task_id, + cancel: cancel_token, + tx, + hub_user_key: None, + } + } + + async fn send_stats(&mut self) { + self.stats.update_stage(); + if let Err(e) = self.tx.send(self.stats.clone()).await { + error!( + "stat send error: task_id={} kaspa_whale_id={} error={:?}", + self.task_id, self.kaspa_whale.id, e + ); + } + } + + async fn deposit(&self, hub_user_key: &EasyHubKey) -> Result<(TransactionId, u128)> { + let a = self.res.args.escrow_address.clone(); + let amt = self.res.args.deposit_amount; + debug!( + "deposit starting: task_id={} kaspa_whale_id={} escrow_addr={} amount={}", + self.task_id, self.kaspa_whale.id, a, amt + ); + + let payload = make_deposit_payload_easy( + self.res.args.domain_kas, + self.res.args.token_kas_placeholder, + self.res.args.domain_hub, + self.res.args.token_hub, + amt, + &hub_user_key.signer(), + ); + + let kaspa_whale = self.kaspa_whale.clone(); + let task_id = self.task_id; + let tx_id = retry_with_backoff("kaspa_deposit", task_id, || { + let kaspa_whale = kaspa_whale.clone(); + let a = a.clone(); + let payload = payload.clone(); + async move { kaspa_whale.deposit_with_payload(a, amt, payload).await } + }) + .await?; + + Ok((tx_id, now_millis())) + } + + async fn await_hub_credit(&self, hub_user_key: &EasyHubKey) -> Result<()> { + let expected_amount = self.res.args.deposit_amount; + let a = hub_user_key.signer().address_string; + debug!( + "await hub credit starting: task_id={} hub_whale_id={} hub_user_addr={} expected_value={}", + self.task_id, self.hub_whale.id, a, expected_amount + ); + loop { + let balance = self + .res + .hub + .rpc() + .get_balance_denom(a.clone(), "adym".to_string()) + .await?; + if balance == U256::from(0) { + if self.cancel.is_cancelled() { + return Err(RoundTripError::Cancelled.into()); + } + tokio::time::sleep(Duration::from_millis(3000)).await; + continue; + } + break; + } + loop { + let balance = self + .res + .hub + .rpc() + .get_balance_denom(a.clone(), self.res.args.hub_denom()) + .await?; + if balance == U256::from(0) { + if self.cancel.is_cancelled() { + return Err(RoundTripError::Cancelled.into()); + } + tokio::time::sleep(Duration::from_millis(3000)).await; + continue; + } + if balance != U256::from(expected_amount) { + let e = RoundTripError::HubBalanceMismatch { + balance: balance.as_u64() as i64, + expected: expected_amount as i64, + }; + return Err(e.into()); + } + break; + } + + Ok(()) + } + + async fn withdraw(&self, hub_user_key: &EasyHubKey) -> Result<(Address, String, u128)> { + let withdrawal_amount = self.res.args.net_withdrawal_amount(); + let fee_amount = self.res.args.deposit_amount - withdrawal_amount; + let fee_denom = self.res.args.hub_denom(); + + let kaspa_prefix = match self.res.kaspa_network { + Network::KaspaTest10 => kaspa_addresses::Prefix::Testnet, + Network::KaspaMainnet => kaspa_addresses::Prefix::Mainnet, + }; + let kaspa_recipient = get_kaspa_keypair(kaspa_prefix); + let hub_user_addr = hub_user_key.signer().address_string.clone(); + info!( + "kaspa_recipient_key: {:?}", + kaspa_recipient.private_key.secret_bytes() + ); + debug!( + "withdraw starting: task_id={} hub_whale_id={} hub_user_addr={} kaspa_recipient_addr={} amount={} fee_amount={} fee_denom={}", + self.task_id, self.hub_whale.id, hub_user_addr, kaspa_recipient.address, withdrawal_amount, fee_amount, fee_denom + ); + + let mut res_hub = self.res.hub.clone(); + res_hub.rpc = res_hub.rpc().with_signer(hub_user_key.signer()); + let domain_kas = self.res.args.domain_kas; + let token_hub_str = self.res.args.token_hub_str(); + let kaspa_addr = kaspa_recipient.address.clone(); + let task_id = self.task_id; + + let (response, timestamp) = retry_with_backoff("hub_withdrawal", task_id, || { + let res_hub = res_hub.clone(); + let token_hub_str = token_hub_str.clone(); + let kaspa_addr = kaspa_addr.clone(); + let fee_denom = fee_denom.clone(); + async move { + let rpc = res_hub.rpc(); + let amount = withdrawal_amount.to_string(); + let recipient = dymension_kaspa::ops::addr::kaspa_address_to_hex_recipient( + &kaspa_addr.to_string(), + ); + let sender = rpc.get_signer()?.address_string.clone(); + + let req = MsgRemoteTransfer { + sender: sender.clone(), + token_id: token_hub_str, + destination_domain: domain_kas, + recipient, + amount, + custom_hook_id: "".to_string(), + gas_limit: "0".to_string(), + max_fee: Some(Coin { + denom: fee_denom, + amount: fee_amount.to_string(), + }), + custom_hook_metadata: "".to_string(), + }; + let a = Any { + type_url: MsgRemoteTransfer::type_url(), + value: req.encode_to_vec(), + }; + let gas_limit = None; + let response = rpc.send(vec![a], gas_limit).await; + + match response { + Ok(response) => { + if response.tx_result.code.is_ok() & response.check_tx.code.is_ok() { + Ok((response, now_millis())) + } else { + Err(RoundTripError::WithdrawalTxFailed { response }.into()) + } + } + Err(e) => Err(eyre::eyre!( + "hub send error: sender={} error={:?}", + sender, + e + )), + } + } + }) + .await?; + + Ok((kaspa_addr, hub_tx_query_id(&response), timestamp)) + } + + async fn await_kaspa_credit(&self, kaspa_addr: Address) -> Result<()> { + let expected_credit = self.res.args.net_withdrawal_amount(); + + debug!( + "await kaspa credit starting: task_id={} hub_whale_id={} kaspa_addr={} expected_value={}", + self.task_id, self.hub_whale.id, kaspa_addr, expected_credit + ); + loop { + let balance = self + .res + .kas_rest + .get_balance_by_address(&kaspa_addr.to_string()) + .await?; + if balance == 0 { + if self.cancel.is_cancelled() { + return Err(RoundTripError::Cancelled.into()); + } + tokio::time::sleep(Duration::from_millis(3000)).await; + continue; + } + if balance != expected_credit as i64 { + let e = RoundTripError::KaspaBalanceMismatch { + balance, + expected: expected_credit as i64, + }; + return Err(e.into()); + } + break; + } + + Ok(()) + } +} + +fn hub_tx_query_id(response: &HubResponse) -> String { + let as_h256 = H256::from_slice(response.hash.as_bytes()).into(); + let tx_hash = hyperlane_cosmos::native::h512_to_h256(as_h256).encode_hex_upper::(); + tx_hash +} + +#[derive(Debug, thiserror::Error)] +pub enum RoundTripError { + #[error("hub balance mismatch: {balance} != {expected}")] + HubBalanceMismatch { balance: i64, expected: i64 }, + #[error("kaspa balance mismatch: {balance} != {expected}")] + KaspaBalanceMismatch { balance: i64, expected: i64 }, + #[error("withdrawal tx failed: {response:?}")] + WithdrawalTxFailed { response: HubResponse }, + #[error("cancelled")] + Cancelled, +} + +#[cfg(test)] +mod tests { + use super::TaskArgs; + use hyperlane_core::H256; + use kaspa_addresses::Address; + use std::str::FromStr; + + #[test] + fn test_hub_denom() { + let token_hub = + H256::from_str("0x726f757465725f61707000000000000000000000000000020000000000000000") + .unwrap(); + let args = TaskArgs { + domain_kas: 0, + token_kas_placeholder: H256::zero(), + domain_hub: 0, + token_hub, + escrow_address: Address::try_from( + "kaspatest:pzlq49spp66vkjjex0w7z8708f6zteqwr6swy33fmy4za866ne90v7e6pyrfr", + ) + .unwrap(), + deposit_amount: 4_100_000_000, + withdrawal_fee_pct: 0.01, + }; + let denom = args.hub_denom(); + assert_eq!( + denom, + "hyperlane/0x726f757465725f61707000000000000000000000000000020000000000000000" + ); + } +} diff --git a/rust/main/utils/kaspa-tools/src/sim/roundtrip.rs b/rust/main/utils/kaspa-tools/src/sim/roundtrip.rs new file mode 100644 index 00000000000..667811a8f54 --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/sim/roundtrip.rs @@ -0,0 +1,200 @@ +//! Single roundtrip command - deposit from Kaspa to Hub, then withdraw back + +use super::hub_whale_pool::HubWhale; +use super::kaspa_whale_pool::KaspaWhale; +use super::key_cosmos::EasyHubKey; +use super::round_trip::{do_round_trip, TaskArgs, TaskResources}; +use super::stats::RoundTripStats; +use super::util::{create_cosmos_provider, SOMPI_PER_KAS}; +use crate::x::args::RoundtripCli; +use dym_kas_core::api::base::RateLimitConfig; +use dym_kas_core::api::client::HttpClient; +use dym_kas_core::wallet::{EasyKaspaWallet, EasyKaspaWalletArgs}; +use eyre::Result; +use kaspa_wallet_core::prelude::Secret; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; +use tokio::sync::mpsc; +use tokio::sync::Mutex as AsyncMutex; +use tokio_util::sync::CancellationToken; + +pub async fn do_roundtrip(cli: RoundtripCli) -> Result<()> { + let kaspa_network = cli.bridge.parse_kaspa_network()?; + let escrow_address = cli.bridge.parse_escrow_address()?; + + // Initialize wallets + let kaspa_wallet = EasyKaspaWallet::try_new(EasyKaspaWalletArgs { + wallet_secret: cli.kaspa_wallet_secret.clone(), + wrpc_url: cli.bridge.kaspa_wrpc_url.clone(), + net: kaspa_network.clone(), + storage_folder: cli.kaspa_wallet_dir.clone(), + }) + .await?; + let kaspa_secret = Secret::from(cli.kaspa_wallet_secret.clone()); + let kaspa_addr = kaspa_wallet.wallet.account()?.receive_address()?; + + let hub_key = EasyHubKey::from_hex(&cli.hub_priv_key); + let hub_addr = hub_key.signer().address_string.clone(); + + let hub_provider = create_cosmos_provider( + &hub_key, + &cli.bridge.hub_rpc_url, + &cli.bridge.hub_grpc_url, + &cli.bridge.hub_chain_id, + &cli.bridge.hub_prefix, + &cli.bridge.hub_denom, + cli.bridge.hub_decimals, + ) + .await?; + + // Print config + println!( + "Roundtrip: {} sompi ({:.2} KAS)", + cli.bridge.deposit_amount, + cli.bridge.deposit_amount as f64 / SOMPI_PER_KAS as f64 + ); + println!(" Kaspa: {}", kaspa_addr); + println!(" Hub: {}", hub_addr); + println!(); + + // Build resources + let task_args = TaskArgs { + domain_kas: cli.bridge.domain_kas, + token_kas_placeholder: cli.bridge.token_kas_placeholder, + domain_hub: cli.bridge.domain_hub, + token_hub: cli.bridge.token_hub, + escrow_address, + deposit_amount: cli.bridge.deposit_amount, + withdrawal_fee_pct: cli.bridge.withdrawal_fee_pct, + }; + + let task_resources = TaskResources { + hub: hub_provider.clone(), + args: task_args, + kas_rest: HttpClient::new( + cli.bridge.kaspa_rest_url.clone(), + RateLimitConfig::default(), + ), + kaspa_network, + }; + + // Wrap as whales (required by do_round_trip interface) + let kaspa_whale = Arc::new(KaspaWhale { + wallet: kaspa_wallet, + secret: kaspa_secret, + last_used: Mutex::new(Instant::now()), + id: 0, + }); + let hub_whale = Arc::new(HubWhale { + provider: hub_provider, + last_used: Mutex::new(Instant::now()), + id: 0, + tx_lock: AsyncMutex::new(()), + }); + + // Setup stats channel and timeout + let (tx, mut rx) = mpsc::channel::(32); + let cancel_token = CancellationToken::new(); + let cancel_clone = cancel_token.clone(); + let timeout_secs = cli.timeout; + tokio::spawn(async move { + tokio::time::sleep(Duration::from_secs(timeout_secs)).await; + cancel_clone.cancel(); + }); + + // Run roundtrip in background, print progress from stats + let rt_handle = tokio::spawn(async move { + do_round_trip(task_resources, kaspa_whale, hub_whale, &tx, 0, cancel_token).await; + }); + + // Track progress + let mut last_stage = String::new(); + let mut final_stats: Option = None; + + while let Some(stats) = rx.recv().await { + if stats.stage != last_stage { + print_stage(&stats.stage); + last_stage = stats.stage.clone(); + } + final_stats = Some(stats); + } + + rt_handle.await?; + + // Print result + println!(); + let Some(stats) = final_stats else { + println!("FAILED: no response"); + return Err(eyre::eyre!("no stats received")); + }; + print_result(&stats) +} + +fn print_stage(stage: &str) { + let msg = match stage { + "PreDeposit" => "[1/4] Submitting deposit...", + "AwaitingDepositCredit" => "[2/4] Waiting for hub credit...", + "PreWithdrawal" => "[3/4] Submitting withdrawal...", + "AwaitingWithdrawalCredit" => "[4/4] Waiting for kaspa credit...", + "Complete" => "Done", + s if s.contains("NotCredited") => return, // error states handled in result + _ => return, + }; + println!("{}", msg); +} + +fn print_result(stats: &RoundTripStats) -> Result<()> { + let deposit_ok = stats.deposit_error.is_none() && stats.deposit_credit_error.is_none(); + let withdraw_ok = stats.withdrawal_error.is_none() && stats.withdraw_credit_error.is_none(); + + let deposit_time = stats + .deposit_credit_time_millis + .zip(stats.kaspa_deposit_tx_time_millis) + .map(|(end, start)| format_duration(end - start)); + + let withdraw_time = stats + .withdraw_credit_time_millis + .zip(stats.hub_withdraw_tx_time_millis) + .map(|(end, start)| format_duration(end - start)); + + // Deposit result + print!("Deposit: "); + if let Some(ref e) = stats.deposit_error { + println!("FAILED ({})", e); + } else if let Some(ref e) = stats.deposit_credit_error { + println!("FAILED ({})", e); + } else if let Some(t) = deposit_time { + println!("OK ({})", t); + } else { + println!("INCOMPLETE"); + } + + // Withdrawal result + print!("Withdrawal: "); + if !deposit_ok { + println!("SKIPPED"); + } else if let Some(ref e) = stats.withdrawal_error { + println!("FAILED ({})", e); + } else if let Some(ref e) = stats.withdraw_credit_error { + println!("FAILED ({})", e); + } else if let Some(t) = withdraw_time { + println!("OK ({})", t); + } else { + println!("INCOMPLETE"); + } + + if deposit_ok && withdraw_ok && stats.stage == "Complete" { + Ok(()) + } else { + Err(eyre::eyre!("roundtrip failed")) + } +} + +fn format_duration(ms: u128) -> String { + let secs = ms / 1000; + if secs >= 60 { + format!("{}m{}s", secs / 60, secs % 60) + } else { + format!("{}s", secs) + } +} diff --git a/rust/main/utils/kaspa-tools/src/sim/sim.rs b/rust/main/utils/kaspa-tools/src/sim/sim.rs new file mode 100644 index 00000000000..e47dc14bacf --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/sim/sim.rs @@ -0,0 +1,274 @@ +use super::hub_whale_pool::HubWhalePool; +use super::kaspa_whale_pool::KaspaWhalePool; +use super::round_trip::do_round_trip; +use super::round_trip::TaskArgs; +use super::round_trip::TaskResources; +use super::stats::write_metadata; +use super::stats::StatsWriter; +use chrono::{DateTime, Utc}; +use dym_kas_core::api::base::RateLimitConfig; +use dym_kas_core::api::client::HttpClient; +use dym_kas_core::wallet::Network; +use eyre::Result; +use rand_distr::{Distribution, Exp}; +use std::time::SystemTime; +use std::time::{Duration, Instant}; +use tokio::sync::mpsc; +use tokio_util::sync::CancellationToken; + +use crate::x::args::SimulateTrafficCli; +use hyperlane_core::H256; +use tracing::info; + +pub struct Params { + pub time_limit: Duration, + pub ops_per_minute: u64, + pub max_wait_for_cancel: Duration, +} + +impl Params { + pub fn distr_time(&self) -> Exp { + Exp::new(self.ops_per_second() / 1000.0).unwrap() + } + + pub fn ops_per_second(&self) -> f64 { + self.ops_per_minute as f64 / 60.0 + } + + pub fn max_ops(&self) -> u64 { + (self.time_limit.as_secs_f64() * self.ops_per_second()) as u64 + } +} + +pub struct SimulateTrafficArgs { + pub params: Params, + pub task_args: TaskArgs, + pub kaspa_whale_secrets: Vec, + pub hub_whale_priv_keys: Vec, + pub kaspa_whale_wallet_dir_prefix: Option, + pub kaspa_wrpc_url: String, + pub output_dir: String, + pub hub_rpc_url: String, + pub hub_grpc_url: String, + pub hub_chain_id: String, + pub hub_prefix: String, + pub hub_denom: String, + pub hub_decimals: u32, + pub kaspa_rest_url: String, + pub kaspa_network: Network, +} + +impl TryFrom for SimulateTrafficArgs { + type Error = eyre::Error; + + fn try_from(cli: SimulateTrafficCli) -> Result { + let escrow_address = cli.bridge.parse_escrow_address()?; + let kaspa_network = cli.bridge.parse_kaspa_network()?; + + Ok(SimulateTrafficArgs { + params: Params { + time_limit: std::time::Duration::from_secs(cli.time_limit), + ops_per_minute: cli.ops_per_minute, + max_wait_for_cancel: std::time::Duration::from_secs(cli.cancel_wait), + }, + task_args: TaskArgs { + domain_kas: cli.bridge.domain_kas, + token_kas_placeholder: cli.bridge.token_kas_placeholder, + domain_hub: cli.bridge.domain_hub, + token_hub: cli.bridge.token_hub, + escrow_address, + deposit_amount: cli.bridge.deposit_amount, + withdrawal_fee_pct: cli.bridge.withdrawal_fee_pct, + }, + kaspa_whale_secrets: cli.kaspa_whale_secrets, + hub_whale_priv_keys: cli.hub_whale_priv_keys, + kaspa_whale_wallet_dir_prefix: cli.kaspa_whale_wallet_dir_prefix, + kaspa_wrpc_url: cli.bridge.kaspa_wrpc_url, + output_dir: cli.output_dir, + hub_rpc_url: cli.bridge.hub_rpc_url, + hub_grpc_url: cli.bridge.hub_grpc_url, + hub_chain_id: cli.bridge.hub_chain_id, + hub_prefix: cli.bridge.hub_prefix, + hub_denom: cli.bridge.hub_denom, + hub_decimals: cli.bridge.hub_decimals, + kaspa_rest_url: cli.bridge.kaspa_rest_url, + kaspa_network, + }) + } +} + +pub struct TrafficSim { + params: Params, + resources: TaskResources, + kaspa_whale_pool: KaspaWhalePool, + hub_whale_pool: HubWhalePool, + output_dir: String, +} + +impl TrafficSim { + pub async fn new(args: SimulateTrafficArgs) -> Result { + info!( + "Initializing whale pools: kaspa_whales={} hub_whales={} network={:?}", + args.kaspa_whale_secrets.len(), + args.hub_whale_priv_keys.len(), + args.kaspa_network + ); + + let kaspa_whale_pool = KaspaWhalePool::new( + args.kaspa_whale_secrets, + args.kaspa_wrpc_url.clone(), + args.kaspa_network.clone(), + args.kaspa_whale_wallet_dir_prefix, + ) + .await?; + + let hub_whale_pool = HubWhalePool::new( + args.hub_whale_priv_keys, + args.hub_rpc_url.clone(), + args.hub_grpc_url.clone(), + args.hub_chain_id.clone(), + args.hub_prefix.clone(), + args.hub_denom.clone(), + args.hub_decimals, + ) + .await?; + + let first_hub_whale = hub_whale_pool.select_whale(); + let resources = TaskResources { + args: args.task_args, + hub: first_hub_whale.provider.clone(), + kas_rest: HttpClient::new(args.kaspa_rest_url.clone(), RateLimitConfig::default()), + kaspa_network: args.kaspa_network.clone(), + }; + + Ok(TrafficSim { + params: args.params, + resources, + kaspa_whale_pool, + hub_whale_pool, + output_dir: args.output_dir, + }) + } + + pub async fn run(&self) -> Result<()> { + let mut rng = rand::rng(); + + let max_ops = self.params.max_ops(); + info!( + "Starting simulation: max_ops={} time_limit={}s ops_per_minute={} kaspa_whales={} hub_whales={}", + max_ops, + self.params.time_limit.as_secs(), + self.params.ops_per_minute, + self.kaspa_whale_pool.count(), + self.hub_whale_pool.count() + ); + + let random_filename = H256::random(); + let now = SystemTime::now(); + let datetime: DateTime = now.into(); + let stats_file_path = format!( + "{}/stats_{}_{}.jsonl", + self.output_dir, + random_filename, + datetime.format("%Y-%m-%d_%H-%M-%S") + ); + let metadata_file_path = format!( + "{}/metadata_{}_{}.json", + self.output_dir, + random_filename, + datetime.format("%Y-%m-%d_%H-%M-%S") + ); + + let stats_writer = StatsWriter::new(stats_file_path.clone())?; + info!("Writing stats to: {}", stats_file_path); + + let (stats_tx, mut stats_rx) = mpsc::channel(10000); + + let collector_handle = tokio::spawn(async move { + let mut count = 0u64; + while let Some(stats) = stats_rx.recv().await { + stats_writer.log_stat(&stats); + if let Err(e) = stats_writer.write_stat(&stats) { + tracing::error!("stat write error: error={:?}", e); + } + count += 1; + if count % 10 == 0 { + info!("stats written: count={}", count); + } + } + info!("stats collection finished: total={}", count); + count + }); + + let start_time = Instant::now(); + let mut total_ops = 0u64; + let cancel = CancellationToken::new(); + + while start_time.elapsed() < self.params.time_limit { + let kaspa_whale = self.kaspa_whale_pool.select_whale(); + let hub_whale = self.hub_whale_pool.select_whale(); + + let tx_clone = stats_tx.clone(); + let r = self.resources.clone(); + let task_id = total_ops; + let cancel_token_clone = cancel.clone(); + + tokio::spawn(async move { + do_round_trip( + r, + kaspa_whale, + hub_whale, + &tx_clone, + task_id, + cancel_token_clone, + ) + .await; + drop(tx_clone); + }); + + total_ops += 1; + + let sleep_millis = self.params.distr_time().sample(&mut rng) as u64; + tokio::time::sleep(Duration::from_millis(sleep_millis)).await; + + if total_ops % 10 == 0 { + info!( + "ops started: count={} elapsed={}s", + total_ops, + start_time.elapsed().as_secs() + ); + } + } + + info!("time limit reached, waiting for tasks to finish"); + drop(stats_tx); + tokio::time::sleep(self.params.max_wait_for_cancel).await; + cancel.cancel(); + + let stats_count = collector_handle.await?; + info!("stats collection complete: count={}", stats_count); + + let total_spend = total_ops * self.resources.args.deposit_amount; + info!("writing metadata to: {}", metadata_file_path); + write_metadata(&metadata_file_path, total_spend, total_ops)?; + + info!("simulation complete"); + info!("stats file: {}", stats_file_path); + info!("metadata file: {}", metadata_file_path); + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use hyperlane_core::H256; + + #[test] + fn test_h256_random_stringify() { + let h = H256::random(); + let s = format!("{:?}", h); + println!("s: {}", s); + } +} diff --git a/rust/main/utils/kaspa-tools/src/sim/stats.rs b/rust/main/utils/kaspa-tools/src/sim/stats.rs new file mode 100644 index 00000000000..d32e0f2c4c8 --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/sim/stats.rs @@ -0,0 +1,143 @@ +use crate::sim::util::som_to_kas; +use eyre::Result; +use kaspa_addresses::Address; +use kaspa_consensus_core::tx::TransactionId; +use serde::Serialize; +use std::fs::OpenOptions; +use std::io::Write; +use std::sync::Arc; +use std::sync::Mutex; +use tracing::info; + +/// Continuous stats writer that appends to JSONL file immediately +pub struct StatsWriter { + file: Arc>, +} + +impl StatsWriter { + pub fn new(stats_file_path: String) -> Result { + let file = OpenOptions::new() + .create(true) + .append(true) + .open(&stats_file_path)?; + + Ok(Self { + file: Arc::new(Mutex::new(file)), + }) + } + + pub fn write_stat(&self, stat: &RoundTripStats) -> Result<()> { + let mut file = self.file.lock().unwrap(); + let json = serde_json::to_string(stat)?; + writeln!(file, "{}", json)?; + file.flush()?; + Ok(()) + } + + pub fn log_stat(&self, stat: &RoundTripStats) { + info!("{:#?}", stat); + if let Some(deposit_time_ms) = stat.deposit_time_ms() { + info!("deposit credit time: ms={}", deposit_time_ms); + } + if let Some(withdraw_time_ms) = stat.withdraw_time_ms() { + info!("withdraw credit time: ms={}", withdraw_time_ms); + } + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct MetadataStats { + pub total_spend: u64, + pub total_ops: u64, + pub total_spend_kas: String, +} + +pub fn write_metadata(file_path: &str, total_spend: u64, total_ops: u64) -> Result<()> { + let metadata = MetadataStats { + total_spend, + total_ops, + total_spend_kas: som_to_kas(total_spend), + }; + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(file_path)?; + serde_json::to_writer_pretty(file, &metadata)?; + Ok(()) +} + +#[derive(Debug, Clone, Default, Serialize)] +pub struct RoundTripStats { + pub op_id: u64, + pub stage: String, + pub kaspa_whale_id: Option, + pub hub_whale_id: Option, + pub kaspa_deposit_tx_id: Option, + pub kaspa_deposit_tx_time_millis: Option, + pub deposit_error: Option, + pub deposit_credit_time_millis: Option, + pub deposit_credit_error: Option, + pub hub_withdraw_tx_id: Option, + pub hub_withdraw_tx_time_millis: Option, + pub withdrawal_error: Option, + pub withdraw_credit_time_millis: Option, + pub withdraw_credit_error: Option, + pub deposit_addr_hub: Option, + pub withdraw_addr_kaspa: Option
, +} + +impl RoundTripStats { + pub fn new(op_id: u64) -> Self { + let mut d = RoundTripStats::default(); + d.op_id = op_id; + d.update_stage(); + d + } + + pub fn update_stage(&mut self) { + self.stage = self.compute_stage().to_string(); + } + + fn compute_stage(&self) -> &'static str { + if self.kaspa_deposit_tx_time_millis.is_none() { + return "PreDeposit"; + } + if self.deposit_credit_error.is_some() { + return "PostDepositNotCredited"; + } + if self.deposit_credit_time_millis.is_none() { + return "AwaitingDepositCredit"; + } + if self.hub_withdraw_tx_time_millis.is_none() { + return "PreWithdrawal"; + } + if self.withdraw_credit_error.is_some() { + return "PostWithdrawalNotCredited"; + } + if self.withdraw_credit_time_millis.is_none() { + return "AwaitingWithdrawalCredit"; + } + "Complete" + } + + pub fn deposit_time_ms(&self) -> Option { + match ( + self.kaspa_deposit_tx_time_millis, + self.deposit_credit_time_millis, + ) { + (Some(start), Some(end)) => Some(end.saturating_sub(start)), + _ => None, + } + } + + pub fn withdraw_time_ms(&self) -> Option { + match ( + self.hub_withdraw_tx_time_millis, + self.withdraw_credit_time_millis, + ) { + (Some(start), Some(end)) => Some(end.saturating_sub(start)), + _ => None, + } + } +} diff --git a/rust/main/utils/kaspa-tools/src/sim/util.rs b/rust/main/utils/kaspa-tools/src/sim/util.rs new file mode 100644 index 00000000000..804ba007591 --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/sim/util.rs @@ -0,0 +1,63 @@ +use super::key_cosmos::EasyHubKey; +use eyre::Result; +use hyperlane_core::config::OpSubmissionConfig; +use hyperlane_core::{ContractLocator, HyperlaneDomain, KnownHyperlaneDomain, NativeToken, H256}; +use hyperlane_cosmos::RawCosmosAmount; +use hyperlane_cosmos::{ + native::ModuleQueryClient, ConnectionConf as CosmosConnectionConf, CosmosProvider, +}; +use hyperlane_metric::prometheus_metric::PrometheusClientMetrics; +use url::Url; + +pub const SOMPI_PER_KAS: u64 = 100_000_000; + +pub fn som_to_kas(sompi: u64) -> String { + format!("{} KAS", sompi as f64 / SOMPI_PER_KAS as f64) +} + +pub fn now_millis() -> u128 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("system time before Unix epoch") + .as_millis() +} + +pub async fn create_cosmos_provider( + key: &EasyHubKey, + rpc_url: &str, + grpc_url: &str, + chain_id: &str, + prefix: &str, + denom: &str, + decimals: u32, +) -> Result> { + let conf = CosmosConnectionConf::new( + vec![Url::parse(grpc_url).map_err(|e| eyre::eyre!("invalid gRPC URL: {}", e))?], + vec![Url::parse(rpc_url).map_err(|e| eyre::eyre!("invalid RPC URL: {}", e))?], + chain_id.to_string(), + prefix.to_string(), + denom.to_string(), + RawCosmosAmount { + amount: "100000000000.0".to_string(), + denom: denom.to_string(), + }, + 32, + OpSubmissionConfig::default(), + NativeToken { + decimals, + denom: denom.to_string(), + }, + 1.0, + None, + ) + .map_err(|e| eyre::eyre!(e))?; + + let d = HyperlaneDomain::Known(KnownHyperlaneDomain::Osmosis); + let locator = ContractLocator::new(&d, H256::zero()); + let signer = Some(key.signer()); + let metrics = PrometheusClientMetrics::default(); + let chain = None; + + CosmosProvider::::new(&conf, &locator, signer, metrics, chain) + .map_err(eyre::Report::from) +} diff --git a/rust/main/utils/kaspa-tools/src/x/args.rs b/rust/main/utils/kaspa-tools/src/x/args.rs new file mode 100644 index 00000000000..4c724552a84 --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/x/args.rs @@ -0,0 +1,330 @@ +use super::deposit::DepositArgs; +use clap::{Args, Parser, Subcommand}; +use dym_kas_core::wallet::Network; +use hyperlane_core::H256; +use kaspa_addresses::Address; +use kaspa_consensus_core::network::NetworkId; +use std::str::FromStr; + +#[derive(Parser, Debug)] +#[command( + name = "kaspa-tools", + author, + version, // `version()` is automatically called by clap + about = "Tools for users, validator operators, developers etc", + subcommand_required = true, + arg_required_else_help = true, +)] +pub struct Cli { + #[command(subcommand)] + pub command: Commands, +} + +#[derive(Subcommand, Debug)] +pub enum Commands { + /// Convert kaspa address (like kaspatest:pzlq49sp...y4za866ne90v7e6pyrfr) to HL address (like 0x000000000..0000000) + Recipient(RecipientCli), + /// Get the escrow address for some secp256k1 pub keys (like kaspatest:pzlq49spp6...66ne90v7e6pyrfr) + Escrow(EscrowCli), + /// Validator management commands + Validator { + #[command(subcommand)] + action: ValidatorAction, + }, + /// Make a user deposit (to escrow) + Deposit(DepositCli), + /// Create a relayer + Relayer, + /// Stress test traffic simulation with multiple concurrent operations + #[clap(name = "sim")] + Sim(SimulateTrafficCli), + /// Single roundtrip test: deposit from kaspa to hub, then withdraw back + Roundtrip(RoundtripCli), + /// Decode a Kaspa withdrawal tx payload to extract Hyperlane message IDs + #[clap(name = "decode-payload")] + DecodePayload(DecodePayloadCli), + /// Compute the Hyperlane message ID for a Kaspa deposit transaction + #[clap(name = "compute-deposit-id")] + ComputeDepositId(ComputeDepositIdCli), +} + +#[derive(Subcommand, Debug)] +pub enum ValidatorAction { + /// Create new validator keys + Create { + #[command(subcommand)] + backend: ValidatorBackend, + }, +} + +#[derive(Subcommand, Debug)] +pub enum ValidatorBackend { + /// Generate and store validator keys locally + Local(ValidatorLocalArgs), + + /// Generate and store validator keys in AWS Secrets Manager + Aws(ValidatorAwsArgs), +} + +#[derive(Args, Debug)] +pub struct ValidatorLocalArgs { + /// Number of validators to generate + #[arg(short = 'n', long, default_value = "1")] + pub count: u32, + + /// Optional: save output to JSON file + #[arg(short, long)] + pub output: Option, +} + +#[derive(Args, Debug)] +pub struct ValidatorAwsArgs { + /// Secret path for storing the validator keys (e.g., /hyperlane/kaspa/validator-1) + /// All validator key properties will be stored as an encrypted JSON object at this path + #[arg(short, long)] + pub path: String, + + /// AWS KMS symmetric key ID or ARN for encryption (must be SYMMETRIC_DEFAULT, not RSA/ECC) + #[arg(long)] + pub kms_key_id: String, +} + +#[derive(Args, Debug)] +pub struct EscrowCli { + /// Comma separated list of pub keys + #[arg(required = true, index = 1)] + pub pub_keys: String, + /// Required signatures + #[arg(required = true, index = 2)] + pub required_signatures: u8, + /// Kaspa environment (testnet or mainnet) + #[arg(long, required = true)] + pub env: String, +} + +#[derive(Args, Debug)] +pub struct RecipientCli { + /// The address to be converted + #[arg(required = true, index = 1)] + pub address: String, +} + +#[derive(Args, Debug)] +/// Simulate/benchmark traffic on Kaspa and the Hub +/// Launches tasks at Poisson-distributed intervals with fixed 41 KAS transfers +/// Each task does a kaspa deposit from a whale to escrow, then transfers back to a kaspa address +/// In this way errors and latencies can be tracked +pub struct SimulateTrafficCli { + /// Comma-separated kaspa whale wallet secrets (need ~450 for 90 ops/sec) + #[arg(long, required = true, value_delimiter = ',')] + pub kaspa_whale_secrets: Vec, + + /// Comma-separated hub whale private keys in hex (need ~450 for 90 ops/sec) + #[arg(long, required = true, value_delimiter = ',')] + pub hub_whale_priv_keys: Vec, + + /// Optional: shared wallet directory prefix for kaspa whales + #[arg(long)] + pub kaspa_whale_wallet_dir_prefix: Option, + + /// Filesystem dir to write logs/stats/debug info from the run + #[arg(long, required = true)] + pub output_dir: String, + + /// Total time limit to run the simulation in seconds + #[arg(long, required = true)] + pub time_limit: u64, + + /// Number of ops per minute to run (e.g. 90 for osmosis-level traffic) + #[arg(long, required = true)] + pub ops_per_minute: u64, + + /// The number of seconds to wait for the simulation to cancel + #[arg(long, required = true)] + pub cancel_wait: u64, + + #[command(flatten)] + pub bridge: CommonBridgeArgs, +} + +#[derive(Args, Debug, Clone)] +pub struct DepositCli { + /// The escrow address (like kaspatest:pzlq49spp66vkjjex0w7z8708f6zteqwr6swy33fmy4za866ne90v7e6pyrfr) + #[arg(long, required = true)] + pub escrow_address: String, + + /// The amount to deposit in sompi (like 100000) + #[arg(long, required = true)] + pub amount: String, + + /// The payload to deposit (hex without 0x prefix) + #[arg(long, required = false, default_value = "")] + pub payload: String, + + #[command(flatten)] + pub wallet: WalletCli, +} + +#[derive(Args, Debug, Clone)] +pub struct WalletCli { + /// The wRPC url (like localhost:17210) + #[arg(long("wrpc-url"), required = true)] + pub rpc_url: String, + + /// The kaspa network id (like testnet-10) + #[arg(long("network-id"), required = true)] + // If you have a NetworkId type that implements `FromStr`, you can use it directly: + // pub network_id: kaspa_consensus_core::network::NetworkId, + pub network_id: String, + + /// Local kaspa wallet keychain secret (not private key) + #[arg(long("wallet-secret"), required = true)] + pub wallet_secret: String, + + /// Local kaspa wallet directory + #[arg(long("wallet-dir"), required = false)] + pub wallet_dir: Option, +} + +impl DepositCli { + pub fn to_deposit_args(&self) -> DepositArgs { + DepositArgs { + escrow_address: self.escrow_address.clone(), + amount: self.amount.clone(), + payload: self.payload.clone(), + network_id: NetworkId::from_str(&self.wallet.network_id).unwrap(), + rpc_url: self.wallet.rpc_url.clone(), + wallet_secret: self.wallet.wallet_secret.clone(), + wallet_dir: self.wallet.wallet_dir.clone(), + } + } +} + +/// Shared bridge configuration used by both stress test and roundtrip commands +#[derive(Args, Debug, Clone)] +pub struct CommonBridgeArgs { + /// Kaspa HL domain + #[arg(long, required = true)] + pub domain_kas: u32, + + /// Kaspa HL token placeholder contract addr + #[arg(long, required = true)] + pub token_kas_placeholder: H256, + + /// Hub HL domain + #[arg(long, required = true)] + pub domain_hub: u32, + + /// The HL Warp token ID for kaspa on the Hub + #[arg(long, required = true)] + pub token_hub: H256, + + /// Kaspa escrow address + #[arg(long, required = true)] + pub escrow_address: String, + + /// Kaspa wRPC URL + #[arg(long, required = true)] + pub kaspa_wrpc_url: String, + + /// Hub RPC URL + #[arg(long, required = true)] + pub hub_rpc_url: String, + + /// Hub gRPC URL + #[arg(long, required = true)] + pub hub_grpc_url: String, + + /// Hub chain ID + #[arg(long, required = true)] + pub hub_chain_id: String, + + /// Hub address prefix + #[arg(long, required = true)] + pub hub_prefix: String, + + /// Hub native denom + #[arg(long, required = true)] + pub hub_denom: String, + + /// Hub native token decimals + #[arg(long, required = true)] + pub hub_decimals: u32, + + /// Kaspa REST API URL + #[arg(long, required = true)] + pub kaspa_rest_url: String, + + /// Deposit amount in sompi (e.g. 4100000000 for 41 KAS) + #[arg(long, required = true)] + pub deposit_amount: u64, + + /// Withdrawal fee percentage as decimal in [0,1] (e.g. 0.01 for 1%) + #[arg(long, required = true)] + pub withdrawal_fee_pct: f64, + + /// Kaspa network (testnet or mainnet) + #[arg(long, required = true)] + pub kaspa_network: String, +} + +impl CommonBridgeArgs { + pub fn parse_kaspa_network(&self) -> eyre::Result { + match self.kaspa_network.to_lowercase().as_str() { + "testnet" => Ok(Network::KaspaTest10), + "mainnet" => Ok(Network::KaspaMainnet), + _ => Err(eyre::eyre!("invalid kaspa network: {}", self.kaspa_network)), + } + } + + pub fn parse_escrow_address(&self) -> eyre::Result
{ + Address::try_from(self.escrow_address.clone()) + .map_err(|e| eyre::eyre!("invalid escrow address: {}", e)) + } +} + +#[derive(Args, Debug)] +/// Single roundtrip test: deposit from kaspa wallet to hub wallet, then withdraw back +/// Uses the same kaspa wallet for deposit source and withdrawal destination +pub struct RoundtripCli { + /// Kaspa wallet secret (password for the wallet keychain) + #[arg(long, required = true)] + pub kaspa_wallet_secret: String, + + /// Kaspa wallet directory + #[arg(long)] + pub kaspa_wallet_dir: Option, + + /// Hub wallet private key in hex (for receiving deposit and sending withdrawal) + #[arg(long, required = true)] + pub hub_priv_key: String, + + /// Timeout in seconds for waiting for deposit/withdrawal confirmation + #[arg(long, required = true)] + pub timeout: u64, + + #[command(flatten)] + pub bridge: CommonBridgeArgs, +} + +#[derive(Args, Debug)] +pub struct DecodePayloadCli { + /// The payload to decode (hex string, with or without 0x prefix). + /// This is the payload field from a Kaspa withdrawal transaction. + #[arg(required = true, index = 1)] + pub payload: String, +} + +#[derive(Args, Debug)] +pub struct ComputeDepositIdCli { + /// The deposit payload (hex string, with or without 0x prefix). + /// This is the HyperlaneMessage payload from a Kaspa deposit transaction. + #[arg(required = true, index = 1)] + pub payload: String, + /// The Kaspa transaction ID (hex string, with or without 0x prefix). + #[arg(required = true, index = 2)] + pub tx_id: String, + /// The UTXO index of the deposit output in the transaction. + #[arg(required = true, index = 3)] + pub utxo_index: usize, +} diff --git a/rust/main/utils/kaspa-tools/src/x/compute_deposit_id.rs b/rust/main/utils/kaspa-tools/src/x/compute_deposit_id.rs new file mode 100644 index 00000000000..3acf0df6d4c --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/x/compute_deposit_id.rs @@ -0,0 +1,80 @@ +use dymension_kaspa::ops::message::{add_kaspa_metadata_hl_messsage, ParsedHL}; +use eyre::Result; +use kaspa_hashes::Hash; + +use std::str::FromStr as _; + +/// Compute the Hyperlane message ID for a Kaspa deposit. +/// +/// The message ID is deterministically derived from the deposit payload combined +/// with the Kaspa transaction metadata (tx_id, utxo_index). This allows verifying +/// whether a deposit has been processed on the Dymension hub. +pub fn compute_deposit_id(payload: &str, tx_id: &str, utxo_index: usize) -> Result<()> { + let payload = payload.strip_prefix("0x").unwrap_or(payload); + let tx_id = tx_id.strip_prefix("0x").unwrap_or(tx_id); + + let parsed = ParsedHL::parse_string(payload)?; + + let tx_hash = + Hash::from_str(tx_id).map_err(|e| eyre::eyre!("invalid transaction ID: {}", e))?; + + let hl_message_with_metadata = add_kaspa_metadata_hl_messsage(parsed, tx_hash, utxo_index)?; + + let message_id = hl_message_with_metadata.id(); + + println!( + "Hyperlane Message ID: 0x{}", + hex::encode(message_id.as_bytes()) + ); + println!(); + println!("To check delivery on Dymension hub:"); + println!( + " dymd q hyperlane delivered 0x{}", + hex::encode(message_id.as_bytes()) + ); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_compute_deposit_id_basic() { + // This payload is from the user's real transaction + let payload = "03000000014088489d00000000000000000000000000000000000000000000000000000000000000005d990b31726f757465725f617070000000000000000000000000000200000000000000000000000000000000000000005b1ae7408e939e381f1d39b8d5dbe9aae7653453000000000000000000000000000000000000000000000000000000174876e800"; + let tx_id = "242b5987b89e939a8777d42072bbd3527dcfceb61048a4cf874fc473f76a1b79"; + let utxo_index = 0; + + let result = compute_deposit_id(payload, tx_id, utxo_index); + assert!(result.is_ok()); + } + + #[test] + fn test_compute_deposit_id_with_0x_prefix() { + let payload = "0x03000000014088489d00000000000000000000000000000000000000000000000000000000000000005d990b31726f757465725f617070000000000000000000000000000200000000000000000000000000000000000000005b1ae7408e939e381f1d39b8d5dbe9aae7653453000000000000000000000000000000000000000000000000000000174876e800"; + let tx_id = "0x242b5987b89e939a8777d42072bbd3527dcfceb61048a4cf874fc473f76a1b79"; + let utxo_index = 0; + + let result = compute_deposit_id(payload, tx_id, utxo_index); + assert!(result.is_ok()); + } + + #[test] + fn test_compute_deposit_id_invalid_payload() { + let result = compute_deposit_id( + "invalid_hex", + "242b5987b89e939a8777d42072bbd3527dcfceb61048a4cf874fc473f76a1b79", + 0, + ); + assert!(result.is_err()); + } + + #[test] + fn test_compute_deposit_id_invalid_tx_id() { + let payload = "03000000014088489d00000000000000000000000000000000000000000000000000000000000000005d990b31726f757465725f617070000000000000000000000000000200000000000000000000000000000000000000005b1ae7408e939e381f1d39b8d5dbe9aae7653453000000000000000000000000000000000000000000000000000000174876e800"; + let result = compute_deposit_id(payload, "invalid_tx_id", 0); + assert!(result.is_err()); + } +} diff --git a/rust/main/utils/kaspa-tools/src/x/decode_payload.rs b/rust/main/utils/kaspa-tools/src/x/decode_payload.rs new file mode 100644 index 00000000000..636719267dc --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/x/decode_payload.rs @@ -0,0 +1,70 @@ +use dymension_kaspa::ops::payload::MessageIDs; + +/// Decode a Kaspa withdrawal transaction payload (hex string) to extract Hyperlane message IDs. +/// The payload is protobuf-encoded MessageIDs containing a list of 32-byte message IDs. +pub fn decode_payload(payload: &str) -> Result<(), eyre::Error> { + // Strip optional 0x prefix + let payload = payload.strip_prefix("0x").unwrap_or(payload); + + let message_ids = MessageIDs::from_tx_payload(payload)?; + + if message_ids.0.is_empty() { + println!("No message IDs found in payload"); + return Ok(()); + } + + println!("Decoded {} Hyperlane message ID(s):", message_ids.0.len()); + for (i, id) in message_ids.0.iter().enumerate() { + println!(" [{}] 0x{}", i, hex::encode(id.0.as_bytes())); + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use dymension_kaspa::ops::payload::MessageID; + use hyperlane_core::H256; + + #[test] + fn test_decode_known_payload() { + // Create a known payload by encoding some message IDs + let msg_id1 = MessageID(H256::from([1u8; 32])); + let msg_id2 = MessageID(H256::from([2u8; 32])); + let message_ids = MessageIDs::new(vec![msg_id1, msg_id2]); + + let encoded = hex::encode(message_ids.to_bytes()); + + // Decode should succeed + let result = decode_payload(&encoded); + assert!(result.is_ok()); + } + + #[test] + fn test_decode_with_0x_prefix() { + let msg_id = MessageID(H256::from([42u8; 32])); + let message_ids = MessageIDs::new(vec![msg_id]); + + let encoded = format!("0x{}", hex::encode(message_ids.to_bytes())); + + let result = decode_payload(&encoded); + assert!(result.is_ok()); + } + + #[test] + fn test_decode_invalid_payload() { + let result = decode_payload("invalid_hex"); + assert!(result.is_err()); + } + + #[test] + fn test_decode_empty_payload() { + // Empty protobuf message + let message_ids = MessageIDs::new(vec![]); + let encoded = hex::encode(message_ids.to_bytes()); + + let result = decode_payload(&encoded); + assert!(result.is_ok()); + } +} diff --git a/rust/main/utils/kaspa-tools/src/x/deposit.rs b/rust/main/utils/kaspa-tools/src/x/deposit.rs new file mode 100644 index 00000000000..69c4f2fa43f --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/x/deposit.rs @@ -0,0 +1,35 @@ +use dym_kas_core::wallet::get_wallet; +use dymension_kaspa::ops::user::deposit::deposit_with_payload; +use eyre::Result; +use kaspa_addresses::Address; +use kaspa_consensus_core::network::NetworkId; +use kaspa_wallet_keys::secret::Secret; + +pub struct DepositArgs { + pub wallet_secret: String, + pub wallet_dir: Option, + pub amount: String, + pub payload: String, + pub escrow_address: String, + pub network_id: NetworkId, + pub rpc_url: String, +} + +pub async fn do_deposit(args: DepositArgs) -> Result<()> { + let s = Secret::from(args.wallet_secret); + let w = get_wallet(&s, args.network_id, args.rpc_url, args.wallet_dir).await?; + let a = Address::try_from(args.escrow_address)?; + let amt = args.amount.parse::().unwrap(); + // TODO: check amt and payload + + let payload = match args.payload.is_empty() { + true => vec![], + false => hex::decode(&args.payload)?, + }; + + let res = deposit_with_payload(&w, &s, a, amt, payload); + + println!("Deposit sent: {:?}", res.await); + + Ok(()) +} diff --git a/rust/main/utils/kaspa-tools/src/x/escrow.rs b/rust/main/utils/kaspa-tools/src/x/escrow.rs new file mode 100644 index 00000000000..4ec9e2de770 --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/x/escrow.rs @@ -0,0 +1,29 @@ +use dym_kas_core::escrow::EscrowPublic; +use kaspa_addresses::Prefix; +use secp256k1::PublicKey; +use std::str::FromStr; + +pub fn get_escrow_address(pub_keys: Vec<&str>, required_signatures: u8, env: &str) -> String { + let prefix = match env { + "mainnet" => Prefix::Mainnet, + "testnet" => Prefix::Testnet, + _ => panic!("invalid env: {env}, must be 'testnet' or 'mainnet'"), + }; + let pub_keys = pub_keys + .iter() + .enumerate() + .map(|(i, s)| { + PublicKey::from_str(s).unwrap_or_else(|e| { + panic!( + "invalid public key at position {} (0-indexed): '{}' (len={}): {}", + i, + s, + s.len(), + e + ) + }) + }) + .collect::>(); + let e = EscrowPublic::from_pubs(pub_keys, prefix, required_signatures); + e.addr.to_string() +} diff --git a/rust/main/utils/kaspa-tools/src/x/mod.rs b/rust/main/utils/kaspa-tools/src/x/mod.rs new file mode 100644 index 00000000000..b243dd49e48 --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/x/mod.rs @@ -0,0 +1,7 @@ +pub mod args; +pub mod compute_deposit_id; +pub mod decode_payload; +pub mod deposit; +pub mod escrow; +pub mod relayer; +pub mod validator; diff --git a/rust/main/utils/kaspa-tools/src/x/relayer.rs b/rust/main/utils/kaspa-tools/src/x/relayer.rs new file mode 100644 index 00000000000..51f3a4b1bb4 --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/x/relayer.rs @@ -0,0 +1,5 @@ +use dymension_kaspa::validator::signer::{get_ethereum_style_signer, EthereumStyleSigner}; + +pub fn create_relayer() -> EthereumStyleSigner { + get_ethereum_style_signer().unwrap() +} diff --git a/rust/main/utils/kaspa-tools/src/x/validator.rs b/rust/main/utils/kaspa-tools/src/x/validator.rs new file mode 100644 index 00000000000..9080f5ae2c6 --- /dev/null +++ b/rust/main/utils/kaspa-tools/src/x/validator.rs @@ -0,0 +1,232 @@ +use dym_kas_core::escrow::generate_escrow_priv_key; +use dymension_kaspa::validator::signer::get_ethereum_style_signer; +use secp256k1::PublicKey; +use serde::Serialize; +use std::fs; + +use super::args::{ValidatorAwsArgs, ValidatorLocalArgs}; + +#[derive(Debug, Serialize)] +pub struct ValidatorInfos { + // HL style address to register on the Hub for the Kaspa multisig ISM + pub validator_ism_addr: String, + /// what validator will use to sign checkpoints for new deposits (and also progress indications) + pub validator_ism_priv_key: String, + /// secret key to sign kaspa inputs for withdrawals + pub validator_escrow_secret: String, + /// and pub key... + pub validator_escrow_pub_key: String, +} + +pub fn create_validator() -> (ValidatorInfos, PublicKey) { + let kp = generate_escrow_priv_key(); + let s = serde_json::to_string(&kp).unwrap(); + + let signer = get_ethereum_style_signer().unwrap(); + let pub_key = kp.public_key(); + + let ism_unescaped = signer.address.replace("\"", ""); + + ( + ValidatorInfos { + validator_ism_addr: ism_unescaped, + validator_ism_priv_key: signer.private_key, + validator_escrow_secret: s, + validator_escrow_pub_key: pub_key.to_string(), + }, + pub_key, + ) +} + +pub fn handle_local_backend(args: ValidatorLocalArgs) -> Result<(), Box> { + let mut infos = vec![]; + + for _ in 0..args.count { + let (v, _) = create_validator(); + infos.push(v); + } + + // Sort required by Hyperlane Cosmos ISM creation + infos.sort_by(|a, b| a.validator_ism_addr.cmp(&b.validator_ism_addr)); + + let json_output = serde_json::to_string_pretty(&infos)?; + + match args.output { + Some(path) => { + fs::write(&path, json_output)?; + println!("Validator keys saved to: {}", path); + } + None => { + println!("{}", json_output); + } + } + + Ok(()) +} + +pub async fn handle_aws_backend(args: ValidatorAwsArgs) -> Result<(), Box> { + use aws_config::BehaviorVersion; + use aws_sdk_kms::Client as KmsClient; + use aws_sdk_secretsmanager::Client as SecretsManagerClient; + + let kaspa_keypair = generate_escrow_priv_key(); + let kaspa_pub_key = kaspa_keypair.public_key(); + + // Initialize AWS SDK + let config = aws_config::defaults(BehaviorVersion::latest()).load().await; + + let kms_client = KmsClient::new(&config); + let sm_client = SecretsManagerClient::new(&config); + + // Validate KMS key exists and is usable + validate_kms_key(&kms_client, &args.kms_key_id).await?; + + // Normalize the path (remove trailing slash if present) + let secret_path = args.path.trim_end_matches('/'); + + // Serialize ONLY the Kaspa keypair to JSON + // This is compatible with KaspaSecpKeypair deserialization in the validator agent + // The secp256k1 crate with serde serializes Keypair as a hex string of the secret key + let keypair_json = serde_json::to_string(&kaspa_keypair)?; + + let encrypted_keypair = encrypt_with_kms(&kms_client, &args.kms_key_id, &keypair_json).await?; + let secret_arn = + store_encrypted_secret(&sm_client, secret_path, encrypted_keypair, "validator keys") + .await?; + + println!(); + println!("✓ Successfully created Kaspa validator secret!"); + println!(); + println!("Secret ARN: {}", secret_arn); + println!("Secret ID: {}", secret_path); + println!("KMS Key ID: {}", args.kms_key_id); + println!("Kaspa Escrow Public Key: {}", kaspa_pub_key.to_string()); + + Ok(()) +} + +async fn validate_kms_key( + kms_client: &aws_sdk_kms::Client, + key_id: &str, +) -> Result<(), Box> { + match kms_client.describe_key().key_id(key_id).send().await { + Ok(response) => { + let key_metadata = response + .key_metadata() + .ok_or("KMS key metadata not found")?; + + // Check if key is enabled + if !key_metadata.enabled() { + return Err(format!( + "KMS key '{}' exists but is not enabled. Please enable it first.", + key_id + ) + .into()); + } + + // AWS Secrets Manager only supports symmetric KMS keys for encryption + // Check if the key is symmetric (ENCRYPT_DECRYPT usage) + if let Some(key_usage) = key_metadata.key_usage() { + if key_usage.as_str() != "ENCRYPT_DECRYPT" { + return Err(format!( + "KMS key '{}' has key usage '{}' but AWS Secrets Manager requires a symmetric key with ENCRYPT_DECRYPT usage.\n\ + Please create a symmetric encryption key:\n\ + aws kms create-key --description \"Hyperlane Validator Keys\" --key-usage ENCRYPT_DECRYPT", + key_id, key_usage.as_str() + ).into()); + } + } + + // Verify it's a symmetric key (not RSA or ECC) + if let Some(key_spec) = key_metadata.key_spec() { + if !key_spec.as_str().starts_with("SYMMETRIC") { + return Err(format!( + "KMS key '{}' has key spec '{}' but AWS Secrets Manager requires a symmetric key (SYMMETRIC_DEFAULT).\n\ + Asymmetric keys (RSA, ECC) cannot be used for Secrets Manager encryption.\n\ + Please create a symmetric encryption key:\n\ + aws kms create-key --description \"Hyperlane Validator Keys\"", + key_id, key_spec.as_str() + ).into()); + } + } + + Ok(()) + } + Err(e) => Err(format!( + "KMS key '{}' not found or not accessible: {}.\n\ + Please create a symmetric KMS key first:\n\ + aws kms create-key --description \"Hyperlane Validator Keys\"", + key_id, e + ) + .into()), + } +} + +async fn encrypt_with_kms( + kms_client: &aws_sdk_kms::Client, + key_id: &str, + plaintext: &str, +) -> Result> { + use aws_smithy_types::Blob; + + let plaintext_blob = Blob::new(plaintext.as_bytes()); + + match kms_client + .encrypt() + .key_id(key_id) + .plaintext(plaintext_blob) + .send() + .await + { + Ok(response) => { + let ciphertext_blob = response + .ciphertext_blob() + .ok_or("KMS encrypt response missing ciphertext")?; + + // Return the binary ciphertext directly (no base64 encoding needed) + Ok(ciphertext_blob.clone()) + } + Err(e) => Err(format!( + "Failed to encrypt with KMS key '{}': {}.\n\ + Please check your KMS permissions (kms:Encrypt).", + key_id, e + ) + .into()), + } +} + +async fn store_encrypted_secret( + sm_client: &aws_sdk_secretsmanager::Client, + path: &str, + encrypted_value: aws_smithy_types::Blob, + property_name: &str, +) -> Result> { + // Store the encrypted value as binary (raw KMS ciphertext) + // We do NOT specify kms_key_id here because we've already encrypted the value ourselves + match sm_client + .create_secret() + .name(path) + .secret_binary(encrypted_value) + .description(format!("Hyperlane Kaspa validator key: {}", property_name)) + .send() + .await + { + Ok(response) => Ok(response.arn().unwrap_or("unknown").to_string()), + Err(e) => { + // Check if secret already exists + let service_err = e.into_service_error(); + if service_err.is_resource_exists_exception() { + Err(format!( + "Secret '{}' already exists. Please choose a different base path or delete existing secrets:\n\ + aws secretsmanager delete-secret --secret-id {} --force-delete-without-recovery", + path, path + ).into()) + } else { + Err(format!( + "Failed to create secret '{}': {}. Please check your AWS credentials and permissions.", + path, service_err + ).into()) + } + } + } +} diff --git a/rust/main/utils/run-locally/Cargo.toml b/rust/main/utils/run-locally/Cargo.toml index e5152f972e4..525abba914d 100644 --- a/rust/main/utils/run-locally/Cargo.toml +++ b/rust/main/utils/run-locally/Cargo.toml @@ -11,7 +11,6 @@ version.workspace = true hyperlane-base = { path = "../../hyperlane-base" } hyperlane-core = { path = "../../hyperlane-core", features = ["float"] } hyperlane-cosmos = { path = "../../chains/hyperlane-cosmos" } -hyperlane-cosmos-native = { path = "../../chains/hyperlane-cosmos-native" } hyperlane-starknet = { path = "../../chains/hyperlane-starknet" } toml_edit.workspace = true k256.workspace = true diff --git a/rust/main/utils/run-locally/src/server.rs b/rust/main/utils/run-locally/src/server.rs index b9deaba2417..097f8a35d9b 100644 --- a/rust/main/utils/run-locally/src/server.rs +++ b/rust/main/utils/run-locally/src/server.rs @@ -112,6 +112,7 @@ async fn send_insert_message_request_non_blocking() -> io::Result io::Result( search_strings.iter().for_each(|search_string_vec| { if search_string_vec .iter() - .map(|search_string| line.contains(search_string)) - .all(|x| x) + .all(|search_string| line.contains(search_string)) { let count = matches.entry(search_string_vec.clone()).or_insert(0); *count += 1; diff --git a/rust/scripts/README.md b/rust/scripts/README.md new file mode 100644 index 00000000000..575fd9675c4 --- /dev/null +++ b/rust/scripts/README.md @@ -0,0 +1,59 @@ +# Scripts + +## Scripts for ensuring consistency between relayer, validator and on-chain data + +### `check_message_db_integrity_validator_onchain.sh` + +Checks validator posted checkpoint message ids with on-chain message ids (merkle tree hook ism). +This script only works for EVM chains. + +Example usage: + +```bash +./check_message_db_integrity_validator_onchain.sh \ + --rpc-url 'https://eth.llamarpc.com' \ + --merkle-hook-address '0x48e6c30B97748d1e2e03bf3e9FbE3890ca5f8CCA' \ + --chain-name 'ethereum' \ + --start-block 12345 +``` + +### `check_message_db_integrity_relayer_onchain.sh` + +Checks relayer checkpoint message ids with on-chain message ids (merkle tree hook ism). +This script only works for EVM chains. + +Example usage: + +```bash +./check_message_db_integrity_relayer_onchain.sh \ + --rpc-url 'https://eth.llamarpc.com' \ + --merkle-hook-address '0x48e6c30B97748d1e2e03bf3e9FbE3890ca5f8CCA' \ + --domain-id 1 \ + --start-block 12345 +``` + +### `check_message_db_integrity_relayer_validator.sh` + +Checks relayer checkpoint message ids with validator checkpoint message id. + +Example usage: + +```bash +./check_message_db_integrity_relayer_validator.sh \ + --domain-id 1 \ + --chain-name ethereum \ + --leaf-index-start 1100 +``` + +### `check_merkle_db_integrity_relayer_validator.sh` + +Checks relayer checkpoint root with validator checkpoint root. + +Example usage: + +```bash +./check_merkle_db_integrity_relayer_validator.sh \ + --domain-id 1 \ + --chain-name ethereum \ + --leaf-index-start 1100 +``` diff --git a/rust/scripts/check_merkle_db_integrity_relayer_validator.sh b/rust/scripts/check_merkle_db_integrity_relayer_validator.sh new file mode 100755 index 00000000000..902fdbd8740 --- /dev/null +++ b/rust/scripts/check_merkle_db_integrity_relayer_validator.sh @@ -0,0 +1,155 @@ +#!/bin/bash + +HELP_MESSAGE="Script to check merkle root consistency between relayer and validator +Usage: $0 --chain-name --domain-id --leaf-index-start + + --help -h show help menu + --chain-name the name of the chain + --domain-id the domain id of the chain + --leaf-index-start the leaf index to start at and go forward" + +set -e + +# Function to extract message_id from checkpoint response +extract_checkpoint_merkle_root() { + echo "$1" | jq -r '.value.checkpoint.root' 2>/dev/null +} + +# Function to extract message_id from merkle insertions response +extract_relayer_merkle_root() { + echo "$1" | jq -r '.root' 2>/dev/null +} + +# Function to pretty print JSON +pretty_print_json() { + if command -v jq &> /dev/null; then + echo "$1" | jq '.' + else + echo "$1" + fi +} + +main() { + chain=$1 + domain_id=$2 + start_index=$3 + + mismatch_found=false + current_index=$start_index + + url="https://hyperlane-mainnet3-${chain}-validator-0.s3.us-east-1.amazonaws.com" + + echo "Starting comparison from index $start_index..." + echo "===============================================" + + while [ "$mismatch_found" = false ]; do + echo "Checking index $current_index..." + + # Fetch from checkpoint endpoint + checkpoint_url="$url/checkpoint_${current_index}_with_id.json" + echo -e "\n🌐 API Call: GET $checkpoint_url" + checkpoint_response=$(curl -s "$checkpoint_url") + echo "📥 Response size: $(echo "$checkpoint_response" | wc -c) bytes" + + # Debug: Print the relevant part of the response + echo "🔍 Debugging checkpoint response:" + echo "$checkpoint_response" + + checkpoint_root=$(extract_checkpoint_merkle_root "$checkpoint_response") + + # Check if checkpoint request was successful + if [[ -n "$checkpoint_root" && "$checkpoint_root" != "null" ]]; then + checkpoint_root=$(extract_checkpoint_merkle_root "$checkpoint_response") + echo "📋 Extracted checkpoint root: $checkpoint_root" + + # Fetch from merkle insertions endpoint + merkle_url="http://0.0.0.0:9090/merkle_proofs?domain_id=${domain_id}&leaf_index=${current_index}&root_index=${current_index}" + echo -e "\n🌐 API Call: GET $merkle_url" + merkle_response=$(curl -s "$merkle_url") + response_status_code=$? + + # Debug: Print the relevant part of the response + + # Check if merkle request was successful + if [ $response_status_code -eq 0 ]; then + extracted_merkle_root=$(extract_relayer_merkle_root "$merkle_response") + merkle_root="0x${extracted_merkle_root}" + echo "📋 Extracted merkle message_id: $merkle_root" + + echo -e "\n📊 Comparison for index $current_index:" + echo " Checkpoint root: $checkpoint_root" + echo " Merkle root: $merkle_root" + + # Compare the message IDs + if [ "$checkpoint_root" != "$merkle_root" ]; then + echo -e "\n⚠️ MISMATCH FOUND at index $current_index:" + echo " Checkpoint: $checkpoint_root" + echo " Merkle: $merkle_root" + mismatch_found=true + else + echo " ✓ Match" + echo "===============================================" + current_index=$((current_index + 1)) + fi + else + echo -e "\n❌ Error: Failed to parse merkle data for index $current_index" + echo "Raw response:" + pretty_print_json "$merkle_response" + exit 1 + fi + else + echo -e "\n❌ Error: Failed to parse checkpoint data for index $current_index" + echo "Raw response:" + pretty_print_json "$checkpoint_response" + exit 1 + fi + done + + echo -e "\n✅ Comparison complete. First mismatch found at index $current_index." + +} + +## Start of execution + +while [ "$#" -gt 0 ]; do + case "$1" in + "-h") + ;& + "--help") + echo "$HELP_MESSAGE" >&2 + exit 0 + ;; + "--chain-name") + shift + chain_name="$1" + ;; + "--domain-id") + shift + domain_id="$1" + ;; + "--leaf-index-start") + shift + leaf_index_start="$1" + ;; + *) + echo "Unknown argument $1. Run -h for usage" >&2 + exit 1 + ;; + esac + shift +done + +if [ -z "$chain_name" ]; then + echo "$0: Error: Chain name not provided. Run -h for usage" >&2 + exit 1 +fi +if [ -z "$domain_id" ]; then + echo "$0: Error: Domain id not provided. Run -h for usage" >&2 + exit 1 +fi +if [ -z "$leaf_index_start" ]; then + echo "$0: Error: Leaf index start not provided. Run -h for usage" >&2 + exit 1 +fi + +main "$chain_name" "$domain_id" "$leaf_index_start" diff --git a/rust/scripts/check_message_db_integrity_relayer_onchain.sh b/rust/scripts/check_message_db_integrity_relayer_onchain.sh new file mode 100755 index 00000000000..6aef3d928e9 --- /dev/null +++ b/rust/scripts/check_message_db_integrity_relayer_onchain.sh @@ -0,0 +1,193 @@ +#!/bin/bash + +HELP_MESSAGE="Script that goes backwards and compares on-chain merkle tree data with relayer local db +Usage: $0 --rpc-url --merkle-hook-address --domain-id --start-block + + --help -h show help menu + --rpc-url specifies the RPC URL to use + --merkle-hook-address the merkle hook address to get logs for + --domain-id the domain id of the chain + --start-block the block to start at and go backwards" + +set -e + +# Function to extract message_id from merkle insertions response +extract_merkle_message_id() { + echo "$1" | grep -o '"message_id":"[^"]*' | head -1 | cut -d'"' -f4 +} + +# Function to pretty print JSON +pretty_print_json() { + if command -v jq &> /dev/null; then + echo "$1" | jq '.' + else + echo "$1" + fi +} + +fetch_merkle_tree_events() { + rpc_url=$1 + address=$2 + from_block=$3 + to_block=$4 + + from_block_hex=$(printf '0x%x' $from_block) + to_block_hex=$(printf '0x%x' $to_block) + + curl -sS --fail --max-time 20 "$rpc_url" \ + -X POST \ + -H "Content-Type: application/json" \ + --data '{ + "method":"eth_getLogs", + "params":[ + { + "fromBlock": "'$from_block_hex'", + "toBlock": "'$to_block_hex'", + "address": "'$address'" + } + ], + "id":1,"jsonrpc":"2.0"}' +} + +fetch_relayer_checkpoint() { + domain_id=$1 + current_index=$2 + merkle_url="http://0.0.0.0:9090/merkle_tree_insertions?domain_id=${domain_id}&leaf_index_start=${current_index}&leaf_index_end=$((current_index + 1))" + curl -s "$merkle_url" +} + +main() { + rpc_url=$1 + address=$2 + domain_id=$3 + block_start=$4 + + mismatch_found=false + + batch_size=800 + to_block=$block_start + from_block=$to_block + + event_name='InsertedIntoTree(bytes32,uint32)' + event_signature='0x253a3a04cab70d47c1504809242d9350cd81627b4f1d50753e159cf8cd76ed33' + + echo "Starting comparison from block $block_start..." + echo "===============================================" + + while [ "$mismatch_found" = false ]; do + [ "$to_block" -le 0 ] && break + to_block=$from_block + from_block=$((to_block - batch_size)) + from_block=$((to_block - batch_size)) + if [ "$from_block" -lt 0 ]; then + from_block=0 + fi + + echo "Fetching logs $from_block .. $to_block" + events=$(fetch_merkle_tree_events "$rpc_url" "$address" "$from_block" "$to_block" | jq .result) + + # echo "$events" + event_count=$(echo $events | jq -r length) + if [ $event_count -eq 0 ]; then + continue + fi + + for ((i=0; i/dev/null) + message_id=$(echo "$decoded_event" | sed '1q;d') + leaf_index=$(echo "$decoded_event" | sed '2q;d' | cut -d ' ' -f 1) + + echo "⛓️ Leaf Index: $leaf_index" + echo "⛓️ Message ID: $message_id" + + relayer_checkpoint=$(fetch_relayer_checkpoint $domain_id $leaf_index) + relayer_message_id=$(extract_merkle_message_id "$relayer_checkpoint") + echo "📬 Relayer Message ID: $relayer_message_id" + + # Compare the message IDs + if [ "$message_id" != "$relayer_message_id" ]; then + echo -e "\n⚠️ MISMATCH FOUND at index $leaf_index:" + echo "⛓️ Onchain: $message_id" + echo "📬 Relayer: $relayer_message_id" + mismatch_found=true + mismatch_index="$leaf_index" + else + echo " ✅ Match" + echo "===============================================" + fi + + done + done + + if [ "$mismatch_found" = true ]; then + echo -e "\n✅ Comparison complete. First mismatch found at index $mismatch_index." + else + echo -e "\n✅ Comparison complete. No mismatches found in scanned range." + fi +} + +## Start of execution + +while [ "$#" -gt 0 ]; do + case "$1" in + "-h") + ;& + "--help") + echo "$HELP_MESSAGE" >&2 + exit 0 + ;; + "--rpc-url") + shift + rpc_url="$1" + ;; + "--merkle-hook-address") + shift + address="$1" + ;; + "--domain-id") + shift + domain_id="$1" + ;; + "--start-block") + shift + block_start="$1" + ;; + *) + echo "Unknown argument $1. Run -h for usage" >&2 + exit 1 + ;; + esac + shift +done + +if [ -z "$rpc_url" ]; then + echo "$0: Error: RPC URL not provided. Run -h for usage" >&2 + exit 1 +fi +if [ -z "$address" ]; then + echo "$0: Error: Merkle hook address not provided. Run -h for usage" >&2 + exit 1 +fi +if [ -z "$domain_id" ]; then + echo "$0: Error: Domain id not provided. Run -h for usage" >&2 + exit 1 +fi +if [ -z "$block_start" ]; then + echo "$0: Error: Block start not provided. Run -h for usage" >&2 + exit 1 +fi + +main "$rpc_url" "$address" "$domain_id" "$block_start" diff --git a/rust/scripts/check_message_db_integrity_relayer_validator.sh b/rust/scripts/check_message_db_integrity_relayer_validator.sh new file mode 100755 index 00000000000..9167f449636 --- /dev/null +++ b/rust/scripts/check_message_db_integrity_relayer_validator.sh @@ -0,0 +1,152 @@ +#!/bin/bash + +HELP_MESSAGE="Script to check message id consistency between relayer and validator +Usage: $0 --chain-name --domain-id --leaf-index-start + + --help -h show help menu + --chain-name the name of the chain + --domain-id the domain id of the chain + --leaf-index-start the leaf index to start at and go forward" + +set -e + +# Function to extract message_id from checkpoint response +extract_checkpoint_message_id() { + echo "$1" | jq -r '.value.message_id' 2>/dev/null || echo "$1" | grep -o '"value":{[^}]*"message_id":"[^"]*"' | grep -o '"message_id":"[^"]*"' | cut -d'"' -f4 +} + +# Function to extract message_id from merkle insertions response +extract_merkle_message_id() { + echo "$1" | grep -o '"message_id":"[^"]*' | head -1 | cut -d'"' -f4 +} + +# Function to pretty print JSON +pretty_print_json() { + if command -v jq &> /dev/null; then + echo "$1" | jq '.' + else + echo "$1" + fi +} + +main() { + chain=$1 + domain_id=$2 + start_index=$3 + current_index=$start_index + mismatch_found=false + + url="https://hyperlane-mainnet3-${chain}-validator-0.s3.us-east-1.amazonaws.com" + + echo "Starting comparison from index $start_index..." + echo "===============================================" + + while [ "$mismatch_found" = false ]; do + echo "Checking index $current_index..." + + # Fetch from checkpoint endpoint + checkpoint_url="$url/checkpoint_${current_index}_with_id.json" + echo -e "\n🌐 API Call: GET $checkpoint_url" + checkpoint_response=$(curl -s "$checkpoint_url") + echo "📥 Response size: $(echo "$checkpoint_response" | wc -c) bytes" + + # Check if checkpoint request was successful + if [[ "$checkpoint_response" == *"message_id"* ]]; then + # Debug: Print the relevant part of the response + echo "🔍 Debugging checkpoint response:" + echo "$checkpoint_response" + + checkpoint_message_id=$(extract_checkpoint_message_id "$checkpoint_response") + echo "📋 Extracted checkpoint message_id: $checkpoint_message_id" + + # Fetch from merkle insertions endpoint + merkle_url="http://0.0.0.0:9090/merkle_tree_insertions?domain_id=${domain_id}&leaf_index_start=${current_index}&leaf_index_end=$((current_index + 1))" + echo -e "\n🌐 API Call: GET $merkle_url" + merkle_response=$(curl -s "$merkle_url") + echo "📥 Response size: $(echo "$merkle_response" | wc -c) bytes" + + # Debug: Print the relevant part of the response + echo "🔍 Debugging merkle response:" + echo "$merkle_response" | grep -A 1 "message_id" + + # Check if merkle request was successful + if [[ "$merkle_response" == *"message_id"* ]]; then + merkle_message_id=$(extract_merkle_message_id "$merkle_response") + echo "📋 Extracted merkle message_id: $merkle_message_id" + + echo -e "\n📊 Comparison for index $current_index:" + echo " Checkpoint message_id: $checkpoint_message_id" + echo " Merkle message_id: $merkle_message_id" + + # Compare the message IDs + if [ "$checkpoint_message_id" != "$merkle_message_id" ]; then + echo -e "\n⚠️ MISMATCH FOUND at index $current_index:" + echo " Checkpoint: $checkpoint_message_id" + echo " Merkle: $merkle_message_id" + mismatch_found=true + else + echo " ✓ Match" + echo "===============================================" + current_index=$((current_index + 1)) + fi + else + echo -e "\n❌ Error: Failed to parse merkle data for index $current_index" + echo "Raw response:" + pretty_print_json "$merkle_response" + exit 1 + fi + else + echo -e "\n❌ Error: Failed to parse checkpoint data for index $current_index" + echo "Raw response:" + pretty_print_json "$checkpoint_response" + exit 1 + fi + done + + echo -e "\n✅ Comparison complete. First mismatch found at index $current_index." +} + +## Start of execution + +while [ "$#" -gt 0 ]; do + case "$1" in + "-h") + ;& + "--help") + echo "$HELP_MESSAGE" >&2 + exit 0 + ;; + "--chain-name") + shift + chain_name="$1" + ;; + "--domain-id") + shift + domain_id="$1" + ;; + "--leaf-index-start") + shift + leaf_index_start="$1" + ;; + *) + echo "Unknown argument $1. Run -h for usage" >&2 + exit 1 + ;; + esac + shift +done + +if [ -z "$chain_name" ]; then + echo "$0: Error: Chain name not provided. Run -h for usage" >&2 + exit 1 +fi +if [ -z "$domain_id" ]; then + echo "$0: Error: Domain id not provided. Run -h for usage" >&2 + exit 1 +fi +if [ -z "$leaf_index_start" ]; then + echo "$0: Error: Leaf index start not provided. Run -h for usage" >&2 + exit 1 +fi + +main "$chain_name" "$domain_id" "$leaf_index_start" diff --git a/rust/scripts/check_message_db_integrity_validator_onchain.sh b/rust/scripts/check_message_db_integrity_validator_onchain.sh new file mode 100755 index 00000000000..657c3c3b48d --- /dev/null +++ b/rust/scripts/check_message_db_integrity_validator_onchain.sh @@ -0,0 +1,191 @@ +#!/bin/bash + +HELP_MESSAGE="Script that goes backwards and compares on-chain merkle tree data with posted validator checkpoints. +Usage: $0 --rpc-url --merkle-hook-address --chain-name --start-block + + --help -h show help menu + --rpc-url specifies the RPC URL to use + --merkle-hook-address the merkle hook address to get logs for + --chain-name the name of the chain + --start-block the block to start at and go backwards" + +set -e + +# Function to extract message_id from checkpoint response +extract_checkpoint_message_id() { + echo "$1" | jq -r '.value.message_id' 2>/dev/null || echo "$1" | grep -o '"value":{[^}]*"message_id":"[^"]*"' | grep -o '"message_id":"[^"]*"' | cut -d'"' -f4 +} + +# Function to pretty print JSON +pretty_print_json() { + if command -v jq &> /dev/null; then + echo "$1" | jq '.' + else + echo "$1" + fi +} + +fetch_merkle_tree_events() { + rpc_url=$1 + address=$2 + from_block=$3 + to_block=$4 + + from_block_hex=$(printf '0x%x' $from_block) + to_block_hex=$(printf '0x%x' $to_block) + + curl -sS --fail --max-time 20 "$rpc_url" \ + -X POST \ + -H "Content-Type: application/json" \ + --data '{ + "method":"eth_getLogs", + "params":[ + { + "fromBlock": "'$from_block_hex'", + "toBlock": "'$to_block_hex'", + "address": "'$address'" + } + ], + "id":1,"jsonrpc":"2.0"}' +} + +fetch_validator_checkpoint() { + leaf_index=$1 + # Fetch from checkpoint endpoint + checkpoint_url="$validator_url/checkpoint_${leaf_index}_with_id.json" + curl -sS --fail --max-time 20 "$checkpoint_url" +} + +main() { + rpc_url=$1 + address=$2 + chain=$3 + block_start=$4 + + mismatch_found=false + + batch_size=800 + to_block=$block_start + from_block=$to_block + + event_name='InsertedIntoTree(bytes32,uint32)' + event_signature='0x253a3a04cab70d47c1504809242d9350cd81627b4f1d50753e159cf8cd76ed33' + + echo "Starting comparison from block $block_start..." + echo "===============================================" + + validator_url="https://hyperlane-mainnet3-${chain}-validator-0.s3.us-east-1.amazonaws.com" + + while [ "$mismatch_found" = false ]; do + [ "$to_block" -le 0 ] && break + to_block=$from_block + from_block=$(( $to_block - $batch_size )) + + echo "Fetching logs $from_block .. $to_block" + events=$(fetch_merkle_tree_events $rpc_url $address $from_block $to_block | jq .result) + + # echo "$events" + event_count=$(echo "$events" | jq -r length) + if [ $event_count -eq 0 ]; then + continue + fi + + for ((i=0; i/dev/null) + message_id=$(echo "$decoded_event" | sed '1q;d') + leaf_index=$(echo "$decoded_event" | sed '2q;d' | cut -d ' ' -f 1) + + echo "⛓️ Leaf Index: $leaf_index" + echo "⛓️ Message ID: $message_id" + + validator_checkpoint=$(fetch_validator_checkpoint "$leaf_index") + # echo $validator_checkpoint + validator_message_id=$(extract_checkpoint_message_id "$validator_checkpoint") + echo "🛡️ Validator Message ID: $validator_message_id" + + # Compare the message IDs + if [ "$message_id" != "$validator_message_id" ]; then + echo -e "\n⚠️ MISMATCH FOUND at index $leaf_index:" + echo "⛓️ Onchain: $message_id" + echo "📬 Validator: $validator_message_id" + mismatch_found=true + mismatch_index="$leaf_index" + else + echo " ✅ Match" + echo "===============================================" + fi + + done + done + + if [ "$mismatch_found" = true ]; then + echo -e "\n✅ Comparison complete. First mismatch found at index $mismatch_index." + else + echo -e "\n✅ Comparison complete. No mismatches found in scanned range." + fi + +} + +while [ "$#" -gt 0 ]; do + case "$1" in + "-h") + ;& + "--help") + echo "$HELP_MESSAGE" >&2 + exit 0 + ;; + "--rpc-url") + shift + rpc_url="$1" + ;; + "--merkle-hook-address") + shift + address="$1" + ;; + "--chain-name") + shift + chain="$1" + ;; + "--start-block") + shift + block_start="$1" + ;; + *) + echo "Unknown argument $1" >&2 + exit 1 + ;; + esac + shift +done + +if [ -z "$rpc_url" ]; then + echo "$0: Error: RPC URL not provided. Run -h for usage" >&2 + exit 1 +fi +if [ -z "$address" ]; then + echo "$0: Error: Merkle hook address not provided. Run -h for usage" >&2 + exit 1 +fi +if [ -z "$chain" ]; then + echo "$0: Error: Chain name not provided. Run -h for usage" >&2 + exit 1 +fi +if [ -z "$block_start" ]; then + echo "$0: Error: Block start not provided. Run -h for usage" >&2 + exit 1 +fi + +main "$rpc_url" "$address" "$chain" "$block_start" diff --git a/rust/sealevel/Cargo.lock b/rust/sealevel/Cargo.lock index 1135f5b432a..09548c0f252 100644 --- a/rust/sealevel/Cargo.lock +++ b/rust/sealevel/Cargo.lock @@ -424,6 +424,12 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dabbe35f96fb9507f7330793dc490461b2962659ac5d427181e451a623751d1" +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + [[package]] name = "bigdecimal" version = "0.4.5" @@ -938,7 +944,7 @@ checksum = "c94090a6663f224feae66ab01e41a2555a8296ee07b5f20dab8888bdefc9f617" dependencies = [ "base58check", "base64 0.12.3", - "bech32", + "bech32 0.7.3", "blake2", "digest 0.10.7", "generic-array 0.14.7", @@ -1379,6 +1385,12 @@ dependencies = [ "syn 0.15.44", ] +[[package]] +name = "downcast-rs" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea8a8b81cacc08888170eef4d13b775126db426d0b348bee9d18c2c1eaf123cf" + [[package]] name = "dunce" version = "1.0.5" @@ -2409,6 +2421,7 @@ dependencies = [ "async-rwlock", "async-trait", "auto_impl 1.2.0", + "bech32 0.11.0", "bigdecimal", "borsh", "bs58 0.5.1", @@ -2416,6 +2429,7 @@ dependencies = [ "convert_case 0.6.0", "derive-new", "derive_more 0.99.17", + "downcast-rs", "eyre", "fixed-hash 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "getrandom 0.2.15", @@ -2455,10 +2469,14 @@ dependencies = [ "hyperlane-sealevel-igp", "hyperlane-sealevel-mailbox", "hyperlane-sealevel-multisig-ism-message-id", + "hyperlane-sealevel-test-ism", "hyperlane-sealevel-token", "hyperlane-sealevel-token-collateral", + "hyperlane-sealevel-token-collateral-memo", "hyperlane-sealevel-token-lib", + "hyperlane-sealevel-token-memo", "hyperlane-sealevel-token-native", + "hyperlane-sealevel-token-native-memo", "hyperlane-sealevel-validator-announce", "pretty_env_logger", "serde", @@ -2722,6 +2740,35 @@ dependencies = [ "thiserror", ] +[[package]] +name = "hyperlane-sealevel-token-collateral-memo" +version = "0.1.0" +dependencies = [ + "account-utils", + "borsh", + "hyperlane-core", + "hyperlane-sealevel-connection-client", + "hyperlane-sealevel-igp", + "hyperlane-sealevel-mailbox", + "hyperlane-sealevel-message-recipient-interface", + "hyperlane-sealevel-test-ism", + "hyperlane-sealevel-token-collateral", + "hyperlane-sealevel-token-lib", + "hyperlane-test-utils", + "hyperlane-warp-route", + "num-derive 0.4.2", + "num-traits", + "serializable-account-meta", + "solana-program", + "solana-program-test", + "solana-sdk", + "spl-associated-token-account", + "spl-noop", + "spl-token", + "spl-token-2022", + "thiserror", +] + [[package]] name = "hyperlane-sealevel-token-lib" version = "0.1.0" @@ -2746,6 +2793,34 @@ dependencies = [ "thiserror", ] +[[package]] +name = "hyperlane-sealevel-token-memo" +version = "0.1.0" +dependencies = [ + "account-utils", + "borsh", + "hyperlane-core", + "hyperlane-sealevel-connection-client", + "hyperlane-sealevel-igp", + "hyperlane-sealevel-mailbox", + "hyperlane-sealevel-message-recipient-interface", + "hyperlane-sealevel-test-ism", + "hyperlane-sealevel-token-lib", + "hyperlane-test-utils", + "hyperlane-warp-route", + "num-derive 0.4.2", + "num-traits", + "serializable-account-meta", + "solana-program", + "solana-program-test", + "solana-sdk", + "spl-associated-token-account", + "spl-noop", + "spl-token", + "spl-token-2022", + "thiserror", +] + [[package]] name = "hyperlane-sealevel-token-native" version = "0.1.0" @@ -2772,6 +2847,33 @@ dependencies = [ "thiserror", ] +[[package]] +name = "hyperlane-sealevel-token-native-memo" +version = "0.1.0" +dependencies = [ + "account-utils", + "borsh", + "hyperlane-core", + "hyperlane-sealevel-connection-client", + "hyperlane-sealevel-igp", + "hyperlane-sealevel-mailbox", + "hyperlane-sealevel-message-recipient-interface", + "hyperlane-sealevel-test-ism", + "hyperlane-sealevel-token-lib", + "hyperlane-sealevel-token-native", + "hyperlane-test-utils", + "hyperlane-warp-route", + "num-derive 0.4.2", + "num-traits", + "serializable-account-meta", + "solana-program", + "solana-program-test", + "solana-sdk", + "spl-noop", + "tarpc", + "thiserror", +] + [[package]] name = "hyperlane-sealevel-validator-announce" version = "0.1.0" diff --git a/rust/sealevel/Cargo.toml b/rust/sealevel/Cargo.toml index 96a8cfbbf6b..0a4243ba519 100644 --- a/rust/sealevel/Cargo.toml +++ b/rust/sealevel/Cargo.toml @@ -15,8 +15,11 @@ members = [ "programs/hyperlane-sealevel-igp", "programs/hyperlane-sealevel-igp-test", "programs/hyperlane-sealevel-token", + "programs/hyperlane-sealevel-token-memo", "programs/hyperlane-sealevel-token-collateral", + "programs/hyperlane-sealevel-token-collateral-memo", "programs/hyperlane-sealevel-token-native", + "programs/hyperlane-sealevel-token-native-memo", "programs/ism/multisig-ism-message-id", "programs/ism/test-ism", "programs/mailbox", diff --git a/rust/sealevel/client/Cargo.toml b/rust/sealevel/client/Cargo.toml index 499e0f27f1c..2a367f3c168 100644 --- a/rust/sealevel/client/Cargo.toml +++ b/rust/sealevel/client/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-sealevel-client" @@ -36,6 +36,9 @@ hyperlane-sealevel-multisig-ism-message-id = { path = "../programs/ism/multisig- hyperlane-sealevel-token = { path = "../programs/hyperlane-sealevel-token", features = [ "no-entrypoint", ] } +hyperlane-sealevel-token-memo = { path = "../programs/hyperlane-sealevel-token-memo", features = [ + "no-entrypoint", +] } hyperlane-sealevel-igp = { path = "../programs/hyperlane-sealevel-igp", features = [ "no-entrypoint", "serde", @@ -43,11 +46,18 @@ hyperlane-sealevel-igp = { path = "../programs/hyperlane-sealevel-igp", features hyperlane-sealevel-token-collateral = { path = "../programs/hyperlane-sealevel-token-collateral", features = [ "no-entrypoint", ] } +hyperlane-sealevel-token-collateral-memo = { path = "../programs/hyperlane-sealevel-token-collateral-memo", features = [ + "no-entrypoint", +] } hyperlane-sealevel-token-lib = { path = "../libraries/hyperlane-sealevel-token" } hyperlane-sealevel-token-native = { path = "../programs/hyperlane-sealevel-token-native", features = [ "no-entrypoint", ] } +hyperlane-sealevel-token-native-memo = { path = "../programs/hyperlane-sealevel-token-native-memo", features = [ + "no-entrypoint", +] } hyperlane-sealevel-validator-announce = { path = "../programs/validator-announce", features = [ "no-entrypoint", ] } hyperlane-sealevel-hello-world = { path = "../programs/helloworld" } +hyperlane-sealevel-test-ism = { path = "../programs/ism/test-ism", features = ["no-entrypoint"] } diff --git a/rust/sealevel/client/src/core.rs b/rust/sealevel/client/src/core.rs index 534b5979651..6ab9dc16b04 100644 --- a/rust/sealevel/client/src/core.rs +++ b/rust/sealevel/client/src/core.rs @@ -273,7 +273,8 @@ fn deploy_igp(ctx: &mut Context, core: &CoreDeploy, key_dir: &Path) -> (Pubkey, (program_id, overhead_igp_account, igp_account) } -#[derive(Debug, Serialize, Deserialize)] +// Core structure maintained for upstream compatibility +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct CoreProgramIds { #[serde(with = "crate::serde::serde_pubkey")] pub mailbox: Pubkey, @@ -289,6 +290,55 @@ pub struct CoreProgramIds { pub igp_account: Pubkey, } +// Fork-specific extension for flexible deployments +#[derive(Debug, Serialize, Deserialize)] +pub struct FlexibleCoreProgramIds { + #[serde(with = "crate::serde::serde_pubkey")] + pub mailbox: Pubkey, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "crate::serde::serde_option_pubkey")] + pub validator_announce: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "crate::serde::serde_option_pubkey")] + pub multisig_ism_message_id: Option, + #[serde(with = "crate::serde::serde_pubkey")] + pub igp_program_id: Pubkey, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "crate::serde::serde_option_pubkey")] + pub overhead_igp_account: Option, + #[serde(with = "crate::serde::serde_pubkey")] + pub igp_account: Pubkey, +} + +impl FlexibleCoreProgramIds { + /// Convert to CoreProgramIds, using default values for missing components + pub fn to_core_with_defaults(&self) -> CoreProgramIds { + // Use a dummy program ID for missing components + // This is safe as these will fail if actually used + let dummy_program = Pubkey::default(); + + CoreProgramIds { + mailbox: self.mailbox, + validator_announce: self.validator_announce.unwrap_or(dummy_program), + multisig_ism_message_id: self.multisig_ism_message_id.unwrap_or(dummy_program), + igp_program_id: self.igp_program_id, + overhead_igp_account: self.overhead_igp_account.unwrap_or(dummy_program), + igp_account: self.igp_account, + } + } + + /// Check if this has all required components for full core functionality + #[allow(dead_code)] + pub fn is_complete(&self) -> bool { + self.validator_announce.is_some() + && self.multisig_ism_message_id.is_some() + && self.overhead_igp_account.is_some() + } +} + fn write_program_ids(core_dir: &Path, program_ids: CoreProgramIds) { write_json(&core_dir.join("program-ids.json"), program_ids); } @@ -303,7 +353,48 @@ pub(crate) fn read_core_program_ids( .join(chain) .join("core") .join("program-ids.json"); - read_json(&path) + + // Try to read as CoreProgramIds first (upstream format) + if let Ok(core_ids) = + serde_json::from_str::(&std::fs::read_to_string(&path).unwrap_or_default()) + { + return core_ids; + } + + // Fall back to FlexibleCoreProgramIds (fork format) + let flexible_ids: FlexibleCoreProgramIds = read_json(&path); + flexible_ids.to_core_with_defaults() +} + +/// Read core program IDs in flexible format for contexts that need optional components +pub(crate) fn read_flexible_core_program_ids( + environments_dir: &Path, + environment: &str, + chain: &str, +) -> FlexibleCoreProgramIds { + let path = environments_dir + .join(environment) + .join(chain) + .join("core") + .join("program-ids.json"); + + // Try to read as FlexibleCoreProgramIds first + if let Ok(flexible_ids) = serde_json::from_str::( + &std::fs::read_to_string(&path).unwrap_or_default(), + ) { + return flexible_ids; + } + + // Fall back to CoreProgramIds and convert to flexible + let core_ids: CoreProgramIds = read_json(&path); + FlexibleCoreProgramIds { + mailbox: core_ids.mailbox, + validator_announce: Some(core_ids.validator_announce), + multisig_ism_message_id: Some(core_ids.multisig_ism_message_id), + igp_program_id: core_ids.igp_program_id, + overhead_igp_account: Some(core_ids.overhead_igp_account), + igp_account: core_ids.igp_account, + } } #[cfg(test)] diff --git a/rust/sealevel/client/src/igp.rs b/rust/sealevel/client/src/igp.rs index 9943ef137a4..24695ed5a31 100644 --- a/rust/sealevel/client/src/igp.rs +++ b/rust/sealevel/client/src/igp.rs @@ -4,7 +4,7 @@ use std::collections::{HashMap, HashSet}; use crate::{ artifacts::{read_json, try_read_json, write_json, SingularProgramIdArtifact}, cmd_utils::{create_new_directory, deploy_program}, - read_core_program_ids, + read_flexible_core_program_ids, registry::FileSystemRegistry, Context, GasOverheadSubCmd, GetSetCmd, IgpCmd, IgpSubCmd, }; @@ -304,7 +304,7 @@ pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) { .send_with_payer(); } IgpSubCmd::GasOracleConfig(args) => { - let core_program_ids = read_core_program_ids( + let core_program_ids = read_flexible_core_program_ids( &args.env_args.environments_dir, &args.env_args.environment, &args.chain_name, @@ -353,7 +353,7 @@ pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) { } } IgpSubCmd::DestinationGasOverhead(args) => { - let core_program_ids = read_core_program_ids( + let core_program_ids = read_flexible_core_program_ids( &args.env_args.environments_dir, &args.env_args.environment, &args.chain_name, @@ -361,10 +361,13 @@ pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) { match args.cmd { GasOverheadSubCmd::Get => { // Read the gas overhead config + let overhead_igp_account_pubkey = core_program_ids + .overhead_igp_account + .expect("Overhead IGP account not configured"); let overhead_igp_account = ctx .client .get_account_with_commitment( - &core_program_ids.overhead_igp_account, + &overhead_igp_account_pubkey, ctx.commitment, ) .unwrap() @@ -385,10 +388,13 @@ pub(crate) fn process_igp_cmd(mut ctx: Context, cmd: IgpCmd) { gas_overhead: Some(set_args.gas_overhead), }; // Set the gas overhead config + let overhead_igp_account_pubkey = core_program_ids + .overhead_igp_account + .expect("Overhead IGP account not configured"); let instruction = hyperlane_sealevel_igp::instruction::set_destination_gas_overheads( core_program_ids.igp_program_id, - core_program_ids.overhead_igp_account, + overhead_igp_account_pubkey, ctx.payer_pubkey, vec![overhead_config], ) diff --git a/rust/sealevel/client/src/ism.rs b/rust/sealevel/client/src/ism.rs new file mode 100644 index 00000000000..66331cd0128 --- /dev/null +++ b/rust/sealevel/client/src/ism.rs @@ -0,0 +1,282 @@ +//! ISM (Interchain Security Module) query implementation for Solana +//! +//! This module provides a unified interface to query different types of ISM programs. + +use borsh::BorshDeserialize; +use solana_program::pubkey::Pubkey; + +use crate::{Context, IsmCmd, IsmType}; + +use hyperlane_sealevel_multisig_ism_message_id::{ + access_control_pda_seeds, + accounts::{AccessControlAccount, DomainDataAccount}, + domain_data_pda_seeds, +}; +use hyperlane_sealevel_token::plugin::SyntheticPlugin; +use hyperlane_sealevel_token_collateral::plugin::CollateralPlugin; +use hyperlane_sealevel_token_lib::{accounts::HyperlaneTokenAccount, hyperlane_token_pda_seeds}; +use hyperlane_sealevel_token_native::plugin::NativePlugin; + +/// Processes ISM commands +pub(crate) fn process_ism_cmd(mut ctx: Context, cmd: IsmCmd) { + let query = cmd.query; + + // If token is provided, extract domains from token + let domains = if let Some(token_program_id) = query.token { + println!("🔍 Fetching domains from token: {}", token_program_id); + match get_domains_from_token(&mut ctx, token_program_id, query.program_id) { + Ok(domains) => { + if domains.is_empty() { + println!("⚠️ Warning: Token has no remote routers configured"); + None + } else { + println!( + "✅ Found {} remote domain(s) from token: {:?}", + domains.len(), + domains + ); + Some(domains) + } + } + Err(e) => { + println!("❌ Error fetching token data: {}", e); + return; + } + } + } else { + query.domains + }; + + match query.ism_type { + IsmType::MultisigMessageId => { + query_multisig_ism_message_id(&mut ctx, query.program_id, domains); + } + IsmType::Test => { + query_test_ism(&mut ctx, query.program_id); + } + } +} + +/// Fetches token account and extracts remote domains, verifying ISM matches if configured +fn get_domains_from_token( + ctx: &mut Context, + token_program_id: Pubkey, + expected_ism_program_id: Pubkey, +) -> Result, String> { + // Get token PDA account + let (token_pda_key, _token_bump) = + Pubkey::find_program_address(hyperlane_token_pda_seeds!(), &token_program_id); + + let accounts = ctx + .client + .get_multiple_accounts_with_commitment(&[token_pda_key], ctx.commitment) + .map_err(|e| format!("Failed to fetch token account: {}", e))? + .value; + + let account = accounts[0] + .as_ref() + .ok_or_else(|| "Token account not found".to_string())?; + + // Try to deserialize with different plugin types and extract ISM + remote_routers + let (ism, remote_routers): ( + Option, + std::collections::HashMap, + ) = { + // Try SyntheticPlugin first + if let Ok(token_account) = + HyperlaneTokenAccount::::fetch(&mut &account.data[..]) + { + let token = token_account.into_inner(); + (token.interchain_security_module, token.remote_routers) + } else if let Ok(token_account) = + HyperlaneTokenAccount::::fetch(&mut &account.data[..]) + { + let token = token_account.into_inner(); + (token.interchain_security_module, token.remote_routers) + } else if let Ok(token_account) = + HyperlaneTokenAccount::::fetch(&mut &account.data[..]) + { + let token = token_account.into_inner(); + (token.interchain_security_module, token.remote_routers) + } else { + return Err("Failed to deserialize token account with any plugin type (tried Synthetic, Native, Collateral)".to_string()); + } + }; + + // Verify ISM matches if configured (skip validation if None - uses mailbox default) + if let Some(token_ism) = ism { + if token_ism != expected_ism_program_id { + return Err(format!( + "Token ISM ({}) does not match expected ISM ({})", + token_ism, expected_ism_program_id + )); + } + } + + // Extract domains from remote_routers + let mut domains: Vec = remote_routers.keys().copied().collect(); + domains.sort(); + Ok(domains) +} + +/// Queries a Multisig ISM Message ID program +fn query_multisig_ism_message_id(ctx: &mut Context, program_id: Pubkey, domains: Option>) { + println!("================================="); + println!("Multisig ISM Message ID Query"); + println!("Program ID: {}", program_id); + println!("=================================\n"); + + // Query access control PDA + let (access_control_pda_key, access_control_bump) = + Pubkey::find_program_address(access_control_pda_seeds!(), &program_id); + + println!("Access Control PDA: {}", access_control_pda_key); + println!("Access Control Bump: {}\n", access_control_bump); + + let accounts = ctx + .client + .get_multiple_accounts_with_commitment(&[access_control_pda_key], ctx.commitment) + .unwrap() + .value; + + if let Some(account) = &accounts[0] { + let access_control = AccessControlAccount::fetch(&mut &account.data[..]) + .unwrap() + .into_inner(); + println!("Access Control Data:"); + println!(" Owner: {:?}", access_control.owner); + println!(" Bump Seed: {}", access_control.bump_seed); + } else { + println!("Access Control PDA not initialized"); + } + + // Query domain data if domains are specified + if let Some(domains) = domains { + println!("\n---------------------------------"); + println!("Domain Data"); + println!("---------------------------------"); + + for domain in domains { + println!("\nDomain: {}", domain); + + let (domain_data_pda_key, domain_data_bump) = + Pubkey::find_program_address(domain_data_pda_seeds!(domain), &program_id); + + println!(" Domain Data PDA: {}", domain_data_pda_key); + println!(" Domain Data Bump: {}", domain_data_bump); + + let accounts = ctx + .client + .get_multiple_accounts_with_commitment(&[domain_data_pda_key], ctx.commitment) + .unwrap() + .value; + + if let Some(account) = &accounts[0] { + let domain_data = DomainDataAccount::fetch(&mut &account.data[..]) + .unwrap() + .into_inner(); + + println!( + " Validators ({}):", + domain_data.validators_and_threshold.validators.len() + ); + for (i, validator) in domain_data + .validators_and_threshold + .validators + .iter() + .enumerate() + { + println!(" {}: {}", i + 1, validator); + } + println!( + " Threshold: {}", + domain_data.validators_and_threshold.threshold + ); + println!(" Bump Seed: {}", domain_data.bump_seed); + } else { + println!(" Status: Not initialized"); + } + } + } else { + println!("\nℹ️ Tip: Use --domains to query specific domain configurations"); + println!(" Example: --domains 1,2,3"); + println!(" Or use --token to automatically query domains from a token's remote routers"); + println!(" Example: --token "); + } + + println!("\n================================="); +} + +/// Queries a Test ISM program +fn query_test_ism(ctx: &mut Context, program_id: Pubkey) { + println!("================================="); + println!("Test ISM Query"); + println!("Program ID: {}", program_id); + println!("=================================\n"); + + let (storage_pda_key, storage_bump) = + Pubkey::find_program_address(&[b"test_ism", b"-", b"storage"], &program_id); + + println!("Storage PDA: {}", storage_pda_key); + println!("Storage Bump: {}\n", storage_bump); + + let accounts = ctx + .client + .get_multiple_accounts_with_commitment(&[storage_pda_key], ctx.commitment) + .unwrap() + .value; + + if let Some(account) = &accounts[0] { + // Try to deserialize the storage, handling both formats (with and without discriminator) + let storage = if account.data.len() >= 8 { + // Try with 8-byte discriminator first (new format) + match hyperlane_sealevel_test_ism::program::TestIsmStorage::deserialize( + &mut &account.data[8..], + ) { + Ok(s) => s, + Err(_) => { + // Fall back to no discriminator (old format) + match hyperlane_sealevel_test_ism::program::TestIsmStorage::deserialize( + &mut &account.data[..], + ) { + Ok(s) => s, + Err(e) => { + println!("❌ Error deserializing Test ISM storage: {}", e); + println!("Account data (hex): {:02x?}", account.data); + return; + } + } + } + } + } else { + // Small data, try without discriminator + match hyperlane_sealevel_test_ism::program::TestIsmStorage::deserialize( + &mut &account.data[..], + ) { + Ok(s) => s, + Err(e) => { + println!("❌ Error deserializing Test ISM storage: {}", e); + println!("Account data (hex): {:02x?}", account.data); + return; + } + } + }; + + println!("Storage Data:"); + println!( + " Accept: {} ({})", + storage.accept, + if storage.accept { + "✅ Accepts all messages" + } else { + "❌ Rejects all messages" + } + ); + println!("\n⚠️ WARNING: This is a TEST ISM with no access control!"); + println!(" Do NOT use in production!"); + } else { + println!("❌ Test ISM Storage not initialized"); + } + + println!("\n================================="); +} diff --git a/rust/sealevel/client/src/main.rs b/rust/sealevel/client/src/main.rs index 1562658dc89..7ff95a235a9 100644 --- a/rust/sealevel/client/src/main.rs +++ b/rust/sealevel/client/src/main.rs @@ -45,15 +45,26 @@ use hyperlane_sealevel_token::{ use hyperlane_sealevel_token_collateral::{ hyperlane_token_escrow_pda_seeds, plugin::CollateralPlugin, }; +use hyperlane_sealevel_token_collateral_memo::{ + hyperlane_token_ata_payer_pda_seeds as hyperlane_token_ata_payer_pda_seeds_collateral_memo, + hyperlane_token_escrow_pda_seeds as hyperlane_token_escrow_pda_seeds_memo, +}; use hyperlane_sealevel_token_lib::{ accounts::HyperlaneTokenAccount, hyperlane_token_pda_seeds, instruction::{ enroll_remote_routers_instruction, set_destination_gas_configs, - Instruction as HtInstruction, TransferRemote as HtTransferRemote, + DymInstruction as DymHtInstruction, Instruction as HtInstruction, + TransferRemote as HtTransferRemote, TransferRemoteMemo as DymHtTransferRemoteMemo, }, }; +use hyperlane_sealevel_token_memo::{ + hyperlane_token_ata_payer_pda_seeds as hyperlane_token_ata_payer_pda_seeds_memo, + hyperlane_token_mint_pda_seeds as hyperlane_token_mint_pda_seeds_memo, + spl_associated_token_account::get_associated_token_address_with_program_id as get_associated_token_address_with_program_id_memo, +}; use hyperlane_sealevel_token_native::hyperlane_token_native_collateral_pda_seeds; +use hyperlane_sealevel_token_native_memo::hyperlane_token_native_collateral_pda_seeds as hyperlane_token_native_memo_collateral_pda_seeds; use hyperlane_sealevel_validator_announce::{ accounts::ValidatorStorageLocationsAccount, instruction::{ @@ -72,16 +83,20 @@ mod context; mod r#core; mod helloworld; mod igp; +mod ism; mod multisig_ism; mod registry; mod router; mod serde; mod squads; +mod test_ism; mod warp_route; use crate::helloworld::process_helloworld_cmd; use crate::igp::process_igp_cmd; +use crate::ism::process_ism_cmd; use crate::multisig_ism::process_multisig_ism_message_id_cmd; +use crate::test_ism::process_test_ism_cmd; use crate::warp_route::process_warp_route_cmd; pub(crate) use crate::{context::*, core::*}; @@ -116,9 +131,11 @@ enum HyperlaneSealevelCmd { Core(CoreCmd), Mailbox(MailboxCmd), Token(TokenCmd), + Ism(IsmCmd), Igp(IgpCmd), ValidatorAnnounce(ValidatorAnnounceCmd), MultisigIsmMessageId(MultisigIsmMessageIdCmd), + TestIsm(TestIsmCmd), WarpRoute(WarpRouteCmd), HelloWorld(HelloWorldCmd), Squads(SquadsCmd), @@ -301,6 +318,7 @@ struct TokenCmd { enum TokenSubCmd { Query(TokenQuery), TransferRemote(TokenTransferRemote), + TransferRemoteMemo(TokenTransferRemoteMemo), EnrollRemoteRouter(TokenEnrollRemoteRouter), SetDestinationGas(TokenSetDestinationGas), TransferOwnership(TransferOwnership), @@ -311,8 +329,11 @@ enum TokenSubCmd { #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] pub enum TokenType { Native, + NativeMemo, Synthetic, + SyntheticMemo, Collateral, + CollateralMemo, } #[derive(Args)] @@ -323,6 +344,30 @@ struct TokenQuery { token_type: TokenType, } +#[derive(Args)] +pub(crate) struct IsmCmd { + #[command(flatten)] + query: IsmQuery, +} + +#[derive(Args)] +pub(crate) struct IsmQuery { + #[arg(long, short)] + program_id: Pubkey, + #[arg(value_enum)] + ism_type: IsmType, + #[arg(long, value_delimiter = ',', conflicts_with = "token")] + domains: Option>, + #[arg(long, conflicts_with = "domains")] + token: Option, +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +pub enum IsmType { + MultisigMessageId, + Test, +} + #[derive(Args)] struct TokenTransferRemote { #[arg(long, short, default_value_t = HYPERLANE_TOKEN_PROG_ID)] @@ -336,6 +381,21 @@ struct TokenTransferRemote { token_type: TokenType, } +#[derive(Args)] +// TODO: would have been nice to not duplicate +struct TokenTransferRemoteMemo { + #[arg(long, short, default_value_t = HYPERLANE_TOKEN_PROG_ID)] + program_id: Pubkey, + // Note this is the keypair for normal account not the derived associated token account or delegate. + sender: String, + amount: u64, + destination_domain: u32, + recipient: String, + #[arg(value_enum)] + token_type: TokenType, + memo: String, +} + #[derive(Args)] struct TokenEnrollRemoteRouter { #[arg(long, short, default_value_t = HYPERLANE_TOKEN_PROG_ID)] @@ -719,6 +779,52 @@ pub(crate) struct HelloWorldQuery { program_id: Pubkey, } +#[derive(Args)] +struct TestIsmCmd { + #[command(subcommand)] + cmd: TestIsmSubCmd, +} + +#[derive(Subcommand)] +enum TestIsmSubCmd { + Deploy(TestIsmDeploy), + Init(TestIsmInit), + SetAccept(TestIsmSetAccept), + Query(TestIsmQuery), +} + +#[derive(Args)] +struct TestIsmDeploy { + #[command(flatten)] + env_args: EnvironmentArgs, + #[arg(long)] + built_so_dir: PathBuf, + #[arg(long)] + chain: String, + #[arg(long)] + context: String, +} + +#[derive(Args)] +struct TestIsmInit { + #[arg(long, short)] + program_id: Pubkey, +} + +#[derive(Args)] +struct TestIsmSetAccept { + #[arg(long, short)] + program_id: Pubkey, + #[arg(long)] + accept: bool, +} + +#[derive(Args)] +struct TestIsmQuery { + #[arg(long, short)] + program_id: Pubkey, +} + fn main() { pretty_env_logger::init(); @@ -787,10 +893,12 @@ fn main() { match cli.cmd { HyperlaneSealevelCmd::Mailbox(cmd) => process_mailbox_cmd(ctx, cmd), HyperlaneSealevelCmd::Token(cmd) => process_token_cmd(ctx, cmd), + HyperlaneSealevelCmd::Ism(cmd) => process_ism_cmd(ctx, cmd), HyperlaneSealevelCmd::ValidatorAnnounce(cmd) => process_validator_announce_cmd(ctx, cmd), HyperlaneSealevelCmd::MultisigIsmMessageId(cmd) => { process_multisig_ism_message_id_cmd(ctx, cmd) } + HyperlaneSealevelCmd::TestIsm(cmd) => process_test_ism_cmd(ctx, cmd), HyperlaneSealevelCmd::Core(cmd) => process_core_cmd(ctx, cmd), HyperlaneSealevelCmd::WarpRoute(cmd) => process_warp_route_cmd(ctx, cmd), HyperlaneSealevelCmd::HelloWorld(cmd) => process_helloworld_cmd(ctx, cmd), @@ -943,6 +1051,14 @@ fn process_token_cmd(mut ctx: Context, cmd: TokenCmd) { ); accounts_to_query.push(native_collateral_account); } + TokenType::NativeMemo => { + let (native_collateral_account, _native_collateral_bump) = + Pubkey::find_program_address( + hyperlane_token_native_memo_collateral_pda_seeds!(), + &query.program_id, + ); + accounts_to_query.push(native_collateral_account); + } TokenType::Synthetic => { let (mint_account, _mint_bump) = Pubkey::find_program_address( hyperlane_token_mint_pda_seeds!(), @@ -955,6 +1071,18 @@ fn process_token_cmd(mut ctx: Context, cmd: TokenCmd) { accounts_to_query.push(mint_account); accounts_to_query.push(ata_payer_account); } + TokenType::SyntheticMemo => { + let (mint_account, _mint_bump) = Pubkey::find_program_address( + hyperlane_token_mint_pda_seeds_memo!(), + &query.program_id, + ); + let (ata_payer_account, _ata_payer_bump) = Pubkey::find_program_address( + hyperlane_token_ata_payer_pda_seeds_memo!(), + &query.program_id, + ); + accounts_to_query.push(mint_account); + accounts_to_query.push(ata_payer_account); + } TokenType::Collateral => { let (escrow_account, _escrow_bump) = Pubkey::find_program_address( hyperlane_token_escrow_pda_seeds!(), @@ -962,6 +1090,13 @@ fn process_token_cmd(mut ctx: Context, cmd: TokenCmd) { ); accounts_to_query.push(escrow_account); } + TokenType::CollateralMemo => { + let (escrow_account, _escrow_bump) = Pubkey::find_program_address( + hyperlane_token_escrow_pda_seeds_memo!(), + &query.program_id, + ); + accounts_to_query.push(escrow_account); + } } let accounts = ctx @@ -1001,6 +1136,23 @@ fn process_token_cmd(mut ctx: Context, cmd: TokenCmd) { } println!("--------------------------------"); } + TokenType::NativeMemo => { + let (native_collateral_account, native_collateral_bump) = + Pubkey::find_program_address( + hyperlane_token_native_memo_collateral_pda_seeds!(), + &query.program_id, + ); + println!( + "Native Token Memo Collateral: {}, bump={}", + native_collateral_account, native_collateral_bump + ); + if let Some(info) = &accounts[1] { + println!("{:#?}", info); + } else { + println!("Not yet created?"); + } + println!("--------------------------------"); + } TokenType::Synthetic => { let (mint_account, mint_bump) = Pubkey::find_program_address( hyperlane_token_mint_pda_seeds!(), @@ -1030,6 +1182,35 @@ fn process_token_cmd(mut ctx: Context, cmd: TokenCmd) { ata_payer_account, ata_payer_bump, ); } + TokenType::SyntheticMemo => { + let (mint_account, mint_bump) = Pubkey::find_program_address( + hyperlane_token_mint_pda_seeds_memo!(), + &query.program_id, + ); + println!( + "Mint / Mint Authority: {}, bump={}", + mint_account, mint_bump + ); + if let Some(info) = &accounts[1] { + println!("{:#?}", info); + use solana_program::program_pack::Pack as _; + match spl_token_2022::state::Mint::unpack_from_slice(info.data.as_ref()) { + Ok(mint) => println!("{:#?}", mint), + Err(err) => println!("Failed to deserialize account data: {}", err), + } + } else { + println!("Not yet created?"); + } + + let (ata_payer_account, ata_payer_bump) = Pubkey::find_program_address( + hyperlane_token_ata_payer_pda_seeds_memo!(), + &query.program_id, + ); + println!( + "ATA payer account: {}, bump={}", + ata_payer_account, ata_payer_bump, + ); + } TokenType::Collateral => { let (escrow_account, escrow_bump) = Pubkey::find_program_address( hyperlane_token_escrow_pda_seeds!(), @@ -1046,6 +1227,27 @@ fn process_token_cmd(mut ctx: Context, cmd: TokenCmd) { &query.program_id, ); + println!( + "ATA payer account: {}, bump={}", + ata_payer_account, ata_payer_bump, + ); + } + TokenType::CollateralMemo => { + let (escrow_account, escrow_bump) = Pubkey::find_program_address( + hyperlane_token_escrow_pda_seeds_memo!(), + &query.program_id, + ); + + println!( + "escrow_account (key, bump)=({}, {})", + escrow_account, escrow_bump, + ); + + let (ata_payer_account, ata_payer_bump) = Pubkey::find_program_address( + hyperlane_token_ata_payer_pda_seeds_collateral_memo!(), + &query.program_id, + ); + println!( "ATA payer account: {}, bump={}", ata_payer_account, ata_payer_bump, @@ -1171,8 +1373,8 @@ fn process_token_cmd(mut ctx: Context, cmd: TokenCmd) { match xfer.token_type { TokenType::Native => { - // 5. [executable] The system program. - // 6. [writeable] The native token collateral PDA account. + // 14. [executable] The system program. + // 15. [writeable] The native token collateral PDA account. let (native_collateral_account, _native_collateral_bump) = Pubkey::find_program_address( hyperlane_token_native_collateral_pda_seeds!(), @@ -1183,6 +1385,19 @@ fn process_token_cmd(mut ctx: Context, cmd: TokenCmd) { AccountMeta::new(native_collateral_account, false), ]); } + TokenType::NativeMemo => { + // 14. [executable] The system program. + // 15. [writeable] The native token collateral PDA account. + let (native_collateral_account, _native_collateral_bump) = + Pubkey::find_program_address( + hyperlane_token_native_memo_collateral_pda_seeds!(), + &xfer.program_id, + ); + accounts.extend([ + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new(native_collateral_account, false), + ]); + } TokenType::Synthetic => { // 5. [executable] The spl_token_2022 program. // 6. [writeable] The mint / mint authority PDA account. @@ -1203,7 +1418,27 @@ fn process_token_cmd(mut ctx: Context, cmd: TokenCmd) { AccountMeta::new(sender_associated_token_account, false), ]); } - TokenType::Collateral => { + TokenType::SyntheticMemo => { + // 5. [executable] The spl_token_2022 program. + // 6. [writeable] The mint / mint authority PDA account. + // 7. [writeable] The token sender's associated token account, from which tokens will be burned. + let (mint_account, _mint_bump) = Pubkey::find_program_address( + hyperlane_token_mint_pda_seeds_memo!(), + &xfer.program_id, + ); + let sender_associated_token_account = + get_associated_token_address_with_program_id_memo( + &sender.pubkey(), + &mint_account, + &spl_token_2022::id(), + ); + accounts.extend([ + AccountMeta::new_readonly(spl_token_2022::id(), false), + AccountMeta::new(mint_account, false), + AccountMeta::new(sender_associated_token_account, false), + ]); + } + TokenType::Collateral | TokenType::CollateralMemo => { // 5. [executable] The SPL token program for the mint. // 6. [writeable] The mint. // 7. [writeable] The token sender's associated token account, from which tokens will be sent. @@ -1215,6 +1450,7 @@ fn process_token_cmd(mut ctx: Context, cmd: TokenCmd) { .into_inner(); let sender_associated_token_account = get_associated_token_address_with_program_id( + // DYMENSION: BUG? ASSUMES SYNTHETIC USED ON OTHER CHAIN &sender.pubkey(), &token.plugin_data.mint, &token.plugin_data.spl_token_program, @@ -1245,6 +1481,236 @@ fn process_token_cmd(mut ctx: Context, cmd: TokenCmd) { // Print the output so it can be used in e2e tests println!("{:?}", tx_result); } + TokenSubCmd::TransferRemoteMemo(xfer) => { + is_keypair(&xfer.sender).unwrap(); + ctx.commitment = CommitmentConfig::finalized(); + let sender = read_keypair_file(xfer.sender).unwrap(); + + let recipient = if xfer.recipient.starts_with("0x") { + H256::from_str(&xfer.recipient).unwrap() + } else { + let pubkey = Pubkey::from_str(&xfer.recipient).unwrap(); + H256::from_slice(&pubkey.to_bytes()[..]) + }; + + let (token_account, _token_bump) = + Pubkey::find_program_address(hyperlane_token_pda_seeds!(), &xfer.program_id); + let (dispatch_authority_account, _dispatch_authority_bump) = + Pubkey::find_program_address( + mailbox_message_dispatch_authority_pda_seeds!(), + &xfer.program_id, + ); + + let fetched_token_account = ctx + .client + .get_account_with_commitment(&token_account, ctx.commitment) + .unwrap() + .value + .unwrap(); + let token = HyperlaneTokenAccount::<()>::fetch(&mut &fetched_token_account.data[..]) + .unwrap() + .into_inner(); + + let unique_message_account_keypair = Keypair::new(); + let (dispatched_message_account, _dispatched_message_bump) = + Pubkey::find_program_address( + mailbox_dispatched_message_pda_seeds!(&unique_message_account_keypair.pubkey()), + &token.mailbox, + ); + + let (mailbox_outbox_account, _mailbox_outbox_bump) = + Pubkey::find_program_address(mailbox_outbox_pda_seeds!(), &token.mailbox); + + let ixn = DymHtInstruction::TransferRemoteMemo(DymHtTransferRemoteMemo { + base: HtTransferRemote { + destination_domain: xfer.destination_domain, + recipient, + amount_or_id: xfer.amount.into(), + }, + memo: xfer.memo.as_bytes().to_vec(), // TODO: conversion OK? + }); + + // Transfers tokens to a remote. + // Burns the tokens from the sender's associated token account and + // then dispatches a message to the remote recipient. + // + // 0. [executable] The system program. + // 1. [executable] The spl_noop program. + // 2. [] The token PDA account. + // 3. [executable] The mailbox program. + // 4. [writeable] The mailbox outbox account. + // 5. [] Message dispatch authority. + // 6. [signer] The token sender and mailbox payer. + // 7. [signer] Unique message / gas payment account. + // 8. [writeable] Message storage PDA. + // ---- If using an IGP ---- + // 9. [executable] The IGP program. + // 10. [writeable] The IGP program data. + // 11. [writeable] Gas payment PDA. + // 12. [] OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. + // 13. [writeable] The IGP account. + // ---- End if ---- + // 14..N [??..??] Plugin-specific accounts. + let mut accounts = vec![ + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(spl_noop::id(), false), + AccountMeta::new_readonly(token_account, false), + AccountMeta::new_readonly(token.mailbox, false), + AccountMeta::new(mailbox_outbox_account, false), + AccountMeta::new_readonly(dispatch_authority_account, false), + AccountMeta::new(sender.pubkey(), true), + AccountMeta::new_readonly(unique_message_account_keypair.pubkey(), true), + AccountMeta::new(dispatched_message_account, false), + ]; + + if let Some((igp_program_id, igp_account_type)) = token.interchain_gas_paymaster { + let (igp_program_data, _bump) = + Pubkey::find_program_address(igp_program_data_pda_seeds!(), &igp_program_id); + let (gas_payment_pda, _bump) = Pubkey::find_program_address( + igp_gas_payment_pda_seeds!(&unique_message_account_keypair.pubkey()), + &igp_program_id, + ); + + accounts.extend([ + AccountMeta::new_readonly(igp_program_id, false), + AccountMeta::new(igp_program_data, false), + AccountMeta::new(gas_payment_pda, false), + ]); + + match igp_account_type { + InterchainGasPaymasterType::OverheadIgp(overhead_igp_account_id) => { + let overhead_igp_account = ctx + .client + .get_account_with_commitment(&overhead_igp_account_id, ctx.commitment) + .unwrap() + .value + .unwrap(); + let overhead_igp_account = + OverheadIgpAccount::fetch(&mut &overhead_igp_account.data[..]) + .unwrap() + .into_inner(); + accounts.extend([ + AccountMeta::new_readonly(overhead_igp_account_id, false), + AccountMeta::new(overhead_igp_account.inner, false), + ]); + } + InterchainGasPaymasterType::Igp(igp_account_id) => { + accounts.push(AccountMeta::new(igp_account_id, false)); + } + } + } + + match xfer.token_type { + TokenType::Native => { + // 14. [executable] The system program. + // 15. [writeable] The native token collateral PDA account. + let (native_collateral_account, _native_collateral_bump) = + Pubkey::find_program_address( + hyperlane_token_native_collateral_pda_seeds!(), + &xfer.program_id, + ); + accounts.extend([ + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new(native_collateral_account, false), + ]); + } + TokenType::NativeMemo => { + // 14. [executable] The system program. + // 15. [writeable] The native token collateral PDA account. + let (native_collateral_account, _native_collateral_bump) = + Pubkey::find_program_address( + hyperlane_token_native_memo_collateral_pda_seeds!(), + &xfer.program_id, + ); + accounts.extend([ + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new(native_collateral_account, false), + ]); + } + TokenType::Synthetic => { + // 5. [executable] The spl_token_2022 program. + // 6. [writeable] The mint / mint authority PDA account. + // 7. [writeable] The token sender's associated token account, from which tokens will be burned. + let (mint_account, _mint_bump) = Pubkey::find_program_address( + hyperlane_token_mint_pda_seeds!(), + &xfer.program_id, + ); + let sender_associated_token_account = + get_associated_token_address_with_program_id( + &sender.pubkey(), + &mint_account, + &spl_token_2022::id(), + ); + accounts.extend([ + AccountMeta::new_readonly(spl_token_2022::id(), false), + AccountMeta::new(mint_account, false), + AccountMeta::new(sender_associated_token_account, false), + ]); + } + TokenType::SyntheticMemo => { + // 5. [executable] The spl_token_2022 program. + // 6. [writeable] The mint / mint authority PDA account. + // 7. [writeable] The token sender's associated token account, from which tokens will be burned. + let (mint_account, _mint_bump) = Pubkey::find_program_address( + hyperlane_token_mint_pda_seeds_memo!(), + &xfer.program_id, + ); + let sender_associated_token_account = + get_associated_token_address_with_program_id_memo( + &sender.pubkey(), + &mint_account, + &spl_token_2022::id(), + ); + accounts.extend([ + AccountMeta::new_readonly(spl_token_2022::id(), false), + AccountMeta::new(mint_account, false), + AccountMeta::new(sender_associated_token_account, false), + ]); + } + TokenType::Collateral | TokenType::CollateralMemo => { + // 5. [executable] The SPL token program for the mint. + // 6. [writeable] The mint. + // 7. [writeable] The token sender's associated token account, from which tokens will be sent. + // 8. [writeable] The escrow PDA account. + let token = HyperlaneTokenAccount::::fetch( + &mut &fetched_token_account.data[..], + ) + .unwrap() + .into_inner(); + let sender_associated_token_account = + get_associated_token_address_with_program_id( + // DYMENSION: BUG? ASSUMES SYNTHETIC USED ON OTHER CHAIN + &sender.pubkey(), + &token.plugin_data.mint, + &token.plugin_data.spl_token_program, + ); + accounts.extend([ + AccountMeta::new_readonly(token.plugin_data.spl_token_program, false), + AccountMeta::new(token.plugin_data.mint, false), + AccountMeta::new(sender_associated_token_account, false), + AccountMeta::new(token.plugin_data.escrow, false), + ]); + } + } + + eprintln!("accounts={:#?}", accounts); // FIXME remove + let xfer_instruction = Instruction { + program_id: xfer.program_id, + data: ixn.encode().unwrap(), + accounts, + }; + let tx_result = ctx.new_txn().add(xfer_instruction).send( + &[ + ctx.payer_signer().as_deref(), + Some(&sender), + Some(&unique_message_account_keypair), + ], + &ctx.payer_pubkey, + ); + // Print the output so it can be used in e2e tests + println!("{:?}", tx_result); + } + TokenSubCmd::EnrollRemoteRouter(enroll) => { let instruction = enroll_remote_routers_instruction( enroll.program_id, diff --git a/rust/sealevel/client/src/router.rs b/rust/sealevel/client/src/router.rs index 9283f6e0802..08c10eeb5ba 100644 --- a/rust/sealevel/client/src/router.rs +++ b/rust/sealevel/client/src/router.rs @@ -1,4 +1,4 @@ -use hyperlane_core::{utils::hex_or_base58_to_h256, H256}; +use hyperlane_core::{utils::hex_or_base58_or_bech32_to_h256, H256}; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -319,7 +319,7 @@ pub(crate) fn deploy_routers< let chain_metadata = chain_metadatas.get(chain_name).unwrap(); ( chain_metadata.domain_id, - hex_or_base58_to_h256(foreign_deployment).unwrap(), + hex_or_base58_or_bech32_to_h256(foreign_deployment).unwrap(), ) }) }) diff --git a/rust/sealevel/client/src/squads.rs b/rust/sealevel/client/src/squads.rs index 4c12e3d6922..87e181ee42d 100644 --- a/rust/sealevel/client/src/squads.rs +++ b/rust/sealevel/client/src/squads.rs @@ -12,7 +12,9 @@ use solana_sdk::{ pubkey::Pubkey, }; -use crate::{read_core_program_ids, registry::FileSystemRegistry, Context, EnvironmentArgs}; +use crate::{ + read_flexible_core_program_ids, registry::FileSystemRegistry, Context, EnvironmentArgs, +}; const COMPUTE_BUDGET_PROGRAM_ID: Pubkey = pubkey!("ComputeBudget111111111111111111111111111111"); const BPF_LOADER_UPGRADEABLE_PROGRAM_ID: Pubkey = @@ -111,24 +113,30 @@ pub fn process_squads_cmd(ctx: Context, cmd: SquadsCmd) { let client = chain_metadata.client(); // Read existing core program IDs - let core_program_ids = read_core_program_ids( + let core_program_ids = read_flexible_core_program_ids( &verify.env_args.environments_dir, &verify.env_args.environment, &verify.chain, ); - let core_programs = vec![ + let mut core_programs = vec![ ProgramIdWithMetadata::new("Mailbox".into(), core_program_ids.mailbox), - ProgramIdWithMetadata::new( - "Validator Announce".into(), - core_program_ids.validator_announce, - ), - ProgramIdWithMetadata::new( - "Multisig ISM Message ID".into(), - core_program_ids.multisig_ism_message_id, - ), ProgramIdWithMetadata::new("IGP program".into(), core_program_ids.igp_program_id), ]; + if let Some(validator_announce) = core_program_ids.validator_announce { + core_programs.push(ProgramIdWithMetadata::new( + "Validator Announce".into(), + validator_announce, + )); + } + + if let Some(multisig_ism) = core_program_ids.multisig_ism_message_id { + core_programs.push(ProgramIdWithMetadata::new( + "Multisig ISM Message ID".into(), + multisig_ism, + )); + } + // Chain -> (Label, Owner) let chain_owner_lookups: HashMap> = CHAIN_CORE_OWNERS .iter() diff --git a/rust/sealevel/client/src/test_ism.rs b/rust/sealevel/client/src/test_ism.rs new file mode 100644 index 00000000000..3aac8024a6f --- /dev/null +++ b/rust/sealevel/client/src/test_ism.rs @@ -0,0 +1,227 @@ +//! Test ISM (Interchain Security Module) implementation for Solana +//! +//! This module provides a simple ISM implementation designed for testing purposes. +//! The Test ISM can be configured to either accept or reject all messages, +//! making it useful for integration testing without requiring actual validation logic. +//! +//! WARNING: This ISM is for testing only and should NEVER be deployed to production +//! as it has no access control and can be configured by anyone to accept all messages. + +use std::path::Path; + +use borsh::BorshSerialize; +use solana_program::pubkey::Pubkey; + +use crate::{ + artifacts::{write_json, SingularProgramIdArtifact}, + cmd_utils::{create_new_directory, deploy_program}, + registry::FileSystemRegistry, + Context, TestIsmCmd, TestIsmSubCmd, +}; + +/// Creates and sends the Test ISM initialization instruction. +/// This sets up the storage PDA for the Test ISM program. +fn initialize_test_ism(ctx: &mut Context, program_id: Pubkey, description: String) { + let init_instruction = hyperlane_sealevel_test_ism::program::TestIsmInstruction::Init; + let encoded = init_instruction + .try_to_vec() + .expect("Failed to serialize init instruction"); + + let (storage_pda_key, _) = + Pubkey::find_program_address(&[b"test_ism", b"-", b"storage"], &program_id); + + let instruction = solana_program::instruction::Instruction { + program_id, + accounts: vec![ + solana_program::instruction::AccountMeta::new_readonly( + solana_program::system_program::id(), + false, + ), + solana_program::instruction::AccountMeta::new(ctx.payer_pubkey, true), + solana_program::instruction::AccountMeta::new(storage_pda_key, false), + ], + data: encoded, + }; + + ctx.new_txn() + .add_with_description(instruction, description) + .send_with_payer(); +} + +/// Processes Test ISM commands including deploy, init, set accept/reject, and query operations +pub(crate) fn process_test_ism_cmd(mut ctx: Context, cmd: TestIsmCmd) { + match cmd.cmd { + TestIsmSubCmd::Deploy(deploy) => { + let environments_dir = create_new_directory( + &deploy.env_args.environments_dir, + &deploy.env_args.environment, + ); + let ism_dir = create_new_directory(&environments_dir, "test-ism"); + let chain_dir = create_new_directory(&ism_dir, &deploy.chain); + let context_dir = create_new_directory(&chain_dir, &deploy.context); + let key_dir = create_new_directory(&context_dir, "keys"); + + // Load chain metadata to get the proper domain ID + let registry = FileSystemRegistry::new(environments_dir.clone()); + let chain_metadata_map = registry.get_metadata(); + let chain_metadata = chain_metadata_map + .get(&deploy.chain) + .unwrap_or_else(|| panic!("Chain {} not found in registry", deploy.chain)); + + let ism_program_id = deploy_test_ism( + &mut ctx, + &deploy.built_so_dir, + &key_dir, + chain_metadata.domain_id, + ); + + write_json::( + &context_dir.join("program-ids.json"), + ism_program_id.into(), + ); + } + TestIsmSubCmd::Init(init) => { + initialize_test_ism(&mut ctx, init.program_id, "Initialize Test ISM".to_string()); + } + TestIsmSubCmd::SetAccept(set_accept) => { + let instruction_data = + hyperlane_sealevel_test_ism::program::TestIsmInstruction::SetAccept( + set_accept.accept, + ); + let encoded = instruction_data + .try_to_vec() + .expect("Failed to serialize set accept instruction"); + + let (storage_pda_key, _) = Pubkey::find_program_address( + &[b"test_ism", b"-", b"storage"], + &set_accept.program_id, + ); + + let instruction = solana_program::instruction::Instruction { + program_id: set_accept.program_id, + accounts: vec![solana_program::instruction::AccountMeta::new( + storage_pda_key, + false, + )], + data: encoded, + }; + + let description = format!( + "Set Test ISM accept to {}", + if set_accept.accept { + "true (accept all)" + } else { + "false (reject all)" + } + ); + + ctx.new_txn() + .add_with_description(instruction, description) + .send_with_payer(); + } + TestIsmSubCmd::Query(query) => { + let (storage_pda_key, _) = + Pubkey::find_program_address(&[b"test_ism", b"-", b"storage"], &query.program_id); + + let accounts = ctx + .client + .get_multiple_accounts_with_commitment(&[storage_pda_key], ctx.commitment) + .unwrap() + .value; + + if let Some(account) = &accounts[0] { + use borsh::BorshDeserialize; + + // Try to deserialize the storage, handling both formats (with and without discriminator) + let storage = if account.data.len() >= 8 { + // Try with 8-byte discriminator first (new format) + match hyperlane_sealevel_test_ism::program::TestIsmStorage::deserialize( + &mut &account.data[8..], + ) { + Ok(s) => s, + Err(_) => { + // Fall back to no discriminator (old format) + match hyperlane_sealevel_test_ism::program::TestIsmStorage::deserialize( + &mut &account.data[..], + ) { + Ok(s) => s, + Err(e) => { + println!("Error deserializing Test ISM storage: {}", e); + return; + } + } + } + } + } else { + // Small data, try without discriminator + match hyperlane_sealevel_test_ism::program::TestIsmStorage::deserialize( + &mut &account.data[..], + ) { + Ok(s) => s, + Err(e) => { + println!("Error deserializing Test ISM storage: {}", e); + println!("Account data (hex): {:02x?}", account.data); + return; + } + } + }; + + println!("Test ISM Storage:"); + println!( + " Accept: {} ({})", + storage.accept, + if storage.accept { + "accepts all messages" + } else { + "rejects all messages" + } + ); + } else { + println!("Test ISM not initialized"); + } + } + } +} + +/// Deploys and initializes a Test ISM program on Solana. +/// +/// # Arguments +/// * `ctx` - Context containing client and payer information +/// * `built_so_dir` - Directory containing the compiled .so file +/// * `key_dir` - Directory to store program keypairs +/// * `local_domain` - The Hyperlane domain ID for this chain +/// +/// # Returns +/// The deployed program's public key +pub(crate) fn deploy_test_ism( + ctx: &mut Context, + built_so_dir: &Path, + key_dir: &Path, + local_domain: u32, +) -> Pubkey { + let program_id = deploy_program( + ctx.payer_keypair_path(), + key_dir, + "hyperlane_sealevel_test_ism", + built_so_dir + .join("hyperlane_sealevel_test_ism.so") + .to_str() + .unwrap(), + &ctx.client.url(), + local_domain, + ) + .unwrap(); + + println!("Deployed Test ISM at program ID {}", program_id); + + // Initialize the Test ISM + initialize_test_ism( + ctx, + program_id, + format!("Initializing Test ISM with payer {}", ctx.payer_pubkey), + ); + + println!("Initialized Test ISM at program ID {}", program_id); + + program_id +} diff --git a/rust/sealevel/client/src/warp_route.rs b/rust/sealevel/client/src/warp_route.rs index a6efbd15559..22fd83bc9f6 100644 --- a/rust/sealevel/client/src/warp_route.rs +++ b/rust/sealevel/client/src/warp_route.rs @@ -31,6 +31,10 @@ use hyperlane_sealevel_token_lib::{ set_interchain_security_module_instruction, transfer_ownership_instruction, Init, }, }; +use hyperlane_sealevel_token_memo::{ + hyperlane_token_ata_payer_pda_seeds as hyperlane_token_ata_payer_pda_seeds_memo, + hyperlane_token_mint_pda_seeds as hyperlane_token_mint_pda_seeds_memo, +}; use crate::{ cmd_utils::account_exists, @@ -42,6 +46,69 @@ use crate::{ Context, TokenType as FlatTokenType, WarpRouteCmd, WarpRouteSubCmd, }; +const MAX_LOCAL_DECIMALS: u8 = 9; + +/// Creates a metadata pointer initialization instruction manually +/// This is needed because spl-token-2022 v0.5.0 doesn't have the metadata_pointer extension +/// (it was added in later versions, but the workspace is locked to Solana 1.14.13 which is incompatible with newer versions) +fn create_metadata_pointer_initialize_instruction( + token_program_id: &Pubkey, + mint: &Pubkey, + authority: Option, + metadata_address: Option, +) -> Instruction { + // MetadataPointerExtension instruction discriminator + // From spl-token-2022 v4.x: TokenInstruction::MetadataPointerExtension + let token_instruction_discriminator: u8 = 39; // MetadataPointerExtension + let metadata_pointer_instruction_discriminator: u8 = 0; // Initialize + + // Format: [token_instruction(1), metadata_pointer_instruction(1), InitializeInstructionData(64)] + // InitializeInstructionData = authority(32 bytes) + metadata_address(32 bytes) + // OptionalNonZeroPubkey is just a Pubkey (32 bytes) - None is represented as Pubkey::default() (all zeros) + let mut data = vec![ + token_instruction_discriminator, + metadata_pointer_instruction_discriminator, + ]; + + // Serialize authority as OptionalNonZeroPubkey (32 bytes - Pubkey::default() if None) + let authority_bytes = authority.unwrap_or_default(); + data.extend_from_slice(authority_bytes.as_ref()); + + // Serialize metadata_address as OptionalNonZeroPubkey (32 bytes - Pubkey::default() if None) + let metadata_address_bytes = metadata_address.unwrap_or_default(); + data.extend_from_slice(metadata_address_bytes.as_ref()); + + println!( + " - Metadata pointer instruction data_len={} bytes (manual implementation for Solana 1.14.13 compatibility)", + data.len() + ); + println!( + " - Authority: {} (is_some={})", + authority_bytes, + authority.is_some() + ); + println!( + " - Metadata address: {} (is_some={})", + metadata_address_bytes, + metadata_address.is_some() + ); + + Instruction { + program_id: *token_program_id, + accounts: vec![solana_sdk::instruction::AccountMeta::new(*mint, false)], + data, + } +} + +pub(crate) fn assert_decimals_max(decimals: u8) { + assert!( + decimals <= MAX_LOCAL_DECIMALS, + "Invalid decimals: {}. Decimals must be <= {}. Use remoteDecimals instead.", + decimals, + MAX_LOCAL_DECIMALS + ); +} + #[derive(Debug, Deserialize, Serialize, Clone)] struct SplTokenOffchainMetadata { name: String, @@ -100,8 +167,11 @@ impl DecimalMetadata { #[serde(tag = "type", rename_all = "camelCase")] enum TokenType { Native, + NativeMemo, Synthetic(TokenMetadata), + SyntheticMemo(TokenMetadata), Collateral(CollateralInfo), + CollateralMemo(CollateralInfo), } impl TokenType { @@ -112,8 +182,11 @@ impl TokenType { // enforce gas amounts to Sealevel chains. match &self { TokenType::Synthetic(_) => 64_000, + TokenType::SyntheticMemo(_) => 64_000, TokenType::Native => 44_000, + TokenType::NativeMemo => 44_000, TokenType::Collateral(_) => 68_000, + TokenType::CollateralMemo(_) => 68_000, } } } @@ -188,8 +261,11 @@ impl RouterDeployer for WarpRouteDeployer { fn program_name(&self, config: &TokenConfig) -> &str { match config.token_type { TokenType::Native => "hyperlane_sealevel_token_native", + TokenType::NativeMemo => "hyperlane_sealevel_token_native_memo", TokenType::Synthetic(_) => "hyperlane_sealevel_token", + TokenType::SyntheticMemo(_) => "hyperlane_sealevel_token_memo", TokenType::Collateral(_) => "hyperlane_sealevel_token_collateral", + TokenType::CollateralMemo(_) => "hyperlane_sealevel_token_collateral_memo", } } @@ -217,26 +293,106 @@ impl RouterDeployer for WarpRouteDeployer { app_config: &TokenConfig, program_id: Pubkey, ) { + // Enforce decimals limit + assert_decimals_max(app_config.decimal_metadata.decimals); let try_fund_ata_payer = |ctx: &mut Context, client: &RpcClient| { if let Some(ata_payer_funding_amount) = self.ata_payer_funding_amount { if matches!( app_config.token_type, - TokenType::Collateral(_) | TokenType::Synthetic(_) + TokenType::Collateral(_) + | TokenType::CollateralMemo(_) + | TokenType::Synthetic(_) + | TokenType::SyntheticMemo(_) ) { - fund_ata_payer_up_to(ctx, client, program_id, ata_payer_funding_amount); + fund_ata_payer_up_to( + ctx, + client, + program_id, + &app_config.token_type, + ata_payer_funding_amount, + ); } } }; let (token_pda, _token_bump) = Pubkey::find_program_address(hyperlane_token_pda_seeds!(), &program_id); - if account_exists(client, &token_pda).unwrap() { - println!("Warp route token already exists, skipping init"); - // Fund the ATA payer up to the specified amount. - try_fund_ata_payer(ctx, client); + // Check if token PDA exists and is initialized + if account_exists(client, &token_pda).unwrap() { + println!("Token PDA exists at: {}", token_pda); + + // For Synthetic/SyntheticMemo, also check if mint is initialized + if matches!( + app_config.token_type, + TokenType::Synthetic(_) | TokenType::SyntheticMemo(_) + ) { + let mint_account = match &app_config.token_type { + TokenType::Synthetic(_) => { + let (mint, _) = Pubkey::find_program_address( + hyperlane_token_mint_pda_seeds!(), + &program_id, + ); + mint + } + TokenType::SyntheticMemo(_) => { + let (mint, _) = Pubkey::find_program_address( + hyperlane_token_mint_pda_seeds_memo!(), + &program_id, + ); + mint + } + _ => unreachable!(), + }; - return; + // Check if mint account exists and has the right owner + match client.get_account(&mint_account) { + Ok(account) => { + if account.owner == spl_token_2022::id() && account.data.len() > 0 { + println!( + "Mint account fully initialized: {}\n\ + Owner: {}\n\ + Data length: {}\n\ + Skipping initialization.", + mint_account, + account.owner, + account.data.len() + ); + try_fund_ata_payer(ctx, client); + return; + } else { + println!( + "⚠️ WARNING: Mint account exists but is not properly initialized!\n\ + Mint: {}\n\ + Owner: {} (expected: {})\n\ + Data length: {}\n\ + This suggests a previous deployment failed.\n\ + You may need to clean up and redeploy with a new program ID.", + mint_account, + account.owner, + spl_token_2022::id(), + account.data.len() + ); + // Continue with initialization attempt + } + } + Err(_) => { + println!( + "⚠️ WARNING: Token PDA exists but mint account not found!\n\ + This suggests a previous deployment failed.\n\ + Mint account should be: {}\n\ + Attempting to continue with initialization...", + mint_account + ); + // Continue with initialization attempt + } + } + } else { + // For Native/Collateral tokens, just checking token PDA is enough + println!("Warp route token already exists, skipping init"); + try_fund_ata_payer(ctx, client); + return; + } } let domain_id = chain_metadata.domain_id; @@ -291,62 +447,147 @@ impl RouterDeployer for WarpRouteDeployer { ) .unwrap(), ), + TokenType::NativeMemo => ctx.new_txn().add( + hyperlane_sealevel_token_native_memo::instruction::init_instruction( + program_id, + ctx.payer_pubkey, + init, + ) + .unwrap(), + ), TokenType::Synthetic(_token_metadata) => { let decimals = init.decimals; - ctx.new_txn() - .add( - hyperlane_sealevel_token::instruction::init_instruction( - program_id, - ctx.payer_pubkey, - init, - ) - .unwrap(), - ) - .with_client(client) - .send_with_payer(); - let (mint_account, _mint_bump) = Pubkey::find_program_address(hyperlane_token_mint_pda_seeds!(), &program_id); - let mut cmd = Command::new(spl_token_binary_path.clone()); - cmd.args([ - "create-token", - mint_account.to_string().as_str(), - "--enable-metadata", - "-p", - spl_token_2022::id().to_string().as_str(), - "--url", - client.url().as_str(), - "--with-compute-unit-limit", - "500000", - "--mint-authority", - &ctx.payer_pubkey.to_string(), - "--fee-payer", - ctx.payer_keypair_path(), - ]); + println!( + "=== Initializing Synthetic token atomically ===\n\ + Program ID: {}\n\ + Mint Account: {}\n\ + Decimals: {}\n\ + Payer: {}", + program_id, mint_account, decimals, ctx.payer_pubkey + ); - println!("running command: {:?}", cmd); - let status = cmd - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .status() - .expect("Failed to run command"); + // Create a single atomic transaction with all three operations: + // 1. init_instruction (creates mint account + token PDA + dispatch authority) + println!("Step 1: Creating init_instruction"); + let init_ix = hyperlane_sealevel_token::instruction::init_instruction( + program_id, + ctx.payer_pubkey, + init, + ) + .unwrap(); + println!(" - Accounts: {}", init_ix.accounts.len()); + + // 2. metadata pointer initialization (must happen before mint initialization) + println!("Step 2: Creating metadata_pointer initialize instruction"); + let metadata_pointer_ix = create_metadata_pointer_initialize_instruction( + &spl_token_2022::id(), + &mint_account, + Some(ctx.payer_pubkey), + Some(mint_account), + ); + println!( + " - Metadata pointer: authority={}, metadata_address={}", + ctx.payer_pubkey, mint_account + ); - println!("initialized metadata pointer. Status: {status}"); + // 3. initialize_mint2 (initializes the mint) + println!("Step 3: Creating initialize_mint2 instruction"); + let init_mint_ix = spl_token_2022::instruction::initialize_mint2( + &spl_token_2022::id(), + &mint_account, + &ctx.payer_pubkey, + None, + decimals, + ) + .unwrap(); + println!(" - Mint authority: {}", ctx.payer_pubkey); + + ctx.new_txn() + .add(init_ix) + .add(metadata_pointer_ix) + .add(init_mint_ix) + } + TokenType::SyntheticMemo(_token_metadata) => { + let decimals = init.decimals; + + let (mint_account, _mint_bump) = Pubkey::find_program_address( + hyperlane_token_mint_pda_seeds_memo!(), + &program_id, + ); + + println!( + "=== Initializing SyntheticMemo token atomically ===\n\ + Program ID: {}\n\ + Mint Account: {}\n\ + Decimals: {}\n\ + Payer: {}", + program_id, mint_account, decimals, ctx.payer_pubkey + ); + + // Create a single atomic transaction with all three operations: + // 1. init_instruction (creates mint account + token PDA + dispatch authority) + println!("Step 1: Creating init_instruction"); + let init_ix = hyperlane_sealevel_token_memo::instruction::init_instruction( + program_id, + ctx.payer_pubkey, + init, + ) + .unwrap(); + println!(" - Accounts: {}", init_ix.accounts.len()); + + // 2. metadata pointer initialization (must happen before mint initialization) + println!("Step 2: Creating metadata_pointer initialize instruction"); + let metadata_pointer_ix = create_metadata_pointer_initialize_instruction( + &spl_token_2022::id(), + &mint_account, + Some(ctx.payer_pubkey), + Some(mint_account), + ); + println!( + " - Metadata pointer: authority={}, metadata_address={}", + ctx.payer_pubkey, mint_account + ); + + // 3. initialize_mint2 (initializes the mint) + println!("Step 3: Creating initialize_mint2 instruction"); + let init_mint_ix = spl_token_2022::instruction::initialize_mint2( + &spl_token_2022::id(), + &mint_account, + &ctx.payer_pubkey, + None, + decimals, + ) + .unwrap(); + println!(" - Mint authority: {}", ctx.payer_pubkey); + + ctx.new_txn() + .add(init_ix) + .add(metadata_pointer_ix) + .add(init_mint_ix) + } + TokenType::Collateral(collateral_info) => { + let collateral_mint = collateral_info.mint.parse().expect("Invalid mint address"); + let collateral_mint_account = client.get_account(&collateral_mint).unwrap(); + // The owner of the mint account is the SPL Token program responsible for it + // (either spl-token or spl-token-2022). + let collateral_spl_token_program = collateral_mint_account.owner; ctx.new_txn().add( - spl_token_2022::instruction::initialize_mint2( - &spl_token_2022::id(), - &mint_account, - &ctx.payer_pubkey, - None, - decimals, + hyperlane_sealevel_token_collateral::instruction::init_instruction( + program_id, + ctx.payer_pubkey, + init, + collateral_spl_token_program, + collateral_mint, ) .unwrap(), ) } - TokenType::Collateral(collateral_info) => { + TokenType::CollateralMemo(collateral_info) => { let collateral_mint = collateral_info.mint.parse().expect("Invalid mint address"); let collateral_mint_account = client.get_account(&collateral_mint).unwrap(); // The owner of the mint account is the SPL Token program responsible for it @@ -354,7 +595,7 @@ impl RouterDeployer for WarpRouteDeployer { let collateral_spl_token_program = collateral_mint_account.owner; ctx.new_txn().add( - hyperlane_sealevel_token_collateral::instruction::init_instruction( + hyperlane_sealevel_token_collateral_memo::instruction::init_instruction( program_id, ctx.payer_pubkey, init, @@ -368,9 +609,32 @@ impl RouterDeployer for WarpRouteDeployer { .with_client(client) .send_with_payer(); - if let TokenType::Synthetic(token_metadata) = &app_config.token_type { - let (mint_account, _mint_bump) = - Pubkey::find_program_address(hyperlane_token_mint_pda_seeds!(), &program_id); + if matches!( + &app_config.token_type, + TokenType::Synthetic(_) | TokenType::SyntheticMemo(_) + ) { + let token_metadata = match &app_config.token_type { + TokenType::Synthetic(metadata) | TokenType::SyntheticMemo(metadata) => metadata, + _ => unreachable!(), + }; + let (mint_account, _mint_bump) = match &app_config.token_type { + TokenType::Synthetic(_) => { + Pubkey::find_program_address(hyperlane_token_mint_pda_seeds!(), &program_id) + } + TokenType::SyntheticMemo(_) => Pubkey::find_program_address( + hyperlane_token_mint_pda_seeds_memo!(), + &program_id, + ), + _ => unreachable!(), + }; + + println!( + "Initializing metadata for mint: {} with name: {}, symbol: {}, uri: {}", + mint_account, + token_metadata.name, + token_metadata.symbol, + token_metadata.uri.as_deref().unwrap_or("") + ); let mut cmd = Command::new(spl_token_binary_path.clone()); cmd.args([ @@ -446,7 +710,10 @@ impl RouterDeployer for WarpRouteDeployer { ) { // We only have validations for SVM tokens at the moment. for (chain, config) in app_configs_to_deploy.iter() { - if let TokenType::Synthetic(synthetic) = &config.token_type { + assert_decimals_max(config.decimal_metadata.decimals); + if let TokenType::Synthetic(synthetic) | TokenType::SyntheticMemo(synthetic) = + &config.token_type + { // Verify that the metadata URI provided points to a valid JSON file. let metadata_uri = match synthetic.uri.as_ref() { Some(uri) => uri, @@ -676,12 +943,23 @@ fn fund_ata_payer_up_to( ctx: &mut Context, client: &RpcClient, program_id: Pubkey, + token_type: &TokenType, ata_payer_funding_amount: u64, ) { - let (ata_payer_account, _ata_payer_bump) = Pubkey::find_program_address( - hyperlane_sealevel_token::hyperlane_token_ata_payer_pda_seeds!(), - &program_id, - ); + let (ata_payer_account, _ata_payer_bump) = match token_type { + TokenType::Synthetic(_) | TokenType::Collateral(_) => Pubkey::find_program_address( + hyperlane_sealevel_token::hyperlane_token_ata_payer_pda_seeds!(), + &program_id, + ), + TokenType::SyntheticMemo(_) => { + Pubkey::find_program_address(hyperlane_token_ata_payer_pda_seeds_memo!(), &program_id) + } + TokenType::CollateralMemo(_) => Pubkey::find_program_address( + hyperlane_sealevel_token_collateral_memo::hyperlane_token_ata_payer_pda_seeds!(), + &program_id, + ), + _ => unreachable!("Native tokens don't have ATA payers"), + }; let current_balance = client.get_balance(&ata_payer_account).unwrap(); @@ -717,15 +995,15 @@ pub fn parse_token_account_data(token_type: FlatTokenType, data: &mut &[u8]) { } match token_type { - FlatTokenType::Native => { + FlatTokenType::Native | FlatTokenType::NativeMemo => { let res = HyperlaneTokenAccount::::fetch(data); print_data_or_err(res); } - FlatTokenType::Synthetic => { + FlatTokenType::Synthetic | FlatTokenType::SyntheticMemo => { let res = HyperlaneTokenAccount::::fetch(data); print_data_or_err(res); } - FlatTokenType::Collateral => { + FlatTokenType::Collateral | FlatTokenType::CollateralMemo => { let res = HyperlaneTokenAccount::::fetch(data); print_data_or_err(res); } diff --git a/rust/sealevel/environments/mainnet3/gas-oracle-configs.json b/rust/sealevel/environments/mainnet3/gas-oracle-configs.json index 7480f9a369f..1214cb8fcaf 100644 --- a/rust/sealevel/environments/mainnet3/gas-oracle-configs.json +++ b/rust/sealevel/environments/mainnet3/gas-oracle-configs.json @@ -2,111 +2,135 @@ "solanamainnet": { "abstract": { "oracleConfig": { - "tokenExchangeRate": "251984749943933617403", - "gasPrice": "154604207", + "tokenExchangeRate": "299671814671814671814", + "gasPrice": "79933569", "tokenDecimals": 18 }, "overhead": 333774 }, "apechain": { "oracleConfig": { - "tokenExchangeRate": "63400650370038125", - "gasPrice": "1087286206132", + "tokenExchangeRate": "42397131825703254", + "gasPrice": "999725217617", "tokenDecimals": 18 }, "overhead": 166887 }, "arbitrum": { "oracleConfig": { - "tokenExchangeRate": "251984749943933617403", - "gasPrice": "683917002", + "tokenExchangeRate": "299671814671814671814", + "gasPrice": "141439668", "tokenDecimals": 18 }, "overhead": 166887 }, "artela": { "oracleConfig": { - "tokenExchangeRate": "132904238618524", - "gasPrice": "518679113049071", + "tokenExchangeRate": "12740623276337", + "gasPrice": "3326798141775078", "tokenDecimals": 18 }, "overhead": 166887 }, "avalanche": { "oracleConfig": { - "tokenExchangeRate": "1892801076474545862", - "gasPrice": "36419385780", + "tokenExchangeRate": "1796745725317153888", + "gasPrice": "23590139241", "tokenDecimals": 18 }, "overhead": 166887 }, "base": { "oracleConfig": { - "tokenExchangeRate": "251984749943933617403", - "gasPrice": "273566764", + "tokenExchangeRate": "299671814671814671814", + "gasPrice": "141439668", "tokenDecimals": 18 }, "overhead": 166887 }, "bsc": { "oracleConfig": { - "tokenExchangeRate": "69372056514913657770", - "gasPrice": "993694812", + "tokenExchangeRate": "60648097076668505240", + "gasPrice": "698875709", "tokenDecimals": 18 }, "overhead": 166887 }, + "celestia": { + "oracleConfig": { + "tokenExchangeRate": "12824048538334", + "gasPrice": "2000", + "tokenDecimals": 6 + }, + "overhead": 600000 + }, "eclipsemainnet": { "oracleConfig": { - "tokenExchangeRate": "25198474994393361", - "gasPrice": "2283", + "tokenExchangeRate": "29967181467181467", + "gasPrice": "1180", "tokenDecimals": 9 }, "overhead": 600000 }, + "electroneum": { + "oracleConfig": { + "tokenExchangeRate": "230784611141754", + "gasPrice": "183658180807992", + "tokenDecimals": 18 + }, + "overhead": 166887 + }, "ethereum": { "oracleConfig": { - "tokenExchangeRate": "251984749943933617403", - "gasPrice": "4000000000", + "tokenExchangeRate": "299671814671814671814", + "gasPrice": "2500000000", "tokenDecimals": 18 }, "overhead": 166887 }, "everclear": { "oracleConfig": { - "tokenExchangeRate": "251984749943933617403", - "gasPrice": "273566764", + "tokenExchangeRate": "299671814671814671814", + "gasPrice": "141439668", "tokenDecimals": 18 }, "overhead": 166887 }, "flowmainnet": { "oracleConfig": { - "tokenExchangeRate": "34437429917021753", - "gasPrice": "2001736272807", + "tokenExchangeRate": "28288610038610038", + "gasPrice": "1498323239740", "tokenDecimals": 18 }, "overhead": 166887 }, "form": { "oracleConfig": { - "tokenExchangeRate": "251984749943933617403", - "gasPrice": "273566764", + "tokenExchangeRate": "299671814671814671814", + "gasPrice": "141439668", + "tokenDecimals": 18 + }, + "overhead": 166887 + }, + "galactica": { + "oracleConfig": { + "tokenExchangeRate": "66050198150594451", + "gasPrice": "614759236838", "tokenDecimals": 18 }, "overhead": 166887 }, "hyperevm": { "oracleConfig": { - "tokenExchangeRate": "3932496075353218210", - "gasPrice": "17529490504", + "tokenExchangeRate": "3696221731936017650", + "gasPrice": "11467245451", "tokenDecimals": 18 }, "overhead": 166887 }, "infinityvmmainnet": { "oracleConfig": { - "tokenExchangeRate": "112132765193989683", + "tokenExchangeRate": "68946497517926089", "gasPrice": "0", "tokenDecimals": 18 }, @@ -114,87 +138,103 @@ }, "ink": { "oracleConfig": { - "tokenExchangeRate": "251984749943933617403", - "gasPrice": "273566764", + "tokenExchangeRate": "299671814671814671814", + "gasPrice": "141439668", "tokenDecimals": 18 }, "overhead": 166887 }, "mint": { "oracleConfig": { - "tokenExchangeRate": "251984749943933617403", - "gasPrice": "273566764", + "tokenExchangeRate": "299671814671814671814", + "gasPrice": "141439668", + "tokenDecimals": 18 + }, + "overhead": 166887 + }, + "mode": { + "oracleConfig": { + "tokenExchangeRate": "299671814671814671814", + "gasPrice": "141439668", "tokenDecimals": 18 }, "overhead": 166887 }, "optimism": { "oracleConfig": { - "tokenExchangeRate": "251984749943933617403", - "gasPrice": "273566764", + "tokenExchangeRate": "299671814671814671814", + "gasPrice": "141439668", "tokenDecimals": 18 }, "overhead": 166887 }, "paradex": { "oracleConfig": { - "tokenExchangeRate": "112132765193989683", - "gasPrice": "987653967", + "tokenExchangeRate": "68946497517926089", + "gasPrice": "987653641", "tokenDecimals": 18 }, "overhead": 130000000 }, - "rivalz": { + "polygon": { "oracleConfig": { - "tokenExchangeRate": "251984749943933617403", - "gasPrice": "273566764", + "tokenExchangeRate": "19000896304467733", + "gasPrice": "2230709602411", + "tokenDecimals": 18 + }, + "overhead": 166887 + }, + "pulsechain": { + "oracleConfig": { + "tokenExchangeRate": "2837837837837", + "gasPrice": "14935836458181995", "tokenDecimals": 18 }, "overhead": 166887 }, "solaxy": { "oracleConfig": { - "tokenExchangeRate": "11213276519398", - "gasPrice": "5128204", - "tokenDecimals": 9 + "tokenExchangeRate": "2161541643", + "gasPrice": "16357386", + "tokenDecimals": 6 }, "overhead": 600000 }, "sonicsvm": { "oracleConfig": { "tokenExchangeRate": "1500000000000000", - "gasPrice": "38336", + "gasPrice": "23572", "tokenDecimals": 9 }, "overhead": 600000 }, "soon": { "oracleConfig": { - "tokenExchangeRate": "25198474994393361", - "gasPrice": "2283", + "tokenExchangeRate": "29967181467181467", + "gasPrice": "1180", "tokenDecimals": 9 }, "overhead": 600000 }, "sophon": { "oracleConfig": { - "tokenExchangeRate": "3969376541825521", - "gasPrice": "9814614939295", + "tokenExchangeRate": "2206603695532266", + "gasPrice": "10855523111481", "tokenDecimals": 18 }, "overhead": 333774 }, "starknet": { "oracleConfig": { - "tokenExchangeRate": "251984749943933617403", - "gasPrice": "439505", + "tokenExchangeRate": "299671814671814671814", + "gasPrice": "227233", "tokenDecimals": 18 }, "overhead": 130000000 }, "subtensor": { "oracleConfig": { - "tokenExchangeRate": "34505494505494505494", + "tokenExchangeRate": "23685879757308328736", "gasPrice": "10000000000", "tokenDecimals": 18 }, @@ -202,34 +242,50 @@ }, "superseed": { "oracleConfig": { - "tokenExchangeRate": "251984749943933617403", - "gasPrice": "273566764", + "tokenExchangeRate": "299671814671814671814", + "gasPrice": "141439668", "tokenDecimals": 18 }, "overhead": 166887 }, "svmbnb": { "oracleConfig": { - "tokenExchangeRate": "6937205651491365", - "gasPrice": "8290", + "tokenExchangeRate": "6064809707666850", + "gasPrice": "5830", "tokenDecimals": 9 }, "overhead": 600000 }, + "unichain": { + "oracleConfig": { + "tokenExchangeRate": "299671814671814671814", + "gasPrice": "141439668", + "tokenDecimals": 18 + }, + "overhead": 166887 + }, "worldchain": { "oracleConfig": { - "tokenExchangeRate": "251984749943933617403", - "gasPrice": "273566764", + "tokenExchangeRate": "299671814671814671814", + "gasPrice": "141439668", "tokenDecimals": 18 }, "overhead": 166887 } }, "eclipsemainnet": { + "celestia": { + "oracleConfig": { + "tokenExchangeRate": "641904639198", + "gasPrice": "2000", + "tokenDecimals": 6 + }, + "overhead": 600000 + }, "ethereum": { "oracleConfig": { "tokenExchangeRate": "15000000000000000000", - "gasPrice": "4000000000", + "gasPrice": "2500000000", "tokenDecimals": 18 }, "overhead": 166460 @@ -237,23 +293,23 @@ "katana": { "oracleConfig": { "tokenExchangeRate": "15000000000000000000", - "gasPrice": "274104531", + "gasPrice": "141716715", "tokenDecimals": 18 }, "overhead": 166460 }, "solanamainnet": { "oracleConfig": { - "tokenExchangeRate": "89291117835528", - "gasPrice": "92007", + "tokenExchangeRate": "75082136185015", + "gasPrice": "56572", "tokenDecimals": 9 }, "overhead": 600000 }, "sonicsvm": { "oracleConfig": { - "tokenExchangeRate": "89291117835528", - "gasPrice": "38336", + "tokenExchangeRate": "75082136185015", + "gasPrice": "23572", "tokenDecimals": 9 }, "overhead": 600000 @@ -261,15 +317,15 @@ "soon": { "oracleConfig": { "tokenExchangeRate": "1500000000000000", - "gasPrice": "2283", + "gasPrice": "1180", "tokenDecimals": 9 }, "overhead": 600000 }, "stride": { "oracleConfig": { - "tokenExchangeRate": "189347632609", - "gasPrice": "7232", + "tokenExchangeRate": "20948523389", + "gasPrice": "33793", "tokenDecimals": 6 }, "overhead": 600000 @@ -278,8 +334,8 @@ "soon": { "bsc": { "oracleConfig": { - "tokenExchangeRate": "4129538981844072623", - "gasPrice": "1027568011", + "tokenExchangeRate": "3035725789381654871", + "gasPrice": "722694030", "tokenDecimals": 18 }, "overhead": 159736 @@ -287,23 +343,23 @@ "eclipsemainnet": { "oracleConfig": { "tokenExchangeRate": "1500000000000000", - "gasPrice": "2283", + "gasPrice": "1180", "tokenDecimals": 9 }, "overhead": 600000 }, "solanamainnet": { "oracleConfig": { - "tokenExchangeRate": "89291117835528", - "gasPrice": "92007", + "tokenExchangeRate": "75082136185015", + "gasPrice": "56572", "tokenDecimals": 9 }, "overhead": 600000 }, "svmbnb": { "oracleConfig": { - "tokenExchangeRate": "412953898184407", - "gasPrice": "8290", + "tokenExchangeRate": "303572578938165", + "gasPrice": "5830", "tokenDecimals": 9 }, "overhead": 600000 @@ -312,8 +368,8 @@ "sonicsvm": { "eclipsemainnet": { "oracleConfig": { - "tokenExchangeRate": "25198474994393361", - "gasPrice": "2283", + "tokenExchangeRate": "29967181467181467", + "gasPrice": "1180", "tokenDecimals": 9 }, "overhead": 600000 @@ -321,7 +377,7 @@ "solanamainnet": { "oracleConfig": { "tokenExchangeRate": "1500000000000000", - "gasPrice": "92007", + "gasPrice": "56572", "tokenDecimals": 9 }, "overhead": 600000 @@ -331,23 +387,23 @@ "bsc": { "oracleConfig": { "tokenExchangeRate": "15000000000000000000", - "gasPrice": "1027574348", + "gasPrice": "722702191", "tokenDecimals": 18 }, "overhead": 159736 }, "solanamainnet": { "oracleConfig": { - "tokenExchangeRate": "324338085539714", - "gasPrice": "92007", + "tokenExchangeRate": "370992678823154", + "gasPrice": "56572", "tokenDecimals": 9 }, "overhead": 600000 }, "soon": { "oracleConfig": { - "tokenExchangeRate": "5448550092134613", - "gasPrice": "2283", + "tokenExchangeRate": "7411736619526169", + "gasPrice": "1180", "tokenDecimals": 9 }, "overhead": 600000 @@ -356,16 +412,16 @@ "solaxy": { "ethereum": { "oracleConfig": { - "tokenExchangeRate": "33708000000000000000000", - "gasPrice": "4000000000", + "tokenExchangeRate": "207957002966412554623456987", + "gasPrice": "2500000000", "tokenDecimals": 18 }, - "overhead": 151966 + "overhead": 159337 }, "solanamainnet": { "oracleConfig": { - "tokenExchangeRate": "200655000000000000", - "gasPrice": "92007", + "tokenExchangeRate": "1040923734490127906605", + "gasPrice": "56572", "tokenDecimals": 9 }, "overhead": 600000 diff --git a/rust/sealevel/environments/mainnet3/multisig-ism-message-id/eclipsemainnet/hyperlane/multisig-config.json b/rust/sealevel/environments/mainnet3/multisig-ism-message-id/eclipsemainnet/hyperlane/multisig-config.json index 89d9c34c465..3c2f60d4dbc 100644 --- a/rust/sealevel/environments/mainnet3/multisig-ism-message-id/eclipsemainnet/hyperlane/multisig-config.json +++ b/rust/sealevel/environments/mainnet3/multisig-ism-message-id/eclipsemainnet/hyperlane/multisig-config.json @@ -220,6 +220,18 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, + "celestia": { + "type": "messageIdMultisigIsm", + "threshold": 4, + "validators": [ + "0x6dbc192c06907784fb0af0c0c2d8809ea50ba675", + "0x761980c3debdc8ddb69a2713cf5126d4db900f0f", + "0x885a8c1ef7f7eea8955c8f116fc1fbe1113c4a78", + "0xa6c998f0db2b56d7a63faf30a9b677c8b9b6faab", + "0x21e93a81920b73c0e98aed8e6b058dae409e4909", + "0x7b8606d61bc990165d1e5977037ddcf7f2de74d6" + ] + }, "celo": { "type": "messageIdMultisigIsm", "threshold": 4, diff --git a/rust/sealevel/environments/mainnet3/multisig-ism-message-id/solanamainnet/hyperlane/multisig-config.json b/rust/sealevel/environments/mainnet3/multisig-ism-message-id/solanamainnet/hyperlane/multisig-config.json index 6c527319676..ae69bdd90f9 100644 --- a/rust/sealevel/environments/mainnet3/multisig-ism-message-id/solanamainnet/hyperlane/multisig-config.json +++ b/rust/sealevel/environments/mainnet3/multisig-ism-message-id/solanamainnet/hyperlane/multisig-config.json @@ -50,10 +50,10 @@ "threshold": 3, "validators": [ "0x4d966438fe9e2b1e7124c87bbb90cb4f0f6c59a1", - "0xec68258a7c882ac2fc46b81ce80380054ffb4ef2", "0x5450447aee7b544c462c9352bef7cad049b0c2dc", - "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8", - "0xb3ac35d3988bca8c2ffd195b1c6bee18536b317b" + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", + "0x57ddf0cd46f31ead8084069ce481507f4305c716", + "0xde6c50c3e49852dd9fe0388166ebc1ba39ad8505" ] }, "arbitrumnova": { @@ -83,15 +83,6 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "arthera": { - "type": "messageIdMultisigIsm", - "threshold": 2, - "validators": [ - "0x13710ac11c36c169f62fba95767ae59a1e57098d", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" - ] - }, "astar": { "type": "messageIdMultisigIsm", "threshold": 2, @@ -112,11 +103,12 @@ }, "avalanche": { "type": "messageIdMultisigIsm", - "threshold": 2, + "threshold": 3, "validators": [ "0x3fb8263859843bffb02950c492d492cae169f4cf", - "0x402e0f8c6e4210d408b6ac00d197d4a099fcd25a", - "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8" + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", + "0x74de235ace64fa8a3d5e3d5e414360888e655c62", + "0x4488dbc191c39ae026b4a1fdb2aefe21960226d5" ] }, "b3": { @@ -130,14 +122,13 @@ }, "base": { "type": "messageIdMultisigIsm", - "threshold": 4, + "threshold": 3, "validators": [ "0xb9453d675e0fa3c178a17b4ce1ad5b1a279b3af9", - "0xb3ac35d3988bca8c2ffd195b1c6bee18536b317b", - "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8", - "0xcff391b4e516452d424db66beb9052b041a9ed79", "0x5450447aee7b544c462c9352bef7cad049b0c2dc", - "0x761980c3debdc8ddb69a2713cf5126d4db900f0f" + "0xb8cf45d7bab79c965843206d5f4d83bb866d6e86", + "0xe957310e17730f29862e896709cce62d24e4b773", + "0x34a14934d7c18a21440b59dfe9bf132ce601457d" ] }, "berachain": { @@ -158,9 +149,9 @@ "0x1d9b0f4ea80dbfc71cb7d64d8005eccf7c41e75f", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", - "0x6d113ae51bfea7b63a8828f97e9dce393b25c189", - "0x761980c3debdc8ddb69a2713cf5126d4db900f0f", - "0x5aed2fd5cc5f9749c455646c86b0db6126cafcbb" + "0xaa00a849fc770d742724cbd2862f91d51db7fb62", + "0x68e869315e51f6bd0ba4aac844cf216fd3dec762", + "0x0677b2daf18b71a2c4220fb17dc81cd3aa7d355b" ] }, "blast": { @@ -175,11 +166,13 @@ }, "bob": { "type": "messageIdMultisigIsm", - "threshold": 2, + "threshold": 3, "validators": [ "0x20f283be1eb0e81e22f51705dcb79883cfdd34aa", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", + "0x53d2738453c222e49c556d937bcef3f80f1c2eec", + "0xb574b2b5822a8cb9ca071e7d43865694f23b0bde" ] }, "boba": { @@ -191,12 +184,11 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "bouncebit": { + "botanix": { "type": "messageIdMultisigIsm", "threshold": 2, "validators": [ - "0xaf38612d1e79ec67320d21c5f7e92419427cd154", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", + "0xc944176bc4d4e5c7b0598884478a27a2b1904664", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, @@ -205,11 +197,11 @@ "threshold": 4, "validators": [ "0x570af9b7b36568c8877eebba6c6727aa9dab7268", - "0x8292b1a53907ece0f76af8a50724e9492bcdc8a3", - "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8", "0x5450447aee7b544c462c9352bef7cad049b0c2dc", - "0x6d113ae51bfea7b63a8828f97e9dce393b25c189", - "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4" + "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4", + "0x24c1506142b2c859aee36474e59ace09784f71e8", + "0xc67789546a7a983bf06453425231ab71c119153f", + "0x2d74f6edfd08261c927ddb6cb37af57ab89f0eff" ] }, "bsquared": { @@ -221,6 +213,18 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, + "celestia": { + "type": "messageIdMultisigIsm", + "threshold": 4, + "validators": [ + "0x6dbc192c06907784fb0af0c0c2d8809ea50ba675", + "0x761980c3debdc8ddb69a2713cf5126d4db900f0f", + "0x885a8c1ef7f7eea8955c8f116fc1fbe1113c4a78", + "0xa6c998f0db2b56d7a63faf30a9b677c8b9b6faab", + "0x21e93a81920b73c0e98aed8e6b058dae409e4909", + "0x7b8606d61bc990165d1e5977037ddcf7f2de74d6" + ] + }, "celo": { "type": "messageIdMultisigIsm", "threshold": 4, @@ -229,8 +233,8 @@ "0xeb0c31e2f2671d724a2589d4a8eca91b97559148", "0x033e391e9fc57a7b5dd6c91b69be9a1ed11c4986", "0x4a2423ef982b186729e779b6e54b0e84efea7285", - "0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3", - "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4" + "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4", + "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ] }, "cheesechain": { @@ -238,8 +242,7 @@ "threshold": 2, "validators": [ "0x478fb53c6860ae8fc35235ba0d38d49b13128226", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x101cE77261245140A0871f9407d6233C8230Ec47" + "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f" ] }, "chilizmainnet": { @@ -251,24 +254,6 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "conflux": { - "type": "messageIdMultisigIsm", - "threshold": 2, - "validators": [ - "0x113dfa1dc9b0a2efb6ad01981e2aad86d3658490", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" - ] - }, - "conwai": { - "type": "messageIdMultisigIsm", - "threshold": 2, - "validators": [ - "0x949e2cdd7e79f99ee9bbe549540370cdc62e73c3", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" - ] - }, "coredao": { "type": "messageIdMultisigIsm", "threshold": 2, @@ -278,15 +263,6 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "corn": { - "type": "messageIdMultisigIsm", - "threshold": 2, - "validators": [ - "0xc80b2e3e38220e02d194a0effa9d5bfe89894c07", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" - ] - }, "coti": { "type": "messageIdMultisigIsm", "threshold": 2, @@ -305,15 +281,6 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "deepbrainchain": { - "type": "messageIdMultisigIsm", - "threshold": 2, - "validators": [ - "0x3825ea1e0591b58461cc4aa34867668260c0e6a8", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" - ] - }, "degenchain": { "type": "messageIdMultisigIsm", "threshold": 2, @@ -332,23 +299,22 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "duckchain": { - "type": "messageIdMultisigIsm", - "threshold": 2, - "validators": [ - "0x91d55fe6dac596a6735d96365e21ce4bca21d83c", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" - ] - }, "eclipsemainnet": { "type": "messageIdMultisigIsm", "threshold": 3, "validators": [ "0xebb52d7eaa3ff7a5a6260bfe5111ce52d57401d0", "0x3571223e745dc0fcbdefa164c9b826b90c0d2dac", - "0xea83086a62617a7228ce4206fae2ea8b0ab23513", - "0x4d4629f5bfeabe66edc7a78da26ef5273c266f97" + "0x4d4629f5bfeabe66edc7a78da26ef5273c266f97", + "0x5450447aee7b544c462c9352bef7cad049b0c2dc" + ] + }, + "electroneum": { + "type": "messageIdMultisigIsm", + "threshold": 2, + "validators": [ + "0x32917f0a38c60ff5b1c4968cb40bc88b14ef0d83", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, "endurance": { @@ -367,12 +333,12 @@ "0x03c842db86a6a3e524d4a6615390c1ea8e2b9541", "0x94438a7de38d4548ae54df5c6010c4ebc5239eae", "0x5450447aee7b544c462c9352bef7cad049b0c2dc", - "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8", "0xb3ac35d3988bca8c2ffd195b1c6bee18536b317b", "0xb683b742b378632a5f73a2a5a45801b3489bba44", - "0xbf1023eff3dba21263bf2db2add67a0d6bcda2de", - "0x5d7442439959af11172bf92d9a8d21cf88d136e3", - "0x761980c3debdc8ddb69a2713cf5126d4db900f0f" + "0x3786083ca59dc806d894104e65a13a70c2b39276", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", + "0x29d783efb698f9a2d3045ef4314af1f5674f52c5", + "0x36a669703ad0e11a0382b098574903d2084be22c" ] }, "everclear": { @@ -384,15 +350,6 @@ "0xD79DFbF56ee2268f061cc613027a44A880f61Ba2" ] }, - "evmos": { - "type": "messageIdMultisigIsm", - "threshold": 2, - "validators": [ - "0x8f82387ad8b7b13aa9e06ed3f77f78a77713afe0", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" - ] - }, "fantom": { "type": "messageIdMultisigIsm", "threshold": 2, @@ -402,17 +359,6 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "flame": { - "type": "messageIdMultisigIsm", - "threshold": 3, - "validators": [ - "0x1fa928ce884fa16357d4b8866e096392d4d81f43", - "0xa6c998f0db2b56d7a63faf30a9b677c8b9b6faab", - "0x09f9de08f7570c4146caa708dc9f75b56958957f", - "0xf1f4ae9959490380ad7863e79c3faf118c1fbf77", - "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4" - ] - }, "flare": { "type": "messageIdMultisigIsm", "threshold": 2, @@ -458,8 +404,8 @@ "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4", "0x1c3C3013B863Cf666499Da1A61949AE396E3Ab82", "0x573e960e07ad74ea2c5f1e3c31b2055994b12797", - "0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3", - "0x25b3a88f7cfd3c9f7d7e32b295673a16a6ddbd91" + "0x25b3a88f7cfd3c9f7d7e32b295673a16a6ddbd91", + "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ] }, "fusemainnet": { @@ -471,31 +417,28 @@ "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f" ] }, - "game7": { + "galactica": { "type": "messageIdMultisigIsm", "threshold": 2, "validators": [ - "0x691dc4e763514df883155df0952f847b539454c0", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", + "0xfc48af3372d621f476c53d79d42a9e96ce11fd7d", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "glue": { + "game7": { "type": "messageIdMultisigIsm", "threshold": 2, "validators": [ - "0xbe2ded12f7b023916584836506677ea89a0b6924", + "0x691dc4e763514df883155df0952f847b539454c0", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, "gnosis": { "type": "messageIdMultisigIsm", - "threshold": 3, + "threshold": 2, "validators": [ "0xd4df66a859585678f2ea8357161d896be19cc1ca", - "0x19fb7e04a1be6b39b6966a0b0c60b929a93ed672", - "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8", "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ] }, @@ -508,15 +451,6 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "guru": { - "type": "messageIdMultisigIsm", - "threshold": 2, - "validators": [ - "0x0d756d9051f12c4de6aee2ee972193a2adfe00ef", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" - ] - }, "harmony": { "type": "messageIdMultisigIsm", "threshold": 2, @@ -551,7 +485,7 @@ "0x01be14a9eceeca36c9c1d46c056ca8c87f77c26f", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", - "0x36f2bd8200ede5f969d63a0a28e654392c51a193" + "0x04d949c615c9976f89595ddcb9008c92f8ba7278" ] }, "immutablezkevmmainnet": { @@ -598,8 +532,8 @@ "0xd207a6dfd887d91648b672727ff1aef6223cb15a", "0xa40203b5301659f1e201848d92f5e81f64f206f5", "0xff9c1e7b266a36eda0d9177d4236994d94819dc0", - "0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3", - "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4" + "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4", + "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ] }, "kaia": { @@ -611,12 +545,11 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "kroma": { + "katana": { "type": "messageIdMultisigIsm", "threshold": 2, "validators": [ - "0x71b83c21342787d758199e4b8634d3a15f02dc6e", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", + "0xf23003ebdc6c53765d52b1fe7a65046eabb0e73b", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, @@ -636,9 +569,9 @@ "0xf2d5409a59e0f5ae7635aff73685624904a77d94", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", - "0x6d113ae51bfea7b63a8828f97e9dce393b25c189", - "0x761980c3debdc8ddb69a2713cf5126d4db900f0f", - "0x5aed2fd5cc5f9749c455646c86b0db6126cafcbb" + "0x5450447aee7b544c462c9352bef7cad049b0c2dc", + "0x0c760f4bcb508db9144b0579e26f5ff8d94daf4d", + "0x6fbceb2680c8181acf3d1b5f0189e3beaa985338" ] }, "lisk": { @@ -649,9 +582,9 @@ "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4", "0x3DA4ee2801Ec6CC5faD73DBb94B10A203ADb3d9e", "0x4df6e8878992c300e7bfe98cac6bf7d3408b9cbf", - "0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3", "0xf0da628f3fb71652d48260bad4691054045832ce", - "0xead4141b6ea149901ce4f4b556953f66d04b1d0c" + "0xead4141b6ea149901ce4f4b556953f66d04b1d0c", + "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ] }, "lukso": { @@ -663,15 +596,6 @@ "0x101cE77261245140A0871f9407d6233C8230Ec47" ] }, - "lumia": { - "type": "messageIdMultisigIsm", - "threshold": 2, - "validators": [ - "0x9e283254ed2cd2c80f007348c2822fc8e5c2fa5f", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" - ] - }, "lumiaprism": { "type": "messageIdMultisigIsm", "threshold": 2, @@ -700,9 +624,9 @@ "0xf930636c5a1a8bf9302405f72e3af3c96ebe4a52", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", - "0x761980c3debdc8ddb69a2713cf5126d4db900f0f", - "0x5aed2fd5cc5f9749c455646c86b0db6126cafcbb", - "0x6d113ae51bfea7b63a8828f97e9dce393b25c189" + "0x5450447aee7b544c462c9352bef7cad049b0c2dc", + "0xcd3b3a2007aab3b00418fbac12bea19d04243497", + "0x332b3710e56b843027d4c6da7bca219ece7099b0" ] }, "matchain": { @@ -731,8 +655,8 @@ "0x01e3909133d20c05bbc94247769235d30101f748", "0xaba06266f47e3ef554d218b879bd86114a8dabd4", "0x05d91f80377ff5e9c6174025ffaf094c57a4766a", - "0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3", - "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4" + "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4", + "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ] }, "metis": { @@ -742,9 +666,9 @@ "0xc4a3d25107060e800a43842964546db508092260", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", - "0x6d113ae51bfea7b63a8828f97e9dce393b25c189", - "0x761980c3debdc8ddb69a2713cf5126d4db900f0f", - "0x5aed2fd5cc5f9749c455646c86b0db6126cafcbb" + "0x5450447aee7b544c462c9352bef7cad049b0c2dc", + "0xad1df94ae078631bfea1623520125e93a6085555", + "0x4272e7b93e127da5bc7cee617febf47bcad20def" ] }, "miraclechain": { @@ -758,9 +682,13 @@ }, "milkyway": { "type": "messageIdMultisigIsm", - "threshold": 1, + "threshold": 3, "validators": [ - "0x9985e0c6df8e25b655b46a317af422f5e7756875" + "0x9985e0c6df8e25b655b46a317af422f5e7756875", + "0x55010624d5e239281d0850dc7915b78187e8bc0e", + "0x9ecf299947b030f9898faf328e5edbf77b13e974", + "0x56fa9ac314ad49836ffb35918043d6b2dec304fb", + "0xb69c0d1aacd305edeca88b482b9dd9657f3a8b5c" ] }, "mint": { @@ -772,6 +700,17 @@ "0x0230505530b80186f8cdccfaf9993eb97aebe98a" ] }, + "mitosis": { + "type": "messageIdMultisigIsm", + "threshold": 3, + "validators": [ + "0x3b3eb808d90a4e19bb601790a6b6297812d6a61f", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", + "0x5450447aee7b544c462c9352bef7cad049b0c2dc", + "0x401f25ff73769ed85bdb449a4347a4fd2678acfe", + "0x340058f071e8376c2ecff219e1e6620deea8a3c7" + ] + }, "mode": { "type": "messageIdMultisigIsm", "threshold": 4, @@ -780,8 +719,8 @@ "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4", "0x65C140e3a05F33192384AffEF985696Fe3cDDE42", "0x20eade18ea2af6dfd54d72b3b5366b40fcb47f4b", - "0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3", - "0x485a4f0009d9afbbf44521016f9b8cdd718e36ea" + "0x485a4f0009d9afbbf44521016f9b8cdd718e36ea", + "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ] }, "molten": { @@ -795,12 +734,10 @@ }, "moonbeam": { "type": "messageIdMultisigIsm", - "threshold": 3, + "threshold": 2, "validators": [ "0x2225e2f4e9221049456da93b71d2de41f3b6b2a8", - "0x645428d198d2e76cbd9c1647f5c80740bb750b97", - "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8", - "0xb3ac35d3988bca8c2ffd195b1c6bee18536b317b" + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, "morph": { @@ -812,15 +749,6 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "nero": { - "type": "messageIdMultisigIsm", - "threshold": 2, - "validators": [ - "0xb86f872df37f11f33acbe75b6ed208b872b57183", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" - ] - }, "neutron": { "type": "messageIdMultisigIsm", "threshold": 4, @@ -842,6 +770,14 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, + "noble": { + "type": "messageIdMultisigIsm", + "threshold": 2, + "validators": [ + "0x28495e5c72a7dafd1658e5d99dfeffaada175c46", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" + ] + }, "ontology": { "type": "messageIdMultisigIsm", "threshold": 3, @@ -878,8 +814,8 @@ "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4", "0xd8c1cCbfF28413CE6c6ebe11A3e29B0D8384eDbB", "0x1b9e5f36c4bfdb0e3f0df525ef5c888a4459ef99", - "0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3", - "0xf9dfaa5c20ae1d84da4b2696b8dc80c919e48b12" + "0xf9dfaa5c20ae1d84da4b2696b8dc80c919e48b12", + "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ] }, "orderly": { @@ -915,6 +851,14 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, + "plasma": { + "type": "messageIdMultisigIsm", + "threshold": 2, + "validators": [ + "0x4ba900a8549fe503bca674114dc98a254637fc2c", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" + ] + }, "plume": { "type": "messageIdMultisigIsm", "threshold": 2, @@ -926,11 +870,10 @@ }, "polygon": { "type": "messageIdMultisigIsm", - "threshold": 3, + "threshold": 2, "validators": [ "0x12ecb319c7f4e8ac5eb5226662aeb8528c5cefac", "0x008f24cbb1cc30ad0f19f2516ca75730e37efb5f", - "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8", "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ] }, @@ -939,8 +882,7 @@ "threshold": 2, "validators": [ "0x86f2a44592bb98da766e880cfd70d3bbb295e61a", - "0x865818fe1db986036d5fd0466dcd462562436d1a", - "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8" + "0x865818fe1db986036d5fd0466dcd462562436d1a" ] }, "polynomialfi": { @@ -970,48 +912,45 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "rarichain": { + "pulsechain": { "type": "messageIdMultisigIsm", "threshold": 2, "validators": [ - "0xeac012df7530720dd7d6f9b727e4fe39807d1516", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", + "0xa73fc7ebb2149d9c6992ae002cb1849696be895b", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "reactive": { + "radix": { "type": "messageIdMultisigIsm", "threshold": 2, "validators": [ - "0x45768525f6c5ca2e4e7cc50d405370eadee2d624", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", + "0xa715a7cd97f68caeedb7be64f9e1da10f8ffafb4", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "real": { + "rarichain": { "type": "messageIdMultisigIsm", "threshold": 2, "validators": [ - "0xaebadd4998c70b05ce8715cf0c3cb8862fe0beec", + "0xeac012df7530720dd7d6f9b727e4fe39807d1516", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "redstone": { + "reactive": { "type": "messageIdMultisigIsm", - "threshold": 3, + "threshold": 2, "validators": [ - "0x1400b9737007f7978d8b4bbafb4a69c83f0641a7", + "0x45768525f6c5ca2e4e7cc50d405370eadee2d624", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", - "0x101cE77261245140A0871f9407d6233C8230Ec47" + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "rivalz": { + "redstone": { "type": "messageIdMultisigIsm", "threshold": 2, "validators": [ - "0xf87c3eb3dde972257b0d6d110bdadcda951c0dc1", + "0x1400b9737007f7978d8b4bbafb4a69c83f0641a7", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] @@ -1023,46 +962,25 @@ "0xa3e11929317e4a871c3d47445ea7bb8c4976fd8a", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", - "0x6d113ae51bfea7b63a8828f97e9dce393b25c189", - "0x761980c3debdc8ddb69a2713cf5126d4db900f0f", - "0x5aed2fd5cc5f9749c455646c86b0db6126cafcbb" - ] - }, - "rootstockmainnet": { - "type": "messageIdMultisigIsm", - "threshold": 2, - "validators": [ - "0x8675eb603d62ab64e3efe90df914e555966e04ac", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" - ] - }, - "sanko": { - "type": "messageIdMultisigIsm", - "threshold": 2, - "validators": [ - "0x795c37d5babbc44094b084b0c89ed9db9b5fae39", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" + "0x5450447aee7b544c462c9352bef7cad049b0c2dc", + "0x808a3945d5f9c2f9ccf7a76bde4c4b54c9c7dba4", + "0xe8a821e77bd1ee4658c29e8c3f43c0200b0f06a1" ] }, "scroll": { "type": "messageIdMultisigIsm", - "threshold": 3, + "threshold": 2, "validators": [ "0xad557170a9f2f21c35e03de07cb30dcbcc3dff63", - "0xb3ac35d3988bca8c2ffd195b1c6bee18536b317b", - "0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8", - "0xbac4ac39f1d8b5ef15f26fdb1294a7c9aba3f948" + "0xb3ac35d3988bca8c2ffd195b1c6bee18536b317b" ] }, "sei": { "type": "messageIdMultisigIsm", - "threshold": 3, + "threshold": 2, "validators": [ "0x9920d2dbf6c85ffc228fdc2e810bf895732c6aa5", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x101cE77261245140A0871f9407d6233C8230Ec47", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, @@ -1084,6 +1002,14 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, + "solaxy": { + "type": "messageIdMultisigIsm", + "threshold": 2, + "validators": [ + "0x4fa10dd6d854cd05f57bacf6f46d1a72eb1396e5", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" + ] + }, "soneium": { "type": "messageIdMultisigIsm", "threshold": 4, @@ -1092,8 +1018,8 @@ "0x9f4fa50ce49815b0932428a0eb1988382cef4a97", "0x8d2f8ebd61d055d58768cf3b07cb2fb565d87716", "0x6c5f6ab7a369222e6691218ad981fe08a5def094", - "0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3", - "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4" + "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4", + "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ] }, "sonic": { @@ -1103,9 +1029,9 @@ "0xa313d72dbbd3fa51a2ed1611ea50c37946fa42f7", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", - "0x6d113ae51bfea7b63a8828f97e9dce393b25c189", - "0x761980c3debdc8ddb69a2713cf5126d4db900f0f", - "0x5aed2fd5cc5f9749c455646c86b0db6126cafcbb" + "0x5450447aee7b544c462c9352bef7cad049b0c2dc", + "0x7f0e75c5151d0938eaa9ab8a30f9ddbd74c4ebef", + "0x4e3d1c926843dcc8ff47061bbd7143a2755899f3" ] }, "sonicsvm": { @@ -1172,14 +1098,12 @@ }, "subtensor": { "type": "messageIdMultisigIsm", - "threshold": 4, + "threshold": 3, "validators": [ "0xd5f8196d7060b85bea491f0b52a671e05f3d10a2", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", - "0x6d113ae51bfea7b63a8828f97e9dce393b25c189", - "0x761980c3debdc8ddb69a2713cf5126d4db900f0f", - "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4" + "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ] }, "superpositionmainnet": { @@ -1199,8 +1123,8 @@ "0x68f3a3b244f6ddc135130200a6b8729e290b4240", "0x6ff4554cffbc2e4e4230b78e526eab255101d05a", "0x55880ac03fdf15fccff54ed6f8a83455033edd22", - "0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3", - "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4" + "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4", + "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ] }, "svmbnb": { @@ -1220,8 +1144,16 @@ "0x9eadf9217be22d9878e0e464727a2176d5c69ff8", "0xa5a23fa2a67782bbf1a540cb5ca6a47a0f3f66fb", "0x3f707633ccab09d2978e29107c0bbef8a993e7a0", - "0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3", - "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4" + "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4", + "0x5450447aee7b544c462c9352bef7cad049b0c2dc" + ] + }, + "tac": { + "type": "messageIdMultisigIsm", + "threshold": 2, + "validators": [ + "0x606561d6a45188ba0a486e513e440bfc421dbc36", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, "taiko": { @@ -1243,15 +1175,6 @@ "0xe271ef9a6e312540f099a378865432fa73f26689" ] }, - "telos": { - "type": "messageIdMultisigIsm", - "threshold": 2, - "validators": [ - "0xcb08410b14d3adf0d0646f0c61cd07e0daba8e54", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" - ] - }, "torus": { "type": "messageIdMultisigIsm", "threshold": 2, @@ -1261,26 +1184,6 @@ "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "treasure": { - "type": "messageIdMultisigIsm", - "threshold": 3, - "validators": [ - "0x6ad994819185553e8baa01533f0cd2c7cadfe6cc", - "0x278460fa51ff448eb53ffa62951b4b8e3e8f74e3", - "0xe92ff70bb463e2aa93426fd2ba51afc39567d426", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", - "0x5aed2fd5cc5f9749c455646c86b0db6126cafcbb" - ] - }, - "trumpchain": { - "type": "messageIdMultisigIsm", - "threshold": 2, - "validators": [ - "0x3ada634c8dfa57a67f5f22ca757b35cde6cfab5e", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0xcF8151b8aEFfF4e22F6B48fe2Ffe2d60F00C890C" - ] - }, "unichain": { "type": "messageIdMultisigIsm", "threshold": 4, @@ -1289,27 +1192,17 @@ "0xa2549be30fb852c210c2fe8e7639039dca779936", "0xbcbed4d11e946844162cd92c6d09d1cf146b4006", "0xa9d517776fe8beba7d67c21cac1e805bd609c08e", - "0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3", - "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4" - ] - }, - "unitzero": { - "type": "messageIdMultisigIsm", - "threshold": 2, - "validators": [ - "0x18818e3ad2012728465d394f2e3c0ea2357ae9c5", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" + "0x0d4c1394a255568ec0ecd11795b28d1bda183ca4", + "0x5450447aee7b544c462c9352bef7cad049b0c2dc" ] }, "vana": { "type": "messageIdMultisigIsm", - "threshold": 3, + "threshold": 2, "validators": [ "0xfdf3b0dfd4b822d10cacb15c8ae945ea269e7534", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", - "0xba2f4f89cae6863d8b49e4ca0208ed48ad9ac354" + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, "viction": { @@ -1328,9 +1221,9 @@ "0x31048785845325b22817448b68d08f8a8fe36854", "0x11e2a683e83617f186614071e422b857256a9aae", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x761980c3debdc8ddb69a2713cf5126d4db900f0f", - "0x5aed2fd5cc5f9749c455646c86b0db6126cafcbb", - "0x6d113ae51bfea7b63a8828f97e9dce393b25c189" + "0x5450447aee7b544c462c9352bef7cad049b0c2dc", + "0xc1545f9fe903736b2e438b733740bd3516486da5", + "0x698810f8ae471f7e34860b465aeeb03df407be47" ] }, "xai": { @@ -1347,16 +1240,15 @@ "threshold": 2, "validators": [ "0xa2ae7c594703e988f23d97220717c513db638ea3", - "0xfed056cC0967F5BC9C6350F6C42eE97d3983394d", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f" + "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, - "xpla": { + "xrplevm": { "type": "messageIdMultisigIsm", "threshold": 2, "validators": [ - "0xc11cba01d67f2b9f0288c4c8e8b23c0eca03f26e", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", + "0x14d3e2f28d60d54a1659a205cb71e6e440f06510", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, @@ -1371,11 +1263,10 @@ }, "zetachain": { "type": "messageIdMultisigIsm", - "threshold": 3, + "threshold": 2, "validators": [ "0xa3bca0b80317dbf9c7dce16a16ac89f4ff2b23ef", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x101cE77261245140A0871f9407d6233C8230Ec47", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, @@ -1385,37 +1276,26 @@ "validators": [ "0x169ec400cc758fef3df6a0d6c51fbc6cdd1015bb", "0x7aC6584c068eb2A72d4Db82A7B7cd5AB34044061", - "0x0180444c9342BD672867Df1432eb3dA354413a6E", - "0x1da9176C2CE5cC7115340496fa7D1800a98911CE" - ] - }, - "zklink": { - "type": "messageIdMultisigIsm", - "threshold": 2, - "validators": [ - "0x217a8cb4789fc45abf56cb6e2ca96f251a5ac181", - "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", + "0x1da9176C2CE5cC7115340496fa7D1800a98911CE", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, "zksync": { "type": "messageIdMultisigIsm", - "threshold": 3, + "threshold": 2, "validators": [ "0xadd1d39ce7a687e32255ac457cf99a6d8c5b5d1a", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", - "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36", - "0x75237d42ce8ea27349a0254ada265db94157e0c1" + "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] }, "zoramainnet": { "type": "messageIdMultisigIsm", - "threshold": 3, + "threshold": 2, "validators": [ "0x35130945b625bb69b28aee902a3b9a76fa67125f", - "0x7089b6352d37d23fb05a7fee4229c78e038fba09", "0xcf0211fafbb91fd9d06d7e306b30032dc3a1934f", "0x4f977a59fdc2d9e39f6d780a84d5b4add1495a36" ] } -} +} \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet3/multisig-ism-message-id/solanamainnet/hyperlane/program-ids.json b/rust/sealevel/environments/mainnet3/multisig-ism-message-id/solanamainnet/hyperlane/program-ids.json index d85f705a6f4..c042eb84913 100644 --- a/rust/sealevel/environments/mainnet3/multisig-ism-message-id/solanamainnet/hyperlane/program-ids.json +++ b/rust/sealevel/environments/mainnet3/multisig-ism-message-id/solanamainnet/hyperlane/program-ids.json @@ -1,3 +1,3 @@ { - "program_id": "EpAuVN1oc5GccKAk41VMBHTgzJFtB5bftvi92SywQdbS" + "program_id": "Da6Lp9syj8hLRiqjZLTLbZEC1NPhPMPd1JJ3HQRN4NyJ" } \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet3/solanamainnet/core/program-ids.json b/rust/sealevel/environments/mainnet3/solanamainnet/core/program-ids.json index 744818fc1a5..62b8e98d083 100644 --- a/rust/sealevel/environments/mainnet3/solanamainnet/core/program-ids.json +++ b/rust/sealevel/environments/mainnet3/solanamainnet/core/program-ids.json @@ -1,8 +1,8 @@ { "mailbox": "E588QtVUvresuXq2KoNEwAmoifCzYGpRBdHByN9KQMbi", "validator_announce": "pRgs5vN4Pj7WvFbxf6QDHizo2njq2uksqEUbaSghVA8", - "multisig_ism_message_id": "EpAuVN1oc5GccKAk41VMBHTgzJFtB5bftvi92SywQdbS", + "multisig_ism_message_id": "Da6Lp9syj8hLRiqjZLTLbZEC1NPhPMPd1JJ3HQRN4NyJ", "igp_program_id": "BhNcatUDC2D5JTyeaqrdSukiVFsEHK7e3hVmKMztwefv", "overhead_igp_account": "AkeHBbE5JkwVppujCQQ6WuxsVsJtruBAjUo6fDCFp6fF", "igp_account": "JAvHW21tYXE9dtdG83DReqU2b4LUexFuCbtJT5tF8X6M" -} \ No newline at end of file +} diff --git a/rust/sealevel/environments/mainnet3/warp-routes/MONEY-sonicsvm/program-ids.json b/rust/sealevel/environments/mainnet3/warp-routes/MONEY-sonicsvm/program-ids.json new file mode 100644 index 00000000000..c54d9ec0c14 --- /dev/null +++ b/rust/sealevel/environments/mainnet3/warp-routes/MONEY-sonicsvm/program-ids.json @@ -0,0 +1,10 @@ +{ + "solanamainnet": { + "hex": "0x965db8107d1265bff45fbcc3b8bbcf7912d079537686ec2365697b0f4f9b837b", + "base58": "B7y3LKF5bRmeZBveaeLLtnsxXvS8NK7iegT7F1zP7mFp" + }, + "sonicsvm": { + "hex": "0x21cdfa51eb9f435167bb5e1b195d76e2dc8737cd3209790499816f880bd84d60", + "base58": "3Gxd4twoKcMMqyTJx9vbe9znhzMDdPktHRj56yj4UUo1" + } +} \ No newline at end of file diff --git a/rust/sealevel/environments/mainnet3/warp-routes/MONEY-sonicsvm/token-config.json b/rust/sealevel/environments/mainnet3/warp-routes/MONEY-sonicsvm/token-config.json new file mode 100644 index 00000000000..525700ec6e6 --- /dev/null +++ b/rust/sealevel/environments/mainnet3/warp-routes/MONEY-sonicsvm/token-config.json @@ -0,0 +1,18 @@ +{ + "solanamainnet": { + "type": "synthetic", + "decimals": 6, + "name": "Money", + "symbol": "MONEY", + "uri": "https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-registry/189d2a8f9a4eb2c0410e52d22e130d1376b469b1/deployments/warp_routes/MONEY/metadata.json", + "interchainGasPaymaster": "AkeHBbE5JkwVppujCQQ6WuxsVsJtruBAjUo6fDCFp6fF", + "owner": "9hixEzn9pBzYG1MVWNq3jAuG7RP3vBT5ijN4UFWDVsfD" + }, + "sonicsvm": { + "type": "collateral", + "decimals": 6, + "token": "4atQffe9q6xwcv5YmZKapSRofHFtJXtG6jJvdiVojqt7", + "interchainGasPaymaster": "7VResHbw6jRVUa8qfD6e1cbzGmErcLGwgx4o7mLhZief", + "owner": "4fws6acTdt9yVhh1tvdXoqew99WfviCJsse5hUiNaEoJ" + } +} diff --git a/rust/sealevel/environments/testnet4/warp-routes/foowarp/token-config.json b/rust/sealevel/environments/testnet4/warp-routes/foowarp/token-config.json new file mode 100644 index 00000000000..aea499582a3 --- /dev/null +++ b/rust/sealevel/environments/testnet4/warp-routes/foowarp/token-config.json @@ -0,0 +1,7 @@ +{ + "solanatestnet": { + "type": "nativeMemo", + "decimals": 9, + "interchainGasPaymaster": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy" + } +} diff --git a/rust/sealevel/libraries/access-control/Cargo.toml b/rust/sealevel/libraries/access-control/Cargo.toml index 964624bf581..80c39d52d17 100644 --- a/rust/sealevel/libraries/access-control/Cargo.toml +++ b/rust/sealevel/libraries/access-control/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "access-control" diff --git a/rust/sealevel/libraries/account-utils/Cargo.toml b/rust/sealevel/libraries/account-utils/Cargo.toml index c74f43ad320..be77ff5b17f 100644 --- a/rust/sealevel/libraries/account-utils/Cargo.toml +++ b/rust/sealevel/libraries/account-utils/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "account-utils" diff --git a/rust/sealevel/libraries/ecdsa-signature/Cargo.toml b/rust/sealevel/libraries/ecdsa-signature/Cargo.toml index 11b25b0dac8..0bb2688d858 100644 --- a/rust/sealevel/libraries/ecdsa-signature/Cargo.toml +++ b/rust/sealevel/libraries/ecdsa-signature/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "ecdsa-signature" diff --git a/rust/sealevel/libraries/hyperlane-sealevel-connection-client/Cargo.toml b/rust/sealevel/libraries/hyperlane-sealevel-connection-client/Cargo.toml index 8c7f0001c37..fdf32c76452 100644 --- a/rust/sealevel/libraries/hyperlane-sealevel-connection-client/Cargo.toml +++ b/rust/sealevel/libraries/hyperlane-sealevel-connection-client/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-sealevel-connection-client" diff --git a/rust/sealevel/libraries/hyperlane-sealevel-token/Cargo.toml b/rust/sealevel/libraries/hyperlane-sealevel-token/Cargo.toml index 46d9b45ef30..c998cfea7a6 100644 --- a/rust/sealevel/libraries/hyperlane-sealevel-token/Cargo.toml +++ b/rust/sealevel/libraries/hyperlane-sealevel-token/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-sealevel-token-lib" diff --git a/rust/sealevel/libraries/hyperlane-sealevel-token/src/instruction.rs b/rust/sealevel/libraries/hyperlane-sealevel-token/src/instruction.rs index 2193ea355da..ffa86ebd981 100644 --- a/rust/sealevel/libraries/hyperlane-sealevel-token/src/instruction.rs +++ b/rust/sealevel/libraries/hyperlane-sealevel-token/src/instruction.rs @@ -42,6 +42,29 @@ impl DiscriminatorData for Instruction { const DISCRIMINATOR: [u8; Self::DISCRIMINATOR_LENGTH] = PROGRAM_INSTRUCTION_DISCRIMINATOR; } +// ~~~~~~~~~~~~~~~~ DYMENSION ~~~~~~~~~~~~~~~~~~ + +/// Instruction data for transferring `amount_or_id` token to `recipient` +/// on `destination` domain, including a memo. +#[derive(BorshDeserialize, BorshSerialize, Debug, PartialEq)] +pub struct TransferRemoteMemo { + /// Base transfer instruction. + pub base: TransferRemote, + /// Arbitrary metadata. + pub memo: Vec, +} + +/// Instructions specifically for this token program +#[derive(BorshDeserialize, BorshSerialize, Debug, PartialEq)] +pub enum DymInstruction { + /// Transfer tokens to a remote recipient, including a memo. + TransferRemoteMemo(TransferRemoteMemo), +} + +impl DiscriminatorData for DymInstruction { + const DISCRIMINATOR: [u8; Self::DISCRIMINATOR_LENGTH] = PROGRAM_INSTRUCTION_DISCRIMINATOR; +} + /// Instruction data for initializing the program. #[derive(BorshDeserialize, BorshSerialize, Debug, PartialEq)] pub struct Init { diff --git a/rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs b/rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs index 09e19d9339c..6ea3c2ded24 100644 --- a/rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs +++ b/rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs @@ -618,6 +618,226 @@ where Ok(()) } + /// Transfers tokens to a remote. Copy of transfer remote with memo added. + pub fn transfer_remote_memo( + program_id: &Pubkey, + accounts: &[AccountInfo], + xfer: TransferRemote, + memo: Vec, + ) -> ProgramResult { + let accounts_iter = &mut accounts.iter(); + + // Account 0: System program. + let system_program_account = next_account_info(accounts_iter)?; + if system_program_account.key != &solana_program::system_program::id() { + return Err(ProgramError::InvalidArgument); + } + + // Account 1: SPL Noop. + let spl_noop = next_account_info(accounts_iter)?; + if spl_noop.key != &spl_noop::id() { + return Err(ProgramError::InvalidArgument); + } + + // Account 2: Token storage account + let token_account = next_account_info(accounts_iter)?; + let token = + HyperlaneTokenAccount::fetch(&mut &token_account.data.borrow()[..])?.into_inner(); + let token_seeds: &[&[u8]] = hyperlane_token_pda_seeds!(token.bump); + let expected_token_key = Pubkey::create_program_address(token_seeds, program_id)?; + if token_account.key != &expected_token_key { + return Err(ProgramError::InvalidArgument); + } + if token_account.owner != program_id { + return Err(ProgramError::IncorrectProgramId); + } + + // Account 3: Mailbox program + let mailbox_info = next_account_info(accounts_iter)?; + if mailbox_info.key != &token.mailbox { + return Err(ProgramError::IncorrectProgramId); + } + + // Account 4: Mailbox Outbox data account. + // No verification is performed here, the Mailbox will do that. + let mailbox_outbox_account = next_account_info(accounts_iter)?; + + // Account 5: Message dispatch authority + let dispatch_authority_account = next_account_info(accounts_iter)?; + let dispatch_authority_seeds: &[&[u8]] = + mailbox_message_dispatch_authority_pda_seeds!(token.dispatch_authority_bump); + let dispatch_authority_key = + Pubkey::create_program_address(dispatch_authority_seeds, program_id)?; + if *dispatch_authority_account.key != dispatch_authority_key { + return Err(ProgramError::InvalidArgument); + } + + // Account 6: Sender account / mailbox payer + let sender_wallet = next_account_info(accounts_iter)?; + if !sender_wallet.is_signer { + return Err(ProgramError::MissingRequiredSignature); + } + + // Account 7: Unique message / gas payment account + // Defer to the checks in the Mailbox / IGP, no need to verify anything here. + let unique_message_account = next_account_info(accounts_iter)?; + + // Account 8: Message storage PDA. + // Similarly defer to the checks in the Mailbox to ensure account validity. + let dispatched_message_pda = next_account_info(accounts_iter)?; + + let igp_payment_accounts = + if let Some((igp_program_id, igp_account_type)) = token.interchain_gas_paymaster() { + // Account 9: The IGP program + let igp_program_account = next_account_info(accounts_iter)?; + if igp_program_account.key != igp_program_id { + return Err(ProgramError::InvalidArgument); + } + + // Account 10: The IGP program data. + // No verification is performed here, the IGP will do that. + let igp_program_data_account = next_account_info(accounts_iter)?; + + // Account 11: The gas payment PDA. + // No verification is performed here, the IGP will do that. + let igp_payment_pda_account = next_account_info(accounts_iter)?; + + // Account 12: The configured IGP account. + let configured_igp_account = next_account_info(accounts_iter)?; + if configured_igp_account.key != igp_account_type.key() { + return Err(ProgramError::InvalidArgument); + } + + // Accounts expected by the IGP's `PayForGas` instruction: + // + // 0. `[executable]` The system program. + // 1. `[signer]` The payer. + // 2. `[writeable]` The IGP program data. + // 3. `[signer]` Unique gas payment account. + // 4. `[writeable]` Gas payment PDA. + // 5. `[writeable]` The IGP account. + // 6. `[]` Overhead IGP account (optional). + + let mut igp_payment_account_metas = vec![ + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(*sender_wallet.key, true), + AccountMeta::new(*igp_program_data_account.key, false), + AccountMeta::new_readonly(*unique_message_account.key, true), + AccountMeta::new(*igp_payment_pda_account.key, false), + ]; + let mut igp_payment_account_infos = vec![ + system_program_account.clone(), + sender_wallet.clone(), + igp_program_data_account.clone(), + unique_message_account.clone(), + igp_payment_pda_account.clone(), + ]; + + match igp_account_type { + InterchainGasPaymasterType::Igp(_) => { + igp_payment_account_metas + .push(AccountMeta::new(*configured_igp_account.key, false)); + igp_payment_account_infos.push(configured_igp_account.clone()); + } + InterchainGasPaymasterType::OverheadIgp(_) => { + // Account 13: The inner IGP account. + let inner_igp_account = next_account_info(accounts_iter)?; + + // The inner IGP is expected first, then the overhead IGP. + igp_payment_account_metas.extend([ + AccountMeta::new(*inner_igp_account.key, false), + AccountMeta::new_readonly(*configured_igp_account.key, false), + ]); + igp_payment_account_infos + .extend([inner_igp_account.clone(), configured_igp_account.clone()]); + } + }; + + Some((igp_payment_account_metas, igp_payment_account_infos)) + } else { + None + }; + + // The amount denominated in the local decimals. + let local_amount: u64 = xfer + .amount_or_id + .try_into() + .map_err(|_| Error::IntegerOverflow)?; + // Convert to the remote number of decimals, which is universally understood + // by the remote routers as the number of decimals used by the message amount. + let remote_amount = token.local_amount_to_remote_amount(local_amount)?; + + // Transfer `local_amount` of tokens in... + T::transfer_in( + program_id, + &*token, + sender_wallet, + accounts_iter, + local_amount, + )?; + + if accounts_iter.next().is_some() { + return Err(ProgramError::from(Error::ExtraneousAccount)); + } + + let dispatch_account_metas = vec![ + AccountMeta::new(*mailbox_outbox_account.key, false), + AccountMeta::new_readonly(*dispatch_authority_account.key, true), + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new_readonly(spl_noop::id(), false), + AccountMeta::new(*sender_wallet.key, true), + AccountMeta::new_readonly(*unique_message_account.key, true), + AccountMeta::new(*dispatched_message_pda.key, false), + ]; + let dispatch_account_infos = &[ + mailbox_outbox_account.clone(), + dispatch_authority_account.clone(), + system_program_account.clone(), + spl_noop.clone(), + sender_wallet.clone(), + unique_message_account.clone(), + dispatched_message_pda.clone(), + ]; + + // The token message body, which specifies the remote_amount. + let token_transfer_message = + TokenMessage::new(xfer.recipient, remote_amount, memo).to_vec(); + + if let Some((igp_payment_account_metas, igp_payment_account_infos)) = igp_payment_accounts { + // Dispatch the message and pay for gas. + HyperlaneGasRouterDispatch::dispatch_with_gas( + &*token, + program_id, + dispatch_authority_seeds, + xfer.destination_domain, + token_transfer_message, + dispatch_account_metas, + dispatch_account_infos, + igp_payment_account_metas, + &igp_payment_account_infos, + )?; + } else { + // Dispatch the message. + token.dispatch( + program_id, + dispatch_authority_seeds, + xfer.destination_domain, + token_transfer_message, + dispatch_account_metas, + dispatch_account_infos, + )?; + } + + msg!( + "Warp route transfer completed to destination: {}, recipient: {}, remote_amount: {}", + xfer.destination_domain, + xfer.recipient, + remote_amount + ); + + Ok(()) + } + /// Enrolls a remote router. /// /// Accounts: diff --git a/rust/sealevel/libraries/interchain-security-module-interface/Cargo.toml b/rust/sealevel/libraries/interchain-security-module-interface/Cargo.toml index 350cf2e4c41..9e88e3044e0 100644 --- a/rust/sealevel/libraries/interchain-security-module-interface/Cargo.toml +++ b/rust/sealevel/libraries/interchain-security-module-interface/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-sealevel-interchain-security-module-interface" diff --git a/rust/sealevel/libraries/message-recipient-interface/Cargo.toml b/rust/sealevel/libraries/message-recipient-interface/Cargo.toml index 7f9468cb820..11e464218aa 100644 --- a/rust/sealevel/libraries/message-recipient-interface/Cargo.toml +++ b/rust/sealevel/libraries/message-recipient-interface/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-sealevel-message-recipient-interface" diff --git a/rust/sealevel/libraries/multisig-ism/Cargo.toml b/rust/sealevel/libraries/multisig-ism/Cargo.toml index 963db7a31ae..3564f22ad40 100644 --- a/rust/sealevel/libraries/multisig-ism/Cargo.toml +++ b/rust/sealevel/libraries/multisig-ism/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "multisig-ism" diff --git a/rust/sealevel/libraries/serializable-account-meta/Cargo.toml b/rust/sealevel/libraries/serializable-account-meta/Cargo.toml index cab84fe371e..4296b645fc9 100644 --- a/rust/sealevel/libraries/serializable-account-meta/Cargo.toml +++ b/rust/sealevel/libraries/serializable-account-meta/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "serializable-account-meta" diff --git a/rust/sealevel/libraries/test-transaction-utils/Cargo.toml b/rust/sealevel/libraries/test-transaction-utils/Cargo.toml index 418c7c38fde..8c0506b7cac 100644 --- a/rust/sealevel/libraries/test-transaction-utils/Cargo.toml +++ b/rust/sealevel/libraries/test-transaction-utils/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-test-transaction-utils" diff --git a/rust/sealevel/libraries/test-utils/Cargo.toml b/rust/sealevel/libraries/test-utils/Cargo.toml index 4197771e7f9..188b6757d65 100644 --- a/rust/sealevel/libraries/test-utils/Cargo.toml +++ b/rust/sealevel/libraries/test-utils/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-test-utils" diff --git a/rust/sealevel/programs/build-programs.sh b/rust/sealevel/programs/build-programs.sh index 32c94641402..29590056725 100755 --- a/rust/sealevel/programs/build-programs.sh +++ b/rust/sealevel/programs/build-programs.sh @@ -12,8 +12,8 @@ PROGRAM_TYPE="${1:-all}" SOLANA_CLI_VERSION_FOR_BUILDING_PROGRAMS="1.14.20" # The paths to the programs -CORE_PROGRAM_PATHS=("mailbox" "ism/multisig-ism-message-id" "validator-announce" "hyperlane-sealevel-igp") -TOKEN_PROGRAM_PATHS=("hyperlane-sealevel-token" "hyperlane-sealevel-token-collateral" "hyperlane-sealevel-token-native") +CORE_PROGRAM_PATHS=("mailbox" "ism/multisig-ism-message-id" "ism/test-ism" "validator-announce" "hyperlane-sealevel-igp") +TOKEN_PROGRAM_PATHS=("hyperlane-sealevel-token" "hyperlane-sealevel-token-collateral" "hyperlane-sealevel-token-native" "hyperlane-sealevel-token-memo" "hyperlane-sealevel-token-collateral-memo" "hyperlane-sealevel-token-native-memo") build_program () { PROGRAM_PATH=$1 diff --git a/rust/sealevel/programs/helloworld/Cargo.toml b/rust/sealevel/programs/helloworld/Cargo.toml index 7aa1fd699a6..d8d1331d243 100644 --- a/rust/sealevel/programs/helloworld/Cargo.toml +++ b/rust/sealevel/programs/helloworld/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-sealevel-hello-world" diff --git a/rust/sealevel/programs/helloworld/src/lib.rs b/rust/sealevel/programs/helloworld/src/lib.rs index 7900575e42a..7f753973854 100644 --- a/rust/sealevel/programs/helloworld/src/lib.rs +++ b/rust/sealevel/programs/helloworld/src/lib.rs @@ -3,6 +3,7 @@ #![deny(warnings)] #![deny(missing_docs)] #![deny(unsafe_code)] +#![allow(unexpected_cfgs)] pub mod accounts; pub mod instruction; diff --git a/rust/sealevel/programs/hyperlane-sealevel-igp-test/Cargo.toml b/rust/sealevel/programs/hyperlane-sealevel-igp-test/Cargo.toml index b636c26922c..30a259867a7 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-igp-test/Cargo.toml +++ b/rust/sealevel/programs/hyperlane-sealevel-igp-test/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-sealevel-igp-test" diff --git a/rust/sealevel/programs/hyperlane-sealevel-igp/Cargo.toml b/rust/sealevel/programs/hyperlane-sealevel-igp/Cargo.toml index f02e1865f89..6f5dd58061f 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-igp/Cargo.toml +++ b/rust/sealevel/programs/hyperlane-sealevel-igp/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-sealevel-igp" diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/Cargo.toml b/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/Cargo.toml new file mode 100644 index 00000000000..65b1b2bec4a --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/Cargo.toml @@ -0,0 +1,47 @@ + + +[package] +name = "hyperlane-sealevel-token-collateral-memo" +version = "0.1.0" +edition = "2021" + +[features] +no-entrypoint = [] + +[dependencies] +borsh.workspace = true +num-derive.workspace = true +num-traits.workspace = true +solana-program.workspace = true +spl-associated-token-account.workspace = true +spl-noop.workspace = true +spl-token-2022.workspace = true # FIXME Should we actually use 2022 here or try normal token program? +spl-token.workspace = true +thiserror.workspace = true + +account-utils = { path = "../../libraries/account-utils" } +hyperlane-core = { path = "../../../main/hyperlane-core" } +hyperlane-sealevel-connection-client = { path = "../../libraries/hyperlane-sealevel-connection-client" } +hyperlane-sealevel-mailbox = { path = "../mailbox", features = [ + "no-entrypoint", +] } +hyperlane-sealevel-igp = { path = "../hyperlane-sealevel-igp", features = [ + "no-entrypoint", +] } +hyperlane-sealevel-message-recipient-interface = { path = "../../libraries/message-recipient-interface" } +hyperlane-sealevel-token-lib = { path = "../../libraries/hyperlane-sealevel-token" } +hyperlane-warp-route = { path = "../../../main/applications/hyperlane-warp-route" } +serializable-account-meta = { path = "../../libraries/serializable-account-meta" } +hyperlane-sealevel-token-collateral = { path = "../hyperlane-sealevel-token-collateral" } + +[dev-dependencies] +solana-program-test.workspace = true +solana-sdk.workspace = true + +hyperlane-test-utils = { path = "../../libraries/test-utils" } +hyperlane-sealevel-test-ism = { path = "../ism/test-ism", features = [ + "no-entrypoint", +] } + +[lib] +crate-type = ["cdylib", "lib"] diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/src/instruction.rs b/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/src/instruction.rs new file mode 100644 index 00000000000..3f3d0af86da --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/src/instruction.rs @@ -0,0 +1,47 @@ +//! Instructions for the program. + +use hyperlane_sealevel_token_lib::instruction::{init_instruction as lib_init_instruction, Init}; + +use crate::{hyperlane_token_ata_payer_pda_seeds, hyperlane_token_escrow_pda_seeds}; + +use solana_program::{ + instruction::{AccountMeta, Instruction as SolanaInstruction}, + program_error::ProgramError, + pubkey::Pubkey, + rent::Rent, + sysvar::SysvarId, +}; + +/// Gets an instruction to initialize the program. +pub fn init_instruction( + program_id: Pubkey, + payer: Pubkey, + init: Init, + spl_program: Pubkey, + mint: Pubkey, +) -> Result { + let mut instruction = lib_init_instruction(program_id, payer, init)?; + + // Add additional account metas: + // 0. `[executable]` The SPL token program for the mint, i.e. either SPL token program or the 2022 version. + // 1. `[]` The mint. + // 2. `[executable]` The Rent sysvar program. + // 3. `[writable]` The escrow PDA account. + // 4. `[writable]` The ATA payer PDA account. + + let (escrow_key, _escrow_bump) = + Pubkey::find_program_address(hyperlane_token_escrow_pda_seeds!(), &program_id); + + let (ata_payer_key, _ata_payer_bump) = + Pubkey::find_program_address(hyperlane_token_ata_payer_pda_seeds!(), &program_id); + + instruction.accounts.append(&mut vec![ + AccountMeta::new_readonly(spl_program, false), + AccountMeta::new_readonly(mint, false), + AccountMeta::new_readonly(Rent::id(), false), + AccountMeta::new(escrow_key, false), + AccountMeta::new(ata_payer_key, false), + ]); + + Ok(instruction) +} diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/src/lib.rs b/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/src/lib.rs new file mode 100644 index 00000000000..ee25eb133e7 --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/src/lib.rs @@ -0,0 +1,14 @@ +//! The hyperlane-sealevel-token-collateral program. + +#![deny(warnings)] +#![deny(missing_docs)] +#![deny(unsafe_code)] + +pub mod instruction; +pub mod plugin; +pub mod processor; + +pub use spl_associated_token_account; +pub use spl_noop; +pub use spl_token; +pub use spl_token_2022; diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/src/plugin.rs b/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/src/plugin.rs new file mode 100644 index 00000000000..2b97b0f1c5f --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/src/plugin.rs @@ -0,0 +1,457 @@ +//! A plugin for the Hyperlane token program that escrows SPL tokens as collateral. + +use account_utils::{create_pda_account, verify_rent_exempt, SizedData}; +use borsh::{BorshDeserialize, BorshSerialize}; +use hyperlane_sealevel_token_lib::{ + accounts::HyperlaneToken, processor::HyperlaneSealevelTokenPlugin, +}; +use hyperlane_warp_route::TokenMessage; +use serializable_account_meta::SerializableAccountMeta; +use solana_program::{ + account_info::{next_account_info, AccountInfo}, + instruction::AccountMeta, + program::{get_return_data, invoke, invoke_signed}, + program_error::ProgramError, + pubkey::Pubkey, + rent::Rent, + sysvar::{self, Sysvar}, +}; +use spl_associated_token_account::{ + get_associated_token_address_with_program_id, + instruction::create_associated_token_account_idempotent, +}; +use spl_token_2022::instruction::{get_account_data_size, initialize_account, transfer_checked}; + +/// Seeds relating to the PDA account that acts both as the mint +/// *and* the mint authority. +#[macro_export] +macro_rules! hyperlane_token_escrow_pda_seeds { + () => {{ + &[b"hyperlane_token", b"-", b"escrow"] + }}; + + ($bump_seed:expr) => {{ + &[b"hyperlane_token", b"-", b"escrow", &[$bump_seed]] + }}; +} + +/// Seeds relating to the PDA account that acts as the payer for +/// ATA creation. +#[macro_export] +macro_rules! hyperlane_token_ata_payer_pda_seeds { + () => {{ + &[b"hyperlane_token", b"-", b"ata_payer"] + }}; + + ($bump_seed:expr) => {{ + &[b"hyperlane_token", b"-", b"ata_payer", &[$bump_seed]] + }}; +} + +/// A plugin for the Hyperlane token program that escrows SPL +/// tokens when transferring out to a remote chain, and pays them +/// out when transferring in from a remote chain. +#[derive(BorshDeserialize, BorshSerialize, Debug, PartialEq, Default)] +pub struct CollateralPlugin { + /// The SPL token program, i.e. either SPL token program or the 2022 version. + pub spl_token_program: Pubkey, + /// The mint. + pub mint: Pubkey, + /// The escrow PDA account. + pub escrow: Pubkey, + /// The escrow PDA bump seed. + pub escrow_bump: u8, + /// The ATA payer PDA bump seed. + pub ata_payer_bump: u8, +} + +impl SizedData for CollateralPlugin { + fn size(&self) -> usize { + // spl_token_program + 32 + // mint + + 32 + // escrow + + 32 + // escrow_bump + + std::mem::size_of::() + // ata_payer_bump + + std::mem::size_of::() + } +} + +impl CollateralPlugin { + fn verify_ata_payer_account_info( + program_id: &Pubkey, + token: &HyperlaneToken, + ata_payer_account_info: &AccountInfo, + ) -> Result<(), ProgramError> { + let ata_payer_seeds: &[&[u8]] = + hyperlane_token_ata_payer_pda_seeds!(token.plugin_data.ata_payer_bump); + let expected_ata_payer_account = + Pubkey::create_program_address(ata_payer_seeds, program_id)?; + if ata_payer_account_info.key != &expected_ata_payer_account { + return Err(ProgramError::InvalidArgument); + } + Ok(()) + } +} + +impl HyperlaneSealevelTokenPlugin for CollateralPlugin { + /// Initializes the plugin. + /// + /// Accounts: + /// 0. `[executable]` The SPL token program for the mint, i.e. either SPL token program or the 2022 version. + /// 1. `[]` The mint. + /// 2. `[executable]` The Rent sysvar program. + /// 3. `[writable]` The escrow PDA account. + /// 4. `[writable]` The ATA payer PDA account. + fn initialize<'a, 'b>( + program_id: &Pubkey, + system_program: &'a AccountInfo<'b>, + _token_account_info: &'a AccountInfo<'b>, + payer_account_info: &'a AccountInfo<'b>, + accounts_iter: &mut std::slice::Iter<'a, AccountInfo<'b>>, + ) -> Result { + // Account 0: The SPL token program. + // This can either be the original SPL token program or the 2022 version. + // This is saved in the HyperlaneToken plugin data so that future interactions + // are done with the correct SPL token program. + let spl_token_account_info = next_account_info(accounts_iter)?; + if spl_token_account_info.key != &spl_token_2022::id() + && spl_token_account_info.key != &spl_token::id() + { + return Err(ProgramError::IncorrectProgramId); + } + + // Account 1: The mint. + let mint_account_info = next_account_info(accounts_iter)?; + if mint_account_info.owner != spl_token_account_info.key { + return Err(ProgramError::IllegalOwner); + } + + // Account 2: The Rent sysvar program. + let rent_account_info = next_account_info(accounts_iter)?; + if rent_account_info.key != &sysvar::rent::id() { + return Err(ProgramError::IncorrectProgramId); + } + + // Account 3: Escrow PDA account. + let escrow_account_info = next_account_info(accounts_iter)?; + let (escrow_key, escrow_bump) = + Pubkey::find_program_address(hyperlane_token_escrow_pda_seeds!(), program_id); + if &escrow_key != escrow_account_info.key { + return Err(ProgramError::IncorrectProgramId); + } + + // Get the required account size for the escrow PDA. + invoke( + &get_account_data_size( + spl_token_account_info.key, + mint_account_info.key, + // No additional extensions + &[], + )?, + &[mint_account_info.clone()], + )?; + let account_data_size: u64 = get_return_data() + .ok_or(ProgramError::InvalidArgument) + .and_then(|(returning_pubkey, data)| { + if &returning_pubkey != spl_token_account_info.key { + return Err(ProgramError::InvalidArgument); + } + let data: [u8; 8] = data + .as_slice() + .try_into() + .map_err(|_| ProgramError::InvalidArgument)?; + Ok(u64::from_le_bytes(data)) + })?; + + let rent = Rent::get()?; + + // Create escrow PDA owned by the SPL token program. + create_pda_account( + payer_account_info, + &rent, + account_data_size.try_into().unwrap(), + spl_token_account_info.key, + system_program, + escrow_account_info, + hyperlane_token_escrow_pda_seeds!(escrow_bump), + )?; + + // And initialize the escrow account. + invoke( + &initialize_account( + spl_token_account_info.key, + escrow_account_info.key, + mint_account_info.key, + escrow_account_info.key, + )?, + &[ + escrow_account_info.clone(), + mint_account_info.clone(), + escrow_account_info.clone(), + rent_account_info.clone(), + ], + )?; + + // Account 4: ATA payer. + let ata_payer_account_info = next_account_info(accounts_iter)?; + let (ata_payer_key, ata_payer_bump) = + Pubkey::find_program_address(hyperlane_token_ata_payer_pda_seeds!(), program_id); + if &ata_payer_key != ata_payer_account_info.key { + return Err(ProgramError::IncorrectProgramId); + } + + // Create the ATA payer. + // This is a separate PDA because the ATA program requires + // the payer to have no data in it. + create_pda_account( + payer_account_info, + &rent, + 0, + // Grant ownership to the system program so that the ATA program + // can call into the system program with the ATA payer as the + // payer. + &solana_program::system_program::id(), + system_program, + ata_payer_account_info, + hyperlane_token_ata_payer_pda_seeds!(ata_payer_bump), + )?; + + Ok(Self { + spl_token_program: *spl_token_account_info.key, + mint: *mint_account_info.key, + escrow: escrow_key, + escrow_bump, + ata_payer_bump, + }) + } + + /// Transfers tokens to the escrow account so they can be sent to a remote chain. + /// Burns the tokens from the sender's associated token account. + /// + /// Accounts: + /// 0. `[executable]` The SPL token program for the mint. + /// 1. `[writeable]` The mint. + /// 2. `[writeable]` The token sender's associated token account, from which tokens will be sent. + /// 3. `[writeable]` The escrow PDA account. + fn transfer_in<'a, 'b>( + _program_id: &Pubkey, + token: &HyperlaneToken, + sender_wallet_account_info: &'a AccountInfo<'b>, + accounts_iter: &mut std::slice::Iter<'a, AccountInfo<'b>>, + amount: u64, + ) -> Result<(), ProgramError> { + // Account 0: SPL token program. + let spl_token_account_info = next_account_info(accounts_iter)?; + if spl_token_account_info.key != &token.plugin_data.spl_token_program { + return Err(ProgramError::IncorrectProgramId); + } + if !spl_token_account_info.executable { + return Err(ProgramError::InvalidAccountData); + } + + // Account 1: The mint. + let mint_account_info = next_account_info(accounts_iter)?; + if mint_account_info.key != &token.plugin_data.mint { + return Err(ProgramError::IncorrectProgramId); + } + if mint_account_info.owner != spl_token_account_info.key { + return Err(ProgramError::InvalidAccountData); + } + + // Account 2: The sender's associated token account. + let sender_ata_account_info = next_account_info(accounts_iter)?; + let expected_sender_associated_token_key = get_associated_token_address_with_program_id( + sender_wallet_account_info.key, + mint_account_info.key, + spl_token_account_info.key, + ); + if sender_ata_account_info.key != &expected_sender_associated_token_key { + return Err(ProgramError::IncorrectProgramId); + } + + // Account 3: The escrow PDA account. + let escrow_account_info = next_account_info(accounts_iter)?; + if escrow_account_info.key != &token.plugin_data.escrow { + return Err(ProgramError::IncorrectProgramId); + } + + let transfer_instruction = transfer_checked( + spl_token_account_info.key, + sender_ata_account_info.key, + mint_account_info.key, + escrow_account_info.key, + sender_wallet_account_info.key, + // Multisignatures not supported at the moment. + &[], + amount, + token.decimals, + )?; + + // Sender wallet is expected to have signed this transaction. + invoke( + &transfer_instruction, + &[ + sender_ata_account_info.clone(), + mint_account_info.clone(), + escrow_account_info.clone(), + sender_wallet_account_info.clone(), + ], + )?; + + Ok(()) + } + + /// Transfers tokens out to a recipient's associated token account as a + /// result of a transfer to this chain from a remote chain. + /// + /// Accounts: + /// 0. `[executable]` SPL token for the mint. + /// 1. `[executable]` SPL associated token account. + /// 2. `[writeable]` Mint account. + /// 3. `[writeable]` Recipient associated token account. + /// 4. `[writeable]` ATA payer PDA account. + /// 5. `[writeable]` Escrow account. + fn transfer_out<'a, 'b>( + program_id: &Pubkey, + token: &HyperlaneToken, + system_program_account_info: &'a AccountInfo<'b>, + recipient_wallet_account_info: &'a AccountInfo<'b>, + accounts_iter: &mut std::slice::Iter<'a, AccountInfo<'b>>, + amount: u64, + ) -> Result<(), ProgramError> { + // Account 0: SPL token program. + let spl_token_account_info = next_account_info(accounts_iter)?; + if spl_token_account_info.key != &token.plugin_data.spl_token_program { + return Err(ProgramError::IncorrectProgramId); + } + if !spl_token_account_info.executable { + return Err(ProgramError::InvalidAccountData); + } + + // Account 1: SPL associated token account + let spl_ata_account_info = next_account_info(accounts_iter)?; + if spl_ata_account_info.key != &spl_associated_token_account::id() { + return Err(ProgramError::IncorrectProgramId); + } + if !spl_ata_account_info.executable { + return Err(ProgramError::InvalidAccountData); + } + + // Account 2: Mint account + let mint_account_info = next_account_info(accounts_iter)?; + if mint_account_info.key != &token.plugin_data.mint { + return Err(ProgramError::IncorrectProgramId); + } + + // Account 3: Recipient associated token account + let recipient_ata_account_info = next_account_info(accounts_iter)?; + let expected_recipient_associated_token_account_key = + get_associated_token_address_with_program_id( + recipient_wallet_account_info.key, + mint_account_info.key, + spl_token_account_info.key, + ); + if recipient_ata_account_info.key != &expected_recipient_associated_token_account_key { + return Err(ProgramError::IncorrectProgramId); + } + if !recipient_ata_account_info.is_writable { + return Err(ProgramError::InvalidAccountData); + } + + // Account 4: ATA payer PDA account + let ata_payer_account_info = next_account_info(accounts_iter)?; + Self::verify_ata_payer_account_info(program_id, token, ata_payer_account_info)?; + + // Account 5: Escrow account. + let escrow_account_info = next_account_info(accounts_iter)?; + if escrow_account_info.key != &token.plugin_data.escrow { + return Err(ProgramError::IncorrectProgramId); + } + + // Create and init (this does both) associated token account if necessary. + invoke_signed( + &create_associated_token_account_idempotent( + ata_payer_account_info.key, + recipient_wallet_account_info.key, + mint_account_info.key, + spl_token_account_info.key, + ), + &[ + ata_payer_account_info.clone(), + recipient_ata_account_info.clone(), + recipient_wallet_account_info.clone(), + mint_account_info.clone(), + system_program_account_info.clone(), + spl_token_account_info.clone(), + ], + &[hyperlane_token_ata_payer_pda_seeds!( + token.plugin_data.ata_payer_bump + )], + )?; + + // After potentially paying for the ATA creation, we need to make sure + // the ATA payer still meets the rent-exemption requirements! + verify_rent_exempt(ata_payer_account_info, &Rent::get()?)?; + + let transfer_instruction = transfer_checked( + spl_token_account_info.key, + escrow_account_info.key, + mint_account_info.key, + recipient_ata_account_info.key, + escrow_account_info.key, + &[], + amount, + token.decimals, + )?; + + invoke_signed( + &transfer_instruction, + &[ + escrow_account_info.clone(), + mint_account_info.clone(), + recipient_ata_account_info.clone(), + escrow_account_info.clone(), + ], + &[hyperlane_token_escrow_pda_seeds!( + token.plugin_data.escrow_bump + )], + )?; + + Ok(()) + } + + /// Returns the accounts required for `transfer_out`. + fn transfer_out_account_metas( + program_id: &Pubkey, + token: &HyperlaneToken, + token_message: &TokenMessage, + ) -> Result<(Vec, bool), ProgramError> { + let ata_payer_account_key = Pubkey::create_program_address( + hyperlane_token_ata_payer_pda_seeds!(token.plugin_data.ata_payer_bump), + program_id, + )?; + + let recipient_associated_token_account = get_associated_token_address_with_program_id( + &Pubkey::new_from_array(token_message.recipient().into()), + &token.plugin_data.mint, + &token.plugin_data.spl_token_program, + ); + + Ok(( + vec![ + AccountMeta::new_readonly(token.plugin_data.spl_token_program, false).into(), + AccountMeta::new_readonly(spl_associated_token_account::id(), false).into(), + AccountMeta::new_readonly(token.plugin_data.mint, false).into(), + AccountMeta::new(recipient_associated_token_account, false).into(), + AccountMeta::new(ata_payer_account_key, false).into(), + AccountMeta::new(token.plugin_data.escrow, false).into(), + ], + // The recipient does not need to be writeable + false, + )) + } +} diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/src/processor.rs b/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/src/processor.rs new file mode 100644 index 00000000000..ba360809609 --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/src/processor.rs @@ -0,0 +1,297 @@ +//! Program processor. + +use account_utils::DiscriminatorDecode; +use hyperlane_sealevel_connection_client::{ + gas_router::GasRouterConfig, router::RemoteRouterConfig, +}; +use hyperlane_sealevel_igp::accounts::InterchainGasPaymasterType; +use hyperlane_sealevel_message_recipient_interface::{ + HandleInstruction, MessageRecipientInstruction, +}; +use hyperlane_sealevel_token_lib::{ + instruction::{ + DymInstruction, Init, Instruction as TokenIxn, TransferRemote, TransferRemoteMemo, + }, + processor::HyperlaneSealevelToken, +}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey}; + +use crate::plugin::CollateralPlugin; + +#[cfg(not(feature = "no-entrypoint"))] +solana_program::entrypoint!(process_instruction); + +/// Processes an instruction. +pub fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + // First, check if the instruction has a discriminant relating to + // the message recipient interface. + if let Ok(message_recipient_instruction) = MessageRecipientInstruction::decode(instruction_data) + { + return match message_recipient_instruction { + MessageRecipientInstruction::InterchainSecurityModule => { + interchain_security_module(program_id, accounts) + } + MessageRecipientInstruction::InterchainSecurityModuleAccountMetas => { + interchain_security_module_account_metas(program_id) + } + MessageRecipientInstruction::Handle(handle) => transfer_from_remote( + program_id, + accounts, + HandleInstruction { + origin: handle.origin, + sender: handle.sender, + message: handle.message, + }, + ), + MessageRecipientInstruction::HandleAccountMetas(handle) => { + transfer_from_remote_account_metas( + program_id, + accounts, + HandleInstruction { + origin: handle.origin, + sender: handle.sender, + message: handle.message, + }, + ) + } + }; + } + if let Ok(instr) = DymInstruction::decode(instruction_data) { + return match instr { + DymInstruction::TransferRemoteMemo(xfer) => { + transfer_remote_memo(program_id, accounts, xfer) + } + } + .map_err(|err| { + msg!("{}", err); + err + }); + } + + // Otherwise, try decoding a "normal" token instruction + match TokenIxn::decode(instruction_data)? { + TokenIxn::Init(init) => initialize(program_id, accounts, init), + TokenIxn::TransferRemote(xfer) => transfer_remote(program_id, accounts, xfer), + TokenIxn::EnrollRemoteRouter(config) => enroll_remote_router(program_id, accounts, config), + TokenIxn::EnrollRemoteRouters(configs) => { + enroll_remote_routers(program_id, accounts, configs) + } + TokenIxn::SetDestinationGasConfigs(configs) => { + set_destination_gas_configs(program_id, accounts, configs) + } + TokenIxn::TransferOwnership(new_owner) => { + transfer_ownership(program_id, accounts, new_owner) + } + TokenIxn::SetInterchainSecurityModule(new_ism) => { + set_interchain_security_module(program_id, accounts, new_ism) + } + TokenIxn::SetInterchainGasPaymaster(new_igp) => { + set_interchain_gas_paymaster(program_id, accounts, new_igp) + } + } + .map_err(|err| { + msg!("{}", err); + err + }) +} + +fn transfer_remote_memo( + program_id: &Pubkey, + accounts: &[AccountInfo], + transfer: TransferRemoteMemo, +) -> ProgramResult { + let base = transfer.base; + let memo = transfer.memo; + HyperlaneSealevelToken::::transfer_remote_memo( + program_id, accounts, base, memo, + ) +} + +/// Initializes the program. +/// +/// Accounts: +/// 0. `[executable]` The system program. +/// 1. `[writable]` The token PDA account. +/// 2. `[writable]` The dispatch authority PDA account. +/// 3. `[signer]` The payer and access control owner of the program. +/// 4. `[executable]` The SPL token program for the mint, i.e. either SPL token program or the 2022 version. +/// 5. `[]` The mint. +/// 6. `[executable]` The Rent sysvar program. +/// 7. `[writable]` The escrow PDA account. +/// 8. `[writable]` The ATA payer PDA account. +fn initialize(program_id: &Pubkey, accounts: &[AccountInfo], init: Init) -> ProgramResult { + HyperlaneSealevelToken::::initialize(program_id, accounts, init) +} + +/// Transfers tokens to a remote. +/// Transfers the collateral token into the escrow PDA account and +/// then dispatches a message to the remote recipient. +/// +/// Accounts: +/// 0. `[executable]` The system program. +/// 1. `[executable]` The spl_noop program. +/// 2. `[]` The token PDA account. +/// 3. `[executable]` The mailbox program. +/// 4. `[writeable]` The mailbox outbox account. +/// 5. `[]` Message dispatch authority. +/// 6. `[signer]` The token sender and mailbox payer. +/// 7. `[signer]` Unique message / gas payment account. +/// 8. `[writeable]` Message storage PDA. +/// ---- If using an IGP ---- +/// 9. `[executable]` The IGP program. +/// 10. `[writeable]` The IGP program data. +/// 11. `[writeable]` Gas payment PDA. +/// 12. `[]` OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. +/// 13. `[writeable]` The IGP account. +/// ---- End if ---- +/// 14. `[executable]` The SPL token program for the mint. +/// 15. `[writeable]` The mint. +/// 16. `[writeable]` The token sender's associated token account, from which tokens will be sent. +/// 17. `[writeable]` The escrow PDA account. +fn transfer_remote( + program_id: &Pubkey, + accounts: &[AccountInfo], + transfer: TransferRemote, +) -> ProgramResult { + HyperlaneSealevelToken::::transfer_remote(program_id, accounts, transfer) +} + +// Accounts: +// 0. `[signer]` Mailbox process authority specific to this program. +// 1. `[executable]` system_program +// 2. `[]` hyperlane_token storage +// 3. `[]` recipient wallet address +// 4. `[executable]` SPL token 2022 program. +// 5. `[executable]` SPL associated token account. +// 6. `[writeable]` Mint account. +// 7. `[writeable]` Recipient associated token account. +// 8. `[writeable]` ATA payer PDA account. +// 9. `[writeable]` Escrow account. +fn transfer_from_remote( + program_id: &Pubkey, + accounts: &[AccountInfo], + transfer: HandleInstruction, +) -> ProgramResult { + HyperlaneSealevelToken::::transfer_from_remote(program_id, accounts, transfer) +} + +/// Gets the account metas for a `transfer_from_remote` instruction. +/// +/// Accounts: +/// None +fn transfer_from_remote_account_metas( + program_id: &Pubkey, + accounts: &[AccountInfo], + transfer: HandleInstruction, +) -> ProgramResult { + HyperlaneSealevelToken::::transfer_from_remote_account_metas( + program_id, accounts, transfer, + ) +} + +/// Enrolls a remote router. +/// +/// Accounts: +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. +fn enroll_remote_router( + program_id: &Pubkey, + accounts: &[AccountInfo], + config: RemoteRouterConfig, +) -> ProgramResult { + HyperlaneSealevelToken::::enroll_remote_router(program_id, accounts, config) +} + +/// Enrolls remote routers. +/// +/// Accounts: +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. +fn enroll_remote_routers( + program_id: &Pubkey, + accounts: &[AccountInfo], + configs: Vec, +) -> ProgramResult { + HyperlaneSealevelToken::::enroll_remote_routers(program_id, accounts, configs) +} + +/// Sets the destination gas configs. +/// +/// Accounts: +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. +fn set_destination_gas_configs( + program_id: &Pubkey, + accounts: &[AccountInfo], + configs: Vec, +) -> ProgramResult { + HyperlaneSealevelToken::::set_destination_gas_configs( + program_id, accounts, configs, + ) +} + +/// Transfers ownership. +/// +/// Accounts: +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The current owner. +fn transfer_ownership( + program_id: &Pubkey, + accounts: &[AccountInfo], + new_owner: Option, +) -> ProgramResult { + HyperlaneSealevelToken::::transfer_ownership(program_id, accounts, new_owner) +} + +/// Gets the interchain security module, returning it as a serialized Option. +/// +/// Accounts: +/// 0. `[]` The token PDA account. +fn interchain_security_module(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { + HyperlaneSealevelToken::::interchain_security_module(program_id, accounts) +} + +/// Gets the account metas for getting the interchain security module. +/// +/// Accounts: +/// None +fn interchain_security_module_account_metas(program_id: &Pubkey) -> ProgramResult { + HyperlaneSealevelToken::::interchain_security_module_account_metas(program_id) +} + +/// Lets the owner set the interchain security module. +/// +/// Accounts: +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The access control owner. +fn set_interchain_security_module( + program_id: &Pubkey, + accounts: &[AccountInfo], + new_ism: Option, +) -> ProgramResult { + HyperlaneSealevelToken::::set_interchain_security_module( + program_id, accounts, new_ism, + ) +} + +/// Lets the owner set the interchain gas paymaster. +/// +/// Accounts: +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The access control owner. +fn set_interchain_gas_paymaster( + program_id: &Pubkey, + accounts: &[AccountInfo], + new_igp: Option<(Pubkey, InterchainGasPaymasterType)>, +) -> ProgramResult { + HyperlaneSealevelToken::::set_interchain_gas_paymaster( + program_id, accounts, new_igp, + ) +} diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/tests/functional.rs b/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/tests/functional.rs new file mode 100644 index 00000000000..a2ad7efeb00 --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-collateral-memo/tests/functional.rs @@ -0,0 +1,1809 @@ +//! Contains functional tests for things that cannot be done +//! strictly in unit tests. This includes CPIs, like creating +//! new PDA accounts. + +use account_utils::DiscriminatorEncode; +use hyperlane_core::{Encode, HyperlaneMessage, H256, U256}; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + program_pack::Pack, + pubkey, + pubkey::Pubkey, + rent::Rent, + system_instruction, +}; +use std::collections::HashMap; + +use hyperlane_sealevel_connection_client::{ + gas_router::GasRouterConfig, router::RemoteRouterConfig, +}; +use hyperlane_sealevel_igp::{ + accounts::{GasPaymentAccount, GasPaymentData, InterchainGasPaymasterType}, + igp_gas_payment_pda_seeds, +}; +use hyperlane_sealevel_mailbox::{ + accounts::{DispatchedMessage, DispatchedMessageAccount}, + mailbox_dispatched_message_pda_seeds, mailbox_message_dispatch_authority_pda_seeds, + mailbox_process_authority_pda_seeds, + protocol_fee::ProtocolFee, +}; +use hyperlane_sealevel_message_recipient_interface::{ + HandleInstruction, MessageRecipientInstruction, +}; +use hyperlane_sealevel_token_collateral::{ + hyperlane_token_ata_payer_pda_seeds, hyperlane_token_escrow_pda_seeds, plugin::CollateralPlugin, +}; +use hyperlane_sealevel_token_collateral_memo::processor::process_instruction; +use hyperlane_sealevel_token_lib::{ + accounts::{convert_decimals, HyperlaneToken, HyperlaneTokenAccount}, + hyperlane_token_pda_seeds, + instruction::{ + DymInstruction, Init, Instruction as HyperlaneTokenInstruction, TransferRemote, + TransferRemoteMemo, + }, +}; +use hyperlane_test_utils::{ + assert_token_balance, assert_transaction_error, igp_program_id, initialize_igp_accounts, + initialize_mailbox, mailbox_id, new_funded_keypair, process, transfer_lamports, IgpAccounts, +}; +use hyperlane_warp_route::TokenMessage; +use solana_program_test::*; +use solana_sdk::{ + instruction::InstructionError, + signature::Signer, + signer::keypair::Keypair, + transaction::{Transaction, TransactionError}, +}; +use spl_associated_token_account::instruction::create_associated_token_account_idempotent; +use spl_token_2022::instruction::initialize_mint2; + +/// There are 1e9 lamports in one SOL. +const ONE_SOL_IN_LAMPORTS: u64 = 1000000000; +const LOCAL_DOMAIN: u32 = 1234; +const LOCAL_DECIMALS: u8 = 8; +const LOCAL_DECIMALS_U32: u32 = LOCAL_DECIMALS as u32; +const REMOTE_DOMAIN: u32 = 4321; +const REMOTE_DECIMALS: u8 = 18; +const REMOTE_GAS_AMOUNT: u64 = 200000; +// Same for spl_token_2022 and spl_token +const MINT_ACCOUNT_LEN: usize = spl_token_2022::state::Mint::LEN; + +fn hyperlane_sealevel_token_collateral_id() -> Pubkey { + pubkey!("G8t1qe3YnYvhi1zS9ioUXuVFkwhBgvfHaLJt5X6PF18z") +} + +async fn setup_client() -> (BanksClient, Keypair) { + let program_id = hyperlane_sealevel_token_collateral_id(); + let mut program_test = ProgramTest::new( + "hyperlane_sealevel_token_collateral", + program_id, + processor!(process_instruction), + ); + + program_test.add_program( + "spl_token_2022", + spl_token_2022::id(), + processor!(spl_token_2022::processor::Processor::process), + ); + + program_test.add_program( + "spl_token", + spl_token::id(), + processor!(spl_token::processor::Processor::process), + ); + + program_test.add_program( + "spl_associated_token_account", + spl_associated_token_account::id(), + processor!(spl_associated_token_account::processor::process_instruction), + ); + + program_test.add_program("spl_noop", spl_noop::id(), processor!(spl_noop::noop)); + + let mailbox_program_id = mailbox_id(); + program_test.add_program( + "hyperlane_sealevel_mailbox", + mailbox_program_id, + processor!(hyperlane_sealevel_mailbox::processor::process_instruction), + ); + + program_test.add_program( + "hyperlane_sealevel_igp", + igp_program_id(), + processor!(hyperlane_sealevel_igp::processor::process_instruction), + ); + + // This serves as the default ISM on the Mailbox + program_test.add_program( + "hyperlane_sealevel_test_ism", + hyperlane_sealevel_test_ism::id(), + processor!(hyperlane_sealevel_test_ism::program::process_instruction), + ); + + let (banks_client, payer, _recent_blockhash) = program_test.start().await; + + (banks_client, payer) +} + +async fn initialize_mint( + banks_client: &mut BanksClient, + payer: &Keypair, + decimals: u8, + spl_token_program: &Pubkey, +) -> (Pubkey, Keypair) { + let mint = Keypair::new(); + let mint_authority = new_funded_keypair(banks_client, payer, ONE_SOL_IN_LAMPORTS).await; + + let payer_pubkey = payer.pubkey(); + let mint_pubkey = mint.pubkey(); + let mint_authority_pubkey = mint_authority.pubkey(); + + let init_mint_instruction = initialize_mint2( + spl_token_program, + &mint_pubkey, + &mint_authority_pubkey, + // No freeze authority + None, + decimals, + ) + .unwrap(); + + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[ + system_instruction::create_account( + &payer_pubkey, + &mint_pubkey, + Rent::default().minimum_balance(MINT_ACCOUNT_LEN), + MINT_ACCOUNT_LEN.try_into().unwrap(), + spl_token_program, + ), + init_mint_instruction, + ], + Some(&payer_pubkey), + &[payer, &mint], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await.unwrap(); + + (mint_pubkey, mint_authority) +} + +async fn mint_to( + banks_client: &mut BanksClient, + spl_token_program_id: &Pubkey, + mint: &Pubkey, + mint_authority: &Keypair, + recipient_account: &Pubkey, + amount: u64, +) { + let mint_instruction = spl_token_2022::instruction::mint_to( + spl_token_program_id, + mint, + recipient_account, + &mint_authority.pubkey(), + &[], + amount, + ) + .unwrap(); + + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[mint_instruction], + Some(&mint_authority.pubkey()), + &[mint_authority], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await.unwrap(); +} + +async fn create_and_mint_to_ata( + banks_client: &mut BanksClient, + spl_token_program_id: &Pubkey, + mint: &Pubkey, + mint_authority: &Keypair, + payer: &Keypair, + recipient_wallet: &Pubkey, + amount: u64, +) -> Pubkey { + let recipient_associated_token_account = + spl_associated_token_account::get_associated_token_address_with_program_id( + recipient_wallet, + mint, + spl_token_program_id, + ); + + // Create and init (this does both) associated token account if necessary. + let create_ata_instruction = create_associated_token_account_idempotent( + &payer.pubkey(), + recipient_wallet, + mint, + spl_token_program_id, + ); + + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[create_ata_instruction], + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await.unwrap(); + + // Mint tokens to the associated token account. + if amount > 0 { + mint_to( + banks_client, + spl_token_program_id, + mint, + mint_authority, + &recipient_associated_token_account, + amount, + ) + .await; + } + + recipient_associated_token_account +} + +struct HyperlaneTokenAccounts { + token: Pubkey, + token_bump: u8, + mailbox_process_authority: Pubkey, + dispatch_authority: Pubkey, + dispatch_authority_bump: u8, + escrow: Pubkey, + escrow_bump: u8, + ata_payer: Pubkey, + ata_payer_bump: u8, +} + +async fn initialize_hyperlane_token( + program_id: &Pubkey, + banks_client: &mut BanksClient, + payer: &Keypair, + igp_accounts: Option<&IgpAccounts>, + mint: &Pubkey, + spl_token_program: &Pubkey, +) -> Result { + let (mailbox_process_authority_key, _mailbox_process_authority_bump) = + Pubkey::find_program_address( + mailbox_process_authority_pda_seeds!(program_id), + &mailbox_id(), + ); + + let (token_account_key, token_account_bump_seed) = + Pubkey::find_program_address(hyperlane_token_pda_seeds!(), program_id); + + let (dispatch_authority_key, dispatch_authority_seed) = + Pubkey::find_program_address(mailbox_message_dispatch_authority_pda_seeds!(), program_id); + + let (escrow_account_key, escrow_account_bump_seed) = + Pubkey::find_program_address(hyperlane_token_escrow_pda_seeds!(), program_id); + + let (ata_payer_account_key, ata_payer_account_bump_seed) = + Pubkey::find_program_address(hyperlane_token_ata_payer_pda_seeds!(), program_id); + + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + *program_id, + &HyperlaneTokenInstruction::Init(Init { + mailbox: mailbox_id(), + interchain_security_module: None, + interchain_gas_paymaster: igp_accounts.map(|igp_accounts| { + ( + igp_accounts.program, + InterchainGasPaymasterType::OverheadIgp(igp_accounts.overhead_igp), + ) + }), + decimals: LOCAL_DECIMALS, + remote_decimals: REMOTE_DECIMALS, + }) + .encode() + .unwrap(), + vec![ + // 0. `[executable]` The system program. + // 1. `[writable]` The token PDA account. + // 2. `[writable]` The dispatch authority PDA account. + // 3. `[signer]` The payer. + // 4. `[executable]` The SPL token program for the mint, i.e. either SPL token program or the 2022 version. + // 5. `[]` The mint. + // 6. `[executable]` The Rent sysvar program. + // 7. `[writable]` The escrow PDA account. + // 8. `[writable]` The ATA payer PDA account. + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(token_account_key, false), + AccountMeta::new(dispatch_authority_key, false), + AccountMeta::new_readonly(payer.pubkey(), true), + AccountMeta::new_readonly(*spl_token_program, false), + AccountMeta::new(*mint, false), + AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false), + AccountMeta::new(escrow_account_key, false), + AccountMeta::new(ata_payer_account_key, false), + ], + )], + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await?; + + // Set destination gas configs + set_destination_gas_config( + banks_client, + program_id, + payer, + &token_account_key, + REMOTE_DOMAIN, + REMOTE_GAS_AMOUNT, + ) + .await?; + + Ok(HyperlaneTokenAccounts { + token: token_account_key, + token_bump: token_account_bump_seed, + mailbox_process_authority: mailbox_process_authority_key, + dispatch_authority: dispatch_authority_key, + dispatch_authority_bump: dispatch_authority_seed, + escrow: escrow_account_key, + escrow_bump: escrow_account_bump_seed, + ata_payer: ata_payer_account_key, + ata_payer_bump: ata_payer_account_bump_seed, + }) +} + +async fn enroll_remote_router( + banks_client: &mut BanksClient, + program_id: &Pubkey, + payer: &Keypair, + token_account: &Pubkey, + domain: u32, + router: H256, +) -> Result<(), BanksClientError> { + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + // Enroll the remote router + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + *program_id, + &HyperlaneTokenInstruction::EnrollRemoteRouter(RemoteRouterConfig { + domain, + router: Some(router), + }) + .encode() + .unwrap(), + vec![ + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(*token_account, false), + AccountMeta::new_readonly(payer.pubkey(), true), + ], + )], + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await?; + + Ok(()) +} + +async fn set_destination_gas_config( + banks_client: &mut BanksClient, + program_id: &Pubkey, + payer: &Keypair, + token_account: &Pubkey, + domain: u32, + gas: u64, +) -> Result<(), BanksClientError> { + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + // Enroll the remote router + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + *program_id, + &HyperlaneTokenInstruction::SetDestinationGasConfigs(vec![GasRouterConfig { + domain, + gas: Some(gas), + }]) + .encode() + .unwrap(), + vec![ + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(*token_account, false), + AccountMeta::new_readonly(payer.pubkey(), true), + ], + )], + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await?; + + Ok(()) +} + +#[tokio::test] +async fn test_initialize() { + let program_id = hyperlane_sealevel_token_collateral_id(); + let mailbox_program_id = mailbox_id(); + let spl_token_program_id = spl_token_2022::id(); + + let (mut banks_client, payer) = setup_client().await; + + let mailbox_accounts = initialize_mailbox( + &mut banks_client, + &mailbox_program_id, + &payer, + LOCAL_DOMAIN, + ONE_SOL_IN_LAMPORTS, + ProtocolFee::default(), + ) + .await + .unwrap(); + + let igp_accounts = + initialize_igp_accounts(&mut banks_client, &igp_program_id(), &payer, REMOTE_DOMAIN) + .await + .unwrap(); + + let (mint, _mint_authority) = initialize_mint( + &mut banks_client, + &payer, + LOCAL_DECIMALS, + &spl_token_program_id, + ) + .await; + + let hyperlane_token_accounts = initialize_hyperlane_token( + &program_id, + &mut banks_client, + &payer, + Some(&igp_accounts), + &mint, + &spl_token_program_id, + ) + .await + .unwrap(); + + // Get the token account. + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!( + token, + Box::new(HyperlaneToken { + bump: hyperlane_token_accounts.token_bump, + mailbox: mailbox_accounts.program, + mailbox_process_authority: hyperlane_token_accounts.mailbox_process_authority, + dispatch_authority_bump: hyperlane_token_accounts.dispatch_authority_bump, + decimals: LOCAL_DECIMALS, + remote_decimals: REMOTE_DECIMALS, + owner: Some(payer.pubkey()), + interchain_security_module: None, + interchain_gas_paymaster: Some(( + igp_accounts.program, + InterchainGasPaymasterType::OverheadIgp(igp_accounts.overhead_igp), + )), + destination_gas: HashMap::from([(REMOTE_DOMAIN, REMOTE_GAS_AMOUNT)]), + remote_routers: HashMap::new(), + plugin_data: CollateralPlugin { + spl_token_program: spl_token_2022::id(), + mint, + escrow: hyperlane_token_accounts.escrow, + escrow_bump: hyperlane_token_accounts.escrow_bump, + ata_payer_bump: hyperlane_token_accounts.ata_payer_bump, + }, + }), + ); + + // Verify the escrow account was created. + let escrow_account = banks_client + .get_account(hyperlane_token_accounts.escrow) + .await + .unwrap() + .unwrap(); + assert_eq!(escrow_account.owner, spl_token_2022::id()); + assert!(!escrow_account.data.is_empty()); + + // Verify the ATA payer account was created. + let ata_payer_account = banks_client + .get_account(hyperlane_token_accounts.ata_payer) + .await + .unwrap() + .unwrap(); + assert!(ata_payer_account.lamports > 0); +} + +#[tokio::test] +async fn test_initialize_errors_if_called_twice() { + let program_id = hyperlane_sealevel_token_collateral_id(); + let spl_token_program_id = spl_token_2022::id(); + + let (mut banks_client, payer) = setup_client().await; + + let (mint, mint_authority) = initialize_mint( + &mut banks_client, + &payer, + LOCAL_DECIMALS, + &spl_token_program_id, + ) + .await; + + initialize_hyperlane_token( + &program_id, + &mut banks_client, + &payer, + None, + &mint, + &spl_token_program_id, + ) + .await + .unwrap(); + + // To ensure a different signature is used, we'll use a different payer + let init_result = initialize_hyperlane_token( + &program_id, + &mut banks_client, + &mint_authority, + None, + &mint, + &spl_token_program_id, + ) + .await; + + assert_transaction_error( + init_result, + TransactionError::InstructionError(0, InstructionError::AccountAlreadyInitialized), + ); +} + +async fn test_transfer_remote_memo(spl_token_program_id: Pubkey) { + let program_id = hyperlane_sealevel_token_collateral_id(); + let mailbox_program_id = mailbox_id(); + + let (mut banks_client, payer) = setup_client().await; + + let mailbox_accounts = initialize_mailbox( + &mut banks_client, + &mailbox_program_id, + &payer, + LOCAL_DOMAIN, + ONE_SOL_IN_LAMPORTS, + ProtocolFee::default(), + ) + .await + .unwrap(); + + let igp_accounts = + initialize_igp_accounts(&mut banks_client, &igp_program_id(), &payer, REMOTE_DOMAIN) + .await + .unwrap(); + + let (mint, mint_authority) = initialize_mint( + &mut banks_client, + &payer, + LOCAL_DECIMALS, + &spl_token_program_id, + ) + .await; + + let hyperlane_token_accounts = initialize_hyperlane_token( + &program_id, + &mut banks_client, + &payer, + Some(&igp_accounts), + &mint, + &spl_token_program_id, + ) + .await + .unwrap(); + + // Enroll the remote router + let remote_router = H256::random(); + enroll_remote_router( + &mut banks_client, + &program_id, + &payer, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + remote_router, + ) + .await + .unwrap(); + + let token_sender = new_funded_keypair(&mut banks_client, &payer, ONE_SOL_IN_LAMPORTS).await; + let token_sender_pubkey = token_sender.pubkey(); + // Mint 100 tokens to the token sender's ATA + let token_sender_ata = create_and_mint_to_ata( + &mut banks_client, + &spl_token_program_id, + &mint, + &mint_authority, + &payer, + &token_sender_pubkey, + 100 * 10u64.pow(LOCAL_DECIMALS_U32), + ) + .await; + + // Call transfer_remote + let unique_message_account_keypair = Keypair::new(); + let (dispatched_message_key, _dispatched_message_bump) = Pubkey::find_program_address( + mailbox_dispatched_message_pda_seeds!(&unique_message_account_keypair.pubkey()), + &mailbox_program_id, + ); + let (gas_payment_pda_key, _gas_payment_pda_bump) = Pubkey::find_program_address( + igp_gas_payment_pda_seeds!(&unique_message_account_keypair.pubkey()), + &igp_program_id(), + ); + + let remote_token_recipient = H256::random(); + // Transfer 69 tokens. + let transfer_amount = 69 * 10u64.pow(LOCAL_DECIMALS_U32); + let remote_transfer_amount = + convert_decimals(transfer_amount.into(), LOCAL_DECIMALS, REMOTE_DECIMALS).unwrap(); + + let test_memo = vec![1, 2, 3]; + + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &DymInstruction::TransferRemoteMemo(TransferRemoteMemo { + base: TransferRemote { + destination_domain: REMOTE_DOMAIN, + recipient: remote_token_recipient, + amount_or_id: transfer_amount.into(), + }, + memo: test_memo.clone(), + }) + .encode() + .unwrap(), + // 0. `[executable]` The system program. + // 1. `[executable]` The spl_noop program. + // 2. `[]` The token PDA account. + // 3. `[executable]` The mailbox program. + // 4. `[writeable]` The mailbox outbox account. + // 5. `[]` Message dispatch authority. + // 6. `[signer]` The token sender and mailbox payer. + // 7. `[signer]` Unique message account. + // 8. `[writeable]` Message storage PDA. + // ---- If using an IGP ---- + // 9. `[executable]` The IGP program. + // 10. `[writeable]` The IGP program data. + // 11. `[writeable]` Gas payment PDA. + // 12. `[]` OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. + // 13. `[writeable]` The IGP account. + // ---- End if ---- + // 14. `[executable]` The spl_token_2022 program. + // 15. `[writeable]` The mint. + // 16. `[writeable]` The token sender's associated token account, from which tokens will be sent. + // 17. `[writeable]` The escrow PDA account. + vec![ + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new_readonly(spl_noop::id(), false), + AccountMeta::new_readonly(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(mailbox_accounts.program, false), + AccountMeta::new(mailbox_accounts.outbox, false), + AccountMeta::new_readonly(hyperlane_token_accounts.dispatch_authority, false), + AccountMeta::new_readonly(token_sender_pubkey, true), + AccountMeta::new_readonly(unique_message_account_keypair.pubkey(), true), + AccountMeta::new(dispatched_message_key, false), + AccountMeta::new_readonly(igp_accounts.program, false), + AccountMeta::new(igp_accounts.program_data, false), + AccountMeta::new(gas_payment_pda_key, false), + AccountMeta::new_readonly(igp_accounts.overhead_igp, false), + AccountMeta::new(igp_accounts.igp, false), + AccountMeta::new_readonly(spl_token_2022::id(), false), + AccountMeta::new(mint, false), + AccountMeta::new(token_sender_ata, false), + AccountMeta::new(hyperlane_token_accounts.escrow, false), + ], + )], + Some(&token_sender_pubkey), + &[&token_sender, &unique_message_account_keypair], + recent_blockhash, + ); + let tx_signature = transaction.signatures[0]; + banks_client.process_transaction(transaction).await.unwrap(); + + // Verify the token sender's ATA balance is 31 full tokens. + assert_token_balance( + &mut banks_client, + &token_sender_ata, + 31 * 10u64.pow(LOCAL_DECIMALS_U32), + ) + .await; + + // And that the escrow's balance is 69 tokens. + assert_token_balance( + &mut banks_client, + &hyperlane_token_accounts.escrow, + 69 * 10u64.pow(LOCAL_DECIMALS_U32), + ) + .await; + + // And let's take a look at the dispatched message account data to verify the message looks right. + let dispatched_message_account_data = banks_client + .get_account(dispatched_message_key) + .await + .unwrap() + .unwrap() + .data; + let dispatched_message = + DispatchedMessageAccount::fetch(&mut &dispatched_message_account_data[..]) + .unwrap() + .into_inner(); + + let transfer_remote_tx_status = banks_client + .get_transaction_status(tx_signature) + .await + .unwrap() + .unwrap(); + + let message = HyperlaneMessage { + version: 3, + nonce: 0, + origin: LOCAL_DOMAIN, + sender: program_id.to_bytes().into(), + destination: REMOTE_DOMAIN, + recipient: remote_router, + // Expect the remote_transfer_amount to be in the message. + body: TokenMessage::new(remote_token_recipient, remote_transfer_amount, test_memo).to_vec(), + }; + + assert_eq!( + dispatched_message, + Box::new(DispatchedMessage::new( + message.nonce, + transfer_remote_tx_status.slot, + unique_message_account_keypair.pubkey(), + message.to_vec(), + )), + ); + + // And let's also look at the gas payment account to verify the gas payment looks right. + let gas_payment_account_data = banks_client + .get_account(gas_payment_pda_key) + .await + .unwrap() + .unwrap() + .data; + let gas_payment = GasPaymentAccount::fetch(&mut &gas_payment_account_data[..]) + .unwrap() + .into_inner(); + + assert_eq!( + *gas_payment, + GasPaymentData { + sequence_number: 0, + igp: igp_accounts.igp, + destination_domain: REMOTE_DOMAIN, + message_id: message.id(), + gas_amount: REMOTE_GAS_AMOUNT, + unique_gas_payment_pubkey: unique_message_account_keypair.pubkey(), + slot: transfer_remote_tx_status.slot, + payment: REMOTE_GAS_AMOUNT + } + .into(), + ); +} + +// Test transfer_remote with spl_token +#[tokio::test] +async fn test_transfer_remote_spl_token() { + test_transfer_remote_memo(spl_token_2022::id()).await; +} + +// Test transfer_remote with spl_token_2022 +#[tokio::test] +async fn test_transfer_remote_spl_token_2022() { + test_transfer_remote_memo(spl_token_2022::id()).await; +} + +async fn transfer_from_remote( + initial_escrow_balance: u64, + remote_transfer_amount: U256, + sender_override: Option, + origin_override: Option, + spl_token_program_id: Pubkey, +) -> Result<(BanksClient, HyperlaneTokenAccounts, Pubkey), BanksClientError> { + let program_id = hyperlane_sealevel_token_collateral_id(); + let mailbox_program_id = mailbox_id(); + + let (mut banks_client, payer) = setup_client().await; + + let mailbox_accounts = initialize_mailbox( + &mut banks_client, + &mailbox_program_id, + &payer, + LOCAL_DOMAIN, + ONE_SOL_IN_LAMPORTS, + ProtocolFee::default(), + ) + .await + .unwrap(); + + let igp_accounts = + initialize_igp_accounts(&mut banks_client, &igp_program_id(), &payer, REMOTE_DOMAIN) + .await + .unwrap(); + + let (mint, mint_authority) = initialize_mint( + &mut banks_client, + &payer, + LOCAL_DECIMALS, + &spl_token_program_id, + ) + .await; + + let hyperlane_token_accounts = initialize_hyperlane_token( + &program_id, + &mut banks_client, + &payer, + Some(&igp_accounts), + &mint, + &spl_token_program_id, + ) + .await + .unwrap(); + // ATA payer must have a balance to create new ATAs + transfer_lamports( + &mut banks_client, + &payer, + &hyperlane_token_accounts.ata_payer, + ONE_SOL_IN_LAMPORTS, + ) + .await; + + // Enroll the remote router + let remote_router = H256::random(); + enroll_remote_router( + &mut banks_client, + &program_id, + &payer, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + remote_router, + ) + .await + .unwrap(); + + // Give an initial balance to the escrow account which will be used by the + // transfer_from_remote. + mint_to( + &mut banks_client, + &spl_token_program_id, + &mint, + &mint_authority, + &hyperlane_token_accounts.escrow, + initial_escrow_balance, + ) + .await; + + let recipient_pubkey = Pubkey::new_unique(); + let recipient: H256 = recipient_pubkey.to_bytes().into(); + + let recipient_associated_token_account = + spl_associated_token_account::get_associated_token_address_with_program_id( + &recipient_pubkey, + &mint, + &spl_token_program_id, + ); + + let message = HyperlaneMessage { + version: 3, + nonce: 0, + origin: origin_override.unwrap_or(REMOTE_DOMAIN), + // Default to the remote router as the sender + sender: sender_override.unwrap_or(remote_router), + destination: LOCAL_DOMAIN, + recipient: program_id.to_bytes().into(), + body: TokenMessage::new(recipient, remote_transfer_amount, vec![]).to_vec(), + }; + + process( + &mut banks_client, + &payer, + &mailbox_accounts, + vec![], + &message, + ) + .await?; + + Ok(( + banks_client, + hyperlane_token_accounts, + recipient_associated_token_account, + )) +} + +// Tests when the SPL token is the non-2022 version +#[tokio::test] +async fn test_transfer_from_remote_spl_token() { + let initial_escrow_balance = 100 * 10u64.pow(LOCAL_DECIMALS_U32); + let local_transfer_amount = 69 * 10u64.pow(LOCAL_DECIMALS_U32); + let remote_transfer_amount = convert_decimals( + local_transfer_amount.into(), + LOCAL_DECIMALS, + REMOTE_DECIMALS, + ) + .unwrap(); + + let (mut banks_client, hyperlane_token_accounts, recipient_associated_token_account) = + transfer_from_remote( + initial_escrow_balance, + remote_transfer_amount, + None, + None, + spl_token::id(), + ) + .await + .unwrap(); + + // Check that the recipient's ATA got the tokens! + assert_token_balance( + &mut banks_client, + &recipient_associated_token_account, + local_transfer_amount, + ) + .await; + + // And that the escrow's balance is lower because it was spent in the transfer. + assert_token_balance( + &mut banks_client, + &hyperlane_token_accounts.escrow, + initial_escrow_balance - local_transfer_amount, + ) + .await; +} + +// Tests when the SPL token is the 2022 version +#[tokio::test] +async fn test_transfer_from_remote_spl_token_2022() { + let initial_escrow_balance = 100 * 10u64.pow(LOCAL_DECIMALS_U32); + let local_transfer_amount = 69 * 10u64.pow(LOCAL_DECIMALS_U32); + let remote_transfer_amount = convert_decimals( + local_transfer_amount.into(), + LOCAL_DECIMALS, + REMOTE_DECIMALS, + ) + .unwrap(); + + let (mut banks_client, hyperlane_token_accounts, recipient_associated_token_account) = + transfer_from_remote( + initial_escrow_balance, + remote_transfer_amount, + None, + None, + spl_token_2022::id(), + ) + .await + .unwrap(); + + // Check that the recipient's ATA got the tokens! + assert_token_balance( + &mut banks_client, + &recipient_associated_token_account, + local_transfer_amount, + ) + .await; + + // And that the escrow's balance is lower because it was spent in the transfer. + assert_token_balance( + &mut banks_client, + &hyperlane_token_accounts.escrow, + initial_escrow_balance - local_transfer_amount, + ) + .await; +} + +#[tokio::test] +async fn test_transfer_from_remote_errors_if_sender_not_router() { + let initial_escrow_balance = 100 * 10u64.pow(LOCAL_DECIMALS_U32); + let local_transfer_amount = 69 * 10u64.pow(LOCAL_DECIMALS_U32); + let remote_transfer_amount = convert_decimals( + local_transfer_amount.into(), + LOCAL_DECIMALS, + REMOTE_DECIMALS, + ) + .unwrap(); + + // Same remote domain origin, but wrong sender. + let result = transfer_from_remote( + initial_escrow_balance, + remote_transfer_amount, + Some(H256::random()), + None, + spl_token_2022::id(), + ) + .await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidInstructionData), + ); + + // Wrong remote domain origin, but correct sender. + let result = transfer_from_remote( + initial_escrow_balance, + remote_transfer_amount, + None, + Some(REMOTE_DOMAIN + 1), + spl_token_2022::id(), + ) + .await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidInstructionData), + ); +} + +#[tokio::test] +async fn test_transfer_from_remote_errors_if_process_authority_not_signer() { + let program_id = hyperlane_sealevel_token_collateral_id(); + let mailbox_program_id = mailbox_id(); + let spl_token_program_id = spl_token_2022::id(); + + let (mut banks_client, payer) = setup_client().await; + + let _mailbox_accounts = initialize_mailbox( + &mut banks_client, + &mailbox_program_id, + &payer, + LOCAL_DOMAIN, + ONE_SOL_IN_LAMPORTS, + ProtocolFee::default(), + ) + .await + .unwrap(); + + let (mint, _mint_authority) = initialize_mint( + &mut banks_client, + &payer, + LOCAL_DECIMALS, + &spl_token_program_id, + ) + .await; + + let hyperlane_token_accounts = initialize_hyperlane_token( + &program_id, + &mut banks_client, + &payer, + None, + &mint, + &spl_token_program_id, + ) + .await + .unwrap(); + + // Enroll the remote router + let remote_router = H256::random(); + enroll_remote_router( + &mut banks_client, + &program_id, + &payer, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + remote_router, + ) + .await + .unwrap(); + + let recipient_pubkey = Pubkey::new_unique(); + let recipient: H256 = recipient_pubkey.to_bytes().into(); + + let recipient_associated_token_account = + spl_associated_token_account::get_associated_token_address_with_program_id( + &recipient_pubkey, + &mint, + &spl_token_2022::id(), + ); + + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + // Try calling directly into the message handler, skipping the mailbox. + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &MessageRecipientInstruction::Handle(HandleInstruction { + origin: REMOTE_DOMAIN, + sender: remote_router, + message: TokenMessage::new(recipient, 12345u64.into(), vec![]).to_vec(), + }) + .encode() + .unwrap(), + vec![ + // Recipient.handle accounts + // 0. `[signer]` Mailbox process authority + // 1. `[executable]` system_program + // 2. `[]` hyperlane_token storage + // 3. `[]` recipient wallet address + // 4. `[executable]` SPL token 2022 program. + // 5. `[executable]` SPL associated token account. + // 6. `[writeable]` Mint account. + // 7. `[writeable]` Recipient associated token account. + // 8. `[writeable]` ATA payer PDA account. + // 9. `[writeable]` Escrow account. + AccountMeta::new_readonly( + hyperlane_token_accounts.mailbox_process_authority, + false, + ), + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new_readonly(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(recipient_pubkey, false), + AccountMeta::new_readonly(spl_token_2022::id(), false), + AccountMeta::new_readonly(spl_associated_token_account::id(), false), + AccountMeta::new(mint, false), + AccountMeta::new(recipient_associated_token_account, false), + AccountMeta::new(hyperlane_token_accounts.ata_payer, false), + AccountMeta::new(hyperlane_token_accounts.escrow, false), + ], + )], + Some(&payer.pubkey()), + &[&payer], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature), + ); +} + +#[tokio::test] +async fn test_enroll_remote_router() { + let program_id = hyperlane_sealevel_token_collateral_id(); + let spl_token_program_id = spl_token_2022::id(); + + let (mut banks_client, payer) = setup_client().await; + + let (mint, _mint_authority) = initialize_mint( + &mut banks_client, + &payer, + LOCAL_DECIMALS, + &spl_token_program_id, + ) + .await; + + let hyperlane_token_accounts = initialize_hyperlane_token( + &program_id, + &mut banks_client, + &payer, + None, + &mint, + &spl_token_program_id, + ) + .await + .unwrap(); + + // Enroll the remote router + let remote_router = H256::random(); + enroll_remote_router( + &mut banks_client, + &program_id, + &payer, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + remote_router, + ) + .await + .unwrap(); + + // Verify the remote router was enrolled. + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!( + token.remote_routers, + vec![(REMOTE_DOMAIN, remote_router)].into_iter().collect(), + ); +} + +#[tokio::test] +async fn test_enroll_remote_router_errors_if_not_signed_by_owner() { + let program_id = hyperlane_sealevel_token_collateral_id(); + let spl_token_program_id = spl_token_2022::id(); + + let (mut banks_client, payer) = setup_client().await; + + let (mint, mint_authority) = initialize_mint( + &mut banks_client, + &payer, + LOCAL_DECIMALS, + &spl_token_program_id, + ) + .await; + + let hyperlane_token_accounts = initialize_hyperlane_token( + &program_id, + &mut banks_client, + &payer, + None, + &mint, + &spl_token_program_id, + ) + .await + .unwrap(); + + // Use the mint authority as the payer, which has a balance but is not the owner, + // so we expect this to fail. + let result = enroll_remote_router( + &mut banks_client, + &program_id, + &mint_authority, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + H256::random(), + ) + .await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidArgument), + ); + + // Also try using the mint authority as the payer and specifying the correct + // owner account, but the owner isn't a signer: + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + // Enroll the remote router + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::EnrollRemoteRouter(RemoteRouterConfig { + domain: REMOTE_DOMAIN, + router: Some(H256::random()), + }) + .encode() + .unwrap(), + vec![ + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), false), + ], + )], + Some(&mint_authority.pubkey()), + &[&mint_authority], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature), + ); +} + +#[tokio::test] +async fn test_set_destination_gas_configs() { + let program_id = hyperlane_sealevel_token_collateral_id(); + let spl_token_program_id = spl_token_2022::id(); + + let (mut banks_client, payer) = setup_client().await; + + let (mint, _mint_authority) = initialize_mint( + &mut banks_client, + &payer, + LOCAL_DECIMALS, + &spl_token_program_id, + ) + .await; + + let hyperlane_token_accounts = initialize_hyperlane_token( + &program_id, + &mut banks_client, + &payer, + None, + &mint, + &spl_token_program_id, + ) + .await + .unwrap(); + + // Set the destination gas config + let gas = 111222333; + set_destination_gas_config( + &mut banks_client, + &program_id, + &payer, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + gas, + ) + .await + .unwrap(); + + // Verify the destination gas was set. + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!( + token.destination_gas, + vec![(REMOTE_DOMAIN, gas)].into_iter().collect(), + ); +} + +#[tokio::test] +async fn test_set_destination_gas_configs_errors_if_not_signed_by_owner() { + let program_id = hyperlane_sealevel_token_collateral_id(); + let spl_token_program_id = spl_token_2022::id(); + + let (mut banks_client, payer) = setup_client().await; + + let (mint, _mint_authority) = initialize_mint( + &mut banks_client, + &payer, + LOCAL_DECIMALS, + &spl_token_program_id, + ) + .await; + + let hyperlane_token_accounts = initialize_hyperlane_token( + &program_id, + &mut banks_client, + &payer, + None, + &mint, + &spl_token_program_id, + ) + .await + .unwrap(); + + let non_owner = new_funded_keypair(&mut banks_client, &payer, ONE_SOL_IN_LAMPORTS).await; + + // Use the non_owner as the payer, which has a balance but is not the owner, + // so we expect this to fail. + let gas = 111222333; + let result = set_destination_gas_config( + &mut banks_client, + &program_id, + &non_owner, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + gas, + ) + .await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidArgument), + ); + + // Also try using the non_owner as the payer and specifying the correct + // owner account, but the owner isn't a signer: + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + // Try setting + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetDestinationGasConfigs(vec![GasRouterConfig { + domain: REMOTE_DOMAIN, + gas: Some(gas), + }]) + .encode() + .unwrap(), + vec![ + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), false), + ], + )], + Some(&non_owner.pubkey()), + &[&non_owner], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature), + ); +} + +#[tokio::test] +async fn test_transfer_ownership() { + let program_id = hyperlane_sealevel_token_collateral_id(); + let spl_token_program_id = spl_token_2022::id(); + + let (mut banks_client, payer) = setup_client().await; + + let (mint, _mint_authority) = initialize_mint( + &mut banks_client, + &payer, + LOCAL_DECIMALS, + &spl_token_program_id, + ) + .await; + + let hyperlane_token_accounts = initialize_hyperlane_token( + &program_id, + &mut banks_client, + &payer, + None, + &mint, + &spl_token_program_id, + ) + .await + .unwrap(); + + let new_owner = Some(Pubkey::new_unique()); + + // Transfer ownership + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::TransferOwnership(new_owner) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), true), + ], + )], + Some(&payer.pubkey()), + &[&payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await.unwrap(); + + // Verify the new owner is set + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!(token.owner, new_owner); +} + +#[tokio::test] +async fn test_transfer_ownership_errors_if_owner_not_signer() { + let program_id = hyperlane_sealevel_token_collateral_id(); + let spl_token_program_id = spl_token_2022::id(); + + let (mut banks_client, payer) = setup_client().await; + + let (mint, mint_authority) = initialize_mint( + &mut banks_client, + &payer, + LOCAL_DECIMALS, + &spl_token_program_id, + ) + .await; + + let hyperlane_token_accounts = initialize_hyperlane_token( + &program_id, + &mut banks_client, + &payer, + None, + &mint, + &spl_token_program_id, + ) + .await + .unwrap(); + + let new_owner = Some(Pubkey::new_unique()); + + // Try transferring ownership using the mint authority key + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::TransferOwnership(new_owner) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(mint_authority.pubkey(), true), + ], + )], + Some(&mint_authority.pubkey()), + &[&mint_authority], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidArgument), + ); +} + +#[tokio::test] +async fn test_set_interchain_security_module() { + let program_id = hyperlane_sealevel_token_collateral_id(); + let spl_token_program_id = spl_token_2022::id(); + + let (mut banks_client, payer) = setup_client().await; + + let (mint, _mint_authority) = initialize_mint( + &mut banks_client, + &payer, + LOCAL_DECIMALS, + &spl_token_program_id, + ) + .await; + + let hyperlane_token_accounts = initialize_hyperlane_token( + &program_id, + &mut banks_client, + &payer, + None, + &mint, + &spl_token_program_id, + ) + .await + .unwrap(); + + let new_ism = Some(Pubkey::new_unique()); + + // Set the ISM + // Transfer ownership + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainSecurityModule(new_ism) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), true), + ], + )], + Some(&payer.pubkey()), + &[&payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await.unwrap(); + + // Verify the new ISM is set + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!(token.interchain_security_module, new_ism); +} + +#[tokio::test] +async fn test_set_interchain_security_module_errors_if_owner_not_signer() { + let program_id = hyperlane_sealevel_token_collateral_id(); + let spl_token_program_id = spl_token_2022::id(); + + let (mut banks_client, payer) = setup_client().await; + + let (mint, mint_authority) = initialize_mint( + &mut banks_client, + &payer, + LOCAL_DECIMALS, + &spl_token_program_id, + ) + .await; + + let hyperlane_token_accounts = initialize_hyperlane_token( + &program_id, + &mut banks_client, + &payer, + None, + &mint, + &spl_token_program_id, + ) + .await + .unwrap(); + + let new_ism = Some(Pubkey::new_unique()); + + // Try setting the ISM using the mint authority key + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainSecurityModule(new_ism) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(mint_authority.pubkey(), true), + ], + )], + Some(&mint_authority.pubkey()), + &[&mint_authority], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidArgument), + ); + + // Also try using the non_owner as the payer and specifying the correct + // owner account, but the owner isn't a signer: + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainSecurityModule(new_ism) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), false), + ], + )], + Some(&mint_authority.pubkey()), + &[&mint_authority], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature), + ); +} + +#[tokio::test] +async fn test_set_interchain_gas_paymaster() { + let program_id = hyperlane_sealevel_token_collateral_id(); + let spl_token_program_id = spl_token_2022::id(); + + let (mut banks_client, payer) = setup_client().await; + + let (mint, _mint_authority) = initialize_mint( + &mut banks_client, + &payer, + LOCAL_DECIMALS, + &spl_token_program_id, + ) + .await; + + let hyperlane_token_accounts = initialize_hyperlane_token( + &program_id, + &mut banks_client, + &payer, + None, + &mint, + &spl_token_program_id, + ) + .await + .unwrap(); + + let new_igp = Some(( + Pubkey::new_unique(), + InterchainGasPaymasterType::OverheadIgp(Pubkey::new_unique()), + )); + + // Set the IGP + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainGasPaymaster(new_igp.clone()) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), true), + ], + )], + Some(&payer.pubkey()), + &[&payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await.unwrap(); + + // Verify the new IGP is set + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!(token.interchain_gas_paymaster, new_igp); +} + +#[tokio::test] +async fn test_set_interchain_gas_paymaster_errors_if_owner_not_signer() { + let program_id = hyperlane_sealevel_token_collateral_id(); + let spl_token_program_id = spl_token_2022::id(); + + let (mut banks_client, payer) = setup_client().await; + + let (mint, _mint_authority) = initialize_mint( + &mut banks_client, + &payer, + LOCAL_DECIMALS, + &spl_token_program_id, + ) + .await; + + let hyperlane_token_accounts = initialize_hyperlane_token( + &program_id, + &mut banks_client, + &payer, + None, + &mint, + &spl_token_program_id, + ) + .await + .unwrap(); + + let new_igp = Some(( + Pubkey::new_unique(), + InterchainGasPaymasterType::OverheadIgp(Pubkey::new_unique()), + )); + let non_owner = new_funded_keypair(&mut banks_client, &payer, ONE_SOL_IN_LAMPORTS).await; + + // Try setting the ISM using the mint authority key + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainGasPaymaster(new_igp.clone()) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(non_owner.pubkey(), true), + ], + )], + Some(&non_owner.pubkey()), + &[&non_owner], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidArgument), + ); + + // Also try using the non_owner as the payer and specifying the correct + // owner account, but the owner isn't a signer: + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainGasPaymaster(new_igp) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), false), + ], + )], + Some(&non_owner.pubkey()), + &[&non_owner], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature), + ); +} diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-collateral/Cargo.toml b/rust/sealevel/programs/hyperlane-sealevel-token-collateral/Cargo.toml index 95ebb3ee130..b3668b1ee63 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token-collateral/Cargo.toml +++ b/rust/sealevel/programs/hyperlane-sealevel-token-collateral/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-sealevel-token-collateral" diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-memo/Cargo.toml b/rust/sealevel/programs/hyperlane-sealevel-token-memo/Cargo.toml new file mode 100644 index 00000000000..5502ea4d9fe --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-memo/Cargo.toml @@ -0,0 +1,46 @@ + + +[package] +name = "hyperlane-sealevel-token-memo" +version = "0.1.0" +edition = "2021" + +[features] +no-entrypoint = [] + +[dependencies] +borsh.workspace = true +num-derive.workspace = true +num-traits.workspace = true +solana-program.workspace = true +spl-associated-token-account.workspace = true +spl-noop.workspace = true +spl-token-2022.workspace = true +spl-token.workspace = true +thiserror.workspace = true + +account-utils = { path = "../../libraries/account-utils" } +hyperlane-core = { path = "../../../main/hyperlane-core" } +hyperlane-sealevel-connection-client = { path = "../../libraries/hyperlane-sealevel-connection-client" } +hyperlane-sealevel-mailbox = { path = "../mailbox", features = [ + "no-entrypoint", +] } +hyperlane-sealevel-igp = { path = "../hyperlane-sealevel-igp", features = [ + "no-entrypoint", +] } +hyperlane-sealevel-message-recipient-interface = { path = "../../libraries/message-recipient-interface" } +hyperlane-sealevel-token-lib = { path = "../../libraries/hyperlane-sealevel-token" } +hyperlane-warp-route = { path = "../../../main/applications/hyperlane-warp-route" } +serializable-account-meta = { path = "../../libraries/serializable-account-meta" } + +[dev-dependencies] +solana-program-test.workspace = true +solana-sdk.workspace = true + +hyperlane-test-utils = { path = "../../libraries/test-utils" } +hyperlane-sealevel-test-ism = { path = "../ism/test-ism", features = [ + "no-entrypoint", +] } + +[lib] +crate-type = ["cdylib", "lib"] diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-memo/src/instruction.rs b/rust/sealevel/programs/hyperlane-sealevel-token-memo/src/instruction.rs new file mode 100644 index 00000000000..0bfab1f334f --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-memo/src/instruction.rs @@ -0,0 +1,37 @@ +//! Instructions for the program. + +use hyperlane_sealevel_token_lib::instruction::{init_instruction as lib_init_instruction, Init}; + +use crate::{hyperlane_token_ata_payer_pda_seeds, hyperlane_token_mint_pda_seeds}; + +use solana_program::{ + instruction::{AccountMeta, Instruction as SolanaInstruction}, + program_error::ProgramError, + pubkey::Pubkey, +}; + +/// Gets an instruction to initialize the program. +pub fn init_instruction( + program_id: Pubkey, + payer: Pubkey, + init: Init, +) -> Result { + let mut instruction = lib_init_instruction(program_id, payer, init)?; + + // Add additional account metas: + // 0. `[writable]` The mint / mint authority PDA account. + // 1. `[writable]` The ATA payer PDA account. + + let (mint_key, _mint_bump) = + Pubkey::find_program_address(hyperlane_token_mint_pda_seeds!(), &program_id); + + let (ata_payer_key, _ata_payer_bump) = + Pubkey::find_program_address(hyperlane_token_ata_payer_pda_seeds!(), &program_id); + + instruction.accounts.append(&mut vec![ + AccountMeta::new(mint_key, false), + AccountMeta::new(ata_payer_key, false), + ]); + + Ok(instruction) +} diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-memo/src/lib.rs b/rust/sealevel/programs/hyperlane-sealevel-token-memo/src/lib.rs new file mode 100644 index 00000000000..b08a06f9f5f --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-memo/src/lib.rs @@ -0,0 +1,14 @@ +//! Hyperlane Token program for synthetic tokens. +#![allow(unexpected_cfgs)] // TODO: `rustc` 1.80.1 clippy issue +#![deny(warnings)] +#![deny(missing_docs)] +#![deny(unsafe_code)] + +pub mod instruction; +pub mod plugin; +pub mod processor; + +pub use spl_associated_token_account; +pub use spl_noop; +pub use spl_token; +pub use spl_token_2022; diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-memo/src/plugin.rs b/rust/sealevel/programs/hyperlane-sealevel-token-memo/src/plugin.rs new file mode 100644 index 00000000000..149062f39bb --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-memo/src/plugin.rs @@ -0,0 +1,375 @@ +//! A plugin for the Hyperlane token program that mints synthetic +//! tokens upon receiving a transfer from a remote chain, and burns +//! synthetic tokens when transferring out to a remote chain. + +use account_utils::{create_pda_account, verify_rent_exempt, SizedData}; +use borsh::{BorshDeserialize, BorshSerialize}; +use hyperlane_sealevel_token_lib::{ + accounts::HyperlaneToken, processor::HyperlaneSealevelTokenPlugin, +}; +use hyperlane_warp_route::TokenMessage; +use serializable_account_meta::SerializableAccountMeta; +#[cfg(not(target_arch = "sbf"))] +use solana_program::program_pack::Pack as _; +use solana_program::{ + account_info::{next_account_info, AccountInfo}, + instruction::AccountMeta, + program::{invoke, invoke_signed}, + program_error::ProgramError, + pubkey::Pubkey, + rent::Rent, + sysvar::Sysvar, +}; +use spl_associated_token_account::{ + get_associated_token_address_with_program_id, + instruction::create_associated_token_account_idempotent, +}; +use spl_token_2022::instruction::{burn_checked, mint_to_checked}; + +/// Seeds relating to the PDA account that acts both as the mint +/// *and* the mint authority. +#[macro_export] +macro_rules! hyperlane_token_mint_pda_seeds { + () => {{ + &[b"hyperlane_token", b"-", b"mint"] + }}; + + ($bump_seed:expr) => {{ + &[b"hyperlane_token", b"-", b"mint", &[$bump_seed]] + }}; +} + +/// Seeds relating to the PDA account that acts as the ATA payer. +#[macro_export] +macro_rules! hyperlane_token_ata_payer_pda_seeds { + () => {{ + &[b"hyperlane_token", b"-", b"ata_payer"] + }}; + + ($bump_seed:expr) => {{ + &[b"hyperlane_token", b"-", b"ata_payer", &[$bump_seed]] + }}; +} + +/// A plugin for the Hyperlane token program that mints synthetic +/// tokens upon receiving a transfer from a remote chain, and burns +/// synthetic tokens when transferring out to a remote chain. +#[derive(BorshDeserialize, BorshSerialize, Debug, PartialEq, Default)] +pub struct SyntheticPlugin { + /// The mint / mint authority PDA account. + pub mint: Pubkey, + /// The bump seed for the mint / mint authority PDA account. + pub mint_bump: u8, + /// The bump seed for the ATA payer PDA account. + pub ata_payer_bump: u8, +} + +impl SizedData for SyntheticPlugin { + fn size(&self) -> usize { + // mint + 32 + + // mint_bump + std::mem::size_of::() + + // ata_payer_bump + std::mem::size_of::() + } +} + +impl SyntheticPlugin { + /// The size of the mint account. + // Need to hardcode this value because our `spl_token_2022` version doesn't include it. + // It was calculated by calling `ExtensionType::try_calculate_account_len::(vec![ExtensionType::MetadataPointer]).unwrap()` + #[cfg(target_arch = "sbf")] + const MINT_ACCOUNT_SIZE: usize = 234; + /// The size of the mint account. + #[cfg(not(target_arch = "sbf"))] + const MINT_ACCOUNT_SIZE: usize = spl_token_2022::state::Mint::LEN; + + /// Returns Ok(()) if the mint account info is valid. + /// Errors if the key or owner is incorrect. + fn verify_mint_account_info( + program_id: &Pubkey, + token: &HyperlaneToken, + mint_account_info: &AccountInfo, + ) -> Result<(), ProgramError> { + let mint_seeds: &[&[u8]] = hyperlane_token_mint_pda_seeds!(token.plugin_data.mint_bump); + let expected_mint_key = Pubkey::create_program_address(mint_seeds, program_id)?; + if mint_account_info.key != &expected_mint_key { + return Err(ProgramError::InvalidArgument); + } + if *mint_account_info.key != token.plugin_data.mint { + return Err(ProgramError::InvalidArgument); + } + if mint_account_info.owner != &spl_token_2022::id() { + return Err(ProgramError::IncorrectProgramId); + } + + Ok(()) + } + + fn verify_ata_payer_account_info( + program_id: &Pubkey, + token: &HyperlaneToken, + ata_payer_account_info: &AccountInfo, + ) -> Result<(), ProgramError> { + let ata_payer_seeds: &[&[u8]] = + hyperlane_token_ata_payer_pda_seeds!(token.plugin_data.ata_payer_bump); + let expected_ata_payer_account = + Pubkey::create_program_address(ata_payer_seeds, program_id)?; + if ata_payer_account_info.key != &expected_ata_payer_account { + return Err(ProgramError::InvalidArgument); + } + Ok(()) + } +} + +impl HyperlaneSealevelTokenPlugin for SyntheticPlugin { + /// Initializes the plugin. + /// Note this will create a PDA account that will serve as the mint, + /// so the transaction calling this instruction must include a subsequent + /// instruction initializing the mint with the SPL token 2022 program. + /// + /// Accounts: + /// 0. `[writable]` The mint / mint authority PDA account. + /// 1. `[writable]` The ATA payer PDA account. + fn initialize<'a, 'b>( + program_id: &Pubkey, + system_program: &'a AccountInfo<'b>, + _token_account: &'a AccountInfo<'b>, + payer_account: &'a AccountInfo<'b>, + accounts_iter: &mut std::slice::Iter<'a, AccountInfo<'b>>, + ) -> Result { + // Account 0: Mint / mint authority + let mint_account = next_account_info(accounts_iter)?; + let (mint_key, mint_bump) = + Pubkey::find_program_address(hyperlane_token_mint_pda_seeds!(), program_id); + if &mint_key != mint_account.key { + return Err(ProgramError::InvalidArgument); + } + + let rent = Rent::get()?; + + // Create mint / mint authority PDA. + // Grant ownership to the SPL token 2022 program. + create_pda_account( + payer_account, + &rent, + Self::MINT_ACCOUNT_SIZE, + &spl_token_2022::id(), + system_program, + mint_account, + hyperlane_token_mint_pda_seeds!(mint_bump), + )?; + + // Account 1: ATA payer. + let ata_payer_account = next_account_info(accounts_iter)?; + let (ata_payer_key, ata_payer_bump) = + Pubkey::find_program_address(hyperlane_token_ata_payer_pda_seeds!(), program_id); + if &ata_payer_key != ata_payer_account.key { + return Err(ProgramError::InvalidArgument); + } + + // Create the ATA payer. + // This is a separate PDA because the ATA program requires + // the payer to have no data in it. + create_pda_account( + payer_account, + &rent, + 0, + // Grant ownership to the system program so that the ATA program + // can call into the system program with the ATA payer as the + // payer. + &solana_program::system_program::id(), + system_program, + ata_payer_account, + hyperlane_token_ata_payer_pda_seeds!(ata_payer_bump), + )?; + + Ok(Self { + mint: mint_key, + mint_bump, + ata_payer_bump, + }) + } + + /// Transfers tokens into the program so they can be sent to a remote chain. + /// Burns the tokens from the sender's associated token account. + /// + /// Accounts: + /// 0. `[executable]` The spl_token_2022 program. + /// 1. `[writeable]` The mint / mint authority PDA account. + /// 2. `[writeable]` The token sender's associated token account, from which tokens will be burned. + fn transfer_in<'a, 'b>( + program_id: &Pubkey, + token: &HyperlaneToken, + sender_wallet: &'a AccountInfo<'b>, + accounts_iter: &mut std::slice::Iter<'a, AccountInfo<'b>>, + amount: u64, + ) -> Result<(), ProgramError> { + // 0. SPL token 2022 program + let spl_token_2022 = next_account_info(accounts_iter)?; + if spl_token_2022.key != &spl_token_2022::id() || !spl_token_2022.executable { + return Err(ProgramError::InvalidArgument); + } + + // 1. The mint / mint authority. + let mint_account = next_account_info(accounts_iter)?; + Self::verify_mint_account_info(program_id, token, mint_account)?; + + // 2. The sender's associated token account. + let sender_ata = next_account_info(accounts_iter)?; + let expected_sender_associated_token_account = get_associated_token_address_with_program_id( + sender_wallet.key, + mint_account.key, + &spl_token_2022::id(), + ); + if sender_ata.key != &expected_sender_associated_token_account { + return Err(ProgramError::InvalidArgument); + } + + let burn_ixn = burn_checked( + &spl_token_2022::id(), + sender_ata.key, + mint_account.key, + sender_wallet.key, + &[sender_wallet.key], + amount, + token.decimals, + )?; + // Sender wallet is expected to have signed this transaction + invoke( + &burn_ixn, + &[ + sender_ata.clone(), + mint_account.clone(), + sender_wallet.clone(), + ], + )?; + + Ok(()) + } + + /// Transfers tokens out to a recipient's associated token account as a + /// result of a transfer to this chain from a remote chain. + /// + /// Accounts: + /// 0. `[executable]` SPL token 2022 program + /// 1. `[executable]` SPL associated token account + /// 2. `[writeable]` Mint account + /// 3. `[writeable]` Recipient associated token account + /// 4. `[writeable]` ATA payer PDA account. + fn transfer_out<'a, 'b>( + program_id: &Pubkey, + token: &HyperlaneToken, + system_program: &'a AccountInfo<'b>, + recipient_wallet: &'a AccountInfo<'b>, + accounts_iter: &mut std::slice::Iter<'a, AccountInfo<'b>>, + amount: u64, + ) -> Result<(), ProgramError> { + // Account 0: SPL token 2022 program + let spl_token_2022 = next_account_info(accounts_iter)?; + if spl_token_2022.key != &spl_token_2022::id() || !spl_token_2022.executable { + return Err(ProgramError::InvalidArgument); + } + // Account 1: SPL associated token account + let spl_ata = next_account_info(accounts_iter)?; + if spl_ata.key != &spl_associated_token_account::id() || !spl_ata.executable { + return Err(ProgramError::InvalidArgument); + } + + // Account 2: Mint account + let mint_account = next_account_info(accounts_iter)?; + Self::verify_mint_account_info(program_id, token, mint_account)?; + + // Account 3: Recipient associated token account + let recipient_ata = next_account_info(accounts_iter)?; + let expected_recipient_associated_token_account = + get_associated_token_address_with_program_id( + recipient_wallet.key, + mint_account.key, + &spl_token_2022::id(), + ); + if recipient_ata.key != &expected_recipient_associated_token_account { + return Err(ProgramError::InvalidArgument); + } + + // Account 4: ATA payer PDA account + let ata_payer_account = next_account_info(accounts_iter)?; + Self::verify_ata_payer_account_info(program_id, token, ata_payer_account)?; + + // Create and init (this does both) associated token account if necessary. + invoke_signed( + &create_associated_token_account_idempotent( + ata_payer_account.key, + recipient_wallet.key, + mint_account.key, + &spl_token_2022::id(), + ), + &[ + ata_payer_account.clone(), + recipient_ata.clone(), + recipient_wallet.clone(), + mint_account.clone(), + system_program.clone(), + spl_token_2022.clone(), + ], + &[hyperlane_token_ata_payer_pda_seeds!( + token.plugin_data.ata_payer_bump + )], + )?; + + // After potentially paying for the ATA creation, we need to make sure + // the ATA payer still meets the rent-exemption requirements. + verify_rent_exempt(recipient_ata, &Rent::get()?)?; + + let mint_ixn = mint_to_checked( + &spl_token_2022::id(), + mint_account.key, + recipient_ata.key, + mint_account.key, + &[], + amount, + token.decimals, + )?; + invoke_signed( + &mint_ixn, + &[ + mint_account.clone(), + recipient_ata.clone(), + mint_account.clone(), + ], + &[hyperlane_token_mint_pda_seeds!(token.plugin_data.mint_bump)], + )?; + + Ok(()) + } + + fn transfer_out_account_metas( + program_id: &Pubkey, + token: &HyperlaneToken, + token_message: &TokenMessage, + ) -> Result<(Vec, bool), ProgramError> { + let ata_payer_account_key = Pubkey::create_program_address( + hyperlane_token_ata_payer_pda_seeds!(token.plugin_data.ata_payer_bump), + program_id, + )?; + + let recipient_associated_token_account = get_associated_token_address_with_program_id( + &Pubkey::new_from_array(token_message.recipient().into()), + &token.plugin_data.mint, + &spl_token_2022::id(), + ); + + Ok(( + vec![ + AccountMeta::new_readonly(spl_token_2022::id(), false).into(), + AccountMeta::new_readonly(spl_associated_token_account::id(), false).into(), + AccountMeta::new(token.plugin_data.mint, false).into(), + AccountMeta::new(recipient_associated_token_account, false).into(), + AccountMeta::new(ata_payer_account_key, false).into(), + ], + // The recipient does not need to be writeable + false, + )) + } +} diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-memo/src/processor.rs b/rust/sealevel/programs/hyperlane-sealevel-token-memo/src/processor.rs new file mode 100644 index 00000000000..b98a51d4bfb --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-memo/src/processor.rs @@ -0,0 +1,290 @@ +//! Program processor. + +use account_utils::DiscriminatorDecode; +use hyperlane_sealevel_connection_client::{ + gas_router::GasRouterConfig, router::RemoteRouterConfig, +}; +use hyperlane_sealevel_igp::accounts::InterchainGasPaymasterType; +use hyperlane_sealevel_message_recipient_interface::{ + HandleInstruction, MessageRecipientInstruction, +}; +use hyperlane_sealevel_token_lib::{ + instruction::{ + DymInstruction, Init, Instruction as TokenIxn, TransferRemote, TransferRemoteMemo, + }, + processor::HyperlaneSealevelToken, +}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey}; + +use crate::plugin::SyntheticPlugin; + +#[cfg(not(feature = "no-entrypoint"))] +solana_program::entrypoint!(process_instruction); + +/// Processes an instruction. +pub fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + // First, check if the instruction has a discriminant relating to + // the message recipient interface. + if let Ok(message_recipient_instruction) = MessageRecipientInstruction::decode(instruction_data) + { + return match message_recipient_instruction { + MessageRecipientInstruction::InterchainSecurityModule => { + interchain_security_module(program_id, accounts) + } + MessageRecipientInstruction::InterchainSecurityModuleAccountMetas => { + interchain_security_module_account_metas(program_id) + } + MessageRecipientInstruction::Handle(handle) => transfer_from_remote( + program_id, + accounts, + HandleInstruction { + origin: handle.origin, + sender: handle.sender, + message: handle.message, + }, + ), + MessageRecipientInstruction::HandleAccountMetas(handle) => { + transfer_from_remote_account_metas( + program_id, + accounts, + HandleInstruction { + origin: handle.origin, + sender: handle.sender, + message: handle.message, + }, + ) + } + }; + } + if let Ok(instr) = DymInstruction::decode(instruction_data) { + return match instr { + DymInstruction::TransferRemoteMemo(xfer) => { + transfer_remote_memo(program_id, accounts, xfer) + } + } + .map_err(|err| { + msg!("{}", err); + err + }); + } + + // Otherwise, try decoding a "normal" token instruction + match TokenIxn::decode(instruction_data)? { + TokenIxn::Init(init) => initialize(program_id, accounts, init), + TokenIxn::TransferRemote(xfer) => transfer_remote(program_id, accounts, xfer), + TokenIxn::EnrollRemoteRouter(config) => enroll_remote_router(program_id, accounts, config), + TokenIxn::EnrollRemoteRouters(configs) => { + enroll_remote_routers(program_id, accounts, configs) + } + TokenIxn::SetDestinationGasConfigs(configs) => { + set_destination_gas_configs(program_id, accounts, configs) + } + TokenIxn::SetInterchainSecurityModule(new_ism) => { + set_interchain_security_module(program_id, accounts, new_ism) + } + TokenIxn::SetInterchainGasPaymaster(new_igp) => { + set_interchain_gas_paymaster(program_id, accounts, new_igp) + } + TokenIxn::TransferOwnership(new_owner) => { + transfer_ownership(program_id, accounts, new_owner) + } + } + .map_err(|err| { + msg!("{}", err); + err + }) +} + +fn transfer_remote_memo( + program_id: &Pubkey, + accounts: &[AccountInfo], + transfer: TransferRemoteMemo, +) -> ProgramResult { + HyperlaneSealevelToken::::transfer_remote_memo( + program_id, + accounts, + transfer.base, + transfer.memo, + ) +} + +/// Initializes the program. +/// +/// Accounts: +/// 0. `[executable]` The system program. +/// 1. `[writable]` The token PDA account. +/// 2. `[writable]` The dispatch authority PDA account. +/// 3. `[signer]` The payer. +/// 4. `[writable]` The mint / mint authority PDA account. +/// 5. `[writable]` The ATA payer PDA account. +fn initialize(program_id: &Pubkey, accounts: &[AccountInfo], init: Init) -> ProgramResult { + HyperlaneSealevelToken::::initialize(program_id, accounts, init) +} + +/// Transfers tokens to a remote. +/// Burns the tokens from the sender's associated token account and +/// then dispatches a message to the remote recipient. +/// +/// Accounts: +/// 0. `[executable]` The system program. +/// 1. `[executable]` The spl_noop program. +/// 2. `[]` The token PDA account. +/// 3. `[executable]` The mailbox program. +/// 4. `[writeable]` The mailbox outbox account. +/// 5. `[]` Message dispatch authority. +/// 6. `[signer]` The token sender and mailbox payer. +/// 7. `[signer]` Unique message / gas payment account. +/// 8. `[writeable]` Message storage PDA. +/// ---- If using an IGP ---- +/// 9. `[executable]` The IGP program. +/// 10. `[writeable]` The IGP program data. +/// 11. `[writeable]` Gas payment PDA. +/// 12. `[]` OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. +/// 13. `[writeable]` The IGP account. +/// ---- End if ---- +/// 14. `[executable]` The spl_token_2022 program. +/// 15. `[writeable]` The mint / mint authority PDA account. +/// 16. `[writeable]` The token sender's associated token account, from which tokens will be burned. +fn transfer_remote( + program_id: &Pubkey, + accounts: &[AccountInfo], + transfer: TransferRemote, +) -> ProgramResult { + HyperlaneSealevelToken::::transfer_remote(program_id, accounts, transfer) +} + +// Accounts: +// 0. `[signer]` Mailbox process authority specific to this program. +// 1. `[executable]` system_program +// 2. `[]` hyperlane_token storage +// 3. `[]` recipient wallet address +// 4. `[executable]` SPL token 2022 program +// 5. `[executable]` SPL associated token account +// 6. `[writeable]` Mint account +// 7. `[writeable]` Recipient associated token account +// 8. `[writeable]` ATA payer PDA account. +fn transfer_from_remote( + program_id: &Pubkey, + accounts: &[AccountInfo], + transfer: HandleInstruction, +) -> ProgramResult { + HyperlaneSealevelToken::::transfer_from_remote(program_id, accounts, transfer) +} + +/// Gets the account metas required for the `HandleInstruction` instruction. +fn transfer_from_remote_account_metas( + program_id: &Pubkey, + accounts: &[AccountInfo], + transfer: HandleInstruction, +) -> ProgramResult { + HyperlaneSealevelToken::::transfer_from_remote_account_metas( + program_id, accounts, transfer, + ) +} + +/// Enrolls a remote router. +/// +/// Accounts: +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. +fn enroll_remote_router( + program_id: &Pubkey, + accounts: &[AccountInfo], + config: RemoteRouterConfig, +) -> ProgramResult { + HyperlaneSealevelToken::::enroll_remote_router(program_id, accounts, config) +} + +/// Enrolls remote routers. +/// +/// Accounts: +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. +fn enroll_remote_routers( + program_id: &Pubkey, + accounts: &[AccountInfo], + configs: Vec, +) -> ProgramResult { + HyperlaneSealevelToken::::enroll_remote_routers(program_id, accounts, configs) +} + +/// Sets the destination gas configs. +/// +/// Accounts: +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. +fn set_destination_gas_configs( + program_id: &Pubkey, + accounts: &[AccountInfo], + configs: Vec, +) -> ProgramResult { + HyperlaneSealevelToken::::set_destination_gas_configs( + program_id, accounts, configs, + ) +} + +/// Transfers ownership. +/// +/// Accounts: +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The current owner. +fn transfer_ownership( + program_id: &Pubkey, + accounts: &[AccountInfo], + new_owner: Option, +) -> ProgramResult { + HyperlaneSealevelToken::::transfer_ownership(program_id, accounts, new_owner) +} + +/// Gets the interchain security module, returning it as a serialized Option. +/// +/// Accounts: +/// 0. `[]` The token PDA account. +fn interchain_security_module(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { + HyperlaneSealevelToken::::interchain_security_module(program_id, accounts) +} + +/// Gets the account metas for getting the interchain security module. +/// +/// Accounts: +/// None +fn interchain_security_module_account_metas(program_id: &Pubkey) -> ProgramResult { + HyperlaneSealevelToken::::interchain_security_module_account_metas(program_id) +} + +/// Lets the owner set the interchain security module. +/// +/// Accounts: +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The access control owner. +fn set_interchain_security_module( + program_id: &Pubkey, + accounts: &[AccountInfo], + new_ism: Option, +) -> ProgramResult { + HyperlaneSealevelToken::::set_interchain_security_module( + program_id, accounts, new_ism, + ) +} + +/// Lets the owner set the interchain gas paymaster. +/// +/// Accounts: +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The access control owner. +fn set_interchain_gas_paymaster( + program_id: &Pubkey, + accounts: &[AccountInfo], + new_igp: Option<(Pubkey, InterchainGasPaymasterType)>, +) -> ProgramResult { + HyperlaneSealevelToken::::set_interchain_gas_paymaster( + program_id, accounts, new_igp, + ) +} diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-memo/tests/functional.rs b/rust/sealevel/programs/hyperlane-sealevel-token-memo/tests/functional.rs new file mode 100644 index 00000000000..b4af2604021 --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-memo/tests/functional.rs @@ -0,0 +1,1365 @@ +//! Contains functional tests for things that cannot be done +//! strictly in unit tests. This includes CPIs, like creating +//! new PDA accounts. + +use account_utils::DiscriminatorEncode; +use hyperlane_core::{Encode, HyperlaneMessage, H256, U256}; +use hyperlane_sealevel_connection_client::{ + gas_router::GasRouterConfig, router::RemoteRouterConfig, +}; +use hyperlane_sealevel_igp::{ + accounts::{GasPaymentAccount, GasPaymentData, InterchainGasPaymasterType}, + igp_gas_payment_pda_seeds, +}; +use hyperlane_sealevel_mailbox::{ + accounts::{DispatchedMessage, DispatchedMessageAccount}, + mailbox_dispatched_message_pda_seeds, mailbox_message_dispatch_authority_pda_seeds, + mailbox_process_authority_pda_seeds, + protocol_fee::ProtocolFee, +}; +use hyperlane_sealevel_message_recipient_interface::{ + HandleInstruction, MessageRecipientInstruction, +}; +use hyperlane_sealevel_token_lib::{ + accounts::{convert_decimals, HyperlaneToken, HyperlaneTokenAccount}, + hyperlane_token_pda_seeds, + instruction::{ + DymInstruction, Init, Instruction as HyperlaneTokenInstruction, TransferRemote, + TransferRemoteMemo, + }, +}; +use hyperlane_sealevel_token_memo::processor::process_instruction; +use hyperlane_sealevel_token_memo::{ + hyperlane_token_ata_payer_pda_seeds, hyperlane_token_mint_pda_seeds, plugin::SyntheticPlugin, +}; +use hyperlane_test_utils::{ + assert_token_balance, assert_transaction_error, igp_program_id, initialize_igp_accounts, + initialize_mailbox, mailbox_id, new_funded_keypair, process, transfer_lamports, IgpAccounts, + MailboxAccounts, +}; +use hyperlane_warp_route::TokenMessage; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey, + pubkey::Pubkey, +}; +use solana_program_test::*; +use solana_sdk::{ + instruction::InstructionError, + signature::Signer, + signer::keypair::Keypair, + transaction::{Transaction, TransactionError}, +}; +use spl_token_2022::instruction::initialize_mint2; +use std::collections::HashMap; + +/// There are 1e9 lamports in one SOL. +const ONE_SOL_IN_LAMPORTS: u64 = 1000000000; +const LOCAL_DOMAIN: u32 = 1234; +const LOCAL_DECIMALS: u8 = 8; +const LOCAL_DECIMALS_U32: u32 = LOCAL_DECIMALS as u32; +const REMOTE_DOMAIN: u32 = 4321; +const REMOTE_DECIMALS: u8 = 18; +const REMOTE_GAS_AMOUNT: u64 = 200000; + +fn hyperlane_sealevel_token_id() -> Pubkey { + pubkey!("3MzUPjP5LEkiHH82nEAe28Xtz9ztuMqWc8UmuKxrpVQH") +} + +async fn setup_client() -> (BanksClient, Keypair) { + let program_id = hyperlane_sealevel_token_id(); + let mut program_test = ProgramTest::new( + "hyperlane_sealevel_token", + program_id, + processor!(process_instruction), + ); + + program_test.add_program( + "spl_token_2022", + spl_token_2022::id(), + processor!(spl_token_2022::processor::Processor::process), + ); + + program_test.add_program( + "spl_associated_token_account", + spl_associated_token_account::id(), + processor!(spl_associated_token_account::processor::process_instruction), + ); + + program_test.add_program("spl_noop", spl_noop::id(), processor!(spl_noop::noop)); + + let mailbox_program_id = mailbox_id(); + program_test.add_program( + "hyperlane_sealevel_mailbox", + mailbox_program_id, + processor!(hyperlane_sealevel_mailbox::processor::process_instruction), + ); + + program_test.add_program( + "hyperlane_sealevel_igp", + igp_program_id(), + processor!(hyperlane_sealevel_igp::processor::process_instruction), + ); + + // This serves as the default ISM on the Mailbox + program_test.add_program( + "hyperlane_sealevel_test_ism", + hyperlane_sealevel_test_ism::id(), + processor!(hyperlane_sealevel_test_ism::program::process_instruction), + ); + + let (banks_client, payer, _recent_blockhash) = program_test.start().await; + + (banks_client, payer) +} + +struct HyperlaneTokenAccounts { + token: Pubkey, + token_bump: u8, + mailbox_process_authority: Pubkey, + dispatch_authority: Pubkey, + dispatch_authority_bump: u8, + mint: Pubkey, + mint_bump: u8, + ata_payer: Pubkey, + ata_payer_bump: u8, +} + +async fn initialize_hyperlane_token( + program_id: &Pubkey, + banks_client: &mut BanksClient, + payer: &Keypair, + igp_accounts: Option<&IgpAccounts>, +) -> Result { + let (mailbox_process_authority_key, _mailbox_process_authority_bump) = + Pubkey::find_program_address( + mailbox_process_authority_pda_seeds!(program_id), + &mailbox_id(), + ); + + let (token_account_key, token_account_bump_seed) = + Pubkey::find_program_address(hyperlane_token_pda_seeds!(), program_id); + + let (dispatch_authority_key, dispatch_authority_seed) = + Pubkey::find_program_address(mailbox_message_dispatch_authority_pda_seeds!(), program_id); + + let (mint_account_key, mint_account_bump_seed) = + Pubkey::find_program_address(hyperlane_token_mint_pda_seeds!(), program_id); + + let (ata_payer_account_key, ata_payer_account_bump_seed) = + Pubkey::find_program_address(hyperlane_token_ata_payer_pda_seeds!(), program_id); + + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[ + Instruction::new_with_bytes( + *program_id, + &HyperlaneTokenInstruction::Init(Init { + mailbox: mailbox_id(), + interchain_security_module: None, + interchain_gas_paymaster: igp_accounts.map(|igp_accounts| { + ( + igp_accounts.program, + InterchainGasPaymasterType::OverheadIgp(igp_accounts.overhead_igp), + ) + }), + decimals: LOCAL_DECIMALS, + remote_decimals: REMOTE_DECIMALS, + }) + .encode() + .unwrap(), + vec![ + // 0. `[executable]` The system program. + // 1. `[writable]` The token PDA account. + // 2. `[writable]` The dispatch authority PDA account. + // 3. `[signer]` The payer. + // 4. `[writable]` The mint / mint authority PDA account. + // 5. `[writable]` The ATA payer PDA account. + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(token_account_key, false), + AccountMeta::new(dispatch_authority_key, false), + AccountMeta::new_readonly(payer.pubkey(), true), + AccountMeta::new(mint_account_key, false), + AccountMeta::new(ata_payer_account_key, false), + ], + ), + initialize_mint2( + &spl_token_2022::id(), + &mint_account_key, + &mint_account_key, + None, + LOCAL_DECIMALS, + ) + .unwrap(), + ], + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await?; + + // Set destination gas configs + set_destination_gas_config( + banks_client, + program_id, + payer, + &token_account_key, + REMOTE_DOMAIN, + REMOTE_GAS_AMOUNT, + ) + .await?; + + Ok(HyperlaneTokenAccounts { + token: token_account_key, + token_bump: token_account_bump_seed, + mailbox_process_authority: mailbox_process_authority_key, + dispatch_authority: dispatch_authority_key, + dispatch_authority_bump: dispatch_authority_seed, + mint: mint_account_key, + mint_bump: mint_account_bump_seed, + ata_payer: ata_payer_account_key, + ata_payer_bump: ata_payer_account_bump_seed, + }) +} + +async fn enroll_remote_router( + banks_client: &mut BanksClient, + program_id: &Pubkey, + payer: &Keypair, + token_account: &Pubkey, + domain: u32, + router: H256, +) -> Result<(), BanksClientError> { + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + // Enroll the remote router + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + *program_id, + &HyperlaneTokenInstruction::EnrollRemoteRouter(RemoteRouterConfig { + domain, + router: Some(router), + }) + .encode() + .unwrap(), + vec![ + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(*token_account, false), + AccountMeta::new_readonly(payer.pubkey(), true), + ], + )], + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await?; + + Ok(()) +} + +async fn set_destination_gas_config( + banks_client: &mut BanksClient, + program_id: &Pubkey, + payer: &Keypair, + token_account: &Pubkey, + domain: u32, + gas: u64, +) -> Result<(), BanksClientError> { + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + // Enroll the remote router + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + *program_id, + &HyperlaneTokenInstruction::SetDestinationGasConfigs(vec![GasRouterConfig { + domain, + gas: Some(gas), + }]) + .encode() + .unwrap(), + vec![ + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(*token_account, false), + AccountMeta::new_readonly(payer.pubkey(), true), + ], + )], + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await?; + + Ok(()) +} + +#[tokio::test] +async fn test_initialize() { + let program_id = hyperlane_sealevel_token_id(); + let mailbox_program_id = mailbox_id(); + + let (mut banks_client, payer) = setup_client().await; + + let mailbox_accounts = initialize_mailbox( + &mut banks_client, + &mailbox_program_id, + &payer, + LOCAL_DOMAIN, + ONE_SOL_IN_LAMPORTS, + ProtocolFee::default(), + ) + .await + .unwrap(); + + let igp_accounts = + initialize_igp_accounts(&mut banks_client, &igp_program_id(), &payer, REMOTE_DOMAIN) + .await + .unwrap(); + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, Some(&igp_accounts)) + .await + .unwrap(); + + // Get the token account. + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!( + token, + Box::new(HyperlaneToken { + bump: hyperlane_token_accounts.token_bump, + mailbox: mailbox_accounts.program, + mailbox_process_authority: hyperlane_token_accounts.mailbox_process_authority, + dispatch_authority_bump: hyperlane_token_accounts.dispatch_authority_bump, + decimals: LOCAL_DECIMALS, + remote_decimals: REMOTE_DECIMALS, + owner: Some(payer.pubkey()), + interchain_security_module: None, + interchain_gas_paymaster: Some(( + igp_accounts.program, + InterchainGasPaymasterType::OverheadIgp(igp_accounts.overhead_igp), + )), + destination_gas: HashMap::from([(REMOTE_DOMAIN, REMOTE_GAS_AMOUNT)]), + remote_routers: HashMap::new(), + plugin_data: SyntheticPlugin { + mint: hyperlane_token_accounts.mint, + mint_bump: hyperlane_token_accounts.mint_bump, + ata_payer_bump: hyperlane_token_accounts.ata_payer_bump, + }, + }), + ); + + // Verify the mint account was created. + let mint_account = banks_client + .get_account(hyperlane_token_accounts.mint) + .await + .unwrap() + .unwrap(); + assert_eq!(mint_account.owner, spl_token_2022::id()); + assert!(!mint_account.data.is_empty()); + + // Verify the ATA payer account was created. + let ata_payer_account = banks_client + .get_account(hyperlane_token_accounts.ata_payer) + .await + .unwrap() + .unwrap(); + assert!(ata_payer_account.lamports > 0); +} + +#[tokio::test] +async fn test_initialize_errors_if_called_twice() { + let program_id = hyperlane_sealevel_token_id(); + + let (mut banks_client, payer) = setup_client().await; + + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let new_payer = new_funded_keypair(&mut banks_client, &payer, ONE_SOL_IN_LAMPORTS).await; + + // To ensure a different signature is used, we'll use a different payer + let init_result = + initialize_hyperlane_token(&program_id, &mut banks_client, &new_payer, None).await; + + assert_transaction_error( + init_result, + TransactionError::InstructionError(0, InstructionError::AccountAlreadyInitialized), + ); +} + +async fn transfer_from_remote( + remote_transfer_amount: U256, + sender_override: Option, + origin_override: Option, + recipient_wallet: Option, +) -> Result< + ( + BanksClient, + Keypair, + MailboxAccounts, + IgpAccounts, + HyperlaneTokenAccounts, + Pubkey, + ), + BanksClientError, +> { + let program_id = hyperlane_sealevel_token_id(); + let mailbox_program_id = mailbox_id(); + + let (mut banks_client, payer) = setup_client().await; + + let mailbox_accounts = initialize_mailbox( + &mut banks_client, + &mailbox_program_id, + &payer, + LOCAL_DOMAIN, + ONE_SOL_IN_LAMPORTS, + ProtocolFee::default(), + ) + .await + .unwrap(); + + let igp_accounts = + initialize_igp_accounts(&mut banks_client, &igp_program_id(), &payer, REMOTE_DOMAIN) + .await + .unwrap(); + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, Some(&igp_accounts)) + .await + .unwrap(); + // ATA payer must have a balance to create new ATAs + transfer_lamports( + &mut banks_client, + &payer, + &hyperlane_token_accounts.ata_payer, + ONE_SOL_IN_LAMPORTS, + ) + .await; + + // Enroll the remote router + let remote_router = H256::random(); + enroll_remote_router( + &mut banks_client, + &program_id, + &payer, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + remote_router, + ) + .await + .unwrap(); + + let recipient_pubkey = recipient_wallet.unwrap_or_else(Pubkey::new_unique); + let recipient: H256 = recipient_pubkey.to_bytes().into(); + + let recipient_associated_token_account = + spl_associated_token_account::get_associated_token_address_with_program_id( + &recipient_pubkey, + &hyperlane_token_accounts.mint, + &spl_token_2022::id(), + ); + + let message = HyperlaneMessage { + version: 3, + nonce: 0, + origin: origin_override.unwrap_or(REMOTE_DOMAIN), + // Default to the remote router as the sender + sender: sender_override.unwrap_or(remote_router), + destination: LOCAL_DOMAIN, + recipient: program_id.to_bytes().into(), + body: TokenMessage::new(recipient, remote_transfer_amount, vec![]).to_vec(), + }; + + process( + &mut banks_client, + &payer, + &mailbox_accounts, + vec![], + &message, + ) + .await?; + + Ok(( + banks_client, + payer, + mailbox_accounts, + igp_accounts, + hyperlane_token_accounts, + recipient_associated_token_account, + )) +} + +// Tests when the SPL token is the 2022 version +#[tokio::test] +async fn test_transfer_from_remote() { + let local_transfer_amount = 69 * 10u64.pow(LOCAL_DECIMALS_U32); + let remote_transfer_amount = convert_decimals( + local_transfer_amount.into(), + LOCAL_DECIMALS, + REMOTE_DECIMALS, + ) + .unwrap(); + + let ( + mut banks_client, + _payer, + _mailbox_accounts, + _igp_accounts, + _hyperlane_token_accounts, + recipient_associated_token_account, + ) = transfer_from_remote(remote_transfer_amount, None, None, None) + .await + .unwrap(); + + // Check that the recipient's ATA got the tokens! + assert_token_balance( + &mut banks_client, + &recipient_associated_token_account, + local_transfer_amount, + ) + .await; +} + +#[tokio::test] +async fn test_transfer_from_remote_errors_if_sender_not_router() { + let local_transfer_amount = 69 * 10u64.pow(LOCAL_DECIMALS_U32); + let remote_transfer_amount = convert_decimals( + local_transfer_amount.into(), + LOCAL_DECIMALS, + REMOTE_DECIMALS, + ) + .unwrap(); + + // Same remote domain origin, but wrong sender. + let result = + transfer_from_remote(remote_transfer_amount, Some(H256::random()), None, None).await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidInstructionData), + ); + + // Wrong remote domain origin, but correct sender. + let result = + transfer_from_remote(remote_transfer_amount, None, Some(REMOTE_DOMAIN + 1), None).await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidInstructionData), + ); +} + +#[tokio::test] +async fn test_transfer_from_remote_errors_if_process_authority_not_signer() { + let program_id = hyperlane_sealevel_token_id(); + let mailbox_program_id = mailbox_id(); + + let (mut banks_client, payer) = setup_client().await; + + let _mailbox_accounts = initialize_mailbox( + &mut banks_client, + &mailbox_program_id, + &payer, + LOCAL_DOMAIN, + ONE_SOL_IN_LAMPORTS, + ProtocolFee::default(), + ) + .await + .unwrap(); + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + // Enroll the remote router + let remote_router = H256::random(); + enroll_remote_router( + &mut banks_client, + &program_id, + &payer, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + remote_router, + ) + .await + .unwrap(); + + let recipient_pubkey = Pubkey::new_unique(); + let recipient: H256 = recipient_pubkey.to_bytes().into(); + + let recipient_associated_token_account = + spl_associated_token_account::get_associated_token_address_with_program_id( + &recipient_pubkey, + &hyperlane_token_accounts.mint, + &spl_token_2022::id(), + ); + + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + // Try calling directly into the message handler, skipping the mailbox. + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &MessageRecipientInstruction::Handle(HandleInstruction { + origin: REMOTE_DOMAIN, + sender: remote_router, + message: TokenMessage::new(recipient, 12345u64.into(), vec![]).to_vec(), + }) + .encode() + .unwrap(), + vec![ + // Recipient.handle accounts + // 0. `[signer]` Mailbox process authority specific to this program. + // 1. `[executable]` system_program + // 2. `[]` hyperlane_token storage + // 3. `[]` recipient wallet address + // 4. `[executable]` SPL token 2022 program + // 5. `[executable]` SPL associated token account + // 6. `[writeable]` Mint account + // 7. `[writeable]` Recipient associated token account + // 8. `[writeable]` ATA payer PDA account. + AccountMeta::new_readonly( + hyperlane_token_accounts.mailbox_process_authority, + false, + ), + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new_readonly(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(recipient_pubkey, false), + AccountMeta::new_readonly(spl_token_2022::id(), false), + AccountMeta::new_readonly(spl_associated_token_account::id(), false), + AccountMeta::new(hyperlane_token_accounts.mint, false), + AccountMeta::new(recipient_associated_token_account, false), + AccountMeta::new(hyperlane_token_accounts.ata_payer, false), + ], + )], + Some(&payer.pubkey()), + &[&payer], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature), + ); +} + +#[tokio::test] +async fn test_transfer_remote_memo() { + let program_id = hyperlane_sealevel_token_id(); + let mailbox_program_id = mailbox_id(); + + let token_sender = Keypair::new(); + let token_sender_pubkey = token_sender.pubkey(); + + // Mint 100 tokens to the token sender's ATA. + // We do this by just faking a transfer from remote. + let sender_initial_balance = 100 * 10u64.pow(LOCAL_DECIMALS_U32); + let ( + mut banks_client, + payer, + mailbox_accounts, + igp_accounts, + hyperlane_token_accounts, + token_sender_ata, + ) = transfer_from_remote( + // The amount of remote tokens is expected + convert_decimals( + sender_initial_balance.into(), + LOCAL_DECIMALS, + REMOTE_DECIMALS, + ) + .unwrap(), + None, + None, + Some(token_sender_pubkey), + ) + .await + .unwrap(); + + // Give the token_sender a SOL balance to pay tx fees. + transfer_lamports( + &mut banks_client, + &payer, + &token_sender_pubkey, + ONE_SOL_IN_LAMPORTS, + ) + .await; + + // Enroll the remote router + let remote_router = H256::random(); + enroll_remote_router( + &mut banks_client, + &program_id, + &payer, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + remote_router, + ) + .await + .unwrap(); + + // Call transfer_remote + let unique_message_account_keypair = Keypair::new(); + let (dispatched_message_key, _dispatched_message_bump) = Pubkey::find_program_address( + mailbox_dispatched_message_pda_seeds!(&unique_message_account_keypair.pubkey()), + &mailbox_program_id, + ); + let (gas_payment_pda_key, _gas_payment_pda_bump) = Pubkey::find_program_address( + igp_gas_payment_pda_seeds!(&unique_message_account_keypair.pubkey()), + &igp_program_id(), + ); + + let remote_token_recipient = H256::random(); + // Transfer 69 tokens. + let transfer_amount = 69 * 10u64.pow(LOCAL_DECIMALS_U32); + let remote_transfer_amount = + convert_decimals(transfer_amount.into(), LOCAL_DECIMALS, REMOTE_DECIMALS).unwrap(); + + let test_memo = vec![1, 2, 3]; + + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &DymInstruction::TransferRemoteMemo(TransferRemoteMemo { + base: TransferRemote { + destination_domain: REMOTE_DOMAIN, + recipient: remote_token_recipient, + amount_or_id: transfer_amount.into(), + }, + memo: test_memo.clone(), + }) + .encode() + .unwrap(), + // 0. `[executable]` The system program. + // 1. `[executable]` The spl_noop program. + // 2. `[]` The token PDA account. + // 3. `[executable]` The mailbox program. + // 4. `[writeable]` The mailbox outbox account. + // 5. `[]` Message dispatch authority. + // 6. `[signer]` The token sender and mailbox payer. + // 7. `[signer]` Unique message account. + // 8. `[writeable]` Message storage PDA. + // ---- If using an IGP ---- + // 9. `[executable]` The IGP program. + // 10. `[writeable]` The IGP program data. + // 11. `[writeable]` Gas payment PDA. + // 12. `[]` OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. + // 13. `[writeable]` The IGP account. + // ---- End if ---- + // 14. `[executable]` The spl_token_2022 program. + // 15. `[writeable]` The mint / mint authority PDA account. + // 16. `[writeable]` The token sender's associated token account, from which tokens will be burned. + vec![ + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new_readonly(spl_noop::id(), false), + AccountMeta::new_readonly(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(mailbox_accounts.program, false), + AccountMeta::new(mailbox_accounts.outbox, false), + AccountMeta::new_readonly(hyperlane_token_accounts.dispatch_authority, false), + AccountMeta::new_readonly(token_sender_pubkey, true), + AccountMeta::new_readonly(unique_message_account_keypair.pubkey(), true), + AccountMeta::new(dispatched_message_key, false), + AccountMeta::new_readonly(igp_accounts.program, false), + AccountMeta::new(igp_accounts.program_data, false), + AccountMeta::new(gas_payment_pda_key, false), + AccountMeta::new_readonly(igp_accounts.overhead_igp, false), + AccountMeta::new(igp_accounts.igp, false), + AccountMeta::new_readonly(spl_token_2022::id(), false), + AccountMeta::new(hyperlane_token_accounts.mint, false), + AccountMeta::new(token_sender_ata, false), + ], + )], + Some(&token_sender_pubkey), + &[&token_sender, &unique_message_account_keypair], + recent_blockhash, + ); + let tx_signature = transaction.signatures[0]; + banks_client.process_transaction(transaction).await.unwrap(); + + // Verify the token sender's ATA balance went down + assert_token_balance( + &mut banks_client, + &token_sender_ata, + sender_initial_balance - transfer_amount, + ) + .await; + + // And let's take a look at the dispatched message account data to verify the message looks right. + let dispatched_message_account_data = banks_client + .get_account(dispatched_message_key) + .await + .unwrap() + .unwrap() + .data; + let dispatched_message = + DispatchedMessageAccount::fetch(&mut &dispatched_message_account_data[..]) + .unwrap() + .into_inner(); + + let transfer_remote_tx_status = banks_client + .get_transaction_status(tx_signature) + .await + .unwrap() + .unwrap(); + + let message = HyperlaneMessage { + version: 3, + nonce: 0, + origin: LOCAL_DOMAIN, + sender: program_id.to_bytes().into(), + destination: REMOTE_DOMAIN, + recipient: remote_router, + // Expect the remote_transfer_amount to be in the message. + body: TokenMessage::new(remote_token_recipient, remote_transfer_amount, test_memo).to_vec(), + }; + + assert_eq!( + dispatched_message, + Box::new(DispatchedMessage::new( + message.nonce, + transfer_remote_tx_status.slot, + unique_message_account_keypair.pubkey(), + message.to_vec(), + )), + ); + + // And let's also look at the gas payment account to verify the gas payment looks right. + let gas_payment_account_data = banks_client + .get_account(gas_payment_pda_key) + .await + .unwrap() + .unwrap() + .data; + let gas_payment = GasPaymentAccount::fetch(&mut &gas_payment_account_data[..]) + .unwrap() + .into_inner(); + + assert_eq!( + *gas_payment, + GasPaymentData { + sequence_number: 0, + igp: igp_accounts.igp, + destination_domain: REMOTE_DOMAIN, + message_id: message.id(), + gas_amount: REMOTE_GAS_AMOUNT, + unique_gas_payment_pubkey: unique_message_account_keypair.pubkey(), + slot: transfer_remote_tx_status.slot, + payment: REMOTE_GAS_AMOUNT + } + .into(), + ); +} + +#[tokio::test] +async fn test_enroll_remote_router() { + let program_id = hyperlane_sealevel_token_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + // Enroll the remote router + let remote_router = H256::random(); + enroll_remote_router( + &mut banks_client, + &program_id, + &payer, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + remote_router, + ) + .await + .unwrap(); + + // Verify the remote router was enrolled. + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!( + token.remote_routers, + vec![(REMOTE_DOMAIN, remote_router)].into_iter().collect(), + ); +} + +#[tokio::test] +async fn test_enroll_remote_router_errors_if_not_signed_by_owner() { + let program_id = hyperlane_sealevel_token_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let non_owner = new_funded_keypair(&mut banks_client, &payer, ONE_SOL_IN_LAMPORTS).await; + + // Use the non_owner as the payer, which has a balance but is not the owner, + // so we expect this to fail. + let result = enroll_remote_router( + &mut banks_client, + &program_id, + &non_owner, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + H256::random(), + ) + .await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidArgument), + ); + + // Also try using the non_owner as the payer and specifying the correct + // owner account, but the owner isn't a signer: + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + // Enroll the remote router + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::EnrollRemoteRouter(RemoteRouterConfig { + domain: REMOTE_DOMAIN, + router: Some(H256::random()), + }) + .encode() + .unwrap(), + vec![ + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), false), + ], + )], + Some(&non_owner.pubkey()), + &[&non_owner], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature), + ); +} + +#[tokio::test] +async fn test_set_destination_gas_configs() { + let program_id = hyperlane_sealevel_token_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + // Set the destination gas config + let gas = 111222333; + set_destination_gas_config( + &mut banks_client, + &program_id, + &payer, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + gas, + ) + .await + .unwrap(); + + // Verify the destination gas was set. + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!( + token.destination_gas, + vec![(REMOTE_DOMAIN, gas)].into_iter().collect(), + ); +} + +#[tokio::test] +async fn test_set_destination_gas_configs_errors_if_not_signed_by_owner() { + let program_id = hyperlane_sealevel_token_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let non_owner = new_funded_keypair(&mut banks_client, &payer, ONE_SOL_IN_LAMPORTS).await; + + // Use the non_owner as the payer, which has a balance but is not the owner, + // so we expect this to fail. + let gas = 111222333; + let result = set_destination_gas_config( + &mut banks_client, + &program_id, + &non_owner, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + gas, + ) + .await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidArgument), + ); + + // Also try using the non_owner as the payer and specifying the correct + // owner account, but the owner isn't a signer: + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + // Try setting + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetDestinationGasConfigs(vec![GasRouterConfig { + domain: REMOTE_DOMAIN, + gas: Some(gas), + }]) + .encode() + .unwrap(), + vec![ + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), false), + ], + )], + Some(&non_owner.pubkey()), + &[&non_owner], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature), + ); +} + +#[tokio::test] +async fn test_transfer_ownership() { + let program_id = hyperlane_sealevel_token_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let new_owner = Some(Pubkey::new_unique()); + + // Transfer ownership + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::TransferOwnership(new_owner) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), true), + ], + )], + Some(&payer.pubkey()), + &[&payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await.unwrap(); + + // Verify the new owner is set + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!(token.owner, new_owner); +} + +#[tokio::test] +async fn test_transfer_ownership_errors_if_owner_not_signer() { + let program_id = hyperlane_sealevel_token_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let new_owner = Some(Pubkey::new_unique()); + let non_owner = new_funded_keypair(&mut banks_client, &payer, ONE_SOL_IN_LAMPORTS).await; + + // Try transferring ownership using the mint authority key + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::TransferOwnership(new_owner) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(non_owner.pubkey(), true), + ], + )], + Some(&non_owner.pubkey()), + &[&non_owner], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidArgument), + ); +} + +#[tokio::test] +async fn test_set_interchain_security_module() { + let program_id = hyperlane_sealevel_token_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let new_ism = Some(Pubkey::new_unique()); + + // Set the ISM + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainSecurityModule(new_ism) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), true), + ], + )], + Some(&payer.pubkey()), + &[&payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await.unwrap(); + + // Verify the new ISM is set + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!(token.interchain_security_module, new_ism); +} + +#[tokio::test] +async fn test_set_interchain_security_module_errors_if_owner_not_signer() { + let program_id = hyperlane_sealevel_token_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let new_ism = Some(Pubkey::new_unique()); + let non_owner = new_funded_keypair(&mut banks_client, &payer, ONE_SOL_IN_LAMPORTS).await; + + // Try setting the ISM using the mint authority key + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainSecurityModule(new_ism) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(non_owner.pubkey(), true), + ], + )], + Some(&non_owner.pubkey()), + &[&non_owner], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidArgument), + ); + + // Also try using the non_owner as the payer and specifying the correct + // owner account, but the owner isn't a signer: + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainSecurityModule(new_ism) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), false), + ], + )], + Some(&non_owner.pubkey()), + &[&non_owner], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature), + ); +} + +#[tokio::test] +async fn test_set_interchain_gas_paymaster() { + let program_id = hyperlane_sealevel_token_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let new_igp = Some(( + Pubkey::new_unique(), + InterchainGasPaymasterType::OverheadIgp(Pubkey::new_unique()), + )); + + // Set the IGP + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainGasPaymaster(new_igp.clone()) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), true), + ], + )], + Some(&payer.pubkey()), + &[&payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await.unwrap(); + + // Verify the new IGP is set + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!(token.interchain_gas_paymaster, new_igp); +} + +#[tokio::test] +async fn test_set_interchain_gas_paymaster_errors_if_owner_not_signer() { + let program_id = hyperlane_sealevel_token_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let new_igp = Some(( + Pubkey::new_unique(), + InterchainGasPaymasterType::OverheadIgp(Pubkey::new_unique()), + )); + let non_owner = new_funded_keypair(&mut banks_client, &payer, ONE_SOL_IN_LAMPORTS).await; + + // Try setting the ISM using the mint authority key + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainGasPaymaster(new_igp.clone()) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(non_owner.pubkey(), true), + ], + )], + Some(&non_owner.pubkey()), + &[&non_owner], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidArgument), + ); + + // Also try using the non_owner as the payer and specifying the correct + // owner account, but the owner isn't a signer: + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainGasPaymaster(new_igp) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), false), + ], + )], + Some(&non_owner.pubkey()), + &[&non_owner], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature), + ); +} diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/Cargo.toml b/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/Cargo.toml new file mode 100644 index 00000000000..b3c05a93da4 --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/Cargo.toml @@ -0,0 +1,47 @@ + + +[package] +name = "hyperlane-sealevel-token-native-memo" +version = "0.1.0" +edition = "2021" + +[features] +no-entrypoint = [] + +[dependencies] +borsh.workspace = true +num-derive.workspace = true +num-traits.workspace = true +solana-program.workspace = true +spl-noop.workspace = true +thiserror.workspace = true + +account-utils = { path = "../../libraries/account-utils" } +hyperlane-core = { path = "../../../main/hyperlane-core" } +hyperlane-sealevel-connection-client = { path = "../../libraries/hyperlane-sealevel-connection-client" } +hyperlane-sealevel-mailbox = { path = "../mailbox", features = [ + "no-entrypoint", +] } +hyperlane-sealevel-igp = { path = "../hyperlane-sealevel-igp", features = [ + "no-entrypoint", +] } +hyperlane-sealevel-message-recipient-interface = { path = "../../libraries/message-recipient-interface" } +hyperlane-sealevel-token-lib = { path = "../../libraries/hyperlane-sealevel-token" } +hyperlane-warp-route = { path = "../../../main/applications/hyperlane-warp-route" } +serializable-account-meta = { path = "../../libraries/serializable-account-meta" } +hyperlane-sealevel-token-native = { path = "../hyperlane-sealevel-token-native" } + +[dev-dependencies] +solana-program-test.workspace = true +solana-sdk.workspace = true + +hyperlane-test-utils = { path = "../../libraries/test-utils" } +hyperlane-sealevel-test-ism = { path = "../ism/test-ism", features = [ + "no-entrypoint", +] } +# Unfortunately required for some functions in `solana-program-test`, and is not +# re-exported +tarpc = "~0.29" + +[lib] +crate-type = ["cdylib", "lib"] diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/src/instruction.rs b/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/src/instruction.rs new file mode 100644 index 00000000000..cd51385a252 --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/src/instruction.rs @@ -0,0 +1,32 @@ +//! Instructions for the program. + +use crate::hyperlane_token_native_collateral_pda_seeds; + +use hyperlane_sealevel_token_lib::instruction::{init_instruction as lib_init_instruction, Init}; + +use solana_program::{ + instruction::{AccountMeta, Instruction as SolanaInstruction}, + program_error::ProgramError, + pubkey::Pubkey, +}; + +/// Gets an instruction to initialize the program. +pub fn init_instruction( + program_id: Pubkey, + payer: Pubkey, + init: Init, +) -> Result { + let mut instruction = lib_init_instruction(program_id, payer, init)?; + + // Add additional account metas: + // 0. `[writable]` The native collateral PDA account. + + let (native_collateral_key, _native_collatera_bump) = + Pubkey::find_program_address(hyperlane_token_native_collateral_pda_seeds!(), &program_id); + + instruction + .accounts + .append(&mut vec![AccountMeta::new(native_collateral_key, false)]); + + Ok(instruction) +} diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/src/lib.rs b/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/src/lib.rs new file mode 100644 index 00000000000..78ec3b2d339 --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/src/lib.rs @@ -0,0 +1,11 @@ +//! Hyperlane token program for native tokens. + +#![deny(warnings)] +#![deny(missing_docs)] +#![deny(unsafe_code)] + +pub mod instruction; +pub mod plugin; +pub mod processor; + +pub use spl_noop; diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/src/plugin.rs b/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/src/plugin.rs new file mode 100644 index 00000000000..cac54e4b643 --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/src/plugin.rs @@ -0,0 +1,207 @@ +//! A plugin for the Hyperlane token program that transfers native +//! tokens in from a sender when sending to a remote chain, and transfers +//! native tokens out to recipients when receiving from a remote chain. + +use account_utils::{create_pda_account, verify_rent_exempt, SizedData}; +use borsh::{BorshDeserialize, BorshSerialize}; +use hyperlane_sealevel_token_lib::{ + accounts::HyperlaneToken, processor::HyperlaneSealevelTokenPlugin, +}; +use hyperlane_warp_route::TokenMessage; +use serializable_account_meta::SerializableAccountMeta; +use solana_program::{ + account_info::{next_account_info, AccountInfo}, + instruction::AccountMeta, + program::{invoke, invoke_signed}, + program_error::ProgramError, + pubkey::Pubkey, + rent::Rent, + system_instruction, + sysvar::Sysvar, +}; + +/// Seeds relating to the PDA account that holds native collateral. +#[macro_export] +macro_rules! hyperlane_token_native_collateral_pda_seeds { + () => {{ + &[b"hyperlane_token", b"-", b"native_collateral"] + }}; + + ($bump_seed:expr) => {{ + &[ + b"hyperlane_token", + b"-", + b"native_collateral", + &[$bump_seed], + ] + }}; +} + +/// A plugin for the Hyperlane token program that transfers native +/// tokens in from a sender when sending to a remote chain, and transfers +/// native tokens out to recipients when receiving from a remote chain. +#[derive(BorshDeserialize, BorshSerialize, Debug, PartialEq, Default)] +pub struct NativePlugin { + /// The bump seed for the native collateral PDA account. + pub native_collateral_bump: u8, +} + +impl SizedData for NativePlugin { + fn size(&self) -> usize { + // native_collateral_bump + std::mem::size_of::() + } +} + +impl NativePlugin { + /// Returns Ok(()) if the native collateral account info is valid. + /// Errors if the key or owner is incorrect. + fn verify_native_collateral_account_info( + program_id: &Pubkey, + token: &HyperlaneToken, + native_collateral_account_info: &AccountInfo, + ) -> Result<(), ProgramError> { + let native_collateral_seeds: &[&[u8]] = + hyperlane_token_native_collateral_pda_seeds!(token.plugin_data.native_collateral_bump); + let expected_native_collateral_key = + Pubkey::create_program_address(native_collateral_seeds, program_id)?; + + if native_collateral_account_info.key != &expected_native_collateral_key { + return Err(ProgramError::InvalidArgument); + } + Ok(()) + } +} + +impl HyperlaneSealevelTokenPlugin for NativePlugin { + /// Initializes the plugin. + /// + /// Accounts: + /// 0. `[writable]` The native collateral PDA account. + fn initialize<'a, 'b>( + program_id: &Pubkey, + system_program: &'a AccountInfo<'b>, + _token_account: &'a AccountInfo<'b>, + payer_account: &'a AccountInfo<'b>, + accounts_iter: &mut std::slice::Iter<'a, AccountInfo<'b>>, + ) -> Result { + // Account 0: Native collateral PDA account. + let native_collateral_account = next_account_info(accounts_iter)?; + let (native_collateral_key, native_collateral_bump) = Pubkey::find_program_address( + hyperlane_token_native_collateral_pda_seeds!(), + program_id, + ); + if &native_collateral_key != native_collateral_account.key { + return Err(ProgramError::InvalidArgument); + } + + // Create native collateral PDA account. + // Assign ownership to the system program so it can transfer tokens. + create_pda_account( + payer_account, + &Rent::get()?, + 0, + &solana_program::system_program::id(), + system_program, + native_collateral_account, + hyperlane_token_native_collateral_pda_seeds!(native_collateral_bump), + )?; + + Ok(Self { + native_collateral_bump, + }) + } + + /// Transfers tokens into the program so they can be sent to a remote chain. + /// Burns the tokens from the sender's associated token account. + /// + /// Accounts: + /// 0. `[executable]` The system program. + /// 1. `[writeable]` The native token collateral PDA account. + fn transfer_in<'a, 'b>( + program_id: &Pubkey, + token: &HyperlaneToken, + sender_wallet: &'a AccountInfo<'b>, + accounts_iter: &mut std::slice::Iter<'a, AccountInfo<'b>>, + amount: u64, + ) -> Result<(), ProgramError> { + // Account 0: System program. + let system_program = next_account_info(accounts_iter)?; + if system_program.key != &solana_program::system_program::id() { + return Err(ProgramError::InvalidArgument); + } + + // Account 1: Native collateral PDA account. + let native_collateral_account = next_account_info(accounts_iter)?; + Self::verify_native_collateral_account_info(program_id, token, native_collateral_account)?; + + // Transfer tokens into the native collateral account. + invoke( + &system_instruction::transfer(sender_wallet.key, native_collateral_account.key, amount), + &[sender_wallet.clone(), native_collateral_account.clone()], + ) + } + + /// Transfers tokens out to a recipient's associated token account as a + /// result of a transfer to this chain from a remote chain. + /// + /// Accounts: + /// 0. `[executable]` The system program. + /// 1. `[writeable]` The native token collateral PDA account. + fn transfer_out<'a, 'b>( + program_id: &Pubkey, + token: &HyperlaneToken, + _system_program: &'a AccountInfo<'b>, + recipient_wallet: &'a AccountInfo<'b>, + accounts_iter: &mut std::slice::Iter<'a, AccountInfo<'b>>, + amount: u64, + ) -> Result<(), ProgramError> { + // Account 0: System program. + let system_program = next_account_info(accounts_iter)?; + if system_program.key != &solana_program::system_program::id() { + return Err(ProgramError::InvalidArgument); + } + + // Account 1: Native collateral PDA account. + let native_collateral_account = next_account_info(accounts_iter)?; + Self::verify_native_collateral_account_info(program_id, token, native_collateral_account)?; + + invoke_signed( + &system_instruction::transfer( + native_collateral_account.key, + recipient_wallet.key, + amount, + ), + &[native_collateral_account.clone(), recipient_wallet.clone()], + &[hyperlane_token_native_collateral_pda_seeds!( + token.plugin_data.native_collateral_bump + )], + )?; + + // Ensure the native collateral account is still rent exempt. + verify_rent_exempt(native_collateral_account, &Rent::get()?)?; + + Ok(()) + } + + /// Returns the accounts required for `transfer_out`. + fn transfer_out_account_metas( + program_id: &Pubkey, + _token: &HyperlaneToken, + _token_message: &TokenMessage, + ) -> Result<(Vec, bool), ProgramError> { + let (native_collateral_key, _native_collateral_bump) = Pubkey::find_program_address( + hyperlane_token_native_collateral_pda_seeds!(), + program_id, + ); + + Ok(( + vec![ + AccountMeta::new_readonly(solana_program::system_program::id(), false).into(), + AccountMeta::new(native_collateral_key, false).into(), + ], + // Recipient wallet must be writeable to send lamports to it. + true, + )) + } +} diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/src/processor.rs b/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/src/processor.rs new file mode 100644 index 00000000000..3ec47f4c06a --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/src/processor.rs @@ -0,0 +1,286 @@ +//! Program processor. + +use account_utils::DiscriminatorDecode; +use hyperlane_sealevel_connection_client::{ + gas_router::GasRouterConfig, router::RemoteRouterConfig, +}; +use hyperlane_sealevel_igp::accounts::InterchainGasPaymasterType; +use hyperlane_sealevel_message_recipient_interface::{ + HandleInstruction, MessageRecipientInstruction, +}; +use hyperlane_sealevel_token_lib::{ + instruction::{ + DymInstruction, Init, Instruction as TokenIxn, TransferRemote, TransferRemoteMemo, + }, + processor::HyperlaneSealevelToken, +}; +use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, msg, pubkey::Pubkey}; + +use crate::plugin::NativePlugin; + +#[cfg(not(feature = "no-entrypoint"))] +solana_program::entrypoint!(process_instruction); + +/// Processes an instruction. +pub fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + // First, check if the instruction has a discriminant relating to + // the message recipient interface. + if let Ok(message_recipient_instruction) = MessageRecipientInstruction::decode(instruction_data) + { + return match message_recipient_instruction { + MessageRecipientInstruction::InterchainSecurityModule => { + interchain_security_module(program_id, accounts) + } + MessageRecipientInstruction::InterchainSecurityModuleAccountMetas => { + interchain_security_module_account_metas(program_id) + } + MessageRecipientInstruction::Handle(handle) => transfer_from_remote( + program_id, + accounts, + HandleInstruction { + origin: handle.origin, + sender: handle.sender, + message: handle.message, + }, + ), + MessageRecipientInstruction::HandleAccountMetas(handle) => { + transfer_from_remote_account_metas( + program_id, + accounts, + HandleInstruction { + origin: handle.origin, + sender: handle.sender, + message: handle.message, + }, + ) + } + }; + } + + if let Ok(instr) = DymInstruction::decode(instruction_data) { + return match instr { + DymInstruction::TransferRemoteMemo(xfer) => { + transfer_remote_memo(program_id, accounts, xfer) + } + } + .map_err(|err| { + msg!("{}", err); + err + }); + } + + // Otherwise, try decoding a "normal" token instruction + match TokenIxn::decode(instruction_data)? { + TokenIxn::Init(init) => initialize(program_id, accounts, init), + TokenIxn::TransferRemote(xfer) => transfer_remote(program_id, accounts, xfer), + TokenIxn::EnrollRemoteRouter(config) => enroll_remote_router(program_id, accounts, config), + TokenIxn::EnrollRemoteRouters(configs) => { + enroll_remote_routers(program_id, accounts, configs) + } + TokenIxn::SetDestinationGasConfigs(configs) => { + set_destination_gas_configs(program_id, accounts, configs) + } + TokenIxn::TransferOwnership(new_owner) => { + transfer_ownership(program_id, accounts, new_owner) + } + TokenIxn::SetInterchainSecurityModule(new_ism) => { + set_interchain_security_module(program_id, accounts, new_ism) + } + TokenIxn::SetInterchainGasPaymaster(new_igp) => { + set_interchain_gas_paymaster(program_id, accounts, new_igp) + } + } + .map_err(|err| { + msg!("{}", err); + err + }) +} + +fn transfer_remote_memo( + program_id: &Pubkey, + accounts: &[AccountInfo], + transfer: TransferRemoteMemo, +) -> ProgramResult { + let base = transfer.base; + let memo = transfer.memo; + HyperlaneSealevelToken::::transfer_remote_memo(program_id, accounts, base, memo) +} + +/// Initializes the program. +/// +/// Accounts: +/// 0. `[executable]` The system program. +/// 1. `[writable]` The token PDA account. +/// 2. `[writable]` The dispatch authority PDA account. +/// 3. `[signer]` The payer and mailbox payer. +/// 4. `[writable]` The native collateral PDA account. +fn initialize(program_id: &Pubkey, accounts: &[AccountInfo], init: Init) -> ProgramResult { + HyperlaneSealevelToken::::initialize(program_id, accounts, init) +} + +/// Transfers tokens to a remote. +/// Transfers the native lamports into the native token collateral PDA account and +/// then dispatches a message to the remote recipient. +/// +/// Accounts: +/// 0. `[executable]` The system program. +/// 1. `[executable]` The spl_noop program. +/// 2. `[]` The token PDA account. +/// 3. `[executable]` The mailbox program. +/// 4. `[writeable]` The mailbox outbox account. +/// 5. `[]` Message dispatch authority. +/// 6. `[signer]` The token sender and mailbox payer. +/// 7. `[signer]` Unique message / gas payment account. +/// 8. `[writeable]` Message storage PDA. +/// ---- If using an IGP ---- +/// 9. `[executable]` The IGP program. +/// 10. `[writeable]` The IGP program data. +/// 11. `[writeable]` Gas payment PDA. +/// 12. `[]` OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. +/// 13. `[writeable]` The IGP account. +/// ---- End if ---- +/// 14. `[executable]` The system program. +/// 15. `[writeable]` The native token collateral PDA account. +fn transfer_remote( + program_id: &Pubkey, + accounts: &[AccountInfo], + transfer: TransferRemote, +) -> ProgramResult { + HyperlaneSealevelToken::::transfer_remote(program_id, accounts, transfer) +} + +/// Accounts: +/// 0. `[signer]` Mailbox processor authority specific to this program. +/// 1. `[executable]` system_program +/// 2. `[]` hyperlane_token storage +/// 3. `[writeable]` recipient wallet address +/// 4. `[executable]` The system program. +/// 5. `[writeable]` The native token collateral PDA account. +fn transfer_from_remote( + program_id: &Pubkey, + accounts: &[AccountInfo], + transfer: HandleInstruction, +) -> ProgramResult { + HyperlaneSealevelToken::::transfer_from_remote(program_id, accounts, transfer) +} + +/// Gets the account metas for a `transfer_from_remote` instruction. +/// +/// Accounts: +/// None +fn transfer_from_remote_account_metas( + program_id: &Pubkey, + accounts: &[AccountInfo], + transfer: HandleInstruction, +) -> ProgramResult { + HyperlaneSealevelToken::::transfer_from_remote_account_metas( + program_id, accounts, transfer, + ) +} + +/// Enrolls a remote router. +/// +/// Accounts: +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. +fn enroll_remote_router( + program_id: &Pubkey, + accounts: &[AccountInfo], + config: RemoteRouterConfig, +) -> ProgramResult { + HyperlaneSealevelToken::::enroll_remote_router(program_id, accounts, config) +} + +/// Enrolls remote routers. +/// +/// Accounts: +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. +fn enroll_remote_routers( + program_id: &Pubkey, + accounts: &[AccountInfo], + configs: Vec, +) -> ProgramResult { + HyperlaneSealevelToken::::enroll_remote_routers(program_id, accounts, configs) +} + +/// Sets the destination gas configs. +/// +/// Accounts: +/// 0. `[executable]` The system program. +/// 1. `[writeable]` The token PDA account. +/// 2. `[signer]` The owner. +fn set_destination_gas_configs( + program_id: &Pubkey, + accounts: &[AccountInfo], + configs: Vec, +) -> ProgramResult { + HyperlaneSealevelToken::::set_destination_gas_configs( + program_id, accounts, configs, + ) +} + +/// Transfers ownership. +/// +/// Accounts: +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The current owner. +fn transfer_ownership( + program_id: &Pubkey, + accounts: &[AccountInfo], + new_owner: Option, +) -> ProgramResult { + HyperlaneSealevelToken::::transfer_ownership(program_id, accounts, new_owner) +} + +/// Gets the interchain security module, returning it as a serialized Option. +/// +/// Accounts: +/// 0. `[]` The token PDA account. +fn interchain_security_module(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { + HyperlaneSealevelToken::::interchain_security_module(program_id, accounts) +} + +/// Gets the account metas for getting the interchain security module. +/// +/// Accounts: +/// None +fn interchain_security_module_account_metas(program_id: &Pubkey) -> ProgramResult { + HyperlaneSealevelToken::::interchain_security_module_account_metas(program_id) +} + +/// Lets the owner set the interchain security module. +/// +/// Accounts: +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The access control owner. +fn set_interchain_security_module( + program_id: &Pubkey, + accounts: &[AccountInfo], + new_ism: Option, +) -> ProgramResult { + HyperlaneSealevelToken::::set_interchain_security_module( + program_id, accounts, new_ism, + ) +} + +/// Lets the owner set the interchain gas paymaster. +/// +/// Accounts: +/// 0. `[writeable]` The token PDA account. +/// 1. `[signer]` The access control owner. +fn set_interchain_gas_paymaster( + program_id: &Pubkey, + accounts: &[AccountInfo], + new_igp: Option<(Pubkey, InterchainGasPaymasterType)>, +) -> ProgramResult { + HyperlaneSealevelToken::::set_interchain_gas_paymaster( + program_id, accounts, new_igp, + ) +} diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/tests/functional.rs b/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/tests/functional.rs new file mode 100644 index 00000000000..0b210b6d704 --- /dev/null +++ b/rust/sealevel/programs/hyperlane-sealevel-token-native-memo/tests/functional.rs @@ -0,0 +1,1333 @@ +//! Contains functional tests for things that cannot be done +//! strictly in unit tests. This includes CPIs, like creating +//! new PDA accounts. + +use account_utils::DiscriminatorEncode; +use hyperlane_core::{Encode, HyperlaneMessage, H256, U256}; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey, + pubkey::Pubkey, +}; +use std::collections::HashMap; + +use hyperlane_sealevel_connection_client::{ + gas_router::GasRouterConfig, router::RemoteRouterConfig, +}; +use hyperlane_sealevel_igp::{ + accounts::{GasPaymentAccount, GasPaymentData, InterchainGasPaymasterType}, + igp_gas_payment_pda_seeds, +}; +use hyperlane_sealevel_mailbox::{ + accounts::{DispatchedMessage, DispatchedMessageAccount}, + mailbox_dispatched_message_pda_seeds, mailbox_message_dispatch_authority_pda_seeds, + mailbox_process_authority_pda_seeds, + protocol_fee::ProtocolFee, +}; +use hyperlane_sealevel_message_recipient_interface::{ + HandleInstruction, MessageRecipientInstruction, +}; +use hyperlane_sealevel_token_lib::{ + accounts::{convert_decimals, HyperlaneToken, HyperlaneTokenAccount}, + hyperlane_token_pda_seeds, + instruction::{ + DymInstruction, Init, Instruction as HyperlaneTokenInstruction, TransferRemote, + TransferRemoteMemo, + }, +}; +use hyperlane_sealevel_token_native::{ + hyperlane_token_native_collateral_pda_seeds, plugin::NativePlugin, +}; +use hyperlane_sealevel_token_native_memo::processor::process_instruction; +use hyperlane_test_utils::{ + assert_lamports, assert_transaction_error, igp_program_id, initialize_igp_accounts, + initialize_mailbox, mailbox_id, new_funded_keypair, process, transfer_lamports, IgpAccounts, +}; +use hyperlane_warp_route::TokenMessage; +use solana_program_test::*; +use solana_sdk::{ + commitment_config::CommitmentLevel, + instruction::InstructionError, + signature::Signer, + signer::keypair::Keypair, + transaction::{Transaction, TransactionError}, +}; +use tarpc::context::Context; + +/// There are 1e9 lamports in one SOL. +const ONE_SOL_IN_LAMPORTS: u64 = 1000000000; +const LOCAL_DOMAIN: u32 = 1234; +const LOCAL_DECIMALS: u8 = 9; +const LOCAL_DECIMALS_U32: u32 = LOCAL_DECIMALS as u32; +const REMOTE_DOMAIN: u32 = 4321; +const REMOTE_DECIMALS: u8 = 18; +const REMOTE_GAS_AMOUNT: u64 = 200000; + +fn hyperlane_sealevel_token_native_id() -> Pubkey { + pubkey!("CGn8yNtSD3aTTqJfYhUb6s1aVTN75NzwtsFKo1e83aga") +} + +async fn setup_client() -> (BanksClient, Keypair) { + let program_id = hyperlane_sealevel_token_native_id(); + let mut program_test = ProgramTest::new( + "hyperlane_sealevel_token_native", + program_id, + processor!(process_instruction), + ); + + program_test.add_program("spl_noop", spl_noop::id(), processor!(spl_noop::noop)); + + let mailbox_program_id = mailbox_id(); + program_test.add_program( + "hyperlane_sealevel_mailbox", + mailbox_program_id, + processor!(hyperlane_sealevel_mailbox::processor::process_instruction), + ); + + program_test.add_program( + "hyperlane_sealevel_igp", + igp_program_id(), + processor!(hyperlane_sealevel_igp::processor::process_instruction), + ); + + // This serves as the default ISM on the Mailbox + program_test.add_program( + "hyperlane_sealevel_test_ism", + hyperlane_sealevel_test_ism::id(), + processor!(hyperlane_sealevel_test_ism::program::process_instruction), + ); + + let (banks_client, payer, _recent_blockhash) = program_test.start().await; + + (banks_client, payer) +} + +struct HyperlaneTokenAccounts { + token: Pubkey, + token_bump: u8, + mailbox_process_authority: Pubkey, + dispatch_authority: Pubkey, + dispatch_authority_bump: u8, + native_collateral: Pubkey, + native_collateral_bump: u8, +} + +async fn initialize_hyperlane_token( + program_id: &Pubkey, + banks_client: &mut BanksClient, + payer: &Keypair, + igp_accounts: Option<&IgpAccounts>, +) -> Result { + let (mailbox_process_authority_key, _mailbox_process_authority_bump) = + Pubkey::find_program_address( + mailbox_process_authority_pda_seeds!(program_id), + &mailbox_id(), + ); + + let (token_account_key, token_account_bump_seed) = + Pubkey::find_program_address(hyperlane_token_pda_seeds!(), program_id); + + let (dispatch_authority_key, dispatch_authority_seed) = + Pubkey::find_program_address(mailbox_message_dispatch_authority_pda_seeds!(), program_id); + + let (native_collateral_account_key, native_collateral_account_bump_seed) = + Pubkey::find_program_address(hyperlane_token_native_collateral_pda_seeds!(), program_id); + + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + *program_id, + &HyperlaneTokenInstruction::Init(Init { + mailbox: mailbox_id(), + interchain_security_module: None, + interchain_gas_paymaster: igp_accounts.map(|igp_accounts| { + ( + igp_accounts.program, + InterchainGasPaymasterType::OverheadIgp(igp_accounts.overhead_igp), + ) + }), + decimals: LOCAL_DECIMALS, + remote_decimals: REMOTE_DECIMALS, + }) + .encode() + .unwrap(), + vec![ + // 0. `[executable]` The system program. + // 1. `[writable]` The token PDA account. + // 2. `[writable]` The dispatch authority PDA account. + // 3. `[signer]` The payer and mailbox payer. + // 4. `[writable]` The native collateral PDA account. + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(token_account_key, false), + AccountMeta::new(dispatch_authority_key, false), + AccountMeta::new_readonly(payer.pubkey(), true), + AccountMeta::new(native_collateral_account_key, false), + ], + )], + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await?; + + // Set destination gas configs + set_destination_gas_config( + banks_client, + program_id, + payer, + &token_account_key, + REMOTE_DOMAIN, + REMOTE_GAS_AMOUNT, + ) + .await?; + + Ok(HyperlaneTokenAccounts { + token: token_account_key, + token_bump: token_account_bump_seed, + mailbox_process_authority: mailbox_process_authority_key, + dispatch_authority: dispatch_authority_key, + dispatch_authority_bump: dispatch_authority_seed, + native_collateral: native_collateral_account_key, + native_collateral_bump: native_collateral_account_bump_seed, + }) +} + +async fn enroll_remote_router( + banks_client: &mut BanksClient, + program_id: &Pubkey, + payer: &Keypair, + token_account: &Pubkey, + domain: u32, + router: H256, +) -> Result<(), BanksClientError> { + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + // Enroll the remote router + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + *program_id, + &HyperlaneTokenInstruction::EnrollRemoteRouter(RemoteRouterConfig { + domain, + router: Some(router), + }) + .encode() + .unwrap(), + vec![ + AccountMeta::new(solana_program::system_program::id(), false), + AccountMeta::new(*token_account, false), + AccountMeta::new_readonly(payer.pubkey(), true), + ], + )], + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await?; + + Ok(()) +} + +async fn set_destination_gas_config( + banks_client: &mut BanksClient, + program_id: &Pubkey, + payer: &Keypair, + token_account: &Pubkey, + domain: u32, + gas: u64, +) -> Result<(), BanksClientError> { + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + // Enroll the remote router + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + *program_id, + &HyperlaneTokenInstruction::SetDestinationGasConfigs(vec![GasRouterConfig { + domain, + gas: Some(gas), + }]) + .encode() + .unwrap(), + vec![ + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(*token_account, false), + AccountMeta::new_readonly(payer.pubkey(), true), + ], + )], + Some(&payer.pubkey()), + &[payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await?; + + Ok(()) +} + +#[tokio::test] +async fn test_initialize() { + let program_id = hyperlane_sealevel_token_native_id(); + let mailbox_program_id = mailbox_id(); + + let (mut banks_client, payer) = setup_client().await; + + let mailbox_accounts = initialize_mailbox( + &mut banks_client, + &mailbox_program_id, + &payer, + LOCAL_DOMAIN, + ONE_SOL_IN_LAMPORTS, + ProtocolFee::default(), + ) + .await + .unwrap(); + + let igp_accounts = + initialize_igp_accounts(&mut banks_client, &igp_program_id(), &payer, REMOTE_DOMAIN) + .await + .unwrap(); + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, Some(&igp_accounts)) + .await + .unwrap(); + + // Get the token account. + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!( + token, + Box::new(HyperlaneToken { + bump: hyperlane_token_accounts.token_bump, + mailbox: mailbox_accounts.program, + mailbox_process_authority: hyperlane_token_accounts.mailbox_process_authority, + dispatch_authority_bump: hyperlane_token_accounts.dispatch_authority_bump, + decimals: LOCAL_DECIMALS, + remote_decimals: REMOTE_DECIMALS, + owner: Some(payer.pubkey()), + interchain_security_module: None, + interchain_gas_paymaster: Some(( + igp_accounts.program, + InterchainGasPaymasterType::OverheadIgp(igp_accounts.overhead_igp), + )), + destination_gas: HashMap::from([(REMOTE_DOMAIN, REMOTE_GAS_AMOUNT)]), + remote_routers: HashMap::new(), + plugin_data: NativePlugin { + native_collateral_bump: hyperlane_token_accounts.native_collateral_bump, + }, + }), + ); + + // Verify the ATA payer account was created. + let native_collateral_account = banks_client + .get_account(hyperlane_token_accounts.native_collateral) + .await + .unwrap() + .unwrap(); + assert!(native_collateral_account.lamports > 0); +} + +#[tokio::test] +async fn test_initialize_errors_if_called_twice() { + let program_id = hyperlane_sealevel_token_native_id(); + + let (mut banks_client, payer) = setup_client().await; + + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let other_payer = new_funded_keypair(&mut banks_client, &payer, ONE_SOL_IN_LAMPORTS).await; + + // To ensure a different signature is used, we'll use a different payer + let init_result = + initialize_hyperlane_token(&program_id, &mut banks_client, &other_payer, None).await; + + assert_transaction_error( + init_result, + TransactionError::InstructionError(0, InstructionError::AccountAlreadyInitialized), + ); +} + +#[tokio::test] +async fn test_transfer_remote_memo() { + let program_id = hyperlane_sealevel_token_native_id(); + let mailbox_program_id = mailbox_id(); + + let (mut banks_client, payer) = setup_client().await; + + let mailbox_accounts = initialize_mailbox( + &mut banks_client, + &mailbox_program_id, + &payer, + LOCAL_DOMAIN, + ONE_SOL_IN_LAMPORTS, + ProtocolFee::default(), + ) + .await + .unwrap(); + + let igp_accounts = + initialize_igp_accounts(&mut banks_client, &igp_program_id(), &payer, REMOTE_DOMAIN) + .await + .unwrap(); + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, Some(&igp_accounts)) + .await + .unwrap(); + + // Enroll the remote router + let remote_router = H256::random(); + enroll_remote_router( + &mut banks_client, + &program_id, + &payer, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + remote_router, + ) + .await + .unwrap(); + + // Send 100 SOL for the token sender to start with. + let token_sender = + new_funded_keypair(&mut banks_client, &payer, 100 * ONE_SOL_IN_LAMPORTS).await; + let token_sender_pubkey = token_sender.pubkey(); + + // Call transfer_remote + let unique_message_account_keypair = Keypair::new(); + let (dispatched_message_key, _dispatched_message_bump) = Pubkey::find_program_address( + mailbox_dispatched_message_pda_seeds!(&unique_message_account_keypair.pubkey()), + &mailbox_program_id, + ); + let (gas_payment_pda_key, _gas_payment_pda_bump) = Pubkey::find_program_address( + igp_gas_payment_pda_seeds!(&unique_message_account_keypair.pubkey()), + &igp_program_id(), + ); + + let remote_token_recipient = H256::random(); + // Transfer 69 tokens. + let transfer_amount = 69 * ONE_SOL_IN_LAMPORTS; + let remote_transfer_amount = + convert_decimals(transfer_amount.into(), LOCAL_DECIMALS, REMOTE_DECIMALS).unwrap(); + + let sender_balance_before = banks_client.get_balance(token_sender_pubkey).await.unwrap(); + let native_collateral_account_lamports_before = banks_client + .get_balance(hyperlane_token_accounts.native_collateral) + .await + .unwrap(); + + let test_memo = vec![1, 2, 3]; + + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &DymInstruction::TransferRemoteMemo(TransferRemoteMemo { + base: TransferRemote { + destination_domain: REMOTE_DOMAIN, + recipient: remote_token_recipient, + amount_or_id: transfer_amount.into(), + }, + memo: test_memo.clone(), + }) + .encode() + .unwrap(), + // 0. `[executable]` The system program. + // 1. `[executable]` The spl_noop program. + // 2. `[]` The token PDA account. + // 3. `[executable]` The mailbox program. + // 4. `[writeable]` The mailbox outbox account. + // 5. `[]` Message dispatch authority. + // 6. `[signer]` The token sender and mailbox payer. + // 7. `[signer]` Unique message / gas payment account. + // 8. `[writeable]` Message storage PDA. + // ---- If using an IGP ---- + // 9. `[executable]` The IGP program. + // 10. `[writeable]` The IGP program data. + // 11. `[writeable]` Gas payment PDA. + // 12. `[]` OPTIONAL - The Overhead IGP program, if the configured IGP is an Overhead IGP. + // 13. `[writeable]` The IGP account. + // ---- End if ---- + // 14. `[executable]` The system program. + // 15. `[writeable]` The native token collateral PDA account. + vec![ + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new_readonly(spl_noop::id(), false), + AccountMeta::new_readonly(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(mailbox_accounts.program, false), + AccountMeta::new(mailbox_accounts.outbox, false), + AccountMeta::new_readonly(hyperlane_token_accounts.dispatch_authority, false), + AccountMeta::new_readonly(token_sender_pubkey, true), + AccountMeta::new_readonly(unique_message_account_keypair.pubkey(), true), + AccountMeta::new(dispatched_message_key, false), + AccountMeta::new_readonly(igp_accounts.program, false), + AccountMeta::new(igp_accounts.program_data, false), + AccountMeta::new(gas_payment_pda_key, false), + AccountMeta::new_readonly(igp_accounts.overhead_igp, false), + AccountMeta::new(igp_accounts.igp, false), + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(hyperlane_token_accounts.native_collateral, false), + ], + )], + Some(&token_sender_pubkey), + &[&token_sender, &unique_message_account_keypair], + recent_blockhash, + ); + + let transaction_fee = banks_client + .get_fee_for_message_with_commitment_and_context( + Context::current(), + CommitmentLevel::Processed, + transaction.message.clone(), + ) + .await + .unwrap() + .unwrap(); + + let tx_signature = transaction.signatures[0]; + banks_client.process_transaction(transaction).await.unwrap(); + + // The transaction fee doesn't seem to be entirely accurate - + // this may be due to a mismatch between the SDK and the actual + // transaction fee calculation. + // For now, we'll just check that the sender's balance roughly correct. + let sender_balance_after = banks_client.get_balance(token_sender_pubkey).await.unwrap(); + let expected_balance_after = sender_balance_before - transfer_amount - transaction_fee; + // Allow 0.005 SOL of extra transaction fees + assert!( + sender_balance_after >= expected_balance_after - 5000000 + && sender_balance_after <= expected_balance_after + ); + + // And that the native collateral account's balance is 69 tokens. + assert_lamports( + &mut banks_client, + &hyperlane_token_accounts.native_collateral, + native_collateral_account_lamports_before + transfer_amount, + ) + .await; + + // And let's take a look at the dispatched message account data to verify the message looks right. + let dispatched_message_account_data = banks_client + .get_account(dispatched_message_key) + .await + .unwrap() + .unwrap() + .data; + let dispatched_message = + DispatchedMessageAccount::fetch(&mut &dispatched_message_account_data[..]) + .unwrap() + .into_inner(); + + let transfer_remote_tx_status = banks_client + .get_transaction_status(tx_signature) + .await + .unwrap() + .unwrap(); + + let message = HyperlaneMessage { + version: 3, + nonce: 0, + origin: LOCAL_DOMAIN, + sender: program_id.to_bytes().into(), + destination: REMOTE_DOMAIN, + recipient: remote_router, + // Expect the remote_transfer_amount to be in the message. + body: TokenMessage::new(remote_token_recipient, remote_transfer_amount, test_memo).to_vec(), + }; + + assert_eq!( + dispatched_message, + Box::new(DispatchedMessage::new( + message.nonce, + transfer_remote_tx_status.slot, + unique_message_account_keypair.pubkey(), + message.to_vec(), + )), + ); + + // And let's also look at the gas payment account to verify the gas payment looks right. + let gas_payment_account_data = banks_client + .get_account(gas_payment_pda_key) + .await + .unwrap() + .unwrap() + .data; + let gas_payment = GasPaymentAccount::fetch(&mut &gas_payment_account_data[..]) + .unwrap() + .into_inner(); + + assert_eq!( + *gas_payment, + GasPaymentData { + sequence_number: 0, + igp: igp_accounts.igp, + destination_domain: REMOTE_DOMAIN, + message_id: message.id(), + gas_amount: REMOTE_GAS_AMOUNT, + unique_gas_payment_pubkey: unique_message_account_keypair.pubkey(), + slot: transfer_remote_tx_status.slot, + payment: REMOTE_GAS_AMOUNT + } + .into(), + ); +} + +async fn transfer_from_remote( + initial_native_collateral_balance: u64, + remote_transfer_amount: U256, + sender_override: Option, + origin_override: Option, +) -> Result<(BanksClient, HyperlaneTokenAccounts, Pubkey), BanksClientError> { + let program_id = hyperlane_sealevel_token_native_id(); + let mailbox_program_id = mailbox_id(); + + let (mut banks_client, payer) = setup_client().await; + + let mailbox_accounts = initialize_mailbox( + &mut banks_client, + &mailbox_program_id, + &payer, + LOCAL_DOMAIN, + ONE_SOL_IN_LAMPORTS, + ProtocolFee::default(), + ) + .await + .unwrap(); + + let igp_accounts = + initialize_igp_accounts(&mut banks_client, &igp_program_id(), &payer, REMOTE_DOMAIN) + .await + .unwrap(); + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, Some(&igp_accounts)) + .await + .unwrap(); + + // Enroll the remote router + let remote_router = H256::random(); + enroll_remote_router( + &mut banks_client, + &program_id, + &payer, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + remote_router, + ) + .await + .unwrap(); + + // The native collateral account will have some lamports because it's rent-exempt. + let current_native_collateral_balance = banks_client + .get_balance(hyperlane_token_accounts.native_collateral) + .await + .unwrap(); + + // Give an initial balance to the native collateral account which will be used by the + // transfer_from_remote. + transfer_lamports( + &mut banks_client, + &payer, + &hyperlane_token_accounts.native_collateral, + initial_native_collateral_balance.saturating_sub(current_native_collateral_balance), + ) + .await; + + let recipient_pubkey = Pubkey::new_unique(); + let recipient: H256 = recipient_pubkey.to_bytes().into(); + + let message = HyperlaneMessage { + version: 3, + nonce: 0, + origin: origin_override.unwrap_or(REMOTE_DOMAIN), + // Default to the remote router as the sender + sender: sender_override.unwrap_or(remote_router), + destination: LOCAL_DOMAIN, + recipient: program_id.to_bytes().into(), + body: TokenMessage::new(recipient, remote_transfer_amount, vec![]).to_vec(), + }; + + process( + &mut banks_client, + &payer, + &mailbox_accounts, + vec![], + &message, + ) + .await?; + + Ok((banks_client, hyperlane_token_accounts, recipient_pubkey)) +} + +// Tests when the SPL token is the non-2022 version +#[tokio::test] +async fn test_transfer_from_success() { + let initial_native_collateral_balance = 100 * 10u64.pow(LOCAL_DECIMALS_U32); + let local_transfer_amount = 69 * 10u64.pow(LOCAL_DECIMALS_U32); + let remote_transfer_amount = convert_decimals( + local_transfer_amount.into(), + LOCAL_DECIMALS, + REMOTE_DECIMALS, + ) + .unwrap(); + + let (mut banks_client, hyperlane_token_accounts, recipient_associated_token_account) = + transfer_from_remote( + initial_native_collateral_balance, + remote_transfer_amount, + None, + None, + ) + .await + .unwrap(); + + // Check that the recipient's ATA got the tokens! + assert_lamports( + &mut banks_client, + &recipient_associated_token_account, + local_transfer_amount, + ) + .await; + + // And that the native collateral's balance is lower because it was spent in the transfer. + assert_lamports( + &mut banks_client, + &hyperlane_token_accounts.native_collateral, + initial_native_collateral_balance - local_transfer_amount, + ) + .await; +} + +#[tokio::test] +async fn test_transfer_from_remote_errors_if_sender_not_router() { + let initial_native_collateral_balance = 100 * 10u64.pow(LOCAL_DECIMALS_U32); + let local_transfer_amount = 69 * 10u64.pow(LOCAL_DECIMALS_U32); + let remote_transfer_amount = convert_decimals( + local_transfer_amount.into(), + LOCAL_DECIMALS, + REMOTE_DECIMALS, + ) + .unwrap(); + + // Same remote domain origin, but wrong sender. + let result = transfer_from_remote( + initial_native_collateral_balance, + remote_transfer_amount, + Some(H256::random()), + None, + ) + .await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidInstructionData), + ); + + // Wrong remote domain origin, but correct sender. + let result = transfer_from_remote( + initial_native_collateral_balance, + remote_transfer_amount, + None, + Some(REMOTE_DOMAIN + 1), + ) + .await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidInstructionData), + ); +} + +#[tokio::test] +async fn test_transfer_from_remote_errors_if_process_authority_not_signer() { + let program_id = hyperlane_sealevel_token_native_id(); + let mailbox_program_id = mailbox_id(); + + let (mut banks_client, payer) = setup_client().await; + + let _mailbox_accounts = initialize_mailbox( + &mut banks_client, + &mailbox_program_id, + &payer, + LOCAL_DOMAIN, + ONE_SOL_IN_LAMPORTS, + ProtocolFee::default(), + ) + .await + .unwrap(); + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + // Enroll the remote router + let remote_router = H256::random(); + enroll_remote_router( + &mut banks_client, + &program_id, + &payer, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + remote_router, + ) + .await + .unwrap(); + + let recipient_pubkey = Pubkey::new_unique(); + let recipient: H256 = recipient_pubkey.to_bytes().into(); + + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + // Try calling directly into the message handler, skipping the mailbox. + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &MessageRecipientInstruction::Handle(HandleInstruction { + origin: REMOTE_DOMAIN, + sender: remote_router, + message: TokenMessage::new(recipient, 12345u64.into(), vec![]).to_vec(), + }) + .encode() + .unwrap(), + vec![ + // Recipient.handle accounts + // 0. `[signer]` Mailbox processor authority specific to this program. + // 1. `[executable]` system_program + // 2. `[]` hyperlane_token storage + // 3. `[writeable]` recipient wallet address + // 4. `[executable]` The system program. + // 5. `[writeable]` The native token collateral PDA account. + AccountMeta::new_readonly( + hyperlane_token_accounts.mailbox_process_authority, + false, + ), + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new_readonly(hyperlane_token_accounts.token, false), + AccountMeta::new(recipient_pubkey, false), + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(hyperlane_token_accounts.native_collateral, false), + ], + )], + Some(&payer.pubkey()), + &[&payer], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature), + ); +} + +#[tokio::test] +async fn test_enroll_remote_router() { + let program_id = hyperlane_sealevel_token_native_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + // Enroll the remote router + let remote_router = H256::random(); + enroll_remote_router( + &mut banks_client, + &program_id, + &payer, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + remote_router, + ) + .await + .unwrap(); + + // Verify the remote router was enrolled. + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!( + token.remote_routers, + vec![(REMOTE_DOMAIN, remote_router)].into_iter().collect(), + ); +} + +#[tokio::test] +async fn test_enroll_remote_router_errors_if_not_signed_by_owner() { + let program_id = hyperlane_sealevel_token_native_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let non_owner = new_funded_keypair(&mut banks_client, &payer, ONE_SOL_IN_LAMPORTS).await; + + // Use the mint authority as the payer, which has a balance but is not the owner, + // so we expect this to fail. + let result = enroll_remote_router( + &mut banks_client, + &program_id, + &non_owner, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + H256::random(), + ) + .await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidArgument), + ); + + // Also try using the mint authority as the payer and specifying the correct + // owner account, but the owner isn't a signer: + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + // Enroll the remote router + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::EnrollRemoteRouter(RemoteRouterConfig { + domain: REMOTE_DOMAIN, + router: Some(H256::random()), + }) + .encode() + .unwrap(), + vec![ + AccountMeta::new(solana_program::system_program::id(), false), + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), false), + ], + )], + Some(&non_owner.pubkey()), + &[&non_owner], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature), + ); +} + +#[tokio::test] +async fn test_set_destination_gas_configs() { + let program_id = hyperlane_sealevel_token_native_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + // Set the destination gas config + let gas = 111222333; + set_destination_gas_config( + &mut banks_client, + &program_id, + &payer, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + gas, + ) + .await + .unwrap(); + + // Verify the destination gas was set. + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!( + token.destination_gas, + vec![(REMOTE_DOMAIN, gas)].into_iter().collect(), + ); +} + +#[tokio::test] +async fn test_set_destination_gas_configs_errors_if_not_signed_by_owner() { + let program_id = hyperlane_sealevel_token_native_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let non_owner = new_funded_keypair(&mut banks_client, &payer, ONE_SOL_IN_LAMPORTS).await; + + // Use the non_owner as the payer, which has a balance but is not the owner, + // so we expect this to fail. + let gas = 111222333; + let result = set_destination_gas_config( + &mut banks_client, + &program_id, + &non_owner, + &hyperlane_token_accounts.token, + REMOTE_DOMAIN, + gas, + ) + .await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidArgument), + ); + + // Also try using the non_owner as the payer and specifying the correct + // owner account, but the owner isn't a signer: + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + // Try setting + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetDestinationGasConfigs(vec![GasRouterConfig { + domain: REMOTE_DOMAIN, + gas: Some(gas), + }]) + .encode() + .unwrap(), + vec![ + AccountMeta::new_readonly(solana_program::system_program::id(), false), + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), false), + ], + )], + Some(&non_owner.pubkey()), + &[&non_owner], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature), + ); +} + +#[tokio::test] +async fn test_transfer_ownership() { + let program_id = hyperlane_sealevel_token_native_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let new_owner = Some(Pubkey::new_unique()); + + // Transfer ownership + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::TransferOwnership(new_owner) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), true), + ], + )], + Some(&payer.pubkey()), + &[&payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await.unwrap(); + + // Verify the new owner is set + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!(token.owner, new_owner); +} + +#[tokio::test] +async fn test_transfer_ownership_errors_if_owner_not_signer() { + let program_id = hyperlane_sealevel_token_native_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let new_owner = Some(Pubkey::new_unique()); + let non_owner = new_funded_keypair(&mut banks_client, &payer, ONE_SOL_IN_LAMPORTS).await; + + // Try transferring ownership using the mint authority key + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::TransferOwnership(new_owner) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(non_owner.pubkey(), true), + ], + )], + Some(&non_owner.pubkey()), + &[&non_owner], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidArgument), + ); +} + +#[tokio::test] +async fn test_set_interchain_security_module() { + let program_id = hyperlane_sealevel_token_native_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let new_ism = Some(Pubkey::new_unique()); + + // Set the ISM + // Transfer ownership + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainSecurityModule(new_ism) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), true), + ], + )], + Some(&payer.pubkey()), + &[&payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await.unwrap(); + + // Verify the new ISM is set + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!(token.interchain_security_module, new_ism); +} + +#[tokio::test] +async fn test_set_interchain_security_module_errors_if_owner_not_signer() { + let program_id = hyperlane_sealevel_token_native_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let new_ism = Some(Pubkey::new_unique()); + let non_owner = new_funded_keypair(&mut banks_client, &payer, ONE_SOL_IN_LAMPORTS).await; + + // Try setting the ISM using the non_owner key + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainSecurityModule(new_ism) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(non_owner.pubkey(), true), + ], + )], + Some(&non_owner.pubkey()), + &[&non_owner], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidArgument), + ); + + // Also try using the non_owner as the payer and specifying the correct + // owner account, but the owner isn't a signer: + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainSecurityModule(new_ism) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), false), + ], + )], + Some(&non_owner.pubkey()), + &[&non_owner], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature), + ); +} + +#[tokio::test] +async fn test_set_interchain_gas_paymaster() { + let program_id = hyperlane_sealevel_token_native_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let new_igp = Some(( + Pubkey::new_unique(), + InterchainGasPaymasterType::OverheadIgp(Pubkey::new_unique()), + )); + + // Set the IGP + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainGasPaymaster(new_igp.clone()) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), true), + ], + )], + Some(&payer.pubkey()), + &[&payer], + recent_blockhash, + ); + banks_client.process_transaction(transaction).await.unwrap(); + + // Verify the new IGP is set + let token_account_data = banks_client + .get_account(hyperlane_token_accounts.token) + .await + .unwrap() + .unwrap() + .data; + let token = HyperlaneTokenAccount::::fetch(&mut &token_account_data[..]) + .unwrap() + .into_inner(); + assert_eq!(token.interchain_gas_paymaster, new_igp); +} + +#[tokio::test] +async fn test_set_interchain_gas_paymaster_errors_if_owner_not_signer() { + let program_id = hyperlane_sealevel_token_native_id(); + + let (mut banks_client, payer) = setup_client().await; + + let hyperlane_token_accounts = + initialize_hyperlane_token(&program_id, &mut banks_client, &payer, None) + .await + .unwrap(); + + let new_igp = Some(( + Pubkey::new_unique(), + InterchainGasPaymasterType::OverheadIgp(Pubkey::new_unique()), + )); + let non_owner = new_funded_keypair(&mut banks_client, &payer, ONE_SOL_IN_LAMPORTS).await; + + // Try setting the ISM using the mint authority key + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainGasPaymaster(new_igp.clone()) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(non_owner.pubkey(), true), + ], + )], + Some(&non_owner.pubkey()), + &[&non_owner], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::InvalidArgument), + ); + + // Also try using the non_owner as the payer and specifying the correct + // owner account, but the owner isn't a signer: + let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); + let transaction = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + program_id, + &HyperlaneTokenInstruction::SetInterchainGasPaymaster(new_igp) + .encode() + .unwrap(), + vec![ + AccountMeta::new(hyperlane_token_accounts.token, false), + AccountMeta::new_readonly(payer.pubkey(), false), + ], + )], + Some(&non_owner.pubkey()), + &[&non_owner], + recent_blockhash, + ); + let result = banks_client.process_transaction(transaction).await; + assert_transaction_error( + result, + TransactionError::InstructionError(0, InstructionError::MissingRequiredSignature), + ); +} diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-native/Cargo.toml b/rust/sealevel/programs/hyperlane-sealevel-token-native/Cargo.toml index 2e89ce51796..a4bb650b7c5 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token-native/Cargo.toml +++ b/rust/sealevel/programs/hyperlane-sealevel-token-native/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-sealevel-token-native" diff --git a/rust/sealevel/programs/hyperlane-sealevel-token/Cargo.toml b/rust/sealevel/programs/hyperlane-sealevel-token/Cargo.toml index 1b0ae7ebccc..4ac11a21a53 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token/Cargo.toml +++ b/rust/sealevel/programs/hyperlane-sealevel-token/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-sealevel-token" diff --git a/rust/sealevel/programs/ism/multisig-ism-message-id/Cargo.toml b/rust/sealevel/programs/ism/multisig-ism-message-id/Cargo.toml index 53136752158..1cd16c8c921 100644 --- a/rust/sealevel/programs/ism/multisig-ism-message-id/Cargo.toml +++ b/rust/sealevel/programs/ism/multisig-ism-message-id/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-sealevel-multisig-ism-message-id" diff --git a/rust/sealevel/programs/ism/test-ism/Cargo.toml b/rust/sealevel/programs/ism/test-ism/Cargo.toml index 113fdede375..98dcc6f8b20 100644 --- a/rust/sealevel/programs/ism/test-ism/Cargo.toml +++ b/rust/sealevel/programs/ism/test-ism/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-sealevel-test-ism" diff --git a/rust/sealevel/programs/mailbox-test/Cargo.toml b/rust/sealevel/programs/mailbox-test/Cargo.toml index a48891f067d..0ad39634da8 100644 --- a/rust/sealevel/programs/mailbox-test/Cargo.toml +++ b/rust/sealevel/programs/mailbox-test/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-sealevel-mailbox-test" diff --git a/rust/sealevel/programs/mailbox/Cargo.toml b/rust/sealevel/programs/mailbox/Cargo.toml index 1d09b5e935b..d98202360a2 100644 --- a/rust/sealevel/programs/mailbox/Cargo.toml +++ b/rust/sealevel/programs/mailbox/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-sealevel-mailbox" diff --git a/rust/sealevel/programs/test-send-receiver/Cargo.toml b/rust/sealevel/programs/test-send-receiver/Cargo.toml index 785080d8774..6152134415c 100644 --- a/rust/sealevel/programs/test-send-receiver/Cargo.toml +++ b/rust/sealevel/programs/test-send-receiver/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-sealevel-test-send-receiver" diff --git a/rust/sealevel/programs/validator-announce/Cargo.toml b/rust/sealevel/programs/validator-announce/Cargo.toml index d508349338f..2aacfa9df7e 100644 --- a/rust/sealevel/programs/validator-announce/Cargo.toml +++ b/rust/sealevel/programs/validator-announce/Cargo.toml @@ -1,4 +1,4 @@ -cargo-features = ["workspace-inheritance"] + [package] name = "hyperlane-sealevel-validator-announce" diff --git a/solidity/CHANGELOG.md b/solidity/CHANGELOG.md index 043ca0da954..059c1b5dee3 100644 --- a/solidity/CHANGELOG.md +++ b/solidity/CHANGELOG.md @@ -1,5 +1,50 @@ # @hyperlane-xyz/core +## 9.0.9 + +### Patch Changes + +- @hyperlane-xyz/utils@18.2.0 + +## 9.0.8 + +### Patch Changes + +- @hyperlane-xyz/utils@18.1.0 + +## 9.0.7 + +### Patch Changes + +- Updated dependencies [cfc0eb2a7] + - @hyperlane-xyz/utils@18.0.0 + +## 9.0.6 + +### Patch Changes + +- Updated dependencies [8c15edc67] +- Updated dependencies [e0bda316a] + - @hyperlane-xyz/utils@17.0.0 + +## 9.0.5 + +### Patch Changes + +- @hyperlane-xyz/utils@16.2.0 + +## 9.0.4 + +### Patch Changes + +- @hyperlane-xyz/utils@16.1.1 + +## 9.0.3 + +### Patch Changes + +- @hyperlane-xyz/utils@16.1.0 + ## 9.0.2 ### Patch Changes diff --git a/solidity/contracts/PackageVersioned.sol b/solidity/contracts/PackageVersioned.sol index 7eec1f849a4..4ad84a880c5 100644 --- a/solidity/contracts/PackageVersioned.sol +++ b/solidity/contracts/PackageVersioned.sol @@ -7,5 +7,5 @@ pragma solidity >=0.6.11; **/ abstract contract PackageVersioned { // GENERATED CODE - DO NOT EDIT - string public constant PACKAGE_VERSION = "9.0.2"; + string public constant PACKAGE_VERSION = "9.0.9"; } diff --git a/solidity/contracts/token/extensions/HypERC20CollateralMemo.sol b/solidity/contracts/token/extensions/HypERC20CollateralMemo.sol new file mode 100644 index 00000000000..9d4113cb180 --- /dev/null +++ b/solidity/contracts/token/extensions/HypERC20CollateralMemo.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; + +import {HypERC20Collateral} from "../HypERC20Collateral.sol"; + +// collateral +contract HypERC20CollateralMemo is HypERC20Collateral { + event IncludedMemo(bytes memo); + bytes private _memo; + + constructor( + address erc20, + uint256 _scale, + address _mailbox + ) HypERC20Collateral(erc20, _scale, _mailbox) {} + + function transferRemoteMemo( + uint32 _destination, + bytes32 _recipient, + uint256 _amountOrId, + bytes calldata memo + ) external payable virtual returns (bytes32 messageId) { + _memo = memo; + return + _transferRemote(_destination, _recipient, _amountOrId, msg.value); + } + + function _transferFromSender( + uint256 _amount + ) internal virtual override returns (bytes memory) { + // Follow CEI pattern: read and clear _memo BEFORE external call + // to prevent reentrancy if wrappedToken has callbacks (ERC777, custom hooks) + bytes memory memo = _memo; + delete _memo; + + super._transferFromSender(_amount); + + emit IncludedMemo(memo); + return memo; + } +} diff --git a/solidity/contracts/token/extensions/HypERC20Memo.sol b/solidity/contracts/token/extensions/HypERC20Memo.sol new file mode 100644 index 00000000000..42f4db9f3ab --- /dev/null +++ b/solidity/contracts/token/extensions/HypERC20Memo.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; + +import {HypERC20} from "../HypERC20.sol"; + +// synthetic +contract HypERC20Memo is HypERC20 { + event IncludedMemo(bytes memo); + bytes private _memo; + + constructor( + uint8 __decimals, + uint256 _scale, + address _mailbox + ) HypERC20(__decimals, _scale, _mailbox) {} + + function transferRemoteMemo( + uint32 _destination, + bytes32 _recipient, + uint256 _amountOrId, + bytes calldata memo + ) external payable virtual returns (bytes32 messageId) { + _memo = memo; + return + _transferRemote(_destination, _recipient, _amountOrId, msg.value); + } + + function _transferFromSender( + uint256 _amount + ) internal virtual override returns (bytes memory) { + // Follow CEI pattern: read and clear _memo BEFORE burn + // to prevent reentrancy if child contracts override token hooks with external calls + bytes memory memo = _memo; + delete _memo; + + super._transferFromSender(_amount); + + emit IncludedMemo(memo); + return memo; + } +} diff --git a/solidity/contracts/token/extensions/HypNativeMemo.sol b/solidity/contracts/token/extensions/HypNativeMemo.sol new file mode 100644 index 00000000000..9c0a36489b8 --- /dev/null +++ b/solidity/contracts/token/extensions/HypNativeMemo.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; + +import {HypNative} from "../HypNative.sol"; + +// native +contract HypNativeMemo is HypNative { + event IncludedMemo(bytes memo); + bytes private _memo; + uint256 private _transferFromSenderCallCount; + + constructor(uint256 _scale, address _mailbox) HypNative(_scale, _mailbox) {} + + function transferRemoteMemo( + uint32 _destination, + bytes32 _recipient, + uint256 _amount, + bytes calldata memo + ) external payable virtual returns (bytes32 messageId) { + _memo = memo; + _transferFromSenderCallCount = 0; + return super.transferRemote(_destination, _recipient, _amount); + } + + function _transferFromSender( + uint256 _amount + ) internal override returns (bytes memory) { + // Check msg.value without calling super to avoid double-processing + require(msg.value >= _amount, "Native: amount exceeds msg.value"); + + _transferFromSenderCallCount++; + + // First call is from HypNative._transferRemote (return value ignored) + // Second call is from TokenRouter._transferRemote (return value used) + if (_transferFromSenderCallCount == 1) { + // First call - return empty, keep memo for second call + return bytes(""); + } else { + // Second call - return the actual memo + bytes memory memo = _memo; + delete _memo; + delete _transferFromSenderCallCount; + emit IncludedMemo(memo); + return memo; + } + } +} diff --git a/solidity/contracts/token/libs/TokenRouter.sol b/solidity/contracts/token/libs/TokenRouter.sol index e9a1653d052..48295e3b778 100644 --- a/solidity/contracts/token/libs/TokenRouter.sol +++ b/solidity/contracts/token/libs/TokenRouter.sol @@ -55,7 +55,7 @@ abstract contract TokenRouter is GasRouter, ITokenBridge { uint32 _destination, bytes32 _recipient, uint256 _amountOrId - ) external payable virtual returns (bytes32 messageId) { + ) public payable virtual returns (bytes32 messageId) { return _transferRemote(_destination, _recipient, _amountOrId, msg.value); } diff --git a/solidity/contracts/upgrade/ProxyAdmin.sol b/solidity/contracts/upgrade/ProxyAdmin.sol index b97b2e50f45..6fac18fe45e 100644 --- a/solidity/contracts/upgrade/ProxyAdmin.sol +++ b/solidity/contracts/upgrade/ProxyAdmin.sol @@ -3,4 +3,4 @@ pragma solidity ^0.8.0; -import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; diff --git a/solidity/contracts/upgrade/TransparentUpgradeableProxy.sol b/solidity/contracts/upgrade/TransparentUpgradeableProxy.sol index 887aa90ed99..d9600cf98ba 100644 --- a/solidity/contracts/upgrade/TransparentUpgradeableProxy.sol +++ b/solidity/contracts/upgrade/TransparentUpgradeableProxy.sol @@ -3,4 +3,4 @@ pragma solidity ^0.8.0; -import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; diff --git a/solidity/core-utils/index.ts b/solidity/core-utils/index.ts index 5f8aa9f03d7..109ae3abc8e 100644 --- a/solidity/core-utils/index.ts +++ b/solidity/core-utils/index.ts @@ -1,4 +1,4 @@ export * from './typechain/index.js'; export * from './zksync/index.js'; // GENERATED CODE - DO NOT EDIT -export const CONTRACTS_PACKAGE_VERSION = '9.0.2'; +export const CONTRACTS_PACKAGE_VERSION = '9.0.9'; diff --git a/solidity/lib/fx-portal b/solidity/lib/fx-portal new file mode 160000 index 00000000000..ebd046507d7 --- /dev/null +++ b/solidity/lib/fx-portal @@ -0,0 +1 @@ +Subproject commit ebd046507d76cd03fa2b2559257091471a259ed7 diff --git a/solidity/package.json b/solidity/package.json index 3bcdee0bf69..04fcf32639d 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -1,12 +1,12 @@ { "name": "@hyperlane-xyz/core", "description": "Core solidity contracts for Hyperlane", - "version": "9.0.2", + "version": "9.0.9", "dependencies": { "@arbitrum/nitro-contracts": "^1.2.1", "@chainlink/contracts-ccip": "^1.5.0", "@eth-optimism/contracts": "^0.6.0", - "@hyperlane-xyz/utils": "16.0.0", + "@hyperlane-xyz/utils": "18.2.0", "@matterlabs/hardhat-zksync-solc": "1.2.5", "@matterlabs/hardhat-zksync-verify": "1.7.1", "@openzeppelin/contracts": "^4.9.3", diff --git a/starknet/CHANGELOG.md b/starknet/CHANGELOG.md index acd6df46007..b8e7b4bef23 100644 --- a/starknet/CHANGELOG.md +++ b/starknet/CHANGELOG.md @@ -1,5 +1,19 @@ # @hyperlane-xyz/starknet-core +## 18.2.0 + +## 18.1.0 + +## 18.0.0 + +## 17.0.0 + +## 16.2.0 + +## 16.1.1 + +## 16.1.0 + ## 16.0.0 ## 15.0.0 diff --git a/starknet/package.json b/starknet/package.json index 34a362623f3..568c658ac1c 100644 --- a/starknet/package.json +++ b/starknet/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/starknet-core", "description": "Core cairo contracts for Hyperlane", - "version": "16.0.0", + "version": "18.2.0", "type": "module", "homepage": "https://www.hyperlane.xyz", "license": "Apache-2.0", diff --git a/typescript/ccip-server/CHANGELOG.md b/typescript/ccip-server/CHANGELOG.md index 41b1b87b451..c9a068c00c8 100644 --- a/typescript/ccip-server/CHANGELOG.md +++ b/typescript/ccip-server/CHANGELOG.md @@ -1,5 +1,81 @@ # @hyperlane-xyz/ccip-server +## 18.2.0 + +### Patch Changes + +- Updated dependencies [fed6906e4] +- Updated dependencies [ca64e73cd] +- Updated dependencies [dfa9d368c] + - @hyperlane-xyz/sdk@18.2.0 + - @hyperlane-xyz/utils@18.2.0 + - @hyperlane-xyz/core@9.0.9 + +## 18.1.0 + +### Patch Changes + +- Updated dependencies [73be9b8d2] + - @hyperlane-xyz/sdk@18.1.0 + - @hyperlane-xyz/utils@18.1.0 + - @hyperlane-xyz/core@9.0.8 + +## 18.0.0 + +### Patch Changes + +- Updated dependencies [552b253b9] +- Updated dependencies [ba832828f] +- Updated dependencies [cfc0eb2a7] + - @hyperlane-xyz/sdk@18.0.0 + - @hyperlane-xyz/utils@18.0.0 + - @hyperlane-xyz/core@9.0.7 + +## 17.0.0 + +### Patch Changes + +- Updated dependencies [400c02460] +- Updated dependencies [8c15edc67] +- Updated dependencies [76a5db49a] +- Updated dependencies [6583df016] +- Updated dependencies [e0bda316a] +- Updated dependencies [7f542b288] + - @hyperlane-xyz/sdk@17.0.0 + - @hyperlane-xyz/utils@17.0.0 + - @hyperlane-xyz/core@9.0.6 + +## 16.2.0 + +### Patch Changes + +- Updated dependencies [22ceaa109] +- Updated dependencies [ce4974214] +- Updated dependencies [a89018a3f] + - @hyperlane-xyz/sdk@16.2.0 + - @hyperlane-xyz/utils@16.2.0 + - @hyperlane-xyz/core@9.0.5 + +## 16.1.1 + +### Patch Changes + +- Updated dependencies [ea77b6ae4] + - @hyperlane-xyz/sdk@16.1.1 + - @hyperlane-xyz/utils@16.1.1 + - @hyperlane-xyz/core@9.0.4 + +## 16.1.0 + +### Patch Changes + +- Updated dependencies [2a2c29c39] +- Updated dependencies [e69ac9f62] +- Updated dependencies [d9b8a7551] + - @hyperlane-xyz/sdk@16.1.0 + - @hyperlane-xyz/utils@16.1.0 + - @hyperlane-xyz/core@9.0.3 + ## 16.0.0 ### Patch Changes diff --git a/typescript/ccip-server/package.json b/typescript/ccip-server/package.json index d354b5d1987..e4e3b9bfe7f 100644 --- a/typescript/ccip-server/package.json +++ b/typescript/ccip-server/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/ccip-server", - "version": "16.0.0", + "version": "18.2.0", "description": "CCIP server", "typings": "dist/index.d.ts", "typedocMain": "src/index.ts", @@ -47,9 +47,9 @@ "dependencies": { "@eth-optimism/sdk": "^3.3.3", "@google-cloud/pino-logging-gcp-config": "^1.0.6", - "@hyperlane-xyz/core": "9.0.2", - "@hyperlane-xyz/sdk": "16.0.0", - "@hyperlane-xyz/utils": "16.0.0", + "@hyperlane-xyz/core": "9.0.9", + "@hyperlane-xyz/sdk": "18.2.0", + "@hyperlane-xyz/utils": "18.2.0", "@prisma/client": "^6.8.2", "cors": "^2.8.5", "dotenv-flow": "^4.1.0", diff --git a/typescript/cli/CHANGELOG.md b/typescript/cli/CHANGELOG.md index 72d486bdc44..2d3cba2d0be 100644 --- a/typescript/cli/CHANGELOG.md +++ b/typescript/cli/CHANGELOG.md @@ -1,5 +1,35 @@ # @hyperlane-xyz/cli +## 18.2.0 + +### Minor Changes + +- dfa9d368c: updated command context initialization logic to simplify signer configuration and remove private usage assumption + +## 18.1.0 + +## 18.0.0 + +### Major Changes + +- 552b253b9: deprecated dry-run support in the cli in favour of `hyperlane warp fork` and `hyperlane fork` commands + +## 17.0.0 + +### Minor Changes + +- dfa883e24: Fixes chain resolver for `hyperlane submit` by adding the STRATEGY enum into the chain resolver switch. + +## 16.2.0 + +### Patch Changes + +- db55c3e00: fix: parse private keys as string instead of number + +## 16.1.1 + +## 16.1.0 + ## 16.0.0 ### Minor Changes diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index 6529094b298..2d07fee91d2 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -79,6 +79,9 @@ try { .demandCommand() .strict() .help() + .parserConfiguration({ + 'parse-numbers': false, + }) .showHelpOnFail(false).argv; } catch (error: any) { errorRed('Error: ' + error.message); diff --git a/typescript/cli/examples/cosmosnative/warp-route-deployment.yaml b/typescript/cli/examples/cosmosnative/warp-route-deployment.yaml new file mode 100644 index 00000000000..c7f8608fc7b --- /dev/null +++ b/typescript/cli/examples/cosmosnative/warp-route-deployment.yaml @@ -0,0 +1,21 @@ +# A config for a Warp Route deployment +# Typically used with the 'hyperlane warp deploy' command +# +# Token Types: +# native +# collateral +# synthetic +# +# see comprehensive [list](https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/token/config.ts#L8) +--- +hyp1: + type: collateral + # token: "0x123" # Collateral/vault address. Required for collateral types + owner: 'hyp1jq304cthpx0lwhpqzrdjrcza559ukyy3sc4dw5' # Optional owner address for synthetic token + # mailbox: "0x123" # mailbox address route + # interchainGasPaymaster: "0x123" # Optional interchainGasPaymaster address + # isNft: true # If the token is an NFT (ERC721), set to true + # You can optionally set the token metadata + # name: "MyCollateralToken" + # symbol: "MCT" + # totalSupply: 10000000 diff --git a/typescript/cli/examples/submit/transactions/alfajores-transactions.json b/typescript/cli/examples/submit/transactions/sepolia-transactions.json similarity index 90% rename from typescript/cli/examples/submit/transactions/alfajores-transactions.json rename to typescript/cli/examples/submit/transactions/sepolia-transactions.json index b256b9886bb..6b45a405f65 100644 --- a/typescript/cli/examples/submit/transactions/alfajores-transactions.json +++ b/typescript/cli/examples/submit/transactions/sepolia-transactions.json @@ -3,6 +3,6 @@ "data": "0x0e72cc06000000000000000000000000744ad987ee7c65d3b3333bfc3e6ecbf963eb872a", "to": "0x9a4a3124F2a86bB5BE46267De85D31762b7a05Fd", "from": "0x16F4898F47c085C41d7Cc6b1dc72B91EA617dcBb", - "chainId": 44787 + "chainId": 11155111 } ] diff --git a/typescript/cli/package.json b/typescript/cli/package.json index f1626a6d8de..291dd0c8e48 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/cli", - "version": "16.0.0", + "version": "18.2.0", "description": "A command-line utility for common Hyperlane operations", "devDependencies": { "@aws-sdk/client-kms": "^3.577.0", @@ -9,12 +9,12 @@ "@eslint/js": "^9.31.0", "@ethersproject/abi": "*", "@ethersproject/providers": "*", - "@hyperlane-xyz/cosmos-sdk": "16.0.0", - "@hyperlane-xyz/http-registry-server": "16.0.0", + "@hyperlane-xyz/cosmos-sdk": "18.2.0", + "@hyperlane-xyz/http-registry-server": "18.2.0", "@hyperlane-xyz/registry": "20.0.0", - "@hyperlane-xyz/sdk": "16.0.0", + "@hyperlane-xyz/sdk": "18.2.0", "@hyperlane-xyz/tsconfig": "workspace:^", - "@hyperlane-xyz/utils": "16.0.0", + "@hyperlane-xyz/utils": "18.2.0", "@inquirer/core": "9.0.10", "@inquirer/figures": "1.0.5", "@inquirer/prompts": "3.3.2", diff --git a/typescript/cli/src/commands/core.ts b/typescript/cli/src/commands/core.ts index ff0d03743ac..64a09b1a168 100644 --- a/typescript/cli/src/commands/core.ts +++ b/typescript/cli/src/commands/core.ts @@ -19,7 +19,6 @@ import { CommandModuleWithWriteContext, } from '../context/types.js'; import { runCoreApply, runCoreDeploy } from '../deploy/core.js'; -import { evaluateIfDryRunFailure } from '../deploy/dry-run.js'; import { log, logCommandHeader, logGreen } from '../logger.js'; import { executeCoreRead } from '../read/core.js'; import { @@ -32,8 +31,6 @@ import { formatYamlViolationsOutput } from '../utils/output.js'; import { DEFAULT_CORE_DEPLOYMENT_CONFIG_PATH, chainCommandOption, - dryRunCommandOption, - fromAddressCommandOption, inputFileCommandOption, outputFileCommandOption, } from './options.js'; @@ -103,8 +100,6 @@ export const apply: CommandModuleWithWriteContext<{ export const deploy: CommandModuleWithWriteContext<{ chain: string; config: string; - dryRun: string; - fromAddress: string; multiProtocolSigner?: MultiProtocolSignerManager; }> = { command: 'deploy', @@ -116,29 +111,22 @@ export const deploy: CommandModuleWithWriteContext<{ false, 'The path to a JSON or YAML file with a core deployment config.', ), - 'dry-run': dryRunCommandOption, - 'from-address': fromAddressCommandOption, }, handler: async ({ context, chain, config: configFilePath, - dryRun, multiProtocolSigner, }) => { - logCommandHeader(`Hyperlane Core deployment${dryRun ? ' dry-run' : ''}`); + logCommandHeader(`Hyperlane Core deployment`); + + await runCoreDeploy({ + context, + chain, + config: readYamlOrJson(configFilePath), + multiProtocolSigner, + }); - try { - await runCoreDeploy({ - context, - chain, - config: readYamlOrJson(configFilePath), - multiProtocolSigner, - }); - } catch (error: any) { - evaluateIfDryRunFailure(error, dryRun); - throw error; - } process.exit(0); }, }; diff --git a/typescript/cli/src/commands/ism.ts b/typescript/cli/src/commands/ism.ts index c4363ec8268..b5eab66f6c1 100644 --- a/typescript/cli/src/commands/ism.ts +++ b/typescript/cli/src/commands/ism.ts @@ -25,8 +25,8 @@ export const ismCommand: CommandModule = { // hyperlane ism read --chain celo --address 0x99e8E56Dce3402D6E09A82718937fc1cA2A9491E // Aggregation ISM for bsc domain on inevm (may take 5s) // hyperlane ism read --chain inevm --address 0x79A7c7Fe443971CBc6baD623Fdf8019C379a7178 -// Test ISM on alfajores testnet -// hyperlane ism read --chain alfajores --address 0xdB52E4853b6A40D2972E6797E0BDBDb3eB761966 +// Test ISM on sepolia testnet +// hyperlane ism read --chain sepolia --address 0xdB52E4853b6A40D2972E6797E0BDBDb3eB761966 export const read: CommandModuleWithContext<{ chain: string; address: string; diff --git a/typescript/cli/src/commands/options.ts b/typescript/cli/src/commands/options.ts index 502a82655ad..5cc02af54b1 100644 --- a/typescript/cli/src/commands/options.ts +++ b/typescript/cli/src/commands/options.ts @@ -176,13 +176,6 @@ export const fromAddressCommandOption: Options = { alias: 'f', }; -export const dryRunCommandOption: Options = { - type: 'string', - description: - 'Chain name to fork and simulate deployment. Please ensure an anvil node instance is running during execution via `anvil`.', - alias: 'd', -}; - export const chainCommandOption: Options = { type: 'string', description: 'The specific chain to perform operations with.', diff --git a/typescript/cli/src/commands/signCommands.ts b/typescript/cli/src/commands/signCommands.ts index 165392ac4f1..b9bb9cff67b 100644 --- a/typescript/cli/src/commands/signCommands.ts +++ b/typescript/cli/src/commands/signCommands.ts @@ -1,7 +1,5 @@ // Commands that send tx and require a key to sign. // It's useful to have this listed here so the context -import { ProtocolMap } from '@hyperlane-xyz/sdk'; -import { ProtocolType } from '@hyperlane-xyz/utils'; // middleware can request keys up front when required. export const SIGN_COMMANDS = [ @@ -21,28 +19,11 @@ export function isSignCommand(argv: any): boolean { ); } -export function isValidKey(key: string | ProtocolMap): boolean { - if (typeof key === 'string') { - return true; - } else if (Array.isArray(key)) { - // if type if array it means the user inputted both --key.{protocol} - // and the legacy flag --key at the same time - return false; - } else if (typeof key === 'object') { - // if key is of type protocol map check if every provided protocol - // is valid - return Object.keys(key).every((protocol) => - Object.values(ProtocolType).includes(protocol), - ); - } else { - return false; - } -} - export enum CommandType { WARP_DEPLOY = 'warp:deploy', WARP_SEND = 'warp:send', WARP_APPLY = 'warp:apply', + WARP_REBALANCER = 'warp:rebalancer', SEND_MESSAGE = 'send:message', AGENT_KURTOSIS = 'deploy:kurtosis-agents', STATUS = 'status:', diff --git a/typescript/cli/src/commands/warp.ts b/typescript/cli/src/commands/warp.ts index a5b9d2cda33..dcf2667912d 100644 --- a/typescript/cli/src/commands/warp.ts +++ b/typescript/cli/src/commands/warp.ts @@ -20,7 +20,6 @@ import { CommandModuleWithWarpDeployContext, CommandModuleWithWriteContext, } from '../context/types.js'; -import { evaluateIfDryRunFailure } from '../deploy/dry-run.js'; import { runWarpRouteApply, runWarpRouteDeploy } from '../deploy/warp.js'; import { runForkCommand } from '../fork/fork.js'; import { @@ -51,9 +50,7 @@ import { runVerifyWarpRoute } from '../verify/warp.js'; import { addressCommandOption, chainCommandOption, - dryRunCommandOption, forkCommandOptions, - fromAddressCommandOption, outputFileCommandOption, strategyCommandOption, symbolCommandOption, @@ -157,25 +154,14 @@ export const apply: CommandModuleWithWarpApplyContext< }, }; -export const deploy: CommandModuleWithWarpDeployContext< - SelectWarpRouteBuilder & { - 'dry-run': string; - 'from-address': string; - } -> = { - command: 'deploy', - describe: 'Deploy Warp Route contracts', - builder: { - ...SELECT_WARP_ROUTE_BUILDER, - 'dry-run': dryRunCommandOption, - 'from-address': fromAddressCommandOption, - }, - handler: async ({ context, dryRun, warpRouteId, config }) => { - logCommandHeader( - `Hyperlane Warp Route Deployment${dryRun ? ' Dry-Run' : ''}`, - ); +export const deploy: CommandModuleWithWarpDeployContext = + { + command: 'deploy', + describe: 'Deploy Warp Route contracts', + builder: SELECT_WARP_ROUTE_BUILDER, + handler: async ({ context, warpRouteId, config }) => { + logCommandHeader(`Hyperlane Warp Route Deployment`); - try { await runWarpRouteDeploy({ context, // Already fetched in the resolveWarpRouteConfigChains @@ -183,13 +169,10 @@ export const deploy: CommandModuleWithWarpDeployContext< warpRouteId, warpDeployConfigFileName: config, }); - } catch (error: any) { - evaluateIfDryRunFailure(error, dryRun); - throw error; - } - process.exit(0); - }, -}; + + process.exit(0); + }, + }; export const init: CommandModuleWithContext<{ advanced: boolean; diff --git a/typescript/cli/src/config/warp.ts b/typescript/cli/src/config/warp.ts index 0017524d11e..d9079ea7adf 100644 --- a/typescript/cli/src/config/warp.ts +++ b/typescript/cli/src/config/warp.ts @@ -42,13 +42,19 @@ import { createAdvancedIsmConfig } from './ism.js'; const TYPE_DESCRIPTIONS: Record = { [TokenType.synthetic]: 'A new ERC20 with remote transfer functionality', + [TokenType.syntheticMemo]: + 'A new ERC20 with remote transfer functionality, with outbound memo support', [TokenType.syntheticRebase]: `A rebasing ERC20 with remote transfer functionality. Must be paired with ${TokenType.collateralVaultRebase}`, [TokenType.collateral]: 'Extends an existing ERC20 with remote transfer functionality', + [TokenType.collateralMemo]: + 'Extends an existing ERC20 with remote transfer functionality, with outbound memo support', [TokenType.collateralCctp]: 'A collateral token that can be transferred via CCTP', [TokenType.native]: 'Extends the native token with remote transfer functionality', + [TokenType.nativeMemo]: + 'Extends the native token with remote transfer functionality, with outbound memo support', [TokenType.collateralVault]: 'Extends an existing ERC4626 with remote transfer functionality. Yields are manually claimed by owner.', [TokenType.collateralVaultRebase]: @@ -233,6 +239,7 @@ export async function createWarpRouteDeployConfig({ switch (type) { case TokenType.collateral: + case TokenType.collateralMemo: case TokenType.XERC20: case TokenType.XERC20Lockbox: case TokenType.collateralFiat: @@ -305,7 +312,9 @@ export async function createWarpRouteDeployConfig({ }; break; case TokenType.native: + case TokenType.nativeMemo: case TokenType.synthetic: + case TokenType.syntheticMemo: result[chain] = { type, owner, diff --git a/typescript/cli/src/consts.ts b/typescript/cli/src/consts.ts index 9c931cab26d..ba7417c65e4 100644 --- a/typescript/cli/src/consts.ts +++ b/typescript/cli/src/consts.ts @@ -1,6 +1,21 @@ -export const MINIMUM_CORE_DEPLOY_GAS = (1e8).toString(); -export const MINIMUM_WARP_DEPLOY_GAS = (3e7).toString(); -export const MINIMUM_TEST_SEND_GAS = (3e5).toString(); -export const MINIMUM_AVS_GAS = (3e6).toString(); +import { ProtocolType } from '@hyperlane-xyz/utils'; + +export const MINIMUM_CORE_DEPLOY_GAS = { + [ProtocolType.Ethereum]: (1e8).toString(), + [ProtocolType.CosmosNative]: (1e6).toString(), +}; +export const MINIMUM_WARP_DEPLOY_GAS = { + [ProtocolType.Ethereum]: (3e7).toString(), + [ProtocolType.CosmosNative]: (3e6).toString(), +}; +export const MINIMUM_TEST_SEND_GAS = { + [ProtocolType.Ethereum]: (3e5).toString(), + [ProtocolType.CosmosNative]: (3e5).toString(), +}; +export const MINIMUM_AVS_GAS = { + [ProtocolType.Ethereum]: (3e6).toString(), + [ProtocolType.CosmosNative]: (3e6).toString(), +}; + export const PROXY_DEPLOYED_URL = 'https://proxy.hyperlane.xyz'; export const EXPLORER_URL = 'https://explorer.hyperlane.xyz'; diff --git a/typescript/cli/src/context/context.ts b/typescript/cli/src/context/context.ts index 9348948f3a4..8b3ec7a6dc1 100644 --- a/typescript/cli/src/context/context.ts +++ b/typescript/cli/src/context/context.ts @@ -1,5 +1,5 @@ import { confirm } from '@inquirer/prompts'; -import { Signer, ethers } from 'ethers'; +import { ethers } from 'ethers'; import { IRegistry } from '@hyperlane-xyz/registry'; import { getRegistry } from '@hyperlane-xyz/registry/fs'; @@ -11,64 +11,42 @@ import { MultiProtocolProvider, MultiProvider, } from '@hyperlane-xyz/sdk'; -import { assert, isNullish, rootLogger } from '@hyperlane-xyz/utils'; +import { Address, ProtocolType, rootLogger } from '@hyperlane-xyz/utils'; -import { isSignCommand, isValidKey } from '../commands/signCommands.js'; +import { isSignCommand } from '../commands/signCommands.js'; import { readChainSubmissionStrategyConfig } from '../config/strategy.js'; -import { forkNetworkToMultiProvider, verifyAnvil } from '../deploy/dry-run.js'; -import { logBlue } from '../logger.js'; -import { runSingleChainSelectionStep } from '../utils/chains.js'; import { detectAndConfirmOrPrompt } from '../utils/input.js'; -import { getImpersonatedSigner, getSigner } from '../utils/keys.js'; +import { getSigner } from '../utils/keys.js'; import { ChainResolverFactory } from './strategies/chain/ChainResolverFactory.js'; import { MultiProtocolSignerManager } from './strategies/signer/MultiProtocolSignerManager.js'; import { CommandContext, ContextSettings, - WriteCommandContext, + SignerKeyProtocolMap, + SignerKeyProtocolMapSchema, } from './types.js'; export async function contextMiddleware(argv: Record) { - const isDryRun = !isNullish(argv.dryRun); const requiresKey = isSignCommand(argv); - // if a key was provided, check if it has a valid format - if (argv.key) { - assert( - isValidKey(argv.key), - `Key inputs not valid, make sure to use --key.{protocol} or the legacy flag --key but not both at the same time`, - ); - } - const settings: ContextSettings = { - registryUris: [ - ...argv.registry, - ...(argv.overrides ? [argv.overrides] : []), - ], + registryUris: [...argv.registry], key: argv.key, - fromAddress: argv.fromAddress, requiresKey, disableProxy: argv.disableProxy, skipConfirmation: argv.yes, strategyPath: argv.strategy, authToken: argv.authToken, }; - if (!isDryRun && settings.fromAddress) - throw new Error( - "'--from-address' or '-f' should only be used for dry-runs", - ); - const context = isDryRun - ? await getDryRunContext(settings, argv.dryRun) - : await getContext(settings); - argv.context = context; + + argv.context = await getContext(settings); } export async function signerMiddleware(argv: Record) { - const { key, requiresKey, multiProvider, strategyPath, chainMetadata } = + const { key, requiresKey, strategyPath, multiProtocolProvider } = argv.context; - const multiProtocolProvider = new MultiProtocolProvider(chainMetadata); if (!requiresKey) return argv; const strategyConfig = strategyPath @@ -88,10 +66,9 @@ export async function signerMiddleware(argv: Record) { /** * Extracts signer config */ - const multiProtocolSigner = new MultiProtocolSignerManager( + const multiProtocolSigner = await MultiProtocolSignerManager.init( strategyConfig, chains, - multiProvider, multiProtocolProvider, { key }, ); @@ -125,13 +102,10 @@ export async function getContext({ authToken, }); - //Just for backward compatibility - let signerAddress: string | undefined = undefined; - if (key && typeof key === 'string') { - let signer: Signer; - ({ key, signer } = await getSigner({ key, skipConfirmation })); - signerAddress = await signer.getAddress(); - } + const { keyMap, ethereumSignerAddress } = await getSignerKeyMap( + key, + !!skipConfirmation, + ); const multiProvider = await getMultiProvider(registry); const multiProtocolProvider = await getMultiProtocolProvider(registry); @@ -142,75 +116,55 @@ export async function getContext({ chainMetadata: multiProvider.metadata, multiProvider, multiProtocolProvider, - key, + key: keyMap, skipConfirmation: !!skipConfirmation, - signerAddress, + signerAddress: ethereumSignerAddress, strategyPath, - } as CommandContext; + }; } /** - * Retrieves dry-run context for the user-selected command - * @returns dry-run context for the current command + * Resolves private keys by protocol type by reading either the key + * argument passed to the CLI or falling back to reading from env */ -export async function getDryRunContext( - { - registryUris, - key, - fromAddress, - skipConfirmation, - disableProxy = false, - authToken, - }: ContextSettings, - chain?: ChainName, -): Promise { - const registry = getRegistry({ - registryUris, - enableProxy: !disableProxy, - logger: rootLogger, - authToken, - }); - const chainMetadata = await registry.getMetadata(); - - if (!chain) { - if (skipConfirmation) throw new Error('No chains provided'); - chain = await runSingleChainSelectionStep( - chainMetadata, - 'Select chain to dry-run against:', - ); - } +async function getSignerKeyMap( + rawKeyMap: ContextSettings['key'], + skipConfirmation: boolean, +): Promise<{ keyMap: SignerKeyProtocolMap; ethereumSignerAddress?: Address }> { + const keyMap: SignerKeyProtocolMap = SignerKeyProtocolMapSchema.parse( + rawKeyMap ?? {}, + ); - logBlue(`Dry-running against chain: ${chain}`); - await verifyAnvil(); + Object.values(ProtocolType).forEach((protocol) => { + if (keyMap[protocol]) { + return; + } - let multiProvider = await getMultiProvider(registry); - const multiProtocolProvider = await getMultiProtocolProvider(registry); - multiProvider = await forkNetworkToMultiProvider(multiProvider, chain); + if (process.env[`HYP_KEY_${protocol.toUpperCase()}`]) { + keyMap[protocol] = process.env[`HYP_KEY_${protocol.toUpperCase()}`]; + return; + } - if (typeof key === 'string') { - const { impersonatedKey, impersonatedSigner } = await getImpersonatedSigner( - { - fromAddress, - key: key as string, - skipConfirmation, - }, - ); - multiProvider.setSharedSigner(impersonatedSigner); + if (protocol === ProtocolType.Ethereum && process.env.HYP_KEY) { + keyMap[protocol] = process.env.HYP_KEY; + return; + } + }); - return { - registry, - chainMetadata: multiProvider.metadata, - key: impersonatedKey, - signer: impersonatedSigner, - multiProvider: multiProvider, - multiProtocolProvider: multiProtocolProvider, - skipConfirmation: !!skipConfirmation, - isDryRun: true, - dryRunChain: chain, - } as WriteCommandContext; - } else { - throw new Error(`dry-run needs --key legacy key flag`); + // Just for backward compatibility + let signerAddress: string | undefined = undefined; + if (keyMap[ProtocolType.Ethereum]) { + const { signer } = await getSigner({ + key: keyMap[ProtocolType.Ethereum], + skipConfirmation, + }); + signerAddress = await signer.getAddress(); } + + return { + keyMap, + ethereumSignerAddress: signerAddress, + }; } /** diff --git a/typescript/cli/src/context/strategies/chain/ChainResolverFactory.ts b/typescript/cli/src/context/strategies/chain/ChainResolverFactory.ts index 21a0903b1f8..f242f391fc7 100644 --- a/typescript/cli/src/context/strategies/chain/ChainResolverFactory.ts +++ b/typescript/cli/src/context/strategies/chain/ChainResolverFactory.ts @@ -13,6 +13,7 @@ export class ChainResolverFactory { // Using the forRelayer resolver because warp send allows the user to self relay the tx [CommandType.WARP_SEND, () => MultiChainResolver.forRelayer()], [CommandType.WARP_APPLY, () => MultiChainResolver.forWarpApply()], + [CommandType.WARP_REBALANCER, () => MultiChainResolver.forWarpRebalancer()], // Using the forRelayer resolver because send allows the user to self relay the tx [CommandType.SEND_MESSAGE, () => MultiChainResolver.forRelayer()], [CommandType.AGENT_KURTOSIS, () => MultiChainResolver.forAgentKurtosis()], diff --git a/typescript/cli/src/context/strategies/chain/MultiChainResolver.ts b/typescript/cli/src/context/strategies/chain/MultiChainResolver.ts index 349829b0a85..8dcae34af43 100644 --- a/typescript/cli/src/context/strategies/chain/MultiChainResolver.ts +++ b/typescript/cli/src/context/strategies/chain/MultiChainResolver.ts @@ -5,11 +5,13 @@ import { DeployedCoreAddressesSchema, EvmCoreModule, MultiProvider, + WarpCore, } from '@hyperlane-xyz/sdk'; import { ProtocolType, assert } from '@hyperlane-xyz/utils'; import { readCoreDeployConfigs } from '../../../config/core.js'; import { getWarpRouteDeployConfig } from '../../../config/warp.js'; +import { RebalancerConfig } from '../../../rebalancer/config/RebalancerConfig.js'; import { runMultiChainSelectionStep, runSingleChainSelectionStep, @@ -23,6 +25,7 @@ enum ChainSelectionMode { AGENT_KURTOSIS, WARP_CONFIG, WARP_APPLY, + WARP_REBALANCER, STRATEGY, CORE_APPLY, CORE_DEPLOY, @@ -40,10 +43,13 @@ export class MultiChainResolver implements ChainResolver { async resolveChains(argv: ChainMap): Promise { switch (this.mode) { + case ChainSelectionMode.STRATEGY: case ChainSelectionMode.WARP_CONFIG: return this.resolveWarpRouteConfigChains(argv); case ChainSelectionMode.WARP_APPLY: return this.resolveWarpApplyChains(argv); + case ChainSelectionMode.WARP_REBALANCER: + return this.resolveWarpRebalancerChains(argv); case ChainSelectionMode.AGENT_KURTOSIS: return this.resolveAgentChains(argv); case ChainSelectionMode.CORE_APPLY: @@ -95,6 +101,36 @@ export class MultiChainResolver implements ChainResolver { return argv.context.chains; } + private async resolveWarpRebalancerChains( + argv: Record, + ): Promise { + // Load rebalancer config to get the warp route ID + const rebalancerConfig = RebalancerConfig.load(argv.config); + + // Get warp route config from registry using the warp route ID + const warpCoreConfig = await argv.context.registry.getWarpRoute( + rebalancerConfig.warpRouteId, + ); + if (!warpCoreConfig) { + throw new Error( + `Warp route config for ${rebalancerConfig.warpRouteId} not found in registry`, + ); + } + + // Create WarpCore instance to extract chain names from tokens + const warpCore = WarpCore.FromConfig( + argv.context.multiProvider, + warpCoreConfig, + ); + + // Extract chain names from the tokens in the warp core + const chains = warpCore.tokens.map((token) => token.chainName); + + assert(chains.length !== 0, 'No chains found in warp route config'); + + return chains; + } + private async resolveAgentChains( argv: Record, ): Promise { @@ -202,15 +238,12 @@ export class MultiChainResolver implements ChainResolver { argv: Record, ): Promise { try { - const { chainMetadata, dryRunChain, registry, skipConfirmation } = - argv.context; + const { chainMetadata, registry, skipConfirmation } = argv.context; let chain: string; if (argv.chain) { chain = argv.chain; - } else if (dryRunChain) { - chain = dryRunChain; } else { if (skipConfirmation) throw new Error('No chain provided'); chain = await runSingleChainSelectionStep( @@ -270,6 +303,10 @@ export class MultiChainResolver implements ChainResolver { return new MultiChainResolver(ChainSelectionMode.WARP_APPLY); } + static forWarpRebalancer(): MultiChainResolver { + return new MultiChainResolver(ChainSelectionMode.WARP_REBALANCER); + } + static forCoreApply(): MultiChainResolver { return new MultiChainResolver(ChainSelectionMode.CORE_APPLY); } diff --git a/typescript/cli/src/context/strategies/signer/BaseMultiProtocolSigner.ts b/typescript/cli/src/context/strategies/signer/BaseMultiProtocolSigner.ts index 62a88349439..69e80e09095 100644 --- a/typescript/cli/src/context/strategies/signer/BaseMultiProtocolSigner.ts +++ b/typescript/cli/src/context/strategies/signer/BaseMultiProtocolSigner.ts @@ -1,27 +1,57 @@ +import { password } from '@inquirer/prompts'; import { Signer } from 'ethers'; import { SigningHyperlaneModuleClient } from '@hyperlane-xyz/cosmos-sdk'; -import { ChainName } from '@hyperlane-xyz/sdk'; +import { MultiProtocolProvider, TxSubmitterType } from '@hyperlane-xyz/sdk'; import { Address } from '@hyperlane-xyz/utils'; import { ExtendedChainSubmissionStrategy } from '../../../submitters/types.js'; export type TypedSigner = Signer | SigningHyperlaneModuleClient; -export interface SignerConfig { - privateKey: string; - address?: Address; // For chains like StarkNet that require address - extraParams?: Record; // For any additional chain-specific params -} +export type SignerConfig = Omit< + Extract< + ExtendedChainSubmissionStrategy[string]['submitter'], + { type: TxSubmitterType.JSON_RPC } + >, + 'type' +>; export interface IMultiProtocolSigner { - getSignerConfig(chain: ChainName): Promise | SignerConfig; getSigner(config: SignerConfig): Promise; } export abstract class BaseMultiProtocolSigner implements IMultiProtocolSigner { - constructor(protected config: ExtendedChainSubmissionStrategy) {} + protected defaultProtocolConfig?: SignerConfig; + + constructor( + protected readonly multiProtocolProvider: MultiProtocolProvider, + ) {} - abstract getSignerConfig(chain: ChainName): Promise; abstract getSigner(config: SignerConfig): Promise; + + protected async getPrivateKey( + config: SignerConfig, + ): Promise<{ privateKey: string; address?: Address }> { + let privateKey: string; + let address: Address | undefined; + if (config.privateKey) { + privateKey = config.privateKey; + address = config.userAddress; + } else if (this.defaultProtocolConfig?.privateKey) { + privateKey = this.defaultProtocolConfig.privateKey; + address = this.defaultProtocolConfig.userAddress; + } else { + privateKey = await password({ + message: `Please enter the private key for chain ${config.chain} (will be re-used for other chains with the same protocol type)`, + }); + + this.defaultProtocolConfig = { + chain: config.chain, + privateKey, + }; + } + + return { privateKey, address }; + } } diff --git a/typescript/cli/src/context/strategies/signer/MultiProtocolSignerFactory.ts b/typescript/cli/src/context/strategies/signer/MultiProtocolSignerFactory.ts index 914439592b1..50e077b9c9d 100644 --- a/typescript/cli/src/context/strategies/signer/MultiProtocolSignerFactory.ts +++ b/typescript/cli/src/context/strategies/signer/MultiProtocolSignerFactory.ts @@ -3,21 +3,13 @@ import { DirectSecp256k1Wallet, } from '@cosmjs/proto-signing'; import { GasPrice } from '@cosmjs/stargate'; -import { password } from '@inquirer/prompts'; import { Signer, Wallet, ethers } from 'ethers'; import { Wallet as ZKSyncWallet } from 'zksync-ethers'; import { SigningHyperlaneModuleClient } from '@hyperlane-xyz/cosmos-sdk'; -import { - ChainName, - ChainTechnicalStack, - MultiProvider, - TxSubmitterType, -} from '@hyperlane-xyz/sdk'; +import { ChainTechnicalStack, MultiProtocolProvider } from '@hyperlane-xyz/sdk'; import { ProtocolType, assert, ensure0x } from '@hyperlane-xyz/utils'; -import { ExtendedChainSubmissionStrategy } from '../../../submitters/types.js'; - import { BaseMultiProtocolSigner, IMultiProtocolSigner, @@ -26,90 +18,47 @@ import { export class MultiProtocolSignerFactory { static getSignerStrategy( - chain: ChainName, - strategyConfig: ExtendedChainSubmissionStrategy, - multiProvider: MultiProvider, + protocol: ProtocolType, + multiProtocolProvider: MultiProtocolProvider, ): IMultiProtocolSigner { - const { protocol, technicalStack } = multiProvider.getChainMetadata(chain); - switch (protocol) { case ProtocolType.Ethereum: - if (technicalStack === ChainTechnicalStack.ZkSync) - return new ZKSyncSignerStrategy(strategyConfig); - return new EthereumSignerStrategy(strategyConfig); + return new EvmSignerStrategy(multiProtocolProvider); case ProtocolType.CosmosNative: - return new CosmosNativeSignerStrategy(strategyConfig); + return new CosmosNativeSignerStrategy(multiProtocolProvider); default: throw new Error(`Unsupported protocol: ${protocol}`); } } } -class EthereumSignerStrategy extends BaseMultiProtocolSigner { - async getSignerConfig(chain: ChainName): Promise { - const submitter = this.config[chain]?.submitter as { - type: TxSubmitterType.JSON_RPC; - privateKey?: string; - }; - - const privateKey = - submitter?.privateKey ?? - (await password({ - message: `Please enter the private key for chain ${chain}`, - })); - - return { privateKey }; - } - +class EvmSignerStrategy extends BaseMultiProtocolSigner { async getSigner(config: SignerConfig): Promise { - return new Wallet(config.privateKey); - } -} + const { privateKey } = await this.getPrivateKey(config); -// 99% overlap with EthereumSignerStrategy for the sake of keeping MultiProtocolSignerFactory clean -class ZKSyncSignerStrategy extends BaseMultiProtocolSigner { - async getSignerConfig(chain: ChainName): Promise { - const submitter = this.config[chain]?.submitter as { - privateKey?: string; - }; - - const privateKey = - submitter?.privateKey ?? - (await password({ - message: `Please enter the private key for chain ${chain}`, - })); + const { technicalStack } = this.multiProtocolProvider.getChainMetadata( + config.chain, + ); + if (technicalStack === ChainTechnicalStack.ZkSync) { + return new ZKSyncWallet(privateKey); + } - return { privateKey }; - } - - async getSigner(config: SignerConfig): Promise { - return new ZKSyncWallet(config.privateKey); + return new Wallet(privateKey); } } class CosmosNativeSignerStrategy extends BaseMultiProtocolSigner { - async getSignerConfig(chain: ChainName): Promise { - const submitter = this.config[chain]?.submitter as { - privateKey?: string; - }; - - const privateKey = - submitter?.privateKey ?? - (await password({ - message: `Please enter the private key for chain ${chain}`, - })); + async getSigner(config: SignerConfig): Promise { + const { privateKey } = await this.getPrivateKey(config); - return { - privateKey, - }; - } + const provider = await this.multiProtocolProvider.getCosmJsNativeProvider( + config.chain, + ); + const { bech32Prefix, gasPrice: nativeTokenConfig } = + this.multiProtocolProvider.getChainMetadata(config.chain); - async getSigner({ - privateKey, - extraParams, - }: SignerConfig): Promise { assert( - extraParams?.provider && extraParams?.prefix && extraParams?.gasPrice, + bech32Prefix && nativeTokenConfig, 'Missing Cosmos Signer arguments', ); @@ -118,19 +67,19 @@ class CosmosNativeSignerStrategy extends BaseMultiProtocolSigner { if (ethers.utils.isHexString(ensure0x(privateKey))) { wallet = await DirectSecp256k1Wallet.fromKey( Buffer.from(privateKey, 'hex'), - extraParams.prefix, + bech32Prefix, ); } else { wallet = await DirectSecp256k1HdWallet.fromMnemonic(privateKey, { - prefix: extraParams.prefix, + prefix: bech32Prefix, }); } - const cometClient = extraParams?.provider.getCometClient(); + const cometClient = provider.getCometClientOrFail(); // parse gas price so it has the correct format const gasPrice = GasPrice.fromString( - `${extraParams.gasPrice.amount}${extraParams.gasPrice.denom}`, + `${nativeTokenConfig.amount}${nativeTokenConfig.denom}`, ); return SigningHyperlaneModuleClient.createWithSigner(cometClient, wallet, { diff --git a/typescript/cli/src/context/strategies/signer/MultiProtocolSignerManager.ts b/typescript/cli/src/context/strategies/signer/MultiProtocolSignerManager.ts index b5c087f5829..023339829d3 100644 --- a/typescript/cli/src/context/strategies/signer/MultiProtocolSignerManager.ts +++ b/typescript/cli/src/context/strategies/signer/MultiProtocolSignerManager.ts @@ -1,17 +1,24 @@ -import { Signer } from 'ethers'; +import { BigNumber, Signer } from 'ethers'; import { Logger } from 'pino'; import { SigningHyperlaneModuleClient } from '@hyperlane-xyz/cosmos-sdk'; import { ChainName, + IMultiProtocolSignerManager, MultiProtocolProvider, MultiProvider, ProtocolMap, + isJsonRpcSubmitterConfig, } from '@hyperlane-xyz/sdk'; -import { ProtocolType, assert, rootLogger } from '@hyperlane-xyz/utils'; +import { + Address, + ProtocolType, + assert, + rootLogger, +} from '@hyperlane-xyz/utils'; import { ExtendedChainSubmissionStrategy } from '../../../submitters/types.js'; -import { ENV } from '../../../utils/env.js'; +import { SignerKeyProtocolMap } from '../../types.js'; import { IMultiProtocolSigner, @@ -22,190 +29,174 @@ import { MultiProtocolSignerFactory } from './MultiProtocolSignerFactory.js'; export interface MultiProtocolSignerOptions { logger?: Logger; - key?: string | ProtocolMap; + key?: SignerKeyProtocolMap; +} + +function getSignerCompatibleChains( + multiProtocolProvider: MultiProtocolProvider, + chains: ChainName[], +): ReadonlyArray { + return chains.filter( + (chain) => + multiProtocolProvider.getProtocol(chain) === ProtocolType.Ethereum || + multiProtocolProvider.getProtocol(chain) === ProtocolType.CosmosNative, + ); +} + +function getProtocolsFromChains( + multiProtocolProvider: MultiProtocolProvider, + chains: ReadonlyArray, +): ReadonlyArray { + const protocols = chains.map((chain) => + multiProtocolProvider.getProtocol(chain), + ); + + return Array.from(new Set(protocols)); } /** * @title MultiProtocolSignerManager * @dev Context manager for signers across multiple protocols */ -export class MultiProtocolSignerManager { - protected readonly signerStrategies: Map; +export class MultiProtocolSignerManager implements IMultiProtocolSignerManager { protected readonly signers: Map; public readonly logger: Logger; - constructor( - protected readonly submissionStrategy: ExtendedChainSubmissionStrategy, - protected readonly chains: ChainName[], - protected readonly multiProvider: MultiProvider, + protected constructor( + protected readonly submissionStrategy: Partial, + protected readonly chains: ReadonlyArray, + protected readonly signerStrategiesByProtocol: Partial< + ProtocolMap + >, protected readonly multiProtocolProvider: MultiProtocolProvider, protected readonly options: MultiProtocolSignerOptions = {}, ) { this.logger = options?.logger || rootLogger.child({ - module: 'MultiProtocolSignerManager', + module: MultiProtocolSignerManager.name, }); - this.signerStrategies = new Map(); this.signers = new Map(); - this.initializeStrategies(); - } - - protected get compatibleChains(): ChainName[] { - return this.chains.filter( - (chain) => - this.multiProvider.getProtocol(chain) === ProtocolType.Ethereum || - this.multiProvider.getProtocol(chain) === ProtocolType.CosmosNative, - ); } /** - * @notice Sets up chain-specific signer strategies + * Creates an instance of {@link MultiProtocolSignerManager} with all the signers + * initialized for the provided supported chains */ - protected initializeStrategies(): void { - for (const chain of this.compatibleChains) { - const strategy = MultiProtocolSignerFactory.getSignerStrategy( - chain, - this.submissionStrategy, - this.multiProvider, + static async init( + submissionStrategy: Partial, + chains: ChainName[], + multiProtocolProvider: MultiProtocolProvider, + options: MultiProtocolSignerOptions = {}, + ): Promise { + const supportedChains = getSignerCompatibleChains( + multiProtocolProvider, + chains, + ); + const supportedProtocols = getProtocolsFromChains( + multiProtocolProvider, + supportedChains, + ); + + const strategiesByProtocol: Partial> = + Object.fromEntries( + supportedProtocols.map((protocol) => [ + protocol, + MultiProtocolSignerFactory.getSignerStrategy( + protocol, + multiProtocolProvider, + ), + ]), ); - this.signerStrategies.set(chain, strategy); - } + + const instance = new MultiProtocolSignerManager( + submissionStrategy, + supportedChains, + strategiesByProtocol, + multiProtocolProvider, + options, + ); + + await instance.initAllSigners(); + + return instance; } /** * @dev Configures signers for EVM chains in MultiProvider */ async getMultiProvider(): Promise { + const multiProvider = this.multiProtocolProvider.toMultiProvider(); + const evmChains = this.chains.filter( (chain) => - this.multiProvider.getProtocol(chain) === ProtocolType.Ethereum, + this.multiProtocolProvider.getProtocol(chain) === ProtocolType.Ethereum, ); for (const chain of evmChains) { - const signer = await this.initSigner(chain); - this.multiProvider.setSigner(chain, signer as Signer); + multiProvider.setSigner(chain, this.getEVMSigner(chain)); } - return this.multiProvider; + return multiProvider; } /** * @notice Creates signer for specific chain */ async initSigner(chain: ChainName): Promise { - const config = await this.resolveConfig(chain); - const signerStrategy = this.getSignerStrategyOrFail(chain); - const signer = await signerStrategy.getSigner(config); + const maybeSigner = this.signers.get(chain); + if (maybeSigner) { + return maybeSigner; + } - this.signers.set(chain, signer); - return signer; - } + const protocolType = this.multiProtocolProvider.getProtocol(chain); - /** - * @notice Creates signers for all chains - */ - async initAllSigners(): Promise { - for (const chain of this.compatibleChains) { - const signerStrategy = this.signerStrategies.get(chain); - if (signerStrategy) { - await this.initSigner(chain); - } - } + const signerStrategy = this.signerStrategiesByProtocol[protocolType]; + assert(signerStrategy, `No signer strategy found for chain ${chain}`); - return this.signers; - } + const rawConfig = this.submissionStrategy[chain]?.submitter; - /** - * @notice Resolves single chain configuration - */ - private async resolveConfig( - chain: ChainName, - ): Promise<{ chain: ChainName } & SignerConfig> { - const { protocol } = this.multiProvider.getChainMetadata(chain); - - let config = await this.extractPrivateKey(chain); - - // For Cosmos, we get additional params - if (protocol === ProtocolType.CosmosNative) { - const provider = - await this.multiProtocolProvider.getCosmJsNativeProvider(chain); - const { bech32Prefix, gasPrice } = - this.multiProvider.getChainMetadata(chain); - - config = { - ...config, - extraParams: { provider, prefix: bech32Prefix, gasPrice }, + let signerConfig: SignerConfig; + const defaultPrivateKey = (this.options.key ?? {})[protocolType]; + if (isJsonRpcSubmitterConfig(rawConfig)) { + signerConfig = rawConfig; + + // Even if the config is a json rpc one, + // the private key might be undefined + signerConfig.privateKey ??= defaultPrivateKey; + } else { + signerConfig = { + chain, + privateKey: defaultPrivateKey, }; } - return { chain, ...config }; + const signer = await signerStrategy.getSigner(signerConfig); + + this.signers.set(chain, signer); + return signer; } /** - * @notice Gets private key from strategy + * @notice Creates signers for all chains */ - private async extractPrivateKey(chain: ChainName): Promise { - const protocol = this.multiProvider.getProtocol(chain); - - if ( - protocol === ProtocolType.Ethereum && - typeof this.options.key === 'string' - ) { - this.logger.debug( - `Using private key passed via CLI --key flag for chain ${chain}`, - ); - return { privateKey: this.options.key }; - } - - if (typeof this.options.key === 'object') { - assert( - this.options.key[protocol], - `Key flag --key.${protocol} for chain ${chain} not provided`, - ); - this.logger.debug( - `Using private key passed via CLI --key.${protocol} flag for chain ${chain}`, - ); - return { privateKey: this.options.key[protocol] }; + protected async initAllSigners(): Promise { + for (const chain of this.chains) { + await this.initSigner(chain); } - if (process.env[`HYP_KEY_${protocol.toUpperCase()}`]) { - this.logger.debug(`Using private key from .env for chain ${chain}`); - return { privateKey: process.env[`HYP_KEY_${protocol.toUpperCase()}`]! }; - } - - if (protocol === ProtocolType.Ethereum) { - if (ENV.HYP_KEY) { - this.logger.debug(`Using private key from .env for chain ${chain}`); - return { privateKey: ENV.HYP_KEY }; - } - } - - const signerStrategy = this.getSignerStrategyOrFail(chain); - const strategyConfig = await signerStrategy.getSignerConfig(chain); - assert( - strategyConfig.privateKey, - `No private key found for chain ${chain}`, - ); - this.logger.debug( - `Extracting private key from strategy config/user prompt for chain ${chain}`, - ); - - return { privateKey: strategyConfig.privateKey }; + return this.signers; } - private getSignerStrategyOrFail(chain: ChainName): IMultiProtocolSigner { - const strategy = this.signerStrategies.get(chain); - assert(strategy, `No signer strategy found for chain ${chain}`); - return strategy; - } + getSpecificSigner(chain: ChainName): T { + const maybeSigner = this.signers.get(chain); + assert(maybeSigner, `Signer not set for chain ${chain}`); - protected getSpecificSigner(chain: ChainName): T { - return this.signers.get(chain) as T; + return maybeSigner as T; } getEVMSigner(chain: ChainName): Signer { - const protocolType = this.multiProvider.getChainMetadata(chain).protocol; + const protocolType = this.multiProtocolProvider.getProtocol(chain); assert( protocolType === ProtocolType.Ethereum, `Chain ${chain} is not an Ethereum chain`, @@ -214,11 +205,82 @@ export class MultiProtocolSignerManager { } getCosmosNativeSigner(chain: ChainName): SigningHyperlaneModuleClient { - const protocolType = this.multiProvider.getProtocol(chain); + const protocolType = this.multiProtocolProvider.getProtocol(chain); assert( protocolType === ProtocolType.CosmosNative, `Chain ${chain} is not a Cosmos Native chain`, ); return this.getSpecificSigner(chain); } + + async getSignerAddress(chain: ChainName): Promise
{ + const metadata = this.multiProtocolProvider.getChainMetadata(chain); + + switch (metadata.protocol) { + case ProtocolType.Ethereum: { + const signer = this.getEVMSigner(chain); + return signer.getAddress(); + } + case ProtocolType.CosmosNative: { + const signer = this.getCosmosNativeSigner(chain); + return signer.account.address; + } + default: { + throw new Error( + `Signer for protocol type ${metadata.protocol} not supported`, + ); + } + } + } + + async getBalance(params: { + address: Address; + chain: ChainName; + denom?: string; + }): Promise { + const metadata = this.multiProtocolProvider.getChainMetadata(params.chain); + + switch (metadata.protocol) { + case ProtocolType.Ethereum: { + try { + const provider = this.multiProtocolProvider.getEthersV5Provider( + params.chain, + ); + const balance = await provider.getBalance(params.address); + return balance; + } catch (err) { + throw new Error( + `failed to get balance of address ${params.address} on EVM chain ${params.chain}: ${err}`, + ); + } + } + case ProtocolType.CosmosNative: { + assert( + params.denom, + `need denom to get balance of Cosmos Native chain ${params.chain}`, + ); + + try { + const provider = + await this.multiProtocolProvider.getCosmJsNativeProvider( + params.chain, + ); + const balance = await provider.getBalance( + params.address, + params.denom, + ); + return BigNumber.from(balance.amount); + } catch (err) { + throw new Error( + `failed to get balance of address ${params.address} on Cosmos Native chain ${params.chain}: ${err}`, + ); + } + } + default: { + throw new Error( + `Retrieving balance for account of protocol type ${metadata.protocol} not supported chain ${params.chain}`, + ); + } + } + } } diff --git a/typescript/cli/src/context/types.ts b/typescript/cli/src/context/types.ts index 05578b8f518..0574fb64560 100644 --- a/typescript/cli/src/context/types.ts +++ b/typescript/cli/src/context/types.ts @@ -1,5 +1,6 @@ import type { ethers } from 'ethers'; import type { CommandModule } from 'yargs'; +import { z } from 'zod'; import type { IRegistry } from '@hyperlane-xyz/registry'; import type { @@ -7,42 +8,55 @@ import type { ChainMetadata, MultiProtocolProvider, MultiProvider, - ProtocolMap, WarpCoreConfig, WarpRouteDeployConfigMailboxRequired, } from '@hyperlane-xyz/sdk'; +import { ProtocolType } from '@hyperlane-xyz/utils'; import { MultiProtocolSignerManager } from './strategies/signer/MultiProtocolSignerManager.js'; -export interface ContextSettings { - registryUris: string[]; - key?: string | ProtocolMap; - fromAddress?: string; +export const SignerKeyProtocolMapSchema = z + .record(z.nativeEnum(ProtocolType), z.string().nonempty(), { + errorMap: (_issue, _ctx) => ({ + message: `Key inputs not valid, make sure to use --key.{protocol} or the legacy flag --key but not both at the same time or avoid defining multiple --key or --key.{protocol} flags for the same protocol.`, + }), + }) + .or(z.string().nonempty()) + .transform((value) => + typeof value === 'string' ? { [ProtocolType.Ethereum]: value } : value, + ); + +export type SignerKeyProtocolMap = z.infer; + +interface BaseContext { + key?: string | SignerKeyProtocolMap; requiresKey?: boolean; - disableProxy?: boolean; skipConfirmation?: boolean; strategyPath?: string; +} + +export interface ContextSettings extends BaseContext { + registryUris: string[]; + disableProxy?: boolean; authToken?: string; } -export interface CommandContext { +export interface CommandContext + extends Omit { + key?: SignerKeyProtocolMap; registry: IRegistry; chainMetadata: ChainMap; multiProvider: MultiProvider; multiProtocolProvider: MultiProtocolProvider; skipConfirmation: boolean; - key?: string; // just for evm chains backward compatibility signerAddress?: string; - strategyPath?: string; } -export interface WriteCommandContext extends CommandContext { - key: string; +export interface WriteCommandContext extends Omit { + key: SignerKeyProtocolMap; signer: ethers.Signer; multiProtocolSigner?: MultiProtocolSignerManager; - isDryRun?: boolean; - dryRunChain?: string; apiKeys?: ChainMap; } diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index b40e89a9853..47d94ca61bc 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -44,8 +44,7 @@ interface ApplyParams extends DeployParams { export async function runCoreDeploy(params: DeployParams) { const { context, config } = params; const chain = params.chain; - const { isDryRun, registry, multiProvider, multiProtocolSigner, apiKeys } = - context; + const { registry, multiProvider, multiProtocolSigner, apiKeys } = context; // Validate ISM compatibility validateCoreIsmCompatibility(chain, config, context); @@ -121,12 +120,10 @@ export async function runCoreDeploy(params: DeployParams) { throw new Error('Chain protocol is not supported yet!'); } - if (!isDryRun) { - await registry.updateChain({ - chainName: chain, - addresses: deployedAddresses, - }); - } + await registry.updateChain({ + chainName: chain, + addresses: deployedAddresses, + }); logGreen('✅ Core contract deployments complete:\n'); log(indentYamlOrJson(yamlStringify(deployedAddresses, null, 2), 4)); diff --git a/typescript/cli/src/deploy/dry-run.ts b/typescript/cli/src/deploy/dry-run.ts deleted file mode 100644 index 13a0399dbd5..00000000000 --- a/typescript/cli/src/deploy/dry-run.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { - ANVIL_RPC_METHODS, - MultiProvider, - getLocalProvider, - resetFork, - setFork, -} from '@hyperlane-xyz/sdk'; -import { toUpperCamelCase } from '@hyperlane-xyz/utils'; - -import { logGray, logGreen, warnYellow } from '../logger.js'; -import { ENV } from '../utils/env.js'; - -/** - * Forks a provided network onto MultiProvider - * @param multiProvider the MultiProvider to be prepared - * @param chains the chain selection passed-in by the user - */ -export async function forkNetworkToMultiProvider( - multiProvider: MultiProvider, - chain: string, -) { - multiProvider = multiProvider.extendChainMetadata({ - [chain]: { blocks: { confirmations: 1 } }, - }); - - await setFork(multiProvider, chain); - return multiProvider; -} - -/** - * Ensures an anvil node is running locally. - */ -export async function verifyAnvil() { - logGray('🔎 Verifying anvil node is running...'); - - const provider = getLocalProvider({ - anvilIPAddr: ENV.ANVIL_IP_ADDR, - anvilPort: ENV.ANVIL_PORT, - }); - try { - await provider.send(ANVIL_RPC_METHODS.NODE_INFO, []); - } catch (error: any) { - if (error.message.includes('missing response')) - throw new Error(`No active anvil node detected. -\tPlease run \`anvil\` in a separate instance.`); - } - - logGreen('✅ Successfully verified anvil node is running'); -} - -/** - * Evaluates if an error is related to the current dry-run. - * @param error the thrown error - * @param dryRun the chain name to execute the dry-run on - */ -export function evaluateIfDryRunFailure(error: any, dryRun: string) { - if (dryRun && error.message.includes('call revert exception')) - warnYellow( - '⛔️ [dry-run] The current RPC may not support forking. Please consider using a different RPC provider.', - ); -} - -export async function completeDryRun(command: string) { - await resetFork(); - - logGreen(`✅ ${toUpperCamelCase(command)} dry-run completed successfully`); -} diff --git a/typescript/cli/src/deploy/utils.ts b/typescript/cli/src/deploy/utils.ts index 2ff42eef410..a6d5bc37cdf 100644 --- a/typescript/cli/src/deploy/utils.ts +++ b/typescript/cli/src/deploy/utils.ts @@ -11,13 +11,14 @@ import { IsmConfig, IsmType, MultisigConfig, + TypedSigner, WarpRouteDeployConfig, - getLocalProvider, isIsmCompatible, } from '@hyperlane-xyz/sdk'; -import { Address, ProtocolType, assert } from '@hyperlane-xyz/utils'; +import { Address, assert } from '@hyperlane-xyz/utils'; import { parseIsmConfig } from '../config/ism.js'; +import { MINIMUM_WARP_DEPLOY_GAS } from '../consts.js'; import { CommandContext, WriteCommandContext } from '../context/types.js'; import { log, @@ -28,10 +29,6 @@ import { logTable, } from '../logger.js'; import { nativeBalancesAreSufficient } from '../utils/balances.js'; -import { ENV } from '../utils/env.js'; -import { assertSigner } from '../utils/keys.js'; - -import { completeDryRun } from './dry-run.js'; export async function runPreflightChecksForChains({ context, @@ -41,28 +38,40 @@ export async function runPreflightChecksForChains({ }: { context: WriteCommandContext; chains: ChainName[]; - minGas: string; + minGas: typeof MINIMUM_WARP_DEPLOY_GAS; // Chains for which to assert a native balance // Defaults to all chains if not specified chainsToGasCheck?: ChainName[]; }) { log('Running pre-flight checks for chains...'); - const { multiProvider, skipConfirmation } = context; + const { + multiProvider, + skipConfirmation, + multiProtocolProvider, + multiProtocolSigner, + } = context; + + assert(multiProtocolSigner, 'multiProtocolSigner not defined'); if (!chains?.length) throw new Error('Empty chain selection'); for (const chain of chains) { const metadata = multiProvider.tryGetChainMetadata(chain); if (!metadata) throw new Error(`No chain config found for ${chain}`); - if (metadata.protocol !== ProtocolType.Ethereum) - throw new Error('Only Ethereum chains are supported for now'); - const signer = multiProvider.getSigner(chain); - assertSigner(signer); + + const signer = multiProtocolSigner.getSpecificSigner(chain); + + if (!signer) { + throw new Error('signer is invalid'); + } + logGreen(`✅ ${metadata.displayName ?? chain} signer is valid`); } logGreen('✅ Chains are valid'); await nativeBalancesAreSufficient( multiProvider, + multiProtocolProvider, + multiProtocolSigner, chainsToGasCheck ?? chains, minGas, skipConfirmation, @@ -138,22 +147,20 @@ export async function prepareDeploy( userAddress: Address | null, chains: ChainName[], ): Promise> { - const { multiProvider, isDryRun } = context; + const { multiProvider, multiProtocolSigner } = context; const initialBalances: Record = {}; - await Promise.all( - chains.map(async (chain: ChainName) => { - const provider = isDryRun - ? getLocalProvider({ - anvilIPAddr: ENV.ANVIL_IP_ADDR, - anvilPort: ENV.ANVIL_PORT, - }) - : multiProvider.getProvider(chain); - const address = - userAddress ?? (await multiProvider.getSigner(chain).getAddress()); - const currentBalance = await provider.getBalance(address); - initialBalances[chain] = currentBalance; - }), - ); + + for (const chain of chains) { + const { nativeToken } = multiProvider.getChainMetadata(chain); + const address = + userAddress ?? (await multiProtocolSigner!.getSignerAddress(chain)); + initialBalances[chain] = await multiProtocolSigner!.getBalance({ + address, + chain, + denom: nativeToken?.denom, + }); + } + return initialBalances; } @@ -164,30 +171,28 @@ export async function completeDeploy( userAddress: Address | null, chains: ChainName[], ) { - const { multiProvider, isDryRun } = context; + const { multiProvider, multiProtocolSigner } = context; + assert(multiProtocolSigner, `multiProtocolSigner not defined`); + if (chains.length > 0) logPink(`⛽️ Gas Usage Statistics`); for (const chain of chains) { - const provider = isDryRun - ? getLocalProvider({ - anvilIPAddr: ENV.ANVIL_IP_ADDR, - anvilPort: ENV.ANVIL_PORT, - }) - : multiProvider.getProvider(chain); - const address = - userAddress ?? (await multiProvider.getSigner(chain).getAddress()); - const currentBalance = await provider.getBalance(address); + const { nativeToken } = multiProvider.getChainMetadata(chain); + const address = userAddress + ? userAddress + : await multiProtocolSigner.getSignerAddress(chain); + const currentBalance = await multiProtocolSigner!.getBalance({ + address, + chain, + denom: nativeToken?.denom, + }); const balanceDelta = initialBalances[chain].sub(currentBalance); - if (isDryRun && balanceDelta.lt(0)) break; + logPink( - `\t- Gas required for ${command} ${ - isDryRun ? 'dry-run' : 'deploy' - } on ${chain}: ${ethers.utils.formatEther(balanceDelta)} ${ - multiProvider.getChainMetadata(chain).nativeToken?.symbol ?? 'ETH' + `\t- Gas required for ${command} deploy on ${chain}: ${ethers.utils.formatEther(balanceDelta)} ${ + nativeToken?.symbol ?? 'UNKNOWN SYMBOL' }`, ); } - - if (isDryRun) await completeDryRun(command); } function transformChainMetadataForDisplay(chainMetadata: ChainMetadata) { diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 9563c21b83c..bb14ff3dee0 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -15,13 +15,9 @@ import { ChainMap, ChainName, ContractVerifier, - EVM_TOKEN_TYPE_TO_STANDARD, EvmERC20WarpModule, ExplorerLicenseType, HypERC20Deployer, - HypERC20Factories, - HypERC721Factories, - HyperlaneContractsMap, IsmType, MultiProvider, MultisigIsmConfig, @@ -29,7 +25,6 @@ import { PausableIsmConfig, RoutingIsmConfig, SubmissionStrategy, - TokenFactories, TokenMetadataMap, TrustedRelayerIsmConfig, TxSubmitterBuilder, @@ -38,20 +33,20 @@ import { WarpCoreConfigSchema, WarpRouteDeployConfigMailboxRequired, WarpRouteDeployConfigSchema, - attachContractsMap, - connectContractsMap, + enrollCrossChainRouters, executeWarpDeploy, expandWarpDeployConfig, extractIsmAndHookFactoryAddresses, getRouterAddressesFromWarpCoreConfig, getSubmitterBuilder, getTokenConnectionId, - hypERC20factories, isCollateralTokenConfig, isXERC20TokenConfig, splitWarpCoreAndExtendedConfigs, + tokenTypeToStandard, } from '@hyperlane-xyz/sdk'; import { + Address, ProtocolType, assert, objFilter, @@ -117,7 +112,15 @@ export async function runWarpRouteDeploy({ warpRouteId?: string; warpDeployConfigFileName?: string; }) { - const { skipConfirmation, chainMetadata, registry } = context; + const { + skipConfirmation, + chainMetadata, + registry, + multiProvider, + multiProtocolSigner, + } = context; + + assert(multiProtocolSigner, `multiProtocolSigner not defined`); // Validate ISM compatibility for all chains validateWarpIsmCompatibility(warpDeployConfig, context); @@ -134,20 +137,30 @@ export async function runWarpRouteDeploy({ }; await runDeployPlanStep(deploymentParams); - // Some of the below functions throw if passed non-EVM chains - const ethereumChains = chains.filter( - (chain) => chainMetadata[chain].protocol === ProtocolType.Ethereum, + + // Some of the below functions throw if passed non-EVM or Cosmos Native chains + const deploymentChains = chains.filter( + (chain) => + chainMetadata[chain].protocol === ProtocolType.Ethereum || + chainMetadata[chain].protocol === ProtocolType.CosmosNative, ); await runPreflightChecksForChains({ context, - chains: ethereumChains, + chains: deploymentChains, minGas: MINIMUM_WARP_DEPLOY_GAS, }); - const initialBalances = await prepareDeploy(context, null, ethereumChains); + const initialBalances = await prepareDeploy(context, null, deploymentChains); + + const { deployedContracts } = await executeDeploy(deploymentParams, apiKeys); - const deployedContracts = await executeDeploy(deploymentParams, apiKeys); + const registryAddresses = await registry.getAddresses(); + + await enrollCrossChainRouters( + { multiProvider, multiProtocolSigner, registryAddresses, warpDeployConfig }, + deployedContracts, + ); const { warpCoreConfig, addWarpRouteOptions } = await getWarpCoreConfig( deploymentParams, @@ -187,7 +200,13 @@ export async function runWarpRouteDeploy({ await writeDeploymentArtifacts(warpCoreConfig, context, warpRouteIdOptions); - await completeDeploy(context, 'warp', initialBalances, null, ethereumChains!); + await completeDeploy( + context, + 'warp', + initialBalances, + null, + deploymentChains, + ); } async function runDeployPlanStep({ context, warpDeployConfig }: DeployParams) { @@ -195,7 +214,7 @@ async function runDeployPlanStep({ context, warpDeployConfig }: DeployParams) { displayWarpDeployPlan(warpDeployConfig); - if (skipConfirmation || context.isDryRun) return; + if (skipConfirmation) return; const isConfirmed = await confirm({ message: 'Is this deployment plan correct?', @@ -206,29 +225,36 @@ async function runDeployPlanStep({ context, warpDeployConfig }: DeployParams) { async function executeDeploy( params: DeployParams, apiKeys: ChainMap, -): Promise> { +): Promise<{ + deployedContracts: ChainMap
; + deployments: WarpCoreConfig; +}> { logBlue('🚀 All systems ready, captain! Beginning deployment...'); const { warpDeployConfig, - context: { multiProvider, isDryRun, dryRunChain, registry }, + context: { multiProvider, multiProtocolSigner, registry }, } = params; - const config: WarpRouteDeployConfigMailboxRequired = - isDryRun && dryRunChain - ? { [dryRunChain]: warpDeployConfig[dryRunChain] } - : warpDeployConfig; + assert(multiProtocolSigner, `multiProtocolSigner not defined`); + const registryAddresses = await registry.getAddresses(); const deployedContracts = await executeWarpDeploy( + warpDeployConfig, multiProvider, - config, + multiProtocolSigner, registryAddresses, apiKeys, ); + const { warpCoreConfig: deployments } = await getWarpCoreConfig( + { context: params.context, warpDeployConfig }, + deployedContracts, + ); + logGreen('✅ Warp contract deployments complete'); - return deployedContracts; + return { deployedContracts, deployments }; } async function writeDeploymentArtifacts( @@ -236,16 +262,15 @@ async function writeDeploymentArtifacts( context: WriteCommandContext, addWarpRouteOptions?: AddWarpRouteConfigOptions, ) { - if (!context.isDryRun) { - log('Writing deployment artifacts...'); - await context.registry.addWarpRoute(warpCoreConfig, addWarpRouteOptions); - } + log('Writing deployment artifacts...'); + await context.registry.addWarpRoute(warpCoreConfig, addWarpRouteOptions); + log(indentYamlOrJson(yamlStringify(warpCoreConfig, null, 2), 4)); } async function getWarpCoreConfig( params: DeployParams, - contracts: HyperlaneContractsMap, + contracts: ChainMap
, ): Promise<{ warpCoreConfig: WarpCoreConfig; addWarpRouteOptions: AddWarpRouteConfigOptions; @@ -260,13 +285,14 @@ async function getWarpCoreConfig( ); generateTokenConfigs( + params.context.multiProvider, warpCoreConfig, params.warpDeployConfig, contracts, tokenMetadataMap, ); - fullyConnectTokens(warpCoreConfig); + fullyConnectTokens(warpCoreConfig, params.context.multiProvider); const symbol = tokenMetadataMap.getDefaultSymbol(); @@ -277,34 +303,28 @@ async function getWarpCoreConfig( * Creates token configs. */ function generateTokenConfigs( + multiProvider: MultiProvider, warpCoreConfig: WarpCoreConfig, warpDeployConfig: WarpRouteDeployConfigMailboxRequired, - contracts: HyperlaneContractsMap, + contracts: ChainMap
, tokenMetadataMap: TokenMetadataMap, ): void { - for (const [chainName, contract] of Object.entries(contracts)) { + for (const chainName of Object.keys(contracts)) { const config = warpDeployConfig[chainName]; const collateralAddressOrDenom = isCollateralTokenConfig(config) || isXERC20TokenConfig(config) ? config.token // gets set in the above deriveTokenMetadata() : undefined; - const decimals: number | undefined = - tokenMetadataMap.getDecimals(chainName); - const name: any = tokenMetadataMap.getName(chainName); - const symbol: any = tokenMetadataMap.getSymbol(chainName); - - assert(decimals, `Decimals for ${chainName} doesn't exist`); + const protocol = multiProvider.getProtocol(chainName); warpCoreConfig.tokens.push({ chainName, - standard: EVM_TOKEN_TYPE_TO_STANDARD[config.type], - decimals, - symbol: config.symbol || symbol, - name, - addressOrDenom: - contract[warpDeployConfig[chainName].type as keyof TokenFactories] - .address, + standard: tokenTypeToStandard(protocol as ProtocolType, config.type), + decimals: tokenMetadataMap.getDecimals(chainName)!, + symbol: config.symbol || tokenMetadataMap.getSymbol(chainName)!, + name: tokenMetadataMap.getName(chainName)!, + addressOrDenom: contracts[chainName], collateralAddressOrDenom, }); } @@ -316,7 +336,10 @@ function generateTokenConfigs( * Assumes full interconnectivity between all tokens for now b.c. that's * what the deployers do by default. */ -function fullyConnectTokens(warpCoreConfig: WarpCoreConfig): void { +function fullyConnectTokens( + warpCoreConfig: WarpCoreConfig, + multiProvider: MultiProvider, +): void { for (const token1 of warpCoreConfig.tokens) { for (const token2 of warpCoreConfig.tokens) { if ( @@ -327,7 +350,7 @@ function fullyConnectTokens(warpCoreConfig: WarpCoreConfig): void { token1.connections ||= []; token1.connections.push({ token: getTokenConnectionId( - ProtocolType.Ethereum, + multiProvider.getProtocol(token2.chainName), token2.chainName, token2.addressOrDenom!, ), @@ -415,7 +438,7 @@ async function deployWarpExtensionContracts( initialExtendedConfigs, ); - const newDeployedContracts = await executeDeploy( + const { deployedContracts: newDeployedContracts } = await executeDeploy( { context: params.context, warpDeployConfig: extendedConfigs, @@ -425,7 +448,6 @@ async function deployWarpExtensionContracts( // Merge existing and new routers const mergedRouters = mergeAllRouters( - params.context.multiProvider, existingConfigs, newDeployedContracts, warpCoreConfigByChain, @@ -667,26 +689,23 @@ async function deriveMetadataFromExisting( * Merges existing router configs with newly deployed router contracts. */ function mergeAllRouters( - multiProvider: MultiProvider, existingConfigs: WarpRouteDeployConfigMailboxRequired, - deployedContractsMap: HyperlaneContractsMap< - HypERC20Factories | HypERC721Factories - >, + deployedContractsMap: ChainMap
, warpCoreConfigByChain: ChainMap, -) { - const existingContractAddresses = objMap( - existingConfigs, - (chain, config) => ({ - [config.type]: warpCoreConfigByChain[chain].addressOrDenom!, - }), - ); +): ChainMap
{ + let result: ChainMap
= {}; + + for (const chain of Object.keys(existingConfigs)) { + result = { + ...result, + [chain]: warpCoreConfigByChain[chain].addressOrDenom!, + }; + } + return { - ...connectContractsMap( - attachContractsMap(existingContractAddresses, hypERC20factories), - multiProvider, - ), + ...result, ...deployedContractsMap, - } as HyperlaneContractsMap; + }; } function displayWarpDeployPlan( diff --git a/typescript/cli/src/rebalancer/core/WithInflightGuard.test.ts b/typescript/cli/src/rebalancer/core/WithInflightGuard.test.ts new file mode 100644 index 00000000000..15b91717e1c --- /dev/null +++ b/typescript/cli/src/rebalancer/core/WithInflightGuard.test.ts @@ -0,0 +1,131 @@ +import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { ethers } from 'ethers'; +import { pino } from 'pino'; +import Sinon from 'sinon'; + +import { chainMetadata } from '@hyperlane-xyz/registry'; +import { ChainMetadataManager } from '@hyperlane-xyz/sdk'; + +import { RebalancingRoute } from '../interfaces/IStrategy.js'; +import { MockRebalancer, buildTestConfig } from '../test/helpers.js'; +import { ExplorerClient } from '../utils/ExplorerClient.js'; + +import { WithInflightGuard } from './WithInflightGuard.js'; + +chai.use(chaiAsPromised); + +const testLogger = pino({ level: 'silent' }); + +describe('WithInflightGuard', () => { + it('forwards empty routes without calling Explorer', async () => { + const config = buildTestConfig(); + + const rebalancer = new MockRebalancer(); + const rebalanceSpy = Sinon.spy(rebalancer, 'rebalance'); + + const explorer = new ExplorerClient('http://localhost'); + const explorerSpy = Sinon.stub(explorer, 'hasUndeliveredRebalance'); + + const guard = new WithInflightGuard( + config, + rebalancer, + explorer, + ethers.Wallet.createRandom().address, + new ChainMetadataManager(chainMetadata as any), + testLogger, + ); + + await guard.rebalance([]); + + expect(explorerSpy.called).to.be.false; + expect(rebalanceSpy.calledOnce).to.be.true; + expect(rebalanceSpy.calledWith([])).to.be.true; + }); + + it('calls underlying rebalancer when no inflight is detected', async () => { + const config = buildTestConfig({}, ['ethereum', 'arbitrum']); + const routes: RebalancingRoute[] = [{ origin: 'ethereum' } as any]; + + const rebalancer = new MockRebalancer(); + const rebalanceSpy = Sinon.spy(rebalancer, 'rebalance'); + + const explorer = new ExplorerClient('http://localhost'); + const explorerSpy = Sinon.stub( + explorer, + 'hasUndeliveredRebalance', + ).resolves(false); + + const guard = new WithInflightGuard( + config, + rebalancer, + explorer, + ethers.Wallet.createRandom().address, + new ChainMetadataManager(chainMetadata as any), + testLogger, + ); + + await guard.rebalance(routes); + + expect(explorerSpy.calledOnce).to.be.true; + expect(rebalanceSpy.calledOnce).to.be.true; + expect(rebalanceSpy.calledWith(routes)).to.be.true; + }); + + it('skips rebalancing when inflight is detected', async () => { + const config = buildTestConfig({}, ['ethereum', 'arbitrum']); + const routes: RebalancingRoute[] = [{ origin: 'ethereum' } as any]; + + const rebalancer = new MockRebalancer(); + const rebalanceSpy = Sinon.spy(rebalancer, 'rebalance'); + + const explorer = new ExplorerClient('http://localhost'); + const explorerSpy = Sinon.stub( + explorer, + 'hasUndeliveredRebalance', + ).resolves(true); + + const guard = new WithInflightGuard( + config, + rebalancer, + explorer, + ethers.Wallet.createRandom().address, + new ChainMetadataManager(chainMetadata as any), + testLogger, + ); + + await guard.rebalance(routes); + + expect(explorerSpy.calledOnce).to.be.true; + expect(rebalanceSpy.called).to.be.false; + }); + + it('propagates explorer query error', async () => { + const config = buildTestConfig({}, ['ethereum', 'arbitrum']); + const routes: RebalancingRoute[] = [{ origin: 'ethereum' } as any]; + + const rebalancer = new MockRebalancer(); + const rebalanceSpy = Sinon.spy(rebalancer, 'rebalance'); + + const explorer = new ExplorerClient('http://localhost'); + const explorerSpy = Sinon.stub(explorer, 'hasUndeliveredRebalance').rejects( + new Error('Explorer HTTP 405'), + ); + + const guard = new WithInflightGuard( + config, + rebalancer, + explorer, + ethers.Wallet.createRandom().address, + new ChainMetadataManager(chainMetadata as any), + testLogger, + ); + + await expect(guard.rebalance(routes)).to.be.rejectedWith( + 'Explorer HTTP 405', + ); + + expect(explorerSpy.calledOnce).to.be.true; + expect(rebalanceSpy.called).to.be.false; + }); +}); diff --git a/typescript/cli/src/rebalancer/core/WithInflightGuard.ts b/typescript/cli/src/rebalancer/core/WithInflightGuard.ts new file mode 100644 index 00000000000..a6e4cbe9237 --- /dev/null +++ b/typescript/cli/src/rebalancer/core/WithInflightGuard.ts @@ -0,0 +1,67 @@ +import type { Logger } from 'pino'; + +import { ChainMetadataManager } from '@hyperlane-xyz/sdk'; + +import { RebalancerConfig } from '../config/RebalancerConfig.js'; +import type { IRebalancer } from '../interfaces/IRebalancer.js'; +import type { RebalancingRoute } from '../interfaces/IStrategy.js'; +import { ExplorerClient } from '../utils/ExplorerClient.js'; + +/** + * Prevents rebalancing if there are inflight rebalances for the warp route. + */ +export class WithInflightGuard implements IRebalancer { + private readonly logger: Logger; + + constructor( + private readonly config: RebalancerConfig, + private readonly rebalancer: IRebalancer, + private readonly explorer: ExplorerClient, + private readonly txSender: string, + private readonly chainManager: ChainMetadataManager, + logger: Logger, + ) { + this.logger = logger.child({ class: WithInflightGuard.name }); + } + + async rebalance(routes: RebalancingRoute[]): Promise { + // Always enforce the inflight guard + if (routes.length === 0) { + return this.rebalancer.rebalance(routes); + } + + const chains = Object.keys(this.config.strategyConfig.chains); + const bridges = chains.map( + (chain) => this.config.strategyConfig.chains[chain].bridge, + ); + const domains = chains.map((chain) => this.chainManager.getDomainId(chain)); + + let hasInflightRebalances = false; + try { + hasInflightRebalances = await this.explorer.hasUndeliveredRebalance( + { + bridges, + domains: Array.from(new Set(domains)), + txSender: this.txSender, + limit: 5, + }, + this.logger, + ); + } catch (e: any) { + this.logger.error( + { status: e.status, body: e.body }, + 'Explorer inflight query failed', + ); + throw e; + } + + if (hasInflightRebalances) { + this.logger.info( + 'Inflight rebalance detected via Explorer; skipping this cycle', + ); + return; + } + + return this.rebalancer.rebalance(routes); + } +} diff --git a/typescript/cli/src/rebalancer/core/WithSemaphore.test.ts b/typescript/cli/src/rebalancer/core/WithSemaphore.test.ts index a8f7c836317..6536f668537 100644 --- a/typescript/cli/src/rebalancer/core/WithSemaphore.test.ts +++ b/typescript/cli/src/rebalancer/core/WithSemaphore.test.ts @@ -1,14 +1,12 @@ import chai, { expect } from 'chai'; import chaiAsPromised from 'chai-as-promised'; -import { ethers } from 'ethers'; import { pino } from 'pino'; import Sinon from 'sinon'; import { RebalancerStrategyOptions } from '@hyperlane-xyz/sdk'; -import { RebalancerConfig } from '../config/RebalancerConfig.js'; -import { IRebalancer } from '../interfaces/IRebalancer.js'; import { RebalancingRoute } from '../interfaces/IStrategy.js'; +import { MockRebalancer, buildTestConfig } from '../test/helpers.js'; import { WithSemaphore } from './WithSemaphore.js'; @@ -16,36 +14,6 @@ chai.use(chaiAsPromised); const testLogger = pino({ level: 'silent' }); -class MockRebalancer implements IRebalancer { - rebalance(_routes: RebalancingRoute[]): Promise { - return Promise.resolve(); - } -} - -function buildTestConfig( - overrides: Partial = {}, -): RebalancerConfig { - return { - warpRouteId: 'test-route', - strategyConfig: { - rebalanceStrategy: RebalancerStrategyOptions.Weighted, - chains: { - chain1: { - bridgeLockTime: 60 * 1000, - bridge: ethers.constants.AddressZero, - weighted: { - weight: BigInt(1), - tolerance: BigInt(0), - }, - }, - ...(overrides.strategyConfig?.chains ?? {}), - }, - ...overrides.strategyConfig, - }, - ...overrides, - }; -} - describe('WithSemaphore', () => { it('should call the underlying rebalancer', async () => { const config = buildTestConfig(); diff --git a/typescript/cli/src/rebalancer/core/WithSemaphore.ts b/typescript/cli/src/rebalancer/core/WithSemaphore.ts index b082088fe41..60b844be189 100644 --- a/typescript/cli/src/rebalancer/core/WithSemaphore.ts +++ b/typescript/cli/src/rebalancer/core/WithSemaphore.ts @@ -1,4 +1,4 @@ -import { Logger } from 'pino'; +import type { Logger } from 'pino'; import { RebalancerConfig } from '../config/RebalancerConfig.js'; import type { IRebalancer } from '../interfaces/IRebalancer.js'; diff --git a/typescript/cli/src/rebalancer/factories/RebalancerContextFactory.ts b/typescript/cli/src/rebalancer/factories/RebalancerContextFactory.ts index 2d9396845c1..cdcbd17face 100644 --- a/typescript/cli/src/rebalancer/factories/RebalancerContextFactory.ts +++ b/typescript/cli/src/rebalancer/factories/RebalancerContextFactory.ts @@ -2,7 +2,7 @@ import { Logger } from 'pino'; import { type ChainMap, - type ChainMetadata, + ChainMetadataManager, type Token, WarpCore, } from '@hyperlane-xyz/sdk'; @@ -11,6 +11,7 @@ import { objMap } from '@hyperlane-xyz/utils'; import type { WriteCommandContext } from '../../context/types.js'; import { RebalancerConfig } from '../config/RebalancerConfig.js'; import { Rebalancer } from '../core/Rebalancer.js'; +import { WithInflightGuard } from '../core/WithInflightGuard.js'; import { WithSemaphore } from '../core/WithSemaphore.js'; import type { IRebalancer } from '../interfaces/IRebalancer.js'; import type { IStrategy } from '../interfaces/IStrategy.js'; @@ -18,6 +19,7 @@ import { Metrics } from '../metrics/Metrics.js'; import { PriceGetter } from '../metrics/PriceGetter.js'; import { Monitor } from '../monitor/Monitor.js'; import { StrategyFactory } from '../strategy/StrategyFactory.js'; +import { ExplorerClient } from '../utils/ExplorerClient.js'; import { isCollateralizedTokenEligibleForRebalancing } from '../utils/index.js'; export class RebalancerContextFactory { @@ -30,7 +32,6 @@ export class RebalancerContextFactory { */ private constructor( private readonly config: RebalancerConfig, - private readonly metadata: ChainMap, private readonly warpCore: WarpCore, private readonly tokensByChainName: ChainMap, private readonly context: WriteCommandContext, @@ -53,7 +54,6 @@ export class RebalancerContextFactory { 'Creating RebalancerContextFactory', ); const { registry } = context; - const metadata = await registry.getMetadata(); const addresses = await registry.getAddresses(); // The Sealevel warp adapters require the Mailbox address, so we @@ -81,7 +81,6 @@ export class RebalancerContextFactory { ); return new RebalancerContextFactory( config, - metadata, warpCore, tokensByChainName, context, @@ -103,7 +102,7 @@ export class RebalancerContextFactory { 'Creating Metrics', ); const tokenPriceGetter = PriceGetter.create( - this.metadata, + this.context.chainMetadata, this.logger, coingeckoApiKey, ); @@ -161,14 +160,37 @@ export class RebalancerContextFactory { override: v.override, })), this.warpCore, - this.metadata, + this.context.chainMetadata, this.tokensByChainName, this.context.multiProvider, this.logger, metrics, ); - return new WithSemaphore(this.config, rebalancer, this.logger); + const explorerUrl = + process.env.EXPLORER_API_URL || 'https://api.hyperlane.xyz/v1/graphql'; + + const rebalancerAddress = this.context.signerAddress; + if (!rebalancerAddress) { + throw new Error('rebalancer address is required'); + } + + const explorer = new ExplorerClient(explorerUrl); + // Compose decorators: Inflight guard first, then semaphore, then core rebalancer + const withSemaphore = new WithSemaphore( + this.config, + rebalancer, + this.logger, + ); + const withInflight = new WithInflightGuard( + this.config, + withSemaphore, + explorer, + rebalancerAddress, + new ChainMetadataManager(this.context.chainMetadata), + this.logger, + ); + return withInflight; } private async getInitialTotalCollateral(): Promise { @@ -180,7 +202,6 @@ export class RebalancerContextFactory { this.warpCore.tokens.map(async (token) => { if ( isCollateralizedTokenEligibleForRebalancing(token) && - token.collateralAddressOrDenom && chainNames.has(token.chainName) ) { const adapter = token.getHypAdapter(this.warpCore.multiProvider); diff --git a/typescript/cli/src/rebalancer/test/helpers.ts b/typescript/cli/src/rebalancer/test/helpers.ts new file mode 100644 index 00000000000..87a4a29b85c --- /dev/null +++ b/typescript/cli/src/rebalancer/test/helpers.ts @@ -0,0 +1,46 @@ +import { ethers } from 'ethers'; + +import { RebalancerStrategyOptions } from '@hyperlane-xyz/sdk'; + +import type { RebalancerConfig } from '../config/RebalancerConfig.js'; +import type { IRebalancer } from '../interfaces/IRebalancer.js'; +import type { RebalancingRoute } from '../interfaces/IStrategy.js'; + +export class MockRebalancer implements IRebalancer { + rebalance(_routes: RebalancingRoute[]): Promise { + return Promise.resolve(); + } +} + +export function buildTestConfig( + overrides: Partial = {}, + chains: string[] = ['chain1'], +): RebalancerConfig { + const baseChains = chains.reduce( + (acc, chain) => { + (acc as any)[chain] = { + bridgeLockTime: 60 * 1000, + bridge: ethers.constants.AddressZero, + weighted: { + weight: BigInt(1), + tolerance: BigInt(0), + }, + }; + return acc; + }, + {} as Record, + ); + + return { + warpRouteId: 'test-route', + strategyConfig: { + rebalanceStrategy: RebalancerStrategyOptions.Weighted, + chains: { + ...baseChains, + ...(overrides.strategyConfig?.chains ?? {}), + }, + ...overrides.strategyConfig, + }, + ...overrides, + } as any as RebalancerConfig; +} diff --git a/typescript/cli/src/rebalancer/utils/ExplorerClient.ts b/typescript/cli/src/rebalancer/utils/ExplorerClient.ts new file mode 100644 index 00000000000..bcf0fbf9854 --- /dev/null +++ b/typescript/cli/src/rebalancer/utils/ExplorerClient.ts @@ -0,0 +1,99 @@ +import type { Logger } from 'pino'; + +export type InflightRebalanceQueryParams = { + bridges: string[]; + domains: number[]; + txSender: string; + limit?: number; +}; + +export class ExplorerClient { + constructor(private readonly baseUrl: string) {} + + private toBytea(addr: string): string { + return addr.replace(/^0x/i, '\\x').toLowerCase(); + } + + async hasUndeliveredRebalance( + params: InflightRebalanceQueryParams, + logger: Logger, + ): Promise { + const { bridges, domains, txSender, limit = 5 } = params; + + const variables = { + senders: bridges.map((a) => this.toBytea(a)), + recipients: bridges.map((a) => this.toBytea(a)), + originDomains: domains, + destDomains: domains, + txSenders: [this.toBytea(txSender)], + limit, + }; + + logger.debug({ variables }, 'Explorer query variables'); + + const query = ` + query InflightRebalancesForRoute( + $senders: [bytea!], + $recipients: [bytea!], + $originDomains: [Int!], + $destDomains: [Int!], + $txSenders: [bytea!], + $limit: Int = 25 + ) { + message_view( + where: { + _and: [ + { is_delivered: { _eq: false } }, + { sender: { _in: $senders } }, + { recipient: { _in: $recipients } }, + { origin_domain_id: { _in: $originDomains } }, + { destination_domain_id: { _in: $destDomains } }, + { origin_tx_sender: { _in: $txSenders } } + ] + } + order_by: { origin_tx_id: desc } + limit: $limit + ) { + msg_id + origin_domain_id + destination_domain_id + sender + recipient + origin_tx_hash + origin_tx_sender + is_delivered + } + }`; + + const res = await fetch(this.baseUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query, variables }), + }); + + logger.debug({ status: res.status }, 'Explorer query response'); + + if (!res.ok) { + let errorDetails: string; + try { + const errorJson = await res.json(); + errorDetails = JSON.stringify(errorJson); + } catch (_e) { + try { + // Fallback to text if JSON parsing fails + errorDetails = await res.text(); + } catch (_textError) { + errorDetails = 'Unable to read response body'; + } + } + throw new Error(`Explorer query failed: ${res.status} ${errorDetails}`); + } + + const json = await res.json(); + const rows = json?.data?.message_view ?? []; + + logger.debug({ rows }, 'Explorer query rows'); + + return rows.length > 0; + } +} diff --git a/typescript/cli/src/tests/commands/warp.ts b/typescript/cli/src/tests/commands/warp.ts new file mode 100644 index 00000000000..f30181bff7e --- /dev/null +++ b/typescript/cli/src/tests/commands/warp.ts @@ -0,0 +1,151 @@ +import { $, ProcessPromise } from 'zx'; + +import { + WarpCoreConfig, + WarpCoreConfigSchema, + WarpRouteDeployConfigMailboxRequired, +} from '@hyperlane-xyz/sdk'; +import { ProtocolType } from '@hyperlane-xyz/utils'; + +import { readYamlOrJson } from '../../utils/files.js'; + +import { localTestRunCmdPrefix } from './helpers.js'; + +export class HyperlaneE2EWarpTestCommands { + protected cmdPrefix: string[]; + + protected protocol: ProtocolType; + protected registryPath: string; + + protected outputPath: string; + + constructor( + protocol: ProtocolType, + registryPath: string, + outputPath: string, + ) { + this.cmdPrefix = localTestRunCmdPrefix(); + + this.protocol = protocol; + this.registryPath = registryPath; + + this.outputPath = outputPath; + } + + protected get privateKeyFlag() { + if (this.protocol === ProtocolType.Ethereum) { + return '--key'; + } + + return `--key.${this.protocol}`; + } + + protected get hypKeyEnvName() { + if (this.protocol === ProtocolType.Ethereum) { + return 'HYP_KEY'; + } + + return `HYP_KEY_${this.protocol.toUpperCase()}`; + } + + public setCoreOutputPath(outputPath: string) { + this.outputPath = outputPath; + } + + /** + * Retrieves the deployed Warp address from the Warp core config. + */ + private getDeployedWarpAddress(chain: string, warpCorePath: string) { + const warpCoreConfig: WarpCoreConfig = readYamlOrJson(warpCorePath); + WarpCoreConfigSchema.parse(warpCoreConfig); + return warpCoreConfig.tokens.find((t) => t.chainName === chain)! + .addressOrDenom; + } + + public readRaw({ + chain, + warpAddress, + symbol, + outputPath, + }: { + chain?: string; + symbol?: string; + warpAddress?: string; + outputPath?: string; + }): ProcessPromise { + return $`${localTestRunCmdPrefix()} hyperlane warp read \ + --registry ${this.registryPath} \ + ${warpAddress ? ['--address', warpAddress] : []} \ + ${chain ? ['--chain', chain] : []} \ + ${symbol ? ['--symbol', symbol] : []} \ + --verbosity debug \ + ${outputPath || this.outputPath ? ['--config', outputPath || this.outputPath] : []}`; + } + + public read(chain: string, warpAddress: string): ProcessPromise { + return this.readRaw({ + chain, + warpAddress, + }); + } + + /** + * Reads the Warp route deployment config to specified output path. + * @param warpCorePath path to warp core + * @returns The Warp route deployment config. + */ + public async readConfig( + chain: string, + warpCorePath: string, + ): Promise { + const warpAddress = this.getDeployedWarpAddress(chain, warpCorePath); + await this.read(chain, warpAddress!); + return readYamlOrJson(this.outputPath); + } + + /** + * Deploys the Warp route to the specified chain using the provided config. + */ + public deployRaw({ + warpCorePath, + warpDeployPath, + hypKey, + skipConfirmationPrompts, + privateKey, + warpRouteId, + }: { + warpCorePath?: string; + warpDeployPath?: string; + hypKey?: string; + skipConfirmationPrompts?: boolean; + privateKey?: string; + warpRouteId?: string; + }): ProcessPromise { + return $`${ + hypKey ? [`${this.hypKeyEnvName}=${hypKey}`] : [] + } ${localTestRunCmdPrefix()} hyperlane warp deploy \ + --registry ${this.registryPath} \ + ${warpDeployPath ? ['--config', warpDeployPath] : []} \ + ${warpCorePath ? ['--warp', warpCorePath] : []} \ + ${privateKey ? [this.privateKeyFlag, privateKey] : []} \ + --verbosity debug \ + ${warpRouteId ? ['--warpRouteId', warpRouteId] : []} \ + ${skipConfirmationPrompts ? ['--yes'] : []}`; + } + + /** + * Deploys the Warp route to the specified chain using the provided config. + */ + public deploy( + warpDeployPath: string, + privateKey: string, + warpRouteId?: string, + ): ProcessPromise { + return this.deployRaw({ + privateKey, + warpDeployPath, + skipConfirmationPrompts: true, + warpRouteId, + }); + } +} diff --git a/typescript/cli/src/tests/cosmosnative/commands/helpers.ts b/typescript/cli/src/tests/cosmosnative/commands/helpers.ts new file mode 100644 index 00000000000..2a73e2192fd --- /dev/null +++ b/typescript/cli/src/tests/cosmosnative/commands/helpers.ts @@ -0,0 +1,87 @@ +import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; +import { GasPrice } from '@cosmjs/stargate'; +import path from 'path'; + +import { SigningHyperlaneModuleClient } from '@hyperlane-xyz/cosmos-sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +import { getContext } from '../../../context/context.js'; +import { REGISTRY_PATH, getCombinedWarpRoutePath } from '../consts.js'; + +export const GET_WARP_DEPLOY_CORE_CONFIG_OUTPUT_PATH = ( + originalDeployConfigPath: string, + symbol: string, +): string => { + const fileName = path.parse(originalDeployConfigPath).name; + + return getCombinedWarpRoutePath(symbol, [fileName]); +}; + +export async function deployCollateralToken( + mailbox: string, + privateKey: string, + chain: string, +): Promise
{ + const { multiProvider } = await getContext({ + registryUris: [REGISTRY_PATH], + key: privateKey, + }); + + const metadata = multiProvider.getChainMetadata(chain); + + const wallet = await DirectSecp256k1Wallet.fromKey( + Buffer.from(privateKey, 'hex'), + metadata.bech32Prefix, + ); + + const signer = await SigningHyperlaneModuleClient.connectWithSigner( + metadata.rpcUrls[0].http, + wallet, + { + gasPrice: GasPrice.fromString( + `${metadata.gasPrice?.amount}${metadata.gasPrice?.denom}`, + ), + }, + ); + + const { response } = await signer.createCollateralToken({ + origin_mailbox: mailbox, + origin_denom: metadata.nativeToken?.denom ?? '', + }); + + return response.id; +} + +export async function deploySyntheticToken( + mailbox: string, + privateKey: string, + chain: string, +): Promise
{ + const { multiProvider } = await getContext({ + registryUris: [REGISTRY_PATH], + key: privateKey, + }); + + const metadata = multiProvider.getChainMetadata(chain); + + const wallet = await DirectSecp256k1Wallet.fromKey( + Buffer.from(privateKey, 'hex'), + metadata.bech32Prefix, + ); + + const signer = await SigningHyperlaneModuleClient.connectWithSigner( + metadata.rpcUrls[0].http, + wallet, + { + gasPrice: GasPrice.fromString( + `${metadata.gasPrice?.amount}${metadata.gasPrice?.denom}`, + ), + }, + ); + + const { response } = await signer.createSyntheticToken({ + origin_mailbox: mailbox, + }); + + return response.id; +} diff --git a/typescript/cli/src/tests/cosmosnative/consts.ts b/typescript/cli/src/tests/cosmosnative/consts.ts index 8d062dc25d1..bd2f39a1bec 100644 --- a/typescript/cli/src/tests/cosmosnative/consts.ts +++ b/typescript/cli/src/tests/cosmosnative/consts.ts @@ -1,3 +1,5 @@ +import { createWarpRouteConfigId } from '@hyperlane-xyz/registry'; + export const E2E_TEST_CONFIGS_PATH = './test-configs'; export const REGISTRY_PATH = `${E2E_TEST_CONFIGS_PATH}/hyp`; export const TEMP_PATH = '/tmp'; // /temp gets removed at the end of all-test.sh @@ -16,6 +18,33 @@ export const CHAIN_2_METADATA_PATH = `${REGISTRY_PATH}/chains/${CHAIN_NAME_2}/me export const CHAIN_3_METADATA_PATH = `${REGISTRY_PATH}/chains/${CHAIN_NAME_3}/metadata.yaml`; export const CORE_CONFIG_PATH = `${EXAMPLES_PATH}/core-config.yaml`; + export const CORE_READ_CONFIG_PATH_1 = `${TEMP_PATH}/${CHAIN_NAME_1}/core-config-read.yaml`; +export const CORE_READ_CONFIG_PATH_2 = `${TEMP_PATH}/${CHAIN_NAME_2}/core-config-read.yaml`; +export const CORE_READ_CONFIG_PATH_3 = `${TEMP_PATH}/${CHAIN_NAME_3}/core-config-read.yaml`; export const DEFAULT_E2E_TEST_TIMEOUT = 100_000; // Long timeout since these tests can take a while + +export const WARP_CONFIG_PATH_EXAMPLE = `${EXAMPLES_PATH}/warp-route-deployment.yaml`; + +export const WARP_CONFIG_PATH_1 = `${TEMP_PATH}/${CHAIN_NAME_1}/warp-route-deployment-hyp1.yaml`; +export const WARP_CONFIG_PATH_2 = `${TEMP_PATH}/${CHAIN_NAME_2}/warp-route-deployment-hyp2.yaml`; +export const WARP_CONFIG_PATH_3 = `${TEMP_PATH}/${CHAIN_NAME_3}/warp-route-deployment-hyp3.yaml`; + +export const WARP_DEPLOY_DEFAULT_FILE_NAME = `warp-route-deployment`; +export const WARP_DEPLOY_OUTPUT_PATH = `${TEMP_PATH}/${WARP_DEPLOY_DEFAULT_FILE_NAME}.yaml`; + +export const WARP_DEPLOY_1_ID = 'TEST/hyp1'; +export const WARP_CORE_CONFIG_PATH_1 = getCombinedWarpRoutePath('TEST', [ + CHAIN_NAME_1, +]); + +export function getCombinedWarpRoutePath( + tokenSymbol: string, + chains: string[], +): string { + return `${REGISTRY_PATH}/deployments/warp_routes/${createWarpRouteConfigId( + tokenSymbol.toUpperCase(), + chains.sort().join('-'), + )}-config.yaml`; +} diff --git a/typescript/cli/src/tests/cosmosnative/warp/warp-deploy.e2e-test.ts b/typescript/cli/src/tests/cosmosnative/warp/warp-deploy.e2e-test.ts new file mode 100644 index 00000000000..7e50681c321 --- /dev/null +++ b/typescript/cli/src/tests/cosmosnative/warp/warp-deploy.e2e-test.ts @@ -0,0 +1,365 @@ +import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; +import * as chai from 'chai'; +import chaiAsPromised from 'chai-as-promised'; +import { fs } from 'zx'; + +import { ChainAddresses } from '@hyperlane-xyz/registry'; +import { + ChainName, + TokenType, + WarpRouteDeployConfig, +} from '@hyperlane-xyz/sdk'; +import { Address, ProtocolType } from '@hyperlane-xyz/utils'; + +import { writeYamlOrJson } from '../../../utils/files.js'; +import { HyperlaneE2ECoreTestCommands } from '../../commands/core.js'; +import { + KeyBoardKeys, + TestPromptAction, + handlePrompts, +} from '../../commands/helpers.js'; +import { HyperlaneE2EWarpTestCommands } from '../../commands/warp.js'; +import { GET_WARP_DEPLOY_CORE_CONFIG_OUTPUT_PATH } from '../commands/helpers.js'; +import { + CHAIN_NAME_1, + CHAIN_NAME_2, + CORE_CONFIG_PATH, + CORE_READ_CONFIG_PATH_1, + CORE_READ_CONFIG_PATH_2, + DEFAULT_E2E_TEST_TIMEOUT, + HYP_KEY, + REGISTRY_PATH, + WARP_CONFIG_PATH_1, + WARP_DEPLOY_OUTPUT_PATH, +} from '../consts.js'; + +chai.use(chaiAsPromised); +const expect = chai.expect; +chai.should(); + +describe('hyperlane warp deploy e2e tests', async function () { + this.timeout(DEFAULT_E2E_TEST_TIMEOUT); + + const hyperlaneCore1 = new HyperlaneE2ECoreTestCommands( + ProtocolType.CosmosNative, + CHAIN_NAME_1, + REGISTRY_PATH, + CORE_CONFIG_PATH, + CORE_READ_CONFIG_PATH_1, + ); + + const hyperlaneCore2 = new HyperlaneE2ECoreTestCommands( + ProtocolType.CosmosNative, + CHAIN_NAME_2, + REGISTRY_PATH, + CORE_CONFIG_PATH, + CORE_READ_CONFIG_PATH_2, + ); + + const hyperlaneWarp = new HyperlaneE2EWarpTestCommands( + ProtocolType.CosmosNative, + REGISTRY_PATH, + WARP_CONFIG_PATH_1, + ); + + let chain1Addresses: ChainAddresses = {}; + let chain2Addresses: ChainAddresses = {}; + + let ownerAddress: Address; + + before(async function () { + const wallet = await DirectSecp256k1Wallet.fromKey( + Buffer.from(HYP_KEY, 'hex'), + ); + const accounts = await wallet.getAccounts(); + ownerAddress = accounts[0].address; + + await hyperlaneCore1.deploy(HYP_KEY); + await hyperlaneCore2.deploy(HYP_KEY); + + chain1Addresses = await hyperlaneCore1.deployOrUseExistingCore(HYP_KEY); + chain2Addresses = await hyperlaneCore2.deployOrUseExistingCore(HYP_KEY); + }); + + async function assertWarpRouteConfig( + warpDeployConfig: Readonly, + warpCoreConfigPath: string, + chainName: ChainName, + ): Promise { + const currentWarpDeployConfig = await hyperlaneWarp.readConfig( + chainName, + warpCoreConfigPath, + ); + + expect(currentWarpDeployConfig[chainName].type).to.equal( + warpDeployConfig[chainName].type, + ); + expect(currentWarpDeployConfig[chainName].mailbox).to.equal( + chainName === CHAIN_NAME_1 + ? chain1Addresses.mailbox + : chain2Addresses.mailbox, + ); + } + + describe('hyperlane warp deploy --config ...', () => { + it(`should exit early when the provided deployment file does not exist`, async function () { + const nonExistingFilePath = 'non-existing-path'; + // Currently if the file provided in the config flag does not exist a prompt will still be shown to the + // user to enter a valid file and then it will finally fail + const steps: TestPromptAction[] = [ + { + check: (currentOutput: string) => + currentOutput.includes('Select Warp route deployment config file'), + input: `${KeyBoardKeys.ARROW_DOWN}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput: string) => + currentOutput.includes( + 'Enter Warp route deployment config filepath', + ), + input: `${nonExistingFilePath}${KeyBoardKeys.ENTER}`, + }, + ]; + + const output = hyperlaneWarp + .deployRaw({ + warpDeployPath: nonExistingFilePath, + }) + .stdio('pipe') + .nothrow(); + + const finalOutput = await handlePrompts(output, steps); + + expect(finalOutput.exitCode).to.equal(1); + expect(finalOutput.text()).to.include( + `Warp route deployment config file not found at ${nonExistingFilePath}`, + ); + }); + + it(`should successfully deploy a ${TokenType.collateral} -> ${TokenType.synthetic} warp route`, async function () { + const COMBINED_WARP_CORE_CONFIG_PATH = + GET_WARP_DEPLOY_CORE_CONFIG_OUTPUT_PATH( + WARP_DEPLOY_OUTPUT_PATH, + 'TEST', + ); + + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_1]: { + type: TokenType.collateral, + token: 'uhyp', + mailbox: chain1Addresses.mailbox, + owner: ownerAddress, + name: 'TEST', + symbol: 'TEST', + decimals: 6, + }, + [CHAIN_NAME_2]: { + type: TokenType.synthetic, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + name: 'TEST', + symbol: 'TEST', + decimals: 6, + }, + }; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + + const steps: TestPromptAction[] = [ + { + check: (currentOutput) => + currentOutput.includes('Please enter the private key for chain'), + input: `${HYP_KEY}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes('Please enter the private key for chain'), + input: `${HYP_KEY}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes('Is this deployment plan correct?'), + input: KeyBoardKeys.ENTER, + }, + ]; + + // Deploy + const output = hyperlaneWarp + .deployRaw({ warpDeployPath: WARP_DEPLOY_OUTPUT_PATH }) + .stdio('pipe') + .nothrow(); + + const finalOutput = await handlePrompts(output, steps); + + // Assertions + expect(finalOutput.exitCode).to.equal(0); + for (const chainName of [CHAIN_NAME_1, CHAIN_NAME_2]) { + await assertWarpRouteConfig( + warpConfig, + COMBINED_WARP_CORE_CONFIG_PATH, + chainName, + ); + } + }); + + it(`should successfully deploy a warp route with a custom warp route id`, async function () { + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_1]: { + type: TokenType.collateral, + token: 'uhyp', + mailbox: chain1Addresses.mailbox, + owner: ownerAddress, + name: 'TEST', + symbol: 'TEST', + decimals: 6, + }, + [CHAIN_NAME_2]: { + type: TokenType.synthetic, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + name: 'TEST', + symbol: 'TEST', + decimals: 6, + }, + }; + const warpRouteId = 'TEST/custom-warp-route-id'; + const warpDeployPath = `${REGISTRY_PATH}/deployments/warp_routes/${warpRouteId}-deploy.yaml`; + writeYamlOrJson(warpDeployPath, warpConfig); + + const steps: TestPromptAction[] = [ + { + check: (currentOutput) => + currentOutput.includes('Please enter the private key for chain'), + input: `${HYP_KEY}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes('Please enter the private key for chain'), + input: `${HYP_KEY}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes('Is this deployment plan correct?'), + input: KeyBoardKeys.ENTER, + }, + ]; + + // Deploy + const output = hyperlaneWarp + .deployRaw({ + warpRouteId, + }) + .stdio('pipe') + .nothrow(); + + const finalOutput = await handlePrompts(output, steps); + + // Assertions + expect(finalOutput.exitCode).to.equal(0); + + const warpCorePath = `${REGISTRY_PATH}/deployments/warp_routes/${warpRouteId}-config.yaml`; + expect(fs.existsSync(warpCorePath)).to.be.true; + }); + }); + + describe('hyperlane warp deploy --config ... --yes', () => { + it(`should exit early when the provided deployment file does not exist and the skip flag is provided`, async function () { + const nonExistingFilePath = 'non-existing-path'; + // Currently if the file provided in the config flag does not exist a prompt will still be shown to the + // user to enter a valid file and then it will finally fail + const steps: TestPromptAction[] = [ + { + check: (currentOutput: string) => + currentOutput.includes('Select Warp route deployment config file'), + input: `${KeyBoardKeys.ARROW_DOWN}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput: string) => + currentOutput.includes( + 'Enter Warp route deployment config filepath', + ), + input: `${nonExistingFilePath}${KeyBoardKeys.ENTER}`, + }, + ]; + + const output = hyperlaneWarp + .deployRaw({ + warpDeployPath: nonExistingFilePath, + skipConfirmationPrompts: true, + }) + .stdio('pipe') + .nothrow(); + + const finalOutput = await handlePrompts(output, steps); + + expect(finalOutput.exitCode).to.equal(1); + expect(finalOutput.text()).to.include( + `Warp route deployment config file not found at ${nonExistingFilePath}`, + ); + }); + + it(`should successfully deploy a ${TokenType.collateral} -> ${TokenType.synthetic} warp route`, async function () { + const COMBINED_WARP_CORE_CONFIG_PATH = + GET_WARP_DEPLOY_CORE_CONFIG_OUTPUT_PATH( + WARP_DEPLOY_OUTPUT_PATH, + 'TEST', + ); + + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_1]: { + type: TokenType.collateral, + token: 'uhyp', + mailbox: chain1Addresses.mailbox, + owner: ownerAddress, + name: 'TEST', + symbol: 'TEST', + decimals: 6, + }, + [CHAIN_NAME_2]: { + type: TokenType.synthetic, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + name: 'TEST', + symbol: 'TEST', + decimals: 6, + }, + }; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + + const steps: TestPromptAction[] = [ + { + check: (currentOutput) => + currentOutput.includes('Please enter the private key for chain'), + input: `${HYP_KEY}${KeyBoardKeys.ENTER}`, + }, + { + check: (currentOutput) => + currentOutput.includes('Please enter the private key for chain'), + input: `${HYP_KEY}${KeyBoardKeys.ENTER}`, + }, + ]; + + // Deploy + const output = hyperlaneWarp + .deployRaw({ + warpDeployPath: WARP_DEPLOY_OUTPUT_PATH, + skipConfirmationPrompts: true, + }) + .stdio('pipe') + .nothrow(); + + const finalOutput = await handlePrompts(output, steps); + + // Assertions + expect(finalOutput.exitCode).to.equal(0); + for (const chainName of [CHAIN_NAME_1, CHAIN_NAME_2]) { + await assertWarpRouteConfig( + warpConfig, + COMBINED_WARP_CORE_CONFIG_PATH, + chainName, + ); + } + }); + }); +}); diff --git a/typescript/cli/src/tests/cosmosnative/warp/warp-read.e2e-test.ts b/typescript/cli/src/tests/cosmosnative/warp/warp-read.e2e-test.ts new file mode 100644 index 00000000000..8032c3aaa56 --- /dev/null +++ b/typescript/cli/src/tests/cosmosnative/warp/warp-read.e2e-test.ts @@ -0,0 +1,187 @@ +import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; +import { expect } from 'chai'; + +import { ChainAddresses } from '@hyperlane-xyz/registry'; +import { TokenType, WarpRouteDeployConfig } from '@hyperlane-xyz/sdk'; +import { Address, ProtocolType } from '@hyperlane-xyz/utils'; + +import { readYamlOrJson, writeYamlOrJson } from '../../../utils/files.js'; +import { HyperlaneE2ECoreTestCommands } from '../../commands/core.js'; +import { + KeyBoardKeys, + TestPromptAction, + handlePrompts, +} from '../../commands/helpers.js'; +import { HyperlaneE2EWarpTestCommands } from '../../commands/warp.js'; +import { + CHAIN_NAME_1, + CHAIN_NAME_2, + CORE_CONFIG_PATH, + CORE_READ_CONFIG_PATH_1, + CORE_READ_CONFIG_PATH_2, + DEFAULT_E2E_TEST_TIMEOUT, + HYP_KEY, + REGISTRY_PATH, + TEMP_PATH, + WARP_CONFIG_PATH_1, + WARP_CORE_CONFIG_PATH_1, + WARP_DEPLOY_1_ID, + WARP_DEPLOY_OUTPUT_PATH, +} from '../consts.js'; + +describe('hyperlane warp read e2e tests', async function () { + this.timeout(DEFAULT_E2E_TEST_TIMEOUT); + + const hyperlaneCore1 = new HyperlaneE2ECoreTestCommands( + ProtocolType.CosmosNative, + CHAIN_NAME_1, + REGISTRY_PATH, + CORE_CONFIG_PATH, + CORE_READ_CONFIG_PATH_1, + ); + + const hyperlaneCore2 = new HyperlaneE2ECoreTestCommands( + ProtocolType.CosmosNative, + CHAIN_NAME_2, + REGISTRY_PATH, + CORE_CONFIG_PATH, + CORE_READ_CONFIG_PATH_2, + ); + + const hyperlaneWarp = new HyperlaneE2EWarpTestCommands( + ProtocolType.CosmosNative, + REGISTRY_PATH, + WARP_CONFIG_PATH_1, + ); + + let chain1Addresses: ChainAddresses = {}; + let chain2Addresses: ChainAddresses = {}; + + let ownerAddress: Address; + + let warpConfig: WarpRouteDeployConfig = {}; + + before(async function () { + const wallet = await DirectSecp256k1Wallet.fromKey( + Buffer.from(HYP_KEY, 'hex'), + ); + const accounts = await wallet.getAccounts(); + ownerAddress = accounts[0].address; + + await hyperlaneCore1.deploy(HYP_KEY); + await hyperlaneCore2.deploy(HYP_KEY); + + chain1Addresses = await hyperlaneCore1.deployOrUseExistingCore(HYP_KEY); + chain2Addresses = await hyperlaneCore2.deployOrUseExistingCore(HYP_KEY); + + warpConfig = { + [CHAIN_NAME_1]: { + type: TokenType.collateral, + token: 'uhyp', + mailbox: chain1Addresses.mailbox, + owner: ownerAddress, + name: 'TEST', + symbol: 'TEST', + decimals: 6, + }, + [CHAIN_NAME_2]: { + type: TokenType.synthetic, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + name: 'TEST', + symbol: 'TEST', + decimals: 6, + }, + }; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + }); + + describe('hyperlane warp read --config ...', () => { + it('should exit early if no symbol or no chain and address', async () => { + await hyperlaneWarp.deploy(WARP_DEPLOY_OUTPUT_PATH, HYP_KEY); + + const output = await hyperlaneWarp.readRaw({}).nothrow(); + + expect(output.exitCode).to.equal(1); + expect(output.text()).to.include( + 'Invalid input parameters. Please provide either a token symbol or both chain name and token address', + ); + }); + }); + + describe('hyperlane warp read --symbol ...', () => { + it('should successfully read the complete warp route config from all the chains', async () => { + const readOutputPath = `${TEMP_PATH}/warp-read-all-chain-with-symbol.yaml`; + + const warpConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_1]: { + type: TokenType.collateral, + token: 'uhyp', + mailbox: chain1Addresses.mailbox, + owner: ownerAddress, + name: 'TEST', + symbol: 'TEST', + decimals: 6, + }, + [CHAIN_NAME_2]: { + type: TokenType.synthetic, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + name: 'TEST', + symbol: 'TEST', + decimals: 6, + }, + }; + + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + await hyperlaneWarp.deploy(WARP_DEPLOY_OUTPUT_PATH, HYP_KEY); + + const steps: TestPromptAction[] = [ + // Select the hyp1-hyp2 HYP route from the selection prompt + { + check: (currentOutput: string) => + currentOutput.includes('Select from matching warp routes'), + input: KeyBoardKeys.ENTER, + }, + ]; + + const output = hyperlaneWarp + .readRaw({ + symbol: 'TEST', + outputPath: readOutputPath, + }) + .stdio('pipe') + .nothrow(); + + const finalOutput = await handlePrompts(output, steps); + + expect(finalOutput.exitCode).to.equal(0); + + const warpReadResult: WarpRouteDeployConfig = + readYamlOrJson(readOutputPath); + expect(warpReadResult[CHAIN_NAME_1]).not.to.be.undefined; + expect(warpReadResult[CHAIN_NAME_1].type).to.equal(TokenType.collateral); + + expect(warpReadResult[CHAIN_NAME_2]).not.to.be.undefined; + expect(warpReadResult[CHAIN_NAME_2].type).to.equal(TokenType.synthetic); + }); + }); + + describe('hyperlane warp read --chain ... --config ...', () => { + it('should be able to read a warp route', async function () { + await hyperlaneWarp.deploy( + WARP_DEPLOY_OUTPUT_PATH, + HYP_KEY, + WARP_DEPLOY_1_ID, + ); + + const warpReadResult: WarpRouteDeployConfig = + await hyperlaneWarp.readConfig(CHAIN_NAME_1, WARP_CORE_CONFIG_PATH_1); + + expect(warpReadResult[CHAIN_NAME_1].type).to.be.equal( + warpConfig[CHAIN_NAME_1].type, + ); + }); + }); +}); diff --git a/typescript/cli/src/tests/ethereum/commands/warp.ts b/typescript/cli/src/tests/ethereum/commands/warp.ts index 464f427af6d..11cc390fddf 100644 --- a/typescript/cli/src/tests/ethereum/commands/warp.ts +++ b/typescript/cli/src/tests/ethereum/commands/warp.ts @@ -5,19 +5,23 @@ import { ChainAddresses } from '@hyperlane-xyz/registry'; import { ChainName, HypTokenRouterConfig, + MultiProtocolProvider, TokenType, WarpCoreConfig, WarpRouteDeployConfig, WarpRouteDeployConfigMailboxRequired, WarpRouteDeployConfigSchema, } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; +import { Address, ProtocolType } from '@hyperlane-xyz/utils'; +import { readChainSubmissionStrategyConfig } from '../../../config/strategy.js'; import { getContext } from '../../../context/context.js'; +import { MultiProtocolSignerManager } from '../../../context/strategies/signer/MultiProtocolSignerManager.js'; import { CommandContext } from '../../../context/types.js'; import { extendWarpRoute as extendWarpRouteWithoutApplyTransactions } from '../../../deploy/warp.js'; import { readYamlOrJson, writeYamlOrJson } from '../../../utils/files.js'; import { + ANVIL_DEPLOYER_ADDRESS, ANVIL_KEY, CHAIN_NAME_2, CHAIN_NAME_3, @@ -255,8 +259,12 @@ export function hyperlaneWarpRebalancer( destination?: string, amount?: string, key?: string, + explorerUrl?: string, ): ProcessPromise { - return $`${localTestRunCmdPrefix()} hyperlane warp rebalancer \ + const rebalancerAddress = key + ? new Wallet(key).address + : ANVIL_DEPLOYER_ADDRESS; + return $`${explorerUrl ? [`EXPLORER_API_URL=${explorerUrl}`] : []} REBALANCER=${rebalancerAddress} ${localTestRunCmdPrefix()} hyperlane warp rebalancer \ --registry ${REGISTRY_PATH} \ --checkFrequency ${checkFrequency} \ --config ${config} \ @@ -571,12 +579,35 @@ export async function setupIncompleteWarpRouteExtension( context.multiProvider.setSigner(CHAIN_NAME_2, signer2); context.multiProvider.setSigner(CHAIN_NAME_3, signer3); + const strategyConfig = context.strategyPath + ? await readChainSubmissionStrategyConfig(context.strategyPath) + : {}; + + const multiProtocolProvider = new MultiProtocolProvider( + context.chainMetadata, + ); + const multiProtocolSigner = await MultiProtocolSignerManager.init( + strategyConfig, + [CHAIN_NAME_2, CHAIN_NAME_3], + multiProtocolProvider, + { + key: { + [ProtocolType.Ethereum]: ANVIL_KEY, + }, + }, + ); + + context.multiProvider = await multiProtocolSigner.getMultiProvider(); + await extendWarpRouteWithoutApplyTransactions( { context: { ...context, signer: signer3, - key: ANVIL_KEY, + key: { + [ProtocolType.Ethereum]: ANVIL_KEY, + }, + multiProtocolSigner, }, warpCoreConfig, warpDeployConfig, diff --git a/typescript/cli/src/tests/ethereum/run-e2e-test.sh b/typescript/cli/src/tests/ethereum/run-e2e-test.sh index 1072c588a46..74af1368f6f 100755 --- a/typescript/cli/src/tests/ethereum/run-e2e-test.sh +++ b/typescript/cli/src/tests/ethereum/run-e2e-test.sh @@ -7,6 +7,7 @@ function cleanup() { rm -rf ./test-configs/anvil/deployments rm -f ./test-configs/anvil/chains/anvil2/addresses.yaml rm -f ./test-configs/anvil/chains/anvil3/addresses.yaml + rm -f ./test-configs/anvil/chains/anvil4/addresses.yaml set -e } diff --git a/typescript/cli/src/tests/ethereum/warp/warp-apply-1.e2e-test.ts b/typescript/cli/src/tests/ethereum/warp/warp-apply-1.e2e-test.ts new file mode 100644 index 00000000000..db0007e3263 --- /dev/null +++ b/typescript/cli/src/tests/ethereum/warp/warp-apply-1.e2e-test.ts @@ -0,0 +1,209 @@ +import { expect } from 'chai'; + +import { + HookType, + WarpRouteDeployConfig, + normalizeConfig, + randomAddress, +} from '@hyperlane-xyz/sdk'; + +import { readYamlOrJson, writeYamlOrJson } from '../../../utils/files.js'; +import { deployOrUseExistingCore } from '../commands/core.js'; +import { + hyperlaneWarpApply, + hyperlaneWarpDeploy, + readWarpConfig, + updateOwner, +} from '../commands/warp.js'; +import { + ANVIL_DEPLOYER_ADDRESS, + ANVIL_KEY, + CHAIN_NAME_2, + CORE_CONFIG_PATH, + DEFAULT_E2E_TEST_TIMEOUT, + E2E_TEST_BURN_ADDRESS, + TEMP_PATH, + WARP_CONFIG_PATH_2, + WARP_CONFIG_PATH_EXAMPLE, + WARP_CORE_CONFIG_PATH_2, + WARP_DEPLOY_2_ID, +} from '../consts.js'; + +describe('hyperlane warp apply owner update tests', async function () { + this.timeout(2 * DEFAULT_E2E_TEST_TIMEOUT); + + before(async function () { + await deployOrUseExistingCore(CHAIN_NAME_2, CORE_CONFIG_PATH, ANVIL_KEY); + + // Create a new warp config using the example + const warpConfig: WarpRouteDeployConfig = readYamlOrJson( + WARP_CONFIG_PATH_EXAMPLE, + ); + const anvil2Config = { anvil2: { ...warpConfig.anvil1 } }; + writeYamlOrJson(WARP_CONFIG_PATH_2, anvil2Config); + }); + + beforeEach(async function () { + await hyperlaneWarpDeploy(WARP_CONFIG_PATH_2, WARP_DEPLOY_2_ID); + }); + + it('should burn owner address', async function () { + const warpConfigPath = `${TEMP_PATH}/warp-route-deployment-2.yaml`; + await updateOwner( + E2E_TEST_BURN_ADDRESS, + CHAIN_NAME_2, + warpConfigPath, + WARP_CORE_CONFIG_PATH_2, + ); + const updatedWarpDeployConfig = await readWarpConfig( + CHAIN_NAME_2, + WARP_CORE_CONFIG_PATH_2, + warpConfigPath, + ); + expect(updatedWarpDeployConfig.anvil2.owner).to.equal( + E2E_TEST_BURN_ADDRESS, + ); + }); + + it('should not update the same owner', async () => { + const warpConfigPath = `${TEMP_PATH}/warp-route-deployment-2.yaml`; + await updateOwner( + E2E_TEST_BURN_ADDRESS, + CHAIN_NAME_2, + warpConfigPath, + WARP_CORE_CONFIG_PATH_2, + ); + const { stdout } = await updateOwner( + E2E_TEST_BURN_ADDRESS, + CHAIN_NAME_2, + warpConfigPath, + WARP_CORE_CONFIG_PATH_2, + ); + expect(stdout).to.include( + 'Warp config is the same as target. No updates needed.', + ); + }); + + it('should update the owner of both the warp token and the proxy admin', async () => { + const warpConfigPath = `${TEMP_PATH}/warp-route-deploy-config-2.yaml`; + + const warpConfig: WarpRouteDeployConfig = readYamlOrJson( + WARP_CONFIG_PATH_EXAMPLE, + ); + + // Set to undefined if it was defined in the config + warpConfig.anvil1.proxyAdmin = undefined; + warpConfig.anvil1.owner = E2E_TEST_BURN_ADDRESS; + const anvil2Config = { anvil2: { ...warpConfig.anvil1 } }; + writeYamlOrJson(warpConfigPath, anvil2Config); + + await hyperlaneWarpApply( + warpConfigPath, + WARP_CORE_CONFIG_PATH_2, + undefined, + WARP_DEPLOY_2_ID, + ); + + const updatedWarpDeployConfig1 = await readWarpConfig( + CHAIN_NAME_2, + WARP_CORE_CONFIG_PATH_2, + warpConfigPath, + ); + + expect(updatedWarpDeployConfig1.anvil2.owner).to.eq(E2E_TEST_BURN_ADDRESS); + expect(updatedWarpDeployConfig1.anvil2.proxyAdmin?.owner).to.eq( + E2E_TEST_BURN_ADDRESS, + ); + }); + + it('should update only the owner of the warp token if the proxy admin config is specified', async () => { + const warpConfigPath = `${TEMP_PATH}/warp-route-deploy-config-2.yaml`; + + const warpConfig: WarpRouteDeployConfig = readYamlOrJson( + WARP_CONFIG_PATH_EXAMPLE, + ); + + // Explicitly set it to the deployer address if it was not defined + warpConfig.anvil1.proxyAdmin = { owner: ANVIL_DEPLOYER_ADDRESS }; + warpConfig.anvil1.owner = E2E_TEST_BURN_ADDRESS; + const anvil2Config = { anvil2: { ...warpConfig.anvil1 } }; + writeYamlOrJson(warpConfigPath, anvil2Config); + + await hyperlaneWarpApply(warpConfigPath, WARP_CORE_CONFIG_PATH_2); + + const updatedWarpDeployConfig1 = await readWarpConfig( + CHAIN_NAME_2, + WARP_CORE_CONFIG_PATH_2, + warpConfigPath, + ); + + expect(updatedWarpDeployConfig1.anvil2.owner).to.eq(E2E_TEST_BURN_ADDRESS); + expect(updatedWarpDeployConfig1.anvil2.proxyAdmin?.owner).to.eq( + ANVIL_DEPLOYER_ADDRESS, + ); + }); + + it('should update only the owner of the proxy admin if the proxy admin config is specified', async () => { + const warpConfigPath = `${TEMP_PATH}/warp-route-deploy-config-2.yaml`; + + const warpConfig: WarpRouteDeployConfig = readYamlOrJson( + WARP_CONFIG_PATH_EXAMPLE, + ); + + warpConfig.anvil1.proxyAdmin = { owner: E2E_TEST_BURN_ADDRESS }; + const anvil2Config = { anvil2: { ...warpConfig.anvil1 } }; + writeYamlOrJson(warpConfigPath, anvil2Config); + + await hyperlaneWarpApply(warpConfigPath, WARP_CORE_CONFIG_PATH_2); + + const updatedWarpDeployConfig1 = await readWarpConfig( + CHAIN_NAME_2, + WARP_CORE_CONFIG_PATH_2, + warpConfigPath, + ); + + expect(updatedWarpDeployConfig1.anvil2.owner).to.eq(ANVIL_DEPLOYER_ADDRESS); + expect(updatedWarpDeployConfig1.anvil2.proxyAdmin?.owner).to.eq( + E2E_TEST_BURN_ADDRESS, + ); + }); + + it('should update hook configuration', async () => { + const warpDeployPath = `${TEMP_PATH}/warp-route-deployment-2.yaml`; + + // First read the existing config + const warpDeployConfig = await readWarpConfig( + CHAIN_NAME_2, + WARP_CORE_CONFIG_PATH_2, + warpDeployPath, + ); + + // Update with a new hook config + const owner = randomAddress(); + warpDeployConfig[CHAIN_NAME_2].hook = { + type: HookType.PROTOCOL_FEE, + beneficiary: owner, + maxProtocolFee: '1000000', + protocolFee: '100000', + owner, + }; + + // Write the updated config + await writeYamlOrJson(warpDeployPath, warpDeployConfig); + + // Apply the changes + await hyperlaneWarpApply(warpDeployPath, WARP_CORE_CONFIG_PATH_2); + + // Read back the config to verify changes + const updatedConfig = await readWarpConfig( + CHAIN_NAME_2, + WARP_CORE_CONFIG_PATH_2, + warpDeployPath, + ); + + // Verify the hook was updated with all properties + expect(normalizeConfig(updatedConfig[CHAIN_NAME_2].hook)).to.deep.equal( + normalizeConfig(warpDeployConfig[CHAIN_NAME_2].hook), + ); + }); +}); diff --git a/typescript/cli/src/tests/ethereum/warp/warp-apply.e2e-test.ts b/typescript/cli/src/tests/ethereum/warp/warp-apply-2.e2e-test.ts similarity index 65% rename from typescript/cli/src/tests/ethereum/warp/warp-apply.e2e-test.ts rename to typescript/cli/src/tests/ethereum/warp/warp-apply-2.e2e-test.ts index 87cdbbb0520..cf243bc8e77 100644 --- a/typescript/cli/src/tests/ethereum/warp/warp-apply.e2e-test.ts +++ b/typescript/cli/src/tests/ethereum/warp/warp-apply-2.e2e-test.ts @@ -4,13 +4,11 @@ import { Wallet, ethers } from 'ethers'; import { ChainAddresses } from '@hyperlane-xyz/registry'; import { ChainMetadata, - HookType, HypTokenRouterConfig, HypTokenRouterConfigMailboxOptionalSchema, TokenType, WarpCoreConfig, WarpRouteDeployConfig, - normalizeConfig, randomAddress, } from '@hyperlane-xyz/sdk'; import { @@ -30,7 +28,6 @@ import { hyperlaneWarpApplyRaw, hyperlaneWarpDeploy, readWarpConfig, - updateOwner, } from '../commands/warp.js'; import { ANVIL_DEPLOYER_ADDRESS, @@ -75,166 +72,6 @@ describe('hyperlane warp apply owner update tests', async function () { await hyperlaneWarpDeploy(WARP_CONFIG_PATH_2, WARP_DEPLOY_2_ID); }); - it('should burn owner address', async function () { - const warpConfigPath = `${TEMP_PATH}/warp-route-deployment-2.yaml`; - await updateOwner( - E2E_TEST_BURN_ADDRESS, - CHAIN_NAME_2, - warpConfigPath, - WARP_CORE_CONFIG_PATH_2, - ); - const updatedWarpDeployConfig = await readWarpConfig( - CHAIN_NAME_2, - WARP_CORE_CONFIG_PATH_2, - warpConfigPath, - ); - expect(updatedWarpDeployConfig.anvil2.owner).to.equal( - E2E_TEST_BURN_ADDRESS, - ); - }); - - it('should not update the same owner', async () => { - const warpConfigPath = `${TEMP_PATH}/warp-route-deployment-2.yaml`; - await updateOwner( - E2E_TEST_BURN_ADDRESS, - CHAIN_NAME_2, - warpConfigPath, - WARP_CORE_CONFIG_PATH_2, - ); - const { stdout } = await updateOwner( - E2E_TEST_BURN_ADDRESS, - CHAIN_NAME_2, - warpConfigPath, - WARP_CORE_CONFIG_PATH_2, - ); - expect(stdout).to.include( - 'Warp config is the same as target. No updates needed.', - ); - }); - - it('should update the owner of both the warp token and the proxy admin', async () => { - const warpConfigPath = `${TEMP_PATH}/warp-route-deploy-config-2.yaml`; - - const warpConfig: WarpRouteDeployConfig = readYamlOrJson( - WARP_CONFIG_PATH_EXAMPLE, - ); - - // Set to undefined if it was defined in the config - warpConfig.anvil1.proxyAdmin = undefined; - warpConfig.anvil1.owner = E2E_TEST_BURN_ADDRESS; - const anvil2Config = { anvil2: { ...warpConfig.anvil1 } }; - writeYamlOrJson(warpConfigPath, anvil2Config); - - await hyperlaneWarpApply( - warpConfigPath, - WARP_CORE_CONFIG_PATH_2, - undefined, - WARP_DEPLOY_2_ID, - ); - - const updatedWarpDeployConfig1 = await readWarpConfig( - CHAIN_NAME_2, - WARP_CORE_CONFIG_PATH_2, - warpConfigPath, - ); - - expect(updatedWarpDeployConfig1.anvil2.owner).to.eq(E2E_TEST_BURN_ADDRESS); - expect(updatedWarpDeployConfig1.anvil2.proxyAdmin?.owner).to.eq( - E2E_TEST_BURN_ADDRESS, - ); - }); - - it('should update only the owner of the warp token if the proxy admin config is specified', async () => { - const warpConfigPath = `${TEMP_PATH}/warp-route-deploy-config-2.yaml`; - - const warpConfig: WarpRouteDeployConfig = readYamlOrJson( - WARP_CONFIG_PATH_EXAMPLE, - ); - - // Explicitly set it to the deployer address if it was not defined - warpConfig.anvil1.proxyAdmin = { owner: ANVIL_DEPLOYER_ADDRESS }; - warpConfig.anvil1.owner = E2E_TEST_BURN_ADDRESS; - const anvil2Config = { anvil2: { ...warpConfig.anvil1 } }; - writeYamlOrJson(warpConfigPath, anvil2Config); - - await hyperlaneWarpApply(warpConfigPath, WARP_CORE_CONFIG_PATH_2); - - const updatedWarpDeployConfig1 = await readWarpConfig( - CHAIN_NAME_2, - WARP_CORE_CONFIG_PATH_2, - warpConfigPath, - ); - - expect(updatedWarpDeployConfig1.anvil2.owner).to.eq(E2E_TEST_BURN_ADDRESS); - expect(updatedWarpDeployConfig1.anvil2.proxyAdmin?.owner).to.eq( - ANVIL_DEPLOYER_ADDRESS, - ); - }); - - it('should update only the owner of the proxy admin if the proxy admin config is specified', async () => { - const warpConfigPath = `${TEMP_PATH}/warp-route-deploy-config-2.yaml`; - - const warpConfig: WarpRouteDeployConfig = readYamlOrJson( - WARP_CONFIG_PATH_EXAMPLE, - ); - - warpConfig.anvil1.proxyAdmin = { owner: E2E_TEST_BURN_ADDRESS }; - const anvil2Config = { anvil2: { ...warpConfig.anvil1 } }; - writeYamlOrJson(warpConfigPath, anvil2Config); - - await hyperlaneWarpApply(warpConfigPath, WARP_CORE_CONFIG_PATH_2); - - const updatedWarpDeployConfig1 = await readWarpConfig( - CHAIN_NAME_2, - WARP_CORE_CONFIG_PATH_2, - warpConfigPath, - ); - - expect(updatedWarpDeployConfig1.anvil2.owner).to.eq(ANVIL_DEPLOYER_ADDRESS); - expect(updatedWarpDeployConfig1.anvil2.proxyAdmin?.owner).to.eq( - E2E_TEST_BURN_ADDRESS, - ); - }); - - it('should update hook configuration', async () => { - const warpDeployPath = `${TEMP_PATH}/warp-route-deployment-2.yaml`; - - // First read the existing config - const warpDeployConfig = await readWarpConfig( - CHAIN_NAME_2, - WARP_CORE_CONFIG_PATH_2, - warpDeployPath, - ); - - // Update with a new hook config - const owner = randomAddress(); - warpDeployConfig[CHAIN_NAME_2].hook = { - type: HookType.PROTOCOL_FEE, - beneficiary: owner, - maxProtocolFee: '1000000', - protocolFee: '100000', - owner, - }; - - // Write the updated config - await writeYamlOrJson(warpDeployPath, warpDeployConfig); - - // Apply the changes - await hyperlaneWarpApply(warpDeployPath, WARP_CORE_CONFIG_PATH_2); - - // Read back the config to verify changes - const updatedConfig = await readWarpConfig( - CHAIN_NAME_2, - WARP_CORE_CONFIG_PATH_2, - warpDeployPath, - ); - - // Verify the hook was updated with all properties - expect(normalizeConfig(updatedConfig[CHAIN_NAME_2].hook)).to.deep.equal( - normalizeConfig(warpDeployConfig[CHAIN_NAME_2].hook), - ); - }); - it('should extend a warp route with a custom warp route id', async () => { // Read existing config const warpConfigPath = `${TEMP_PATH}/warp-route-deployment-2.yaml`; diff --git a/typescript/cli/src/tests/ethereum/warp/warp-check-1.e2e-test.ts b/typescript/cli/src/tests/ethereum/warp/warp-check-1.e2e-test.ts new file mode 100644 index 00000000000..cdc2d0e3100 --- /dev/null +++ b/typescript/cli/src/tests/ethereum/warp/warp-check-1.e2e-test.ts @@ -0,0 +1,173 @@ +import { expect } from 'chai'; +import { Wallet } from 'ethers'; + +import { ERC20Test } from '@hyperlane-xyz/core'; +import { + ChainAddresses, + createWarpRouteConfigId, +} from '@hyperlane-xyz/registry'; +import { + HookConfig, + IsmConfig, + IsmType, + MUTABLE_HOOK_TYPE, + MUTABLE_ISM_TYPE, + TokenType, + WarpRouteDeployConfig, + randomAddress, + randomHookConfig, + randomIsmConfig, +} from '@hyperlane-xyz/sdk'; +import { Address, assert, deepCopy } from '@hyperlane-xyz/utils'; + +import { writeYamlOrJson } from '../../../utils/files.js'; +import { deployOrUseExistingCore } from '../commands/core.js'; +import { deployToken } from '../commands/helpers.js'; +import { + hyperlaneWarpCheckRaw, + hyperlaneWarpDeploy, +} from '../commands/warp.js'; +import { + ANVIL_KEY, + CHAIN_NAME_2, + CHAIN_NAME_3, + CORE_CONFIG_PATH, + DEFAULT_E2E_TEST_TIMEOUT, + WARP_DEPLOY_OUTPUT_PATH, + getCombinedWarpRoutePath, +} from '../consts.js'; + +describe('hyperlane warp check e2e tests', async function () { + this.timeout(2 * DEFAULT_E2E_TEST_TIMEOUT); + + let chain2Addresses: ChainAddresses = {}; + let chain3Addresses: ChainAddresses = {}; + let token: ERC20Test; + let tokenSymbol: string; + let ownerAddress: Address; + let combinedWarpCoreConfigPath: string; + let warpConfig: WarpRouteDeployConfig; + + before(async function () { + [chain2Addresses, chain3Addresses] = await Promise.all([ + deployOrUseExistingCore(CHAIN_NAME_2, CORE_CONFIG_PATH, ANVIL_KEY), + deployOrUseExistingCore(CHAIN_NAME_3, CORE_CONFIG_PATH, ANVIL_KEY), + ]); + + token = await deployToken(ANVIL_KEY, CHAIN_NAME_2); + tokenSymbol = await token.symbol(); + + combinedWarpCoreConfigPath = getCombinedWarpRoutePath(tokenSymbol, [ + CHAIN_NAME_3, + ]); + }); + + async function deployAndExportWarpRoute(): Promise { + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + // currently warp deploy is not writing the deploy config to the registry + // should remove this once the deploy config is written to the registry + writeYamlOrJson( + combinedWarpCoreConfigPath.replace('-config.yaml', '-deploy.yaml'), + warpConfig, + ); + + const currentWarpId = createWarpRouteConfigId( + await token.symbol(), + CHAIN_NAME_3, + ); + + await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH, currentWarpId); + + return warpConfig; + } + + // Reset config before each test to avoid test changes intertwining + beforeEach(async function () { + ownerAddress = new Wallet(ANVIL_KEY).address; + warpConfig = { + [CHAIN_NAME_2]: { + type: TokenType.collateral, + token: token.address, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + }, + [CHAIN_NAME_3]: { + type: TokenType.synthetic, + mailbox: chain3Addresses.mailbox, + owner: ownerAddress, + }, + }; + }); + + for (const hookType of MUTABLE_HOOK_TYPE) { + it(`should find owner differences between the local config and the on chain config for hook of type ${hookType}`, async function () { + warpConfig[CHAIN_NAME_3].hook = randomHookConfig(0, 2, hookType); + await deployAndExportWarpRoute(); + + const mutatedWarpConfig = deepCopy(warpConfig); + + const hookConfig: Extract< + HookConfig, + { type: (typeof MUTABLE_HOOK_TYPE)[number]; owner: string } + > = mutatedWarpConfig[CHAIN_NAME_3].hook!; + const actualOwner = hookConfig.owner; + const wrongOwner = randomAddress(); + assert(actualOwner !== wrongOwner, 'Random owner matches actualOwner'); + hookConfig.owner = wrongOwner; + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, mutatedWarpConfig); + + const expectedDiffText = `EXPECTED: "${wrongOwner.toLowerCase()}"\n`; + const expectedActualText = `ACTUAL: "${actualOwner.toLowerCase()}"\n`; + + const output = await hyperlaneWarpCheckRaw({ + warpDeployPath: WARP_DEPLOY_OUTPUT_PATH, + warpCoreConfigPath: combinedWarpCoreConfigPath, + }).nothrow(); + expect(output.exitCode).to.equal(1); + expect(output.text().includes(expectedDiffText)).to.be.true; + expect(output.text().includes(expectedActualText)).to.be.true; + }); + } + + // Removing the offchain lookup ism because it is a family of different isms + for (const ismType of MUTABLE_ISM_TYPE.filter( + (ismType) => ismType !== IsmType.OFFCHAIN_LOOKUP, + )) { + it(`should find owner differences between the local config and the on chain config for ism of type ${ismType}`, async function () { + // Create a Pausable because randomIsmConfig() cannot generate it (reason: NULL type Isms) + warpConfig[CHAIN_NAME_3].interchainSecurityModule = + ismType === IsmType.PAUSABLE + ? { + type: IsmType.PAUSABLE, + owner: randomAddress(), + paused: true, + } + : randomIsmConfig(0, 2, ismType); + await deployAndExportWarpRoute(); + + const mutatedWarpConfig = deepCopy(warpConfig); + + const ismConfig: Extract< + IsmConfig, + { type: (typeof MUTABLE_ISM_TYPE)[number]; owner: string } + > = mutatedWarpConfig[CHAIN_NAME_3].interchainSecurityModule; + const actualOwner = ismConfig.owner; + const wrongOwner = randomAddress(); + assert(actualOwner !== wrongOwner, 'Random owner matches actualOwner'); + ismConfig.owner = wrongOwner; + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, mutatedWarpConfig); + + const expectedDiffText = `EXPECTED: "${wrongOwner.toLowerCase()}"\n`; + const expectedActualText = `ACTUAL: "${actualOwner.toLowerCase()}"\n`; + + const output = await hyperlaneWarpCheckRaw({ + warpDeployPath: WARP_DEPLOY_OUTPUT_PATH, + warpCoreConfigPath: combinedWarpCoreConfigPath, + }).nothrow(); + + expect(output.exitCode).to.equal(1); + expect(output.text().includes(expectedDiffText)).to.be.true; + expect(output.text().includes(expectedActualText)).to.be.true; + }); + } +}); diff --git a/typescript/cli/src/tests/ethereum/warp/warp-check-2.e2e-test.ts b/typescript/cli/src/tests/ethereum/warp/warp-check-2.e2e-test.ts new file mode 100644 index 00000000000..bc7dfdd7bed --- /dev/null +++ b/typescript/cli/src/tests/ethereum/warp/warp-check-2.e2e-test.ts @@ -0,0 +1,329 @@ +import { expect } from 'chai'; +import { Signer, Wallet, ethers } from 'ethers'; +import { zeroAddress } from 'viem'; + +import { ERC20Test, HypERC20Collateral__factory } from '@hyperlane-xyz/core'; +import { + ChainAddresses, + createWarpRouteConfigId, +} from '@hyperlane-xyz/registry'; +import { + ChainMetadata, + TokenStandard, + TokenType, + WarpCoreConfig, + WarpRouteDeployConfig, + randomAddress, +} from '@hyperlane-xyz/sdk'; +import { Address, assert } from '@hyperlane-xyz/utils'; + +import { readYamlOrJson, writeYamlOrJson } from '../../../utils/files.js'; +import { deployOrUseExistingCore } from '../commands/core.js'; +import { deployToken } from '../commands/helpers.js'; +import { + hyperlaneWarpCheckRaw, + hyperlaneWarpDeploy, +} from '../commands/warp.js'; +import { + ANVIL_DEPLOYER_ADDRESS, + ANVIL_KEY, + CHAIN_2_METADATA_PATH, + CHAIN_3_METADATA_PATH, + CHAIN_NAME_2, + CHAIN_NAME_3, + CORE_CONFIG_PATH, + DEFAULT_E2E_TEST_TIMEOUT, + WARP_DEPLOY_OUTPUT_PATH, + getCombinedWarpRoutePath, +} from '../consts.js'; + +describe('hyperlane warp check e2e tests', async function () { + this.timeout(2 * DEFAULT_E2E_TEST_TIMEOUT); + + let signer: Signer; + let chain2Addresses: ChainAddresses = {}; + let chain3Addresses: ChainAddresses = {}; + let chain3DomainId: number; + let token: ERC20Test; + let tokenSymbol: string; + let ownerAddress: Address; + let combinedWarpCoreConfigPath: string; + let warpConfig: WarpRouteDeployConfig; + + before(async function () { + [chain2Addresses, chain3Addresses] = await Promise.all([ + deployOrUseExistingCore(CHAIN_NAME_2, CORE_CONFIG_PATH, ANVIL_KEY), + deployOrUseExistingCore(CHAIN_NAME_3, CORE_CONFIG_PATH, ANVIL_KEY), + ]); + + const chainMetadata: ChainMetadata = readYamlOrJson(CHAIN_2_METADATA_PATH); + chain3DomainId = (readYamlOrJson(CHAIN_3_METADATA_PATH) as ChainMetadata) + .domainId; + + const provider = new ethers.providers.JsonRpcProvider( + chainMetadata.rpcUrls[0].http, + ); + + signer = new Wallet(ANVIL_KEY).connect(provider); + + token = await deployToken(ANVIL_KEY, CHAIN_NAME_2); + tokenSymbol = await token.symbol(); + + combinedWarpCoreConfigPath = getCombinedWarpRoutePath(tokenSymbol, [ + CHAIN_NAME_3, + ]); + }); + + async function deployAndExportWarpRoute(): Promise { + writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); + // currently warp deploy is not writing the deploy config to the registry + // should remove this once the deploy config is written to the registry + writeYamlOrJson( + combinedWarpCoreConfigPath.replace('-config.yaml', '-deploy.yaml'), + warpConfig, + ); + + const currentWarpId = createWarpRouteConfigId( + await token.symbol(), + CHAIN_NAME_3, + ); + + await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH, currentWarpId); + + return warpConfig; + } + + // Reset config before each test to avoid test changes intertwining + beforeEach(async function () { + ownerAddress = new Wallet(ANVIL_KEY).address; + warpConfig = { + [CHAIN_NAME_2]: { + type: TokenType.collateral, + token: token.address, + mailbox: chain2Addresses.mailbox, + owner: ownerAddress, + }, + [CHAIN_NAME_3]: { + type: TokenType.synthetic, + mailbox: chain3Addresses.mailbox, + owner: ownerAddress, + }, + }; + }); + + describe('hyperlane warp check --config ... and hyperlane warp check --warp ...', () => { + const expectedError = + 'Both --config/-wd and --warp/-wc must be provided together when using individual file paths'; + it(`should require both warp core & warp deploy config paths to be provided together`, async function () { + await deployAndExportWarpRoute(); + + const output1 = await hyperlaneWarpCheckRaw({ + warpDeployPath: WARP_DEPLOY_OUTPUT_PATH, + }) + .stdio('pipe') + .nothrow(); + + const output2 = await hyperlaneWarpCheckRaw({ + warpCoreConfigPath: combinedWarpCoreConfigPath, + }) + .stdio('pipe') + .nothrow(); + + expect(output1.exitCode).to.equal(1); + expect(output1.text()).to.include(expectedError); + expect(output2.exitCode).to.equal(1); + expect(output2.text()).to.include(expectedError); + }); + }); + + describe('hyperlane warp check --symbol ...', () => { + it(`should not find any differences between the on chain config and the local one`, async function () { + await deployAndExportWarpRoute(); + + // only one route exists for this token so no need to interact with prompts + const output = await hyperlaneWarpCheckRaw({ + symbol: tokenSymbol, + }) + .stdio('pipe') + .nothrow(); + + expect(output.exitCode).to.equal(0); + expect(output.text()).to.include('No violations found'); + }); + }); + + describe('hyperlane warp check --warpRouteId ...', () => { + it(`should not find any differences between the on chain config and the local one`, async function () { + await deployAndExportWarpRoute(); + + const output = await hyperlaneWarpCheckRaw({ + warpRouteId: createWarpRouteConfigId(tokenSymbol, CHAIN_NAME_3), + }) + .stdio('pipe') + .nothrow(); + + expect(output.exitCode).to.equal(0); + expect(output.text()).to.include('No violations found'); + }); + + it(`should successfully check warp routes that are not deployed as proxies`, async () => { + // Deploy the token and the hyp adapter + const symbol = 'NTAP'; + const tokenName = 'NOTAPROXY'; + const tokenDecimals = 10; + const collateral = await deployToken( + ANVIL_KEY, + CHAIN_NAME_2, + tokenDecimals, + symbol, + ); + + const contract = new HypERC20Collateral__factory(signer); + const tx = await contract.deploy( + collateral.address, + 1, + chain2Addresses.mailbox, + ); + + const deployedContract = await tx.deployed(); + const tx2 = await deployedContract.initialize( + zeroAddress, + zeroAddress, + ANVIL_DEPLOYER_ADDRESS, + ); + + await tx2.wait(); + + // Manually add config files to the registry + const routePath = getCombinedWarpRoutePath(symbol, [CHAIN_NAME_2]); + const warpDeployConfig: WarpRouteDeployConfig = { + [CHAIN_NAME_2]: { + type: TokenType.collateral, + token: collateral.address, + owner: ANVIL_DEPLOYER_ADDRESS, + }, + }; + writeYamlOrJson( + routePath.replace('-config.yaml', '-deploy.yaml'), + warpDeployConfig, + ); + + const warpCoreConfig: WarpCoreConfig = { + tokens: [ + { + addressOrDenom: deployedContract.address, + chainName: CHAIN_NAME_2, + decimals: tokenDecimals, + collateralAddressOrDenom: token.address, + name: tokenName, + standard: TokenStandard.EvmHypCollateral, + symbol, + }, + ], + }; + writeYamlOrJson(routePath, warpCoreConfig); + + // Finally run warp check + const output = await hyperlaneWarpCheckRaw({ + warpRouteId: createWarpRouteConfigId(symbol, CHAIN_NAME_2), + }) + .stdio('pipe') + .nothrow(); + + expect(output.exitCode).to.equal(0); + expect(output.text()).to.include('No violations found'); + }); + }); + + it('should successfully check allowedRebalancers', async () => { + assert( + warpConfig[CHAIN_NAME_2].type === TokenType.collateral, + 'Expected config to be for a collateral token', + ); + warpConfig[CHAIN_NAME_2].allowedRebalancers = [randomAddress()]; + await deployAndExportWarpRoute(); + + const output = await hyperlaneWarpCheckRaw({ + warpDeployPath: WARP_DEPLOY_OUTPUT_PATH, + warpCoreConfigPath: combinedWarpCoreConfigPath, + }) + .stdio('pipe') + .nothrow(); + + expect(output.exitCode).to.equal(0); + expect(output.text()).to.include('No violations found'); + }); + + it('should report a violation if no rebalancers are in the config but are set on chain', async () => { + assert( + warpConfig[CHAIN_NAME_2].type === TokenType.collateral, + 'Expected config to be for a collateral token', + ); + warpConfig[CHAIN_NAME_2].allowedRebalancers = [randomAddress()]; + await deployAndExportWarpRoute(); + + warpConfig[CHAIN_NAME_2].allowedRebalancers = undefined; + const wrongDeployConfigPath = combinedWarpCoreConfigPath.replace( + '-config.yaml', + '-deploy.yaml', + ); + writeYamlOrJson(wrongDeployConfigPath, warpConfig); + + const output = await hyperlaneWarpCheckRaw({ + warpDeployPath: wrongDeployConfigPath, + warpCoreConfigPath: combinedWarpCoreConfigPath, + }) + .stdio('pipe') + .nothrow(); + + expect(output.exitCode).to.equal(1); + }); + + it('should successfully check the allowed rebalancing bridges', async () => { + assert( + warpConfig[CHAIN_NAME_2].type === TokenType.collateral, + 'Expected config to be for a collateral token', + ); + warpConfig[CHAIN_NAME_2].allowedRebalancingBridges = { + [chain3DomainId]: [{ bridge: randomAddress() }], + }; + await deployAndExportWarpRoute(); + + const output = await hyperlaneWarpCheckRaw({ + warpDeployPath: WARP_DEPLOY_OUTPUT_PATH, + warpCoreConfigPath: combinedWarpCoreConfigPath, + }) + .stdio('pipe') + .nothrow(); + + expect(output.exitCode).to.equal(0); + expect(output.text()).to.include('No violations found'); + }); + + it('should report a violation if no allowed bridges are in the config but are set on chain', async () => { + assert( + warpConfig[CHAIN_NAME_2].type === TokenType.collateral, + 'Expected config to be for a collateral token', + ); + warpConfig[CHAIN_NAME_2].allowedRebalancingBridges = { + [chain3DomainId]: [{ bridge: randomAddress() }], + }; + await deployAndExportWarpRoute(); + + warpConfig[CHAIN_NAME_2].allowedRebalancingBridges = undefined; + const wrongDeployConfigPath = combinedWarpCoreConfigPath.replace( + '-config.yaml', + '-deploy.yaml', + ); + writeYamlOrJson(wrongDeployConfigPath, warpConfig); + + const output = await hyperlaneWarpCheckRaw({ + warpDeployPath: wrongDeployConfigPath, + warpCoreConfigPath: combinedWarpCoreConfigPath, + }) + .stdio('pipe') + .nothrow(); + + expect(output.exitCode).to.equal(1); + }); +}); diff --git a/typescript/cli/src/tests/ethereum/warp/warp-check.e2e-test.ts b/typescript/cli/src/tests/ethereum/warp/warp-check-3.e2e-test.ts similarity index 56% rename from typescript/cli/src/tests/ethereum/warp/warp-check.e2e-test.ts rename to typescript/cli/src/tests/ethereum/warp/warp-check-3.e2e-test.ts index 49d539139f4..0cd6b1781f8 100644 --- a/typescript/cli/src/tests/ethereum/warp/warp-check.e2e-test.ts +++ b/typescript/cli/src/tests/ethereum/warp/warp-check-3.e2e-test.ts @@ -2,37 +2,21 @@ import { expect } from 'chai'; import { Signer, Wallet, ethers } from 'ethers'; import { zeroAddress } from 'viem'; -import { - ERC20Test, - HypERC20Collateral__factory, - Mailbox__factory, -} from '@hyperlane-xyz/core'; +import { ERC20Test, Mailbox__factory } from '@hyperlane-xyz/core'; import { ChainAddresses, createWarpRouteConfigId, } from '@hyperlane-xyz/registry'; import { ChainMetadata, - HookConfig, HookType, - IsmConfig, IsmType, - MUTABLE_HOOK_TYPE, - MUTABLE_ISM_TYPE, - TokenStandard, TokenType, WarpCoreConfig, WarpRouteDeployConfig, randomAddress, - randomHookConfig, - randomIsmConfig, } from '@hyperlane-xyz/sdk'; -import { - Address, - addressToBytes32, - assert, - deepCopy, -} from '@hyperlane-xyz/utils'; +import { Address, addressToBytes32 } from '@hyperlane-xyz/utils'; import { readYamlOrJson, writeYamlOrJson } from '../../../utils/files.js'; import { deployOrUseExistingCore } from '../commands/core.js'; @@ -43,10 +27,8 @@ import { hyperlaneWarpDeploy, } from '../commands/warp.js'; import { - ANVIL_DEPLOYER_ADDRESS, ANVIL_KEY, CHAIN_2_METADATA_PATH, - CHAIN_3_METADATA_PATH, CHAIN_NAME_2, CHAIN_NAME_3, CORE_CONFIG_PATH, @@ -62,7 +44,6 @@ describe('hyperlane warp check e2e tests', async function () { let signer: Signer; let chain2Addresses: ChainAddresses = {}; let chain3Addresses: ChainAddresses = {}; - let chain3DomainId: number; let token: ERC20Test; let tokenSymbol: string; let ownerAddress: Address; @@ -76,8 +57,6 @@ describe('hyperlane warp check e2e tests', async function () { ]); const chainMetadata: ChainMetadata = readYamlOrJson(CHAIN_2_METADATA_PATH); - chain3DomainId = (readYamlOrJson(CHAIN_3_METADATA_PATH) as ChainMetadata) - .domainId; const provider = new ethers.providers.JsonRpcProvider( chainMetadata.rpcUrls[0].http, @@ -130,130 +109,6 @@ describe('hyperlane warp check e2e tests', async function () { }; }); - describe('hyperlane warp check --config ... and hyperlane warp check --warp ...', () => { - const expectedError = - 'Both --config/-wd and --warp/-wc must be provided together when using individual file paths'; - it(`should require both warp core & warp deploy config paths to be provided together`, async function () { - await deployAndExportWarpRoute(); - - const output1 = await hyperlaneWarpCheckRaw({ - warpDeployPath: WARP_DEPLOY_OUTPUT_PATH, - }) - .stdio('pipe') - .nothrow(); - - const output2 = await hyperlaneWarpCheckRaw({ - warpCoreConfigPath: combinedWarpCoreConfigPath, - }) - .stdio('pipe') - .nothrow(); - - expect(output1.exitCode).to.equal(1); - expect(output1.text()).to.include(expectedError); - expect(output2.exitCode).to.equal(1); - expect(output2.text()).to.include(expectedError); - }); - }); - - describe('hyperlane warp check --symbol ...', () => { - it(`should not find any differences between the on chain config and the local one`, async function () { - await deployAndExportWarpRoute(); - - // only one route exists for this token so no need to interact with prompts - const output = await hyperlaneWarpCheckRaw({ - symbol: tokenSymbol, - }) - .stdio('pipe') - .nothrow(); - - expect(output.exitCode).to.equal(0); - expect(output.text()).to.include('No violations found'); - }); - }); - - describe('hyperlane warp check --warpRouteId ...', () => { - it(`should not find any differences between the on chain config and the local one`, async function () { - await deployAndExportWarpRoute(); - - const output = await hyperlaneWarpCheckRaw({ - warpRouteId: createWarpRouteConfigId(tokenSymbol, CHAIN_NAME_3), - }) - .stdio('pipe') - .nothrow(); - - expect(output.exitCode).to.equal(0); - expect(output.text()).to.include('No violations found'); - }); - - it(`should successfully check warp routes that are not deployed as proxies`, async () => { - // Deploy the token and the hyp adapter - const symbol = 'NTAP'; - const tokenName = 'NOTAPROXY'; - const tokenDecimals = 10; - const collateral = await deployToken( - ANVIL_KEY, - CHAIN_NAME_2, - tokenDecimals, - symbol, - ); - - const contract = new HypERC20Collateral__factory(signer); - const tx = await contract.deploy( - collateral.address, - 1, - chain2Addresses.mailbox, - ); - - const deployedContract = await tx.deployed(); - const tx2 = await deployedContract.initialize( - zeroAddress, - zeroAddress, - ANVIL_DEPLOYER_ADDRESS, - ); - - await tx2.wait(); - - // Manually add config files to the registry - const routePath = getCombinedWarpRoutePath(symbol, [CHAIN_NAME_2]); - const warpDeployConfig: WarpRouteDeployConfig = { - [CHAIN_NAME_2]: { - type: TokenType.collateral, - token: collateral.address, - owner: ANVIL_DEPLOYER_ADDRESS, - }, - }; - writeYamlOrJson( - routePath.replace('-config.yaml', '-deploy.yaml'), - warpDeployConfig, - ); - - const warpCoreConfig: WarpCoreConfig = { - tokens: [ - { - addressOrDenom: deployedContract.address, - chainName: CHAIN_NAME_2, - decimals: tokenDecimals, - collateralAddressOrDenom: token.address, - name: tokenName, - standard: TokenStandard.EvmHypCollateral, - symbol, - }, - ], - }; - writeYamlOrJson(routePath, warpCoreConfig); - - // Finally run warp check - const output = await hyperlaneWarpCheckRaw({ - warpRouteId: createWarpRouteConfigId(symbol, CHAIN_NAME_2), - }) - .stdio('pipe') - .nothrow(); - - expect(output.exitCode).to.equal(0); - expect(output.text()).to.include('No violations found'); - }); - }); - describe('hyperlane warp check --config ... --warp ...', () => { it(`should not find any differences between the on chain config and the local one`, async function () { await deployAndExportWarpRoute(); @@ -547,168 +402,4 @@ describe('hyperlane warp check e2e tests', async function () { ); }); }); - - for (const hookType of MUTABLE_HOOK_TYPE) { - it(`should find owner differences between the local config and the on chain config for ${hookType}`, async function () { - warpConfig[CHAIN_NAME_3].hook = randomHookConfig(0, 2, hookType); - await deployAndExportWarpRoute(); - - const mutatedWarpConfig = deepCopy(warpConfig); - - const hookConfig: Extract< - HookConfig, - { type: (typeof MUTABLE_HOOK_TYPE)[number]; owner: string } - > = mutatedWarpConfig[CHAIN_NAME_3].hook!; - const actualOwner = hookConfig.owner; - const wrongOwner = randomAddress(); - assert(actualOwner !== wrongOwner, 'Random owner matches actualOwner'); - hookConfig.owner = wrongOwner; - writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, mutatedWarpConfig); - - const expectedDiffText = `EXPECTED: "${wrongOwner.toLowerCase()}"\n`; - const expectedActualText = `ACTUAL: "${actualOwner.toLowerCase()}"\n`; - - const output = await hyperlaneWarpCheckRaw({ - warpDeployPath: WARP_DEPLOY_OUTPUT_PATH, - warpCoreConfigPath: combinedWarpCoreConfigPath, - }).nothrow(); - expect(output.exitCode).to.equal(1); - expect(output.text().includes(expectedDiffText)).to.be.true; - expect(output.text().includes(expectedActualText)).to.be.true; - }); - } - - // Removing the offchain lookup ism because it is a family of different isms - for (const ismType of MUTABLE_ISM_TYPE.filter( - (ismType) => ismType !== IsmType.OFFCHAIN_LOOKUP, - )) { - it(`should find owner differences between the local config and the on chain config for ${ismType}`, async function () { - // Create a Pausable because randomIsmConfig() cannot generate it (reason: NULL type Isms) - warpConfig[CHAIN_NAME_3].interchainSecurityModule = - ismType === IsmType.PAUSABLE - ? { - type: IsmType.PAUSABLE, - owner: randomAddress(), - paused: true, - } - : randomIsmConfig(0, 2, ismType); - await deployAndExportWarpRoute(); - - const mutatedWarpConfig = deepCopy(warpConfig); - - const ismConfig: Extract< - IsmConfig, - { type: (typeof MUTABLE_ISM_TYPE)[number]; owner: string } - > = mutatedWarpConfig[CHAIN_NAME_3].interchainSecurityModule; - const actualOwner = ismConfig.owner; - const wrongOwner = randomAddress(); - assert(actualOwner !== wrongOwner, 'Random owner matches actualOwner'); - ismConfig.owner = wrongOwner; - writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, mutatedWarpConfig); - - const expectedDiffText = `EXPECTED: "${wrongOwner.toLowerCase()}"\n`; - const expectedActualText = `ACTUAL: "${actualOwner.toLowerCase()}"\n`; - - const output = await hyperlaneWarpCheckRaw({ - warpDeployPath: WARP_DEPLOY_OUTPUT_PATH, - warpCoreConfigPath: combinedWarpCoreConfigPath, - }).nothrow(); - - expect(output.exitCode).to.equal(1); - expect(output.text().includes(expectedDiffText)).to.be.true; - expect(output.text().includes(expectedActualText)).to.be.true; - }); - } - - it('should successfully check allowedRebalancers', async () => { - assert( - warpConfig[CHAIN_NAME_2].type === TokenType.collateral, - 'Expected config to be for a collateral token', - ); - warpConfig[CHAIN_NAME_2].allowedRebalancers = [randomAddress()]; - await deployAndExportWarpRoute(); - - const output = await hyperlaneWarpCheckRaw({ - warpDeployPath: WARP_DEPLOY_OUTPUT_PATH, - warpCoreConfigPath: combinedWarpCoreConfigPath, - }) - .stdio('pipe') - .nothrow(); - - expect(output.exitCode).to.equal(0); - expect(output.text()).to.include('No violations found'); - }); - - it('should report a violation if no rebalancers are in the config but are set on chain', async () => { - assert( - warpConfig[CHAIN_NAME_2].type === TokenType.collateral, - 'Expected config to be for a collateral token', - ); - warpConfig[CHAIN_NAME_2].allowedRebalancers = [randomAddress()]; - await deployAndExportWarpRoute(); - - warpConfig[CHAIN_NAME_2].allowedRebalancers = undefined; - const wrongDeployConfigPath = combinedWarpCoreConfigPath.replace( - '-config.yaml', - '-deploy.yaml', - ); - writeYamlOrJson(wrongDeployConfigPath, warpConfig); - - const output = await hyperlaneWarpCheckRaw({ - warpDeployPath: wrongDeployConfigPath, - warpCoreConfigPath: combinedWarpCoreConfigPath, - }) - .stdio('pipe') - .nothrow(); - - expect(output.exitCode).to.equal(1); - }); - - it('should successfully check the allowed rebalancing bridges', async () => { - assert( - warpConfig[CHAIN_NAME_2].type === TokenType.collateral, - 'Expected config to be for a collateral token', - ); - warpConfig[CHAIN_NAME_2].allowedRebalancingBridges = { - [chain3DomainId]: [{ bridge: randomAddress() }], - }; - await deployAndExportWarpRoute(); - - const output = await hyperlaneWarpCheckRaw({ - warpDeployPath: WARP_DEPLOY_OUTPUT_PATH, - warpCoreConfigPath: combinedWarpCoreConfigPath, - }) - .stdio('pipe') - .nothrow(); - - expect(output.exitCode).to.equal(0); - expect(output.text()).to.include('No violations found'); - }); - - it('should report a violation if no allowed bridges are in the config but are set on chain', async () => { - assert( - warpConfig[CHAIN_NAME_2].type === TokenType.collateral, - 'Expected config to be for a collateral token', - ); - warpConfig[CHAIN_NAME_2].allowedRebalancingBridges = { - [chain3DomainId]: [{ bridge: randomAddress() }], - }; - await deployAndExportWarpRoute(); - - warpConfig[CHAIN_NAME_2].allowedRebalancingBridges = undefined; - const wrongDeployConfigPath = combinedWarpCoreConfigPath.replace( - '-config.yaml', - '-deploy.yaml', - ); - writeYamlOrJson(wrongDeployConfigPath, warpConfig); - - const output = await hyperlaneWarpCheckRaw({ - warpDeployPath: wrongDeployConfigPath, - warpCoreConfigPath: combinedWarpCoreConfigPath, - }) - .stdio('pipe') - .nothrow(); - - expect(output.exitCode).to.equal(1); - }); }); diff --git a/typescript/cli/src/tests/ethereum/warp/warp-read.e2e-test.ts b/typescript/cli/src/tests/ethereum/warp/warp-read.e2e-test.ts index b8b49454b1a..0c65b366124 100644 --- a/typescript/cli/src/tests/ethereum/warp/warp-read.e2e-test.ts +++ b/typescript/cli/src/tests/ethereum/warp/warp-read.e2e-test.ts @@ -3,26 +3,25 @@ import { Wallet } from 'ethers'; import { ChainAddresses } from '@hyperlane-xyz/registry'; import { TokenType, WarpRouteDeployConfig } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; +import { Address, ProtocolType } from '@hyperlane-xyz/utils'; import { readYamlOrJson, writeYamlOrJson } from '../../../utils/files.js'; +import { HyperlaneE2ECoreTestCommands } from '../../commands/core.js'; import { KeyBoardKeys, TestPromptAction, handlePrompts, } from '../../commands/helpers.js'; -import { deployOrUseExistingCore } from '../commands/core.js'; -import { - hyperlaneWarpDeploy, - hyperlaneWarpReadRaw, - readWarpConfig, -} from '../commands/warp.js'; +import { HyperlaneE2EWarpTestCommands } from '../../commands/warp.js'; import { ANVIL_KEY, CHAIN_NAME_2, CHAIN_NAME_3, CORE_CONFIG_PATH, + CORE_READ_CONFIG_PATH_2, + CORE_READ_CONFIG_PATH_3, DEFAULT_E2E_TEST_TIMEOUT, + REGISTRY_PATH, TEMP_PATH, WARP_CONFIG_PATH_2, WARP_CONFIG_PATH_EXAMPLE, @@ -34,6 +33,28 @@ import { describe('hyperlane warp read e2e tests', async function () { this.timeout(DEFAULT_E2E_TEST_TIMEOUT); + const hyperlaneCore2 = new HyperlaneE2ECoreTestCommands( + ProtocolType.Ethereum, + CHAIN_NAME_2, + REGISTRY_PATH, + CORE_CONFIG_PATH, + CORE_READ_CONFIG_PATH_2, + ); + + const hyperlaneCore3 = new HyperlaneE2ECoreTestCommands( + ProtocolType.Ethereum, + CHAIN_NAME_3, + REGISTRY_PATH, + CORE_CONFIG_PATH, + CORE_READ_CONFIG_PATH_3, + ); + + const hyperlaneWarp = new HyperlaneE2EWarpTestCommands( + ProtocolType.Ethereum, + REGISTRY_PATH, + WARP_CONFIG_PATH_2, + ); + let anvil2Config: WarpRouteDeployConfig; let chain2Addresses: ChainAddresses = {}; @@ -43,15 +64,15 @@ describe('hyperlane warp read e2e tests', async function () { before(async function () { [chain2Addresses, chain3Addresses] = await Promise.all([ - deployOrUseExistingCore(CHAIN_NAME_2, CORE_CONFIG_PATH, ANVIL_KEY), - deployOrUseExistingCore(CHAIN_NAME_3, CORE_CONFIG_PATH, ANVIL_KEY), + hyperlaneCore2.deployOrUseExistingCore(ANVIL_KEY), + hyperlaneCore3.deployOrUseExistingCore(ANVIL_KEY), ]); ownerAddress = new Wallet(ANVIL_KEY).address; }); before(async function () { - await deployOrUseExistingCore(CHAIN_NAME_2, CORE_CONFIG_PATH, ANVIL_KEY); + await hyperlaneCore2.deployOrUseExistingCore(ANVIL_KEY); // Create a new warp config using the example const exampleWarpConfig: WarpRouteDeployConfig = readYamlOrJson( @@ -63,11 +84,9 @@ describe('hyperlane warp read e2e tests', async function () { describe('hyperlane warp read --config ...', () => { it('should exit early if no symbol or no chain and address', async () => { - await hyperlaneWarpDeploy(WARP_CONFIG_PATH_2); + await hyperlaneWarp.deploy(WARP_CONFIG_PATH_2, ANVIL_KEY); - const output = await hyperlaneWarpReadRaw({ - outputPath: WARP_CONFIG_PATH_2, - }).nothrow(); + const output = await hyperlaneWarp.readRaw({}).nothrow(); expect(output.exitCode).to.equal(1); expect(output.text()).to.include( @@ -78,12 +97,12 @@ describe('hyperlane warp read e2e tests', async function () { describe('hyperlane warp read --config ... --symbol ...', () => { it('should successfully read the complete warp route config from all the chains', async () => { - await hyperlaneWarpDeploy(WARP_CONFIG_PATH_2); + await hyperlaneWarp.deploy(WARP_CONFIG_PATH_2, ANVIL_KEY); - const output = await hyperlaneWarpReadRaw({ - symbol: 'ETH', - outputPath: WARP_CONFIG_PATH_2, - }) + const output = await hyperlaneWarp + .readRaw({ + symbol: 'ETH', + }) .stdio('pipe') .nothrow(); @@ -114,7 +133,7 @@ describe('hyperlane warp read e2e tests', async function () { }; writeYamlOrJson(WARP_DEPLOY_OUTPUT_PATH, warpConfig); - await hyperlaneWarpDeploy(WARP_DEPLOY_OUTPUT_PATH); + await hyperlaneWarp.deploy(WARP_DEPLOY_OUTPUT_PATH, ANVIL_KEY); const steps: TestPromptAction[] = [ // Select the anvil2-anvil3 ETH route from the selection prompt @@ -125,10 +144,11 @@ describe('hyperlane warp read e2e tests', async function () { }, ]; - const output = hyperlaneWarpReadRaw({ - symbol: 'ETH', - outputPath: readOutputPath, - }) + const output = hyperlaneWarp + .readRaw({ + symbol: 'ETH', + outputPath: readOutputPath, + }) .stdio('pipe') .nothrow(); @@ -148,14 +168,15 @@ describe('hyperlane warp read e2e tests', async function () { describe('hyperlane warp read --chain ... --config ...', () => { it('should be able to read a warp route', async function () { - await hyperlaneWarpDeploy(WARP_CONFIG_PATH_2, WARP_DEPLOY_2_ID); - - const warpReadResult: WarpRouteDeployConfig = await readWarpConfig( - CHAIN_NAME_2, - WARP_CORE_CONFIG_PATH_2, - WARP_DEPLOY_OUTPUT_PATH, + await hyperlaneWarp.deploy( + WARP_CONFIG_PATH_2, + ANVIL_KEY, + WARP_DEPLOY_2_ID, ); + const warpReadResult: WarpRouteDeployConfig = + await hyperlaneWarp.readConfig(CHAIN_NAME_2, WARP_CORE_CONFIG_PATH_2); + expect(warpReadResult[CHAIN_NAME_2].type).to.be.equal( anvil2Config[CHAIN_NAME_2].type, ); diff --git a/typescript/cli/src/tests/ethereum/warp/warp-rebalancer.e2e-test.ts b/typescript/cli/src/tests/ethereum/warp/warp-rebalancer.e2e-test.ts index 8bc28551e54..2b2f3c350f6 100644 --- a/typescript/cli/src/tests/ethereum/warp/warp-rebalancer.e2e-test.ts +++ b/typescript/cli/src/tests/ethereum/warp/warp-rebalancer.e2e-test.ts @@ -13,6 +13,7 @@ import { import { createWarpRouteConfigId } from '@hyperlane-xyz/registry'; import { ChainMetadata, + RebalancerConfigFileInput, RebalancerMinAmountType, RebalancerStrategyOptions, TokenType, @@ -253,6 +254,7 @@ describe('hyperlane warp rebalancer e2e tests', async function () { destination?: string; amount?: string; key?: string; + explorerUrl?: string; } = {}, ): ProcessPromise { const { @@ -264,6 +266,7 @@ describe('hyperlane warp rebalancer e2e tests', async function () { destination, amount, key, + explorerUrl, } = options; return hyperlaneWarpRebalancer( @@ -276,9 +279,43 @@ describe('hyperlane warp rebalancer e2e tests', async function () { destination, amount, key, + explorerUrl, ); } + /** + * Creates a mock GraphQL server for Explorer API + * @param responseData The data to return in the GraphQL response + * @returns Promise with server instance and URL + */ + async function createMockExplorerServer(responseData: any): Promise<{ + server: any; + url: string; + close: () => Promise; + }> { + const http = await import('http'); + const server = http.createServer((req, res) => { + if (req.method === 'POST') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(responseData)); + } else { + res.statusCode = 404; + res.end(); + } + }); + + await new Promise((resolve) => server.listen(0, resolve)); + const address: any = server.address(); + const url = `http://127.0.0.1:${address.port}`; + + return { + server, + url, + close: () => + new Promise((resolve) => server.close(() => resolve())), + }; + } + async function startRebalancerAndExpectLog( log: string | string[], options: { @@ -291,6 +328,7 @@ describe('hyperlane warp rebalancer e2e tests', async function () { destination?: string; amount?: string; key?: string; + explorerUrl?: string; } = {}, ) { const { @@ -303,6 +341,7 @@ describe('hyperlane warp rebalancer e2e tests', async function () { destination, amount, key, + explorerUrl, } = options; const rebalancer = startRebalancer({ @@ -314,6 +353,7 @@ describe('hyperlane warp rebalancer e2e tests', async function () { destination, amount, key, + explorerUrl, }); let timeoutId: NodeJS.Timeout; @@ -338,15 +378,28 @@ describe('hyperlane warp rebalancer e2e tests', async function () { // Handle when the process exits due to an error that is not the expected log rebalancer.catch((e) => { - const lines = e.lines(); - const error = lines[lines.length - 1]; + const lines = typeof e.lines === 'function' ? e.lines() : []; + const combined = Array.isArray(lines) ? lines.join('\n') : String(e); + + // Consume any expected logs that appear in the error output + while (expectedLogs.length && combined.includes(expectedLogs[0])) { + expectedLogs.shift(); + } clearTimeout(timeoutId); - reject( - new Error( - `Process failed before logging: "${expectedLogs[0]}" with error: ${error}`, - ), - ); + if (!expectedLogs.length) { + resolve(void 0); + } else { + const lastLine = + Array.isArray(lines) && lines.length + ? lines[lines.length - 1] + : String(e); + reject( + new Error( + `Process failed before logging: "${expectedLogs[0]}" with error: ${lastLine}`, + ), + ); + } }); (async () => { // Wait for the process to output the expected log. @@ -387,11 +440,117 @@ describe('hyperlane warp rebalancer e2e tests', async function () { await startRebalancerAndExpectLog('Rebalancer started successfully 🚀'); }); + it('should skip when inflight detected by explorer', async () => { + // Create mock server that returns inflight messages + const mockServer = await createMockExplorerServer({ + data: { message_view: [{ msg_id: '1' }] }, + }); + + try { + // Ensure there is a potential route by creating an imbalance + const config: RebalancerConfigFileInput = { + warpRouteId, + strategy: { + rebalanceStrategy: RebalancerStrategyOptions.Weighted, + chains: { + [CHAIN_NAME_2]: { + weighted: { weight: '25', tolerance: '0' }, + bridge: ethers.constants.AddressZero, + bridgeLockTime: 1, + }, + [CHAIN_NAME_3]: { + weighted: { weight: '75', tolerance: '0' }, + bridge: ethers.constants.AddressZero, + bridgeLockTime: 1, + }, + }, + }, + }; + + writeYamlOrJson(REBALANCER_CONFIG_PATH, config); + + await startRebalancerAndExpectLog( + 'Inflight rebalance detected via Explorer; skipping this cycle', + { explorerUrl: mockServer.url, checkFrequency: 2000 }, + ); + } finally { + await mockServer.close(); + } + }); + + it('should proceed when no inflight detected by explorer', async () => { + // Create mock server that returns no inflight messages + const mockServer = await createMockExplorerServer({ + data: { message_view: [] }, + }); + + try { + // Deploy and allow a bridge so the route can succeed + const chain3Provider = new ethers.providers.JsonRpcProvider( + chain3Metadata.rpcUrls[0].http, + ); + const chain3Signer = new Wallet(ANVIL_KEY, chain3Provider); + const chain3CollateralContract = HypERC20Collateral__factory.connect( + getTokenAddressFromWarpConfig(warpCoreConfig, CHAIN_NAME_3), + chain3Signer, + ); + + const bridgeContract = await new MockValueTransferBridge__factory( + chain3Signer, + ).deploy(); + + await chain3CollateralContract.addBridge( + chain2Metadata.domainId, + bridgeContract.address, + ); + + // Configure imbalance and set bridge on origin chain + const config: RebalancerConfigFileInput = { + warpRouteId, + strategy: { + rebalanceStrategy: RebalancerStrategyOptions.Weighted, + chains: { + [CHAIN_NAME_2]: { + weighted: { weight: '75', tolerance: '0' }, + bridge: ethers.constants.AddressZero, + bridgeLockTime: 1, + }, + [CHAIN_NAME_3]: { + weighted: { weight: '25', tolerance: '0' }, + bridge: bridgeContract.address, + bridgeLockTime: 1, + }, + }, + }, + }; + + writeYamlOrJson(REBALANCER_CONFIG_PATH, config); + + await startRebalancerAndExpectLog( + [ + 'Found rebalancing routes', + 'Preparing all rebalance transactions.', + 'Preparing transaction for route', + 'Sending valid transactions.', + 'Sending transaction for route', + 'Transaction confirmed for route.', + '✅ Rebalance successful', + ], + { + explorerUrl: mockServer.url, + timeout: 30000, + }, + ); + } finally { + await mockServer.close(); + } + }); + it('should throw when strategy config file does not exist', async () => { rmSync(REBALANCER_CONFIG_PATH); await startRebalancerAndExpectLog( - `Rebalancer startup error: Error: File doesn't exist at ${REBALANCER_CONFIG_PATH}`, + `Error: File doesn't exist at ${REBALANCER_CONFIG_PATH}`, ); }); @@ -424,7 +583,7 @@ describe('hyperlane warp rebalancer e2e tests', async function () { }); await startRebalancerAndExpectLog( - `Rebalancer startup error: Error: Validation error: All chains must use the same minAmount type. at "strategy.chains"`, + `Error: Validation error: All chains must use the same minAmount type. at "strategy.chains"`, ); }); @@ -455,7 +614,7 @@ describe('hyperlane warp rebalancer e2e tests', async function () { }); await startRebalancerAndExpectLog( - `Rebalancer startup error: SyntaxError: Cannot convert weight to a BigInt`, + `SyntaxError: Cannot convert weight to a BigInt`, ); }); @@ -486,7 +645,7 @@ describe('hyperlane warp rebalancer e2e tests', async function () { }); await startRebalancerAndExpectLog( - `Rebalancer startup error: SyntaxError: Cannot convert tolerance to a BigInt`, + `SyntaxError: Cannot convert tolerance to a BigInt`, ); }); @@ -517,7 +676,7 @@ describe('hyperlane warp rebalancer e2e tests', async function () { }); await startRebalancerAndExpectLog( - `Rebalancer startup error: Error: Validation error: Invalid at "strategy.chains.anvil2.bridge"`, + `Error: Validation error: Invalid at "strategy.chains.anvil2.bridge"`, ); }); @@ -837,7 +996,7 @@ describe('hyperlane warp rebalancer e2e tests', async function () { }); await startRebalancerAndExpectLog( - `Rebalancer startup error: Error: Consider reducing the targets as the sum (23) is greater than sum of collaterals (20)`, + `Error: Consider reducing the targets as the sum (23) is greater than sum of collaterals (20)`, ); }); diff --git a/typescript/cli/src/utils/balances.ts b/typescript/cli/src/utils/balances.ts index 46ca58834a0..eb71c12f90b 100644 --- a/typescript/cli/src/utils/balances.ts +++ b/typescript/cli/src/utils/balances.ts @@ -1,43 +1,110 @@ -import { ethers } from 'ethers'; +import { GasPrice } from '@cosmjs/stargate'; +import { BigNumber } from 'ethers'; +import { formatUnits, parseUnits } from 'ethers/lib/utils.js'; -import { ChainName, MultiProvider } from '@hyperlane-xyz/sdk'; -import { ProtocolType } from '@hyperlane-xyz/utils'; +import { + ChainMetadataManager, + ChainName, + MultiProtocolProvider, +} from '@hyperlane-xyz/sdk'; +import { Address, ProtocolType, assert } from '@hyperlane-xyz/utils'; import { autoConfirm } from '../config/prompts.js'; +import { MINIMUM_WARP_DEPLOY_GAS } from '../consts.js'; +import { MultiProtocolSignerManager } from '../context/strategies/signer/MultiProtocolSignerManager.js'; import { logBlue, logGray, logGreen, logRed, warnYellow } from '../logger.js'; export async function nativeBalancesAreSufficient( - multiProvider: MultiProvider, + metadataManager: ChainMetadataManager, + multiProtocolProvider: MultiProtocolProvider, + multiProtocolSigner: MultiProtocolSignerManager, chains: ChainName[], - minGas: string, + minGas: typeof MINIMUM_WARP_DEPLOY_GAS, skipConfirmation: boolean, ) { const sufficientBalances: boolean[] = []; for (const chain of chains) { - // Only Ethereum chains are supported - if (multiProvider.getProtocol(chain) !== ProtocolType.Ethereum) { - logGray(`Skipping balance check for non-EVM chain: ${chain}`); - continue; + const protocolType = metadataManager.getProtocol(chain); + + const symbol = metadataManager.getChainMetadata(chain).nativeToken?.symbol; + assert(symbol, `no symbol found for native token on chain ${chain}`); + + let address: Address = ''; + let requiredMinBalanceNativeDenom = BigNumber.from(0); + let requiredMinBalance: string = '0'; + + let deployerBalanceNativeDenom = BigNumber.from(0); + let deployerBalance: string = '0'; + + switch (protocolType) { + case ProtocolType.Ethereum: { + address = await multiProtocolSigner.getEVMSigner(chain).getAddress(); + + const provider = multiProtocolProvider.getEthersV5Provider(chain); + const gasPrice = await provider.getGasPrice(); + + requiredMinBalanceNativeDenom = gasPrice.mul( + minGas[ProtocolType.Ethereum], + ); + requiredMinBalance = formatUnits( + requiredMinBalanceNativeDenom.toString(), + ); + + deployerBalanceNativeDenom = await provider.getBalance(address); + deployerBalance = formatUnits(deployerBalanceNativeDenom.toString()); + break; + } + case ProtocolType.CosmosNative: { + address = + multiProtocolSigner.getCosmosNativeSigner(chain).account.address; + + const provider = await multiProtocolProvider.getCosmJsProvider(chain); + const { gasPrice, nativeToken } = + metadataManager.getChainMetadata(chain); + + assert(nativeToken, `nativeToken is not defined on chain ${chain}`); + assert( + nativeToken.denom, + `nativeToken denom is not defined on chain ${chain}`, + ); + assert(gasPrice, `gasPrice is not defined on chain ${chain}`); + + const gasPriceInNativeDenom = parseUnits( + GasPrice.fromString( + `${gasPrice.amount}${gasPrice.denom}`, + ).amount.toString(), + nativeToken.decimals, + ); + requiredMinBalanceNativeDenom = gasPriceInNativeDenom.mul( + minGas[ProtocolType.CosmosNative], + ); + requiredMinBalance = formatUnits( + requiredMinBalanceNativeDenom, + nativeToken.decimals, + ); + + deployerBalanceNativeDenom = BigNumber.from( + (await provider.getBalance(address, nativeToken.denom)).amount, + ); + deployerBalance = formatUnits( + deployerBalanceNativeDenom, + nativeToken.decimals, + ); + break; + } + default: { + logGray(`Skipping balance check for unsupported chain: ${chain}`); + } } - const address = await multiProvider.getSigner(chain).getAddress(); - const provider = multiProvider.getProvider(chain); - const gasPrice = await provider.getGasPrice(); - const minBalanceWei = gasPrice.mul(minGas).toString(); - const minBalance = ethers.utils.formatEther(minBalanceWei.toString()); - - const balanceWei = await multiProvider - .getProvider(chain) - .getBalance(address); - const balance = ethers.utils.formatEther(balanceWei.toString()); - if (balanceWei.lt(minBalanceWei)) { - const symbol = - multiProvider.getChainMetadata(chain).nativeToken?.symbol ?? 'ETH'; + + if (deployerBalanceNativeDenom.lt(requiredMinBalanceNativeDenom)) { logRed( - `WARNING: ${address} has low balance on ${chain}. At least ${minBalance} ${symbol} recommended but found ${balance} ${symbol}`, + `WARNING: ${address} has low balance on ${chain}. At least ${requiredMinBalance} ${symbol} recommended but found ${deployerBalance} ${symbol}`, ); sufficientBalances.push(false); } } + const allSufficient = sufficientBalances.every((sufficient) => sufficient); if (allSufficient) { diff --git a/typescript/cli/src/version.ts b/typescript/cli/src/version.ts index 480c907b259..4b2baaff5b3 100644 --- a/typescript/cli/src/version.ts +++ b/typescript/cli/src/version.ts @@ -1 +1 @@ -export const VERSION = '16.0.0'; +export const VERSION = '18.2.0'; diff --git a/typescript/cli/test-configs/dry-run/chains/alfajores/metadata.yaml b/typescript/cli/test-configs/dry-run/chains/alfajores/metadata.yaml deleted file mode 100644 index edcd99183a1..00000000000 --- a/typescript/cli/test-configs/dry-run/chains/alfajores/metadata.yaml +++ /dev/null @@ -1,13 +0,0 @@ -chainId: 44787 -domainId: 44787 -name: alfajores -nativeToken: - decimals: 18 - name: CELO - symbol: CELO -protocol: ethereum -rpcUrls: - - http: https://alfajores-forno.celo-testnet.org -blocks: - confirmations: 1 - estimateBlockTime: 1 diff --git a/typescript/cli/test-configs/dry-run/chains/sepolia/metadata.yaml b/typescript/cli/test-configs/dry-run/chains/sepolia/metadata.yaml new file mode 100644 index 00000000000..55b7fa3a53f --- /dev/null +++ b/typescript/cli/test-configs/dry-run/chains/sepolia/metadata.yaml @@ -0,0 +1,13 @@ +chainId: 11155111 +domainId: 11155111 +name: sepolia +nativeToken: + decimals: 18 + name: ETH + symbol: ETH +protocol: ethereum +rpcUrls: + - http: https://ethereum-sepolia.publicnode.com +blocks: + confirmations: 1 + estimateBlockTime: 13 diff --git a/typescript/cli/test-configs/dry-run/ism.yaml b/typescript/cli/test-configs/dry-run/ism.yaml index a9d20e61350..f4646a55149 100644 --- a/typescript/cli/test-configs/dry-run/ism.yaml +++ b/typescript/cli/test-configs/dry-run/ism.yaml @@ -2,7 +2,7 @@ anvil: threshold: 1 validators: - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' -alfajores: +sepolia: threshold: 1 validators: - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' diff --git a/typescript/cli/test-configs/dry-run/warp-route-deployment.yaml b/typescript/cli/test-configs/dry-run/warp-route-deployment.yaml index 0c05f1c3d3b..bac078e1f64 100644 --- a/typescript/cli/test-configs/dry-run/warp-route-deployment.yaml +++ b/typescript/cli/test-configs/dry-run/warp-route-deployment.yaml @@ -1,4 +1,4 @@ -alfajores: +sepolia: type: native fuji: type: synthetic diff --git a/typescript/cli/test-configs/hyp/chains/hyp1/metadata.yaml b/typescript/cli/test-configs/hyp/chains/hyp1/metadata.yaml index 326da8a0787..14920ef986a 100644 --- a/typescript/cli/test-configs/hyp/chains/hyp1/metadata.yaml +++ b/typescript/cli/test-configs/hyp/chains/hyp1/metadata.yaml @@ -3,8 +3,8 @@ # Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts --- bech32Prefix: hyp -chainId: hyperlane-local -domainId: 75898669 +chainId: hyperlane-local-1 +domainId: 758986691 name: hyp1 protocol: cosmosnative slip44: 118 diff --git a/typescript/cli/test-configs/hyp/chains/hyp2/metadata.yaml b/typescript/cli/test-configs/hyp/chains/hyp2/metadata.yaml new file mode 100644 index 00000000000..0255a6fcb05 --- /dev/null +++ b/typescript/cli/test-configs/hyp/chains/hyp2/metadata.yaml @@ -0,0 +1,32 @@ +# Configs for describing chain metadata for use in Hyperlane deployments or apps +# Consists of a map of chain names to metadata +# Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts +--- +bech32Prefix: hyp +chainId: hyperlane-local-2 +domainId: 758986692 +name: hyp2 +protocol: cosmosnative +slip44: 118 +restUrls: + - http: http://127.0.0.1:1317 +rpcUrls: + - http: http://127.0.0.1:26657 +grpcUrls: + - http: http://127.0.0.1:9090 +blockExplorers: # Array: List of BlockExplorer configs + # Required fields: + - name: My Chain Explorer # String: Human-readable name for the explorer + url: https://mychain.com/explorer # String: Base URL for the explorer + apiUrl: https://mychain.com/api # String: Base URL for the explorer API + # Optional fields: + apiKey: myapikey # String: API key for the explorer (optional) + family: etherscan # ExplorerFamily: See ExplorerFamily for valid values +nativeToken: + decimals: 6 + denom: uhyp + name: TEST + symbol: TEST +gasPrice: + amount: '1' + denom: uhyp diff --git a/typescript/cli/test-configs/hyp/chains/hyp3/metadata.yaml b/typescript/cli/test-configs/hyp/chains/hyp3/metadata.yaml new file mode 100644 index 00000000000..152cf6ab485 --- /dev/null +++ b/typescript/cli/test-configs/hyp/chains/hyp3/metadata.yaml @@ -0,0 +1,32 @@ +# Configs for describing chain metadata for use in Hyperlane deployments or apps +# Consists of a map of chain names to metadata +# Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts +--- +bech32Prefix: hyp +chainId: hyperlane-local-3 +domainId: 758986693 +name: hyp3 +protocol: cosmosnative +slip44: 118 +restUrls: + - http: http://127.0.0.1:1317 +rpcUrls: + - http: http://127.0.0.1:26657 +grpcUrls: + - http: http://127.0.0.1:9090 +blockExplorers: # Array: List of BlockExplorer configs + # Required fields: + - name: My Chain Explorer # String: Human-readable name for the explorer + url: https://mychain.com/explorer # String: Base URL for the explorer + apiUrl: https://mychain.com/api # String: Base URL for the explorer API + # Optional fields: + apiKey: myapikey # String: API key for the explorer (optional) + family: etherscan # ExplorerFamily: See ExplorerFamily for valid values +nativeToken: + decimals: 6 + denom: uhyp + name: TEST + symbol: TEST +gasPrice: + amount: '1' + denom: uhyp diff --git a/typescript/cosmos-sdk/CHANGELOG.md b/typescript/cosmos-sdk/CHANGELOG.md index 6406ccc1786..a416beffe60 100644 --- a/typescript/cosmos-sdk/CHANGELOG.md +++ b/typescript/cosmos-sdk/CHANGELOG.md @@ -1,5 +1,51 @@ # @hyperlane-xyz/cosmos-sdk +## 18.2.0 + +### Minor Changes + +- dfa9d368c: Added a `getCometClientOrFail` to the `HyperlaneModuleClient` to expose the internal provider connection + +### Patch Changes + +- @hyperlane-xyz/cosmos-types@18.2.0 + +## 18.1.0 + +### Patch Changes + +- @hyperlane-xyz/cosmos-types@18.1.0 + +## 18.0.0 + +### Patch Changes + +- @hyperlane-xyz/cosmos-types@18.0.0 + +## 17.0.0 + +### Patch Changes + +- @hyperlane-xyz/cosmos-types@17.0.0 + +## 16.2.0 + +### Patch Changes + +- @hyperlane-xyz/cosmos-types@16.2.0 + +## 16.1.1 + +### Patch Changes + +- @hyperlane-xyz/cosmos-types@16.1.1 + +## 16.1.0 + +### Patch Changes + +- @hyperlane-xyz/cosmos-types@16.1.0 + ## 16.0.0 ### Patch Changes diff --git a/typescript/cosmos-sdk/package.json b/typescript/cosmos-sdk/package.json index dae33771c38..9227810f7ef 100644 --- a/typescript/cosmos-sdk/package.json +++ b/typescript/cosmos-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/cosmos-sdk", - "version": "16.0.0", + "version": "18.2.0", "description": "Hyperlane TypeScript SDK for the Cosmos Hyperlane SDK module", "type": "module", "exports": { @@ -47,6 +47,6 @@ }, "dependencies": { "@cosmjs/stargate": "^0.32.4", - "@hyperlane-xyz/cosmos-types": "16.0.0" + "@hyperlane-xyz/cosmos-types": "18.2.0" } } diff --git a/typescript/cosmos-sdk/src/clients/client.ts b/typescript/cosmos-sdk/src/clients/client.ts index 591eb59e8cc..04a50175538 100644 --- a/typescript/cosmos-sdk/src/clients/client.ts +++ b/typescript/cosmos-sdk/src/clients/client.ts @@ -86,4 +86,14 @@ export class HyperlaneModuleClient extends StargateClient { ); return Uint53.fromString(gasInfo?.gasUsed.toString() ?? '0').toNumber(); } + + public getCometClientOrFail(): CometClient { + const maybeClient = super.getCometClient(); + + if (!maybeClient) { + throw new Error('Failed to get CometClient'); + } + + return maybeClient; + } } diff --git a/typescript/cosmos-types/CHANGELOG.md b/typescript/cosmos-types/CHANGELOG.md index 7f35f022941..80d20e3701d 100644 --- a/typescript/cosmos-types/CHANGELOG.md +++ b/typescript/cosmos-types/CHANGELOG.md @@ -1,5 +1,19 @@ # @hyperlane-xyz/cosmos-types +## 18.2.0 + +## 18.1.0 + +## 18.0.0 + +## 17.0.0 + +## 16.2.0 + +## 16.1.1 + +## 16.1.0 + ## 16.0.0 ## 15.0.0 diff --git a/typescript/cosmos-types/package.json b/typescript/cosmos-types/package.json index e0bff6df3c3..52a37d64269 100644 --- a/typescript/cosmos-types/package.json +++ b/typescript/cosmos-types/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/cosmos-types", - "version": "16.0.0", + "version": "18.2.0", "description": "Hyperlane TypeScript SDK types for the Cosmos Hyperlane SDK module", "type": "module", "exports": { diff --git a/typescript/eslint-config/CHANGELOG.md b/typescript/eslint-config/CHANGELOG.md index 618e0719a9a..986bd5303b7 100644 --- a/typescript/eslint-config/CHANGELOG.md +++ b/typescript/eslint-config/CHANGELOG.md @@ -1,5 +1,19 @@ # @hyperlane-xyz/eslint-config +## 18.2.0 + +## 18.1.0 + +## 18.0.0 + +## 17.0.0 + +## 16.2.0 + +## 16.1.1 + +## 16.1.0 + ## 16.0.0 ### Patch Changes diff --git a/typescript/eslint-config/package.json b/typescript/eslint-config/package.json index 3a487a4a665..6171200214b 100644 --- a/typescript/eslint-config/package.json +++ b/typescript/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/eslint-config", - "version": "16.0.0", + "version": "18.2.0", "description": "Hyperlane ESLint config", "private": true, "type": "module", diff --git a/typescript/github-proxy/CHANGELOG.md b/typescript/github-proxy/CHANGELOG.md index 69cd03cbfde..c08c031f37c 100644 --- a/typescript/github-proxy/CHANGELOG.md +++ b/typescript/github-proxy/CHANGELOG.md @@ -1,5 +1,19 @@ # @hyperlane-xyz/github-proxy +## 18.2.0 + +## 18.1.0 + +## 18.0.0 + +## 17.0.0 + +## 16.2.0 + +## 16.1.1 + +## 16.1.0 + ## 16.0.0 ## 15.0.0 diff --git a/typescript/github-proxy/package.json b/typescript/github-proxy/package.json index 723780a9d51..c3d5db466fa 100644 --- a/typescript/github-proxy/package.json +++ b/typescript/github-proxy/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/github-proxy", "description": "Github proxy that adds the API key to requests", - "version": "16.0.0", + "version": "18.2.0", "private": true, "scripts": { "deploy": "wrangler deploy", diff --git a/typescript/helloworld/CHANGELOG.md b/typescript/helloworld/CHANGELOG.md index b052f8e2697..3b4517fbd87 100644 --- a/typescript/helloworld/CHANGELOG.md +++ b/typescript/helloworld/CHANGELOG.md @@ -1,5 +1,72 @@ # @hyperlane-xyz/helloworld +## 18.2.0 + +### Patch Changes + +- Updated dependencies [fed6906e4] +- Updated dependencies [ca64e73cd] +- Updated dependencies [dfa9d368c] + - @hyperlane-xyz/sdk@18.2.0 + - @hyperlane-xyz/core@9.0.9 + +## 18.1.0 + +### Patch Changes + +- Updated dependencies [73be9b8d2] + - @hyperlane-xyz/sdk@18.1.0 + - @hyperlane-xyz/core@9.0.8 + +## 18.0.0 + +### Patch Changes + +- Updated dependencies [552b253b9] +- Updated dependencies [ba832828f] + - @hyperlane-xyz/sdk@18.0.0 + - @hyperlane-xyz/core@9.0.7 + +## 17.0.0 + +### Patch Changes + +- Updated dependencies [400c02460] +- Updated dependencies [8c15edc67] +- Updated dependencies [76a5db49a] +- Updated dependencies [6583df016] +- Updated dependencies [7f542b288] + - @hyperlane-xyz/sdk@17.0.0 + - @hyperlane-xyz/core@9.0.6 + +## 16.2.0 + +### Patch Changes + +- Updated dependencies [22ceaa109] +- Updated dependencies [ce4974214] +- Updated dependencies [a89018a3f] + - @hyperlane-xyz/sdk@16.2.0 + - @hyperlane-xyz/core@9.0.5 + +## 16.1.1 + +### Patch Changes + +- Updated dependencies [ea77b6ae4] + - @hyperlane-xyz/sdk@16.1.1 + - @hyperlane-xyz/core@9.0.4 + +## 16.1.0 + +### Patch Changes + +- Updated dependencies [2a2c29c39] +- Updated dependencies [e69ac9f62] +- Updated dependencies [d9b8a7551] + - @hyperlane-xyz/sdk@16.1.0 + - @hyperlane-xyz/core@9.0.3 + ## 16.0.0 ### Patch Changes diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index e85aea1027a..72351b6aedd 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -1,11 +1,11 @@ { "name": "@hyperlane-xyz/helloworld", "description": "A basic skeleton of an Hyperlane app", - "version": "16.0.0", + "version": "18.2.0", "dependencies": { - "@hyperlane-xyz/core": "9.0.2", + "@hyperlane-xyz/core": "9.0.9", "@hyperlane-xyz/registry": "20.0.0", - "@hyperlane-xyz/sdk": "16.0.0", + "@hyperlane-xyz/sdk": "18.2.0", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.8.0" }, diff --git a/typescript/http-registry-server/CHANGELOG.md b/typescript/http-registry-server/CHANGELOG.md index 3a20213eab0..332b51dc33f 100644 --- a/typescript/http-registry-server/CHANGELOG.md +++ b/typescript/http-registry-server/CHANGELOG.md @@ -1,5 +1,74 @@ # @hyperlane-xyz/http-registry-server +## 18.2.0 + +### Patch Changes + +- Updated dependencies [fed6906e4] +- Updated dependencies [ca64e73cd] +- Updated dependencies [dfa9d368c] + - @hyperlane-xyz/sdk@18.2.0 + - @hyperlane-xyz/utils@18.2.0 + +## 18.1.0 + +### Patch Changes + +- Updated dependencies [73be9b8d2] + - @hyperlane-xyz/sdk@18.1.0 + - @hyperlane-xyz/utils@18.1.0 + +## 18.0.0 + +### Patch Changes + +- Updated dependencies [552b253b9] +- Updated dependencies [ba832828f] +- Updated dependencies [cfc0eb2a7] + - @hyperlane-xyz/sdk@18.0.0 + - @hyperlane-xyz/utils@18.0.0 + +## 17.0.0 + +### Patch Changes + +- Updated dependencies [400c02460] +- Updated dependencies [8c15edc67] +- Updated dependencies [76a5db49a] +- Updated dependencies [6583df016] +- Updated dependencies [e0bda316a] +- Updated dependencies [7f542b288] + - @hyperlane-xyz/sdk@17.0.0 + - @hyperlane-xyz/utils@17.0.0 + +## 16.2.0 + +### Patch Changes + +- Updated dependencies [22ceaa109] +- Updated dependencies [ce4974214] +- Updated dependencies [a89018a3f] + - @hyperlane-xyz/sdk@16.2.0 + - @hyperlane-xyz/utils@16.2.0 + +## 16.1.1 + +### Patch Changes + +- Updated dependencies [ea77b6ae4] + - @hyperlane-xyz/sdk@16.1.1 + - @hyperlane-xyz/utils@16.1.1 + +## 16.1.0 + +### Patch Changes + +- Updated dependencies [2a2c29c39] +- Updated dependencies [e69ac9f62] +- Updated dependencies [d9b8a7551] + - @hyperlane-xyz/sdk@16.1.0 + - @hyperlane-xyz/utils@16.1.0 + ## 16.0.0 ### Minor Changes diff --git a/typescript/http-registry-server/package.json b/typescript/http-registry-server/package.json index 886379e4290..ff025c59535 100644 --- a/typescript/http-registry-server/package.json +++ b/typescript/http-registry-server/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/http-registry-server", - "version": "16.0.0", + "version": "18.2.0", "private": true, "description": "An HTTP server for the Hyperlane registry", "license": "Apache-2.0", @@ -26,8 +26,8 @@ }, "dependencies": { "@hyperlane-xyz/registry": "20.0.0", - "@hyperlane-xyz/sdk": "16.0.0", - "@hyperlane-xyz/utils": "16.0.0", + "@hyperlane-xyz/sdk": "18.2.0", + "@hyperlane-xyz/utils": "18.2.0", "express": "^5.1.0", "pino": "^8.19.0", "zod": "^3.21.2", diff --git a/typescript/infra/CHANGELOG.md b/typescript/infra/CHANGELOG.md index 08cb94e6abf..b28c6ffa26a 100644 --- a/typescript/infra/CHANGELOG.md +++ b/typescript/infra/CHANGELOG.md @@ -1,5 +1,92 @@ # @hyperlane-xyz/infra +## 18.2.0 + +### Minor Changes + +- ca64e73cd: Update the oXAUT bridge limits for avax, celo, ethereum, worldchain and base config. Export XERC20LimitsTokenConfig. + +### Patch Changes + +- Updated dependencies [fed6906e4] +- Updated dependencies [ca64e73cd] +- Updated dependencies [dfa9d368c] + - @hyperlane-xyz/sdk@18.2.0 + - @hyperlane-xyz/helloworld@18.2.0 + - @hyperlane-xyz/utils@18.2.0 + +## 18.1.0 + +### Minor Changes + +- d7c5f30df: Add radix USDC warp route getter + +### Patch Changes + +- Updated dependencies [73be9b8d2] + - @hyperlane-xyz/sdk@18.1.0 + - @hyperlane-xyz/helloworld@18.1.0 + - @hyperlane-xyz/utils@18.1.0 + +## 18.0.0 + +### Patch Changes + +- 0bcb90abc: set token decimals in more warp config getters to satisfy warp checker rules +- cb908b8aa: set token decimals in warp config getters to satisfy stricter warp checker rules +- Updated dependencies [552b253b9] +- Updated dependencies [ba832828f] +- Updated dependencies [cfc0eb2a7] + - @hyperlane-xyz/sdk@18.0.0 + - @hyperlane-xyz/utils@18.0.0 + - @hyperlane-xyz/helloworld@18.0.0 + +## 17.0.0 + +### Patch Changes + +- b23bcd111: Fix: Corrected logger usage by avoiding destructured debug method calls +- Updated dependencies [400c02460] +- Updated dependencies [8c15edc67] +- Updated dependencies [76a5db49a] +- Updated dependencies [6583df016] +- Updated dependencies [e0bda316a] +- Updated dependencies [7f542b288] + - @hyperlane-xyz/sdk@17.0.0 + - @hyperlane-xyz/utils@17.0.0 + - @hyperlane-xyz/helloworld@17.0.0 + +## 16.2.0 + +### Patch Changes + +- Updated dependencies [22ceaa109] +- Updated dependencies [ce4974214] +- Updated dependencies [a89018a3f] + - @hyperlane-xyz/sdk@16.2.0 + - @hyperlane-xyz/helloworld@16.2.0 + - @hyperlane-xyz/utils@16.2.0 + +## 16.1.1 + +### Patch Changes + +- Updated dependencies [ea77b6ae4] + - @hyperlane-xyz/sdk@16.1.1 + - @hyperlane-xyz/helloworld@16.1.1 + - @hyperlane-xyz/utils@16.1.1 + +## 16.1.0 + +### Patch Changes + +- Updated dependencies [2a2c29c39] +- Updated dependencies [e69ac9f62] +- Updated dependencies [d9b8a7551] + - @hyperlane-xyz/sdk@16.1.0 + - @hyperlane-xyz/helloworld@16.1.0 + - @hyperlane-xyz/utils@16.1.0 + ## 16.0.0 ### Patch Changes diff --git a/typescript/infra/config/contexts.ts b/typescript/infra/config/contexts.ts index 51d6c45d8b3..309b1c7601d 100644 --- a/typescript/infra/config/contexts.ts +++ b/typescript/infra/config/contexts.ts @@ -3,12 +3,6 @@ export enum Contexts { Hyperlane = 'hyperlane', ReleaseCandidate = 'rc', Neutron = 'neutron', - Vanguard0 = 'vanguard0', - Vanguard1 = 'vanguard1', - Vanguard2 = 'vanguard2', - Vanguard3 = 'vanguard3', - Vanguard4 = 'vanguard4', - Vanguard5 = 'vanguard5', } function isValidContext(context: string): context is Contexts { diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index c0b462676f9..ad59a7fd889 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -66,10 +66,11 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< > = { // Generally, we run all production validators in the Hyperlane context. [Role.Validator]: { + sepolia: false, // Dymension abstract: true, // acala: true, ancient8: true, - alephzeroevmmainnet: true, + alephzeroevmmainnet: false, apechain: true, appchain: true, arbitrum: true, @@ -87,37 +88,32 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< bob: true, boba: true, botanix: true, - bouncebit: true, bsc: true, bsquared: true, + celestia: true, celo: true, cheesechain: true, chilizmainnet: true, - conflux: true, - conwai: true, coredao: true, coti: true, cyber: true, - deepbrainchain: true, degenchain: true, dogechain: true, - duckchain: true, eclipsemainnet: true, + electroneum: true, endurance: true, ethereum: true, everclear: true, - evmos: true, fantom: true, - flame: true, flare: true, flowmainnet: true, fluence: true, form: true, - // fractal: false, + forma: false, // relayer + scraper only fraxtal: true, fusemainnet: true, galactica: true, - game7: true, + game7: false, gnosis: true, gravity: true, harmony: true, @@ -126,7 +122,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< hyperevm: true, immutablezkevmmainnet: true, inevm: true, - infinityvmmainnet: true, + infinityvmmainnet: false, injective: true, ink: true, kaia: true, @@ -135,10 +131,10 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< linea: true, lisk: true, lukso: true, - lumia: true, lumiaprism: true, mantapacific: true, mantle: true, + mantra: true, matchain: true, merlin: true, metal: true, @@ -146,11 +142,11 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< milkyway: true, mint: true, miraclechain: true, + mitosis: true, mode: true, molten: true, moonbeam: true, morph: true, - nero: true, neutron: true, nibiru: true, noble: true, @@ -162,19 +158,19 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< osmosis: true, paradex: true, peaq: true, + plasma: true, plume: true, polygon: true, polygonzkevm: true, polynomialfi: true, prom: true, proofofplay: true, + pulsechain: true, + radix: true, rarichain: true, reactive: true, redstone: true, - rivalz: true, ronin: true, - rootstockmainnet: true, - sanko: true, scroll: true, sei: true, shibarium: true, @@ -186,6 +182,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< sonicsvm: true, soon: true, sophon: true, + sova: true, starknet: true, story: true, stride: false, @@ -197,29 +194,27 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< tac: true, taiko: true, tangle: true, - telos: true, torus: true, unichain: true, - unitzero: true, vana: true, viction: true, worldchain: true, xai: true, xlayer: true, - xpla: true, xrplevm: true, + zerogravity: true, zeronetwork: true, zetachain: true, zircuit: true, - zklink: true, zksync: true, zoramainnet: true, }, [Role.Relayer]: { + sepolia: false, // Dymension abstract: true, // acala: true, ancient8: true, - alephzeroevmmainnet: true, + alephzeroevmmainnet: false, apechain: true, appchain: true, arcadia: true, @@ -237,37 +232,32 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< bob: true, boba: true, botanix: true, - bouncebit: true, bsc: true, bsquared: true, + celestia: true, celo: true, cheesechain: true, chilizmainnet: true, - conflux: true, - conwai: true, coredao: true, coti: true, cyber: true, - deepbrainchain: true, degenchain: true, dogechain: true, - duckchain: true, eclipsemainnet: true, + electroneum: true, endurance: true, ethereum: true, everclear: true, - evmos: true, fantom: true, - flame: true, flare: true, flowmainnet: true, fluence: true, form: true, - // fractal: false, + forma: true, fraxtal: true, fusemainnet: true, galactica: true, - game7: true, + game7: false, gnosis: true, gravity: true, harmony: true, @@ -276,7 +266,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< hyperevm: true, immutablezkevmmainnet: true, inevm: true, - infinityvmmainnet: true, + infinityvmmainnet: false, injective: true, ink: true, kaia: true, @@ -285,10 +275,10 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< linea: true, lisk: true, lukso: true, - lumia: true, lumiaprism: true, mantapacific: true, mantle: true, + mantra: true, matchain: true, merlin: true, metal: true, @@ -296,11 +286,11 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< milkyway: true, mint: true, miraclechain: true, + mitosis: true, mode: true, molten: true, moonbeam: true, morph: true, - nero: true, neutron: true, nibiru: true, noble: true, @@ -312,19 +302,19 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< osmosis: true, paradex: true, peaq: true, + plasma: true, plume: true, polygon: true, polygonzkevm: true, polynomialfi: true, prom: true, proofofplay: true, + pulsechain: true, + radix: true, rarichain: true, reactive: true, redstone: true, - rivalz: true, ronin: true, - rootstockmainnet: true, - sanko: true, scroll: true, sei: true, shibarium: true, @@ -336,6 +326,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< sonicsvm: true, soon: true, sophon: true, + sova: true, starknet: true, story: true, stride: true, @@ -347,29 +338,27 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< tac: true, taiko: true, tangle: true, - telos: true, torus: true, unichain: true, - unitzero: true, vana: true, viction: true, worldchain: true, xai: true, xlayer: true, - xpla: true, xrplevm: true, + zerogravity: true, zeronetwork: true, zetachain: true, zircuit: true, - zklink: true, zksync: true, zoramainnet: true, }, [Role.Scraper]: { + sepolia: false, // Dymension abstract: true, // acala: true, ancient8: true, - alephzeroevmmainnet: true, + alephzeroevmmainnet: false, apechain: true, appchain: true, arbitrum: true, @@ -387,37 +376,32 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< bob: true, boba: true, botanix: true, - bouncebit: true, bsc: true, bsquared: true, + celestia: true, celo: true, cheesechain: true, chilizmainnet: true, - conflux: true, - conwai: true, coredao: true, coti: true, cyber: true, - deepbrainchain: true, degenchain: true, dogechain: true, - duckchain: true, eclipsemainnet: true, + electroneum: true, endurance: true, ethereum: true, everclear: true, - evmos: true, fantom: true, - flame: true, flare: true, flowmainnet: true, fluence: true, form: true, - // fractal: false, + forma: true, fraxtal: true, fusemainnet: true, galactica: true, - game7: true, + game7: false, gnosis: true, gravity: true, harmony: true, @@ -426,7 +410,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< hyperevm: true, immutablezkevmmainnet: true, inevm: true, - infinityvmmainnet: true, + infinityvmmainnet: false, ink: true, injective: true, kaia: true, @@ -435,10 +419,10 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< linea: true, lisk: true, lukso: true, - lumia: true, lumiaprism: true, mantapacific: true, mantle: true, + mantra: true, matchain: true, merlin: true, metal: true, @@ -446,11 +430,11 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< milkyway: true, mint: true, miraclechain: true, + mitosis: true, mode: true, molten: true, moonbeam: true, morph: true, - nero: true, neutron: true, nibiru: true, noble: true, @@ -462,19 +446,19 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< osmosis: true, paradex: true, peaq: true, + plasma: true, plume: true, polygon: true, polygonzkevm: true, polynomialfi: true, prom: true, proofofplay: true, + pulsechain: true, + radix: true, rarichain: true, reactive: true, redstone: true, - rivalz: true, ronin: true, - rootstockmainnet: true, - sanko: true, scroll: true, sei: true, shibarium: true, @@ -486,6 +470,7 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< sonicsvm: true, soon: true, sophon: true, + sova: true, starknet: true, story: true, stride: true, @@ -497,22 +482,19 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< tac: true, taiko: true, tangle: true, - telos: true, torus: true, unichain: true, - unitzero: true, vana: true, // Has RPC non-compliance that breaks scraping. viction: false, worldchain: true, xai: true, xlayer: true, - xpla: true, xrplevm: true, + zerogravity: true, zeronetwork: true, zetachain: true, zircuit: true, - zklink: true, zksync: true, zoramainnet: true, }, @@ -520,7 +502,6 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< // Chains not in our core set of supported chains, and supported ONLY by the scraper export const scraperOnlyChains: BaseScraperConfig['scraperOnlyChains'] = { - forma: true, edgenchain: true, }; @@ -597,20 +578,20 @@ const veloMessageModuleMatchingList = consistentSenderRecipientMatchingList( ); // ICA v2 deploys that superswaps make use of -const superswapIcaV2MatchingList = chainMapMatchingList({ - base: '0x44647Cd983E80558793780f9a0c7C2aa9F384D07', - bob: '0xA6f0A37DFDe9C2c8F46F010989C47d9edB3a9FA8', - celo: '0x1eA7aC243c398671194B7e2C51d76d1a1D312953', - fraxtal: '0xD59a200cCEc5b3b1bF544dD7439De452D718f594', - ink: '0x55Ba00F1Bac2a47e0A73584d7c900087642F9aE3', - lisk: '0xE59592a179c4f436d5d2e4caA6e2750beA4E3166', - metal: '0x0b2d429acccAA411b867d57703F88Ed208eC35E4', - mode: '0x860ec58b115930EcbC53EDb8585C1B16AFFF3c50', - optimism: '0x3E343D07D024E657ECF1f8Ae8bb7a12f08652E75', - soneium: '0xc08C1451979e9958458dA3387E92c9Feb1571f9C', - superseed: '0x3CA0e8AEfC14F962B13B40c6c4b9CEE3e4927Ae3', - swell: '0x95Fb6Ca1BBF441386b119ad097edcAca3b1C35B7', - unichain: '0x43320f6B410322Bf5ca326a0DeAaa6a2FC5A021B', +const superswapIcaV2MatchingList = senderMatchingList({ + base: { sender: '0x44647Cd983E80558793780f9a0c7C2aa9F384D07' }, + bob: { sender: '0xA6f0A37DFDe9C2c8F46F010989C47d9edB3a9FA8' }, + celo: { sender: '0x1eA7aC243c398671194B7e2C51d76d1a1D312953' }, + fraxtal: { sender: '0xD59a200cCEc5b3b1bF544dD7439De452D718f594' }, + ink: { sender: '0x55Ba00F1Bac2a47e0A73584d7c900087642F9aE3' }, + lisk: { sender: '0xE59592a179c4f436d5d2e4caA6e2750beA4E3166' }, + metal: { sender: '0x0b2d429acccAA411b867d57703F88Ed208eC35E4' }, + mode: { sender: '0x860ec58b115930EcbC53EDb8585C1B16AFFF3c50' }, + optimism: { sender: '0x3E343D07D024E657ECF1f8Ae8bb7a12f08652E75' }, + soneium: { sender: '0xc08C1451979e9958458dA3387E92c9Feb1571f9C' }, + superseed: { sender: '0x3CA0e8AEfC14F962B13B40c6c4b9CEE3e4927Ae3' }, + swell: { sender: '0x95Fb6Ca1BBF441386b119ad097edcAca3b1C35B7' }, + unichain: { sender: '0x43320f6B410322Bf5ca326a0DeAaa6a2FC5A021B' }, }); const gasPaymentEnforcement: GasPaymentEnforcement[] = [ @@ -625,6 +606,8 @@ const gasPaymentEnforcement: GasPaymentEnforcement[] = [ { originDomain: getDomainId('noble') }, { originDomain: getDomainId('starknet') }, { originDomain: getDomainId('paradex') }, + // Not a core chain + { originDomain: getDomainId('forma') }, ], }, { @@ -635,6 +618,8 @@ const gasPaymentEnforcement: GasPaymentEnforcement[] = [ { destinationDomain: getDomainId('mantle') }, // Temporary workaround due to funky Torus gas amounts. { destinationDomain: getDomainId('torus') }, + // Not a core chain + { destinationDomain: getDomainId('forma') }, // Infinity VM is gasless, so enforcing min 1 wei here ensures outbound txs // outside of Solana are ignored. { originDomain: getDomainId('infinityvmmainnet') }, @@ -684,13 +669,6 @@ const stagingStHyperMatchingList = chainMapMatchingList({ ethereum: '0x0C919509663cb273E156B706f065b9F7e6331891', }); -const vanguardMatchingList = [ - ...hyperMatchingList, - ...stHyperMatchingList, - ...stagingHyperMatchingList, - ...stagingStHyperMatchingList, -]; - // Gets metric app contexts, including: // - helloworld // - all warp routes defined in WarpRouteIds, using addresses from the registry @@ -788,6 +766,12 @@ const metricAppContextsGetter = (): MetricAppContext[] => { name: 'superswap_ica_v2', matchingList: superswapIcaV2MatchingList, }, + { + name: 'm0', + matchingList: consistentSenderRecipientMatchingList( + '0x36f586A30502AE3afb555b8aA4dCc05d233c2ecE', + ), + }, ]; }; @@ -818,6 +802,43 @@ const blacklist: MatchingList = [ ...blacklistedMessageIds.map((messageId) => ({ messageId, })), + // legacy forma routes we are not relaying + { + destinationDomain: getDomainId('forma'), + recipientAddress: [ + '0x4ca56fbecfe8431996c6b4ec8da140d4201338e8', + '0x0a5c7d4ee3d65b2581d5606f8081fc8d8be22319', + '0x832d26b6904ba7539248db4d58614251fd63dc05', + '0x74a26075fa2eec77936a56b0f9645d32a79b28af', + '0xfcee86f472d0c19fccdd3aedb89aa9cc0a1fb0d1', + ], + }, + // legacy forma routes we are not relaying + { + originDomain: getDomainId('forma'), + senderAddress: [ + '0x6052c5c075212f013c856bff015872914ed3492a', + '0xd5ebc5e5f38c2d8c91c43122a105327c1f0260b4', + ], + }, + // routes on legacy pulsechain mailbox + { + destinationDomain: getDomainId('pulsechain'), + recipientAddress: [ + '0x688B161745c4eCEc2Ce07DC385cF2B57D9980244', + '0x50d03965Ed30de5d246BdDa18E7B10A8904B8CF1', + '0xD110Cbf543c3b237aB3EC171D7117932A4807F9B', + '0x87353B9AA546F8cf7290DeD2d339C0Ec694d7144', + '0x5DD749B87FA2e456482adb231FB9c2b0302A3027', + '0xe227B51F5D7079fAa07b7621657e3aa5906d2185', + '0x867c1fd9341DEC12e4B779C35D7b7C475316b334', + ], + }, + // StarkNet<>StarkNet messages + { + originDomain: getDomainId('starknet'), + destinationDomain: getDomainId('starknet'), + }, ]; const ismCacheConfigs: Array = [ @@ -848,7 +869,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '2fe58b3-20250718-114304', + tag: '34424c5-20250922-102920', }, blacklist, gasPaymentEnforcement: gasPaymentEnforcement, @@ -868,7 +889,7 @@ const hyperlane: RootAgentConfig = { validators: { docker: { repo, - tag: '5291797-20250701-134531', + tag: '4359931-20250921-174119', }, rpcConsensusType: RpcConsensusType.Quorum, chains: validatorChainConfig(Contexts.Hyperlane), @@ -879,7 +900,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '5291797-20250701-134531', + tag: '4359931-20250921-174119', }, resources: scraperResources, }, @@ -894,7 +915,7 @@ const releaseCandidate: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '2fe58b3-20250718-114304', + tag: '34424c5-20250922-102920', }, blacklist, // We're temporarily (ab)using the RC relayer as a way to increase @@ -917,7 +938,7 @@ const releaseCandidate: RootAgentConfig = { validators: { docker: { repo, - tag: '5291797-20250701-134531', + tag: '093e0be-20250904-212216', }, rpcConsensusType: RpcConsensusType.Quorum, chains: validatorChainConfig(Contexts.ReleaseCandidate), @@ -938,7 +959,7 @@ const neutron: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '5291797-20250701-134531', + tag: 'd21f3b3-20250806-144826', }, blacklist, gasPaymentEnforcement, @@ -957,79 +978,8 @@ const neutron: RootAgentConfig = { }, }; -const getVanguardRootAgentConfig = (index: number): RootAgentConfig => ({ - ...contextBase, - context: mustBeValidContext(`vanguard${index}`), - contextChainNames: { - validator: [], - relayer: ['bsc', 'arbitrum', 'optimism', 'ethereum', 'base'], - scraper: [], - }, - rolesWithKeys: [Role.Relayer], - relayer: { - rpcConsensusType: RpcConsensusType.Fallback, - docker: { - repo, - // includes gasPriceCap overrides + per-chain maxSubmitQueueLength - tag: '420c950-20250612-172436', - }, - whitelist: vanguardMatchingList, - // Not specifying a blacklist for optimization purposes -- all the message IDs - // in there are not vanguard-specific. - gasPaymentEnforcement: [ - { - type: GasPaymentEnforcementPolicyType.None, - matchingList: vanguardMatchingList, - }, - ], - metricAppContextsGetter, - ismCacheConfigs, - cache: { - enabled: true, - // Cache for 10 minutes - defaultExpirationSeconds: 10 * 60, - }, - resources: { - requests: { - // Big enough to claim a c3-standard-44 each - cpu: '35000m', - memory: '100Gi', - }, - }, - dbBootstrap: true, - mixing: { - enabled: true, - // Arbitrary salt to ensure different agents have different sorting behavior for pending messages - salt: 69690 + index, - }, - batch: { - defaultBatchSize: 32, - batchSizeOverrides: { - // Slightly lower to ideally fit within 5M - ethereum: 26, - }, - bypassBatchSimulation: true, - maxSubmitQueueLength: { - arbitrum: 350, - base: 350, - bsc: 350, - optimism: 350, - ethereum: 75, - }, - }, - txIdIndexingEnabled: false, - igpIndexingEnabled: false, - }, -}); - export const agents = { [Contexts.Hyperlane]: hyperlane, [Contexts.ReleaseCandidate]: releaseCandidate, [Contexts.Neutron]: neutron, - [Contexts.Vanguard0]: getVanguardRootAgentConfig(0), - [Contexts.Vanguard1]: getVanguardRootAgentConfig(1), - [Contexts.Vanguard2]: getVanguardRootAgentConfig(2), - [Contexts.Vanguard3]: getVanguardRootAgentConfig(3), - [Contexts.Vanguard4]: getVanguardRootAgentConfig(4), - [Contexts.Vanguard5]: getVanguardRootAgentConfig(5), }; diff --git a/typescript/infra/config/environments/mainnet3/aw-validators/hyperlane.json b/typescript/infra/config/environments/mainnet3/aw-validators/hyperlane.json index 50bcb948151..5744188d2d0 100644 --- a/typescript/infra/config/environments/mainnet3/aw-validators/hyperlane.json +++ b/typescript/infra/config/environments/mainnet3/aw-validators/hyperlane.json @@ -2,15 +2,12 @@ "abstract": { "validators": ["0x2ef8ece5b51562e65970c7d36007baa43a1de685"] }, - "zoramainnet": { - "validators": ["0x35130945b625bb69b28aee902a3b9a76fa67125f"] + "forma": { + "validators": [] }, "ancient8": { "validators": ["0xbb5842ae0e05215b53df4787a29144efb7e67551"] }, - "alephzeroevmmainnet": { - "validators": ["0x33f20e6e775747d60301c6ea1c50e51f0389740c"] - }, "apechain": { "validators": ["0x773d7fe6ffb1ba4de814c28044ff9a2d83a48221"] }, @@ -18,11 +15,7 @@ "validators": ["0x0531251bbadc1f9f19ccce3ca6b3f79f08eae1be"] }, "arbitrum": { - "validators": [ - "0x4d966438fe9e2b1e7124c87bbb90cb4f0f6c59a1", - "0x6333e110b8a261cab28acb43030bcde59f26978a", - "0x3369e12edd52570806f126eb50be269ba5e65843" - ] + "validators": ["0x4d966438fe9e2b1e7124c87bbb90cb4f0f6c59a1"] }, "arbitrumnova": { "validators": ["0xd2a5e9123308d187383c87053811a2c21bd8af1f"] @@ -40,21 +33,13 @@ "validators": ["0x37105aec3ff37c7bb0abdb0b1d75112e1e69fa86"] }, "avalanche": { - "validators": [ - "0x3fb8263859843bffb02950c492d492cae169f4cf", - "0xe58c63ad669b946e7c8211299f22679deecc9c83", - "0x6c754f1e9cd8287088b46a7c807303d55d728b49" - ] + "validators": ["0x3fb8263859843bffb02950c492d492cae169f4cf"] }, "b3": { "validators": ["0xd77b516730a836fc41934e7d5864e72c165b934e"] }, "base": { - "validators": [ - "0xb9453d675e0fa3c178a17b4ce1ad5b1a279b3af9", - "0x4512985a574cb127b2af2d4bb676876ce804e3f8", - "0xb144bb2f599a5af095bc30367856f27ea8a8adc7" - ] + "validators": ["0xb9453d675e0fa3c178a17b4ce1ad5b1a279b3af9"] }, "berachain": { "validators": ["0x0190915c55d9c7555e6d2cb838f04d18b5e2260e"] @@ -74,25 +59,17 @@ "botanix": { "validators": ["0xc944176bc4d4e5c7b0598884478a27a2b1904664"] }, - "bouncebit": { - "validators": ["0xaf38612d1e79ec67320d21c5f7e92419427cd154"] - }, "bsc": { - "validators": [ - "0x570af9b7b36568c8877eebba6c6727aa9dab7268", - "0x7bf928d5d262365d31d64eaa24755d48c3cae313", - "0x03047213365800f065356b4a2fe97c3c3a52296a" - ] + "validators": ["0x570af9b7b36568c8877eebba6c6727aa9dab7268"] }, "bsquared": { "validators": ["0xcadc90933c9fbe843358a4e70e46ad2db78e28aa"] }, + "celestia": { + "validators": ["0x6dbc192c06907784fb0af0c0c2d8809ea50ba675"] + }, "celo": { - "validators": [ - "0x63478422679303c3e4fc611b771fa4a707ef7f4a", - "0x2f4e808744df049d8acc050628f7bdd8265807f9", - "0x7bf30afcb6a7d92146d5a910ea4c154fba38d25e" - ] + "validators": ["0x63478422679303c3e4fc611b771fa4a707ef7f4a"] }, "cheesechain": { "validators": ["0x478fb53c6860ae8fc35235ba0d38d49b13128226"] @@ -100,12 +77,6 @@ "chilizmainnet": { "validators": ["0x7403e5d58b48b0f5f715d9c78fbc581f01a625cb"] }, - "conflux": { - "validators": ["0x113dfa1dc9b0a2efb6ad01981e2aad86d3658490"] - }, - "conwai": { - "validators": ["0x949e2cdd7e79f99ee9bbe549540370cdc62e73c3"] - }, "coredao": { "validators": ["0xbd6e158a3f5830d99d7d2bce192695bc4a148de2"] }, @@ -115,43 +86,30 @@ "cyber": { "validators": ["0x94d7119ceeb802173b6924e6cc8c4cd731089a27"] }, - "deepbrainchain": { - "validators": ["0x3825ea1e0591b58461cc4aa34867668260c0e6a8"] - }, "degenchain": { "validators": ["0x433e311f19524cd64fb2123ad0aa1579a4e1fc83"] }, "dogechain": { "validators": ["0xe43f742c37858746e6d7e458bc591180d0cba440"] }, - "duckchain": { - "validators": ["0x91d55fe6dac596a6735d96365e21ce4bca21d83c"] - }, "eclipsemainnet": { "validators": ["0xebb52d7eaa3ff7a5a6260bfe5111ce52d57401d0"] }, + "electroneum": { + "validators": ["0x32917f0a38c60ff5b1c4968cb40bc88b14ef0d83"] + }, "endurance": { "validators": ["0x28c5b322da06f184ebf68693c5d19df4d4af13e5"] }, "ethereum": { - "validators": [ - "0x03c842db86a6a3e524d4a6615390c1ea8e2b9541", - "0x4346776b10f5e0d9995d884b7a1dbaee4e24c016", - "0x749d6e7ad949e522c92181dc77f7bbc1c5d71506" - ] + "validators": ["0x03c842db86a6a3e524d4a6615390c1ea8e2b9541"] }, "everclear": { "validators": ["0xeff20ae3d5ab90abb11e882cfce4b92ea6c74837"] }, - "evmos": { - "validators": ["0x8f82387ad8b7b13aa9e06ed3f77f78a77713afe0"] - }, "fantom": { "validators": ["0xa779572028e634e16f26af5dfd4fa685f619457d"] }, - "flame": { - "validators": ["0x1fa928ce884fa16357d4b8866e096392d4d81f43"] - }, "flare": { "validators": ["0xb65e52be342dba3ab2c088ceeb4290c744809134"] }, @@ -173,15 +131,8 @@ "galactica": { "validators": ["0xfc48af3372d621f476c53d79d42a9e96ce11fd7d"] }, - "game7": { - "validators": ["0x691dc4e763514df883155df0952f847b539454c0"] - }, "gnosis": { - "validators": [ - "0xd4df66a859585678f2ea8357161d896be19cc1ca", - "0x06a833508579f8b59d756b3a1e72451fc70840c3", - "0xb93a72cee19402553c9dd7fed2461aebd04e2454" - ] + "validators": ["0xd4df66a859585678f2ea8357161d896be19cc1ca"] }, "gravity": { "validators": ["0x23d549bf757a02a6f6068e9363196ecd958c974e"] @@ -202,14 +153,7 @@ "validators": ["0xbdda85b19a5efbe09e52a32db1a072f043dd66da"] }, "inevm": { - "validators": [ - "0xf9e35ee88e4448a3673b4676a4e153e3584a08eb", - "0xae3e6bb6b3ece1c425aa6f47adc8cb0453c1f9a2", - "0xd98c9522cd9d3e3e00bee05ff76c34b91b266ec3" - ] - }, - "infinityvmmainnet": { - "validators": ["0x777c19c87aaa625486dff5aab0a479100f4249ad"] + "validators": ["0xf9e35ee88e4448a3673b4676a4e153e3584a08eb"] }, "injective": { "validators": ["0xbfb8911b72cfb138c7ce517c57d9c691535dc517"] @@ -235,22 +179,18 @@ "lukso": { "validators": ["0xa5e953701dcddc5b958b5defb677a829d908df6d"] }, - "lumia": { - "validators": ["0x9e283254ed2cd2c80f007348c2822fc8e5c2fa5f"] - }, "lumiaprism": { "validators": ["0xb69731640ffd4338a2c9358a935b0274c6463f85"] }, "mantapacific": { - "validators": [ - "0x8e668c97ad76d0e28375275c41ece4972ab8a5bc", - "0x80afdde2a81f3fb056fd088a97f0af3722dbc4f3", - "0x5dda0c4cf18de3b3ab637f8df82b24921082b54c" - ] + "validators": ["0x8e668c97ad76d0e28375275c41ece4972ab8a5bc"] }, "mantle": { "validators": ["0xf930636c5a1a8bf9302405f72e3af3c96ebe4a52"] }, + "mantra": { + "validators": ["0x89b8064e29f125e896f6081ebb77090c46bca9cd"] + }, "matchain": { "validators": ["0x8a052f7934b0626105f34f980c875ec03aaf82e8"] }, @@ -272,6 +212,9 @@ "miraclechain": { "validators": ["0x8fc655174e99194399822ce2d3a0f71d9fc2de7b"] }, + "mitosis": { + "validators": ["0x3b3eb808d90a4e19bb601790a6b6297812d6a61f"] + }, "mode": { "validators": ["0x7eb2e1920a4166c19d6884c1cec3d2cf356fc9b7"] }, @@ -279,24 +222,13 @@ "validators": ["0xad5aa33f0d67f6fa258abbe75458ea4908f1dc9f"] }, "moonbeam": { - "validators": [ - "0x2225e2f4e9221049456da93b71d2de41f3b6b2a8", - "0x4fe067bb455358e295bfcfb92519a6f9de94b98e", - "0xcc4a78aa162482bea43313cd836ba7b560b44fc4" - ] + "validators": ["0x2225e2f4e9221049456da93b71d2de41f3b6b2a8"] }, "morph": { "validators": ["0x4884535f393151ec419add872100d352f71af380"] }, - "nero": { - "validators": ["0xb86f872df37f11f33acbe75b6ed208b872b57183"] - }, "neutron": { - "validators": [ - "0xa9b8c1f4998f781f958c63cfcd1708d02f004ff0", - "0x60e890b34cb44ce3fa52f38684f613f31b47a1a6", - "0x7885fae56dbcf5176657f54adbbd881dc6714132" - ] + "validators": ["0xa9b8c1f4998f781f958c63cfcd1708d02f004ff0"] }, "nibiru": { "validators": ["0xba9779d84a8efba1c6bc66326d875c3611a24b24"] @@ -314,11 +246,7 @@ "validators": ["0x1bdf52749ef2411ab9c28742dea92f209e96c9c4"] }, "optimism": { - "validators": [ - "0x20349eadc6c72e94ce38268b96692b1a5c20de4f", - "0x04d040cee072272789e2d1f29aef73b3ad098db5", - "0x779a17e035018396724a6dec8a59bda1b5adf738" - ] + "validators": ["0x20349eadc6c72e94ce38268b96692b1a5c20de4f"] }, "orderly": { "validators": ["0xec3dc91f9fa2ad35edf5842aa764d5573b778bb6"] @@ -329,22 +257,17 @@ "peaq": { "validators": ["0x7f7fe70b676f65097e2a1e2683d0fc96ea8fea49"] }, + "plasma": { + "validators": ["0x4ba900a8549fe503bca674114dc98a254637fc2c"] + }, "plume": { "validators": ["0x63c9b5ea28710d956a51f0f746ee8df81215663f"] }, "polygon": { - "validators": [ - "0x12ecb319c7f4e8ac5eb5226662aeb8528c5cefac", - "0x8dd8f8d34b5ecaa5f66de24b01acd7b8461c3916", - "0xdbf3666de031bea43ec35822e8c33b9a9c610322" - ] + "validators": ["0x12ecb319c7f4e8ac5eb5226662aeb8528c5cefac"] }, "polygonzkevm": { - "validators": [ - "0x86f2a44592bb98da766e880cfd70d3bbb295e61a", - "0xc84076030bdabaabb9e61161d833dd84b700afda", - "0x6a1da2e0b7ae26aaece1377c0a4dbe25b85fa3ca" - ] + "validators": ["0x86f2a44592bb98da766e880cfd70d3bbb295e61a"] }, "polynomialfi": { "validators": ["0x23d348c2d365040e56f3fee07e6897122915f513"] @@ -355,6 +278,12 @@ "proofofplay": { "validators": ["0xcda40baa71970a06e5f55e306474de5ca4e21c3b"] }, + "pulsechain": { + "validators": ["0xa73fc7ebb2149d9c6992ae002cb1849696be895b"] + }, + "radix": { + "validators": ["0xa715a7cd97f68caeedb7be64f9e1da10f8ffafb4"] + }, "rarichain": { "validators": ["0xeac012df7530720dd7d6f9b727e4fe39807d1516"] }, @@ -364,24 +293,11 @@ "redstone": { "validators": ["0x1400b9737007f7978d8b4bbafb4a69c83f0641a7"] }, - "rivalz": { - "validators": ["0xf87c3eb3dde972257b0d6d110bdadcda951c0dc1"] - }, "ronin": { "validators": ["0xa3e11929317e4a871c3d47445ea7bb8c4976fd8a"] }, - "rootstockmainnet": { - "validators": ["0x8675eb603d62ab64e3efe90df914e555966e04ac"] - }, - "sanko": { - "validators": ["0x795c37d5babbc44094b084b0c89ed9db9b5fae39"] - }, "scroll": { - "validators": [ - "0xad557170a9f2f21c35e03de07cb30dcbcc3dff63", - "0xb37fe43a9f47b7024c2d5ae22526cc66b5261533", - "0x7210fa0a6be39a75cb14d682ebfb37e2b53ecbe5" - ] + "validators": ["0xad557170a9f2f21c35e03de07cb30dcbcc3dff63"] }, "sei": { "validators": ["0x9920d2dbf6c85ffc228fdc2e810bf895732c6aa5"] @@ -413,6 +329,9 @@ "sophon": { "validators": ["0xb84c5d02120ed0b39d0f78bbc0e298d89ebcd10b"] }, + "sova": { + "validators": ["0x1763d686c45df79f6d5f63564255546b08cb206c"] + }, "story": { "validators": ["0x501eda013378c60557d763df98d617b6ba55447a"] }, @@ -440,18 +359,12 @@ "tangle": { "validators": ["0x1ee52cbbfacd7dcb0ba4e91efaa6fbc61602b15b"] }, - "telos": { - "validators": ["0xcb08410b14d3adf0d0646f0c61cd07e0daba8e54"] - }, "torus": { "validators": ["0x96982a325c28a842bc8cf61b63000737bb9f1f7d"] }, "unichain": { "validators": ["0x9773a382342ebf604a2e5de0a1f462fb499e28b1"] }, - "unitzero": { - "validators": ["0x18818e3ad2012728465d394f2e3c0ea2357ae9c5"] - }, "vana": { "validators": ["0xfdf3b0dfd4b822d10cacb15c8ae945ea269e7534"] }, @@ -467,12 +380,12 @@ "xlayer": { "validators": ["0xa2ae7c594703e988f23d97220717c513db638ea3"] }, - "xpla": { - "validators": ["0xc11cba01d67f2b9f0288c4c8e8b23c0eca03f26e"] - }, "xrplevm": { "validators": ["0x14d3e2f28d60d54a1659a205cb71e6e440f06510"] }, + "zerogravity": { + "validators": ["0xc37e7dad064c11d7ecfc75813a4d8d649d797275"] + }, "zeronetwork": { "validators": ["0x1bd9e3f8a90ea1a13b0f2838a1858046368aad87"] }, @@ -482,10 +395,10 @@ "zircuit": { "validators": ["0x169ec400cc758fef3df6a0d6c51fbc6cdd1015bb"] }, - "zklink": { - "validators": ["0x217a8cb4789fc45abf56cb6e2ca96f251a5ac181"] - }, "zksync": { "validators": ["0xadd1d39ce7a687e32255ac457cf99a6d8c5b5d1a"] + }, + "zoramainnet": { + "validators": ["0x35130945b625bb69b28aee902a3b9a76fa67125f"] } } diff --git a/typescript/infra/config/environments/mainnet3/aw-validators/rc.json b/typescript/infra/config/environments/mainnet3/aw-validators/rc.json index fa23ca6b8c0..f93db7ff89f 100644 --- a/typescript/infra/config/environments/mainnet3/aw-validators/rc.json +++ b/typescript/infra/config/environments/mainnet3/aw-validators/rc.json @@ -178,9 +178,6 @@ "redstone": { "validators": ["0x51ed7127c0afc0513a0f141e910c5e02b2a9a4b5"] }, - "sanko": { - "validators": ["0xe12dcb5cab4a2979ac46c045fe7da3c93cf62808"] - }, "scroll": { "validators": [ "0x11387d89856219cf685f22781bf4e85e00468d54", diff --git a/typescript/infra/config/environments/mainnet3/balances/dailyRelayerBurn.json b/typescript/infra/config/environments/mainnet3/balances/dailyRelayerBurn.json index 22d9b3dca5f..06393335d6f 100644 --- a/typescript/infra/config/environments/mainnet3/balances/dailyRelayerBurn.json +++ b/typescript/infra/config/environments/mainnet3/balances/dailyRelayerBurn.json @@ -1,13 +1,13 @@ { "abstract": 0.00428, - "alephzeroevmmainnet": 114, + "alephzeroevmmainnet": 121, "ancient8": 0.00253, "apechain": 7.04, "appchain": 0.0155, "arbitrum": 0.0344, "arbitrumnova": 0.00195, "arcadia": 0.00195, - "artela": 3350, + "artela": 16900, "astar": 137, "aurora": 0.00214, "avalanche": 0.174, @@ -18,37 +18,33 @@ "blast": 0.00325, "bob": 0.00249, "boba": 0.00241, - "botanix": 0.0000293, - "bouncebit": 38.5, + "botanix": 0.000071, "bsc": 0.264, "bsquared": 0.00245, + "celestia": 1.89, "celo": 12.1, "cheesechain": 12400, "chilizmainnet": 89.1, - "conflux": 44.5, - "conwai": 1760, "coredao": 6.82, "coti": 60.5, "cyber": 0.00321, - "deepbrainchain": 6400, "degenchain": 1230, "dogechain": 19.7, - "duckchain": 1.05, "eclipsemainnet": 0.0232, + "electroneum": 964, "endurance": 6.29, "ethereum": 0.399, - "everclear": 0.0106, - "evmos": 994, - "fantom": 9.8, - "flame": 2.23, + "everclear": 0.0148, + "fantom": 10.4, "flare": 208, "flowmainnet": 9.51, "fluence": 3580, "form": 0.00195, + "forma": 2.1, "fraxtal": 0.0217, "fusemainnet": 315, "galactica": 3.13, - "game7": 768, + "game7": 7520, "gnosis": 3.12, "gravity": 278, "harmony": 313, @@ -62,28 +58,28 @@ "ink": 0.0233, "kaia": 31.1, "katana": 0.00127, - "kyve": 367, + "kyve": 414, "linea": 0.0765, "lisk": 0.0104, "lukso": 4.86, - "lumia": 13.5, "lumiaprism": 13.7, "mantapacific": 0.00336, "mantle": 7.51, + "mantra": 15.1, "matchain": 0.00537, "merlin": 0.000172, "metal": 0.00791, "metis": 0.229, - "milkyway": 58.1, + "milkyway": 65.1, "mint": 0.00272, - "miraclechain": 190, + "miraclechain": 312, + "mitosis": 59.1, "mode": 0.0229, "molten": 29.8, "moonbeam": 49.5, "morph": 0.178, - "nero": 3.13, "neutron": 36, - "nibiru": 230, + "nibiru": 304, "noble": 3.13, "ontology": 18.8, "oortmainnet": 79.8, @@ -93,56 +89,54 @@ "osmosis": 22, "paradex": 3.13, "peaq": 39.7, + "plasma": 3.13, "plume": 38.3, "polygon": 16.4, "polygonzkevm": 0.0244, "polynomialfi": 0.00275, "prom": 1.55, "proofofplay": 0.00195, + "pulsechain": 72700, + "radix": 595, "rarichain": 0.00329, "reactive": 123, "redstone": 0.00217, - "rivalz": 0.00195, "ronin": 7.04, - "rootstockmainnet": 0.00277, - "sanko": 0.532, - "scroll": 0.00951, + "scroll": 0.01, "sei": 18.4, - "shibarium": 15.7, + "shibarium": 19.4, "snaxchain": 0.00283, "solanamainnet": 3.27, - "solaxy": 6070, + "solaxy": 7170, "soneium": 0.0394, - "sonic": 9.8, + "sonic": 10.4, "sonicsvm": 0.193, "soon": 0.00195, - "sophon": 93.3, - "starknet": 27, + "sophon": 105, + "sova": 0.000695, + "starknet": 30.8, "story": 1.67, - "stride": 15.5, - "subtensor": 0.266, + "stride": 57.4, + "subtensor": 0.304, "superpositionmainnet": 0.0235, "superseed": 0.0126, "svmbnb": 0.00524, "swell": 0.00856, - "tac": 3.13, - "taiko": 0.005, + "tac": 8.15, + "taiko": 0.00671, "tangle": 3.13, - "telos": 85.1, "torus": 22.7, "unichain": 0.00897, - "unitzero": 23.3, "vana": 0.705, "viction": 19.5, "worldchain": 0.0027, "xai": 71.2, "xlayer": 0.114, - "xpla": 114, - "xrplevm": 1.43, + "xrplevm": 4.19, + "zerogravity": 3.13, "zeronetwork": 0.00195, "zetachain": 17.7, "zircuit": 0.00424, - "zklink": 0.00195, "zksync": 0.00195, "zoramainnet": 0.00876 } diff --git a/typescript/infra/config/environments/mainnet3/balances/desiredRebalancerBalances.json b/typescript/infra/config/environments/mainnet3/balances/desiredRebalancerBalances.json new file mode 100644 index 00000000000..d413e24d944 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/balances/desiredRebalancerBalances.json @@ -0,0 +1,9 @@ +{ + "arbitrum": 0.6, + "avalanche": 0.1, + "base": 0.2, + "ethereum": 0.3, + "optimism": 0.3, + "polygon": 130, + "unichain": 0.07 +} diff --git a/typescript/infra/config/environments/mainnet3/balances/desiredRelayerBalanceOverrides.json b/typescript/infra/config/environments/mainnet3/balances/desiredRelayerBalanceOverrides.json index 85fa39a3fe3..6a261f7e4cf 100644 --- a/typescript/infra/config/environments/mainnet3/balances/desiredRelayerBalanceOverrides.json +++ b/typescript/infra/config/environments/mainnet3/balances/desiredRelayerBalanceOverrides.json @@ -1,11 +1,12 @@ { "artela": 2200, "coti": 0.1, - "deepbrainchain": 100, - "ethereum": 4, "eclipsemainnet": 0.5, + "electroneum": 10, "fluence": 2000, + "forma": 5, "galactica": 1, + "game7": 1000, "infinityvmmainnet": 0, "kyve": 5, "miraclechain": 0.05, @@ -13,13 +14,16 @@ "ontology": 10, "osmosis": 0, "paradex": 0, + "plasma": 0.1, "plume": 0.05, + "radix": 0, "reactive": 0.1, "starknet": 200, "solaxy": 0.05, - "solanamainnet": 30, "sophon": 50, + "sova": 0.005, "tac": 5, "tangle": 6, - "vana": 0.001 + "vana": 0.001, + "zerogravity": 0.005 } diff --git a/typescript/infra/config/environments/mainnet3/balances/desiredRelayerBalances.json b/typescript/infra/config/environments/mainnet3/balances/desiredRelayerBalances.json index d4b0a5f20b2..a6f9d51b52b 100644 --- a/typescript/infra/config/environments/mainnet3/balances/desiredRelayerBalances.json +++ b/typescript/infra/config/environments/mainnet3/balances/desiredRelayerBalances.json @@ -1,6 +1,7 @@ { + "sepolia": 0, "abstract": 0.0342, - "alephzeroevmmainnet": 912, + "alephzeroevmmainnet": 968, "ancient8": 0.0202, "apechain": 56.3, "appchain": 0.124, @@ -18,37 +19,33 @@ "blast": 0.026, "bob": 0.0199, "boba": 0.0193, - "botanix": 0.000234, - "bouncebit": 308, + "botanix": 0.000568, "bsc": 2.11, "bsquared": 0.0196, + "celestia": 15.1, "celo": 96.8, "cheesechain": 99200, "chilizmainnet": 713, - "conflux": 356, - "conwai": 14100, "coredao": 54.6, "coti": 0.1, "cyber": 0.0257, - "deepbrainchain": 100, "degenchain": 9840, "dogechain": 158, - "duckchain": 8.4, "eclipsemainnet": 0.5, + "electroneum": 10, "endurance": 50.3, - "ethereum": 4, - "everclear": 0.0848, - "evmos": 7950, - "fantom": 78.4, - "flame": 17.8, + "ethereum": 3.19, + "everclear": 0.118, + "fantom": 83.2, "flare": 1660, "flowmainnet": 76.1, "fluence": 2000, "form": 0.0156, + "forma": 5, "fraxtal": 0.2, "fusemainnet": 2520, "galactica": 1, - "game7": 6140, + "game7": 1000, "gnosis": 25, "gravity": 2220, "harmony": 2500, @@ -66,22 +63,22 @@ "linea": 0.612, "lisk": 0.0832, "lukso": 38.9, - "lumia": 108, "lumiaprism": 110, "mantapacific": 0.0269, "mantle": 60.1, + "mantra": 121, "matchain": 0.05, "merlin": 0.002, "metal": 0.0633, "metis": 1.83, - "milkyway": 465, + "milkyway": 521, "mint": 0.0218, "miraclechain": 0.05, + "mitosis": 473, "mode": 0.2, "molten": 238, "moonbeam": 396, "morph": 1.42, - "nero": 25, "neutron": 288, "nibiru": 10, "noble": 25, @@ -93,56 +90,54 @@ "osmosis": 0, "paradex": 0, "peaq": 318, + "plasma": 0.1, "plume": 0.05, "polygon": 131, "polygonzkevm": 0.195, "polynomialfi": 0.022, "prom": 18, "proofofplay": 0.0156, + "pulsechain": 582000, + "radix": 0, "rarichain": 0.0263, "reactive": 0.1, "redstone": 0.0174, - "rivalz": 0.0156, "ronin": 56.3, - "rootstockmainnet": 0.0222, - "sanko": 4.26, - "scroll": 0.0761, + "scroll": 0.08, "sei": 147, - "shibarium": 126, + "shibarium": 155, "snaxchain": 0.0226, - "solanamainnet": 30, + "solanamainnet": 26.2, "solaxy": 0.05, "soneium": 0.315, - "sonic": 78.4, + "sonic": 83.2, "sonicsvm": 1.54, "soon": 0.0156, "sophon": 50, + "sova": 0.005, "starknet": 200, "story": 13.4, - "stride": 124, - "subtensor": 2.13, + "stride": 459, + "subtensor": 2.43, "superpositionmainnet": 0.188, "superseed": 0.101, - "svmbnb": 0.1, + "svmbnb": 0.0419, "swell": 0.0685, "tac": 5, - "taiko": 0.04, + "taiko": 0.0537, "tangle": 6, - "telos": 681, "torus": 182, "unichain": 0.0718, - "unitzero": 186, "vana": 0.001, "viction": 156, "worldchain": 0.0216, "xai": 570, "xlayer": 0.912, - "xpla": 912, - "xrplevm": 11.4, + "xrplevm": 33.5, + "zerogravity": 0.005, "zeronetwork": 0.0156, "zetachain": 142, "zircuit": 0.0339, - "zklink": 0.0156, "zksync": 0.0156, "zoramainnet": 0.0701 } diff --git a/typescript/infra/config/environments/mainnet3/balances/highUrgencyRelayerBalance.json b/typescript/infra/config/environments/mainnet3/balances/highUrgencyRelayerBalance.json index ffcedd24d83..49688c6b876 100644 --- a/typescript/infra/config/environments/mainnet3/balances/highUrgencyRelayerBalance.json +++ b/typescript/infra/config/environments/mainnet3/balances/highUrgencyRelayerBalance.json @@ -1,6 +1,6 @@ { "abstract": 0.00856, - "alephzeroevmmainnet": 228, + "alephzeroevmmainnet": 242, "ancient8": 0.00506, "apechain": 14.1, "appchain": 0.031, @@ -18,37 +18,33 @@ "blast": 0.0065, "bob": 0.00498, "boba": 0.00482, - "botanix": 0.0000586, - "bouncebit": 77, + "botanix": 0.000142, "bsc": 0.528, "bsquared": 0.0049, + "celestia": 3.78, "celo": 24.2, "cheesechain": 24800, "chilizmainnet": 178, - "conflux": 89, - "conwai": 3520, "coredao": 13.6, "coti": 0.025, "cyber": 0.00642, - "deepbrainchain": 25, "degenchain": 2460, "dogechain": 39.4, - "duckchain": 2.1, "eclipsemainnet": 0.125, + "electroneum": 2.5, "endurance": 12.6, - "ethereum": 1, - "everclear": 0.0212, - "evmos": 1990, - "fantom": 19.6, - "flame": 4.46, + "ethereum": 0.798, + "everclear": 0.0296, + "fantom": 20.8, "flare": 416, "flowmainnet": 19, "fluence": 500, "form": 0.0039, + "forma": 1.25, "fraxtal": 0.0434, "fusemainnet": 630, "galactica": 0.25, - "game7": 1540, + "game7": 250, "gnosis": 6.24, "gravity": 556, "harmony": 626, @@ -65,22 +61,22 @@ "linea": 0.153, "lisk": 0.0208, "lukso": 9.72, - "lumia": 27, "lumiaprism": 27.4, "mantapacific": 0.00672, "mantle": 15, + "mantra": 30.2, "matchain": 0.0107, "merlin": 0.000344, "metal": 0.0158, "metis": 0.458, - "milkyway": 116, + "milkyway": 130, "mint": 0.00544, "miraclechain": 0.0125, + "mitosis": 118, "mode": 0.0458, "molten": 59.6, "moonbeam": 99, "morph": 0.356, - "nero": 6.26, "neutron": 72, "nibiru": 2.5, "noble": 6.26, @@ -90,56 +86,53 @@ "optimism": 0.0766, "orderly": 0.00562, "peaq": 79.4, + "plasma": 0.025, "plume": 0.0125, "polygon": 32.8, "polygonzkevm": 0.0488, "polynomialfi": 0.0055, "prom": 3.1, "proofofplay": 0.0039, + "pulsechain": 145000, "rarichain": 0.00658, "reactive": 0.025, "redstone": 0.00434, - "rivalz": 0.0039, "ronin": 14.1, - "rootstockmainnet": 0.00554, - "sanko": 1.06, - "scroll": 0.019, + "scroll": 0.02, "sei": 36.8, - "shibarium": 31.4, + "shibarium": 38.8, "snaxchain": 0.00566, "solanamainnet": 7.5, "solaxy": 0.0125, "soneium": 0.0788, - "sonic": 19.6, + "sonic": 20.8, "sonicsvm": 0.5, "soon": 0.0039, "sophon": 12.5, + "sova": 0.00125, "starknet": 50, "story": 3.34, - "stride": 31, - "subtensor": 0.532, + "stride": 115, + "subtensor": 0.608, "superpositionmainnet": 0.047, "superseed": 0.0252, - "svmbnb": 0.025, + "svmbnb": 0.0105, "swell": 0.0171, "tac": 1.25, - "taiko": 0.01, + "taiko": 0.0134, "tangle": 1.5, - "telos": 170, "torus": 45.4, "unichain": 0.0179, - "unitzero": 46.6, "vana": 0.00025, "viction": 39, "worldchain": 0.0054, "xai": 142, "xlayer": 0.228, - "xpla": 228, - "xrplevm": 2.86, + "xrplevm": 8.38, + "zerogravity": 0.00125, "zeronetwork": 0.0039, "zetachain": 35.4, "zircuit": 0.0094, - "zklink": 0.0039, "zksync": 0.0039, "zoramainnet": 0.0175 } diff --git a/typescript/infra/config/environments/mainnet3/balances/lowUrgencyEngKeyFunderBalance.json b/typescript/infra/config/environments/mainnet3/balances/lowUrgencyEngKeyFunderBalance.json index 320e50b078f..2d55db3fef2 100644 --- a/typescript/infra/config/environments/mainnet3/balances/lowUrgencyEngKeyFunderBalance.json +++ b/typescript/infra/config/environments/mainnet3/balances/lowUrgencyEngKeyFunderBalance.json @@ -1,6 +1,6 @@ { "abstract": 0.0257, - "alephzeroevmmainnet": 684, + "alephzeroevmmainnet": 726, "ancient8": 0.0152, "apechain": 42.2, "appchain": 0.093, @@ -18,37 +18,33 @@ "blast": 0.0195, "bob": 0.0149, "boba": 0.0145, - "botanix": 0.000176, - "bouncebit": 231, + "botanix": 0.000426, "bsc": 1.58, "bsquared": 0.0147, + "celestia": 11.3, "celo": 72.6, "cheesechain": 74400, "chilizmainnet": 535, - "conflux": 267, - "conwai": 10600, "coredao": 40.9, "coti": 0.075, "cyber": 0.0193, - "deepbrainchain": 75, "degenchain": 7380, "dogechain": 118, - "duckchain": 6.3, "eclipsemainnet": 0.375, + "electroneum": 7.5, "endurance": 37.7, - "ethereum": 3, - "everclear": 0.0636, - "evmos": 5960, - "fantom": 58.8, - "flame": 13.4, + "ethereum": 2.39, + "everclear": 0.0888, + "fantom": 62.4, "flare": 1250, "flowmainnet": 57.1, "fluence": 1500, "form": 0.0117, + "forma": 3.75, "fraxtal": 0.13, "fusemainnet": 1890, "galactica": 0.75, - "game7": 4610, + "game7": 750, "gnosis": 18.7, "gravity": 1670, "harmony": 1880, @@ -65,22 +61,22 @@ "linea": 0.459, "lisk": 0.0624, "lukso": 29.2, - "lumia": 81, "lumiaprism": 82.2, "mantapacific": 0.0202, "mantle": 45.1, + "mantra": 90.6, "matchain": 0.0322, "merlin": 0.00103, "metal": 0.0475, "metis": 1.37, - "milkyway": 349, + "milkyway": 391, "mint": 0.0163, "miraclechain": 0.0375, + "mitosis": 355, "mode": 0.137, "molten": 179, "moonbeam": 297, "morph": 1.07, - "nero": 18.8, "neutron": 216, "nibiru": 7.5, "noble": 18.8, @@ -90,56 +86,53 @@ "optimism": 0.23, "orderly": 0.0169, "peaq": 238, + "plasma": 0.075, "plume": 0.0375, "polygon": 98.4, "polygonzkevm": 0.146, "polynomialfi": 0.0165, "prom": 9.3, "proofofplay": 0.0117, + "pulsechain": 436000, "rarichain": 0.0197, "reactive": 0.075, "redstone": 0.013, - "rivalz": 0.0117, "ronin": 42.2, - "rootstockmainnet": 0.0166, - "sanko": 3.19, - "scroll": 0.0571, + "scroll": 0.06, "sei": 110, - "shibarium": 94.2, + "shibarium": 116, "snaxchain": 0.017, "solanamainnet": 22.5, "solaxy": 0.0375, "soneium": 0.236, - "sonic": 58.8, + "sonic": 62.4, "sonicsvm": 1.16, "soon": 0.0117, "sophon": 37.5, + "sova": 0.00375, "starknet": 150, "story": 10, - "stride": 93, - "subtensor": 1.6, + "stride": 344, + "subtensor": 1.82, "superpositionmainnet": 0.141, "superseed": 0.0756, - "svmbnb": 0.075, + "svmbnb": 0.0314, "swell": 0.0514, "tac": 3.75, - "taiko": 0.03, + "taiko": 0.0403, "tangle": 4.5, - "telos": 511, "torus": 136, "unichain": 0.0538, - "unitzero": 140, "vana": 0.00075, "viction": 117, "worldchain": 0.0162, "xai": 427, "xlayer": 0.684, - "xpla": 684, - "xrplevm": 8.58, + "xrplevm": 25.1, + "zerogravity": 0.00375, "zeronetwork": 0.0117, "zetachain": 106, "zircuit": 0.0254, - "zklink": 0.0117, "zksync": 0.0117, "zoramainnet": 0.0526 } diff --git a/typescript/infra/config/environments/mainnet3/balances/lowUrgencyKeyFunderBalance.json b/typescript/infra/config/environments/mainnet3/balances/lowUrgencyKeyFunderBalance.json index d7246b08a33..49e7d2c8b33 100644 --- a/typescript/infra/config/environments/mainnet3/balances/lowUrgencyKeyFunderBalance.json +++ b/typescript/infra/config/environments/mainnet3/balances/lowUrgencyKeyFunderBalance.json @@ -1,6 +1,6 @@ { "abstract": 0.0514, - "alephzeroevmmainnet": 1370, + "alephzeroevmmainnet": 1450, "ancient8": 0.0304, "apechain": 100, "appchain": 0.186, @@ -18,37 +18,33 @@ "blast": 0.039, "bob": 0.0299, "boba": 0.0289, - "botanix": 0.000352, - "bouncebit": 462, + "botanix": 0.000852, "bsc": 3.17, "bsquared": 0.0294, + "celestia": 22.7, "celo": 145, "cheesechain": 149000, "chilizmainnet": 1070, - "conflux": 534, - "conwai": 21100, "coredao": 81.8, "coti": 0.15, "cyber": 0.0385, - "deepbrainchain": 150, "degenchain": 14800, "dogechain": 236, - "duckchain": 12.6, "eclipsemainnet": 0.75, + "electroneum": 15, "endurance": 75.5, - "ethereum": 6, - "everclear": 0.127, - "evmos": 11900, - "fantom": 118, - "flame": 26.8, + "ethereum": 4.79, + "everclear": 0.178, + "fantom": 125, "flare": 2500, "flowmainnet": 114, "fluence": 3000, "form": 0.0234, + "forma": 7.5, "fraxtal": 0.4, "fusemainnet": 3780, "galactica": 1.5, - "game7": 9220, + "game7": 1500, "gnosis": 37.4, "gravity": 3340, "harmony": 3760, @@ -65,22 +61,22 @@ "linea": 0.918, "lisk": 0.2, "lukso": 58.3, - "lumia": 162, "lumiaprism": 164, "mantapacific": 0.0403, "mantle": 90.1, + "mantra": 181, "matchain": 0.0644, "merlin": 0.003, "metal": 0.1, "metis": 2.75, - "milkyway": 697, + "milkyway": 781, "mint": 0.0326, "miraclechain": 0.075, + "mitosis": 709, "mode": 0.4, "molten": 358, "moonbeam": 700, "morph": 2.14, - "nero": 37.6, "neutron": 432, "nibiru": 15, "noble": 37.6, @@ -90,56 +86,53 @@ "optimism": 0.46, "orderly": 0.0337, "peaq": 476, + "plasma": 0.15, "plume": 0.075, "polygon": 250, "polygonzkevm": 0.293, "polynomialfi": 0.033, "prom": 36, "proofofplay": 0.0234, + "pulsechain": 872000, "rarichain": 0.0395, "reactive": 0.15, "redstone": 0.026, - "rivalz": 0.0234, "ronin": 84.5, - "rootstockmainnet": 0.0332, - "sanko": 6.38, - "scroll": 0.114, + "scroll": 0.12, "sei": 221, - "shibarium": 188, + "shibarium": 233, "snaxchain": 0.034, "solanamainnet": 45, "solaxy": 0.075, "soneium": 0.473, - "sonic": 118, + "sonic": 125, "sonicsvm": 2.32, "soon": 0.0234, "sophon": 75, + "sova": 0.0075, "starknet": 300, "story": 20, - "stride": 186, - "subtensor": 3.19, + "stride": 689, + "subtensor": 3.65, "superpositionmainnet": 0.282, "superseed": 0.151, - "svmbnb": 0.15, + "svmbnb": 0.0629, "swell": 0.103, "tac": 7.5, - "taiko": 0.06, + "taiko": 0.0805, "tangle": 9, - "telos": 1020, "torus": 272, "unichain": 0.2, - "unitzero": 280, "vana": 0.0015, "viction": 234, "worldchain": 0.0324, "xai": 854, "xlayer": 1.37, - "xpla": 1370, - "xrplevm": 17.2, + "xrplevm": 50.3, + "zerogravity": 0.0075, "zeronetwork": 0.0234, "zetachain": 212, "zircuit": 0.0509, - "zklink": 0.0234, "zksync": 0.0234, "zoramainnet": 0.105 } diff --git a/typescript/infra/config/environments/mainnet3/chains.ts b/typescript/infra/config/environments/mainnet3/chains.ts index bc040ff8014..21116a4f632 100644 --- a/typescript/infra/config/environments/mainnet3/chains.ts +++ b/typescript/infra/config/environments/mainnet3/chains.ts @@ -50,12 +50,6 @@ export const chainMetadataOverrides: ChainMap> = { gasPrice: 1 * 10 ** 6, // 0.001 gwei }, }, - rootstockmainnet: { - transactionOverrides: { - gasPrice: 7 * 10 ** 7, // 0.07 gwei - // gasLimit: 6800000, // set when deploying contracts - }, - }, // Deploy-only overrides, set when deploying contracts // chilizmainnet: { // transactionOverrides: { @@ -91,11 +85,6 @@ export const chainMetadataOverrides: ChainMap> = { // maxPriorityFeePerGas: 50 * 10 ** 9, // 50 gwei // }, // }, - // unitzero: { - // transactionOverrides: { - // gasPrice: 600 * 10 ** 9, // 600 gwei - // }, - // }, // matchain: { // blocks: { // confirmations: 5, diff --git a/typescript/infra/config/environments/mainnet3/core.ts b/typescript/infra/config/environments/mainnet3/core.ts index eb8315a6901..a554bea1498 100644 --- a/typescript/infra/config/environments/mainnet3/core.ts +++ b/typescript/infra/config/environments/mainnet3/core.ts @@ -36,7 +36,10 @@ export const core: ChainMap = objMap( (local, owner) => { const originMultisigs: ChainMap = Object.fromEntries( supportedChainNames + // no reflexivity .filter((chain) => chain !== local) + // exclude forma as it's not a core chain + .filter((chain) => chain !== 'forma') .map((origin) => [origin, defaultMultisigConfigs[origin]]), ); diff --git a/typescript/infra/config/environments/mainnet3/core/verification.json b/typescript/infra/config/environments/mainnet3/core/verification.json index 754483bca22..a94cdb6afa3 100644 --- a/typescript/infra/config/environments/mainnet3/core/verification.json +++ b/typescript/infra/config/environments/mainnet3/core/verification.json @@ -2021,45 +2021,6 @@ "name": "PausableIsm" } ], - "sanko": [ - { - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "constructorArguments": "", - "isProxy": false, - "name": "ProxyAdmin" - }, - { - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000007cc", - "isProxy": false, - "name": "Mailbox" - }, - { - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "constructorArguments": "0000000000000000000000004ed7d626f1e96cd1c0401607bf70d95243e3ded10000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "expectedimplementation": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "isProxy": true, - "name": "TransparentUpgradeableProxy" - }, - { - "address": "0xC88bAD76EC7acD9fd3b9Bb264f7f5C18097c5710", - "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", - "isProxy": false, - "name": "PausableIsm" - }, - { - "address": "0x92cdbF0Ccdf8E93467FA858fb986fa650A02f2A8", - "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", - "isProxy": false, - "name": "PausableIsm" - }, - { - "address": "0xDab56C5A1EffFdd23f6BD1243E457B1575984Bc6", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", - "isProxy": false, - "name": "ProtocolFee" - } - ], "scroll": [ { "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", @@ -3564,82 +3525,6 @@ "isProxy": false } ], - "lumia": [ - { - "name": "ProxyAdmin", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b4c8eb9", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E" - }, - { - "name": "PausableIsm", - "address": "0x5d69BC38eF3eDb491c0b7186BEc4eC45c4013f93", - "constructorArguments": "000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0x9c44E6b8F0dB517C2c3a0478caaC5349b614F912", - "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x73db9c7430548f399e335f3424e8d56080e9010c", - "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000009c44e6b8f0db517c2c3a0478caac5349b614f912", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0x53e912b41125d6094590a7DBEf1360d3d56EEa19", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x089DdA086dCbfA0C2cCa69B45F2eB6DE7Fd71F38", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0x4757Bdd68Bba8a6d901cEC82E61E184fF2986918", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x9024A3902B542C87a5C4A2b3e15d60B2f087Dc3E", - "constructorArguments": "0000000000000000000000004757bdd68bba8a6d901cec82e61e184ff2986918000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x4757Bdd68Bba8a6d901cEC82E61E184fF2986918" - }, - { - "name": "ProtocolFee", - "address": "0xb129828B9EDa48192D0B2db35D0E40dCF51B3594", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0x989B7307d266151BE763935C856493D968b2affF", - "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", - "isProxy": false - } - ], "zksync": [ { "name": "ProxyAdmin", @@ -4878,76 +4763,6 @@ "isProxy": false } ], - "flame": [ - { - "name": "ProxyAdmin", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000f1a177e", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" - }, - { - "name": "MerkleTreeHook", - "address": "0xEe08043cf22c80b27BF24d19999231dF4a3fC256", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xf3dFf6747E7FC74B431C943961054B7BF6309d8a", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000ee08043cf22c80b27bf24d19999231df4a3fc256", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0x145566181A18E23bB6a8A3eC6D87765542A7F754", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x18B0688990720103dB63559a3563f7E8d0f63EDb", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0x8C3e1794018a589c9E9226b8543105fCb6cC88C4", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x61374178e45F65fF9D6252d017Cd580FC60B7654", - "constructorArguments": "0000000000000000000000008c3e1794018a589c9e9226b8543105fcb6cc88c40000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x8C3e1794018a589c9E9226b8543105fCb6cC88C4" - }, - { - "name": "ProtocolFee", - "address": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0xc5D6aCaafBCcEC6D7fD7d92F4509befce641c563", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - } - ], "chilizmainnet": [ { "name": "ProxyAdmin", @@ -5216,96 +5031,77 @@ "isProxy": false } ], - "rootstockmainnet": [ - { - "name": "ProxyAdmin", - "address": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca1e", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xA8A311B69f688c1D9928259D872C31ca0d473642", - "constructorArguments": "0000000000000000000000002f0e57527bb37e5e064ef243fad56cce6241906c000000000000000000000000dc1508844b99c606e16c2ae87f33c373edd4b0f600000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c" - }, + "boba": [ { "name": "ProxyAdmin", - "address": "0xe93f2f409ad8B5000431D234472973fe848dcBEC", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "constructorArguments": "", "isProxy": false }, { "name": "Mailbox", - "address": "0x8d9Bd7E9ec3cd799a659EE650DfF6C799309fA91", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca1e", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000120", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x96D51cc3f7500d501bAeB1A2a62BB96fa03532F8", - "constructorArguments": "0000000000000000000000008d9bd7e9ec3cd799a659ee650dff6c799309fa91000000000000000000000000e93f2f409ad8b5000431d234472973fe848dcbec00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x8d9Bd7E9ec3cd799a659EE650DfF6C799309fA91" + "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" }, { "name": "MerkleTreeHook", - "address": "0x086c3947F71BE98A0bDf4AB7239955e7542b0CbA", - "constructorArguments": "00000000000000000000000096d51cc3f7500d501baeb1a2a62bb96fa03532f8", + "address": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x282629Af1A2f9b8e2c5Cbc54C35C7989f21950c6", - "constructorArguments": "00000000000000000000000096d51cc3f7500d501baeb1a2a62bb96fa03532f8000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000086c3947f71be98a0bdf4ab7239955e7542b0cba", + "address": "0x0D3bD9F1bcDA82bD1682b2C895a907d7aaE45849", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000009eaac366bfd70430cfee6e70265fefff1cfc9e47", "isProxy": false }, { "name": "PausableHook", - "address": "0x9C6e8d989ea7F212e679191BEb44139d83ac927a", + "address": "0x9eb56085DdbDA60aDf7d2B533AFeD90e38fC9666", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0xa36172F79a248C24693a208b0CEF0e7D6FB995F0", + "address": "0xbb0AE51BCa526cF313b6a95BfaB020794af6C394", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x2A532fc8cF9a72142eA8753a0d2AB68098C19585", + "address": "0x83475ca5bEB2Eaa59A2FF48a0544ebaa4a32c2de", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x5ae1ECA065aC8ee92Ce98E584fc3CE43070020e7", - "constructorArguments": "0000000000000000000000002a532fc8cf9a72142ea8753a0d2ab68098c19585000000000000000000000000e93f2f409ad8b5000431d234472973fe848dcbec00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x9534122Aae7978dB8f5f10dF4432233c53e820A1", + "constructorArguments": "00000000000000000000000083475ca5beb2eaa59a2ff48a0544ebaa4a32c2de0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x2A532fc8cF9a72142eA8753a0d2AB68098C19585" + "expectedimplementation": "0x83475ca5bEB2Eaa59A2FF48a0544ebaa4a32c2de" }, { "name": "ProtocolFee", - "address": "0x8Ea50255C282F89d1A14ad3F159437EE5EF0507f", + "address": "0x99fEFc1119E86Ee0153eb887cF8E8ab2d92A16e8", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x20a0A32a110362920597F72974E1E0d7e25cA20a", - "constructorArguments": "00000000000000000000000096d51cc3f7500d501baeb1a2a62bb96fa03532f8", + "address": "0xD743801ABB6c7664B623D8534C0f5AF8cD2F1C5e", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "boba": [ + "superseed": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -5315,7 +5111,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000120", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000014d2", "isProxy": false }, { @@ -5375,7 +5171,7 @@ "isProxy": false } ], - "superseed": [ + "unichain": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -5385,7 +5181,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000014d2", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000082", "isProxy": false }, { @@ -5445,7 +5241,7 @@ "isProxy": false } ], - "unichain": [ + "vana": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -5455,7 +5251,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000082", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000005c8", "isProxy": false }, { @@ -5515,7 +5311,7 @@ "isProxy": false } ], - "duckchain": [ + "bsquared": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -5525,7 +5321,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000015a9", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000000df", "isProxy": false }, { @@ -5537,205 +5333,65 @@ }, { "name": "MerkleTreeHook", - "address": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", + "address": "0xbb0AE51BCa526cF313b6a95BfaB020794af6C394", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x0D3bD9F1bcDA82bD1682b2C895a907d7aaE45849", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000009eaac366bfd70430cfee6e70265fefff1cfc9e47", + "address": "0x60bB6D060393D3C206719A7bD61844cC82891cfB", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000bb0ae51bca526cf313b6a95bfab020794af6c394", "isProxy": false }, { "name": "PausableHook", - "address": "0x9eb56085DdbDA60aDf7d2B533AFeD90e38fC9666", + "address": "0x83475ca5bEB2Eaa59A2FF48a0544ebaa4a32c2de", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0xbb0AE51BCa526cF313b6a95BfaB020794af6C394", + "address": "0x451dF8AB0936D85526D816f0b4dCaDD934A034A4", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x83475ca5bEB2Eaa59A2FF48a0544ebaa4a32c2de", + "address": "0x81EbEdfc1220BE33C3B9c5E09c1FCab849a392A6", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x9534122Aae7978dB8f5f10dF4432233c53e820A1", - "constructorArguments": "00000000000000000000000083475ca5beb2eaa59a2ff48a0544ebaa4a32c2de0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x70EbA87Cd15616f32C736B3f3BdCfaeD0713a82B", + "constructorArguments": "00000000000000000000000081ebedfc1220be33c3b9c5e09c1fcab849a392a60000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x83475ca5bEB2Eaa59A2FF48a0544ebaa4a32c2de" + "expectedimplementation": "0x81EbEdfc1220BE33C3B9c5E09c1FCab849a392A6" }, { "name": "ProtocolFee", - "address": "0x99fEFc1119E86Ee0153eb887cF8E8ab2d92A16e8", + "address": "0xbB88a31E4b709b645c06825c0E0b5CAC906d97DE", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0xD743801ABB6c7664B623D8534C0f5AF8cD2F1C5e", + "address": "0x25EAC2007b0D40E3f0AF112FD346412321038719", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "vana": [ + "lumiaprism": [ { "name": "ProxyAdmin", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "address": "0x200183De44bf765ECB73cD62A74010EaaBC43146", "constructorArguments": "", "isProxy": false }, { "name": "Mailbox", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000005c8", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" - }, - { - "name": "MerkleTreeHook", - "address": "0x9eaaC366BFD70430cFee6E70265fefFf1CfC9E47", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x0D3bD9F1bcDA82bD1682b2C895a907d7aaE45849", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000009eaac366bfd70430cfee6e70265fefff1cfc9e47", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0x9eb56085DdbDA60aDf7d2B533AFeD90e38fC9666", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0xbb0AE51BCa526cF313b6a95BfaB020794af6C394", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0x83475ca5bEB2Eaa59A2FF48a0544ebaa4a32c2de", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x9534122Aae7978dB8f5f10dF4432233c53e820A1", - "constructorArguments": "00000000000000000000000083475ca5beb2eaa59a2ff48a0544ebaa4a32c2de0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x83475ca5bEB2Eaa59A2FF48a0544ebaa4a32c2de" - }, - { - "name": "ProtocolFee", - "address": "0x99fEFc1119E86Ee0153eb887cF8E8ab2d92A16e8", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0xD743801ABB6c7664B623D8534C0f5AF8cD2F1C5e", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - } - ], - "bsquared": [ - { - "name": "ProxyAdmin", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000000df", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" - }, - { - "name": "MerkleTreeHook", - "address": "0xbb0AE51BCa526cF313b6a95BfaB020794af6C394", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x60bB6D060393D3C206719A7bD61844cC82891cfB", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000bb0ae51bca526cf313b6a95bfab020794af6c394", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0x83475ca5bEB2Eaa59A2FF48a0544ebaa4a32c2de", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x451dF8AB0936D85526D816f0b4dCaDD934A034A4", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0x81EbEdfc1220BE33C3B9c5E09c1FCab849a392A6", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x70EbA87Cd15616f32C736B3f3BdCfaeD0713a82B", - "constructorArguments": "00000000000000000000000081ebedfc1220be33c3b9c5e09c1fcab849a392a60000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x81EbEdfc1220BE33C3B9c5E09c1FCab849a392A6" - }, - { - "name": "ProtocolFee", - "address": "0xbB88a31E4b709b645c06825c0E0b5CAC906d97DE", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0x25EAC2007b0D40E3f0AF112FD346412321038719", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - } - ], - "lumiaprism": [ - { - "name": "ProxyAdmin", - "address": "0x200183De44bf765ECB73cD62A74010EaaBC43146", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0xcD78A32Da8cfe9452cD2F50F547c11B979Afcf1b", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9be739", + "address": "0xcD78A32Da8cfe9452cD2F50F547c11B979Afcf1b", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9be739", "isProxy": false }, { @@ -5865,141 +5521,153 @@ "isProxy": false } ], - "zklink": [ + "appchain": [ { "name": "ProxyAdmin", - "address": "0x038F9F4e93e88Af2C688da265222FdE80e455aA4", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "constructorArguments": "", "isProxy": false }, { "name": "Mailbox", - "address": "0xA3949b37109d64b10De93252EeFebBB2E6B8944F", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000c5cc4", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000001d2", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x9BbDf86b272d224323136E15594fdCe487F40ce7", - "constructorArguments": "000000000000000000000000a3949b37109d64b10de93252eefebbb2e6b8944f000000000000000000000000038f9f4e93e88af2c688da265222fde80e455aa400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xA3949b37109d64b10De93252EeFebBB2E6B8944F" + "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" }, { "name": "MerkleTreeHook", - "address": "0xA1ADFCa9666Bcd68b7b5C8b55e3ecC465DcDfE65", - "constructorArguments": "0000000000000000000000009bbdf86b272d224323136e15594fdce487f40ce7", + "address": "0xcd90D49b046772F710250b9119117169CB2e4D8b", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { - "name": "FallbackDomainRoutingHook", - "address": "0x388289cd5862e17AAfD6ffF7F46A9Ec48a969bCd", - "constructorArguments": "0000000000000000000000009bbdf86b272d224323136e15594fdce487f40ce7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a1adfca9666bcd68b7b5c8b55e3ecc465dcdfe65", + "name": "FallbackRoutingHook", + "address": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000cd90d49b046772f710250b9119117169cb2e4d8b", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x7CE76f5f0C469bBB4cd7Ea6EbabB54437A093127", + "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x73a82061Cd258d02BEa145fe183120456e718c2A", + "address": "0xA376b27212D608324808923Add679A2c9FAFe9Da", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x54E88f2ab58E0Ab4B7Ce081FB20D85b16af041d2", + "address": "0xBC53dACd8c0ac0d2bAC461479EAaf5519eCC8853", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xB35eCb9714e8f48332Af22B48C18ca21E2607438", - "constructorArguments": "00000000000000000000000054e88f2ab58e0ab4b7ce081fb20d85b16af041d2000000000000000000000000038f9f4e93e88af2c688da265222fde80e455aa400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x28291a7062afA569104bEd52F7AcCA3dD2FafD11", + "constructorArguments": "000000000000000000000000bc53dacd8c0ac0d2bac461479eaaf5519ecc88530000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x54E88f2ab58E0Ab4B7Ce081FB20D85b16af041d2" + "expectedimplementation": "0xBC53dACd8c0ac0d2bAC461479EAaf5519eCC8853" + }, + { + "name": "ProtocolFee", + "address": "0xD35Aa652C1F808d3f87DA3DC7974fea888D7d625", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0xf5626c0f33Ca102eb3ca1633A410cd8aa92909e4", - "constructorArguments": "0000000000000000000000009bbdf86b272d224323136e15594fdce487f40ce7", + "address": "0x1196055C61af3e3DA6f8458B07b255a72b64Bcf7", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "appchain": [ + "aurora": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "constructorArguments": "", "isProxy": false }, + { + "name": "ProxyAdmin", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "", + "isProxy": false + }, { "name": "Mailbox", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000001d2", + "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "constructorArguments": "000000000000000000000000000000000000000000000000000000004e454152", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa70000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" + "expectedimplementation": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7" }, { "name": "MerkleTreeHook", - "address": "0xcd90D49b046772F710250b9119117169CB2e4D8b", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0xA8A311B69f688c1D9928259D872C31ca0d473642", + "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b39", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000cd90d49b046772f710250b9119117169cb2e4d8b", + "address": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", + "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b39000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a8a311b69f688c1d9928259d872c31ca0d473642", "isProxy": false }, { "name": "PausableHook", - "address": "0x7CE76f5f0C469bBB4cd7Ea6EbabB54437A093127", + "address": "0x48C427782Bc1e9ecE406b3e277481b28ABcBdf03", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0xA376b27212D608324808923Add679A2c9FAFe9Da", + "address": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0xBC53dACd8c0ac0d2bAC461479EAaf5519eCC8853", + "address": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x28291a7062afA569104bEd52F7AcCA3dD2FafD11", - "constructorArguments": "000000000000000000000000bc53dacd8c0ac0d2bac461479eaaf5519ecc88530000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0xc0C2dB448fC2c84213394Fcb93a3C467e50ECa9E", + "constructorArguments": "000000000000000000000000df178647cab5e0222f4b53c57274fd2a03beaed60000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xBC53dACd8c0ac0d2bAC461479EAaf5519eCC8853" + "expectedimplementation": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6" }, { "name": "ProtocolFee", - "address": "0xD35Aa652C1F808d3f87DA3DC7974fea888D7d625", + "address": "0x84444cE490233CFa76E3F1029bc166aa8c266907", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x1196055C61af3e3DA6f8458B07b255a72b64Bcf7", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0x426a3CE72C1586b1867F9339550371E86DB3e396", + "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b39", "isProxy": false } ], - "aurora": [ - { - "name": "ProxyAdmin", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "constructorArguments": "", - "isProxy": false - }, + "ink": [ { "name": "ProxyAdmin", "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", @@ -6009,7 +5677,7 @@ { "name": "Mailbox", "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", - "constructorArguments": "000000000000000000000000000000000000000000000000000000004e454152", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000000def1", "isProxy": false }, { @@ -6069,7 +5737,7 @@ "isProxy": false } ], - "conwai": [ + "form": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -6079,7 +5747,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000a33fc", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000001de", "isProxy": false }, { @@ -6139,7 +5807,7 @@ "isProxy": false } ], - "telos": [ + "sonic": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -6149,7 +5817,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000028", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000092", "isProxy": false }, { @@ -6209,77 +5877,77 @@ "isProxy": false } ], - "ink": [ + "soneium": [ { "name": "ProxyAdmin", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "constructorArguments": "", "isProxy": false }, { "name": "Mailbox", - "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000def1", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000000074c", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", - "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa70000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7" + "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" }, { "name": "MerkleTreeHook", - "address": "0xA8A311B69f688c1D9928259D872C31ca0d473642", - "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b39", - "isProxy": false + "address": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", - "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b39000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a8a311b69f688c1d9928259d872c31ca0d473642", + "address": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000dc1508844b99c606e16c2ae87f33c373edd4b0f6", "isProxy": false }, { "name": "PausableHook", - "address": "0x48C427782Bc1e9ecE406b3e277481b28ABcBdf03", + "address": "0xA8A311B69f688c1D9928259D872C31ca0d473642", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7", + "address": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", + "address": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xc0C2dB448fC2c84213394Fcb93a3C467e50ECa9E", - "constructorArguments": "000000000000000000000000df178647cab5e0222f4b53c57274fd2a03beaed60000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", + "constructorArguments": "0000000000000000000000009e8b689e83d929cb8c2d9166e55319a4e6aa83b70000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6" + "expectedimplementation": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7" }, { "name": "ProtocolFee", - "address": "0x84444cE490233CFa76E3F1029bc166aa8c266907", + "address": "0x5DdFCA27f9a308c1429A010C4daB291b5534a297", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x426a3CE72C1586b1867F9339550371E86DB3e396", - "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b39", + "address": "0x84444cE490233CFa76E3F1029bc166aa8c266907", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "evmos": [ + "torus": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -6289,7 +5957,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000002329", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000005208", "isProxy": false }, { @@ -6301,55 +5969,55 @@ }, { "name": "MerkleTreeHook", - "address": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", + "address": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000dc1508844b99c606e16c2ae87f33c373edd4b0f6", + "address": "0x48C427782Bc1e9ecE406b3e277481b28ABcBdf03", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000001c6f404800ba49ed581af734ea0d25c0c7d017b2", "isProxy": false }, { "name": "PausableHook", - "address": "0xA8A311B69f688c1D9928259D872C31ca0d473642", + "address": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", + "address": "0x248aDe14C0489E20C9a7Fea5F86DBfC3702208eF", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7", + "address": "0x5e8a0fCc0D1DF583322943e01F02cB243e5300f6", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "constructorArguments": "0000000000000000000000009e8b689e83d929cb8c2d9166e55319a4e6aa83b70000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x3cECBa60A580dE20CC57D87528953a00f4ED99EA", + "constructorArguments": "0000000000000000000000005e8a0fcc0d1df583322943e01f02cb243e5300f60000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7" + "expectedimplementation": "0x5e8a0fCc0D1DF583322943e01F02cB243e5300f6" }, { "name": "ProtocolFee", - "address": "0x5DdFCA27f9a308c1429A010C4daB291b5534a297", + "address": "0x4Ee9dEBB3046139661b51E17bdfD54Fd63211de7", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x84444cE490233CFa76E3F1029bc166aa8c266907", + "address": "0x65dCf8F6b3f6a0ECEdf3d0bdCB036AEa47A1d615", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "form": [ + "artela": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -6359,7 +6027,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000001de", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000002e2c", "isProxy": false }, { @@ -6371,55 +6039,55 @@ }, { "name": "MerkleTreeHook", - "address": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", + "address": "0x7B032cBB00AD7438E802A66D8b64761A06E5df22", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000dc1508844b99c606e16c2ae87f33c373edd4b0f6", + "address": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000007b032cbb00ad7438e802a66d8b64761a06e5df22", "isProxy": false }, { "name": "PausableHook", - "address": "0xA8A311B69f688c1D9928259D872C31ca0d473642", + "address": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", + "address": "0x60515f328B2c55Df63f456D9D839a0082892dEf8", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7", + "address": "0x028B04386031b9648A8D78d06c58F6E763Be5cD0", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "constructorArguments": "0000000000000000000000009e8b689e83d929cb8c2d9166e55319a4e6aa83b70000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0xc2466492C451E1AE49d8C874bB9f89293Aaad59b", + "constructorArguments": "000000000000000000000000028b04386031b9648a8d78d06c58f6e763be5cd00000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7" + "expectedimplementation": "0x028B04386031b9648A8D78d06c58F6E763Be5cD0" }, { "name": "ProtocolFee", - "address": "0x5DdFCA27f9a308c1429A010C4daB291b5534a297", + "address": "0x46008F5971eFb16e6c354Ef993EA021B489bc055", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x84444cE490233CFa76E3F1029bc166aa8c266907", + "address": "0xb89c6ED617f5F46175E41551350725A09110bbCE", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "sonic": [ + "hemi": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -6429,7 +6097,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000092", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000000a867", "isProxy": false }, { @@ -6441,125 +6109,113 @@ }, { "name": "MerkleTreeHook", - "address": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", + "address": "0x7B032cBB00AD7438E802A66D8b64761A06E5df22", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000dc1508844b99c606e16c2ae87f33c373edd4b0f6", + "address": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000007b032cbb00ad7438e802a66d8b64761a06e5df22", "isProxy": false }, { "name": "PausableHook", - "address": "0xA8A311B69f688c1D9928259D872C31ca0d473642", + "address": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", + "address": "0x60515f328B2c55Df63f456D9D839a0082892dEf8", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7", + "address": "0x028B04386031b9648A8D78d06c58F6E763Be5cD0", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "constructorArguments": "0000000000000000000000009e8b689e83d929cb8c2d9166e55319a4e6aa83b70000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0xc2466492C451E1AE49d8C874bB9f89293Aaad59b", + "constructorArguments": "000000000000000000000000028b04386031b9648a8d78d06c58f6e763be5cd00000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7" + "expectedimplementation": "0x028B04386031b9648A8D78d06c58F6E763Be5cD0" }, { "name": "ProtocolFee", - "address": "0x5DdFCA27f9a308c1429A010C4daB291b5534a297", + "address": "0x46008F5971eFb16e6c354Ef993EA021B489bc055", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x84444cE490233CFa76E3F1029bc166aa8c266907", + "address": "0xb89c6ED617f5F46175E41551350725A09110bbCE", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "soneium": [ + "abstract": [ { "name": "ProxyAdmin", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "address": "0x038F9F4e93e88Af2C688da265222FdE80e455aA4", "constructorArguments": "", "isProxy": false }, { "name": "Mailbox", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000074c", + "address": "0xA3949b37109d64b10De93252EeFebBB2E6B8944F", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000ab5", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x9BbDf86b272d224323136E15594fdCe487F40ce7", + "constructorArguments": "000000000000000000000000a3949b37109d64b10de93252eefebbb2e6b8944f000000000000000000000000038f9f4e93e88af2c688da265222fde80e455aa400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" + "expectedimplementation": "0xA3949b37109d64b10De93252EeFebBB2E6B8944F" }, { "name": "MerkleTreeHook", - "address": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000dc1508844b99c606e16c2ae87f33c373edd4b0f6", + "address": "0x11b69aB33AD8a550dcF9B4A041AA1121255F08A5", + "constructorArguments": "0000000000000000000000009bbdf86b272d224323136e15594fdce487f40ce7", "isProxy": false }, { - "name": "PausableHook", - "address": "0xA8A311B69f688c1D9928259D872C31ca0d473642", - "constructorArguments": "", + "name": "FallbackDomainRoutingHook", + "address": "0x72f747270ED9C92c7026b222c5767561Be281DaF", + "constructorArguments": "0000000000000000000000009bbdf86b272d224323136e15594fdce487f40ce7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000011b69ab33ad8a550dcf9b4a041aa1121255f08a5", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", + "address": "0x1cE4d0E16570C362feef85Ce2713555fCbd3dBC7", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7", + "address": "0x1741E83C2504fFEd6E60CF41BbC207c2d61a095F", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "constructorArguments": "0000000000000000000000009e8b689e83d929cb8c2d9166e55319a4e6aa83b70000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x874AaCa847B365592B2b9dB7235517c5F3a5c689", + "constructorArguments": "0000000000000000000000001741e83c2504ffed6e60cf41bbc207c2d61a095f000000000000000000000000038f9f4e93e88af2c688da265222fde80e455aa400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7" - }, - { - "name": "ProtocolFee", - "address": "0x5DdFCA27f9a308c1429A010C4daB291b5534a297", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", - "isProxy": false + "expectedimplementation": "0x1741E83C2504fFEd6E60CF41BbC207c2d61a095F" }, { "name": "ValidatorAnnounce", - "address": "0x84444cE490233CFa76E3F1029bc166aa8c266907", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0x2235662a9a8ED39AE489aafb2feE13Db26f72044", + "constructorArguments": "0000000000000000000000009bbdf86b272d224323136e15594fdce487f40ce7", "isProxy": false } ], - "conflux": [ + "matchain": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -6569,7 +6225,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000406", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000002ba", "isProxy": false }, { @@ -6581,195 +6237,183 @@ }, { "name": "MerkleTreeHook", - "address": "0xDc1508844B99C606E16C2Ae87f33c373edD4B0F6", + "address": "0x021D2810a758c833080DEc2F1Fa8F571Aae97D45", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x2f0E57527Bb37E5E064EF243fad56CCE6241906c", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000dc1508844b99c606e16c2ae87f33c373edd4b0f6", + "address": "0xf0F937943Cd6D2a5D02b7f96C9Dd9e04AB633813", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000021d2810a758c833080dec2f1fa8f571aae97d45", "isProxy": false }, { "name": "PausableHook", - "address": "0xA8A311B69f688c1D9928259D872C31ca0d473642", + "address": "0x46008F5971eFb16e6c354Ef993EA021B489bc055", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", + "address": "0xff72A726Ce261846f2dF6F32113e514b5Ddb0E37", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7", + "address": "0xa2401b57A8CCBF6AbD9b7e62e28811b2b523AB2B", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xDf178647caB5e0222F4B53C57274FD2A03BEaed6", - "constructorArguments": "0000000000000000000000009e8b689e83d929cb8c2d9166e55319a4e6aa83b70000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x9629c28990F11c31735765A6FD59E1E1bC197DbD", + "constructorArguments": "000000000000000000000000a2401b57a8ccbf6abd9b7e62e28811b2b523ab2b0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7" + "expectedimplementation": "0xa2401b57A8CCBF6AbD9b7e62e28811b2b523AB2B" }, { "name": "ProtocolFee", - "address": "0x5DdFCA27f9a308c1429A010C4daB291b5534a297", + "address": "0xb201817dFdd822B75Fa9b595457E6Ee466a7C187", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x84444cE490233CFa76E3F1029bc166aa8c266907", + "address": "0xC5f2c60073DCAA9D157C45d5B017D639dF9C5CeB", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "rivalz": [ + "berachain": [ { "name": "ProxyAdmin", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "constructorArguments": "", "isProxy": false }, { "name": "Mailbox", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000002f1", + "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000138de", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa70000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" + "expectedimplementation": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7" }, { "name": "MerkleTreeHook", - "address": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0x8F23872dAb3B166cef411EeB6C391Ff6Ce419532", + "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b39", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x48C427782Bc1e9ecE406b3e277481b28ABcBdf03", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000001c6f404800ba49ed581af734ea0d25c0c7d017b2", + "address": "0x7937CB2886f01F38210506491A69B0D107Ea0ad9", + "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b39000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000008f23872dab3b166cef411eeb6c391ff6ce419532", "isProxy": false }, { "name": "PausableHook", - "address": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7", + "address": "0x2351FBe24C1212F253b7a300ff0cBCFd97952a19", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x248aDe14C0489E20C9a7Fea5F86DBfC3702208eF", + "address": "0xb201817dFdd822B75Fa9b595457E6Ee466a7C187", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x5e8a0fCc0D1DF583322943e01F02cB243e5300f6", + "address": "0xC5f2c60073DCAA9D157C45d5B017D639dF9C5CeB", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x3cECBa60A580dE20CC57D87528953a00f4ED99EA", - "constructorArguments": "0000000000000000000000005e8a0fcc0d1df583322943e01f02cb243e5300f60000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x7Ce3a48cd9FD80004d95b088760bD05bA86C1f7b", + "constructorArguments": "000000000000000000000000c5f2c60073dcaa9d157c45d5b017d639df9c5ceb0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x5e8a0fCc0D1DF583322943e01F02cB243e5300f6" + "expectedimplementation": "0xC5f2c60073DCAA9D157C45d5B017D639dF9C5CeB" }, { "name": "ProtocolFee", - "address": "0x4Ee9dEBB3046139661b51E17bdfD54Fd63211de7", + "address": "0xF16E63B42Df7f2676B373979120BBf7e6298F473", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x65dCf8F6b3f6a0ECEdf3d0bdCB036AEa47A1d615", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0xa377b8269e0A47cdd2fD5AAeAe860b45623c6d82", + "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b39", "isProxy": false } ], - "torus": [ + "sophon": [ { "name": "ProxyAdmin", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "address": "0x038F9F4e93e88Af2C688da265222FdE80e455aA4", "constructorArguments": "", "isProxy": false }, { "name": "Mailbox", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000005208", + "address": "0xA3949b37109d64b10De93252EeFebBB2E6B8944F", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000000c3b8", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x9BbDf86b272d224323136E15594fdCe487F40ce7", + "constructorArguments": "000000000000000000000000a3949b37109d64b10de93252eefebbb2e6b8944f000000000000000000000000038f9f4e93e88af2c688da265222fde80e455aa400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" + "expectedimplementation": "0xA3949b37109d64b10De93252EeFebBB2E6B8944F" }, { "name": "MerkleTreeHook", - "address": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x48C427782Bc1e9ecE406b3e277481b28ABcBdf03", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000001c6f404800ba49ed581af734ea0d25c0c7d017b2", + "address": "0x697a90753B7dCf6512189c239E612fC12baaE500", + "constructorArguments": "0000000000000000000000009bbdf86b272d224323136e15594fdce487f40ce7", "isProxy": false }, { - "name": "PausableHook", - "address": "0x9e8b689e83d929cb8c2d9166E55319a4e6aA83B7", - "constructorArguments": "", + "name": "FallbackDomainRoutingHook", + "address": "0xc364cfedefE854c1275B0f4088EaFA9695e1FC56", + "constructorArguments": "0000000000000000000000009bbdf86b272d224323136e15594fdce487f40ce7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000697a90753b7dcf6512189c239e612fc12baae500", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x248aDe14C0489E20C9a7Fea5F86DBfC3702208eF", + "address": "0xA1ADFCa9666Bcd68b7b5C8b55e3ecC465DcDfE65", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x5e8a0fCc0D1DF583322943e01F02cB243e5300f6", + "address": "0x388289cd5862e17AAfD6ffF7F46A9Ec48a969bCd", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x3cECBa60A580dE20CC57D87528953a00f4ED99EA", - "constructorArguments": "0000000000000000000000005e8a0fcc0d1df583322943e01f02cb243e5300f60000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x73a82061Cd258d02BEa145fe183120456e718c2A", + "constructorArguments": "000000000000000000000000388289cd5862e17aafd6fff7f46a9ec48a969bcd000000000000000000000000038f9f4e93e88af2c688da265222fde80e455aa400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x5e8a0fCc0D1DF583322943e01F02cB243e5300f6" - }, - { - "name": "ProtocolFee", - "address": "0x4Ee9dEBB3046139661b51E17bdfD54Fd63211de7", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", - "isProxy": false + "expectedimplementation": "0x388289cd5862e17AAfD6ffF7F46A9Ec48a969bCd" }, { "name": "ValidatorAnnounce", - "address": "0x65dCf8F6b3f6a0ECEdf3d0bdCB036AEa47A1d615", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0x54E88f2ab58E0Ab4B7Ce081FB20D85b16af041d2", + "constructorArguments": "0000000000000000000000009bbdf86b272d224323136e15594fdce487f40ce7", "isProxy": false } ], - "artela": [ + "story": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -6779,7 +6423,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000002e2c", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000005ea", "isProxy": false }, { @@ -6791,55 +6435,55 @@ }, { "name": "MerkleTreeHook", - "address": "0x7B032cBB00AD7438E802A66D8b64761A06E5df22", + "address": "0xa377b8269e0A47cdd2fD5AAeAe860b45623c6d82", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000007b032cbb00ad7438e802a66d8b64761a06e5df22", + "address": "0x82540c4C1C6956FC4815E583DDc6d88A782E0F3e", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a377b8269e0a47cdd2fd5aaeae860b45623c6d82", "isProxy": false }, { "name": "PausableHook", - "address": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94", + "address": "0x3E57E4908FB4Ac05e9928feE2Ffd78f59692317C", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x60515f328B2c55Df63f456D9D839a0082892dEf8", + "address": "0x6e1B9f776bd415d7cC3C7458A5f0d801016918f8", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x028B04386031b9648A8D78d06c58F6E763Be5cD0", + "address": "0x63012EE26bda8E5D1b96218778Eaf2492E553469", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xc2466492C451E1AE49d8C874bB9f89293Aaad59b", - "constructorArguments": "000000000000000000000000028b04386031b9648a8d78d06c58f6e763be5cd00000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x5cD695ADCB156589cde501822C314bFD74398cA1", + "constructorArguments": "00000000000000000000000063012ee26bda8e5d1b96218778eaf2492e5534690000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x028B04386031b9648A8D78d06c58F6E763Be5cD0" + "expectedimplementation": "0x63012EE26bda8E5D1b96218778Eaf2492E553469" }, { "name": "ProtocolFee", - "address": "0x46008F5971eFb16e6c354Ef993EA021B489bc055", + "address": "0xd386Bb418B61E296e1689C95AfE94A2E321a6eaD", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0xb89c6ED617f5F46175E41551350725A09110bbCE", + "address": "0xB2b0A80b2fa3fC9aB1564A4FaF013d4D6084B325", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "xpla": [ + "ronin": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -6849,7 +6493,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000025", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000007e4", "isProxy": false }, { @@ -6859,33 +6503,57 @@ "isProxy": true, "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" }, + { + "name": "MerkleTreeHook", + "address": "0xa377b8269e0A47cdd2fD5AAeAe860b45623c6d82", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x82540c4C1C6956FC4815E583DDc6d88A782E0F3e", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a377b8269e0a47cdd2fd5aaeae860b45623c6d82", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0x3E57E4908FB4Ac05e9928feE2Ffd78f59692317C", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x6e1B9f776bd415d7cC3C7458A5f0d801016918f8", + "constructorArguments": "", + "isProxy": false + }, { "name": "InterchainGasPaymaster", - "address": "0xE885941aF52eab9E7f4c67392eACd96ea2A65d9B", + "address": "0x63012EE26bda8E5D1b96218778Eaf2492E553469", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x8F23872dAb3B166cef411EeB6C391Ff6Ce419532", - "constructorArguments": "000000000000000000000000e885941af52eab9e7f4c67392eacd96ea2a65d9b0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x5cD695ADCB156589cde501822C314bFD74398cA1", + "constructorArguments": "00000000000000000000000063012ee26bda8e5d1b96218778eaf2492e5534690000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xE885941aF52eab9E7f4c67392eACd96ea2A65d9B" + "expectedimplementation": "0x63012EE26bda8E5D1b96218778Eaf2492E553469" }, { "name": "ProtocolFee", - "address": "0xc31B1E6c8E706cF40842C3d728985Cd2f85413eD", + "address": "0xd386Bb418B61E296e1689C95AfE94A2E321a6eaD", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0xFa6fDABA1d0688675f05cE1B9DE17461247Bce9e", + "address": "0xB2b0A80b2fa3fC9aB1564A4FaF013d4D6084B325", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "nero": [ + "arcadia": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -6895,7 +6563,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000699", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000414950", "isProxy": false }, { @@ -6907,55 +6575,55 @@ }, { "name": "MerkleTreeHook", - "address": "0x7B032cBB00AD7438E802A66D8b64761A06E5df22", + "address": "0x6e1B9f776bd415d7cC3C7458A5f0d801016918f8", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000007b032cbb00ad7438e802a66d8b64761a06e5df22", + "address": "0x7927B6fE8FA061c32CE3771d11076E6161DE5f52", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006e1b9f776bd415d7cc3c7458a5f0d801016918f8", "isProxy": false }, { "name": "PausableHook", - "address": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94", + "address": "0x63012EE26bda8E5D1b96218778Eaf2492E553469", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x60515f328B2c55Df63f456D9D839a0082892dEf8", + "address": "0x3862A9B1aCd89245a59002C2a08658EC1d5690E3", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x028B04386031b9648A8D78d06c58F6E763Be5cD0", + "address": "0x8da1aE5A1fA3883c1c12b46270989EAC0EE7BA78", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xc2466492C451E1AE49d8C874bB9f89293Aaad59b", - "constructorArguments": "000000000000000000000000028b04386031b9648a8d78d06c58f6e763be5cd00000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x30a539E2E2d09FB4e68661B1EDD70D266211602a", + "constructorArguments": "0000000000000000000000008da1ae5a1fa3883c1c12b46270989eac0ee7ba780000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x028B04386031b9648A8D78d06c58F6E763Be5cD0" + "expectedimplementation": "0x8da1aE5A1fA3883c1c12b46270989EAC0EE7BA78" }, { "name": "ProtocolFee", - "address": "0x46008F5971eFb16e6c354Ef993EA021B489bc055", + "address": "0x51545389E04c2Ac07d98A40b85d29B480a2AF6ce", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0xb89c6ED617f5F46175E41551350725A09110bbCE", + "address": "0xEa2Bcee14eA30bbBe3018E5E7829F963230F71C3", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "hemi": [ + "hyperevm": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -6965,7 +6633,53 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000a867", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000003e7", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" + }, + { + "name": "InterchainGasPaymaster", + "address": "0x51545389E04c2Ac07d98A40b85d29B480a2AF6ce", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xEa2Bcee14eA30bbBe3018E5E7829F963230F71C3", + "constructorArguments": "00000000000000000000000051545389e04c2ac07d98a40b85d29b480a2af6ce0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x51545389E04c2Ac07d98A40b85d29B480a2AF6ce" + }, + { + "name": "ProtocolFee", + "address": "0x8d7E604460E1133ebB91513a6D1024f3A3ca17F9", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0xf4035357EB3e3B48E498FA6e1207892f615A2c2f", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false + } + ], + "plume": [ + { + "name": "ProxyAdmin", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000018232", "isProxy": false }, { @@ -6977,113 +6691,88 @@ }, { "name": "MerkleTreeHook", - "address": "0x7B032cBB00AD7438E802A66D8b64761A06E5df22", + "address": "0x25C87e735021F72d8728438C2130b02E3141f2cb", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000007b032cbb00ad7438e802a66d8b64761a06e5df22", + "address": "0xB2b0A80b2fa3fC9aB1564A4FaF013d4D6084B325", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000025c87e735021f72d8728438c2130b02e3141f2cb", "isProxy": false }, { "name": "PausableHook", - "address": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94", + "address": "0x51545389E04c2Ac07d98A40b85d29B480a2AF6ce", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x60515f328B2c55Df63f456D9D839a0082892dEf8", + "address": "0x1e4dE25C3b07c8DF66D4c193693d8B5f3b431d51", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x028B04386031b9648A8D78d06c58F6E763Be5cD0", + "address": "0x2D374F85AE2B80147CffEb34d294ce02d1afd4D8", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xc2466492C451E1AE49d8C874bB9f89293Aaad59b", - "constructorArguments": "000000000000000000000000028b04386031b9648a8d78d06c58f6e763be5cd00000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0xc261Bd2BD995d3D0026e918cBFD44b0Cc5416a57", + "constructorArguments": "0000000000000000000000002d374f85ae2b80147cffeb34d294ce02d1afd4d80000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x028B04386031b9648A8D78d06c58F6E763Be5cD0" + "expectedimplementation": "0x2D374F85AE2B80147CffEb34d294ce02d1afd4D8" }, { "name": "ProtocolFee", - "address": "0x46008F5971eFb16e6c354Ef993EA021B489bc055", + "address": "0x4eB0d97B48711950ecB01871125c4523939c6Fce", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0xb89c6ED617f5F46175E41551350725A09110bbCE", + "address": "0xbE58F200ffca4e1cE4D2F4541E94Ae18370fC405", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "abstract": [ + "infinityvm": [ { "name": "ProxyAdmin", - "address": "0x038F9F4e93e88Af2C688da265222FdE80e455aA4", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "constructorArguments": "", "isProxy": false }, { "name": "Mailbox", - "address": "0xA3949b37109d64b10De93252EeFebBB2E6B8944F", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000ab5", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000fbf49", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x9BbDf86b272d224323136E15594fdCe487F40ce7", - "constructorArguments": "000000000000000000000000a3949b37109d64b10de93252eefebbb2e6b8944f000000000000000000000000038f9f4e93e88af2c688da265222fde80e455aa400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xA3949b37109d64b10De93252EeFebBB2E6B8944F" - }, - { - "name": "MerkleTreeHook", - "address": "0x11b69aB33AD8a550dcF9B4A041AA1121255F08A5", - "constructorArguments": "0000000000000000000000009bbdf86b272d224323136e15594fdce487f40ce7", - "isProxy": false - }, - { - "name": "FallbackDomainRoutingHook", - "address": "0x72f747270ED9C92c7026b222c5767561Be281DaF", - "constructorArguments": "0000000000000000000000009bbdf86b272d224323136e15594fdce487f40ce7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000011b69ab33ad8a550dcf9b4a041aa1121255f08a5", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x1cE4d0E16570C362feef85Ce2713555fCbd3dBC7", - "constructorArguments": "", - "isProxy": false + "expectedimplementation": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E" }, { - "name": "InterchainGasPaymaster", - "address": "0x1741E83C2504fFEd6E60CF41BbC207c2d61a095F", - "constructorArguments": "", + "name": "ProtocolFee", + "address": "0xC4F464C89184c7870858A8B12ca822daB6ed04F3", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x874AaCa847B365592B2b9dB7235517c5F3a5c689", - "constructorArguments": "0000000000000000000000001741e83c2504ffed6e60cf41bbc207c2d61a095f000000000000000000000000038f9f4e93e88af2c688da265222fde80e455aa400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x1741E83C2504fFEd6E60CF41BbC207c2d61a095F" - }, { "name": "ValidatorAnnounce", - "address": "0x2235662a9a8ED39AE489aafb2feE13Db26f72044", - "constructorArguments": "0000000000000000000000009bbdf86b272d224323136e15594fdce487f40ce7", + "address": "0x74B2b1fC57B28e11A5bAf32a758bbC98FA7837da", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", "isProxy": false } ], - "matchain": [ + "opbnb": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -7093,7 +6782,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000002ba", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000000cc", "isProxy": false }, { @@ -7105,55 +6794,55 @@ }, { "name": "MerkleTreeHook", - "address": "0x021D2810a758c833080DEc2F1Fa8F571Aae97D45", + "address": "0xcDD89f19b2d00DCB9510BB3fBd5eCeCa761fe5Ab", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0xf0F937943Cd6D2a5D02b7f96C9Dd9e04AB633813", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000021d2810a758c833080dec2f1fa8f571aae97d45", + "address": "0xdf4aA3905e0391C7763e33CB6A08fFa97221D49B", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000cdd89f19b2d00dcb9510bb3fbd5ececa761fe5ab", "isProxy": false }, { "name": "PausableHook", - "address": "0x46008F5971eFb16e6c354Ef993EA021B489bc055", + "address": "0x72246331d057741008751AB3976a8297Ce7267Bc", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0xff72A726Ce261846f2dF6F32113e514b5Ddb0E37", + "address": "0x7947b7Fe737B4bd1D3387153f32148974066E591", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0xa2401b57A8CCBF6AbD9b7e62e28811b2b523AB2B", + "address": "0xf303B04d9ad21dAe2658Cf302478A424e0B45368", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x9629c28990F11c31735765A6FD59E1E1bC197DbD", - "constructorArguments": "000000000000000000000000a2401b57a8ccbf6abd9b7e62e28811b2b523ab2b0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0xE911e75CECe0eF01df3ceD96BCd362941AE474D4", + "constructorArguments": "000000000000000000000000f303b04d9ad21dae2658cf302478a424e0b453680000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xa2401b57A8CCBF6AbD9b7e62e28811b2b523AB2B" + "expectedimplementation": "0xf303B04d9ad21dAe2658Cf302478A424e0B45368" }, { "name": "ProtocolFee", - "address": "0xb201817dFdd822B75Fa9b595457E6Ee466a7C187", + "address": "0x6D48135b7584E8Bf828B6e23110Bc0Da4252704f", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0xC5f2c60073DCAA9D157C45d5B017D639dF9C5CeB", + "address": "0x9fE454AA2B01fc7A2a777AE561bc58Ce560CD5a9", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "unitzero": [ + "reactive": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -7163,7 +6852,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000015aeb", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000000063d", "isProxy": false }, { @@ -7175,463 +6864,125 @@ }, { "name": "MerkleTreeHook", - "address": "0x021D2810a758c833080DEc2F1Fa8F571Aae97D45", + "address": "0xcDD89f19b2d00DCB9510BB3fBd5eCeCa761fe5Ab", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0xf0F937943Cd6D2a5D02b7f96C9Dd9e04AB633813", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000021d2810a758c833080dec2f1fa8f571aae97d45", + "address": "0xdf4aA3905e0391C7763e33CB6A08fFa97221D49B", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000cdd89f19b2d00dcb9510bb3fbd5ececa761fe5ab", "isProxy": false }, { "name": "PausableHook", - "address": "0x46008F5971eFb16e6c354Ef993EA021B489bc055", + "address": "0x72246331d057741008751AB3976a8297Ce7267Bc", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0xff72A726Ce261846f2dF6F32113e514b5Ddb0E37", + "address": "0x7947b7Fe737B4bd1D3387153f32148974066E591", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0xa2401b57A8CCBF6AbD9b7e62e28811b2b523AB2B", + "address": "0xf303B04d9ad21dAe2658Cf302478A424e0B45368", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x9629c28990F11c31735765A6FD59E1E1bC197DbD", - "constructorArguments": "000000000000000000000000a2401b57a8ccbf6abd9b7e62e28811b2b523ab2b0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0xE911e75CECe0eF01df3ceD96BCd362941AE474D4", + "constructorArguments": "000000000000000000000000f303b04d9ad21dae2658cf302478a424e0b453680000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xa2401b57A8CCBF6AbD9b7e62e28811b2b523AB2B" - }, - { - "name": "ProtocolFee", - "address": "0xb201817dFdd822B75Fa9b595457E6Ee466a7C187", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0xC5f2c60073DCAA9D157C45d5B017D639dF9C5CeB", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - } - ], - "berachain": [ - { - "name": "ProxyAdmin", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000138de", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", - "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa70000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7" - }, - { - "name": "MerkleTreeHook", - "address": "0x8F23872dAb3B166cef411EeB6C391Ff6Ce419532", - "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b39", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x7937CB2886f01F38210506491A69B0D107Ea0ad9", - "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b39000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000008f23872dab3b166cef411eeb6c391ff6ce419532", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0x2351FBe24C1212F253b7a300ff0cBCFd97952a19", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0xb201817dFdd822B75Fa9b595457E6Ee466a7C187", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0xC5f2c60073DCAA9D157C45d5B017D639dF9C5CeB", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x7Ce3a48cd9FD80004d95b088760bD05bA86C1f7b", - "constructorArguments": "000000000000000000000000c5f2c60073dcaa9d157c45d5b017d639df9c5ceb0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xC5f2c60073DCAA9D157C45d5B017D639dF9C5CeB" - }, - { - "name": "ProtocolFee", - "address": "0xF16E63B42Df7f2676B373979120BBf7e6298F473", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0xa377b8269e0A47cdd2fD5AAeAe860b45623c6d82", - "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b39", - "isProxy": false - } - ], - "sophon": [ - { - "name": "ProxyAdmin", - "address": "0x038F9F4e93e88Af2C688da265222FdE80e455aA4", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0xA3949b37109d64b10De93252EeFebBB2E6B8944F", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000c3b8", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x9BbDf86b272d224323136E15594fdCe487F40ce7", - "constructorArguments": "000000000000000000000000a3949b37109d64b10de93252eefebbb2e6b8944f000000000000000000000000038f9f4e93e88af2c688da265222fde80e455aa400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xA3949b37109d64b10De93252EeFebBB2E6B8944F" - }, - { - "name": "MerkleTreeHook", - "address": "0x697a90753B7dCf6512189c239E612fC12baaE500", - "constructorArguments": "0000000000000000000000009bbdf86b272d224323136e15594fdce487f40ce7", - "isProxy": false - }, - { - "name": "FallbackDomainRoutingHook", - "address": "0xc364cfedefE854c1275B0f4088EaFA9695e1FC56", - "constructorArguments": "0000000000000000000000009bbdf86b272d224323136e15594fdce487f40ce7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000697a90753b7dcf6512189c239e612fc12baae500", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0xA1ADFCa9666Bcd68b7b5C8b55e3ecC465DcDfE65", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0x388289cd5862e17AAfD6ffF7F46A9Ec48a969bCd", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x73a82061Cd258d02BEa145fe183120456e718c2A", - "constructorArguments": "000000000000000000000000388289cd5862e17aafd6fff7f46a9ec48a969bcd000000000000000000000000038f9f4e93e88af2c688da265222fde80e455aa400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x388289cd5862e17AAfD6ffF7F46A9Ec48a969bCd" - }, - { - "name": "ValidatorAnnounce", - "address": "0x54E88f2ab58E0Ab4B7Ce081FB20D85b16af041d2", - "constructorArguments": "0000000000000000000000009bbdf86b272d224323136e15594fdce487f40ce7", - "isProxy": false - } - ], - "story": [ - { - "name": "ProxyAdmin", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000005ea", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" - }, - { - "name": "MerkleTreeHook", - "address": "0xa377b8269e0A47cdd2fD5AAeAe860b45623c6d82", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x82540c4C1C6956FC4815E583DDc6d88A782E0F3e", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a377b8269e0a47cdd2fd5aaeae860b45623c6d82", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0x3E57E4908FB4Ac05e9928feE2Ffd78f59692317C", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x6e1B9f776bd415d7cC3C7458A5f0d801016918f8", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0x63012EE26bda8E5D1b96218778Eaf2492E553469", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x5cD695ADCB156589cde501822C314bFD74398cA1", - "constructorArguments": "00000000000000000000000063012ee26bda8e5d1b96218778eaf2492e5534690000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x63012EE26bda8E5D1b96218778Eaf2492E553469" - }, - { - "name": "ProtocolFee", - "address": "0xd386Bb418B61E296e1689C95AfE94A2E321a6eaD", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0xB2b0A80b2fa3fC9aB1564A4FaF013d4D6084B325", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - } - ], - "bouncebit": [ - { - "name": "ProxyAdmin", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000001771", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" - }, - { - "name": "MerkleTreeHook", - "address": "0xa377b8269e0A47cdd2fD5AAeAe860b45623c6d82", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x82540c4C1C6956FC4815E583DDc6d88A782E0F3e", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a377b8269e0a47cdd2fd5aaeae860b45623c6d82", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0x3E57E4908FB4Ac05e9928feE2Ffd78f59692317C", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x6e1B9f776bd415d7cC3C7458A5f0d801016918f8", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0x63012EE26bda8E5D1b96218778Eaf2492E553469", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x5cD695ADCB156589cde501822C314bFD74398cA1", - "constructorArguments": "00000000000000000000000063012ee26bda8e5d1b96218778eaf2492e5534690000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x63012EE26bda8E5D1b96218778Eaf2492E553469" - }, - { - "name": "ProtocolFee", - "address": "0xd386Bb418B61E296e1689C95AfE94A2E321a6eaD", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0xB2b0A80b2fa3fC9aB1564A4FaF013d4D6084B325", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - } - ], - "ronin": [ - { - "name": "ProxyAdmin", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000007e4", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" - }, - { - "name": "MerkleTreeHook", - "address": "0xa377b8269e0A47cdd2fD5AAeAe860b45623c6d82", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x82540c4C1C6956FC4815E583DDc6d88A782E0F3e", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a377b8269e0a47cdd2fd5aaeae860b45623c6d82", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0x3E57E4908FB4Ac05e9928feE2Ffd78f59692317C", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x6e1B9f776bd415d7cC3C7458A5f0d801016918f8", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0x63012EE26bda8E5D1b96218778Eaf2492E553469", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x5cD695ADCB156589cde501822C314bFD74398cA1", - "constructorArguments": "00000000000000000000000063012ee26bda8e5d1b96218778eaf2492e5534690000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x63012EE26bda8E5D1b96218778Eaf2492E553469" + "expectedimplementation": "0xf303B04d9ad21dAe2658Cf302478A424e0B45368" }, { "name": "ProtocolFee", - "address": "0xd386Bb418B61E296e1689C95AfE94A2E321a6eaD", + "address": "0x6D48135b7584E8Bf828B6e23110Bc0Da4252704f", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0xB2b0A80b2fa3fC9aB1564A4FaF013d4D6084B325", + "address": "0x9fE454AA2B01fc7A2a777AE561bc58Ce560CD5a9", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "arcadia": [ + "coti": [ { "name": "ProxyAdmin", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "address": "0xC343A7054838FE9F249D7E3Ec1Fa6f1D108694b8", "constructorArguments": "", "isProxy": false }, { "name": "Mailbox", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000414950", + "address": "0x14c3CEee8F431aE947364f43429a98EA89800238", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000282b34", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x398633D19f4371e1DB5a8EFE90468eB70B1176AA", + "constructorArguments": "00000000000000000000000014c3ceee8f431ae947364f43429a98ea89800238000000000000000000000000c343a7054838fe9f249d7e3ec1fa6f1d108694b800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" + "expectedimplementation": "0x14c3CEee8F431aE947364f43429a98EA89800238" }, { "name": "MerkleTreeHook", - "address": "0x6e1B9f776bd415d7cC3C7458A5f0d801016918f8", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0x6D48135b7584E8Bf828B6e23110Bc0Da4252704f", + "constructorArguments": "000000000000000000000000398633d19f4371e1db5a8efe90468eb70b1176aa", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x7927B6fE8FA061c32CE3771d11076E6161DE5f52", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006e1b9f776bd415d7cc3c7458a5f0d801016918f8", + "address": "0x9fE454AA2B01fc7A2a777AE561bc58Ce560CD5a9", + "constructorArguments": "000000000000000000000000398633d19f4371e1db5a8efe90468eb70b1176aa000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006d48135b7584e8bf828b6e23110bc0da4252704f", "isProxy": false }, { "name": "PausableHook", - "address": "0x63012EE26bda8E5D1b96218778Eaf2492E553469", + "address": "0x2FF6cf2651fec512D0618E33c9d1374aaCd8b310", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x3862A9B1aCd89245a59002C2a08658EC1d5690E3", + "address": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x8da1aE5A1fA3883c1c12b46270989EAC0EE7BA78", + "address": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x30a539E2E2d09FB4e68661B1EDD70D266211602a", - "constructorArguments": "0000000000000000000000008da1ae5a1fa3883c1c12b46270989eac0ee7ba780000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x8452363d5c78bf95538614441Dc8B465e03A89ca", + "constructorArguments": "00000000000000000000000089ebf977e83087959ad78e5372f4af15dcdc8143000000000000000000000000c343a7054838fe9f249d7e3ec1fa6f1d108694b800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x8da1aE5A1fA3883c1c12b46270989EAC0EE7BA78" + "expectedimplementation": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143" }, { "name": "ProtocolFee", - "address": "0x51545389E04c2Ac07d98A40b85d29B480a2AF6ce", + "address": "0x7B8AA8f23Ab6B0757eC6FC71894211376D9335b0", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0xEa2Bcee14eA30bbBe3018E5E7829F963230F71C3", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0xD233433AeC23F8382DAd87D808F60557Ea35399f", + "constructorArguments": "000000000000000000000000398633d19f4371e1db5a8efe90468eb70b1176aa", "isProxy": false } ], - "hyperevm": [ + "nibiru": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -7641,7 +6992,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000003e7", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000001af4", "isProxy": false }, { @@ -7651,33 +7002,20 @@ "isProxy": true, "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" }, - { - "name": "InterchainGasPaymaster", - "address": "0x51545389E04c2Ac07d98A40b85d29B480a2AF6ce", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xEa2Bcee14eA30bbBe3018E5E7829F963230F71C3", - "constructorArguments": "00000000000000000000000051545389e04c2ac07d98a40b85d29b480a2af6ce0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x51545389E04c2Ac07d98A40b85d29B480a2AF6ce" - }, { "name": "ProtocolFee", - "address": "0x8d7E604460E1133ebB91513a6D1024f3A3ca17F9", + "address": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0xf4035357EB3e3B48E498FA6e1207892f615A2c2f", + "address": "0x4A91738390a3D55CB27c2863e8950c9cD1b89d0e", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "plume": [ + "fluence": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -7687,7 +7025,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000018232", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000098967f", "isProxy": false }, { @@ -7699,88 +7037,55 @@ }, { "name": "MerkleTreeHook", - "address": "0x25C87e735021F72d8728438C2130b02E3141f2cb", + "address": "0x2FF6cf2651fec512D0618E33c9d1374aaCd8b310", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0xB2b0A80b2fa3fC9aB1564A4FaF013d4D6084B325", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000025c87e735021f72d8728438c2130b02e3141f2cb", + "address": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000002ff6cf2651fec512d0618e33c9d1374aacd8b310", "isProxy": false }, { "name": "PausableHook", - "address": "0x51545389E04c2Ac07d98A40b85d29B480a2AF6ce", + "address": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x1e4dE25C3b07c8DF66D4c193693d8B5f3b431d51", + "address": "0x4A91738390a3D55CB27c2863e8950c9cD1b89d0e", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x2D374F85AE2B80147CffEb34d294ce02d1afd4D8", + "address": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xc261Bd2BD995d3D0026e918cBFD44b0Cc5416a57", - "constructorArguments": "0000000000000000000000002d374f85ae2b80147cffeb34d294ce02d1afd4d80000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x0DbB60c348DF645c295Fd0ce26F87bB850710185", + "constructorArguments": "00000000000000000000000089ebf977e83087959ad78e5372f4af15dcdc81430000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x2D374F85AE2B80147CffEb34d294ce02d1afd4D8" + "expectedimplementation": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143" }, { "name": "ProtocolFee", - "address": "0x4eB0d97B48711950ecB01871125c4523939c6Fce", + "address": "0xF6CC9B10c607afB777380bF71F272E4D7037C3A9", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0xbE58F200ffca4e1cE4D2F4541E94Ae18370fC405", + "address": "0x7B8AA8f23Ab6B0757eC6FC71894211376D9335b0", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "infinityvm": [ - { - "name": "ProxyAdmin", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000fbf49", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E" - }, - { - "name": "ProtocolFee", - "address": "0xC4F464C89184c7870858A8B12ca822daB6ed04F3", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0x74B2b1fC57B28e11A5bAf32a758bbC98FA7837da", - "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", - "isProxy": false - } - ], - "opbnb": [ + "game7": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -7790,7 +7095,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000000cc", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000000088b", "isProxy": false }, { @@ -7802,125 +7107,125 @@ }, { "name": "MerkleTreeHook", - "address": "0xcDD89f19b2d00DCB9510BB3fBd5eCeCa761fe5Ab", + "address": "0x2FF6cf2651fec512D0618E33c9d1374aaCd8b310", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0xdf4aA3905e0391C7763e33CB6A08fFa97221D49B", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000cdd89f19b2d00dcb9510bb3fbd5ececa761fe5ab", + "address": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000002ff6cf2651fec512d0618e33c9d1374aacd8b310", "isProxy": false }, { "name": "PausableHook", - "address": "0x72246331d057741008751AB3976a8297Ce7267Bc", + "address": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x7947b7Fe737B4bd1D3387153f32148974066E591", + "address": "0x4A91738390a3D55CB27c2863e8950c9cD1b89d0e", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0xf303B04d9ad21dAe2658Cf302478A424e0B45368", + "address": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xE911e75CECe0eF01df3ceD96BCd362941AE474D4", - "constructorArguments": "000000000000000000000000f303b04d9ad21dae2658cf302478a424e0b453680000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x0DbB60c348DF645c295Fd0ce26F87bB850710185", + "constructorArguments": "00000000000000000000000089ebf977e83087959ad78e5372f4af15dcdc81430000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xf303B04d9ad21dAe2658Cf302478A424e0B45368" + "expectedimplementation": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143" }, { "name": "ProtocolFee", - "address": "0x6D48135b7584E8Bf828B6e23110Bc0Da4252704f", + "address": "0xF6CC9B10c607afB777380bF71F272E4D7037C3A9", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x9fE454AA2B01fc7A2a777AE561bc58Ce560CD5a9", + "address": "0x7B8AA8f23Ab6B0757eC6FC71894211376D9335b0", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "deepbrainchain": [ + "hashkey": [ { "name": "ProxyAdmin", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "constructorArguments": "", "isProxy": false }, { "name": "Mailbox", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000012f5b72", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000000b1", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" + "expectedimplementation": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E" }, { "name": "MerkleTreeHook", - "address": "0xcDD89f19b2d00DCB9510BB3fBd5eCeCa761fe5Ab", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0xdf4aA3905e0391C7763e33CB6A08fFa97221D49B", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000cdd89f19b2d00dcb9510bb3fbd5ececa761fe5ab", + "address": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000005b7a808caa2c3f1378b07cdd46eb8cca52f67e3b", "isProxy": false }, { "name": "PausableHook", - "address": "0x72246331d057741008751AB3976a8297Ce7267Bc", + "address": "0x4A91738390a3D55CB27c2863e8950c9cD1b89d0e", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x7947b7Fe737B4bd1D3387153f32148974066E591", + "address": "0xBCD18636e5876DFd7AAb5F2B2a5Eb5ca168BA1d8", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0xf303B04d9ad21dAe2658Cf302478A424e0B45368", + "address": "0x284226F651eb5cbd696365BC27d333028FCc5D54", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xE911e75CECe0eF01df3ceD96BCd362941AE474D4", - "constructorArguments": "000000000000000000000000f303b04d9ad21dae2658cf302478a424e0b453680000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x8452363d5c78bf95538614441Dc8B465e03A89ca", + "constructorArguments": "000000000000000000000000284226f651eb5cbd696365bc27d333028fcc5d54000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xf303B04d9ad21dAe2658Cf302478A424e0B45368" + "expectedimplementation": "0x284226F651eb5cbd696365BC27d333028FCc5D54" }, { "name": "ProtocolFee", - "address": "0x6D48135b7584E8Bf828B6e23110Bc0Da4252704f", + "address": "0x794Fe7970EE45945b0ad2667f99A5bBc9ddfB5d7", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x9fE454AA2B01fc7A2a777AE561bc58Ce560CD5a9", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0x6c5012B7eDfE317Be53D13Fc730a460f4810e234", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", "isProxy": false } ], - "reactive": [ + "infinityvmmainnet": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -7930,7 +7235,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000063d", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003baa8949", "isProxy": false }, { @@ -7942,95 +7247,95 @@ }, { "name": "MerkleTreeHook", - "address": "0xcDD89f19b2d00DCB9510BB3fBd5eCeCa761fe5Ab", + "address": "0x2FF6cf2651fec512D0618E33c9d1374aaCd8b310", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0xdf4aA3905e0391C7763e33CB6A08fFa97221D49B", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000cdd89f19b2d00dcb9510bb3fbd5ececa761fe5ab", + "address": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000002ff6cf2651fec512d0618e33c9d1374aacd8b310", "isProxy": false }, { "name": "PausableHook", - "address": "0x72246331d057741008751AB3976a8297Ce7267Bc", + "address": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x7947b7Fe737B4bd1D3387153f32148974066E591", + "address": "0x4A91738390a3D55CB27c2863e8950c9cD1b89d0e", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0xf303B04d9ad21dAe2658Cf302478A424e0B45368", + "address": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xE911e75CECe0eF01df3ceD96BCd362941AE474D4", - "constructorArguments": "000000000000000000000000f303b04d9ad21dae2658cf302478a424e0b453680000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x0DbB60c348DF645c295Fd0ce26F87bB850710185", + "constructorArguments": "00000000000000000000000089ebf977e83087959ad78e5372f4af15dcdc81430000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xf303B04d9ad21dAe2658Cf302478A424e0B45368" + "expectedimplementation": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143" }, { "name": "ProtocolFee", - "address": "0x6D48135b7584E8Bf828B6e23110Bc0Da4252704f", + "address": "0xF6CC9B10c607afB777380bF71F272E4D7037C3A9", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x9fE454AA2B01fc7A2a777AE561bc58Ce560CD5a9", + "address": "0x7B8AA8f23Ab6B0757eC6FC71894211376D9335b0", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "coti": [ + "peaq": [ { "name": "ProxyAdmin", - "address": "0xC343A7054838FE9F249D7E3Ec1Fa6f1D108694b8", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "constructorArguments": "", "isProxy": false }, { "name": "Mailbox", - "address": "0x14c3CEee8F431aE947364f43429a98EA89800238", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000282b34", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000d0a", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x398633D19f4371e1DB5a8EFE90468eB70B1176AA", - "constructorArguments": "00000000000000000000000014c3ceee8f431ae947364f43429a98ea89800238000000000000000000000000c343a7054838fe9f249d7e3ec1fa6f1d108694b800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x14c3CEee8F431aE947364f43429a98EA89800238" + "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" }, { "name": "MerkleTreeHook", - "address": "0x6D48135b7584E8Bf828B6e23110Bc0Da4252704f", - "constructorArguments": "000000000000000000000000398633d19f4371e1db5a8efe90468eb70b1176aa", + "address": "0x2FF6cf2651fec512D0618E33c9d1374aaCd8b310", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x9fE454AA2B01fc7A2a777AE561bc58Ce560CD5a9", - "constructorArguments": "000000000000000000000000398633d19f4371e1db5a8efe90468eb70b1176aa000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000006d48135b7584e8bf828b6e23110bc0da4252704f", + "address": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000002ff6cf2651fec512d0618e33c9d1374aacd8b310", "isProxy": false }, { "name": "PausableHook", - "address": "0x2FF6cf2651fec512D0618E33c9d1374aaCd8b310", + "address": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", + "address": "0x4A91738390a3D55CB27c2863e8950c9cD1b89d0e", "constructorArguments": "", "isProxy": false }, @@ -8042,25 +7347,25 @@ }, { "name": "TransparentUpgradeableProxy", - "address": "0x8452363d5c78bf95538614441Dc8B465e03A89ca", - "constructorArguments": "00000000000000000000000089ebf977e83087959ad78e5372f4af15dcdc8143000000000000000000000000c343a7054838fe9f249d7e3ec1fa6f1d108694b800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x0DbB60c348DF645c295Fd0ce26F87bB850710185", + "constructorArguments": "00000000000000000000000089ebf977e83087959ad78e5372f4af15dcdc81430000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143" }, { "name": "ProtocolFee", - "address": "0x7B8AA8f23Ab6B0757eC6FC71894211376D9335b0", + "address": "0xF6CC9B10c607afB777380bF71F272E4D7037C3A9", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0xD233433AeC23F8382DAd87D808F60557Ea35399f", - "constructorArguments": "000000000000000000000000398633d19f4371e1db5a8efe90468eb70b1176aa", + "address": "0x7B8AA8f23Ab6B0757eC6FC71894211376D9335b0", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "nibiru": [ + "miraclechain": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -8070,7 +7375,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000001af4", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000016876", "isProxy": false }, { @@ -8080,20 +7385,57 @@ "isProxy": true, "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" }, + { + "name": "MerkleTreeHook", + "address": "0x64F077915B41479901a23Aa798B30701589C5063", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0x0dc95Af5156fb0cC34a8c9BD646B748B9989A956", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000064f077915b41479901a23aa798b30701589c5063", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xF6CC9B10c607afB777380bF71F272E4D7037C3A9", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x794Fe7970EE45945b0ad2667f99A5bBc9ddfB5d7", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0xF9aE87E9ACE51aa16AED25Ca38F17D258aECb73f", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xD233433AeC23F8382DAd87D808F60557Ea35399f", + "constructorArguments": "000000000000000000000000f9ae87e9ace51aa16aed25ca38f17d258aecb73f0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xF9aE87E9ACE51aa16AED25Ca38F17D258aECb73f" + }, { "name": "ProtocolFee", - "address": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", + "address": "0x946E9f4540E032a9fAc038AE58187eFcad9DE952", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x4A91738390a3D55CB27c2863e8950c9cD1b89d0e", + "address": "0x5b1A451c85e5bcCd79A56eb638aBd9998fA215f9", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "fluence": [ + "ontology": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -8103,7 +7445,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000098967f", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000000003a", "isProxy": false }, { @@ -8115,55 +7457,55 @@ }, { "name": "MerkleTreeHook", - "address": "0x2FF6cf2651fec512D0618E33c9d1374aaCd8b310", + "address": "0xb5668713E9BA8bC96f97D691663E70b54CE90b0A", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000002ff6cf2651fec512d0618e33c9d1374aacd8b310", + "address": "0x3E12271EbD523d0886D0D51A4FF8D8e046CF2E1D", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000b5668713e9ba8bc96f97d691663e70b54ce90b0a", "isProxy": false }, { "name": "PausableHook", - "address": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", + "address": "0xC34aD1fB13a9eE1A0C0AB75aD9B9ceeA0690Cc74", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x4A91738390a3D55CB27c2863e8950c9cD1b89d0e", + "address": "0xc5068BB6803ADbe5600DE5189fe27A4dAcE31170", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143", + "address": "0x8E273260EAd8B72A085B19346A676d355740e875", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x0DbB60c348DF645c295Fd0ce26F87bB850710185", - "constructorArguments": "00000000000000000000000089ebf977e83087959ad78e5372f4af15dcdc81430000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x11EF91d17c5ad3330DbCa709a8841743d3Af6819", + "constructorArguments": "0000000000000000000000008e273260ead8b72a085b19346a676d355740e8750000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143" + "expectedimplementation": "0x8E273260EAd8B72A085B19346A676d355740e875" }, { "name": "ProtocolFee", - "address": "0xF6CC9B10c607afB777380bF71F272E4D7037C3A9", + "address": "0xaD09d78f4c6b9dA2Ae82b1D34107802d380Bb74f", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x7B8AA8f23Ab6B0757eC6FC71894211376D9335b0", + "address": "0x4e128A1b613A9C9Ecf650FeE461c353612559fcf", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "game7": [ + "katana": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -8173,7 +7515,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000088b", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000b67d2", "isProxy": false }, { @@ -8185,125 +7527,125 @@ }, { "name": "MerkleTreeHook", - "address": "0x2FF6cf2651fec512D0618E33c9d1374aaCd8b310", + "address": "0x2ed6030D204745aC0Cd6be8301C3a63bf14D97Cc", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000002ff6cf2651fec512d0618e33c9d1374aacd8b310", + "address": "0x5CBD4c5f9CD55747285652f815Cc7b9A2Ef6c586", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000002ed6030d204745ac0cd6be8301c3a63bf14d97cc", "isProxy": false }, { "name": "PausableHook", - "address": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", + "address": "0x946E9f4540E032a9fAc038AE58187eFcad9DE952", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x4A91738390a3D55CB27c2863e8950c9cD1b89d0e", + "address": "0xA9Adb480F10547f10202173a49b7F52116304476", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143", + "address": "0xc8826EA18D9884A1A335b2Cd7d5f44B159084301", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x0DbB60c348DF645c295Fd0ce26F87bB850710185", - "constructorArguments": "00000000000000000000000089ebf977e83087959ad78e5372f4af15dcdc81430000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x23cc88CF424d48fDB05b4f0A8Ff6099aa4D56D8e", + "constructorArguments": "000000000000000000000000c8826ea18d9884a1a335b2cd7d5f44b1590843010000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143" + "expectedimplementation": "0xc8826EA18D9884A1A335b2Cd7d5f44B159084301" }, { "name": "ProtocolFee", - "address": "0xF6CC9B10c607afB777380bF71F272E4D7037C3A9", + "address": "0xf50e2f7ff2D367fac0B90Be2564A096168801b2d", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x7B8AA8f23Ab6B0757eC6FC71894211376D9335b0", + "address": "0x466b330C2e360c0214A9Da2428415298f720883E", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "hashkey": [ + "botanix": [ { "name": "ProxyAdmin", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "constructorArguments": "", "isProxy": false }, { "name": "Mailbox", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000000b1", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000e35", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E" + "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" }, { "name": "MerkleTreeHook", - "address": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", - "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "address": "0x2ed6030D204745aC0Cd6be8301C3a63bf14D97Cc", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", - "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000005b7a808caa2c3f1378b07cdd46eb8cca52f67e3b", + "address": "0x5CBD4c5f9CD55747285652f815Cc7b9A2Ef6c586", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000002ed6030d204745ac0cd6be8301c3a63bf14d97cc", "isProxy": false }, { "name": "PausableHook", - "address": "0x4A91738390a3D55CB27c2863e8950c9cD1b89d0e", + "address": "0x946E9f4540E032a9fAc038AE58187eFcad9DE952", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0xBCD18636e5876DFd7AAb5F2B2a5Eb5ca168BA1d8", + "address": "0xA9Adb480F10547f10202173a49b7F52116304476", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x284226F651eb5cbd696365BC27d333028FCc5D54", + "address": "0xc8826EA18D9884A1A335b2Cd7d5f44B159084301", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x8452363d5c78bf95538614441Dc8B465e03A89ca", - "constructorArguments": "000000000000000000000000284226f651eb5cbd696365bc27d333028fcc5d54000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x23cc88CF424d48fDB05b4f0A8Ff6099aa4D56D8e", + "constructorArguments": "000000000000000000000000c8826ea18d9884a1a335b2cd7d5f44b1590843010000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x284226F651eb5cbd696365BC27d333028FCc5D54" + "expectedimplementation": "0xc8826EA18D9884A1A335b2Cd7d5f44B159084301" }, { "name": "ProtocolFee", - "address": "0x794Fe7970EE45945b0ad2667f99A5bBc9ddfB5d7", + "address": "0xf50e2f7ff2D367fac0B90Be2564A096168801b2d", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x6c5012B7eDfE317Be53D13Fc730a460f4810e234", - "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "address": "0x466b330C2e360c0214A9Da2428415298f720883E", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "infinityvmmainnet": [ + "tac": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -8313,7 +7655,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003baa8949", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000000ef", "isProxy": false }, { @@ -8325,55 +7667,55 @@ }, { "name": "MerkleTreeHook", - "address": "0x2FF6cf2651fec512D0618E33c9d1374aaCd8b310", + "address": "0x58CA3F0D65FFd40727cF6Eb2f323151853Ad3D44", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000002ff6cf2651fec512d0618e33c9d1374aacd8b310", + "address": "0x23cc88CF424d48fDB05b4f0A8Ff6099aa4D56D8e", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000058ca3f0d65ffd40727cf6eb2f323151853ad3d44", "isProxy": false }, { "name": "PausableHook", - "address": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", + "address": "0xA697222b77cDe62A8C47E627d5A1c49A87018236", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x4A91738390a3D55CB27c2863e8950c9cD1b89d0e", + "address": "0xcF84aa21501483B3D907d16c8102082a0991fC5F", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143", + "address": "0xf50e2f7ff2D367fac0B90Be2564A096168801b2d", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x0DbB60c348DF645c295Fd0ce26F87bB850710185", - "constructorArguments": "00000000000000000000000089ebf977e83087959ad78e5372f4af15dcdc81430000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x466b330C2e360c0214A9Da2428415298f720883E", + "constructorArguments": "000000000000000000000000f50e2f7ff2d367fac0b90be2564a096168801b2d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143" + "expectedimplementation": "0xf50e2f7ff2D367fac0B90Be2564A096168801b2d" }, { "name": "ProtocolFee", - "address": "0xF6CC9B10c607afB777380bF71F272E4D7037C3A9", + "address": "0xe93f2f409ad8B5000431D234472973fe848dcBEC", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x7B8AA8f23Ab6B0757eC6FC71894211376D9335b0", + "address": "0x96D51cc3f7500d501bAeB1A2a62BB96fa03532F8", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "peaq": [ + "galactica": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -8383,7 +7725,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000d0a", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000095c2b", "isProxy": false }, { @@ -8395,55 +7737,55 @@ }, { "name": "MerkleTreeHook", - "address": "0x2FF6cf2651fec512D0618E33c9d1374aaCd8b310", + "address": "0x64F077915B41479901a23Aa798B30701589C5063", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000002ff6cf2651fec512d0618e33c9d1374aacd8b310", + "address": "0x0dc95Af5156fb0cC34a8c9BD646B748B9989A956", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000064f077915b41479901a23aa798b30701589c5063", "isProxy": false }, { "name": "PausableHook", - "address": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", + "address": "0xF6CC9B10c607afB777380bF71F272E4D7037C3A9", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x4A91738390a3D55CB27c2863e8950c9cD1b89d0e", + "address": "0x794Fe7970EE45945b0ad2667f99A5bBc9ddfB5d7", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143", + "address": "0xF9aE87E9ACE51aa16AED25Ca38F17D258aECb73f", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x0DbB60c348DF645c295Fd0ce26F87bB850710185", - "constructorArguments": "00000000000000000000000089ebf977e83087959ad78e5372f4af15dcdc81430000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0xD233433AeC23F8382DAd87D808F60557Ea35399f", + "constructorArguments": "000000000000000000000000f9ae87e9ace51aa16aed25ca38f17d258aecb73f0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143" + "expectedimplementation": "0xF9aE87E9ACE51aa16AED25Ca38F17D258aECb73f" }, { "name": "ProtocolFee", - "address": "0xF6CC9B10c607afB777380bF71F272E4D7037C3A9", + "address": "0x946E9f4540E032a9fAc038AE58187eFcad9DE952", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x7B8AA8f23Ab6B0757eC6FC71894211376D9335b0", + "address": "0x5b1A451c85e5bcCd79A56eb638aBd9998fA215f9", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "miraclechain": [ + "xrplevm": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -8453,7 +7795,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000016876", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000015f900", "isProxy": false }, { @@ -8513,7 +7855,7 @@ "isProxy": false } ], - "ontology": [ + "mitosis": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -8523,7 +7865,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000003a", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000001e790", "isProxy": false }, { @@ -8535,125 +7877,125 @@ }, { "name": "MerkleTreeHook", - "address": "0xb5668713E9BA8bC96f97D691663E70b54CE90b0A", + "address": "0x1e4dE25C3b07c8DF66D4c193693d8B5f3b431d51", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x3E12271EbD523d0886D0D51A4FF8D8e046CF2E1D", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000b5668713e9ba8bc96f97d691663e70b54ce90b0a", + "address": "0xEa2Bcee14eA30bbBe3018E5E7829F963230F71C3", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000001e4de25c3b07c8df66d4c193693d8b5f3b431d51", "isProxy": false }, { "name": "PausableHook", - "address": "0xC34aD1fB13a9eE1A0C0AB75aD9B9ceeA0690Cc74", + "address": "0x2D374F85AE2B80147CffEb34d294ce02d1afd4D8", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0xc5068BB6803ADbe5600DE5189fe27A4dAcE31170", + "address": "0xf6092016a7bef22F7aC9C982C9E96D968F709C05", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x8E273260EAd8B72A085B19346A676d355740e875", + "address": "0x8d7E604460E1133ebB91513a6D1024f3A3ca17F9", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x11EF91d17c5ad3330DbCa709a8841743d3Af6819", - "constructorArguments": "0000000000000000000000008e273260ead8b72a085b19346a676d355740e8750000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0xf4035357EB3e3B48E498FA6e1207892f615A2c2f", + "constructorArguments": "0000000000000000000000008d7e604460e1133ebb91513a6d1024f3a3ca17f90000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x8E273260EAd8B72A085B19346A676d355740e875" + "expectedimplementation": "0x8d7E604460E1133ebB91513a6D1024f3A3ca17F9" }, { "name": "ProtocolFee", - "address": "0xaD09d78f4c6b9dA2Ae82b1D34107802d380Bb74f", + "address": "0x8D8979F2C29bA49FAb259A826D0271c43F70288c", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x4e128A1b613A9C9Ecf650FeE461c353612559fcf", + "address": "0xdf4aA3905e0391C7763e33CB6A08fFa97221D49B", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "katana": [ + "pulsechain": [ { "name": "ProxyAdmin", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "address": "0xEe3147deeDFC0c6DDD7c65D4e9ff9a48632BE645", "constructorArguments": "", "isProxy": false }, { "name": "Mailbox", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000b67d2", + "address": "0x9521291A43ebA3aD3FD24d610F4b7F7543C8d761", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000171", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x56176C7Fb66FdD70ef962Ae53a46A226c7F6a2Cc", + "constructorArguments": "0000000000000000000000009521291a43eba3ad3fd24d610f4b7f7543c8d761000000000000000000000000ee3147deedfc0c6ddd7c65d4e9ff9a48632be64500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D" + "expectedimplementation": "0x9521291A43ebA3aD3FD24d610F4b7F7543C8d761" }, { "name": "MerkleTreeHook", - "address": "0x2ed6030D204745aC0Cd6be8301C3a63bf14D97Cc", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0x9DaC51dF95298453C7fb5b43233818CfA4604daC", + "constructorArguments": "00000000000000000000000056176c7fb66fdd70ef962ae53a46a226c7f6a2cc", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x5CBD4c5f9CD55747285652f815Cc7b9A2Ef6c586", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000002ed6030d204745ac0cd6be8301c3a63bf14d97cc", + "address": "0x4ECc899c97A5a70273C291cB30740CdF1244a931", + "constructorArguments": "00000000000000000000000056176c7fb66fdd70ef962ae53a46a226c7f6a2cc000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000009dac51df95298453c7fb5b43233818cfa4604dac", "isProxy": false }, { "name": "PausableHook", - "address": "0x946E9f4540E032a9fAc038AE58187eFcad9DE952", + "address": "0x7528697bdc7300fFA9470EfbDb6007Ea34e56c38", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0xA9Adb480F10547f10202173a49b7F52116304476", + "address": "0xe974B5F169a5770DbfFB20F669e662Bd721c9Cc4", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0xc8826EA18D9884A1A335b2Cd7d5f44B159084301", + "address": "0xA28Ad72be5c957993B5f347F3E7de55a375D8304", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x23cc88CF424d48fDB05b4f0A8Ff6099aa4D56D8e", - "constructorArguments": "000000000000000000000000c8826ea18d9884a1a335b2cd7d5f44b1590843010000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0xc996F4D7d7F39189921A08F3DaAf1b9ff0b20006", + "constructorArguments": "000000000000000000000000a28ad72be5c957993b5f347f3e7de55a375d8304000000000000000000000000ee3147deedfc0c6ddd7c65d4e9ff9a48632be64500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xc8826EA18D9884A1A335b2Cd7d5f44B159084301" + "expectedimplementation": "0xA28Ad72be5c957993B5f347F3E7de55a375D8304" }, { "name": "ProtocolFee", - "address": "0xf50e2f7ff2D367fac0B90Be2564A096168801b2d", + "address": "0xbadC9416567503f01A737477f88f64d41f817Cba", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x466b330C2e360c0214A9Da2428415298f720883E", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0xB08bd33086CF244D3757843DdD771c3263e1C02c", + "constructorArguments": "00000000000000000000000056176c7fb66fdd70ef962ae53a46a226c7f6a2cc", "isProxy": false } ], - "botanix": [ + "plasma": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -8663,7 +8005,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000e35", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000002611", "isProxy": false }, { @@ -8675,55 +8017,55 @@ }, { "name": "MerkleTreeHook", - "address": "0x2ed6030D204745aC0Cd6be8301C3a63bf14D97Cc", + "address": "0xA7d42B7a7603bEb87f84a1f3D5C97a033DFd2Cc9", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x5CBD4c5f9CD55747285652f815Cc7b9A2Ef6c586", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000002ed6030d204745ac0cd6be8301c3a63bf14d97cc", + "address": "0xbE58F200ffca4e1cE4D2F4541E94Ae18370fC405", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7d42b7a7603beb87f84a1f3d5c97a033dfd2cc9", "isProxy": false }, { "name": "PausableHook", - "address": "0x946E9f4540E032a9fAc038AE58187eFcad9DE952", + "address": "0x8D8979F2C29bA49FAb259A826D0271c43F70288c", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0xA9Adb480F10547f10202173a49b7F52116304476", + "address": "0xcDD89f19b2d00DCB9510BB3fBd5eCeCa761fe5Ab", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0xc8826EA18D9884A1A335b2Cd7d5f44B159084301", + "address": "0x72246331d057741008751AB3976a8297Ce7267Bc", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x23cc88CF424d48fDB05b4f0A8Ff6099aa4D56D8e", - "constructorArguments": "000000000000000000000000c8826ea18d9884a1a335b2cd7d5f44b1590843010000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x1A41a365A693b6A7aED1a46316097d290f569F22", + "constructorArguments": "00000000000000000000000072246331d057741008751ab3976a8297ce7267bc0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xc8826EA18D9884A1A335b2Cd7d5f44B159084301" + "expectedimplementation": "0x72246331d057741008751AB3976a8297Ce7267Bc" }, { "name": "ProtocolFee", - "address": "0xf50e2f7ff2D367fac0B90Be2564A096168801b2d", + "address": "0x238b3Fc6f3D32102AA655984059A051647DA98e2", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x466b330C2e360c0214A9Da2428415298f720883E", + "address": "0x75719C858e0c73e07128F95B2C466d142490e933", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "tac": [ + "electroneum": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -8733,7 +8075,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000000ef", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000000cb2e", "isProxy": false }, { @@ -8745,55 +8087,55 @@ }, { "name": "MerkleTreeHook", - "address": "0x58CA3F0D65FFd40727cF6Eb2f323151853Ad3D44", + "address": "0xA7d42B7a7603bEb87f84a1f3D5C97a033DFd2Cc9", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x23cc88CF424d48fDB05b4f0A8Ff6099aa4D56D8e", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000058ca3f0d65ffd40727cf6eb2f323151853ad3d44", + "address": "0xbE58F200ffca4e1cE4D2F4541E94Ae18370fC405", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7d42b7a7603beb87f84a1f3d5c97a033dfd2cc9", "isProxy": false }, { "name": "PausableHook", - "address": "0xA697222b77cDe62A8C47E627d5A1c49A87018236", + "address": "0x8D8979F2C29bA49FAb259A826D0271c43F70288c", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0xcF84aa21501483B3D907d16c8102082a0991fC5F", + "address": "0xcDD89f19b2d00DCB9510BB3fBd5eCeCa761fe5Ab", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0xf50e2f7ff2D367fac0B90Be2564A096168801b2d", + "address": "0x72246331d057741008751AB3976a8297Ce7267Bc", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x466b330C2e360c0214A9Da2428415298f720883E", - "constructorArguments": "000000000000000000000000f50e2f7ff2d367fac0b90be2564a096168801b2d0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x1A41a365A693b6A7aED1a46316097d290f569F22", + "constructorArguments": "00000000000000000000000072246331d057741008751ab3976a8297ce7267bc0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xf50e2f7ff2D367fac0B90Be2564A096168801b2d" + "expectedimplementation": "0x72246331d057741008751AB3976a8297Ce7267Bc" }, { "name": "ProtocolFee", - "address": "0xe93f2f409ad8B5000431D234472973fe848dcBEC", + "address": "0x238b3Fc6f3D32102AA655984059A051647DA98e2", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x96D51cc3f7500d501bAeB1A2a62BB96fa03532F8", + "address": "0x75719C858e0c73e07128F95B2C466d142490e933", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } ], - "galactica": [ + "sova": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -8803,7 +8145,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000095c2b", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000186b5", "isProxy": false }, { @@ -8815,40 +8157,73 @@ }, { "name": "MerkleTreeHook", - "address": "0x64F077915B41479901a23Aa798B30701589C5063", + "address": "0x7947b7Fe737B4bd1D3387153f32148974066E591", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x0dc95Af5156fb0cC34a8c9BD646B748B9989A956", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000064f077915b41479901a23aa798b30701589c5063", + "address": "0x1A41a365A693b6A7aED1a46316097d290f569F22", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba0000000000000000000000007947b7fe737b4bd1d3387153f32148974066e591", "isProxy": false }, { "name": "PausableHook", - "address": "0xF6CC9B10c607afB777380bF71F272E4D7037C3A9", + "address": "0xf303B04d9ad21dAe2658Cf302478A424e0B45368", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x794Fe7970EE45945b0ad2667f99A5bBc9ddfB5d7", + "address": "0xC4F464C89184c7870858A8B12ca822daB6ed04F3", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0xF9aE87E9ACE51aa16AED25Ca38F17D258aECb73f", + "address": "0x238b3Fc6f3D32102AA655984059A051647DA98e2", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xD233433AeC23F8382DAd87D808F60557Ea35399f", - "constructorArguments": "000000000000000000000000f9ae87e9ace51aa16aed25ca38f17d258aecb73f0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x75719C858e0c73e07128F95B2C466d142490e933", + "constructorArguments": "000000000000000000000000238b3fc6f3d32102aa655984059a051647da98e20000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xF9aE87E9ACE51aa16AED25Ca38F17D258aECb73f" + "expectedimplementation": "0x238b3Fc6f3D32102AA655984059A051647DA98e2" + }, + { + "name": "ProtocolFee", + "address": "0x76F2cC245882ceFf209A61d75b9F0f1A3b7285fB", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "isProxy": false + } + ], + "zerogravity": [ + { + "name": "ProxyAdmin", + "address": "0x02d16BC51af6BfD153d67CA61754cF912E82C4d9", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0xC343A7054838FE9F249D7E3Ec1Fa6f1D108694b8", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000004115", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x8428a1a7E97Fc75Fb7Ba5c4aec31B55e52bbe9D6", + "constructorArguments": "000000000000000000000000c343a7054838fe9f249d7e3ec1fa6f1d108694b800000000000000000000000002d16bc51af6bfd153d67ca61754cf912e82c4d900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xC343A7054838FE9F249D7E3Ec1Fa6f1D108694b8" }, { "name": "ProtocolFee", @@ -8859,11 +8234,11 @@ { "name": "ValidatorAnnounce", "address": "0x5b1A451c85e5bcCd79A56eb638aBd9998fA215f9", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "constructorArguments": "0000000000000000000000008428a1a7e97fc75fb7ba5c4aec31b55e52bbe9d6", "isProxy": false } ], - "xrplevm": [ + "mantra": [ { "name": "ProxyAdmin", "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -8873,7 +8248,7 @@ { "name": "Mailbox", "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000015f900", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000001700", "isProxy": false }, { @@ -8885,50 +8260,50 @@ }, { "name": "MerkleTreeHook", - "address": "0x64F077915B41479901a23Aa798B30701589C5063", + "address": "0xC4F464C89184c7870858A8B12ca822daB6ed04F3", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x0dc95Af5156fb0cC34a8c9BD646B748B9989A956", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000064f077915b41479901a23aa798b30701589c5063", + "address": "0xE911e75CECe0eF01df3ceD96BCd362941AE474D4", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000c4f464c89184c7870858a8b12ca822dab6ed04f3", "isProxy": false }, { "name": "PausableHook", - "address": "0xF6CC9B10c607afB777380bF71F272E4D7037C3A9", + "address": "0x238b3Fc6f3D32102AA655984059A051647DA98e2", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x794Fe7970EE45945b0ad2667f99A5bBc9ddfB5d7", + "address": "0x5887BDA66EC9e854b0da6BFFe423511e69d327DC", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0xF9aE87E9ACE51aa16AED25Ca38F17D258aECb73f", + "address": "0x6D48135b7584E8Bf828B6e23110Bc0Da4252704f", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xD233433AeC23F8382DAd87D808F60557Ea35399f", - "constructorArguments": "000000000000000000000000f9ae87e9ace51aa16aed25ca38f17d258aecb73f0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "address": "0x9fE454AA2B01fc7A2a777AE561bc58Ce560CD5a9", + "constructorArguments": "0000000000000000000000006d48135b7584e8bf828b6e23110bc0da4252704f0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xF9aE87E9ACE51aa16AED25Ca38F17D258aECb73f" + "expectedimplementation": "0x6D48135b7584E8Bf828B6e23110Bc0Da4252704f" }, { "name": "ProtocolFee", - "address": "0x946E9f4540E032a9fAc038AE58187eFcad9DE952", + "address": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x5b1A451c85e5bcCd79A56eb638aBd9998fA215f9", + "address": "0xBCD18636e5876DFd7AAb5F2B2a5Eb5ca168BA1d8", "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", "isProxy": false } diff --git a/typescript/infra/config/environments/mainnet3/customBlacklist.ts b/typescript/infra/config/environments/mainnet3/customBlacklist.ts index 2efce38d63e..9ecc11d3450 100644 --- a/typescript/infra/config/environments/mainnet3/customBlacklist.ts +++ b/typescript/infra/config/environments/mainnet3/customBlacklist.ts @@ -85,6 +85,7 @@ export const blacklistedMessageIds = [ '0xd48b8599f52e75d43916a7d1ec9512c98c5025394bc4983bf7c6309744758b9e', '0x4f984fb0312da11f55b931a809563072bd8a46d44da0e42dfce40a9ccf1b97f2', '0x50b8a9b21a4a2c980db8232d819cab72fe04707e39fc1b78ef5e583a44c412cf', + '0x9ceacfacc3264f45dce5a02475ea5bda33c5cff023d91431a8cae3dc35fc31c3', // Messages to a recipient that Neutron doesn't seem to allow '0x70be30b4f21cfe4dbb05c0c22601c6b4c9cf4a2a73727331dbbb6404d7566e1f', @@ -171,4 +172,11 @@ export const blacklistedMessageIds = [ // Superseed dest: // 7/1/2025 '0x84c7565f1f0b0fc7b5571c86e9f23187d4078779f607b13f73c6016efcf90bc4', + // Paradex USDC: + // 8/27/2025 + '0x151135eda5f04110084c6673d23f041d9141a50bdb02de028e32fe7f4e14e7c2', + + // 09/09/2025 + // Our own message on SVM that references our own relayer address + '0x337d60f17842a6b152f52f41ead0fc6c5f944543a7f15b1c7d31e35dcb943309', ]; diff --git a/typescript/infra/config/environments/mainnet3/funding.ts b/typescript/infra/config/environments/mainnet3/funding.ts index d138dee9f17..495fb80f142 100644 --- a/typescript/infra/config/environments/mainnet3/funding.ts +++ b/typescript/infra/config/environments/mainnet3/funding.ts @@ -1,7 +1,10 @@ +import { objMap } from '@hyperlane-xyz/utils'; + import { KeyFunderConfig } from '../../../src/config/funding.js'; import { Role } from '../../../src/roles.js'; import { Contexts } from '../../contexts.js'; +import desiredRebalancerBalances from './balances/desiredRebalancerBalances.json' with { type: 'json' }; import desiredRelayerBalances from './balances/desiredRelayerBalances.json' with { type: 'json' }; import { environment } from './chains.js'; import { mainnet3SupportedChainNames } from './supportedChainNames.js'; @@ -14,12 +17,18 @@ const desiredRelayerBalancePerChain = Object.fromEntries( ]), ) as Record; +type DesiredRebalancerBalanceChains = keyof typeof desiredRebalancerBalances; +const desiredRebalancerBalancePerChain = objMap( + desiredRebalancerBalances, + (_, balance) => balance.toString(), +) as Record; + export const keyFunderConfig: KeyFunderConfig< typeof mainnet3SupportedChainNames > = { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'f3b9391-20250630-150644', + tag: '6333bb1-20250923-101546', }, // We're currently using the same deployer/key funder key as mainnet2. // To minimize nonce clobbering we offset the key funder cron @@ -30,14 +39,15 @@ export const keyFunderConfig: KeyFunderConfig< 'http://prometheus-prometheus-pushgateway.monitoring.svc.cluster.local:9091', contextFundingFrom: Contexts.Hyperlane, contextsAndRolesToFund: { - [Contexts.Hyperlane]: [Role.Relayer, Role.Kathy], + [Contexts.Hyperlane]: [Role.Relayer, Role.Kathy, Role.Rebalancer], [Contexts.ReleaseCandidate]: [Role.Relayer, Role.Kathy], }, - chainsToSkip: [], + chainsToSkip: ['infinityvmmainnet', 'game7'], // desired balance config, must be set for each chain desiredBalancePerChain: desiredRelayerBalancePerChain, // if not set, keyfunder defaults to 0 desiredKathyBalancePerChain: { + sepolia: '0', ancient8: '0', arbitrum: '0.1', avalanche: '6', @@ -70,7 +80,6 @@ export const keyFunderConfig: KeyFunderConfig< polygonzkevm: '0.05', proofofplay: '0', redstone: '0', - sanko: '0', scroll: '0.05', sei: '0', taiko: '0', @@ -91,8 +100,11 @@ export const keyFunderConfig: KeyFunderConfig< soon: '0', sonicsvm: '0', }, + // desired rebalancer balance config + desiredRebalancerBalancePerChain, // if not set, keyfunder defaults to using desired balance * 0.2 as the threshold igpClaimThresholdPerChain: { + sepolia: '0', ancient8: '0.1', arbitrum: '0.1', avalanche: '2', @@ -125,7 +137,6 @@ export const keyFunderConfig: KeyFunderConfig< polygonzkevm: '0.1', proofofplay: '0.025', redstone: '0.1', - sanko: '1', scroll: '0.1', sei: '5', taiko: '0.1', diff --git a/typescript/infra/config/environments/mainnet3/gasPrices.json b/typescript/infra/config/environments/mainnet3/gasPrices.json index c1e4d8b57e4..6a313bc8b31 100644 --- a/typescript/infra/config/environments/mainnet3/gasPrices.json +++ b/typescript/infra/config/environments/mainnet3/gasPrices.json @@ -43,16 +43,8 @@ "amount": "0.07", "decimals": 9 }, - "bouncebit": { - "amount": "11.25", - "decimals": 9 - }, "botanix": { - "amount": "0.000000021", - "decimals": 9 - }, - "flame": { - "amount": "42.393213952", + "amount": "0.000822245", "decimals": 9 }, "avalanche": { @@ -95,6 +87,10 @@ "amount": "0.001000252", "decimals": 9 }, + "celestia": { + "amount": "0.02", + "decimals": 1 + }, "celo": { "amount": "25.001", "decimals": 9 @@ -107,30 +103,18 @@ "amount": "2501.0", "decimals": 9 }, - "conflux": { - "amount": "20.0", - "decimals": 9 - }, - "conwai": { - "amount": "0.01", - "decimals": 9 - }, "coredao": { "amount": "30.0", "decimals": 9 }, "coti": { - "amount": "1.500000007", + "amount": "2.000000007", "decimals": 9 }, "cyber": { "amount": "0.001000252", "decimals": 9 }, - "deepbrainchain": { - "amount": "10.0", - "decimals": 9 - }, "degenchain": { "amount": "100.0", "decimals": 9 @@ -139,30 +123,26 @@ "amount": "250.0", "decimals": 9 }, - "duckchain": { - "amount": "10.0", - "decimals": 9 - }, "eclipsemainnet": { "amount": "0.0000001", "decimals": 1 }, + "electroneum": { + "amount": "57.349384849", + "decimals": 9 + }, "endurance": { "amount": "1.500000007", "decimals": 9 }, "ethereum": { - "amount": "4.0", + "amount": "2.5", "decimals": 9 }, "everclear": { "amount": "0.1", "decimals": 9 }, - "evmos": { - "amount": "27.5", - "decimals": 9 - }, "fantom": { "amount": "1.0246", "decimals": 9 @@ -183,8 +163,12 @@ "amount": "0.001000252", "decimals": 9 }, + "forma": { + "amount": "19.0", + "decimals": 9 + }, "fraxtal": { - "amount": "0.001000253", + "amount": "0.00100041", "decimals": 9 }, "fusemainnet": { @@ -200,7 +184,7 @@ "decimals": 9 }, "gnosis": { - "amount": "0.000000103", + "amount": "0.000308849", "decimals": 9 }, "gravity": { @@ -256,7 +240,7 @@ "decimals": 1 }, "linea": { - "amount": "0.076937668", + "amount": "0.098465617", "decimals": 9 }, "lisk": { @@ -264,15 +248,11 @@ "decimals": 9 }, "lukso": { - "amount": "0.180084749", - "decimals": 9 - }, - "lumia": { - "amount": "3.77", + "amount": "1.0", "decimals": 9 }, "lumiaprism": { - "amount": "3.77", + "amount": "9.28", "decimals": 9 }, "mantapacific": { @@ -283,6 +263,10 @@ "amount": "0.02", "decimals": 9 }, + "mantra": { + "amount": "11.25", + "decimals": 9 + }, "matchain": { "amount": "0.001", "decimals": 9 @@ -296,7 +280,7 @@ "decimals": 9 }, "metis": { - "amount": "2.879267811", + "amount": "3.214164502", "decimals": 9 }, "milkyway": { @@ -311,6 +295,10 @@ "amount": "0.01", "decimals": 9 }, + "mitosis": { + "amount": "0.1", + "decimals": 9 + }, "mode": { "amount": "0.001000457", "decimals": 9 @@ -327,10 +315,6 @@ "amount": "0.03", "decimals": 9 }, - "nero": { - "amount": "5.534752437", - "decimals": 9 - }, "neutron": { "amount": "0.0053", "decimals": 1 @@ -375,6 +359,10 @@ "amount": "100.0", "decimals": 9 }, + "plasma": { + "amount": "1.000000007", + "decimals": 9 + }, "plume": { "amount": "1000.0", "decimals": 9 @@ -399,34 +387,30 @@ "amount": "0.01", "decimals": 9 }, + "pulsechain": { + "amount": "955251.886808412", + "decimals": 9 + }, + "radix": { + "amount": "50.0", + "decimals": 9 + }, "rarichain": { "amount": "0.15", "decimals": 9 }, "reactive": { - "amount": "1.001", + "amount": "100.001", "decimals": 9 }, "redstone": { "amount": "0.00020005", "decimals": 9 }, - "rivalz": { - "amount": "0.01", - "decimals": 9 - }, "ronin": { "amount": "21.492605086", "decimals": 9 }, - "rootstockmainnet": { - "amount": "0.0260656", - "decimals": 9 - }, - "sanko": { - "amount": "0.5", - "decimals": 9 - }, "scroll": { "amount": "0.015823137", "decimals": 9 @@ -436,7 +420,7 @@ "decimals": 9 }, "shibarium": { - "amount": "1.910994042", + "amount": "3.011075299", "decimals": 9 }, "snaxchain": { @@ -471,6 +455,10 @@ "amount": "3554.023647439", "decimals": 9 }, + "sova": { + "amount": "0.001000251", + "decimals": 9 + }, "starknet": { "amount": "5000", "decimals": 1 @@ -504,21 +492,17 @@ "decimals": 9 }, "tac": { - "amount": "28.125", + "amount": "450.0", "decimals": 9 }, "taiko": { - "amount": "0.011", + "amount": "0.0495", "decimals": 9 }, "tangle": { "amount": "1.0", "decimals": 9 }, - "telos": { - "amount": "531.562896801", - "decimals": 9 - }, "torus": { "amount": "31.25", "decimals": 9 @@ -527,12 +511,8 @@ "amount": "0.00100063", "decimals": 9 }, - "unitzero": { - "amount": "11.000000007", - "decimals": 9 - }, "vana": { - "amount": "0.002108205", + "amount": "0.005", "decimals": 9 }, "viction": { @@ -551,14 +531,14 @@ "amount": "2.49", "decimals": 9 }, - "xpla": { - "amount": "280.0", - "decimals": 9 - }, "xrplevm": { "amount": "275.0", "decimals": 9 }, + "zerogravity": { + "amount": "2.000000007", + "decimals": 9 + }, "zeronetwork": { "amount": "0.06", "decimals": 9 @@ -571,10 +551,6 @@ "amount": "0.001000249", "decimals": 9 }, - "zklink": { - "amount": "0.125", - "decimals": 9 - }, "zksync": { "amount": "0.04525", "decimals": 9 diff --git a/typescript/infra/config/environments/mainnet3/governance/ica/_awLegacy.ts b/typescript/infra/config/environments/mainnet3/governance/ica/_awLegacy.ts new file mode 100644 index 00000000000..11e79bee21c --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/governance/ica/_awLegacy.ts @@ -0,0 +1,159 @@ +// Found by running: +import { ChainMap } from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +// yarn tsx ./scripts/keys/get-owner-ica.ts -e mainnet3 --ownerChain ethereum --destinationChains ... +export const awIcasLegacy: ChainMap
= { + viction: '0x23ed65DE22ac29Ec1C16E75EddB0cE3A187357b4', + inevm: '0xFDF9EDcb2243D51f5f317b9CEcA8edD2bEEE036e', + + // Jul 26, 2024 batch + // ---------------------------------------------------------- + xlayer: '0x1571c482fe9E76bbf50829912b1c746792966369', + cheesechain: '0xEe2C5320BE9bC7A1492187cfb289953b53E3ff1b', + worldchain: '0x1996DbFcFB433737fE404F58D2c32A7f5f334210', + // zircuit: '0x0d67c56E818a02ABa58cd2394b95EF26db999aA3', // already has a safe + + // Aug 5, 2024 batch + // ---------------------------------------------------------- + cyber: '0x984Fe5a45Ac4aaeC4E4655b50f776aB79c9Be19F', + degenchain: '0x22d952d3b9F493442731a3c7660aCaD98e55C00A', + lisk: '0x22d952d3b9F493442731a3c7660aCaD98e55C00A', + lukso: '0xc1e20A0D78E79B94D71d4bDBC8FD0Af7c856Dd7A', + merlin: '0xCf867cEaeeE8CBe65C680c734D29d26440931D5b', + metis: '0xb51e63CD0842D670a13c88B159fCFc268DA652A3', + mint: '0xb51e63CD0842D670a13c88B159fCFc268DA652A3', + proofofplay: '0xb51e63CD0842D670a13c88B159fCFc268DA652A3', + tangle: '0xCC2aeb692197C7894E561d31ADFE8F79746f7d9F', + xai: '0x22d952d3b9F493442731a3c7660aCaD98e55C00A', + // taiko: '0x483D218D2FEe7FC7204ba15F00C7901acbF9697D', // renzo chain + + // Aug 26, 2024 batch + // ---------------------------------------------------------- + astar: '0x6b241544eBa7d89B51b72DF85a0342dAa37371Ca', + bitlayer: '0xe6239316cA60814229E801fF0B9DD71C9CA29008', + coredao: '0x84802CdF47565C95d8ffd59E7c4B1cf027F5452F', + dogechain: '0x84802CdF47565C95d8ffd59E7c4B1cf027F5452F', + flare: '0x689b8DaBBF2f9Fd83D37427A062B30edF463e20b', + molten: '0x84802CdF47565C95d8ffd59E7c4B1cf027F5452F', + shibarium: '0x6348FAe3a8374dbAAaE84EEe5458AE4063Fe2be7', + + // Sep 9, 2024 batch + // ---------------------------------------------------------- + everclear: '0x63B2075372D6d0565F51210D0A296D3c8a773aB6', + oortmainnet: '0x7021D11F9fAe455AB2f45D72dbc2C64d116Cb657', + + // Sep 19, 2024 SAFE --> ICA v1 Migration + // ---------------------------------------------------------- + celo: '0x3fA264c58E1365f1d5963B831b864EcdD2ddD19b', + avalanche: '0x8c8695cD9905e22d84E466804ABE55408A87e595', + polygon: '0xBDD25dd5203fedE33FD631e30fEF9b9eF2598ECE', + moonbeam: '0x480e5b5De6a29F07fe8295C60A1845d36b7BfdE6', + gnosis: '0xD42125a4889A7A36F32d7D12bFa0ae52B0AD106b', + scroll: '0x2a3fe2513F4A7813683d480724AB0a3683EfF8AC', + polygonzkevm: '0x66037De438a59C966214B78c1d377c4e93a5C7D1', + ancient8: '0xA9FD5BeB556AB1859D7625B381110a257f56F98C', + redstone: '0x5DAcd2f1AafC749F2935A160865Ab1568eC23752', + mantle: '0x08C880b88335CA3e85Ebb4E461245a7e899863c9', + bob: '0xc99e58b9A4E330e2E4d09e2c94CD3c553904F588', + zetachain: '0xc876B8e63c3ff5b636d9492715BE375644CaD345', + zoramainnet: '0x84977Eb15E0ff5824a6129c789F70e88352C230b', + fusemainnet: '0xbBdb1682B2922C282b56DD716C29db5EFbdb5632', + endurance: '0x470E04D8a3b7938b385093B93CeBd8Db7A1E557C', + // sei: '0xabad187003EdeDd6C720Fc633f929EA632996567', // renzo chain + + // Oct 30, 2024 batch + // ---------------------------------------------------------- + apechain: '0xe68b0aB6BB8c11D855556A5d3539524f6DB3bdc6', + arbitrumnova: '0x8965d9f19336EB4e910d5f1B9070205FdBee6837', + b3: '0x8965d9f19336EB4e910d5f1B9070205FdBee6837', + fantom: '0x8965d9f19336EB4e910d5f1B9070205FdBee6837', + gravity: '0x3104ADE26e21AEbdB325321433541DfE8B5dCF23', + harmony: '0x8965d9f19336EB4e910d5f1B9070205FdBee6837', + kaia: '0x8965d9f19336EB4e910d5f1B9070205FdBee6837', + morph: '0x8965d9f19336EB4e910d5f1B9070205FdBee6837', + orderly: '0x8965d9f19336EB4e910d5f1B9070205FdBee6837', + snaxchain: '0x8965d9f19336EB4e910d5f1B9070205FdBee6837', + + // Nov 8, 2024 batch + // ---------------------------------------------------------- + alephzeroevmmainnet: '0xDE91AC081E12107a033728A287b06B1Fc640A637', + chilizmainnet: '0x54AF0FCDCD58428f8dF3f825267DfB58f2C710eb', + flowmainnet: '0x65528D447C93CC1A1A7186CB4449d9fE0d5C1928', + immutablezkevmmainnet: '0x54AF0FCDCD58428f8dF3f825267DfB58f2C710eb', + metal: '0xf1d25462e1f82BbF25b3ef7A4C94F738a30a968B', + polynomialfi: '0x6ACa36E710dC0C80400090EA0bC55dA913a3D20D', + rarichain: '0xD0A4Ad2Ca0251BBc6541f8c2a594F1A82b67F114', + superpositionmainnet: '0x5F17Dc2e1fd1371dc6e694c51f22aBAF8E27667B', + prom: '0x1cDd3C143387cD1FaE23e2B66bc3F409D073aC3D', + + // Nov 21, 2024 batch + // ---------------------------------------------------------- + boba: '0x29dfa34765e29ea353FC8aB70A19e32a5578E603', + unichain: '0x29dfa34765e29ea353FC8aB70A19e32a5578E603', + vana: '0x29dfa34765e29ea353FC8aB70A19e32a5578E603', + bsquared: '0xd9564EaaA68A327933f758A54450D3A0531E60BB', + superseed: '0x29dfa34765e29ea353FC8aB70A19e32a5578E603', + + // Dec 4, 2024 batch + // ---------------------------------------------------------- + lumiaprism: '0xAFfA863646D1bC74ecEC0dB1070f069Af065EBf5', + appchain: '0x4F25DFFd10A6D61C365E1a605d07B2ab0E82A7E6', + + // Dec 13, 2024 batch + // ---------------------------------------------------------- + aurora: '0x853f40c807cbb08EDd19B326b9b6A669bf3c274c', + // corn: '0x5926599B8Aff45f1708b804B30213babdAD78C83', + form: '0x5926599B8Aff45f1708b804B30213babdAD78C83', + ink: '0xDde4Ce691d1c0579d48BCdd3491aA71472b6cC38', + soneium: '0x5926599B8Aff45f1708b804B30213babdAD78C83', + sonic: '0x5926599B8Aff45f1708b804B30213babdAD78C83', + + // Jan 13, 2025 batch + // ---------------------------------------------------------- + artela: '0x745CEA119757ea3e27093da590bC91f408bD4448', + hemi: '0x8D18CBB212920e5ef070b23b813d82F8981cC276', + torus: '0xc1e20A0D78E79B94D71d4bDBC8FD0Af7c856Dd7A', + + // Feb 3, 2025 batch + // ---------------------------------------------------------- + // glue: '0x24832680dF0468967F413be1C83acfE24154F88D', + matchain: '0x66af72e46b3e8DFc19992A2A88C05d9EEFE01ffB', + + // Q5, 2024 batch + // ---------------------------------------------------------- + // berachain: '0x56895bFa7f7dFA5743b2A0994B5B0f88b88350F9', + + // Feb 17, 2025 batch + // ---------------------------------------------------------- + arcadia: '0xD2344a364b6Dc6B2Fe0f7D836fa344d83056cbaD', + ronin: '0x8768A14AA6eD2A62C77155501E742376cbE97981', + story: '0x8768A14AA6eD2A62C77155501E742376cbE97981', + subtensor: '0x61BFbb5FEC57f5470388A80946F0415138630b9c', + + // Mar 14, 2025 batch + // ---------------------------------------------------------- + plume: '0x61BFbb5FEC57f5470388A80946F0415138630b9c', + + // Mar 31, 2025 batch + // ---------------------------------------------------------- + coti: '0x294589E4913A132A49F7830a2A219363A25c0529', + // nibiru: '0x40cD75e80d04663FAe0CE30687504074F163C346', // temporary while looking into decimals + opbnb: '0xeFb7D10Da69A0a913485851ccec6B85cF98d9cab', + reactive: '0x9312B04076efA12D69b95bcE7F4F0EA847073E6a', + + // Jun 5, 2025 - oUSDT v2 + // ---------------------------------------------------------- + hashkey: '0xEE01c007f89c9255f43b91B591b93cD1459048D1', + + // Jun 21, 2025 - oUSDT v3 + // ---------------------------------------------------------- + swell: '0xff8326468e7AaB51c53D3569cf7C45Dd54c11687', + botanix: '0xf06c254d1Df285BC16B2D53A426dC106897CfDf9', + + // Jun 30, 2025 - cctp upgrade + // ---------------------------------------------------------- + // arbitrum: '0xaB547e6cde21a5cC3247b8F80e6CeC3a030FAD4A', + // optimism: '0x20E9C1776A9408923546b64D5ea8BfdF0B7319d6', + // base: '0xA6D9Aa3878423C266480B5a7cEe74917220a1ad2', +} as const; diff --git a/typescript/infra/config/environments/mainnet3/governance/ica/_regularLegacy.ts b/typescript/infra/config/environments/mainnet3/governance/ica/_regularLegacy.ts new file mode 100644 index 00000000000..f8b034667ce --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/governance/ica/_regularLegacy.ts @@ -0,0 +1,108 @@ +import { ChainMap } from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +export const regularIcasLegacy: ChainMap
= { + // Apr 19, 2025 batch + // ---------------------------------------------------------- + ancient8: '0xf789D8a609247c448E28d3af5b8EFC6Cb786C4ee', + alephzeroevmmainnet: '0xA3dfBa2447F0A50706F22e38f2785e0Bf30BC069', + apechain: '0x9422838f6fA763354756a3Aba18f34015cB4bF74', + appchain: '0x8A4E9b5B445504727D7a9377eA329Bb38700F8FA', + arbitrumnova: '0x83be90021870A93ff794E00E8CFe454547877E3E', + arcadia: '0x32879753603c140E34D46375F43Db8BdC9a8c545', + artela: '0x2754282484EBC6103042BE6c2f17acFA96B2546a', + astar: '0x87433Bf4d46fB11C6c39c6F1876b95c9001D06E1', + aurora: '0x793C99f45CF63dd101D7D6819e02333Fb6cFd57f', + avalanche: '0x3a1014df0202477a1222999c72bD36395904e8AB', + b3: '0x83be90021870A93ff794E00E8CFe454547877E3E', + bitlayer: '0x39EBb0D2b62D623BBEB874505079a21d05A7Ab9d', + bob: '0x4FB145Da6407F4F485A209332a38A5327B61f83e', + boba: '0xEc65c06c606006Db682197230bF8E7740C2BcFF9', + bsquared: '0x7C475877fc180f5aAB628Ae1B506316a0F3ADE5A', + celo: '0x20D701Ac137BB131e735B403e0471b101423dDeC', + cheesechain: '0x5e4277969e2EEEbe04091dc270c2363b6e694F8d', + chilizmainnet: '0x97827F6191296077587929Ca870620bD5D79F9e7', + coredao: '0x100D940B953b20378242Fe0FbC45792805d50556', + // corn: '0x4BAe702C0D1cC7d6d343594735812b5723041528', + coti: '0x54b625734673868027c70F656B807588910f3a6f', + cyber: '0x162F8F6B70f9EE57fED16C4337B90e939851ECA1', + degenchain: '0xF01B503E5cC884f0EB6eF062F704A610FD69842D', + dogechain: '0x100D940B953b20378242Fe0FbC45792805d50556', + endurance: '0xd8b01D2fA7F50889eE0f51114D00ab4c8581A5F4', + everclear: '0x975866f773aC8f6d816089B0A5Ab9DC8A01c9462', + fantom: '0x83be90021870A93ff794E00E8CFe454547877E3E', + flare: '0x68be5bC1b1E373f3c6a278D290F39461DDD21968', + flowmainnet: '0x8e9558D9bA4d3FB52ea75D18B337f778589AB1aF', + form: '0x4BAe702C0D1cC7d6d343594735812b5723041528', + fusemainnet: '0x4601d6260C532E73187248D0608cB88D446149AD', + // glue: '0x0Fb0635eAbB9332eDc2544A57e7C15fBc5204C0B', + gnosis: '0x2984A89A1A0331ae200FE2Cb46cCa190e795672E', + gravity: '0x4Ce5ecE7349E467EBd77F39Eb4232Dd8C6a7314D', + harmony: '0x83be90021870A93ff794E00E8CFe454547877E3E', + hemi: '0xa16C2860414e52945Bae3eb957cC0897A21f04a6', + immutablezkevmmainnet: '0x97827F6191296077587929Ca870620bD5D79F9e7', + inevm: '0x8427F6021a84a992008DA6a949cad986CEB0d0b3', + ink: '0xf36dC13eE34034709390b6dF3de7305eA298BFec', + kaia: '0x83be90021870A93ff794E00E8CFe454547877E3E', + lisk: '0xF01B503E5cC884f0EB6eF062F704A610FD69842D', + lukso: '0xBd887119d776f0c990e9a03B1A157A107CD45033', + lumiaprism: '0x60a51cB66CF6012A2adF27090fb7D51aE17369CF', + mantle: '0x9652721a385AF5B410AA0865Df959255a798F761', + matchain: '0x262E9A3eDA180fCb84846BaBa2aEB9EDa88e9BeF', + merlin: '0xe4a82d029b3aB2D8Ed12b6800e86452E979D8c5c', + metal: '0x9E359Bd54B59D28218a47E4C9a1e84568f09aFb4', + metis: '0x319aB070390C8f2A914a61f8DE366f3295bb44fF', + mint: '0x319aB070390C8f2A914a61f8DE366f3295bb44fF', + molten: '0x100D940B953b20378242Fe0FbC45792805d50556', + moonbeam: '0xb3178E470E959340206AD9d9eE4E53d0911b2ba9', + morph: '0x83be90021870A93ff794E00E8CFe454547877E3E', + oortmainnet: '0xBc2cc7372C018C2a0b1F78E29D8B49d21bc5f3bA', + opbnb: '0x1fbE174fc6B1d3123B890f34aBd982577377Bec8', + orderly: '0x83be90021870A93ff794E00E8CFe454547877E3E', + plume: '0xB9d20ea5f3bB574C923F0af67b06b8D87F111819', + polygon: '0xff5b2F0Ae1FFBf6b92Ea9B5851D7643C81102064', + polygonzkevm: '0x258B14038C610f7889C74162760A3A70Ce526CD5', + polynomialfi: '0x45EFBEAD9009C2662a633ee6F307C90B647B7793', + prom: '0x9123625687807F02FC92A9817cFb8db13A9a8B4d', + proofofplay: '0x319aB070390C8f2A914a61f8DE366f3295bb44fF', + rarichain: '0x80c7c5fF8F84BEeDEd18382eb45aA3246063fbc5', + reactive: '0x97B3BE8f063E98Da6ac254716DA194da4634aC80', + redstone: '0x27C06C13E512Cdd80A2E49fc9c803cFd0de7Ba9e', + ronin: '0xa3EC6913675e9686bfC458F02a0F737EdD0362c8', + scroll: '0xDD2e306EE337952b4f7C0c4Eb44A8B5b9925Bc76', + shibarium: '0x778f0e4CD1434258A811ab493217f3DC8d501C1b', + snaxchain: '0x83be90021870A93ff794E00E8CFe454547877E3E', + soneium: '0x4BAe702C0D1cC7d6d343594735812b5723041528', + sonic: '0x4BAe702C0D1cC7d6d343594735812b5723041528', + story: '0xa3EC6913675e9686bfC458F02a0F737EdD0362c8', + subtensor: '0xB9d20ea5f3bB574C923F0af67b06b8D87F111819', + superseed: '0xEc65c06c606006Db682197230bF8E7740C2BcFF9', + superpositionmainnet: '0x9f921e87309829ecc61F6df0F2016fD08B80CDD1', + tangle: '0x60109e724a30E89fEB1877fAd006317f8b81768a', + torus: '0xBd887119d776f0c990e9a03B1A157A107CD45033', + unichain: '0xEc65c06c606006Db682197230bF8E7740C2BcFF9', + vana: '0xEc65c06c606006Db682197230bF8E7740C2BcFF9', + viction: '0x426FC4C5CC60E5e47101fe30d4f8B94F1b7C1C70', + worldchain: '0x51E4E4b7317043A1D6e2fe6ccd9dA51844C385f0', + xai: '0xF01B503E5cC884f0EB6eF062F704A610FD69842D', + xlayer: '0x73581C1c03E64BDE5775b7236B57d78155B75fA7', + zetachain: '0x33d8605D4dF6661259640268DD32BC99A2F55a83', + zoramainnet: '0xdbB6E8FDe96dE3a1604b93884d8fF9Cb521059fC', + + // May 18, 2025 batch + // ---------------------------------------------------------- + hashkey: '0x51e43Fe2Db5B950e115a9f5ad7603E07c656fBe9', + infinityvmmainnet: '0xDEaE9c3D3c4e16Fbf7eFEBE733feEf044a79BF9B', + game7: '0xDEaE9c3D3c4e16Fbf7eFEBE733feEf044a79BF9B', + fluence: '0xDEaE9c3D3c4e16Fbf7eFEBE733feEf044a79BF9B', + peaq: '0xDEaE9c3D3c4e16Fbf7eFEBE733feEf044a79BF9B', + ontology: '0xA59Eb6F5C4C365f533407E978321531E9F610b02', + miraclechain: '0x4733050AdC12BCaad14A239418aAdD06cce2fB7d', + nibiru: '0x966A57078204CA6cA13f091eE66eE61Ada5D2318', + blast: '0xeA45A1031cA2Eb20Ffa729d3AD6f2F92789DCF3F', + + // Jun 4, 2025 batch + // ---------------------------------------------------------- + botanix: '0x86F361C9d39F5cA4FC770449BAB5fa17489B37FF', + katana: '0x86F361C9d39F5cA4FC770449BAB5fa17489B37FF', +}; diff --git a/typescript/infra/config/environments/mainnet3/governance/ica/aw.ts b/typescript/infra/config/environments/mainnet3/governance/ica/aw.ts index cdc8763c57e..ed09793c239 100644 --- a/typescript/infra/config/environments/mainnet3/governance/ica/aw.ts +++ b/typescript/infra/config/environments/mainnet3/governance/ica/aw.ts @@ -2,176 +2,134 @@ import { ChainMap } from '@hyperlane-xyz/sdk'; import { Address } from '@hyperlane-xyz/utils'; -// yarn tsx ./scripts/keys/get-owner-ica.ts -e mainnet3 --ownerChain ethereum --destinationChains ... +// REGISTRY_URI=/Users/pbio/work/tmpauditq2/hyperlane-registry \ +// yarn tsx scripts/keys/get-owner-ica.ts -e mainnet3 --ownerChain ethereum --deploy \ +// --governanceType abacusWorks +// -c ... \ export const awIcas: ChainMap
= { - viction: '0x23ed65DE22ac29Ec1C16E75EddB0cE3A187357b4', - inevm: '0xFDF9EDcb2243D51f5f317b9CEcA8edD2bEEE036e', - - // Jul 26, 2024 batch - // ---------------------------------------------------------- - xlayer: '0x1571c482fe9E76bbf50829912b1c746792966369', - cheesechain: '0xEe2C5320BE9bC7A1492187cfb289953b53E3ff1b', - worldchain: '0x1996DbFcFB433737fE404F58D2c32A7f5f334210', - // zircuit: '0x0d67c56E818a02ABa58cd2394b95EF26db999aA3', // already has a safe - - // Aug 5, 2024 batch - // ---------------------------------------------------------- - cyber: '0x984Fe5a45Ac4aaeC4E4655b50f776aB79c9Be19F', - degenchain: '0x22d952d3b9F493442731a3c7660aCaD98e55C00A', - lisk: '0x22d952d3b9F493442731a3c7660aCaD98e55C00A', - lukso: '0xc1e20A0D78E79B94D71d4bDBC8FD0Af7c856Dd7A', - merlin: '0xCf867cEaeeE8CBe65C680c734D29d26440931D5b', - metis: '0xb51e63CD0842D670a13c88B159fCFc268DA652A3', - mint: '0xb51e63CD0842D670a13c88B159fCFc268DA652A3', - proofofplay: '0xb51e63CD0842D670a13c88B159fCFc268DA652A3', - sanko: '0x5DAcd2f1AafC749F2935A160865Ab1568eC23752', - tangle: '0xCC2aeb692197C7894E561d31ADFE8F79746f7d9F', - xai: '0x22d952d3b9F493442731a3c7660aCaD98e55C00A', - // taiko: '0x483D218D2FEe7FC7204ba15F00C7901acbF9697D', // renzo chain - - // Aug 26, 2024 batch - // ---------------------------------------------------------- - astar: '0x6b241544eBa7d89B51b72DF85a0342dAa37371Ca', - bitlayer: '0xe6239316cA60814229E801fF0B9DD71C9CA29008', - coredao: '0x84802CdF47565C95d8ffd59E7c4B1cf027F5452F', - dogechain: '0x84802CdF47565C95d8ffd59E7c4B1cf027F5452F', - flare: '0x689b8DaBBF2f9Fd83D37427A062B30edF463e20b', - molten: '0x84802CdF47565C95d8ffd59E7c4B1cf027F5452F', - shibarium: '0x6348FAe3a8374dbAAaE84EEe5458AE4063Fe2be7', - - // Sep 9, 2024 batch - // ---------------------------------------------------------- - everclear: '0x63B2075372D6d0565F51210D0A296D3c8a773aB6', - oortmainnet: '0x7021D11F9fAe455AB2f45D72dbc2C64d116Cb657', - - // Sep 19, 2024 SAFE --> ICA v1 Migration - // ---------------------------------------------------------- - celo: '0x3fA264c58E1365f1d5963B831b864EcdD2ddD19b', - avalanche: '0x8c8695cD9905e22d84E466804ABE55408A87e595', - polygon: '0xBDD25dd5203fedE33FD631e30fEF9b9eF2598ECE', - moonbeam: '0x480e5b5De6a29F07fe8295C60A1845d36b7BfdE6', - gnosis: '0xD42125a4889A7A36F32d7D12bFa0ae52B0AD106b', - scroll: '0x2a3fe2513F4A7813683d480724AB0a3683EfF8AC', - polygonzkevm: '0x66037De438a59C966214B78c1d377c4e93a5C7D1', - ancient8: '0xA9FD5BeB556AB1859D7625B381110a257f56F98C', - redstone: '0x5DAcd2f1AafC749F2935A160865Ab1568eC23752', - mantle: '0x08C880b88335CA3e85Ebb4E461245a7e899863c9', - bob: '0xc99e58b9A4E330e2E4d09e2c94CD3c553904F588', - zetachain: '0xc876B8e63c3ff5b636d9492715BE375644CaD345', - zoramainnet: '0x84977Eb15E0ff5824a6129c789F70e88352C230b', - fusemainnet: '0xbBdb1682B2922C282b56DD716C29db5EFbdb5632', - endurance: '0x470E04D8a3b7938b385093B93CeBd8Db7A1E557C', - // sei: '0xabad187003EdeDd6C720Fc633f929EA632996567', // renzo chain - - // Oct 16, 2024 batch - // ---------------------------------------------------------- - // lumia: '0x418E10Ac9e0b84022d0636228d05bc74172e0e41', - - // Oct 30, 2024 batch - // ---------------------------------------------------------- - apechain: '0xe68b0aB6BB8c11D855556A5d3539524f6DB3bdc6', - arbitrumnova: '0x8965d9f19336EB4e910d5f1B9070205FdBee6837', - b3: '0x8965d9f19336EB4e910d5f1B9070205FdBee6837', - fantom: '0x8965d9f19336EB4e910d5f1B9070205FdBee6837', - gravity: '0x3104ADE26e21AEbdB325321433541DfE8B5dCF23', - harmony: '0x8965d9f19336EB4e910d5f1B9070205FdBee6837', - kaia: '0x8965d9f19336EB4e910d5f1B9070205FdBee6837', - morph: '0x8965d9f19336EB4e910d5f1B9070205FdBee6837', - orderly: '0x8965d9f19336EB4e910d5f1B9070205FdBee6837', - snaxchain: '0x8965d9f19336EB4e910d5f1B9070205FdBee6837', - - // Nov 8, 2024 batch - // ---------------------------------------------------------- - alephzeroevmmainnet: '0xDE91AC081E12107a033728A287b06B1Fc640A637', - chilizmainnet: '0x54AF0FCDCD58428f8dF3f825267DfB58f2C710eb', - flowmainnet: '0x65528D447C93CC1A1A7186CB4449d9fE0d5C1928', - immutablezkevmmainnet: '0x54AF0FCDCD58428f8dF3f825267DfB58f2C710eb', - metal: '0xf1d25462e1f82BbF25b3ef7A4C94F738a30a968B', - polynomialfi: '0x6ACa36E710dC0C80400090EA0bC55dA913a3D20D', - rarichain: '0xD0A4Ad2Ca0251BBc6541f8c2a594F1A82b67F114', - rootstockmainnet: '0x0C15f7479E0B46868693568a3f1C747Fdec9f17d', - superpositionmainnet: '0x5F17Dc2e1fd1371dc6e694c51f22aBAF8E27667B', - flame: '0x4F3d85360840497Cd1bc34Ca55f27629eee2AA2e', - prom: '0x1cDd3C143387cD1FaE23e2B66bc3F409D073aC3D', - - // Nov 21, 2024 batch - // ---------------------------------------------------------- - boba: '0x29dfa34765e29ea353FC8aB70A19e32a5578E603', - duckchain: '0x29dfa34765e29ea353FC8aB70A19e32a5578E603', - unichain: '0x29dfa34765e29ea353FC8aB70A19e32a5578E603', - vana: '0x29dfa34765e29ea353FC8aB70A19e32a5578E603', - bsquared: '0xd9564EaaA68A327933f758A54450D3A0531E60BB', - superseed: '0x29dfa34765e29ea353FC8aB70A19e32a5578E603', - - // Dec 4, 2024 batch - // ---------------------------------------------------------- - lumiaprism: '0xAFfA863646D1bC74ecEC0dB1070f069Af065EBf5', - appchain: '0x4F25DFFd10A6D61C365E1a605d07B2ab0E82A7E6', - - // Dec 13, 2024 batch - // ---------------------------------------------------------- - aurora: '0x853f40c807cbb08EDd19B326b9b6A669bf3c274c', - conflux: '0xac8f0e306A126312C273080d149ca01d461603FE', - conwai: '0x5926599B8Aff45f1708b804B30213babdAD78C83', - // corn: '0x5926599B8Aff45f1708b804B30213babdAD78C83', - evmos: '0x5926599B8Aff45f1708b804B30213babdAD78C83', - form: '0x5926599B8Aff45f1708b804B30213babdAD78C83', - ink: '0xDde4Ce691d1c0579d48BCdd3491aA71472b6cC38', - rivalz: '0xc1e20A0D78E79B94D71d4bDBC8FD0Af7c856Dd7A', - soneium: '0x5926599B8Aff45f1708b804B30213babdAD78C83', - sonic: '0x5926599B8Aff45f1708b804B30213babdAD78C83', - telos: '0xDde4Ce691d1c0579d48BCdd3491aA71472b6cC38', - - // Jan 13, 2025 batch - // ---------------------------------------------------------- - artela: '0x745CEA119757ea3e27093da590bC91f408bD4448', - hemi: '0x8D18CBB212920e5ef070b23b813d82F8981cC276', - nero: '0xbBdb1682B2922C282b56DD716C29db5EFbdb5632', - torus: '0xc1e20A0D78E79B94D71d4bDBC8FD0Af7c856Dd7A', - xpla: '0x24832680dF0468967F413be1C83acfE24154F88D', - - // Feb 3, 2025 batch - // ---------------------------------------------------------- - // glue: '0x24832680dF0468967F413be1C83acfE24154F88D', - matchain: '0x66af72e46b3e8DFc19992A2A88C05d9EEFE01ffB', - unitzero: '0x66af72e46b3e8DFc19992A2A88C05d9EEFE01ffB', - - // Q5, 2024 batch - // ---------------------------------------------------------- - // berachain: '0x56895bFa7f7dFA5743b2A0994B5B0f88b88350F9', - - // Feb 17, 2025 batch - // ---------------------------------------------------------- - bouncebit: '0x8768A14AA6eD2A62C77155501E742376cbE97981', - arcadia: '0xD2344a364b6Dc6B2Fe0f7D836fa344d83056cbaD', - ronin: '0x8768A14AA6eD2A62C77155501E742376cbE97981', - story: '0x8768A14AA6eD2A62C77155501E742376cbE97981', - subtensor: '0x61BFbb5FEC57f5470388A80946F0415138630b9c', - - // Mar 14, 2025 batch - // ---------------------------------------------------------- - plume: '0x61BFbb5FEC57f5470388A80946F0415138630b9c', - - // Mar 31, 2025 batch - // ---------------------------------------------------------- - coti: '0x294589E4913A132A49F7830a2A219363A25c0529', - deepbrainchain: '0xeFb7D10Da69A0a913485851ccec6B85cF98d9cab', - // nibiru: '0x40cD75e80d04663FAe0CE30687504074F163C346', // temporary while looking into decimals - opbnb: '0xeFb7D10Da69A0a913485851ccec6B85cF98d9cab', - reactive: '0x9312B04076efA12D69b95bcE7F4F0EA847073E6a', - - // Jun 5, 2025 - oUSDT v2 - // ---------------------------------------------------------- - hashkey: '0xEE01c007f89c9255f43b91B591b93cD1459048D1', - - // Jun 21, 2025 - oUSDT v3 - // ---------------------------------------------------------- - swell: '0xff8326468e7AaB51c53D3569cf7C45Dd54c11687', - botanix: '0xf06c254d1Df285BC16B2D53A426dC106897CfDf9', - - // Jun 30, 2025 - cctp upgrade - // ---------------------------------------------------------- - // arbitrum: '0xaB547e6cde21a5cC3247b8F80e6CeC3a030FAD4A', - // optimism: '0x20E9C1776A9408923546b64D5ea8BfdF0B7319d6', - // base: '0xA6D9Aa3878423C266480B5a7cEe74917220a1ad2', + // owner chain + // ethereum: '0x24c2160941cB0A75E1C2aA6B70Be3e6EC4FE3a29', + + // keep safe-owned for now + // arbitrum: '0xD2757Bbc28C80789Ed679f22Ac65597Cacf51A45', + // base: '0x61756c4beBC1BaaC09d89729E2cbaD8BD30c62B7', + // bsc: '0x269Af9E53192AF49a22ff47e30b89dE1375AE1fd', + // optimism: '0x1E2afA8d1B841c53eDe9474D188Cd4FcfEd40dDC', + + // Jul 2, 2025 - ICA 2.0 Migration + // ---------------------------------------------------------- + ancient8: '0xE78B22275FfbF789aD5dAD25cC6d9C50c38286f7', + alephzeroevmmainnet: '0x7113dcf70eF420Ab74435A680E3ACCDb3dA4056d', + apechain: '0x4745601a50CEE53b66221032318a2547D5741ae8', + appchain: '0xA843bFe58EbffaDc274b6718b238d60141E1281b', + arbitrumnova: '0x29eE2B1F2cA4C53DcbC237d81ea1e7d6a885FeDC', + // arcadia: '0x2616bb811BBE23A85D828d0732C170aC29ddf68A', + artela: '0x17C13CD3211d1b0532faC4Ec967254e63A2D4625', + astar: '0x6E8BAA8b8326A984D231ccD98421184D1a0826e0', + aurora: '0x8B56A03460515A073eb28cD6EBECbf609fCCa163', + botanix: '0x514A0b6B06941561c3A33ce78e4d46F006450C01', + avalanche: '0xe9232de913DfB7AC5E3e8dC3c3f48e2b7480889D', + b3: '0x4E5265D24E7EDd96e695dF9EB2983811fb4Fd21e', + berachain: '0x602C75F468df1AC861862D8fdBFdf5f08d8Bdb88', + bitlayer: '0x2A59725D978616DebFc43f8136A0d6258bC23cc5', + blast: '0xc242beF1ffd95C1b3B7e8d6bB87947d3ADb335Ad', + bob: '0xcBbB0A004177f12672fF02837AEB25d04bE74621', + boba: '0x1301a4D0E15ca5F595a0207aD7990B864E93469A', + bsquared: '0x304B82cca054b240a3127b34Bd1dc91772Baa630', + celo: '0x0691c077d180ca7615911b65DE4fA0B313d75aDd', + cheesechain: '0x570B22210aA421577B5b084874E5b8BAfE097315', + chilizmainnet: '0x59E97fEb19c9E023c10489426a2a2B90BEcE8e40', + coredao: '0x51f6780aA5fe3C5bfb64504d474884756382a32D', + coti: '0xBC6Fae7e5e18624F5892399BA29705e039E4c9fD', + cyber: '0x69D9a8412eB285d41b06763631fe7d734d3Fcec3', + degenchain: '0x1E865970021b2DBD005c550bEd69fBe9b1a5c854', + dogechain: '0x1526bC988a6B66bAe5813504531F72BCB7a86Aad', + endurance: '0xf07a953b6AfD49CDa9C3b1C166e830d5A1C811B5', + everclear: '0xf8689875C9b3a92F1b843886B04c64Ed663C6036', + fantom: '0x7B8C44B832aA5D95f3A40f6f0e8F54Cb3EB3DC55', + flare: '0x6BA6f07b8f7186413Cdfe6D606d040cB8bBD5386', + flowmainnet: '0x82fa5cB2e567248aD37CCd96b6Fc3111B8956293', + fluence: '0xd4beC84030d88D0EbF72dCDae361B449a29C4c34', + form: '0x3cFd16e8fC1Fa344f00af3D8A18672A8C1Fd9a45', + fraxtal: '0x1f8f770c963b17721C0cb084c18b3a3166a8d659', + fusemainnet: '0x5aDD39216bABc8215974ed033a83868d00C71810', + galactica: '0x085DC299cB1ceB3d4b584b320bb07C074F6A7c8a', + game7: '0xd4beC84030d88D0EbF72dCDae361B449a29C4c34', + gnosis: '0x80224Ae676F55Be8dcaFA425aCEF89eAe8f73c22', + gravity: '0x752A50183E24CCAdD6f84AC5BeFCf2eC0091FDd8', + harmony: '0x27C61d7A6839C170a73ECfE06Aa7f5C728cfa16E', + hashkey: '0x28b830D48ABF9c35f34ca1EDA8F0BeE4331Ef050', + hemi: '0x07a66eBd9B0D8F438F889ecbcfd8D4681C21Fbc6', + hyperevm: '0x7934e65283455BF25e6E9a6E3C48350D66944812', + immutablezkevmmainnet: '0xEC23b0e4fb816B921940c4F11f70de63D0FcCa49', + inevm: '0x15dF0C4F54615e6053Bb2CC9B5f2a7231a50F7B1', + infinityvmmainnet: '0xd095B269A3Ea56FaB241f069e4B1cCE18B7011d1', + ink: '0x92EdF061E46Dcf15941CA4D5F2d168f643079343', + kaia: '0x890CF3fD86fb02bE2448433667d83D542Ec9fc9d', + katana: '0x956993Aee1CFFA32942b5ddC6Db2E8620b85cfbC', + linea: '0x7b3d694BdeAE8F496Cc5eD0736c5f734cd231079', + lisk: '0x7cB0e36Ec34C7c97A270bcBF64d4c967A22f1371', + lukso: '0x4FE49931D3577c64B94cD1f1040A9781D41ACf95', + lumiaprism: '0x1eeF460F51e86Da8D0a9c16f827237f682fBC532', + mantapacific: '0xDB1f169C05Ff9484523E4556A1B70dDCb477c79E', + mantle: '0x352A39EeeFCaaf13b157F869AD8ed69e7cf0a340', + matchain: '0xCDA995D800F9Bc32814eC95277DF5BF0F9F243F9', + merlin: '0x27C61d7A6839C170a73ECfE06Aa7f5C728cfa16E', + metal: '0x0F6c9f5753f03748fB61A8C7b038f1D7C2240C10', + metis: '0xd37fD87a28A5702643a0f937de248E7673594528', + mint: '0xf540577E128127aD395FA62A8947E5359825282B', + miraclechain: '0x15Fe5787c215E200Cb164AdcB2bFbe9BF4300053', + mode: '0x7Ef84bbc24118619655031f6404d6C81BC1AB534', + molten: '0x84C72d2C3Ce2f5AF43D189BcF79Bc2778B72610E', + moonbeam: '0xe09a9a00c6EeC364A018Db91E0429F41a1CD0735', + morph: '0x8fe7d190e657D20e1958AB1497AbC7F1Ab4503F5', + nibiru: '0x985D77eeC388e7A8b827d8f3f7EF853b3d70320f', + oortmainnet: '0xE92Bf41B0fd76f72274fECa87179AE6E235c3c7F', + // ontology: '0x6F9c6b3979E46B64bE0Cb3E29376A1e1aa54a160', + opbnb: '0xc8B74D2bD580F122a9d5Ce40d4631afa2Ae8FB3C', + orderly: '0x60021F107A32e7E747bDeca64e6FCC21DD45C4AC', + peaq: '0xbeCd9B31ba4DbAC6DDc5C42cCC5fAF408CAd4921', + plume: '0x2a8320c609dcdcdC4Ad906d7e5D6426f7e1dA0e4', + polygon: '0x8708C96f9879805c2E54818865cbFF27fb64000D', + polygonzkevm: '0x00c9F4250836B58582e2B62aDA28AEbB2cF08D23', + polynomialfi: '0x69D9a8412eB285d41b06763631fe7d734d3Fcec3', + prom: '0x0A335686C1AdAD375feF9267dA235496E190991f', + proofofplay: '0x6729ffCD2DBFDf9C6495c2F8021A4216a8e1EE3F', + rarichain: '0x1dB8093b1C0c79F6A85ecbd0423c6696df4C57EC', + reactive: '0x9B56aBc4Fd0b01E143f5856607d261C5D871718F', + redstone: '0x64A2F705Fe46d71120263C1c0d5A15fE35a198F4', + ronin: '0x76554F623DaF13EEE8132F15983060555bf4668f', + scroll: '0x4B0910867a52de380bc5f54481AF199B64bC46EF', + sei: '0xd30AF4e3786995Aa89Ef58ec5f3280b73386a944', + shibarium: '0x5aCE843FdFFDe09ff83a264e1a002A8981BbEa12', + snaxchain: '0x60021F107A32e7E747bDeca64e6FCC21DD45C4AC', + soneium: '0x1472ab941e43D5D9Eadf33661D884F1A1ce0Ecb7', + sonic: '0x888da4CDD0Af7c9BE436e31CE2Bb84b70447a22a', + story: '0x25756750F44d433219e9F13373Cd26983130bf21', + subtensor: '0xaABBDE4930e7D0a3F4E1e5296BB34F71533854AA', + superseed: '0xb1Cb9F64B7C1cA6f58a1AF5DdA1B4c9982bAcE59', + superpositionmainnet: '0xC8d728C5Cf70640a86Ae3A7168B5CA2CE9213E18', + swell: '0x4F9fD8470A636F7a9D10C04b2593b1B1504c507B', + tac: '0x04a1F95339610733a6FdB4645A4b2e0B84770CB6', + taiko: '0x1Ce682C48acBA8A5a5370dd05a09637Ea2977676', + tangle: '0x760746Ab6531c422cB338117ecD15C317EA8B7Ad', + torus: '0x3d3461DF23038f108ccF074947A958c93750CA9a', + unichain: '0x20c2B4B4C7409D08AD70D5F9e317E98cfA9F49f7', + vana: '0xcdB472A1411360151308be799cc36db7f982533E', + // viction: '0x23ed65DE22ac29Ec1C16E75EddB0cE3A187357b4', + worldchain: '0xF618140B147beeD17cB83d4Bb8343484D5295cd2', + xai: '0x0bE9B6460fb9300F88bC56B9E6668275f49DBe9b', + xlayer: '0xda9dbF5a41eAFE58D76120Adc2442F2c02AA76dF', + xrplevm: '0x6E1d1Ffab3691b18194A71e25b1fabcc381Ff0Ad', + zetachain: '0x6F3665B9cA225A53dcf9d77A356bbff782C15196', + zircuit: '0x83075a810feE2390D95b3d281E78e5Ada8b5A9C9', + zoramainnet: '0xAd92FA49D4dcB010999B7e4289e790C644f3b853', + + // Aug 18, 2025 - Mitosis + // ---------------------------------------------------------- + mitosis: '0x8C94C6c26c752fAa33bACD377b9198FE7fae0bF5', + + // Sept 22, 2025 + // ---------------------------------------------------------- + // zerogravity: '0x8d8703Ea7E7A129a581DCA59B916Cc4410a61D47', } as const; diff --git a/typescript/infra/config/environments/mainnet3/governance/ica/aw2.ts b/typescript/infra/config/environments/mainnet3/governance/ica/aw2.ts deleted file mode 100644 index 7c3593c19b7..00000000000 --- a/typescript/infra/config/environments/mainnet3/governance/ica/aw2.ts +++ /dev/null @@ -1,141 +0,0 @@ -// Found by running: -import { ChainMap } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; - -// REGISTRY_URI=/Users/pbio/work/tmpauditq2/hyperlane-registry \ -// yarn tsx scripts/keys/get-owner-ica.ts -e mainnet3 --ownerChain ethereum --deploy \ -// --governanceType abacusWorks -// -c ... \ -export const awIcasV2: ChainMap
= { - // owner chain - // ethereum: '0x24c2160941cB0A75E1C2aA6B70Be3e6EC4FE3a29', - - // keep safe-owned for now - // arbitrum: '0xD2757Bbc28C80789Ed679f22Ac65597Cacf51A45', - // base: '0x61756c4beBC1BaaC09d89729E2cbaD8BD30c62B7', - // bsc: '0x269Af9E53192AF49a22ff47e30b89dE1375AE1fd', - // optimism: '0x1E2afA8d1B841c53eDe9474D188Cd4FcfEd40dDC', - - // Jul 2, 2025 - ICA 2.0 Migration - // ---------------------------------------------------------- - ancient8: '0xE78B22275FfbF789aD5dAD25cC6d9C50c38286f7', - alephzeroevmmainnet: '0x7113dcf70eF420Ab74435A680E3ACCDb3dA4056d', - apechain: '0x4745601a50CEE53b66221032318a2547D5741ae8', - appchain: '0xA843bFe58EbffaDc274b6718b238d60141E1281b', - arbitrumnova: '0x29eE2B1F2cA4C53DcbC237d81ea1e7d6a885FeDC', - // arcadia: '0x2616bb811BBE23A85D828d0732C170aC29ddf68A', - artela: '0x17C13CD3211d1b0532faC4Ec967254e63A2D4625', - astar: '0x6E8BAA8b8326A984D231ccD98421184D1a0826e0', - aurora: '0x8B56A03460515A073eb28cD6EBECbf609fCCa163', - // bouncebit: '0x3A640968d9Be619e048D13bafCC94d676db196f4', - botanix: '0x514A0b6B06941561c3A33ce78e4d46F006450C01', - flame: '0x3105fcaDc39f3FBb356bB6EcfdF7026Fa741d11D', - avalanche: '0xe9232de913DfB7AC5E3e8dC3c3f48e2b7480889D', - b3: '0x4E5265D24E7EDd96e695dF9EB2983811fb4Fd21e', - berachain: '0x602C75F468df1AC861862D8fdBFdf5f08d8Bdb88', - bitlayer: '0x2A59725D978616DebFc43f8136A0d6258bC23cc5', - blast: '0xc242beF1ffd95C1b3B7e8d6bB87947d3ADb335Ad', - bob: '0xcBbB0A004177f12672fF02837AEB25d04bE74621', - boba: '0x1301a4D0E15ca5F595a0207aD7990B864E93469A', - bsquared: '0x304B82cca054b240a3127b34Bd1dc91772Baa630', - celo: '0x0691c077d180ca7615911b65DE4fA0B313d75aDd', - cheesechain: '0x570B22210aA421577B5b084874E5b8BAfE097315', - chilizmainnet: '0x59E97fEb19c9E023c10489426a2a2B90BEcE8e40', - // conflux: '0xac8f0e306A126312C273080d149ca01d461603FE', - conwai: '0x76554F623DaF13EEE8132F15983060555bf4668f', - coredao: '0x51f6780aA5fe3C5bfb64504d474884756382a32D', - coti: '0xBC6Fae7e5e18624F5892399BA29705e039E4c9fD', - cyber: '0x69D9a8412eB285d41b06763631fe7d734d3Fcec3', - deepbrainchain: '0x28b830D48ABF9c35f34ca1EDA8F0BeE4331Ef050', - degenchain: '0x1E865970021b2DBD005c550bEd69fBe9b1a5c854', - dogechain: '0x1526bC988a6B66bAe5813504531F72BCB7a86Aad', - duckchain: '0xB8921fc1142C4a15e62A4D77f6F7f655201D2a4e', - endurance: '0xf07a953b6AfD49CDa9C3b1C166e830d5A1C811B5', - everclear: '0xf8689875C9b3a92F1b843886B04c64Ed663C6036', - evmos: '0x8738f5E689573C930CA0f4120Dd831e214089A35', - fantom: '0x7B8C44B832aA5D95f3A40f6f0e8F54Cb3EB3DC55', - flare: '0x6BA6f07b8f7186413Cdfe6D606d040cB8bBD5386', - flowmainnet: '0x82fa5cB2e567248aD37CCd96b6Fc3111B8956293', - fluence: '0xd4beC84030d88D0EbF72dCDae361B449a29C4c34', - form: '0x3cFd16e8fC1Fa344f00af3D8A18672A8C1Fd9a45', - fraxtal: '0x1f8f770c963b17721C0cb084c18b3a3166a8d659', - fusemainnet: '0x5aDD39216bABc8215974ed033a83868d00C71810', - galactica: '0x085DC299cB1ceB3d4b584b320bb07C074F6A7c8a', - game7: '0xd4beC84030d88D0EbF72dCDae361B449a29C4c34', - gnosis: '0x80224Ae676F55Be8dcaFA425aCEF89eAe8f73c22', - gravity: '0x752A50183E24CCAdD6f84AC5BeFCf2eC0091FDd8', - harmony: '0x27C61d7A6839C170a73ECfE06Aa7f5C728cfa16E', - hashkey: '0x28b830D48ABF9c35f34ca1EDA8F0BeE4331Ef050', - hemi: '0x07a66eBd9B0D8F438F889ecbcfd8D4681C21Fbc6', - hyperevm: '0x7934e65283455BF25e6E9a6E3C48350D66944812', - immutablezkevmmainnet: '0xEC23b0e4fb816B921940c4F11f70de63D0FcCa49', - inevm: '0x15dF0C4F54615e6053Bb2CC9B5f2a7231a50F7B1', - infinityvmmainnet: '0xd095B269A3Ea56FaB241f069e4B1cCE18B7011d1', - ink: '0x92EdF061E46Dcf15941CA4D5F2d168f643079343', - kaia: '0x890CF3fD86fb02bE2448433667d83D542Ec9fc9d', - katana: '0x956993Aee1CFFA32942b5ddC6Db2E8620b85cfbC', - linea: '0x7b3d694BdeAE8F496Cc5eD0736c5f734cd231079', - lisk: '0x7cB0e36Ec34C7c97A270bcBF64d4c967A22f1371', - lukso: '0x4FE49931D3577c64B94cD1f1040A9781D41ACf95', - lumiaprism: '0x1eeF460F51e86Da8D0a9c16f827237f682fBC532', - mantapacific: '0xDB1f169C05Ff9484523E4556A1B70dDCb477c79E', - mantle: '0x352A39EeeFCaaf13b157F869AD8ed69e7cf0a340', - matchain: '0xCDA995D800F9Bc32814eC95277DF5BF0F9F243F9', - merlin: '0x27C61d7A6839C170a73ECfE06Aa7f5C728cfa16E', - metal: '0x0F6c9f5753f03748fB61A8C7b038f1D7C2240C10', - metis: '0xd37fD87a28A5702643a0f937de248E7673594528', - mint: '0xf540577E128127aD395FA62A8947E5359825282B', - miraclechain: '0x15Fe5787c215E200Cb164AdcB2bFbe9BF4300053', - mode: '0x7Ef84bbc24118619655031f6404d6C81BC1AB534', - molten: '0x84C72d2C3Ce2f5AF43D189BcF79Bc2778B72610E', - moonbeam: '0xe09a9a00c6EeC364A018Db91E0429F41a1CD0735', - morph: '0x8fe7d190e657D20e1958AB1497AbC7F1Ab4503F5', - nero: '0x1a9FF6a9795Fac862bcfe1cD2F31C9bEF0629108', - nibiru: '0x985D77eeC388e7A8b827d8f3f7EF853b3d70320f', - oortmainnet: '0xE92Bf41B0fd76f72274fECa87179AE6E235c3c7F', - // ontology: '0x6F9c6b3979E46B64bE0Cb3E29376A1e1aa54a160', - opbnb: '0xc8B74D2bD580F122a9d5Ce40d4631afa2Ae8FB3C', - orderly: '0x60021F107A32e7E747bDeca64e6FCC21DD45C4AC', - peaq: '0xbeCd9B31ba4DbAC6DDc5C42cCC5fAF408CAd4921', - plume: '0x2a8320c609dcdcdC4Ad906d7e5D6426f7e1dA0e4', - polygon: '0x8708C96f9879805c2E54818865cbFF27fb64000D', - polygonzkevm: '0x00c9F4250836B58582e2B62aDA28AEbB2cF08D23', - polynomialfi: '0x69D9a8412eB285d41b06763631fe7d734d3Fcec3', - prom: '0x0A335686C1AdAD375feF9267dA235496E190991f', - proofofplay: '0x6729ffCD2DBFDf9C6495c2F8021A4216a8e1EE3F', - rarichain: '0x1dB8093b1C0c79F6A85ecbd0423c6696df4C57EC', - reactive: '0x9B56aBc4Fd0b01E143f5856607d261C5D871718F', - redstone: '0x64A2F705Fe46d71120263C1c0d5A15fE35a198F4', - rivalz: '0x4e74DB78d67B96496ABbeF2AEf224f9B0159Caf4', - ronin: '0x76554F623DaF13EEE8132F15983060555bf4668f', - rootstockmainnet: '0x4aE0F7f27672b7C8785BaFFa3DDAC27aE6365B2A', - sanko: '0xB3455607B0fA778CAC02a19F987023964d937512', - scroll: '0x4B0910867a52de380bc5f54481AF199B64bC46EF', - sei: '0xd30AF4e3786995Aa89Ef58ec5f3280b73386a944', - shibarium: '0x5aCE843FdFFDe09ff83a264e1a002A8981BbEa12', - snaxchain: '0x60021F107A32e7E747bDeca64e6FCC21DD45C4AC', - soneium: '0x1472ab941e43D5D9Eadf33661D884F1A1ce0Ecb7', - sonic: '0x888da4CDD0Af7c9BE436e31CE2Bb84b70447a22a', - story: '0x25756750F44d433219e9F13373Cd26983130bf21', - subtensor: '0xaABBDE4930e7D0a3F4E1e5296BB34F71533854AA', - superseed: '0xb1Cb9F64B7C1cA6f58a1AF5DdA1B4c9982bAcE59', - superpositionmainnet: '0xC8d728C5Cf70640a86Ae3A7168B5CA2CE9213E18', - swell: '0x4F9fD8470A636F7a9D10C04b2593b1B1504c507B', - tac: '0x04a1F95339610733a6FdB4645A4b2e0B84770CB6', - taiko: '0x1Ce682C48acBA8A5a5370dd05a09637Ea2977676', - tangle: '0x760746Ab6531c422cB338117ecD15C317EA8B7Ad', - telos: '0x2c404A83bA820aA39E10ff7752c7208d400e7A87', - torus: '0x3d3461DF23038f108ccF074947A958c93750CA9a', - unichain: '0x20c2B4B4C7409D08AD70D5F9e317E98cfA9F49f7', - unitzero: '0xCDA995D800F9Bc32814eC95277DF5BF0F9F243F9', - vana: '0xcdB472A1411360151308be799cc36db7f982533E', - // viction: '0x23ed65DE22ac29Ec1C16E75EddB0cE3A187357b4', - worldchain: '0xF618140B147beeD17cB83d4Bb8343484D5295cd2', - xai: '0x0bE9B6460fb9300F88bC56B9E6668275f49DBe9b', - xlayer: '0xda9dbF5a41eAFE58D76120Adc2442F2c02AA76dF', - xpla: '0x4273d602954087ED57125870Bd3Df7a578e59432', - xrplevm: '0x6E1d1Ffab3691b18194A71e25b1fabcc381Ff0Ad', - zetachain: '0x6F3665B9cA225A53dcf9d77A356bbff782C15196', - zircuit: '0x83075a810feE2390D95b3d281E78e5Ada8b5A9C9', - zoramainnet: '0xAd92FA49D4dcB010999B7e4289e790C644f3b853', -} as const; diff --git a/typescript/infra/config/environments/mainnet3/governance/ica/dymension.ts b/typescript/infra/config/environments/mainnet3/governance/ica/dymension.ts new file mode 100644 index 00000000000..e94e87342ba --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/governance/ica/dymension.ts @@ -0,0 +1,10 @@ +// Found by running: +import { ChainMap } from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +// REGISTRY_URI=/Users/pbio/work/tmpauditq2/hyperlane-registry \ +// yarn tsx scripts/keys/get-owner-ica.ts -e mainnet3 --ownerChain ethereum --deploy \ +// --governanceType abacusWorks +// -c ... \ +export const dymensionIcas: ChainMap
= { +} as const; diff --git a/typescript/infra/config/environments/mainnet3/governance/ica/regular.ts b/typescript/infra/config/environments/mainnet3/governance/ica/regular.ts index 702927c5156..9b747a15b0b 100644 --- a/typescript/infra/config/environments/mainnet3/governance/ica/regular.ts +++ b/typescript/infra/config/environments/mainnet3/governance/ica/regular.ts @@ -1,122 +1,146 @@ +// Found by running: import { ChainMap } from '@hyperlane-xyz/sdk'; import { Address } from '@hyperlane-xyz/utils'; +// REGISTRY_URI=/Users/pbio/work/tmpauditq2/hyperlane-registry \ +// yarn tsx scripts/keys/get-owner-ica.ts -e mainnet3 --ownerChain ethereum --deploy \ +// --governanceType regular +// -c ... \ export const regularIcas: ChainMap
= { - // Apr 19, 2025 batch + // owner chain + // ethereum: '0x28699e5d9feb54131FB14D7446d2771623295781', + + // keep safe-owned for now + // arbitrum: '0xCC7F1D17351AD37E58758A329E553FB9566562E6', + // base: '0xE3020af3784788cA04E4E68d3A9A827f2B14Bf3b', + // bsc: '0x2be13bE357bd9Bca5aAA4Fb4917A8bc4EFDF661c', + // optimism: '0x62FAaf58331B379569D435F08633361f0Be50cB8', + + // Jul 2, 2025 - ICA 2.0 Migration + // ---------------------------------------------------------- + ancient8: '0x72Ba22a1a8B8c2b7B1795a35a1E05c058217E9a7', + alephzeroevmmainnet: '0xa1352242B0508b211F5b1d6408488207754497B6', + apechain: '0x0880453D948E4D2a06F40c6Cf4eF33E4e938411f', + appchain: '0x863226b1F78f40a3Ab63D89910B2CC5e899CC94D', + arbitrumnova: '0x2A20bb14cA28A189Bb7faE65072E05c1226C0D77', + // arcadia: '0x56eDf2dCFA1cBA0Df7D1EE80D02F930d74B12f39', + artela: '0xb53734Ed3832d764462E62316c35B81cCC92197a', + astar: '0x794D8EA7C8f7f19a1D8F5dEE1207fcae7f41c16b', + aurora: '0x2fb1f965B35725D3b30f50A0A4BA6865E7368e78', + botanix: '0xe30183bab232021BcA9f56588CE09fF9842AB29D', + avalanche: '0x66C21CFa8b765318a458435519A31A5cf0F7Ae4b', + b3: '0x220FBA23d125b52FAeCF8D36454343E1dE4E448E', + berachain: '0xb6DD23C2Bef77e90DdDBeEdEa29a56880ED750f0', + bitlayer: '0x033cF796F8f84279F9bA61206869c0E05E41BcFc', + blast: '0x59C89E3f2481C8c062E7DeEFFF70B75fbBd0b89a', + bob: '0x2282ef5C6654DF3796ccc3e6A1AE33c40801E89D', + boba: '0x247510A4001b78decE23B2d773aa8C2c9f6939C3', + bsquared: '0xEF600dD98EA1c7681cA75Bd109e4B0c5AA5a58F5', + celo: '0x39feCE4cA4b41bd2dfA886A6cB08353A42DbB6E3', + cheesechain: '0xB6Ce375D1aA9513cFb744dB3c06FfCA4f4bA20A7', + chilizmainnet: '0xf5A0F82fF0F93699b4C32e49fac35Db2932aEc6f', + coredao: '0xa2f6D0570F16061c5abE785067dd9502561ec30b', + coti: '0x41A362f117D4EbE2680F6d4E37ddC5454dc5f07a', + cyber: '0xb765a7d803b2bB92EE228B6D46Af744CB5BDDD79', + degenchain: '0x96725ED287b1e9bF5151367D4F9adfB9671EC5b5', + dogechain: '0x751705e7cE45FB860F4728BA1aCa404C896e51A9', + endurance: '0x1DF9D80551381E29EC0Dad4F2Bfb79694076B47b', + everclear: '0x7a7716A400aA13B156317D683Ae2097db18B3E55', + fantom: '0x15278182cBcC9283c0970a8Ea6F893C3d99A75D4', + flare: '0x691a19F5eCEb2357606910d596EA5bD17e01Fed1', + flowmainnet: '0xf2A87fd04C42D51D53c2A35E957981bA59b46F9e', + fluence: '0x7F30B7724040fC3384864ef3812f7a437Bc80166', + form: '0xbAda620Da6f81819783621c57d9648A3A0cff5fc', + fraxtal: '0xc96634cdc475FfC7702f123EFD77cB43c861956b', + fusemainnet: '0x66148C55906967F4F55dCD6E45e74b43b8624444', + galactica: '0x6C8210C82B17bE639CeDB3349A5aeDFF1471f72A', + game7: '0x7F30B7724040fC3384864ef3812f7a437Bc80166', + gnosis: '0xf38bb17117000b560ecdeaA5b736f3aFCc39C3b5', + gravity: '0x9603D1409150F1895349E71c4267CeE4B59635fF', + harmony: '0xA5544aeadb395Ce08A49871CF0EA0a16B2fE444B', + hashkey: '0xDfC5cAEC02737d0FD2868cCa379D866DF96ACe27', + hemi: '0xAf6dC78dCA99812D3bB729e3C17C527F74CD261F', + hyperevm: '0xD50d4E42F41392d5c7E9526423D3dc38a8d1f875', + immutablezkevmmainnet: '0x71c47adC3270EA319705626b8769E0ee54D1EA41', + inevm: '0xCf0F066B8D5F14f264EA99D67CFa389D6c1De20E', + infinityvmmainnet: '0x8cf2Ec273D7C82614640dB1d5F6AF0f27F5071cd', + ink: '0x20b3D70fA3d7B69Aba42b0bA9D8F25Ee0C656e78', + kaia: '0x47bd809B484DB532F9E2Cf540cCE24af79341deA', + katana: '0x53dE35b799fD29B129E683E43ef31cEEB3b88D22', + linea: '0x999bD2da1f24aece8c558E54350FB06eF8ceFe5C', + lisk: '0xe1D145f3c539f287789D6f8C6Bc010a1fECdb259', + lukso: '0x5045dE416efC1738046E26Bf30dfEf9CB60C8de7', + lumiaprism: '0x5C6Bc53c4e0d7DAAee0bb5F9181f476c77643576', + mantapacific: '0xF899c68F4a8C79a2E1aC66Da9976a990F2b78Da1', + mantle: '0x556C39820DBBB3Cd6f4c164CD91F755f89Ac8c62', + matchain: '0x9F31268d17Eab404ED2DEdFbb8ba0022E3fF621f', + merlin: '0xA5544aeadb395Ce08A49871CF0EA0a16B2fE444B', + metal: '0x1547282562E9b32561C7bdD41cAfB5ce89Ff08dA', + metis: '0xD998C3Ee6b8EE592C7d0bcf8e5b43F4Dc314C07F', + mint: '0x3C8bd6d095c77Dc8251D5802162b10898b1925Cd', + miraclechain: '0x7c06d94A3177c550f17239442Dd1dA9C68c33a85', + mode: '0x917445680fbd406747abFa78F381efd63F4aF599', + molten: '0x07Db6F557F8C758a485cEb188EDb727c34D44FDf', + moonbeam: '0x9D91e377655380010f8795F08753F30F54D4aEEc', + morph: '0x1a4f0d915ce6FAe98B6b1c78FcbB8c122f30afCd', + nibiru: '0xdb10a32F38A8c1B944277fA2Dd6Dfd89aD83480F', + oortmainnet: '0x7038d9B38D417bB0b9f30158697e333B1dd921E3', + // ontology: '0xA59Eb6F5C4C365f533407E978321531E9F610b02', + opbnb: '0x121BA5F314168B2E34CC0b1d81784af384B09B04', + orderly: '0xFfaf98AC934B84B54171c6f116B1d23cde4e347E', + peaq: '0xE21447D4f0F993dD8C1C86Cd216E2E68f2a1CdcE', + plume: '0x2E625B8191C8b1CEceAB6B6d3c833547A337e7F2', + polygon: '0x20e52e3BeDf7BD305Ca816Dc54a0835D3bDeD820', + polygonzkevm: '0xA136C9297ECA646b2dbFcD4Fb06cf9A10CF8027e', + polynomialfi: '0xb765a7d803b2bB92EE228B6D46Af744CB5BDDD79', + prom: '0x024b2F836A29F80859dE21C0Dc0410b538742560', + proofofplay: '0x367fD428ed583F6B3B800F1C0E0450Aff7645C7E', + rarichain: '0x69DAb5389A8CA6f6e1268B701e9ec7E38182F58b', + reactive: '0xc192035485ac67334A8A2080f7C4930624DAAa9C', + redstone: '0x8D69E4d1CBCDF7B14E89652D70F25aaf61A97517', + ronin: '0x8B15B5861d1F391F98e2014aB510aF2320CA6eBD', + scroll: '0xe4927B0D71c86C7bad2236DD191ECABdab796936', + sei: '0x8a3A8A4C9f188bbD45E75271Dc590077fc96EDc7', + shibarium: '0xE08183fbd724B43Da0c043Af02cb3D1F1ECA2a98', + snaxchain: '0xFfaf98AC934B84B54171c6f116B1d23cde4e347E', + soneium: '0x6af23EB68a6223a4af9F056E10D93ef4d960Da05', + sonic: '0x39e4A8F7AA0826b1Bf94551A00eB2142AC7D5Bb5', + story: '0x769926C9f2CCc61dB22A45c8F75Bd2fAeFd25748', + subtensor: '0x4C8D6Ae76a04108E20d1Cc114041c632aC040ABC', + superseed: '0xE9617FA7edD4741997Ee994038D977a377612Ae1', + superpositionmainnet: '0xE2067Bf557FF7024fb50dB30bCC01fBb5FF81712', + swell: '0x4E8d79E902502e165130eC3f8cfbfafD771DFb4b', + tac: '0xb801C2712f92E2d29e1120862e0f9C93d80CbBcA', + taiko: '0x2ca9CC72285EE7D6c7344623F2Bfffcc8894005c', + tangle: '0x93b0515c4CB2Af39626B334E47afCC8e4BDAB8Db', + torus: '0xA9143712A8c5CB4e7687F5b4f7163A1E6D1c1476', + unichain: '0xD39c288D2A021207Dfec7780e778D73460f563D7', + vana: '0x486815B6D0dC66B041965dB1904872167ddcE433', + // viction: '0x426FC4C5CC60E5e47101fe30d4f8B94F1b7C1C70', + worldchain: '0x17f0207b9529cAb39F6e02394157b5a6c7064393', + xai: '0xf4afbc0C86D9ee6C52E30947FC3D7E22dCA9BA4A', + xlayer: '0xAD598d164ecFf737C7556871Bb1D50215c4D9517', + xrplevm: '0x772C35b16FC5B9a989915a1753b2A281cC79C44D', + zetachain: '0x4ffB88393797A9c42c837A317C1c28dB83313379', + zircuit: '0xFC862c01cE846D151a7F45E5d38e778ed330b151', + zoramainnet: '0x1c4E1d5fDd4F862D219fC7eeF45F4EA1a4fEE2D2', + + // Aug 18, 2025 - Mitosis + // ---------------------------------------------------------- + mitosis: '0x287Df0906671Fb56f6c2FDC0617F82D422796F8D', + + // Sept 3, 2025 - Pulsechain // ---------------------------------------------------------- - ancient8: '0xf789D8a609247c448E28d3af5b8EFC6Cb786C4ee', - alephzeroevmmainnet: '0xA3dfBa2447F0A50706F22e38f2785e0Bf30BC069', - apechain: '0x9422838f6fA763354756a3Aba18f34015cB4bF74', - appchain: '0x8A4E9b5B445504727D7a9377eA329Bb38700F8FA', - arbitrumnova: '0x83be90021870A93ff794E00E8CFe454547877E3E', - arcadia: '0x32879753603c140E34D46375F43Db8BdC9a8c545', - artela: '0x2754282484EBC6103042BE6c2f17acFA96B2546a', - astar: '0x87433Bf4d46fB11C6c39c6F1876b95c9001D06E1', - aurora: '0x793C99f45CF63dd101D7D6819e02333Fb6cFd57f', - bouncebit: '0xa3EC6913675e9686bfC458F02a0F737EdD0362c8', - flame: '0x355e54B6D423db49735Cb701452bd98434A90BAd', - avalanche: '0x3a1014df0202477a1222999c72bD36395904e8AB', - b3: '0x83be90021870A93ff794E00E8CFe454547877E3E', - bitlayer: '0x39EBb0D2b62D623BBEB874505079a21d05A7Ab9d', - bob: '0x4FB145Da6407F4F485A209332a38A5327B61f83e', - boba: '0xEc65c06c606006Db682197230bF8E7740C2BcFF9', - bsquared: '0x7C475877fc180f5aAB628Ae1B506316a0F3ADE5A', - celo: '0x20D701Ac137BB131e735B403e0471b101423dDeC', - cheesechain: '0x5e4277969e2EEEbe04091dc270c2363b6e694F8d', - chilizmainnet: '0x97827F6191296077587929Ca870620bD5D79F9e7', - conflux: '0x04b7cBD37eeFe304655c7e8638BbE4ddEff576E8', - conwai: '0x4BAe702C0D1cC7d6d343594735812b5723041528', - coredao: '0x100D940B953b20378242Fe0FbC45792805d50556', - // corn: '0x4BAe702C0D1cC7d6d343594735812b5723041528', - coti: '0x54b625734673868027c70F656B807588910f3a6f', - cyber: '0x162F8F6B70f9EE57fED16C4337B90e939851ECA1', - deepbrainchain: '0x1fbE174fc6B1d3123B890f34aBd982577377Bec8', - degenchain: '0xF01B503E5cC884f0EB6eF062F704A610FD69842D', - dogechain: '0x100D940B953b20378242Fe0FbC45792805d50556', - duckchain: '0xEc65c06c606006Db682197230bF8E7740C2BcFF9', - endurance: '0xd8b01D2fA7F50889eE0f51114D00ab4c8581A5F4', - everclear: '0x975866f773aC8f6d816089B0A5Ab9DC8A01c9462', - evmos: '0x4BAe702C0D1cC7d6d343594735812b5723041528', - fantom: '0x83be90021870A93ff794E00E8CFe454547877E3E', - flare: '0x68be5bC1b1E373f3c6a278D290F39461DDD21968', - flowmainnet: '0x8e9558D9bA4d3FB52ea75D18B337f778589AB1aF', - form: '0x4BAe702C0D1cC7d6d343594735812b5723041528', - fusemainnet: '0x4601d6260C532E73187248D0608cB88D446149AD', - // glue: '0x0Fb0635eAbB9332eDc2544A57e7C15fBc5204C0B', - gnosis: '0x2984A89A1A0331ae200FE2Cb46cCa190e795672E', - gravity: '0x4Ce5ecE7349E467EBd77F39Eb4232Dd8C6a7314D', - harmony: '0x83be90021870A93ff794E00E8CFe454547877E3E', - hemi: '0xa16C2860414e52945Bae3eb957cC0897A21f04a6', - immutablezkevmmainnet: '0x97827F6191296077587929Ca870620bD5D79F9e7', - inevm: '0x8427F6021a84a992008DA6a949cad986CEB0d0b3', - ink: '0xf36dC13eE34034709390b6dF3de7305eA298BFec', - kaia: '0x83be90021870A93ff794E00E8CFe454547877E3E', - lisk: '0xF01B503E5cC884f0EB6eF062F704A610FD69842D', - lukso: '0xBd887119d776f0c990e9a03B1A157A107CD45033', - lumiaprism: '0x60a51cB66CF6012A2adF27090fb7D51aE17369CF', - mantle: '0x9652721a385AF5B410AA0865Df959255a798F761', - matchain: '0x262E9A3eDA180fCb84846BaBa2aEB9EDa88e9BeF', - merlin: '0xe4a82d029b3aB2D8Ed12b6800e86452E979D8c5c', - metal: '0x9E359Bd54B59D28218a47E4C9a1e84568f09aFb4', - metis: '0x319aB070390C8f2A914a61f8DE366f3295bb44fF', - mint: '0x319aB070390C8f2A914a61f8DE366f3295bb44fF', - molten: '0x100D940B953b20378242Fe0FbC45792805d50556', - moonbeam: '0xb3178E470E959340206AD9d9eE4E53d0911b2ba9', - morph: '0x83be90021870A93ff794E00E8CFe454547877E3E', - nero: '0x4601d6260C532E73187248D0608cB88D446149AD', - oortmainnet: '0xBc2cc7372C018C2a0b1F78E29D8B49d21bc5f3bA', - opbnb: '0x1fbE174fc6B1d3123B890f34aBd982577377Bec8', - orderly: '0x83be90021870A93ff794E00E8CFe454547877E3E', - plume: '0xB9d20ea5f3bB574C923F0af67b06b8D87F111819', - polygon: '0xff5b2F0Ae1FFBf6b92Ea9B5851D7643C81102064', - polygonzkevm: '0x258B14038C610f7889C74162760A3A70Ce526CD5', - polynomialfi: '0x45EFBEAD9009C2662a633ee6F307C90B647B7793', - prom: '0x9123625687807F02FC92A9817cFb8db13A9a8B4d', - proofofplay: '0x319aB070390C8f2A914a61f8DE366f3295bb44fF', - rarichain: '0x80c7c5fF8F84BEeDEd18382eb45aA3246063fbc5', - reactive: '0x97B3BE8f063E98Da6ac254716DA194da4634aC80', - redstone: '0x27C06C13E512Cdd80A2E49fc9c803cFd0de7Ba9e', - rivalz: '0xBd887119d776f0c990e9a03B1A157A107CD45033', - ronin: '0xa3EC6913675e9686bfC458F02a0F737EdD0362c8', - rootstockmainnet: '0x77d190423A18e78C95269f4D1349141D15E433f3', - sanko: '0x27C06C13E512Cdd80A2E49fc9c803cFd0de7Ba9e', - scroll: '0xDD2e306EE337952b4f7C0c4Eb44A8B5b9925Bc76', - shibarium: '0x778f0e4CD1434258A811ab493217f3DC8d501C1b', - snaxchain: '0x83be90021870A93ff794E00E8CFe454547877E3E', - soneium: '0x4BAe702C0D1cC7d6d343594735812b5723041528', - sonic: '0x4BAe702C0D1cC7d6d343594735812b5723041528', - story: '0xa3EC6913675e9686bfC458F02a0F737EdD0362c8', - subtensor: '0xB9d20ea5f3bB574C923F0af67b06b8D87F111819', - superseed: '0xEc65c06c606006Db682197230bF8E7740C2BcFF9', - superpositionmainnet: '0x9f921e87309829ecc61F6df0F2016fD08B80CDD1', - tangle: '0x60109e724a30E89fEB1877fAd006317f8b81768a', - telos: '0xf36dC13eE34034709390b6dF3de7305eA298BFec', - torus: '0xBd887119d776f0c990e9a03B1A157A107CD45033', - unichain: '0xEc65c06c606006Db682197230bF8E7740C2BcFF9', - unitzero: '0x262E9A3eDA180fCb84846BaBa2aEB9EDa88e9BeF', - vana: '0xEc65c06c606006Db682197230bF8E7740C2BcFF9', - viction: '0x426FC4C5CC60E5e47101fe30d4f8B94F1b7C1C70', - worldchain: '0x51E4E4b7317043A1D6e2fe6ccd9dA51844C385f0', - xai: '0xF01B503E5cC884f0EB6eF062F704A610FD69842D', - xlayer: '0x73581C1c03E64BDE5775b7236B57d78155B75fA7', - xpla: '0x0Fb0635eAbB9332eDc2544A57e7C15fBc5204C0B', - zetachain: '0x33d8605D4dF6661259640268DD32BC99A2F55a83', - zoramainnet: '0xdbB6E8FDe96dE3a1604b93884d8fF9Cb521059fC', + pulsechain: '0x72655e4683E802AeaF7bff4Dd0189293dc16cD62', - // May 18, 2025 batch + // Sept 8, 2025 // ---------------------------------------------------------- - hashkey: '0x51e43Fe2Db5B950e115a9f5ad7603E07c656fBe9', - infinityvmmainnet: '0xDEaE9c3D3c4e16Fbf7eFEBE733feEf044a79BF9B', - game7: '0xDEaE9c3D3c4e16Fbf7eFEBE733feEf044a79BF9B', - fluence: '0xDEaE9c3D3c4e16Fbf7eFEBE733feEf044a79BF9B', - peaq: '0xDEaE9c3D3c4e16Fbf7eFEBE733feEf044a79BF9B', - ontology: '0xA59Eb6F5C4C365f533407E978321531E9F610b02', - miraclechain: '0x4733050AdC12BCaad14A239418aAdD06cce2fB7d', - nibiru: '0x966A57078204CA6cA13f091eE66eE61Ada5D2318', - blast: '0xeA45A1031cA2Eb20Ffa729d3AD6f2F92789DCF3F', + plasma: '0xd7e64bA7BB6beE321D5E0C42a966FDc97f70a92f', + electroneum: '0xd7e64bA7BB6beE321D5E0C42a966FDc97f70a92f', - // Jun 4, 2025 batch + // Sept 22, 2025 // ---------------------------------------------------------- - botanix: '0x86F361C9d39F5cA4FC770449BAB5fa17489B37FF', - katana: '0x86F361C9d39F5cA4FC770449BAB5fa17489B37FF', -}; + // zerogravity: '0x53FEEdcF42C1aACFeC3FA6Da573a3470FcD5C658', + // sova: '0x759E0dBC1DE631db48Eb10ED011D3B86a1556DEb', + // mantra: '0x6b3353A453689a92aE3138c4d26e4eaD894b39D8', +} as const; diff --git a/typescript/infra/config/environments/mainnet3/governance/ica/regular2.ts b/typescript/infra/config/environments/mainnet3/governance/ica/regular2.ts deleted file mode 100644 index 08cb36ab4a5..00000000000 --- a/typescript/infra/config/environments/mainnet3/governance/ica/regular2.ts +++ /dev/null @@ -1,141 +0,0 @@ -// Found by running: -import { ChainMap } from '@hyperlane-xyz/sdk'; -import { Address } from '@hyperlane-xyz/utils'; - -// REGISTRY_URI=/Users/pbio/work/tmpauditq2/hyperlane-registry \ -// yarn tsx scripts/keys/get-owner-ica.ts -e mainnet3 --ownerChain ethereum --deploy \ -// --governanceType regular -// -c ... \ -export const regularIcasV2: ChainMap
= { - // owner chain - // ethereum: '0x28699e5d9feb54131FB14D7446d2771623295781', - - // keep safe-owned for now - // arbitrum: '0xCC7F1D17351AD37E58758A329E553FB9566562E6', - // base: '0xE3020af3784788cA04E4E68d3A9A827f2B14Bf3b', - // bsc: '0x2be13bE357bd9Bca5aAA4Fb4917A8bc4EFDF661c', - // optimism: '0x62FAaf58331B379569D435F08633361f0Be50cB8', - - // Jul 2, 2025 - ICA 2.0 Migration - // ---------------------------------------------------------- - ancient8: '0x72Ba22a1a8B8c2b7B1795a35a1E05c058217E9a7', - alephzeroevmmainnet: '0xa1352242B0508b211F5b1d6408488207754497B6', - apechain: '0x0880453D948E4D2a06F40c6Cf4eF33E4e938411f', - appchain: '0x863226b1F78f40a3Ab63D89910B2CC5e899CC94D', - arbitrumnova: '0x2A20bb14cA28A189Bb7faE65072E05c1226C0D77', - // arcadia: '0x56eDf2dCFA1cBA0Df7D1EE80D02F930d74B12f39', - artela: '0xb53734Ed3832d764462E62316c35B81cCC92197a', - astar: '0x794D8EA7C8f7f19a1D8F5dEE1207fcae7f41c16b', - aurora: '0x2fb1f965B35725D3b30f50A0A4BA6865E7368e78', - // bouncebit: '0xe2002ac30Ad403805C5060fC006EDeC7809D070D', - botanix: '0xe30183bab232021BcA9f56588CE09fF9842AB29D', - flame: '0x7dA713Ef6bF1904e8b6080a148d16ED8618Cb538', - avalanche: '0x66C21CFa8b765318a458435519A31A5cf0F7Ae4b', - b3: '0x220FBA23d125b52FAeCF8D36454343E1dE4E448E', - berachain: '0xb6DD23C2Bef77e90DdDBeEdEa29a56880ED750f0', - bitlayer: '0x033cF796F8f84279F9bA61206869c0E05E41BcFc', - blast: '0x59C89E3f2481C8c062E7DeEFFF70B75fbBd0b89a', - bob: '0x2282ef5C6654DF3796ccc3e6A1AE33c40801E89D', - boba: '0x247510A4001b78decE23B2d773aa8C2c9f6939C3', - bsquared: '0xEF600dD98EA1c7681cA75Bd109e4B0c5AA5a58F5', - celo: '0x39feCE4cA4b41bd2dfA886A6cB08353A42DbB6E3', - cheesechain: '0xB6Ce375D1aA9513cFb744dB3c06FfCA4f4bA20A7', - chilizmainnet: '0xf5A0F82fF0F93699b4C32e49fac35Db2932aEc6f', - // conflux: '0x04b7cBD37eeFe304655c7e8638BbE4ddEff576E8', - conwai: '0x8B15B5861d1F391F98e2014aB510aF2320CA6eBD', - coredao: '0xa2f6D0570F16061c5abE785067dd9502561ec30b', - coti: '0x41A362f117D4EbE2680F6d4E37ddC5454dc5f07a', - cyber: '0xb765a7d803b2bB92EE228B6D46Af744CB5BDDD79', - deepbrainchain: '0xDfC5cAEC02737d0FD2868cCa379D866DF96ACe27', - degenchain: '0x96725ED287b1e9bF5151367D4F9adfB9671EC5b5', - dogechain: '0x751705e7cE45FB860F4728BA1aCa404C896e51A9', - duckchain: '0xA461e36Aa7FFe6eD07d7CC76741EfeB1B384b4Ef', - endurance: '0x1DF9D80551381E29EC0Dad4F2Bfb79694076B47b', - everclear: '0x7a7716A400aA13B156317D683Ae2097db18B3E55', - evmos: '0x54b1F5ADcC61e8e1c0149E751380104e9e00dEbD', - fantom: '0x15278182cBcC9283c0970a8Ea6F893C3d99A75D4', - flare: '0x691a19F5eCEb2357606910d596EA5bD17e01Fed1', - flowmainnet: '0xf2A87fd04C42D51D53c2A35E957981bA59b46F9e', - fluence: '0x7F30B7724040fC3384864ef3812f7a437Bc80166', - form: '0xbAda620Da6f81819783621c57d9648A3A0cff5fc', - fraxtal: '0xc96634cdc475FfC7702f123EFD77cB43c861956b', - fusemainnet: '0x66148C55906967F4F55dCD6E45e74b43b8624444', - galactica: '0x6C8210C82B17bE639CeDB3349A5aeDFF1471f72A', - game7: '0x7F30B7724040fC3384864ef3812f7a437Bc80166', - gnosis: '0xf38bb17117000b560ecdeaA5b736f3aFCc39C3b5', - gravity: '0x9603D1409150F1895349E71c4267CeE4B59635fF', - harmony: '0xA5544aeadb395Ce08A49871CF0EA0a16B2fE444B', - hashkey: '0xDfC5cAEC02737d0FD2868cCa379D866DF96ACe27', - hemi: '0xAf6dC78dCA99812D3bB729e3C17C527F74CD261F', - hyperevm: '0xD50d4E42F41392d5c7E9526423D3dc38a8d1f875', - immutablezkevmmainnet: '0x71c47adC3270EA319705626b8769E0ee54D1EA41', - inevm: '0xCf0F066B8D5F14f264EA99D67CFa389D6c1De20E', - infinityvmmainnet: '0x8cf2Ec273D7C82614640dB1d5F6AF0f27F5071cd', - ink: '0x20b3D70fA3d7B69Aba42b0bA9D8F25Ee0C656e78', - kaia: '0x47bd809B484DB532F9E2Cf540cCE24af79341deA', - katana: '0x53dE35b799fD29B129E683E43ef31cEEB3b88D22', - linea: '0x999bD2da1f24aece8c558E54350FB06eF8ceFe5C', - lisk: '0xe1D145f3c539f287789D6f8C6Bc010a1fECdb259', - lukso: '0x5045dE416efC1738046E26Bf30dfEf9CB60C8de7', - lumiaprism: '0x5C6Bc53c4e0d7DAAee0bb5F9181f476c77643576', - mantapacific: '0xF899c68F4a8C79a2E1aC66Da9976a990F2b78Da1', - mantle: '0x556C39820DBBB3Cd6f4c164CD91F755f89Ac8c62', - matchain: '0x9F31268d17Eab404ED2DEdFbb8ba0022E3fF621f', - merlin: '0xA5544aeadb395Ce08A49871CF0EA0a16B2fE444B', - metal: '0x1547282562E9b32561C7bdD41cAfB5ce89Ff08dA', - metis: '0xD998C3Ee6b8EE592C7d0bcf8e5b43F4Dc314C07F', - mint: '0x3C8bd6d095c77Dc8251D5802162b10898b1925Cd', - miraclechain: '0x7c06d94A3177c550f17239442Dd1dA9C68c33a85', - mode: '0x917445680fbd406747abFa78F381efd63F4aF599', - molten: '0x07Db6F557F8C758a485cEb188EDb727c34D44FDf', - moonbeam: '0x9D91e377655380010f8795F08753F30F54D4aEEc', - morph: '0x1a4f0d915ce6FAe98B6b1c78FcbB8c122f30afCd', - nero: '0x8f454FfcF4eb83fbf41a6b9e5fA18F4602587EB7', - nibiru: '0xdb10a32F38A8c1B944277fA2Dd6Dfd89aD83480F', - oortmainnet: '0x7038d9B38D417bB0b9f30158697e333B1dd921E3', - // ontology: '0xA59Eb6F5C4C365f533407E978321531E9F610b02', - opbnb: '0x121BA5F314168B2E34CC0b1d81784af384B09B04', - orderly: '0xFfaf98AC934B84B54171c6f116B1d23cde4e347E', - peaq: '0xE21447D4f0F993dD8C1C86Cd216E2E68f2a1CdcE', - plume: '0x2E625B8191C8b1CEceAB6B6d3c833547A337e7F2', - polygon: '0x20e52e3BeDf7BD305Ca816Dc54a0835D3bDeD820', - polygonzkevm: '0xA136C9297ECA646b2dbFcD4Fb06cf9A10CF8027e', - polynomialfi: '0xb765a7d803b2bB92EE228B6D46Af744CB5BDDD79', - prom: '0x024b2F836A29F80859dE21C0Dc0410b538742560', - proofofplay: '0x367fD428ed583F6B3B800F1C0E0450Aff7645C7E', - rarichain: '0x69DAb5389A8CA6f6e1268B701e9ec7E38182F58b', - reactive: '0xc192035485ac67334A8A2080f7C4930624DAAa9C', - redstone: '0x8D69E4d1CBCDF7B14E89652D70F25aaf61A97517', - rivalz: '0xa97762E6e3A2596d591921be26223EEC9999d642', - ronin: '0x8B15B5861d1F391F98e2014aB510aF2320CA6eBD', - rootstockmainnet: '0xEc535F1F95Be450123Aa8Cd2Dfa5E2C69970c34E', - sanko: '0xF1657741cd4670c7c135657d473dED2f847894C1', - scroll: '0xe4927B0D71c86C7bad2236DD191ECABdab796936', - sei: '0x8a3A8A4C9f188bbD45E75271Dc590077fc96EDc7', - shibarium: '0xE08183fbd724B43Da0c043Af02cb3D1F1ECA2a98', - snaxchain: '0xFfaf98AC934B84B54171c6f116B1d23cde4e347E', - soneium: '0x6af23EB68a6223a4af9F056E10D93ef4d960Da05', - sonic: '0x39e4A8F7AA0826b1Bf94551A00eB2142AC7D5Bb5', - story: '0x769926C9f2CCc61dB22A45c8F75Bd2fAeFd25748', - subtensor: '0x4C8D6Ae76a04108E20d1Cc114041c632aC040ABC', - superseed: '0xE9617FA7edD4741997Ee994038D977a377612Ae1', - superpositionmainnet: '0xE2067Bf557FF7024fb50dB30bCC01fBb5FF81712', - swell: '0x4E8d79E902502e165130eC3f8cfbfafD771DFb4b', - tac: '0xb801C2712f92E2d29e1120862e0f9C93d80CbBcA', - taiko: '0x2ca9CC72285EE7D6c7344623F2Bfffcc8894005c', - tangle: '0x93b0515c4CB2Af39626B334E47afCC8e4BDAB8Db', - telos: '0xdfeC16589b2aC0aA029858A039A2d6b53b45e47E', - torus: '0xA9143712A8c5CB4e7687F5b4f7163A1E6D1c1476', - unichain: '0xD39c288D2A021207Dfec7780e778D73460f563D7', - unitzero: '0x9F31268d17Eab404ED2DEdFbb8ba0022E3fF621f', - vana: '0x486815B6D0dC66B041965dB1904872167ddcE433', - // viction: '0x426FC4C5CC60E5e47101fe30d4f8B94F1b7C1C70', - worldchain: '0x17f0207b9529cAb39F6e02394157b5a6c7064393', - xai: '0xf4afbc0C86D9ee6C52E30947FC3D7E22dCA9BA4A', - xlayer: '0xAD598d164ecFf737C7556871Bb1D50215c4D9517', - xpla: '0xf6311eA6675b551fe4F4185FD75470ec55Db3a3d', - xrplevm: '0x772C35b16FC5B9a989915a1753b2A281cC79C44D', - zetachain: '0x4ffB88393797A9c42c837A317C1c28dB83313379', - zircuit: '0xFC862c01cE846D151a7F45E5d38e778ed330b151', - zoramainnet: '0x1c4E1d5fDd4F862D219fC7eeF45F4EA1a4fEE2D2', -} as const; diff --git a/typescript/infra/config/environments/mainnet3/governance/safe/aw.ts b/typescript/infra/config/environments/mainnet3/governance/safe/aw.ts index 83b880f5e9a..2a96a828d26 100644 --- a/typescript/infra/config/environments/mainnet3/governance/safe/aw.ts +++ b/typescript/infra/config/environments/mainnet3/governance/safe/aw.ts @@ -45,7 +45,6 @@ export const awSafes: ChainMap
= { zeronetwork: '0xCB21F61A3c8139F18e635d45aD1e62A4A61d2c3D', abstract: '0xCB21F61A3c8139F18e635d45aD1e62A4A61d2c3D', zksync: '0x9C81aA0cC233e9BddeA426F5d395Ab5B65135450', - zklink: '0xCB21F61A3c8139F18e635d45aD1e62A4A61d2c3D', sophon: '0x3D1baf8cA4935f416671640B1Aa9E17E005986eE', // ousdt extension diff --git a/typescript/infra/config/environments/mainnet3/governance/safe/dymension.ts b/typescript/infra/config/environments/mainnet3/governance/safe/dymension.ts new file mode 100644 index 00000000000..4ce7b7afeca --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/governance/safe/dymension.ts @@ -0,0 +1,8 @@ +import { ChainMap } from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +export const dymensionSafes: ChainMap
= { + ethereum: '0x97E4720cE86428FC2609eA0B36a69F5fB68a6712', + base: '0x97E4720cE86428FC2609eA0B36a69F5fB68a6712', + bsc: '0x97E4720cE86428FC2609eA0B36a69F5fB68a6712', +}; diff --git a/typescript/infra/config/environments/mainnet3/governance/safe/regular.ts b/typescript/infra/config/environments/mainnet3/governance/safe/regular.ts index e77cd06ed0f..3c8e0ca80ce 100644 --- a/typescript/infra/config/environments/mainnet3/governance/safe/regular.ts +++ b/typescript/infra/config/environments/mainnet3/governance/safe/regular.ts @@ -24,5 +24,4 @@ export const regularSafes: ChainMap
= { sophon: '0x113d3a19031Fe5DB58884D6aa54545dD4De499c0', zeronetwork: '0xcd81ccFe7D9306849136Fa96397113345a32ECf3', zksync: '0xcd81ccFe7D9306849136Fa96397113345a32ECf3', - zklink: '0xcd81ccFe7D9306849136Fa96397113345a32ECf3', }; diff --git a/typescript/infra/config/environments/mainnet3/governance/signers/dymension.ts b/typescript/infra/config/environments/mainnet3/governance/signers/dymension.ts new file mode 100644 index 00000000000..efbd8798573 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/governance/signers/dymension.ts @@ -0,0 +1,9 @@ +import { Address } from '@hyperlane-xyz/utils'; + +export const dymensionSigners: Address[] = [ + '0x7DF3E48032AEadBf9125e0396bE50e7cDF29D2FB', + '0x5C6733E5B9FC1e943f6Ffad86f6B2D2A8982f7E4', + '0xaBc2D203ea7743cd11CE7d2111Bc7d109d5664e1', +]; + +export const dymensionThreshold = 2; diff --git a/typescript/infra/config/environments/mainnet3/governance/signers/regular.ts b/typescript/infra/config/environments/mainnet3/governance/signers/regular.ts index f06ddd4f8fd..00e3e15f881 100644 --- a/typescript/infra/config/environments/mainnet3/governance/signers/regular.ts +++ b/typescript/infra/config/environments/mainnet3/governance/signers/regular.ts @@ -4,8 +4,8 @@ export const regularSigners: Address[] = [ '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba', // 1 '0xc3E966E79eF1aA4751221F55fB8A36589C24C0cA', // 2 '0x2f43Ac3cD6A22E4Ba20d3d18d116b1f9420eD84B', // 3 - '0xfae231524539698f1d136d7b21e3b4144cdbf2a3', // 4 - '0x2C073004A6e4f37377F848193d6433260Ebe9b99', // 5 + '0x3247FC16763c340EeFc1e60eda172ef9Db7c96B6', // 4 + '0x97a1b7D1B6D52CACf4B6754f144fd6E404790346', // 5 '0x9f500df92175b2ac36f8d443382b219d211d354a', // 6 '0x82950a6356316272dF1928C72F5F0A44D9673c88', // 7 '0x861FC61a961F8AFDf115B8DE274101B9ECea2F26', // 8 @@ -14,3 +14,18 @@ export const regularSigners: Address[] = [ ]; export const regularThreshold = 6; + +export const regularSvmSigners: Address[] = [ + '9bRSUPjfS3xS6n5EfkJzHFTRDa4AHLda8BU2pP4HoWnf', // 1 + '3c5oFeqTRDXUkTcaxCMS2jsHWAkvUi4treoMBCP1aUPo', // 2 + '4ocDADfUkH6qSMBGV977rPJEvmFxiG1MSmBbKwMUX9Ya', // 3 + 'FwHF7jDNvuwCuYhBCZZPY1VBoBVSHPsVBqu7UCp7dnA3', // 4 + '8V5Uu7xMQhaUcD6pzNReVzrSe89KMCXoGk7ErbkXik2b', // 5 + 'A4voX3UC3TGLsKyEsDGWAvrjT3mpnA67Fx9cccdoPKqm', // 6 + 'Cstnx15y98NCnjXddxEcgpJrW44TFYq9QSXYcdmfW4HR', // 7 + 'DqGAc8YvHUPnpA5cuausLhkCp1cUvEQFYjpWVHJTPiNM', // 8 + 'EpaKfP4sd2xhEQvnYS3EgCXj7rkg7jTCasTByFj8kuLg', // 9 + 'AkG81cTMBD2Kh1gmDF66mAeRosDQjvATJyDWN48BSh85', // 10 +]; + +export const regularSvmThreshold = 6; diff --git a/typescript/infra/config/environments/mainnet3/governance/timelock/aw.ts b/typescript/infra/config/environments/mainnet3/governance/timelock/aw.ts index 0bc07d6c3eb..0bc71ca30b0 100644 --- a/typescript/infra/config/environments/mainnet3/governance/timelock/aw.ts +++ b/typescript/infra/config/environments/mainnet3/governance/timelock/aw.ts @@ -31,18 +31,14 @@ export const awTimelocks: ChainMap
= { celo: '0xb527ea7ff1B14fEb9FFF98b5Cd750Bd311cD598F', cheesechain: '0x74ff0c99e190d94Fa869da967d6cAC850944b888', chilizmainnet: '0x273CC7b479f8ca580433A7e2F2997A3FDdb8DCe1', - conwai: '0x0424eB0D5E83F58BB3405137ca0f489D8BB6e866', coredao: '0x6d93817FF18DFe745b8C268F8cf22CD9F5E2CDAb', coti: '0x4Fea96D613F51fF83459d101e256Cd165a1e73BB', cyber: '0x6125770D3a587B63b98db489aDcd461EC8949a8c', degenchain: '0x3b9a5b3940f61b17cc57D19B8623Ad4DA4eB5D53', dogechain: '0x0c7b67793c56eD93773cEee07A43B3D7aDF533b7', - duckchain: '0x10d9e54b91EE8f65E9f694b1d5e4aB34976a62e6', endurance: '0x0389faCac114023C123E22F3E54394944cAbcb48', everclear: '0xEe2CCBfdC231989DCBB18fBfBbFE14673D98b7A0', - evmos: '0xb7C9307fE90B9AB093c6D3EdeE3259f5378D5f03', fantom: '0x2B2E1C0773D46B923df9b37De405A946FBf38437', - flame: '0x181118Cc6F1f3f99f3FDB38eb54fe99E4a9B7d91', flare: '0x96E298f018d0543b0a006d7f4fcfc7CF6e2acf9B', flowmainnet: '0x3D6597Ae622D6223d60a57E92c2F259283dD2D69', fluence: '0xC8E323036AAFB4B4201e7B640E79C4Db285A3FC8', @@ -79,7 +75,6 @@ export const awTimelocks: ChainMap
= { molten: '0x7c4b6F70EC184e8159364cCDe9F66f5eD5853d54', moonbeam: '0x556ACaA31C67DF3dCa61254bd8452a74390fED7D', morph: '0x118B90DADa439Fc915525701C57A4f5bca757Ecb', - nero: '0xA6a6Deeaa09cb0C451f6cF5762928f4C2dEBeF31', nibiru: '0xe3742c54e8E5564BD89F132B939e631a8a1654cE', oortmainnet: '0xCD45B07Db7cDE0cbb2683FA84dc5b7a2dbDEfDc7', opbnb: '0x200183De44bf765ECB73cD62A74010EaaBC43146', @@ -95,10 +90,7 @@ export const awTimelocks: ChainMap
= { rarichain: '0x30FAbD6257BEA5e09C24225da59E9eD1B6f12B01', reactive: '0xcD78A32Da8cfe9452cD2F50F547c11B979Afcf1b', redstone: '0x6ff6F7e23a51c2a45cd31B066f5a23C38849fc13', - rivalz: '0x1004820F571C86b2d8F5482F86b22D27D6dB62BF', ronin: '0x96c418BaB57953765B97532c494125F9b2e2B38D', - rootstockmainnet: '0x73188a42421FD5F5EC1A2D55760A60eEE21BC82B', - sanko: '0xD9C0856Aa6764665EcD8C18E83E5a81FdB8996f9', scroll: '0xeFC55057FC31D3fbbEeE53f64D63d2D336927D33', sei: '0xd223107e1B9fd4a298b52a49564626D10d6E5c44', shibarium: '0x7759513f71a1EDF48385f7EE5948Bf80786D59a2', @@ -113,17 +105,18 @@ export const awTimelocks: ChainMap
= { tac: '0x9c64f327F0140DeBd430aab3E2F1d6cbcA921227', taiko: '0x61683848c92927376DE30F3B52558655c13269d1', tangle: '0x34C76076248ab6caA626FEd09d364c698347aB32', - telos: '0x0B134ed7d946FEfeafb31F0A0fDa584438119C89', torus: '0x588A8beD606cd1F4946d8B10Ad9a56b0B294d0fF', unichain: '0x6291596339A6EDD6cD68aca1d1c08B1fa2115F8C', - unitzero: '0x8FD8382057654d70f24879404475D960808346c3', vana: '0xA205e9979e0beE2fBD1DD23e702cA98a118BCd5c', worldchain: '0x1008FAbD07aBd93a7D9bB81803a89cC3a834E1A9', xai: '0xE59592a179c4f436d5d2e4caA6e2750beA4E3166', xlayer: '0x575a4b7D13978421Bb7cEbf470A8e5E40f911a29', - xpla: '0x560046aE692B4599bAC99A6FA6dbC3D135b22506', xrplevm: '0x1721d03e7aEe1688706f98e7538a1B562a87237B', zetachain: '0x9A291D353Fc52CE3b5F6fC7e9432eB1743d1bBe0', zircuit: '0x3f1332D80E390BE30c231BB57efFFAa56546aB27', zoramainnet: '0xaB402f227e892Ef37C105bf06619c0fa106a1fB2', + + // Sep 22, 2025 - Timelock for 0G oUSDT extension + // ---------------------------------------------------------- + zerogravity: '0x11EF91d17c5ad3330DbCa709a8841743d3Af6819', } as const; diff --git a/typescript/infra/config/environments/mainnet3/governance/timelock/dymension.ts b/typescript/infra/config/environments/mainnet3/governance/timelock/dymension.ts new file mode 100644 index 00000000000..59dd3e187be --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/governance/timelock/dymension.ts @@ -0,0 +1,5 @@ +import { ChainMap } from '@hyperlane-xyz/sdk'; +import { Address } from '@hyperlane-xyz/utils'; + +export const dymensionTimelocks: ChainMap
= { +} as const; diff --git a/typescript/infra/config/environments/mainnet3/governance/timelock/regular.ts b/typescript/infra/config/environments/mainnet3/governance/timelock/regular.ts index d0fafaab546..6242f6aaacc 100644 --- a/typescript/infra/config/environments/mainnet3/governance/timelock/regular.ts +++ b/typescript/infra/config/environments/mainnet3/governance/timelock/regular.ts @@ -31,18 +31,14 @@ export const regularTimelocks: ChainMap
= { celo: '0xA32592640206E267BEB3f4260020D636e60E7b5C', cheesechain: '0x92FDcD6ECa2B05a6ceAdb209D81DeB9e91f02495', chilizmainnet: '0xA87F683D055386e2b96EBD6F11e8DA5e089a2527', - conwai: '0x7523364bee77C0AdbacC71D0Ff4CbfAbD2A8aC3A', coredao: '0x4c50799aD95eF0DF91fF26dd406a068D0a28b4E8', coti: '0x32A2eeC8601968505F311f2ab6aCbcC52E1861BB', cyber: '0xc7420c8d8B6dF031D9D24221573dd1b8ba67F011', degenchain: '0x8437e8675033350951203c8f811718d924308a7e', dogechain: '0x544bd682Ba218A194A3209f89D92a31E009315E0', - duckchain: '0x212D727Eb1e697da467045ef8F56f56Ce2C0F8fB', endurance: '0xf3b1F415740A26568C45b1c771A737E31C198F09', everclear: '0xb57e5bB020E0B1c377bFf84EF6793DAF30264737', - evmos: '0x3c38fC01159E7BE0685653A0C896eA49F2BAa7c1', fantom: '0x65b4b894CB1150EAE28500e02c8A17eeb37C7637', - flame: '0x4ca4541f2Fe9590d8D11b005bFFfe9F231CCb5d0', flare: '0xa2fd91b39926daBB5009C5e4ee237c1C0b677bCe', flowmainnet: '0x04dB778f05854f26E67e0a66b740BBbE9070D366', fluence: '0x9c64f327F0140DeBd430aab3E2F1d6cbcA921227', @@ -79,7 +75,6 @@ export const regularTimelocks: ChainMap
= { molten: '0xB46f02eaA31CE5aE4Aa6F8C2B72C7ce441C17778', moonbeam: '0xD4CC98dC4eb07B023BB8C62Bc6592d356c91b0c4', morph: '0x77D6D70BB10f8FecB553db5942dd0977f298952B', - nero: '0x244829Edf5a35D0d48883908375D4dEE6570d5F7', nibiru: '0x2C284FDAEd6c200BA9557d52E6147F8F585adBEd', oortmainnet: '0xf63Eb8e72Cbc59eC1185620c44050aF266d3eC19', opbnb: '0xf40eE9FF75Fa34910b7C4C8d68d4850B3bD184D3', @@ -95,10 +90,7 @@ export const regularTimelocks: ChainMap
= { rarichain: '0x92684D97F3397EE9762EAE3908Db2C7FDCd33Ea3', reactive: '0x8eC5f0239C77295452Ed899FDB851e785cA5FC31', redstone: '0x76038685a83E4E3Deaee85d05Ac6151Ab6db7981', - rivalz: '0x5fc85d8f7Ba13334E2917fCe0963989a26a13E35', ronin: '0xf728C884De5275a608dEC222dACd0f2BF2E23AB6', - rootstockmainnet: '0x63ed5aFF2163F2A130DAf8aBdb8Af83bf3D1e904', - sanko: '0x4d9D942010eB2411fF16a9d910Badbc8520a9BfF', scroll: '0x9Fca159607687AE26367d66166e680A930af0780', sei: '0xB97d3bF2fC296c2cAC4056bBC8A783ff39408e20', shibarium: '0xc0Fb1Fca107061C72d122b72903691435C9976a5', @@ -113,15 +105,12 @@ export const regularTimelocks: ChainMap
= { tac: '0x42eDF86Ba9cCF1a8934De882087E19C96396bbD2', taiko: '0xF42f2F985309A75BF6eFFE91d3E83FD6773226d0', tangle: '0x1c103FF92333b2F1975704D20EdFE6eFD05CeAE7', - telos: '0x454438382f7f8c313D9D8c02398d7599B6904eA6', torus: '0x53d1f53045447A610B117A11e96A89D77eC12627', unichain: '0x824e8685fC8131173164B00274e35CAd65100E1A', - unitzero: '0x13e7C9A544aa94625d94e1aE8222EBCB416560bB', vana: '0x8873A96FEd6e01369D59369Ab26E4ed3400011ce', worldchain: '0x616736AFFF3C2894Ed557cEa3A8Ce031E4ed9d3e', xai: '0xF32F45fB4315841F93289A1ECD62efD89F9964C9', xlayer: '0x8401CB61bF208cA238974712ce665e37AD861257', - xpla: '0x2B285f471e9De91dCe4ff755DDe60204464B9286', xrplevm: '0xa7D6042eEf06E81168e640b5C41632eE5295227D', zetachain: '0x2C89178b0b83cC88dDF30b38d20939F3E4cB91e9', zircuit: '0xa01d42Cb504a632963cAaA10B53AEF76e3E9451B', diff --git a/typescript/infra/config/environments/mainnet3/governance/utils.ts b/typescript/infra/config/environments/mainnet3/governance/utils.ts index 3657443b602..bfd66280173 100644 --- a/typescript/infra/config/environments/mainnet3/governance/utils.ts +++ b/typescript/infra/config/environments/mainnet3/governance/utils.ts @@ -3,9 +3,9 @@ import { Address } from '@hyperlane-xyz/utils'; import { GovernanceType } from '../../../../src/governance.js'; -import { awIcasV2 } from './ica/aw2.js'; +import { awIcasLegacy } from './ica/_awLegacy.js'; +import { regularIcasLegacy } from './ica/_regularLegacy.js'; import { awIcas } from './ica/aw.js'; -import { regularIcasV2 } from './ica/regular2.js'; import { regularIcas } from './ica/regular.js'; import { awSafes } from './safe/aw.js'; import { irregularSafes } from './safe/irregular.js'; @@ -16,6 +16,11 @@ import { irregularSigners, irregularThreshold } from './signers/irregular.js'; import { regularSigners, regularThreshold } from './signers/regular.js'; import { awTimelocks } from './timelock/aw.js'; import { regularTimelocks } from './timelock/regular.js'; +import { dymensionTimelocks } from './timelock/dymension.js'; +import { dymensionSafes } from './safe/dymension.js'; +import { dymensionIcas } from './ica/dymension.js'; +import { dymensionSigners, dymensionThreshold } from './signers/dymension.js'; + export function getGovernanceTimelocks(governanceType: GovernanceType) { switch (governanceType) { @@ -27,8 +32,10 @@ export function getGovernanceTimelocks(governanceType: GovernanceType) { return {}; case GovernanceType.OUSDT: return {}; + case GovernanceType.Dymension: + return dymensionTimelocks; default: - throw new Error(`Unknown governance type: ${governanceType}`); + throw new Error(`Unsupported governance type: ${governanceType}`); } } @@ -42,36 +49,42 @@ export function getGovernanceSafes(governanceType: GovernanceType) { return irregularSafes; case GovernanceType.OUSDT: return ousdtSafes; + case GovernanceType.Dymension: + return dymensionSafes; default: - throw new Error(`Unknown governance type: ${governanceType}`); + throw new Error(`Unsupported governance type: ${governanceType}`); } } export function getLegacyGovernanceIcas(governanceType: GovernanceType) { switch (governanceType) { case GovernanceType.Regular: - return regularIcas; + return regularIcasLegacy; case GovernanceType.AbacusWorks: - return awIcas; + return awIcasLegacy; case GovernanceType.Irregular: return {}; case GovernanceType.OUSDT: return {}; + case GovernanceType.Dymension: + return {}; default: - throw new Error(`Unknown governance type: ${governanceType}`); + throw new Error(`Unsupported governance type: ${governanceType}`); } } export function getGovernanceIcas(governanceType: GovernanceType) { switch (governanceType) { case GovernanceType.Regular: - return regularIcasV2; + return regularIcas; case GovernanceType.AbacusWorks: - return awIcasV2; + return awIcas; case GovernanceType.Irregular: return {}; case GovernanceType.OUSDT: return {}; + case GovernanceType.Dymension: + return dymensionIcas; default: throw new Error(`Unknown governance type: ${governanceType}`); } @@ -97,6 +110,11 @@ export function getGovernanceSigners(governanceType: GovernanceType): { signers: irregularSigners, threshold: irregularThreshold, }; + case GovernanceType.Dymension: + return { + signers: dymensionSigners, + threshold: dymensionThreshold, + }; default: throw new Error( `Unsupported method for governance type: ${governanceType}`, @@ -110,6 +128,7 @@ export function getSafeChains(): Set { ...Object.keys(getGovernanceSafes(GovernanceType.Regular)), ...Object.keys(getGovernanceSafes(GovernanceType.Irregular)), ...Object.keys(getGovernanceSafes(GovernanceType.OUSDT)), + ...Object.keys(getGovernanceSafes(GovernanceType.Dymension)), ); } diff --git a/typescript/infra/config/environments/mainnet3/igp.ts b/typescript/infra/config/environments/mainnet3/igp.ts index 8efe6b6081a..3171a8d6230 100644 --- a/typescript/infra/config/environments/mainnet3/igp.ts +++ b/typescript/infra/config/environments/mainnet3/igp.ts @@ -24,12 +24,6 @@ const tokenPrices: ChainMap = rawTokenPrices; export function getOverheadWithOverrides(local: ChainName, remote: ChainName) { let overhead = getOverhead(local, remote); - // DeepBrainChain gas metering is different to vanilla EVM - // https://hyperlaneworkspace.slack.com/archives/C08GR6PBPGT/p1743074511084179?thread_ts=1743073273.793169&cid=C08GR6PBPGT - if (remote === 'deepbrainchain') { - overhead *= 8; - } - // Moonbeam/Torus gas usage can be up to 4x higher than vanilla EVM if (remote === 'moonbeam' || remote === 'torus') { overhead *= 4; diff --git a/typescript/infra/config/environments/mainnet3/igp/verification.json b/typescript/infra/config/environments/mainnet3/igp/verification.json index 0967ef424bc..9ea55c61cd1 100644 --- a/typescript/infra/config/environments/mainnet3/igp/verification.json +++ b/typescript/infra/config/environments/mainnet3/igp/verification.json @@ -1 +1,17 @@ -{} +{ + "zerogravity": [ + { + "name": "InterchainGasPaymaster", + "address": "0xF6CC9B10c607afB777380bF71F272E4D7037C3A9", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x7B8AA8f23Ab6B0757eC6FC71894211376D9335b0", + "constructorArguments": "000000000000000000000000f6cc9b10c607afb777380bf71f272e4d7037c3a900000000000000000000000002d16bc51af6bfd153d67ca61754cf912e82c4d900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xF6CC9B10c607afB777380bF71F272E4D7037C3A9" + } + ] +} diff --git a/typescript/infra/config/environments/mainnet3/infrastructure.ts b/typescript/infra/config/environments/mainnet3/infrastructure.ts index 2d1e81f48ed..36c15751c76 100644 --- a/typescript/infra/config/environments/mainnet3/infrastructure.ts +++ b/typescript/infra/config/environments/mainnet3/infrastructure.ts @@ -41,10 +41,6 @@ export const infrastructure: InfrastructureConfig = { 'hyperlane-mainnet3-', 'rc-mainnet3-', 'neutron-mainnet3-', - // All vanguard context secrets. There's a cap on the number of - // prefixes you can specify in a single IAM policy, so for convenience - // we just use a single prefix for all vanguard contexts. - 'vanguard', 'mainnet3-', ], }, diff --git a/typescript/infra/config/environments/mainnet3/ism/verification.json b/typescript/infra/config/environments/mainnet3/ism/verification.json index c1eb497cdbe..121bc24d259 100644 --- a/typescript/infra/config/environments/mainnet3/ism/verification.json +++ b/typescript/infra/config/environments/mainnet3/ism/verification.json @@ -2919,92 +2919,6 @@ "name": "StaticMessageIdWeightedMultisigIsm" } ], - "sanko": [ - { - "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMerkleRootMultisigIsmFactory" - }, - { - "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", - "constructorArguments": "", - "isProxy": true, - "name": "StaticMerkleRootMultisigIsm" - }, - { - "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMessageIdMultisigIsmFactory" - }, - { - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", - "constructorArguments": "", - "isProxy": true, - "name": "StaticMessageIdMultisigIsm" - }, - { - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "constructorArguments": "", - "isProxy": false, - "name": "StaticAggregationIsmFactory" - }, - { - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", - "constructorArguments": "", - "isProxy": true, - "name": "StaticAggregationIsm" - }, - { - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "constructorArguments": "", - "isProxy": false, - "name": "StaticAggregationHookFactory" - }, - { - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", - "constructorArguments": "", - "isProxy": true, - "name": "StaticAggregationHook" - }, - { - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "constructorArguments": "", - "isProxy": false, - "name": "DomainRoutingIsmFactory" - }, - { - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", - "constructorArguments": "", - "isProxy": true, - "name": "DomainRoutingIsm" - }, - { - "address": "0x749848D7b783A328638C3ea74AcFcfb73c977CbE", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMerkleRootWeightedMultisigIsmFactory" - }, - { - "address": "0x5C28C7EA165222CA2f1B5dB1B1c7eA221dFe862c", - "constructorArguments": "", - "isProxy": true, - "name": "StaticMerkleRootWeightedMultisigIsm" - }, - { - "address": "0x148CF67B8A242c1360bb2C93fCe203EC4d4f9B56", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMessageIdWeightedMultisigIsmFactory" - }, - { - "address": "0xAAD3028b6c948134cf1202493297E5e6F609b613", - "constructorArguments": "", - "isProxy": true, - "name": "StaticMessageIdWeightedMultisigIsm" - } - ], "scroll": [ { "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -4853,92 +4767,6 @@ "isProxy": true } ], - "lumia": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsmFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationIsm", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHookFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationHook", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsmFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "DomainRoutingIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", - "constructorArguments": "", - "isProxy": true - } - ], "gravity": [ { "name": "StaticMerkleRootMultisigIsmFactory", @@ -6229,92 +6057,6 @@ "isProxy": true } ], - "flame": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsmFactory", - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHookFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationHook", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsmFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "DomainRoutingIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", - "constructorArguments": "", - "isProxy": true - } - ], "immutablezkevmmainnet": [ { "name": "StaticMerkleRootMultisigIsmFactory", @@ -6659,93 +6401,93 @@ "isProxy": true } ], - "rootstockmainnet": [ + "superseed": [ { "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x8a5524a1b898fe848819ff675c577b5c7F224684", + "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootMultisigIsm", - "address": "0x6cFEbe3c6f25Aa7E7D0028F8c903ad9F2AE46aA6", + "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdMultisigIsmFactory", - "address": "0xD127D4549cb4A5B2781303a4fE99a10EAd13263A", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdMultisigIsm", - "address": "0x4dA182318490873d27b1360b2A7ae8705d6bAdf5", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationIsmFactory", - "address": "0x7C012DCA02C42cfA3Fd7Da3B0ED7234B52AE68eF", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationIsm", - "address": "0xbc16e8081F24ef09067B3E40DB750Cc143f2e675", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationHookFactory", - "address": "0xc441521bA37EaCd9af4f319CcdA27E9D48f74281", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationHook", - "address": "0x3B61D3AFa1d530c955EbE5B2790E84De3Ed5262e", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", "constructorArguments": "", "isProxy": true }, { "name": "DomainRoutingIsmFactory", - "address": "0x730f8a4128Fa8c53C777B62Baa1abeF94cAd34a9", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "constructorArguments": "", "isProxy": false }, { "name": "DomainRoutingIsm", - "address": "0xf19CF2b7A4939fdF2EFe3aeB8459281e85E4b48e", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", "constructorArguments": "", "isProxy": true }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0xbed53B5C5BCE9433f25A2A702e6df13E22d84Ae9", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0xC92aBFea0904f46eFd8B66E80456dB2f9d09f339", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x5bdADEAD721Eb4C4038fF7c989E3C7BbBA302435", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x40e8e9caEc0cb405319AC37e966DC2c9746cCb92", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", "constructorArguments": "", "isProxy": true } ], - "superseed": [ + "unichain": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -6831,7 +6573,7 @@ "isProxy": true } ], - "unichain": [ + "vana": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -6917,7 +6659,7 @@ "isProxy": true } ], - "duckchain": [ + "boba": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -7003,7 +6745,7 @@ "isProxy": true } ], - "vana": [ + "bsquared": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -7089,93 +6831,93 @@ "isProxy": true } ], - "boba": [ + "lumiaprism": [ { "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "address": "0xf376247BdE9763808034567E7E87c84970e81E8F", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootMultisigIsm", - "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", + "address": "0x918582303e7984Bc4c9ae4ac3Ff49883f4aCC8a2", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "address": "0x280626Ea62fBB3C1A38641D5e735c1d0EE3F28cF", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "address": "0x5549d1353Ead03F61f1891BF540e7CcF95d8E8E8", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationIsmFactory", - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "address": "0xD7777efaC7241E63f0c7708aA912AEd8CFAfee69", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "address": "0x4c75c9F10C42034D1A226A205E34f5E8abDF0310", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationHookFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "address": "0xf40eE9FF75Fa34910b7C4C8d68d4850B3bD184D3", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationHook", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "address": "0x87A6623E709990358905c48a19F7912C4B3AAD26", "constructorArguments": "", "isProxy": true }, { "name": "DomainRoutingIsmFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "address": "0x8eC5f0239C77295452Ed899FDB851e785cA5FC31", "constructorArguments": "", "isProxy": false }, { "name": "DomainRoutingIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "address": "0x0deb89922170b20AB549C25C93aAc67DeAEDFe14", "constructorArguments": "", "isProxy": true }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "address": "0x408F58D25003001C7e5F30259217F1040b8F5AaD", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "address": "0xF9Dd08bd70867b29154BBdA6c110F1d9145fc5A3", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "address": "0xf6fB78dc009C1A4286c0E7d90C10c9E8906a62Ea", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "address": "0xa12bACe4d09cBf37a3c98CB18978E069360552F4", "constructorArguments": "", "isProxy": true } ], - "bsquared": [ + "swell": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -7261,93 +7003,93 @@ "isProxy": true } ], - "lumiaprism": [ + "appchain": [ { "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0xf376247BdE9763808034567E7E87c84970e81E8F", + "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootMultisigIsm", - "address": "0x918582303e7984Bc4c9ae4ac3Ff49883f4aCC8a2", + "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x280626Ea62fBB3C1A38641D5e735c1d0EE3F28cF", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdMultisigIsm", - "address": "0x5549d1353Ead03F61f1891BF540e7CcF95d8E8E8", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationIsmFactory", - "address": "0xD7777efaC7241E63f0c7708aA912AEd8CFAfee69", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationIsm", - "address": "0x4c75c9F10C42034D1A226A205E34f5E8abDF0310", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationHookFactory", - "address": "0xf40eE9FF75Fa34910b7C4C8d68d4850B3bD184D3", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationHook", - "address": "0x87A6623E709990358905c48a19F7912C4B3AAD26", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", "constructorArguments": "", "isProxy": true }, { "name": "DomainRoutingIsmFactory", - "address": "0x8eC5f0239C77295452Ed899FDB851e785cA5FC31", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "constructorArguments": "", "isProxy": false }, { "name": "DomainRoutingIsm", - "address": "0x0deb89922170b20AB549C25C93aAc67DeAEDFe14", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", "constructorArguments": "", "isProxy": true }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x408F58D25003001C7e5F30259217F1040b8F5AaD", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0xF9Dd08bd70867b29154BBdA6c110F1d9145fc5A3", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0xf6fB78dc009C1A4286c0E7d90C10c9E8906a62Ea", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0xa12bACe4d09cBf37a3c98CB18978E069360552F4", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", "constructorArguments": "", "isProxy": true } ], - "swell": [ + "ink": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -7378,62 +7120,74 @@ "constructorArguments": "", "isProxy": false }, + { + "name": "StaticAggregationIsmFactory", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticAggregationIsmFactory", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "constructorArguments": "", + "isProxy": false + }, { "name": "StaticAggregationIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationHookFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationHook", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", "constructorArguments": "", "isProxy": true }, { "name": "DomainRoutingIsmFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "constructorArguments": "", "isProxy": false }, { "name": "DomainRoutingIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", "constructorArguments": "", "isProxy": true }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "address": "0xDFF18Bf286c9cDd0fC653a28616460Cf7443F8EF", "constructorArguments": "", "isProxy": true } ], - "appchain": [ + "form": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -7519,7 +7273,7 @@ "isProxy": true } ], - "conwai": [ + "sonic": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -7605,7 +7359,7 @@ "isProxy": true } ], - "telos": [ + "soneium": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -7691,7 +7445,7 @@ "isProxy": true } ], - "ink": [ + "aurora": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -7722,74 +7476,62 @@ "constructorArguments": "", "isProxy": false }, - { - "name": "StaticAggregationIsmFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationIsmFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "constructorArguments": "", - "isProxy": false - }, { "name": "StaticAggregationIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationHookFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationHook", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", "constructorArguments": "", "isProxy": true }, { "name": "DomainRoutingIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "constructorArguments": "", "isProxy": false }, { "name": "DomainRoutingIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", "constructorArguments": "", "isProxy": true }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0xDFF18Bf286c9cDd0fC653a28616460Cf7443F8EF", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", "constructorArguments": "", "isProxy": true } ], - "form": [ + "torus": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -7875,7 +7617,7 @@ "isProxy": true } ], - "sonic": [ + "artela": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -7961,7 +7703,7 @@ "isProxy": true } ], - "soneium": [ + "hemi": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -8047,7 +7789,7 @@ "isProxy": true } ], - "conflux": [ + "matchain": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -8133,93 +7875,137 @@ "isProxy": true } ], - "evmos": [ + "berachain": [ { "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootMultisigIsm", - "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationIsmFactory", - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationHookFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationHook", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", "constructorArguments": "", "isProxy": true }, { "name": "DomainRoutingIsmFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "constructorArguments": "", "isProxy": false }, { "name": "DomainRoutingIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", "constructorArguments": "", "isProxy": true }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "address": "0xDFF18Bf286c9cDd0fC653a28616460Cf7443F8EF", "constructorArguments": "", "isProxy": true } ], - "aurora": [ + "subtensor": [ + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x2bBcC41a504ED176512744d10f451c9e2666a814", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0xa675b28C680f9398E89a6352b4c98275555cd538", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0xdCb9bD8258b6521DA3dff57b05Eb3116a838558A", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHook", + "address": "0x2A1D274Ea7dAcCD8Ac4eFE6a33C3468E9Aa54548", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "DomainRoutingIsm", + "address": "0x9D5Fd5E68359504F32E34F88fC86Bc7145E851b8", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMerkleRootWeightedMultisigIsm", + "address": "0xeca47EA25BA694deF99244fc887f55aA0aa03068", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdWeightedMultisigIsm", + "address": "0xf8D9E9b457a1009E6802b91ee52fdDdE14E9F244", + "constructorArguments": "", + "isProxy": true + } + ], + "story": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -8305,7 +8091,7 @@ "isProxy": true } ], - "rivalz": [ + "ronin": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -8391,7 +8177,7 @@ "isProxy": true } ], - "torus": [ + "arcadia": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -8477,7 +8263,7 @@ "isProxy": true } ], - "artela": [ + "hyperevm": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -8563,7 +8349,7 @@ "isProxy": true } ], - "nero": [ + "plume": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -8649,93 +8435,93 @@ "isProxy": true } ], - "hemi": [ + "infinityvm": [ { "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootMultisigIsm", - "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationIsmFactory", - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationHookFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationHook", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", "constructorArguments": "", "isProxy": true }, { "name": "DomainRoutingIsmFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "constructorArguments": "", "isProxy": false }, { "name": "DomainRoutingIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", "constructorArguments": "", "isProxy": true }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", "constructorArguments": "", "isProxy": true } ], - "xpla": [ + "nibiru": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -8821,7 +8607,7 @@ "isProxy": true } ], - "unitzero": [ + "opbnb": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -8907,7 +8693,7 @@ "isProxy": true } ], - "matchain": [ + "coti": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -8922,208 +8708,164 @@ }, { "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationIsmFactory", - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationHookFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationHook", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", "constructorArguments": "", "isProxy": true }, { "name": "DomainRoutingIsmFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "constructorArguments": "", "isProxy": false }, { "name": "DomainRoutingIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "address": "0xDFF18Bf286c9cDd0fC653a28616460Cf7443F8EF", "constructorArguments": "", "isProxy": true }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "address": "0xfb288565DBa8489e745Fb814584d06331809d16F", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "address": "0x2f9DB5616fa3fAd1aB06cB2C906830BA63d135e3", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "address": "0x9B77c7762d7884C0AAd5953502d3b42AEFcdCd45", "constructorArguments": "", "isProxy": true } ], - "berachain": [ + "reactive": [ { "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootMultisigIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdMultisigIsmFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdMultisigIsm", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationIsmFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationHookFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationHook", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", "constructorArguments": "", "isProxy": true }, { "name": "DomainRoutingIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "constructorArguments": "", "isProxy": false }, { "name": "DomainRoutingIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", "constructorArguments": "", "isProxy": true }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0xDFF18Bf286c9cDd0fC653a28616460Cf7443F8EF", - "constructorArguments": "", - "isProxy": true - } - ], - "subtensor": [ - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x2bBcC41a504ED176512744d10f451c9e2666a814", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0xa675b28C680f9398E89a6352b4c98275555cd538", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsm", - "address": "0xdCb9bD8258b6521DA3dff57b05Eb3116a838558A", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHook", - "address": "0x2A1D274Ea7dAcCD8Ac4eFE6a33C3468E9Aa54548", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsm", - "address": "0x9D5Fd5E68359504F32E34F88fC86Bc7145E851b8", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0xeca47EA25BA694deF99244fc887f55aA0aa03068", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0xf8D9E9b457a1009E6802b91ee52fdDdE14E9F244", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", "constructorArguments": "", "isProxy": true } ], - "bouncebit": [ + "fluence": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -9209,7 +8951,7 @@ "isProxy": true } ], - "story": [ + "game7": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -9295,7 +9037,7 @@ "isProxy": true } ], - "ronin": [ + "peaq": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -9381,93 +9123,93 @@ "isProxy": true } ], - "arcadia": [ + "hashkey": [ { "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootMultisigIsm", - "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationIsmFactory", - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationHookFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationHook", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", "constructorArguments": "", "isProxy": true }, { "name": "DomainRoutingIsmFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "constructorArguments": "", "isProxy": false }, { "name": "DomainRoutingIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", "constructorArguments": "", "isProxy": true }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", "constructorArguments": "", "isProxy": true } ], - "hyperevm": [ + "infinityvmmainnet": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -9553,7 +9295,7 @@ "isProxy": true } ], - "plume": [ + "miraclechain": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -9639,93 +9381,93 @@ "isProxy": true } ], - "infinityvm": [ + "ontology": [ { "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", + "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdMultisigIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationIsmFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationIsm", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationHookFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationHook", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", "constructorArguments": "", "isProxy": true }, { "name": "DomainRoutingIsmFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "constructorArguments": "", "isProxy": false }, { "name": "DomainRoutingIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", "constructorArguments": "", "isProxy": true }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", "constructorArguments": "", "isProxy": true } ], - "nibiru": [ + "botanix": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -9811,7 +9553,7 @@ "isProxy": true } ], - "opbnb": [ + "katana": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -9897,7 +9639,7 @@ "isProxy": true } ], - "coti": [ + "tac": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -9912,605 +9654,127 @@ }, { "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdMultisigIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", + "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationIsmFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", + "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationHookFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationHook", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", "constructorArguments": "", "isProxy": true }, { "name": "DomainRoutingIsmFactory", - "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "constructorArguments": "", "isProxy": false }, { "name": "DomainRoutingIsm", - "address": "0xDFF18Bf286c9cDd0fC653a28616460Cf7443F8EF", + "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", "constructorArguments": "", "isProxy": true }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0xfb288565DBa8489e745Fb814584d06331809d16F", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x2f9DB5616fa3fAd1aB06cB2C906830BA63d135e3", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x9B77c7762d7884C0AAd5953502d3b42AEFcdCd45", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", "constructorArguments": "", "isProxy": true } ], - "deepbrainchain": [ + "galactica": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticMessageIdMultisigIsmFactory", "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticAggregationIsmFactory", "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticAggregationIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticAggregationHookFactory", "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticAggregationHook", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", - "constructorArguments": "", - "isProxy": true - }, { "name": "DomainRoutingIsmFactory", "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "constructorArguments": "", "isProxy": false }, - { - "name": "DomainRoutingIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "constructorArguments": "", "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", - "constructorArguments": "", - "isProxy": true - } - ], - "reactive": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsmFactory", - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHookFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationHook", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsmFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "DomainRoutingIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", - "constructorArguments": "", - "isProxy": true - } - ], - "fluence": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsmFactory", - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHookFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationHook", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsmFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "DomainRoutingIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", - "constructorArguments": "", - "isProxy": true - } - ], - "game7": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsmFactory", - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHookFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationHook", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsmFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "DomainRoutingIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", - "constructorArguments": "", - "isProxy": true - } - ], - "peaq": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsmFactory", - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHookFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationHook", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsmFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "DomainRoutingIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", - "constructorArguments": "", - "isProxy": true } ], - "hashkey": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsmFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationIsm", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHookFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationHook", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsmFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "DomainRoutingIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", - "constructorArguments": "", - "isProxy": true - } - ], - "infinityvmmainnet": [ + "xrplevm": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", - "constructorArguments": "", - "isProxy": true + "isProxy": false }, { "name": "StaticMessageIdMultisigIsmFactory", @@ -10518,74 +9782,38 @@ "constructorArguments": "", "isProxy": false }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticAggregationIsmFactory", "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticAggregationIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticAggregationHookFactory", "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticAggregationHook", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", - "constructorArguments": "", - "isProxy": true - }, { "name": "DomainRoutingIsmFactory", "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "constructorArguments": "", "isProxy": false }, - { - "name": "DomainRoutingIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "constructorArguments": "", "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", - "constructorArguments": "", - "isProxy": true } ], - "miraclechain": [ + "mitosis": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -10671,93 +9899,51 @@ "isProxy": true } ], - "ontology": [ + "pulsechain": [ { "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "address": "0x7aed6B857796806C890D969B40325F5bc87e0313", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "address": "0x55e1e72051872F571C2B9e7e738276F59ffe3148", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticAggregationIsmFactory", - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "address": "0xD6ba15FF7191b4b823d4e212EDA4530a76506D46", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticAggregationIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticAggregationHookFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "address": "0x651178ADa5378d82401D2E6543fc00f8CDcf3aaf", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticAggregationHook", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", - "constructorArguments": "", - "isProxy": true - }, { "name": "DomainRoutingIsmFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "address": "0x175113a9E1B81DB97482edF427C1Ac079eFD9b2a", "constructorArguments": "", "isProxy": false }, - { - "name": "DomainRoutingIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "address": "0x99652737deCa1924F9D267bD7eFE784f28E282C8", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "address": "0xD9B8aCD833452ab6021290719CeF5759DDbF2E53", "constructorArguments": "", "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", - "constructorArguments": "", - "isProxy": true } ], - "botanix": [ + "plasma": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", @@ -10812,254 +9998,200 @@ "constructorArguments": "", "isProxy": false }, - { - "name": "DomainRoutingIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "constructorArguments": "", "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", - "constructorArguments": "", - "isProxy": true } ], - "katana": [ + "electroneum": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticMessageIdMultisigIsmFactory", "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticAggregationIsmFactory", "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticAggregationIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticAggregationHookFactory", "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticAggregationHook", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", - "constructorArguments": "", - "isProxy": true - }, { "name": "DomainRoutingIsmFactory", "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "constructorArguments": "", "isProxy": false }, - { - "name": "DomainRoutingIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "constructorArguments": "", "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", - "constructorArguments": "", - "isProxy": true } ], - "tac": [ + "sova": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x4725F7b8037513915aAf6D6CBDE2920E28540dDc", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticMessageIdMultisigIsmFactory", "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0xAF03386044373E2fe26C5b1dCedF5a7e854a7a3F", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticAggregationIsmFactory", "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticAggregationIsm", - "address": "0x882CD0C5D50b6dD74b36Da4BDb059507fddEDdf2", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticAggregationHookFactory", "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticAggregationHook", - "address": "0x19930232E9aFC4f4F09d09fe2375680fAc2100D0", - "constructorArguments": "", - "isProxy": true - }, { "name": "DomainRoutingIsmFactory", "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", "constructorArguments": "", "isProxy": false }, - { - "name": "DomainRoutingIsm", - "address": "0x12Ed1BbA182CbC63692F813651BD493B7445C874", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "constructorArguments": "", "isProxy": false }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", - "constructorArguments": "", - "isProxy": true - }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "constructorArguments": "", "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", - "constructorArguments": "", - "isProxy": true } ], - "galactica": [ + "zerogravity": [ { "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", + "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", + "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationIsmFactory", - "address": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", + "address": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationHookFactory", - "address": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", + "address": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0x3b9f24fD2ecfed0d3A88fa7f0E4e5747671981D7", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticMessageIdMultisigIsm", + "address": "0x71DCcD21B912F7d4f636af0C9eA5DC0C10617354", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationIsm", + "address": "0x7f51A658837A315134A97ff8B586d71B726B7e61", + "constructorArguments": "", + "isProxy": true + }, + { + "name": "StaticAggregationHookFactory", + "address": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "constructorArguments": "", "isProxy": false }, + { + "name": "StaticAggregationHook", + "address": "0x3a49EcAC1031612D66fa20D6F40f214aCeAc2B4B", + "constructorArguments": "", + "isProxy": true + }, { "name": "DomainRoutingIsmFactory", - "address": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", + "address": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", "constructorArguments": "", "isProxy": false }, + { + "name": "DomainRoutingIsm", + "address": "0xfb288565DBa8489e745Fb814584d06331809d16F", + "constructorArguments": "", + "isProxy": true + }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", + "address": "0x7f50C5776722630a0024fAE05fDe8b47571D7B39", "constructorArguments": "", "isProxy": false }, + { + "name": "StaticMerkleRootWeightedMultisigIsm", + "address": "0x7EAF8D95DaB8A9615D100fa1D8EB8469A5d549A4", + "constructorArguments": "", + "isProxy": true + }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", + "address": "0x2f9DB5616fa3fAd1aB06cB2C906830BA63d135e3", "constructorArguments": "", "isProxy": false + }, + { + "name": "StaticMessageIdWeightedMultisigIsm", + "address": "0x9B77c7762d7884C0AAd5953502d3b42AEFcdCd45", + "constructorArguments": "", + "isProxy": true } ], - "xrplevm": [ + "mantra": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", diff --git a/typescript/infra/config/environments/mainnet3/middleware/accounts/verification.json b/typescript/infra/config/environments/mainnet3/middleware/accounts/verification.json index ca0d0be6c65..7faa45e5608 100644 --- a/typescript/infra/config/environments/mainnet3/middleware/accounts/verification.json +++ b/typescript/infra/config/environments/mainnet3/middleware/accounts/verification.json @@ -24,6 +24,12 @@ "expectedimplementation": "0x30a539E2E2d09FB4e68661B1EDD70D266211602a", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0x23C6D8145DDb71a24965AAAdf4CA4B095b4eC85F", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "arbitrum": [ @@ -45,6 +51,12 @@ "expectedimplementation": "0xCd2858B6bCaA9b628EBc4892F578b7d37E9ec229", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0xF90A3d406C6F8321fe118861A357F4D7107760D7", + "constructorArguments": "000000000000000000000000979ca5202784112f4738403dbec5d0f3b9daabb90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "avalanche": [ @@ -66,6 +78,12 @@ "expectedimplementation": "0xe24F3b6244Bb442DFe5DcdD4451D3D5d04481200", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0x2c58687fFfCD5b7043a5bF256B196216a98a6587", + "constructorArguments": "000000000000000000000000ff06afcaabaddd1fb08371f9cca15d73d51febd60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "base": [ @@ -87,6 +105,12 @@ "expectedimplementation": "0x5ed29F0f32636CC69b0c189D5ec82C09DE7Cb0a7", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0x44647Cd983E80558793780f9a0c7C2aa9F384D07", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "blast": [ @@ -108,6 +132,12 @@ "expectedimplementation": "0x8d9Bd7E9ec3cd799a659EE650DfF6C799309fA91", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0x7d58D7F052792e54eeEe91B2467c2A17a163227e", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "bob": [ @@ -129,6 +159,12 @@ "expectedimplementation": "0x9534122Aae7978dB8f5f10dF4432233c53e820A1", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0xA6f0A37DFDe9C2c8F46F010989C47d9edB3a9FA8", + "constructorArguments": "0000000000000000000000008358d8291e3bedb04804975eea0fe9fe0fafb1470000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "bsc": [ @@ -150,6 +186,12 @@ "expectedimplementation": "0x798f88c240f33d1204fe8a24520F2d0809f873B9", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0xf453B589F0166b90e050691EAc281C01a8959897", + "constructorArguments": "0000000000000000000000002971b9aec44be4eb673df1b88cdb57b96eefe8a40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "celo": [ @@ -171,6 +213,12 @@ "expectedimplementation": "0x9fd96E2f1D3Dd2240d07baaEbAF050392127C658", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0x1eA7aC243c398671194B7e2C51d76d1a1D312953", + "constructorArguments": "00000000000000000000000050da3b3907a08a24fe4999f4dcf337e8dc7954bb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "cheesechain": [ @@ -192,6 +240,12 @@ "expectedimplementation": "0x46fa3A5780e5B90Eaf34BDED554d5353B5ABE9E7", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0xBF4f96006d98780120f950b36505bC317aC6b6a4", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "endurance": [ @@ -213,6 +267,12 @@ "expectedimplementation": "0xcAf034CE568fad5B85Cfbf088FF43974C39287cC", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0xB5CA647Fd7b28cb8Ec80144f2C6BAEE2Dfc12E03", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "ethereum": [ @@ -234,6 +294,12 @@ "expectedimplementation": "0x5Cf82c727380bda6560E0Ffb1aD6FF077aE77248", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0xC00b94c115742f711a6F9EA90373c33e9B72A4A9", + "constructorArguments": "000000000000000000000000c005dc82818d67af737725bd4bf75435d065d2390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "fraxtal": [ @@ -255,6 +321,12 @@ "expectedimplementation": "0xc441521bA37EaCd9af4f319CcdA27E9D48f74281", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0xD59a200cCEc5b3b1bF544dD7439De452D718f594", + "constructorArguments": "0000000000000000000000002f9db5616fa3fad1ab06cb2c906830ba63d135e30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "fusemainnet": [ @@ -276,6 +348,12 @@ "expectedimplementation": "0x8F23872dAb3B166cef411EeB6C391Ff6Ce419532", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0xc57fe3d144434d0aBaF8D3698E3103a4ddFD777A", + "constructorArguments": "0000000000000000000000003071d4da6020c956fe15bfd0a9ca8d4574f166960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "gnosis": [ @@ -297,6 +375,12 @@ "expectedimplementation": "0xdE09b0BBbf93effa5A7C959f8De401A92AB6A93e", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0xef0Adeb4103A7A1AcE86371867202f2171126362", + "constructorArguments": "000000000000000000000000ad09d78f4c6b9da2ae82b1d34107802d380bb74f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "inevm": [ @@ -318,6 +402,12 @@ "expectedimplementation": "0xe147ce60b9B15dCAd9bf7e79F0A8fA0Bae199416", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0xd2884F7042d0a819F5276A69770F70d9C89Bf823", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "linea": [ @@ -339,6 +429,12 @@ "expectedimplementation": "0x58186a1870B1A1B458332011C8F7AF23A678D7B8", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0xBfC8DCEf3eFabC064f5afff4Ac875a82D2Dc9E55", + "constructorArguments": "00000000000000000000000002d16bc51af6bfd153d67ca61754cf912e82c4d90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "mantapacific": [ @@ -360,6 +456,12 @@ "expectedimplementation": "0x06De94EfBE80A2804c5FDE2e7C4278a10575A272", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0x620ffeEB3359649dbE48278d3Cffd00CC36976EA", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "mantle": [ @@ -381,6 +483,12 @@ "expectedimplementation": "0xd64d126941EaC2Cf53e0E4E8146cC70449b60D73", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0x31e81982E98F5D321F839E82789b628AedB15751", + "constructorArguments": "000000000000000000000000398633d19f4371e1db5a8efe90468eb70b1176aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "mode": [ @@ -402,6 +510,12 @@ "expectedimplementation": "0x82540c4C1C6956FC4815E583DDc6d88A782E0F3e", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0x860ec58b115930EcbC53EDb8585C1B16AFFF3c50", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "moonbeam": [ @@ -423,6 +537,12 @@ "expectedimplementation": "0x4e2B1FcC07a8E80736c80Bcf53753e8d0b8042f1", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0x24b900De85479A586aC8568b471AAC1CEeD6370c", + "constructorArguments": "000000000000000000000000094d03e751f49908080eff000dd6fd177fd44cc30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "optimism": [ @@ -444,6 +564,12 @@ "expectedimplementation": "0x0BC8e181c04428301309DD1abfF804C0ecF6B5A8", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0x3E343D07D024E657ECF1f8Ae8bb7a12f08652E75", + "constructorArguments": "000000000000000000000000d4c1905bb1d26bc93dac913e13cacc278cdcc80d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "polygon": [ @@ -465,6 +591,12 @@ "expectedimplementation": "0x4c80eB0C760197C805d01be6A2c30F7Cf12599E9", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0xd8B641FEb587844854aeC97544ccEA426DFF04a3", + "constructorArguments": "0000000000000000000000005d934f4e2f797775e53561bb72aca21ba36b96bb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "polygonzkevm": [ @@ -486,6 +618,12 @@ "expectedimplementation": "0x8Ea50255C282F89d1A14ad3F159437EE5EF0507f", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0x0b6C22e18fDcA681049A7ce003372DFfb3C71214", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "redstone": [ @@ -507,6 +645,12 @@ "expectedimplementation": "0x45285463352c53a481e882cD5E2AF2E25BBdAd0D", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0x27e88AeB8EA4B159d81df06355Ea3d20bEB1de38", + "constructorArguments": "000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "scroll": [ @@ -528,6 +672,12 @@ "expectedimplementation": "0xfF4872B62225c1f029a894D4682b250dD5577AC7", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0x7E4a3CdF715650A2EF407C86186ec8Fd2d1fb46c", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "sei": [ @@ -549,6 +699,12 @@ "expectedimplementation": "0x49530E07DF0628ef2DccFc2f13dF371db3007d2b", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0xA70482D7359816809988AC4053d83F0C8C98D292", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "taiko": [ @@ -570,6 +726,12 @@ "expectedimplementation": "0x10d8B04D5369C8240a8aC555C9a9f658346A7EB3", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0xEE47aD8f6582CDcBF4B8581A1c3482E72E4DeaBf", + "constructorArguments": "00000000000000000000000028efbcada00a7ed6772b3666f3898d276e88cae30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "viction": [ @@ -612,6 +774,12 @@ "expectedimplementation": "0x03cF708E42C89623bd83B281A56935cB562b9258", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0xd55bFDfb3486fE49a0b2E2Af324453452329051F", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "xlayer": [ @@ -633,6 +801,12 @@ "expectedimplementation": "0xEF9A332Ec1fD233Bf9344A58be56ff9E104B4f60", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0x39d3c2Cf646447ee302178EDBe5a15E13B6F33aC", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "zetachain": [ @@ -654,6 +828,12 @@ "expectedimplementation": "0xa3D9cfa4220d6a5ba7b9067D33ebEdbF2DE6F1CF", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0x6783dC9f1Bf88DC554c8716c4C42C5bf640dDcc8", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "zoramainnet": [ @@ -675,6 +855,12 @@ "expectedimplementation": "0xD8aF449f8fEFbA2064863DCE5aC248F8B232635F", "isProxy": true, "name": "TransparentUpgradeableProxy" + }, + { + "name": "InterchainAccountRouter", + "address": "0x9e6B1022bE9BBF5aFd152483DAD9b88911bC8611", + "constructorArguments": "000000000000000000000000f5da68b2577ef5c0a0d98aa2a58483a68c2f232a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "degenchain": [ @@ -696,6 +882,12 @@ "constructorArguments": "0000000000000000000000007b032cbb00ad7438e802a66d8b64761a06e5df220000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d8af449f8fefba2064863dce5ac248f8b232635f000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x7B032cBB00AD7438E802A66D8b64761A06E5df22" + }, + { + "name": "InterchainAccountRouter", + "address": "0xAb65C41a1BC580a52f0b166879122EFdce0cB868", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "astarzkevm": [ @@ -738,6 +930,12 @@ "constructorArguments": "000000000000000000000000b2674e213019972f937ccfc5e23bf963d915809e0000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000067f36550b73b731e5b2fc44e4f8f250d89c87bd6000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xb2674E213019972f937CCFc5e23BF963D915809e" + }, + { + "name": "InterchainAccountRouter", + "address": "0x1B947F6246ACe28abAf073FF11c098F31ce4f899", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "coredao": [ @@ -759,6 +957,12 @@ "constructorArguments": "000000000000000000000000a97ec3e58cbd60199dcfdd6396431be85c2e363e0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000087bdfabbcc36d8b1aeda871cd54b2e86c7a4d597000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xa97ec3E58cBd60199dcFDd6396431BE85c2E363e" + }, + { + "name": "InterchainAccountRouter", + "address": "0xB8736c87da7DEc750fA0226e3bdE1Ac35B88f43d", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "flare": [ @@ -780,6 +984,12 @@ "constructorArguments": "00000000000000000000000047bf94790241b1764fc41a35a8329a15569e121c0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c2da384799488b4e1e773d70a83346529145085b000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x47bf94790241B1764fC41A35a8329A15569E121C" + }, + { + "name": "InterchainAccountRouter", + "address": "0xC1272CCea251c85b7D11eDeD1204a88DEde90f46", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "bitlayer": [ @@ -801,6 +1011,12 @@ "constructorArguments": "000000000000000000000000a35cbc2d169284580d82aeced883d0800aa7fbfc0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a97ec3e58cbd60199dcfdd6396431be85c2e363e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xa35cbc2d169284580d82AecED883d0800aa7fbfC" + }, + { + "name": "InterchainAccountRouter", + "address": "0xE0208ddBe76c703eb3Cd758a76e2c8c1Ff9472fD", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "dogechain": [ @@ -822,6 +1038,12 @@ "constructorArguments": "000000000000000000000000a97ec3e58cbd60199dcfdd6396431be85c2e363e0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000087bdfabbcc36d8b1aeda871cd54b2e86c7a4d597000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xa97ec3E58cBd60199dcFDd6396431BE85c2E363e" + }, + { + "name": "InterchainAccountRouter", + "address": "0xe05f59ec3AE5B475050a735522Def832F602152f", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "molten": [ @@ -843,6 +1065,12 @@ "constructorArguments": "000000000000000000000000a97ec3e58cbd60199dcfdd6396431be85c2e363e0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000087bdfabbcc36d8b1aeda871cd54b2e86c7a4d597000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xa97ec3E58cBd60199dcFDd6396431BE85c2E363e" + }, + { + "name": "InterchainAccountRouter", + "address": "0xCf42106b85fC72c43Ac4976f20fA2aD7D9592c31", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "astar": [ @@ -864,6 +1092,12 @@ "constructorArguments": "0000000000000000000000007621e04860f0bde63311db9d5d8b589ad3458a1f0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d01a3e167d59ff98c983e83baa5da0c3e0ade726000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x7621e04860F0bDe63311db9D5D8b589AD3458A1f" + }, + { + "name": "InterchainAccountRouter", + "address": "0xC3d9e724c6Bf3c4456EB8572Be05AA52f8acC9Ae", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "metis": [ @@ -885,6 +1119,12 @@ "constructorArguments": "0000000000000000000000007a4d31a686a36285d68e14edd53631417eb196030000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a4f09a615aa4a35e5a146dc2fa19975bebf21a5000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x7a4d31a686A36285d68e14EDD53631417eB19603" + }, + { + "name": "InterchainAccountRouter", + "address": "0x04Bd82Ba84a165BE5D555549ebB9890Bb327336E", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "proofofplay": [ @@ -906,27 +1146,12 @@ "constructorArguments": "0000000000000000000000007a4d31a686a36285d68e14edd53631417eb196030000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a4f09a615aa4a35e5a146dc2fa19975bebf21a5000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x7a4d31a686A36285d68e14EDD53631417eB19603" - } - ], - "sanko": [ - { - "name": "InterchainAccountIsm", - "address": "0x5DA60220C5dDe35b7aE91c042ff5979047FA0785", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", - "isProxy": false }, { "name": "InterchainAccountRouter", - "address": "0x45285463352c53a481e882cD5E2AF2E25BBdAd0D", - "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a7", + "address": "0xbED975874FD8b5Be7358988Bb856d9FD77C2Df01", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x7a4d31a686A36285d68e14EDD53631417eB19603", - "constructorArguments": "00000000000000000000000045285463352c53a481e882cd5e2af2e25bbdad0d0000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000005da60220c5dde35b7ae91c042ff5979047fa0785000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x45285463352c53a481e882cD5E2AF2E25BBdAd0D" } ], "mint": [ @@ -948,6 +1173,12 @@ "constructorArguments": "0000000000000000000000007a4d31a686a36285d68e14edd53631417eb196030000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a4f09a615aa4a35e5a146dc2fa19975bebf21a5000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x7a4d31a686A36285d68e14EDD53631417eB19603" + }, + { + "name": "InterchainAccountRouter", + "address": "0x511C21cF98AB0D07a6fB9Fb65E9e66DD483375B5", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "merlin": [ @@ -969,6 +1200,12 @@ "constructorArguments": "0000000000000000000000001c6f404800ba49ed581af734ea0d25c0c7d017b20000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a8a311b69f688c1d9928259d872c31ca0d473642000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x1c6f404800bA49Ed581af734eA0d25c0c7d017B2" + }, + { + "name": "InterchainAccountRouter", + "address": "0xFCfE7344d7a769C89B3A22c596fE83a1bF8458Da", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "lisk": [ @@ -990,6 +1227,12 @@ "constructorArguments": "0000000000000000000000007b032cbb00ad7438e802a66d8b64761a06e5df220000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d8af449f8fefba2064863dce5ac248f8b232635f000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x7B032cBB00AD7438E802A66D8b64761A06E5df22" + }, + { + "name": "InterchainAccountRouter", + "address": "0xE59592a179c4f436d5d2e4caA6e2750beA4E3166", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "lukso": [ @@ -1011,6 +1254,12 @@ "constructorArguments": "0000000000000000000000005da60220c5dde35b7ae91c042ff5979047fa07850000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d64d126941eac2cf53e0e4e8146cc70449b60d73000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x5DA60220C5dDe35b7aE91c042ff5979047FA0785" + }, + { + "name": "InterchainAccountRouter", + "address": "0x7e0956bfEE5C4dEAd8Ced283C934299998100362", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "xai": [ @@ -1032,6 +1281,12 @@ "constructorArguments": "0000000000000000000000007b032cbb00ad7438e802a66d8b64761a06e5df220000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d8af449f8fefba2064863dce5ac248f8b232635f000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x7B032cBB00AD7438E802A66D8b64761A06E5df22" + }, + { + "name": "InterchainAccountRouter", + "address": "0xA8aB763aB8ab133236afc7b31aFC606F268048f5", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "zircuit": [ @@ -1053,6 +1308,12 @@ "constructorArguments": "00000000000000000000000025c87e735021f72d8728438c2130b02e3141f2cb000000000000000000000000a5580d7af50f3fd869ebea51e352e2656f8dd5c200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d386bb418b61e296e1689c95afe94a2e321a6ead000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x25C87e735021F72d8728438C2130b02E3141f2cb" + }, + { + "name": "InterchainAccountRouter", + "address": "0xf20414268e76d0e943533aFa1F2b99DBfb4e0F71", + "constructorArguments": "000000000000000000000000c2fbb9411186ab3b1a6afcca702d1a80b48b197c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "tangle": [ @@ -1074,6 +1335,12 @@ "constructorArguments": "0000000000000000000000001a4f09a615aa4a35e5a146dc2fa19975bebf21a50000000000000000000000000761b0827849abbf7b0cc09ce14e1c93d87f500400000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045285463352c53a481e882cd5e2af2e25bbdad0d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x1A4F09A615aA4a35E5a146DC2fa19975bebF21A5" + }, + { + "name": "InterchainAccountRouter", + "address": "0xf63Eb8e72Cbc59eC1185620c44050aF266d3eC19", + "constructorArguments": "0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "shibarium": [ @@ -1095,6 +1362,12 @@ "constructorArguments": "000000000000000000000000f1854214392864c628a16930e73b699f7a51b3ee0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0a44cb8bc0f7ede788b0cd29524a5b14fed7b45000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xF1854214392864c628A16930E73B699f7a51b3EE" + }, + { + "name": "InterchainAccountRouter", + "address": "0xa4fc7C90a4D4ae2A11637D04A6c5286E00B4bAA0", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "everclear": [ @@ -1116,6 +1389,12 @@ "constructorArguments": "00000000000000000000000058556aaeb2e3829d52ee5e711d44735412efa43b0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd9d3744512f07ae844c40e27912092d7c503565000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x58556AaeB2e3829d52EE5E711D44735412efA43B" + }, + { + "name": "InterchainAccountRouter", + "address": "0x6FD739221F53F8dc1565F3aF830Cb687cfe5932D", + "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "oortmainnet": [ @@ -1156,46 +1435,12 @@ "constructorArguments": "000000000000000000000000376ad181e8cd45ead5403f78d5a871d08c3c4d77000000000000000000000000148cf67b8a242c1360bb2c93fce203ec4d4f9b5600000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c23baf5eb5848d19701bbe7f139645e6bd58a319000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x376aD181E8cd45eAd5403F78d5A871D08c3c4D77" - } - ], - "lumia": [ - { - "name": "InterchainAccountIsm", - "address": "0xD569fb1753167312ec5b78526743F2Bea027E5d8", - "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0x504236Da6344e5E144def5653C2b1d0fFd18cB7d", - "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xC9c1A8E0d7A389ff4E3A5ab1C3F9555c50BaD325", - "constructorArguments": "000000000000000000000000504236da6344e5e144def5653c2b1d0ffd18cb7d000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d569fb1753167312ec5b78526743f2bea027e5d8000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x504236Da6344e5E144def5653C2b1d0fFd18cB7d" - }, - { - "name": "InterchainAccountIsm", - "address": "0x25EAC2007b0D40E3f0AF112FD346412321038719", - "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", - "isProxy": false }, { "name": "InterchainAccountRouter", - "address": "0x0F9d4704E1Fb25e416042524e594F1cEac6fF597", - "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa7", + "address": "0x6c3b61e60Ff510E35Ba51D25bb2E0F90B0307E7D", + "constructorArguments": "000000000000000000000000b129828b9eda48192d0b2db35d0e40dcf51b35940000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", - "constructorArguments": "0000000000000000000000000f9d4704e1fb25e416042524e594f1ceac6ff597000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025eac2007b0d40e3f0af112fd346412321038719000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x0F9d4704E1Fb25e416042524e594F1cEac6fF597" } ], "gravity": [ @@ -1217,6 +1462,12 @@ "constructorArguments": "000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000783ec5e105234a570eb90f314284e5dbe53bdd90000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xFB9e40D811Cea562cc8a322b029eF2BDcC3ef6ed" + }, + { + "name": "InterchainAccountRouter", + "address": "0x335593971F655220a760837b64fbeABd09dE6dD9", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "apechain": [ @@ -1238,6 +1489,12 @@ "constructorArguments": "000000000000000000000000c5d6acaafbccec6d7fd7d92f4509befce641c5630000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xc5D6aCaafBCcEC6D7fD7d92F4509befce641c563" + }, + { + "name": "InterchainAccountRouter", + "address": "0xb347c2cbfc32e0bdf365183635352e0C38c97147", + "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "arbitrumnova": [ @@ -1259,6 +1516,12 @@ "constructorArguments": "0000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1" + }, + { + "name": "InterchainAccountRouter", + "address": "0xf2F83b26d56f0e9B9Bd81efAb9e0ECB9ba5708be", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "harmony": [ @@ -1280,6 +1543,12 @@ "constructorArguments": "0000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1" + }, + { + "name": "InterchainAccountRouter", + "address": "0xFCfE7344d7a769C89B3A22c596fE83a1bF8458Da", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "kaia": [ @@ -1301,7 +1570,13 @@ "constructorArguments": "0000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1" - } + }, + { + "name": "InterchainAccountRouter", + "address": "0xcfe6dBaD47c3B8cf4fecbb28B53Df4617F8538A7", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false + } ], "b3": [ { @@ -1322,6 +1597,12 @@ "constructorArguments": "0000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1" + }, + { + "name": "InterchainAccountRouter", + "address": "0x0fC7b3518C03BfA5e01995285b1eF3c4B55c8922", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "orderly": [ @@ -1343,6 +1624,12 @@ "constructorArguments": "0000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1" + }, + { + "name": "InterchainAccountRouter", + "address": "0x9121E58Cb02890cEEF1a21EF4B80420eC2b8B61C", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "snaxchain": [ @@ -1364,6 +1651,12 @@ "constructorArguments": "0000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1" + }, + { + "name": "InterchainAccountRouter", + "address": "0x9121E58Cb02890cEEF1a21EF4B80420eC2b8B61C", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "fantom": [ @@ -1385,6 +1678,12 @@ "constructorArguments": "0000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1" + }, + { + "name": "InterchainAccountRouter", + "address": "0x01016c0A5118dBD87E34a50fF1a5D8D9306aAa2e", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "morph": [ @@ -1406,6 +1705,12 @@ "constructorArguments": "0000000000000000000000006119b76720ccfeb3d256ec1b91218eeffd6756e1000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb9e40d811cea562cc8a322b029ef2bdcc3ef6ed000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x6119B76720CcfeB3D256EC1b91218EEfFD6756E1" + }, + { + "name": "InterchainAccountRouter", + "address": "0x36E437699E3658396Bf6229ddDaE54884cf28779", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "alephzeroevmmainnet": [ @@ -1427,6 +1732,12 @@ "constructorArguments": "00000000000000000000000016625230dd6cfe1b2bec3ecaec7d43ba3a902cd6000000000000000000000000730f8a4128fa8c53c777b62baa1abef94cad34a900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000004d264424905535e97396db83bd553d0d73a4ef9d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x16625230dD6cFe1B2bec3eCaEc7d43bA3A902CD6" + }, + { + "name": "InterchainAccountRouter", + "address": "0xAab1D11E2063Bae5EB01fa946cA8d2FDe3db05D5", + "constructorArguments": "0000000000000000000000005bdadead721eb4c4038ff7c989e3c7bbba3024350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "superpositionmainnet": [ @@ -1448,6 +1759,12 @@ "constructorArguments": "000000000000000000000000262076f0f90a9a49b1b4eca88eda62ff30c46d94000000000000000000000000248ade14c0489e20c9a7fea5f86dbfc3702208ef00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000008a733038ef4bbc314ee0f7595257d8d3799b6aa9000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x262076f0f90A9a49b1b4ECa88EDa62fF30C46D94" + }, + { + "name": "InterchainAccountRouter", + "address": "0x02b833b8a0fB7680e2d233176B54538c81505131", + "constructorArguments": "0000000000000000000000005e8a0fcc0d1df583322943e01f02cb243e5300f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "rarichain": [ @@ -1469,6 +1786,12 @@ "constructorArguments": "0000000000000000000000008ec5f0239c77295452ed899fdb851e785ca5fc310000000000000000000000004ee9debb3046139661b51e17bdfd54fd63211de700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f40ee9ff75fa34910b7c4c8d68d4850b3bd184d3000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x8eC5f0239C77295452Ed899FDB851e785cA5FC31" + }, + { + "name": "InterchainAccountRouter", + "address": "0xecA217aB573506eaB6E51CDD1c3a84B626CDf7b4", + "constructorArguments": "00000000000000000000000065dcf8f6b3f6a0ecedf3d0bdcb036aea47a1d6150000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "immutablezkevmmainnet": [ @@ -1490,6 +1813,12 @@ "constructorArguments": "000000000000000000000000dde46032baf4da13fdd79bf9dfbaa2749615c4090000000000000000000000002f0e57527bb37e5e064ef243fad56cce6241906c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000545e289b88c6d97b74ec0b96e308cae46bf5f832000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xDDE46032Baf4da13fDD79BF9dfbaA2749615C409" + }, + { + "name": "InterchainAccountRouter", + "address": "0xE2cBbc708411eAf2DfbaA31DaA531d4FF089d7b0", + "constructorArguments": "0000000000000000000000001c6f404800ba49ed581af734ea0d25c0c7d017b20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "metal": [ @@ -1511,6 +1840,12 @@ "constructorArguments": "00000000000000000000000001eba6d613dc09cb899af1e8e8a747416d7250ad0000000000000000000000007c012dca02c42cfa3fd7da3b0ed7234b52ae68ef00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000008c794a781327b819416e7b67908f1d22397f1e67000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x01EBa6D613DC09Cb899aF1e8E8a747416d7250ad" + }, + { + "name": "InterchainAccountRouter", + "address": "0x0b2d429acccAA411b867d57703F88Ed208eC35E4", + "constructorArguments": "000000000000000000000000730f8a4128fa8c53c777b62baa1abef94cad34a90000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "polynomialfi": [ @@ -1532,6 +1867,12 @@ "constructorArguments": "00000000000000000000000026a29486480bd74f9b830a9b8db33cb43c40f4960000000000000000000000005bdadead721eb4c4038ff7c989e3c7bbba30243500000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e67dc24970b482579923551ede52bd35a2858989000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x26A29486480BD74f9B830a9B8dB33cb43C40f496" + }, + { + "name": "InterchainAccountRouter", + "address": "0x1B947F6246ACe28abAf073FF11c098F31ce4f899", + "constructorArguments": "0000000000000000000000002f0e57527bb37e5e064ef243fad56cce6241906c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "prom": [ @@ -1553,27 +1894,12 @@ "constructorArguments": "000000000000000000000000693a4ce39d99e46b04cb562329e3f0141ca173310000000000000000000000009534122aae7978db8f5f10df4432233c53e820a100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020a0a32a110362920597f72974e1e0d7e25ca20a000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x693A4cE39d99e46B04cb562329e3F0141cA17331" - } - ], - "flame": [ - { - "name": "InterchainAccountIsm", - "address": "0x60bB6D060393D3C206719A7bD61844cC82891cfB", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false }, { "name": "InterchainAccountRouter", - "address": "0x83475ca5bEB2Eaa59A2FF48a0544ebaa4a32c2de", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0xd5D8c2d9A3F4974E89396928FEb7829d9C5e0788", + "constructorArguments": "0000000000000000000000005c02157068a52cecfc98edb6115de6134ecb47640000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x9534122Aae7978dB8f5f10dF4432233c53e820A1", - "constructorArguments": "00000000000000000000000083475ca5beb2eaa59a2ff48a0544ebaa4a32c2de0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060bb6d060393d3c206719a7bd61844cc82891cfb000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x83475ca5bEB2Eaa59A2FF48a0544ebaa4a32c2de" } ], "chilizmainnet": [ @@ -1595,6 +1921,12 @@ "constructorArguments": "000000000000000000000000dde46032baf4da13fdd79bf9dfbaa2749615c40900000000000000000000000048c427782bc1e9ece406b3e277481b28abcbdf0300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000545e289b88c6d97b74ec0b96e308cae46bf5f832000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xDDE46032Baf4da13fDD79BF9dfbaA2749615C409" + }, + { + "name": "InterchainAccountRouter", + "address": "0x246BBe3983C22553362A42aa4B06320E2fB4E880", + "constructorArguments": "000000000000000000000000248ade14c0489e20c9a7fea5f86dbfc3702208ef0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "flowmainnet": [ @@ -1616,27 +1948,12 @@ "constructorArguments": "000000000000000000000000b5c9f154a6013f2965713a77b0cafd6edbc123c40000000000000000000000005dfcce8da81b542426211c99fccfed647e9aa49600000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cdc31ba959de8c035a03167ebae1961208cdf172000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xb5C9f154a6013f2965713a77B0CAFD6EDBC123C4" - } - ], - "rootstockmainnet": [ - { - "name": "InterchainAccountIsm", - "address": "0xd9Cc2e652A162bb93173d1c44d46cd2c0bbDA59D", - "constructorArguments": "00000000000000000000000096d51cc3f7500d501baeb1a2a62bb96fa03532f8", - "isProxy": false }, { "name": "InterchainAccountRouter", - "address": "0x28846fCb579747E8ddad9E93b55BE51b0A1Bf1f3", - "constructorArguments": "00000000000000000000000096d51cc3f7500d501baeb1a2a62bb96fa03532f8", + "address": "0x1D43Eb638ABF43B4147B7985402a4FfbDd89D4ac", + "constructorArguments": "000000000000000000000000d9cc2e652a162bb93173d1c44d46cd2c0bbda59d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x7279B1e11142078b8dC9e69620200f4C84FB8aaa", - "constructorArguments": "00000000000000000000000028846fcb579747e8ddad9e93b55be51b0a1bf1f3000000000000000000000000e93f2f409ad8b5000431d234472973fe848dcbec00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d9cc2e652a162bb93173d1c44d46cd2c0bbda59d000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x28846fCb579747E8ddad9E93b55BE51b0A1Bf1f3" } ], "superseed": [ @@ -1658,6 +1975,12 @@ "constructorArguments": "0000000000000000000000000f9d4704e1fb25e416042524e594f1ceac6ff5970000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025eac2007b0d40e3f0af112fd346412321038719000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x0F9d4704E1Fb25e416042524e594F1cEac6fF597" + }, + { + "name": "InterchainAccountRouter", + "address": "0x3CA0e8AEfC14F962B13B40c6c4b9CEE3e4927Ae3", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "boba": [ @@ -1679,6 +2002,12 @@ "constructorArguments": "0000000000000000000000000f9d4704e1fb25e416042524e594f1ceac6ff5970000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025eac2007b0d40e3f0af112fd346412321038719000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x0F9d4704E1Fb25e416042524e594F1cEac6fF597" + }, + { + "name": "InterchainAccountRouter", + "address": "0x625324ebE9Fe13fEDD8ac3761F153b90aa35B404", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "unichain": [ @@ -1700,27 +2029,12 @@ "constructorArguments": "0000000000000000000000000f9d4704e1fb25e416042524e594f1ceac6ff5970000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025eac2007b0d40e3f0af112fd346412321038719000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x0F9d4704E1Fb25e416042524e594F1cEac6fF597" - } - ], - "duckchain": [ - { - "name": "InterchainAccountIsm", - "address": "0x25EAC2007b0D40E3f0AF112FD346412321038719", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false }, { "name": "InterchainAccountRouter", - "address": "0x0F9d4704E1Fb25e416042524e594F1cEac6fF597", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0x43320f6B410322Bf5ca326a0DeAaa6a2FC5A021B", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xfF26696DcDb6BbFD27e959b847D4f1399D5BcF64", - "constructorArguments": "0000000000000000000000000f9d4704e1fb25e416042524e594f1ceac6ff5970000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025eac2007b0d40e3f0af112fd346412321038719000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x0F9d4704E1Fb25e416042524e594F1cEac6fF597" } ], "vana": [ @@ -1742,6 +2056,12 @@ "constructorArguments": "0000000000000000000000000f9d4704e1fb25e416042524e594f1ceac6ff5970000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025eac2007b0d40e3f0af112fd346412321038719000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x0F9d4704E1Fb25e416042524e594F1cEac6fF597" + }, + { + "name": "InterchainAccountRouter", + "address": "0x3adf8f4219BdCcd4B727B9dD67E277C58799b57C", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "bsquared": [ @@ -1763,6 +2083,12 @@ "constructorArguments": "0000000000000000000000007ce76f5f0c469bbb4cd7ea6ebabb54437a0931270000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff26696dcdb6bbfd27e959b847d4f1399d5bcf64000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x7CE76f5f0C469bBB4cd7Ea6EbabB54437A093127" + }, + { + "name": "InterchainAccountRouter", + "address": "0x9f4012ba9368FBb95F56c2Fc2D956df803D8779e", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "lumiaprism": [ @@ -1784,6 +2110,12 @@ "constructorArguments": "00000000000000000000000059ee0ae1f527ded5bcb1bf71af027ddc996a50b6000000000000000000000000200183de44bf765ecb73cd62a74010eaabc4314600000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000031bb27f6007c33acd1be83aced3164c60f8f7b13000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x59EE0Ae1f527DEd5BcB1bF71aF027DdC996a50b6" + }, + { + "name": "InterchainAccountRouter", + "address": "0x3C330D4A2e2b8443AFaB8E326E64ab4251B7Eae0", + "constructorArguments": "0000000000000000000000000df25a2d59f03f039b56e90edc5b89679ace28bc0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "swell": [ @@ -1805,6 +2137,12 @@ "constructorArguments": "0000000000000000000000006a77331ce28e47c3cb9fea48ab6cd1e9594ce0a90000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028291a7062afa569104bed52f7acca3dd2fafd11000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x6a77331cE28E47c3Cb9Fea48AB6cD1e9594ce0A9" + }, + { + "name": "InterchainAccountRouter", + "address": "0x95Fb6Ca1BBF441386b119ad097edcAca3b1C35B7", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "appchain": [ @@ -1826,9 +2164,15 @@ "constructorArguments": "000000000000000000000000ae557e108b3336130370ac74836f1356b4b30cf20000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000027efd1695941969435aa640542b690044df7e06000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xAE557e108b3336130370aC74836f1356B4b30Cf2" + }, + { + "name": "InterchainAccountRouter", + "address": "0x24f89395e932961C27167F42DB928Ec92047B695", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], - "conwai": [ + "corn": [ { "name": "InterchainAccountIsm", "address": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", @@ -1847,48 +2191,12 @@ "constructorArguments": "0000000000000000000000003881c3e945cbb89ae67c43e82f570badf1c6ea940000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011b76d93a9d39eb51f54ebf5566308640cde882b000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94" - } - ], - "rivalz": [ - { - "name": "InterchainAccountIsm", - "address": "0xd64d126941EaC2Cf53e0E4E8146cC70449b60D73", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false }, { "name": "InterchainAccountRouter", - "address": "0x5DA60220C5dDe35b7aE91c042ff5979047FA0785", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0x902ca431309D6bD16ce570E0B1F0fe3EF2196222", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x1A4F09A615aA4a35E5a146DC2fa19975bebF21A5", - "constructorArguments": "0000000000000000000000005da60220c5dde35b7ae91c042ff5979047fa07850000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d64d126941eac2cf53e0e4e8146cc70449b60d73000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x5DA60220C5dDe35b7aE91c042ff5979047FA0785" - } - ], - "telos": [ - { - "name": "InterchainAccountIsm", - "address": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xF457D831d9F55e87B2F0b35AD6D033fd6b4181Ed", - "constructorArguments": "00000000000000000000000093d41e41ca545a35a81d11b08d2ee8b852c768df0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011b76d93a9d39eb51f54ebf5566308640cde882b000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df" } ], "sonic": [ @@ -1910,6 +2218,12 @@ "constructorArguments": "0000000000000000000000003881c3e945cbb89ae67c43e82f570badf1c6ea940000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011b76d93a9d39eb51f54ebf5566308640cde882b000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94" + }, + { + "name": "InterchainAccountRouter", + "address": "0xEfad3f079048bE2765b6bCfAa3E9d99e9A2C3Df6", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "soneium": [ @@ -1931,27 +2245,12 @@ "constructorArguments": "0000000000000000000000003881c3e945cbb89ae67c43e82f570badf1c6ea940000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011b76d93a9d39eb51f54ebf5566308640cde882b000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94" - } - ], - "evmos": [ - { - "name": "InterchainAccountIsm", - "address": "0x11b76D93a9D39Eb51F54eBf5566308640cDe882b", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false }, { "name": "InterchainAccountRouter", - "address": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0xc08C1451979e9958458dA3387E92c9Feb1571f9C", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df", - "constructorArguments": "0000000000000000000000003881c3e945cbb89ae67c43e82f570badf1c6ea940000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011b76d93a9d39eb51f54ebf5566308640cde882b000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94" } ], "form": [ @@ -1973,6 +2272,12 @@ "constructorArguments": "0000000000000000000000003881c3e945cbb89ae67c43e82f570badf1c6ea940000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011b76d93a9d39eb51f54ebf5566308640cde882b000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x3881c3e945CBB89ae67c43E82f570baDF1c6EA94" + }, + { + "name": "InterchainAccountRouter", + "address": "0xab6Ce17c7E323A8962E1BD445097D07C5693fF98", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "aurora": [ @@ -1994,27 +2299,12 @@ "constructorArguments": "000000000000000000000000c2466492c451e1ae49d8c874bb9f89293aaad59b0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f457d831d9f55e87b2f0b35ad6d033fd6b4181ed000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xc2466492C451E1AE49d8C874bB9f89293Aaad59b" - } - ], - "conflux": [ - { - "name": "InterchainAccountIsm", - "address": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false }, { "name": "InterchainAccountRouter", - "address": "0x028B04386031b9648A8D78d06c58F6E763Be5cD0", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0x0FdAa7296D06bB5E2365c25b2bF3BB8f188Ecf4F", + "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xc2466492C451E1AE49d8C874bB9f89293Aaad59b", - "constructorArguments": "000000000000000000000000028b04386031b9648a8d78d06c58f6e763be5cd00000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000093d41e41ca545a35a81d11b08d2ee8b852c768df000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x028B04386031b9648A8D78d06c58F6E763Be5cD0" } ], "ink": [ @@ -2036,6 +2326,12 @@ "constructorArguments": "00000000000000000000000093d41e41ca545a35a81d11b08d2ee8b852c768df0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060515f328b2c55df63f456d9d839a0082892def8000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x93D41E41cA545a35A81d11b08D2eE8b852C768df" + }, + { + "name": "InterchainAccountRouter", + "address": "0x55Ba00F1Bac2a47e0A73584d7c900087642F9aE3", + "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "torus": [ @@ -2057,6 +2353,12 @@ "constructorArguments": "0000000000000000000000005da60220c5dde35b7ae91c042ff5979047fa07850000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d64d126941eac2cf53e0e4e8146cc70449b60d73000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x5DA60220C5dDe35b7aE91c042ff5979047FA0785" + }, + { + "name": "InterchainAccountRouter", + "address": "0xCCceDFFAA987F47D0D3A26430c3d3f3270fE6369", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "artela": [ @@ -2078,48 +2380,12 @@ "constructorArguments": "000000000000000000000000b201817dfdd822b75fa9b595457e6ee466a7c1870000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000002351fbe24c1212f253b7a300ff0cbcfd97952a19000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xb201817dFdd822B75Fa9b595457E6Ee466a7C187" - } - ], - "nero": [ - { - "name": "InterchainAccountIsm", - "address": "0x9629c28990F11c31735765A6FD59E1E1bC197DbD", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0x8F23872dAb3B166cef411EeB6C391Ff6Ce419532", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x2351FBe24C1212F253b7a300ff0cBCFd97952a19", - "constructorArguments": "0000000000000000000000008f23872dab3b166cef411eeb6c391ff6ce4195320000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000009629c28990f11c31735765a6fd59e1e1bc197dbd000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x8F23872dAb3B166cef411EeB6C391Ff6Ce419532" - } - ], - "xpla": [ - { - "name": "InterchainAccountIsm", - "address": "0xE350143242a2F7962F23D71ee9Dd98f6e86D1772", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false }, { "name": "InterchainAccountRouter", - "address": "0xa8a750e0Ac90B3f6C05b11F3C0D7D5372cD1a90e", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0x3A220676CFD4e21726cbF20E8F5df4F138364f69", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x5B24EE24049582fF74c1d311d72c70bA5B76a554", - "constructorArguments": "000000000000000000000000a8a750e0ac90b3f6c05b11f3c0d7d5372cd1a90e0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e350143242a2f7962f23d71ee9dd98f6e86d1772000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xa8a750e0Ac90B3f6C05b11F3C0D7D5372cD1a90e" } ], "hemi": [ @@ -2141,6 +2407,12 @@ "constructorArguments": "0000000000000000000000002351fbe24c1212f253b7a300ff0cbcfd97952a190000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000007937cb2886f01f38210506491a69b0d107ea0ad9000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x2351FBe24C1212F253b7a300ff0cBCFd97952a19" + }, + { + "name": "InterchainAccountRouter", + "address": "0x1604d2D3DaFba7D302F86BD7e79B3931414E4625", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "matchain": [ @@ -2162,27 +2434,12 @@ "constructorArguments": "000000000000000000000000e350143242a2f7962f23d71ee9dd98f6e86d17720000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d84981ecd4c411a86e1ccda77f944c8f3d9737ab000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xE350143242a2F7962F23D71ee9Dd98f6e86D1772" - } - ], - "unitzero": [ - { - "name": "InterchainAccountIsm", - "address": "0xD84981Ecd4c411A86E1Ccda77F944c8f3D9737ab", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false }, { "name": "InterchainAccountRouter", - "address": "0xE350143242a2F7962F23D71ee9Dd98f6e86D1772", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0xcb98BD947B58445Fc4815f10285F44De42129918", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xF16E63B42Df7f2676B373979120BBf7e6298F473", - "constructorArguments": "000000000000000000000000e350143242a2f7962f23d71ee9dd98f6e86d17720000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d84981ecd4c411a86e1ccda77f944c8f3d9737ab000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xE350143242a2F7962F23D71ee9Dd98f6e86D1772" } ], "berachain": [ @@ -2204,6 +2461,12 @@ "constructorArguments": "000000000000000000000000d386bb418b61e296e1689c95afe94a2e321a6ead0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030a539e2e2d09fb4e68661b1edd70d266211602a000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xd386Bb418B61E296e1689C95AfE94A2E321a6eaD" + }, + { + "name": "InterchainAccountRouter", + "address": "0x84Fcd67D2B723416e2aFDd61484BD19bd9C32f27", + "constructorArguments": "0000000000000000000000007f50c5776722630a0024fae05fde8b47571d7b390000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "story": [ @@ -2225,6 +2488,12 @@ "constructorArguments": "000000000000000000000000df4aa3905e0391c7763e33cb6a08ffa97221d49b0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cdd89f19b2d00dcb9510bb3fbd5ececa761fe5ab000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xdf4aA3905e0391C7763e33CB6A08fFa97221D49B" + }, + { + "name": "InterchainAccountRouter", + "address": "0x4ef363Da5bb09CC6aeA16973786963d0C8820778", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "ronin": [ @@ -2246,6 +2515,12 @@ "constructorArguments": "000000000000000000000000df4aa3905e0391c7763e33cb6a08ffa97221d49b0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cdd89f19b2d00dcb9510bb3fbd5ececa761fe5ab000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xdf4aA3905e0391C7763e33CB6A08fFa97221D49B" + }, + { + "name": "InterchainAccountRouter", + "address": "0xd6b12ecC223b483427ea66B029b4EEfcC1af86DC", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "arcadia": [ @@ -2267,27 +2542,12 @@ "constructorArguments": "0000000000000000000000008d7e604460e1133ebb91513a6d1024f3a3ca17f90000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c261bd2bd995d3d0026e918cbfd44b0cc5416a57000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x8d7E604460E1133ebB91513a6D1024f3A3ca17F9" - } - ], - "bouncebit": [ - { - "name": "InterchainAccountIsm", - "address": "0xcDD89f19b2d00DCB9510BB3fBd5eCeCa761fe5Ab", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false }, { "name": "InterchainAccountRouter", - "address": "0xdf4aA3905e0391C7763e33CB6A08fFa97221D49B", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0x87ED6926abc9E38b9C7C19f835B41943b622663c", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x7947b7Fe737B4bd1D3387153f32148974066E591", - "constructorArguments": "000000000000000000000000df4aa3905e0391c7763e33cb6a08ffa97221d49b0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cdd89f19b2d00dcb9510bb3fbd5ececa761fe5ab000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xdf4aA3905e0391C7763e33CB6A08fFa97221D49B" } ], "hyperevm": [ @@ -2309,6 +2569,12 @@ "constructorArguments": "0000000000000000000000008d8979f2c29ba49fab259a826d0271c43f70288c0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000be58f200ffca4e1ce4d2f4541e94ae18370fc405000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x8D8979F2C29bA49FAb259A826D0271c43F70288c" + }, + { + "name": "InterchainAccountRouter", + "address": "0x1CF975C9bF2DF76c43a14405066007f8393142E9", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "plume": [ @@ -2330,6 +2596,12 @@ "constructorArguments": "0000000000000000000000007947b7fe737b4bd1d3387153f32148974066e5910000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000072246331d057741008751ab3976a8297ce7267bc000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x7947b7Fe737B4bd1D3387153f32148974066E591" + }, + { + "name": "InterchainAccountRouter", + "address": "0xd9Cc2e652A162bb93173d1c44d46cd2c0bbDA59D", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "infinityvm": [ @@ -2372,27 +2644,12 @@ "constructorArguments": "000000000000000000000000c8826ea18d9884a1a335b2cd7d5f44b159084301000000000000000000000000c343a7054838fe9f249d7e3ec1fa6f1d108694b800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a9adb480f10547f10202173a49b7f52116304476000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xc8826EA18D9884A1A335b2Cd7d5f44B159084301" - } - ], - "deepbrainchain": [ - { - "name": "InterchainAccountIsm", - "address": "0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", - "isProxy": false }, { "name": "InterchainAccountRouter", - "address": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e", - "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e", + "address": "0x7D5a79539d7B1c9aE5e54d18EEE188840f1Fe4CC", + "constructorArguments": "000000000000000000000000398633d19f4371e1db5a8efe90468eb70b1176aa0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xBCD18636e5876DFd7AAb5F2B2a5Eb5ca168BA1d8", - "constructorArguments": "0000000000000000000000005244d3359065c883bdfeeeff5329de38c0bd227e0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b7a808caa2c3f1378b07cdd46eb8cca52f67e3b000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e" } ], "nibiru": [ @@ -2414,6 +2671,12 @@ "constructorArguments": "0000000000000000000000000dbb60c348df645c295fd0ce26f87bb8507101850000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000284226f651eb5cbd696365bc27d333028fcc5d54000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x0DbB60c348DF645c295Fd0ce26F87bB850710185" + }, + { + "name": "InterchainAccountRouter", + "address": "0xd5e0859Cf2e9C790bE6ec4499A39d75Cb84836Dc", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "opbnb": [ @@ -2435,6 +2698,12 @@ "constructorArguments": "0000000000000000000000005244d3359065c883bdfeeeff5329de38c0bd227e0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b7a808caa2c3f1378b07cdd46eb8cca52f67e3b000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x5244d3359065C883BDfeEEff5329DE38c0Bd227e" + }, + { + "name": "InterchainAccountRouter", + "address": "0x8847A94861C299e6AD408923A604dEe057baB5dC", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "reactive": [ @@ -2456,6 +2725,12 @@ "constructorArguments": "0000000000000000000000004a91738390a3d55cb27c2863e8950c9cd1b89d0e0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000005244d3359065c883bdfeeeff5329de38c0bd227e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x4A91738390a3D55CB27c2863e8950c9cD1b89d0e" + }, + { + "name": "InterchainAccountRouter", + "address": "0x2A532fc8cF9a72142eA8753a0d2AB68098C19585", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "fluence": [ @@ -2477,6 +2752,12 @@ "constructorArguments": "000000000000000000000000a28344ac1fc47c1dc212e178540dd0f3e7a781a60000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d233433aec23f8382dad87d808f60557ea35399f000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xA28344Ac1Fc47C1dc212E178540dD0F3e7a781A6" + }, + { + "name": "InterchainAccountRouter", + "address": "0x1504Dff2ab3196a41FC0565E41B64247dc405022", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "game7": [ @@ -2498,6 +2779,12 @@ "constructorArguments": "000000000000000000000000a28344ac1fc47c1dc212e178540dd0f3e7a781a60000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d233433aec23f8382dad87d808f60557ea35399f000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xA28344Ac1Fc47C1dc212E178540dD0F3e7a781A6" + }, + { + "name": "InterchainAccountRouter", + "address": "0x1504Dff2ab3196a41FC0565E41B64247dc405022", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "peaq": [ @@ -2519,6 +2806,12 @@ "constructorArguments": "000000000000000000000000a28344ac1fc47c1dc212e178540dd0f3e7a781a60000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d233433aec23f8382dad87d808f60557ea35399f000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xA28344Ac1Fc47C1dc212E178540dD0F3e7a781A6" + }, + { + "name": "InterchainAccountRouter", + "address": "0xdcA646C56E7768DD11654956adE24bfFf9Ba4893", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "infinityvmmainnet": [ @@ -2540,6 +2833,12 @@ "constructorArguments": "000000000000000000000000a28344ac1fc47c1dc212e178540dd0f3e7a781a60000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d233433aec23f8382dad87d808f60557ea35399f000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xA28344Ac1Fc47C1dc212E178540dD0F3e7a781A6" + }, + { + "name": "InterchainAccountRouter", + "address": "0xF192f901e3204317aC9Eb27E838a25E4d073944B", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "hashkey": [ @@ -2561,6 +2860,12 @@ "constructorArguments": "0000000000000000000000005cbd4c5f9cd55747285652f815cc7b9a2ef6c586000000000000000000000000ea87ae93fa0019a82a727bfd3ebd1cfca8f64f1d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000002ed6030d204745ac0cd6be8301c3a63bf14d97cc000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x5CBD4c5f9CD55747285652f815Cc7b9A2Ef6c586" + }, + { + "name": "InterchainAccountRouter", + "address": "0xD79A14EA21db52F130A57Ea6e2af55949B00086E", + "constructorArguments": "0000000000000000000000003a867fcffec2b790970eebdc9023e75b0a172aa70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "miraclechain": [ @@ -2582,6 +2887,12 @@ "constructorArguments": "000000000000000000000000a697222b77cde62a8c47e627d5a1c49a870182360000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023cc88cf424d48fdb05b4f0a8ff6099aa4d56d8e000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xA697222b77cDe62A8C47E627d5A1c49A87018236" + }, + { + "name": "InterchainAccountRouter", + "address": "0x38D361861d321B8B05de200c61B8F18740Daf4D8", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "ontology": [ @@ -2624,6 +2935,12 @@ "constructorArguments": "000000000000000000000000e93f2f409ad8b5000431d234472973fe848dcbec0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001fbccdc677c10671ee50b46c61f0f7d135112450000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xe93f2f409ad8B5000431D234472973fe848dcBEC" + }, + { + "name": "InterchainAccountRouter", + "address": "0xbF2D3b1a37D54ce86d0e1455884dA875a97C87a8", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "botanix": [ @@ -2645,6 +2962,20 @@ "constructorArguments": "000000000000000000000000e93f2f409ad8b5000431d234472973fe848dcbec0000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001fbccdc677c10671ee50b46c61f0f7d135112450000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0xe93f2f409ad8B5000431D234472973fe848dcBEC" + }, + { + "name": "InterchainAccountRouter", + "address": "0x21b5a2fA1f53e94cF4871201aeD30C6ad5E405f2", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false + } + ], + "subtensor": [ + { + "name": "InterchainAccountRouter", + "address": "0x9097869cb719335f45A069D41dEFAFA2858af676", + "constructorArguments": "000000000000000000000000f767d698c510fe5e53b46ba6fd1174f5271e390a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ], "tac": [ @@ -2666,6 +2997,84 @@ "constructorArguments": "0000000000000000000000001d9c7400e3392e1854cfebd7c409eb22824d90230000000000000000000000002f2afae1139ce54fefc03593fee8ab2adf4a85a700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fb7d175d6f53800d68d32c3fe1416807a394cc24000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba00000000000000000000000000000000000000000000000000000000", "isProxy": true, "expectedimplementation": "0x1D9c7400E3392e1854CFeBD7C409EB22824d9023" + }, + { + "name": "InterchainAccountRouter", + "address": "0x829AcBc15a66F6B32a189CFB6451B2Ee583706BA", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false + } + ], + "galactica": [ + { + "name": "InterchainAccountRouter", + "address": "0x1fbcCdc677c10671eE50b46C61F0f7d135112450", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false + } + ], + "xrplevm": [ + { + "name": "InterchainAccountRouter", + "address": "0xe6fC77B08b457A29747682aB1dBfb32AF4A1A999", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false + } + ], + "mitosis": [ + { + "name": "InterchainAccountRouter", + "address": "0x1A41a365A693b6A7aED1a46316097d290f569F22", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false + } + ], + "pulsechain": [ + { + "name": "InterchainAccountRouter", + "address": "0x7823Ce52c254F71321a70b2b87EcC63a516008a1", + "constructorArguments": "00000000000000000000000056176c7fb66fdd70ef962ae53a46a226c7f6a2cc0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false + } + ], + "plasma": [ + { + "name": "InterchainAccountRouter", + "address": "0x9fE454AA2B01fc7A2a777AE561bc58Ce560CD5a9", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false + } + ], + "electroneum": [ + { + "name": "InterchainAccountRouter", + "address": "0x9fE454AA2B01fc7A2a777AE561bc58Ce560CD5a9", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false + } + ], + "sova": [ + { + "name": "InterchainAccountRouter", + "address": "0x89Ebf977E83087959aD78e5372F4AF15DcdC8143", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false + } + ], + "zerogravity": [ + { + "name": "InterchainAccountRouter", + "address": "0x23cc88CF424d48fDB05b4f0A8Ff6099aa4D56D8e", + "constructorArguments": "0000000000000000000000008428a1a7e97fc75fb7ba5c4aec31b55e52bbe9d60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false + } + ], + "mantra": [ + { + "name": "InterchainAccountRouter", + "address": "0x0DbB60c348DF645c295Fd0ce26F87bB850710185", + "constructorArguments": "0000000000000000000000003a464f746d23ab22155710f44db16dca53e0775e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a7eccdb9be08178f896c26b7bbd8c3d4e844d9ba000000000000000000000000000000000000000000000000000000000000c35000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005868747470733a2f2f6f6666636861696e2d6c6f6f6b75702e73657276696365732e68797065726c616e652e78797a2f63616c6c436f6d6d69746d656e74732f67657443616c6c7346726f6d52657665616c4d6573736167650000000000000000", + "isProxy": false } ] } diff --git a/typescript/infra/config/environments/mainnet3/misc-artifacts/merkly-erc20-addresses.json b/typescript/infra/config/environments/mainnet3/misc-artifacts/merkly-erc20-addresses.json index 2af6d1e53e5..0869ec81e1f 100644 --- a/typescript/infra/config/environments/mainnet3/misc-artifacts/merkly-erc20-addresses.json +++ b/typescript/infra/config/environments/mainnet3/misc-artifacts/merkly-erc20-addresses.json @@ -149,9 +149,6 @@ "proofofplay": { "router": "0x6E55472109E6aBE4054a8E8b8d9EdFfCb31032C5" }, - "sanko": { - "router": "0x6E55472109E6aBE4054a8E8b8d9EdFfCb31032C5" - }, "tangle": { "router": "0x6f6aE8851a460406bBB3c929a415d2Df9305AcD5" }, diff --git a/typescript/infra/config/environments/mainnet3/misc-artifacts/merkly-nft-addresses.json b/typescript/infra/config/environments/mainnet3/misc-artifacts/merkly-nft-addresses.json index cf837a82d4e..f6d2b83af0a 100644 --- a/typescript/infra/config/environments/mainnet3/misc-artifacts/merkly-nft-addresses.json +++ b/typescript/infra/config/environments/mainnet3/misc-artifacts/merkly-nft-addresses.json @@ -149,9 +149,6 @@ "proofofplay": { "router": "0xAFa5f9313F1F2b599173f24807a882F498Be118c" }, - "sanko": { - "router": "0xAFa5f9313F1F2b599173f24807a882F498Be118c" - }, "tangle": { "router": "0x6E55472109E6aBE4054a8E8b8d9EdFfCb31032C5" }, diff --git a/typescript/infra/config/environments/mainnet3/owners.ts b/typescript/infra/config/environments/mainnet3/owners.ts index e6e9d7d180a..dbb7d9bd560 100644 --- a/typescript/infra/config/environments/mainnet3/owners.ts +++ b/typescript/infra/config/environments/mainnet3/owners.ts @@ -2,8 +2,8 @@ import { ChainMap, OwnableConfig } from '@hyperlane-xyz/sdk'; import { Address } from '@hyperlane-xyz/utils'; import { ethereumChainNames } from './chains.js'; -import { awIcas } from './governance/ica/aw.js'; -import { regularIcasV2 } from './governance/ica/regular2.js'; +import { awIcasLegacy } from './governance/ica/_awLegacy.js'; +import { regularIcasLegacy } from './governance/ica/_regularLegacy.js'; import { regularIcas } from './governance/ica/regular.js'; import { awSafes } from './governance/safe/aw.js'; import { regularSafes } from './governance/safe/regular.js'; @@ -23,10 +23,10 @@ export const DEPLOYER = '0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba'; export const ethereumChainOwners: ChainMap = Object.fromEntries( ethereumChainNames.map((local) => { const owner = - regularIcasV2[local] ?? regularIcas[local] ?? + regularIcasLegacy[local] ?? regularSafes[local] ?? - awIcas[local] ?? + awIcasLegacy[local] ?? awSafes[local] ?? DEPLOYER; @@ -100,4 +100,10 @@ export const chainOwners: ChainMap = { noble: { owner: 'TODO: configure noble owner', }, + celestia: { + owner: 'TODO: configure celestia owner', + }, + radix: { + owner: 'account_rdx1280taxhhnuek02y59yapsg4kjtux954qkyufpwmy4dlfcxdrjzr7fj', + }, }; diff --git a/typescript/infra/config/environments/mainnet3/rebalancer/USDC/arbitrum-base-ethereum-ink-optimism-solanamainnet-superseed-config.yaml b/typescript/infra/config/environments/mainnet3/rebalancer/USDC/arbitrum-base-ethereum-ink-optimism-solanamainnet-superseed-config.yaml index d9565ea1a09..6ea322f077a 100644 --- a/typescript/infra/config/environments/mainnet3/rebalancer/USDC/arbitrum-base-ethereum-ink-optimism-solanamainnet-superseed-config.yaml +++ b/typescript/infra/config/environments/mainnet3/rebalancer/USDC/arbitrum-base-ethereum-ink-optimism-solanamainnet-superseed-config.yaml @@ -1,32 +1,39 @@ warpRouteId: USDC/arbitrum-base-ethereum-ink-optimism-solanamainnet-superseed strategy: - rebalanceStrategy: weighted - # this config aims to maintain 80% of the total USDC on ethereum + rebalanceStrategy: minAmount chains: base: - weighted: - weight: 1 - tolerance: 30 + minAmount: + min: 3000 + target: 5000 + type: 'absolute' bridgeLockTime: 1800 # 30 mins in seconds + bridgeMinAcceptedAmount: 2000 bridge: '0x5C4aFb7e23B1Dc1B409dc1702f89C64527b25975' ethereum: - weighted: - weight: 12 - tolerance: 30 + minAmount: + min: 30000 + target: 40000 + type: 'absolute' bridgeLockTime: 1800 # 30 mins in seconds + bridgeMinAcceptedAmount: 2000 bridge: '0xedCBAa585FD0F80f20073F9958246476466205b8' optimism: - weighted: - weight: 1 - tolerance: 30 + minAmount: + min: 3000 + target: 5000 + type: 'absolute' bridgeLockTime: 1800 # 30 mins in seconds + bridgeMinAcceptedAmount: 2000 bridge: '0xfB7681ECB05F85c383A5ce4439C7dF5ED12c77DE' arbitrum: - weighted: - weight: 1 - tolerance: 30 + minAmount: + min: 3000 + target: 5000 + type: 'absolute' bridgeLockTime: 1800 # 30 mins in seconds + bridgeMinAcceptedAmount: 2000 bridge: '0x8a82186EA618b91D13A2041fb7aC31Bf01C02aD2' diff --git a/typescript/infra/config/environments/mainnet3/rebalancer/USDC/paradex-config.yaml b/typescript/infra/config/environments/mainnet3/rebalancer/USDC/paradex-config.yaml index d622c70c132..e67495c437a 100644 --- a/typescript/infra/config/environments/mainnet3/rebalancer/USDC/paradex-config.yaml +++ b/typescript/infra/config/environments/mainnet3/rebalancer/USDC/paradex-config.yaml @@ -5,24 +5,24 @@ strategy: chains: base: weighted: - weight: 0 - tolerance: 0 + weight: 15 + tolerance: 3 bridgeLockTime: 1800 # 30 mins in seconds - bridgeMinAcceptedAmount: 20000 + bridgeMinAcceptedAmount: 3000 bridge: '0x5C4aFb7e23B1Dc1B409dc1702f89C64527b25975' ethereum: weighted: - weight: 1 - tolerance: 0 + weight: 40 + tolerance: 5 bridgeLockTime: 1800 # 30 mins in seconds - bridgeMinAcceptedAmount: 20000 + bridgeMinAcceptedAmount: 3000 bridge: '0xedCBAa585FD0F80f20073F9958246476466205b8' arbitrum: weighted: - weight: 0 - tolerance: 0 + weight: 45 + tolerance: 5 bridgeLockTime: 1800 # 30 mins in seconds - bridgeMinAcceptedAmount: 20000 + bridgeMinAcceptedAmount: 3000 bridge: '0x8a82186EA618b91D13A2041fb7aC31Bf01C02aD2' diff --git a/typescript/infra/config/environments/mainnet3/rebalancer/USDC/pulsechain-config.yaml b/typescript/infra/config/environments/mainnet3/rebalancer/USDC/pulsechain-config.yaml new file mode 100644 index 00000000000..c287d87aea5 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/rebalancer/USDC/pulsechain-config.yaml @@ -0,0 +1,36 @@ +warpRouteId: USDC/pulsechain + +strategy: + rebalanceStrategy: weighted + chains: + arbitrum: + weighted: + weight: 25 # should have 25% of all the collateral in this route + tolerance: 5 # 5% deviation on the expected balance before triggering a rebalance + bridgeLockTime: 1800 # 30 mins in seconds (required for CCTP latency) + bridgeMinAcceptedAmount: 3500 + bridge: '0x8a82186EA618b91D13A2041fb7aC31Bf01C02aD2' + + base: + weighted: + weight: 25 # should have 25% of all the collateral in this route + tolerance: 5 # 5% deviation on the expected balance before triggering a rebalance + bridgeLockTime: 1800 # 30 mins in seconds (required for CCTP latency) + bridgeMinAcceptedAmount: 3500 + bridge: '0x5C4aFb7e23B1Dc1B409dc1702f89C64527b25975' + + ethereum: + weighted: + weight: 25 # should have 25% of all the collateral in this route + tolerance: 5 # 5% deviation on the expected balance before triggering a rebalance + bridgeLockTime: 1800 # 30 mins in seconds (required for CCTP latency) + bridgeMinAcceptedAmount: 3500 + bridge: '0xedCBAa585FD0F80f20073F9958246476466205b8' + + polygon: + weighted: + weight: 25 # should have 25% of all the collateral in this route + tolerance: 5 # 5% deviation on the expected balance before triggering a rebalance + bridgeLockTime: 1800 # 30 mins in seconds (required for CCTP latency) + bridgeMinAcceptedAmount: 3500 + bridge: '0xa62F45662809f5F6535b58bae9A572a2EC4A1f84' diff --git a/typescript/infra/config/environments/mainnet3/rebalancer/USDC/subtensor-config.yaml b/typescript/infra/config/environments/mainnet3/rebalancer/USDC/subtensor-config.yaml new file mode 100644 index 00000000000..c5ad4a179a1 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/rebalancer/USDC/subtensor-config.yaml @@ -0,0 +1,48 @@ +warpRouteId: USDC/subtensor +strategy: + rebalanceStrategy: minAmount + chains: + base: + minAmount: + min: 10000 + target: 20000 + type: 'absolute' + bridgeLockTime: 1800 # 30 mins in seconds + bridgeMinAcceptedAmount: 3000 + bridge: '0x5C4aFb7e23B1Dc1B409dc1702f89C64527b25975' + + ethereum: + minAmount: + min: 50000 + target: 60000 + type: 'absolute' + bridgeLockTime: 1800 # 30 mins in seconds + bridgeMinAcceptedAmount: 3000 + bridge: '0xedCBAa585FD0F80f20073F9958246476466205b8' + + polygon: + minAmount: + min: 10000 + target: 20000 + type: 'absolute' + bridgeLockTime: 1800 # 30 mins in seconds + bridgeMinAcceptedAmount: 3000 + bridge: '0xa62F45662809f5F6535b58bae9A572a2EC4A1f84' + + arbitrum: + minAmount: + min: 10000 + target: 20000 + type: 'absolute' + bridgeLockTime: 1800 # 30 mins in seconds + bridgeMinAcceptedAmount: 3000 + bridge: '0x8a82186EA618b91D13A2041fb7aC31Bf01C02aD2' + + unichain: + minAmount: + min: 10000 + target: 20000 + type: 'absolute' + bridgeLockTime: 1800 # 30 mins in seconds + bridgeMinAcceptedAmount: 3000 + bridge: '0x296aF86bff91b23cF980f6a443bc15A3A5d30682' diff --git a/typescript/infra/config/environments/mainnet3/supportedChainNames.ts b/typescript/infra/config/environments/mainnet3/supportedChainNames.ts index 6b0f2a32020..855577280ef 100644 --- a/typescript/infra/config/environments/mainnet3/supportedChainNames.ts +++ b/typescript/infra/config/environments/mainnet3/supportedChainNames.ts @@ -1,8 +1,8 @@ // These chains may be any protocol type. // Placing them here instead of adjacent chains file to avoid circular dep export const mainnet3SupportedChainNames = [ + 'sepolia', // Dymension 'abstract', - // 'acala', 'ancient8', 'alephzeroevmmainnet', 'apechain', @@ -13,9 +13,7 @@ export const mainnet3SupportedChainNames = [ 'artela', 'astar', 'aurora', - 'bouncebit', 'botanix', - 'flame', 'avalanche', 'b3', 'base', @@ -26,29 +24,26 @@ export const mainnet3SupportedChainNames = [ 'boba', 'bsc', 'bsquared', + 'celestia', 'celo', 'cheesechain', 'chilizmainnet', - 'conflux', - 'conwai', 'coredao', 'coti', 'cyber', - 'deepbrainchain', 'degenchain', 'dogechain', - 'duckchain', 'eclipsemainnet', + 'electroneum', 'endurance', 'ethereum', 'everclear', - 'evmos', 'fantom', 'flare', 'flowmainnet', 'fluence', 'form', - // 'fractal', + 'forma', 'fraxtal', 'fusemainnet', 'galactica', @@ -70,10 +65,10 @@ export const mainnet3SupportedChainNames = [ 'linea', 'lisk', 'lukso', - 'lumia', 'lumiaprism', 'mantapacific', 'mantle', + 'mantra', 'matchain', 'merlin', 'metal', @@ -81,11 +76,11 @@ export const mainnet3SupportedChainNames = [ 'milkyway', 'mint', 'miraclechain', + 'mitosis', 'mode', 'molten', 'moonbeam', 'morph', - 'nero', 'neutron', 'nibiru', 'noble', @@ -97,19 +92,19 @@ export const mainnet3SupportedChainNames = [ 'osmosis', 'paradex', 'peaq', + 'plasma', 'plume', 'polygon', 'polygonzkevm', 'polynomialfi', 'prom', 'proofofplay', + 'pulsechain', + 'radix', 'rarichain', 'reactive', 'redstone', - 'rivalz', 'ronin', - 'rootstockmainnet', - 'sanko', 'scroll', 'sei', 'shibarium', @@ -121,6 +116,7 @@ export const mainnet3SupportedChainNames = [ 'sonicsvm', 'soon', 'sophon', + 'sova', 'starknet', 'story', 'stride', @@ -132,21 +128,18 @@ export const mainnet3SupportedChainNames = [ 'tac', 'taiko', 'tangle', - 'telos', 'torus', 'unichain', - 'unitzero', 'vana', 'viction', 'worldchain', 'xai', 'xlayer', - 'xpla', 'xrplevm', + 'zerogravity', 'zeronetwork', 'zetachain', 'zircuit', - 'zklink', 'zksync', 'zoramainnet', ] as const; diff --git a/typescript/infra/config/environments/mainnet3/testrecipient/verification.json b/typescript/infra/config/environments/mainnet3/testrecipient/verification.json index 30ffe8f37cd..d917886415d 100644 --- a/typescript/infra/config/environments/mainnet3/testrecipient/verification.json +++ b/typescript/infra/config/environments/mainnet3/testrecipient/verification.json @@ -55,14 +55,6 @@ "name": "TestRecipient" } ], - "sanko": [ - { - "address": "0x2c61Cda929e4e2174cb10cd8e2724A9ceaD62E67", - "constructorArguments": "", - "isProxy": false, - "name": "TestRecipient" - } - ], "tangle": [ { "address": "0x2c61Cda929e4e2174cb10cd8e2724A9ceaD62E67", @@ -78,13 +70,5 @@ "isProxy": false, "name": "TestRecipient" } - ], - "lumia": [ - { - "name": "TestRecipient", - "address": "0xf7D882A816D4845BB221Ceb03CE531d1e7645F60", - "constructorArguments": "", - "isProxy": false - } ] } diff --git a/typescript/infra/config/environments/mainnet3/tokenPrices.json b/typescript/infra/config/environments/mainnet3/tokenPrices.json index 1b22e03982f..b71a7b1d9c7 100644 --- a/typescript/infra/config/environments/mainnet3/tokenPrices.json +++ b/typescript/infra/config/environments/mainnet3/tokenPrices.json @@ -1,148 +1,142 @@ { - "abstract": "3882.24", - "ancient8": "3882.24", - "alephzeroevmmainnet": "0.02641716", - "apechain": "0.66352", - "appchain": "3882.24", - "arbitrum": "3882.24", - "arbitrumnova": "3882.24", - "arcadia": "3882.24", + "abstract": "4430.91", + "ancient8": "4430.91", + "alephzeroevmmainnet": "0.0248428", + "apechain": "0.601076", + "appchain": "4430.91", + "arbitrum": "4430.91", + "arbitrumnova": "4430.91", + "arcadia": "4430.91", "artela": "0.00018479", - "astar": "0.02688078", - "aurora": "3882.24", - "bouncebit": "0.12239", - "botanix": "118807", - "flame": "2.07", - "avalanche": "26.93", - "b3": "3882.24", - "base": "3882.24", - "berachain": "2.28", - "bitlayer": "118807", - "blast": "3882.24", - "bob": "3882.24", - "boba": "3882.24", - "bsc": "857.09", - "bsquared": "118807", - "celo": "0.365539", - "cheesechain": "0.00024472", - "chilizmainnet": "0.0441722", - "conflux": "0.188388", - "conwai": "0.00165729", - "coredao": "0.566962", - "coti": "0.063808", - "cyber": "3882.24", - "deepbrainchain": "0.00064133", - "degenchain": "0.00438422", - "dogechain": "0.241474", - "duckchain": "3.41", - "eclipsemainnet": "3882.24", - "endurance": "0.619692", - "ethereum": "3882.24", - "everclear": "3882.24", - "evmos": "0.0037625", - "fantom": "0.341691", - "flare": "0.0248478", - "flowmainnet": "0.424892", - "fluence": "0.03866775", - "form": "3882.24", - "fraxtal": "3864.99", - "fusemainnet": "0.01124251", + "astar": "0.02374928", + "aurora": "4430.91", + "botanix": "114494", + "avalanche": "28.83", + "b3": "4430.91", + "base": "4430.91", + "berachain": "2.27", + "bitlayer": "114494", + "blast": "4430.91", + "bob": "4430.91", + "boba": "4430.91", + "bsc": "899.13", + "bsquared": "114494", + "celestia": "1.75", + "celo": "0.307985", + "cheesechain": "0.00025691", + "chilizmainnet": "0.04184919", + "coredao": "0.457869", + "coti": "0.050351", + "cyber": "4430.91", + "degenchain": "0.00329688", + "dogechain": "0.249341", + "eclipsemainnet": "4430.91", + "electroneum": "0.00337411", + "endurance": "0.65759", + "ethereum": "4430.91", + "everclear": "4430.91", + "fantom": "0.300602", + "flare": "0.02224346", + "flowmainnet": "0.410636", + "fluence": "0.03249195", + "form": "4430.91", + "forma": "1.49", + "fraxtal": "4401.15", + "fusemainnet": "0.01023947", "galactica": "1", - "game7": "0.00334906", - "gnosis": "0.997212", - "gravity": "0.01386376", - "harmony": "0.01217518", - "hashkey": "0.559218", - "hemi": "3882.24", - "hyperevm": "44.26", - "immutablezkevmmainnet": "0.603968", - "inevm": "16.02", + "game7": "0.00041558", + "gnosis": "0.99965", + "gravity": "0.01158206", + "harmony": "0.01075327", + "hashkey": "0.44311", + "hemi": "4430.91", + "hyperevm": "54.4", + "immutablezkevmmainnet": "0.566847", + "inevm": "14.06", "infinityvmmainnet": "1", - "ink": "3882.24", - "injective": "16.02", - "kaia": "0.169271", - "katana": "3882.24", - "kyve": "0.00776138", - "linea": "3882.24", - "lisk": "3882.24", - "lukso": "0.787787", - "lumia": "0.401241", - "lumiaprism": "0.378847", - "mantapacific": "3882.24", - "mantle": "0.820072", - "matchain": "857.09", - "merlin": "118802", - "metal": "3882.24", - "metis": "18.43", - "milkyway": "0.050429", - "mint": "3882.24", - "miraclechain": "0.01745825", - "mode": "3882.24", - "molten": "0.179636", - "moonbeam": "0.080017", - "morph": "3882.24", - "nero": "1", - "neutron": "0.107564", - "nibiru": "0.0100238", - "noble": "0.999719", - "oortmainnet": "0.03415101", - "ontology": "0.199227", - "opbnb": "857.09", - "optimism": "3882.24", - "orderly": "3882.24", - "osmosis": "0.19064", + "ink": "4430.91", + "injective": "14.06", + "kaia": "0.155204", + "katana": "4430.91", + "kyve": "0.00754525", + "linea": "4430.91", + "lisk": "4430.91", + "lukso": "0.92334", + "lumiaprism": "0.329507", + "mantapacific": "4430.91", + "mantle": "1.61", + "mantra": "0.206374", + "matchain": "899.13", + "merlin": "114494", + "metal": "4430.91", + "metis": "15.47", + "milkyway": "0.04812142", + "mint": "4430.91", + "miraclechain": "0.01002978", + "mitosis": "0.202754", + "mode": "4430.91", + "molten": "0.114569", + "moonbeam": "0.06863", + "morph": "4430.91", + "neutron": "0.097245", + "nibiru": "0.01029056", + "noble": "0.999811", + "oortmainnet": "0.052275", + "ontology": "0.16901", + "opbnb": "899.13", + "optimism": "4430.91", + "orderly": "4430.91", + "osmosis": "0.171823", "paradex": "1", - "peaq": "0.074864", - "plume": "0.111592", - "polygon": "0.241657", - "polygonzkevm": "3882.24", - "polynomialfi": "3882.24", - "prom": "9.2", - "proofofplay": "3882.24", - "rarichain": "3882.24", - "reactive": "0.073309", - "redstone": "3882.24", - "rivalz": "3882.24", - "ronin": "0.584067", - "rootstockmainnet": "119047", - "sanko": "12.29", - "scroll": "3882.24", - "sei": "0.347181", - "shibarium": "0.19541", - "snaxchain": "3882.24", - "solanamainnet": "192.54", - "solaxy": "0.00084275", - "soneium": "3882.24", - "sonic": "0.341691", - "sonicsvm": "192.54", - "soon": "3882.24", - "sophon": "0.03990604", - "starknet": "3882.24", - "story": "5.74", - "stride": "0.178772", - "subtensor": "429.86", - "superseed": "3882.24", - "superpositionmainnet": "3882.24", - "svmbnb": "857.09", - "swell": "3882.24", + "peaq": "0.075954", + "plasma": "1", + "plume": "0.112386", + "polygon": "0.270244", + "polygonzkevm": "4430.91", + "polynomialfi": "4430.91", + "prom": "9.11", + "proofofplay": "4430.91", + "pulsechain": "0.00004223", + "radix": "0.00634327", + "rarichain": "4430.91", + "reactive": "0.091741", + "redstone": "4430.91", + "ronin": "0.515126", + "scroll": "4430.91", + "sei": "0.321168", + "shibarium": "0.161399", + "snaxchain": "4430.91", + "solanamainnet": "227.1", + "solaxy": "0.00043586", + "soneium": "4430.91", + "sonic": "0.300602", + "sonicsvm": "227.1", + "soon": "4430.91", + "sophon": "0.03149473", + "sova": "4497.39", + "starknet": "4430.91", + "story": "9.91", + "stride": "0.05441", + "subtensor": "355.15", + "superseed": "4430.91", + "superpositionmainnet": "4430.91", + "svmbnb": "899.13", + "swell": "4430.91", "tac": "1", - "taiko": "3882.24", + "taiko": "4430.91", "tangle": "1", - "telos": "0.03778625", - "torus": "0.556303", - "unichain": "3882.24", - "unitzero": "0.102455", - "vana": "5.16", - "viction": "0.273402", - "worldchain": "3882.24", - "xai": "0.059099", - "xlayer": "49.25", - "xpla": "0.04243768", - "xrplevm": "3.25", - "zeronetwork": "3882.24", - "zetachain": "0.227746", - "zircuit": "3882.24", - "zklink": "3882.24", - "zksync": "3882.24", - "zoramainnet": "3882.24" + "torus": "0.204862", + "unichain": "4430.91", + "vana": "4.38", + "viction": "0.239723", + "worldchain": "4430.91", + "xai": "0.056589", + "xlayer": "194.25", + "xrplevm": "3", + "zerogravity": "1", + "zeronetwork": "4430.91", + "zetachain": "0.187149", + "zircuit": "4430.91", + "zksync": "4430.91", + "zoramainnet": "4430.91" } diff --git a/typescript/infra/config/environments/mainnet3/validators.ts b/typescript/infra/config/environments/mainnet3/validators.ts index 1ce63d26b63..8e949aca370 100644 --- a/typescript/infra/config/environments/mainnet3/validators.ts +++ b/typescript/infra/config/environments/mainnet3/validators.ts @@ -29,11 +29,7 @@ export const validatorChainConfig = ( reorgPeriod: getReorgPeriod('celo'), validators: validatorsConfig( { - [Contexts.Hyperlane]: [ - '0x63478422679303c3e4fc611b771fa4a707ef7f4a', - '0x2f4e808744df049d8acc050628f7bdd8265807f9', - '0x7bf30afcb6a7d92146d5a910ea4c154fba38d25e', - ], + [Contexts.Hyperlane]: ['0x63478422679303c3e4fc611b771fa4a707ef7f4a'], [Contexts.ReleaseCandidate]: [ '0xb51768c1388e976486a43dbbbbf9ce04cf45e990', '0x6325de37b33e20089c091950518a471e29c52883', @@ -49,11 +45,7 @@ export const validatorChainConfig = ( reorgPeriod: getReorgPeriod('ethereum'), validators: validatorsConfig( { - [Contexts.Hyperlane]: [ - '0x03c842db86a6a3e524d4a6615390c1ea8e2b9541', - '0x4346776b10f5e0d9995d884b7a1dbaee4e24c016', - '0x749d6e7ad949e522c92181dc77f7bbc1c5d71506', - ], + [Contexts.Hyperlane]: ['0x03c842db86a6a3e524d4a6615390c1ea8e2b9541'], [Contexts.ReleaseCandidate]: [ '0x0580884289890805802012b9872afa5ae41a5fa6', '0xa5465cb5095a2e6093587e644d6121d6ed55c632', @@ -69,11 +61,7 @@ export const validatorChainConfig = ( reorgPeriod: getReorgPeriod('avalanche'), validators: validatorsConfig( { - [Contexts.Hyperlane]: [ - '0x3fb8263859843bffb02950c492d492cae169f4cf', - '0xe58c63ad669b946e7c8211299f22679deecc9c83', - '0x6c754f1e9cd8287088b46a7c807303d55d728b49', - ], + [Contexts.Hyperlane]: ['0x3fb8263859843bffb02950c492d492cae169f4cf'], [Contexts.ReleaseCandidate]: [ '0x2c7cf6d1796e37676ba95f056ff21bf536c6c2d3', '0xcd250d48d16e2ce4b939d44b5215f9e978975152', @@ -84,7 +72,6 @@ export const validatorChainConfig = ( 'avalanche', ), }, - cheesechain: { interval: 5, reorgPeriod: getReorgPeriod('cheesechain'), @@ -99,7 +86,6 @@ export const validatorChainConfig = ( 'cheesechain', ), }, - worldchain: { interval: 5, reorgPeriod: getReorgPeriod('worldchain'), @@ -114,7 +100,6 @@ export const validatorChainConfig = ( 'worldchain', ), }, - xlayer: { interval: 5, reorgPeriod: getReorgPeriod('xlayer'), @@ -129,17 +114,12 @@ export const validatorChainConfig = ( 'xlayer', ), }, - polygon: { interval: 5, reorgPeriod: getReorgPeriod('polygon'), validators: validatorsConfig( { - [Contexts.Hyperlane]: [ - '0x12ecb319c7f4e8ac5eb5226662aeb8528c5cefac', - '0x8dd8f8d34b5ecaa5f66de24b01acd7b8461c3916', - '0xdbf3666de031bea43ec35822e8c33b9a9c610322', - ], + [Contexts.Hyperlane]: ['0x12ecb319c7f4e8ac5eb5226662aeb8528c5cefac'], [Contexts.ReleaseCandidate]: [ '0xf0a990959f833ccde624c8bcd4c7669286a57a0f', '0x456b636bdde99d69176261d7a4fba42c16f57f56', @@ -155,11 +135,7 @@ export const validatorChainConfig = ( reorgPeriod: getReorgPeriod('bsc'), validators: validatorsConfig( { - [Contexts.Hyperlane]: [ - '0x570af9b7b36568c8877eebba6c6727aa9dab7268', - '0x7bf928d5d262365d31d64eaa24755d48c3cae313', - '0x03047213365800f065356b4a2fe97c3c3a52296a', - ], + [Contexts.Hyperlane]: ['0x570af9b7b36568c8877eebba6c6727aa9dab7268'], [Contexts.ReleaseCandidate]: [ '0x911dfcc19dd5b723e84be452f6af52adef020bc8', '0xee2d4fd5fe2170e51c6279552297117feaeb19e1', @@ -175,11 +151,7 @@ export const validatorChainConfig = ( reorgPeriod: getReorgPeriod('arbitrum'), validators: validatorsConfig( { - [Contexts.Hyperlane]: [ - '0x4d966438fe9e2b1e7124c87bbb90cb4f0f6c59a1', - '0x6333e110b8a261cab28acb43030bcde59f26978a', - '0x3369e12edd52570806f126eb50be269ba5e65843', - ], + [Contexts.Hyperlane]: ['0x4d966438fe9e2b1e7124c87bbb90cb4f0f6c59a1'], [Contexts.ReleaseCandidate]: [ '0xb4c18167c163391facb345bb069d12d0430a6a89', '0x2f6dc057ae079997f76205903b85c8302164a78c', @@ -195,11 +167,7 @@ export const validatorChainConfig = ( reorgPeriod: getReorgPeriod('optimism'), validators: validatorsConfig( { - [Contexts.Hyperlane]: [ - '0x20349eadc6c72e94ce38268b96692b1a5c20de4f', - '0x04d040cee072272789e2d1f29aef73b3ad098db5', - '0x779a17e035018396724a6dec8a59bda1b5adf738', - ], + [Contexts.Hyperlane]: ['0x20349eadc6c72e94ce38268b96692b1a5c20de4f'], [Contexts.ReleaseCandidate]: [ '0x7e4391786e0b5b0cbaada12d32c931e46e44f104', '0x138ca73e805afa14e85d80f6e35c46e6f235429e', @@ -227,11 +195,7 @@ export const validatorChainConfig = ( reorgPeriod: getReorgPeriod('moonbeam'), validators: validatorsConfig( { - [Contexts.Hyperlane]: [ - '0x2225e2f4e9221049456da93b71d2de41f3b6b2a8', - '0x4fe067bb455358e295bfcfb92519a6f9de94b98e', - '0xcc4a78aa162482bea43313cd836ba7b560b44fc4', - ], + [Contexts.Hyperlane]: ['0x2225e2f4e9221049456da93b71d2de41f3b6b2a8'], [Contexts.ReleaseCandidate]: [ '0x75e3cd4e909089ae6c9f3a42b1468b33eec84161', '0xc28418d0858a82a46a11e07db75f8bf4eed43881', @@ -247,11 +211,7 @@ export const validatorChainConfig = ( reorgPeriod: getReorgPeriod('gnosis'), validators: validatorsConfig( { - [Contexts.Hyperlane]: [ - '0xd4df66a859585678f2ea8357161d896be19cc1ca', - '0x06a833508579f8b59d756b3a1e72451fc70840c3', - '0xb93a72cee19402553c9dd7fed2461aebd04e2454', - ], + [Contexts.Hyperlane]: ['0xd4df66a859585678f2ea8357161d896be19cc1ca'], [Contexts.ReleaseCandidate]: [ '0xd5122daa0c3dfc94a825ae928f3ea138cdb6a2e1', '0x2d1f367e942585f8a1c25c742397dc8be9a61dee', @@ -267,11 +227,7 @@ export const validatorChainConfig = ( reorgPeriod: getReorgPeriod('base'), validators: validatorsConfig( { - [Contexts.Hyperlane]: [ - '0xb9453d675e0fa3c178a17b4ce1ad5b1a279b3af9', - '0x4512985a574cb127b2af2d4bb676876ce804e3f8', - '0xb144bb2f599a5af095bc30367856f27ea8a8adc7', - ], + [Contexts.Hyperlane]: ['0xb9453d675e0fa3c178a17b4ce1ad5b1a279b3af9'], [Contexts.ReleaseCandidate]: [ '0xa8363570749080c7faa1de714e0782ff444af4cc', '0x3b55d9febe02a9038ef8c867fa8bbfdd8d70f9b8', @@ -313,11 +269,7 @@ export const validatorChainConfig = ( reorgPeriod: getReorgPeriod('inevm'), validators: validatorsConfig( { - [Contexts.Hyperlane]: [ - '0xf9e35ee88e4448a3673b4676a4e153e3584a08eb', - '0xae3e6bb6b3ece1c425aa6f47adc8cb0453c1f9a2', - '0xd98c9522cd9d3e3e00bee05ff76c34b91b266ec3', - ], + [Contexts.Hyperlane]: ['0xf9e35ee88e4448a3673b4676a4e153e3584a08eb'], [Contexts.ReleaseCandidate]: [ '0x52a0376903294c796c091c785a66c62943d99aa8', '0xc2ea1799664f753bedb9872d617e3ebc60b2e0ab', @@ -387,11 +339,7 @@ export const validatorChainConfig = ( reorgPeriod: getReorgPeriod('scroll'), validators: validatorsConfig( { - [Contexts.Hyperlane]: [ - '0xad557170a9f2f21c35e03de07cb30dcbcc3dff63', - '0xb37fe43a9f47b7024c2d5ae22526cc66b5261533', - '0x7210fa0a6be39a75cb14d682ebfb37e2b53ecbe5', - ], + [Contexts.Hyperlane]: ['0xad557170a9f2f21c35e03de07cb30dcbcc3dff63'], [Contexts.ReleaseCandidate]: [ '0x11387d89856219cf685f22781bf4e85e00468d54', '0x64b98b96ccae6e660ecf373b5dd61bcc34fd19ee', @@ -443,11 +391,7 @@ export const validatorChainConfig = ( reorgPeriod: getReorgPeriod('polygonzkevm'), validators: validatorsConfig( { - [Contexts.Hyperlane]: [ - '0x86f2a44592bb98da766e880cfd70d3bbb295e61a', - '0xc84076030bdabaabb9e61161d833dd84b700afda', - '0x6a1da2e0b7ae26aaece1377c0a4dbe25b85fa3ca', - ], + [Contexts.Hyperlane]: ['0x86f2a44592bb98da766e880cfd70d3bbb295e61a'], [Contexts.ReleaseCandidate]: [ '0x75cffb90391d7ecf58a84e9e70c67e7b306211c0', '0x82c10acb56f3d7ed6738b61668111a6b5250283e', @@ -473,11 +417,7 @@ export const validatorChainConfig = ( reorgPeriod: getReorgPeriod('neutron'), validators: validatorsConfig( { - [Contexts.Hyperlane]: [ - '0xa9b8c1f4998f781f958c63cfcd1708d02f004ff0', - '0x60e890b34cb44ce3fa52f38684f613f31b47a1a6', - '0x7885fae56dbcf5176657f54adbbd881dc6714132', - ], + [Contexts.Hyperlane]: ['0xa9b8c1f4998f781f958c63cfcd1708d02f004ff0'], [Contexts.ReleaseCandidate]: [ '0x307a8fe091b8273c7ce3d277b161b4a2167279b1', '0xb825c1bd020cb068f477b320f591b32e26814b5b', @@ -493,11 +433,7 @@ export const validatorChainConfig = ( reorgPeriod: getReorgPeriod('mantapacific'), validators: validatorsConfig( { - [Contexts.Hyperlane]: [ - '0x8e668c97ad76d0e28375275c41ece4972ab8a5bc', - '0x80afdde2a81f3fb056fd088a97f0af3722dbc4f3', - '0x5dda0c4cf18de3b3ab637f8df82b24921082b54c', - ], + [Contexts.Hyperlane]: ['0x8e668c97ad76d0e28375275c41ece4972ab8a5bc'], [Contexts.ReleaseCandidate]: [ '0x84fcb05e6e5961df2dfd9f36e8f2b3e87ede7d76', '0x45f3e2655a08feda821ee7b495cf2595401e1569', @@ -628,7 +564,6 @@ export const validatorChainConfig = ( 'zircuit', ), }, - cyber: { interval: 5, reorgPeriod: getReorgPeriod('cyber'), @@ -653,7 +588,6 @@ export const validatorChainConfig = ( 'degenchain', ), }, - lisk: { interval: 5, reorgPeriod: getReorgPeriod('lisk'), @@ -714,7 +648,6 @@ export const validatorChainConfig = ( 'mint', ), }, - proofofplay: { interval: 5, reorgPeriod: getReorgPeriod('proofofplay'), @@ -727,18 +660,6 @@ export const validatorChainConfig = ( 'proofofplay', ), }, - sanko: { - interval: 5, - reorgPeriod: getReorgPeriod('sanko'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x795c37d5babbc44094b084b0c89ed9db9b5fae39'], - [Contexts.ReleaseCandidate]: [''], - [Contexts.Neutron]: [], - }, - 'sanko', - ), - }, tangle: { interval: 5, reorgPeriod: getReorgPeriod('tangle'), @@ -763,7 +684,6 @@ export const validatorChainConfig = ( 'xai', ), }, - astar: { interval: 5, reorgPeriod: getReorgPeriod('astar'), @@ -872,18 +792,6 @@ export const validatorChainConfig = ( 'oortmainnet', ), }, - - lumia: { - interval: 5, - reorgPeriod: getReorgPeriod('lumia'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x9e283254ed2cd2c80f007348c2822fc8e5c2fa5f'], - }, - 'lumia', - ), - }, - zeronetwork: { interval: 5, reorgPeriod: getReorgPeriod('zeronetwork'), @@ -904,7 +812,6 @@ export const validatorChainConfig = ( 'zksync', ), }, - apechain: { interval: 5, reorgPeriod: getReorgPeriod('apechain'), @@ -1005,7 +912,6 @@ export const validatorChainConfig = ( 'snaxchain', ), }, - alephzeroevmmainnet: { interval: 5, reorgPeriod: getReorgPeriod('alephzeroevmmainnet'), @@ -1076,16 +982,6 @@ export const validatorChainConfig = ( 'rarichain', ), }, - rootstockmainnet: { - interval: 5, - reorgPeriod: getReorgPeriod('rootstockmainnet'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x8675eb603d62ab64e3efe90df914e555966e04ac'], - }, - 'rootstockmainnet', - ), - }, superpositionmainnet: { interval: 5, reorgPeriod: getReorgPeriod('superpositionmainnet'), @@ -1096,16 +992,6 @@ export const validatorChainConfig = ( 'superpositionmainnet', ), }, - flame: { - interval: 5, - reorgPeriod: getReorgPeriod('flame'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x1fa928ce884fa16357d4b8866e096392d4d81f43'], - }, - 'flame', - ), - }, prom: { interval: 5, reorgPeriod: getReorgPeriod('prom'), @@ -1116,7 +1002,6 @@ export const validatorChainConfig = ( 'prom', ), }, - boba: { interval: 5, reorgPeriod: getReorgPeriod('boba'), @@ -1127,16 +1012,6 @@ export const validatorChainConfig = ( 'boba', ), }, - duckchain: { - interval: 5, - reorgPeriod: getReorgPeriod('duckchain'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x91d55fe6dac596a6735d96365e21ce4bca21d83c'], - }, - 'duckchain', - ), - }, superseed: { interval: 5, reorgPeriod: getReorgPeriod('superseed'), @@ -1167,7 +1042,6 @@ export const validatorChainConfig = ( 'vana', ), }, - bsquared: { interval: 5, reorgPeriod: getReorgPeriod('bsquared'), @@ -1178,7 +1052,6 @@ export const validatorChainConfig = ( 'bsquared', ), }, - lumiaprism: { interval: 5, reorgPeriod: getReorgPeriod('lumiaprism'), @@ -1199,17 +1072,6 @@ export const validatorChainConfig = ( 'swell', ), }, - - zklink: { - interval: 5, - reorgPeriod: getReorgPeriod('zklink'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x217a8cb4789fc45abf56cb6e2ca96f251a5ac181'], - }, - 'zklink', - ), - }, appchain: { interval: 5, reorgPeriod: getReorgPeriod('appchain'), @@ -1220,7 +1082,6 @@ export const validatorChainConfig = ( 'appchain', ), }, - aurora: { interval: 5, reorgPeriod: getReorgPeriod('aurora'), @@ -1231,36 +1092,6 @@ export const validatorChainConfig = ( 'aurora', ), }, - conflux: { - interval: 5, - reorgPeriod: getReorgPeriod('conflux'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x113dfa1dc9b0a2efb6ad01981e2aad86d3658490'], - }, - 'conflux', - ), - }, - conwai: { - interval: 5, - reorgPeriod: getReorgPeriod('conwai'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x949e2cdd7e79f99ee9bbe549540370cdc62e73c3'], - }, - 'conwai', - ), - }, - evmos: { - interval: 5, - reorgPeriod: getReorgPeriod('evmos'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x8f82387ad8b7b13aa9e06ed3f77f78a77713afe0'], - }, - 'evmos', - ), - }, form: { interval: 5, reorgPeriod: getReorgPeriod('form'), @@ -1301,26 +1132,6 @@ export const validatorChainConfig = ( 'sonic', ), }, - telos: { - interval: 5, - reorgPeriod: getReorgPeriod('telos'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0xcb08410b14d3adf0d0646f0c61cd07e0daba8e54'], - }, - 'telos', - ), - }, - rivalz: { - interval: 5, - reorgPeriod: getReorgPeriod('rivalz'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0xf87c3eb3dde972257b0d6d110bdadcda951c0dc1'], - }, - 'rivalz', - ), - }, soon: { interval: 5, reorgPeriod: getReorgPeriod('soon'), @@ -1343,18 +1154,6 @@ export const validatorChainConfig = ( 'stride', ), }, - - // fractal: { - // interval: 5, - // reorgPeriod: getReorgPeriod('fractal'), - // validators: validatorsConfig( - // { - // [Contexts.Hyperlane]: ['0x3476c9652d3371bb01bbb4962516fffee5e73754'], - // }, - // 'fractal', - // ), - // }, - torus: { interval: 5, reorgPeriod: getReorgPeriod('torus'), @@ -1365,17 +1164,6 @@ export const validatorChainConfig = ( 'torus', ), }, - - // acala: { - // interval: 5, - // reorgPeriod: getReorgPeriod('acala'), - // validators: validatorsConfig( - // { - // [Contexts.Hyperlane]: ['0x3229bbeeab163c102d0b1fa15119b9ae0ed37cfa'], - // }, - // 'acala', - // ), - // }, artela: { interval: 5, reorgPeriod: getReorgPeriod('artela'), @@ -1396,37 +1184,6 @@ export const validatorChainConfig = ( 'hemi', ), }, - nero: { - interval: 5, - reorgPeriod: getReorgPeriod('nero'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0xb86f872df37f11f33acbe75b6ed208b872b57183'], - }, - 'nero', - ), - }, - // subtensor: { - // interval: 5, - // reorgPeriod: getReorgPeriod('subtensor'), - // validators: validatorsConfig( - // { - // [Contexts.Hyperlane]: ['0xd5f8196d7060b85bea491f0b52a671e05f3d10a2'], - // }, - // 'subtensor', - // ), - // }, - xpla: { - interval: 5, - reorgPeriod: getReorgPeriod('xpla'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0xc11cba01d67f2b9f0288c4c8e8b23c0eca03f26e'], - }, - 'xpla', - ), - }, - abstract: { interval: 5, reorgPeriod: getReorgPeriod('abstract'), @@ -1447,17 +1204,6 @@ export const validatorChainConfig = ( 'matchain', ), }, - unitzero: { - interval: 5, - reorgPeriod: getReorgPeriod('unitzero'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x18818e3ad2012728465d394f2e3c0ea2357ae9c5'], - }, - 'unitzero', - ), - }, - sonicsvm: { interval: 5, reorgPeriod: getReorgPeriod('sonicsvm'), @@ -1468,7 +1214,6 @@ export const validatorChainConfig = ( 'sonicsvm', ), }, - berachain: { interval: 5, reorgPeriod: getReorgPeriod('berachain'), @@ -1479,17 +1224,6 @@ export const validatorChainConfig = ( 'berachain', ), }, - - bouncebit: { - interval: 5, - reorgPeriod: getReorgPeriod('bouncebit'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0xaf38612d1e79ec67320d21c5f7e92419427cd154'], - }, - 'bouncebit', - ), - }, arcadia: { interval: 5, reorgPeriod: getReorgPeriod('arcadia'), @@ -1550,7 +1284,6 @@ export const validatorChainConfig = ( 'subtensor', ), }, - hyperevm: { interval: 5, reorgPeriod: getReorgPeriod('hyperevm'), @@ -1564,7 +1297,6 @@ export const validatorChainConfig = ( 'hyperevm', ), }, - plume: { interval: 5, reorgPeriod: getReorgPeriod('plume'), @@ -1575,7 +1307,6 @@ export const validatorChainConfig = ( 'plume', ), }, - coti: { interval: 5, reorgPeriod: getReorgPeriod('coti'), @@ -1586,16 +1317,6 @@ export const validatorChainConfig = ( 'coti', ), }, - deepbrainchain: { - interval: 5, - reorgPeriod: getReorgPeriod('deepbrainchain'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x3825ea1e0591b58461cc4aa34867668260c0e6a8'], - }, - 'deepbrainchain', - ), - }, nibiru: { interval: 5, reorgPeriod: getReorgPeriod('nibiru'), @@ -1626,7 +1347,6 @@ export const validatorChainConfig = ( 'reactive', ), }, - milkyway: { interval: 5, reorgPeriod: getReorgPeriod('milkyway'), @@ -1637,7 +1357,6 @@ export const validatorChainConfig = ( 'milkyway', ), }, - hashkey: { interval: 5, reorgPeriod: getReorgPeriod('hashkey'), @@ -1648,7 +1367,6 @@ export const validatorChainConfig = ( 'hashkey', ), }, - infinityvmmainnet: { interval: 5, reorgPeriod: getReorgPeriod('infinityvmmainnet'), @@ -1659,7 +1377,6 @@ export const validatorChainConfig = ( 'infinityvmmainnet', ), }, - ontology: { interval: 5, reorgPeriod: getReorgPeriod('ontology'), @@ -1670,7 +1387,6 @@ export const validatorChainConfig = ( 'ontology', ), }, - game7: { interval: 5, reorgPeriod: getReorgPeriod('game7'), @@ -1681,7 +1397,6 @@ export const validatorChainConfig = ( 'game7', ), }, - fluence: { interval: 5, reorgPeriod: getReorgPeriod('fluence'), @@ -1692,7 +1407,6 @@ export const validatorChainConfig = ( 'fluence', ), }, - peaq: { interval: 5, reorgPeriod: getReorgPeriod('peaq'), @@ -1703,7 +1417,6 @@ export const validatorChainConfig = ( 'peaq', ), }, - svmbnb: { interval: 5, reorgPeriod: getReorgPeriod('svmbnb'), @@ -1714,7 +1427,6 @@ export const validatorChainConfig = ( 'svmbnb', ), }, - miraclechain: { interval: 5, reorgPeriod: getReorgPeriod('miraclechain'), @@ -1735,7 +1447,6 @@ export const validatorChainConfig = ( 'kyve', ), }, - botanix: { interval: 5, reorgPeriod: getReorgPeriod('botanix'), @@ -1756,7 +1467,6 @@ export const validatorChainConfig = ( 'katana', ), }, - solaxy: { interval: 5, reorgPeriod: getReorgPeriod('solaxy'), @@ -1767,7 +1477,6 @@ export const validatorChainConfig = ( 'solaxy', ), }, - tac: { interval: 5, reorgPeriod: getReorgPeriod('tac'), @@ -1778,7 +1487,6 @@ export const validatorChainConfig = ( 'tac', ), }, - galactica: { interval: 5, reorgPeriod: getReorgPeriod('galactica'), @@ -1789,7 +1497,6 @@ export const validatorChainConfig = ( 'galactica', ), }, - xrplevm: { interval: 5, reorgPeriod: getReorgPeriod('xrplevm'), @@ -1810,5 +1517,95 @@ export const validatorChainConfig = ( 'noble', ), }, + celestia: { + interval: 5, + reorgPeriod: getReorgPeriod('celestia'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x6dbc192c06907784fb0af0c0c2d8809ea50ba675'], + }, + 'celestia', + ), + }, + mitosis: { + interval: 5, + reorgPeriod: getReorgPeriod('mitosis'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x3b3eb808d90a4e19bb601790a6b6297812d6a61f'], + }, + 'mitosis', + ), + }, + radix: { + interval: 5, + reorgPeriod: getReorgPeriod('radix'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0xa715a7cd97f68caeedb7be64f9e1da10f8ffafb4'], + }, + 'radix', + ), + }, + pulsechain: { + interval: 5, + reorgPeriod: getReorgPeriod('pulsechain'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0xa73fc7ebb2149d9c6992ae002cb1849696be895b'], + }, + 'pulsechain', + ), + }, + plasma: { + interval: 5, + reorgPeriod: getReorgPeriod('plasma'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x4ba900a8549fe503bca674114dc98a254637fc2c'], + }, + 'plasma', + ), + }, + electroneum: { + interval: 5, + reorgPeriod: getReorgPeriod('electroneum'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x32917f0a38c60ff5b1c4968cb40bc88b14ef0d83'], + }, + 'electroneum', + ), + }, + zerogravity: { + interval: 5, + reorgPeriod: getReorgPeriod('zerogravity'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0xc37e7dad064c11d7ecfc75813a4d8d649d797275'], + }, + 'zerogravity', + ), + }, + sova: { + interval: 5, + reorgPeriod: getReorgPeriod('sova'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x1763d686c45df79f6d5f63564255546b08cb206c'], + }, + 'sova', + ), + }, + mantra: { + interval: 5, + reorgPeriod: getReorgPeriod('mantra'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x89b8064e29f125e896f6081ebb77090c46bca9cd'], + }, + 'mantra', + ), + }, }; }; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumBaseEthereumRadixUSDCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumBaseEthereumRadixUSDCWarpConfig.ts new file mode 100644 index 00000000000..0cc6ae81ebc --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumBaseEthereumRadixUSDCWarpConfig.ts @@ -0,0 +1,93 @@ +import { + ChainMap, + HypTokenRouterConfig, + IsmType, + OwnableConfig, + RoutingIsmConfig, + TokenType, + buildAggregationIsmConfigs, + defaultMultisigConfigs, +} from '@hyperlane-xyz/sdk'; + +import { + RouterConfigWithoutOwner, + tokens, +} from '../../../../../src/config/warp.js'; + +import { getUSDCRebalancingBridgesConfigFor } from './utils.js'; + +const getIsm = (local: keyof typeof owners): RoutingIsmConfig => { + return { + type: IsmType.FALLBACK_ROUTING, + owner: owners[local], + domains: buildAggregationIsmConfigs( + local, + ['radix'], + defaultMultisigConfigs, + ), + }; +}; + +const owners = { + ethereum: '0xA365Bf3Da1f1B01E2a80f9261Ec717B305b2Eb8F', + arbitrum: '0xA365Bf3Da1f1B01E2a80f9261Ec717B305b2Eb8F', + base: '0xA365Bf3Da1f1B01E2a80f9261Ec717B305b2Eb8F', + bnb: '0xA365Bf3Da1f1B01E2a80f9261Ec717B305b2Eb8F', + radix: 'account_rdx1280taxhhnuek02y59yapsg4kjtux954qkyufpwmy4dlfcxdrjzr7fj', +}; + +export const getArbitrumBaseEthereumRadixUSDCWarpConfig = async ( + routerConfig: ChainMap, + abacusWorksEnvOwnerConfig: ChainMap, +): Promise> => { + const rebalancingConfig = getUSDCRebalancingBridgesConfigFor( + Object.keys(owners), + ); + + const ethereum: HypTokenRouterConfig = { + ...routerConfig.ethereum, + decimals: 6, + interchainSecurityModule: getIsm('ethereum'), + owner: owners.ethereum, + type: TokenType.collateral, + token: tokens.ethereum.USDC, + ...rebalancingConfig.ethereum, + }; + + const arbitrum: HypTokenRouterConfig = { + ...routerConfig.arbitrum, + decimals: 6, + interchainSecurityModule: getIsm('arbitrum'), + owner: owners.arbitrum, + type: TokenType.collateral, + token: tokens.arbitrum.USDC, + ...rebalancingConfig.arbitrum, + }; + + const base: HypTokenRouterConfig = { + ...routerConfig.base, + decimals: 6, + interchainSecurityModule: getIsm('base'), + owner: owners.base, + type: TokenType.collateral, + token: tokens.base.USDC, + ...rebalancingConfig.base, + }; + + const radix: HypTokenRouterConfig = { + ...routerConfig.radix, + owner: owners.radix, + type: TokenType.synthetic, + symbol: 'hUSDC', + name: 'Hyperlane USD Coin', + gas: 30_000_000, + decimals: 6, + }; + + return { + arbitrum, + base, + ethereum, + radix, + }; +}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumNeutronTiaWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumNeutronTiaWarpConfig.ts index e1dc0c5ab17..41451e6d930 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumNeutronTiaWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumNeutronTiaWarpConfig.ts @@ -21,6 +21,7 @@ export const getArbitrumNeutronTiaWarpConfig = async ( token: 'ibc/773B4D0A3CD667B2275D5A4A7A2F0909C0BA0F4059C0B9181E680DDF4965DCC7', foreignDeployment: neutronRouter, + decimals: 6, gas: 600000, }; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanaTRUMPWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanaTRUMPWarpConfig.ts index 12d31e96811..c7b01299e11 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanaTRUMPWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBaseSolanaTRUMPWarpConfig.ts @@ -28,7 +28,9 @@ export const getTRUMPWarpConfig = async ( token: '6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN', owner: DEPLOYER, foreignDeployment: '21tAY4poz2VXvghqdSQpn9j7gYravQmGpuQi8pHPx9DS', + decimals: 6, gas: 64000, + scale: 1_000_000_000_000, }, base: { ...routerConfig.base, diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBscMilkywayMILKWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBscMilkywayMILKWarpConfig.ts index 45cb9525b5b..b592c3bddcf 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBscMilkywayMILKWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBscMilkywayMILKWarpConfig.ts @@ -25,6 +25,7 @@ export const getBscMilkywayMILKWarpConfig = async ( ...routerConfig.milkyway, owner: safeOwners.milkyway, type: TokenType.native, + decimals: 6, foreignDeployment: '0x726f757465725f61707000000000000000000000000000010000000000000000', }, diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBsquaredUBTCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBsquaredUBTCWarpConfig.ts index d88847f62c4..4d81209dd38 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getBsquaredUBTCWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getBsquaredUBTCWarpConfig.ts @@ -18,6 +18,7 @@ const safeOwners: ChainMap
= { boba: '0x207FfFa7325fC5d0362aB01605D84B268b61888f', soneium: '0x8433e6e9183B5AAdaf4b52c624B963D95956e3C9', nibiru: '0x2D439F9B80F7f5010A577B25E1Ec9d84C4e69e4E', + ethereum: '0x47ECa4cbfB1a4849Eea25892d1B15aC3F02f24Ed', }; export const getBsquaredUBTCWarpConfig = async ( @@ -27,6 +28,9 @@ export const getBsquaredUBTCWarpConfig = async ( mailbox: routerConfig.boba.mailbox, owner: safeOwners.boba, type: TokenType.synthetic, + decimals: 18, + name: 'uBTC', + symbol: 'uBTC', }; const bsquared: HypTokenRouterConfig = { @@ -34,6 +38,7 @@ export const getBsquaredUBTCWarpConfig = async ( owner: safeOwners.bsquared, type: TokenType.collateral, token: tokens.bsquared.uBTC, + decimals: 18, }; const nibiru: HypTokenRouterConfig = { @@ -49,12 +54,27 @@ export const getBsquaredUBTCWarpConfig = async ( mailbox: routerConfig.soneium.mailbox, owner: safeOwners.soneium, type: TokenType.synthetic, + decimals: 18, + name: 'uBTC', + symbol: 'uBTC', }; const swell: HypTokenRouterConfig = { mailbox: routerConfig.swell.mailbox, owner: safeOwners.swell, type: TokenType.synthetic, + decimals: 18, + name: 'uBTC', + symbol: 'uBTC', + }; + + const ethereum: HypTokenRouterConfig = { + mailbox: routerConfig.ethereum.mailbox, + owner: safeOwners.ethereum, + type: TokenType.synthetic, + decimals: 18, + name: 'uBTC', + symbol: 'uBTC', }; return { @@ -63,6 +83,7 @@ export const getBsquaredUBTCWarpConfig = async ( nibiru, soneium, swell, + ethereum, }; }; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getCCTPConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getCCTPConfig.ts index 47c1a9604e2..7ee9a7f2984 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getCCTPConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getCCTPConfig.ts @@ -10,7 +10,7 @@ import { import { assert } from '@hyperlane-xyz/utils'; import { RouterConfigWithoutOwner } from '../../../../../src/config/warp.js'; -import { awIcas } from '../../governance/ica/aw.js'; +import { awIcasLegacy } from '../../governance/ica/_awLegacy.js'; import { awSafes } from '../../governance/safe/aw.js'; import { messageTransmitterAddresses, @@ -30,6 +30,17 @@ export const CCTP_CHAINS = [ 'unichain', ] as const; +// TODO: remove this once the route has been updated to be owned by non-legacy ownership +const owners: Record<(typeof CCTP_CHAINS)[number], string> = { + arbitrum: '0xaB547e6cde21a5cC3247b8F80e6CeC3a030FAD4A', + avalanche: awIcasLegacy['avalanche'], + base: '0xA6D9Aa3878423C266480B5a7cEe74917220a1ad2', + ethereum: awSafes['ethereum'], + optimism: '0x20E9C1776A9408923546b64D5ea8BfdF0B7319d6', + polygon: awIcasLegacy['polygon'], + unichain: awIcasLegacy['unichain'], +}; + export const getCCTPWarpConfig = async ( routerConfig: ChainMap, _abacusWorksEnvOwnerConfig: ChainMap, @@ -37,7 +48,11 @@ export const getCCTPWarpConfig = async ( ): Promise> => { return Object.fromEntries( CCTP_CHAINS.map((chain) => { - const owner = awIcas[chain] ?? awSafes[chain]; + // TODO: restore after route has been updated + // const owner = awIcasLegacy[chain] ?? awSafes[chain]; + + const owner = owners[chain]; + assert(owner, `Owner not found for ${chain}`); const config: HypTokenRouterConfig = { owner, @@ -47,7 +62,6 @@ export const getCCTPWarpConfig = async ( messageTransmitter: messageTransmitterAddresses[chain], tokenMessenger: tokenMessengerAddresses[chain], urls: [`${SERVICE_URL}/cctp/getCctpAttestation`], - contractVersion: '8.1.0', }; return [chain, config]; }), @@ -62,7 +76,7 @@ const safeSubmitter: SubmitterMetadata = { safeAddress: icaOwner, }; -const icaChains = Object.keys(awIcas); +const icaChains = Object.keys(awIcasLegacy); export const getCCTPStrategyConfig = (): ChainSubmissionStrategy => { const submitterMetadata = CCTP_CHAINS.map((chain): SubmitterMetadata => { diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionETHWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionETHWarpConfig.ts index 55153b258b7..8c1066685a0 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionETHWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionETHWarpConfig.ts @@ -28,6 +28,7 @@ export const getEthereumVictionETHWarpConfig = async ( ...routerConfig.ethereum, ...abacusWorksEnvOwnerConfig.ethereum, type: TokenType.native, + decimals: 18, gas: 65_000, interchainSecurityModule: ethers.constants.AddressZero, hook: '0xb87ac8ea4533ae017604e44470f7c1e550ac6f10', diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionUSDCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionUSDCWarpConfig.ts index 23676804e7e..b219bd5b25f 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionUSDCWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionUSDCWarpConfig.ts @@ -32,6 +32,7 @@ export const getEthereumVictionUSDCWarpConfig = async ( ...abacusWorksEnvOwnerConfig.ethereum, type: TokenType.collateral, token: tokens.ethereum.USDC, + decimals: 6, gas: 65_000, interchainSecurityModule: ethers.constants.AddressZero, hook: '0xb87ac8ea4533ae017604e44470f7c1e550ac6f10', diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionUSDTWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionUSDTWarpConfig.ts index 30f8c9cdb9f..c6d24a22ccc 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionUSDTWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getEthereumVictionUSDTWarpConfig.ts @@ -32,6 +32,7 @@ export const getEthereumVictionUSDTWarpConfig = async ( ...abacusWorksEnvOwnerConfig.ethereum, type: TokenType.collateral, token: tokens.ethereum.USDT, + decimals: 6, gas: 65_000, interchainSecurityModule: ethers.constants.AddressZero, hook: '0xb87ac8ea4533ae017604e44470f7c1e550ac6f10', diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getLumiaUSDCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getLumiaUSDCWarpConfig.ts index e945551b28f..c86dbd4f9bd 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getLumiaUSDCWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getLumiaUSDCWarpConfig.ts @@ -49,8 +49,6 @@ const submitterConfig = objMap( }, ); -console.log(JSON.stringify(submitterConfig, null, 2)); - export const getLumiaUSDCWarpConfig = async ( routerConfig: ChainMap, ): Promise> => { diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getMantapacificNeutronTiaWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getMantapacificNeutronTiaWarpConfig.ts index 949c102c94d..879b6754416 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getMantapacificNeutronTiaWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getMantapacificNeutronTiaWarpConfig.ts @@ -19,6 +19,7 @@ export const getMantapacificNeutronTiaWarpConfig = async ( foreignDeployment: neutronRouter, owner: abacusWorksEnvOwnerConfig.neutron.owner, type: TokenType.native, + decimals: 6, gas: 0, }; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getMitosisMITOWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getMitosisMITOWarpConfig.ts new file mode 100644 index 00000000000..aa5612880dd --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getMitosisMITOWarpConfig.ts @@ -0,0 +1,106 @@ +import { + ChainMap, + HookType, + HypTokenRouterConfig, + IsmType, + TokenType, + buildAggregationIsmConfigs, + defaultMultisigConfigs, +} from '@hyperlane-xyz/sdk'; + +import { RouterConfigWithoutOwner } from '../../../../../src/config/warp.js'; + +// TODO: update with new owners once they are updated +const mitosisOwner = '0x8f51e8e0Ce90CC1B6E60a3E434c7E63DeaD13612'; +const mitosisTimelockOwner = '0x1248163200964459971c7cC9631909132AD28C27'; +const bscTimelockOwner = '0x1248163214D9A0D6F02932A245370D3fD9613A82'; + +export const getMitosisMITOWarpConfig = async ( + routerConfig: ChainMap, +): Promise> => { + const mitosis: HypTokenRouterConfig = { + ...routerConfig.mitosis, + owner: mitosisTimelockOwner, + type: TokenType.native, + ownerOverrides: { + proxyAdmin: mitosisOwner, + }, + hook: { + type: HookType.AGGREGATION, + hooks: [ + { + type: HookType.MAILBOX_DEFAULT, + }, + { + type: HookType.PAUSABLE, + owner: mitosisTimelockOwner, + paused: false, + }, + ], + }, + interchainSecurityModule: { + type: IsmType.AGGREGATION, + threshold: 2, + modules: [ + { + type: IsmType.FALLBACK_ROUTING, + owner: mitosisOwner, + domains: {}, + }, + { + type: IsmType.PAUSABLE, + owner: mitosisOwner, + paused: false, + }, + ], + }, + }; + + const bsc: HypTokenRouterConfig = { + ...routerConfig.bsc, + owner: bscTimelockOwner, + ownerOverrides: { + proxyAdmin: mitosisOwner, + }, + type: TokenType.synthetic, + symbol: 'MITO', + hook: { + type: HookType.AGGREGATION, + hooks: [ + { + type: HookType.MAILBOX_DEFAULT, + }, + { + type: HookType.PAUSABLE, + owner: bscTimelockOwner, + paused: false, + }, + ], + }, + interchainSecurityModule: { + type: IsmType.AGGREGATION, + threshold: 2, + modules: [ + { + type: IsmType.FALLBACK_ROUTING, + owner: mitosisOwner, + domains: buildAggregationIsmConfigs( + 'bsc', + ['mitosis'], + defaultMultisigConfigs, + ), + }, + { + type: IsmType.PAUSABLE, + owner: mitosisOwner, + paused: false, + }, + ], + }, + }; + + return { + mitosis, + bsc, + }; +}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getParadexUSDCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getParadexUSDCWarpConfig.ts index eb81d18f390..ede33c677b4 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getParadexUSDCWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getParadexUSDCWarpConfig.ts @@ -1,9 +1,7 @@ -import { CONTRACTS_PACKAGE_VERSION } from '@hyperlane-xyz/core'; import { ChainMap, HypTokenRouterConfig, TokenType } from '@hyperlane-xyz/sdk'; import { assert } from '@hyperlane-xyz/utils'; import { RouterConfigWithoutOwner } from '../../../../../src/config/warp.js'; -import { DEPLOYER } from '../../owners.js'; import { usdcTokenAddresses } from '../cctp.js'; import { SEALEVEL_WARP_ROUTE_HANDLER_GAS_AMOUNT, @@ -49,14 +47,14 @@ const syntheticChain: DeploymentChain = 'mode'; // Waiting on the addresses for the final ownership config const ownersByChain: Record = { - arbitrum: DEPLOYER, - base: DEPLOYER, - ethereum: DEPLOYER, - mode: DEPLOYER, - paradex: '0x041e326bf455461926b9c334d02039cb0d4f09698c5158ef8d939b33b240a0e0', - solanamainnet: '9bRSUPjfS3xS6n5EfkJzHFTRDa4AHLda8BU2pP4HoWnf', + arbitrum: '0xFF57A3bB6465501c993acF8f3b29125a862661C0', + base: '0xFF57A3bB6465501c993acF8f3b29125a862661C0', + ethereum: '0xFF57A3bB6465501c993acF8f3b29125a862661C0', + mode: '0xFF57A3bB6465501c993acF8f3b29125a862661C0', + paradex: '0x00395a1eebf43d06be83684da623c4c2ab8e1ea4a89dfa71ee04677b6e19a428', + solanamainnet: 'HBPwc1dSuaJCEwWkJvfeWUqJguFqPTVaggfDGssc3LVt', starknet: - '0x06ae465e0c05735820a75500c40cb4dabbe46ebf1f1665f9ba3f9a7dcc78a6d1', + '0x00af66284c430cc46fd5048312ef134e35141d4499f9450f2e9eff170c7dde08', }; export const getParadexUSDCWarpConfig = async ( diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getPulsechainUSDCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getPulsechainUSDCWarpConfig.ts new file mode 100644 index 00000000000..797dfbd4f95 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getPulsechainUSDCWarpConfig.ts @@ -0,0 +1,106 @@ +import { + ChainMap, + ChainName, + HypTokenRouterConfig, + TokenType, +} from '@hyperlane-xyz/sdk'; +import { Address, assert } from '@hyperlane-xyz/utils'; + +import { RouterConfigWithoutOwner } from '../../../../../src/config/warp.js'; +import { usdcTokenAddresses } from '../cctp.js'; + +import { getUSDCRebalancingBridgesConfigFor } from './utils.js'; + +type DeploymentChains = { + arbitrum: T; + base: T; + polygon: T; + pulsechain: T; + ethereum: T; +}; + +type SyntheticChain = Extract, 'pulsechain'>; + +type CollateralChain = Exclude, 'pulsechain'>; + +// SAFE wallets from the team +const ownersByChain: DeploymentChains
= { + arbitrum: '0x9adBd244557F59eE8F5633D2d2e2c0abec8FCCC2', + base: '0x9adBd244557F59eE8F5633D2d2e2c0abec8FCCC2', + polygon: '0x9adBd244557F59eE8F5633D2d2e2c0abec8FCCC2', + ethereum: '0x9adBd244557F59eE8F5633D2d2e2c0abec8FCCC2', + pulsechain: '0x703cf58975B14142eD0Ba272555789610c85520c', +}; + +const rebalancingConfigByChain = getUSDCRebalancingBridgesConfigFor( + Object.keys(ownersByChain), +); + +const getRebalanceableCollateralTokenConfigForChain = ( + currentChain: CollateralChain, + routerConfigByChain: ChainMap, +): HypTokenRouterConfig => { + const owner = ownersByChain[currentChain]; + assert(owner, `Owner not found for chain ${currentChain}`); + + const usdcTokenAddress = usdcTokenAddresses[currentChain]; + assert( + usdcTokenAddress, + `USDC token address not found for chain ${currentChain}`, + ); + + const currentRebalancingConfig = rebalancingConfigByChain[currentChain]; + assert( + currentRebalancingConfig, + `Rebalancing config not found for chain ${currentChain}`, + ); + + const { allowedRebalancers, allowedRebalancingBridges } = + currentRebalancingConfig; + + return { + type: TokenType.collateral, + token: usdcTokenAddress, + mailbox: routerConfigByChain[currentChain].mailbox, + owner, + allowedRebalancers, + allowedRebalancingBridges, + }; +}; + +const getSyntheticTokenConfigForChain = ( + currentChain: SyntheticChain, + routerConfigByChain: ChainMap, +): HypTokenRouterConfig => { + const owner = ownersByChain[currentChain]; + assert(owner, `Owner not found for chain ${currentChain}`); + + return { + type: TokenType.synthetic, + mailbox: routerConfigByChain[currentChain].mailbox, + owner, + }; +}; + +export const getPulsechainUSDCWarpConfig = async ( + routerConfig: ChainMap, +): Promise> => { + const deployConfig: DeploymentChains = { + arbitrum: getRebalanceableCollateralTokenConfigForChain( + 'arbitrum', + routerConfig, + ), + base: getRebalanceableCollateralTokenConfigForChain('base', routerConfig), + ethereum: getRebalanceableCollateralTokenConfigForChain( + 'ethereum', + routerConfig, + ), + polygon: getRebalanceableCollateralTokenConfigForChain( + 'polygon', + routerConfig, + ), + pulsechain: getSyntheticTokenConfigForChain('pulsechain', routerConfig), + }; + + return deployConfig; +}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getSubtensorUSDCWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getSubtensorUSDCWarpConfig.ts index 6127988a10d..09b079cf3a8 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getSubtensorUSDCWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getSubtensorUSDCWarpConfig.ts @@ -2,7 +2,7 @@ import { ChainMap, HypTokenRouterConfig, TokenType } from '@hyperlane-xyz/sdk'; import { assert } from '@hyperlane-xyz/utils'; import { RouterConfigWithoutOwner } from '../../../../../src/config/warp.js'; -import { awIcas } from '../../governance/ica/aw.js'; +import { awIcasLegacy } from '../../governance/ica/_awLegacy.js'; import { awSafes } from '../../governance/safe/aw.js'; import { chainOwners } from '../../owners.js'; import { usdcTokenAddresses } from '../cctp.js'; @@ -34,7 +34,7 @@ export const getSubtensorUSDCWarpConfig = async ( deploymentChains.map( (currentChain): [DeploymentChain, HypTokenRouterConfig] => { const owner = - awIcas[currentChain] ?? + awIcasLegacy[currentChain] ?? awSafes[currentChain] ?? chainOwners[currentChain].owner; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getoUSDTTokenWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getoUSDTTokenWarpConfig.ts index c518386699d..a34bdb014bc 100644 --- a/typescript/infra/config/environments/mainnet3/warp/configGetters/getoUSDTTokenWarpConfig.ts +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getoUSDTTokenWarpConfig.ts @@ -8,19 +8,18 @@ import { IsmConfig, IsmType, TokenType, - XERC20LimitConfig, XERC20TokenExtraBridgesLimits, + XERC20Type, + XERC20VSLimitConfig, } from '@hyperlane-xyz/sdk'; import { Address, assert } from '@hyperlane-xyz/utils'; import { RouterConfigWithoutOwner } from '../../../../../src/config/warp.js'; -import { awIcas } from '../../governance/ica/aw.js'; -import { awSafes } from '../../governance/safe/aw.js'; -import { ousdtSafes } from '../../governance/safe/ousdt.js'; +import { awTimelocks } from '../../governance/timelock/aw.js'; import { DEPLOYER } from '../../owners.js'; // Environment-independent configuration -const deploymentChains = [ +export const deploymentChains = [ 'ethereum', 'celo', 'optimism', @@ -44,6 +43,7 @@ const deploymentChains = [ 'hashkey', 'swell', 'botanix', + 'zerogravity', ] as const; const supportedCCIPChains = ['base', 'mode', 'optimism']; const xERC20LockboxChains: oUSDTTokenChainName[] = ['celo', 'ethereum']; @@ -83,6 +83,7 @@ const productionBufferCapByChain: TypedoUSDTTokenChainMap = { hashkey: lowerBufferCap, swell: middleBufferCap, botanix: middleBufferCap, + zerogravity: middleBufferCap, }; const productionDefaultRateLimitPerSecond = '5000000000'; // 5k/s = 5 * 10^3 ^ 10^6 const middleRateLimitPerSecond = '2000000000'; // 2k/s = 2 * 10^3 ^ 10^6 @@ -111,24 +112,18 @@ const productionRateLimitByChain: TypedoUSDTTokenChainMap = { hashkey: lowerRateLimitPerSecond, swell: middleRateLimitPerSecond, botanix: middleRateLimitPerSecond, + zerogravity: middleRateLimitPerSecond, }; -const ICA_OWNED_CHAINS: oUSDTTokenChainName[] = [ - 'bob', - 'hashkey', - 'swell', - 'botanix', -]; -const DPL_OWNED_CHAINS: oUSDTTokenChainName[] = []; +const DPL_OWNED_CHAINS: oUSDTTokenChainName[] = ['zerogravity']; const productionOwnerByChain: TypedoUSDTTokenChainMap = deploymentChains.reduce((acc, chain) => { if (DPL_OWNED_CHAINS.includes(chain as oUSDTTokenChainName)) { acc[chain] = DEPLOYER; - } else if (ICA_OWNED_CHAINS.includes(chain as oUSDTTokenChainName)) { - assert(awIcas[chain], `ICA for ${chain} not found`); - acc[chain] = awIcas[chain]; } else { - acc[chain] = ousdtSafes[chain] ?? awSafes[chain] ?? DEPLOYER; + const timelock = awTimelocks[chain]; + assert(timelock, `Timelock for ${chain} not found`); + acc[chain] = timelock; } return acc; }, {} as TypedoUSDTTokenChainMap); @@ -228,6 +223,10 @@ const productionOwnerOverridesByChain: TypedoUSDTTokenChainMap< collateralToken: productionOwnerByChain.botanix, collateralProxyAdmin: productionOwnerByChain.botanix, }, + zerogravity: { + collateralToken: productionOwnerByChain.zerogravity, + collateralProxyAdmin: productionOwnerByChain.zerogravity, + }, }; const productionAmountRoutingThreshold = 250000000000; // 250k = 250 * 10^3 ^ 10^6 @@ -238,7 +237,8 @@ const productionCeloXERC20LockboxAddress = const productionXERC20TokenAddress = '0x1217BfE6c773EEC6cc4A38b5Dc45B92292B6E189'; -const zeroLimits: XERC20LimitConfig = { +const zeroLimits: XERC20VSLimitConfig = { + type: XERC20Type.Velo, bufferCap: '0', rateLimitPerSecond: '0', }; @@ -252,9 +252,11 @@ const productionCCIPTokenPoolAddresses: ChainMap
= { bob: '0xAFEd606Bd2CAb6983fC6F10167c98aaC2173D77f', hashkey: '0x55aeb80Aa6Ab34aA83E1F387903F8Bb2Aa9e2F2d', botanix: '0x0EEFa8b75587bcD4A909a0F3c36180D4441481a0', + zerogravity: '0xd7502CaBdb70c79382deF58FB6df3CdA69cb2A1b', }; -const productionCCIPTokenPoolLimits: XERC20LimitConfig = { +const productionCCIPTokenPoolLimits: XERC20VSLimitConfig = { + type: XERC20Type.Velo, bufferCap: upperBufferCap, rateLimitPerSecond: productionDefaultRateLimitPerSecond, }; @@ -264,6 +266,7 @@ const productionExtraBridges: ChainMap = { { lockbox: productionEthereumXERC20LockboxAddress, limits: { + type: XERC20Type.Velo, bufferCap: productionBufferCapByChain.ethereum, rateLimitPerSecond: productionRateLimitByChain.ethereum, }, @@ -335,6 +338,12 @@ const productionExtraBridges: ChainMap = { limits: productionCCIPTokenPoolLimits, }, ], + zerogravity: [ + { + lockbox: productionCCIPTokenPoolAddresses.zerogravity, + limits: productionCCIPTokenPoolLimits, + }, + ], }; const productionXERC20AddressesByChain: TypedoUSDTTokenChainMap
= { @@ -361,6 +370,7 @@ const productionXERC20AddressesByChain: TypedoUSDTTokenChainMap
= { hashkey: productionXERC20TokenAddress, swell: productionXERC20TokenAddress, botanix: productionXERC20TokenAddress, + zerogravity: productionXERC20TokenAddress, }; // Staging @@ -389,6 +399,7 @@ const stagingBufferCapByChain: TypedoUSDTTokenChainMap = { hashkey: stagingDefaultBufferCap, swell: stagingDefaultBufferCap, botanix: stagingDefaultBufferCap, + zerogravity: stagingDefaultBufferCap, }; const stagingDefaultRateLimitPerSecond = '120000000'; const stagingRateLimitByChain: TypedoUSDTTokenChainMap = { @@ -415,6 +426,7 @@ const stagingRateLimitByChain: TypedoUSDTTokenChainMap = { hashkey: stagingDefaultRateLimitPerSecond, swell: stagingDefaultRateLimitPerSecond, botanix: stagingDefaultRateLimitPerSecond, + zerogravity: stagingDefaultRateLimitPerSecond, }; const stagingOwnerByChain: TypedoUSDTTokenChainMap = @@ -453,6 +465,7 @@ const stagingXERC20AddressesByChain: TypedoUSDTTokenChainMap
= { hashkey: stagingXERC20TokenAddress, swell: stagingXERC20TokenAddress, botanix: stagingXERC20TokenAddress, + zerogravity: stagingXERC20TokenAddress, }; const stagingExtraBridges: ChainMap = { @@ -460,6 +473,7 @@ const stagingExtraBridges: ChainMap = { { lockbox: stagingEthereumXERC20LockboxAddress, limits: { + type: XERC20Type.Velo, bufferCap: stagingBufferCapByChain.ethereum, rateLimitPerSecond: stagingRateLimitByChain.ethereum, }, @@ -584,6 +598,7 @@ function generateoUSDTTokenConfig( token: xERC20AddressesByChain[chain], xERC20: { warpRouteLimits: { + type: XERC20Type.Velo, rateLimitPerSecond: rateLimitPerSecondPerChain[chain], bufferCap: bufferCapPerChain[chain], }, diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getoXAUTTokenWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getoXAUTTokenWarpConfig.ts new file mode 100644 index 00000000000..794f148c3af --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getoXAUTTokenWarpConfig.ts @@ -0,0 +1,150 @@ +import { + ChainMap, + HypTokenRouterConfig, + OwnableConfig, + TokenType, + XERC20LimitsTokenConfig, + XERC20Type, +} from '@hyperlane-xyz/sdk'; +import { objFilter } from '@hyperlane-xyz/utils'; + +import { RouterConfigWithoutOwner } from '../../../../../src/config/warp.js'; +import { getGnosisSafeSubmitterStrategyConfigGenerator } from '../../../utils.js'; +import { awSafes } from '../../governance/safe/aw.js'; + +const chainsToDeploy = ['avalanche', 'base', 'celo', 'ethereum', 'worldchain']; + +const EXTRA_BRIDGE_MINT_LIMIT = '10000000000000'; +const EXTRA_BRIDGE_BURN_LIMIT = '10000000000000'; +const WARP_ROUTE_MINT_LIMIT = '20000000000000'; +const WARP_ROUTE_BURN_LIMIT = '20000000000000'; +const ownerMap: ChainMap = objFilter( + awSafes, + (chain, _safe): _safe is string => chainsToDeploy.includes(chain), +); +const tokenMetadata: ChainMap = { + avalanche: { + type: TokenType.XERC20, + token: '0x30974f73A4ac9E606Ed80da928e454977ac486D2', + xERC20: { + extraBridges: [ + { + // Chainlink Pool + lockbox: '0x18e25Ac83477d7013D43174508B7AE7EC2CE2e08', + limits: { + mint: EXTRA_BRIDGE_MINT_LIMIT, + burn: EXTRA_BRIDGE_BURN_LIMIT, + type: XERC20Type.Standard, + }, + }, + ], + warpRouteLimits: { + mint: WARP_ROUTE_MINT_LIMIT, + burn: WARP_ROUTE_BURN_LIMIT, + type: XERC20Type.Standard, + }, + }, + }, + base: { + type: TokenType.XERC20, + token: '0x30974f73A4ac9E606Ed80da928e454977ac486D2', + xERC20: { + extraBridges: [ + { + // Chainlink Pool + lockbox: '0xaF35bef911A5e0be90987cE5070d7c9CbF5cFd3c', + limits: { + mint: EXTRA_BRIDGE_MINT_LIMIT, + burn: EXTRA_BRIDGE_BURN_LIMIT, + type: XERC20Type.Standard, + }, + }, + ], + warpRouteLimits: { + mint: WARP_ROUTE_MINT_LIMIT, + burn: WARP_ROUTE_BURN_LIMIT, + type: XERC20Type.Standard, + }, + }, + }, + celo: { + type: TokenType.XERC20, + token: '0x30974f73A4ac9E606Ed80da928e454977ac486D2', + xERC20: { + warpRouteLimits: { + mint: WARP_ROUTE_MINT_LIMIT, + burn: WARP_ROUTE_BURN_LIMIT, + type: XERC20Type.Standard, + }, + }, + }, + ethereum: { + type: TokenType.XERC20Lockbox, + token: '0x0797c6f55f5c9005996A55959A341018cF69A963', + xERC20: { + extraBridges: [ + { + // Chainlink Pool + lockbox: '0x04db9b1D7f52cB288b95B4934a1fA688F6d0cBc3', + limits: { + mint: EXTRA_BRIDGE_MINT_LIMIT, + burn: EXTRA_BRIDGE_BURN_LIMIT, + type: XERC20Type.Standard, + }, + }, + ], + warpRouteLimits: { + mint: WARP_ROUTE_MINT_LIMIT, + burn: WARP_ROUTE_BURN_LIMIT, + type: XERC20Type.Standard, + }, + }, + }, + worldchain: { + type: TokenType.XERC20, + token: '0x30974f73A4ac9E606Ed80da928e454977ac486D2', + xERC20: { + extraBridges: [ + { + // Chainlink Pool + lockbox: '0xF8AE5209DE22dbd06Dace938934b0D75B5E80299', + limits: { + mint: EXTRA_BRIDGE_MINT_LIMIT, + burn: EXTRA_BRIDGE_BURN_LIMIT, + type: XERC20Type.Standard, + }, + }, + ], + warpRouteLimits: { + mint: WARP_ROUTE_MINT_LIMIT, + burn: WARP_ROUTE_BURN_LIMIT, + type: XERC20Type.Standard, + }, + }, + }, +}; + +export const getoXAUTTokenProductionWarpConfig = async ( + routerConfig: ChainMap, + _abacusWorksEnvOwnerConfig: ChainMap, +): Promise> => { + const configs: ChainMap = {}; + + for (const chain of chainsToDeploy) { + configs[chain] = { + type: tokenMetadata[chain].type, + mailbox: routerConfig[chain].mailbox, + owner: ownerMap[chain], + ownerOverrides: { + collateralToken: ownerMap[chain], + }, + token: tokenMetadata[chain].token, + xERC20: tokenMetadata[chain].xERC20, + }; + } + + return configs; +}; + +export const getoXAUTGnosisSafeSubmitterStrategyConfig = + getGnosisSafeSubmitterStrategyConfigGenerator(ownerMap); diff --git a/typescript/infra/config/environments/mainnet3/warp/strategies/ousdt.yaml b/typescript/infra/config/environments/mainnet3/warp/strategies/ousdt.yaml index 4a2c84e1c77..82b1336d8f5 100644 --- a/typescript/infra/config/environments/mainnet3/warp/strategies/ousdt.yaml +++ b/typescript/infra/config/environments/mainnet3/warp/strategies/ousdt.yaml @@ -1,127 +1,344 @@ base: submitter: chain: base - safeAddress: '0x125d1b64dfd7898DD06ac3E060A432691b8Fa676' - type: gnosisSafe + proposerSubmitter: + chain: ethereum + destinationChain: base + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0xe38714D00cAa906065D872D177C1374C847035fF' + type: timelockController bitlayer: submitter: chain: bitlayer - safeAddress: '0x5F7771EA40546e2932754C263455Cb0023a55ca7' - type: gnosisSafe + proposerSubmitter: + chain: ethereum + destinationChain: bitlayer + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0x5066d3C3c8511A0f0e0146C99b17458126F6c4E4' + type: timelockController bob: submitter: - chain: ethereum - destinationChain: bob - internalSubmitter: - safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' - type: gnosisSafeTxBuilder - version: '1.0' - owner: '0xc99e58b9A4E330e2E4d09e2c94CD3c553904F588' - type: interchainAccount + chain: bob + proposerSubmitter: + chain: ethereum + destinationChain: bob + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0x790306c30eFeeAF3943ee3700C71D7B7812c85a1' + type: timelockController botanix: submitter: chain: botanix - type: jsonRpc + proposerSubmitter: + chain: ethereum + destinationChain: botanix + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0x4C2C26664D9A72F50e8B260408B070F34e7F748f' + type: timelockController celo: submitter: chain: celo - safeAddress: '0xf1b3fc934bB46c459253fb38555A400b94909800' - type: gnosisSafe + proposerSubmitter: + chain: ethereum + destinationChain: celo + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0xb527ea7ff1B14fEb9FFF98b5Cd750Bd311cD598F' + type: timelockController ethereum: submitter: chain: ethereum - safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' - type: gnosisSafeTxBuilder - version: '1.0' + proposerSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + timelockAddress: '0x469f45d05F8C3B3cE40d1640ffE66b795B1d2d22' + type: timelockController fraxtal: submitter: chain: fraxtal - safeAddress: '0x21C0CA5be5aC9BC6161Bf1cfE281A18Fe2190079' - type: gnosisSafe + proposerSubmitter: + chain: ethereum + destinationChain: fraxtal + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0x09F478e8dEB9Ef466025bf96d13cd9DC56881E18' + type: timelockController hashkey: submitter: - chain: ethereum - destinationChain: hashkey - internalSubmitter: - safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' - type: gnosisSafeTxBuilder - version: '1.0' - owner: '0xEE01c007f89c9255f43b91B591b93cD1459048D1' - type: interchainAccount + chain: hashkey + proposerSubmitter: + chain: ethereum + destinationChain: hashkey + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0x072DDFd63F1dcf7819c80A971F6CEbFF9b0277Cc' + type: timelockController ink: submitter: chain: ink - safeAddress: '0x1BBf2CE75A77b8A10dA7e73dC1F76456008010bD' - type: gnosisSafe + proposerSubmitter: + chain: ethereum + destinationChain: ink + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0xED56728fb977b0bBdacf65bCdD5e17Bb7e84504f' + type: timelockController linea: submitter: chain: linea - safeAddress: '0xaCD1865B262C89Fb0b50dcc8fB095330ae8F35b5' - type: gnosisSafe + proposerSubmitter: + chain: ethereum + destinationChain: linea + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0x3A2e96403d076e9f953166A9E4c61bcD9D164CFe' + type: timelockController lisk: submitter: chain: lisk - safeAddress: '0x6F0A0038FcDB2F1655219f1b92f7E9aD4b78Aa49' - type: gnosisSafe + proposerSubmitter: + chain: ethereum + destinationChain: lisk + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0xfA96BCb61C7CbD7839689E807CD6d7FC27754Af3' + type: timelockController mantle: submitter: chain: mantle - safeAddress: '0x8aFE6EECc6CcB02aA20DA8Fff7d29aadEBbc2DCd' - type: gnosisSafe + proposerSubmitter: + chain: ethereum + destinationChain: mantle + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0xA3C59Caa046Ac6234272c74ADaE5f202E57F6e33' + type: timelockController metal: submitter: chain: metal - safeAddress: '0x41A4e3425c7FeE8711D1C1b2c2acc1879F849b45' - type: gnosisSafe + proposerSubmitter: + chain: ethereum + destinationChain: metal + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0xA312a8329bCDc2e5EA5dc2849326a45D40C58e8F' + type: timelockController metis: submitter: chain: metis - safeAddress: '0xf6B817Cf8b4440F38951851cf1160969039966A2' - type: gnosisSafeTxBuilder - version: '1.0' + proposerSubmitter: + chain: ethereum + destinationChain: metis + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0xfc8b34Fa72310A2926A0668e05F17F21c9811b80' + type: timelockController mode: submitter: chain: mode - safeAddress: '0xD4c01B4753575899AD81aAca0bb2DB7796E9F7C0' - type: gnosisSafe + proposerSubmitter: + chain: ethereum + destinationChain: mode + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0x5CC74C639310B6865d2Ef2E92ed4B68fcd96Ff88' + type: timelockController optimism: submitter: chain: optimism - safeAddress: '0x8E3340E241880F80359AA95Ae20Dc498d3f62503' - type: gnosisSafe + proposerSubmitter: + chain: ethereum + destinationChain: optimism + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0x1C9192aB4aDc226FF20121624590650b076492BE' + type: timelockController ronin: submitter: chain: ronin - safeAddress: '0x5F7771EA40546e2932754C263455Cb0023a55ca7' - type: gnosisSafe + proposerSubmitter: + chain: ethereum + destinationChain: ronin + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0x96c418BaB57953765B97532c494125F9b2e2B38D' + type: timelockController soneium: submitter: chain: soneium - safeAddress: '0x31Bf112F33556A0F1dc76881cfA8A36Bc2134A57' - type: gnosisSafeTxBuilder - version: '1.0' + proposerSubmitter: + chain: ethereum + destinationChain: soneium + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0x39d3c2Cf646447ee302178EDBe5a15E13B6F33aC' + type: timelockController sonic: submitter: chain: sonic - safeAddress: '0x7f56412491D8E77331Ff0300d3C8E42A6D233FdC' - type: gnosisSafe + proposerSubmitter: + chain: ethereum + destinationChain: sonic + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0x591273A518b59B4E9E4c104B001Fee4B9920244F' + type: timelockController superseed: submitter: chain: superseed - safeAddress: '0x0731a8e0DC88Df79d9643BD6C1f26cfe6fa53382' - type: gnosisSafeTxBuilder - version: '1.0' + proposerSubmitter: + chain: ethereum + destinationChain: superseed + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0xEd9c6B30482ACe8De6366a1858D0702111852449' + type: timelockController swell: submitter: chain: swell - type: jsonRpc + proposerSubmitter: + chain: ethereum + destinationChain: swell + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0xc82C44E3b5fA9fa9915F4c09fB0b5bb9e417625c' + type: timelockController unichain: submitter: chain: unichain - safeAddress: '0xf306ad5bF95960188c67A30f5546D193760ca3D0' - type: gnosisSafe + proposerSubmitter: + chain: ethereum + destinationChain: unichain + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0x6291596339A6EDD6cD68aca1d1c08B1fa2115F8C' + type: timelockController worldchain: submitter: chain: worldchain - safeAddress: '0x998238aF5A2DDC7ae08Dbe4B60b82EF63A1538cd' - type: gnosisSafe + proposerSubmitter: + chain: ethereum + destinationChain: worldchain + internalSubmitter: + chain: ethereum + safeAddress: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: gnosisSafeTxBuilder + version: '1.0' + owner: '0x3965AC3D295641E452E0ea896a086A9cD7C6C5b6' + type: interchainAccount + timelockAddress: '0x1008FAbD07aBd93a7D9bB81803a89cC3a834E1A9' + type: timelockController +zerogravity: + submitter: + chain: zerogravity + type: jsonRpc diff --git a/typescript/infra/config/environments/mainnet3/warp/warpIds.ts b/typescript/infra/config/environments/mainnet3/warp/warpIds.ts index 8ec5f28b43b..e8711e6eab6 100644 --- a/typescript/infra/config/environments/mainnet3/warp/warpIds.ts +++ b/typescript/infra/config/environments/mainnet3/warp/warpIds.ts @@ -3,10 +3,11 @@ export enum WarpRouteIds { Ancient8EthereumUSDC = 'USDC/ancient8-ethereum', RenzoEZETH = 'EZETH/renzo-prod', RenzoEZETHSTAGE = 'EZETHSTAGE/renzo-stage', + ArbitrumBaseEthereumRadixUSDC = 'USDC/arbitrum-base-ethereum-radix', ArbitrumBaseEnduranceUSDC = 'USDC/arbitrum-base-endurance', ArbitrumEthereumZircuitAMPHRETH = 'AMPHRETH/arbitrum-ethereum-zircuit', ArbitrumNeutronEclip = 'ECLIP/arbitrum-neutron', - ArbitrumNeutronTIA = 'TIA/arbitrum-neutron', + ArbitrumNeutronTIA = 'TIA/arbitrum-celestia-neutron', ArtelaBaseSolanaART = 'ART/artela-base-solanamainnet', BscEthereumLumiaPrismPNDR = 'PNDR/bsc-ethereum-lumiaprism', BaseSolanamainnetTONY = 'TONY/base-solanamainnet', @@ -80,12 +81,14 @@ export enum WarpRouteIds { ArbitrumSolanaLOGX = 'LOGX/arbitrum-solanamainnet', oUSDT = 'oUSDT/production', oUSDTSTAGE = 'oUSDT/staging', + oXAUT = 'oXAUT/production', HyperevmSolanaSOL = 'SOL/hyperevm-solanamainnet', MintSolanaMINT = 'MINT/mint-solanamainnet', EthereumUnichainPumpBTC = 'pumpBTCuni/ethereum-unichain', ArbitrumBaseEthereumLumiaprismOptimismPolygonETH = 'ETH/arbitrum-base-ethereum-lumiaprism-optimism-polygon', BscHyperevmEnzoBTC = 'enzoBTC/bsc-hyperevm', BscHyperevmSTBTC = 'stBTC/bsc-hyperevm', + MitosisMITO = 'MITO/mitosis', // Soon Routes SolanaSoonAi16z = 'ai16z/solanamainnet-soon', SolanaSoonELIZA = 'ELIZA/solanamainnet-soon', @@ -101,6 +104,10 @@ export enum WarpRouteIds { SubtensorUSDC = 'USDC/subtensor', ParadexUSDC = 'USDC/paradex', + SonicSVMMoney = 'MONEY/sonicsvm', + + PulsechainUSDC = 'USDC/pulsechain', + MainnetCCTP = 'USDC/mainnet-cctp', TestnetCCTP = 'USDC/testnet-cctp', diff --git a/typescript/infra/config/environments/test/middleware/accounts/addresses.json b/typescript/infra/config/environments/test/middleware/accounts/addresses.json index 1299ae73073..70ea74e4bb0 100644 --- a/typescript/infra/config/environments/test/middleware/accounts/addresses.json +++ b/typescript/infra/config/environments/test/middleware/accounts/addresses.json @@ -1,7 +1,4 @@ { - "alfajores": { - "router": "0xc011170d9795a7a2d065E384EAd1CA3394A7d35E" - }, "fuji": { "router": "0xc011170d9795a7a2d065E384EAd1CA3394A7d35E" }, diff --git a/typescript/infra/config/environments/test/middleware/queries/addresses.json b/typescript/infra/config/environments/test/middleware/queries/addresses.json index 1710187de57..7e5a63a2ff8 100644 --- a/typescript/infra/config/environments/test/middleware/queries/addresses.json +++ b/typescript/infra/config/environments/test/middleware/queries/addresses.json @@ -1,7 +1,4 @@ { - "alfajores": { - "router": "0x6141e7E7fA2c1beB8be030B0a7DB4b8A10c7c3cd" - }, "fuji": { "router": "0x6141e7E7fA2c1beB8be030B0a7DB4b8A10c7c3cd" }, diff --git a/typescript/infra/config/environments/testnet4/agent.ts b/typescript/infra/config/environments/testnet4/agent.ts index e12617028c3..62190b0c384 100644 --- a/typescript/infra/config/environments/testnet4/agent.ts +++ b/typescript/infra/config/environments/testnet4/agent.ts @@ -20,7 +20,7 @@ import { routerMatchingList, } from '../../../src/config/agent/relayer.js'; import { ALL_KEY_ROLES, Role } from '../../../src/roles.js'; -import { Contexts, mustBeValidContext } from '../../contexts.js'; +import { Contexts } from '../../contexts.js'; import { getDomainId } from '../../registry.js'; import { environment, ethereumChainNames } from './chains.js'; @@ -46,157 +46,115 @@ export const hyperlaneContextAgentChainConfig: AgentChainConfig< typeof testnet4SupportedChainNames > = { [Role.Validator]: { - abstracttestnet: true, - alephzeroevmtestnet: true, - alfajores: true, arbitrumsepolia: true, arcadiatestnet2: true, auroratestnet: true, basecamptestnet: true, basesepolia: true, - bepolia: true, bsctestnet: true, carrchaintestnet: true, celestiatestnet: true, - chronicleyellowstone: true, + celosepolia: true, citreatestnet: true, - connextsepolia: true, cotitestnet: true, - ecotestnet: true, eclipsetestnet: false, - flametestnet: true, - formtestnet: true, fuji: true, holesky: true, hyperliquidevmtestnet: true, - infinityvmmonza: true, - inksepolia: true, + incentivtestnet: true, + infinityvmmonza: false, kyvetestnet: false, - megaethtestnet: true, + megaethtestnet: false, milkywaytestnet: true, modetestnet: true, monadtestnet: true, neuratestnet: true, - nobletestnet: true, - odysseytestnet: true, + nobletestnet: false, optimismsepolia: true, paradexsepolia: true, - plumetestnet2: true, polygonamoy: true, + radixtestnet: true, scrollsepolia: true, sepolia: true, solanatestnet: true, - soneiumtestnet: true, somniatestnet: true, - sonicblaze: true, sonicsvmtestnet: false, starknetsepolia: true, - subtensortestnet: true, - superpositiontestnet: true, - unichaintestnet: true, - weavevmtestnet: true, + subtensortestnet: false, }, [Role.Relayer]: { - abstracttestnet: true, - alephzeroevmtestnet: true, - alfajores: true, arbitrumsepolia: true, arcadiatestnet2: true, auroratestnet: true, basecamptestnet: true, basesepolia: true, - bepolia: true, bsctestnet: true, carrchaintestnet: true, celestiatestnet: true, - chronicleyellowstone: true, + celosepolia: true, citreatestnet: true, - connextsepolia: true, cotitestnet: true, - ecotestnet: true, eclipsetestnet: false, - flametestnet: true, - formtestnet: true, fuji: true, holesky: true, hyperliquidevmtestnet: true, + incentivtestnet: true, infinityvmmonza: false, - inksepolia: true, kyvetestnet: false, - megaethtestnet: true, + megaethtestnet: false, milkywaytestnet: true, modetestnet: true, monadtestnet: true, neuratestnet: true, - nobletestnet: true, - odysseytestnet: true, + nobletestnet: false, optimismsepolia: true, paradexsepolia: true, - plumetestnet2: true, polygonamoy: true, + radixtestnet: true, scrollsepolia: true, sepolia: true, solanatestnet: true, - soneiumtestnet: true, somniatestnet: true, - sonicblaze: true, sonicsvmtestnet: false, starknetsepolia: true, - subtensortestnet: true, - superpositiontestnet: true, - unichaintestnet: true, - weavevmtestnet: true, + subtensortestnet: false, }, [Role.Scraper]: { - abstracttestnet: true, - alephzeroevmtestnet: true, - alfajores: true, arbitrumsepolia: true, arcadiatestnet2: true, auroratestnet: true, basecamptestnet: true, basesepolia: true, - bepolia: true, bsctestnet: true, carrchaintestnet: true, celestiatestnet: true, - chronicleyellowstone: true, + celosepolia: true, citreatestnet: true, - connextsepolia: false, cotitestnet: true, - ecotestnet: true, eclipsetestnet: false, - flametestnet: true, - formtestnet: true, fuji: true, holesky: true, hyperliquidevmtestnet: true, - infinityvmmonza: true, - inksepolia: true, + incentivtestnet: true, + infinityvmmonza: false, kyvetestnet: false, - megaethtestnet: true, + megaethtestnet: false, milkywaytestnet: true, modetestnet: true, monadtestnet: true, neuratestnet: true, - nobletestnet: true, - odysseytestnet: true, + nobletestnet: false, optimismsepolia: true, paradexsepolia: true, - plumetestnet2: true, polygonamoy: true, + radixtestnet: true, scrollsepolia: true, sepolia: true, solanatestnet: true, somniatestnet: true, - soneiumtestnet: true, - sonicblaze: true, sonicsvmtestnet: false, starknetsepolia: true, - subtensortestnet: true, - superpositiontestnet: false, - unichaintestnet: true, - weavevmtestnet: true, + subtensortestnet: false, }, }; @@ -410,7 +368,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'ac07f38-20250716-204548', + tag: '6d543bd-20250923-094109', }, blacklist: [...releaseCandidateHelloworldMatchingList, ...relayBlacklist], gasPaymentEnforcement, @@ -431,7 +389,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '373482a-20250707-140617', + tag: 'e5e9700-20250909-164323', }, chains: validatorChainConfig(Contexts.Hyperlane), resources: validatorResources, @@ -440,7 +398,7 @@ const hyperlane: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '373482a-20250707-140617', + tag: '6d543bd-20250923-094109', }, resources: scraperResources, }, @@ -455,7 +413,7 @@ const releaseCandidate: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: 'ac07f38-20250716-204548', + tag: '6d543bd-20250923-094109', }, blacklist: relayBlacklist, gasPaymentEnforcement, @@ -476,7 +434,7 @@ const releaseCandidate: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '33b3649-20250612-161513', + tag: 'e5e9700-20250909-164323', }, chains: validatorChainConfig(Contexts.ReleaseCandidate), resources: validatorResources, @@ -504,7 +462,7 @@ const neutron: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '33b3649-20250612-161513', + tag: '62ec77a-20250827-085602', }, blacklist: relayBlacklist, gasPaymentEnforcement, @@ -525,81 +483,15 @@ const neutron: RootAgentConfig = { rpcConsensusType: RpcConsensusType.Fallback, docker: { repo, - tag: '33b3649-20250612-161513', + tag: '62ec77a-20250827-085602', }, chains: validatorChainConfig(Contexts.ReleaseCandidate), resources: validatorResources, }, }; -const getVanguardRootAgentConfig = (index: number): RootAgentConfig => ({ - ...contextBase, - context: mustBeValidContext(`vanguard${index}`), - contextChainNames: { - validator: [], - relayer: kesselRunnerNetworks, - scraper: [], - }, - rolesWithKeys: [Role.Relayer], - relayer: { - rpcConsensusType: RpcConsensusType.Fallback, - docker: { - repo, - // includes gasPriceCap overrides + per-chain maxSubmitQueueLength - tag: '9d20c65-20250418-220918', - }, - whitelist: kesselMatchingList, - gasPaymentEnforcement: [ - { - type: GasPaymentEnforcementPolicyType.None, - matchingList: kesselMatchingList, - }, - ], - metricAppContextsGetter, - ismCacheConfigs, - cache: { - enabled: true, - }, - resources: { - requests: { - cpu: '30000m', - memory: '100Gi', - }, - }, - dbBootstrap: true, - mixing: { - enabled: true, - // Arbitrary salt to ensure different agents have different sorting behavior for pending messages - salt: 69690 + index, - }, - batch: { - defaultBatchSize: 32, - batchSizeOverrides: { - // Slightly lower to ideally fit within 5M - sepolia: 26, - }, - bypassBatchSimulation: true, - maxSubmitQueueLength: { - arbitrumsepolia: 350, - basesepolia: 350, - bsctestnet: 350, - optimismsepolia: 350, - sepolia: 75, - }, - }, - txIdIndexingEnabled: false, - igpIndexingEnabled: false, - }, -}); - export const agents = { [Contexts.Hyperlane]: hyperlane, [Contexts.ReleaseCandidate]: releaseCandidate, [Contexts.Neutron]: neutron, - [Contexts.Vanguard0]: getVanguardRootAgentConfig(0), - [Contexts.Vanguard1]: getVanguardRootAgentConfig(1), - [Contexts.Vanguard2]: getVanguardRootAgentConfig(2), - [Contexts.Vanguard3]: getVanguardRootAgentConfig(3), - [Contexts.Vanguard4]: getVanguardRootAgentConfig(4), - [Contexts.Vanguard5]: getVanguardRootAgentConfig(5), }; diff --git a/typescript/infra/config/environments/testnet4/aw-validators/hyperlane.json b/typescript/infra/config/environments/testnet4/aw-validators/hyperlane.json index a727c2ce330..7d705483f8b 100644 --- a/typescript/infra/config/environments/testnet4/aw-validators/hyperlane.json +++ b/typescript/infra/config/environments/testnet4/aw-validators/hyperlane.json @@ -1,20 +1,4 @@ { - "abstracttestnet": { - "validators": ["0x7655bc4c9802bfcb3132b8822155b60a4fbbce3e"] - }, - "weavevmtestnet": { - "validators": ["0x6d2ee6688de903bb31f3ae2ea31da87b697f7f40"] - }, - "alephzeroevmtestnet": { - "validators": ["0x556cd94bcb6e5773e8df75e7eb3f91909d266a26"] - }, - "alfajores": { - "validators": [ - "0x2233a5ce12f814bd64c9cdd73410bb8693124d40", - "0xba279f965489d90f90490e3c49e860e0b43c2ae6", - "0x86485dcec5f7bb8478dd251676372d054dea6653" - ] - }, "arbitrumsepolia": { "validators": ["0x09fabfbca0b8bf042e2a1161ee5010d147b0f603"] }, @@ -30,9 +14,6 @@ "basesepolia": { "validators": ["0x82e3b437a2944e3ff00258c93e72cd1ba5e0e921"] }, - "bepolia": { - "validators": ["0xdb0128bb3d3f204eb18de7e8268e94fde0382daf"] - }, "bsctestnet": { "validators": [ "0x242d8a855a8c932dec51f7999ae7d1e48b10c95e", @@ -46,27 +27,15 @@ "celestiatestnet": { "validators": ["0x3e0227b7f129576c53ff5d98d17c9b8433445094"] }, - "chronicleyellowstone": { - "validators": ["0xf11cfeb2b6db66ec14c2ef7b685b36390cd648b4"] + "celosepolia": { + "validators": ["0x4a5cfcfd7f793f4ceba170c3decbe43bd8253ef6"] }, "citreatestnet": { "validators": ["0x60d7380a41eb95c49be18f141efd2fde5e3dba20"] }, - "connextsepolia": { - "validators": ["0xffbbec8c499585d80ef69eb613db624d27e089ab"] - }, "cotitestnet": { "validators": ["0x5c535dff16237a2cae97c97f9556404cd230c9c0"] }, - "ecotestnet": { - "validators": ["0xb3191420d463c2af8bd9b4a395e100ec5c05915a"] - }, - "flametestnet": { - "validators": ["0x0272625243bf2377f87538031fed54e21853cc2d"] - }, - "formtestnet": { - "validators": ["0x72ad7fddf16d17ff902d788441151982fa31a7bc"] - }, "fuji": { "validators": [ "0xd8154f73d04cc7f7f0c332793692e6e6f6b2402e", @@ -80,11 +49,8 @@ "hyperliquidevmtestnet": { "validators": ["0xea673a92a23ca319b9d85cc16b248645cd5158da"] }, - "infinityvmmonza": { - "validators": ["0x635e1ad8646f80ac7bdcd0be9bb69b6f229a31bb"] - }, - "inksepolia": { - "validators": ["0xe61c846aee275070207fcbf43674eb254f06097a"] + "incentivtestnet": { + "validators": ["0x3133eeb96fd96f9f99291088613edf7401149e6f"] }, "megaethtestnet": { "validators": ["0xf5c8a82f966d2ec8563a2012ccf556ee3f4b25ef"] @@ -104,18 +70,15 @@ "nobletestnet": { "validators": ["0xc30427bd74fdcf179a15b9a6e3c4e1d66104726a"] }, - "odysseytestnet": { - "validators": ["0xcc0a6e2d6aa8560b45b384ced7aa049870b66ea3"] - }, "optimismsepolia": { "validators": ["0x03efe4d0632ee15685d7e8f46dea0a874304aa29"] }, - "plumetestnet2": { - "validators": ["0x16637c78e1ea169132efcf4df8ebd03de349e740"] - }, "polygonamoy": { "validators": ["0xf0290b06e446b320bd4e9c4a519420354d7ddccd"] }, + "radixtestnet": { + "validators": ["0xeddaf7958627cfd35400c95db19a656a4a8a92c6"] + }, "scrollsepolia": { "validators": [ "0xbe18dbd758afb367180260b524e6d4bcd1cb6d05", @@ -133,22 +96,10 @@ "solanatestnet": { "validators": ["0xd4ce8fa138d4e083fc0e480cca0dbfa4f5f30bd5"] }, - "soneiumtestnet": { - "validators": ["0x2e2101020ccdbe76aeda1c27823b0150f43d0c63"] - }, "somniatestnet": { "validators": ["0xb3b27a27bfa94002d344e9cf5217a0e3502e018b"] }, - "sonicblaze": { - "validators": ["0xe5b98110d0688691ea280edea9a4faa1e3617ba1"] - }, "subtensortestnet": { "validators": ["0xbe2cd57e9fd46b12107cfec7a2db61aa23edbe33"] - }, - "superpositiontestnet": { - "validators": ["0x1d3168504b23b73cdf9c27f13bb0a595d7f1a96a"] - }, - "unichaintestnet": { - "validators": ["0x5e99961cf71918308c3b17ef21b5f515a4f86fe5"] } } diff --git a/typescript/infra/config/environments/testnet4/aw-validators/rc.json b/typescript/infra/config/environments/testnet4/aw-validators/rc.json index 8c85c09e231..4cfbba72675 100644 --- a/typescript/infra/config/environments/testnet4/aw-validators/rc.json +++ b/typescript/infra/config/environments/testnet4/aw-validators/rc.json @@ -2,13 +2,6 @@ "weavevmtestnet": { "validators": [] }, - "alfajores": { - "validators": [ - "0xace978aaa61d9ee44fe3ab147fd227e0e66b8909", - "0x6c8bfdfb8c40aba10cc9fb2cf0e3e856e0e5dbb3", - "0x54c65eb7677e6086cdde3d5ccef89feb2103a11d" - ] - }, "bsctestnet": { "validators": [ "0x6353c7402626054c824bd0eca721f82b725e2b4d", diff --git a/typescript/infra/config/environments/testnet4/core/verification.json b/typescript/infra/config/environments/testnet4/core/verification.json index 02a93846187..81a0d308432 100644 --- a/typescript/infra/config/environments/testnet4/core/verification.json +++ b/typescript/infra/config/environments/testnet4/core/verification.json @@ -1,78 +1,4 @@ { - "alfajores": [ - { - "address": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", - "constructorArguments": "0x", - "isProxy": false, - "name": "ProxyAdmin" - }, - { - "address": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", - "constructorArguments": "0x000000000000000000000000592248baa7e27ed50a99cf821c61bf2ac8d6f2b70000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "name": "TransparentUpgradeableProxy" - }, - { - "address": "0x592248Baa7e27ed50A99cf821C61Bf2ac8D6F2B7", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000000000aef3", - "isProxy": false, - "name": "Mailbox" - }, - { - "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", - "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false, - "name": "MerkleTreeHook" - }, - { - "address": "0x8356113754C7aCa297Db3089b89F87CC125499fb", - "constructorArguments": "0x", - "isProxy": false, - "name": "StorageGasOracle" - }, - { - "address": "0x1246529edDcA523AfE5c6b9414299633d2E16697", - "constructorArguments": "0x00000000000000000000000076a1aae73e9d837cef10ac5af5afdd30d7612f980000000000000000000000004edbf5846d973c53af478cf62ab5bc92807521e300000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "name": "TransparentUpgradeableProxy" - }, - { - "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", - "constructorArguments": "0x", - "isProxy": false, - "name": "InterchainGasPaymaster" - }, - { - "address": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", - "constructorArguments": "0x000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false, - "name": "ProtocolFee" - }, - { - "address": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0", - "constructorArguments": "0x000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false, - "name": "ValidatorAnnounce" - }, - { - "address": "0xE1386148385275A27D29fC39Bd58a969CD5dCAF0", - "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000221fa9cbafcd6c1c3d206571cf4427703e023ffa", - "isProxy": false, - "name": "FallbackRoutingHook" - }, - { - "address": "0xA4caB1565083D33899A6eE69B174cC7729b3EaDF", - "constructorArguments": "000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false, - "name": "PausableIsm" - }, - { - "address": "0x4a2902395A40Ecf0B57CaB362b59bAffba9BB4aE", - "constructorArguments": "", - "isProxy": false, - "name": "PausableHook" - } - ], "arbitrumsepolia": [ { "address": "0x666a24F62f7A97BA33c151776Eb3D9441a059eB8", @@ -297,156 +223,6 @@ "name": "PausableHook" } ], - "connextsepolia": [ - { - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "constructorArguments": "", - "isProxy": false, - "name": "ProxyAdmin" - }, - { - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000018fe", - "isProxy": false, - "name": "Mailbox" - }, - { - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "constructorArguments": "000000000000000000000000c2e36cd6e32e194ee11f15d9273b64461a4d49a200000000000000000000000044b764045bfdc68517e10e783e69b376cef196b200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "name": "TransparentUpgradeableProxy" - }, - { - "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", - "constructorArguments": "000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false, - "name": "PausableIsm" - }, - { - "address": "0x4926a10788306D84202A2aDbd290b7743146Cc17", - "constructorArguments": "0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e02039", - "isProxy": false, - "name": "MerkleTreeHook" - }, - { - "address": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE", - "constructorArguments": "0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e02039000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004926a10788306d84202a2adbd290b7743146cc17", - "isProxy": false, - "name": "FallbackRoutingHook" - }, - { - "address": "0x07009DA2249c388aD0f416a235AfE90D784e1aAc", - "constructorArguments": "", - "isProxy": false, - "name": "PausableHook" - }, - { - "address": "0xF7561c34f17A32D5620583A3397C304e7038a7F6", - "constructorArguments": "", - "isProxy": false, - "name": "StorageGasOracle" - }, - { - "address": "0x26cD82217c5cfc1b4A3b36D2799c7cD84b0fd7B5", - "constructorArguments": "", - "isProxy": false, - "name": "InterchainGasPaymaster" - }, - { - "address": "0xeC7eb4196Bd601DEa7585A744FbFB4CF11278450", - "constructorArguments": "00000000000000000000000026cd82217c5cfc1b4a3b36d2799c7cd84b0fd7b500000000000000000000000044b764045bfdc68517e10e783e69b376cef196b200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "name": "TransparentUpgradeableProxy" - }, - { - "address": "0x863E8c26621c52ACa1849C53500606e73BA272F0", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false, - "name": "ProtocolFee" - }, - { - "address": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", - "constructorArguments": "0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e02039", - "isProxy": false, - "name": "ValidatorAnnounce" - } - ], - "ecotestnet": [ - { - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "constructorArguments": "", - "isProxy": false, - "name": "ProxyAdmin" - }, - { - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000073373", - "isProxy": false, - "name": "Mailbox" - }, - { - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "constructorArguments": "000000000000000000000000c2e36cd6e32e194ee11f15d9273b64461a4d49a200000000000000000000000044b764045bfdc68517e10e783e69b376cef196b200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "expectedimplementation": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "isProxy": true, - "name": "TransparentUpgradeableProxy" - }, - { - "address": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", - "constructorArguments": "000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false, - "name": "PausableIsm" - }, - { - "address": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", - "constructorArguments": "0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e02039", - "isProxy": false, - "name": "MerkleTreeHook" - }, - { - "address": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75", - "constructorArguments": "0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e02039000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000086fb9f1c124fb20ff130c41a79a432f770f67afd", - "isProxy": false, - "name": "FallbackRoutingHook" - }, - { - "address": "0x19Be55D859368e02d7b9C00803Eb677BDC1359Bd", - "constructorArguments": "", - "isProxy": false, - "name": "PausableHook" - }, - { - "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "constructorArguments": "", - "isProxy": false, - "name": "StorageGasOracle" - }, - { - "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", - "constructorArguments": "", - "isProxy": false, - "name": "InterchainGasPaymaster" - }, - { - "address": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e800000000000000000000000044b764045bfdc68517e10e783e69b376cef196b200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "expectedimplementation": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", - "isProxy": true, - "name": "TransparentUpgradeableProxy" - }, - { - "address": "0x1b33611fCc073aB0737011d5512EF673Bff74962", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false, - "name": "ProtocolFee" - }, - { - "address": "0x20c44b1E3BeaDA1e9826CFd48BeEDABeE9871cE9", - "constructorArguments": "0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e02039", - "isProxy": false, - "name": "ValidatorAnnounce" - } - ], "fuji": [ { "address": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", @@ -1031,133 +807,59 @@ "name": "PausableIsm" } ], - "superpositiontestnet": [ + "berabartio": [ { - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", + "name": "ProxyAdmin", + "address": "0x54148470292C24345fb828B003461a9444414517", "constructorArguments": "", - "isProxy": false, - "name": "ProxyAdmin" + "isProxy": false }, { - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000182a9", - "isProxy": false, - "name": "Mailbox" + "name": "Mailbox", + "address": "0x589C201a07c26b4725A4A829d772f24423da480B", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000138d4", + "isProxy": false }, { - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "constructorArguments": "000000000000000000000000c2e36cd6e32e194ee11f15d9273b64461a4d49a200000000000000000000000044b764045bfdc68517e10e783e69b376cef196b200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "name": "TransparentUpgradeableProxy", + "address": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", + "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, - "name": "TransparentUpgradeableProxy" + "expectedimplementation": "0x589C201a07c26b4725A4A829d772f24423da480B" }, { - "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", + "name": "PausableIsm", + "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", "constructorArguments": "000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false, - "name": "PausableIsm" + "isProxy": false }, { - "address": "0x4926a10788306D84202A2aDbd290b7743146Cc17", - "constructorArguments": "0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e02039", - "isProxy": false, - "name": "MerkleTreeHook" + "name": "MerkleTreeHook", + "address": "0x6c13643B3927C57DB92c790E4E3E7Ee81e13f78C", + "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", + "isProxy": false }, { - "address": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE", - "constructorArguments": "0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e02039000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004926a10788306d84202a2adbd290b7743146cc17", - "isProxy": false, - "name": "FallbackRoutingHook" + "name": "FallbackRoutingHook", + "address": "0x20c44b1E3BeaDA1e9826CFd48BeEDABeE9871cE9", + "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000006c13643b3927c57db92c790e4e3e7ee81e13f78c", + "isProxy": false }, { - "address": "0x07009DA2249c388aD0f416a235AfE90D784e1aAc", + "name": "PausableHook", + "address": "0x783c4a0bB6663359281aD4a637D5af68F83ae213", "constructorArguments": "", - "isProxy": false, - "name": "PausableHook" + "isProxy": false }, { - "address": "0xF7561c34f17A32D5620583A3397C304e7038a7F6", + "name": "StorageGasOracle", + "address": "0xeAEfB1458b032e75de3e9A3a480d005c426FB1c5", "constructorArguments": "", - "isProxy": false, - "name": "StorageGasOracle" + "isProxy": false }, { - "address": "0x26cD82217c5cfc1b4A3b36D2799c7cD84b0fd7B5", - "constructorArguments": "", - "isProxy": false, - "name": "InterchainGasPaymaster" - }, - { - "address": "0xeC7eb4196Bd601DEa7585A744FbFB4CF11278450", - "constructorArguments": "00000000000000000000000026cd82217c5cfc1b4a3b36d2799c7cd84b0fd7b500000000000000000000000044b764045bfdc68517e10e783e69b376cef196b200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "name": "TransparentUpgradeableProxy" - }, - { - "address": "0x863E8c26621c52ACa1849C53500606e73BA272F0", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false, - "name": "ProtocolFee" - }, - { - "address": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", - "constructorArguments": "0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e02039", - "isProxy": false, - "name": "ValidatorAnnounce" - } - ], - "berabartio": [ - { - "name": "ProxyAdmin", - "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000138d4", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x589C201a07c26b4725A4A829d772f24423da480B" - }, - { - "name": "PausableIsm", - "address": "0xEe421285728284000ec6c6C55C6F9161faeFfa99", - "constructorArguments": "000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0x6c13643B3927C57DB92c790E4E3E7Ee81e13f78C", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x20c44b1E3BeaDA1e9826CFd48BeEDABeE9871cE9", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000006c13643b3927c57db92c790e4e3e7ee81e13f78c", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0x783c4a0bB6663359281aD4a637D5af68F83ae213", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0xeAEfB1458b032e75de3e9A3a480d005c426FB1c5", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0xae7a78916Ba4c507aCB2F0e474ace545Ff4bF841", + "name": "InterchainGasPaymaster", + "address": "0xae7a78916Ba4c507aCB2F0e474ace545Ff4bF841", "constructorArguments": "", "isProxy": false }, @@ -1333,7 +1035,7 @@ "isProxy": false } ], - "soneiumtestnet": [ + "sonictestnet": [ { "name": "ProxyAdmin", "address": "0x54148470292C24345fb828B003461a9444414517", @@ -1343,7 +1045,7 @@ { "name": "Mailbox", "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000079a", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000000faa5", "isProxy": false }, { @@ -1355,61 +1057,61 @@ }, { "name": "PausableIsm", - "address": "0x04438ef7622f5412f82915F59caD4f704C61eA48", + "address": "0xb94F96D398eA5BAB5CA528EE9Fdc19afaA825818", "constructorArguments": "000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", "isProxy": false }, { "name": "MerkleTreeHook", - "address": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A", + "address": "0x086E902d2f99BcCEAa28B31747eC6Dc5fd43B1bE", "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0xb94F96D398eA5BAB5CA528EE9Fdc19afaA825818", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000d5eb5fa3f470ebbb93a4a58c644c87031268a04a", + "address": "0xB057Fb841027a8554521DcCdeC3c3474CaC99AB5", + "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000086e902d2f99bcceaa28b31747ec6dc5fd43b1be", "isProxy": false }, { "name": "PausableHook", - "address": "0x51A0a100e7BC63Ea7821A3a023B6F17fb94FF011", + "address": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x086E902d2f99BcCEAa28B31747eC6Dc5fd43B1bE", + "address": "0x7c5B5bdA7F1d1F70A6678ABb4d894612Fc76498F", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F", + "address": "0x628BC518ED1e0E8C6cbcD574EbA0ee29e7F6943E", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8", - "constructorArguments": "000000000000000000000000e0b988062a0c6492177d64823ab95a9c256c2a5f00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "address": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA", + "constructorArguments": "000000000000000000000000628bc518ed1e0e8c6cbcd574eba0ee29e7f6943e00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F" + "expectedimplementation": "0x628BC518ED1e0E8C6cbcD574EbA0ee29e7F6943E" }, { "name": "ProtocolFee", - "address": "0xc76E477437065093D353b7d56c81ff54D167B0Ab", + "address": "0x7483faD0Bc297667664A43A064bA7c9911659f57", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0xEa7e618Bee8927fBb2fA20Bc41eE8DEA51838aAD", + "address": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", "isProxy": false } ], - "formtestnet": [ + "arcadiatestnet2": [ { "name": "ProxyAdmin", "address": "0x54148470292C24345fb828B003461a9444414517", @@ -1419,7 +1121,7 @@ { "name": "Mailbox", "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000020726", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000041786f6e", "isProxy": false }, { @@ -1431,952 +1133,170 @@ }, { "name": "PausableIsm", - "address": "0x04438ef7622f5412f82915F59caD4f704C61eA48", + "address": "0xB057Fb841027a8554521DcCdeC3c3474CaC99AB5", "constructorArguments": "000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", "isProxy": false }, { "name": "MerkleTreeHook", - "address": "0xD5eB5fa3f470eBBB93a4A58C644c87031268a04A", + "address": "0x7c5B5bdA7F1d1F70A6678ABb4d894612Fc76498F", "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0xb94F96D398eA5BAB5CA528EE9Fdc19afaA825818", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000d5eb5fa3f470ebbb93a4a58c644c87031268a04a", + "address": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8", + "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000007c5b5bda7f1d1f70a6678abb4d894612fc76498f", "isProxy": false }, { "name": "PausableHook", - "address": "0x51A0a100e7BC63Ea7821A3a023B6F17fb94FF011", + "address": "0x628BC518ED1e0E8C6cbcD574EbA0ee29e7F6943E", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x086E902d2f99BcCEAa28B31747eC6Dc5fd43B1bE", + "address": "0xFfa913705484C9BAea32Ffe9945BeA099A1DFF72", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F", + "address": "0xc76E477437065093D353b7d56c81ff54D167B0Ab", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8", - "constructorArguments": "000000000000000000000000e0b988062a0c6492177d64823ab95a9c256c2a5f00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "address": "0xEa7e618Bee8927fBb2fA20Bc41eE8DEA51838aAD", + "constructorArguments": "000000000000000000000000c76e477437065093d353b7d56c81ff54d167b0ab00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F" + "expectedimplementation": "0xc76E477437065093D353b7d56c81ff54D167B0Ab" }, { "name": "ProtocolFee", - "address": "0xc76E477437065093D353b7d56c81ff54D167B0Ab", + "address": "0x01812D60958798695391dacF092BAc4a715B1718", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0xEa7e618Bee8927fBb2fA20Bc41eE8DEA51838aAD", + "address": "0x867f2089D09903f208AeCac84E599B90E5a4A821", "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", "isProxy": false - } - ], - "sonictestnet": [ - { - "name": "ProxyAdmin", - "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000faa5", - "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", + "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x589C201a07c26b4725A4A829d772f24423da480B" - }, - { - "name": "PausableIsm", - "address": "0xb94F96D398eA5BAB5CA528EE9Fdc19afaA825818", - "constructorArguments": "000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0x086E902d2f99BcCEAa28B31747eC6Dc5fd43B1bE", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false + "expectedimplementation": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD" }, { "name": "FallbackRoutingHook", - "address": "0xB057Fb841027a8554521DcCdeC3c3474CaC99AB5", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000086e902d2f99bcceaa28b31747ec6dc5fd43b1be", + "address": "0x7483faD0Bc297667664A43A064bA7c9911659f57", + "constructorArguments": "00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f065000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000ea7e618bee8927fbb2fa20bc41ee8dea51838aad", "isProxy": false }, { "name": "PausableHook", - "address": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F", + "address": "0x4fE19d49F45854Da50b6009258929613EC92C147", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x7c5B5bdA7F1d1F70A6678ABb4d894612Fc76498F", + "address": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x628BC518ED1e0E8C6cbcD574EbA0ee29e7F6943E", + "address": "0xE67CfA164cDa449Ae38a0a09391eF6bCDf8e4e2c", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA", - "constructorArguments": "000000000000000000000000628bc518ed1e0e8c6cbcd574eba0ee29e7f6943e00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "address": "0xfBeaF07855181f8476B235Cf746A7DF3F9e386Fb", + "constructorArguments": "000000000000000000000000e67cfa164cda449ae38a0a09391ef6bcdf8e4e2c000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x628BC518ED1e0E8C6cbcD574EbA0ee29e7F6943E" + "expectedimplementation": "0xE67CfA164cDa449Ae38a0a09391eF6bCDf8e4e2c" }, { "name": "ProtocolFee", - "address": "0x7483faD0Bc297667664A43A064bA7c9911659f57", + "address": "0xA0aB1750b4F68AE5E8C42d936fa78871eae52643", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", + "address": "0x843908541D24d9F6Fa30C8Bb1c39038C947D08fC", + "constructorArguments": "00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f065", "isProxy": false } ], - "unichaintestnet": [ - { - "name": "ProxyAdmin", - "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "", - "isProxy": false - }, + "subtensortestnet": [ { "name": "Mailbox", - "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000000515", + "address": "0x54148470292C24345fb828B003461a9444414517", + "constructorArguments": "00000000000000000000000000000000000000000000000000000000000003b1", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0x589C201a07c26b4725A4A829d772f24423da480B", + "constructorArguments": "00000000000000000000000054148470292c24345fb828b003461a9444414517000000000000000000000000c2e36cd6e32e194ee11f15d9273b64461a4d49a200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x589C201a07c26b4725A4A829d772f24423da480B" - }, - { - "name": "PausableIsm", - "address": "0xb94F96D398eA5BAB5CA528EE9Fdc19afaA825818", - "constructorArguments": "000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false + "expectedimplementation": "0x54148470292C24345fb828B003461a9444414517" }, { "name": "MerkleTreeHook", - "address": "0x086E902d2f99BcCEAa28B31747eC6Dc5fd43B1bE", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", + "address": "0x342B5630Ba1C1e4d3048E51Dad208201aF52692c", + "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0xB057Fb841027a8554521DcCdeC3c3474CaC99AB5", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000086e902d2f99bcceaa28b31747ec6dc5fd43b1be", + "address": "0x39c85C84876479694A2470c0E8075e9d68049aFc", + "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000342b5630ba1c1e4d3048e51dad208201af52692c", "isProxy": false }, { "name": "PausableHook", - "address": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F", + "address": "0x4eC139a771eBdD3b0a0b67bb7E08960210882d44", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x7c5B5bdA7F1d1F70A6678ABb4d894612Fc76498F", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0x628BC518ED1e0E8C6cbcD574EbA0ee29e7F6943E", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA", - "constructorArguments": "000000000000000000000000628bc518ed1e0e8c6cbcd574eba0ee29e7f6943e00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x628BC518ED1e0E8C6cbcD574EbA0ee29e7F6943E" - }, - { - "name": "ProtocolFee", - "address": "0x7483faD0Bc297667664A43A064bA7c9911659f57", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - } - ], - "arcadiatestnet2": [ - { - "name": "ProxyAdmin", - "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000041786f6e", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x589C201a07c26b4725A4A829d772f24423da480B" - }, - { - "name": "PausableIsm", - "address": "0xB057Fb841027a8554521DcCdeC3c3474CaC99AB5", - "constructorArguments": "000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0x7c5B5bdA7F1d1F70A6678ABb4d894612Fc76498F", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000007c5b5bda7f1d1f70a6678abb4d894612fc76498f", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0x628BC518ED1e0E8C6cbcD574EbA0ee29e7F6943E", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0xFfa913705484C9BAea32Ffe9945BeA099A1DFF72", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0xc76E477437065093D353b7d56c81ff54D167B0Ab", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xEa7e618Bee8927fBb2fA20Bc41eE8DEA51838aAD", - "constructorArguments": "000000000000000000000000c76e477437065093d353b7d56c81ff54d167b0ab00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xc76E477437065093D353b7d56c81ff54D167B0Ab" - }, - { - "name": "ProtocolFee", - "address": "0x01812D60958798695391dacF092BAc4a715B1718", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0x867f2089D09903f208AeCac84E599B90E5a4A821", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD" - }, - { - "name": "FallbackRoutingHook", - "address": "0x7483faD0Bc297667664A43A064bA7c9911659f57", - "constructorArguments": "00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f065000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000ea7e618bee8927fbb2fa20bc41ee8dea51838aad", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0x4fE19d49F45854Da50b6009258929613EC92C147", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0xE67CfA164cDa449Ae38a0a09391eF6bCDf8e4e2c", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xfBeaF07855181f8476B235Cf746A7DF3F9e386Fb", - "constructorArguments": "000000000000000000000000e67cfa164cda449ae38a0a09391ef6bcdf8e4e2c000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xE67CfA164cDa449Ae38a0a09391eF6bCDf8e4e2c" - }, - { - "name": "ProtocolFee", - "address": "0xA0aB1750b4F68AE5E8C42d936fa78871eae52643", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0x843908541D24d9F6Fa30C8Bb1c39038C947D08fC", - "constructorArguments": "00000000000000000000000033db966328ea213b0f76ef96ca368ab37779f065", - "isProxy": false - } - ], - "odysseytestnet": [ - { - "name": "ProxyAdmin", - "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000de9fb", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x589C201a07c26b4725A4A829d772f24423da480B" - }, - { - "name": "PausableIsm", - "address": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8", - "constructorArguments": "000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "MerkleTreeHook", - "address": "0xFfa913705484C9BAea32Ffe9945BeA099A1DFF72", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000ffa913705484c9baea32ffe9945bea099a1dff72", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0xc76E477437065093D353b7d56c81ff54D167B0Ab", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0xB5fB1F5410a2c2b7deD462d018541383968cB01c", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0x7483faD0Bc297667664A43A064bA7c9911659f57", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", - "constructorArguments": "0000000000000000000000007483fad0bc297667664a43a064ba7c9911659f5700000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x7483faD0Bc297667664A43A064bA7c9911659f57" - }, - { - "name": "ProtocolFee", - "address": "0xfBeaF07855181f8476B235Cf746A7DF3F9e386Fb", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0x54Bd02f0f20677e9846F8E9FdB1Abc7315C49C38", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - } - ], - "alephzeroevmtestnet": [ - { - "name": "ProxyAdmin", - "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000007f7", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x589C201a07c26b4725A4A829d772f24423da480B" - }, - { - "name": "MerkleTreeHook", - "address": "0xB5fB1F5410a2c2b7deD462d018541383968cB01c", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xEa7e618Bee8927fBb2fA20Bc41eE8DEA51838aAD", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000b5fb1f5410a2c2b7ded462d018541383968cb01c", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0x7483faD0Bc297667664A43A064bA7c9911659f57", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x4fE19d49F45854Da50b6009258929613EC92C147", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0x01812D60958798695391dacF092BAc4a715B1718", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x867f2089D09903f208AeCac84E599B90E5a4A821", - "constructorArguments": "00000000000000000000000001812d60958798695391dacf092bac4a715b171800000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x01812D60958798695391dacF092BAc4a715B1718" - }, - { - "name": "ProtocolFee", - "address": "0x5e65279Fb7293a058776e37587398fcc3E9184b1", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0xBF2C366530C1269d531707154948494D3fF4AcA7", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - } - ], - "inksepolia": [ - { - "name": "ProxyAdmin", - "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000ba5ed", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x589C201a07c26b4725A4A829d772f24423da480B" - }, - { - "name": "MerkleTreeHook", - "address": "0x4fE19d49F45854Da50b6009258929613EC92C147", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000004fe19d49f45854da50b6009258929613ec92c147", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0x01812D60958798695391dacF092BAc4a715B1718", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0xE67CfA164cDa449Ae38a0a09391eF6bCDf8e4e2c", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0xfBeaF07855181f8476B235Cf746A7DF3F9e386Fb", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x54Bd02f0f20677e9846F8E9FdB1Abc7315C49C38", - "constructorArguments": "000000000000000000000000fbeaf07855181f8476b235cf746a7df3f9e386fb00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xfBeaF07855181f8476B235Cf746A7DF3F9e386Fb" - }, - { - "name": "ProtocolFee", - "address": "0x843908541D24d9F6Fa30C8Bb1c39038C947D08fC", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0xBdf49bE2201A1c4B13023F0a407196C6Adb32680", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - } - ], - "abstracttestnet": [ - { - "name": "ProxyAdmin", - "address": "0xfbA0c57A6BA24B5440D3e2089222099b4663B98B", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0x5f33Bf018C55CD3034ac06e6DA41162F5acc2fF7", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000002b74", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x28f448885bEaaF662f8A9A6c9aF20fAd17A5a1DC", - "constructorArguments": "0000000000000000000000005f33bf018c55cd3034ac06e6da41162f5acc2ff7000000000000000000000000fba0c57a6ba24b5440d3e2089222099b4663b98b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x5f33Bf018C55CD3034ac06e6DA41162F5acc2fF7" - }, - { - "name": "MerkleTreeHook", - "address": "0x7fa6009b59F139813eA710dB5496976eE8D80E64", - "constructorArguments": "00000000000000000000000028f448885beaaf662f8a9a6c9af20fad17a5a1dc", - "isProxy": false - }, - { - "name": "FallbackDomainRoutingHook", - "address": "0x623f284257f133E8bE7c74f6D4D684B61FE8923a", - "constructorArguments": "00000000000000000000000028f448885beaaf662f8a9a6c9af20fad17a5a1dc000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000007fa6009b59f139813ea710db5496976ee8d80e64", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x5F1bADC7e28B9b4C98f58dB4e5841e5bf63A7A52", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0xeBe8D0e2BD026D12Ca5e51edA3B0D2b413e83c9c", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xbAaE1B4e953190b05C757F69B2F6C46b9548fa4f", - "constructorArguments": "000000000000000000000000ebe8d0e2bd026d12ca5e51eda3b0d2b413e83c9c000000000000000000000000fba0c57a6ba24b5440d3e2089222099b4663b98b00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xeBe8D0e2BD026D12Ca5e51edA3B0D2b413e83c9c" - }, - { - "name": "ValidatorAnnounce", - "address": "0xfE9a467831a28Ec3D54deCCf0A2A41fa77dDD1D7", - "constructorArguments": "00000000000000000000000028f448885beaaf662f8a9a6c9af20fad17a5a1dc", - "isProxy": false - } - ], - "flametestnet": [ - { - "name": "ProxyAdmin", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000062f8d1ad", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "00000000000000000000000054148470292c24345fb828b003461a94444145170000000000000000000000006966b0e55883d49bfb24539356a2f8a673e0203900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x54148470292C24345fb828B003461a9444414517" - }, - { - "name": "MerkleTreeHook", - "address": "0x843908541D24d9F6Fa30C8Bb1c39038C947D08fC", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xCB3c489a2FB67a7Cd555D47B3a9A0E654784eD16", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000843908541d24d9f6fa30c8bb1c39038c947d08fc", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0xBdf49bE2201A1c4B13023F0a407196C6Adb32680", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x0e91088824Fa6E2675b2a53DA3491a9B098bD868", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0x3ca332A585FDB9d4FF51f2FA8999eA32184D3606", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x39c85C84876479694A2470c0E8075e9d68049aFc", - "constructorArguments": "0000000000000000000000003ca332a585fdb9d4ff51f2fa8999ea32184d36060000000000000000000000006966b0e55883d49bfb24539356a2f8a673e0203900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x3ca332A585FDB9d4FF51f2FA8999eA32184D3606" - }, - { - "name": "ProtocolFee", - "address": "0x4Ece7b15ba5dCA2708dCE2812016683193102b9F", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0xB589407cf6bEA5CD81AD0946b9F1467933ede74c", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - } - ], - "sonicblaze": [ - { - "name": "ProxyAdmin", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000dede", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "00000000000000000000000054148470292c24345fb828b003461a94444145170000000000000000000000006966b0e55883d49bfb24539356a2f8a673e0203900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x54148470292C24345fb828B003461a9444414517" - }, - { - "name": "MerkleTreeHook", - "address": "0x843908541D24d9F6Fa30C8Bb1c39038C947D08fC", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xCB3c489a2FB67a7Cd555D47B3a9A0E654784eD16", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000843908541d24d9f6fa30c8bb1c39038c947d08fc", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0xBdf49bE2201A1c4B13023F0a407196C6Adb32680", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0x0e91088824Fa6E2675b2a53DA3491a9B098bD868", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0x3ca332A585FDB9d4FF51f2FA8999eA32184D3606", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x39c85C84876479694A2470c0E8075e9d68049aFc", - "constructorArguments": "0000000000000000000000003ca332a585fdb9d4ff51f2fa8999ea32184d36060000000000000000000000006966b0e55883d49bfb24539356a2f8a673e0203900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x3ca332A585FDB9d4FF51f2FA8999eA32184D3606" - }, - { - "name": "ProtocolFee", - "address": "0x4Ece7b15ba5dCA2708dCE2812016683193102b9F", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0xB589407cf6bEA5CD81AD0946b9F1467933ede74c", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - } - ], - "chronicleyellowstone": [ - { - "name": "ProxyAdmin", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000002ac54", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "00000000000000000000000054148470292c24345fb828b003461a94444145170000000000000000000000006966b0e55883d49bfb24539356a2f8a673e0203900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x54148470292C24345fb828B003461a9444414517" - }, - { - "name": "MerkleTreeHook", - "address": "0x342B5630Ba1C1e4d3048E51Dad208201aF52692c", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x39c85C84876479694A2470c0E8075e9d68049aFc", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000342b5630ba1c1e4d3048e51dad208201af52692c", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0x4eC139a771eBdD3b0a0b67bb7E08960210882d44", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0xe036768e48Cb0D42811d2bF0748806FCcBfCd670", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0x4Ece7b15ba5dCA2708dCE2812016683193102b9F", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xB589407cf6bEA5CD81AD0946b9F1467933ede74c", - "constructorArguments": "0000000000000000000000004ece7b15ba5dca2708dce2812016683193102b9f0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e0203900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x4Ece7b15ba5dCA2708dCE2812016683193102b9F" - }, - { - "name": "ProtocolFee", - "address": "0x2bD9aF503B9F608beAD63D4ACC328Abf9796b576", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0x8584590ad637C61C7cDF72eFF3381Ee1c3D1bC8E", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - } - ], - "subtensortestnet": [ - { - "name": "Mailbox", - "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000003b1", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "00000000000000000000000054148470292c24345fb828b003461a9444414517000000000000000000000000c2e36cd6e32e194ee11f15d9273b64461a4d49a200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x54148470292C24345fb828B003461a9444414517" - }, - { - "name": "MerkleTreeHook", - "address": "0x342B5630Ba1C1e4d3048E51Dad208201aF52692c", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x39c85C84876479694A2470c0E8075e9d68049aFc", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000342b5630ba1c1e4d3048e51dad208201af52692c", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0x4eC139a771eBdD3b0a0b67bb7E08960210882d44", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0xe036768e48Cb0D42811d2bF0748806FCcBfCd670", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0x4Ece7b15ba5dCA2708dCE2812016683193102b9F", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xB589407cf6bEA5CD81AD0946b9F1467933ede74c", - "constructorArguments": "0000000000000000000000004ece7b15ba5dca2708dce2812016683193102b9f000000000000000000000000c2e36cd6e32e194ee11f15d9273b64461a4d49a200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x4Ece7b15ba5dCA2708dCE2812016683193102b9F" - }, - { - "name": "ProtocolFee", - "address": "0x2bD9aF503B9F608beAD63D4ACC328Abf9796b576", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0x8584590ad637C61C7cDF72eFF3381Ee1c3D1bC8E", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - } - ], - "weavevmtestnet": [ - { - "name": "ProxyAdmin", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000002518", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "00000000000000000000000054148470292c24345fb828b003461a94444145170000000000000000000000006966b0e55883d49bfb24539356a2f8a673e0203900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x54148470292C24345fb828B003461a9444414517" - }, - { - "name": "MerkleTreeHook", - "address": "0xE1CCB130389f687bf745Dd6dc05E50da17d9ea96", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0xB589407cf6bEA5CD81AD0946b9F1467933ede74c", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000e1ccb130389f687bf745dd6dc05e50da17d9ea96", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0x8d4f112cffa338D3c3Ef2Cf443179C5a48E678e4", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0xb3D796584fDeBE2321894eeF31e0C3ec52169C61", + "address": "0xe036768e48Cb0D42811d2bF0748806FCcBfCd670", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x2bD9aF503B9F608beAD63D4ACC328Abf9796b576", + "address": "0x4Ece7b15ba5dCA2708dCE2812016683193102b9F", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x8584590ad637C61C7cDF72eFF3381Ee1c3D1bC8E", - "constructorArguments": "0000000000000000000000002bd9af503b9f608bead63d4acc328abf9796b5760000000000000000000000006966b0e55883d49bfb24539356a2f8a673e0203900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "address": "0xB589407cf6bEA5CD81AD0946b9F1467933ede74c", + "constructorArguments": "0000000000000000000000004ece7b15ba5dca2708dce2812016683193102b9f000000000000000000000000c2e36cd6e32e194ee11f15d9273b64461a4d49a200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x2bD9aF503B9F608beAD63D4ACC328Abf9796b576" + "expectedimplementation": "0x4Ece7b15ba5dCA2708dCE2812016683193102b9F" }, { "name": "ProtocolFee", - "address": "0x9D19a337f4d11738b8D25A0E3FB06c342783f2ce", + "address": "0x2bD9aF503B9F608beAD63D4ACC328Abf9796b576", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0xA9999B4abC373FF2BB95B8725FABC96CA883d811", + "address": "0x8584590ad637C61C7cDF72eFF3381Ee1c3D1bC8E", "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", "isProxy": false } @@ -2820,76 +1740,6 @@ "isProxy": false } ], - "plumetestnet2": [ - { - "name": "ProxyAdmin", - "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "Mailbox", - "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "0000000000000000000000000000000000000000000000000000000000018233", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x589C201a07c26b4725A4A829d772f24423da480B" - }, - { - "name": "MerkleTreeHook", - "address": "0x0b9A4A46f50f91f353B8Aa0F3Ca80E35E253bDd8", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "FallbackRoutingHook", - "address": "0x6cB503d97D1c900316583C8D55997A1f17b1ABd1", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000000b9a4a46f50f91f353b8aa0f3ca80e35e253bdd8", - "isProxy": false - }, - { - "name": "PausableHook", - "address": "0x9450181a7719dAb93483d43a45473Ac2373E25B0", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StorageGasOracle", - "address": "0xc0Ce04851bF6Ea149fA06bf7a4808c9db81af189", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "InterchainGasPaymaster", - "address": "0x45AEBD45B5Bb1C3A0bDBDf6094e8adA5712e1b74", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xD5B70f7Da85F98A5197E55114A38f3eDcDCf020e", - "constructorArguments": "00000000000000000000000045aebd45b5bb1c3a0bdbdf6094e8ada5712e1b7400000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x45AEBD45B5Bb1C3A0bDBDf6094e8adA5712e1b74" - }, - { - "name": "ProtocolFee", - "address": "0xE6C0740b6aB7C060e197a7bd2952A27bB219c62A", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", - "isProxy": false - }, - { - "name": "ValidatorAnnounce", - "address": "0xF8E6c1222049AAb68E410E43242449994Cb64996", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - } - ], "auroratestnet": [ { "name": "ProxyAdmin", @@ -3030,7 +1880,7 @@ "isProxy": false } ], - "bepolia": [ + "basecamptestnet": [ { "name": "ProxyAdmin", "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", @@ -3040,7 +1890,7 @@ { "name": "Mailbox", "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "00000000000000000000000000000000000000000000000000000000000138c5", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9ace5a", "isProxy": false }, { @@ -3100,7 +1950,7 @@ "isProxy": false } ], - "basecamptestnet": [ + "neuratestnet": [ { "name": "ProxyAdmin", "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", @@ -3110,7 +1960,7 @@ { "name": "Mailbox", "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9ace5a", + "constructorArguments": "000000000000000000000000000000000000000000000000000000000000010b", "isProxy": false }, { @@ -3122,121 +1972,286 @@ }, { "name": "MerkleTreeHook", - "address": "0x86abe4c3493A1eE0Aa42f0231b5594D42aBdA36e", + "address": "0x36502C6e24C51ba4839c4c4A070aeB52E1adB672", "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0x6916690816a1aE1F6D9163dA9e691124737B5f5b", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000086abe4c3493a1ee0aa42f0231b5594d42abda36e", + "address": "0xe18Bc753aa53E938315f57B13EDB6E1b168f0d26", + "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000036502c6e24c51ba4839c4c4a070aeb52e1adb672", "isProxy": false }, { "name": "PausableHook", - "address": "0x9667EfF1556A9D092fdbeC09244CB99b677E9D1E", + "address": "0x2A9E9188C7e76f3345e91fD4650aC654A9FE355C", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0x36502C6e24C51ba4839c4c4A070aeB52E1adB672", + "address": "0xC5BE013eCE7996Cb8F04FF46ea93c57a04408CD5", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0x2A9E9188C7e76f3345e91fD4650aC654A9FE355C", + "address": "0xA9425D5cBcD2c83EB2a5BF453EAA18968db3ef77", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xF78deCe5Cf97e1bd61C202A5ba1af33b87454878", - "constructorArguments": "0000000000000000000000002a9e9188c7e76f3345e91fd4650ac654a9fe355c0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e0203900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "address": "0xFb55597F07417b08195Ba674f4dd58aeC9B89FBB", + "constructorArguments": "000000000000000000000000a9425d5cbcd2c83eb2a5bf453eaa18968db3ef770000000000000000000000006966b0e55883d49bfb24539356a2f8a673e0203900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x2A9E9188C7e76f3345e91fD4650aC654A9FE355C" + "expectedimplementation": "0xA9425D5cBcD2c83EB2a5BF453EAA18968db3ef77" }, { "name": "ProtocolFee", - "address": "0x0E18b28D98C2efDb59252c021320F203305b1B66", + "address": "0xB6a4129c305056d80fFfea96DdbDCf1F58BC8240", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0xe63844Ca06Ce8E6D4097Cb33E9b3d62704122307", + "address": "0x6202f54B22b72526dEC5364F6b7bE2bd0abEd8A6", "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", "isProxy": false } ], - "neuratestnet": [ + "incentivtestnet": [ { "name": "ProxyAdmin", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", + "address": "0xD221ffdAa65518BF56c8623e04eA0380CE1BfaE2", "constructorArguments": "", "isProxy": false }, { "name": "Mailbox", - "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000010b", + "address": "0xcCcC2A71810f9D16770F8062dccc47f7F7C7bA2E", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000007082", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "00000000000000000000000054148470292c24345fb828b003461a94444145170000000000000000000000006966b0e55883d49bfb24539356a2f8a673e0203900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "address": "0xB7697612fbfb4ad02a11dCa16e9711eCB6Da4ceA", + "constructorArguments": "000000000000000000000000cccc2a71810f9d16770f8062dccc47f7f7c7ba2e000000000000000000000000d221ffdaa65518bf56c8623e04ea0380ce1bfae200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0x54148470292C24345fb828B003461a9444414517" + "expectedimplementation": "0xcCcC2A71810f9D16770F8062dccc47f7F7C7bA2E" }, { "name": "MerkleTreeHook", - "address": "0x36502C6e24C51ba4839c4c4A070aeB52E1adB672", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", + "address": "0x7740342863717e26E3d5B828639834ADfce8a061", + "constructorArguments": "000000000000000000000000b7697612fbfb4ad02a11dca16e9711ecb6da4cea", "isProxy": false }, { "name": "FallbackRoutingHook", - "address": "0xe18Bc753aa53E938315f57B13EDB6E1b168f0d26", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000036502c6e24c51ba4839c4c4a070aeb52e1adb672", + "address": "0xa318ffca299412ce76B7cb980850ceDB15584E7e", + "constructorArguments": "000000000000000000000000b7697612fbfb4ad02a11dca16e9711ecb6da4cea000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000007740342863717e26e3d5b828639834adfce8a061", "isProxy": false }, { "name": "PausableHook", - "address": "0x2A9E9188C7e76f3345e91fD4650aC654A9FE355C", + "address": "0xE023239c8dfc172FF008D8087E7442d3eBEd9350", "constructorArguments": "", "isProxy": false }, { "name": "StorageGasOracle", - "address": "0xC5BE013eCE7996Cb8F04FF46ea93c57a04408CD5", + "address": "0xf3e31239257b300Daa566131aA6b9e45Ef2A4266", "constructorArguments": "", "isProxy": false }, { "name": "InterchainGasPaymaster", - "address": "0xA9425D5cBcD2c83EB2a5BF453EAA18968db3ef77", + "address": "0x062dF6670d8F4E1dB8C1caaFf590e9c290147bba", "constructorArguments": "", "isProxy": false }, { "name": "TransparentUpgradeableProxy", - "address": "0xFb55597F07417b08195Ba674f4dd58aeC9B89FBB", - "constructorArguments": "000000000000000000000000a9425d5cbcd2c83eb2a5bf453eaa18968db3ef770000000000000000000000006966b0e55883d49bfb24539356a2f8a673e0203900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "address": "0xFfA20C4c8e3b2A2C1220134684FEe23EEB8872d0", + "constructorArguments": "000000000000000000000000062df6670d8f4e1db8c1caaff590e9c290147bba000000000000000000000000d221ffdaa65518bf56c8623e04ea0380ce1bfae200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", "isProxy": true, - "expectedimplementation": "0xA9425D5cBcD2c83EB2a5BF453EAA18968db3ef77" + "expectedimplementation": "0x062dF6670d8F4E1dB8C1caaFf590e9c290147bba" }, { "name": "ProtocolFee", - "address": "0xB6a4129c305056d80fFfea96DdbDCf1F58BC8240", + "address": "0x1D8742741d87d886F72dC0379541Cd4188DFd46E", "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", "isProxy": false }, { "name": "ValidatorAnnounce", - "address": "0x6202f54B22b72526dEC5364F6b7bE2bd0abEd8A6", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", + "address": "0xE3D93F9296FA3dF262E1a54f0de02F71E845af6b", + "constructorArguments": "000000000000000000000000b7697612fbfb4ad02a11dca16e9711ecb6da4cea", + "isProxy": false + } + ], + "celosepolia": [ + { + "name": "ProxyAdmin", + "address": "0x304cAb315c93B87AAdb2B826A791b2c1Bf749996", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x7d498740A4572f2B5c6b0A1Ba9d1d9DbE207e89E", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000aa044c", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x7FE7EA170cf08A25C2ff315814D96D93C311E692", + "constructorArguments": "0000000000000000000000007d498740a4572f2b5c6b0a1ba9d1d9dbe207e89e000000000000000000000000304cab315c93b87aadb2b826a791b2c1bf74999600000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x7d498740A4572f2B5c6b0A1Ba9d1d9DbE207e89E" + }, + { + "name": "ProxyAdmin", + "address": "0x05Ea36Caee7d92C173334C9D97DcD39ABdCB2b69", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0xcf5BaaF976C80a66Fa7839715C45788f60041A33", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000aa044c", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x58483b754Abb1E8947BE63d6b95DF75b8249543A", + "constructorArguments": "000000000000000000000000cf5baaf976c80a66fa7839715c45788f60041a3300000000000000000000000005ea36caee7d92c173334c9d97dcd39abdcb2b6900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xcf5BaaF976C80a66Fa7839715C45788f60041A33" + }, + { + "name": "ProxyAdmin", + "address": "0x4926a10788306D84202A2aDbd290b7743146Cc17", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000aa044c", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x07009DA2249c388aD0f416a235AfE90D784e1aAc", + "constructorArguments": "00000000000000000000000098aae089cad930c64a76dd2247a2ac5773a4b8ce0000000000000000000000004926a10788306d84202a2adbd290b7743146cc1700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE" + }, + { + "name": "ProxyAdmin", + "address": "0xF7561c34f17A32D5620583A3397C304e7038a7F6", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0x2b2a158B4059C840c7aC67399B153bb567D06303", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000aa044c", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x26cD82217c5cfc1b4A3b36D2799c7cD84b0fd7B5", + "constructorArguments": "0000000000000000000000002b2a158b4059c840c7ac67399b153bb567d06303000000000000000000000000f7561c34f17a32d5620583a3397c304e7038a7f600000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x2b2a158B4059C840c7aC67399B153bb567D06303" + }, + { + "name": "ProxyAdmin", + "address": "0x5CE550e14B82a9F32A0aaF9eFc4Fce548D8A0B3e", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0xeC7eb4196Bd601DEa7585A744FbFB4CF11278450", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000aa044c", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9", + "constructorArguments": "000000000000000000000000ec7eb4196bd601dea7585a744fbfb4cf112784500000000000000000000000005ce550e14b82a9f32a0aaf9efc4fce548d8a0b3e00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xeC7eb4196Bd601DEa7585A744FbFB4CF11278450" + }, + { + "name": "ProxyAdmin", + "address": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "Mailbox", + "address": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8", + "constructorArguments": "0000000000000000000000000000000000000000000000000000000000aa044c", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xD0680F80F4f947968206806C2598Cbc5b6FE5b03", + "constructorArguments": "000000000000000000000000c756cfc1b7d0d4646589edf10ed54b201237f5e8000000000000000000000000267b6b6eaf6790fae5d5e9070f28a9ce64cbf27900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8" + }, + { + "name": "MerkleTreeHook", + "address": "0x1b0d4c88288258D57998F0bdb30489007A42B834", + "constructorArguments": "000000000000000000000000d0680f80f4f947968206806c2598cbc5b6fe5b03", + "isProxy": false + }, + { + "name": "FallbackRoutingHook", + "address": "0xaA3022A7e114D4eaF9BBcA552B850346C55E5550", + "constructorArguments": "000000000000000000000000d0680f80f4f947968206806c2598cbc5b6fe5b03000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c0000000000000000000000001b0d4c88288258d57998f0bdb30489007a42b834", + "isProxy": false + }, + { + "name": "PausableHook", + "address": "0xd09D08a19C6609a1B51e1ca6a055861E7e7A4400", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StorageGasOracle", + "address": "0x56c09458cC7863fff1Cc6Bcb6652Dcc3412FcA86", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "InterchainGasPaymaster", + "address": "0x3572a9d808738922194921b275B2A55414BcDA57", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "TransparentUpgradeableProxy", + "address": "0xB2A79c5e63A3e949e4b3982052958E3eEbD3AA83", + "constructorArguments": "0000000000000000000000003572a9d808738922194921b275b2a55414bcda57000000000000000000000000267b6b6eaf6790fae5d5e9070f28a9ce64cbf27900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044485cc955000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", + "isProxy": true, + "expectedimplementation": "0x3572a9d808738922194921b275B2A55414BcDA57" + }, + { + "name": "ProtocolFee", + "address": "0x4D40f433F2f89cBa4BB9D994FD18D8302C378D26", + "constructorArguments": "000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c", + "isProxy": false + }, + { + "name": "ValidatorAnnounce", + "address": "0x555B5d0AD2C1f5010e39dA82342A2E85402E4637", + "constructorArguments": "000000000000000000000000d0680f80f4f947968206806c2598cbc5b6fe5b03", "isProxy": false } ] diff --git a/typescript/infra/config/environments/testnet4/create2/addresses.json b/typescript/infra/config/environments/testnet4/create2/addresses.json index d4a0d3d88f4..9257cb66f1d 100644 --- a/typescript/infra/config/environments/testnet4/create2/addresses.json +++ b/typescript/infra/config/environments/testnet4/create2/addresses.json @@ -1,7 +1,4 @@ { - "alfajores": { - "Create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a" - }, "fuji": { "Create2Factory": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a" }, diff --git a/typescript/infra/config/environments/testnet4/create2/verification.json b/typescript/infra/config/environments/testnet4/create2/verification.json index b208fd2f668..c27d181287f 100644 --- a/typescript/infra/config/environments/testnet4/create2/verification.json +++ b/typescript/infra/config/environments/testnet4/create2/verification.json @@ -1,12 +1,4 @@ { - "alfajores": [ - { - "name": "Create2Factory", - "address": "0xc97D8e6f57b0d64971453dDc6EB8483fec9d163a", - "constructorArguments": "", - "isProxy": false - } - ], "kovan": [ { "name": "Create2Factory", diff --git a/typescript/infra/config/environments/testnet4/funding.ts b/typescript/infra/config/environments/testnet4/funding.ts index 328478565ba..093401ba3ae 100644 --- a/typescript/infra/config/environments/testnet4/funding.ts +++ b/typescript/infra/config/environments/testnet4/funding.ts @@ -10,7 +10,7 @@ export const keyFunderConfig: KeyFunderConfig< > = { docker: { repo: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: 'ac07f38-20250716-204554', + tag: 'e5e9700-20250909-164327', }, // We're currently using the same deployer key as testnet2. // To minimize nonce clobbering we offset the key funder cron @@ -24,35 +24,27 @@ export const keyFunderConfig: KeyFunderConfig< [Contexts.Hyperlane]: [Role.Relayer, Role.Kathy], [Contexts.ReleaseCandidate]: [Role.Relayer, Role.Kathy], }, - chainsToSkip: ['hyperliquidevmtestnet'], + chainsToSkip: ['hyperliquidevmtestnet', 'infinityvmmonza'], // desired balance config desiredBalancePerChain: { - abstracttestnet: '0.1', - alephzeroevmtestnet: '2', - alfajores: '5', arbitrumsepolia: '0.1', arcadiatestnet2: '0.1', auroratestnet: '0.05', basecamptestnet: '0.05', basesepolia: '0.1', - bepolia: '0.05', bsctestnet: '5', carrchaintestnet: '100', celestiatestnet: '0', - chronicleyellowstone: '0.001', + celosepolia: '0.5', citreatestnet: '0.001', - connextsepolia: '1', cotitestnet: '1', - ecotestnet: '0.02', // no funding for solana eclipsetestnet: '0', - flametestnet: '0.1', - formtestnet: '0.1', fuji: '5', holesky: '5', hyperliquidevmtestnet: '0.1', + incentivtestnet: '1', infinityvmmonza: '0', - inksepolia: '0.1', kyvetestnet: '0', megaethtestnet: '0.01', milkywaytestnet: '0', @@ -60,28 +52,21 @@ export const keyFunderConfig: KeyFunderConfig< monadtestnet: '0.1', neuratestnet: '0.1', nobletestnet: '0', - odysseytestnet: '0.1', optimismsepolia: '0.1', paradexsepolia: '0', - plumetestnet2: '0.1', polygonamoy: '0.2', + radixtestnet: '0', scrollsepolia: '1', sepolia: '5', starknetsepolia: '0', // no funding for SVM chains solanatestnet: '0', somniatestnet: '10', - soneiumtestnet: '0.1', - sonicblaze: '0.1', // no funding for SVM chains sonicsvmtestnet: '0', subtensortestnet: '0.1', - superpositiontestnet: '1', - unichaintestnet: '0.1', - weavevmtestnet: '0.1', }, desiredKathyBalancePerChain: { - alfajores: '1', arbitrumsepolia: '0', basesepolia: '0', bsctestnet: '1', @@ -101,8 +86,8 @@ export const keyFunderConfig: KeyFunderConfig< solanatestnet: '0', superpositiontestnet: '0', }, + desiredRebalancerBalancePerChain: {}, igpClaimThresholdPerChain: { - alfajores: '1', arbitrumsepolia: '0.05', basesepolia: '0.05', bsctestnet: '1', diff --git a/typescript/infra/config/environments/testnet4/gasPrices.json b/typescript/infra/config/environments/testnet4/gasPrices.json index d6d99195a54..99dc0b958de 100644 --- a/typescript/infra/config/environments/testnet4/gasPrices.json +++ b/typescript/infra/config/environments/testnet4/gasPrices.json @@ -1,16 +1,4 @@ { - "abstracttestnet": { - "amount": "0.025", - "decimals": 9 - }, - "alephzeroevmtestnet": { - "amount": "40.0", - "decimals": 9 - }, - "alfajores": { - "amount": "25.001", - "decimals": 9 - }, "arbitrumsepolia": { "amount": "0.1", "decimals": 9 @@ -31,10 +19,6 @@ "amount": "0.001000068", "decimals": 9 }, - "bepolia": { - "amount": "40.000000007", - "decimals": 9 - }, "bsctestnet": { "amount": "1.0", "decimals": 9 @@ -47,38 +31,22 @@ "amount": "0.001", "decimals": 1 }, - "chronicleyellowstone": { - "amount": "0.01", + "celosepolia": { + "amount": "25.001", "decimals": 9 }, "citreatestnet": { "amount": "0.0100001", "decimals": 9 }, - "connextsepolia": { - "amount": "0.1", - "decimals": 9 - }, "cotitestnet": { "amount": "1.2", "decimals": 9 }, - "ecotestnet": { - "amount": "0.001000252", - "decimals": 9 - }, "eclipsetestnet": { "amount": "0.001", "decimals": 9 }, - "flametestnet": { - "amount": "2.0", - "decimals": 9 - }, - "formtestnet": { - "amount": "0.00100005", - "decimals": 9 - }, "fuji": { "amount": "0.000000002", "decimals": 9 @@ -91,12 +59,12 @@ "amount": "0.1", "decimals": 9 }, - "infinityvmmonza": { + "incentivtestnet": { "amount": "1.000000007", "decimals": 9 }, - "inksepolia": { - "amount": "0.001000252", + "infinityvmmonza": { + "amount": "1.000000007", "decimals": 9 }, "kyvetestnet": { @@ -127,10 +95,6 @@ "amount": "0.001", "decimals": 1 }, - "odysseytestnet": { - "amount": "1.000000252", - "decimals": 9 - }, "optimismsepolia": { "amount": "0.001000251", "decimals": 9 @@ -139,14 +103,14 @@ "amount": "0.01", "decimals": 9 }, - "plumetestnet2": { - "amount": "0.01", - "decimals": 9 - }, "polygonamoy": { "amount": "56.551052651", "decimals": 9 }, + "radixtestnet": { + "amount": "50.0", + "decimals": 6 + }, "scrollsepolia": { "amount": "0.015680115", "decimals": 9 @@ -163,14 +127,6 @@ "amount": "10.0", "decimals": 9 }, - "soneiumtestnet": { - "amount": "0.001000252", - "decimals": 9 - }, - "sonicblaze": { - "amount": "1.1", - "decimals": 9 - }, "sonicsvmtestnet": { "amount": "0.01", "decimals": 1 @@ -182,17 +138,5 @@ "subtensortestnet": { "amount": "20.134283587", "decimals": 9 - }, - "superpositiontestnet": { - "amount": "0.01", - "decimals": 9 - }, - "unichaintestnet": { - "amount": "0.001000254", - "decimals": 9 - }, - "weavevmtestnet": { - "amount": "1.2", - "decimals": 9 } } diff --git a/typescript/infra/config/environments/testnet4/helloworld/hyperlane/addresses.json b/typescript/infra/config/environments/testnet4/helloworld/hyperlane/addresses.json index fde39daa8ea..4eda2aade90 100644 --- a/typescript/infra/config/environments/testnet4/helloworld/hyperlane/addresses.json +++ b/typescript/infra/config/environments/testnet4/helloworld/hyperlane/addresses.json @@ -2,9 +2,6 @@ "scrollsepolia": { "router": "0x66b71A4e18FbE09a6977A6520B47fEDdffA82a1c" }, - "alfajores": { - "router": "0xD0Ef694E96Bb695DC829f71956227eD141e3089F" - }, "fuji": { "router": "0x0B1C1B54f45e02552331D3106e71f5e0b573D5D4" }, diff --git a/typescript/infra/config/environments/testnet4/helloworld/hyperlane/verification.json b/typescript/infra/config/environments/testnet4/helloworld/hyperlane/verification.json index 88b12edebbd..119b827da93 100644 --- a/typescript/infra/config/environments/testnet4/helloworld/hyperlane/verification.json +++ b/typescript/infra/config/environments/testnet4/helloworld/hyperlane/verification.json @@ -1,48 +1,4 @@ { - "alfajores": [ - { - "name": "HelloWorld", - "address": "0x921D3A71386d3Ab8f3AD4eC91ce1556D5FC26859", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f857706ce59cb7ae6df81bbd0b0a656db3e6beda", - "isProxy": false - }, - { - "name": "HelloWorld", - "address": "0x477D860f8F41bC69dDD32821F2Bf2C2Af0243F16", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac1", - "isProxy": false - }, - { - "name": "HelloWorld", - "address": "0x76bDE8069b3467A459262192509Ad5c00AcbdaF0", - "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e590000000000000000000000001246529eddca523afe5c6b9414299633d2e16697", - "isProxy": false - }, - { - "name": "HelloWorld", - "address": "0x39e08602570237433673B1340Da17105cA098EE7", - "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e590000000000000000000000001246529eddca523afe5c6b9414299633d2e16697", - "isProxy": false - }, - { - "name": "HelloWorld", - "address": "0x0231C1A2CfDbC2d2FA8363c3eC60c85a458088aE", - "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e590000000000000000000000000000000000000000000000000000000000000000", - "isProxy": false - }, - { - "name": "HelloWorld", - "address": "0xb58F3D0CA2B26803eA6a64696989102cE301Fd23", - "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e590000000000000000000000000000000000000000000000000000000000000000", - "isProxy": false - }, - { - "name": "HelloWorld", - "address": "0xD0Ef694E96Bb695DC829f71956227eD141e3089F", - "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e590000000000000000000000000000000000000000000000000000000000000000", - "isProxy": false - } - ], "fuji": [ { "name": "HelloWorld", diff --git a/typescript/infra/config/environments/testnet4/helloworld/rc/addresses.json b/typescript/infra/config/environments/testnet4/helloworld/rc/addresses.json index c2141bf7bd6..da149ff8e92 100644 --- a/typescript/infra/config/environments/testnet4/helloworld/rc/addresses.json +++ b/typescript/infra/config/environments/testnet4/helloworld/rc/addresses.json @@ -2,9 +2,6 @@ "scrollsepolia": { "router": "0xA9425D5cBcD2c83EB2a5BF453EAA18968db3ef77" }, - "alfajores": { - "router": "0x4D1d8394cBb445A75aE63fDd24421A353B73FF25" - }, "sepolia": { "router": "0xEB25e6e42B743a815E5C0409007993a828a0565f" }, diff --git a/typescript/infra/config/environments/testnet4/helloworld/rc/verification.json b/typescript/infra/config/environments/testnet4/helloworld/rc/verification.json index 0b95867093f..726bf385bff 100644 --- a/typescript/infra/config/environments/testnet4/helloworld/rc/verification.json +++ b/typescript/infra/config/environments/testnet4/helloworld/rc/verification.json @@ -7,14 +7,6 @@ "isProxy": false } ], - "alfajores": [ - { - "name": "HelloWorld", - "address": "0x4D1d8394cBb445A75aE63fDd24421A353B73FF25", - "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e590000000000000000000000000000000000000000000000000000000000000000", - "isProxy": false - } - ], "sepolia": [ { "name": "HelloWorld", diff --git a/typescript/infra/config/environments/testnet4/infrastructure.ts b/typescript/infra/config/environments/testnet4/infrastructure.ts index 0449da0dbb5..eb747804c48 100644 --- a/typescript/infra/config/environments/testnet4/infrastructure.ts +++ b/typescript/infra/config/environments/testnet4/infrastructure.ts @@ -41,8 +41,6 @@ export const infrastructure: InfrastructureConfig = { 'hyperlane-testnet4-', 'rc-testnet4-', 'testnet4-', - // All vanguard secrets - 'vanguard', ], }, }; diff --git a/typescript/infra/config/environments/testnet4/ism/verification.json b/typescript/infra/config/environments/testnet4/ism/verification.json index 5baedce27fb..9908151070f 100644 --- a/typescript/infra/config/environments/testnet4/ism/verification.json +++ b/typescript/infra/config/environments/testnet4/ism/verification.json @@ -1,122 +1,4 @@ { - "alfajores": [ - { - "address": "0x9AF85731EDd41E2E50F81Ef8a0A69D2fB836EDf9", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMultisigIsmFactory" - }, - { - "address": "0xBEd8Fd6d5c6cBd878479C25f4725C7c842a43821", - "constructorArguments": "", - "isProxy": false, - "name": "StaticAggregationIsmFactory" - }, - { - "address": "0x98F44EA5b9cA6aa02a5B75f31E0621083d9096a2", - "constructorArguments": "", - "isProxy": false, - "name": "DomainRoutingIsmFactory" - }, - { - "address": "0x6525Ac4008E38e0E70DaEf59d5f0e1721bd8aA83", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMerkleRootMultisigIsmFactory" - }, - { - "address": "0x4C739E01f295B70762C0bA9D86123E1775C2f703", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMessageIdMultisigIsmFactory" - }, - { - "address": "0x9A574458497FCaB5E5673a2610C108725002DbA3", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMerkleRootMultisigIsmFactory" - }, - { - "address": "0xa9C7e306C0941896CA1fd528aA59089571D8D67E", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMerkleRootMultisigIsmFactory" - }, - { - "address": "0x826Ab153e944cE1B251aBD174071DF70d71132D7", - "name": "StaticMerkleRootMultisigIsm" - }, - { - "address": "0xC1b8c0e56D6a34940Ee2B86172450B54AFd633A7", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMessageIdMultisigIsmFactory" - }, - { - "address": "0x78ebeCE0fCAa39F0F0C422E87EBd6BcB708aa763", - "name": "StaticMessageIdMultisigIsm" - }, - { - "address": "0x4bE8AC22f506B1504C93C3A5b1579C5e7c550D9C", - "constructorArguments": "", - "isProxy": false, - "name": "StaticAggregationIsmFactory" - }, - { - "address": "0xd93F514f36d528DCDc3af1C92788f1bf1f480A7f", - "name": "StaticAggregationIsm" - }, - { - "address": "0x71bB34Ee833467443628CEdFAA188B2387827Cee", - "constructorArguments": "", - "isProxy": false, - "name": "StaticAggregationHookFactory" - }, - { - "address": "0x019e0a350D85eF67250dD88CD6477ad5E0c6E31e", - "name": "StaticAggregationHook" - }, - { - "address": "0x37308d498bc7B0f002cb02Cf8fA01770dC2169c8", - "constructorArguments": "", - "isProxy": false, - "name": "DomainRoutingIsmFactory" - }, - { - "address": "0x94087079Bf22D8e2BCE02e2180e738C3F21dD44E", - "name": "DomainRoutingIsm" - }, - { - "address": "0xd5ab9FaC601Ed731d5e9c87E5977D2e5fD380666", - "constructorArguments": "", - "isProxy": true, - "name": "DomainRoutingIsm" - }, - { - "address": "0x374961678da5911083599314974B94094513F95c", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMerkleRootWeightedMultisigIsmFactory" - }, - { - "address": "0x1Fa22d908f5a5E7F5429D9146E5a3740D8AC10d7", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMessageIdWeightedMultisigIsmFactory" - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x09B61c756B3b85BfE871157734e6460d66C8285b", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x5681fbf346EED083FD86b299c150c9701A65FE74", - "constructorArguments": "", - "isProxy": true - } - ], "arbitrumsepolia": [ { "address": "0xb09A2f4500e4c0BDb7B7c43249FCe475256C9b8c", @@ -377,492 +259,320 @@ "isProxy": true } ], - "connextsepolia": [ + "fuji": [ { - "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", + "address": "0x094652a8ea2153A03916771a778E7b66839A4F58", + "constructorArguments": "", + "isProxy": false, + "name": "StaticMultisigIsmFactory" + }, + { + "address": "0x9fB5D10C07569F2EBdc8ec4432B3a52b6d0ad9A0", + "constructorArguments": "", + "isProxy": false, + "name": "StaticAggregationIsmFactory" + }, + { + "address": "0xB24C91238eA30D59CF58CEB8dd5e4eaf70544d47", + "constructorArguments": "", + "isProxy": false, + "name": "DomainRoutingIsmFactory" + }, + { + "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", "constructorArguments": "", "isProxy": false, "name": "StaticMerkleRootMultisigIsmFactory" }, { - "address": "0x209e7F9d40954E230008B9bb076a0901d32695e5", + "address": "0xA1e6d12a3F5F7e05E4D6cb39E71534F27fE29561", "constructorArguments": "", - "isProxy": true, + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" + }, + { + "address": "0xc25e8cE0960fa2573AFa591484F2cA6e4497C2e5", + "constructorArguments": "", + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" + }, + { + "address": "0x074D3C4249AFdac44B70d1D220F358Ff895F7a80", "name": "StaticMerkleRootMultisigIsm" }, { - "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", + "address": "0x93F50Ac4E5663DAAb03508008d592f6260964f62", "constructorArguments": "", "isProxy": false, - "name": "StaticMessageIdMultisigIsmFactory" + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", + "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "name": "StaticMerkleRootMultisigIsm" + }, + { + "address": "0x90e1F9918F304645e4F6324E5C0EAc70138C84Ce", "constructorArguments": "", - "isProxy": true, + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" + }, + { + "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", "name": "StaticMessageIdMultisigIsm" }, { - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", + "address": "0xF588129ed84F219A1f0f921bE7Aa1B2176516858", "constructorArguments": "", "isProxy": false, "name": "StaticAggregationIsmFactory" }, { - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", - "constructorArguments": "", - "isProxy": true, + "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", "name": "StaticAggregationIsm" }, { - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", + "address": "0x99554CC33cBCd6EDDd2f3fc9c7C9194Cb3b5df1E", "constructorArguments": "", "isProxy": false, "name": "StaticAggregationHookFactory" }, { - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", - "constructorArguments": "", - "isProxy": true, + "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", "name": "StaticAggregationHook" }, { - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", + "address": "0xf9271189Cb30AD1F272f1A9EB2272224135B9350", "constructorArguments": "", "isProxy": false, "name": "DomainRoutingIsmFactory" }, { - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "address": "0x930c36A0c978B0b86B544859692A1D94c5148204", + "name": "DomainRoutingIsm" + }, + { + "address": "0x683a81E0e1a238dcA7341e04c08d3bba6f0Cb74f", + "constructorArguments": "", + "isProxy": false, + "name": "DomainRoutingIsmFactory" + }, + { + "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", "constructorArguments": "", "isProxy": true, "name": "DomainRoutingIsm" }, { - "address": "0x8584590ad637C61C7cDF72eFF3381Ee1c3D1bC8E", + "address": "0xff93F32997Ac5450995121385aCE96b184efe89E", "constructorArguments": "", "isProxy": false, "name": "StaticMerkleRootWeightedMultisigIsmFactory" }, { - "address": "0xcCB305B1f21e5FbC85D1DD7Be5cd8d5bf5B7f863", + "address": "0x8eAB8cBb9037e818C321f675c0bc2EA4649003CF", "constructorArguments": "", "isProxy": false, "name": "StaticMessageIdWeightedMultisigIsmFactory" }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x385aE4e729d86Ac4983d01597c33dFAa8C35686A", + "address": "0xE55d19c15a8EFD8420f6E1b922cbd8C126Bc8582", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0xE14B55637Cc5aF2A55024D2f281446b388D64874", + "address": "0x470e03D0E844DC7D3f11cc3f299D2C48cd14Cf3E", "constructorArguments": "", "isProxy": true } ], - "ecotestnet": [ + "holesky": [ { - "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", + "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", "constructorArguments": "", "isProxy": false, "name": "StaticMerkleRootMultisigIsmFactory" }, { - "address": "0x209e7F9d40954E230008B9bb076a0901d32695e5", + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", "constructorArguments": "", "isProxy": true, "name": "StaticMerkleRootMultisigIsm" }, { - "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", + "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", "constructorArguments": "", "isProxy": false, "name": "StaticMessageIdMultisigIsmFactory" }, { - "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", + "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", "constructorArguments": "", "isProxy": true, "name": "StaticMessageIdMultisigIsm" }, { - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", + "address": "0x54148470292C24345fb828B003461a9444414517", "constructorArguments": "", "isProxy": false, "name": "StaticAggregationIsmFactory" }, { - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2", "constructorArguments": "", "isProxy": true, "name": "StaticAggregationIsm" }, { - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", + "address": "0x589C201a07c26b4725A4A829d772f24423da480B", "constructorArguments": "", "isProxy": false, "name": "StaticAggregationHookFactory" }, { - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "address": "0xD6B8D6C372e07f67FeAb75403c0Ec88E3cce7Ab7", "constructorArguments": "", "isProxy": true, "name": "StaticAggregationHook" }, { - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", + "address": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", "constructorArguments": "", "isProxy": false, "name": "DomainRoutingIsmFactory" }, { - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "address": "0x8CF9aB5F805072A007dAd0ae56E0099e83B14b61", "constructorArguments": "", "isProxy": true, "name": "DomainRoutingIsm" }, { - "address": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8", + "address": "0xFb55597F07417b08195Ba674f4dd58aeC9B89FBB", "constructorArguments": "", "isProxy": false, "name": "StaticMerkleRootWeightedMultisigIsmFactory" }, { - "address": "0x628BC518ED1e0E8C6cbcD574EbA0ee29e7F6943E", + "address": "0x0E18b28D98C2efDb59252c021320F203305b1B66", "constructorArguments": "", "isProxy": false, "name": "StaticMessageIdWeightedMultisigIsmFactory" }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x76743F08b266371dCC7840bD7A445D5A52b3a752", + "address": "0x6E2B21A88b70cd3Bc455174941753512fB8a981e", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x1A70D3cbC72eF4941Ee047FA1F1E0469B940EDda", + "address": "0xEFfa6907D9caB3680b1Be2AA2207B0Fb8900E27a", "constructorArguments": "", "isProxy": true } ], - "fuji": [ + "moonbasealpha": [ { - "address": "0x094652a8ea2153A03916771a778E7b66839A4F58", + "address": "0x4266D8Dd66D8Eb3934c8942968d1e54214D072d3", "constructorArguments": "", "isProxy": false, "name": "StaticMultisigIsmFactory" }, { - "address": "0x9fB5D10C07569F2EBdc8ec4432B3a52b6d0ad9A0", + "address": "0x759c4Eb4575B651a9f0Fb46653dd7B2F32fD7310", "constructorArguments": "", "isProxy": false, "name": "StaticAggregationIsmFactory" }, { - "address": "0xB24C91238eA30D59CF58CEB8dd5e4eaf70544d47", + "address": "0x561331FafB7f2ABa77E77780178ADdD1A37bdaBD", "constructorArguments": "", "isProxy": false, "name": "DomainRoutingIsmFactory" }, { - "address": "0x76a1aaE73e9D837ceF10Ac5AF5AfDD30d7612f98", + "address": "0x0616A79374e81eB1d2275eCe5837aD050f9c53f1", "constructorArguments": "", "isProxy": false, "name": "StaticMerkleRootMultisigIsmFactory" }, { - "address": "0xA1e6d12a3F5F7e05E4D6cb39E71534F27fE29561", + "address": "0x3D696c38Dd958e635f9077e65b64aA9cf7c92627", "constructorArguments": "", "isProxy": false, "name": "StaticMessageIdMultisigIsmFactory" }, { - "address": "0xc25e8cE0960fa2573AFa591484F2cA6e4497C2e5", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMerkleRootMultisigIsmFactory" - }, - { - "address": "0x074D3C4249AFdac44B70d1D220F358Ff895F7a80", - "name": "StaticMerkleRootMultisigIsm" - }, - { - "address": "0x93F50Ac4E5663DAAb03508008d592f6260964f62", + "address": "0xA59Ba0A8D4ea5A5DC9c8B0101ba7E6eE6C3399A4", "constructorArguments": "", "isProxy": false, "name": "StaticMerkleRootMultisigIsmFactory" }, { - "address": "0x0683D0258A162a87E1F25C454BaA4e9E9DE46B0a", + "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186", "name": "StaticMerkleRootMultisigIsm" }, { - "address": "0x90e1F9918F304645e4F6324E5C0EAc70138C84Ce", + "address": "0x8f919348F9C4619A196Acb5e377f49E5E2C0B569", "constructorArguments": "", "isProxy": false, "name": "StaticMessageIdMultisigIsmFactory" }, { - "address": "0xd3262339D39103a4b41379b183b0311B78c9a11F", + "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb", "name": "StaticMessageIdMultisigIsm" }, { - "address": "0xF588129ed84F219A1f0f921bE7Aa1B2176516858", + "address": "0x0048FaB53526D9a0478f66D660059E3E3611FE3E", "constructorArguments": "", "isProxy": false, "name": "StaticAggregationIsmFactory" }, { - "address": "0x6716Ef4e72c74e66eB40198B16b4379a763fCEd8", + "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e", "name": "StaticAggregationIsm" }, { - "address": "0x99554CC33cBCd6EDDd2f3fc9c7C9194Cb3b5df1E", + "address": "0x00DFB81Bfc45fa03060b605273147F274ea807E5", "constructorArguments": "", "isProxy": false, "name": "StaticAggregationHookFactory" }, { - "address": "0xBDD7Cfb61D7ae89343b428D3E527BDA9705c4EAC", + "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9", "name": "StaticAggregationHook" }, { - "address": "0xf9271189Cb30AD1F272f1A9EB2272224135B9350", + "address": "0x385C7f179168f5Da92c72E17AE8EF50F3874077f", "constructorArguments": "", "isProxy": false, "name": "DomainRoutingIsmFactory" }, { - "address": "0x930c36A0c978B0b86B544859692A1D94c5148204", + "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9", "name": "DomainRoutingIsm" }, { - "address": "0x683a81E0e1a238dcA7341e04c08d3bba6f0Cb74f", + "address": "0xE8F752e5C4E1A6a2e3eAfa42d44D601A22d78f2b", "constructorArguments": "", "isProxy": false, "name": "DomainRoutingIsmFactory" }, { - "address": "0xD47593C21C2429de56AcA808bb2Bbc236c6311DE", + "address": "0x063C2D908EAb532Cd207F309F0fd176ae6b2e1d1", "constructorArguments": "", "isProxy": true, "name": "DomainRoutingIsm" - }, + } + ], + "optimismsepolia": [ { - "address": "0xff93F32997Ac5450995121385aCE96b184efe89E", + "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", "constructorArguments": "", "isProxy": false, - "name": "StaticMerkleRootWeightedMultisigIsmFactory" - }, - { - "address": "0x8eAB8cBb9037e818C321f675c0bc2EA4649003CF", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMessageIdWeightedMultisigIsmFactory" - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0xE55d19c15a8EFD8420f6E1b922cbd8C126Bc8582", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x470e03D0E844DC7D3f11cc3f299D2C48cd14Cf3E", - "constructorArguments": "", - "isProxy": true - } - ], - "holesky": [ - { - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMerkleRootMultisigIsmFactory" - }, - { - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", - "constructorArguments": "", - "isProxy": true, - "name": "StaticMerkleRootMultisigIsm" - }, - { - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMessageIdMultisigIsmFactory" - }, - { - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", - "constructorArguments": "", - "isProxy": true, - "name": "StaticMessageIdMultisigIsm" - }, - { - "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "", - "isProxy": false, - "name": "StaticAggregationIsmFactory" - }, - { - "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2", - "constructorArguments": "", - "isProxy": true, - "name": "StaticAggregationIsm" - }, - { - "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "", - "isProxy": false, - "name": "StaticAggregationHookFactory" - }, - { - "address": "0xD6B8D6C372e07f67FeAb75403c0Ec88E3cce7Ab7", - "constructorArguments": "", - "isProxy": true, - "name": "StaticAggregationHook" - }, - { - "address": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", - "constructorArguments": "", - "isProxy": false, - "name": "DomainRoutingIsmFactory" - }, - { - "address": "0x8CF9aB5F805072A007dAd0ae56E0099e83B14b61", - "constructorArguments": "", - "isProxy": true, - "name": "DomainRoutingIsm" - }, - { - "address": "0xFb55597F07417b08195Ba674f4dd58aeC9B89FBB", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMerkleRootWeightedMultisigIsmFactory" - }, - { - "address": "0x0E18b28D98C2efDb59252c021320F203305b1B66", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMessageIdWeightedMultisigIsmFactory" - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x6E2B21A88b70cd3Bc455174941753512fB8a981e", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0xEFfa6907D9caB3680b1Be2AA2207B0Fb8900E27a", - "constructorArguments": "", - "isProxy": true - } - ], - "moonbasealpha": [ - { - "address": "0x4266D8Dd66D8Eb3934c8942968d1e54214D072d3", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMultisigIsmFactory" - }, - { - "address": "0x759c4Eb4575B651a9f0Fb46653dd7B2F32fD7310", - "constructorArguments": "", - "isProxy": false, - "name": "StaticAggregationIsmFactory" - }, - { - "address": "0x561331FafB7f2ABa77E77780178ADdD1A37bdaBD", - "constructorArguments": "", - "isProxy": false, - "name": "DomainRoutingIsmFactory" - }, - { - "address": "0x0616A79374e81eB1d2275eCe5837aD050f9c53f1", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMerkleRootMultisigIsmFactory" - }, - { - "address": "0x3D696c38Dd958e635f9077e65b64aA9cf7c92627", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMessageIdMultisigIsmFactory" - }, - { - "address": "0xA59Ba0A8D4ea5A5DC9c8B0101ba7E6eE6C3399A4", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMerkleRootMultisigIsmFactory" - }, - { - "address": "0x97B07A8BCc0d212A4C76679b303A4C6441BB6186", - "name": "StaticMerkleRootMultisigIsm" - }, - { - "address": "0x8f919348F9C4619A196Acb5e377f49E5E2C0B569", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMessageIdMultisigIsmFactory" - }, - { - "address": "0xb1Fc72f251A6b6c37CD4Ef8f9287578BCc7CE9Eb", - "name": "StaticMessageIdMultisigIsm" - }, - { - "address": "0x0048FaB53526D9a0478f66D660059E3E3611FE3E", - "constructorArguments": "", - "isProxy": false, - "name": "StaticAggregationIsmFactory" - }, - { - "address": "0x13b6Cb8d51C7e15e5fFee432CBB1d4614972A87e", - "name": "StaticAggregationIsm" - }, - { - "address": "0x00DFB81Bfc45fa03060b605273147F274ea807E5", - "constructorArguments": "", - "isProxy": false, - "name": "StaticAggregationHookFactory" - }, - { - "address": "0x713496A1477a08230E526a4e9449FCCE4d6523e9", - "name": "StaticAggregationHook" - }, - { - "address": "0x385C7f179168f5Da92c72E17AE8EF50F3874077f", - "constructorArguments": "", - "isProxy": false, - "name": "DomainRoutingIsmFactory" - }, - { - "address": "0xCd76aC44337BbA003bb4Bed2Ac37604148F36bA9", - "name": "DomainRoutingIsm" - }, - { - "address": "0xE8F752e5C4E1A6a2e3eAfa42d44D601A22d78f2b", - "constructorArguments": "", - "isProxy": false, - "name": "DomainRoutingIsmFactory" - }, - { - "address": "0x063C2D908EAb532Cd207F309F0fd176ae6b2e1d1", - "constructorArguments": "", - "isProxy": true, - "name": "DomainRoutingIsm" - } - ], - "optimismsepolia": [ - { - "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMerkleRootMultisigIsmFactory" + "name": "StaticMerkleRootMultisigIsmFactory" }, { "address": "0x209e7F9d40954E230008B9bb076a0901d32695e5", @@ -1221,1063 +931,191 @@ "name": "DomainRoutingIsmFactory" }, { - "address": "0xA9999B4abC373FF2BB95B8725FABC96CA883d811", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMerkleRootMultisigIsmFactory" - }, - { - "address": "0xCCC126d96efcc342BF2781A7d224D3AB1F25B19C", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMessageIdMultisigIsmFactory" - }, - { - "address": "0x0a71AcC99967829eE305a285750017C4916Ca269", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMerkleRootMultisigIsmFactory" - }, - { - "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd", - "name": "StaticMerkleRootMultisigIsm" - }, - { - "address": "0xFEb9585b2f948c1eD74034205a7439261a9d27DD", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMessageIdMultisigIsmFactory" - }, - { - "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E", - "name": "StaticMessageIdMultisigIsm" - }, - { - "address": "0xC83e12EF2627ACE445C298e6eC418684918a6002", - "constructorArguments": "", - "isProxy": false, - "name": "StaticAggregationIsmFactory" - }, - { - "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89", - "name": "StaticAggregationIsm" - }, - { - "address": "0x160C28C92cA453570aD7C031972b58d5Dd128F72", - "constructorArguments": "", - "isProxy": false, - "name": "StaticAggregationHookFactory" - }, - { - "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD", - "name": "StaticAggregationHook" - }, - { - "address": "0x3603458990BfEb30f99E61B58427d196814D8ce1", - "constructorArguments": "", - "isProxy": false, - "name": "DomainRoutingIsmFactory" - }, - { - "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3", - "name": "DomainRoutingIsm" - }, - { - "address": "0x3F100cBBE5FD5466BdB4B3a15Ac226957e7965Ad", - "constructorArguments": "", - "isProxy": false, - "name": "DomainRoutingIsmFactory" - }, - { - "address": "0x9b0CC3BD9030CE269EF3124Bb36Cf954a490688e", - "constructorArguments": "", - "isProxy": true, - "name": "DomainRoutingIsm" - }, - { - "address": "0x4afB48e864d308409d0D80E98fB7d5d6aA5b245f", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMerkleRootWeightedMultisigIsmFactory" - }, - { - "address": "0x196Ce28ED1Afdf015849ddEE82F03a903Bee9E94", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMessageIdWeightedMultisigIsmFactory" - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x1c6bD6893a3484D603035E1060ed37eaeC53873f", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x0540D3c8C7Ae44f3Eb70C4cb8eA38eF27248347C", - "constructorArguments": "", - "isProxy": true - } - ], - "superpositiontestnet": [ - { - "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMerkleRootMultisigIsmFactory" - }, - { - "address": "0x209e7F9d40954E230008B9bb076a0901d32695e5", - "constructorArguments": "", - "isProxy": true, - "name": "StaticMerkleRootMultisigIsm" - }, - { - "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMessageIdMultisigIsmFactory" - }, - { - "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", - "constructorArguments": "", - "isProxy": true, - "name": "StaticMessageIdMultisigIsm" - }, - { - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "constructorArguments": "", - "isProxy": false, - "name": "StaticAggregationIsmFactory" - }, - { - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", - "constructorArguments": "", - "isProxy": true, - "name": "StaticAggregationIsm" - }, - { - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "constructorArguments": "", - "isProxy": false, - "name": "StaticAggregationHookFactory" - }, - { - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", - "constructorArguments": "", - "isProxy": true, - "name": "StaticAggregationHook" - }, - { - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "constructorArguments": "", - "isProxy": false, - "name": "DomainRoutingIsmFactory" - }, - { - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", - "constructorArguments": "", - "isProxy": true, - "name": "DomainRoutingIsm" - }, - { - "address": "0xE67CfA164cDa449Ae38a0a09391eF6bCDf8e4e2c", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMerkleRootWeightedMultisigIsmFactory" - }, - { - "address": "0x867f2089D09903f208AeCac84E599B90E5a4A821", - "constructorArguments": "", - "isProxy": false, - "name": "StaticMessageIdWeightedMultisigIsmFactory" - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x689C320286d74406AA9A08170DAbB87aC67FF711", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0xA2425897Fe6325162313CA7F0803078b3A20759F", - "constructorArguments": "", - "isProxy": true - } - ], - "mevmdevnet": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x209e7F9d40954E230008B9bb076a0901d32695e5", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsmFactory", - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHookFactory", - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationHook", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsmFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "DomainRoutingIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", - "constructorArguments": "", - "isProxy": true - } - ], - "berabartio": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsmFactory", - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHookFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationHook", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "DomainRoutingIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", - "constructorArguments": "", - "isProxy": true - } - ], - "citreatestnet": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHookFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationHook", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsmFactory", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "DomainRoutingIsm", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x54148470292C24345fb828B003461a9444414517", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x589C201a07c26b4725A4A829d772f24423da480B", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0xD6B8D6C372e07f67FeAb75403c0Ec88E3cce7Ab7", - "constructorArguments": "", - "isProxy": true - } - ], - "hyperliquidevmtestnet": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x209e7F9d40954E230008B9bb076a0901d32695e5", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsmFactory", - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHookFactory", - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationHook", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsmFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "DomainRoutingIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", - "constructorArguments": "", - "isProxy": true - } - ], - "formtestnet": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsmFactory", - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHookFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationHook", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "DomainRoutingIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", - "constructorArguments": "", - "isProxy": true - } - ], - "soneiumtestnet": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsmFactory", - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHookFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationHook", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "DomainRoutingIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", - "constructorArguments": "", - "isProxy": true - } - ], - "sonictestnet": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsmFactory", - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHookFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationHook", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "DomainRoutingIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", - "constructorArguments": "", - "isProxy": true - } - ], - "unichaintestnet": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsmFactory", - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHookFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationHook", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "DomainRoutingIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", - "constructorArguments": "", - "isProxy": true - } - ], - "arcadiatestnet2": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", + "address": "0xA9999B4abC373FF2BB95B8725FABC96CA883d811", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", + "address": "0xCCC126d96efcc342BF2781A7d224D3AB1F25B19C", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "address": "0x0a71AcC99967829eE305a285750017C4916Ca269", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticMerkleRootMultisigIsmFactory" }, { - "name": "StaticAggregationIsmFactory", - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "constructorArguments": "", - "isProxy": false + "address": "0xbe94Cd5d0973dB4A6E63Cb9e6b2b2042D99F9Bcd", + "name": "StaticMerkleRootMultisigIsm" }, { - "name": "StaticAggregationIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "address": "0xFEb9585b2f948c1eD74034205a7439261a9d27DD", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticMessageIdMultisigIsmFactory" }, { - "name": "StaticAggregationHookFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "constructorArguments": "", - "isProxy": false + "address": "0x218C5DFb7C2bC1e051FCFebBa8C6D66Dda28617E", + "name": "StaticMessageIdMultisigIsm" }, { - "name": "StaticAggregationHook", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "address": "0xC83e12EF2627ACE445C298e6eC418684918a6002", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticAggregationIsmFactory" }, { - "name": "DomainRoutingIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", + "address": "0x3dD1aB6B71EBfEd239869365FE5B8b47E0507d89", + "name": "StaticAggregationIsm" + }, + { + "address": "0x160C28C92cA453570aD7C031972b58d5Dd128F72", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticAggregationHookFactory" }, { - "name": "DomainRoutingIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "address": "0x4BC580FF77Dd1Eb320E96D34FddC97F0F04D3feD", + "name": "StaticAggregationHook" + }, + { + "address": "0x3603458990BfEb30f99E61B58427d196814D8ce1", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "address": "0x67543F48DBe592F1A15FD9aE37b1AbD104c70cf3", + "name": "DomainRoutingIsm" + }, + { + "address": "0x3F100cBBE5FD5466BdB4B3a15Ac226957e7965Ad", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "DomainRoutingIsmFactory" }, { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "address": "0x9b0CC3BD9030CE269EF3124Bb36Cf954a490688e", "constructorArguments": "", - "isProxy": true + "isProxy": true, + "name": "DomainRoutingIsm" }, { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", + "address": "0x4afB48e864d308409d0D80E98fB7d5d6aA5b245f", "constructorArguments": "", - "isProxy": false + "isProxy": false, + "name": "StaticMerkleRootWeightedMultisigIsmFactory" }, { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", + "address": "0x196Ce28ED1Afdf015849ddEE82F03a903Bee9E94", "constructorArguments": "", - "isProxy": true + "isProxy": false, + "name": "StaticMessageIdWeightedMultisigIsmFactory" }, { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x54148470292C24345fb828B003461a9444414517", + "name": "StaticMerkleRootWeightedMultisigIsm", + "address": "0x1c6bD6893a3484D603035E1060ed37eaeC53873f", "constructorArguments": "", - "isProxy": false + "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2", + "address": "0x0540D3c8C7Ae44f3Eb70C4cb8eA38eF27248347C", "constructorArguments": "", "isProxy": true } ], - "odysseytestnet": [ + "mevmdevnet": [ { "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", + "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootMultisigIsm", - "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", + "address": "0x209e7F9d40954E230008B9bb076a0901d32695e5", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", + "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationIsmFactory", - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", + "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationHookFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", + "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationHook", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", "constructorArguments": "", "isProxy": true }, { "name": "DomainRoutingIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", + "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", "constructorArguments": "", "isProxy": false }, { "name": "DomainRoutingIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", "constructorArguments": "", "isProxy": true }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", + "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", "constructorArguments": "", "isProxy": true } ], - "alephzeroevmtestnet": [ + "berabartio": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", @@ -2363,93 +1201,93 @@ "isProxy": true } ], - "inksepolia": [ + "citreatestnet": [ { "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", + "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootMultisigIsm", - "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", + "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", + "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationIsmFactory", - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", + "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationHookFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", + "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationHook", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", "constructorArguments": "", "isProxy": true }, { "name": "DomainRoutingIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", + "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", "constructorArguments": "", "isProxy": false }, { "name": "DomainRoutingIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", "constructorArguments": "", "isProxy": true }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "address": "0x54148470292C24345fb828B003461a9444414517", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", + "address": "0x589C201a07c26b4725A4A829d772f24423da480B", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", + "address": "0xD6B8D6C372e07f67FeAb75403c0Ec88E3cce7Ab7", "constructorArguments": "", "isProxy": true } ], - "flametestnet": [ + "hyperliquidevmtestnet": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", @@ -2535,212 +1373,162 @@ "isProxy": true } ], - "sonicblaze": [ + "sonictestnet": [ { "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", + "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootMultisigIsm", - "address": "0x209e7F9d40954E230008B9bb076a0901d32695e5", + "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdMultisigIsmFactory", - "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", + "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdMultisigIsm", - "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", + "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationIsmFactory", - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", + "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", "constructorArguments": "", "isProxy": true }, { "name": "StaticAggregationHookFactory", - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", + "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", "constructorArguments": "", "isProxy": false }, { "name": "StaticAggregationHook", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "address": "0x87935eB971eaA9826060261b07a919451dfd0409", "constructorArguments": "", "isProxy": true }, { "name": "DomainRoutingIsmFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", + "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", "constructorArguments": "", "isProxy": false }, { "name": "DomainRoutingIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", "constructorArguments": "", "isProxy": true }, { "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", + "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", "constructorArguments": "", "isProxy": false }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", "constructorArguments": "", "isProxy": true }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", "constructorArguments": "", "isProxy": true } ], - "chronicleyellowstone": [ + "arcadiatestnet2": [ { "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x209e7F9d40954E230008B9bb076a0901d32695e5", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsmFactory", "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", "constructorArguments": "", "isProxy": false }, { - "name": "StaticMessageIdMultisigIsm", + "name": "StaticMerkleRootMultisigIsm", "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", "constructorArguments": "", "isProxy": true }, { - "name": "StaticAggregationIsmFactory", + "name": "StaticMessageIdMultisigIsmFactory", "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", "constructorArguments": "", "isProxy": false }, { - "name": "StaticAggregationIsm", + "name": "StaticMessageIdMultisigIsm", "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", "constructorArguments": "", "isProxy": true }, { - "name": "StaticAggregationHookFactory", + "name": "StaticAggregationIsmFactory", "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", "constructorArguments": "", "isProxy": false }, { - "name": "StaticAggregationHook", + "name": "StaticAggregationIsm", "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", "constructorArguments": "", "isProxy": true }, { - "name": "DomainRoutingIsmFactory", + "name": "StaticAggregationHookFactory", "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", "constructorArguments": "", "isProxy": false }, { - "name": "DomainRoutingIsm", + "name": "StaticAggregationHook", "address": "0x87935eB971eaA9826060261b07a919451dfd0409", "constructorArguments": "", "isProxy": true }, { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", + "name": "DomainRoutingIsmFactory", "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", "constructorArguments": "", "isProxy": false }, { - "name": "StaticMerkleRootWeightedMultisigIsm", + "name": "DomainRoutingIsm", "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", "constructorArguments": "", "isProxy": true }, { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", - "constructorArguments": "", - "isProxy": true - } - ], - "subtensortestnet": [ - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x209e7F9d40954E230008B9bb076a0901d32695e5", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHook", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "name": "StaticMerkleRootWeightedMultisigIsmFactory", + "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", "constructorArguments": "", - "isProxy": true + "isProxy": false }, { "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", "constructorArguments": "", "isProxy": true }, @@ -2755,26 +1543,26 @@ "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", "constructorArguments": "", "isProxy": true - } - ], - "weavevmtestnet": [ + }, { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", + "name": "StaticMessageIdWeightedMultisigIsmFactory", + "address": "0x54148470292C24345fb828B003461a9444414517", "constructorArguments": "", "isProxy": false }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x209e7F9d40954E230008B9bb076a0901d32695e5", + "name": "StaticMessageIdWeightedMultisigIsm", + "address": "0x10c9FF6EEE4BaD29734322467f541C84001422C2", "constructorArguments": "", "isProxy": true - }, + } + ], + "subtensortestnet": [ { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", + "name": "StaticMerkleRootMultisigIsm", + "address": "0x209e7F9d40954E230008B9bb076a0901d32695e5", "constructorArguments": "", - "isProxy": false + "isProxy": true }, { "name": "StaticMessageIdMultisigIsm", @@ -2782,48 +1570,24 @@ "constructorArguments": "", "isProxy": true }, - { - "name": "StaticAggregationIsmFactory", - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "constructorArguments": "", - "isProxy": false - }, { "name": "StaticAggregationIsm", "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", "constructorArguments": "", "isProxy": true }, - { - "name": "StaticAggregationHookFactory", - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "constructorArguments": "", - "isProxy": false - }, { "name": "StaticAggregationHook", "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", "constructorArguments": "", "isProxy": true }, - { - "name": "DomainRoutingIsmFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "constructorArguments": "", - "isProxy": false - }, { "name": "DomainRoutingIsm", "address": "0x87935eB971eaA9826060261b07a919451dfd0409", "constructorArguments": "", "isProxy": true }, - { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "constructorArguments": "", - "isProxy": false - }, { "name": "StaticMerkleRootWeightedMultisigIsm", "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", @@ -2832,13 +1596,13 @@ }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", + "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", "constructorArguments": "", "isProxy": false }, { "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", "constructorArguments": "", "isProxy": true } @@ -3359,92 +2123,6 @@ "isProxy": true } ], - "plumetestnet2": [ - { - "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdMultisigIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationIsmFactory", - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationIsm", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticAggregationHookFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticAggregationHook", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "DomainRoutingIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "DomainRoutingIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", - "constructorArguments": "", - "isProxy": true - }, - { - "name": "StaticMessageIdWeightedMultisigIsmFactory", - "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x4863236F3a05A1A1F0850fF8cd09afeBAE82d953", - "constructorArguments": "", - "isProxy": true - } - ], "auroratestnet": [ { "name": "StaticMerkleRootMultisigIsmFactory", @@ -3661,7 +2339,7 @@ "isProxy": true } ], - "bepolia": [ + "neuratestnet": [ { "name": "StaticMerkleRootMultisigIsmFactory", "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", @@ -3747,90 +2425,152 @@ "isProxy": true } ], - "neuratestnet": [ + "celosepolia": [ { "name": "StaticMerkleRootMultisigIsmFactory", - "address": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8", + "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", "constructorArguments": "", "isProxy": false }, { - "name": "StaticMerkleRootMultisigIsm", - "address": "0x209e7F9d40954E230008B9bb076a0901d32695e5", + "name": "StaticMessageIdMultisigIsmFactory", + "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", "constructorArguments": "", - "isProxy": true + "isProxy": false }, { - "name": "StaticMessageIdMultisigIsmFactory", - "address": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213", + "name": "StaticAggregationIsmFactory", + "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", "constructorArguments": "", "isProxy": false }, { - "name": "StaticMessageIdMultisigIsm", - "address": "0x99B304925A08aba9305bC0A8FccBf71B4290c5EF", + "name": "StaticAggregationHookFactory", + "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", "constructorArguments": "", - "isProxy": true + "isProxy": false }, { - "name": "StaticAggregationIsmFactory", - "address": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", + "name": "DomainRoutingIsmFactory", + "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", "constructorArguments": "", "isProxy": false }, { - "name": "StaticAggregationIsm", - "address": "0x33999AB153F68D481AAB1B238368Ffd1Fe81F360", + "name": "StaticMerkleRootWeightedMultisigIsmFactory", + "address": "0x54148470292C24345fb828B003461a9444414517", "constructorArguments": "", - "isProxy": true + "isProxy": false }, { - "name": "StaticAggregationHookFactory", - "address": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", + "name": "StaticMessageIdWeightedMultisigIsmFactory", + "address": "0x589C201a07c26b4725A4A829d772f24423da480B", "constructorArguments": "", "isProxy": false }, { - "name": "StaticAggregationHook", - "address": "0x3e6F45B03314bD21BcE4201666d483291575E391", + "name": "StaticMerkleRootMultisigIsmFactory", + "address": "0x68311418D79fE8d96599384ED767d225635d88a8", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootMultisigIsm", + "address": "0xE8AA69CaB49cd40d765aaC213Dd565424fAfd114", "constructorArguments": "", "isProxy": true }, { - "name": "DomainRoutingIsmFactory", - "address": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", + "name": "StaticMerkleRootMultisigIsmFactory", + "address": "0x863E8c26621c52ACa1849C53500606e73BA272F0", "constructorArguments": "", "isProxy": false }, { - "name": "DomainRoutingIsm", - "address": "0x87935eB971eaA9826060261b07a919451dfd0409", + "name": "StaticMerkleRootMultisigIsmFactory", + "address": "0x6b1bb4ce664Bb4164AEB4d3D2E7DE7450DD8084C", "constructorArguments": "", - "isProxy": true + "isProxy": false }, { - "name": "StaticMerkleRootWeightedMultisigIsmFactory", - "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", + "name": "StaticMessageIdMultisigIsmFactory", + "address": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C", "constructorArguments": "", "isProxy": false }, { - "name": "StaticMerkleRootWeightedMultisigIsm", - "address": "0xE5cA56294dA5Bd490D5Bc489B177B002ad16AF83", + "name": "StaticAggregationIsmFactory", + "address": "0xAb9B273366D794B7F80B4378bc8Aaca75C6178E2", "constructorArguments": "", - "isProxy": true + "isProxy": false + }, + { + "name": "StaticAggregationHookFactory", + "address": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomainRoutingIsmFactory", + "address": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootWeightedMultisigIsmFactory", + "address": "0x19Be55D859368e02d7b9C00803Eb677BDC1359Bd", + "constructorArguments": "", + "isProxy": false }, { "name": "StaticMessageIdWeightedMultisigIsmFactory", + "address": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", + "constructorArguments": "", + "isProxy": false + } + ], + "incentivtestnet": [ + { + "name": "StaticMerkleRootMultisigIsmFactory", + "address": "0x44b764045BfDC68517e10e783E69B376cef196B2", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdMultisigIsmFactory", "address": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", "constructorArguments": "", "isProxy": false }, { - "name": "StaticMessageIdWeightedMultisigIsm", - "address": "0x7fFe8C9c17F46F94D784E148FbadD4bF66477722", + "name": "StaticAggregationIsmFactory", + "address": "0x6966b0E55883d49BFB24539356a2f8A673E02039", "constructorArguments": "", - "isProxy": true + "isProxy": false + }, + { + "name": "StaticAggregationHookFactory", + "address": "0x54148470292C24345fb828B003461a9444414517", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "DomainRoutingIsmFactory", + "address": "0x589C201a07c26b4725A4A829d772f24423da480B", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMerkleRootWeightedMultisigIsmFactory", + "address": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD", + "constructorArguments": "", + "isProxy": false + }, + { + "name": "StaticMessageIdWeightedMultisigIsmFactory", + "address": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", + "constructorArguments": "", + "isProxy": false } ] } diff --git a/typescript/infra/config/environments/testnet4/middleware/accounts/verification.json b/typescript/infra/config/environments/testnet4/middleware/accounts/verification.json index 676ab611892..589dbbd5df5 100644 --- a/typescript/infra/config/environments/testnet4/middleware/accounts/verification.json +++ b/typescript/infra/config/environments/testnet4/middleware/accounts/verification.json @@ -1,60 +1,4 @@ { - "alfajores": [ - { - "address": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E", - "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false, - "name": "InterchainAccountIsm" - }, - { - "address": "0x6905eeD99fef740c400cd55883a652471BBBf78D", - "constructorArguments": "000000000000000000000000ef9f292fcebc3848bf4bb92a96a04f9ecbb78e59", - "isProxy": false, - "name": "InterchainAccountRouter" - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xf82b58Bf348a6CEA6e19413e9DE040dB1a363128", - "constructorArguments": "00000000000000000000000013474f85b808034c911b7697dee60b7d8d50ee36000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainAccountRouter", - "address": "0x7D7D885ebeb3B46340E9CF9bD0cA847492C16D4a", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000a869000000000000000000000000f82b58bf348a6cea6e19413e9de040db1a363128", - "isProxy": false - }, - { - "name": "InterchainAccountIsm", - "address": "0x333C9A8c70Ea4F0498Fb58bEe40DEa40e63a9962", - "constructorArguments": "000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x37a6E854eBCfee96EAB431DA3d93Ad099F18E8Ad", - "constructorArguments": "00000000000000000000000013474f85b808034c911b7697dee60b7d8d50ee36000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainAccountRouter", - "address": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000a86900000000000000000000000037a6e854ebcfee96eab431da3d93ad099f18e8ad", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xb0811feF53FF499bd8E09018F8E568b95c42A721", - "constructorArguments": "00000000000000000000000013474f85b808034c911b7697dee60b7d8d50ee36000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainAccountRouter", - "address": "0xC430A2a0F3006B74D7b7563CaD8fDfE989e385f3", - "constructorArguments": "000000000000000000000000000000000000000000000000000000000000a869", - "isProxy": false - } - ], "bsctestnet": [ { "address": "0xa9D8Ec959F34272B1a56D09AF00eeee58970d3AE", @@ -179,69 +123,6 @@ "expectedimplementation": "0xA2cf52064c921C11adCd83588CbEa08cc3bfF5d8" } ], - "formtestnet": [ - { - "name": "InterchainAccountIsm", - "address": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0x01812D60958798695391dacF092BAc4a715B1718", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x867f2089D09903f208AeCac84E599B90E5a4A821", - "constructorArguments": "00000000000000000000000001812d60958798695391dacf092bac4a715b171800000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d356c996277efb7f75ee8bd61b31cc781a12f54f000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x01812D60958798695391dacF092BAc4a715B1718" - } - ], - "soneiumtestnet": [ - { - "name": "InterchainAccountIsm", - "address": "0xD356C996277eFb7f75Ee8bd61b31cC781A12F54f", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0x01812D60958798695391dacF092BAc4a715B1718", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x867f2089D09903f208AeCac84E599B90E5a4A821", - "constructorArguments": "00000000000000000000000001812d60958798695391dacf092bac4a715b171800000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d356c996277efb7f75ee8bd61b31cc781a12f54f000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x01812D60958798695391dacF092BAc4a715B1718" - } - ], - "connextsepolia": [ - { - "name": "InterchainAccountIsm", - "address": "0xA30b2CbC14b97aa55bBC947f4AC6c4254971aFD1", - "constructorArguments": "0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e02039", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0x0FAc4476d5cE3C057141Ab4df47BFC2ceE2bB259", - "constructorArguments": "0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e02039", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xc9ab470A61571ac0c39B7E0923fbEaDdB58d98FE", - "constructorArguments": "0000000000000000000000000fac4476d5ce3c057141ab4df47bfc2cee2bb25900000000000000000000000044b764045bfdc68517e10e783e69b376cef196b200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a30b2cbc14b97aa55bbc947f4ac6c4254971afd1000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x0FAc4476d5cE3C057141Ab4df47BFC2ceE2bB259" - } - ], "arbitrumsepolia": [ { "name": "InterchainAccountIsm", @@ -263,27 +144,6 @@ "expectedimplementation": "0x52Fbf023eDA2610653daD5ACA0b84356e4979669" } ], - "superpositiontestnet": [ - { - "name": "InterchainAccountIsm", - "address": "0xd09D08a19C6609a1B51e1ca6a055861E7e7A4400", - "constructorArguments": "0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e02039", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0x56c09458cC7863fff1Cc6Bcb6652Dcc3412FcA86", - "constructorArguments": "0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e02039", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x3572a9d808738922194921b275B2A55414BcDA57", - "constructorArguments": "00000000000000000000000056c09458cc7863fff1cc6bcb6652dcc3412fca8600000000000000000000000044b764045bfdc68517e10e783e69b376cef196b200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d09d08a19c6609a1b51e1ca6a055861e7e7a4400000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x56c09458cC7863fff1Cc6Bcb6652Dcc3412FcA86" - } - ], "sonictestnet": [ { "name": "InterchainAccountIsm", @@ -305,27 +165,6 @@ "expectedimplementation": "0x3ca332A585FDB9d4FF51f2FA8999eA32184D3606" } ], - "ecotestnet": [ - { - "name": "InterchainAccountIsm", - "address": "0x0De2F539569Fb1e2e3C1d233f7A63a18B9A17110", - "constructorArguments": "0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e02039", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0x111F4f782B47881898755Bd2F67f12876893300E", - "constructorArguments": "0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e02039", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x2C6dD6768E669EDB7b53f26067C1C4534862c3de", - "constructorArguments": "000000000000000000000000111f4f782b47881898755bd2f67f12876893300e00000000000000000000000044b764045bfdc68517e10e783e69b376cef196b200000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de2f539569fb1e2e3c1d233f7a63a18b9a17110000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x111F4f782B47881898755Bd2F67f12876893300E" - } - ], "optimismsepolia": [ { "name": "InterchainAccountIsm", @@ -408,27 +247,6 @@ "expectedimplementation": "0x14EE2f01907707Ce8d13C4F5DBC40778b5b664e0" } ], - "unichaintestnet": [ - { - "name": "InterchainAccountIsm", - "address": "0x3ca332A585FDB9d4FF51f2FA8999eA32184D3606", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0x342B5630Ba1C1e4d3048E51Dad208201aF52692c", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x4eC139a771eBdD3b0a0b67bb7E08960210882d44", - "constructorArguments": "000000000000000000000000342b5630ba1c1e4d3048e51dad208201af52692c00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ca332a585fdb9d4ff51f2fa8999ea32184d3606000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x342B5630Ba1C1e4d3048E51Dad208201aF52692c" - } - ], "holesky": [ { "name": "InterchainAccountIsm", @@ -471,132 +289,6 @@ "expectedimplementation": "0xF61322936D80cd87B49df48F3DE24fD5c02dE9D1" } ], - "odysseytestnet": [ - { - "name": "InterchainAccountIsm", - "address": "0xBF2C366530C1269d531707154948494D3fF4AcA7", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0x843908541D24d9F6Fa30C8Bb1c39038C947D08fC", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xBdf49bE2201A1c4B13023F0a407196C6Adb32680", - "constructorArguments": "000000000000000000000000843908541d24d9f6fa30c8bb1c39038c947d08fc00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bf2c366530c1269d531707154948494d3ff4aca7000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x843908541D24d9F6Fa30C8Bb1c39038C947D08fC" - } - ], - "alephzeroevmtestnet": [ - { - "name": "InterchainAccountIsm", - "address": "0x342B5630Ba1C1e4d3048E51Dad208201aF52692c", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0x39c85C84876479694A2470c0E8075e9d68049aFc", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xe036768e48Cb0D42811d2bF0748806FCcBfCd670", - "constructorArguments": "00000000000000000000000039c85c84876479694a2470c0e8075e9d68049afc00000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000342b5630ba1c1e4d3048e51dad208201af52692c000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x39c85C84876479694A2470c0E8075e9d68049aFc" - } - ], - "sonicblaze": [ - { - "name": "InterchainAccountIsm", - "address": "0x507C18fa4e3b0ce6beBD494488D62d1ed0fB0555", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0x2bD9aF503B9F608beAD63D4ACC328Abf9796b576", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x8584590ad637C61C7cDF72eFF3381Ee1c3D1bC8E", - "constructorArguments": "0000000000000000000000002bd9af503b9f608bead63d4acc328abf9796b5760000000000000000000000006966b0e55883d49bfb24539356a2f8a673e0203900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000507c18fa4e3b0ce6bebd494488d62d1ed0fb0555000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x2bD9aF503B9F608beAD63D4ACC328Abf9796b576" - } - ], - "flametestnet": [ - { - "name": "InterchainAccountIsm", - "address": "0x919Af376D02751bFCaD9CBAD6bad0c3089dAE33f", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0xD9dc83Ea22C6F1A224e51562B32b580695905A1A", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xF64c33DcC5F9Dd8CE9A7bc61A4DabeB1Ac6CCE27", - "constructorArguments": "000000000000000000000000d9dc83ea22c6f1a224e51562b32b580695905a1a0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e0203900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000919af376d02751bfcad9cbad6bad0c3089dae33f000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xD9dc83Ea22C6F1A224e51562B32b580695905A1A" - } - ], - "inksepolia": [ - { - "name": "InterchainAccountIsm", - "address": "0xB7697612fbfb4ad02a11dCa16e9711eCB6Da4ceA", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0x638A831b4d11Be6a72AcB97d1aE79DA05Ae9B1D3", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xE0745955baF7e614707E22c90EA14d329C38941D", - "constructorArguments": "000000000000000000000000638a831b4d11be6a72acb97d1ae79da05ae9b1d300000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b7697612fbfb4ad02a11dca16e9711ecb6da4cea000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x638A831b4d11Be6a72AcB97d1aE79DA05Ae9B1D3" - } - ], - "chronicleyellowstone": [ - { - "name": "InterchainAccountIsm", - "address": "0xc0Ce04851bF6Ea149fA06bf7a4808c9db81af189", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0x740bEd6E4eEc7c57a2818177Fba3f9E896D5DE1c", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xB261C52241E133f957630AeeFEd48a82963AC33e", - "constructorArguments": "000000000000000000000000740bed6e4eec7c57a2818177fba3f9e896d5de1c0000000000000000000000006966b0e55883d49bfb24539356a2f8a673e0203900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0ce04851bf6ea149fa06bf7a4808c9db81af189000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x740bEd6E4eEc7c57a2818177Fba3f9E896D5DE1c" - } - ], "subtensortestnet": [ { "name": "InterchainAccountIsm", @@ -618,27 +310,6 @@ "expectedimplementation": "0x1E98EA1EABA7137b6eC0B42400701b8539Ef2945" } ], - "weavevmtestnet": [ - { - "name": "InterchainAccountIsm", - "address": "0x4da6f7E710137657008D5BCeF26151aac5c9884f", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0x48a53E3B176383BC98fcF4a24c9D470c19475164", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x919Af376D02751bFCaD9CBAD6bad0c3089dAE33f", - "constructorArguments": "00000000000000000000000048a53e3b176383bc98fcf4a24c9d470c194751640000000000000000000000006966b0e55883d49bfb24539356a2f8a673e0203900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000004da6f7e710137657008d5bcef26151aac5c9884f000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x48a53E3B176383BC98fcF4a24c9D470c19475164" - } - ], "monadtestnet": [ { "name": "InterchainAccountIsm", @@ -723,27 +394,6 @@ "expectedimplementation": "0x9450181a7719dAb93483d43a45473Ac2373E25B0" } ], - "plumetestnet2": [ - { - "name": "InterchainAccountIsm", - "address": "0x9667EfF1556A9D092fdbeC09244CB99b677E9D1E", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0x36502C6e24C51ba4839c4c4A070aeB52E1adB672", - "constructorArguments": "000000000000000000000000ddcfecf17586d08a5740b7d91735fcce3dfe3eed", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0x2A9E9188C7e76f3345e91fD4650aC654A9FE355C", - "constructorArguments": "00000000000000000000000036502c6e24c51ba4839c4c4a070aeb52e1adb67200000000000000000000000054148470292c24345fb828b003461a944441451700000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000009667eff1556a9d092fdbec09244cb99b677e9d1e000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0x36502C6e24C51ba4839c4c4A070aeB52E1adB672" - } - ], "modetestnet": [ { "name": "InterchainAccountIsm", @@ -849,27 +499,6 @@ "expectedimplementation": "0xcCcC2A71810f9D16770F8062dccc47f7F7C7bA2E" } ], - "bepolia": [ - { - "name": "InterchainAccountIsm", - "address": "0x1fe349d93078B26C8c7350A401e2B60f100952F5", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - }, - { - "name": "InterchainAccountRouter", - "address": "0xD221ffdAa65518BF56c8623e04eA0380CE1BfaE2", - "constructorArguments": "000000000000000000000000589c201a07c26b4725a4a829d772f24423da480b", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xB7697612fbfb4ad02a11dCa16e9711eCB6Da4ceA", - "constructorArguments": "000000000000000000000000d221ffdaa65518bf56c8623e04ea0380ce1bfae20000000000000000000000006966b0e55883d49bfb24539356a2f8a673e0203900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000064c0c53b8b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001fe349d93078b26c8c7350a401e2b60f100952f5000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true, - "expectedimplementation": "0xD221ffdAa65518BF56c8623e04eA0380CE1BfaE2" - } - ], "auroratestnet": [ { "name": "InterchainAccountIsm", diff --git a/typescript/infra/config/environments/testnet4/middleware/queries/verification.json b/typescript/infra/config/environments/testnet4/middleware/queries/verification.json index 3d8e35edc7e..6513bf6a46a 100644 --- a/typescript/infra/config/environments/testnet4/middleware/queries/verification.json +++ b/typescript/infra/config/environments/testnet4/middleware/queries/verification.json @@ -1,30 +1,4 @@ { - "alfajores": [ - { - "name": "InterchainQueryRouter", - "address": "0xfFf9dB6C772525B17cd4eB863A09DcD43e085F59", - "constructorArguments": "0x", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xC87F9a6cADF77995b18FddE5049b6274695Dd559", - "constructorArguments": "0x000000000000000000000000c97d8e6f57b0d64971453ddc6eb8483fec9d163a000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000", - "isProxy": true - }, - { - "name": "InterchainQueryRouter", - "address": "0xC1816A645cdaB57408c23B97a592CCc06b4b64fF", - "constructorArguments": "", - "isProxy": false - }, - { - "name": "TransparentUpgradeableProxy", - "address": "0xc341cBC69745C541d698cb2cB4eDb91c2F0413aE", - "constructorArguments": "000000000000000000000000c1816a645cdab57408c23b97a592ccc06b4b64ff0000000000000000000000004e4d563e2cbfc35c4bc16003685443fae2fa702f00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000084f8c8765e000000000000000000000000cc737a94fecaec165abcf12ded095bb13f037685000000000000000000000000f90cb82a76492614d07b82a7658917f3ac811ac10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fad1c94469700833717fa8a3017278bc1ca8031c00000000000000000000000000000000000000000000000000000000", - "isProxy": true - } - ], "fuji": [ { "name": "InterchainQueryRouter", diff --git a/typescript/infra/config/environments/testnet4/owners.ts b/typescript/infra/config/environments/testnet4/owners.ts index 995c6b5cf74..25049cc595c 100644 --- a/typescript/infra/config/environments/testnet4/owners.ts +++ b/typescript/infra/config/environments/testnet4/owners.ts @@ -41,6 +41,9 @@ export const owners: ChainMap = { celestiatestnet: { owner: 'n/a - CSDK not supported here', }, + radixtestnet: { + owner: 'n/a - Radix not supported here', + }, }; export const ethereumChainOwners: ChainMap = Object.fromEntries( diff --git a/typescript/infra/config/environments/testnet4/supportedChainNames.ts b/typescript/infra/config/environments/testnet4/supportedChainNames.ts index 9526c703c27..56affd9b5dd 100644 --- a/typescript/infra/config/environments/testnet4/supportedChainNames.ts +++ b/typescript/infra/config/environments/testnet4/supportedChainNames.ts @@ -1,30 +1,22 @@ // Placing them here instead of adjacent chains file to avoid circular dep export const testnet4SupportedChainNames = [ - 'abstracttestnet', - 'alephzeroevmtestnet', - 'alfajores', 'arbitrumsepolia', 'arcadiatestnet2', 'auroratestnet', 'basecamptestnet', 'basesepolia', - 'bepolia', 'bsctestnet', 'carrchaintestnet', 'celestiatestnet', - 'chronicleyellowstone', + 'celosepolia', 'citreatestnet', - 'connextsepolia', 'cotitestnet', - 'ecotestnet', 'eclipsetestnet', - 'flametestnet', - 'formtestnet', 'fuji', 'holesky', 'hyperliquidevmtestnet', + 'incentivtestnet', 'infinityvmmonza', - 'inksepolia', 'kyvetestnet', 'megaethtestnet', 'milkywaytestnet', @@ -32,23 +24,17 @@ export const testnet4SupportedChainNames = [ 'monadtestnet', 'neuratestnet', 'nobletestnet', - 'odysseytestnet', 'optimismsepolia', 'paradexsepolia', - 'plumetestnet2', 'polygonamoy', + 'radixtestnet', 'scrollsepolia', 'sepolia', 'solanatestnet', 'somniatestnet', - 'soneiumtestnet', - 'sonicblaze', 'sonicsvmtestnet', 'starknetsepolia', 'subtensortestnet', - 'superpositiontestnet', - 'unichaintestnet', - 'weavevmtestnet', ] as const; export const supportedChainNames = [...testnet4SupportedChainNames]; diff --git a/typescript/infra/config/environments/testnet4/testquerysender/addresses.json b/typescript/infra/config/environments/testnet4/testquerysender/addresses.json index 7397423e84f..8cf1d4779e6 100644 --- a/typescript/infra/config/environments/testnet4/testquerysender/addresses.json +++ b/typescript/infra/config/environments/testnet4/testquerysender/addresses.json @@ -1,7 +1,4 @@ { - "alfajores": { - "TestQuerySender": "0x96D7D6Eba6C635e3EaC12b593Ef8B2eE1F6E6683" - }, "fuji": { "TestQuerySender": "0x96D7D6Eba6C635e3EaC12b593Ef8B2eE1F6E6683" }, diff --git a/typescript/infra/config/environments/testnet4/testquerysender/verification.json b/typescript/infra/config/environments/testnet4/testquerysender/verification.json index 1c51ff36751..954b088e9ef 100644 --- a/typescript/infra/config/environments/testnet4/testquerysender/verification.json +++ b/typescript/infra/config/environments/testnet4/testquerysender/verification.json @@ -1,12 +1,4 @@ { - "alfajores": [ - { - "name": "TestQuerySender", - "address": "0x96D7D6Eba6C635e3EaC12b593Ef8B2eE1F6E6683", - "isProxy": false, - "constructorArguments": "0x" - } - ], "fuji": [ { "name": "TestQuerySender", diff --git a/typescript/infra/config/environments/testnet4/tokenPrices.json b/typescript/infra/config/environments/testnet4/tokenPrices.json index c8d1ea9c251..186b41fc657 100644 --- a/typescript/infra/config/environments/testnet4/tokenPrices.json +++ b/typescript/infra/config/environments/testnet4/tokenPrices.json @@ -1,29 +1,21 @@ { - "abstracttestnet": "10", - "alephzeroevmtestnet": "10", - "alfajores": "10", "arbitrumsepolia": "10", "arcadiatestnet2": "10", "auroratestnet": "10", "basecamptestnet": "10", "basesepolia": "10", - "bepolia": "10", "bsctestnet": "10", "carrchaintestnet": "10", "celestiatestnet": "10", - "chronicleyellowstone": "10", + "celosepolia": "10", "citreatestnet": "10", - "connextsepolia": "10", "cotitestnet": "10", - "ecotestnet": "10", "eclipsetestnet": "10", - "flametestnet": "10", - "formtestnet": "10", "fuji": "10", "holesky": "10", "hyperliquidevmtestnet": "10", + "incentivtestnet": "10", "infinityvmmonza": "10", - "inksepolia": "10", "kyvetestnet": "10", "megaethtestnet": "10", "milkywaytestnet": "10", @@ -31,21 +23,15 @@ "monadtestnet": "10", "neuratestnet": "10", "nobletestnet": "10", - "odysseytestnet": "10", "optimismsepolia": "10", - "plumetestnet2": "10", "polygonamoy": "10", - "scrollsepolia": "10", "paradexsepolia": "10", + "radixtestnet": "10", + "scrollsepolia": "10", "sepolia": "10", "solanatestnet": "10", "somniatestnet": "10", - "soneiumtestnet": "10", - "sonicblaze": "10", "sonicsvmtestnet": "10", "starknetsepolia": "10", - "subtensortestnet": "10", - "superpositiontestnet": "10", - "unichaintestnet": "10", - "weavevmtestnet": "10" + "subtensortestnet": "10" } diff --git a/typescript/infra/config/environments/testnet4/validators.ts b/typescript/infra/config/environments/testnet4/validators.ts index 795b3ca7782..0269edfb0e0 100644 --- a/typescript/infra/config/environments/testnet4/validators.ts +++ b/typescript/infra/config/environments/testnet4/validators.ts @@ -10,26 +10,6 @@ export const validatorChainConfig = ( ): ValidatorBaseChainConfigMap => { const validatorsConfig = validatorBaseConfigsFn(environment, context); return { - alfajores: { - interval: 5, - reorgPeriod: getReorgPeriod('alfajores'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: [ - '0x2233a5ce12f814bd64c9cdd73410bb8693124d40', - '0xba279f965489d90f90490e3c49e860e0b43c2ae6', - '0x86485dcec5f7bb8478dd251676372d054dea6653', - ], - [Contexts.ReleaseCandidate]: [ - '0xace978aaa61d9ee44fe3ab147fd227e0e66b8909', - '0x6c8bfdfb8c40aba10cc9fb2cf0e3e856e0e5dbb3', - '0x54c65eb7677e6086cdde3d5ccef89feb2103a11d', - ], - [Contexts.Neutron]: [], - }, - 'alfajores', - ), - }, arbitrumsepolia: { interval: 5, reorgPeriod: getReorgPeriod('arbitrumsepolia'), @@ -54,21 +34,9 @@ export const validatorChainConfig = ( 'basesepolia', ), }, - ecotestnet: { - interval: 5, - reorgPeriod: getReorgPeriod('ecotestnet'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0xb3191420d463c2af8bd9b4a395e100ec5c05915a'], - [Contexts.ReleaseCandidate]: [], - [Contexts.Neutron]: [], - }, - 'ecotestnet', - ), - }, fuji: { interval: 5, - reorgPeriod: getReorgPeriod('alfajores'), + reorgPeriod: getReorgPeriod('fuji'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -106,18 +74,6 @@ export const validatorChainConfig = ( 'bsctestnet', ), }, - connextsepolia: { - interval: 5, - reorgPeriod: getReorgPeriod('connextsepolia'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0xffbbec8c499585d80ef69eb613db624d27e089ab'], - [Contexts.ReleaseCandidate]: [], - [Contexts.Neutron]: [], - }, - 'connextsepolia', - ), - }, holesky: { interval: 13, reorgPeriod: getReorgPeriod('holesky'), @@ -173,18 +129,6 @@ export const validatorChainConfig = ( 'sepolia', ), }, - superpositiontestnet: { - interval: 1, - reorgPeriod: getReorgPeriod('superpositiontestnet'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x1d3168504b23b73cdf9c27f13bb0a595d7f1a96a'], - [Contexts.ReleaseCandidate]: [], - [Contexts.Neutron]: [], - }, - 'superpositiontestnet', - ), - }, optimismsepolia: { interval: 5, reorgPeriod: getReorgPeriod('optimismsepolia'), @@ -221,43 +165,6 @@ export const validatorChainConfig = ( 'citreatestnet', ), }, - formtestnet: { - interval: 5, - reorgPeriod: getReorgPeriod('formtestnet'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x72ad7fddf16d17ff902d788441151982fa31a7bc'], - [Contexts.ReleaseCandidate]: [], - [Contexts.Neutron]: [], - }, - 'formtestnet', - ), - }, - soneiumtestnet: { - interval: 5, - reorgPeriod: getReorgPeriod('soneiumtestnet'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x2e2101020ccdbe76aeda1c27823b0150f43d0c63'], - [Contexts.ReleaseCandidate]: [], - [Contexts.Neutron]: [], - }, - 'soneiumtestnet', - ), - }, - - unichaintestnet: { - interval: 5, - reorgPeriod: getReorgPeriod('unichaintestnet'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x5e99961cf71918308c3b17ef21b5f515a4f86fe5'], - [Contexts.ReleaseCandidate]: [], - [Contexts.Neutron]: [], - }, - 'unichaintestnet', - ), - }, solanatestnet: { interval: 5, reorgPeriod: getReorgPeriod('solanatestnet'), @@ -294,58 +201,6 @@ export const validatorChainConfig = ( 'arcadiatestnet2', ), }, - - odysseytestnet: { - interval: 5, - reorgPeriod: getReorgPeriod('odysseytestnet'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0xcc0a6e2d6aa8560b45b384ced7aa049870b66ea3'], - [Contexts.ReleaseCandidate]: [], - [Contexts.Neutron]: [], - }, - 'odysseytestnet', - ), - }, - - alephzeroevmtestnet: { - interval: 5, - reorgPeriod: getReorgPeriod('alephzeroevmtestnet'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x556cd94bcb6e5773e8df75e7eb3f91909d266a26'], - [Contexts.ReleaseCandidate]: [], - [Contexts.Neutron]: [], - }, - 'alephzeroevmtestnet', - ), - }, - inksepolia: { - interval: 5, - reorgPeriod: getReorgPeriod('inksepolia'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0xe61c846aee275070207fcbf43674eb254f06097a'], - [Contexts.ReleaseCandidate]: [], - [Contexts.Neutron]: [], - }, - 'inksepolia', - ), - }, - - abstracttestnet: { - interval: 5, - reorgPeriod: getReorgPeriod('abstracttestnet'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x7655bc4c9802bfcb3132b8822155b60a4fbbce3e'], - [Contexts.ReleaseCandidate]: [], - [Contexts.Neutron]: [], - }, - 'abstracttestnet', - ), - }, - hyperliquidevmtestnet: { interval: 5, reorgPeriod: getReorgPeriod('hyperliquidevmtestnet'), @@ -358,30 +213,6 @@ export const validatorChainConfig = ( 'hyperliquidevmtestnet', ), }, - flametestnet: { - interval: 5, - reorgPeriod: getReorgPeriod('flametestnet'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x0272625243bf2377f87538031fed54e21853cc2d'], - [Contexts.ReleaseCandidate]: [], - [Contexts.Neutron]: [], - }, - 'flametestnet', - ), - }, - sonicblaze: { - interval: 5, - reorgPeriod: getReorgPeriod('sonicblaze'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0xe5b98110d0688691ea280edea9a4faa1e3617ba1'], - [Contexts.ReleaseCandidate]: [], - [Contexts.Neutron]: [], - }, - 'sonicblaze', - ), - }, paradexsepolia: { interval: 5, reorgPeriod: getReorgPeriod('paradexsepolia'), @@ -418,16 +249,6 @@ export const validatorChainConfig = ( 'subtensortestnet', ), }, - chronicleyellowstone: { - interval: 5, - reorgPeriod: getReorgPeriod('chronicleyellowstone'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0xf11cfeb2b6db66ec14c2ef7b685b36390cd648b4'], - }, - 'chronicleyellowstone', - ), - }, monadtestnet: { interval: 5, @@ -439,16 +260,6 @@ export const validatorChainConfig = ( 'monadtestnet', ), }, - weavevmtestnet: { - interval: 5, - reorgPeriod: getReorgPeriod('weavevmtestnet'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x6d2ee6688de903bb31f3ae2ea31da87b697f7f40'], - }, - 'weavevmtestnet', - ), - }, carrchaintestnet: { interval: 5, @@ -501,16 +312,6 @@ export const validatorChainConfig = ( 'modetestnet', ), }, - plumetestnet2: { - interval: 5, - reorgPeriod: getReorgPeriod('plumetestnet2'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0x16637c78e1ea169132efcf4df8ebd03de349e740'], - }, - 'plumetestnet2', - ), - }, kyvetestnet: { interval: 5, @@ -574,16 +375,6 @@ export const validatorChainConfig = ( 'basecamptestnet', ), }, - bepolia: { - interval: 5, - reorgPeriod: getReorgPeriod('bepolia'), - validators: validatorsConfig( - { - [Contexts.Hyperlane]: ['0xdb0128bb3d3f204eb18de7e8268e94fde0382daf'], - }, - 'bepolia', - ), - }, neuratestnet: { interval: 5, @@ -606,5 +397,38 @@ export const validatorChainConfig = ( 'celestiatestnet', ), }, + + celosepolia: { + interval: 5, + reorgPeriod: getReorgPeriod('celosepolia'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x4a5cfcfd7f793f4ceba170c3decbe43bd8253ef6'], + }, + 'celosepolia', + ), + }, + + incentivtestnet: { + interval: 5, + reorgPeriod: getReorgPeriod('incentivtestnet'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0x3133eeb96fd96f9f99291088613edf7401149e6f'], + }, + 'incentivtestnet', + ), + }, + + radixtestnet: { + interval: 5, + reorgPeriod: getReorgPeriod('radixtestnet'), + validators: validatorsConfig( + { + [Contexts.Hyperlane]: ['0xeddaf7958627cfd35400c95db19a656a4a8a92c6'], + }, + 'radixtestnet', + ), + }, }; }; diff --git a/typescript/infra/config/environments/utils.ts b/typescript/infra/config/environments/utils.ts index 3c23fc11c87..8e2a10fda57 100644 --- a/typescript/infra/config/environments/utils.ts +++ b/typescript/infra/config/environments/utils.ts @@ -10,6 +10,10 @@ import { } from '../../src/config/agent/validator.js'; import { Contexts } from '../contexts.js'; +export const DEFAULT_OFFCHAIN_LOOKUP_ISM_URLS = [ + 'https://offchain-lookup.services.hyperlane.xyz/callCommitments/getCallsFromRevealMessage', +]; + export type ValidatorKey = { identifier: string; address: string; @@ -95,3 +99,27 @@ export function getGnosisSafeBuilderStrategyConfigGenerator( ); }; } + +/** + * Create a GnosisSafe Submitter Strategy for each safe address + * @param safes Safe addresses for strategy + * @returns GnosisSafe Submitter Strategy for each safe address + */ +export function getGnosisSafeSubmitterStrategyConfigGenerator( + safes: Record, +) { + return (): ChainSubmissionStrategy => { + return Object.fromEntries( + Object.entries(safes).map(([chain, safeAddress]) => [ + chain, + { + submitter: { + type: TxSubmitterType.GNOSIS_SAFE, + chain, + safeAddress, + }, + }, + ]), + ); + }; +} diff --git a/typescript/infra/config/rcMultisigIsmConfigs.ts b/typescript/infra/config/rcMultisigIsmConfigs.ts index 9f54880c672..5829e14f385 100644 --- a/typescript/infra/config/rcMultisigIsmConfigs.ts +++ b/typescript/infra/config/rcMultisigIsmConfigs.ts @@ -88,15 +88,6 @@ export const rcMultisigIsmConfigs: ChainMap = { ], }, // ----------------- Testnets ----------------- - alfajores: { - threshold: 1, - validators: [ - { - address: '0xace978aaa61d9ee44fe3ab147fd227e0e66b8909', - alias: AW_VALIDATOR_ALIAS, - }, - ], - }, fuji: { threshold: 1, validators: [ diff --git a/typescript/infra/config/rebalancer.json b/typescript/infra/config/rebalancer.json new file mode 100644 index 00000000000..b84c70588f3 --- /dev/null +++ b/typescript/infra/config/rebalancer.json @@ -0,0 +1,8 @@ +{ + "mainnet3": { + "hyperlane": "0xa3948a15e1d0778a7d53268b651B2411AF198FE3" + }, + "testnet4": { + "hyperlane": "0x97Ec110a7891A6Ac0501c0b6e40D991e288868A1" + } +} diff --git a/typescript/infra/config/relayer.json b/typescript/infra/config/relayer.json index 4e7996f05e1..4bb86d3bda3 100644 --- a/typescript/infra/config/relayer.json +++ b/typescript/infra/config/relayer.json @@ -2,13 +2,7 @@ "mainnet3": { "hyperlane": "0x74cae0ecc47b02ed9b9d32e000fd70b9417970c5", "neutron": "0x03787bc64a4f352b4ad172947473342028513ef3", - "rc": "0x09b96417602ed6ac76651f7a8c4860e60e3aa6d0", - "vanguard0": "0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d", - "vanguard1": "0xdbcd22e5223f5d0040398e66dbb525308f27c655", - "vanguard2": "0x226b721316ea44aad50a10f4cc67fc30658ab4a9", - "vanguard3": "0xcdd728647ecd9d75413c9b780de303b1d1eb12a5", - "vanguard4": "0x5401627b69f317da9adf3d6e1e1214724ce49032", - "vanguard5": "0x6fd953d1cbdf3a79663b4238898147a6cf36d459" + "rc": "0x09b96417602ed6ac76651f7a8c4860e60e3aa6d0" }, "test": { "hyperlane": "", @@ -18,12 +12,6 @@ "testnet4": { "hyperlane": "0x16626cd24fd1f228a031e48b77602ae25f8930db", "neutron": "0xf2c72c0befa494d62949a1699a99e2c605a0b636", - "rc": "0x7fe8c60ead4ab10be736f4de2b3090db5a851f16", - "vanguard0": "0x2c9209efcaff2778d945e18fb24174e16845dc62", - "vanguard1": "0x939043d9db00f6ada1b742239beb7ddd5bf82096", - "vanguard2": "0x45b58e4d46a89c003cc7126bd971eb3794a66aeb", - "vanguard3": "0x1f4fdb150e8c9fda70687a2fd481e305af1e7f8e", - "vanguard4": "0xe41b227e7aaaf7bbd1d60258de0dd76a11a0c3fc", - "vanguard5": "0xb1d77c39166972c0873b6ae016d1a54ec3ce289b" + "rc": "0x7fe8c60ead4ab10be736f4de2b3090db5a851f16" } } diff --git a/typescript/infra/config/warp.ts b/typescript/infra/config/warp.ts index 8ab1cc830ee..c71d49811bc 100644 --- a/typescript/infra/config/warp.ts +++ b/typescript/infra/config/warp.ts @@ -28,6 +28,7 @@ import { getArbitrumBaseEthereumLumiaprismOptimismPolygonETHWarpConfig, } from './environments/mainnet3/warp/configGetters/getArbitrumBaseEthereumLumiaprismOptimismPolygonETHWarpConfig.js'; import { getArbitrumBaseEthereumLiskOptimismPolygonZeroNetworkUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumBaseEthereumOptimismPolygonZeroNetworkUSDCWarpConfig.js'; +import { getArbitrumBaseEthereumRadixUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumBaseEthereumRadixUSDCWarpConfig.js'; import { getArbitrumEthereumMantleModePolygonScrollZeroNetworkUSDTWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumEthereumMantleModePolygonScrollZeroNetworkUSDTWarpConfig.js'; import { getArbitrumNeutronTiaWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumNeutronTiaWarpConfig.js'; import { getBaseEthereumSuperseedCBBTCWarpConfig } from './environments/mainnet3/warp/configGetters/getBaseEthereumSuperseedCBBTCWarpConfig.js'; @@ -63,7 +64,9 @@ import { getInevmInjectiveINJWarpConfig } from './environments/mainnet3/warp/con import { getLumiaUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getLumiaUSDCWarpConfig.js'; import { getMantapacificNeutronTiaWarpConfig } from './environments/mainnet3/warp/configGetters/getMantapacificNeutronTiaWarpConfig.js'; import { getMintSolanaMintWarpConfig } from './environments/mainnet3/warp/configGetters/getMintSolanaMintWarpConfig.js'; +import { getMitosisMITOWarpConfig } from './environments/mainnet3/warp/configGetters/getMitosisMITOWarpConfig.js'; import { getParadexUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getParadexUSDCWarpConfig.js'; +import { getPulsechainUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getPulsechainUSDCWarpConfig.js'; import { getEZETHSTAGEGnosisSafeBuilderStrategyConfig, getRenzoEZETHSTAGEWarpConfig, @@ -87,6 +90,10 @@ import { getoUSDTTokenProductionWarpConfig, getoUSDTTokenStagingWarpConfig, } from './environments/mainnet3/warp/configGetters/getoUSDTTokenWarpConfig.js'; +import { + getoXAUTGnosisSafeSubmitterStrategyConfig, + getoXAUTTokenProductionWarpConfig, +} from './environments/mainnet3/warp/configGetters/getoXAUTTokenWarpConfig.js'; import { WarpRouteIds } from './environments/mainnet3/warp/warpIds.js'; import { getCCTPWarpConfig as getTestnetCCTPWarpConfig } from './environments/testnet4/warp/getCCTPConfig.js'; import { DEFAULT_REGISTRY_URI } from './registry.js'; @@ -143,6 +150,7 @@ export const warpConfigGetterMap: Record = { // TODO: uncomment after merging the staging route to registry // this has been commented out as it leads to check-warp-deploy cron job failing [WarpRouteIds.oUSDTSTAGE]: getoUSDTTokenStagingWarpConfig, + [WarpRouteIds.oXAUT]: getoXAUTTokenProductionWarpConfig, [WarpRouteIds.MintSolanaMINT]: getMintSolanaMintWarpConfig, [WarpRouteIds.ArbitrumBaseEthereumLumiaprismOptimismPolygonETH]: getArbitrumBaseEthereumLumiaprismOptimismPolygonETHWarpConfig, @@ -156,6 +164,10 @@ export const warpConfigGetterMap: Record = { [WarpRouteIds.TestnetCCTP]: getTestnetCCTPWarpConfig, [WarpRouteIds.MainnetCCTP]: getMainnetCCTPWarpConfig, [WarpRouteIds.LumiaUSDC]: getLumiaUSDCWarpConfig, + [WarpRouteIds.MitosisMITO]: getMitosisMITOWarpConfig, + [WarpRouteIds.ArbitrumBaseEthereumRadixUSDC]: + getArbitrumBaseEthereumRadixUSDCWarpConfig, + [WarpRouteIds.PulsechainUSDC]: getPulsechainUSDCWarpConfig, }; type StrategyConfigGetter = () => ChainSubmissionStrategy; @@ -175,6 +187,7 @@ export const strategyConfigGetterMap: Record = { getRezStagingGnosisSafeBuilderStrategyConfig, [WarpRouteIds.BsquaredUBTC]: getUbtcGnosisSafeBuilderStrategyConfigGenerator, [WarpRouteIds.MainnetCCTP]: getCCTPStrategyConfig, + [WarpRouteIds.oXAUT]: getoXAUTGnosisSafeSubmitterStrategyConfig, }; /** diff --git a/typescript/infra/helm/key-funder/templates/cron-job.yaml b/typescript/infra/helm/key-funder/templates/cron-job.yaml index 33479c1c1f8..0da731db749 100644 --- a/typescript/infra/helm/key-funder/templates/cron-job.yaml +++ b/typescript/infra/helm/key-funder/templates/cron-job.yaml @@ -40,6 +40,10 @@ spec: - --desired-kathy-balance-per-chain - {{ $chain }}={{ $balance }} {{- end }} +{{- range $chain, $balance := .Values.hyperlane.desiredRebalancerBalancePerChain }} + - --desired-rebalancer-balance-per-chain + - {{ $chain }}={{ $balance }} +{{- end }} {{- range $chain, $balance := .Values.hyperlane.igpClaimThresholdPerChain }} - --igp-claim-threshold-per-chain - {{ $chain }}={{ $balance }} diff --git a/typescript/infra/helm/offchain-lookup-server/values-mainnet.yaml b/typescript/infra/helm/offchain-lookup-server/values-mainnet.yaml index b6ba563803d..310218d2da6 100644 --- a/typescript/infra/helm/offchain-lookup-server/values-mainnet.yaml +++ b/typescript/infra/helm/offchain-lookup-server/values-mainnet.yaml @@ -28,6 +28,6 @@ env: - name: SERVER_PORT value: '3000' - name: REGISTRY_URI - value: 'https://github.com/hyperlane-xyz/hyperlane-registry' + value: 'https://github.com/hyperlane-xyz/hyperlane-registry/tree/820e3940782c196d78496e82382b838cee109635' - name: SERVER_BASE_URL value: 'https://offchain-lookup.services.hyperlane.xyz' diff --git a/typescript/infra/helm/offchain-lookup-server/values-testnet.yaml b/typescript/infra/helm/offchain-lookup-server/values-testnet.yaml index 500426541f3..9787660b36e 100644 --- a/typescript/infra/helm/offchain-lookup-server/values-testnet.yaml +++ b/typescript/infra/helm/offchain-lookup-server/values-testnet.yaml @@ -28,6 +28,6 @@ env: - name: SERVER_PORT value: '3000' - name: REGISTRY_URI - value: 'https://github.com/hyperlane-xyz/hyperlane-registry' + value: 'https://github.com/hyperlane-xyz/hyperlane-registry/tree/820e3940782c196d78496e82382b838cee109635' - name: SERVER_BASE_URL value: 'https://testnet-offchain-lookup.services.hyperlane.xyz' diff --git a/typescript/infra/package.json b/typescript/infra/package.json index 7f9dc748e3c..cfb316abf49 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/infra", "description": "Infrastructure utilities for the Hyperlane Network", - "version": "16.0.0", + "version": "18.2.0", "dependencies": { "@arbitrum/sdk": "^4.0.0", "@aws-sdk/client-iam": "^3.74.0", @@ -14,10 +14,10 @@ "@ethersproject/providers": "*", "@google-cloud/pino-logging-gcp-config": "^1.0.6", "@google-cloud/secret-manager": "^5.5.0", - "@hyperlane-xyz/helloworld": "16.0.0", + "@hyperlane-xyz/helloworld": "18.2.0", "@hyperlane-xyz/registry": "20.0.0", - "@hyperlane-xyz/sdk": "16.0.0", - "@hyperlane-xyz/utils": "16.0.0", + "@hyperlane-xyz/sdk": "18.2.0", + "@hyperlane-xyz/utils": "18.2.0", "@inquirer/prompts": "3.3.2", "@nomiclabs/hardhat-etherscan": "^3.0.3", "@safe-global/api-kit": "1.3.0", @@ -55,6 +55,7 @@ "hardhat": "^2.22.2", "mocha": "^11.5.0", "prettier": "^3.5.3", + "smol-toml": "1.4.2", "tsx": "^4.19.1", "typescript": "5.3.3" }, @@ -83,6 +84,7 @@ "kathy": "yarn tsx ./scripts/send-test-messages.ts", "prettier": "prettier --write ./src ./config ./scripts ./test", "test:warp": "mocha --config ../sdk/.mocharc.json test/warp-configs.test.ts", + "test:gitleaks": "mocha --config ../sdk/.mocharc.json test/gitleaks.test.ts", "test:balance": "mocha --config ../sdk/.mocharc.json test/balance-alerts.test.ts", "test:unit": "mocha --config ../sdk/.mocharc.json \"test/**/*.test.ts\" --ignore \"test/{warp-configs,balance-alerts}.test.ts\"", "test:hardhat": "yarn hardhat-esm test test/govern.hardhat-test.ts", diff --git a/typescript/infra/scripts/agent-utils.ts b/typescript/infra/scripts/agent-utils.ts index 5673cf08845..234da4f4fde 100644 --- a/typescript/infra/scripts/agent-utils.ts +++ b/typescript/infra/scripts/agent-utils.ts @@ -58,7 +58,7 @@ import { writeMergedJSONAtPath, } from '../src/utils/utils.js'; -const debugLog = rootLogger.child({ module: 'infra:scripts:utils' }).debug; +const logger = rootLogger.child({ module: 'infra:scripts:agent-utils' }); export enum Modules { // TODO: change @@ -557,7 +557,7 @@ export function getKeyForRole( chain?: ChainName, index?: number, ): CloudAgentKey { - debugLog(`Getting key for ${role} role`); + logger.debug({ chain }, `Getting key for ${role} role`); const agentConfig = getAgentConfig(context, environment); return getCloudAgentKey(agentConfig, role, chain, index); } @@ -578,10 +578,10 @@ export async function getMultiProviderForRole( index?: number, ): Promise { const chainMetadata = await registry.getMetadata(); - debugLog(`Getting multiprovider for ${role} role`); + logger.debug(`Getting multiprovider for ${role} role`); const multiProvider = new MultiProvider(chainMetadata); if (inCIMode()) { - debugLog('Running in CI, returning multiprovider without secret keys'); + logger.debug('Running in CI, returning multiprovider without secret keys'); return multiProvider; } await promiseObjAll( @@ -619,7 +619,7 @@ export async function getKeysForRole( index?: number, ): Promise> { if (inCIMode()) { - debugLog('No keys to return in CI'); + logger.debug('No keys to return in CI'); return {}; } diff --git a/typescript/infra/scripts/agents/update-agent-config.ts b/typescript/infra/scripts/agents/update-agent-config.ts index d6dc57feef3..1394a09d52a 100644 --- a/typescript/infra/scripts/agents/update-agent-config.ts +++ b/typescript/infra/scripts/agents/update-agent-config.ts @@ -197,23 +197,16 @@ export async function writeAgentConfig( additionalConfig, ); - // Manually add contract addresses for Lumia chain - if (agentConfig.chains['lumia']) { - agentConfig.chains['lumia'] = { - ...agentConfig.chains['lumia'], - interchainGasPaymaster: '0x9024A3902B542C87a5C4A2b3e15d60B2f087Dc3E', - mailbox: '0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7', - merkleTreeHook: '0x9c44E6b8F0dB517C2c3a0478caaC5349b614F912', - validatorAnnounce: '0x989B7307d266151BE763935C856493D968b2affF', - }; - } - const filepath = getAgentConfigJsonPath(envNameToAgentEnv[environment]); if (fs.existsSync(filepath)) { const currentAgentConfig: AgentConfig = readJSONAtPath(filepath); // Remove transactionOverrides from each chain in the agent config // To ensure all overrides are configured in infra code or the registry, and not in JSON for (const chainConfig of Object.values(currentAgentConfig.chains)) { + // special case for 0g as we want some agent specific overrides + if (chainConfig.name === 'zerogravity') { + continue; + } delete chainConfig.transactionOverrides; } writeJsonAtPath(filepath, objMerge(currentAgentConfig, agentConfig)); diff --git a/typescript/infra/scripts/check/check-owner-ica.ts b/typescript/infra/scripts/check/check-owner-ica.ts index bdf33c4756e..162bcee550f 100644 --- a/typescript/infra/scripts/check/check-owner-ica.ts +++ b/typescript/infra/scripts/check/check-owner-ica.ts @@ -10,8 +10,8 @@ import { } from '@hyperlane-xyz/utils'; import { + getGovernanceIcas, getGovernanceSafes, - getLegacyGovernanceIcas, } from '../../config/environments/mainnet3/governance/utils.js'; import { chainsToSkip, @@ -52,7 +52,7 @@ async function main() { multiProvider, ); - const icas = getLegacyGovernanceIcas(governanceType); + const icas = getGovernanceIcas(governanceType); const checkOwnerIcaChains = ( chains?.length ? chains : Object.keys(icas) diff --git a/typescript/infra/scripts/check/check-utils.ts b/typescript/infra/scripts/check/check-utils.ts index d5ae6201720..8895ba8d8b8 100644 --- a/typescript/infra/scripts/check/check-utils.ts +++ b/typescript/infra/scripts/check/check-utils.ts @@ -10,10 +10,10 @@ import { HyperlaneIgpChecker, HyperlaneIsmFactory, InterchainAccount, - InterchainAccountChecker, InterchainAccountConfig, InterchainQuery, InterchainQueryChecker, + IsmType, MultiProvider, attachContractsMapAndGetForeignDeployments, hypERC20factories, @@ -23,6 +23,7 @@ import { eqAddress, objFilter } from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts.js'; import { DEPLOYER } from '../../config/environments/mainnet3/owners.js'; +import { DEFAULT_OFFCHAIN_LOOKUP_ISM_URLS } from '../../config/environments/utils.js'; import { getWarpAddressesFrom } from '../../config/registry.js'; import { getWarpConfig } from '../../config/warp.js'; import { chainsToSkip } from '../../src/config/chain.js'; @@ -67,6 +68,12 @@ export function getCheckDeployArgs() { return withRegistryUris(withWarpRouteId(withModule(getCheckBaseArgs()))); } +const ICA_ENABLED_MODULES = [ + Modules.INTERCHAIN_ACCOUNTS, + Modules.HAAS, + Modules.WARP, +]; + export async function getGovernor( module: Modules, context: Contexts, @@ -120,10 +127,12 @@ export async function getGovernor( (chain, _): _ is Record => !!chainAddresses[chain]?.interchainAccountRouter, ); - const ica = InterchainAccount.fromAddressesMap( - icaChainAddresses, - multiProvider, - ); + + const ica = + ICA_ENABLED_MODULES.includes(module) && + Object.keys(icaChainAddresses).length > 0 + ? InterchainAccount.fromAddressesMap(icaChainAddresses, multiProvider) + : undefined; if (module === Modules.CORE) { chainsToSkip.forEach((chain) => delete envConfig.core[chain]); @@ -134,31 +143,62 @@ export async function getGovernor( ismFactory, chainAddresses, ); - governor = new HyperlaneCoreGovernor(checker, ica); + governor = new HyperlaneCoreGovernor(checker); } else if (module === Modules.INTERCHAIN_GAS_PAYMASTER) { const igp = HyperlaneIgp.fromAddressesMap(chainAddresses, multiProvider); const checker = new HyperlaneIgpChecker(multiProvider, igp, envConfig.igp); governor = new HyperlaneIgpGovernor(checker); } else if (module === Modules.INTERCHAIN_ACCOUNTS) { - const checker = new InterchainAccountChecker( - multiProvider, - ica, - objFilter( - routerConfig, - (chain, _): _ is InterchainAccountConfig => !!icaChainAddresses[chain], - ), - ); - governor = new ProxiedRouterGovernor(checker); + chainsToSkip.forEach((chain) => delete routerConfig[chain]); + + const icaConfig = Object.entries(routerConfig).reduce< + Record + >((acc, [chain, conf]) => { + if (icaChainAddresses[chain]) { + acc[chain] = { + ...conf, + commitmentIsm: { + type: IsmType.OFFCHAIN_LOOKUP, + owner: conf.owner, + ownerOverrides: conf.ownerOverrides, + urls: DEFAULT_OFFCHAIN_LOOKUP_ISM_URLS, + }, + }; + } + return acc; + }, {}); + + if (!ica) { + throw new Error('ICA app not initialized'); + } + + const icaChecker = new HyperlaneICAChecker(multiProvider, ica, icaConfig); + governor = new ProxiedRouterGovernor(icaChecker); } else if (module === Modules.HAAS) { chainsToSkip.forEach((chain) => delete routerConfig[chain]); - const icaChecker = new HyperlaneICAChecker( - multiProvider, - ica, - objFilter( - routerConfig, - (chain, _): _ is InterchainAccountConfig => !!icaChainAddresses[chain], - ), - ); + + const icaConfig = Object.entries(routerConfig).reduce< + Record + >((acc, [chain, conf]) => { + if (icaChainAddresses[chain]) { + acc[chain] = { + ...conf, + commitmentIsm: { + type: IsmType.OFFCHAIN_LOOKUP, + owner: conf.owner, + ownerOverrides: conf.ownerOverrides, + urls: DEFAULT_OFFCHAIN_LOOKUP_ISM_URLS, + }, + }; + } + return acc; + }, {}); + + if (!ica) { + throw new Error('ICA app not initialized'); + } + + const icaChecker = new HyperlaneICAChecker(multiProvider, ica, icaConfig); chainsToSkip.forEach((chain) => delete envConfig.core[chain]); const coreChecker = new HyperlaneCoreChecker( multiProvider, @@ -167,6 +207,9 @@ export async function getGovernor( ismFactory, chainAddresses, ); + if (!ica) { + throw new Error('ICA app not initialized'); + } governor = new HyperlaneHaasGovernor(ica, icaChecker, coreChecker); } else if (module === Modules.INTERCHAIN_QUERY_SYSTEM) { const iqs = InterchainQuery.fromAddressesMap(chainAddresses, multiProvider); diff --git a/typescript/infra/scripts/check/check-validator-announce.ts b/typescript/infra/scripts/check/check-validator-announce.ts index a67aa277202..c300ad2456a 100644 --- a/typescript/infra/scripts/check/check-validator-announce.ts +++ b/typescript/infra/scripts/check/check-validator-announce.ts @@ -7,7 +7,21 @@ import { getEnvironmentConfig, getHyperlaneCore } from '../core-utils.js'; const minimumValidatorCount = 2; +const validatorCountToThreshold: Record = { + 1: 1, + 2: 2, + 3: 2, + 4: 3, + 5: 3, + 6: 4, + 7: 5, + 8: 6, + 9: 6, + 10: 7, +}; + const getMinimumThreshold = (validatorCount: number): number => + validatorCountToThreshold[validatorCount] ?? Math.floor(validatorCount / 2) + 1; const thresholdOK = 'threshold OK'; @@ -36,9 +50,14 @@ async function main() { const allChainsToCheck = chains && chains.length > 0 ? chains : config.supportedChainNames; - const chainsWithUnannouncedValidators: ChainMap = {}; + const chainsWithUnannouncedValidators: ChainMap< + { + address: string; + alias: string; + }[] + > = {}; - const chainsToSkip = ['lumia', 'osmosis']; + const chainsToSkip = ['osmosis']; const results: ChainResult[] = await Promise.all( allChainsToCheck @@ -47,13 +66,15 @@ async function main() { try { const defaultValidatorConfigs = defaultMultisigConfigs[chain]?.validators || []; - const validators = defaultValidatorConfigs.map((v) => v.address); - const validatorCount = validators.length; + const validatorCount = defaultValidatorConfigs.length; const threshold = defaultMultisigConfigs[chain]?.threshold || 0; const minimumThreshold = getMinimumThreshold(validatorCount); let unannouncedValidatorCount = 0; - let unannouncedValidators: string[] = []; + let unannouncedValidators: { + address: string; + alias: string; + }[] = []; // Only check onchain announcements for ethereum protocol chains if (isEthereumProtocolChain(chain)) { @@ -62,9 +83,9 @@ async function main() { const announcedValidators = await validatorAnnounce.getAnnouncedValidators(); - unannouncedValidators = validators.filter( - (validator) => - !announcedValidators.some((x) => eqAddress(x, validator)), + unannouncedValidators = defaultValidatorConfigs.filter( + ({ address }) => + !announcedValidators.some((x) => eqAddress(x, address)), ); if (unannouncedValidators.length > 0) { @@ -78,7 +99,10 @@ async function main() { chain, threshold, [thresholdOK]: - threshold < minimumThreshold || threshold > validatorCount + threshold < minimumThreshold || + threshold > validatorCount || + (threshold === validatorCount && + validatorCount !== minimumThreshold) ? CheckResult.WARNING : CheckResult.OK, total: validatorCount, @@ -123,7 +147,7 @@ async function main() { const minimumThreshold = getMinimumThreshold(validatorCount); console.log( ` - ${chain}:`, - `threshold should be ${minimumThreshold} ≤ t ≤ ${validatorCount}`, + `threshold should be ${minimumThreshold} ≤ t < ${validatorCount}`, ); }); } else { @@ -147,7 +171,11 @@ async function main() { if (unnanouncedChains.length > 0) { console.log('\n⚠️ Chains with unannounced validators:'); unnanouncedChains.forEach((chain) => { - console.log(` - ${chain}: ${chainsWithUnannouncedValidators[chain]}`); + console.log( + ` - ${chain}: ${chainsWithUnannouncedValidators[chain] + .map(({ address, alias }) => `${address} (${alias})`) + .join(', ')}`, + ); }); } else { console.log('\n✅ All validators announced!'); diff --git a/typescript/infra/scripts/check/check-validator-rpcs.ts b/typescript/infra/scripts/check/check-validator-rpcs.ts index b580401da74..72d8c96e7b3 100644 --- a/typescript/infra/scripts/check/check-validator-rpcs.ts +++ b/typescript/infra/scripts/check/check-validator-rpcs.ts @@ -19,10 +19,9 @@ async function main() { const config = getEnvironmentConfig(environment); const { core } = await getHyperlaneCore(environment); - // Ensure we skip lumia, as we don't have the addresses in registry. const targetNetworks = ( chains && chains.length > 0 ? chains : config.supportedChainNames - ).filter((chain) => isEthereumProtocolChain(chain) && chain !== 'lumia'); + ).filter((chain) => isEthereumProtocolChain(chain)); // set useSecrets to `false` to compare with public RPCs instead of private ones const registry = await config.getRegistry(false); diff --git a/typescript/infra/scripts/check/check-validator-version.ts b/typescript/infra/scripts/check/check-validator-version.ts index 78dead97f4a..d7287a2e985 100644 --- a/typescript/infra/scripts/check/check-validator-version.ts +++ b/typescript/infra/scripts/check/check-validator-version.ts @@ -1,6 +1,5 @@ import { execSync } from 'child_process'; -import { ValidatorAnnounce__factory } from '@hyperlane-xyz/core'; import { ChainName, defaultMultisigConfigs, @@ -124,18 +123,9 @@ async function main() { const mismatchedValidators: ValidatorInfo[] = []; const upgradedValidators: ValidatorInfo[] = []; - // Manually add validator announce for OG Lumia chain deployment - const lumiaValidatorAnnounce = ValidatorAnnounce__factory.connect( - '0x989B7307d266151BE763935C856493D968b2affF', - core.multiProvider.getProvider('lumia'), - ); - await Promise.all( targetNetworks.map(async (chain) => { - const validatorAnnounce = - chain === 'lumia' - ? lumiaValidatorAnnounce - : core.getContracts(chain).validatorAnnounce; + const validatorAnnounce = core.getContracts(chain).validatorAnnounce; const expectedValidators = defaultMultisigConfigs[chain].validators || []; const storageLocations = await validatorAnnounce.getAnnouncedStorageLocations( diff --git a/typescript/infra/scripts/cosmos-helpers/generate-igp-commands.ts b/typescript/infra/scripts/cosmos-helpers/generate-igp-commands.ts new file mode 100644 index 00000000000..1631a6b8c3b --- /dev/null +++ b/typescript/infra/scripts/cosmos-helpers/generate-igp-commands.ts @@ -0,0 +1,109 @@ +import { Argv } from 'yargs'; + +import rawGasPrices from '../../config/environments/mainnet3/gasPrices.json' with { type: 'json' }; +import rawTokenPrices from '../../config/environments/mainnet3/tokenPrices.json' with { type: 'json' }; +import { getArgs, withChains } from '../agent-utils.js'; +import { getEnvironmentConfig } from '../core-utils.js'; + +export function withOriginChain(args: Argv) { + return args + .describe('originChain', 'The chain for which IGP prices are calculated') + .alias('o', 'origin-chain') + .default('originChain', '') + .demandOption('originChain'); +} + +export function withCommandPrefix(args: Argv) { + return args + .describe( + 'commandPrefix', + 'The command for the Cosmos CLI, it will be prepended to IGP config args.', + ) + .example( + 'commandPrefix', + 'celestiad tx hyperlane hooks igp set-destination-gas-config [igp-id]', + ) + .alias('p', 'command-prefix') + .default('commandPrefix', ''); +} + +/** + * Generates the command(s) for the CosmosSDK CLI to deploy ISMs. + */ + +async function main() { + const { environment, chains, commandPrefix, originChain } = + await withOriginChain(withCommandPrefix(withChains(getArgs()))).argv; + const config = getEnvironmentConfig(environment); + const multiProvider = await config.getMultiProvider(); + + if (chains == undefined) { + throw new Error('Chains must be provided'); + } + + const gasPrices = rawGasPrices as unknown as { + [key: string]: { amount: string; decimals: number }; + }; + const tokenPrices = rawTokenPrices as unknown as { [key: string]: string }; + + const registry = await config.getRegistry(false); + + const tokenPriceConfigs = chains.map((chain) => { + if (!tokenPrices[chain]) { + throw Error(`No token price found for ${chain}`); + } + if (!gasPrices[chain]) { + throw Error(`No gas price found for ${chain}`); + } + + return { + name: chain, + domain_id: multiProvider.getDomainId(chain), + token_price: tokenPrices[chain], + gas_price_amount: gasPrices[chain].amount, + gas_price_decimals: gasPrices[chain].decimals, + }; + }); + + if (!tokenPrices[originChain]) { + throw Error(`No token price found for ${originChain}`); + } + + for (const entry of tokenPriceConfigs) { + const token_price = parseFloat(entry.token_price); + const origin_token_price = parseFloat(tokenPrices[originChain]); + + const destNativeTokenDecimals = ( + await registry.getChainMetadata(entry.name) + )?.nativeToken?.decimals!; + const originNativeTokenDecimals = ( + await registry.getChainMetadata(originChain) + )?.nativeToken?.decimals!; + + let ratio = token_price / origin_token_price; + + let gasPriceSmallestUnit = + parseFloat(entry.gas_price_amount) * + Math.pow(10, entry.gas_price_decimals); + let originDestDecimalsRatio = Math.pow( + 10, + originNativeTokenDecimals - destNativeTokenDecimals, + ); + + let gasPrice = gasPriceSmallestUnit * originDestDecimalsRatio; + + // Split scaling factor equally between price and ratio + ratio = Math.round(ratio); + const gas_price = Math.round(gasPrice * 1e10); + + console.log(`${entry.name}: ${entry.domain_id}`); + console.log( + `${commandPrefix} ${entry.domain_id} ${ratio} ${gas_price} 50000`, + ); + } +} + +main().catch((err) => { + console.error('Error:', err); + process.exit(1); +}); diff --git a/typescript/infra/scripts/cosmos-helpers/generate-ism-command.ts b/typescript/infra/scripts/cosmos-helpers/generate-ism-command.ts new file mode 100644 index 00000000000..6b28c840feb --- /dev/null +++ b/typescript/infra/scripts/cosmos-helpers/generate-ism-command.ts @@ -0,0 +1,63 @@ +import { Argv } from 'yargs'; + +import { defaultMultisigConfigs } from '@hyperlane-xyz/sdk'; + +import { getArgs, withChains } from '../agent-utils.js'; +import { getEnvironmentConfig } from '../core-utils.js'; + +export function withCommandPrefix(args: Argv) { + return args + .describe( + 'commandPrefix', + 'The command for the Cosmos CLI, it will be prepended to the validators list to create an executable command.', + ) + .example( + 'commandPrefix', + './celestiad tx hyperlane ism create-merkle-root-multisig', + ) + .alias('p', 'command-prefix') + .default('commandPrefix', ''); +} + +/** + * Generates the command(s) for the CosmosSDK CLI to deploy ISMs. + */ + +async function main() { + const { environment, chains, commandPrefix } = await withCommandPrefix( + withChains(getArgs()), + ).argv; + const config = getEnvironmentConfig(environment); + const multiProvider = await config.getMultiProvider(); + + if (chains == undefined) { + throw new Error('Chains must be provided'); + } + + const validatorConfigs = chains.map((chain) => { + const multisig = defaultMultisigConfigs[chain]; + if (!multisig) { + throw Error(`No multisig config found for ${chain}`); + } + + return { + name: chain, + domain_id: multiProvider.getDomainId(chain), + // Sort addresses alphabetically + addresses: multisig.validators.map(({ address }) => address).sort(), + threshold: multisig.threshold, + }; + }); + + for (const entry of validatorConfigs) { + console.log(`${entry.name}: ${entry.domain_id}`); + console.log( + `${commandPrefix} ${entry.addresses.join(',')} ${entry.threshold}`, + ); + } +} + +main().catch((err) => { + console.error('Error:', err); + process.exit(1); +}); diff --git a/typescript/infra/scripts/deploy.ts b/typescript/infra/scripts/deploy.ts index a4207cdc61c..517497dd473 100644 --- a/typescript/infra/scripts/deploy.ts +++ b/typescript/infra/scripts/deploy.ts @@ -18,14 +18,18 @@ import { HyperlaneIsmFactory, HyperlaneProxyFactoryDeployer, InterchainAccount, + InterchainAccountConfig, InterchainAccountDeployer, InterchainQueryDeployer, + IsmType, + RouterConfig, TestRecipientDeployer, } from '@hyperlane-xyz/sdk'; import { inCIMode, objFilter, objMap } from '@hyperlane-xyz/utils'; import { Contexts } from '../config/contexts.js'; import { core as coreConfig } from '../config/environments/mainnet3/core.js'; +import { DEFAULT_OFFCHAIN_LOOKUP_ISM_URLS } from '../config/environments/utils.js'; import { getEnvAddresses } from '../config/registry.js'; import { getWarpConfig } from '../config/warp.js'; import { chainsToSkip } from '../src/config/chain.js'; @@ -159,7 +163,20 @@ async function main() { ); } else if (module === Modules.INTERCHAIN_ACCOUNTS) { const { core } = await getHyperlaneCore(environment, multiProvider); - config = core.getRouterConfig(envConfig.owners); + config = objMap( + core.getRouterConfig(envConfig.owners) as ChainMap, + (_, routerConfig): InterchainAccountConfig => { + return { + ...routerConfig, + commitmentIsm: { + type: IsmType.OFFCHAIN_LOOKUP, + owner: routerConfig.owner, + ownerOverrides: routerConfig.ownerOverrides, + urls: DEFAULT_OFFCHAIN_LOOKUP_ISM_URLS, + }, + }; + }, + ); deployer = new InterchainAccountDeployer( multiProvider, contractVerifier, diff --git a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts index d9f08fa8ac5..d7dae96bca6 100644 --- a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts +++ b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts @@ -45,9 +45,9 @@ import { import { getAgentConfig, getArgs } from '../agent-utils.js'; import { getEnvironmentConfig } from '../core-utils.js'; -import L1ETHGateway from './utils/L1ETHGateway.json'; -import L1MessageQueue from './utils/L1MessageQueue.json'; -import L1ScrollMessenger from './utils/L1ScrollMessenger.json'; +import L1ETHGateway from './utils/L1ETHGateway.json' with { type: 'json' }; +import L1MessageQueue from './utils/L1MessageQueue.json' with { type: 'json' }; +import L1ScrollMessenger from './utils/L1ScrollMessenger.json' with { type: 'json' }; const logger = rootLogger.child({ module: 'fund-keys' }); @@ -91,10 +91,6 @@ const RC_FUNDING_DISCOUNT_DENOMINATOR = ethers.BigNumber.from(10); const CONTEXT_FUNDING_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes const CHAIN_FUNDING_TIMEOUT_MS = 1 * 60 * 1000; // 1 minute -// Need to ensure we don't fund non-vanguard chains in the vanguard contexts -const VANGUARD_CHAINS = ['base', 'arbitrum', 'optimism', 'ethereum', 'bsc']; -const VANGUARD_CONTEXTS: Contexts[] = [Contexts.Vanguard0]; - // Funds key addresses for multiple contexts from the deployer key of the context // specified via the `--context` flag. // The --contexts-and-roles flag is used to specify the contexts and the key roles @@ -146,6 +142,14 @@ async function main() { ) .coerce('desired-kathy-balance-per-chain', parseBalancePerChain) + .string('desired-rebalancer-balance-per-chain') + .array('desired-rebalancer-balance-per-chain') + .describe( + 'desired-rebalancer-balance-per-chain', + 'Array indicating target balance to fund Rebalancer for each chain. Each element is expected as =', + ) + .coerce('desired-rebalancer-balance-per-chain', parseBalancePerChain) + .string('igp-claim-threshold-per-chain') .array('igp-claim-threshold-per-chain') .describe( @@ -181,6 +185,7 @@ async function main() { argv.chainSkipOverride, argv.desiredBalancePerChain, argv.desiredKathyBalancePerChain ?? {}, + argv.desiredRebalancerBalancePerChain ?? {}, argv.igpClaimThresholdPerChain ?? {}, path, ), @@ -198,6 +203,7 @@ async function main() { argv.chainSkipOverride, argv.desiredBalancePerChain, argv.desiredKathyBalancePerChain ?? {}, + argv.desiredRebalancerBalancePerChain ?? {}, argv.igpClaimThresholdPerChain ?? {}, ), ), @@ -255,6 +261,9 @@ class ContextFunder { public readonly desiredKathyBalancePerChain: KeyFunderConfig< ChainName[] >['desiredKathyBalancePerChain'], + public readonly desiredRebalancerBalancePerChain: KeyFunderConfig< + ChainName[] + >['desiredRebalancerBalancePerChain'], public readonly igpClaimThresholdPerChain: KeyFunderConfig< ChainName[] >['igpClaimThresholdPerChain'], @@ -263,18 +272,6 @@ class ContextFunder { roleKeysPerChain = objFilter( roleKeysPerChain, (chain, _roleKeys): _roleKeys is Record => { - // Skip funding for vanguard contexts on non-vanguard chains - if ( - VANGUARD_CONTEXTS.includes(this.context) && - !VANGUARD_CHAINS.includes(chain) - ) { - logger.warn( - { chain, context: this.context }, - 'Skipping funding for vanguard context on non-vanguard chain', - ); - return false; - } - const valid = isEthereumProtocolChain(chain) && multiProvider.tryGetChainName(chain) !== null; @@ -289,12 +286,7 @@ class ContextFunder { ); this.igp = HyperlaneIgp.fromAddressesMap( - { - ...getEnvAddresses(this.environment), - lumia: { - interchainGasPaymaster: '0x9024A3902B542C87a5C4A2b3e15d60B2f087Dc3E', - }, - }, + getEnvAddresses(this.environment), multiProvider, ); this.keysToFundPerChain = objMap(roleKeysPerChain, (_chain, roleKeys) => { @@ -320,6 +312,9 @@ class ContextFunder { desiredKathyBalancePerChain: KeyFunderConfig< ChainName[] >['desiredKathyBalancePerChain'], + desiredRebalancerBalancePerChain: KeyFunderConfig< + ChainName[] + >['desiredRebalancerBalancePerChain'], igpClaimThresholdPerChain: KeyFunderConfig< ChainName[] >['igpClaimThresholdPerChain'], @@ -395,6 +390,7 @@ class ContextFunder { chainSkipOverride, desiredBalancePerChain, desiredKathyBalancePerChain, + desiredRebalancerBalancePerChain, igpClaimThresholdPerChain, ); } @@ -413,6 +409,9 @@ class ContextFunder { desiredKathyBalancePerChain: KeyFunderConfig< ChainName[] >['desiredKathyBalancePerChain'], + desiredRebalancerBalancePerChain: KeyFunderConfig< + ChainName[] + >['desiredRebalancerBalancePerChain'], igpClaimThresholdPerChain: KeyFunderConfig< ChainName[] >['igpClaimThresholdPerChain'], @@ -421,6 +420,7 @@ class ContextFunder { const fundableRoleKeys: Record = { [Role.Relayer]: '', [Role.Kathy]: '', + [Role.Rebalancer]: '', }; const roleKeysPerChain: ChainMap> = {}; const { supportedChainNames } = getEnvironmentConfig(environment); @@ -439,6 +439,7 @@ class ContextFunder { roleKeysPerChain[chain as ChainName] = { [Role.Relayer]: [], [Role.Kathy]: [], + [Role.Rebalancer]: [], }; } roleKeysPerChain[chain][role] = [ @@ -462,6 +463,7 @@ class ContextFunder { chainSkipOverride, desiredBalancePerChain, desiredKathyBalancePerChain, + desiredRebalancerBalancePerChain, igpClaimThresholdPerChain, ); } @@ -699,6 +701,18 @@ class ContextFunder { } else { desiredBalanceEther = this.desiredKathyBalancePerChain[chain]; } + } else if (role === Role.Rebalancer) { + const desiredRebalancerBalance = + this.desiredRebalancerBalancePerChain[chain]; + if (desiredRebalancerBalance === undefined) { + logger.warn( + { chain }, + 'No desired balance for Rebalancer, not funding', + ); + desiredBalanceEther = '0'; + } else { + desiredBalanceEther = this.desiredRebalancerBalancePerChain[chain]; + } } else { desiredBalanceEther = this.desiredBalancePerChain[chain]; } @@ -956,7 +970,7 @@ function parseContextAndRoles(str: string): ContextAndRoles { } // For now, restrict the valid roles we think are reasonable to want to fund - const validRoles = new Set([Role.Relayer, Role.Kathy]); + const validRoles = new Set([Role.Relayer, Role.Kathy, Role.Rebalancer]); for (const role of roles) { if (!validRoles.has(role)) { throw Error( diff --git a/typescript/infra/scripts/funding/fund-wallet-from-deployer-key.ts b/typescript/infra/scripts/funding/fund-wallet-from-deployer-key.ts new file mode 100644 index 00000000000..2153876c6f9 --- /dev/null +++ b/typescript/infra/scripts/funding/fund-wallet-from-deployer-key.ts @@ -0,0 +1,291 @@ +import { ethers } from 'ethers'; +import { formatUnits, parseUnits } from 'ethers/lib/utils.js'; +import { format } from 'util'; + +import { + ChainName, + CoinGeckoTokenPriceGetter, + MultiProtocolSignerSignerAccountInfo, + ProtocolTypedTransaction, + TOKEN_STANDARD_TO_PROVIDER_TYPE, + Token, + TransferParams, + getSignerForChain, +} from '@hyperlane-xyz/sdk'; +import { + Address, + ProtocolType, + assert, + rootLogger, + strip0x, + toWei, +} from '@hyperlane-xyz/utils'; + +import { Contexts } from '../../config/contexts.js'; +import { getDeployerKey } from '../../src/agents/key-utils.js'; +import { getCoinGeckoApiKey } from '../../src/coingecko/utils.js'; +import { EnvironmentConfig } from '../../src/config/environment.js'; +import { assertChain } from '../../src/utils/utils.js'; +import { getAgentConfig, getArgs } from '../agent-utils.js'; +import { getEnvironmentConfig } from '../core-utils.js'; + +const logger = rootLogger.child({ + module: 'fund-hot-wallet', +}); +/** + * For solana deployments at least 2.5 SOL are needed for rent. + * As of 11/09/2025 the price is ~$227 meaning that 2.5 SOL are + * ~$600. + * + * Ethereum mainnet deployments can be expensive too depending + * on network activity. As the price is ~$4400, $1000 should be enough + * to cover mainnet costs + */ +const MAX_FUNDING_AMOUNT_IN_USD = 1000; + +async function main() { + const argv = await getArgs() + .string('recipient') + .alias('r', 'recipient') + .describe('recipient', 'The address to fund') + .demandOption('recipient') + + .string('amount') + .alias('a', 'amount') + .describe( + 'amount', + 'Amount to send (in token units, e.g., "1.5" for 1.5 ETH)', + ) + .demandOption('amount') + + .string('chain') + .alias('c', 'chain') + .describe('chain', 'Chain name to send funds on') + .demandOption('chain') + .coerce('chain', assertChain) + + .boolean('dry-run') + .describe('dry-run', 'Simulate the transaction without sending') + .default('dry-run', false).argv; + + const config = getEnvironmentConfig(argv.environment); + const { recipient, amount, chain, dryRun } = argv; + + logger.info( + { + recipient, + amount, + chain, + dryRun, + }, + 'Starting funding operation', + ); + + try { + await fundAccount({ + config, + chainName: chain!, + recipientAddress: recipient, + amount, + dryRun, + }); + + logger.info('Funding operation completed successfully'); + } catch (error) { + logger.error( + { + error: format(error), + chain, + recipient, + amount, + }, + 'Funding operation failed', + ); + process.exit(1); + } +} + +interface FundingParams { + config: EnvironmentConfig; + chainName: ChainName; + recipientAddress: Address; + amount: string; + dryRun: boolean; +} + +async function fundAccount({ + config, + chainName, + recipientAddress, + amount, + dryRun, +}: FundingParams): Promise { + const multiProtocolProvider = await config.getMultiProtocolProvider(); + + const chainMetadata = multiProtocolProvider.getChainMetadata(chainName); + const protocol = chainMetadata.protocol; + + const fundingLogger = logger.child({ + chainName, + protocol, + }); + + const tokenPriceGetter = new CoinGeckoTokenPriceGetter({ + chainMetadata: { [chainName]: chainMetadata }, + apiKey: await getCoinGeckoApiKey(fundingLogger), + }); + + let tokenPrice; + try { + tokenPrice = await tokenPriceGetter.getTokenPrice(chainName); + } catch (err) { + fundingLogger.error( + { chainName, err }, + `Failed to get native token price for ${chainName}, falling back to 1usd`, + ); + tokenPrice = 1; + } + const fundingAmountInUsd = parseFloat(amount) * tokenPrice; + + if (fundingAmountInUsd > MAX_FUNDING_AMOUNT_IN_USD) { + throw new Error( + `Funding amount in USD exceeds max funding amount. Max: ${MAX_FUNDING_AMOUNT_IN_USD}. Got: ${fundingAmountInUsd}`, + ); + } + + // Create token instance + const token = Token.FromChainMetadataNativeToken(chainMetadata); + const adapter = token.getAdapter(multiProtocolProvider); + + // Get signer + fundingLogger.info('Retrieved signer info'); + + const agentConfig = getAgentConfig(Contexts.Hyperlane, config.environment); + const privateKeyAgent = getDeployerKey(agentConfig, chainName); + + await privateKeyAgent.fetch(); + + let accountInfo: MultiProtocolSignerSignerAccountInfo; + if (protocol === ProtocolType.Starknet) { + const address = privateKeyAgent.addressForProtocol(protocol); + assert(address, `missing private key address for protocol ${protocol}`); + + accountInfo = { + protocol, + address, + privateKey: privateKeyAgent.privateKeyForProtocol(protocol), + }; + } else if (protocol === ProtocolType.Sealevel) { + accountInfo = { + protocol, + privateKey: privateKeyAgent.privateKeyForProtocol(protocol), + }; + } else { + accountInfo = { + protocol, + privateKey: privateKeyAgent.privateKeyForProtocol(protocol), + } as MultiProtocolSignerSignerAccountInfo; + } + + const signer = await getSignerForChain( + chainName, + accountInfo, + multiProtocolProvider, + ); + + fundingLogger.info( + { chainName, protocol }, + 'Performing pre transaction checks', + ); + + // Check balance before transfer + const fromAddress = await signer.address(); + const currentBalance = await adapter.getBalance(fromAddress); + + fundingLogger.info( + { + fromAddress, + currentBalance: currentBalance.toString(), + symbol: token.symbol, + }, + 'Current sender balance', + ); + + // Convert amount to wei/smallest unit + const decimals = token.decimals; + const weiAmount = BigInt(toWei(amount, decimals)); + + fundingLogger.info( + { + amount, + decimals, + weiAmount: weiAmount.toString(), + }, + 'Parsed transfer amount', + ); + + // Check if we have sufficient balance + if (currentBalance < weiAmount) { + throw new Error( + `Insufficient balance. Have: ${formatUnits(currentBalance, decimals)} ${token.symbol}, Need: ${amount} ${token.symbol}`, + ); + } + + // Build transfer parameters based on protocol requirements + const transferParams: TransferParams = { + weiAmountOrId: weiAmount, + recipient: recipientAddress, + fromAccountOwner: fromAddress, + }; + + fundingLogger.info( + { + transferParams, + dryRun, + }, + 'Preparing transfer transaction', + ); + + // Execute the transfer + const transferTx = await adapter.populateTransferTx(transferParams); + + const protocolTypedTx = { + transaction: transferTx, + type: TOKEN_STANDARD_TO_PROVIDER_TYPE[token.standard], + } as ProtocolTypedTransaction; + + if (dryRun) { + fundingLogger.info('DRY RUN: Would execute transfer with above parameters'); + return; + } + + const transactionHash = + await signer.sendAndConfirmTransaction(protocolTypedTx); + console.log( + `Account ${recipientAddress} funded at transaction ${transactionHash}`, + ); + + // Verify the transfer + const newBalance = await adapter.getBalance(fromAddress); + const recipientBalance = await adapter.getBalance(recipientAddress); + + fundingLogger.info( + { + transactionHash, + senderNewBalance: formatUnits(newBalance, decimals), + recipientBalance: formatUnits(recipientBalance, decimals), + symbol: token.symbol, + }, + 'Transfer completed successfully', + ); +} + +main().catch((err) => { + logger.error( + { + error: format(err), + }, + 'Error occurred in main', + ); + process.exit(1); +}); diff --git a/typescript/infra/scripts/funding/reclaim-vanguard-funds.sh b/typescript/infra/scripts/funding/reclaim-vanguard-funds.sh deleted file mode 100755 index 86a83ff7edb..00000000000 --- a/typescript/infra/scripts/funding/reclaim-vanguard-funds.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/bin/bash - -if [ -f "$HOME/.bashrc" ]; then - source "$HOME/.bashrc" -fi - -# Check if environment argument is provided -if [ "$1" != "mainnet3" ] && [ "$1" != "testnet4" ]; then - echo "Usage: $0 " - exit 1 -fi - -ENVIRONMENT=$1 - -# Set the deployer address and vanguard addresses based on environment -# Define the chains to check for each environment -if [ "$ENVIRONMENT" = "mainnet3" ]; then - DEPLOYER="0xa7eccdb9be08178f896c26b7bbd8c3d4e844d9ba" - CHAINS=("base" "bsc" "arbitrum" "optimism" "ethereum") - VANGUARD_ADDRESSES=("0xbe2e6b1ce045422a08a3662fffa3fc5f114efc3d" - "0xdbcd22e5223f5d0040398e66dbb525308f27c655" - "0x226b721316ea44aad50a10f4cc67fc30658ab4a9" - "0xcdd728647ecd9d75413c9b780de303b1d1eb12a5" - "0x5401627b69f317da9adf3d6e1e1214724ce49032" - "0x6fd953d1cbdf3a79663b4238898147a6cf36d459") -else - DEPLOYER="0xfaD1C94469700833717Fa8a3017278BC1cA8031C" - CHAINS=("basesepolia" "bsctestnet" "arbitrumsepolia" "optimismsepolia" "sepolia") - VANGUARD_ADDRESSES=("0x2c9209efcaff2778d945e18fb24174e16845dc62" - "0x939043d9db00f6ada1b742239beb7ddd5bf82096" - "0x45b58e4d46a89c003cc7126bd971eb3794a66aeb" - "0x1f4fdb150e8c9fda70687a2fd481e305af1e7f8e" - "0xe41b227e7aaaf7bbd1d60258de0dd76a11a0c3fc" - "0xb1d77c39166972c0873b6ae016d1a54ec3ce289b" - "0x59f4ee751c5ef680382bdf0bebfa92f278e17284" - "0xd4df81362263d4fbb9ccf6002b0a028b893701b0") -fi - -# Function to get AWS credentials from gcloud secrets -get_aws_credentials() { - local vanguard=$1 - - # Get AWS credentials from gcloud secrets - export AWS_ACCESS_KEY_ID=$(gcloud secrets versions access latest --secret="${vanguard}-${ENVIRONMENT}-relayer-aws-access-key-id") - export AWS_SECRET_ACCESS_KEY=$(gcloud secrets versions access latest --secret="${vanguard}-${ENVIRONMENT}-relayer-aws-secret-access-key") - export AWS_KMS_KEY_ID="alias/${vanguard}-${ENVIRONMENT}-key-relayer" -} - -# Function to check balance and send funds -check_and_send() { - local vanguard_index=$1 - local chain=$2 - local vanguard_address=${VANGUARD_ADDRESSES[$vanguard_index]} - - echo "Checking vanguard$vanguard_index ($vanguard_address) on $chain..." - - # Get the balance of the current wallet - balance=$(cast balance $vanguard_address --rpc-url $(rpc $ENVIRONMENT $chain)) - pretty_balance=$(cast fw $balance) - echo "Balance: $pretty_balance" - - # If pretty_balance is greater than 0, send funds to deployer - if [ $(echo "$pretty_balance > 0" | bc) -eq 1 ]; then - echo "Sending $pretty_balance funds from vanguard$vanguard_index ($vanguard_address) to $DEPLOYER on $chain..." - # Subtract 0.01 ETH (10000000000000000 wei) for sepolia, 0.001 ETH (1000000000000000 wei) for ethereum, 0.0001 ETH (100000000000000 wei) for other chains - if [ "$chain" = "sepolia" ]; then - adjusted_balance=$(echo "$balance - 10000000000000000" | bc) - elif [ "$chain" = "ethereum" ]; then - adjusted_balance=$(echo "$balance - 1000000000000000" | bc) - else - adjusted_balance=$(echo "$balance - 100000000000000" | bc) - fi - if [ $(echo "$adjusted_balance > 0" | bc) -eq 1 ]; then - cast send $DEPLOYER --value $adjusted_balance --rpc-url $(rpc $ENVIRONMENT $chain) --aws - else - echo "Insufficient balance after gas cost deduction." - fi - fi - echo "------------------------------------------------------------------------------------------------------------------------------------------------------" -} - -# Set the range based on environment -if [ "$ENVIRONMENT" = "mainnet3" ]; then - RANGE="1 2 3 4 5" -else - RANGE="0 1 2 3 4 5 6 7" -fi - -# Iterate through vanguards based on environment -for i in $RANGE; do - echo "######################################################################################################################################################" - echo "Processing vanguard$i..." - - # Get AWS credentials once per vanguard - get_aws_credentials "vanguard$i" - - # Check each chain - for chain in "${CHAINS[@]}"; do - check_and_send $i $chain - done -done - -echo "Funds reclamation process completed!" diff --git a/typescript/infra/scripts/generate-scraper-add-domain-sql.sh b/typescript/infra/scripts/generate-scraper-add-domain-sql.sh index ea06fafb30b..a9e22709f5a 100755 --- a/typescript/infra/scripts/generate-scraper-add-domain-sql.sh +++ b/typescript/infra/scripts/generate-scraper-add-domain-sql.sh @@ -1,7 +1,7 @@ #!/bin/bash # Example usage: -# ./generate-scraper-add-domain-sql.sh alfajores ancient8 arbitrum avalanche base blast bob bsc bsctestnet +# ./generate-scraper-add-domain-sql.sh ancient8 arbitrum avalanche base blast bob bsc bsctestnet # This will insert the specified domains into the scraper database. # Change directory to repo root diff --git a/typescript/infra/scripts/keys/get-timelocks.ts b/typescript/infra/scripts/keys/get-timelocks.ts index 4c59cb71e06..042e53312cf 100644 --- a/typescript/infra/scripts/keys/get-timelocks.ts +++ b/typescript/infra/scripts/keys/get-timelocks.ts @@ -1,15 +1,13 @@ -import { ChainMap, EvmTimelockDeployer } from '@hyperlane-xyz/sdk'; +import { EvmTimelockDeployer } from '@hyperlane-xyz/sdk'; import { - Address, LogFormat, LogLevel, configureRootLogger, rootLogger, } from '@hyperlane-xyz/utils'; -import { awIcasV2 } from '../../config/environments/mainnet3/governance/ica/aw2.js'; -import { regularIcasV2 } from '../../config/environments/mainnet3/governance/ica/regular2.js'; import { + getGovernanceIcas, getGovernanceSafes, getGovernanceTimelocks, } from '../../config/environments/mainnet3/governance/utils.js'; @@ -85,17 +83,7 @@ async function main() { const timelockDeployer = new EvmTimelockDeployer(multiProvider, true); - let governanceIcas: ChainMap
; - switch (governanceType) { - case GovernanceType.AbacusWorks: - governanceIcas = awIcasV2; - break; - case GovernanceType.Regular: - governanceIcas = regularIcasV2; - break; - default: - throw new Error(`Governance type ${governanceType} not supported.`); - } + const governanceIcas = getGovernanceIcas(governanceType); const governanceTimelocks = getGovernanceTimelocks(governanceType); diff --git a/typescript/infra/scripts/print-gas-prices.ts b/typescript/infra/scripts/print-gas-prices.ts index f63771211ef..f5f67d1b975 100644 --- a/typescript/infra/scripts/print-gas-prices.ts +++ b/typescript/infra/scripts/print-gas-prices.ts @@ -17,11 +17,35 @@ import { supportedChainNames as mainnet3SupportedChainNames } from '../config/en import { getRegistry as getTestnet4Registry } from '../config/environments/testnet4/chains.js'; import testnet4GasPrices from '../config/environments/testnet4/gasPrices.json' with { type: 'json' }; import { supportedChainNames as testnet4SupportedChainNames } from '../config/environments/testnet4/supportedChainNames.js'; +import { DeployEnvironment } from '../src/config/environment.js'; +import { + getSafeNumericValue, + updatePriceIfNeeded, +} from '../src/config/gas-oracle.js'; +import { writeJsonAtPath } from '../src/utils/utils.js'; + +import { getArgs, withWrite } from './agent-utils.js'; + +const gasPricesFilePath = (environment: DeployEnvironment) => { + return `config/environments/${environment}/gasPrices.json`; +}; + +// Helper function to extract numeric amount from GasPriceConfig +const getGasPriceAmount = (gasPrice: GasPriceConfig | undefined): number => { + return getSafeNumericValue(gasPrice?.amount, '0'); +}; -import { getArgs } from './agent-utils.js'; +// Helper function to create default gas price config +const createDefaultGasPrice = ( + chain: string, + decimals: number = 9, +): GasPriceConfig => ({ + amount: `PLEASE SET A GAS PRICE FOR ${chain.toUpperCase()}`, + decimals, +}); async function main() { - const { environment } = await getArgs().argv; + const { environment, write } = await withWrite(getArgs()).argv; const { registry, supportedChainNames, gasPrices } = environment === 'mainnet3' ? { @@ -42,29 +66,42 @@ async function main() { await Promise.all( supportedChainNames.map(async (chain) => { try { - return [ - chain, - await getGasPrice( - mpp, - chain, - gasPrices[chain as keyof typeof gasPrices], - ), - ]; + const currentGasPrice = gasPrices[ + chain as keyof typeof gasPrices + ] as GasPriceConfig; + const newGasPrice = await getGasPrice(mpp, chain, currentGasPrice); + + const currentAmount = getGasPriceAmount(currentGasPrice); + const newAmount = getGasPriceAmount(newGasPrice); + + const finalGasPrice = updatePriceIfNeeded( + newGasPrice, + currentGasPrice, + newAmount, + currentAmount, + ); + + return [chain, finalGasPrice]; } catch (error) { console.error(`Error getting gas price for ${chain}:`, error); return [ chain, - gasPrices[chain as keyof typeof gasPrices] || { - amount: '0', - decimals: 9, - }, + gasPrices[chain as keyof typeof gasPrices] || + createDefaultGasPrice(chain), ]; } }), ), ); - console.log(JSON.stringify(prices, null, 2)); + if (write) { + const outFile = gasPricesFilePath(environment); + console.log(`Writing gas prices to ${outFile}`); + writeJsonAtPath(outFile, prices); + } else { + console.log(JSON.stringify(prices, null, 2)); + } + process.exit(0); } @@ -96,26 +133,15 @@ async function getGasPrice( `Error getting gas price for cosmos chain ${chain}:`, error, ); - if (currentGasPrice) { - return currentGasPrice; - } else { - return { - amount: 'PLEASE SET A GAS PRICE FOR COSMOS CHAIN', - decimals: 1, - }; - } + return currentGasPrice || createDefaultGasPrice(chain, 1); } } + case ProtocolType.Radix: case ProtocolType.Sealevel: case ProtocolType.Starknet: // Return the gas price from the config if it exists, otherwise return some default // TODO get a reasonable value - return ( - currentGasPrice ?? { - amount: `PLEASE SET A GAS PRICE FOR ${chain.toUpperCase()}`, - decimals: 9, - } - ); + return currentGasPrice || createDefaultGasPrice(chain); default: throw new Error(`Unsupported protocol type: ${protocolType}`); } diff --git a/typescript/infra/scripts/print-token-prices.ts b/typescript/infra/scripts/print-token-prices.ts index 8ed77c3d26b..afda1573626 100644 --- a/typescript/infra/scripts/print-token-prices.ts +++ b/typescript/infra/scripts/print-token-prices.ts @@ -1,16 +1,24 @@ import chalk from 'chalk'; -import { ChainMetadata } from '@hyperlane-xyz/sdk'; +import { ChainMap, ChainMetadata } from '@hyperlane-xyz/sdk'; import { objMap, pick } from '@hyperlane-xyz/utils'; // Intentionally circumvent `{mainnet3,testnet4}/index.ts` and `getEnvironmentConfig({'mainnet3','testnet4'})` // to avoid circular dependencies. import { getRegistry as getMainnet3Registry } from '../config/environments/mainnet3/chains.js'; import { supportedChainNames as mainnet3SupportedChainNames } from '../config/environments/mainnet3/supportedChainNames.js'; +import mainnet3TokenPrices from '../config/environments/mainnet3/tokenPrices.json' with { type: 'json' }; import { getRegistry as getTestnet4Registry } from '../config/environments/testnet4/chains.js'; import { supportedChainNames as testnet4SupportedChainNames } from '../config/environments/testnet4/supportedChainNames.js'; +import testnet4TokenPrices from '../config/environments/testnet4/tokenPrices.json' with { type: 'json' }; +import { DeployEnvironment } from '../src/config/environment.js'; +import { + getSafeNumericValue, + shouldUpdatePrice, +} from '../src/config/gas-oracle.js'; +import { writeJsonAtPath } from '../src/utils/utils.js'; -import { getArgs } from './agent-utils.js'; +import { getArgs, withWrite } from './agent-utils.js'; const CURRENCY = 'usd'; @@ -20,8 +28,29 @@ const DEFAULT_PRICE = { test: '100', }; +const tokenPricesFilePath = (environment: DeployEnvironment) => { + return `config/environments/${environment}/tokenPrices.json`; +}; + +// Helper function to get new price with proper fallback logic +const getNewTokenPrice = ( + idData: any, + id: string, + environment: DeployEnvironment, +): number => { + if (!idData?.usd) { + console.log( + chalk.yellow( + `No ${CURRENCY} price for ${id}, using ${DEFAULT_PRICE[environment]} as a default`, + ), + ); + return Number(DEFAULT_PRICE[environment]); + } + return Number(idData.usd); +}; + async function main() { - const { environment } = await getArgs().argv; + const { environment, write } = await withWrite(getArgs()).argv; const { registry, supportedChainNames } = environment === 'mainnet3' @@ -56,32 +85,32 @@ async function main() { const idPrices = await resp.json(); - const prices = objMap(ids, (_, id) => { - const idData = idPrices[id]; + // Only update if the new price diff is greater than DIFF_THRESHOLD_PCT, otherwise keep the old price. + // Defensive: handle missing or malformed current price + const prevTokenPrices: ChainMap = + environment === 'mainnet3' ? mainnet3TokenPrices : testnet4TokenPrices; - if (!idData) { - console.warn( - chalk.yellow( - `No data for ${id}, using ${DEFAULT_PRICE[environment]} as a default`, - ), - ); - return DEFAULT_PRICE[environment]; - } - - const price = idData[CURRENCY]; - if (!price) { - console.warn( - chalk.yellow( - `No ${CURRENCY} price for ${id}, using ${DEFAULT_PRICE[environment]} as a default`, - ), - ); - return DEFAULT_PRICE[environment]; - } - - return price.toString(); + const prices = objMap(ids, (chain, id) => { + const idData = idPrices[id]; + const prevPrice = getSafeNumericValue( + prevTokenPrices[chain], + DEFAULT_PRICE[environment], + ); + const newPrice = getNewTokenPrice(idData, id, environment); + + return shouldUpdatePrice(newPrice, prevPrice) + ? newPrice.toString() + : prevPrice.toString(); }); - console.log(JSON.stringify(prices, null, 2)); + if (write) { + const outFile = tokenPricesFilePath(environment); + console.log(`Writing token prices to ${outFile}`); + writeJsonAtPath(outFile, prices); + } else { + console.log(JSON.stringify(prices, null, 2)); + } + process.exit(0); } diff --git a/typescript/infra/scripts/safes/combine-txs.ts b/typescript/infra/scripts/safes/combine-txs.ts index c6e284185d6..5ac0ea594aa 100644 --- a/typescript/infra/scripts/safes/combine-txs.ts +++ b/typescript/infra/scripts/safes/combine-txs.ts @@ -25,7 +25,22 @@ function readJSONFiles(directory: string): Record { for (const file of files) { if (path.extname(file) === '.json') { const filePath = path.join(directory, file); - const txs: TxFile = readJSONAtPath(filePath); + + let txs: TxFile; + + // If the filename contains 'timelock', expect an array and only parse the first object + if (file.includes('timelock')) { + const arr = readJSONAtPath(filePath); + if (!Array.isArray(arr) || arr.length === 0) { + throw new Error( + `Expected an array of objects in ${filePath} for TimelockController, but got: ${JSON.stringify(arr)}`, + ); + } + txs = arr[0]; + } else { + txs = readJSONAtPath(filePath); + } + const chainId = txs.chainId; if (!transactionsByChainId[chainId]) { transactionsByChainId[chainId] = []; diff --git a/typescript/infra/scripts/safes/create-safe.ts b/typescript/infra/scripts/safes/create-safe.ts index f21ae84f86f..b8f64a9cd45 100644 --- a/typescript/infra/scripts/safes/create-safe.ts +++ b/typescript/infra/scripts/safes/create-safe.ts @@ -11,6 +11,7 @@ import { Contexts } from '../../config/contexts.js'; import { getGovernanceSigners } from '../../config/environments/mainnet3/governance/utils.js'; import { withGovernanceType } from '../../src/governance.js'; import { Role } from '../../src/roles.js'; +import { setSignerFromPrivateKey } from '../../src/utils/safe.js'; import { getArgs, withChainRequired, @@ -29,10 +30,13 @@ async function main() { const multiProvider = await envConfig.getMultiProvider( Contexts.Hyperlane, Role.Deployer, - true, + false, // DYMENSION: changed to false to use public RPCs [chain], ); + // DYMENSION: Use private key from env var if provided + setSignerFromPrivateKey(multiProvider, [chain]); + const signer = multiProvider.getSigner(chain); const ethAdapter = new EthersAdapter({ ethers, diff --git a/typescript/infra/scripts/safes/get-pending-txs.ts b/typescript/infra/scripts/safes/get-pending-txs.ts index 89ce203eddb..46e5b362c1c 100644 --- a/typescript/infra/scripts/safes/get-pending-txs.ts +++ b/typescript/infra/scripts/safes/get-pending-txs.ts @@ -1,5 +1,6 @@ import { confirm } from '@inquirer/prompts'; import chalk from 'chalk'; +import { ethers } from 'ethers'; import yargs from 'yargs'; import { @@ -13,10 +14,12 @@ import { Contexts } from '../../config/contexts.js'; import { getGovernanceSafes } from '../../config/environments/mainnet3/governance/utils.js'; import { withGovernanceType } from '../../src/governance.js'; import { Role } from '../../src/roles.js'; +import { logTable } from '../../src/utils/log.js'; import { SafeTxStatus, executeTx, getPendingTxsForChains, + setSignerFromPrivateKey, } from '../../src/utils/safe.js'; import { withChains } from '../agent-utils.js'; import { getEnvironmentConfig } from '../core-utils.js'; @@ -47,10 +50,13 @@ async function main() { const multiProvider = await envConfig.getMultiProvider( Contexts.Hyperlane, Role.Deployer, - true, + false, // Dymension: changed to false chainsToCheck, ); + // DYMENSION: USE KEY IN ENV + setSignerFromPrivateKey(multiProvider, chainsToCheck); + const pendingTxs = await getPendingTxsForChains( chainsToCheck, multiProvider, @@ -60,8 +66,8 @@ async function main() { rootLogger.info(chalk.green('No pending transactions found!')); process.exit(0); } - // eslint-disable-next-line no-console - console.table(pendingTxs, [ + + logTable(pendingTxs, [ 'chain', 'nonce', 'submissionDate', diff --git a/typescript/infra/scripts/safes/governance/check-safe-signers.ts b/typescript/infra/scripts/safes/governance/check-safe-signers.ts index 6bfe14c67b9..ccbcb3ee4cc 100644 --- a/typescript/infra/scripts/safes/governance/check-safe-signers.ts +++ b/typescript/infra/scripts/safes/governance/check-safe-signers.ts @@ -1,4 +1,5 @@ import Safe from '@safe-global/protocol-kit'; +import { ethers } from 'ethers'; import yargs from 'yargs'; import { rootLogger } from '@hyperlane-xyz/utils'; @@ -10,7 +11,11 @@ import { } from '../../../config/environments/mainnet3/governance/utils.js'; import { GovernanceType, withGovernanceType } from '../../../src/governance.js'; import { Role } from '../../../src/roles.js'; -import { getOwnerChanges, getSafeAndService } from '../../../src/utils/safe.js'; +import { + getOwnerChanges, + getSafeAndService, + setSignerFromPrivateKey, +} from '../../../src/utils/safe.js'; import { getEnvironmentConfig } from '../../core-utils.js'; enum SafeConfigViolationType { @@ -41,10 +46,13 @@ async function main() { const multiProvider = await getEnvironmentConfig('mainnet3').getMultiProvider( Contexts.Hyperlane, Role.Deployer, - true, + false, // Dymension: changed to false Object.keys(safes), ); + // DYMENSION: USE KEY IN ENV + setSignerFromPrivateKey(multiProvider, Object.keys(safes)); + const chainViolations = await Promise.all( Object.entries(safes).map(async ([chain, safeAddress]) => { let safeSdk: Safe.default; diff --git a/typescript/infra/scripts/safes/governance/update-signers.ts b/typescript/infra/scripts/safes/governance/update-signers.ts index d51025d0af7..07aeb5184fc 100644 --- a/typescript/infra/scripts/safes/governance/update-signers.ts +++ b/typescript/infra/scripts/safes/governance/update-signers.ts @@ -1,4 +1,5 @@ import Safe from '@safe-global/protocol-kit'; +import { ethers } from 'ethers'; import yargs from 'yargs'; import { ChainName } from '@hyperlane-xyz/sdk'; @@ -13,7 +14,11 @@ import { AnnotatedCallData } from '../../../src/govern/HyperlaneAppGovernor.js'; import { SafeMultiSend } from '../../../src/govern/multisend.js'; import { GovernanceType, withGovernanceType } from '../../../src/governance.js'; import { Role } from '../../../src/roles.js'; -import { getSafeAndService, updateSafeOwner } from '../../../src/utils/safe.js'; +import { + getSafeAndService, + setSignerFromPrivateKey, + updateSafeOwner, +} from '../../../src/utils/safe.js'; import { withPropose } from '../../agent-utils.js'; import { getEnvironmentConfig } from '../../core-utils.js'; @@ -28,10 +33,13 @@ async function main() { const multiProvider = await envConfig.getMultiProvider( Contexts.Hyperlane, Role.Deployer, - true, + false, // Dymension: changed to false Object.keys(safes), ); + // DYMENSION: USE KEY IN ENV + setSignerFromPrivateKey(multiProvider, Object.keys(safes)); + for (const [chain, safeAddress] of Object.entries(safes)) { let safeSdk: Safe.default; try { diff --git a/typescript/infra/scripts/safes/parse-txs.ts b/typescript/infra/scripts/safes/parse-txs.ts index 8fd60248ffa..7c1a0a86777 100644 --- a/typescript/infra/scripts/safes/parse-txs.ts +++ b/typescript/infra/scripts/safes/parse-txs.ts @@ -1,5 +1,5 @@ import chalk from 'chalk'; -import { BigNumber } from 'ethers'; +import { BigNumber, ethers } from 'ethers'; import yargs from 'yargs'; import { AnnotatedEV5Transaction } from '@hyperlane-xyz/sdk'; @@ -8,14 +8,21 @@ import { LogLevel, configureRootLogger, rootLogger, - stringifyObject, } from '@hyperlane-xyz/utils'; import { getGovernanceSafes } from '../../config/environments/mainnet3/governance/utils.js'; import { withGovernanceType } from '../../src/governance.js'; -import { GovernTransactionReader } from '../../src/tx/govern-transaction-reader.js'; -import { getPendingTxsForChains, getSafeTx } from '../../src/utils/safe.js'; -import { writeYamlAtPath } from '../../src/utils/utils.js'; +import { + GovernTransaction, + GovernTransactionReader, +} from '../../src/tx/govern-transaction-reader.js'; +import { processGovernorReaderResult } from '../../src/tx/utils.js'; +import { logTable } from '../../src/utils/log.js'; +import { + getPendingTxsForChains, + getSafeTx, + setSignerFromPrivateKey, +} from '../../src/utils/safe.js'; import { withChains } from '../agent-utils.js'; import { getEnvironmentConfig } from '../core-utils.js'; @@ -29,11 +36,18 @@ async function main() { // Get the multiprovider for the environment const config = getEnvironmentConfig(environment); - const multiProvider = await config.getMultiProvider(); + const multiProvider = await config.getMultiProvider( + undefined, + undefined, + false, // Dymension: changed to false + ); // Get the relevant set of governance safes and icas const safes = getGovernanceSafes(governanceType); + // DYMENSION: USE KEY IN ENV + setSignerFromPrivateKey(multiProvider, Object.keys(safes)); + // Initialize the transaction reader for the given governance type const reader = await GovernTransactionReader.create( environment, @@ -50,8 +64,8 @@ async function main() { rootLogger.info(chalk.green('No pending transactions found!')); process.exit(0); } - // eslint-disable-next-line no-console - console.table(pendingTxs, [ + + logTable(pendingTxs, [ 'chain', 'nonce', 'submissionDate', @@ -63,52 +77,43 @@ async function main() { ]); const chainResultEntries = await Promise.all( - pendingTxs.map(async ({ chain, nonce, fullTxHash }) => { - rootLogger.info( - chalk.gray.italic(`Reading tx ${fullTxHash} on ${chain}`), - ); - const safeTx = await getSafeTx(chain, multiProvider, fullTxHash); - const tx: AnnotatedEV5Transaction = { - to: safeTx.to, - data: safeTx.data, - value: BigNumber.from(safeTx.value), - }; - - try { - const results = await reader.read(chain, tx); + pendingTxs.map( + async ({ + chain, + nonce, + fullTxHash, + }): Promise<[string, GovernTransaction]> => { rootLogger.info( - chalk.blue(`Finished reading tx ${fullTxHash} on ${chain}`), - ); - return [`${chain}-${nonce}-${fullTxHash}`, results]; - } catch (err) { - rootLogger.error( - chalk.red('Error reading transaction', err, chain, tx), + chalk.gray.italic(`Reading tx ${fullTxHash} on ${chain}`), ); - process.exit(1); - } - }), - ); - - if (reader.errors.length) { - rootLogger.error( - chalk.red('❌❌❌❌❌ Encountered fatal errors ❌❌❌❌❌'), - ); - rootLogger.info(stringifyObject(reader.errors, 'yaml', 2)); - rootLogger.error( - chalk.red('❌❌❌❌❌ Encountered fatal errors ❌❌❌❌❌'), - ); - } else { - rootLogger.info(chalk.green('✅✅✅✅✅ No fatal errors ✅✅✅✅✅')); - } + const safeTx = await getSafeTx(chain, multiProvider, fullTxHash); + const tx: AnnotatedEV5Transaction = { + to: safeTx.to, + data: safeTx.data, + value: BigNumber.from(safeTx.value), + }; - const chainResults = Object.fromEntries(chainResultEntries); - const resultsPath = `safe-tx-results-${Date.now()}.yaml`; - writeYamlAtPath(resultsPath, chainResults); - rootLogger.info(`Results written to ${resultsPath}`); + try { + const results = await reader.read(chain, tx); + rootLogger.info( + chalk.blue(`Finished reading tx ${fullTxHash} on ${chain}`), + ); + return [`${chain}-${nonce}-${fullTxHash}`, results]; + } catch (err) { + rootLogger.error( + chalk.red('Error reading transaction', err, chain, tx), + ); + process.exit(1); + } + }, + ), + ); - if (reader.errors.length) { - process.exit(1); - } + processGovernorReaderResult( + chainResultEntries, + reader.errors, + 'safe-tx-parse-results', + ); } main().catch((err) => { diff --git a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts index 8c889e7806c..580f26c4724 100644 --- a/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts +++ b/typescript/infra/scripts/sealevel-helpers/print-gas-oracles.ts @@ -101,14 +101,15 @@ function getChainConnections( if (environment === 'mainnet3') { // All the mainnet3 warp route chains connectedChains = [ - // For the Rivalz team building out their own warp route - ['solanamainnet', 'rivalz'], ['solanamainnet', 'everclear'], ['solanamainnet', 'infinityvmmainnet'], ['solanamainnet', 'sophon'], ['solanamainnet', 'abstract'], ['solanamainnet', 'apechain'], ['solanamainnet', 'subtensor'], + ['solanamainnet', 'pulsechain'], + ['solanamainnet', 'electroneum'], + ['solanamainnet', 'galactica'], // For Starknet / Paradex ['solanamainnet', 'starknet'], ['solanamainnet', 'paradex'], @@ -126,6 +127,9 @@ function getChainConnections( // for solaxy routes ['solaxy', 'solanamainnet'], ['solaxy', 'ethereum'], + // for celestia svm routes + ['celestia', 'solanamainnet'], + ['celestia', 'eclipsemainnet'], // All warp routes ...Object.values(WarpRouteIds).map(getWarpChains), ]; diff --git a/typescript/infra/scripts/timelocks/cancel-pending-txs.ts b/typescript/infra/scripts/timelocks/cancel-pending-txs.ts new file mode 100644 index 00000000000..7ee554d5817 --- /dev/null +++ b/typescript/infra/scripts/timelocks/cancel-pending-txs.ts @@ -0,0 +1,50 @@ +import yargs from 'yargs'; + +import { + LogFormat, + LogLevel, + configureRootLogger, + rootLogger, +} from '@hyperlane-xyz/utils'; + +import { Contexts } from '../../config/contexts.js'; +import { getGovernanceTimelocks } from '../../config/environments/mainnet3/governance/utils.js'; +import { withGovernanceType } from '../../src/governance.js'; +import { Role } from '../../src/roles.js'; +import { cancelAllTimelockTxs } from '../../src/utils/timelock.js'; +import { withChains } from '../agent-utils.js'; +import { getEnvironmentConfig } from '../core-utils.js'; + +async function main() { + configureRootLogger(LogFormat.Pretty, LogLevel.Info); + + const { chains, governanceType } = await withGovernanceType( + withChains(yargs(process.argv.slice(2))), + ).argv; + + if (!chains || chains.length === 0) { + rootLogger.error('No chains provided'); + process.exit(1); + } + + const envConfig = getEnvironmentConfig('mainnet3'); + const multiProvider = await envConfig.getMultiProvider( + Contexts.Hyperlane, + Role.Deployer, + true, + chains, + ); + + await cancelAllTimelockTxs( + chains, + getGovernanceTimelocks(governanceType), + multiProvider, + ); +} + +main() + .then() + .catch((e) => { + rootLogger.error(e); + process.exit(1); + }); diff --git a/typescript/infra/scripts/timelocks/cancel-tx.ts b/typescript/infra/scripts/timelocks/cancel-tx.ts new file mode 100644 index 00000000000..3a4cf68c5ee --- /dev/null +++ b/typescript/infra/scripts/timelocks/cancel-tx.ts @@ -0,0 +1,72 @@ +import yargs from 'yargs'; + +import { + LogFormat, + LogLevel, + configureRootLogger, + rootLogger, +} from '@hyperlane-xyz/utils'; + +import { Contexts } from '../../config/contexts.js'; +import { getGovernanceTimelocks } from '../../config/environments/mainnet3/governance/utils.js'; +import { withGovernanceType } from '../../src/governance.js'; +import { Role } from '../../src/roles.js'; +import { deleteTimelockTx } from '../../src/utils/timelock.js'; +import { withChain } from '../agent-utils.js'; +import { getEnvironmentConfig } from '../core-utils.js'; + +async function main() { + configureRootLogger(LogFormat.Pretty, LogLevel.Info); + + const { chain, tx, governanceType } = await withGovernanceType( + withChain( + yargs(process.argv.slice(2)).option('tx', { + type: 'string', + description: 'Transaction hash to delete', + demandOption: true, + }), + ), + ).argv; + + if (!chain) { + rootLogger.error('No chain provided'); + process.exit(1); + } + + if (!tx) { + rootLogger.error('No transaction hash provided'); + process.exit(1); + } + + const envConfig = getEnvironmentConfig('mainnet3'); + const multiProvider = await envConfig.getMultiProvider( + Contexts.Hyperlane, + Role.Deployer, + true, + [chain], + ); + + const timelocks = getGovernanceTimelocks(governanceType); + + const currentChainTimelock = timelocks[chain]; + if (!currentChainTimelock) { + rootLogger.error(`No timelock contract found for chain ${chain}`); + process.exit(1); + } + + try { + await deleteTimelockTx(chain, currentChainTimelock, tx, multiProvider); + } catch (error) { + rootLogger.error( + `Error deleting timelock operation "${tx}" on "${chain}":`, + error, + ); + } +} + +main() + .then() + .catch((e) => { + rootLogger.error(e); + process.exit(1); + }); diff --git a/typescript/infra/scripts/timelocks/get-pending-txs.ts b/typescript/infra/scripts/timelocks/get-pending-txs.ts new file mode 100644 index 00000000000..5ad6655f87a --- /dev/null +++ b/typescript/infra/scripts/timelocks/get-pending-txs.ts @@ -0,0 +1,122 @@ +import { confirm } from '@inquirer/prompts'; +import chalk from 'chalk'; +import yargs from 'yargs'; + +import { + LogFormat, + LogLevel, + configureRootLogger, + rootLogger, +} from '@hyperlane-xyz/utils'; + +import { Contexts } from '../../config/contexts.js'; +import { getGovernanceTimelocks } from '../../config/environments/mainnet3/governance/utils.js'; +import { withGovernanceType } from '../../src/governance.js'; +import { Role } from '../../src/roles.js'; +import { logTable } from '../../src/utils/log.js'; +import { + TimelockOperationStatus, + getPendingTimelockTxs, +} from '../../src/utils/timelock.js'; +import { withChains } from '../agent-utils.js'; +import { getEnvironmentConfig } from '../core-utils.js'; + +async function main() { + configureRootLogger(LogFormat.Pretty, LogLevel.Info); + + const { chains, governanceType } = await withGovernanceType( + withChains(yargs(process.argv.slice(2))), + ).argv; + + const timelocks = getGovernanceTimelocks(governanceType); + const timelockChains = Object.keys(timelocks); + + const chainsToCheck = chains || timelockChains; + if (chainsToCheck.length === 0) { + rootLogger.error('No chains provided'); + process.exit(1); + } + + const envConfig = getEnvironmentConfig('mainnet3'); + const multiProvider = await envConfig.getMultiProvider( + Contexts.Hyperlane, + Role.Deployer, + true, + chainsToCheck, + ); + + const pendingTxs = await getPendingTimelockTxs( + chainsToCheck, + multiProvider, + timelocks, + ); + if (pendingTxs.length === 0) { + rootLogger.info(chalk.green('No pending transactions found!')); + process.exit(0); + } + + logTable(pendingTxs, [ + 'chain', + 'id', + 'predecessorId', + 'salt', + 'status', + 'canSignerExecute', + ]); + + const executableTxs = pendingTxs.filter( + (tx) => + tx.status === TimelockOperationStatus.READY_TO_EXECUTE && + tx.canSignerExecute, + ); + if (executableTxs.length === 0) { + rootLogger.info(chalk.green('No transactions to execute!')); + process.exit(0); + } + + const shouldExecute = await confirm({ + message: 'Execute transactions?', + default: false, + }); + + if (!shouldExecute) { + rootLogger.info( + chalk.blue( + `${executableTxs.length} transactions available for execution`, + ), + ); + process.exit(0); + } + + rootLogger.info(chalk.blueBright('Executing transactions...')); + for (const tx of executableTxs) { + const confirmExecuteTx = await confirm({ + message: `Execute transaction ${tx.id} on chain ${tx.chain}?`, + default: false, + }); + + if (!confirmExecuteTx) { + continue; + } + + rootLogger.info(`Executing transaction ${tx.id} on chain ${tx.chain}`); + try { + await multiProvider.sendTransaction(tx.chain, { + to: tx.timelockAddress, + data: tx.executeTransactionData, + }); + } catch (error) { + rootLogger.error(chalk.red(`Error executing transaction: ${error}`)); + return; + } + } + + process.exit(0); +} + +main() + .then() + .catch((e) => { + rootLogger.error(e); + process.exit(1); + }); diff --git a/typescript/infra/scripts/timelocks/parse-txs.ts b/typescript/infra/scripts/timelocks/parse-txs.ts new file mode 100644 index 00000000000..0edde052d28 --- /dev/null +++ b/typescript/infra/scripts/timelocks/parse-txs.ts @@ -0,0 +1,112 @@ +import chalk from 'chalk'; +import yargs from 'yargs'; + +import { AnnotatedEV5Transaction } from '@hyperlane-xyz/sdk'; +import { + LogFormat, + LogLevel, + configureRootLogger, + rootLogger, + stringifyObject, +} from '@hyperlane-xyz/utils'; + +import { getGovernanceTimelocks } from '../../config/environments/mainnet3/governance/utils.js'; +import { withGovernanceType } from '../../src/governance.js'; +import { + GovernTransaction, + GovernTransactionReader, +} from '../../src/tx/govern-transaction-reader.js'; +import { processGovernorReaderResult } from '../../src/tx/utils.js'; +import { logTable } from '../../src/utils/log.js'; +import { getPendingTimelockTxs } from '../../src/utils/timelock.js'; +import { writeYamlAtPath } from '../../src/utils/utils.js'; +import { withChains } from '../agent-utils.js'; +import { getEnvironmentConfig } from '../core-utils.js'; + +const environment = 'mainnet3'; + +async function main() { + configureRootLogger(LogFormat.Pretty, LogLevel.Info); + const { chains, governanceType } = await withGovernanceType( + withChains(yargs(process.argv.slice(2))), + ).argv; + + const timelocks = getGovernanceTimelocks(governanceType); + const timelockChains = Object.keys(timelocks); + + const chainsToCheck = chains || timelockChains; + if (chainsToCheck.length === 0) { + rootLogger.error('No chains provided'); + process.exit(1); + } + + // Get the multiprovider for the environment + const config = getEnvironmentConfig(environment); + const multiProvider = await config.getMultiProvider(); + + // Initialize the transaction reader for the given governance type + const reader = await GovernTransactionReader.create( + environment, + governanceType, + ); + + // Get the pending transactions for the relevant chains, for the chosen governance type + const pendingTxs = await getPendingTimelockTxs( + chainsToCheck, + multiProvider, + timelocks, + ); + if (pendingTxs.length === 0) { + rootLogger.info(chalk.green('No pending transactions found!')); + process.exit(0); + } + + logTable(pendingTxs, [ + 'chain', + 'id', + 'predecessorId', + 'salt', + 'status', + 'canSignerExecute', + ]); + + const chainResultEntries = await Promise.all( + pendingTxs.map( + async ({ + chain, + timelockAddress, + executeTransactionData, + id, + salt, + }): Promise<[string, GovernTransaction]> => { + rootLogger.info(chalk.gray.italic(`Reading tx ${id} on ${chain}`)); + const tx: AnnotatedEV5Transaction = { + to: timelockAddress, + data: executeTransactionData, + }; + + try { + const results = await reader.read(chain, tx); + rootLogger.info(chalk.blue(`Finished reading tx ${id} on ${chain}`)); + return [`${chain}-${salt}-${id}`, results]; + } catch (err) { + rootLogger.error( + chalk.red('Error reading transaction', err, chain, tx), + ); + process.exit(1); + } + }, + ), + ); + + processGovernorReaderResult( + chainResultEntries, + reader.errors, + 'timelock-tx-parse-result', + ); +} + +main().catch((err) => { + rootLogger.error('Error:', err); + process.exit(1); +}); diff --git a/typescript/infra/scripts/validators/announce-validators.ts b/typescript/infra/scripts/validators/announce-validators.ts index e7e75278f4b..b26e6b7d16a 100644 --- a/typescript/infra/scripts/validators/announce-validators.ts +++ b/typescript/infra/scripts/validators/announce-validators.ts @@ -73,11 +73,6 @@ async function main() { await Promise.all( Object.entries(agentConfig.validators.chains) .filter(([validatorChain, _]) => { - // Ensure we skip lumia, as we don't have the addresses in registry. - if (validatorChain === 'lumia') { - return false; - } - // If a chain arg was specified, filter to only that chain if (!!chain && chain !== validatorChain) { return false; diff --git a/typescript/infra/scripts/validators/print-latest-checkpoints.ts b/typescript/infra/scripts/validators/print-latest-checkpoints.ts index f0e54992582..5a1a078b690 100644 --- a/typescript/infra/scripts/validators/print-latest-checkpoints.ts +++ b/typescript/infra/scripts/validators/print-latest-checkpoints.ts @@ -26,10 +26,14 @@ async function main() { environment, chains, all = false, + validator, } = await withChainsRequired(getArgs()) .describe('all', 'all validators, including non-default ISM') .boolean('all') - .alias('a', 'all').argv; + .alias('a', 'all') + .describe('validator', 'specific validator address to check') + .string('validator') + .alias('v', 'validator').argv; if (chains.length === 0) { rootLogger.error('Must provide at least one chain'); @@ -74,11 +78,6 @@ async function main() { await Promise.all( targetNetworks.map(async (chain) => { - if (chain === 'lumia') { - rootLogger.info('Skipping deprecated chain: lumia'); - return; - } - const validatorAnnounce = ValidatorAnnounce__factory.connect( chainAddresses[chain]['validatorAnnounce'], multiProvider.getProvider(chain), @@ -103,13 +102,24 @@ async function main() { // For each validator on this chain for (let i = 0; i < announcedValidators.length; i++) { - const validator = announcedValidators[i]; + const validatorAddress = announcedValidators[i]; const location = storageLocations[i][storageLocations[i].length - 1]; - // Skip validators not in default ISM unless --all flag is set + // If a specific validator address is provided, only process that one + if (validator && !eqAddress(validatorAddress, validator)) { + continue; + } + + // Skip validators not in default ISM unless --all flag is set or specific validator is provided // If it's not a core chain, then we'll want to check all announced validators - const isDefaultIsmValidator = findDefaultValidatorAlias(validator); - if (defaultIsmValidators.length > 0 && !isDefaultIsmValidator && !all) { + const isDefaultIsmValidator = + findDefaultValidatorAlias(validatorAddress); + if ( + defaultIsmValidators.length > 0 && + !isDefaultIsmValidator && + !all && + !validator + ) { continue; } @@ -125,8 +135,8 @@ async function main() { if (!validators[chain]) { validators[chain] = {}; } - const alias = findDefaultValidatorAlias(validator); - validators[chain][validator] = { + const alias = findDefaultValidatorAlias(validatorAddress); + validators[chain][validatorAddress] = { alias, default: alias ? '✅' : '', latest: latestCheckpoint, @@ -139,9 +149,9 @@ async function main() { // get metadata. const logLevel = isDefaultIsmValidator ? 'error' : 'debug'; rootLogger[logLevel]( - `Error getting metadata for ${validator} on chain ${chain}: ${error}`, + `Error getting metadata for ${validatorAddress} on chain ${chain}: ${error}`, ); - validators[chain][validator] = { + validators[chain][validatorAddress] = { alias: '', default: '', latest: -1, diff --git a/typescript/infra/scripts/warp-routes/monitor/monitor-warp-route-balances.ts b/typescript/infra/scripts/warp-routes/monitor/monitor-warp-route-balances.ts index 62d05f6f556..f56200084fb 100644 --- a/typescript/infra/scripts/warp-routes/monitor/monitor-warp-route-balances.ts +++ b/typescript/infra/scripts/warp-routes/monitor/monitor-warp-route-balances.ts @@ -26,8 +26,7 @@ import { } from '@hyperlane-xyz/utils'; import { getWarpCoreConfig } from '../../../config/registry.js'; -import { DeployEnvironment } from '../../../src/config/environment.js'; -import { fetchGCPSecret } from '../../../src/utils/gcloud.js'; +import { getCoinGeckoApiKey } from '../../../src/coingecko/utils.js'; import { startMetricsServer } from '../../../src/utils/metrics.js'; import { getArgs, @@ -108,7 +107,7 @@ async function pollAndUpdateWarpRouteMetrics( ) { const tokenPriceGetter = new CoinGeckoTokenPriceGetter({ chainMetadata, - apiKey: await getCoinGeckoApiKey(), + apiKey: await getCoinGeckoApiKey(logger), }); while (true) { @@ -569,24 +568,6 @@ async function getCoingeckoPrice( return prices[0]; } -async function getCoinGeckoApiKey(): Promise { - const environment: DeployEnvironment = 'mainnet3'; - let apiKey: string | undefined; - try { - apiKey = (await fetchGCPSecret( - `${environment}-coingecko-api-key`, - false, - )) as string; - } catch (err) { - logger.error( - err, - 'Failed to fetch CoinGecko API key, proceeding with public tier', - ); - } - - return apiKey; -} - function getWarpRouteCollateralTokenSymbol(warpCore: WarpCore): string { // We need to have a deterministic way to determine the symbol of the warp route // as its used to identify the warp route in metrics. This method should support routes where: diff --git a/typescript/infra/scripts/warp-routes/ousdt/prune-warp-config.ts b/typescript/infra/scripts/warp-routes/ousdt/prune-warp-config.ts index 95adb374300..221a7c80017 100644 --- a/typescript/infra/scripts/warp-routes/ousdt/prune-warp-config.ts +++ b/typescript/infra/scripts/warp-routes/ousdt/prune-warp-config.ts @@ -4,24 +4,30 @@ import yargs from 'yargs'; import { TokenStandard, WarpCoreConfig, + getTokenConnectionId, parseTokenConnectionId, } from '@hyperlane-xyz/sdk'; +import { ProtocolType } from '@hyperlane-xyz/utils'; import { WarpRouteIds } from '../../../config/environments/mainnet3/warp/warpIds.js'; import { getRegistry } from '../../../config/registry.js'; const collateralChains = ['celo', 'ethereum']; -const chainsToPrune = [ - 'worldchain', - 'ronin', - 'bitlayer', - 'linea', - 'mantle', - 'sonic', -]; +const chainsToPrune = ['worldchain', 'linea']; function pruneProdConfig({ tokens, options }: WarpCoreConfig): WarpCoreConfig { const prunedTokens = tokens.map((token) => { + // first force-add all connections and then prune the ones we don't want + token.connections = tokens + .filter((t) => t.chainName !== token.chainName) + .map((t) => ({ + token: getTokenConnectionId( + ProtocolType.Ethereum, + t.chainName, + t.addressOrDenom!, + ), + })); + if (chainsToPrune.includes(token.chainName)) { return { ...token, connections: undefined }; } @@ -61,7 +67,7 @@ async function main() { if (collateralChains.includes(token.chainName)) { token.coinGeckoId = 'tether'; token.name = 'USDT'; - token.symbol = 'USD₮'; + token.symbol = token.chainName === 'ethereum' ? 'USDT' : 'USD₮'; token.standard = TokenStandard.EvmHypVSXERC20Lockbox; token.logoURI = isStaging ? undefined diff --git a/typescript/infra/scripts/warp-routes/ousdt/update-submitter-strategy.ts b/typescript/infra/scripts/warp-routes/ousdt/update-submitter-strategy.ts index a67ac49ceec..0aefdb59f71 100644 --- a/typescript/infra/scripts/warp-routes/ousdt/update-submitter-strategy.ts +++ b/typescript/infra/scripts/warp-routes/ousdt/update-submitter-strategy.ts @@ -50,54 +50,59 @@ async function main() { const chainSubmissionStrategy: ChainSubmissionStrategy = {}; for (const [chain, config] of Object.entries(parsed.data)) { const { ownerType } = await determineGovernanceType(chain, config.owner); - if (ownerType === Owner.SAFE) { - switch (chain) { - case 'metis': - case 'soneium': - case 'superseed': - case 'ethereum': - chainSubmissionStrategy[chain] = { - submitter: { - chain, - type: TxSubmitterType.GNOSIS_TX_BUILDER, - version: '1.0', - safeAddress: config.owner, - }, - }; - break; - default: - chainSubmissionStrategy[chain] = { - submitter: { - chain, - type: TxSubmitterType.GNOSIS_SAFE, - safeAddress: config.owner, - }, - }; - break; - } - } - // New ICA submitter config from https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/4980 - else if (ownerType === Owner.ICA) { - chainSubmissionStrategy[chain] = { - submitter: { - chain: ICA_OWNER_CHAIN, - type: 'interchainAccount', - destinationChain: chain, - internalSubmitter: { + + const ownerChainSubmitter = { + chain: ICA_OWNER_CHAIN, + type: TxSubmitterType.GNOSIS_TX_BUILDER, + version: '1.0', + safeAddress: ICA_OWNER_SAFE, + } as const; + + const icaSubmitter = { + chain: ICA_OWNER_CHAIN, + type: TxSubmitterType.INTERCHAIN_ACCOUNT, + owner: ICA_OWNER_SAFE, + destinationChain: chain, + internalSubmitter: ownerChainSubmitter, + } as const; + + switch (ownerType) { + case Owner.SAFE: + chainSubmissionStrategy[chain] = { + submitter: { + chain, type: TxSubmitterType.GNOSIS_TX_BUILDER, version: '1.0', - safeAddress: ICA_OWNER_SAFE, + safeAddress: config.owner, + }, + }; + break; + case Owner.ICA: + chainSubmissionStrategy[chain] = { + submitter: icaSubmitter, + }; + break; + case Owner.TIMELOCK: + // By default timelock go through ICAs, apart from the owner chain. + // On the owner chain, the safe controls the timelock without an ICA wrapper. + chainSubmissionStrategy[chain] = { + submitter: { + chain, + type: TxSubmitterType.TIMELOCK_CONTROLLER, + timelockAddress: config.owner, + proposerSubmitter: + chain === ICA_OWNER_CHAIN ? ownerChainSubmitter : icaSubmitter, + }, + }; + break; + default: + chainSubmissionStrategy[chain] = { + submitter: { + chain, + type: TxSubmitterType.JSON_RPC, }, - owner: config.owner, - } as any, - }; - } else { - chainSubmissionStrategy[chain] = { - submitter: { - chain, - type: TxSubmitterType.JSON_RPC, - }, - }; + }; + break; } } diff --git a/typescript/infra/scripts/xerc20/xerc20standard-add-bridge.ts b/typescript/infra/scripts/xerc20/xerc20standard-add-bridge.ts new file mode 100644 index 00000000000..7be398b354a --- /dev/null +++ b/typescript/infra/scripts/xerc20/xerc20standard-add-bridge.ts @@ -0,0 +1,84 @@ +import chalk from 'chalk'; + +import { + LogFormat, + LogLevel, + configureRootLogger, + rootLogger, +} from '@hyperlane-xyz/utils'; + +import { + addBridgeToChainForXERC20Standard, + deriveStandardBridgesConfig, + getAndValidateBridgesToUpdate, + getWarpConfigsAndArtifacts, +} from '../../src/xerc20/utils.js'; +import { + getArgs, + withChains, + withDryRun, + withWarpRouteIdRequired, +} from '../agent-utils.js'; +import { getEnvironmentConfig } from '../core-utils.js'; + +// Designed to work with Standard variant of xERC20: https://github.com/hyperlane-xyz/xERC20 +async function main() { + configureRootLogger(LogFormat.Pretty, LogLevel.Info); + const { environment, warpRouteId, chains, dryRun } = await withChains( + withWarpRouteIdRequired(withDryRun(getArgs())), + ).argv; + + const { warpDeployConfig, warpCoreConfig } = + getWarpConfigsAndArtifacts(warpRouteId); + + const envConfig = getEnvironmentConfig(environment); + const multiProtocolProvider = await envConfig.getMultiProtocolProvider(); + const envMultiProvider = await envConfig.getMultiProvider(); + + const bridgesConfig = await deriveStandardBridgesConfig( + chains, + warpDeployConfig, + warpCoreConfig, + envMultiProvider, + ); + + // if chains are provided, validate that they are in the warp config + // throw an error if they are not + const bridgesToUpdate = getAndValidateBridgesToUpdate(chains, bridgesConfig); + + const erroredChains: string[] = []; + + for (const bridgeConfig of bridgesToUpdate) { + try { + await addBridgeToChainForXERC20Standard({ + chain: bridgeConfig.chain, + bridgeConfig, + multiProtocolProvider, + envMultiProvider, + dryRun, + }); + } catch (e) { + rootLogger.error( + chalk.red( + `Error occurred while adding bridge to chain ${bridgeConfig.chain}: ${e}`, + ), + ); + erroredChains.push(bridgeConfig.chain); + } + } + + if (erroredChains.length > 0) { + rootLogger.error( + chalk.red( + `Errors occurred on the following chains: ${erroredChains.join(', ')}`, + ), + ); + } +} + +main() + .then() + .catch((e) => { + rootLogger.error(e); + process.exit(1); + }); diff --git a/typescript/infra/scripts/xerc20/xerc20vs-add-bridge.ts b/typescript/infra/scripts/xerc20/xerc20vs-add-bridge.ts index 7d03fdcfcd0..749a42d6e9f 100644 --- a/typescript/infra/scripts/xerc20/xerc20vs-add-bridge.ts +++ b/typescript/infra/scripts/xerc20/xerc20vs-add-bridge.ts @@ -8,7 +8,7 @@ import { } from '@hyperlane-xyz/utils'; import { - addBridgeToChain, + addBridgeToChainXERC20VS, deriveBridgesConfig, getAndValidateBridgesToUpdate, getWarpConfigsAndArtifacts, @@ -48,7 +48,7 @@ async function main() { for (const bridgeConfig of bridgesToUpdate) { try { - await addBridgeToChain({ + await addBridgeToChainXERC20VS({ chain: bridgeConfig.chain, bridgeConfig, multiProtocolProvider, diff --git a/typescript/infra/src/agents/agent.ts b/typescript/infra/src/agents/agent.ts index c7a25795843..3c524200b1c 100644 --- a/typescript/infra/src/agents/agent.ts +++ b/typescript/infra/src/agents/agent.ts @@ -23,6 +23,9 @@ function identifier( if (!chainName) throw Error('Expected chainName for validator key'); if (index === undefined) throw Error('Expected index for validator key'); return `${prefix}${chainName}-${role}-${index}`; + // In the case of deployer keys not all the keys have the chainName included in their identifier + case Role.Deployer: + return `${context}-${environment}-${chainName ? chainName + '-' : ''}${isKey ? 'key-' : ''}${role}`; default: return `${prefix}${role}`; } diff --git a/typescript/infra/src/agents/gcp.ts b/typescript/infra/src/agents/gcp.ts index 3f63b783852..e633a3beeb7 100644 --- a/typescript/infra/src/agents/gcp.ts +++ b/typescript/infra/src/agents/gcp.ts @@ -5,7 +5,12 @@ import { Logger } from 'pino'; import { Provider as ZkProvider, Wallet as ZkWallet } from 'zksync-ethers'; import { ChainName } from '@hyperlane-xyz/sdk'; -import { ProtocolType, rootLogger, strip0x } from '@hyperlane-xyz/utils'; +import { + HexString, + ProtocolType, + rootLogger, + strip0x, +} from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts.js'; import { getChain } from '../../config/registry.js'; @@ -98,11 +103,31 @@ export class AgentGCPKey extends CloudAgentKey { } get identifier() { + const protocolType = this.chainName + ? getChain(this.chainName).protocol + : undefined; + + // If the role is Deployer and the ProtocolType + // - is Ethereum we don't add the chain name as the key does not have it to get the correct secret key value + // - is Sealvel then we use the protocol name instead of the chain name as all sealevel chains use the same key + // - is none of the above, fallback to using the chain name in all other cases as other roles might require it + let protocolOrChain: string | undefined; + if (this.role === Role.Deployer && protocolType === ProtocolType.Ethereum) { + protocolOrChain = undefined; + } else if ( + this.role === Role.Deployer && + protocolType === ProtocolType.Sealevel + ) { + protocolOrChain = ProtocolType.Sealevel; + } else { + protocolOrChain = this.chainName; + } + return keyIdentifier( this.environment, this.context, this.role, - this.chainName, + protocolOrChain, this.index, ); } @@ -127,9 +152,12 @@ export class AgentGCPKey extends CloudAgentKey { case ProtocolType.Ethereum: return this.address; case ProtocolType.Sealevel: - return Keypair.fromSeed( - Buffer.from(strip0x(this.privateKey), 'hex'), + return Keypair.fromSecretKey( + this.privateKeyForProtocol(ProtocolType.Sealevel), ).publicKey.toBase58(); + case ProtocolType.Starknet: + // Assumes that the address is base58 encoded in secrets manager + return ethers.utils.hexlify(ethers.utils.base58.decode(this.address)); case ProtocolType.Cosmos: case ProtocolType.CosmosNative: { const compressedPubkey = ethers.utils.computePublicKey( @@ -149,6 +177,33 @@ export class AgentGCPKey extends CloudAgentKey { } } + override privateKeyForProtocol( + protocol: Exclude, + ): HexString; + override privateKeyForProtocol(protocol: ProtocolType.Sealevel): Uint8Array; + override privateKeyForProtocol( + protocol: ProtocolType, + ): HexString | Uint8Array { + this.requireFetched(); + + if (protocol === ProtocolType.Sealevel) { + // This assumes the key is stored as the base64 encoded + // string of the stringified version of the private key + // in array format (format used by the solana CLI). + // So we need to: + // - convert the base64 string to a buffer so we can get the stringified json string + // - get the the json array from its stringified representation + // - finally get the byte array from the parsed json array + return Uint8Array.from( + JSON.parse(String(Buffer.from(this.privateKey, 'base64'))), + ); + } else if (protocol === ProtocolType.Starknet) { + return ethers.utils.hexlify(ethers.utils.base58.decode(this.privateKey)); + } else { + return this.privateKey; + } + } + async fetch() { this.logger.debug('Fetching key'); const secret: SecretManagerPersistedKeys = (await fetchGCPSecret( diff --git a/typescript/infra/src/agents/index.ts b/typescript/infra/src/agents/index.ts index 84f248ce00d..c1854089a19 100644 --- a/typescript/infra/src/agents/index.ts +++ b/typescript/infra/src/agents/index.ts @@ -251,15 +251,6 @@ export class RelayerHelmManager extends OmniscientAgentHelmManager { effect: 'NoSchedule', }); - if (this.context.includes('vanguard')) { - values.tolerations.push({ - key: 'context-family', - operator: 'Equal', - value: 'vanguard', - effect: 'NoSchedule', - }); - } - return values; } diff --git a/typescript/infra/src/agents/key-utils.ts b/typescript/infra/src/agents/key-utils.ts index a20f15f7a68..e4c89ac1b44 100644 --- a/typescript/infra/src/agents/key-utils.ts +++ b/typescript/infra/src/agents/key-utils.ts @@ -43,7 +43,7 @@ export const relayerAddresses: LocalRoleAddresses = export const kathyAddresses: LocalRoleAddresses = localKathyAddresses as LocalRoleAddresses; -const debugLog = rootLogger.child({ module: 'infra:agents:key:utils' }).debug; +const logger = rootLogger.child({ module: 'infra:agents:key-utils' }); export interface KeyAsAddress { identifier: string; @@ -200,7 +200,7 @@ function getRoleKeyMapPerChain( export function getAllCloudAgentKeys( agentConfig: RootAgentConfig, ): Array { - debugLog('Retrieving all cloud agent keys'); + logger.debug('Retrieving all cloud agent keys'); const keysPerChain = getRoleKeyMapPerChain(agentConfig); const keysByIdentifier = Object.keys(keysPerChain).reduce( @@ -235,7 +235,7 @@ export function getCloudAgentKey( chainName?: ChainName, index?: number, ): CloudAgentKey { - debugLog(`Retrieving cloud agent key for ${role} on ${chainName}`); + logger.debug(`Retrieving cloud agent key for ${role} on ${chainName}`); switch (role) { case Role.Validator: if (chainName === undefined || index === undefined) { @@ -271,7 +271,7 @@ export function getRelayerKeyForChain( agentConfig: AgentContextConfig, chainName: ChainName, ): CloudAgentKey { - debugLog(`Retrieving relayer key for ${chainName}`); + logger.debug(`Retrieving relayer key for ${chainName}`); // If AWS is enabled and the chain is an Ethereum-based chain, we want to use // an AWS key. if (agentConfig.aws && isEthereumProtocolChain(chainName)) { @@ -294,7 +294,7 @@ export function getKathyKeyForChain( agentConfig: AgentContextConfig, chainName: ChainName, ): CloudAgentKey { - debugLog(`Retrieving kathy key for ${chainName}`); + logger.debug(`Retrieving kathy key for ${chainName}`); // If AWS is enabled and the chain is an Ethereum-based chain, we want to use // an AWS key. if (agentConfig.aws && isEthereumProtocolChain(chainName)) { @@ -304,11 +304,19 @@ export function getKathyKeyForChain( return new AgentGCPKey(agentConfig.runEnv, agentConfig.context, Role.Kathy); } -// Returns the deployer key. This is always a GCP key, not chain specific, -// and in the Hyperlane context. -export function getDeployerKey(agentConfig: AgentContextConfig): CloudAgentKey { - debugLog('Retrieving deployer key'); - return new AgentGCPKey(agentConfig.runEnv, Contexts.Hyperlane, Role.Deployer); +// Returns the deployer key for the specified chain or EVM by default in the provided context. +export function getDeployerKey( + agentConfig: AgentContextConfig, + chainName?: ChainName, +): CloudAgentKey { + logger.debug('Retrieving deployer key'); + + return new AgentGCPKey( + agentConfig.runEnv, + Contexts.Hyperlane, + Role.Deployer, + chainName, + ); } // Returns the rebalancer key. This is always a GCP key, not chain specific @@ -316,7 +324,7 @@ export function getDeployerKey(agentConfig: AgentContextConfig): CloudAgentKey { export function getRebalancerKey( agentConfig: AgentContextConfig, ): CloudAgentKey { - debugLog('Retrieving rebalancer key'); + logger.debug('Retrieving rebalancer key'); return new AgentGCPKey( agentConfig.runEnv, Contexts.Hyperlane, @@ -342,7 +350,7 @@ export function getValidatorKeysForChain( validator: CloudAgentKey; chainSigner: CloudAgentKey; } { - debugLog(`Retrieving validator keys for ${chainName}`); + logger.debug(`Retrieving validator keys for ${chainName}`); const validator = agentConfig.aws ? new AgentAwsKey(agentConfig, Role.Validator, chainName, index) : new AgentGCPKey( @@ -359,7 +367,7 @@ export function getValidatorKeysForChain( if (isEthereumProtocolChain(chainName)) { chainSigner = validator; } else { - debugLog(`Retrieving GCP key for ${chainName}, as it is not EVM`); + logger.debug(`Retrieving GCP key for ${chainName}, as it is not EVM`); chainSigner = new AgentGCPKey( agentConfig.runEnv, agentConfig.context, @@ -420,7 +428,7 @@ async function createAgentKeys( agentConfig: AgentContextConfig, agentKeysToCreate: string[], ) { - debugLog('Creating agent keys if none exist'); + logger.debug('Creating agent keys if none exist'); const keys = getAllCloudAgentKeys(agentConfig); @@ -431,7 +439,7 @@ async function createAgentKeys( // Process only non-Starknet keys for creation await Promise.all( keysToCreate.map(async (key) => { - debugLog(`Creating key if not exists: ${key.identifier}`); + logger.debug(`Creating key if not exists: ${key.identifier}`); return key.createIfNotExists(); }), ); @@ -470,7 +478,7 @@ async function agentKeysToBeCreated( } export async function deleteAgentKeys(agentConfig: AgentContextConfig) { - debugLog('Deleting agent keys'); + logger.debug('Deleting agent keys'); // Filter out Starknet keys - we don't want to delete them const keysToDelete = getModifiableKeys(agentConfig); @@ -489,7 +497,7 @@ export async function rotateKey( role: Role, chainName: ChainName, ) { - debugLog(`Rotating key for ${role} on ${chainName}`); + logger.debug(`Rotating key for ${role} on ${chainName}`); const key = getCloudAgentKey(agentConfig, role, chainName); await key.update(); await persistAddressesLocally(agentConfig, [key]); @@ -506,19 +514,19 @@ async function persistAddressesInGcp( true, )) as KeyAsAddress[]; if (deepEquals(keys, existingSecret)) { - debugLog( + logger.debug( `Addresses already persisted to GCP for ${context} context in ${environment} environment`, ); return; } } catch (e) { // If the secret doesn't exist, we'll create it below. - debugLog( + logger.debug( `No existing secret found for ${context} context in ${environment} environment`, ); } - debugLog( + logger.debug( `Persisting addresses to GCP for ${context} context in ${environment} environment`, ); await setGCPSecretUsingClient( @@ -535,8 +543,8 @@ async function persistAddressesLocally( agentConfig: AgentContextConfig, keys: CloudAgentKey[], ) { - debugLog( - `Persisting addresses to GCP for ${agentConfig.context} context in ${agentConfig.runEnv} environment`, + logger.debug( + `Persisting addresses locally for ${agentConfig.context} context in ${agentConfig.runEnv} environment`, ); // recent keys fetched from aws saved to local artifacts const multisigValidatorKeys: ChainMap<{ validators: Address[] }> = {}; @@ -638,7 +646,7 @@ export function fetchLocalKeyAddresses(role: Role): LocalRoleAddresses { `${role}.json`, ); - debugLog(`Fetching addresses from GCP for ${role} role ...`); + logger.debug(`Fetching addresses locally for ${role} role ...`); return addresses; } catch (e) { throw new Error(`Error fetching addresses locally for ${role} role: ${e}`); diff --git a/typescript/infra/src/agents/keys.ts b/typescript/infra/src/agents/keys.ts index f259c180661..011344729ad 100644 --- a/typescript/infra/src/agents/keys.ts +++ b/typescript/infra/src/agents/keys.ts @@ -2,7 +2,7 @@ import { ethers } from 'ethers'; import { Provider as ZkProvider, Wallet as ZkWallet } from 'zksync-ethers'; import { ChainName } from '@hyperlane-xyz/sdk'; -import { ProtocolType } from '@hyperlane-xyz/utils'; +import { HexString, ProtocolType } from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts.js'; import { DeployEnvironment } from '../config/environment.js'; @@ -74,6 +74,20 @@ export abstract class CloudAgentKey extends BaseCloudAgentKey { address: this.address, }; } + + privateKeyForProtocol( + protocol: Exclude, + ): HexString; + privateKeyForProtocol(protocol: ProtocolType.Sealevel): Uint8Array; + privateKeyForProtocol(protocol: ProtocolType): HexString | Uint8Array { + if (protocol === ProtocolType.Ethereum) { + return this.privateKey; + } + + throw new Error( + `Base implementation of CloudAgentKey.privateKeyForProtocol does not support protocol ${protocol}`, + ); + } } export class LocalAgentKey extends BaseAgentKey { @@ -109,7 +123,7 @@ export class ReadOnlyCloudAgentKey extends BaseCloudAgentKey { * flavors, e.g.: * alias/hyperlane-testnet2-key-kathy (<-- hyperlane context, not specific to any chain) * alias/hyperlane-testnet2-key-optimismkovan-relayer (<-- hyperlane context, chain specific) - * alias/hyperlane-testnet2-key-alfajores-validator-0 (<-- hyperlane context, chain specific and has an index) + * alias/hyperlane-testnet2-key-sepolia-validator-0 (<-- hyperlane context, chain specific and has an index) * hyperlane-dev-key-kathy (<-- same idea as above, but without the `alias/` prefix if it's not AWS-based) * alias/flowcarbon-testnet2-key-optimismkovan-relayer (<-- flowcarbon context & chain specific, intended to show that there are non-hyperlane contexts) * @param address The address of the key. diff --git a/typescript/infra/src/coingecko/utils.ts b/typescript/infra/src/coingecko/utils.ts new file mode 100644 index 00000000000..d1d8712c34c --- /dev/null +++ b/typescript/infra/src/coingecko/utils.ts @@ -0,0 +1,24 @@ +import { Logger } from 'pino'; + +import { DeployEnvironment } from '../config/environment.js'; +import { fetchGCPSecret } from '../utils/gcloud.js'; + +export async function getCoinGeckoApiKey( + logger: Logger, +): Promise { + const environment: DeployEnvironment = 'mainnet3'; + let apiKey: string | undefined; + try { + apiKey = (await fetchGCPSecret( + `${environment}-coingecko-api-key`, + false, + )) as string; + } catch (err) { + logger.error( + err, + 'Failed to fetch CoinGecko API key, proceeding with public tier', + ); + } + + return apiKey; +} diff --git a/typescript/infra/src/config/agent/agent.ts b/typescript/infra/src/config/agent/agent.ts index 924cc530c08..5ff5b97ede1 100644 --- a/typescript/infra/src/config/agent/agent.ts +++ b/typescript/infra/src/config/agent/agent.ts @@ -127,11 +127,17 @@ export type StarknetKeyConfig = { type: AgentSignerKeyType.Starknet; legacy: boolean; }; +// Radix key config +export type RadixKeyConfig = { + type: AgentSignerKeyType.Radix; + suffix: string; +}; export type KeyConfig = | AwsKeyConfig | HexKeyConfig | CosmosKeyConfig - | StarknetKeyConfig; + | StarknetKeyConfig + | RadixKeyConfig; interface IndexingConfig { from: number; chunk: number; @@ -243,6 +249,20 @@ export function defaultChainSignerKeyConfig(chainName: ChainName): KeyConfig { ); } return { type: AgentSignerKeyType.Cosmos, prefix: metadata.bech32Prefix }; + case ProtocolType.Radix: + // get the suffix based on the chain id + let suffix: string; + switch (metadata.chainId) { + case 240: // localnet + suffix = 'loc'; + break; + case 2: // stokenet + suffix = 'tdx_2_'; + break; + default: // mainnet + suffix = 'rdx'; + } + return { type: AgentSignerKeyType.Radix, suffix: suffix }; // Use starknet key for starknet & paradexsepolia case ProtocolType.Starknet: { return { type: AgentSignerKeyType.Starknet, legacy: false }; diff --git a/typescript/infra/src/config/chain.ts b/typescript/infra/src/config/chain.ts index a87066f32da..497f7b0ff87 100644 --- a/typescript/infra/src/config/chain.ts +++ b/typescript/infra/src/config/chain.ts @@ -21,9 +21,6 @@ import { getSecretRpcEndpoints } from '../agents/index.js'; import { DeployEnvironment } from './environment.js'; -// Separate list of chains that we want to keep updated operationally but -// skip in regular check-deploy as they require manual updates via -// legacy ICAs. // V2 ICAs are not supported on these chains, due to the block gas limit being // lower than the amount required to deploy the new InterchainAccountRouter // implementation. @@ -34,10 +31,6 @@ export const legacyIcaChainRouters: Record< interchainAccountRouter: Address; } > = { - conflux: { - interchainAccountIsm: '0x93D41E41cA545a35A81d11b08D2eE8b852C768df', - interchainAccountRouter: '0xc2466492C451E1AE49d8C874bB9f89293Aaad59b', - }, viction: { interchainAccountIsm: '0x551BbEc45FD665a8C95ca8731CbC32b7653Bc59B', interchainAccountRouter: '0xc11f8Cf2343d3788405582F65B8af6A4F7a6FfC8', @@ -52,49 +45,41 @@ export const legacyIcaChainRouters: Record< interchainAccountIsm: '0xc261Bd2BD995d3D0026e918cBFD44b0Cc5416a57', interchainAccountRouter: '0xf4035357EB3e3B48E498FA6e1207892f615A2c2f', }, - // special case for bouncebit as there are RPC issues currently - // will update this separately in the next batch - bouncebit: { - interchainAccountIsm: '0xcDD89f19b2d00DCB9510BB3fBd5eCeCa761fe5Ab', - interchainAccountRouter: '0x7947b7Fe737B4bd1D3387153f32148974066E591', - }, - // special case for deepbrainchain as there are RPC issues currently - // will update this separately in the next batch - deepbrainchain: { - interchainAccountIsm: '0x5B7a808CaA2C3F1378B07cDd46eB8ccA52F67e3B', - interchainAccountRouter: '0xBCD18636e5876DFd7AAb5F2B2a5Eb5ca168BA1d8', - }, }; export const legacyIcaChains = Object.keys(legacyIcaChainRouters); export const legacyEthIcaRouter = '0x5E532F7B610618eE73C2B462978e94CB1F7995Ce'; // A list of chains to skip during deploy, check-deploy and ICA operations. // Used by scripts like check-owner-ica.ts to exclude chains that are temporarily -// unsupported (e.g. zksync, zeronetwork) or have known issues (e.g. lumia). +// unsupported (e.g. zksync, zeronetwork) or have known issues (e.g. infinityvmmainnet). export const chainsToSkip: ChainName[] = [ + 'infinityvmmainnet', + + // not AW owned + 'forma', + // TODO: remove once zksync PR is merged into main // mainnets 'zksync', 'zeronetwork', - 'zklink', 'abstract', 'sophon', // testnets 'abstracttestnet', - // Oct 16 batch - 'lumia', - // special case for arcadia as it's currently under maintenance. // will update this separately in the next batch. 'arcadia', - // special case for viction, ontology, bouncebit, deepbrainchain as there are RPC issues currently - // will update this separately in the next batch + + // legacy ICAs 'viction', 'ontology', - 'bouncebit', - 'deepbrainchain', + + // legacy icas + 'carrchaintestnet', + 'infinityvmmonza', + 'rometestnet', ]; export const defaultRetry: ProviderRetryOptions = { diff --git a/typescript/infra/src/config/environment.ts b/typescript/infra/src/config/environment.ts index 8743526a728..34519920d7e 100644 --- a/typescript/infra/src/config/environment.ts +++ b/typescript/infra/src/config/environment.ts @@ -13,7 +13,7 @@ import { mustGet, objKeys, objMap, objMerge } from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts.js'; import { environments } from '../../config/environments/index.js'; -import { awIcas } from '../../config/environments/mainnet3/governance/ica/aw.js'; +import { awIcasLegacy } from '../../config/environments/mainnet3/governance/ica/_awLegacy.js'; import { awSafes } from '../../config/environments/mainnet3/governance/safe/aw.js'; import { DEPLOYER, @@ -95,7 +95,7 @@ export async function getRouterConfigsForAllVms( const ownerConfigs: ChainMap = objMap( envConfig.owners, (chain, _) => { - const owner = awIcas[chain] ?? awSafes[chain] ?? DEPLOYER; + const owner = awIcasLegacy[chain] ?? awSafes[chain] ?? DEPLOYER; return { owner, ownerOverrides: { @@ -104,7 +104,7 @@ export async function getRouterConfigsForAllVms( testRecipient: DEPLOYER, fallbackRoutingHook: DEPLOYER, ...(awSafes[chain] && { _safeAddress: awSafes[chain] }), - ...(awIcas[chain] && { _icaAddress: awIcas[chain] }), + ...(awIcasLegacy[chain] && { _icaAddress: awIcasLegacy[chain] }), }, }; }, diff --git a/typescript/infra/src/config/funding.ts b/typescript/infra/src/config/funding.ts index f92d7c95ad4..fbf2f5a5270 100644 --- a/typescript/infra/src/config/funding.ts +++ b/typescript/infra/src/config/funding.ts @@ -26,6 +26,7 @@ export interface KeyFunderConfig cyclesBetweenEthereumMessages?: number; desiredBalancePerChain: Record; desiredKathyBalancePerChain: ChainMap; + desiredRebalancerBalancePerChain: ChainMap; igpClaimThresholdPerChain: ChainMap; chainsToSkip: ChainName[]; } diff --git a/typescript/infra/src/config/gas-oracle.ts b/typescript/infra/src/config/gas-oracle.ts index e76f7b16ac6..83d37149338 100644 --- a/typescript/infra/src/config/gas-oracle.ts +++ b/typescript/infra/src/config/gas-oracle.ts @@ -178,6 +178,10 @@ export function getTypicalHandleGasAmount( return 5_000_000; } + if (remoteProtocolType === ProtocolType.Radix) { + return 30_000_000; + } + // A fairly arbitrary amount of gas used in a message's handle function, // generally fits most VMs. return 50_000; @@ -198,38 +202,23 @@ function getMinUsdCost(local: ChainName, remote: ChainName): number { // By default, min cost is 20 cents let minUsdCost = 0.2; - // For Ethereum local, min cost is 1.5 USD - if (local === 'ethereum') { - minUsdCost = Math.max(minUsdCost, 1.5); - } - // For all SVM chains, min cost is 0.50 USD to cover rent needs if (getChain(remote).protocol === ProtocolType.Sealevel) { minUsdCost = Math.max(minUsdCost, 0.5); } const remoteMinCostOverrides: ChainMap = { + // mitosis + mitosis: 0.1, + + // For all SVM chains, min cost is 0.50 USD to cover rent needs // For Ethereum L2s, we need to account for the L1 DA costs that // aren't accounted for directly in the gas price. - arbitrum: 0.5, ancient8: 0.5, blast: 0.5, - bob: 0.5, - linea: 0.5, mantapacific: 0.5, - mantle: 0.5, polygonzkevm: 0.5, - // op stack chains - base: 0.2, - fraxtal: 0.2, - lisk: 0.2, - mode: 0.2, - optimism: 0.2, - soneium: 0.2, - superseed: 0.2, - unichain: 0.2, - // Scroll is more expensive than the rest due to higher L1 fees scroll: 1.5, taiko: 0.5, @@ -354,3 +343,46 @@ export function getAllStorageGasOracleConfigs( }; }, {}) as AllStorageGasOracleConfigs; } + +// 5% threshold, adjust as needed +export const DEFAULT_DIFF_THRESHOLD_PCT = 5; + +/** + * Gets a safe numeric value with fallback, handling NaN and undefined cases + */ +export const getSafeNumericValue = ( + value: string | number | undefined, + fallback: string | number, +): number => { + const parsed = + value && !isNaN(Number(value)) ? Number(value) : Number(fallback); + return parsed; +}; + +/** + * Determines if a price should be updated based on percentage difference threshold + */ +export const shouldUpdatePrice = ( + newPrice: number, + prevPrice: number, + thresholdPct: number = DEFAULT_DIFF_THRESHOLD_PCT, +): boolean => { + if (prevPrice === 0) return true; // Avoid division by zero + const diff = Math.abs(newPrice - prevPrice) / prevPrice; + return diff > thresholdPct / 100; +}; + +/** + * Generic price update logic that can be reused across different price types + */ +export const updatePriceIfNeeded = ( + newValue: T, + prevValue: T, + newNumeric: number, + prevNumeric: number, + thresholdPct: number = DEFAULT_DIFF_THRESHOLD_PCT, +): T => { + return shouldUpdatePrice(newNumeric, prevNumeric, thresholdPct) + ? newValue + : prevValue; +}; diff --git a/typescript/infra/src/funding/key-funder.ts b/typescript/infra/src/funding/key-funder.ts index 4281edf4318..0389a36f0ab 100644 --- a/typescript/infra/src/funding/key-funder.ts +++ b/typescript/infra/src/funding/key-funder.ts @@ -45,6 +45,8 @@ export class KeyFunderHelmManager extends HelmManager { contextsAndRolesToFund: this.config.contextsAndRolesToFund, desiredBalancePerChain: this.config.desiredBalancePerChain, desiredKathyBalancePerChain: this.config.desiredKathyBalancePerChain, + desiredRebalancerBalancePerChain: + this.config.desiredRebalancerBalancePerChain, igpClaimThresholdPerChain: this.config.igpClaimThresholdPerChain, chainsToSkip: this.config.chainsToSkip, }, diff --git a/typescript/infra/src/govern/HyperlaneAppGovernor.ts b/typescript/infra/src/govern/HyperlaneAppGovernor.ts index fbfaa800a83..78cbd971b8e 100644 --- a/typescript/infra/src/govern/HyperlaneAppGovernor.ts +++ b/typescript/infra/src/govern/HyperlaneAppGovernor.ts @@ -27,6 +27,8 @@ import { rootLogger, } from '@hyperlane-xyz/utils'; +import { awIcasLegacy } from '../../config/environments/mainnet3/governance/ica/_awLegacy.js'; +import { regularIcasLegacy } from '../../config/environments/mainnet3/governance/ica/_regularLegacy.js'; import { getGovernanceSafes } from '../../config/environments/mainnet3/governance/utils.js'; import { legacyEthIcaRouter, legacyIcaChainRouters } from '../config/chain.js'; import { @@ -384,8 +386,21 @@ export abstract class HyperlaneAppGovernor< let accountConfig = this.interchainAccount.knownAccounts[account.address]; if (!accountConfig) { - const { ownerType, governanceType: icaGovernanceType } = - await determineGovernanceType(chain, account.address); + let ownerType: Owner | null; + let icaGovernanceType: GovernanceType; + + // Backstop to still be able to parse legacy Abacus Works ICAs + if (eqAddress(account.address, awIcasLegacy[chain])) { + ownerType = Owner.ICA; + icaGovernanceType = GovernanceType.AbacusWorks; + } else if (eqAddress(account.address, regularIcasLegacy[chain])) { + ownerType = Owner.ICA; + icaGovernanceType = GovernanceType.Regular; + } else { + ({ ownerType, governanceType: icaGovernanceType } = + await determineGovernanceType(chain, account.address)); + } + // verify that we expect it to be an ICA assert(ownerType === Owner.ICA, 'ownerType should be ICA'); // get the set of safes for this governance type @@ -547,14 +562,16 @@ export abstract class HyperlaneAppGovernor< await this.checkSubmitterBalance(chain, submitterAddress, call.value); } - // Check if the submitter is the owner of the contract + // If it's not an ICA call, check if the submitter is the owner of the contract try { - const ownable = Ownable__factory.connect(call.to, signer); - const owner = await ownable.owner(); - const isOwner = eqAddress(owner, submitterAddress); + if (!isICACall) { + const ownable = Ownable__factory.connect(call.to, signer); + const owner = await ownable.owner(); + const isOwner = eqAddress(owner, submitterAddress); - if (!isOwner) { - return false; + if (!isOwner) { + return false; + } } } catch { // If the contract does not implement Ownable, just continue diff --git a/typescript/infra/src/govern/HyperlaneICAChecker.ts b/typescript/infra/src/govern/HyperlaneICAChecker.ts index 68111c11347..86117686ff0 100644 --- a/typescript/infra/src/govern/HyperlaneICAChecker.ts +++ b/typescript/infra/src/govern/HyperlaneICAChecker.ts @@ -1,73 +1,97 @@ +import chalk from 'chalk'; + import { - ChainMap, ChainName, - ConnectionClientViolationType, InterchainAccountChecker, - RouterViolation, + MissingRouterViolation, RouterViolationType, } from '@hyperlane-xyz/sdk'; -import { - AddressBytes32, - addressToBytes32, - eqAddress, -} from '@hyperlane-xyz/utils'; +import { rootLogger } from '@hyperlane-xyz/utils'; + +import { deploymentChains as ousdtChains } from '../../config/environments/mainnet3/warp/configGetters/getoUSDTTokenWarpConfig.js'; +import { legacyIcaChains } from '../config/chain.js'; + +const MAINNET = 'ethereum'; + +const FULLY_CONNECTED_ICA_CHAINS = new Set([ + 'arbitrum', + 'bsc', + 'polygon', + 'subtensor', + MAINNET, + ...ousdtChains, +]); export class HyperlaneICAChecker extends InterchainAccountChecker { async checkMailboxClient(chain: ChainName): Promise { const router = this.app.router(this.app.getContracts(chain)); const config = this.configMap[chain]; + + if (!router) { + const violation: MissingRouterViolation = { + chain, + type: RouterViolationType.MissingRouter, + contract: router, + actual: undefined, + expected: config, + description: `Router is not deployed`, + }; + this.addViolation(violation); + return; + } + await this.checkMailbox(chain, router, config); } /* - * Check that the Ethereum router is enrolled correctly, + * Check that the ICA router has the relevant routers enrolled, * and that remote chains have the correct router enrolled. */ - async checkEthRouterEnrollment(chain: ChainName): Promise { - // If the chain is Ethereum, do the regular full check - if (chain === 'ethereum') { - return super.checkEnrolledRouters(chain); + async checkIcaRouterEnrollment(chain: ChainName): Promise { + // If the chain should be fully connected, do the regular full check. + if (FULLY_CONNECTED_ICA_CHAINS.has(chain)) { + // don't try to enroll legacy ica chains + const actualRemoteChains = await this.app.remoteChains(chain); + // .remoteChains() already filters out the origin chain itself + const filteredRemoteChains = actualRemoteChains.filter( + (c) => !legacyIcaChains.includes(c), + ); + return super.checkEnrolledRouters(chain, filteredRemoteChains); } - - // Get the Ethereum router address and domain id - const ethereumRouterAddress = this.app.routerAddress('ethereum'); - const ethereumDomainId = this.multiProvider.getDomainId('ethereum'); - // Get the expected Ethereum router address (with padding) - const expectedRouter = addressToBytes32(ethereumRouterAddress); - - // Get the actual Ethereum router address - const router = this.app.router(this.app.getContracts(chain)); - const actualRouter = await router.routers(ethereumDomainId); - - // Check if the actual router address matches the expected router address - if (actualRouter !== expectedRouter) { - const currentRouters: ChainMap = { ethereum: actualRouter }; - const expectedRouters: ChainMap = { - ethereum: expectedRouter, - }; - const routerDiff: ChainMap<{ - actual: AddressBytes32; - expected: AddressBytes32; - }> = { - ethereum: { actual: actualRouter, expected: expectedRouter }, - }; - - const violation: RouterViolation = { - chain, - type: RouterViolationType.MisconfiguredEnrolledRouter, - contract: router, - actual: currentRouters, - expected: expectedRouters, - routerDiff, - description: `Ethereum router is not enrolled correctly`, - }; - this.addViolation(violation); + // Otherwise only do a partial check to ensure that only fully-connected chains + // are enrolled. This is so any fresh deployments are always controllable from + // the "core" ICA controller chains. + else { + // have to manually filter out the origin chain itself + // and then filter out legacy ica chains + const remotes = Array.from(FULLY_CONNECTED_ICA_CHAINS).filter( + (c) => c !== chain && !legacyIcaChains.includes(c), + ); + return super.checkEnrolledRouters(chain, remotes); } } async checkChain(chain: ChainName): Promise { + if (!this.configMap[chain]) { + rootLogger.warn( + chalk.bold.yellow( + `Skipping check for ${chain} because there is no expected config`, + ), + ); + return; + } + + if (legacyIcaChains.includes(chain)) { + rootLogger.warn( + chalk.bold.yellow( + `Skipping check for ${chain} because it is a legacy ica chain`, + ), + ); + return; + } + await this.checkMailboxClient(chain); - await this.checkEthRouterEnrollment(chain); + await this.checkIcaRouterEnrollment(chain); await super.checkOwnership( chain, this.configMap[chain].owner, diff --git a/typescript/infra/src/governance.ts b/typescript/infra/src/governance.ts index 1d9b793bb0e..3fba01a6b11 100644 --- a/typescript/infra/src/governance.ts +++ b/typescript/infra/src/governance.ts @@ -4,6 +4,7 @@ import { ChainName } from '@hyperlane-xyz/sdk'; import { Address, eqAddressEvm } from '@hyperlane-xyz/utils'; import { + getGovernanceIcas, getGovernanceSafes, getGovernanceTimelocks, getLegacyGovernanceIcas, @@ -16,6 +17,7 @@ export enum GovernanceType { Regular = 'regular', Irregular = 'irregular', OUSDT = 'ousdt', + Dymension = 'dymension', } export enum Owner { @@ -58,12 +60,16 @@ export async function determineGovernanceType( } for (const governanceType of Object.values(GovernanceType)) { + const icas = getGovernanceIcas(governanceType); + if (icas[chain] && eqAddressEvm(icas[chain], address)) { + return { ownerType: Owner.ICA, governanceType }; + } const timelocks = getGovernanceTimelocks(governanceType); if (timelocks[chain] && eqAddressEvm(timelocks[chain], address)) { return { ownerType: Owner.TIMELOCK, governanceType }; } - const icas = getLegacyGovernanceIcas(governanceType); - if (icas[chain] && eqAddressEvm(icas[chain], address)) { + const legacyIcas = getLegacyGovernanceIcas(governanceType); + if (legacyIcas[chain] && eqAddressEvm(legacyIcas[chain], address)) { return { ownerType: Owner.ICA, governanceType }; } const safes = getGovernanceSafes(governanceType); diff --git a/typescript/infra/src/rebalancer/helm.ts b/typescript/infra/src/rebalancer/helm.ts index daeb254e4fe..55400cd322a 100644 --- a/typescript/infra/src/rebalancer/helm.ts +++ b/typescript/infra/src/rebalancer/helm.ts @@ -75,7 +75,7 @@ export class RebalancerHelmManager extends HelmManager { return { image: { repository: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '7c02f78-20250721-125228', + tag: '5abccc4-20250915-165639', }, withMetrics: this.withMetrics, fullnameOverride: this.helmReleaseName, diff --git a/typescript/infra/src/roles.ts b/typescript/infra/src/roles.ts index 95b2bde3c7b..dc3d149b8d4 100644 --- a/typescript/infra/src/roles.ts +++ b/typescript/infra/src/roles.ts @@ -7,7 +7,7 @@ export enum Role { Rebalancer = 'rebalancer', } -export type FundableRole = Role.Relayer | Role.Kathy; +export type FundableRole = Role.Relayer | Role.Kathy | Role.Rebalancer; export const ALL_KEY_ROLES = [ Role.Validator, diff --git a/typescript/infra/src/tx/govern-transaction-reader.ts b/typescript/infra/src/tx/govern-transaction-reader.ts index 8d708c006a9..5097aa2413b 100644 --- a/typescript/infra/src/tx/govern-transaction-reader.ts +++ b/typescript/infra/src/tx/govern-transaction-reader.ts @@ -47,8 +47,8 @@ import { rootLogger, } from '@hyperlane-xyz/utils'; -import { awIcasV2 } from '../../config/environments/mainnet3/governance/ica/aw2.js'; -import { regularIcasV2 } from '../../config/environments/mainnet3/governance/ica/regular2.js'; +import { awIcasLegacy } from '../../config/environments/mainnet3/governance/ica/_awLegacy.js'; +import { regularIcasLegacy } from '../../config/environments/mainnet3/governance/ica/_regularLegacy.js'; import { getAllSafesForChain, getGovernanceIcas, @@ -73,7 +73,7 @@ import { } from '../governance.js'; import { getSafeTx, parseSafeTx } from '../utils/safe.js'; -interface GovernTransaction extends Record { +export interface GovernTransaction extends Record { chain: ChainName; nestedTx?: GovernTransaction; } @@ -121,6 +121,11 @@ type XERC20Metadata = { decimals: number; }; +const ownableFunctionSelectors = [ + 'renounceOwnership()', + 'transferOwnership(address)', +].map((func) => ethers.utils.id(func).substring(0, 10)); + export class GovernTransactionReader { errors: any[] = []; @@ -265,6 +270,11 @@ export class GovernTransactionReader { chain: ChainName, tx: AnnotatedEV5Transaction, ): Promise { + // If it's an Ownable transaction + if (await this.isOwnableTransaction(tx)) { + return this.readOwnableTransaction(chain, tx); + } + // If it's to another Safe if (this.isSafeTransaction(chain, tx)) { return this.readSafeTransaction(chain, tx); @@ -316,11 +326,6 @@ export class GovernTransactionReader { return this.readProxyAdminTransaction(chain, tx); } - // If it's an Ownable transaction - if (await this.isOwnableTransaction(chain, tx)) { - return this.readOwnableTransaction(chain, tx); - } - // If it's a native token transfer (no data, only value) if (this.isNativeTokenTransfer(tx)) { return this.readNativeTokenTransfer(chain, tx); @@ -482,6 +487,7 @@ export class GovernTransactionReader { }); let insight; + let calls; if ( decoded.functionFragment.name === timelockControllerInterface.functions[ @@ -508,10 +514,10 @@ export class GovernTransactionReader { ) { const [targets, values, data, _predecessor, _salt, delay] = decoded.args; - const innerTxs = []; + calls = []; const numOfTxs = targets.length; for (let i = 0; i < numOfTxs; i++) { - innerTxs.push( + calls.push( await this.read(chain, { to: targets[i], data: data[i], @@ -522,7 +528,7 @@ export class GovernTransactionReader { const eta = new Date(Date.now() + delay.toNumber() * 1000); - insight = `Schedule for ${eta}: ${JSON.stringify(innerTxs, null, 2)}`; + insight = `Schedule for ${eta}`; } if ( @@ -535,6 +541,29 @@ export class GovernTransactionReader { insight = `Execute ${target} with ${value} ${data}. Executor: ${executor}`; } + if ( + decoded.functionFragment.name === + timelockControllerInterface.functions[ + 'executeBatch(address[],uint256[],bytes[],bytes32,bytes32)' + ].name + ) { + const [targets, values, data, _predecessor, _salt] = decoded.args; + + calls = []; + const numOfTxs = targets.length; + for (let i = 0; i < numOfTxs; i++) { + calls.push( + await this.read(chain, { + to: targets[i], + data: data[i], + value: values[i], + }), + ); + } + + insight = `Execute batch on ${targets}`; + } + if ( decoded.functionFragment.name === timelockControllerInterface.functions['cancel(bytes32)'].name @@ -552,6 +581,7 @@ export class GovernTransactionReader { chain, to: `Timelock Controller (${chain} ${tx.to})`, ...(insight ? { insight } : { args }), + ...(calls ? { innerTxs: calls } : {}), }; } @@ -945,23 +975,41 @@ export class GovernTransactionReader { const remoteChainName = this.multiProvider.getChainName(domain); const expectedRouter = this.chainAddresses[remoteChainName][routerName]; const routerToBeEnrolled = addresses[index]; - const matchesExpectedRouter = - eqAddress(expectedRouter, bytes32ToAddress(routerToBeEnrolled)) && - // Poor man's check that the 12 byte padding is all zeroes - addressToBytes32(bytes32ToAddress(routerToBeEnrolled)) === - routerToBeEnrolled; + const isAddressMatch = eqAddress( + expectedRouter, + bytes32ToAddress(routerToBeEnrolled), + ); + const isPaddingCorrect = eqAddress( + addressToBytes32(bytes32ToAddress(routerToBeEnrolled)), + routerToBeEnrolled, + ); let insight = '✅ matches expected router from artifacts'; - if (!matchesExpectedRouter) { - insight = `❌ fatal mismatch, expected ${expectedRouter}`; - this.errors.push({ - chain: chain, - remoteDomain: domain, - remoteChain: remoteChainName, - router: routerToBeEnrolled, - expected: expectedRouter, - info: 'Incorrect router getting enrolled', - }); + if (!isAddressMatch || !isPaddingCorrect) { + if (!isAddressMatch) { + insight = `❌ fatal mismatch, expected ${expectedRouter}`; + this.errors.push({ + chain: chain, + remoteDomain: domain, + remoteChain: remoteChainName, + router: routerToBeEnrolled, + expected: expectedRouter, + info: 'Incorrect router address getting enrolled', + }); + } + + if (!isPaddingCorrect) { + // This is a subtle but important check: the address must be properly padded to 32 bytes + insight = `❌ fatal mismatch, expected ${addressToBytes32(bytes32ToAddress(routerToBeEnrolled))}`; + this.errors.push({ + chain: chain, + remoteDomain: domain, + remoteChain: remoteChainName, + router: routerToBeEnrolled, + expected: addressToBytes32(bytes32ToAddress(routerToBeEnrolled)), + info: 'Router address is not properly padded to 32 bytes (should be 12 leading zero bytes)', + }); + } } return { @@ -1226,7 +1274,7 @@ export class GovernTransactionReader { expected: expectedRemoteIcaAddress, info: 'Incorrect destination ICA in ICA call', }); - remoteIcaInsight = `❌ fatal mismatch, expected ${remoteIcaAddress}`; + remoteIcaInsight = `❌ fatal mismatch, expected ${expectedRemoteIcaAddress}`; } const decodedCalls = await Promise.all( @@ -1393,21 +1441,9 @@ export class GovernTransactionReader { ); } - async isOwnableTransaction( - chain: ChainName, - tx: AnnotatedEV5Transaction, - ): Promise { - if (!tx.to) return false; - try { - const account = Ownable__factory.connect( - tx.to, - this.multiProvider.getProvider(chain), - ); - await account.owner(); - return true; - } catch { - return false; - } + async isOwnableTransaction(tx: AnnotatedEV5Transaction): Promise { + if (!tx.to || !tx.data) return false; + return ownableFunctionSelectors.includes(tx.data.substring(0, 10)); } private isSafeTransaction( @@ -1628,12 +1664,15 @@ async function getOwnerInsight( return `${address} (${governanceType.toUpperCase()} ${ownerType})`; } - if (eqAddress(address, awIcasV2[chain])) { - return `${address} (${GovernanceType.AbacusWorks.toUpperCase()} ${Owner.ICA} v2)`; + if (awIcasLegacy[chain] && eqAddress(address, awIcasLegacy[chain])) { + return `${address} (${GovernanceType.AbacusWorks.toUpperCase()} ${Owner.ICA} LEGACY)`; } - if (eqAddress(address, regularIcasV2[chain])) { - return `${address} (${GovernanceType.Regular.toUpperCase()} ${Owner.ICA} v2)`; + if ( + regularIcasLegacy[chain] && + eqAddress(address, regularIcasLegacy[chain]) + ) { + return `${address} (${GovernanceType.Regular.toUpperCase()} ${Owner.ICA} LEGACY)`; } return `${address} (Unknown)`; diff --git a/typescript/infra/src/tx/utils.ts b/typescript/infra/src/tx/utils.ts new file mode 100644 index 00000000000..0b8a82e1e43 --- /dev/null +++ b/typescript/infra/src/tx/utils.ts @@ -0,0 +1,34 @@ +import chalk from 'chalk'; + +import { rootLogger, stringifyObject } from '@hyperlane-xyz/utils'; + +import { writeYamlAtPath } from '../utils/utils.js'; + +import { GovernTransaction } from './govern-transaction-reader.js'; + +export function processGovernorReaderResult( + result: [string, GovernTransaction][], + errors: any[], + outputFileName: string, +) { + if (errors.length) { + rootLogger.error( + chalk.red('❌❌❌❌❌ Encountered fatal errors ❌❌❌❌❌'), + ); + rootLogger.info(stringifyObject(errors, 'yaml', 2)); + rootLogger.error( + chalk.red('❌❌❌❌❌ Encountered fatal errors ❌❌❌❌❌'), + ); + } else { + rootLogger.info(chalk.green('✅✅✅✅✅ No fatal errors ✅✅✅✅✅')); + } + + const chainResults = Object.fromEntries(result); + const resultsPath = `${outputFileName}-${Date.now()}.yaml`; + writeYamlAtPath(resultsPath, chainResults); + rootLogger.info(`Results written to ${resultsPath}`); + + if (errors.length) { + process.exit(1); + } +} diff --git a/typescript/infra/src/utils/gcloud.ts b/typescript/infra/src/utils/gcloud.ts index b7b57df8deb..4bb5b45a30f 100644 --- a/typescript/infra/src/utils/gcloud.ts +++ b/typescript/infra/src/utils/gcloud.ts @@ -14,7 +14,7 @@ interface IamCondition { expression: string; } -const debugLog = rootLogger.child({ module: 'infra:utils:gcloud' }).debug; +const logger = rootLogger.child({ module: 'infra:utils:gcloud' }); // Allows secrets to be overridden via environment variables to avoid // gcloud calls. This is particularly useful for running commands in k8s, @@ -30,12 +30,12 @@ export async function fetchGCPSecret( const envVarOverride = tryGCPSecretFromEnvVariable(secretName); if (envVarOverride !== undefined) { - debugLog( + logger.debug( `Using environment variable instead of GCP secret with name ${secretName}`, ); output = envVarOverride; } else { - debugLog(`Fetching GCP secret with name ${secretName}`); + logger.debug(`Fetching GCP secret with name ${secretName}`); output = await fetchLatestGCPSecret(secretName); } @@ -75,10 +75,10 @@ function tryGCPSecretFromEnvVariable(gcpSecretName: string) { process.env.GCP_SECRET_OVERRIDES_ENABLED && process.env.GCP_SECRET_OVERRIDES_ENABLED.length > 0; if (!overridingEnabled) { - debugLog('GCP secret overrides disabled'); + logger.debug('GCP secret overrides disabled'); return undefined; } - debugLog('GCP secret overrides enabled'); + logger.debug('GCP secret overrides enabled'); const overrideEnvVarName = `GCP_SECRET_OVERRIDE_${gcpSecretName .replaceAll('-', '_') .toUpperCase()}`; @@ -93,12 +93,12 @@ function tryGCPSecretFromEnvVariable(gcpSecretName: string) { */ export async function gcpSecretExists(secretName: string) { const fullName = `projects/${await getCurrentProjectNumber()}/secrets/${secretName}`; - debugLog(`Checking if GCP secret exists for ${fullName}`); + logger.debug(`Checking if GCP secret exists for ${fullName}`); const matches = await execCmdAndParseJson( `gcloud secrets list --filter name=${fullName} --format json`, ); - debugLog(`Matches: ${matches.length}`); + logger.debug(`Matches: ${matches.length}`); return matches.length > 0; } @@ -123,9 +123,12 @@ export async function gcpSecretExistsUsingClient( }); return secrets.length > 0; - } catch (e) { - debugLog(`Error checking if secret exists: ${e}`); - throw e; + } catch (err) { + logger.error( + { err }, + `Error checking if secret ${secretName} exists: ${err}`, + ); + throw err; } } @@ -167,12 +170,12 @@ export async function setGCPSecret( await execCmd( `gcloud secrets create ${secretName} --data-file=${fileName} --replication-policy=automatic --labels=${labelString}`, ); - debugLog(`Created new GCP secret for ${secretName}`); + logger.debug(`Created new GCP secret for ${secretName}`); } else { await execCmd( `gcloud secrets versions add ${secretName} --data-file=${fileName}`, ); - debugLog(`Added new version to existing GCP secret for ${secretName}`); + logger.debug(`Added new version to existing GCP secret for ${secretName}`); } await rm(fileName); } @@ -203,7 +206,7 @@ export async function setGCPSecretUsingClient( labels, }, }); - debugLog(`Created new GCP secret for ${secretName}`); + logger.debug(`Created new GCP secret for ${secretName}`); } await addGCPSecretVersion(secretName, secret, client); } @@ -223,7 +226,7 @@ export async function addGCPSecretVersion( data: Buffer.from(secret, 'utf8'), }, }); - debugLog(`Added secret version ${version?.name}`); + logger.debug(`Added secret version ${version?.name}`); } export async function disableGCPSecretVersion(secretName: string) { @@ -232,7 +235,7 @@ export async function disableGCPSecretVersion(secretName: string) { const [version] = await client.disableSecretVersion({ name: secretName, }); - debugLog(`Disabled secret version ${version?.name}`); + logger.debug(`Disabled secret version ${version?.name}`); } // Returns the email of the service account @@ -242,9 +245,11 @@ export async function createServiceAccountIfNotExists( let serviceAccountInfo = await getServiceAccountInfo(serviceAccountName); if (!serviceAccountInfo) { serviceAccountInfo = await createServiceAccount(serviceAccountName); - debugLog(`Created new service account with name ${serviceAccountName}`); + logger.debug(`Created new service account with name ${serviceAccountName}`); } else { - debugLog(`Service account with name ${serviceAccountName} already exists`); + logger.debug( + `Service account with name ${serviceAccountName} already exists`, + ); } return serviceAccountInfo.email; } @@ -260,7 +265,9 @@ export async function grantServiceAccountRoleIfNotExists( matchedBinding && iamConditionsEqual(condition, matchedBinding.condition) ) { - debugLog(`Service account ${serviceAccountEmail} already has role ${role}`); + logger.debug( + `Service account ${serviceAccountEmail} already has role ${role}`, + ); return; } await execCmd( @@ -270,7 +277,9 @@ export async function grantServiceAccountRoleIfNotExists( : '' }`, ); - debugLog(`Granted role ${role} to service account ${serviceAccountEmail}`); + logger.debug( + `Granted role ${role} to service account ${serviceAccountEmail}`, + ); } export async function grantServiceAccountStorageRoleIfNotExists( @@ -290,7 +299,7 @@ export async function grantServiceAccountStorageRoleIfNotExists( binding.members.includes(`serviceAccount:${serviceAccountEmail}`), ); if (hasRole) { - debugLog( + logger.debug( `Service account ${serviceAccountEmail} already has role ${role} on bucket ${bucketName}`, ); return; @@ -307,14 +316,14 @@ export async function createServiceAccountKey(serviceAccountEmail: string) { ); const key = JSON.parse(fs.readFileSync(localKeyFile, 'utf8')); fs.rmSync(localKeyFile); - debugLog(`Created new service account key for ${serviceAccountEmail}`); + logger.debug(`Created new service account key for ${serviceAccountEmail}`); return key; } // The alphanumeric project name / ID export async function getCurrentProject() { const [result] = await execCmd('gcloud config get-value project'); - debugLog(`Current GCP project ID: ${result.trim()}`); + logger.debug(`Current GCP project ID: ${result.trim()}`); return result.trim(); } @@ -335,7 +344,7 @@ async function getIamMemberPolicyBindings(memberEmail: string) { role: unprocessedRoleObject.bindings.role, condition: unprocessedRoleObject.bindings.condition, })); - debugLog(`Retrieved IAM policy bindings for ${memberEmail}`); + logger.debug(`Retrieved IAM policy bindings for ${memberEmail}`); return bindings; } @@ -352,10 +361,10 @@ async function getServiceAccountInfo(serviceAccountName: string) { `gcloud iam service-accounts list --format json --filter displayName="${serviceAccountName}"`, ); if (matches.length === 0) { - debugLog(`No service account found with name ${serviceAccountName}`); + logger.debug(`No service account found with name ${serviceAccountName}`); return undefined; } - debugLog(`Found service account with name ${serviceAccountName}`); + logger.debug(`Found service account with name ${serviceAccountName}`); return matches[0]; } diff --git a/typescript/infra/src/utils/log.ts b/typescript/infra/src/utils/log.ts new file mode 100644 index 00000000000..83649d5318b --- /dev/null +++ b/typescript/infra/src/utils/log.ts @@ -0,0 +1,6 @@ +export function logTable>( + data: T[], + keys?: (keyof T)[], +) { + return console.table(data, keys as string[]); +} diff --git a/typescript/infra/src/utils/safe.ts b/typescript/infra/src/utils/safe.ts index 9d333ea7a4c..50915d83e5d 100644 --- a/typescript/infra/src/utils/safe.ts +++ b/typescript/infra/src/utils/safe.ts @@ -32,6 +32,21 @@ import { AnnotatedCallData } from '../govern/HyperlaneAppGovernor.js'; const TX_FETCH_RETRIES = 5; const TX_FETCH_RETRY_DELAY = 5000; +// DYMENSION: Helper function to set signers from env var. (NOTE: signer is just to pay gas, it has nothing to do with the safe itself) +export function setSignerFromPrivateKey( + multiProvider: MultiProvider, + chains: string[], +): void { + if (process.env.DYM_GOV_GAS_KEY) { + for (const chain of chains) { + const provider = multiProvider.getProvider(chain); + const signer = new ethers.Wallet(process.env.DYM_GOV_GAS_KEY, provider); + multiProvider.setSigner(chain, signer); + } + rootLogger.info('Dymension: Using private key from DYM_GOV_GAS_KEY environment variable'); + } +} + export async function getSafeAndService( chain: ChainNameOrId, multiProvider: MultiProvider, diff --git a/typescript/infra/src/utils/timelock.ts b/typescript/infra/src/utils/timelock.ts index 0cf121d199b..9a83aafac05 100644 --- a/typescript/infra/src/utils/timelock.ts +++ b/typescript/infra/src/utils/timelock.ts @@ -1,3 +1,4 @@ +import chalk from 'chalk'; import { ethers } from 'ethers'; import { TimelockController__factory } from '@hyperlane-xyz/core'; @@ -6,11 +7,21 @@ import { ChainMap, ChainName, EXECUTOR_ROLE, + EvmTimelockReader, MultiProvider, PROPOSER_ROLE, TimelockConfig, + getTimelockExecutableTransactionFromBatch, } from '@hyperlane-xyz/sdk'; -import { Address, assert, eqAddress } from '@hyperlane-xyz/utils'; +import { + Address, + HexString, + assert, + eqAddress, + isObjEmpty, + retryAsync, + rootLogger, +} from '@hyperlane-xyz/utils'; import { DEPLOYER } from '../../config/environments/mainnet3/owners.js'; @@ -126,6 +137,17 @@ export async function timelockConfigMatches({ return { matches: issues.length === 0, issues }; } +const rpcBlockRangesByChain: ChainMap = { + // The rpc limits to a max of 1024 blocks + moonbeam: 1024, + // The rpc limits to a max of 5000 blocks + merlin: 5000, + // The rpc limits to a max of 1000 blocks + xlayer: 1000, + // The rpc limits to a max of 1000 blocks + dogechain: 5000, +}; + export function getTimelockConfigs({ chains, owners, @@ -149,3 +171,202 @@ export function getTimelockConfigs({ return timelockConfigs; } + +const TX_FETCH_RETRIES = 5; +const TX_FETCH_RETRY_DELAY = 5000; + +export enum TimelockOperationStatus { + PENDING = '🟡', + READY_TO_EXECUTE = '🟢', +} + +type TimelockTransactionStatus = { + chain: ChainName; + id: string; + executeTransactionData: HexString; + predecessorId: HexString; + salt: HexString; + timelockAddress: Address; + status: TimelockOperationStatus; + canSignerExecute: boolean; +}; + +async function getPendingTimelockTxsOnChain( + chain: ChainName, + timelockAddress: Address, + multiProvider: MultiProvider, +): Promise { + const reader = EvmTimelockReader.fromConfig({ + chain, + multiProvider, + timelockAddress, + paginationBlockRange: rpcBlockRangesByChain[chain] ?? 10_000, + }); + + let scheduledTxs: Awaited< + ReturnType + >; + try { + scheduledTxs = await retryAsync( + () => reader.getPendingScheduledOperations(), + TX_FETCH_RETRIES, + TX_FETCH_RETRY_DELAY, + ); + } catch (error) { + rootLogger.error( + chalk.red( + `Failed to fetch pending transactions for Timelock "${timelockAddress}" on ${chain} after ${TX_FETCH_RETRIES} attempts: ${error}`, + ), + ); + return; + } + + if (!scheduledTxs || isObjEmpty(scheduledTxs)) { + rootLogger.info( + chalk.gray.italic( + `No pending transactions found for Timelock ${timelockAddress} on ${chain}`, + ), + ); + return; + } + + const scheduledTxIds = Object.keys(scheduledTxs); + const [readyTransactionIds, canSignerExecute] = await Promise.all([ + reader.getReadyOperationIds(scheduledTxIds), + reader.canExecuteOperations(await multiProvider.getSignerAddress(chain)), + ]); + + return Object.values(scheduledTxs).map((tx): TimelockTransactionStatus => { + return { + chain, + canSignerExecute, + executeTransactionData: getTimelockExecutableTransactionFromBatch(tx), + id: tx.id, + predecessorId: tx.predecessor, + salt: tx.salt, + status: !readyTransactionIds.has(tx.id) + ? TimelockOperationStatus.PENDING + : TimelockOperationStatus.READY_TO_EXECUTE, + timelockAddress, + }; + }); +} + +export async function getPendingTimelockTxs( + chains: ChainName[], + multiProvider: MultiProvider, + timelocks: ChainMap
, +): Promise { + const timelockTransactions: ChainMap = {}; + + await Promise.all( + chains.map(async (chain) => { + const timelockAddress = timelocks[chain]; + + if (!timelockAddress) { + rootLogger.info( + chalk.gray.italic( + `Skipping chain ${chain} as it does not have a Timelock deployment`, + ), + ); + return; + } + + const maybeTxs = await getPendingTimelockTxsOnChain( + chain, + timelockAddress, + multiProvider, + ); + + if (!maybeTxs) { + return; + } + + timelockTransactions[chain] = maybeTxs; + }), + ); + + return Object.values(timelockTransactions).flatMap((txs) => txs); +} + +export async function deleteTimelockTx( + chain: ChainName, + timelockAddress: Address, + operationId: HexString, + multiProvider: MultiProvider, +): Promise { + const timelockInstance = TimelockController__factory.connect( + timelockAddress, + multiProvider.getSigner(chain), + ); + + const isPendingOperation = + await timelockInstance.isOperationPending(operationId); + if (!isPendingOperation) { + rootLogger.error( + `Timelock operation with id ${operationId} on chain ${chain} does not exist or is not pending`, + ); + return; + } + + const signerAddress = await multiProvider.getSignerAddress(chain); + const canCancel = await timelockInstance.hasRole( + CANCELLER_ROLE, + signerAddress, + ); + if (!canCancel) { + rootLogger.error( + `Current signer "${signerAddress}" does not have permission to cancel transaction on timelock "${timelockAddress}" on chain "${chain}"`, + ); + return; + } + + const cancelTx = await timelockInstance.cancel(operationId); + await cancelTx.wait(); + + rootLogger.info( + `Successfully cancelled timelock operation "${operationId}" on chain ${chain} at tx "${cancelTx.hash}"`, + ); +} + +export async function cancelAllTimelockTxs( + chains: ChainName[], + timelocks: ChainMap
, + multiProvider: MultiProvider, +): Promise { + await Promise.all( + chains.map(async (chain) => { + const timelockAddress = timelocks[chain]; + + if (!timelockAddress) { + rootLogger.info( + chalk.gray.italic( + `Skipping chain ${chain} as it does not have a Timelock deployment`, + ), + ); + return; + } + + try { + const maybePendingTxs = await getPendingTimelockTxsOnChain( + chain, + timelockAddress, + multiProvider, + ); + + if (!maybePendingTxs) { + return; + } + + for (const { id } of maybePendingTxs) { + await deleteTimelockTx(chain, timelockAddress, id, multiProvider); + } + } catch (err) { + rootLogger.error( + `Error deleting pending transactions for Timelock "${timelockAddress}" on "${chain}":`, + err, + ); + } + }), + ); +} diff --git a/typescript/infra/src/utils/utils.ts b/typescript/infra/src/utils/utils.ts index ee498581f41..b09a64801f6 100644 --- a/typescript/infra/src/utils/utils.ts +++ b/typescript/infra/src/utils/utils.ts @@ -158,7 +158,7 @@ function ensureDirectoryExists(filepath: string) { } } -function writeToFile(filepath: string, content: string) { +export function writeToFile(filepath: string, content: string) { ensureDirectoryExists(filepath); fs.writeFileSync(filepath, content + '\n'); } @@ -206,7 +206,11 @@ export function assertRole(roleStr: string) { export function assertFundableRole(roleStr: string): FundableRole { const role = roleStr as Role; - if (role !== Role.Relayer && role !== Role.Kathy) { + if ( + role !== Role.Relayer && + role !== Role.Kathy && + role !== Role.Rebalancer + ) { throw Error(`Invalid fundable role ${role}`); } return role; diff --git a/typescript/infra/src/warp/helm.ts b/typescript/infra/src/warp/helm.ts index e0cd0c324be..76488fd2293 100644 --- a/typescript/infra/src/warp/helm.ts +++ b/typescript/infra/src/warp/helm.ts @@ -96,7 +96,7 @@ export class WarpRouteMonitorHelmManager extends HelmManager { return { image: { repository: 'gcr.io/abacus-labs-dev/hyperlane-monorepo', - tag: '037ad48-20250707-164323', + tag: '5abccc4-20250915-165639', }, warpRouteId: this.warpRouteId, fullnameOverride: this.helmReleaseName, diff --git a/typescript/infra/src/xerc20/utils.ts b/typescript/infra/src/xerc20/utils.ts index 0a652b54a03..fff638702b9 100644 --- a/typescript/infra/src/xerc20/utils.ts +++ b/typescript/infra/src/xerc20/utils.ts @@ -9,15 +9,23 @@ import { } from '@hyperlane-xyz/core'; import { ChainName, + EvmXERC20Adapter, EvmXERC20VSAdapter, MultiProtocolProvider, MultiProvider, TokenType, WarpCoreConfig, WarpRouteDeployConfig, + XERC20Type, isXERC20TokenConfig, } from '@hyperlane-xyz/sdk'; -import { Address, CallData, eqAddress, rootLogger } from '@hyperlane-xyz/utils'; +import { + Address, + CallData, + assert, + eqAddress, + rootLogger, +} from '@hyperlane-xyz/utils'; import { getRegistry } from '../../config/registry.js'; import { @@ -35,18 +43,26 @@ export const XERC20_BRIDGES_CONFIG_PATH = join( 'scripts/xerc20/config.yaml', ); -export interface BridgeConfig { +type BridgeConfigBase = { chain: string; type: TokenType.XERC20Lockbox | TokenType.XERC20; xERC20Address: Address; bridgeAddress: Address; decimals: number; owner: Address; +}; + +type BridgeConfigVS = BridgeConfigBase & { bufferCap: number; rateLimitPerSecond: number; -} +}; -export async function addBridgeToChain({ +type BridgeConfigWL = BridgeConfigBase & { + mint: number; + burn: number; +}; + +export async function addBridgeToChainXERC20VS({ chain, bridgeConfig, multiProtocolProvider, @@ -54,7 +70,7 @@ export async function addBridgeToChain({ dryRun, }: { chain: string; - bridgeConfig: BridgeConfig; + bridgeConfig: BridgeConfigVS; multiProtocolProvider: MultiProtocolProvider; envMultiProvider: MultiProvider; dryRun: boolean; @@ -145,6 +161,86 @@ export async function addBridgeToChain({ } } +export async function addBridgeToChainForXERC20Standard({ + chain, + bridgeConfig, + multiProtocolProvider, + envMultiProvider, + dryRun, +}: { + chain: string; + bridgeConfig: BridgeConfigWL; + multiProtocolProvider: MultiProtocolProvider; + envMultiProvider: MultiProvider; + dryRun: boolean; +}) { + const { xERC20Address, bridgeAddress, mint, burn, decimals } = bridgeConfig; + const xERC20Adapter = new EvmXERC20Adapter(chain, multiProtocolProvider, { + token: xERC20Address, + }); + + const mintInConfig = BigInt(mint); + const burnInConfig = BigInt(burn); + + try { + const { mint, burn } = await xERC20Adapter.getLimits(bridgeAddress); + if (mint === mintInConfig || burn === burnInConfig) { + rootLogger.warn( + chalk.yellow( + `[${chain}][${bridgeAddress}] Skipping set mint/burn limit. Actual and expected are the same: ${humanReadableLimit(mint, decimals)} mint limit, ${humanReadableLimit(burn, decimals)} burn limit.`, + ), + ); + return; + } + + const tx = await xERC20Adapter.populateSetLimitsTx( + bridgeAddress, + mintInConfig, + burnInConfig, + ); + + rootLogger.info( + chalk.gray( + `[${chain}][${bridgeAddress}] Preparing to add bridge to ${xERC20Address}`, + ), + ); + rootLogger.info( + chalk.gray( + `[${chain}][${bridgeAddress}] Mint limit: ${humanReadableLimit( + mintInConfig, + decimals, + )}, burn limit: ${humanReadableLimit(mintInConfig, decimals)}`, + ), + ); + + if (!dryRun) { + rootLogger.info( + chalk.gray( + `[${chain}][${bridgeAddress}] Sending addBridge transaction to ${xERC20Address}...`, + ), + ); + await sendTransactions( + envMultiProvider, + chain, + [tx], + xERC20Address, + bridgeAddress, + ); + } else { + rootLogger.info( + chalk.gray( + `[${chain}][${bridgeAddress}] Dry run, no transactions sent, exiting...`, + ), + ); + } + } catch (error) { + rootLogger.error( + chalk.red(`[${chain}][${bridgeAddress}] Error adding bridge:`, error), + ); + throw { chain, error }; + } +} + export async function updateChainLimits({ chain, bridgeConfig, @@ -153,7 +249,7 @@ export async function updateChainLimits({ dryRun, }: { chain: string; - bridgeConfig: BridgeConfig; + bridgeConfig: BridgeConfigVS; multiProtocolProvider: MultiProtocolProvider; envMultiProvider: MultiProvider; dryRun: boolean; @@ -521,8 +617,8 @@ export async function deriveBridgesConfig( warpDeployConfig: WarpRouteDeployConfig, warpCoreConfig: WarpCoreConfig, multiProvider: MultiProvider, -): Promise { - const bridgesConfig: BridgeConfig[] = []; +): Promise { + const bridgesConfig: BridgeConfigVS[] = []; for (const [chainName, chainConfig] of Object.entries(warpDeployConfig)) { if (!isXERC20TokenConfig(chainConfig)) { @@ -540,8 +636,14 @@ export async function deriveBridgesConfig( throw new Error(`Missing "decimals" for chain: ${chainName}`); } + if (!xERC20 || xERC20.warpRouteLimits.type !== XERC20Type.Velo) { + rootLogger.debug( + `Skip deriving bridges config because ${XERC20Type.Velo} type is expected`, + ); + continue; + } + if ( - !xERC20 || !xERC20.warpRouteLimits.bufferCap || !xERC20.warpRouteLimits.rateLimitPerSecond ) { @@ -578,6 +680,10 @@ export async function deriveBridgesConfig( if (xERC20.extraBridges) { for (const extraLockboxLimit of xERC20.extraBridges) { const { lockbox, limits } = extraLockboxLimit; + assert( + limits.type === XERC20Type.Velo, + `Only supports ${XERC20Type.Velo}`, + ); const { bufferCap: extraBufferCap, rateLimitPerSecond: extraRateLimit, @@ -603,7 +709,7 @@ export async function deriveBridgesConfig( } bridgesConfig.push({ - chain: chainName as ChainName, + chain: chainName, type, xERC20Address, bridgeAddress, @@ -617,6 +723,115 @@ export async function deriveBridgesConfig( return bridgesConfig; } +export async function deriveStandardBridgesConfig( + chains: ChainName[] = [], + warpDeployConfig: WarpRouteDeployConfig, + warpCoreConfig: WarpCoreConfig, + multiProvider: MultiProvider, +): Promise { + const bridgesConfig: BridgeConfigWL[] = []; + + for (const [chainName, chainConfig] of Object.entries(warpDeployConfig)) { + if (chains.length > 0 && !chains.includes(chainName)) { + rootLogger.debug( + `Skipping ${chainName} because its not included in chains`, + ); + continue; + } + + if (!isXERC20TokenConfig(chainConfig)) { + throw new Error( + `Chain "${chainName}" is not an xERC20 compliant deployment`, + ); + } + + const { token, type, owner, xERC20 } = chainConfig; + + const decimals = warpCoreConfig.tokens.find( + (t) => t.chainName === chainName, + )?.decimals; + if (!decimals) { + throw new Error(`Missing "decimals" for chain: ${chainName}`); + } + + if (!xERC20 || xERC20.warpRouteLimits.type !== XERC20Type.Standard) { + rootLogger.debug( + `Skip deriving bridges config because ${XERC20Type.Standard} type is expected`, + ); + continue; + } + if (!xERC20.warpRouteLimits.mint || !xERC20.warpRouteLimits.burn) { + throw new Error(`Missing "limits" for chain: ${chainName}`); + } + + let xERC20Address = token; + const bridgeAddress = warpCoreConfig.tokens.find( + (t) => t.chainName === chainName, + )?.addressOrDenom; + if (!bridgeAddress) { + throw new Error( + `Missing router address for chain ${chainName} and type ${type}`, + ); + } + + const mint = Number(xERC20.warpRouteLimits.mint); + const burn = Number(xERC20.warpRouteLimits.burn); + + if (type === TokenType.XERC20Lockbox) { + const provider = multiProvider.getProvider(chainName); + const hypXERC20Lockbox = HypXERC20Lockbox__factory.connect( + bridgeAddress, + provider, + ); + + xERC20Address = await hypXERC20Lockbox.xERC20(); + } + + if (xERC20.extraBridges) { + for (const extraLockboxLimit of xERC20.extraBridges) { + const { lockbox, limits } = extraLockboxLimit; + assert( + limits.type === XERC20Type.Standard, + `Only supports ${XERC20Type.Standard}`, + ); + + const extraBridgeMint = Number(limits.mint); + const extraBridgeBurn = Number(limits.burn); + + if (!extraBridgeMint || !extraBridgeBurn) { + throw new Error( + `Missing "extraBridgeMint" or "extraBridgeBurn" limits for extra lockbox: ${lockbox} on chain: ${chainName}`, + ); + } + + bridgesConfig.push({ + chain: chainName, + type, + xERC20Address, + bridgeAddress: lockbox, + owner, + decimals, + mint: extraBridgeMint, + burn: extraBridgeBurn, + }); + } + } + + bridgesConfig.push({ + chain: chainName, + type, + xERC20Address, + bridgeAddress, + owner, + decimals, + mint, + burn, + }); + } + + return bridgesConfig; +} + export function getWarpConfigsAndArtifacts(warpRouteId: string): { warpDeployConfig: WarpRouteDeployConfig; warpCoreConfig: WarpCoreConfig; @@ -641,10 +856,10 @@ function humanReadableLimit(limit: bigint, decimals: number): string { .toString(); } -export function getAndValidateBridgesToUpdate( +export function getAndValidateBridgesToUpdate( chains: string[] | undefined, - bridgesConfig: BridgeConfig[], -): BridgeConfig[] { + bridgesConfig: T[], +): T[] { // if no chains are provided, return all configs if (!chains || chains.length === 0) { return bridgesConfig; diff --git a/typescript/infra/test/environment.test.ts b/typescript/infra/test/environment.test.ts index dbfbcd613ab..c7f681dbd1b 100644 --- a/typescript/infra/test/environment.test.ts +++ b/typescript/infra/test/environment.test.ts @@ -86,13 +86,16 @@ describe('Environment', () => { const routingIsmDomains = routingIsm.domains; // Check that domains includes all chains except the local one - const expectedChains = supportedChainNames.filter((c) => c !== chain); + const expectedChains = supportedChainNames + .filter((c) => c !== chain) + .filter((c) => c !== 'forma'); // Verify no unexpected chains in domains expect(Object.keys(routingIsmDomains)).to.have.lengthOf( expectedChains.length, ); expect(Object.keys(routingIsmDomains)).to.not.include(chain); + expect(Object.keys(routingIsmDomains)).to.not.include('forma'); // Verify each expected chain has an entry in the domains for (const expectedChain of expectedChains) { diff --git a/typescript/infra/test/gitleaks.test.ts b/typescript/infra/test/gitleaks.test.ts new file mode 100644 index 00000000000..c7897bd1e5d --- /dev/null +++ b/typescript/infra/test/gitleaks.test.ts @@ -0,0 +1,726 @@ +import { Keypair } from '@solana/web3.js'; +import { expect } from 'chai'; +import { execSync } from 'child_process'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { + TomlTable, + parse as parseToml, + stringify as stringifyToml, +} from 'smol-toml'; + +import { bufferToBase58, setEquality } from '@hyperlane-xyz/utils'; + +import { readFileAtPath, writeToFile } from '../src/utils/utils.js'; + +// DO NOT run this test in the merge queue as gitleaks is not supported there +if (process.env.GITHUB_EVENT_NAME !== 'merge_group') { + describe('GitLeaks CLI Integration Tests', function () { + let tempDir: string; + let configPath: string; + let ruleIds: string[]; + + let gitLeaksConfig: TomlTable; + + before(function () { + const originalConfigPath = path.join( + process.cwd(), + '../../', + '.gitleaks.toml', + ); + + if (!fs.existsSync(originalConfigPath)) { + throw new Error( + `GitLeaks config not found at ${originalConfigPath}. Please ensure gitleaks.toml exists in the project root.`, + ); + } + + // Remove the allowlist from the original file to allow secret detection from the temporary file + gitLeaksConfig = parseToml(readFileAtPath(originalConfigPath)); + delete gitLeaksConfig.allowlist; + + ruleIds = (gitLeaksConfig.rules as Array<{ id: string }>).map( + ({ id }) => id, + ); + }); + + beforeEach(function () { + // Create temporary directory and write the config + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gitleaks-test-')); + configPath = path.join(tempDir, 'test-gitleaks.toml'); + writeToFile(configPath, stringifyToml(gitLeaksConfig)); + }); + + afterEach(function () { + // Clean up temporary directory + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + + function generateSvmPrivateKey(): string { + return bufferToBase58(Buffer.from(generateBufferSvmPrivateKey())); + } + + function generateBufferSvmPrivateKey(): Uint8Array { + return Keypair.generate().secretKey; + } + + interface GitLeaksResult { + Description: string; + StartLine: number; + EndLine: number; + StartColumn: number; + EndColumn: number; + Match: string; + Secret: string; + File: string; + SymlinkFile: string; + Commit: string; + Entropy: number; + Author: string; + Email: string; + Date: string; + Message: string; + Tags: string[]; + RuleID: string; + Fingerprint: string; + } + + interface BaseTestCase { + name: string; + content: string; + description?: string; + } + + interface SuccessTestCase extends BaseTestCase { + expectedRuleId: string; + expectedCount?: number; + } + + interface FailureTestCase extends BaseTestCase {} + + interface RuleTestGroup { + ruleId: string; + ruleName: string; + successTestCases: SuccessTestCase[]; + failureTestCases: FailureTestCase[]; + } + + function runGitleaksSuccessTest(testCase: SuccessTestCase): void { + const testFilePath = path.join(tempDir, 'test-file.js'); + writeToFile(testFilePath, testCase.content); + + const reportPath = path.join(tempDir, 'gitleaks-report.json'); + + try { + execSync( + `gitleaks directory "${tempDir}" --config="${configPath}" --report-format=json --report-path="${reportPath}" --no-banner`, + { encoding: 'utf8', stdio: 'pipe' }, + ); + + throw new Error( + `Expected gitleaks to find secrets but it returned success for test: ${testCase.name}`, + ); + } catch (error: any) { + if (error.status === 1) { + // Gitleaks found secrets (exit code 1) - this is expected + let results: GitLeaksResult[] = []; + try { + // Read results from the report file + if (fs.existsSync(reportPath)) { + const reportContent = fs.readFileSync(reportPath, 'utf8'); + if (reportContent.trim()) { + results = JSON.parse(reportContent) as GitLeaksResult[]; + } + } else { + throw new Error( + `Gitleaks report file not found at ${reportPath} for test ${testCase.name}`, + ); + } + } catch (parseError) { + throw new Error( + `Failed to parse gitleaks JSON report for test ${testCase.name}: ${parseError}`, + ); + } + + // Validate results + expect(results).to.have.length.greaterThan( + 0, + `Expected to find secrets but got empty results for test: ${testCase.name}`, + ); + + const ruleIds = results.map((r) => r.RuleID); + expect(ruleIds).to.include( + testCase.expectedRuleId, + `Expected rule ID ${testCase.expectedRuleId} but found: ${ruleIds.join(', ')}`, + ); + + if (testCase.expectedCount) { + expect(results).to.have.length( + testCase.expectedCount, + `Expected ${testCase.expectedCount} results but got ${results.length}`, + ); + } + } else { + throw new Error( + `Gitleaks execution failed for test ${testCase.name}: ${error.message}`, + ); + } + } + } + + function runGitleaksFailureTest(testCase: FailureTestCase): void { + const testFilePath = path.join(tempDir, 'test-file.js'); + writeToFile(testFilePath, testCase.content); + + const reportPath = path.join(tempDir, 'gitleaks-report.json'); + + try { + execSync( + `gitleaks directory "${tempDir}" --config="${configPath}" --report-format=json --report-path="${reportPath}" --no-banner`, + { encoding: 'utf8', stdio: 'pipe' }, + ); + + // No secrets found, which was expected + return; + } catch (error: any) { + if (error.status === 1) { + // Gitleaks found secrets (exit code 1) - this is unexpected for failure tests + throw new Error( + `Gitleaks unexpectedly found secrets for test: ${testCase.name}`, + ); + } else { + throw new Error( + `Gitleaks execution failed for test ${testCase.name}: ${error.message}`, + ); + } + } + } + + // Test data organized by rule + const ruleTestGroups: RuleTestGroup[] = [ + { + ruleId: 'alchemy-api-key', + ruleName: 'Alchemy API Key Detection', + successTestCases: [ + { + name: 'should detect Alchemy API key in JavaScript config', + content: `const config = { rpcUrl: "https://eth-mainnet.g.alchemy.com/v2/your-api-key-here" };`, + expectedRuleId: 'alchemy-api-key', + }, + { + name: 'should detect Alchemy API key in environment file', + content: `ALCHEMY_URL=https://polygon-mainnet.g.alchemy.com/v2/abc123def456`, + expectedRuleId: 'alchemy-api-key', + }, + { + name: 'should detect Alchemy API key in JSON', + content: `{ "providers": { "alchemy": "https://arbitrum-mainnet.g.alchemy.com/v2/test-key-123" } }`, + expectedRuleId: 'alchemy-api-key', + }, + ], + failureTestCases: [ + { + name: 'should not detect invalid Alchemy URL (missing subdomain)', + content: `const config = { rpcUrl: "https://alchemy.com/v2/not-a-real-api-key" };`, + }, + { + name: 'should not detect invalid Alchemy URL (wrong version)', + content: `const config = { rpcUrl: "https://eth-mainnet.g.alchemy.com/v3/api-key" };`, + }, + { + name: 'should not detect Alchemy docs URL', + content: `const docs = "https://docs.alchemy.com/guides";`, + }, + ], + }, + { + ruleId: 'ankr-api-key', + ruleName: 'Ankr API Key Detection', + successTestCases: [ + { + name: 'should detect Ankr API key', + content: `export const ANKR_RPC = "https://rpc.ankr.com/eth/your-api-key";`, + expectedRuleId: 'ankr-api-key', + }, + { + name: 'should detect Ankr API key with different network', + content: `const polygonRpc = "https://rpc.ankr.com/polygon/abc123_def-456";`, + expectedRuleId: 'ankr-api-key', + }, + { + name: 'should detect Ankr API key in YAML', + content: `networks:\n mainnet: "https://rpc.ankr.com/arbitrum/test-key"`, + expectedRuleId: 'ankr-api-key', + }, + ], + failureTestCases: [ + { + name: 'should not detect invalid Ankr URL (missing rpc subdomain)', + content: `const url = "https://ankr.com/eth/api-key";`, + }, + { + name: 'should not detect incomplete Ankr URL', + content: `const url = "https://rpc.ankr.com/api-key";`, + }, + ], + }, + { + ruleId: 'tenderly-api-key', + ruleName: 'Tenderly API Key Detection', + successTestCases: [ + { + name: 'should detect Tenderly API key in JSON', + content: `{ "rpc": "https://mainnet.gateway.tenderly.co/your-api-key" }`, + expectedRuleId: 'tenderly-api-key', + }, + { + name: 'should detect Tenderly API key with network prefix', + content: `const rpc = "https://polygon-mainnet.gateway.tenderly.co/abc123_def-456";`, + expectedRuleId: 'tenderly-api-key', + }, + { + name: 'should detect Tenderly API key with complex subdomain', + content: `rpcUrl: "https://test-network.gateway.tenderly.co/key123"`, + expectedRuleId: 'tenderly-api-key', + }, + ], + failureTestCases: [ + { + name: 'should not detect invalid Tenderly URL (missing gateway)', + content: `const url = "https://tenderly.co/api-key";`, + }, + { + name: 'should not detect invalid Tenderly URL (wrong subdomain)', + content: `const url = "https://mainnet.tenderly.co/api-key";`, + }, + ], + }, + { + ruleId: 'quicknode-api-key', + ruleName: 'QuickNode API Key Detection', + successTestCases: [ + { + name: 'should detect QuickNode API key', + content: `const provider = new ethers.providers.JsonRpcProvider("https://mainnet.ethereum.quiknode.pro/abc123def456");`, + expectedRuleId: 'quicknode-api-key', + }, + { + name: 'should detect QuickNode API key with different network', + content: `const rpc = "https://polygon-main.rpc.quiknode.pro/def456";`, + expectedRuleId: 'quicknode-api-key', + }, + { + name: 'should detect QuickNode API key with hyphenated subdomain', + content: `endpoint: "https://test-node.arbitrum.quiknode.pro/xyz789"`, + expectedRuleId: 'quicknode-api-key', + }, + ], + failureTestCases: [ + { + name: 'should not detect invalid QuickNode URL (missing second subdomain)', + content: `const url = "https://quiknode.pro/abc123";`, + }, + { + name: 'should not detect invalid QuickNode URL (single subdomain)', + content: `const url = "https://mainnet.quiknode.pro/abc123";`, + }, + ], + }, + { + ruleId: 'drpc-api-key', + ruleName: 'DRPC API Key Detection', + successTestCases: [ + { + name: 'should detect DRPC API key with dkey parameter', + content: `const rpcUrl = "https://lb.drpc.org/ogrpc?network=ethereum&dkey=your-secret-key";`, + expectedRuleId: 'drpc-api-key', + }, + { + name: 'should detect DRPC API key with multiple parameters', + content: `const url = "https://lb.drpc.org/oghttp?network=polygon&dkey=def456&other=param";`, + expectedRuleId: 'drpc-api-key', + }, + { + name: 'should detect DRPC API key with dkey at end', + content: `rpc: "https://lb.drpc.org/endpoint123?param=value&dkey=xyz789"`, + expectedRuleId: 'drpc-api-key', + }, + ], + failureTestCases: [ + { + name: 'should not detect invalid DRPC URL (missing lb subdomain)', + content: `const url = "https://drpc.org/ogrpc?dkey=abc123";`, + }, + { + name: 'should not detect DRPC URL without dkey parameter', + content: `const url = "https://lb.drpc.org/ogrpc?network=ethereum";`, + }, + ], + }, + { + ruleId: 'dwellir-api-key', + ruleName: 'Dwellir API Key Detection', + successTestCases: [ + { + name: 'should detect Dwellir API key', + content: `DWELLIR_API=https://api-mainnet.dwellir.com/your-api-key`, + expectedRuleId: 'dwellir-api-key', + }, + { + name: 'should detect Dwellir API key with complex subdomain', + content: `const rpc = "https://api-polygon-mainnet.dwellir.com/def456";`, + expectedRuleId: 'dwellir-api-key', + }, + { + name: 'should detect Dwellir API key with hyphenated path', + content: `endpoint: "https://api-test.dwellir.com/xyz-789"`, + expectedRuleId: 'dwellir-api-key', + }, + ], + failureTestCases: [ + { + name: 'should not detect invalid Dwellir URL (missing api prefix)', + content: `const url = "https://dwellir.com/abc123";`, + }, + { + name: 'should not detect invalid Dwellir URL (wrong subdomain)', + content: `const url = "https://mainnet.dwellir.com/abc123";`, + }, + ], + }, + { + ruleId: 'startale-api-key', + ruleName: 'Startale API Key Detection', + successTestCases: [ + { + name: 'should detect Startale API key', + content: `const rpc = "https://mainnet.startale.com/rpc?apikey=secretkey123";`, + expectedRuleId: 'startale-api-key', + }, + { + name: 'should detect Startale API key with path', + content: `const url = "https://test-network.startale.com/api/v1?apikey=def456";`, + expectedRuleId: 'startale-api-key', + }, + { + name: 'should detect Startale API key with multiple parameters', + content: `rpc: "https://polygon.rpc.startale.com?param=value&apikey=xyz789"`, + expectedRuleId: 'startale-api-key', + }, + ], + failureTestCases: [ + { + name: 'should not detect invalid Startale URL (missing subdomain)', + content: `const url = "https://startale.com?apikey=abc123";`, + }, + { + name: 'should not detect Startale URL without apikey', + content: `const url = "https://mainnet.startale.com/rpc?key=abc123";`, + }, + ], + }, + { + ruleId: 'grove-city-api-key', + ruleName: 'Grove City API Key Detection', + successTestCases: [ + { + name: 'should detect Grove City API key', + content: `fetch("https://mainnet.rpc.grove.city/v1/your-api-key")`, + expectedRuleId: 'grove-city-api-key', + }, + { + name: 'should detect Grove City API key with network prefix', + content: `const rpc = "https://polygon-mainnet.rpc.grove.city/v1/def456";`, + expectedRuleId: 'grove-city-api-key', + }, + { + name: 'should detect Grove City API key with hyphenated subdomain', + content: `endpoint: "https://test-network.rpc.grove.city/v1/xyz789"`, + expectedRuleId: 'grove-city-api-key', + }, + ], + failureTestCases: [ + { + name: 'should not detect invalid Grove City URL (missing rpc subdomain)', + content: `const url = "https://grove.city/v1/abc123";`, + }, + { + name: 'should not detect invalid Grove City URL (wrong subdomain)', + content: `const url = "https://mainnet.grove.city/v1/abc123";`, + }, + ], + }, + { + ruleId: 'ccvalidators-api-key', + ruleName: 'CryptoCrew API Key Detection', + successTestCases: [ + { + name: 'should detect CCValidators RPC endpoint', + content: `const rpcEndpoint = "https://rpc.mainnet.ccvalidators.com:443/cosmos";`, + expectedRuleId: 'ccvalidators-api-key', + }, + { + name: 'should detect CCValidators GRPC endpoint', + content: `grpc: "https://grpc.polygon.ccvalidators.com"`, + expectedRuleId: 'ccvalidators-api-key', + }, + { + name: 'should detect CCValidators REST endpoint with port', + content: `rest: "https://rest.arbitrum.ccvalidators.com:9090"`, + expectedRuleId: 'ccvalidators-api-key', + }, + { + name: 'should detect CCValidators endpoint with path', + content: `endpoint: "https://rpc.test-network.ccvalidators.com/api-endpoint"`, + expectedRuleId: 'ccvalidators-api-key', + }, + ], + failureTestCases: [ + { + name: 'should not detect invalid CCValidators URL (wrong prefix)', + content: `const url = "https://api.mainnet.ccvalidators.com";`, + }, + { + name: 'should not detect invalid CCValidators URL (missing subdomain)', + content: `const url = "https://ccvalidators.com";`, + }, + ], + }, + { + ruleId: 'ccnodes-api-key', + ruleName: 'CryptoCrew Nodes API Key Detection', + successTestCases: [ + { + name: 'should detect CCNodes API endpoint', + content: `grpcEndpoint: "https://polygon.grpc.ccnodes.com:9090"`, + expectedRuleId: 'ccnodes-api-key', + }, + { + name: 'should detect CCNodes endpoint without port', + content: `const rpc = "https://mainnet.rpc.ccnodes.com";`, + expectedRuleId: 'ccnodes-api-key', + }, + { + name: 'should detect CCNodes endpoint with path', + content: `rest: "https://arbitrum.rest.ccnodes.com/cosmos"`, + expectedRuleId: 'ccnodes-api-key', + }, + { + name: 'should detect CCNodes endpoint with port and path', + content: `api: "https://test-network.api.ccnodes.com:443/endpoint"`, + expectedRuleId: 'ccnodes-api-key', + }, + ], + failureTestCases: [ + { + name: 'should not detect invalid CCNodes URL (single subdomain)', + content: `const url = "https://mainnet.ccnodes.com";`, + }, + { + name: 'should not detect invalid CCNodes URL (no subdomain)', + content: `const url = "https://ccnodes.com";`, + }, + ], + }, + { + ruleId: 'svm-cli-private-key', + ruleName: 'Solana CLI Private Key Detection', + successTestCases: [ + { + name: 'should detect Solana CLI private key (compact byte array)', + content: `const keypair = [174,47,154,16,202,193,206,113,199,190,53,133,169,175,31,56,222,53,138,189,224,216,117,173,10,149,53,45,73,228,128,239,168,187,184,9,166,75,164,42,11,58,142,55,91,112,101,50,6,169,105,178,118,191,165,17,138,149,85,184,157,86,205,37];`, + expectedRuleId: 'svm-cli-private-key', + }, + { + name: 'should detect Solana CLI private key (formatted with whitespace)', + content: `const keypair = [ + 174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56, + 222, 53, 138, 189, 224, 216, 117, 173, 10, 149, 53, 45, 73, 228, 128, 239, + 168, 187, 184, 9, 166, 75, 164, 42, 11, 58, 142, 55, 91, 112, 101, 50, + 6, 169, 105, 178, 118, 191, 165, 17, 138, 149, 85, 184, 157, 86, 205, 37 + ];`, + expectedRuleId: 'svm-cli-private-key', + }, + { + name: 'should detect Solana CLI private key (irregular spacing)', + content: `const key = [ 255,0,128,64,32,16,8,4,2,1,255,0,128,64,32,16,8,4,2,1,255,0,128,64,32,16,8,4,2,1,255,0,128,64,32,16,8,4,2,1,255,0,128,64,32,16,8,4,2,1,255,0,128,64,32,16,8,4,2,1,255,0,128,64 ];`, + expectedRuleId: 'svm-cli-private-key', + }, + { + name: 'should detect Solana CLI private key in JSON config', + content: `{ + "keypairs": { + "wallet1": [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64] + } + }`, + expectedRuleId: 'svm-cli-private-key', + }, + ], + failureTestCases: [ + { + name: 'should not detect array too short (63 elements)', + content: `const shortArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63];`, + }, + { + name: 'should not detect array too long (65 elements)', + content: `const longArray = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65];`, + }, + { + name: 'should not detect regular short array', + content: `const shortArray = [1, 2, 3, 4, 5];`, + }, + ], + }, + { + ruleId: 'svm-base58-private-key', + ruleName: 'Solana Base58 Private Key Detection', + successTestCases: [ + { + name: 'should detect Solana Base58 private key', + content: `${generateSvmPrivateKey()}`, + expectedRuleId: 'svm-base58-private-key', + }, + { + name: 'should detect Solana Base58 private key in a js file', + content: `const privateKey = "${generateSvmPrivateKey()}";`, + expectedRuleId: 'svm-base58-private-key', + }, + { + name: 'should detect Base58 key in JSON wallet config', + content: `{ + "wallet": { + "privateKey": "${generateSvmPrivateKey()}" + } + }`, + expectedRuleId: 'svm-base58-private-key', + }, + { + name: 'should detect Base58 key in environment variable', + content: `SOLANA_PRIVATE_KEY=${generateSvmPrivateKey()}`, + expectedRuleId: 'svm-base58-private-key', + }, + { + name: 'should detect Base58 key with mixed case', + content: `const key = "${generateSvmPrivateKey()}";`, + expectedRuleId: 'svm-base58-private-key', + }, + ], + failureTestCases: [ + { + name: 'should not detect Base58 key too short', + content: `const invalidKey = "${generateSvmPrivateKey().slice(0, -2)}";`, + }, + { + name: 'should not detect Base58 key too long', + content: `const invalidKey = "${generateSvmPrivateKey()}eVe";`, + }, + { + name: 'should not detect Base58 key with invalid character 0', + content: `const invalidKey = "${generateSvmPrivateKey().slice(0, -1)}0";`, + }, + { + name: 'should not detect Base58 key with invalid character O', + content: `const invalidKey = "${generateSvmPrivateKey().slice(0, -1)}O";`, + }, + { + name: 'should not detect Base58 key with invalid character I', + content: `const invalidKey = "${generateSvmPrivateKey().slice(0, -1)}I";`, + }, + { + name: 'should not detect Base58 key with invalid character l', + content: `const invalidKey = "${generateSvmPrivateKey().slice(0, -1)}l";`, + }, + ], + }, + ]; + + it('Rule ids in the configuration file and in the test cases match', function () { + const ruleIdsInTestCases = ruleTestGroups.map(({ ruleId }) => ruleId); + + expect( + setEquality(new Set(ruleIds), new Set(ruleIdsInTestCases)), + 'Expected rule ids in gitleaks config file to match rule ids in test cases', + ).to.be.true; + }); + + // Generate tests for each rule + ruleTestGroups.forEach((ruleGroup) => { + describe(ruleGroup.ruleName, function () { + ruleGroup.successTestCases.forEach((testCase) => { + it(testCase.name, function () { + runGitleaksSuccessTest(testCase); + }); + }); + + ruleGroup.failureTestCases.forEach((testCase) => { + it(testCase.name, function () { + runGitleaksFailureTest(testCase); + }); + }); + }); + }); + + describe('Combined Scenarios', function () { + const combinedSuccessTestCases: SuccessTestCase[] = [ + { + name: 'should detect multiple different secrets in one file', + content: `export const config = { + alchemy: "https://eth-mainnet.g.alchemy.com/v2/secret-key", + ankr: "https://rpc.ankr.com/eth/another-secret", + tenderly: "https://mainnet.gateway.tenderly.co/tenderly-key", + solanaWallet: "${generateSvmPrivateKey()}", + solanaKeypair: [${Array.from(generateBufferSvmPrivateKey()).join(',')}] +};`, + expectedRuleId: 'alchemy-api-key', + expectedCount: 5, + }, + { + name: 'should detect mixed format Solana keys', + content: `const wallets = { + wallet1: "${generateSvmPrivateKey()}", + wallet2: [${Array.from(generateBufferSvmPrivateKey()).join(',')}] +};`, + expectedRuleId: 'svm-base58-private-key', + expectedCount: 2, + }, + ]; + + const combinedFailureTestCases: FailureTestCase[] = [ + { + name: 'should not detect secrets in safe content', + content: `const config = { + apiUrl: "https://example.com/api/v1", + timeout: 5000, + retries: 3, + normalArray: [1, 2, 3, 4, 5], + docs: "https://docs.example.com", + regularString: "AbCdEfGhJkMnPqRsUvWx" // too short for base58 +};`, + }, + { + name: 'should prevent false positives with similar but safe URLs', + content: `const urls = { + docs: "https://docs.alchemy.com", + ankrDocs: "https://ankr.com/docs", + example: "https://example.g.alchemy.com/v2/", // Missing API key + almostValid: "https://rpc.ankr.com/api-key", // Missing network + tenderlyDocs: "https://docs.tenderly.co/guides" +};`, + }, + ]; + + combinedSuccessTestCases.forEach((testCase) => { + it(testCase.name, function () { + runGitleaksSuccessTest(testCase); + }); + }); + + combinedFailureTestCases.forEach((testCase) => { + it(testCase.name, function () { + runGitleaksFailureTest(testCase); + }); + }); + }); + }); +} diff --git a/typescript/infra/test/rebalancer.test.ts b/typescript/infra/test/rebalancer.test.ts new file mode 100644 index 00000000000..2d69477677b --- /dev/null +++ b/typescript/infra/test/rebalancer.test.ts @@ -0,0 +1,29 @@ +import { expect } from 'chai'; + +import { environments } from '../config/environments/index.js'; +import { CCTP_CHAINS } from '../config/environments/mainnet3/warp/configGetters/getCCTPConfig.js'; + +describe('Rebalancer Configuration', () => { + describe('Funding configuration for mainnet3', () => { + it('should have desired rebalancer balance settings for all mainnet CCTP warp route chains', () => { + const env = environments.mainnet3; + expect(env.keyFunderConfig).to.not.be.undefined; + const rebalancerBalances = + env.keyFunderConfig!.desiredRebalancerBalancePerChain; + + // Check that all CCTP chains have rebalancer balance settings + for (const chain of CCTP_CHAINS) { + expect( + rebalancerBalances[chain], + `Missing rebalancer balance for CCTP chain ${chain}. All chains in the mainnet CCTP warp route should have desired rebalancer balance settings.`, + ).to.not.be.undefined; + + // Also verify it's a valid numeric string + expect( + parseFloat(rebalancerBalances[chain]), + `Invalid rebalancer balance for chain ${chain}: ${rebalancerBalances[chain]}`, + ).to.be.a('number'); + } + }); + }); +}); diff --git a/typescript/radix-sdk/.gitignore b/typescript/radix-sdk/.gitignore new file mode 100644 index 00000000000..849ddff3b7e --- /dev/null +++ b/typescript/radix-sdk/.gitignore @@ -0,0 +1 @@ +dist/ diff --git a/typescript/radix-sdk/.mocharc-e2e.json b/typescript/radix-sdk/.mocharc-e2e.json new file mode 100644 index 00000000000..44c560068b3 --- /dev/null +++ b/typescript/radix-sdk/.mocharc-e2e.json @@ -0,0 +1,8 @@ +{ + "extensions": ["ts"], + "spec": ["src/tests/index.e2e-test.ts"], + "node-option": [ + "experimental-specifier-resolution=node", + "loader=ts-node/esm" + ] +} diff --git a/typescript/radix-sdk/CHANGELOG.md b/typescript/radix-sdk/CHANGELOG.md new file mode 100644 index 00000000000..1348e60ad88 --- /dev/null +++ b/typescript/radix-sdk/CHANGELOG.md @@ -0,0 +1,21 @@ +# @hyperlane-xyz/radix-sdk + +## 18.2.0 + +### Patch Changes + +- @hyperlane-xyz/utils@18.2.0 + +## 18.1.0 + +### Patch Changes + +- 73be9b8d2: Don't use radix-engine-toolkit for frontend application usage. + - @hyperlane-xyz/utils@18.1.0 + +## 18.0.0 + +### Patch Changes + +- Updated dependencies [cfc0eb2a7] + - @hyperlane-xyz/utils@18.0.0 diff --git a/typescript/radix-sdk/README.md b/typescript/radix-sdk/README.md new file mode 100644 index 00000000000..1735afa8e7f --- /dev/null +++ b/typescript/radix-sdk/README.md @@ -0,0 +1,43 @@ +# Hyperlane Radix SDK + +The Hyperlane Radix SDK is a fully typed TypeScript SDK for the [Radix Implementation](https://github.com/hyperlane-xyz/hyperlane-radix). +It can be used as a standalone SDK for frontend or in backend applications which want to connect to a Radix chain which has the Hyperlane blueprint installed. + +## Install + +```bash +# Install with NPM +npm install @hyperlane-xyz/radix-sdk + +# Or with Yarn +yarn add @hyperlane-xyz/radix-sdk +``` + +## Usage + +```ts +import { RadixSDK, RadixSigningSDK } from "@hyperlane-xyz/radix-sdk"; + +const signingSdk = await RadixSigningSDK.fromPrivateKey( + PRIV_KEY, + { + networkId: NetworkId.Stokenet, + }, +); + +const mailboxAddress = await signingSdk.tx.createMailbox({ domain_id: 75898670 }); + +const mailbox = await signingSdk.query.getMailbox({ mailbox: mailboxAddress }); +... + +// performing queries without signer +const sdk = new RadixSDK({ + networkId: NetworkId.Stokenet, +}) + +const mailbox = await signingSdk.query.getMailbox({ mailbox: mailboxAddress }); +``` + +## Setup + +Node 18 or newer is required. diff --git a/typescript/radix-sdk/eslint.config.mjs b/typescript/radix-sdk/eslint.config.mjs new file mode 100644 index 00000000000..18ef8a9fa54 --- /dev/null +++ b/typescript/radix-sdk/eslint.config.mjs @@ -0,0 +1,11 @@ +import MonorepoDefaults from '../../eslint.config.mjs'; + +export default [ + ...MonorepoDefaults, + { + files: ['src/**/*.ts'], + }, + { + ignores: ['src/tests/**/*.ts'], + }, +]; diff --git a/typescript/radix-sdk/package.json b/typescript/radix-sdk/package.json new file mode 100644 index 00000000000..8cfb2629d3b --- /dev/null +++ b/typescript/radix-sdk/package.json @@ -0,0 +1,55 @@ +{ + "name": "@hyperlane-xyz/radix-sdk", + "version": "18.2.0", + "description": "Hyperlane TypeScript SDK for the Radix Hyperlane SDK module", + "type": "module", + "exports": { + ".": "./dist/index.js" + }, + "types": "dist/index.d.ts", + "files": [ + "/dist" + ], + "homepage": "https://www.hyperlane.xyz", + "repository": "https://github.com/hyperlane-xyz/hyperlane-monorepo", + "keywords": [ + "hyperlane", + "radix", + "blockchain", + "sdk" + ], + "license": "Apache-2.0", + "scripts": { + "build": "rm -rf ./dist && tsc", + "format": "prettier --write .", + "lint": "eslint -c ./eslint.config.mjs", + "prettier": "prettier --write ./src", + "clean": "rm -rf ./dist ./cache", + "test": "echo \"no tests in radix-sdk\"", + "test:ci": "echo \"no tests in radix-sdk\"", + "test:e2e": "echo \"no e2e tests in radix-sdk\"" + }, + "devDependencies": { + "@eslint/js": "^9.31.0", + "@hyperlane-xyz/tsconfig": "workspace:^", + "@types/mocha": "^10.0.1", + "@typescript-eslint/eslint-plugin": "^8.1.6", + "@typescript-eslint/parser": "^8.1.6", + "eslint": "^9.31.0", + "eslint-config-prettier": "^10.1.8", + "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-import": "^2.32.0", + "mocha": "^11.5.0", + "mocha-steps": "^1.3.0", + "prettier": "^3.5.3", + "typescript": "5.3.3", + "typescript-eslint": "^8.37.0" + }, + "dependencies": { + "@hyperlane-xyz/utils": "18.2.0", + "@radixdlt/babylon-core-api-sdk": "^1.3.0", + "@radixdlt/babylon-gateway-api-sdk": "^1.10.1", + "@radixdlt/radix-engine-toolkit": "^1.0.5", + "bignumber.js": "^9.1.1" + } +} diff --git a/typescript/radix-sdk/src/core/populate.ts b/typescript/radix-sdk/src/core/populate.ts new file mode 100644 index 00000000000..c19cc6699db --- /dev/null +++ b/typescript/radix-sdk/src/core/populate.ts @@ -0,0 +1,366 @@ +import { GatewayApiClient } from '@radixdlt/babylon-gateway-api-sdk'; +import { + ValueKind, + address, + array, + tuple, + u32, + u64, + u128, +} from '@radixdlt/radix-engine-toolkit'; + +import { strip0x } from '@hyperlane-xyz/utils'; + +import { RadixBase } from '../utils/base.js'; +import { + EntityDetails, + INSTRUCTIONS, + RadixHookTypes, + RadixIsmTypes, +} from '../utils/types.js'; +import { bytes } from '../utils/utils.js'; + +export class RadixCorePopulate { + protected gateway: GatewayApiClient; + protected base: RadixBase; + protected packageAddress: string; + + constructor( + gateway: GatewayApiClient, + base: RadixBase, + packageAddress: string, + ) { + this.gateway = gateway; + this.base = base; + this.packageAddress = packageAddress; + } + + public createMailbox({ + from_address, + domain_id, + }: { + from_address: string; + domain_id: number; + }) { + return this.base.createCallFunctionManifest( + from_address, + this.packageAddress, + 'Mailbox', + INSTRUCTIONS.INSTANTIATE, + [u32(domain_id)], + ); + } + + public createMerkleTreeHook({ + from_address, + mailbox, + }: { + from_address: string; + mailbox: string; + }) { + return this.base.createCallFunctionManifest( + from_address, + this.packageAddress, + RadixHookTypes.MERKLE_TREE, + INSTRUCTIONS.INSTANTIATE, + [address(mailbox)], + ); + } + + public createMerkleRootMultisigIsm({ + from_address, + validators, + threshold, + }: { + from_address: string; + validators: string[]; + threshold: number; + }) { + return this.base.createCallFunctionManifest( + from_address, + this.packageAddress, + RadixIsmTypes.MERKLE_ROOT_MULTISIG, + INSTRUCTIONS.INSTANTIATE, + [ + array(ValueKind.Array, ...validators.map((v) => bytes(strip0x(v)))), + u64(threshold), + ], + ); + } + + public createMessageIdMultisigIsm({ + from_address, + validators, + threshold, + }: { + from_address: string; + validators: string[]; + threshold: number; + }) { + return this.base.createCallFunctionManifest( + from_address, + this.packageAddress, + RadixIsmTypes.MESSAGE_ID_MULTISIG, + INSTRUCTIONS.INSTANTIATE, + [ + array(ValueKind.Array, ...validators.map((v) => bytes(strip0x(v)))), + u64(threshold), + ], + ); + } + + public createRoutingIsm({ + from_address, + routes, + }: { + from_address: string; + routes: { ism: string; domain: number }[]; + }) { + return this.base.createCallFunctionManifest( + from_address, + this.packageAddress, + RadixIsmTypes.ROUTING_ISM, + INSTRUCTIONS.INSTANTIATE, + [ + array( + ValueKind.Tuple, + ...routes.map((r) => tuple(u32(r.domain), address(r.ism))), + ), + ], + ); + } + + public async setRoutingIsmRoute({ + from_address, + ism, + route, + }: { + from_address: string; + ism: string; + route: { domain: number; ism_address: string }; + }) { + return this.base.createCallMethodManifestWithOwner( + from_address, + ism, + 'set_route', + [u32(route.domain), address(route.ism_address)], + ); + } + + public async removeRoutingIsmRoute({ + from_address, + ism, + domain, + }: { + from_address: string; + ism: string; + domain: number; + }) { + return this.base.createCallMethodManifestWithOwner( + from_address, + ism, + 'remove_route', + [u32(domain)], + ); + } + + public async setRoutingIsmOwner({ + from_address, + ism, + new_owner, + }: { + from_address: string; + ism: string; + new_owner: string; + }) { + const details = + await this.gateway.state.getEntityDetailsVaultAggregated(ism); + + const resource = (details.details as EntityDetails).role_assignments.owner + .rule.access_rule.proof_rule.requirement.resource; + + return this.base.transfer({ + from_address, + to_address: new_owner, + resource_address: resource, + amount: '1', + }); + } + + public createNoopIsm({ from_address }: { from_address: string }) { + return this.base.createCallFunctionManifest( + from_address, + this.packageAddress, + RadixIsmTypes.NOOP_ISM, + INSTRUCTIONS.INSTANTIATE, + [], + ); + } + + public createIgp({ + from_address, + denom, + }: { + from_address: string; + denom: string; + }) { + return this.base.createCallFunctionManifest( + from_address, + this.packageAddress, + RadixHookTypes.IGP, + INSTRUCTIONS.INSTANTIATE, + [address(denom)], + ); + } + + public async setIgpOwner({ + from_address, + igp, + new_owner, + }: { + from_address: string; + igp: string; + new_owner: string; + }) { + const details = + await this.gateway.state.getEntityDetailsVaultAggregated(igp); + + const resource = (details.details as EntityDetails).role_assignments.owner + .rule.access_rule.proof_rule.requirement.resource; + + return this.base.transfer({ + from_address, + to_address: new_owner, + resource_address: resource, + amount: '1', + }); + } + + public async setDestinationGasConfig({ + from_address, + igp, + destination_gas_config, + }: { + from_address: string; + igp: string; + destination_gas_config: { + remote_domain: string; + gas_oracle: { + token_exchange_rate: string; + gas_price: string; + }; + gas_overhead: string; + }; + }) { + return this.base.createCallMethodManifestWithOwner( + from_address, + igp, + 'set_destination_gas_configs', + [ + array( + ValueKind.Tuple, + tuple( + u32(destination_gas_config.remote_domain), + tuple( + tuple( + u128(destination_gas_config.gas_oracle.token_exchange_rate), + u128(destination_gas_config.gas_oracle.gas_price), + ), + u128(destination_gas_config.gas_overhead), + ), + ), + ), + ], + ); + } + + public async setMailboxOwner({ + from_address, + mailbox, + new_owner, + }: { + from_address: string; + mailbox: string; + new_owner: string; + }) { + const details = + await this.gateway.state.getEntityDetailsVaultAggregated(mailbox); + + const resource = (details.details as EntityDetails).role_assignments.owner + .rule.access_rule.proof_rule.requirement.resource; + + return this.base.transfer({ + from_address, + to_address: new_owner, + resource_address: resource, + amount: '1', + }); + } + + public createValidatorAnnounce({ + from_address, + mailbox, + }: { + from_address: string; + mailbox: string; + }) { + return this.base.createCallFunctionManifest( + from_address, + this.packageAddress, + 'ValidatorAnnounce', + INSTRUCTIONS.INSTANTIATE, + [address(mailbox)], + ); + } + + public async setRequiredHook({ + from_address, + mailbox, + hook, + }: { + from_address: string; + mailbox: string; + hook: string; + }) { + return this.base.createCallMethodManifestWithOwner( + from_address, + mailbox, + 'set_required_hook', + [address(hook)], + ); + } + + public async setDefaultHook({ + from_address, + mailbox, + hook, + }: { + from_address: string; + mailbox: string; + hook: string; + }) { + return this.base.createCallMethodManifestWithOwner( + from_address, + mailbox, + 'set_default_hook', + [address(hook)], + ); + } + + public async setDefaultIsm({ + from_address, + mailbox, + ism, + }: { + from_address: string; + mailbox: string; + ism: string; + }) { + return this.base.createCallMethodManifestWithOwner( + from_address, + mailbox, + 'set_default_ism', + [address(ism)], + ); + } +} diff --git a/typescript/radix-sdk/src/core/query.ts b/typescript/radix-sdk/src/core/query.ts new file mode 100644 index 00000000000..c256a60c35c --- /dev/null +++ b/typescript/radix-sdk/src/core/query.ts @@ -0,0 +1,312 @@ +import { GatewayApiClient } from '@radixdlt/babylon-gateway-api-sdk'; + +import { assert, ensure0x } from '@hyperlane-xyz/utils'; + +import { RadixBase } from '../utils/base.js'; +import { + EntityDetails, + EntityField, + Hooks, + Isms, + MultisigIsms, + RadixHookTypes, +} from '../utils/types.js'; + +export class RadixCoreQuery { + protected networkId: number; + protected gateway: GatewayApiClient; + protected base: RadixBase; + + constructor(networkId: number, gateway: GatewayApiClient, base: RadixBase) { + this.networkId = networkId; + this.gateway = gateway; + this.base = base; + } + + public async getMailbox({ mailbox }: { mailbox: string }): Promise<{ + address: string; + owner: string; + local_domain: number; + nonce: number; + default_ism: string; + default_hook: string; + required_hook: string; + }> { + const details = + await this.gateway.state.getEntityDetailsVaultAggregated(mailbox); + const fields = (details.details as EntityDetails).state.fields; + + const ownerResource = (details.details as EntityDetails).role_assignments + .owner.rule.access_rule.proof_rule.requirement.resource; + + const { items } = + await this.gateway.extensions.getResourceHolders(ownerResource); + + const resourceHolders = [ + ...new Set(items.map((item) => item.holder_address)), + ]; + + assert( + resourceHolders.length === 1, + `expected token holders of resource ${ownerResource} to be one, found ${resourceHolders.length} holders instead`, + ); + + const result = { + address: mailbox, + owner: resourceHolders[0], + local_domain: parseInt( + fields.find((f) => f.field_name === 'local_domain')?.value ?? '0', + ), + nonce: parseInt( + fields.find((f) => f.field_name === 'nonce')?.value ?? '0', + ), + default_ism: + fields.find((f) => f.field_name === 'default_ism')?.fields?.at(0) + ?.value ?? '', + default_hook: + fields.find((f) => f.field_name === 'default_hook')?.fields?.at(0) + ?.value ?? '', + required_hook: + fields.find((f) => f.field_name === 'required_hook')?.fields?.at(0) + ?.value ?? '', + }; + + return result; + } + + public async getIsmType({ ism }: { ism: string }): Promise { + const details = + await this.gateway.state.getEntityDetailsVaultAggregated(ism); + + return (details.details as EntityDetails).blueprint_name as Isms; + } + + public async getMultisigIsm({ ism }: { ism: string }): Promise<{ + address: string; + type: MultisigIsms; + threshold: number; + validators: string[]; + }> { + const details = + await this.gateway.state.getEntityDetailsVaultAggregated(ism); + + const fields = (details.details as EntityDetails).state.fields; + + const result = { + address: ism, + type: (details.details as EntityDetails).blueprint_name as MultisigIsms, + validators: ( + fields.find((f) => f.field_name === 'validators')?.elements ?? [] + ).map((v) => ensure0x(v.hex)), + threshold: parseInt( + fields.find((f) => f.field_name === 'threshold')?.value ?? '0', + ), + }; + + return result; + } + + public async getRoutingIsm({ ism }: { ism: string }): Promise<{ + address: string; + owner: string; + routes: { + domain: number; + ism: string; + }[]; + }> { + const details = + await this.gateway.state.getEntityDetailsVaultAggregated(ism); + + const ownerResource = (details.details as EntityDetails).role_assignments + .owner.rule.access_rule.proof_rule.requirement.resource; + + const { items: holders } = + await this.gateway.extensions.getResourceHolders(ownerResource); + + const resourceHolders = [ + ...new Set(holders.map((item) => item.holder_address)), + ]; + + assert( + resourceHolders.length === 1, + `expected token holders of resource ${ownerResource} to be one, found ${resourceHolders.length} holders instead`, + ); + + const type = (details.details as EntityDetails).blueprint_name; + assert( + type === 'RoutingIsm', + `ism is not a RoutingIsm, instead got ${type}`, + ); + + const fields = (details.details as EntityDetails).state.fields; + + const routesKeyValueStore = + fields.find((f) => f.field_name === 'routes')?.value ?? ''; + assert(routesKeyValueStore, `found no routes on RoutingIsm ${ism}`); + + const { items } = await this.gateway.state.innerClient.keyValueStoreKeys({ + stateKeyValueStoreKeysRequest: { + key_value_store_address: routesKeyValueStore, + }, + }); + + const routes = []; + + for (const { key } of items) { + const { entries } = + await this.gateway.state.innerClient.keyValueStoreData({ + stateKeyValueStoreDataRequest: { + key_value_store_address: routesKeyValueStore, + keys: [ + { + key_hex: key.raw_hex, + }, + ], + }, + }); + + const domain = parseInt( + (key.programmatic_json as EntityField)?.value ?? '0', + ); + const ism = (entries[0].value.programmatic_json as EntityField).value; + + routes.push({ + domain, + ism, + }); + } + + return { + address: ism, + owner: resourceHolders[0], + routes, + }; + } + + public async getHookType({ hook }: { hook: string }): Promise { + const details = + await this.gateway.state.getEntityDetailsVaultAggregated(hook); + + return (details.details as EntityDetails).blueprint_name as Hooks; + } + + public async getIgpHook({ hook }: { hook: string }): Promise<{ + address: string; + owner: string; + destination_gas_configs: { + [domain_id: string]: { + gas_oracle: { + token_exchange_rate: string; + gas_price: string; + }; + gas_overhead: string; + }; + }; + }> { + const details = + await this.gateway.state.getEntityDetailsVaultAggregated(hook); + + assert( + (details.details as EntityDetails).blueprint_name === RadixHookTypes.IGP, + `Expected contract at address ${hook} to be "${RadixHookTypes.IGP}" but got ${(details.details as EntityDetails).blueprint_name}`, + ); + + const ownerResource = (details.details as EntityDetails).role_assignments + .owner.rule.access_rule.proof_rule.requirement.resource; + + const { items: holders } = + await this.gateway.extensions.getResourceHolders(ownerResource); + + const resourceHolders = [ + ...new Set(holders.map((item) => item.holder_address)), + ]; + + assert( + resourceHolders.length === 1, + `expected token holders of resource ${ownerResource} to be one, found ${resourceHolders.length} holders instead`, + ); + + const fields = (details.details as EntityDetails).state.fields; + + const destinationGasConfigsKeyValueStore = + fields.find((f) => f.field_name === 'destination_gas_configs')?.value ?? + ''; + + assert( + destinationGasConfigsKeyValueStore, + `found no destination gas configs on hook ${hook}`, + ); + + const destination_gas_configs = {}; + + const { items } = await this.gateway.state.innerClient.keyValueStoreKeys({ + stateKeyValueStoreKeysRequest: { + key_value_store_address: destinationGasConfigsKeyValueStore, + }, + }); + + for (const { key } of items) { + const { entries } = + await this.gateway.state.innerClient.keyValueStoreData({ + stateKeyValueStoreDataRequest: { + key_value_store_address: destinationGasConfigsKeyValueStore, + keys: [ + { + key_hex: key.raw_hex, + }, + ], + }, + }); + + const remoteDomain = (key.programmatic_json as EntityField)?.value ?? '0'; + + const gasConfigFields = ( + entries[0].value.programmatic_json as EntityField + ).fields; + + const gasOracleFields = + gasConfigFields?.find((r) => r.field_name === 'gas_oracle')?.fields ?? + []; + + Object.assign(destination_gas_configs, { + [remoteDomain]: { + gas_oracle: { + token_exchange_rate: + gasOracleFields.find( + (r) => r.field_name === 'token_exchange_rate', + )?.value ?? '0', + gas_price: + gasOracleFields.find((r) => r.field_name === 'gas_price') + ?.value ?? '0', + }, + gas_overhead: + gasConfigFields?.find((r) => r.field_name === 'gas_overhead') + ?.value ?? '0', + }, + }); + } + + return { + address: hook, + owner: resourceHolders[0], + destination_gas_configs, + }; + } + + public async getMerkleTreeHook({ hook }: { hook: string }): Promise<{ + address: string; + }> { + const details = + await this.gateway.state.getEntityDetailsVaultAggregated(hook); + + assert( + (details.details as EntityDetails).blueprint_name === + RadixHookTypes.MERKLE_TREE, + `Expected contract at address ${hook} to be "${RadixHookTypes.MERKLE_TREE}" but got ${(details.details as EntityDetails).blueprint_name}`, + ); + + return { + address: hook, + }; + } +} diff --git a/typescript/radix-sdk/src/core/tx.ts b/typescript/radix-sdk/src/core/tx.ts new file mode 100644 index 00000000000..f9997d5b712 --- /dev/null +++ b/typescript/radix-sdk/src/core/tx.ts @@ -0,0 +1,317 @@ +import { assert } from '@hyperlane-xyz/utils'; + +import { RadixBase } from '../utils/base.js'; +import { RadixSigner } from '../utils/signer.js'; +import { Account, MultisigIsmReq } from '../utils/types.js'; + +import { RadixCorePopulate } from './populate.js'; + +export class RadixCoreTx { + private account: Account; + + protected base: RadixBase; + protected populate: RadixCorePopulate; + protected signer: RadixSigner; + + constructor( + account: Account, + base: RadixBase, + signer: RadixSigner, + populate: RadixCorePopulate, + ) { + this.account = account; + this.base = base; + this.signer = signer; + this.populate = populate; + } + + public async transfer({ + to_address, + resource_address, + amount, + }: { + to_address: string; + resource_address: string; + amount: string; + }) { + const metadata = await this.base.getMetadata({ + resource: resource_address, + }); + assert( + metadata, + `resource with address ${resource_address} does not exist`, + ); + + const transactionManifest = await this.base.transfer({ + from_address: this.account.address, + to_address, + resource_address, + amount, + }); + + await this.signer.signAndBroadcast(transactionManifest); + } + + public async createMailbox({ domain_id }: { domain_id: number }) { + const transactionManifest = await this.populate.createMailbox({ + from_address: this.account.address, + domain_id, + }); + + const intentHashTransactionId = + await this.signer.signAndBroadcast(transactionManifest); + + return this.base.getNewComponent(intentHashTransactionId); + } + + public async createMerkleTreeHook({ mailbox }: { mailbox: string }) { + const transactionManifest = await this.populate.createMerkleTreeHook({ + from_address: this.account.address, + mailbox, + }); + + const intentHashTransactionId = + await this.signer.signAndBroadcast(transactionManifest); + + return this.base.getNewComponent(intentHashTransactionId); + } + + public async createMerkleRootMultisigIsm({ + validators, + threshold, + }: MultisigIsmReq) { + const transactionManifest = await this.populate.createMerkleRootMultisigIsm( + { + from_address: this.account.address, + validators, + threshold, + }, + ); + + const intentHashTransactionId = + await this.signer.signAndBroadcast(transactionManifest); + + return this.base.getNewComponent(intentHashTransactionId); + } + + public async createMessageIdMultisigIsm({ + validators, + threshold, + }: MultisigIsmReq) { + const transactionManifest = await this.populate.createMessageIdMultisigIsm({ + from_address: this.account.address, + validators, + threshold, + }); + + const intentHashTransactionId = + await this.signer.signAndBroadcast(transactionManifest); + + return this.base.getNewComponent(intentHashTransactionId); + } + + public async createRoutingIsm({ + routes, + }: { + routes: { ism: string; domain: number }[]; + }) { + const transactionManifest = await this.populate.createRoutingIsm({ + from_address: this.account.address, + routes, + }); + + const intentHashTransactionId = + await this.signer.signAndBroadcast(transactionManifest); + + return this.base.getNewComponent(intentHashTransactionId); + } + + public async setRoutingIsmRoute({ + ism, + route, + }: { + ism: string; + route: { + domain: number; + ism_address: string; + }; + }) { + const transactionManifest = await this.populate.setRoutingIsmRoute({ + from_address: this.account.address, + ism, + route, + }); + + await this.signer.signAndBroadcast(transactionManifest); + } + + public async removeRoutingIsmRoute({ + ism, + domain, + }: { + ism: string; + domain: number; + }) { + const transactionManifest = await this.populate.removeRoutingIsmRoute({ + from_address: this.account.address, + ism, + domain, + }); + + await this.signer.signAndBroadcast(transactionManifest); + } + + public async setRoutingIsmOwner({ + ism, + new_owner, + }: { + ism: string; + new_owner: string; + }) { + const transactionManifest = await this.populate.setRoutingIsmOwner({ + from_address: this.account.address, + ism, + new_owner, + }); + + await this.signer.signAndBroadcast(transactionManifest); + } + + public async createNoopIsm() { + const transactionManifest = await this.populate.createNoopIsm({ + from_address: this.account.address, + }); + + const intentHashTransactionId = + await this.signer.signAndBroadcast(transactionManifest); + + return this.base.getNewComponent(intentHashTransactionId); + } + + public async createIgp({ denom }: { denom: string }) { + const transactionManifest = await this.populate.createIgp({ + from_address: this.account.address, + denom, + }); + + const intentHashTransactionId = + await this.signer.signAndBroadcast(transactionManifest); + + return this.base.getNewComponent(intentHashTransactionId); + } + + public async setIgpOwner({ + igp, + new_owner, + }: { + igp: string; + new_owner: string; + }) { + const transactionManifest = await this.populate.setIgpOwner({ + from_address: this.account.address, + igp, + new_owner, + }); + + await this.signer.signAndBroadcast(transactionManifest); + } + + public async setDestinationGasConfig({ + igp, + destination_gas_config, + }: { + igp: string; + destination_gas_config: { + remote_domain: string; + gas_oracle: { + token_exchange_rate: string; + gas_price: string; + }; + gas_overhead: string; + }; + }) { + const transactionManifest = await this.populate.setDestinationGasConfig({ + from_address: this.account.address, + igp, + destination_gas_config, + }); + + await this.signer.signAndBroadcast(transactionManifest); + } + + public async setMailboxOwner({ + mailbox, + new_owner, + }: { + mailbox: string; + new_owner: string; + }) { + const transactionManifest = await this.populate.setMailboxOwner({ + from_address: this.account.address, + mailbox, + new_owner, + }); + + await this.signer.signAndBroadcast(transactionManifest); + } + + public async createValidatorAnnounce({ mailbox }: { mailbox: string }) { + const transactionManifest = await this.populate.createValidatorAnnounce({ + from_address: this.account.address, + mailbox, + }); + + const intentHashTransactionId = + await this.signer.signAndBroadcast(transactionManifest); + + return this.base.getNewComponent(intentHashTransactionId); + } + + public async setRequiredHook({ + mailbox, + hook, + }: { + mailbox: string; + hook: string; + }) { + const transactionManifest = await this.populate.setRequiredHook({ + from_address: this.account.address, + mailbox, + hook, + }); + + await this.signer.signAndBroadcast(transactionManifest); + } + + public async setDefaultHook({ + mailbox, + hook, + }: { + mailbox: string; + hook: string; + }) { + const transactionManifest = await this.populate.setDefaultHook({ + from_address: this.account.address, + mailbox, + hook, + }); + + await this.signer.signAndBroadcast(transactionManifest); + } + + public async setDefaultIsm({ + mailbox, + ism, + }: { + mailbox: string; + ism: string; + }) { + const transactionManifest = await this.populate.setDefaultIsm({ + from_address: this.account.address, + mailbox, + ism, + }); + + await this.signer.signAndBroadcast(transactionManifest); + } +} diff --git a/typescript/radix-sdk/src/index.ts b/typescript/radix-sdk/src/index.ts new file mode 100644 index 00000000000..944d8f6dfe6 --- /dev/null +++ b/typescript/radix-sdk/src/index.ts @@ -0,0 +1,154 @@ +import { GatewayApiClient } from '@radixdlt/babylon-gateway-api-sdk'; +import { NetworkId } from '@radixdlt/radix-engine-toolkit'; +import { utils } from 'ethers'; + +import { assert, strip0x } from '@hyperlane-xyz/utils'; + +import { RadixCorePopulate } from './core/populate.js'; +import { RadixCoreQuery } from './core/query.js'; +import { RadixCoreTx } from './core/tx.js'; +import { RadixBase } from './utils/base.js'; +import { RadixSigner } from './utils/signer.js'; +import { Account, RadixSDKOptions } from './utils/types.js'; +import { generateNewEd25519VirtualAccount } from './utils/utils.js'; +import { RadixWarpPopulate } from './warp/populate.js'; +import { RadixWarpQuery } from './warp/query.js'; +import { RadixWarpTx } from './warp/tx.js'; + +const NETWORKS = { + [NetworkId.Stokenet]: { + applicationName: 'hyperlane', + packageAddress: + 'package_tdx_2_1pkn2zdcw8q8rax6mxetdkgp7493mf379afhq7a7peh4wnftz3zej4h', + }, + [NetworkId.Mainnet]: { + applicationName: 'hyperlane', + packageAddress: + 'package_rdx1pk3ldj3ktxuw6sv5txspjt2a8s42c7xxcn6wnf5yuytdrcqhpflfkc', + }, +}; + +export { NetworkId }; +export { RadixIsmTypes } from './utils/types.js'; + +export const DEFAULT_GAS_MULTIPLIER = 1.2; + +export class RadixSDK { + protected networkId: number; + protected gateway: GatewayApiClient; + + public applicationName: string; + public packageAddress: string; + + public base: RadixBase; + public query: { + core: RadixCoreQuery; + warp: RadixWarpQuery; + }; + public populate: { + core: RadixCorePopulate; + warp: RadixWarpPopulate; + }; + + constructor(options?: RadixSDKOptions) { + this.networkId = options?.networkId ?? NetworkId.Mainnet; + + assert( + NETWORKS[this.networkId], + `Network with id ${this.networkId} not supported with the Hyperlane RadixSDK. Supported network ids: ${Object.keys(NETWORKS).join(', ')}`, + ); + + this.applicationName = NETWORKS[this.networkId].applicationName; + this.packageAddress = NETWORKS[this.networkId].packageAddress; + + this.gateway = GatewayApiClient.initialize({ + applicationName: this.applicationName, + networkId: this.networkId, + }); + + this.base = new RadixBase( + this.networkId, + this.gateway, + options?.gasMultiplier ?? DEFAULT_GAS_MULTIPLIER, + ); + + this.query = { + core: new RadixCoreQuery(this.networkId, this.gateway, this.base), + warp: new RadixWarpQuery(this.networkId, this.gateway, this.base), + }; + + this.populate = { + core: new RadixCorePopulate(this.gateway, this.base, this.packageAddress), + warp: new RadixWarpPopulate( + this.gateway, + this.base, + this.query.warp, + this.packageAddress, + ), + }; + } + + public getNetworkId() { + return this.networkId; + } +} + +export class RadixSigningSDK extends RadixSDK { + private account: Account; + + public tx: { + core: RadixCoreTx; + warp: RadixWarpTx; + }; + public signer: RadixSigner; + + private constructor(account: Account, options?: RadixSDKOptions) { + super(options); + + this.account = account; + this.signer = new RadixSigner( + this.networkId, + this.gateway, + this.base, + this.account, + ); + this.tx = { + core: new RadixCoreTx( + account, + this.base, + this.signer, + this.populate.core, + ), + warp: new RadixWarpTx( + account, + this.base, + this.signer, + this.populate.warp, + ), + }; + } + + public getAddress() { + return this.account.address; + } + + public static async fromRandomPrivateKey(options?: RadixSDKOptions) { + const privateKey = Buffer.from(utils.randomBytes(32)).toString('hex'); + const account = await generateNewEd25519VirtualAccount( + privateKey, + options?.networkId ?? NetworkId.Mainnet, + ); + return new RadixSigningSDK(account, options); + } + + public static async fromPrivateKey( + privateKey: string, + options?: RadixSDKOptions, + ) { + const account = await generateNewEd25519VirtualAccount( + strip0x(privateKey), + options?.networkId ?? NetworkId.Mainnet, + ); + return new RadixSigningSDK(account, options); + } +} diff --git a/typescript/radix-sdk/src/utils/base.ts b/typescript/radix-sdk/src/utils/base.ts new file mode 100644 index 00000000000..cce416fe989 --- /dev/null +++ b/typescript/radix-sdk/src/utils/base.ts @@ -0,0 +1,443 @@ +import { CostingParameters, FeeSummary } from '@radixdlt/babylon-core-api-sdk'; +import { + GatewayApiClient, + TransactionStatusResponse, +} from '@radixdlt/babylon-gateway-api-sdk'; +import { + LTSRadixEngineToolkit, + ManifestBuilder, + PrivateKey, + RadixEngineToolkit, + TransactionHash, + TransactionManifest, + Value, + address, + bucket, + decimal, + enumeration, + expression, + generateRandomNonce, +} from '@radixdlt/radix-engine-toolkit'; +import { BigNumber } from 'bignumber.js'; +import { Decimal } from 'decimal.js'; +import { utils } from 'ethers'; + +import { assert, retryAsync } from '@hyperlane-xyz/utils'; + +import { EntityDetails, INSTRUCTIONS } from './types.js'; + +export class RadixBase { + protected networkId: number; + protected gateway: GatewayApiClient; + protected gasMultiplier: number; + + constructor( + networkId: number, + gateway: GatewayApiClient, + gasMultiplier: number, + ) { + this.networkId = networkId; + this.gateway = gateway; + this.gasMultiplier = gasMultiplier; + } + + public async getXrdAddress() { + const knownAddresses = await LTSRadixEngineToolkit.Derive.knownAddresses( + this.networkId, + ); + return knownAddresses.resources.xrdResource; + } + + public async isGatewayHealthy(): Promise { + const status = await this.gateway.status.getCurrent(); + return status.ledger_state.state_version > 0; + } + + public async estimateTransactionFee({ + transactionManifest, + }: { + transactionManifest: TransactionManifest | string; + }): Promise<{ gasUnits: bigint; gasPrice: number; fee: bigint }> { + const pk = new PrivateKey.Ed25519(new Uint8Array(utils.randomBytes(32))); + const constructionMetadata = + await this.gateway.transaction.innerClient.transactionConstruction(); + + const manifest = + typeof transactionManifest === 'string' + ? transactionManifest + : (( + await RadixEngineToolkit.Instructions.convert( + transactionManifest.instructions, + this.networkId, + 'String', + ) + ).value as string); + + const response = + await this.gateway.transaction.innerClient.transactionPreview({ + transactionPreviewRequest: { + manifest, + nonce: generateRandomNonce(), + signer_public_keys: [ + { + key_type: 'EddsaEd25519', + key_hex: pk.publicKeyHex(), + }, + ], + flags: { + use_free_credit: true, + // we have to enable this flag because the signer of the tx is a random pk + // this allows us to simulate txs for different addresses - even if we don't have accesse to their public key + assume_all_signature_proofs: true, + }, + start_epoch_inclusive: constructionMetadata.ledger_state.epoch, + end_epoch_exclusive: constructionMetadata.ledger_state.epoch + 2, + }, + }); + + assert( + !(response.receipt as any).error_message, + `${(response.receipt as any).error_message}`, + ); + + const fee_summary: FeeSummary = (response.receipt as any).fee_summary; + const costing_parameters: CostingParameters = (response.receipt as any) + .costing_parameters; + + const gasUnits = + BigInt(fee_summary.execution_cost_units_consumed) + + BigInt(fee_summary.finalization_cost_units_consumed); + const fee = BigInt( + new BigNumber(fee_summary.xrd_total_execution_cost) + .plus(BigNumber(fee_summary.xrd_total_finalization_cost)) + .plus(BigNumber(fee_summary.xrd_total_storage_cost)) + .times(new BigNumber(10).exponentiatedBy(18)) + .toFixed(0), + ); + const gasPrice = + (parseFloat(costing_parameters.execution_cost_unit_price) + + parseFloat(costing_parameters.finalization_cost_unit_price)) * + 0.5; // average out the cost parameters to get a more accurate estimate + + return { + gasUnits, + fee, + gasPrice, + }; + } + + public async getMetadata({ resource }: { resource: string }): Promise<{ + name: string; + symbol: string; + description: string; + divisibility: number; + }> { + const details = + await this.gateway.state.getEntityDetailsVaultAggregated(resource); + + const result = { + name: + ( + details.metadata.items.find((i) => i.key === 'name')?.value + .typed as any + ).value ?? '', + symbol: + ( + details.metadata.items.find((i) => i.key === 'symbol')?.value + .typed as any + ).value ?? '', + description: + ( + details.metadata.items.find((i) => i.key === 'description')?.value + .typed as any + ).value ?? '', + divisibility: (details.details as any).divisibility as number, + }; + + return result; + } + + public async getXrdMetadata(): Promise<{ + name: string; + symbol: string; + description: string; + divisibility: number; + }> { + const xrdAddress = await this.getXrdAddress(); + return this.getMetadata({ resource: xrdAddress }); + } + + public async getBalance({ + address, + resource, + }: { + address: string; + resource: string; + }): Promise { + const details = + await this.gateway.state.getEntityDetailsVaultAggregated(address); + + const fungibleResource = details.fungible_resources.items.find( + (r) => r.resource_address === resource, + ); + + if (!fungibleResource) { + return BigInt(0); + } + + if (fungibleResource.vaults.items.length !== 1) { + return BigInt(0); + } + + const { divisibility } = await this.getMetadata({ resource }); + + return BigInt( + new BigNumber(fungibleResource.vaults.items[0].amount) + .times(new BigNumber(10).exponentiatedBy(divisibility)) + .toFixed(0), + ); + } + + public async getXrdBalance({ + address, + }: { + address: string; + }): Promise { + const xrdAddress = await this.getXrdAddress(); + return this.getBalance({ address, resource: xrdAddress }); + } + + public async getTotalSupply({ + resource, + }: { + resource: string; + }): Promise { + const details = + await this.gateway.state.getEntityDetailsVaultAggregated(resource); + + const { divisibility } = await this.getMetadata({ resource }); + + return BigInt( + new BigNumber((details.details as any).total_supply) + .times(new BigNumber(10).exponentiatedBy(divisibility)) + .toFixed(0), + ); + } + + public async getXrdTotalSupply(): Promise { + const xrdAddress = await this.getXrdAddress(); + return this.getTotalSupply({ resource: xrdAddress }); + } + + public async pollForCommit(intentHashTransactionId: string): Promise { + const pollAttempts = 500; + const pollDelayMs = 10000; + + for (let i = 0; i < pollAttempts; i++) { + let statusOutput: TransactionStatusResponse; + + try { + statusOutput = + await this.gateway.transaction.innerClient.transactionStatus({ + transactionStatusRequest: { intent_hash: intentHashTransactionId }, + }); + } catch { + await new Promise((resolve) => setTimeout(resolve, pollDelayMs)); + continue; + } + + switch (statusOutput.intent_status) { + case 'CommittedSuccess': + return; + case 'CommittedFailure': + // You will typically wish to build a new transaction and try again. + throw new Error( + `Transaction ${intentHashTransactionId} was not committed successfully - instead it resulted in: ${statusOutput.intent_status} with description: ${statusOutput.error_message}`, + ); + case 'CommitPendingOutcomeUnknown': + // We keep polling + if (i < pollAttempts) { + await new Promise((resolve) => setTimeout(resolve, pollDelayMs)); + } else { + throw new Error( + `Transaction ${intentHashTransactionId} was not committed successfully within ${pollAttempts} poll attempts over ${ + pollAttempts * pollDelayMs + }ms - instead it resulted in STATUS: ${ + statusOutput.intent_status + } DESCRIPTION: ${statusOutput.intent_status_description}`, + ); + } + } + } + } + + public async getNewComponent(transaction: TransactionHash): Promise { + const transactionReceipt = await retryAsync( + () => this.gateway.transaction.getCommittedDetails(transaction.id), + 5, + 5000, + ); + + const receipt = transactionReceipt.transaction.receipt; + assert(receipt, `found no receipt on transaction: ${transaction.id}`); + + const newGlobalGenericComponent = ( + receipt.state_updates as any + ).new_global_entities.find( + (entity: { entity_type: string }) => + entity.entity_type === 'GlobalGenericComponent', + ); + assert( + newGlobalGenericComponent, + `found no newly created component on transaction: ${transaction.id}`, + ); + + return newGlobalGenericComponent.entity_address; + } + + public async createCallFunctionManifest( + from_address: string, + package_address: string | number, + blueprint_name: string, + function_name: string, + args: Value[], + ) { + const simulationManifest = new ManifestBuilder() + .callMethod(from_address, INSTRUCTIONS.LOCK_FEE, [decimal(0)]) + .callFunction(package_address, blueprint_name, function_name, args) + .callMethod(from_address, INSTRUCTIONS.TRY_DEPOSIT_BATCH_OR_ABORT, [ + expression('EntireWorktop'), + enumeration(0), + ]) + .build(); + + const { fee } = await this.estimateTransactionFee({ + transactionManifest: simulationManifest, + }); + + return new ManifestBuilder() + .callMethod(from_address, INSTRUCTIONS.LOCK_FEE, [ + decimal( + new BigNumber(fee.toString()) + .times(this.gasMultiplier) + .dividedBy(new BigNumber(10).exponentiatedBy(18)) + .toFixed(), + ), + ]) + .callFunction(package_address, blueprint_name, function_name, args) + .callMethod(from_address, INSTRUCTIONS.TRY_DEPOSIT_BATCH_OR_ABORT, [ + expression('EntireWorktop'), + enumeration(0), + ]) + .build(); + } + + public async createCallMethodManifestWithOwner( + from_address: string, + contract_address: string, + method_name: string, + args: Value[], + ) { + const details = + await this.gateway.state.getEntityDetailsVaultAggregated( + contract_address, + ); + + const ownerResource = (details.details as EntityDetails).role_assignments + .owner.rule.access_rule.proof_rule.requirement.resource; + + const simulationManifest = new ManifestBuilder() + .callMethod(from_address, INSTRUCTIONS.LOCK_FEE, [decimal(0)]) + .callMethod(from_address, INSTRUCTIONS.CREATE_PROOF_OF_AMOUNT, [ + address(ownerResource), + decimal(1), + ]) + .callMethod(contract_address, method_name, args) + .callMethod(from_address, INSTRUCTIONS.TRY_DEPOSIT_BATCH_OR_ABORT, [ + expression('EntireWorktop'), + enumeration(0), + ]) + .build(); + + const { fee } = await this.estimateTransactionFee({ + transactionManifest: simulationManifest, + }); + + return new ManifestBuilder() + .callMethod(from_address, INSTRUCTIONS.LOCK_FEE, [ + decimal( + new BigNumber(fee.toString()) + .times(this.gasMultiplier) + .dividedBy(new BigNumber(10).exponentiatedBy(18)) + .toFixed(), + ), + ]) + .callMethod(from_address, INSTRUCTIONS.CREATE_PROOF_OF_AMOUNT, [ + address(ownerResource), + decimal(1), + ]) + .callMethod(contract_address, method_name, args) + .callMethod(from_address, INSTRUCTIONS.TRY_DEPOSIT_BATCH_OR_ABORT, [ + expression('EntireWorktop'), + enumeration(0), + ]) + .build(); + } + + public async transfer({ + from_address, + to_address, + resource_address, + amount, + }: { + from_address: string; + to_address: string; + resource_address: string; + amount: string; + }) { + const simulationManifest = new ManifestBuilder() + .callMethod(from_address, INSTRUCTIONS.LOCK_FEE, [decimal(0)]) + .callMethod(from_address, INSTRUCTIONS.WITHDRAW, [ + address(resource_address), + decimal(amount), + ]) + .takeFromWorktop( + resource_address, + new Decimal(amount), + (builder, bucketId) => + builder.callMethod(to_address, INSTRUCTIONS.TRY_DEPOSIT_OR_ABORT, [ + bucket(bucketId), + enumeration(0), + ]), + ) + .build(); + + const { fee } = await this.estimateTransactionFee({ + transactionManifest: simulationManifest, + }); + + return new ManifestBuilder() + .callMethod(from_address, INSTRUCTIONS.LOCK_FEE, [ + decimal( + new BigNumber(fee.toString()) + .times(this.gasMultiplier) + .dividedBy(new BigNumber(10).exponentiatedBy(18)) + .toFixed(), + ), + ]) + .callMethod(from_address, INSTRUCTIONS.WITHDRAW, [ + address(resource_address), + decimal(amount), + ]) + .takeFromWorktop( + resource_address, + new Decimal(amount), + (builder, bucketId) => + builder.callMethod(to_address, INSTRUCTIONS.TRY_DEPOSIT_OR_ABORT, [ + bucket(bucketId), + enumeration(0), + ]), + ) + .build(); + } +} diff --git a/typescript/radix-sdk/src/utils/signer.ts b/typescript/radix-sdk/src/utils/signer.ts new file mode 100644 index 00000000000..430630d1442 --- /dev/null +++ b/typescript/radix-sdk/src/utils/signer.ts @@ -0,0 +1,110 @@ +import { GatewayApiClient } from '@radixdlt/babylon-gateway-api-sdk'; +import { + NotarizedTransaction, + RadixEngineToolkit, + Signature, + SignatureWithPublicKey, + SimpleTransactionBuilder, + TransactionBuilder, + TransactionHash, + TransactionHeader, + TransactionManifest, + generateRandomNonce, +} from '@radixdlt/radix-engine-toolkit'; + +import { RadixBase } from './base.js'; +import { Account } from './types.js'; + +export class RadixSigner { + protected networkId: number; + protected gateway: GatewayApiClient; + protected base: RadixBase; + protected account: Account; + + constructor( + networkId: number, + gateway: GatewayApiClient, + base: RadixBase, + account: Account, + ) { + this.networkId = networkId; + this.gateway = gateway; + this.base = base; + this.account = account; + } + + public async signAndBroadcast( + manifest: TransactionManifest, + ): Promise { + // transaction builder from official example: + // https://github.com/radixdlt/typescript-radix-engine-toolkit?tab=readme-ov-file#constructing-transactions + const constructionMetadata = + await this.gateway.transaction.innerClient.transactionConstruction(); + + const transactionHeader: TransactionHeader = { + networkId: this.networkId, + startEpochInclusive: constructionMetadata.ledger_state.epoch, + endEpochExclusive: constructionMetadata.ledger_state.epoch + 2, + nonce: generateRandomNonce(), + notaryPublicKey: this.account.publicKey, + notaryIsSignatory: true, + tipPercentage: 0, + }; + + const builder = await TransactionBuilder.new(); + const transaction: NotarizedTransaction = await builder + .header(transactionHeader) + .manifest(manifest) + .sign(this.signIntent) + .notarize(this.notarizeIntent); + + const compiledNotarizedTransaction = + await RadixEngineToolkit.NotarizedTransaction.compile(transaction); + + const intentHashTransactionId = + await RadixEngineToolkit.NotarizedTransaction.intentHash(transaction); + + await this.gateway.transaction.innerClient.transactionSubmit({ + transactionSubmitRequest: { + notarized_transaction_hex: Buffer.from( + compiledNotarizedTransaction, + ).toString('hex'), + }, + }); + await this.base.pollForCommit(intentHashTransactionId.id); + + return intentHashTransactionId; + } + + public async getTestnetXrd() { + const constructionMetadata = + await this.gateway.transaction.innerClient.transactionConstruction(); + + const freeXrdForAccountTransaction = + await SimpleTransactionBuilder.freeXrdFromFaucet({ + networkId: this.networkId, + toAccount: this.account.address, + validFromEpoch: constructionMetadata.ledger_state.epoch, + }); + + const intentHashTransactionId = + freeXrdForAccountTransaction.transactionId.id; + + await this.gateway.transaction.innerClient.transactionSubmit({ + transactionSubmitRequest: { + notarized_transaction_hex: freeXrdForAccountTransaction.toHex(), + }, + }); + await this.base.pollForCommit(intentHashTransactionId); + + return intentHashTransactionId; + } + + private signIntent = (hashToSign: Uint8Array): SignatureWithPublicKey => { + return this.account.privateKey.signToSignatureWithPublicKey(hashToSign); + }; + + private notarizeIntent = (hashToSign: Uint8Array): Signature => { + return this.account.privateKey.signToSignature(hashToSign); + }; +} diff --git a/typescript/radix-sdk/src/utils/types.ts b/typescript/radix-sdk/src/utils/types.ts new file mode 100644 index 00000000000..84b250fe976 --- /dev/null +++ b/typescript/radix-sdk/src/utils/types.ts @@ -0,0 +1,98 @@ +import { PrivateKey, PublicKey } from '@radixdlt/radix-engine-toolkit'; + +// https://docs.radixdlt.com/docs/manifest-instructions +export enum INSTRUCTIONS { + LOCK_FEE = 'lock_fee', + INSTANTIATE = 'instantiate', + WITHDRAW = 'withdraw', + TRY_DEPOSIT_BATCH_OR_ABORT = 'try_deposit_batch_or_abort', + TRY_DEPOSIT_OR_ABORT = 'try_deposit_or_abort', + CREATE_PROOF_OF_AMOUNT = 'create_proof_of_amount', +} + +export type Account = { + privateKey: PrivateKey; + publicKey: PublicKey; + address: string; +}; + +export interface RadixSDKOptions { + networkId?: number; + gasMultiplier?: number; +} + +export interface MultisigIsmReq { + validators: string[]; + threshold: number; +} + +export enum RadixIsmTypes { + MERKLE_ROOT_MULTISIG = 'MerkleRootMultisigIsm', + MESSAGE_ID_MULTISIG = 'MessageIdMultisigIsm', + ROUTING_ISM = 'RoutingIsm', + NOOP_ISM = 'NoopIsm', +} + +export type MultisigIsms = + | RadixIsmTypes.MERKLE_ROOT_MULTISIG + | RadixIsmTypes.MESSAGE_ID_MULTISIG + | RadixIsmTypes.NOOP_ISM; + +export type Isms = + | RadixIsmTypes.MERKLE_ROOT_MULTISIG + | RadixIsmTypes.MESSAGE_ID_MULTISIG + | RadixIsmTypes.ROUTING_ISM + | RadixIsmTypes.NOOP_ISM; + +export enum RadixHookTypes { + IGP = 'InterchainGasPaymaster', + MERKLE_TREE = 'MerkleTreeHook', +} + +export type Hooks = RadixHookTypes.IGP | RadixHookTypes.MERKLE_TREE; + +export interface EntityField { + field_name: string; + type_name: string; + variant_name?: string; + value?: any; + elements?: any[]; + fields?: EntityField[]; + hex?: string; +} + +export interface EntityDetails { + blueprint_name: string; + state: { + fields: EntityField[]; + }; + role_assignments: { + owner: { + rule: { + access_rule: { + proof_rule: { + requirement: { + resource: string; + }; + }; + }; + }; + }; + }; +} + +export interface Receipt { + output: { + programmatic_json: { + entries: { + key: { + value: any; + }; + value: { + value: any; + }; + }[]; + }; + }[]; + error_message?: string; +} diff --git a/typescript/radix-sdk/src/utils/utils.ts b/typescript/radix-sdk/src/utils/utils.ts new file mode 100644 index 00000000000..2b8059cc6a7 --- /dev/null +++ b/typescript/radix-sdk/src/utils/utils.ts @@ -0,0 +1,60 @@ +import { + Convert, + LTSRadixEngineToolkit, + PrivateKey, + Value, + ValueKind, + array, + u8, +} from '@radixdlt/radix-engine-toolkit'; + +import { Account } from './types.js'; + +export const bytes = (hex: string): Value => { + return array( + ValueKind.U8, + ...Array.from(Convert.HexString.toUint8Array(hex).values()).map((item) => + u8(item), + ), + ); +}; + +export const getAccountPrefix = (networkId: number) => { + // https://docs.radixdlt.com/docs/concepts-addresses#network-specifiers + let prefix = 'account_'; + + switch (networkId) { + case 1: + prefix += 'rdx'; + break; + case 2: + prefix += 'tdx_2_'; + break; + case 242: + prefix += 'sim'; + break; + default: + prefix += `tdx_${networkId.toString(16)}_`; + } + + return prefix; +}; + +export const generateNewEd25519VirtualAccount = async ( + privateKey: string, + networkId: number, +): Promise => { + const pk = new PrivateKey.Ed25519( + new Uint8Array(Buffer.from(privateKey, 'hex')), + ); + const publicKey = pk.publicKey(); + const address = await LTSRadixEngineToolkit.Derive.virtualAccountAddress( + publicKey, + networkId, + ); + return { + privateKey: pk, + publicKey, + address, + }; +}; diff --git a/typescript/radix-sdk/src/warp/populate.ts b/typescript/radix-sdk/src/warp/populate.ts new file mode 100644 index 00000000000..6cdefc7e826 --- /dev/null +++ b/typescript/radix-sdk/src/warp/populate.ts @@ -0,0 +1,235 @@ +import { GatewayApiClient } from '@radixdlt/babylon-gateway-api-sdk'; +import { + address, + decimal, + enumeration, + str, + u8, + u32, +} from '@radixdlt/radix-engine-toolkit'; +import { BigNumber } from 'bignumber.js'; + +import { assert, strip0x } from '@hyperlane-xyz/utils'; + +import { RadixBase } from '../utils/base.js'; +import { EntityDetails, INSTRUCTIONS } from '../utils/types.js'; +import { bytes } from '../utils/utils.js'; + +import { RadixWarpQuery } from './query.js'; + +export class RadixWarpPopulate { + protected gateway: GatewayApiClient; + protected base: RadixBase; + protected query: RadixWarpQuery; + protected packageAddress: string; + + constructor( + gateway: GatewayApiClient, + base: RadixBase, + query: RadixWarpQuery, + packageAddress: string, + ) { + this.gateway = gateway; + this.base = base; + this.query = query; + this.packageAddress = packageAddress; + } + + public createCollateralToken({ + from_address, + mailbox, + origin_denom, + }: { + from_address: string; + mailbox: string; + origin_denom: string; + }) { + return this.base.createCallFunctionManifest( + from_address, + this.packageAddress, + 'HypToken', + INSTRUCTIONS.INSTANTIATE, + [enumeration(0, address(origin_denom)), address(mailbox)], + ); + } + + public createSyntheticToken({ + from_address, + mailbox, + name, + symbol, + description, + divisibility, + }: { + from_address: string; + mailbox: string; + name: string; + symbol: string; + description: string; + divisibility: number; + }) { + return this.base.createCallFunctionManifest( + from_address, + this.packageAddress, + 'HypToken', + INSTRUCTIONS.INSTANTIATE, + [ + enumeration( + 1, + str(name), + str(symbol), + str(description), + u8(divisibility), + ), + address(mailbox), + ], + ); + } + + public async setTokenOwner({ + from_address, + token, + new_owner, + }: { + from_address: string; + token: string; + new_owner: string; + }) { + const details = + await this.gateway.state.getEntityDetailsVaultAggregated(token); + + const resource = (details.details as EntityDetails).role_assignments.owner + .rule.access_rule.proof_rule.requirement.resource; + + return this.base.transfer({ + from_address, + to_address: new_owner, + resource_address: resource, + amount: '1', + }); + } + + public async setTokenIsm({ + from_address, + token, + ism, + }: { + from_address: string; + token: string; + ism: string; + }) { + return this.base.createCallMethodManifestWithOwner( + from_address, + token, + 'set_ism', + [enumeration(1, address(ism))], + ); + } + + public async enrollRemoteRouter({ + from_address, + token, + receiver_domain, + receiver_address, + gas, + }: { + from_address: string; + token: string; + receiver_domain: number; + receiver_address: string; + gas: string; + }) { + return this.base.createCallMethodManifestWithOwner( + from_address, + token, + 'enroll_remote_router', + [u32(receiver_domain), bytes(strip0x(receiver_address)), decimal(gas)], + ); + } + + public async unenrollRemoteRouter({ + from_address, + token, + receiver_domain, + }: { + from_address: string; + token: string; + receiver_domain: number; + }) { + return this.base.createCallMethodManifestWithOwner( + from_address, + token, + 'unroll_remote_router', + [u32(receiver_domain)], + ); + } + + public async remoteTransfer({ + from_address, + token, + destination_domain, + recipient, + amount, + max_fee, + }: { + from_address: string; + token: string; + destination_domain: number; + recipient: string; + amount: string; + custom_hook_id: string; + gas_limit: string; + custom_hook_metadata: string; + max_fee: { denom: string; amount: string }; + }) { + const { origin_denom, divisibility } = await this.query.getToken({ token }); + + const tokenAmount = new BigNumber(amount) + .dividedBy(new BigNumber(10).pow(divisibility)) + .toFixed(divisibility); + + assert(origin_denom, `no origin_denom found on token ${token}`); + return ` +CALL_METHOD + Address("${from_address}") + "withdraw" + Address("${origin_denom}") + Decimal("${tokenAmount}") +; +CALL_METHOD + Address("${from_address}") + "withdraw" + Address("${max_fee.denom}") + Decimal("${max_fee.amount}") +; +TAKE_FROM_WORKTOP + Address("${origin_denom}") + Decimal("${tokenAmount}") + Bucket("bucket1") +; +TAKE_FROM_WORKTOP + Address("${max_fee.denom}") + Decimal("${max_fee.amount}") + Bucket("bucket2") +; +CALL_METHOD + Address("${token}") + "transfer_remote" + ${destination_domain}u32 + Bytes("${recipient}") + Bucket("bucket1") + Array( + Bucket("bucket2") + ) + Enum<0u8>() + Enum<0u8>() +; +CALL_METHOD + Address("${from_address}") + "try_deposit_batch_or_abort" + Expression("ENTIRE_WORKTOP") + Enum<0u8>() +; +`; + } +} diff --git a/typescript/radix-sdk/src/warp/query.ts b/typescript/radix-sdk/src/warp/query.ts new file mode 100644 index 00000000000..c3bf11262b4 --- /dev/null +++ b/typescript/radix-sdk/src/warp/query.ts @@ -0,0 +1,260 @@ +import { GatewayApiClient } from '@radixdlt/babylon-gateway-api-sdk'; +import { + PrivateKey, + generateRandomNonce, +} from '@radixdlt/radix-engine-toolkit'; +import { BigNumber } from 'bignumber.js'; +import { utils } from 'ethers'; + +import { assert } from '@hyperlane-xyz/utils'; + +import { RadixBase } from '../utils/base.js'; +import { EntityDetails, EntityField, Receipt } from '../utils/types.js'; + +export class RadixWarpQuery { + protected networkId: number; + protected gateway: GatewayApiClient; + protected base: RadixBase; + + constructor(networkId: number, gateway: GatewayApiClient, base: RadixBase) { + this.networkId = networkId; + this.gateway = gateway; + this.base = base; + } + + public async getToken({ token }: { token: string }): Promise<{ + address: string; + owner: string; + token_type: 'Collateral' | 'Synthetic'; + mailbox: string; + ism: string; + origin_denom: string; + name: string; + symbol: string; + description: string; + divisibility: number; + }> { + const details = + await this.gateway.state.getEntityDetailsVaultAggregated(token); + + assert( + (details.details as EntityDetails).blueprint_name === 'HypToken', + `Expected contract at address ${token} to be "HypToken" but got ${(details.details as EntityDetails).blueprint_name}`, + ); + + const ownerResource = (details.details as EntityDetails).role_assignments + .owner.rule.access_rule.proof_rule.requirement.resource; + + const { items } = + await this.gateway.extensions.getResourceHolders(ownerResource); + + const resourceHolders = [ + ...new Set(items.map((item) => item.holder_address)), + ]; + + assert( + resourceHolders.length === 1, + `expected token holders of resource ${ownerResource} to be one, found ${resourceHolders.length} holders instead`, + ); + + const fields = (details.details as EntityDetails).state.fields; + + const token_type = + fields.find((f) => f.field_name === 'token_type')?.variant_name ?? ''; + assert( + token_type === 'Collateral' || token_type === 'Synthetic', + `unknown token type: ${token_type}`, + ); + + const ismFields = fields.find((f) => f.field_name === 'ism')?.fields ?? []; + + const tokenTypeFields = + fields.find((f) => f.field_name === 'token_type')?.fields ?? []; + + let origin_denom; + let metadata = { + name: '', + symbol: '', + description: '', + divisibility: 0, + }; + + if (token_type === 'Collateral') { + origin_denom = + tokenTypeFields.find((t) => t.type_name === 'ResourceAddress')?.value ?? + ''; + + metadata = await this.base.getMetadata({ resource: origin_denom }); + } else if (token_type === 'Synthetic') { + origin_denom = + ( + fields.find((f) => f.field_name === 'resource_manager')?.fields ?? [] + ).find((r) => r.type_name === 'ResourceAddress')?.value ?? ''; + + metadata = await this.base.getMetadata({ resource: origin_denom }); + } + + const result = { + address: token, + owner: resourceHolders[0], + token_type: token_type as 'Collateral' | 'Synthetic', + mailbox: fields.find((f) => f.field_name === 'mailbox')?.value ?? '', + ism: ismFields[0]?.value ?? '', + origin_denom, + ...metadata, + }; + + return result; + } + + public async getRemoteRouters({ token }: { token: string }): Promise<{ + address: string; + remote_routers: { + receiver_domain: string; + receiver_contract: string; + gas: string; + }[]; + }> { + const details = + await this.gateway.state.getEntityDetailsVaultAggregated(token); + + assert( + (details.details as EntityDetails).blueprint_name === 'HypToken', + `Expected contract at address ${token} to be "HypToken" but got ${(details.details as EntityDetails).blueprint_name}`, + ); + + const fields = (details.details as EntityDetails).state.fields; + + const enrolledRoutersKeyValueStore = + fields.find((f) => f.field_name === 'enrolled_routers')?.value ?? ''; + assert( + enrolledRoutersKeyValueStore, + `found no enrolled routers on token ${token}`, + ); + + const remote_routers = []; + + const { items } = await this.gateway.state.innerClient.keyValueStoreKeys({ + stateKeyValueStoreKeysRequest: { + key_value_store_address: enrolledRoutersKeyValueStore, + }, + }); + + for (const { key } of items) { + const { entries } = + await this.gateway.state.innerClient.keyValueStoreData({ + stateKeyValueStoreDataRequest: { + key_value_store_address: enrolledRoutersKeyValueStore, + keys: [ + { + key_hex: key.raw_hex, + }, + ], + }, + }); + + const routerFields = + (entries[0].value.programmatic_json as EntityField)?.fields ?? []; + + remote_routers.push({ + receiver_domain: + routerFields.find((r) => r.field_name === 'domain')?.value ?? '', + receiver_contract: + routerFields.find((r) => r.field_name === 'recipient')?.hex ?? '', + gas: routerFields.find((r) => r.field_name === 'gas')?.value ?? '', + }); + } + + const result = { + address: token, + remote_routers, + }; + + return result; + } + + public async getBridgedSupply({ token }: { token: string }): Promise { + const { token_type, origin_denom } = await this.getToken({ token }); + + switch (token_type) { + case 'Collateral': { + // if the token is collateral we get the token contract balance + // of the origin denom + return this.base.getBalance({ address: token, resource: origin_denom }); + } + case 'Synthetic': { + // if the token is synthetic we get the total supply of the synthetic + // resource + return this.base.getTotalSupply({ resource: origin_denom }); + } + default: { + throw new Error(`unknown token type: ${token_type}`); + } + } + } + + public async quoteRemoteTransfer({ + token, + destination_domain, + }: { + token: string; + destination_domain: number; + }): Promise<{ resource: string; amount: bigint }> { + const pk = new PrivateKey.Ed25519(new Uint8Array(utils.randomBytes(32))); + + const constructionMetadata = + await this.gateway.transaction.innerClient.transactionConstruction(); + + const response = + await this.gateway.transaction.innerClient.transactionPreview({ + transactionPreviewRequest: { + manifest: ` +CALL_METHOD + Address("${token}") + "quote_remote_transfer" + ${destination_domain}u32 + Bytes("0000000000000000000000000000000000000000000000000000000000000000") + Decimal("0") +; +`, + nonce: generateRandomNonce(), + signer_public_keys: [ + { + key_type: 'EddsaEd25519', + key_hex: pk.publicKeyHex(), + }, + ], + flags: { + use_free_credit: true, + }, + start_epoch_inclusive: constructionMetadata.ledger_state.epoch, + end_epoch_exclusive: constructionMetadata.ledger_state.epoch + 2, + }, + }); + + assert( + !(response.receipt as Receipt).error_message, + `${(response.receipt as Receipt).error_message}`, + ); + + const output = (response.receipt as Receipt).output; + assert(output.length, `found no output for quote_remote_transfer method`); + + const entries = output[0].programmatic_json.entries; + assert(entries.length > 0, `quote_remote_transfer returned no resources`); + assert( + entries.length < 2, + `quote_remote_transfer returned multiple resources`, + ); + + return { + resource: entries[0].key.value, + amount: BigInt( + new BigNumber(entries[0].value.value) + .times(new BigNumber(10).pow(18)) + .integerValue(BigNumber.ROUND_FLOOR) + .toFixed(0), + ), + }; + } +} diff --git a/typescript/radix-sdk/src/warp/tx.ts b/typescript/radix-sdk/src/warp/tx.ts new file mode 100644 index 00000000000..e38b3fa577c --- /dev/null +++ b/typescript/radix-sdk/src/warp/tx.ts @@ -0,0 +1,136 @@ +import { RadixBase } from '../utils/base.js'; +import { RadixSigner } from '../utils/signer.js'; +import { Account } from '../utils/types.js'; + +import { RadixWarpPopulate } from './populate.js'; + +export class RadixWarpTx { + private account: Account; + + protected base: RadixBase; + protected populate: RadixWarpPopulate; + protected signer: RadixSigner; + + constructor( + account: Account, + base: RadixBase, + signer: RadixSigner, + populate: RadixWarpPopulate, + ) { + this.account = account; + this.base = base; + this.signer = signer; + this.populate = populate; + } + + public async createCollateralToken({ + mailbox, + origin_denom, + }: { + mailbox: string; + origin_denom: string; + }) { + const transactionManifest = await this.populate.createCollateralToken({ + from_address: this.account.address, + mailbox, + origin_denom, + }); + + const intentHashTransactionId = + await this.signer.signAndBroadcast(transactionManifest); + + return this.base.getNewComponent(intentHashTransactionId); + } + + public async createSyntheticToken({ + mailbox, + name, + symbol, + description, + divisibility, + }: { + mailbox: string; + name: string; + symbol: string; + description: string; + divisibility: number; + }) { + const transactionManifest = await this.populate.createSyntheticToken({ + from_address: this.account.address, + mailbox, + name, + symbol, + description, + divisibility, + }); + + const intentHashTransactionId = + await this.signer.signAndBroadcast(transactionManifest); + + return this.base.getNewComponent(intentHashTransactionId); + } + + public async setTokenOwner({ + token, + new_owner, + }: { + token: string; + new_owner: string; + }) { + const transactionManifest = await this.populate.setTokenOwner({ + from_address: this.account.address, + token, + new_owner, + }); + + await this.signer.signAndBroadcast(transactionManifest); + } + + public async setTokenIsm({ token, ism }: { token: string; ism: string }) { + const transactionManifest = await this.populate.setTokenIsm({ + from_address: this.account.address, + token, + ism, + }); + + await this.signer.signAndBroadcast(transactionManifest); + } + + public async enrollRemoteRouter({ + token, + receiver_domain, + receiver_address, + gas, + }: { + token: string; + receiver_domain: number; + receiver_address: string; + gas: string; + }) { + const transactionManifest = await this.populate.enrollRemoteRouter({ + from_address: this.account.address, + token, + receiver_domain, + receiver_address, + gas, + }); + + await this.signer.signAndBroadcast(transactionManifest); + } + + public async unenrollRemoteRouter({ + token, + receiver_domain, + }: { + token: string; + receiver_domain: number; + }) { + const transactionManifest = await this.populate.unenrollRemoteRouter({ + from_address: this.account.address, + token, + receiver_domain, + }); + + await this.signer.signAndBroadcast(transactionManifest); + } +} diff --git a/typescript/radix-sdk/tsconfig.json b/typescript/radix-sdk/tsconfig.json new file mode 100644 index 00000000000..f16a580965c --- /dev/null +++ b/typescript/radix-sdk/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "@hyperlane-xyz/tsconfig/tsconfig.json", + "compilerOptions": { + "outDir": "./dist/", + "rootDir": "./src" + }, + "include": [ + "./src/*.ts", + "src/core/populate.ts", + "src/core/query.ts", + "src/core/tx.ts", + "src/utils/signer.ts", + "src/utils/types.ts", + "src/utils/utils.ts" + ] +} diff --git a/typescript/sdk/CHANGELOG.md b/typescript/sdk/CHANGELOG.md index ec54c49539a..3d649f197df 100644 --- a/typescript/sdk/CHANGELOG.md +++ b/typescript/sdk/CHANGELOG.md @@ -1,5 +1,112 @@ # @hyperlane-xyz/sdk +## 18.2.0 + +### Minor Changes + +- fed6906e4: Include isHypNative() check to Token and add PROTOCOL_TO_HYP_NATIVE_STANDARD +- ca64e73cd: Update the oXAUT bridge limits for avax, celo, ethereum, worldchain and base config. Export XERC20LimitsTokenConfig. +- dfa9d368c: exposed a `isJsonRpcSubmitterConfig` function to validate submitter configurations and assert the type + +### Patch Changes + +- Updated dependencies [dfa9d368c] + - @hyperlane-xyz/cosmos-sdk@18.2.0 + - @hyperlane-xyz/starknet-core@18.2.0 + - @hyperlane-xyz/radix-sdk@18.2.0 + - @hyperlane-xyz/utils@18.2.0 + - @hyperlane-xyz/core@9.0.9 + +## 18.1.0 + +### Patch Changes + +- 73be9b8d2: Don't use radix-engine-toolkit for frontend application usage. +- Updated dependencies [73be9b8d2] + - @hyperlane-xyz/radix-sdk@18.1.0 + - @hyperlane-xyz/starknet-core@18.1.0 + - @hyperlane-xyz/cosmos-sdk@18.1.0 + - @hyperlane-xyz/utils@18.1.0 + - @hyperlane-xyz/core@9.0.8 + +## 18.0.0 + +### Major Changes + +- 552b253b9: deprecated dry-run support in the cli in favour of `hyperlane warp fork` and `hyperlane fork` commands + +### Patch Changes + +- ba832828f: Made decimals consistency check scale-aware and disallowed partial decimals across chains +- Updated dependencies [cfc0eb2a7] + - @hyperlane-xyz/utils@18.0.0 + - @hyperlane-xyz/core@9.0.7 + - @hyperlane-xyz/radix-sdk@18.0.0 + - @hyperlane-xyz/starknet-core@18.0.0 + - @hyperlane-xyz/cosmos-sdk@18.0.0 + +## 17.0.0 + +### Major Changes + +- 8c15edc67: Added Radix Protocol Type + +### Minor Changes + +- 400c02460: Add fallback logic to the `EvmEventLogsReader` to use the rpc when the block explorer api request fails +- 6583df016: Add the getPendingScheduledOperations and getPendingOperationIds methods on the EvmTimelockReader class and export getTimelockExecutableTransactionFromBatch from sdk + +### Patch Changes + +- 76a5db49a: Fixed a bug when deriving token metadata from chain was erasing existing fields from deployment config +- 7f542b288: Fix `CosmosModuleTokenAdapter` recipient conversion for populateTransferRemoteTx +- Updated dependencies [8c15edc67] +- Updated dependencies [e0bda316a] + - @hyperlane-xyz/utils@17.0.0 + - @hyperlane-xyz/core@9.0.6 + - @hyperlane-xyz/starknet-core@17.0.0 + - @hyperlane-xyz/cosmos-sdk@17.0.0 + +## 16.2.0 + +### Minor Changes + +- 22ceaa109: Add xERC20 adapter with getLimits() +- a89018a3f: Make `getWrappedTokenAddress` public, add LOCKBOX_STANDARDS + +### Patch Changes + +- ce4974214: Add cctp to getActualDecimals. + - @hyperlane-xyz/starknet-core@16.2.0 + - @hyperlane-xyz/cosmos-sdk@16.2.0 + - @hyperlane-xyz/utils@16.2.0 + - @hyperlane-xyz/core@9.0.5 + +## 16.1.1 + +### Patch Changes + +- ea77b6ae4: Add Starknet protocol type. + - @hyperlane-xyz/starknet-core@16.1.1 + - @hyperlane-xyz/cosmos-sdk@16.1.1 + - @hyperlane-xyz/utils@16.1.1 + - @hyperlane-xyz/core@9.0.4 + +## 16.1.0 + +### Minor Changes + +- 2a2c29c39: Add the `EvmTimelockReader` class to get pending/scheduled transaction from a timelock contract. Add the `EvmEventLogsReader` to read logs on a given chain reliably either using the rpc or the block explorer api depending on what is available in the registry + +### Patch Changes + +- e69ac9f62: Updated the HypERC20Checker to use a default anvil address instead of the signer address when asserting if a token is a hyp native +- d9b8a7551: Handle etherscan v2 api migration + - @hyperlane-xyz/starknet-core@16.1.0 + - @hyperlane-xyz/cosmos-sdk@16.1.0 + - @hyperlane-xyz/utils@16.1.0 + - @hyperlane-xyz/core@9.0.3 + ## 16.0.0 ### Major Changes diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 4199c356611..a5a212b5cb1 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -1,17 +1,19 @@ { "name": "@hyperlane-xyz/sdk", "description": "The official SDK for the Hyperlane Network", - "version": "16.0.0", + "version": "18.2.0", "dependencies": { "@arbitrum/sdk": "^4.0.0", "@aws-sdk/client-s3": "^3.577.0", "@chain-registry/types": "^0.50.122", "@cosmjs/cosmwasm-stargate": "^0.32.4", + "@cosmjs/proto-signing": "^0.32.4", "@cosmjs/stargate": "^0.32.4", - "@hyperlane-xyz/core": "9.0.2", - "@hyperlane-xyz/cosmos-sdk": "16.0.0", - "@hyperlane-xyz/starknet-core": "16.0.0", - "@hyperlane-xyz/utils": "16.0.0", + "@hyperlane-xyz/core": "9.0.9", + "@hyperlane-xyz/cosmos-sdk": "18.2.0", + "@hyperlane-xyz/radix-sdk": "18.2.0", + "@hyperlane-xyz/starknet-core": "18.2.0", + "@hyperlane-xyz/utils": "18.2.0", "@safe-global/api-kit": "1.3.0", "@safe-global/protocol-kit": "1.3.0", "@safe-global/safe-core-sdk-types": "2.3.0", diff --git a/typescript/sdk/src/app/MultiProtocolApp.ts b/typescript/sdk/src/app/MultiProtocolApp.ts index d5426971308..b9b8acca2c5 100644 --- a/typescript/sdk/src/app/MultiProtocolApp.ts +++ b/typescript/sdk/src/app/MultiProtocolApp.ts @@ -16,6 +16,7 @@ import { CosmJsProvider, CosmJsWasmProvider, EthersV5Provider, + RadixProvider, SolanaWeb3Provider, StarknetJsProvider, TypedProvider, @@ -113,6 +114,14 @@ export class BaseStarknetAdapter extends BaseAppAdapter { } } +export class BaseRadixAdapter extends BaseAppAdapter { + public readonly protocol: ProtocolType = ProtocolType.Radix; + + public getProvider(): RadixProvider['provider'] { + return this.multiProvider.getRadixProvider(this.chainName); + } +} + /** * A version of HyperlaneApp that can support different * provider types across different protocol types. diff --git a/typescript/sdk/src/block-explorer/utils.ts b/typescript/sdk/src/block-explorer/utils.ts index 8a351bbeeba..1a96fe0952e 100644 --- a/typescript/sdk/src/block-explorer/utils.ts +++ b/typescript/sdk/src/block-explorer/utils.ts @@ -17,6 +17,7 @@ export function isEvmBlockExplorerAndNotEtherscan( [ExplorerFamily.Routescan]: true, [ExplorerFamily.Voyager]: false, [ExplorerFamily.ZkSync]: true, + [ExplorerFamily.RadixDashboard]: false, }; return byFamily[blockExplorer.family] ?? false; diff --git a/typescript/sdk/src/consts/multisigIsm.ts b/typescript/sdk/src/consts/multisigIsm.ts index b390ec227cd..3dcc70fda24 100644 --- a/typescript/sdk/src/consts/multisigIsm.ts +++ b/typescript/sdk/src/consts/multisigIsm.ts @@ -15,10 +15,6 @@ const DEFAULT_ZEE_PRIME_VALIDATOR: ValidatorConfig = { address: '0x5450447aee7b544c462c9352bef7cad049b0c2dc', alias: 'Zee Prime', }; -const DEFAULT_EVERSTAKE_VALIDATOR: ValidatorConfig = { - address: '0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8', - alias: 'Everstake', -}; const DEFAULT_STAKED_VALIDATOR: ValidatorConfig = { address: '0xb3ac35d3988bca8c2ffd195b1c6bee18536b317b', alias: 'Staked', @@ -27,10 +23,6 @@ const DEFAULT_TESSELLATED_VALIDATOR: ValidatorConfig = { address: '0x0d4c1394a255568ec0ecd11795b28d1bda183ca4', alias: 'Tessellated', }; -const DEFAULT_BWARE_LABS_VALIDATOR: ValidatorConfig = { - address: '0x14d0B24d3a8F3aAD17DB4b62cBcEC12821c98Cb3', - alias: 'Bware Labs', -}; const DEFAULT_ZKV_VALIDATOR: ValidatorConfig = { address: '0x761980c3debdc8ddb69a2713cf5126d4db900f0f', alias: 'ZKV', @@ -39,10 +31,6 @@ const DEFAULT_BLOCKPI_VALIDATOR: ValidatorConfig = { address: '0x6d113ae51bfea7b63a8828f97e9dce393b25c189', alias: 'BlockPI', }; -const DEFAULT_HASHKEY_CLOUD_VALIDATOR: ValidatorConfig = { - address: '0x5aed2fd5cc5f9749c455646c86b0db6126cafcbb', - alias: 'Hashkey Cloud', -}; // TODO: consider migrating these to the registry too export const defaultMultisigConfigs: ChainMap = { @@ -58,16 +46,6 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - abstracttestnet: { - threshold: 1, - validators: [ - { - address: '0x7655bc4c9802bfcb3132b8822155b60a4fbbce3e', - alias: AW_VALIDATOR_ALIAS, - }, - ], - }, - // acala: { // threshold: 1, // validators: [ @@ -94,34 +72,6 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - alephzeroevmtestnet: { - threshold: 1, - validators: [ - { - address: '0x556cd94bcb6e5773e8df75e7eb3f91909d266a26', - alias: AW_VALIDATOR_ALIAS, - }, - ], - }, - - alfajores: { - threshold: 2, - validators: [ - { - address: '0x2233a5ce12f814bd64c9cdd73410bb8693124d40', - alias: AW_VALIDATOR_ALIAS, - }, - { - address: '0xba279f965489d90f90490e3c49e860e0b43c2ae6', - alias: AW_VALIDATOR_ALIAS, - }, - { - address: '0x86485dcec5f7bb8478dd251676372d054dea6653', - alias: AW_VALIDATOR_ALIAS, - }, - ], - }, - ancient8: { threshold: 2, validators: [ @@ -171,10 +121,16 @@ export const defaultMultisigConfigs: ChainMap = { address: '0x4d966438fe9e2b1e7124c87bbb90cb4f0f6c59a1', alias: AW_VALIDATOR_ALIAS, }, - { address: '0xec68258a7c882ac2fc46b81ce80380054ffb4ef2', alias: 'DSRV' }, DEFAULT_ZEE_PRIME_VALIDATOR, - DEFAULT_EVERSTAKE_VALIDATOR, - DEFAULT_STAKED_VALIDATOR, + DEFAULT_MITOSIS_VALIDATOR, + { + address: '0x57ddf0cd46f31ead8084069ce481507f4305c716', + alias: 'Luganodes', + }, + { + address: '0xde6c50c3e49852dd9fe0388166ebc1ba39ad8505', + alias: 'Enigma', + }, ], }, @@ -269,14 +225,21 @@ export const defaultMultisigConfigs: ChainMap = { }, avalanche: { - threshold: 2, + threshold: 3, validators: [ { address: '0x3fb8263859843bffb02950c492d492cae169f4cf', alias: AW_VALIDATOR_ALIAS, }, - { address: '0x402e0f8c6e4210d408b6ac00d197d4a099fcd25a', alias: 'DSRV' }, - DEFAULT_EVERSTAKE_VALIDATOR, + DEFAULT_MITOSIS_VALIDATOR, + { + address: '0x74de235ace64fa8a3d5e3d5e414360888e655c62', + alias: 'Substance Labs', + }, + { + address: '0x4488dbc191c39ae026b4a1fdb2aefe21960226d5', + alias: 'Luganodes', + }, ], }, @@ -293,17 +256,25 @@ export const defaultMultisigConfigs: ChainMap = { }, base: { - threshold: 4, + threshold: 3, validators: [ { address: '0xb9453d675e0fa3c178a17b4ce1ad5b1a279b3af9', alias: AW_VALIDATOR_ALIAS, }, - DEFAULT_STAKED_VALIDATOR, - DEFAULT_EVERSTAKE_VALIDATOR, - { address: '0xcff391b4e516452d424db66beb9052b041a9ed79', alias: 'DSRV' }, DEFAULT_ZEE_PRIME_VALIDATOR, - DEFAULT_ZKV_VALIDATOR, + { + address: '0xb8cf45d7bab79c965843206d5f4d83bb866d6e86', + alias: 'Substance Labs', + }, + { + address: '0xe957310e17730f29862e896709cce62d24e4b773', + alias: 'Luganodes', + }, + { + address: '0x34a14934d7c18a21440b59dfe9bf132ce601457d', + alias: 'Enigma', + }, ], }, @@ -327,16 +298,6 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - bepolia: { - threshold: 1, - validators: [ - { - address: '0xdb0128bb3d3f204eb18de7e8268e94fde0382daf', - alias: AW_VALIDATOR_ALIAS, - }, - ], - }, - berachain: { threshold: 3, validators: [ @@ -366,9 +327,18 @@ export const defaultMultisigConfigs: ChainMap = { }, DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, - DEFAULT_BLOCKPI_VALIDATOR, - DEFAULT_ZKV_VALIDATOR, - DEFAULT_HASHKEY_CLOUD_VALIDATOR, + { + address: '0xaa00a849fc770d742724cbd2862f91d51db7fb62', + alias: 'Substance Labs', + }, + { + address: '0x68e869315e51f6bd0ba4aac844cf216fd3dec762', + alias: 'Luganodes', + }, + { + address: '0x0677b2daf18b71a2c4220fb17dc81cd3aa7d355b', + alias: 'Enigma', + }, ], }, @@ -389,7 +359,7 @@ export const defaultMultisigConfigs: ChainMap = { }, bob: { - threshold: 2, + threshold: 3, validators: [ { address: '0x20f283be1eb0e81e22f51705dcb79883cfdd34aa', @@ -397,6 +367,14 @@ export const defaultMultisigConfigs: ChainMap = { }, DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, + { + address: '0x53d2738453c222e49c556d937bcef3f80f1c2eec', + alias: 'Substance Labs', + }, + { + address: '0xb574b2b5822a8cb9ca071e7d43865694f23b0bde', + alias: 'Enigma', + }, ], }, @@ -423,18 +401,6 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - bouncebit: { - threshold: 2, - validators: [ - { - address: '0xaf38612d1e79ec67320d21c5f7e92419427cd154', - alias: AW_VALIDATOR_ALIAS, - }, - DEFAULT_MERKLY_VALIDATOR, - DEFAULT_MITOSIS_VALIDATOR, - ], - }, - bsc: { threshold: 4, validators: [ @@ -442,11 +408,20 @@ export const defaultMultisigConfigs: ChainMap = { address: '0x570af9b7b36568c8877eebba6c6727aa9dab7268', alias: AW_VALIDATOR_ALIAS, }, - { address: '0x8292b1a53907ece0f76af8a50724e9492bcdc8a3', alias: 'DSRV' }, - DEFAULT_EVERSTAKE_VALIDATOR, DEFAULT_ZEE_PRIME_VALIDATOR, - DEFAULT_BLOCKPI_VALIDATOR, DEFAULT_TESSELLATED_VALIDATOR, + { + address: '0x24c1506142b2c859aee36474e59ace09784f71e8', + alias: 'Substance Labs', + }, + { + address: '0xc67789546a7a983bf06453425231ab71c119153f', + alias: 'Luganodes', + }, + { + address: '0x2d74f6edfd08261c927ddb6cb37af57ab89f0eff', + alias: 'Enigma', + }, ], }, @@ -490,6 +465,33 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + celestia: { + threshold: 4, + validators: [ + { + address: '0x6dbc192c06907784fb0af0c0c2d8809ea50ba675', + alias: AW_VALIDATOR_ALIAS, + }, + DEFAULT_ZKV_VALIDATOR, + { + address: '0x885a8c1ef7f7eea8955c8f116fc1fbe1113c4a78', + alias: 'P2P.ORG', + }, + { + address: '0xa6c998f0db2b56d7a63faf30a9b677c8b9b6faab', + alias: 'O-OPS', + }, + { + address: '0x21e93a81920b73c0e98aed8e6b058dae409e4909', + alias: 'Binary Builders', + }, + { + address: '0x7b8606d61bc990165d1e5977037ddcf7f2de74d6', + alias: 'Cosmostation', + }, + ], + }, + celestiatestnet: { threshold: 1, validators: [ @@ -519,8 +521,18 @@ export const defaultMultisigConfigs: ChainMap = { address: '0x4a2423ef982b186729e779b6e54b0e84efea7285', alias: 'Luganodes', }, - DEFAULT_BWARE_LABS_VALIDATOR, DEFAULT_TESSELLATED_VALIDATOR, + DEFAULT_ZEE_PRIME_VALIDATOR, + ], + }, + + celosepolia: { + threshold: 1, + validators: [ + { + address: '0x4a5cfcfd7f793f4ceba170c3decbe43bd8253ef6', + alias: AW_VALIDATOR_ALIAS, + }, ], }, @@ -532,10 +544,6 @@ export const defaultMultisigConfigs: ChainMap = { alias: AW_VALIDATOR_ALIAS, }, DEFAULT_MERKLY_VALIDATOR, - { - address: '0x101cE77261245140A0871f9407d6233C8230Ec47', - alias: 'Blockhunters', - }, ], }, @@ -569,16 +577,6 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - chronicleyellowstone: { - threshold: 1, - validators: [ - { - address: '0xf11cfeb2b6db66ec14c2ef7b685b36390cd648b4', - alias: AW_VALIDATOR_ALIAS, - }, - ], - }, - citreatestnet: { threshold: 1, validators: [ @@ -589,40 +587,6 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - conflux: { - threshold: 2, - validators: [ - { - address: '0x113dfa1dc9b0a2efb6ad01981e2aad86d3658490', - alias: AW_VALIDATOR_ALIAS, - }, - DEFAULT_MERKLY_VALIDATOR, - DEFAULT_MITOSIS_VALIDATOR, - ], - }, - - connextsepolia: { - threshold: 1, - validators: [ - { - address: '0xffbbec8c499585d80ef69eb613db624d27e089ab', - alias: AW_VALIDATOR_ALIAS, - }, - ], - }, - - conwai: { - threshold: 2, - validators: [ - { - address: '0x949e2cdd7e79f99ee9bbe549540370cdc62e73c3', - alias: AW_VALIDATOR_ALIAS, - }, - DEFAULT_MERKLY_VALIDATOR, - DEFAULT_MITOSIS_VALIDATOR, - ], - }, - coredao: { threshold: 2, validators: [ @@ -669,18 +633,6 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - deepbrainchain: { - threshold: 2, - validators: [ - { - address: '0x3825ea1e0591b58461cc4aa34867668260c0e6a8', - alias: AW_VALIDATOR_ALIAS, - }, - DEFAULT_MERKLY_VALIDATOR, - DEFAULT_MITOSIS_VALIDATOR, - ], - }, - degenchain: { threshold: 2, validators: [ @@ -705,18 +657,6 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - duckchain: { - threshold: 2, - validators: [ - { - address: '0x91d55fe6dac596a6735d96365e21ce4bca21d83c', - alias: AW_VALIDATOR_ALIAS, - }, - DEFAULT_MERKLY_VALIDATOR, - DEFAULT_MITOSIS_VALIDATOR, - ], - }, - eclipsemainnet: { threshold: 3, validators: [ @@ -728,14 +668,11 @@ export const defaultMultisigConfigs: ChainMap = { address: '0x3571223e745dc0fcbdefa164c9b826b90c0d2dac', alias: 'Luganodes', }, - { - address: '0xea83086a62617a7228ce4206fae2ea8b0ab23513', - alias: 'Imperator', - }, { address: '0x4d4629f5bfeabe66edc7a78da26ef5273c266f97', alias: 'Eclipse', }, + DEFAULT_ZEE_PRIME_VALIDATOR, ], }, @@ -749,13 +686,14 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - ecotestnet: { - threshold: 1, + electroneum: { + threshold: 2, validators: [ { - address: '0xb3191420d463c2af8bd9b4a395e100ec5c05915a', + address: '0x32917f0a38c60ff5b1c4968cb40bc88b14ef0d83', alias: AW_VALIDATOR_ALIAS, }, + DEFAULT_MITOSIS_VALIDATOR, ], }, @@ -783,21 +721,24 @@ export const defaultMultisigConfigs: ChainMap = { }, { address: '0x94438a7de38d4548ae54df5c6010c4ebc5239eae', alias: 'DSRV' }, DEFAULT_ZEE_PRIME_VALIDATOR, - DEFAULT_EVERSTAKE_VALIDATOR, DEFAULT_STAKED_VALIDATOR, { address: '0xb683b742b378632a5f73a2a5a45801b3489bba44', alias: 'AVS: Luganodes', }, { - address: '0xbf1023eff3dba21263bf2db2add67a0d6bcda2de', - alias: 'AVS: Pier Two', + address: '0x3786083ca59dc806d894104e65a13a70c2b39276', + alias: 'Imperator', + }, + DEFAULT_MITOSIS_VALIDATOR, + { + address: '0x29d783efb698f9a2d3045ef4314af1f5674f52c5', + alias: 'Substance Labs', }, { - address: '0x5d7442439959af11172bf92d9a8d21cf88d136e3', - alias: 'P2P', + address: '0x36a669703ad0e11a0382b098574903d2084be22c', + alias: 'Enigma', }, - DEFAULT_ZKV_VALIDATOR, ], }, @@ -816,18 +757,6 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - evmos: { - threshold: 2, - validators: [ - { - address: '0x8f82387ad8b7b13aa9e06ed3f77f78a77713afe0', - alias: AW_VALIDATOR_ALIAS, - }, - DEFAULT_MERKLY_VALIDATOR, - DEFAULT_MITOSIS_VALIDATOR, - ], - }, - fantom: { threshold: 2, validators: [ @@ -840,39 +769,6 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - flame: { - threshold: 3, - validators: [ - { - address: '0x1fa928ce884fa16357d4b8866e096392d4d81f43', - alias: AW_VALIDATOR_ALIAS, - }, - { - address: '0xa6c998f0db2b56d7a63faf30a9b677c8b9b6faab', - alias: 'P-OPS', - }, - { - address: '0x09f9de08f7570c4146caa708dc9f75b56958957f', - alias: 'Luganodes', - }, - { - address: '0xf1f4ae9959490380ad7863e79c3faf118c1fbf77', - alias: 'DSRV', - }, - DEFAULT_TESSELLATED_VALIDATOR, - ], - }, - - flametestnet: { - threshold: 1, - validators: [ - { - address: '0x0272625243bf2377f87538031fed54e21853cc2d', - alias: AW_VALIDATOR_ALIAS, - }, - ], - }, - flare: { threshold: 2, validators: [ @@ -925,22 +821,36 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - formtestnet: { - threshold: 1, + forma: { + threshold: 4, validators: [ { - address: '0x72ad7fddf16d17ff902d788441151982fa31a7bc', - alias: AW_VALIDATOR_ALIAS, + address: '0x5B19F64F04f495D3958804Ec416c165F00f74898', + alias: 'Cosmostation', }, - ], - }, - - fractal: { - threshold: 1, - validators: [ { - address: '0x3476c9652d3371bb01bbb4962516fffee5e73754', - alias: AW_VALIDATOR_ALIAS, + address: '0x3f869C36110F00D10dC74cca3ac1FB133cf019ad', + alias: 'Polkachu', + }, + { + address: '0xE74c7632aF1De54D208f1b9e18B22988dDc8C4CE', + alias: 'Imperator', + }, + { + address: '0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8', + alias: 'Everstake', + }, + { + address: '0x1734abc14f0e68cdaf64f072831f6a6c8f622c37', + alias: 'DSRV', + }, + { + address: '0xb6536d1b52969d6c66bb85533b9ab04d886b3401', + alias: 'Engima', + }, + { + address: '0x184Fc4899a8271783C962e4841BeE74F8526bC2c', + alias: 'Stakecito', }, ], }, @@ -961,11 +871,11 @@ export const defaultMultisigConfigs: ChainMap = { address: '0x573e960e07ad74ea2c5f1e3c31b2055994b12797', alias: 'Imperator', }, - DEFAULT_BWARE_LABS_VALIDATOR, { address: '0x25b3a88f7cfd3c9f7d7e32b295673a16a6ddbd91', alias: 'Luganodes', }, + DEFAULT_ZEE_PRIME_VALIDATOR, ], }, @@ -1023,14 +933,12 @@ export const defaultMultisigConfigs: ChainMap = { }, gnosis: { - threshold: 3, + threshold: 2, validators: [ { address: '0xd4df66a859585678f2ea8357161d896be19cc1ca', alias: AW_VALIDATOR_ALIAS, }, - { address: '0x19fb7e04a1be6b39b6966a0b0c60b929a93ed672', alias: 'DSRV' }, - DEFAULT_EVERSTAKE_VALIDATOR, DEFAULT_ZEE_PRIME_VALIDATOR, ], }, @@ -1103,8 +1011,8 @@ export const defaultMultisigConfigs: ChainMap = { DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, { - address: '0x36f2bd8200ede5f969d63a0a28e654392c51a193', - alias: 'Imperator', + address: '0x04d949c615c9976f89595ddcb9008c92f8ba7278', + alias: 'Luganodes', }, ], }, @@ -1131,6 +1039,16 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + incentivtestnet: { + threshold: 1, + validators: [ + { + address: '0x3133eeb96fd96f9f99291088613edf7401149e6f', + alias: AW_VALIDATOR_ALIAS, + }, + ], + }, + inevm: { threshold: 2, validators: [ @@ -1209,18 +1127,8 @@ export const defaultMultisigConfigs: ChainMap = { address: '0xff9c1e7b266a36eda0d9177d4236994d94819dc0', alias: 'Luganodes', }, - DEFAULT_BWARE_LABS_VALIDATOR, DEFAULT_TESSELLATED_VALIDATOR, - ], - }, - - inksepolia: { - threshold: 1, - validators: [ - { - address: '0xe61c846aee275070207fcbf43674eb254f06097a', - alias: AW_VALIDATOR_ALIAS, - }, + DEFAULT_ZEE_PRIME_VALIDATOR, ], }, @@ -1278,14 +1186,20 @@ export const defaultMultisigConfigs: ChainMap = { }, DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, - DEFAULT_BLOCKPI_VALIDATOR, - DEFAULT_ZKV_VALIDATOR, - DEFAULT_HASHKEY_CLOUD_VALIDATOR, + DEFAULT_ZEE_PRIME_VALIDATOR, + { + address: '0x0c760f4bcb508db9144b0579e26f5ff8d94daf4d', + alias: 'Luganodes', + }, + { + address: '0x6fbceb2680c8181acf3d1b5f0189e3beaa985338', + alias: 'Enigma', + }, ], }, lisk: { - threshold: 4, + threshold: 5, validators: [ { address: '0xc0b282aa5bac43fee83cf71dc3dd1797c1090ea5', @@ -1300,7 +1214,6 @@ export const defaultMultisigConfigs: ChainMap = { address: '0x4df6e8878992c300e7bfe98cac6bf7d3408b9cbf', alias: 'Imperator', }, - DEFAULT_BWARE_LABS_VALIDATOR, { address: '0xf0da628f3fb71652d48260bad4691054045832ce', alias: 'Luganodes', @@ -1309,6 +1222,7 @@ export const defaultMultisigConfigs: ChainMap = { address: '0xead4141b6ea149901ce4f4b556953f66d04b1d0c', alias: 'Lisk', }, + DEFAULT_ZEE_PRIME_VALIDATOR, ], }, @@ -1327,18 +1241,6 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - lumia: { - threshold: 2, - validators: [ - { - address: '0x9e283254ed2cd2c80f007348c2822fc8e5c2fa5f', - alias: AW_VALIDATOR_ALIAS, - }, - DEFAULT_MERKLY_VALIDATOR, - DEFAULT_MITOSIS_VALIDATOR, - ], - }, - lumiaprism: { threshold: 2, validators: [ @@ -1368,25 +1270,41 @@ export const defaultMultisigConfigs: ChainMap = { alias: 'Neutron', }, { - address: '0xa0eE95e280D46C14921e524B075d0C341e7ad1C8', - alias: 'Cosmos Spaces', + address: '0xa0eE95e280D46C14921e524B075d0C341e7ad1C8', + alias: 'Cosmos Spaces', + }, + { address: '0xcc9a0b6de7fe314bd99223687d784730a75bb957', alias: 'DSRV' }, + ], + }, + + mantle: { + threshold: 4, + validators: [ + { + address: '0xf930636c5a1a8bf9302405f72e3af3c96ebe4a52', + alias: AW_VALIDATOR_ALIAS, + }, + DEFAULT_MERKLY_VALIDATOR, + DEFAULT_MITOSIS_VALIDATOR, + DEFAULT_ZEE_PRIME_VALIDATOR, + { + address: '0xcd3b3a2007aab3b00418fbac12bea19d04243497', + alias: 'Luganodes', + }, + { + address: '0x332b3710e56b843027d4c6da7bca219ece7099b0', + alias: 'Enigma', }, - { address: '0xcc9a0b6de7fe314bd99223687d784730a75bb957', alias: 'DSRV' }, ], }, - mantle: { - threshold: 4, + mantra: { + threshold: 1, validators: [ { - address: '0xf930636c5a1a8bf9302405f72e3af3c96ebe4a52', + address: '0x89b8064e29f125e896f6081ebb77090c46bca9cd', alias: AW_VALIDATOR_ALIAS, }, - DEFAULT_MERKLY_VALIDATOR, - DEFAULT_MITOSIS_VALIDATOR, - DEFAULT_ZKV_VALIDATOR, - DEFAULT_HASHKEY_CLOUD_VALIDATOR, - DEFAULT_BLOCKPI_VALIDATOR, ], }, @@ -1444,8 +1362,8 @@ export const defaultMultisigConfigs: ChainMap = { address: '0x05d91f80377ff5e9c6174025ffaf094c57a4766a', alias: 'Luganodes', }, - DEFAULT_BWARE_LABS_VALIDATOR, DEFAULT_TESSELLATED_VALIDATOR, + DEFAULT_ZEE_PRIME_VALIDATOR, ], }, @@ -1458,9 +1376,15 @@ export const defaultMultisigConfigs: ChainMap = { }, DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, - DEFAULT_BLOCKPI_VALIDATOR, - DEFAULT_ZKV_VALIDATOR, - DEFAULT_HASHKEY_CLOUD_VALIDATOR, + DEFAULT_ZEE_PRIME_VALIDATOR, + { + address: '0xad1df94ae078631bfea1623520125e93a6085555', + alias: 'Luganodes', + }, + { + address: '0x4272e7b93e127da5bc7cee617febf47bcad20def', + alias: 'Enigma', + }, ], }, @@ -1527,6 +1451,26 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + mitosis: { + threshold: 3, + validators: [ + { + address: '0x3b3eb808d90a4e19bb601790a6b6297812d6a61f', + alias: AW_VALIDATOR_ALIAS, + }, + DEFAULT_MITOSIS_VALIDATOR, + DEFAULT_ZEE_PRIME_VALIDATOR, + { + address: '0x401f25ff73769ed85bdb449a4347a4fd2678acfe', + alias: 'Enigma', + }, + { + address: '0x340058f071e8376c2ecff219e1e6620deea8a3c7', + alias: 'Substance Labs', + }, + ], + }, + mode: { threshold: 4, validators: [ @@ -1543,11 +1487,11 @@ export const defaultMultisigConfigs: ChainMap = { address: '0x20eade18ea2af6dfd54d72b3b5366b40fcb47f4b', alias: 'Imperator', }, - DEFAULT_BWARE_LABS_VALIDATOR, { address: '0x485a4f0009d9afbbf44521016f9b8cdd718e36ea', alias: 'Luganodes', }, + DEFAULT_ZEE_PRIME_VALIDATOR, ], }, @@ -1584,15 +1528,13 @@ export const defaultMultisigConfigs: ChainMap = { }, moonbeam: { - threshold: 3, + threshold: 2, validators: [ { address: '0x2225e2f4e9221049456da93b71d2de41f3b6b2a8', alias: AW_VALIDATOR_ALIAS, }, - { address: '0x645428d198d2e76cbd9c1647f5c80740bb750b97', alias: 'DSRV' }, - DEFAULT_EVERSTAKE_VALIDATOR, - DEFAULT_STAKED_VALIDATOR, + DEFAULT_MITOSIS_VALIDATOR, ], }, @@ -1608,18 +1550,6 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - nero: { - threshold: 2, - validators: [ - { - address: '0xb86f872df37f11f33acbe75b6ed208b872b57183', - alias: AW_VALIDATOR_ALIAS, - }, - DEFAULT_MERKLY_VALIDATOR, - DEFAULT_MITOSIS_VALIDATOR, - ], - }, - neuratestnet: { threshold: 1, validators: [ @@ -1687,16 +1617,6 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - odysseytestnet: { - threshold: 1, - validators: [ - { - address: '0xcc0a6e2d6aa8560b45b384ced7aa049870b66ea3', - alias: AW_VALIDATOR_ALIAS, - }, - ], - }, - ontology: { threshold: 3, validators: [ @@ -1753,11 +1673,11 @@ export const defaultMultisigConfigs: ChainMap = { address: '0x1b9e5f36c4bfdb0e3f0df525ef5c888a4459ef99', alias: 'Imperator', }, - DEFAULT_BWARE_LABS_VALIDATOR, { address: '0xf9dfaa5c20ae1d84da4b2696b8dc80c919e48b12', alias: 'Luganodes', }, + DEFAULT_ZEE_PRIME_VALIDATOR, ], }, @@ -1826,47 +1746,47 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - plume: { + plasma: { threshold: 2, validators: [ { - address: '0x63c9b5ea28710d956a51f0f746ee8df81215663f', + address: '0x4ba900a8549fe503bca674114dc98a254637fc2c', alias: AW_VALIDATOR_ALIAS, }, - DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, ], }, - plumetestnet: { - threshold: 1, + plume: { + threshold: 2, validators: [ { - address: '0xe765a214849f3ecdf00793b97d00422f2d408ea6', + address: '0x63c9b5ea28710d956a51f0f746ee8df81215663f', alias: AW_VALIDATOR_ALIAS, }, + DEFAULT_MERKLY_VALIDATOR, + DEFAULT_MITOSIS_VALIDATOR, ], }, - plumetestnet2: { + plumetestnet: { threshold: 1, validators: [ { - address: '0x16637c78e1ea169132efcf4df8ebd03de349e740', + address: '0xe765a214849f3ecdf00793b97d00422f2d408ea6', alias: AW_VALIDATOR_ALIAS, }, ], }, polygon: { - threshold: 3, + threshold: 2, validators: [ { address: '0x12ecb319c7f4e8ac5eb5226662aeb8528c5cefac', alias: AW_VALIDATOR_ALIAS, }, { address: '0x008f24cbb1cc30ad0f19f2516ca75730e37efb5f', alias: 'DSRV' }, - DEFAULT_EVERSTAKE_VALIDATOR, DEFAULT_ZEE_PRIME_VALIDATOR, ], }, @@ -1889,7 +1809,6 @@ export const defaultMultisigConfigs: ChainMap = { alias: AW_VALIDATOR_ALIAS, }, { address: '0x865818fe1db986036d5fd0466dcd462562436d1a', alias: 'DSRV' }, - DEFAULT_EVERSTAKE_VALIDATOR, ], }, @@ -1929,51 +1848,43 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - rarichain: { + pulsechain: { threshold: 2, validators: [ { - address: '0xeac012df7530720dd7d6f9b727e4fe39807d1516', + address: '0xa73fc7ebb2149d9c6992ae002cb1849696be895b', alias: AW_VALIDATOR_ALIAS, }, - DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, ], }, - reactive: { + radix: { threshold: 2, validators: [ { - address: '0x45768525f6c5ca2e4e7cc50d405370eadee2d624', + address: '0xa715a7cd97f68caeedb7be64f9e1da10f8ffafb4', alias: AW_VALIDATOR_ALIAS, }, - DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, ], }, - redstone: { - threshold: 3, + radixtestnet: { + threshold: 1, validators: [ { - address: '0x1400b9737007f7978d8b4bbafb4a69c83f0641a7', + address: '0xeddaf7958627cfd35400c95db19a656a4a8a92c6', alias: AW_VALIDATOR_ALIAS, }, - DEFAULT_MERKLY_VALIDATOR, - DEFAULT_MITOSIS_VALIDATOR, - { - address: '0x101cE77261245140A0871f9407d6233C8230Ec47', - alias: 'Blockhunters', - }, ], }, - rivalz: { + rarichain: { threshold: 2, validators: [ { - address: '0xf87c3eb3dde972257b0d6d110bdadcda951c0dc1', + address: '0xeac012df7530720dd7d6f9b727e4fe39807d1516', alias: AW_VALIDATOR_ALIAS, }, DEFAULT_MERKLY_VALIDATOR, @@ -1981,26 +1892,23 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - ronin: { - threshold: 4, + reactive: { + threshold: 2, validators: [ { - address: '0xa3e11929317e4a871c3d47445ea7bb8c4976fd8a', + address: '0x45768525f6c5ca2e4e7cc50d405370eadee2d624', alias: AW_VALIDATOR_ALIAS, }, DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, - DEFAULT_BLOCKPI_VALIDATOR, - DEFAULT_ZKV_VALIDATOR, - DEFAULT_HASHKEY_CLOUD_VALIDATOR, ], }, - rootstockmainnet: { + redstone: { threshold: 2, validators: [ { - address: '0x8675eb603d62ab64e3efe90df914e555966e04ac', + address: '0x1400b9737007f7978d8b4bbafb4a69c83f0641a7', alias: AW_VALIDATOR_ALIAS, }, DEFAULT_MERKLY_VALIDATOR, @@ -2008,28 +1916,35 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - sanko: { - threshold: 2, + ronin: { + threshold: 4, validators: [ { - address: '0x795c37d5babbc44094b084b0c89ed9db9b5fae39', + address: '0xa3e11929317e4a871c3d47445ea7bb8c4976fd8a', alias: AW_VALIDATOR_ALIAS, }, DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, + DEFAULT_ZEE_PRIME_VALIDATOR, + { + address: '0x808a3945d5f9c2f9ccf7a76bde4c4b54c9c7dba4', + alias: 'Luganodes', + }, + { + address: '0xe8a821e77bd1ee4658c29e8c3f43c0200b0f06a1', + alias: 'Enigma', + }, ], }, scroll: { - threshold: 3, + threshold: 2, validators: [ { address: '0xad557170a9f2f21c35e03de07cb30dcbcc3dff63', alias: AW_VALIDATOR_ALIAS, }, DEFAULT_STAKED_VALIDATOR, - DEFAULT_EVERSTAKE_VALIDATOR, - { address: '0xbac4ac39f1d8b5ef15f26fdb1294a7c9aba3f948', alias: 'DSRV' }, ], }, @@ -2052,17 +1967,13 @@ export const defaultMultisigConfigs: ChainMap = { }, sei: { - threshold: 3, + threshold: 2, validators: [ { address: '0x9920d2dbf6c85ffc228fdc2e810bf895732c6aa5', alias: AW_VALIDATOR_ALIAS, }, DEFAULT_MERKLY_VALIDATOR, - { - address: '0x101cE77261245140A0871f9407d6233C8230Ec47', - alias: 'Blockhunters', - }, DEFAULT_MITOSIS_VALIDATOR, ], }, @@ -2138,12 +2049,12 @@ export const defaultMultisigConfigs: ChainMap = { address: '0x2b7514a2f77bd86bbf093fe6bb67d8611f51c659', alias: 'Luganodes', }, - { address: '0xd90ea26ff731d967c5ea660851f7d63cb04ab820', alias: 'DSRV' }, - DEFAULT_EVERSTAKE_VALIDATOR, { address: '0xcb6bcbd0de155072a7ff486d9d7286b0f71dcc2d', alias: 'Eclipse', }, + DEFAULT_MITOSIS_VALIDATOR, + DEFAULT_ZEE_PRIME_VALIDATOR, ], }, @@ -2197,18 +2108,8 @@ export const defaultMultisigConfigs: ChainMap = { address: '0x6c5f6ab7a369222e6691218ad981fe08a5def094', alias: 'Luganodes', }, - DEFAULT_BWARE_LABS_VALIDATOR, DEFAULT_TESSELLATED_VALIDATOR, - ], - }, - - soneiumtestnet: { - threshold: 1, - validators: [ - { - address: '0x2e2101020ccdbe76aeda1c27823b0150f43d0c63', - alias: AW_VALIDATOR_ALIAS, - }, + DEFAULT_ZEE_PRIME_VALIDATOR, ], }, @@ -2221,18 +2122,14 @@ export const defaultMultisigConfigs: ChainMap = { }, DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, - DEFAULT_BLOCKPI_VALIDATOR, - DEFAULT_ZKV_VALIDATOR, - DEFAULT_HASHKEY_CLOUD_VALIDATOR, - ], - }, - - sonicblaze: { - threshold: 1, - validators: [ + DEFAULT_ZEE_PRIME_VALIDATOR, { - address: '0xe5b98110d0688691ea280edea9a4faa1e3617ba1', - alias: AW_VALIDATOR_ALIAS, + address: '0x7f0e75c5151d0938eaa9ab8a30f9ddbd74c4ebef', + alias: 'Luganodes', + }, + { + address: '0x4e3d1c926843dcc8ff47061bbd7143a2755899f3', + alias: 'Enigma', }, ], }, @@ -2287,6 +2184,16 @@ export const defaultMultisigConfigs: ChainMap = { ], }, + sova: { + threshold: 1, + validators: [ + { + address: '0x1763d686c45df79f6d5f63564255546b08cb206c', + alias: AW_VALIDATOR_ALIAS, + }, + ], + }, + starknet: { threshold: 2, validators: [ @@ -2322,9 +2229,12 @@ export const defaultMultisigConfigs: ChainMap = { }, stride: { - threshold: 6, + threshold: 7, validators: [ - DEFAULT_EVERSTAKE_VALIDATOR, + { + address: '0x38c7a4ca1273ead2e867d096adbcdd0e2acb21d8', + alias: 'Everstake', + }, { address: '0x88f0E5528131b10e3463C4c68108217Dd33462ac', alias: 'Cosmostation', @@ -2356,7 +2266,7 @@ export const defaultMultisigConfigs: ChainMap = { }, subtensor: { - threshold: 4, + threshold: 3, validators: [ { address: '0xd5f8196d7060b85bea491f0b52a671e05f3d10a2', @@ -2364,9 +2274,7 @@ export const defaultMultisigConfigs: ChainMap = { }, DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, - DEFAULT_BLOCKPI_VALIDATOR, - DEFAULT_ZKV_VALIDATOR, - DEFAULT_TESSELLATED_VALIDATOR, + DEFAULT_ZEE_PRIME_VALIDATOR, ], }, @@ -2392,16 +2300,6 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - superpositiontestnet: { - threshold: 1, - validators: [ - { - address: '0x1d3168504b23b73cdf9c27f13bb0a595d7f1a96a', - alias: AW_VALIDATOR_ALIAS, - }, - ], - }, - superseed: { threshold: 4, validators: [ @@ -2421,8 +2319,8 @@ export const defaultMultisigConfigs: ChainMap = { address: '0x55880ac03fdf15fccff54ed6f8a83455033edd22', alias: 'Luganodes', }, - DEFAULT_BWARE_LABS_VALIDATOR, DEFAULT_TESSELLATED_VALIDATOR, + DEFAULT_ZEE_PRIME_VALIDATOR, ], }, @@ -2457,8 +2355,8 @@ export const defaultMultisigConfigs: ChainMap = { address: '0x3f707633ccab09d2978e29107c0bbef8a993e7a0', alias: 'Enigma', }, - DEFAULT_BWARE_LABS_VALIDATOR, DEFAULT_TESSELLATED_VALIDATOR, + DEFAULT_ZEE_PRIME_VALIDATOR, ], }, @@ -2504,18 +2402,6 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - telos: { - threshold: 2, - validators: [ - { - address: '0xcb08410b14d3adf0d0646f0c61cd07e0daba8e54', - alias: AW_VALIDATOR_ALIAS, - }, - DEFAULT_MERKLY_VALIDATOR, - DEFAULT_MITOSIS_VALIDATOR, - ], - }, - torus: { threshold: 2, validators: [ @@ -2548,8 +2434,8 @@ export const defaultMultisigConfigs: ChainMap = { address: '0xa9d517776fe8beba7d67c21cac1e805bd609c08e', alias: 'Luganodes', }, - DEFAULT_BWARE_LABS_VALIDATOR, DEFAULT_TESSELLATED_VALIDATOR, + DEFAULT_ZEE_PRIME_VALIDATOR, ], }, @@ -2563,20 +2449,8 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - unitzero: { - threshold: 2, - validators: [ - { - address: '0x18818e3ad2012728465d394f2e3c0ea2357ae9c5', - alias: AW_VALIDATOR_ALIAS, - }, - DEFAULT_MERKLY_VALIDATOR, - DEFAULT_MITOSIS_VALIDATOR, - ], - }, - vana: { - threshold: 3, + threshold: 2, validators: [ { address: '0xfdf3b0dfd4b822d10cacb15c8ae945ea269e7534', @@ -2584,10 +2458,6 @@ export const defaultMultisigConfigs: ChainMap = { }, DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, - { - address: '0xba2f4f89cae6863d8b49e4ca0208ed48ad9ac354', - alias: 'P2P', - }, ], }, @@ -2603,16 +2473,6 @@ export const defaultMultisigConfigs: ChainMap = { ], }, - weavevmtestnet: { - threshold: 1, - validators: [ - { - address: '0x6d2ee6688de903bb31f3ae2ea31da87b697f7f40', - alias: AW_VALIDATOR_ALIAS, - }, - ], - }, - worldchain: { threshold: 4, validators: [ @@ -2625,9 +2485,15 @@ export const defaultMultisigConfigs: ChainMap = { alias: 'Imperator', }, DEFAULT_MERKLY_VALIDATOR, - DEFAULT_ZKV_VALIDATOR, - DEFAULT_HASHKEY_CLOUD_VALIDATOR, - DEFAULT_BLOCKPI_VALIDATOR, + DEFAULT_ZEE_PRIME_VALIDATOR, + { + address: '0xc1545f9fe903736b2e438b733740bd3516486da5', + alias: 'Luganodes', + }, + { + address: '0x698810f8ae471f7e34860b465aeeb03df407be47', + alias: 'Enigma', + }, ], }, @@ -2650,34 +2516,29 @@ export const defaultMultisigConfigs: ChainMap = { address: '0xa2ae7c594703e988f23d97220717c513db638ea3', alias: AW_VALIDATOR_ALIAS, }, - { - address: '0xfed056cC0967F5BC9C6350F6C42eE97d3983394d', - alias: 'Imperator', - }, DEFAULT_MERKLY_VALIDATOR, + DEFAULT_MITOSIS_VALIDATOR, ], }, - xpla: { + xrplevm: { threshold: 2, validators: [ { - address: '0xc11cba01d67f2b9f0288c4c8e8b23c0eca03f26e', + address: '0x14d3e2f28d60d54a1659a205cb71e6e440f06510', alias: AW_VALIDATOR_ALIAS, }, - DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, ], }, - xrplevm: { - threshold: 2, + zerogravity: { + threshold: 1, validators: [ { - address: '0x14d3e2f28d60d54a1659a205cb71e6e440f06510', + address: '0xc37e7dad064c11d7ecfc75813a4d8d649d797275', alias: AW_VALIDATOR_ALIAS, }, - DEFAULT_MITOSIS_VALIDATOR, ], }, @@ -2694,17 +2555,13 @@ export const defaultMultisigConfigs: ChainMap = { }, zetachain: { - threshold: 3, + threshold: 2, validators: [ { address: '0xa3bca0b80317dbf9c7dce16a16ac89f4ff2b23ef', alias: AW_VALIDATOR_ALIAS, }, DEFAULT_MERKLY_VALIDATOR, - { - address: '0x101cE77261245140A0871f9407d6233C8230Ec47', - alias: 'Blockhunters', - }, DEFAULT_MITOSIS_VALIDATOR, ], }, @@ -2720,28 +2577,13 @@ export const defaultMultisigConfigs: ChainMap = { address: '0x7aC6584c068eb2A72d4Db82A7B7cd5AB34044061', alias: 'Luganodes', }, - { - address: '0x0180444c9342BD672867Df1432eb3dA354413a6E', - alias: 'Hashkey Cloud', - }, { address: '0x1da9176C2CE5cC7115340496fa7D1800a98911CE', alias: 'Renzo' }, - ], - }, - - zklink: { - threshold: 2, - validators: [ - { - address: '0x217a8cb4789fc45abf56cb6e2ca96f251a5ac181', - alias: AW_VALIDATOR_ALIAS, - }, - DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, ], }, zksync: { - threshold: 3, + threshold: 2, validators: [ { address: '0xadd1d39ce7a687e32255ac457cf99a6d8c5b5d1a', @@ -2749,24 +2591,16 @@ export const defaultMultisigConfigs: ChainMap = { }, DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, - { - address: '0x75237d42ce8ea27349a0254ada265db94157e0c1', - alias: 'Imperator', - }, ], }, zoramainnet: { - threshold: 3, + threshold: 2, validators: [ { address: '0x35130945b625bb69b28aee902a3b9a76fa67125f', alias: AW_VALIDATOR_ALIAS, }, - { - address: '0x7089b6352d37d23fb05a7fee4229c78e038fba09', - alias: 'Imperator', - }, DEFAULT_MERKLY_VALIDATOR, DEFAULT_MITOSIS_VALIDATOR, ], diff --git a/typescript/sdk/src/contracts/contracts.ts b/typescript/sdk/src/contracts/contracts.ts index 63cfc727b54..d44b3bab011 100644 --- a/typescript/sdk/src/contracts/contracts.ts +++ b/typescript/sdk/src/contracts/contracts.ts @@ -6,6 +6,7 @@ import { EvmChainId, ProtocolType, ValueOf, + addressToByteHexString, assert, eqAddress, hexOrBase58ToHex, @@ -196,8 +197,12 @@ export function attachContractsMapAndGetForeignDeployments< case ProtocolType.Cosmos: case ProtocolType.CosmosNative: + case ProtocolType.Starknet: return router; + case ProtocolType.Radix: + return addressToByteHexString(router, ProtocolType.Radix); + case ProtocolType.Sealevel: return hexOrBase58ToHex(router); diff --git a/typescript/sdk/src/core/CosmosNativeCoreModule.ts b/typescript/sdk/src/core/CosmosNativeCoreModule.ts index 16c0322eee7..e2e77dc115d 100644 --- a/typescript/sdk/src/core/CosmosNativeCoreModule.ts +++ b/typescript/sdk/src/core/CosmosNativeCoreModule.ts @@ -114,7 +114,6 @@ export class CosmosNativeCoreModule extends HyperlaneModule< chain: chainName, config: config.defaultIsm, addresses: { - deployedIsm: '', mailbox: '', }, multiProvider, diff --git a/typescript/sdk/src/core/MultiProtocolCore.ts b/typescript/sdk/src/core/MultiProtocolCore.ts index c5e5d31893e..b0c0ce665ac 100644 --- a/typescript/sdk/src/core/MultiProtocolCore.ts +++ b/typescript/sdk/src/core/MultiProtocolCore.ts @@ -8,6 +8,7 @@ import { ChainMap, ChainName } from '../types.js'; import { CosmNativeCoreAdapter } from './adapters/CosmNativeCoreAdapter.js'; import { CosmWasmCoreAdapter } from './adapters/CosmWasmCoreAdapter.js'; import { EvmCoreAdapter } from './adapters/EvmCoreAdapter.js'; +import { RadixCoreAdapter } from './adapters/RadixCoreAdapter.js'; import { SealevelCoreAdapter } from './adapters/SealevelCoreAdapter.js'; import { StarknetCoreAdapter } from './adapters/StarknetCoreAdapter.js'; import { ICoreAdapter } from './adapters/types.js'; @@ -43,6 +44,7 @@ export class MultiProtocolCore extends MultiProtocolApp< if (protocol === ProtocolType.Cosmos) return CosmWasmCoreAdapter; if (protocol === ProtocolType.CosmosNative) return CosmNativeCoreAdapter; if (protocol === ProtocolType.Starknet) return StarknetCoreAdapter; + if (protocol === ProtocolType.Radix) return RadixCoreAdapter; throw new Error(`No adapter for protocol ${protocol}`); } diff --git a/typescript/sdk/src/core/RadixCoreReader.ts b/typescript/sdk/src/core/RadixCoreReader.ts new file mode 100644 index 00000000000..eeb7655b2b8 --- /dev/null +++ b/typescript/sdk/src/core/RadixCoreReader.ts @@ -0,0 +1,41 @@ +import { RadixSDK } from '@hyperlane-xyz/radix-sdk'; +import { Address, assert, rootLogger } from '@hyperlane-xyz/utils'; + +import { RadixHookReader } from '../hook/RadixHookReader.js'; +import { RadixIsmReader } from '../ism/RadixIsmReader.js'; +import { ChainMetadataManager } from '../metadata/ChainMetadataManager.js'; + +import { DerivedCoreConfig } from './types.js'; + +export class RadixCoreReader { + protected readonly logger = rootLogger.child({ + module: 'RadixCoreReader', + }); + protected ismReader: RadixIsmReader; + protected hookReader: RadixHookReader; + + constructor( + protected readonly metadataManager: ChainMetadataManager, + protected readonly sdk: RadixSDK, + ) { + this.ismReader = new RadixIsmReader(this.metadataManager, this.sdk); + this.hookReader = new RadixHookReader(this.metadataManager, this.sdk); + } + + async deriveCoreConfig(mailboxAddress: Address): Promise { + const mailbox = await this.sdk.query.core.getMailbox({ + mailbox: mailboxAddress, + }); + + assert(mailbox, `Mailbox not found for address ${mailboxAddress}`); + + return { + owner: mailbox.owner, + defaultIsm: await this.ismReader.deriveIsmConfig(mailbox.default_ism), + defaultHook: await this.hookReader.deriveHookConfig(mailbox.default_hook), + requiredHook: await this.hookReader.deriveHookConfig( + mailbox.required_hook, + ), + }; + } +} diff --git a/typescript/sdk/src/core/adapters/RadixCoreAdapter.ts b/typescript/sdk/src/core/adapters/RadixCoreAdapter.ts new file mode 100644 index 00000000000..b913f33ef0b --- /dev/null +++ b/typescript/sdk/src/core/adapters/RadixCoreAdapter.ts @@ -0,0 +1,79 @@ +import { + Address, + HexString, + assert, + ensure0x, + messageId, +} from '@hyperlane-xyz/utils'; + +import { BaseRadixAdapter } from '../../app/MultiProtocolApp.js'; +import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider.js'; +import { + ProviderType, + TypedTransactionReceipt, +} from '../../providers/ProviderType.js'; +import { ChainName } from '../../types.js'; + +import { ICoreAdapter } from './types.js'; + +const MESSAGE_DISPATCH_EVENT_TYPE = 'DispatchEvent'; +const MESSAGE_FIELD_KEY = 'message'; +const MESSAGE_DESTINATION_FIELD_KEY = 'destination'; + +export class RadixCoreAdapter extends BaseRadixAdapter implements ICoreAdapter { + constructor( + public readonly chainName: ChainName, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: { mailbox: Address }, + ) { + super(chainName, multiProvider, addresses); + } + + extractMessageIds( + sourceTx: TypedTransactionReceipt, + ): Array<{ messageId: string; destination: ChainName }> { + assert( + sourceTx.type === ProviderType.Radix, + `Unsupported provider type for RadixCoreAdapter ${sourceTx.type}`, + ); + + const events = sourceTx.receipt.transaction.receipt?.events ?? []; + if (events.length === 0) { + return []; + } + + const dispatchEvents = events.filter( + (e) => e.name === MESSAGE_DISPATCH_EVENT_TYPE, + ); + + return dispatchEvents.map((event) => { + const findField = (key: string) => + ((event.data as any)?.fields ?? []).find( + (f: any) => f.field_name === key, + ); + + const messageField = findField(MESSAGE_FIELD_KEY); + const destField = findField(MESSAGE_DESTINATION_FIELD_KEY); + + assert(messageField, 'No message field found in dispatch event'); + assert(destField, 'No destination field found in dispatch event'); + + return { + messageId: ensure0x(messageId(ensure0x(messageField.hex))), + destination: this.multiProvider.getChainName(destField.value), + }; + }); + } + + async waitForMessageProcessed( + messageId: HexString, + destination: ChainName, + _delayMs?: number, + _maxAttempts?: number, + ): Promise { + const provider = this.multiProvider.getRadixProvider(destination); + + await provider.base.pollForCommit(messageId); + return true; + } +} diff --git a/typescript/sdk/src/deploy/warp.ts b/typescript/sdk/src/deploy/warp.ts index 25732499f9f..10efd1d19b9 100644 --- a/typescript/sdk/src/deploy/warp.ts +++ b/typescript/sdk/src/deploy/warp.ts @@ -2,25 +2,37 @@ import { ProxyAdmin__factory } from '@hyperlane-xyz/core'; import { buildArtifact as coreBuildArtifact } from '@hyperlane-xyz/core/buildArtifact.js'; import { Address, + ProtocolType, + isObjEmpty, + objFilter, + objKeys, objMap, promiseObjAll, rootLogger, } from '@hyperlane-xyz/utils'; import { CCIPContractCache } from '../ccip/utils.js'; -import { HyperlaneContractsMap } from '../contracts/types.js'; +import { + HyperlaneContracts, + HyperlaneContractsMap, +} from '../contracts/types.js'; import { EvmHookModule } from '../hook/EvmHookModule.js'; import { HookConfig } from '../hook/types.js'; +import { CosmosNativeIsmModule } from '../ism/CosmosNativeIsmModule.js'; import { EvmIsmModule } from '../ism/EvmIsmModule.js'; import { IsmConfig } from '../ism/types.js'; import { MultiProvider } from '../providers/MultiProvider.js'; -import { HypERC20Factories, HypERC721Factories } from '../token/contracts.js'; +import { GroupedTransactions } from '../providers/ProviderType.js'; +import { CosmosNativeWarpModule } from '../token/CosmosNativeWarpModule.js'; +import { EvmERC20WarpModule } from '../token/EvmERC20WarpModule.js'; +import { HypERC20Factories, hypERC20factories } from '../token/contracts.js'; +import { CosmosNativeDeployer } from '../token/cosmosnativeDeploy.js'; import { HypERC20Deployer, HypERC721Deployer } from '../token/deploy.js'; import { HypTokenRouterConfig, WarpRouteDeployConfigMailboxRequired, } from '../token/types.js'; -import { ChainMap } from '../types.js'; +import { ChainMap, IMultiProtocolSignerManager } from '../types.js'; import { extractIsmAndHookFactoryAddresses } from '../utils/ism.js'; import { HyperlaneProxyFactoryDeployer } from './HyperlaneProxyFactoryDeployer.js'; @@ -30,15 +42,12 @@ import { ExplorerLicenseType } from './verify/types.js'; type ChainAddresses = Record; export async function executeWarpDeploy( - multiProvider: MultiProvider, warpDeployConfig: WarpRouteDeployConfigMailboxRequired, + multiProvider: MultiProvider, + multiProtocolSigner: IMultiProtocolSignerManager, registryAddresses: ChainMap, apiKeys: ChainMap, -): Promise> { - const deployer = warpDeployConfig.isNft - ? new HypERC721Deployer(multiProvider) - : new HypERC20Deployer(multiProvider); // TODO: replace with EvmERC20WarpModule - +): Promise> { const contractVerifier = new ContractVerifier( multiProvider, apiKeys, @@ -56,12 +65,70 @@ export async function executeWarpDeploy( const modifiedConfig = await resolveWarpIsmAndHook( warpDeployConfig, multiProvider, + multiProtocolSigner, registryAddresses, ismFactoryDeployer, contractVerifier, ); - const deployedContracts = await deployer.deploy(modifiedConfig); + let deployedContracts: ChainMap
= {}; + + // get unique list of protocols + const protocols = Array.from( + new Set( + Object.keys(modifiedConfig).map((chainName) => + multiProvider.getProtocol(chainName), + ), + ), + ); + + for (const protocol of protocols) { + const protocolSpecificConfig = objFilter( + modifiedConfig, + (chainName, _): _ is any => + multiProvider.getProtocol(chainName) === protocol, + ); + + if (isObjEmpty(protocolSpecificConfig)) { + continue; + } + + switch (protocol) { + case ProtocolType.Ethereum: { + const deployer = warpDeployConfig.isNft + ? new HypERC721Deployer(multiProvider) + : new HypERC20Deployer(multiProvider); // TODO: replace with EvmERC20WarpModule + + const evmContracts = await deployer.deploy(protocolSpecificConfig); + deployedContracts = { + ...deployedContracts, + ...objMap( + evmContracts as HyperlaneContractsMap, + (_, contracts) => getRouter(contracts).address, + ), + }; + + break; + } + case ProtocolType.CosmosNative: { + const signersMap = objMap( + protocolSpecificConfig, + (chain, _) => multiProtocolSigner.getCosmosNativeSigner(chain)!, + ); + + const deployer = new CosmosNativeDeployer(multiProvider, signersMap); + deployedContracts = { + ...deployedContracts, + ...(await deployer.deploy(protocolSpecificConfig)), + }; + + break; + } + default: { + throw new Error(`Protocol type ${protocol} not supported`); + } + } + } return deployedContracts; } @@ -69,9 +136,10 @@ export async function executeWarpDeploy( async function resolveWarpIsmAndHook( warpConfig: WarpRouteDeployConfigMailboxRequired, multiProvider: MultiProvider, + multiProtocolSigner: IMultiProtocolSignerManager, registryAddresses: ChainMap, ismFactoryDeployer: HyperlaneProxyFactoryDeployer, - contractVerifier?: ContractVerifier, + contractVerifier: ContractVerifier, ): Promise { return promiseObjAll( objMap(warpConfig, async (chain, config) => { @@ -87,6 +155,7 @@ async function resolveWarpIsmAndHook( chain, chainAddresses, multiProvider, + multiProtocolSigner, contractVerifier, ismFactoryDeployer, warpConfig: config, @@ -116,6 +185,7 @@ async function createWarpIsm({ chain, chainAddresses, multiProvider, + multiProtocolSigner, contractVerifier, warpConfig, }: { @@ -123,6 +193,7 @@ async function createWarpIsm({ chain: string; chainAddresses: Record; multiProvider: MultiProvider; + multiProtocolSigner: IMultiProtocolSignerManager; contractVerifier?: ContractVerifier; warpConfig: HypTokenRouterConfig; ismFactoryDeployer: HyperlaneProxyFactoryDeployer; @@ -141,24 +212,50 @@ async function createWarpIsm({ } rootLogger.info(`Loading registry factory addresses for ${chain}...`); + rootLogger.info( `Creating ${interchainSecurityModule.type} ISM for token on ${chain} chain...`, ); + rootLogger.info( `Finished creating ${interchainSecurityModule.type} ISM for token on ${chain} chain.`, ); - const evmIsmModule = await EvmIsmModule.create({ - chain, - mailbox: chainAddresses.mailbox, - multiProvider, - proxyFactoryFactories: extractIsmAndHookFactoryAddresses(chainAddresses), - config: interchainSecurityModule, - ccipContractCache, - contractVerifier, - }); - const { deployedIsm } = evmIsmModule.serialize(); - return deployedIsm; + const protocolType = multiProvider.getProtocol(chain); + + switch (protocolType) { + case ProtocolType.Ethereum: { + const evmIsmModule = await EvmIsmModule.create({ + chain, + mailbox: chainAddresses.mailbox, + multiProvider: multiProvider, + proxyFactoryFactories: + extractIsmAndHookFactoryAddresses(chainAddresses), + config: interchainSecurityModule, + ccipContractCache, + contractVerifier, + }); + const { deployedIsm } = evmIsmModule.serialize(); + return deployedIsm; + } + case ProtocolType.CosmosNative: { + const signer = multiProtocolSigner!.getCosmosNativeSigner(chain); + + const cosmosIsmModule = await CosmosNativeIsmModule.create({ + chain, + multiProvider: multiProvider, + addresses: { + mailbox: chainAddresses.mailbox, + }, + config: interchainSecurityModule, + signer, + }); + const { deployedIsm } = cosmosIsmModule.serialize(); + return deployedIsm; + } + default: + throw new Error(`Protocol type ${protocolType} not supported`); + } } async function createWarpHook({ @@ -187,30 +284,190 @@ async function createWarpHook({ } rootLogger.info(`Loading registry factory addresses for ${chain}...`); + rootLogger.info(`Creating ${hook.type} Hook for token on ${chain} chain...`); - // If config.proxyadmin.address exists, then use that. otherwise deploy a new proxyAdmin - const proxyAdminAddress: Address = - warpConfig.proxyAdmin?.address ?? - (await multiProvider.handleDeploy(chain, new ProxyAdmin__factory(), [])) - .address; + const protocolType = multiProvider.getProtocol(chain); + + switch (protocolType) { + case ProtocolType.Ethereum: { + rootLogger.info(`Loading registry factory addresses for ${chain}...`); + + rootLogger.info( + `Creating ${hook.type} Hook for token on ${chain} chain...`, + ); - const evmHookModule = await EvmHookModule.create({ - chain, + // If config.proxyadmin.address exists, then use that. otherwise deploy a new proxyAdmin + const proxyAdminAddress: Address = + warpConfig.proxyAdmin?.address ?? + (await multiProvider.handleDeploy(chain, new ProxyAdmin__factory(), [])) + .address; + + const evmHookModule = await EvmHookModule.create({ + chain, + multiProvider: multiProvider, + coreAddresses: { + mailbox: chainAddresses.mailbox, + proxyAdmin: proxyAdminAddress, + }, + config: hook, + ccipContractCache, + contractVerifier, + proxyFactoryFactories: + extractIsmAndHookFactoryAddresses(chainAddresses), + }); + rootLogger.info( + `Finished creating ${hook.type} Hook for token on ${chain} chain.`, + ); + const { deployedHook } = evmHookModule.serialize(); + return deployedHook; + } + case ProtocolType.CosmosNative: { + rootLogger.info( + `No warp hooks for Cosmos Native chains, skipping deployment.`, + ); + return hook; + } + default: + throw new Error(`Protocol type ${protocolType} not supported`); + } +} + +export async function enrollCrossChainRouters( + { multiProvider, - coreAddresses: { - mailbox: chainAddresses.mailbox, - proxyAdmin: proxyAdminAddress, - }, - config: hook, - ccipContractCache, - contractVerifier, - proxyFactoryFactories: extractIsmAndHookFactoryAddresses(chainAddresses), - }); - rootLogger.info( - `Finished creating ${hook.type} Hook for token on ${chain} chain.`, + multiProtocolSigner, + registryAddresses, + warpDeployConfig, + }: { + multiProvider: MultiProvider; + multiProtocolSigner: IMultiProtocolSignerManager; + registryAddresses: ChainMap; + warpDeployConfig: WarpRouteDeployConfigMailboxRequired; + }, + deployedContracts: ChainMap
, +) { + const resolvedConfigMap = objMap(warpDeployConfig, (_, config) => ({ + gas: 0, // TODO: protocol specific gas?, + ...config, + })); + + const configMapToDeploy = objFilter( + resolvedConfigMap, + (_, config: any): config is any => !config.foreignDeployment, ); - const { deployedHook } = evmHookModule.serialize(); - return deployedHook; + const allChains = Object.keys(configMapToDeploy); + + for (const chain of allChains) { + const protocol = multiProvider.getProtocol(chain); + + const allRemoteChains = multiProvider + .getRemoteChains(chain) + .filter((c) => allChains.includes(c)); + + const protocolTransactions = {} as GroupedTransactions; + + switch (protocol) { + case ProtocolType.Ethereum: { + const { + domainRoutingIsmFactory, + staticMerkleRootMultisigIsmFactory, + staticMessageIdMultisigIsmFactory, + staticAggregationIsmFactory, + staticAggregationHookFactory, + staticMerkleRootWeightedMultisigIsmFactory, + staticMessageIdWeightedMultisigIsmFactory, + } = registryAddresses[chain]; + + const evmWarpModule = new EvmERC20WarpModule(multiProvider, { + chain, + config: configMapToDeploy[chain], + addresses: { + deployedTokenRoute: deployedContracts[chain], + domainRoutingIsmFactory, + staticMerkleRootMultisigIsmFactory, + staticMessageIdMultisigIsmFactory, + staticAggregationIsmFactory, + staticAggregationHookFactory, + staticMerkleRootWeightedMultisigIsmFactory, + staticMessageIdWeightedMultisigIsmFactory, + }, + }); + + const actualConfig = await evmWarpModule.read(); + const expectedConfig = { + ...actualConfig, + remoteRouters: (() => { + const routers: Record = {}; + for (const c of allRemoteChains) { + routers[multiProvider.getDomainId(c).toString()] = { + address: deployedContracts[c], + }; + } + return routers; + })(), + }; + + const transactions = await evmWarpModule.update(expectedConfig); + + if (transactions.length) { + protocolTransactions[ProtocolType.Ethereum] = { + [chain]: transactions, + }; + } + + break; + } + case ProtocolType.CosmosNative: { + const signer = multiProtocolSigner.getCosmosNativeSigner(chain); + + const cosmosNativeWarpModule = new CosmosNativeWarpModule( + multiProvider, + { + chain, + config: configMapToDeploy[chain], + addresses: { + deployedTokenRoute: deployedContracts[chain], + }, + }, + signer, + ); + const actualConfig = await cosmosNativeWarpModule.read(); + const expectedConfig = { + ...actualConfig, + remoteRouters: (() => { + const routers: Record = {}; + for (const c of allRemoteChains) { + routers[multiProvider.getDomainId(c).toString()] = { + address: deployedContracts[c], + }; + } + return routers; + })(), + }; + + const transactions = + await cosmosNativeWarpModule.update(expectedConfig); + + if (transactions.length) { + protocolTransactions[ProtocolType.CosmosNative] = { + [chain]: transactions, + }; + } + + break; + } + default: { + throw new Error(`Protocol type ${protocol} not supported`); + } + } + } +} + +function getRouter(contracts: HyperlaneContracts) { + for (const key of objKeys(hypERC20factories)) { + if (contracts[key]) return contracts[key]; + } + throw new Error('No matching contract found.'); } diff --git a/typescript/sdk/src/gas/utils.ts b/typescript/sdk/src/gas/utils.ts index ce5e67ec94b..5a243d03a7e 100644 --- a/typescript/sdk/src/gas/utils.ts +++ b/typescript/sdk/src/gas/utils.ts @@ -90,13 +90,16 @@ export async function getCosmosChainGasPrice( let cosmosRegistryChain; try { cosmosRegistryChain = await getCosmosRegistryChain(chain); - } catch { + } catch (err) { // Fallback to our registry gas price from the metadata. if (metadata.gasPrice) { return metadata.gasPrice; } throw new Error( `No gas price found for Cosmos chain ${chain} in the registry or metadata`, + { + cause: err, + }, ); } diff --git a/typescript/sdk/src/hook/RadixHookReader.ts b/typescript/sdk/src/hook/RadixHookReader.ts new file mode 100644 index 00000000000..89670b5d331 --- /dev/null +++ b/typescript/sdk/src/hook/RadixHookReader.ts @@ -0,0 +1,89 @@ +import { RadixSDK } from '@hyperlane-xyz/radix-sdk'; +import { Address, WithAddress, assert, rootLogger } from '@hyperlane-xyz/utils'; + +import { RadixHookTypes } from '../../../radix-sdk/dist/utils/types.js'; +import { ChainMetadataManager } from '../metadata/ChainMetadataManager.js'; + +import { + DerivedHookConfig, + HookType, + IgpHookConfig, + MerkleTreeHookConfig, +} from './types.js'; + +export class RadixHookReader { + protected readonly logger = rootLogger.child({ + module: 'RadixHookReader', + }); + + constructor( + protected readonly metadataManager: ChainMetadataManager, + protected readonly sdk: RadixSDK, + ) {} + + async deriveHookConfig(address: Address): Promise { + const hookType = await this.sdk.query.core.getHookType({ hook: address }); + + switch (hookType) { + case RadixHookTypes.IGP: { + return this.deriveIgpConfig(address); + } + case RadixHookTypes.MERKLE_TREE: { + return this.deriveMerkleTreeConfig(address); + } + default: { + throw new Error(`Unsupported hook type for address: ${address}`); + } + } + } + + private async deriveIgpConfig( + address: Address, + ): Promise> { + const igp = await this.sdk.query.core.getIgpHook({ hook: address }); + + assert(igp, `IGP not found for address ${address}`); + + const overhead: IgpHookConfig['overhead'] = {}; + const oracleConfig: IgpHookConfig['oracleConfig'] = {}; + + Object.keys(igp.destination_gas_configs).forEach((remoteDomain) => { + const { name, nativeToken } = + this.metadataManager.getChainMetadata(remoteDomain); + + const gasConfig = igp.destination_gas_configs[remoteDomain]; + + overhead[name] = parseInt(gasConfig?.gas_overhead ?? ''); + oracleConfig[name] = { + gasPrice: gasConfig?.gas_oracle?.gas_price ?? '', + tokenExchangeRate: gasConfig?.gas_oracle?.token_exchange_rate ?? '', + tokenDecimals: gasConfig ? nativeToken?.decimals : 0, + }; + }); + + return { + type: HookType.INTERCHAIN_GAS_PAYMASTER, + owner: igp.owner, + beneficiary: igp.owner, + oracleKey: igp.owner, + overhead, + oracleConfig, + address: igp.address, + }; + } + + private async deriveMerkleTreeConfig( + address: Address, + ): Promise> { + const merkleTreeHook = await this.sdk.query.core.getMerkleTreeHook({ + hook: address, + }); + + assert(merkleTreeHook, `Merkle Tree Hook not found for address ${address}`); + + return { + type: HookType.MERKLE_TREE, + address: merkleTreeHook.address, + }; + } +} diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 938d04ac45a..5395e856362 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -126,7 +126,7 @@ export { } from './deploy/verify/types.js'; export * as verificationUtils from './deploy/verify/utils.js'; export { ZKSyncContractVerifier } from './deploy/verify/ZKSyncContractVerifier.js'; -export { executeWarpDeploy } from './deploy/warp.js'; +export { executeWarpDeploy, enrollCrossChainRouters } from './deploy/warp.js'; export { SealevelIgpAdapter, SealevelOverheadIgpAdapter, @@ -190,6 +190,7 @@ export { } from './hook/types.js'; export { isHookCompatible } from './hook/utils.js'; export { CosmosNativeIsmReader } from './ism/CosmosNativeIsmReader.js'; +export { CosmosNativeWarpModule } from './token/CosmosNativeWarpModule.js'; export { CosmosNativeWarpRouteReader } from './token/CosmosNativeWarpRouteReader.js'; export { EvmIsmReader } from './ism/EvmIsmReader.js'; export { HyperlaneIsmFactory } from './ism/HyperlaneIsmFactory.js'; @@ -384,7 +385,11 @@ export { ViemProvider, ViemTransaction, ViemTransactionReceipt, + GroupedTransactions, ProtocolTypedTransaction, + RadixSDKTransaction, + RadixTransaction, + RadixTransactionReceipt, } from './providers/ProviderType.js'; export { isCosmJsProviderHealthy, @@ -425,6 +430,8 @@ export { EV5GnosisSafeTxSubmitterPropsSchema, EV5ImpersonatedAccountTxSubmitterProps, EV5ImpersonatedAccountTxSubmitterPropsSchema, + EvmIcaTxSubmitterProps, + isJsonRpcSubmitterConfig, } from './providers/transactions/submitter/ethersV5/types.js'; export { TxSubmitterBuilder } from './providers/transactions/submitter/builder/TxSubmitterBuilder.js'; @@ -570,6 +577,8 @@ export { resolveRouterMapConfig, RouterAddress, RouterConfig, + MissingRouterViolation, + MissingEnrolledRouterViolation, RouterViolation, RouterViolationType, } from './router/types.js'; @@ -597,6 +606,7 @@ export { EvmNativeTokenAdapter, EvmTokenAdapter, EvmXERC20VSAdapter, + EvmXERC20Adapter, } from './token/adapters/EvmTokenAdapter.js'; export { IHypTokenAdapter, @@ -659,8 +669,10 @@ export { export { TokenMetadataMap } from './token/TokenMetadataMap.js'; export { EVM_TOKEN_TYPE_TO_STANDARD, + LOCKBOX_STANDARDS, MINT_LIMITED_STANDARDS, PROTOCOL_TO_NATIVE_STANDARD, + PROTOCOL_TO_HYP_NATIVE_STANDARD, TOKEN_COLLATERALIZED_STANDARDS, TOKEN_COSMWASM_STANDARDS, TOKEN_HYP_STANDARDS, @@ -673,6 +685,7 @@ export { XERC20_STANDARDS, } from './token/TokenStandard.js'; export { + XERC20LimitsTokenConfig, CctpTokenConfig, CctpTokenConfigSchema, CollateralRebaseTokenConfigSchema, @@ -711,7 +724,8 @@ export { WarpRouteDeployConfigMailboxRequiredSchema, WarpRouteDeployConfigSchema, WarpRouteDeployConfigSchemaErrors, - XERC20LimitConfig, + XERC20VSLimitConfig, + XERC20Type, XERC20TokenExtraBridgesLimits, XERC20TokenMetadata, } from './token/types.js'; @@ -730,6 +744,8 @@ export { PausableConfig, PausableSchema, ProtocolMap, + TypedSigner, + IMultiProtocolSignerManager, } from './types.js'; export { getCosmosRegistryChain } from './utils/cosmos.js'; export { verifyScale } from './utils/decimals.js'; @@ -798,3 +814,12 @@ export { PROPOSER_ROLE, } from './timelock/evm/constants.js'; export { EvmEventLogsReader } from './rpc/evm/EvmEventLogsReader.js'; +export { getTimelockExecutableTransactionFromBatch } from './timelock/evm/utils.js'; +export { RadixHookReader } from './hook/RadixHookReader.js'; +export { RadixIsmReader } from './ism/RadixIsmReader.js'; +export { RadixCoreReader } from './core/RadixCoreReader.js'; +export { RadixIsmModule } from './ism/RadixIsmModule.js'; +export { + getSignerForChain, + MultiProtocolSignerSignerAccountInfo, +} from './signers/signers.js'; diff --git a/typescript/sdk/src/ism/CosmosNativeIsmModule.ts b/typescript/sdk/src/ism/CosmosNativeIsmModule.ts index 7a58a78ad21..cdc904c2e78 100644 --- a/typescript/sdk/src/ism/CosmosNativeIsmModule.ts +++ b/typescript/sdk/src/ism/CosmosNativeIsmModule.ts @@ -146,14 +146,19 @@ export class CosmosNativeIsmModule extends HyperlaneModule< }: { chain: ChainNameOrId; config: IsmConfig; - addresses: IsmModuleAddresses; + addresses: { + mailbox: string; + }; multiProvider: MultiProvider; signer: SigningHyperlaneModuleClient; }): Promise { const module = new CosmosNativeIsmModule( multiProvider, { - addresses, + addresses: { + ...addresses, + deployedIsm: '', + }, chain, config, }, diff --git a/typescript/sdk/src/ism/RadixIsmModule.ts b/typescript/sdk/src/ism/RadixIsmModule.ts new file mode 100644 index 00000000000..8540c373fb9 --- /dev/null +++ b/typescript/sdk/src/ism/RadixIsmModule.ts @@ -0,0 +1,346 @@ +import { Logger } from 'pino'; + +import { RadixSigningSDK } from '@hyperlane-xyz/radix-sdk'; +import { + Address, + ChainId, + Domain, + ProtocolType, + assert, + deepEquals, + eqAddress, + intersection, + rootLogger, +} from '@hyperlane-xyz/utils'; + +import { + HyperlaneModule, + HyperlaneModuleParams, +} from '../core/AbstractHyperlaneModule.js'; +import { ChainMetadataManager } from '../metadata/ChainMetadataManager.js'; +import { MultiProvider } from '../providers/MultiProvider.js'; +import { AnnotatedRadixTransaction } from '../providers/ProviderType.js'; +import { ChainName, ChainNameOrId } from '../types.js'; +import { normalizeConfig } from '../utils/ism.js'; + +import { RadixIsmReader } from './RadixIsmReader.js'; +import { + DomainRoutingIsmConfig, + IsmConfig, + IsmConfigSchema, + IsmType, + MultisigIsmConfig, + STATIC_ISM_TYPES, +} from './types.js'; +import { calculateDomainRoutingDelta } from './utils.js'; + +type IsmModuleAddresses = { + deployedIsm: Address; + mailbox: Address; +}; + +export class RadixIsmModule extends HyperlaneModule< + ProtocolType.Radix, + IsmConfig, + IsmModuleAddresses +> { + protected readonly logger = rootLogger.child({ + module: 'RadixIsmModule', + }); + protected readonly reader: RadixIsmReader; + protected readonly mailbox: Address; + + // Adding these to reduce how often we need to grab from MetadataManager. + public readonly chain: ChainName; + public readonly chainId: ChainId; + public readonly domainId: Domain; + + constructor( + protected readonly metadataManager: ChainMetadataManager, + params: HyperlaneModuleParams, + protected readonly signer: RadixSigningSDK, + ) { + params.config = IsmConfigSchema.parse(params.config); + super(params); + + this.mailbox = params.addresses.mailbox; + this.chain = metadataManager.getChainName(this.args.chain); + this.chainId = metadataManager.getChainId(this.chain); + this.domainId = metadataManager.getDomainId(this.chain); + + this.reader = new RadixIsmReader(this.metadataManager, this.signer); + } + + public async read(): Promise { + return this.reader.deriveIsmConfig(this.args.addresses.deployedIsm); + } + + // whoever calls update() needs to ensure that targetConfig has a valid owner + public async update( + expectedConfig: IsmConfig, + ): Promise { + expectedConfig = IsmConfigSchema.parse(expectedConfig); + + // Do not support updating to a custom ISM address + if (typeof expectedConfig === 'string') { + throw new Error( + 'Invalid targetConfig: Updating to a custom ISM address is not supported. Please provide a valid ISM configuration.', + ); + } + + // save current config for comparison + // normalize the config to ensure it's in a consistent format for comparison + const actualConfig = normalizeConfig(await this.read()); + expectedConfig = normalizeConfig(expectedConfig); + + assert( + typeof expectedConfig === 'object', + 'normalized expectedConfig should be an object', + ); + + // Update the config + this.args.config = expectedConfig; + + // If configs match, no updates needed + if (deepEquals(actualConfig, expectedConfig)) { + return []; + } + + // if the ISM is a static ISM we can not update it, instead + // it needs to be recreated with the expected config + if (STATIC_ISM_TYPES.includes(expectedConfig.type)) { + this.args.addresses.deployedIsm = await this.deploy({ + config: expectedConfig, + }); + + return []; + } + + let updateTxs: AnnotatedRadixTransaction[] = []; + if (expectedConfig.type === IsmType.ROUTING) { + const logger = this.logger.child({ + destination: this.chain, + ismType: expectedConfig.type, + }); + logger.debug(`Updating ${expectedConfig.type} on ${this.chain}`); + + updateTxs = await this.updateRoutingIsm({ + actual: actualConfig, + expected: expectedConfig, + logger, + }); + } + + return updateTxs; + } + + // manually write static create function + public static async create({ + chain, + config, + addresses, + multiProvider, + signer, + }: { + chain: ChainNameOrId; + config: IsmConfig; + addresses: { + mailbox: Address; + }; + multiProvider: MultiProvider; + signer: RadixSigningSDK; + }): Promise { + const module = new RadixIsmModule( + multiProvider, + { + addresses: { + ...addresses, + deployedIsm: '', + }, + chain, + config, + }, + signer, + ); + + module.args.addresses.deployedIsm = await module.deploy({ config }); + return module; + } + + protected async deploy({ config }: { config: IsmConfig }): Promise
{ + if (typeof config === 'string') { + return config; + } + const ismType = config.type; + this.logger.info(`Deploying ${ismType} to ${this.chain}`); + + switch (ismType) { + case IsmType.MERKLE_ROOT_MULTISIG: { + return this.deployMerkleRootMultisigIsm(config); + } + case IsmType.MESSAGE_ID_MULTISIG: { + return this.deployMessageIdMultisigIsm(config); + } + case IsmType.ROUTING: { + return this.deployRoutingIsm(config); + } + case IsmType.TEST_ISM: { + return this.deployNoopIsm(); + } + default: + throw new Error(`ISM type ${ismType} is not supported on Radix`); + } + } + + protected async deployMerkleRootMultisigIsm( + config: MultisigIsmConfig, + ): Promise
{ + assert( + config.threshold <= config.validators.length, + `threshold (${config.threshold}) for merkle root multisig ISM is greater than number of validators (${config.validators.length})`, + ); + return this.signer.tx.core.createMerkleRootMultisigIsm({ + validators: config.validators, + threshold: config.threshold, + }); + } + + protected async deployMessageIdMultisigIsm( + config: MultisigIsmConfig, + ): Promise
{ + assert( + config.threshold <= config.validators.length, + `threshold (${config.threshold}) for message id multisig ISM is greater than number of validators (${config.validators.length})`, + ); + return this.signer.tx.core.createMessageIdMultisigIsm({ + validators: config.validators, + threshold: config.threshold, + }); + } + + protected async deployRoutingIsm( + config: DomainRoutingIsmConfig, + ): Promise
{ + const routes = []; + + // deploy ISMs for each domain + for (const chainName of Object.keys(config.domains)) { + const domainId = this.metadataManager.tryGetDomainId(chainName); + if (!domainId) { + this.logger.warn( + `Unknown chain ${chainName}, skipping ISM configuration`, + ); + continue; + } + + const address = await this.deploy({ config: config.domains[chainName] }); + routes.push({ + ism: address, + domain: domainId, + }); + } + + const ism = await this.signer.tx.core.createRoutingIsm({ + routes, + }); + + if (!eqAddress(this.signer.getAddress(), config.owner)) { + await this.signer.tx.core.setRoutingIsmOwner({ + ism, + new_owner: config.owner, + }); + } + + return ism; + } + + protected async updateRoutingIsm({ + actual, + expected, + logger, + }: { + actual: DomainRoutingIsmConfig; + expected: DomainRoutingIsmConfig; + logger: Logger; + }): Promise { + assert( + eqAddress(this.signer.getAddress(), actual.owner), + `can not update routing ism since the owner ${actual.owner} is not the signer: ${this.signer.getAddress()}`, + ); + + const updateTxs: AnnotatedRadixTransaction[] = []; + + const knownChains = new Set(this.metadataManager.getKnownChainNames()); + + const { domainsToEnroll, domainsToUnenroll } = calculateDomainRoutingDelta( + actual, + expected, + ); + + const knownEnrolls = intersection(knownChains, new Set(domainsToEnroll)); + + // Enroll domains + for (const origin of knownEnrolls) { + logger.debug( + `Reconfiguring preexisting routing ISM for origin ${origin}...`, + ); + const ism = await this.deploy({ + config: expected.domains[origin], + }); + + const domain = this.metadataManager.getDomainId(origin); + updateTxs.push({ + annotation: `Setting new ISM for origin ${origin}...`, + networkId: this.signer.getNetworkId(), + manifest: await this.signer.populate.core.setRoutingIsmRoute({ + from_address: this.signer.getAddress(), + ism: this.args.addresses.deployedIsm, + route: { + ism_address: ism, + domain, + }, + }), + }); + } + + const knownUnenrolls = intersection( + knownChains, + new Set(domainsToUnenroll), + ); + + // Unenroll domains + for (const origin of knownUnenrolls) { + const domain = this.metadataManager.getDomainId(origin); + updateTxs.push({ + annotation: `Unenrolling originDomain ${domain} from preexisting routing ISM at ${this.args.addresses.deployedIsm}...`, + networkId: this.signer.getNetworkId(), + manifest: await this.signer.populate.core.removeRoutingIsmRoute({ + from_address: this.signer.getAddress(), + ism: this.args.addresses.deployedIsm, + domain, + }), + }); + } + + // Update ownership + if (!eqAddress(actual.owner, expected.owner)) { + updateTxs.push({ + annotation: `Transferring ownership of ISM from ${ + actual.owner + } to ${expected.owner}`, + networkId: this.signer.getNetworkId(), + manifest: await this.signer.populate.core.setRoutingIsmOwner({ + from_address: this.signer.getAddress(), + ism: this.args.addresses.deployedIsm, + new_owner: expected.owner, + }), + }); + } + + return updateTxs; + } + + protected async deployNoopIsm(): Promise
{ + return this.signer.tx.core.createNoopIsm(); + } +} diff --git a/typescript/sdk/src/ism/RadixIsmReader.ts b/typescript/sdk/src/ism/RadixIsmReader.ts new file mode 100644 index 00000000000..b7877fbf849 --- /dev/null +++ b/typescript/sdk/src/ism/RadixIsmReader.ts @@ -0,0 +1,107 @@ +import { RadixSDK } from '@hyperlane-xyz/radix-sdk'; +import { RadixIsmTypes } from '@hyperlane-xyz/radix-sdk'; +import { Address, WithAddress, assert, rootLogger } from '@hyperlane-xyz/utils'; + +import { ChainMetadataManager } from '../metadata/ChainMetadataManager.js'; + +import { + DerivedIsmConfig, + DomainRoutingIsmConfig, + IsmType, + MultisigIsmConfig, +} from './types.js'; + +export class RadixIsmReader { + protected readonly logger = rootLogger.child({ + module: 'RadixIsmReader', + }); + + constructor( + protected readonly metadataManager: ChainMetadataManager, + protected readonly sdk: RadixSDK, + ) {} + + async deriveIsmConfig(address: Address): Promise { + try { + const ismType = await this.sdk.query.core.getIsmType({ ism: address }); + + assert(ismType, `ISM with id ${address} not found`); + + switch (ismType) { + case RadixIsmTypes.MERKLE_ROOT_MULTISIG: + return this.deriveMerkleRootMultisigConfig(address); + case RadixIsmTypes.MESSAGE_ID_MULTISIG: + return this.deriveMessageIdMultisigConfig(address); + case RadixIsmTypes.ROUTING_ISM: + return this.deriveRoutingIsmConfig(address); + case RadixIsmTypes.NOOP_ISM: + return this.deriveTestConfig(address); + default: + throw new Error(`Unknown ISM ModuleType: ${ismType}`); + } + } catch (error) { + this.logger.error(`Failed to derive ISM config for ${address}`, error); + throw error; + } + } + + private async deriveMerkleRootMultisigConfig( + address: Address, + ): Promise> { + const ism = await this.sdk.query.core.getMultisigIsm({ ism: address }); + + return { + type: IsmType.MERKLE_ROOT_MULTISIG, + address, + validators: ism.validators, + threshold: ism.threshold, + }; + } + + private async deriveMessageIdMultisigConfig( + address: Address, + ): Promise> { + const ism = await this.sdk.query.core.getMultisigIsm({ ism: address }); + + return { + type: IsmType.MESSAGE_ID_MULTISIG, + address, + validators: ism.validators, + threshold: ism.threshold, + }; + } + + private async deriveRoutingIsmConfig( + address: Address, + ): Promise> { + const ism = await this.sdk.query.core.getRoutingIsm({ ism: address }); + + const domains: DomainRoutingIsmConfig['domains'] = {}; + + for (const route of ism.routes) { + const chainName = this.metadataManager.tryGetChainName(route.domain); + if (!chainName) { + this.logger.warn( + `Unknown domain ID ${route.domain}, skipping domain configuration`, + ); + continue; + } + + domains[chainName] = await this.deriveIsmConfig(route.ism); + } + + return { + type: IsmType.ROUTING, + address, + owner: ism.owner, + domains, + }; + } + + private async deriveTestConfig(address: Address): Promise { + return { + type: IsmType.TEST_ISM, + address, + }; + } +} diff --git a/typescript/sdk/src/metadata/agentConfig.ts b/typescript/sdk/src/metadata/agentConfig.ts index c365a831f6c..0659a714442 100644 --- a/typescript/sdk/src/metadata/agentConfig.ts +++ b/typescript/sdk/src/metadata/agentConfig.ts @@ -51,6 +51,7 @@ export enum AgentSignerKeyType { Node = 'node', Cosmos = 'cosmosKey', Starknet = 'starkKey', + Radix = 'radixKey', } export enum AgentSealevelPriorityFeeOracleType { @@ -95,6 +96,13 @@ const AgentSignerCosmosKeySchema = z key: ZHash, }) .describe('Cosmos key'); +const AgentSignerRadixKeySchema = z + .object({ + type: z.literal(AgentSignerKeyType.Radix), + suffix: z.string().describe('The network suffix for the signer'), + key: ZHash, + }) + .describe('Radix key'); const AgentSignerNodeSchema = z .object({ type: z.literal(AgentSignerKeyType.Node), @@ -106,6 +114,7 @@ const AgentSignerSchema = z.union([ AgentSignerAwsKeySchema, AgentSignerCosmosKeySchema, AgentSignerNodeSchema, + AgentSignerRadixKeySchema, ]); export type AgentSignerHexKey = z.infer; diff --git a/typescript/sdk/src/metadata/blockExplorer.ts b/typescript/sdk/src/metadata/blockExplorer.ts index b6948860b2b..dbf7097fb1d 100644 --- a/typescript/sdk/src/metadata/blockExplorer.ts +++ b/typescript/sdk/src/metadata/blockExplorer.ts @@ -92,7 +92,12 @@ export function getExplorerTxUrl( if (!baseUrl) return null; const chainName = metadata.name; // TODO consider move handling of these chain/protocol specific quirks to ChainMetadata - const urlPathStub = ['nautilus', 'proteustestnet'].includes(chainName) + const urlPathStub = [ + 'nautilus', + 'proteustestnet', + 'radix', + 'radixtestnet', + ].includes(chainName) ? 'transaction' : 'tx'; return appendToPath(baseUrl, `${urlPathStub}/${hash}`).toString(); @@ -105,7 +110,7 @@ export function getExplorerAddressUrl( const baseUrl = getExplorerBaseUrl(metadata); if (!baseUrl) return null; - const urlPathStub = getExplorerAddressPathStub(metadata); + const urlPathStub = getExplorerAddressPathStub(metadata, 0, address); if (!urlPathStub) return null; return appendToPath(baseUrl, `${urlPathStub}/${address}`).toString(); @@ -121,9 +126,16 @@ function appendToPath(baseUrl: string, pathExtension: string) { return newUrl; } -function getExplorerAddressPathStub(metadata: ChainMetadata, index = 0) { +function getExplorerAddressPathStub( + metadata: ChainMetadata, + index = 0, + address?: string, +) { if (!metadata?.blockExplorers?.[index]) return null; const blockExplorer = metadata.blockExplorers[index]; + if (blockExplorer.family === ExplorerFamily.RadixDashboard) { + return address?.startsWith('account') ? 'account' : 'component'; + } if (!blockExplorer.family) return null; return blockExplorer.family === ExplorerFamily.Voyager diff --git a/typescript/sdk/src/metadata/chainMetadataTypes.ts b/typescript/sdk/src/metadata/chainMetadataTypes.ts index 319711a3c8c..29cfc57fe77 100644 --- a/typescript/sdk/src/metadata/chainMetadataTypes.ts +++ b/typescript/sdk/src/metadata/chainMetadataTypes.ts @@ -24,6 +24,7 @@ export enum ExplorerFamily { Routescan = 'routescan', Voyager = 'voyager', ZkSync = 'zksync', + RadixDashboard = 'radixdashboard', Other = 'other', } diff --git a/typescript/sdk/src/middleware/account/InterchainAccount.ts b/typescript/sdk/src/middleware/account/InterchainAccount.ts index eea918987a2..70a5a93aad4 100644 --- a/typescript/sdk/src/middleware/account/InterchainAccount.ts +++ b/typescript/sdk/src/middleware/account/InterchainAccount.ts @@ -147,8 +147,8 @@ export class InterchainAccount extends RouterApp { originRouterAddress, destinationIsmAddress, { - ...txOverrides, gasLimit: gasWithBuffer, + ...txOverrides, }, ), ); diff --git a/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts b/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts index 8415d720484..10399206039 100644 --- a/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts +++ b/typescript/sdk/src/middleware/account/InterchainAccountDeployer.ts @@ -1,6 +1,7 @@ import { ethers } from 'ethers'; import { Router } from '@hyperlane-xyz/core'; +import { assert } from '@hyperlane-xyz/utils'; import { HyperlaneContracts } from '../../contracts/types.js'; import { ContractVerifier } from '../../deploy/verify/ContractVerifier.js'; @@ -41,6 +42,11 @@ export class InterchainAccountDeployer extends HyperlaneRouterDeployer< throw new Error('Configuration of ISM not supported in ICA deployer'); } + assert( + config.commitmentIsm.urls.length > 0, + 'Commitment ISM URLs are required for deployment of ICA Routers. Please provide at least one URL in the commitmentIsm.urls array.', + ); + const owner = await this.multiProvider.getSignerAddress(chain); const interchainAccountRouter = await this.deployContract( chain, diff --git a/typescript/sdk/src/providers/MultiProtocolProvider.ts b/typescript/sdk/src/providers/MultiProtocolProvider.ts index 1e30cdf4714..19e4b8a63be 100644 --- a/typescript/sdk/src/providers/MultiProtocolProvider.ts +++ b/typescript/sdk/src/providers/MultiProtocolProvider.ts @@ -24,6 +24,7 @@ import { PROTOCOL_TO_DEFAULT_PROVIDER_TYPE, ProviderMap, ProviderType, + RadixProvider, SolanaWeb3Provider, StarknetJsProvider, TypedProvider, @@ -225,6 +226,13 @@ export class MultiProtocolProvider< ); } + getRadixProvider(chainNameOrId: ChainNameOrId): RadixProvider['provider'] { + return this.getSpecificProvider( + chainNameOrId, + ProviderType.Radix, + ); + } + setProvider( chainNameOrId: ChainNameOrId, provider: TypedProvider, diff --git a/typescript/sdk/src/providers/ProviderType.ts b/typescript/sdk/src/providers/ProviderType.ts index 80012225254..3184d7f6fb7 100644 --- a/typescript/sdk/src/providers/ProviderType.ts +++ b/typescript/sdk/src/providers/ProviderType.ts @@ -5,6 +5,8 @@ import type { } from '@cosmjs/cosmwasm-stargate'; import type { EncodeObject as CmTransaction } from '@cosmjs/proto-signing'; import type { DeliverTxResponse, StargateClient } from '@cosmjs/stargate'; +import { TransactionCommittedDetailsResponse } from '@radixdlt/babylon-gateway-api-sdk'; +import { TransactionManifest } from '@radixdlt/radix-engine-toolkit'; import type { Connection, Transaction as SolTransaction, @@ -34,8 +36,11 @@ import { } from 'zksync-ethers'; import { HyperlaneModuleClient } from '@hyperlane-xyz/cosmos-sdk'; +import { RadixSDK } from '@hyperlane-xyz/radix-sdk'; import { Annotated, ProtocolType } from '@hyperlane-xyz/utils'; +import { ChainMap } from '../types.js'; + export enum ProviderType { EthersV5 = 'ethers-v5', Viem = 'viem', @@ -46,6 +51,7 @@ export enum ProviderType { GnosisTxBuilder = 'gnosis-txBuilder', Starknet = 'starknet', ZkSync = 'zksync', + Radix = 'radix', } export const PROTOCOL_TO_DEFAULT_PROVIDER_TYPE: Record< @@ -57,6 +63,7 @@ export const PROTOCOL_TO_DEFAULT_PROVIDER_TYPE: Record< [ProtocolType.Cosmos]: ProviderType.CosmJsWasm, [ProtocolType.CosmosNative]: ProviderType.CosmJsNative, [ProtocolType.Starknet]: ProviderType.Starknet, + [ProtocolType.Radix]: ProviderType.Radix, }; export type ProviderMap = Partial>; @@ -92,6 +99,12 @@ type ProtocolTypesMapping = { contract: StarknetJsContract; receipt: StarknetJsTransactionReceipt; }; + [ProtocolType.Radix]: { + transaction: RadixTransaction; + provider: RadixProvider; + contract: null; + receipt: RadixTransactionReceipt; + }; }; type ProtocolTyped< @@ -165,6 +178,11 @@ export interface StarknetJsProvider provider: StarknetProvider; } +export interface RadixProvider extends TypedProviderBase { + type: ProviderType.Radix; + provider: RadixSDK; +} + export interface ZKSyncProvider extends TypedProviderBase { type: ProviderType.ZkSync; provider: ZKSyncBaseProvider; @@ -179,7 +197,8 @@ export type TypedProvider = | CosmJsWasmProvider | CosmJsNativeProvider | StarknetJsProvider - | ZKSyncProvider; + | ZKSyncProvider + | RadixProvider; /** * Contracts with discriminated union of provider type @@ -254,10 +273,23 @@ export interface EthersV5Transaction transaction: EV5Transaction; } +export interface RadixSDKTransaction { + networkId: number; + manifest: TransactionManifest | string; +} + export type AnnotatedEV5Transaction = Annotated; export type AnnotatedCosmJsNativeTransaction = Annotated; +export type AnnotatedRadixTransaction = Annotated; + +export type GroupedTransactions = { + [ProtocolType.Ethereum]: ChainMap; + [ProtocolType.CosmosNative]: ChainMap; + [ProtocolType.Radix]: ChainMap; +}; + export interface ViemTransaction extends TypedTransactionBase { type: ProviderType.Viem; transaction: VTransaction; @@ -292,6 +324,12 @@ export interface StarknetJsTransaction transaction: StarknetInvocation; } +export interface RadixTransaction + extends TypedTransactionBase { + type: ProviderType.Radix; + transaction: RadixSDKTransaction; +} + export interface ZKSyncTransaction extends TypedTransactionBase { type: ProviderType.ZkSync; @@ -307,7 +345,8 @@ export type TypedTransaction = | CosmJsWasmTransaction | CosmJsNativeTransaction | StarknetJsTransaction - | ZKSyncTransaction; + | ZKSyncTransaction + | RadixTransaction; /** * Transaction receipt/response with discriminated union of provider type @@ -366,6 +405,12 @@ export interface ZKSyncTransactionReceipt receipt: zkSyncTypes.TransactionReceipt; } +export interface RadixTransactionReceipt + extends TypedTransactionReceiptBase { + type: ProviderType.Radix; + receipt: TransactionCommittedDetailsResponse & { transactionHash: string }; +} + export type TypedTransactionReceipt = | EthersV5TransactionReceipt | ViemTransactionReceipt @@ -374,4 +419,5 @@ export type TypedTransactionReceipt = | CosmJsWasmTransactionReceipt | CosmJsNativeTransactionReceipt | StarknetJsTransactionReceipt - | ZKSyncTransactionReceipt; + | ZKSyncTransactionReceipt + | RadixTransactionReceipt; diff --git a/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts b/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts index 4bac64b81a7..117a24d7d71 100644 --- a/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts +++ b/typescript/sdk/src/providers/SmartProvider/SmartProvider.ts @@ -461,9 +461,18 @@ export class HyperlaneSmartProvider }); } else { this.logger.error( + { + errors: errors.map((e) => ({ + message: e?.message, + code: e?.code, + reason: e?.reason, + status: e?.status, + stack: e?.stack, + })), + }, 'Unhandled error case in combined provider error handler', ); - throw Error(fallbackMsg); + throw Error(fallbackMsg, { cause: errors[0] }); } } } diff --git a/typescript/sdk/src/providers/explorerHealthTest.ts b/typescript/sdk/src/providers/explorerHealthTest.ts index b78c33ebf30..fea64c267b8 100644 --- a/typescript/sdk/src/providers/explorerHealthTest.ts +++ b/typescript/sdk/src/providers/explorerHealthTest.ts @@ -14,6 +14,8 @@ const PROTOCOL_TO_ADDRESS: Record = { [ProtocolType.CosmosNative]: 'cosmos100000000000000000000000000000000000000', [ProtocolType.Starknet]: '0x0000000000000000000000000000000000000000000000000000000000000000', + [ProtocolType.Radix]: + 'account_sim0000000000000000000000000000000000000000000000000000000', }; const PROTOCOL_TO_TX_HASH: Partial> = { diff --git a/typescript/sdk/src/providers/providerBuilders.ts b/typescript/sdk/src/providers/providerBuilders.ts index fbb0522bd87..160e1cd1e7e 100644 --- a/typescript/sdk/src/providers/providerBuilders.ts +++ b/typescript/sdk/src/providers/providerBuilders.ts @@ -7,6 +7,7 @@ import { createPublicClient, http } from 'viem'; import { Provider as ZKProvider } from 'zksync-ethers'; import { HyperlaneModuleClient } from '@hyperlane-xyz/cosmos-sdk'; +import { RadixSDK } from '@hyperlane-xyz/radix-sdk'; import { ProtocolType, assert, isNumeric } from '@hyperlane-xyz/utils'; import { ChainMetadata, RpcUrl } from '../metadata/chainMetadataTypes.js'; @@ -17,6 +18,7 @@ import { CosmJsWasmProvider, EthersV5Provider, ProviderType, + RadixProvider, SolanaWeb3Provider, StarknetJsProvider, TypedProvider, @@ -145,6 +147,18 @@ export function defaultZKSyncProviderBuilder( return { type: ProviderType.ZkSync, provider }; } +export function defaultRadixProviderBuilder( + _rpcUrls: RpcUrl[], + network: string | number, +): RadixProvider { + assert(isNumeric(network), 'Radix requires a numeric network id'); + const networkId = parseInt(network.toString(), 10); + const provider = new RadixSDK({ + networkId, + }); + return { provider, type: ProviderType.Radix }; +} + // Kept for backwards compatibility export function defaultProviderBuilder( rpcUrls: RpcUrl[], @@ -174,6 +188,7 @@ export const defaultProviderBuilderMap: ProviderBuilderMap = { [ProviderType.CosmJsNative]: defaultCosmJsNativeProviderBuilder, [ProviderType.Starknet]: defaultStarknetJsProviderBuilder, [ProviderType.ZkSync]: defaultZKSyncProviderBuilder, + [ProviderType.Radix]: defaultRadixProviderBuilder, }; export const protocolToDefaultProviderBuilder: Record< @@ -185,4 +200,5 @@ export const protocolToDefaultProviderBuilder: Record< [ProtocolType.Cosmos]: defaultCosmJsWasmProviderBuilder, [ProtocolType.CosmosNative]: defaultCosmJsNativeProviderBuilder, [ProtocolType.Starknet]: defaultStarknetJsProviderBuilder, + [ProtocolType.Radix]: defaultRadixProviderBuilder, }; diff --git a/typescript/sdk/src/providers/rpcHealthTest.ts b/typescript/sdk/src/providers/rpcHealthTest.ts index 459f918d9c3..8dbff11ee7e 100644 --- a/typescript/sdk/src/providers/rpcHealthTest.ts +++ b/typescript/sdk/src/providers/rpcHealthTest.ts @@ -9,6 +9,7 @@ import { CosmJsWasmProvider, EthersV5Provider, ProviderType, + RadixProvider, SolanaWeb3Provider, StarknetJsProvider, } from './ProviderType.js'; @@ -33,6 +34,8 @@ export async function isRpcHealthy( return isCosmJsProviderHealthy(provider.provider, metadata); else if (provider.type === ProviderType.Starknet) return isStarknetJsProviderHealthy(provider.provider, metadata); + else if (provider.type === ProviderType.Radix) + return isRadixProviderHealthy(provider.provider, metadata); else throw new Error( `Unsupported provider type ${provider.type}, new health check required`, @@ -101,3 +104,22 @@ export async function isStarknetJsProviderHealthy( rootLogger.debug(`Block number is okay for ${metadata.name}`); return true; } + +export async function isRadixProviderHealthy( + provider: RadixProvider['provider'], + metadata: ChainMetadata, +): Promise { + try { + const healthy = await provider.base.isGatewayHealthy(); + if (healthy) { + rootLogger.debug(`Gateway is healthy for ${metadata.name}`); + } + return healthy; + } catch (err) { + rootLogger.warn( + `Radix gateway health check threw for ${metadata.name}`, + err as Error, + ); + return false; + } +} diff --git a/typescript/sdk/src/providers/transactionFeeEstimators.ts b/typescript/sdk/src/providers/transactionFeeEstimators.ts index 2a96683142d..a11e557b53e 100644 --- a/typescript/sdk/src/providers/transactionFeeEstimators.ts +++ b/typescript/sdk/src/providers/transactionFeeEstimators.ts @@ -20,6 +20,8 @@ import { EthersV5Provider, EthersV5Transaction, ProviderType, + RadixProvider, + RadixTransaction, SolanaWeb3Provider, SolanaWeb3Transaction, StarknetJsProvider, @@ -255,6 +257,32 @@ export async function estimateTransactionFeeCosmJsNative({ }; } +// Starknet does not support gas estimation without starknet account +// TODO: Figure out a way to inject starknet account +export async function estimateTransactionFeeStarknet({ + transaction: _transaction, + provider: _provider, + sender: _sender, +}: { + transaction: StarknetJsTransaction; + provider: StarknetJsProvider; + sender: Address; +}): Promise { + return { gasUnits: 0, gasPrice: 0, fee: 0 }; +} + +export async function estimateTransactionFeeRadix({ + transaction, + provider, +}: { + transaction: RadixTransaction; + provider: RadixProvider; +}): Promise { + return provider.provider.base.estimateTransactionFee({ + transactionManifest: transaction.transaction.manifest, + }); +} + export function estimateTransactionFee({ transaction, provider, @@ -333,23 +361,17 @@ export function estimateTransactionFee({ provider.type === ProviderType.Starknet ) { return estimateTransactionFeeStarknet({ transaction, provider, sender }); + } else if ( + transaction.type === ProviderType.Radix && + provider.type === ProviderType.Radix + ) { + return estimateTransactionFeeRadix({ + transaction, + provider, + }); } else { throw new Error( `Unsupported transaction type ${transaction.type} or provider type ${provider.type} for gas estimation`, ); } } - -// Starknet does not support gas estimation without starknet account -// TODO: Figure out a way to inject starknet account -export async function estimateTransactionFeeStarknet({ - transaction: _transaction, - provider: _provider, - sender: _sender, -}: { - transaction: StarknetJsTransaction; - provider: StarknetJsProvider; - sender: Address; -}): Promise { - return { gasUnits: 0, gasPrice: 0, fee: 0 }; -} diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxBuilder.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxBuilder.ts index 3d80a7b9355..50485f8ffbb 100644 --- a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxBuilder.ts +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxBuilder.ts @@ -53,6 +53,13 @@ export class EV5GnosisSafeTxBuilder extends EV5GnosisSafeTxSubmitter { return new EV5GnosisSafeTxBuilder(multiProvider, props, safe, safeService); } + // No requirement to get the next nonce from the Safe service. + // When proposing the JSON file, the Safe UI will automatically update the nonce. + // So we just return 0 and save ourselves from the unreliability of Safe APIs. + protected async getNextNonce(): Promise { + return 0; + } + /** * Creates a Gnosis Safe transaction builder object using the PopulatedTransactions * diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts index 860dce93c28..870697e32bc 100644 --- a/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.ts @@ -66,12 +66,14 @@ export class EV5GnosisSafeTxSubmitter implements EV5TxSubmitterInterface { ); } + protected async getNextNonce(): Promise { + return await this.safeService.getNextNonce(this.props.safeAddress); + } + public async createSafeTransaction( ...transactions: AnnotatedEV5Transaction[] ): Promise { - const nextNonce: number = await this.safeService.getNextNonce( - this.props.safeAddress, - ); + const nextNonce = await this.getNextNonce(); const submitterChainId = this.multiProvider.getChainId(this.props.chain); const safeTransactionData = transactions.map( diff --git a/typescript/sdk/src/providers/transactions/submitter/ethersV5/types.ts b/typescript/sdk/src/providers/transactions/submitter/ethersV5/types.ts index 32e5f4a433d..0f448a8f667 100644 --- a/typescript/sdk/src/providers/transactions/submitter/ethersV5/types.ts +++ b/typescript/sdk/src/providers/transactions/submitter/ethersV5/types.ts @@ -9,6 +9,7 @@ import { ZHash, } from '../../../../metadata/customZodTypes.js'; import { ChainName } from '../../../../types.js'; +import { isCompliant } from '../../../../utils/schemas.js'; import { TxSubmitterType } from '../TxSubmitterTypes.js'; export const EV5GnosisSafeTxSubmitterPropsSchema = z.object({ @@ -34,12 +35,17 @@ export const EV5JsonRpcTxSubmitterPropsSchema = z.object({ chain: ZChainName, userAddress: ZHash.optional(), privateKey: ZHash.optional(), + extraParams: z.record(z.string(), z.string()).optional(), }); export type EV5JsonRpcTxSubmitterProps = z.infer< typeof EV5JsonRpcTxSubmitterPropsSchema >; +export const isJsonRpcSubmitterConfig = isCompliant( + EV5JsonRpcTxSubmitterPropsSchema, +); + export const EV5ImpersonatedAccountTxSubmitterPropsSchema = EV5JsonRpcTxSubmitterPropsSchema.extend({ userAddress: ZHash, diff --git a/typescript/sdk/src/providers/transactions/submitter/submitterBuilderGetter.ts b/typescript/sdk/src/providers/transactions/submitter/submitterBuilderGetter.ts index 7811fd7becc..73d439f6c3d 100644 --- a/typescript/sdk/src/providers/transactions/submitter/submitterBuilderGetter.ts +++ b/typescript/sdk/src/providers/transactions/submitter/submitterBuilderGetter.ts @@ -116,7 +116,7 @@ const defaultSubmitterFactories: Record = { * @param multiProvider - The MultiProvider instance * @param submitterMetadata - The metadata defining the type and configuration of the submitter. * @param coreAddressesByChain - The address of the core Hyperlane deployments by chain. Used for filling some default values for the submission strategies. - * @param additionalSubmitterFactories optional extension to extend the default registry. Can override if specifying the the same key. + * @param additionalSubmitterFactories optional extension to extend the default registry. Can override if specifying the same key. * @returns A promise that resolves to an instance of a TxSubmitterInterface. * @throws If no submitter factory is registered for the type specified in the metadata. */ diff --git a/typescript/sdk/src/rpc/evm/EvmEventLogsReader.ts b/typescript/sdk/src/rpc/evm/EvmEventLogsReader.ts index c520622dc81..6efb015b087 100644 --- a/typescript/sdk/src/rpc/evm/EvmEventLogsReader.ts +++ b/typescript/sdk/src/rpc/evm/EvmEventLogsReader.ts @@ -131,6 +131,7 @@ export class EvmEventLogsReader { protected readonly multiProvider: MultiProvider, protected logReaderStrategy: IEvmEventLogsReaderStrategy, protected readonly logger: Logger, + protected fallbackLogReaderStrategy?: IEvmEventLogsReaderStrategy, ) {} static fromConfig( @@ -143,12 +144,19 @@ export class EvmEventLogsReader { const explorer = multiProvider.tryGetEvmExplorerMetadata(config.chain); let logReaderStrategy: IEvmEventLogsReaderStrategy; + let fallbackLogReaderSrategy: IEvmEventLogsReaderStrategy | undefined; if (explorer && !config.useRPC) { logReaderStrategy = new EvmEtherscanLikeEventLogsReader( config.chain, explorer, multiProvider, ); + + fallbackLogReaderSrategy = new EvmRpcEventLogsReader( + config.chain, + { paginationBlockRange: config.paginationBlockRange }, + multiProvider, + ); } else { logReaderStrategy = new EvmRpcEventLogsReader( config.chain, @@ -162,14 +170,13 @@ export class EvmEventLogsReader { multiProvider, logReaderStrategy, logger, + fallbackLogReaderSrategy, ); } async getLogsByTopic( options: GetLogByTopicOptions, ): Promise { - const parsedOptions = GetLogByTopicOptionsSchema.parse(options); - const provider = this.multiProvider.getProvider(this.config.chain); await assertIsContractAddress( this.multiProvider, @@ -177,14 +184,50 @@ export class EvmEventLogsReader { options.contractAddress, ); + try { + // do NOT remove the await here it is on purpose to catch any error + // here to fallback to the rpc if any is set. + // Removing the await will cause the caller to handle the error if any + // and the fallback logic won't run + const res = await this.getLogsByTopicWithStrategy( + options, + provider, + this.logReaderStrategy, + ); + + return res; + } catch (err) { + if (!this.fallbackLogReaderStrategy) { + throw err; + } + + this.logger.debug( + `Failed to read logs on chain "${this.config.chain}": ${err}. Falling back to using the RPC`, + ); + + return this.getLogsByTopicWithStrategy( + options, + provider, + this.fallbackLogReaderStrategy, + ); + } + } + + private async getLogsByTopicWithStrategy( + options: GetLogByTopicOptions, + provider: ReturnType, + logReaderStrategy: IEvmEventLogsReaderStrategy, + ): Promise { + const parsedOptions = GetLogByTopicOptionsSchema.parse(options); + const fromBlock = parsedOptions.fromBlock ?? - (await this.logReaderStrategy.getContractDeploymentBlockNumber( + (await logReaderStrategy.getContractDeploymentBlockNumber( parsedOptions.contractAddress, )); const toBlock = parsedOptions.toBlock ?? (await provider.getBlockNumber()); - return this.logReaderStrategy.getContractLogs({ + return logReaderStrategy.getContractLogs({ contractAddress: parsedOptions.contractAddress, eventTopic: parsedOptions.eventTopic, fromBlock, diff --git a/typescript/sdk/src/signers/cosmos/cosmjs.ts b/typescript/sdk/src/signers/cosmos/cosmjs.ts new file mode 100644 index 00000000000..90651bef4da --- /dev/null +++ b/typescript/sdk/src/signers/cosmos/cosmjs.ts @@ -0,0 +1,82 @@ +import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; +import { GasPrice, SigningStargateClient } from '@cosmjs/stargate'; + +import { Address, ProtocolType, assert, strip0x } from '@hyperlane-xyz/utils'; + +import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider.js'; +import { CosmJsNativeTransaction } from '../../providers/ProviderType.js'; +import { ChainName } from '../../types.js'; +import { IMultiProtocolSigner } from '../types.js'; + +export class CosmosNativeMultiProtocolSignerAdapter + implements IMultiProtocolSigner +{ + constructor( + private readonly chainName: ChainName, + private readonly accountAddress: Address, + private readonly signer: SigningStargateClient, + ) {} + + static async init( + chainName: ChainName, + privateKey: string, + multiProtocolProvider: MultiProtocolProvider, + ): Promise { + const { bech32Prefix, rpcUrls, gasPrice } = + multiProtocolProvider.getChainMetadata(chainName); + + const [rpc] = rpcUrls; + assert(bech32Prefix, 'prefix is required for cosmos chains'); + assert(rpc, 'rpc is required for configuring cosmos chains'); + assert(gasPrice, 'gas price is required for cosmos chains'); + + const wallet = await DirectSecp256k1Wallet.fromKey( + Buffer.from(strip0x(privateKey), 'hex'), + bech32Prefix, + ); + + const [account] = await wallet.getAccounts(); + assert(account, 'account not found for cosmos chain'); + const signer = await SigningStargateClient.connectWithSigner( + rpc.http, + wallet, + { + gasPrice: GasPrice.fromString(`${gasPrice.amount}${gasPrice.denom}`), + }, + ); + + return new CosmosNativeMultiProtocolSignerAdapter( + chainName, + account.address, + signer, + ); + } + + async address(): Promise { + return this.accountAddress; + } + + async sendAndConfirmTransaction( + tx: CosmJsNativeTransaction, + ): Promise { + await this.signer.simulate( + this.accountAddress, + [tx.transaction], + undefined, + ); + + const res = await this.signer.signAndBroadcast( + this.accountAddress, + [tx.transaction], + 'auto', + ); + + if (res.code !== 0) { + throw new Error( + `Transaction ${res.transactionHash} failed on chain ${this.chainName}`, + ); + } + + return res.transactionHash; + } +} diff --git a/typescript/sdk/src/signers/evm/ethersv5.ts b/typescript/sdk/src/signers/evm/ethersv5.ts new file mode 100644 index 00000000000..ad0d0ebf819 --- /dev/null +++ b/typescript/sdk/src/signers/evm/ethersv5.ts @@ -0,0 +1,51 @@ +import { Wallet, ethers } from 'ethers'; +import { Wallet as ZkSyncWallet } from 'zksync-ethers'; + +import { Address, ProtocolType, assert } from '@hyperlane-xyz/utils'; + +import { ChainTechnicalStack } from '../../index.js'; +import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider.js'; +import { MultiProvider } from '../../providers/MultiProvider.js'; +import { EthersV5Transaction } from '../../providers/ProviderType.js'; +import { ChainName } from '../../types.js'; +import { IMultiProtocolSigner } from '../types.js'; + +export class EvmMultiProtocolSignerAdapter + implements IMultiProtocolSigner +{ + private readonly multiProvider: MultiProvider; + + constructor( + private readonly chainName: ChainName, + privateKey: string, + multiProtocolProvider: MultiProtocolProvider, + ) { + const multiProvider = multiProtocolProvider.toMultiProvider(); + const { technicalStack } = multiProvider.getChainMetadata(chainName); + + assert( + ethers.utils.isHexString(privateKey), + `Private key for chain ${chainName} should be a hex string`, + ); + + const wallet = + technicalStack === ChainTechnicalStack.ZkSync + ? new ZkSyncWallet(privateKey) + : new Wallet(privateKey); + multiProvider.setSigner(this.chainName, wallet); + this.multiProvider = multiProvider; + } + + async address(): Promise
{ + return this.multiProvider.getSignerAddress(this.chainName); + } + + async sendAndConfirmTransaction(tx: EthersV5Transaction): Promise { + const res = await this.multiProvider.sendTransaction( + this.chainName, + tx.transaction, + ); + + return res.transactionHash; + } +} diff --git a/typescript/sdk/src/signers/signers.ts b/typescript/sdk/src/signers/signers.ts new file mode 100644 index 00000000000..8fc8a629d02 --- /dev/null +++ b/typescript/sdk/src/signers/signers.ts @@ -0,0 +1,66 @@ +import { HexString, ProtocolType } from '@hyperlane-xyz/utils'; + +import { MultiProtocolProvider } from '../providers/MultiProtocolProvider.js'; +import { ChainName } from '../types.js'; + +import { CosmosNativeMultiProtocolSignerAdapter } from './cosmos/cosmjs.js'; +import { EvmMultiProtocolSignerAdapter } from './evm/ethersv5.js'; +import { StarknetMultiProtocolSignerAdapter } from './starknet/starknetjs.js'; +import { SvmMultiprotocolSignerAdapter } from './svm/solana-web3js.js'; +import { IMultiProtocolSigner } from './types.js'; + +export type MultiProtocolSignerSignerAccountInfo = + | { + protocol: Exclude< + ProtocolType, + ProtocolType.Sealevel | ProtocolType.Starknet + >; + privateKey: HexString; + } + | { + protocol: ProtocolType.Sealevel; + privateKey: Uint8Array; + } + | { + protocol: ProtocolType.Starknet; + privateKey: HexString; + address: HexString; + }; + +export async function getSignerForChain( + chainName: ChainName, + accountConfig: MultiProtocolSignerSignerAccountInfo, + multiProtocolProvider: MultiProtocolProvider, +): Promise> { + const protocol = accountConfig.protocol; + + switch (accountConfig.protocol) { + case ProtocolType.Ethereum: + return new EvmMultiProtocolSignerAdapter( + chainName, + accountConfig.privateKey, + multiProtocolProvider, + ); + case ProtocolType.Sealevel: + return new SvmMultiprotocolSignerAdapter( + chainName, + accountConfig.privateKey, + multiProtocolProvider, + ); + case ProtocolType.CosmosNative: + return CosmosNativeMultiProtocolSignerAdapter.init( + chainName, + accountConfig.privateKey, + multiProtocolProvider, + ); + case ProtocolType.Starknet: + return new StarknetMultiProtocolSignerAdapter( + chainName, + accountConfig.privateKey, + accountConfig.address, + multiProtocolProvider, + ); + default: + throw new Error(`Signer not supported for protocol type ${protocol}`); + } +} diff --git a/typescript/sdk/src/signers/starknet/starknetjs.ts b/typescript/sdk/src/signers/starknet/starknetjs.ts new file mode 100644 index 00000000000..4de060808fb --- /dev/null +++ b/typescript/sdk/src/signers/starknet/starknetjs.ts @@ -0,0 +1,64 @@ +import { ethers } from 'ethers'; +import { Account as StarknetAccount } from 'starknet'; + +import { ProtocolType, assert } from '@hyperlane-xyz/utils'; + +import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider.js'; +import { StarknetJsTransaction } from '../../providers/ProviderType.js'; +import { ChainName } from '../../types.js'; +import { IMultiProtocolSigner } from '../types.js'; + +export class StarknetMultiProtocolSignerAdapter + implements IMultiProtocolSigner +{ + private readonly signer: StarknetAccount; + + constructor( + private readonly chainName: ChainName, + privateKey: string, + address: string, + multiProtocolProvider: MultiProtocolProvider, + ) { + const provider = multiProtocolProvider.getStarknetProvider(this.chainName); + + assert( + ethers.utils.isHexString(address), + 'Starknet address must be a hex string', + ); + assert( + ethers.utils.isHexString(privateKey), + 'Starknet private key must be a hex string', + ); + + this.signer = new StarknetAccount(provider, address, privateKey); + } + + async address(): Promise { + return this.signer.address; + } + + async sendAndConfirmTransaction(tx: StarknetJsTransaction): Promise { + const { entrypoint, calldata, contractAddress } = tx.transaction; + assert(entrypoint, 'entrypoint is required for starknet transactions'); + + const transaction = await this.signer.execute([ + { + contractAddress, + entrypoint, + calldata, + }, + ]); + + const transactionReceipt = await this.signer.waitForTransaction( + transaction.transaction_hash, + ); + + if (transactionReceipt.isReverted()) { + throw new Error( + `Transaction ${transaction.transaction_hash} failed on chain ${this.chainName}`, + ); + } + + return transaction.transaction_hash; + } +} diff --git a/typescript/sdk/src/signers/svm/solana-web3js.ts b/typescript/sdk/src/signers/svm/solana-web3js.ts new file mode 100644 index 00000000000..d4ef120cab7 --- /dev/null +++ b/typescript/sdk/src/signers/svm/solana-web3js.ts @@ -0,0 +1,75 @@ +import { + Connection, + Keypair, + TransactionConfirmationStatus, +} from '@solana/web3.js'; + +import { Address, ProtocolType, retryAsync } from '@hyperlane-xyz/utils'; + +import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider.js'; +import { SolanaWeb3Transaction } from '../../providers/ProviderType.js'; +import { ChainName } from '../../types.js'; +import { IMultiProtocolSigner } from '../types.js'; + +export class SvmMultiprotocolSignerAdapter + implements IMultiProtocolSigner +{ + private readonly signer: Keypair; + private readonly svmProvider: Connection; + private readonly commitment: TransactionConfirmationStatus = 'confirmed'; + + constructor( + private readonly chainName: ChainName, + private readonly privateKey: Uint8Array, + private readonly multiProtocolProvider: MultiProtocolProvider, + ) { + this.signer = Keypair.fromSecretKey(this.privateKey); + this.svmProvider = this.multiProtocolProvider.getSolanaWeb3Provider( + this.chainName, + ); + } + + async address(): Promise
{ + return this.signer.publicKey.toBase58(); + } + + async sendAndConfirmTransaction(tx: SolanaWeb3Transaction): Promise { + // Manually crafting and sending the transaction as sendTransactionAndConfirm might + // not always work depending on if the `signatureSubscribe` rpc method is available + const { blockhash, lastValidBlockHeight } = + await this.svmProvider.getLatestBlockhash(this.commitment); + + tx.transaction.recentBlockhash = blockhash; + tx.transaction.lastValidBlockHeight = lastValidBlockHeight; + tx.transaction.sign(this.signer); + + const txSignature = await this.svmProvider.sendRawTransaction( + tx.transaction.serialize(), + { + maxRetries: 3, + preflightCommitment: this.commitment, + }, + ); + + // Manually checking if the transaction has been confirmed on chain + await this.waitForTransaction(txSignature); + + return txSignature; + } + + async waitForTransaction(transactionHash: string): Promise { + await retryAsync( + async () => { + const res = await this.svmProvider.getSignatureStatus(transactionHash); + + if (res.value?.confirmationStatus !== this.commitment) { + throw new Error( + `Transaction ${transactionHash} is not yet in the expected commitment state: "${this.commitment}"`, + ); + } + }, + 5, + 1500, + ); + } +} diff --git a/typescript/sdk/src/signers/types.ts b/typescript/sdk/src/signers/types.ts new file mode 100644 index 00000000000..0e922fee455 --- /dev/null +++ b/typescript/sdk/src/signers/types.ts @@ -0,0 +1,10 @@ +import { Address, ProtocolType } from '@hyperlane-xyz/utils'; + +import { ProtocolTypedTransaction } from '../providers/ProviderType.js'; + +export interface IMultiProtocolSigner { + address(): Promise
; + sendAndConfirmTransaction( + tx: ProtocolTypedTransaction, + ): Promise; +} diff --git a/typescript/sdk/src/timelock/evm/EvmTimelockReader.hardhat-test.ts b/typescript/sdk/src/timelock/evm/EvmTimelockReader.hardhat-test.ts index 4486ad92ece..15daf5de9e2 100644 --- a/typescript/sdk/src/timelock/evm/EvmTimelockReader.hardhat-test.ts +++ b/typescript/sdk/src/timelock/evm/EvmTimelockReader.hardhat-test.ts @@ -594,6 +594,219 @@ describe(EvmTimelockReader.name, () => { } }); + describe(`${EvmTimelockReader.prototype.getPendingOperationIds.name}`, () => { + it('should return empty set for empty input', async () => { + const pendingIds = await timelockReader.getPendingOperationIds([]); + + expect(pendingIds.size).to.equal(0); + }); + + type PendingTestCase = { + title: string; + timelockTxs: Array>; + cancelledTxIndexes?: number[]; + executedTxIndexes?: number[]; + expectedPendingCount: number; + }; + + const pendingTestCases: PendingTestCase[] = [ + { + title: 'should return pending operations correctly', + timelockTxs: [ + { + data: [ + { + to: randomAddress(), + value: ethers.BigNumber.from(0), + data: '0x1234', + }, + ], + delay: 0, + predecessor: EMPTY_BYTES_32, + salt: ethers.utils.formatBytes32String('pending-test'), + }, + ], + expectedPendingCount: 1, + }, + { + title: 'should exclude cancelled operations from pending list', + timelockTxs: [ + { + data: [ + { + to: randomAddress(), + value: ethers.BigNumber.from(0), + data: '0x1234', + }, + ], + delay: 0, + predecessor: EMPTY_BYTES_32, + salt: ethers.utils.formatBytes32String('cancelled-pending'), + }, + ], + cancelledTxIndexes: [0], + expectedPendingCount: 0, + }, + { + title: 'should exclude executed operations from pending list', + timelockTxs: [ + { + data: [ + { + to: randomAddress(), + value: ethers.BigNumber.from(0), + data: '0x', + }, + ], + delay: 0, + predecessor: EMPTY_BYTES_32, + salt: ethers.utils.formatBytes32String('executed-pending'), + }, + ], + executedTxIndexes: [0], + expectedPendingCount: 0, + }, + { + title: + 'should handle mixed pending, cancelled, and executed operations', + timelockTxs: [ + { + data: [ + { + to: randomAddress(), + value: ethers.BigNumber.from(0), + data: '0x1234', + }, + ], + delay: 0, + predecessor: EMPTY_BYTES_32, + salt: ethers.utils.formatBytes32String('pending-1'), + }, + { + data: [ + { + to: randomAddress(), + value: ethers.BigNumber.from(0), + data: '0x5678', + }, + ], + delay: 0, + predecessor: EMPTY_BYTES_32, + salt: ethers.utils.formatBytes32String('cancelled-pending-1'), + }, + { + data: [ + { + to: randomAddress(), + value: ethers.BigNumber.from(0), + data: '0x', + }, + ], + delay: 0, + predecessor: EMPTY_BYTES_32, + salt: ethers.utils.formatBytes32String('executed-pending-1'), + }, + ], + cancelledTxIndexes: [1], + executedTxIndexes: [2], + expectedPendingCount: 1, + }, + ]; + + for (const { + title, + timelockTxs, + cancelledTxIndexes, + executedTxIndexes, + expectedPendingCount, + } of pendingTestCases) { + it(title, async () => { + const proposerTimelock = TimelockController__factory.connect( + timelockAddress, + proposer, + ); + const executorTimelock = TimelockController__factory.connect( + timelockAddress, + executor, + ); + + const operationIds: string[] = []; + + // Schedule all transactions + for (const timelockTx of timelockTxs) { + const targets = timelockTx.data.map((tx) => tx.to); + const values = timelockTx.data.map((tx) => tx.value ?? '0'); + const dataArray = timelockTx.data.map((tx) => tx.data); + + // Schedule + const scheduleTx = await proposerTimelock.scheduleBatch( + targets, + values, + dataArray, + timelockTx.predecessor, + timelockTx.salt, + timelockTx.delay, + ); + await scheduleTx.wait(); + + // Get operation ID + const operationId = await proposerTimelock.hashOperationBatch( + targets, + values, + dataArray, + timelockTx.predecessor, + timelockTx.salt, + ); + operationIds.push(operationId); + } + + const expectedPendingIds = operationIds.filter((_, index) => { + const isCancelled = cancelledTxIndexes?.includes(index) ?? false; + const isExecuted = executedTxIndexes?.includes(index) ?? false; + return !isCancelled && !isExecuted; + }); + + // Cancel specific transactions + if (cancelledTxIndexes) { + for (const index of cancelledTxIndexes) { + const cancelTx = await proposerTimelock.cancel( + operationIds[index], + ); + await cancelTx.wait(); + } + } + + // Execute specific transactions + if (executedTxIndexes) { + for (const index of executedTxIndexes) { + const timelockTx = timelockTxs[index]; + const targets = timelockTx.data.map((tx) => tx.to); + const values = timelockTx.data.map((tx) => tx.value ?? '0'); + const dataArray = timelockTx.data.map((tx) => tx.data); + + const executeTx = await executorTimelock.executeBatch( + targets, + values, + dataArray, + timelockTx.predecessor, + timelockTx.salt, + ); + await executeTx.wait(); + } + } + + const pendingIds = + await timelockReader.getPendingOperationIds(operationIds); + expect(pendingIds.size).to.equal(expectedPendingCount); + + expect(pendingIds.size).to.equal(expectedPendingIds.length); + for (const operationId of expectedPendingIds) { + expect(pendingIds.has(operationId)).to.be.true; + } + }); + } + }); + describe(`${EvmTimelockReader.prototype.getScheduledExecutableTransactions.name}`, () => { it('should return empty object when no executable transactions exist', async () => { const executableTxs = diff --git a/typescript/sdk/src/timelock/evm/EvmTimelockReader.ts b/typescript/sdk/src/timelock/evm/EvmTimelockReader.ts index 15fe1bd6d40..714a7c57ca8 100644 --- a/typescript/sdk/src/timelock/evm/EvmTimelockReader.ts +++ b/typescript/sdk/src/timelock/evm/EvmTimelockReader.ts @@ -10,7 +10,13 @@ import { TimelockController, TimelockController__factory, } from '@hyperlane-xyz/core'; -import { Address, objFilter, objMap } from '@hyperlane-xyz/utils'; +import { + Address, + arrayToObject, + objFilter, + objMap, + promiseObjAll, +} from '@hyperlane-xyz/utils'; import { MultiProvider } from '../../providers/MultiProvider.js'; import { @@ -150,34 +156,59 @@ export class EvmTimelockReader { } async getReadyOperationIds(operationIds: string[]): Promise> { - const readyOperationIds = new Set(); - for (const operationId of operationIds) { - const isReady = await this.timelockInstance.isOperationReady(operationId); + const isReadyOperationByOperationId = await promiseObjAll( + objMap(arrayToObject(operationIds), (operationId, _) => + this.timelockInstance.isOperationReady(operationId), + ), + ); - if (isReady) { - readyOperationIds.add(operationId); - } - } + return new Set( + Object.keys( + objFilter( + isReadyOperationByOperationId, + (_operationId, isPendingOperation): isPendingOperation is boolean => + isPendingOperation, + ), + ), + ); + } - return readyOperationIds; + async getPendingOperationIds(operationIds: string[]): Promise> { + const isPendingOperationByOperationId = await promiseObjAll( + objMap(arrayToObject(operationIds), (operationId, _) => + this.timelockInstance.isOperationPending(operationId), + ), + ); + + return new Set( + Object.keys( + objFilter( + isPendingOperationByOperationId, + (_operationId, isPendingOperation): isPendingOperation is boolean => + isPendingOperation, + ), + ), + ); } - async getScheduledExecutableTransactions(): Promise< - Record - > { - const [scheduledOperations, cancelledOperations, executedOperations] = - await Promise.all([ - this.getScheduledOperations(), - this.getCancelledOperationIds(), - this.getExecutedOperationIds(), - ]); + async getPendingScheduledOperations(): Promise> { + const scheduledOperations = await this.getScheduledOperations(); + const pendingOperationIds = await this.getPendingOperationIds( + Object.keys(scheduledOperations), + ); // Remove the operations that have been cancelled or executed - const maybeExecutableOperations = objFilter( + return objFilter( scheduledOperations, - (id, _operation): _operation is TimelockTx => - !(cancelledOperations.has(id) || executedOperations.has(id)), + (id, _operation): _operation is TimelockTx => pendingOperationIds.has(id), ); + } + + async getScheduledExecutableTransactions(): Promise< + Record + > { + const maybeExecutableOperations = + await this.getPendingScheduledOperations(); const readyOperationIds = await this.getReadyOperationIds( Object.keys(maybeExecutableOperations), diff --git a/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts b/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts index 532da565ee8..dd134b718d5 100644 --- a/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts +++ b/typescript/sdk/src/token/EvmERC20WarpModule.hardhat-test.ts @@ -197,6 +197,17 @@ describe('EvmERC20WarpHyperlaneModule', async () => { type: TokenType.nativeScaled, allowedRebalancers, }, + [TokenType.collateralMemo]: { + ...baseConfig, + type: TokenType.collateralMemo, + token: token.address, + allowedRebalancers, + }, + [TokenType.nativeMemo]: { + ...baseConfig, + type: TokenType.nativeMemo, + allowedRebalancers, + }, }; }; diff --git a/typescript/sdk/src/token/EvmERC20WarpModule.ts b/typescript/sdk/src/token/EvmERC20WarpModule.ts index 93828d00eca..85471b2d424 100644 --- a/typescript/sdk/src/token/EvmERC20WarpModule.ts +++ b/typescript/sdk/src/token/EvmERC20WarpModule.ts @@ -731,6 +731,17 @@ export class EvmERC20WarpModule extends HyperlaneModule< }> { assert(expectedConfig.interchainSecurityModule, 'Ism derived incorrectly'); + // If ISM is specified as an address, use it directly without deployment + if (typeof expectedConfig.interchainSecurityModule === 'string') { + this.logger.info( + `Using existing ISM at ${expectedConfig.interchainSecurityModule}`, + ); + return { + deployedIsm: expectedConfig.interchainSecurityModule, + updateTransactions: [], + }; + } + const ismModule = new EvmIsmModule( this.multiProvider, { diff --git a/typescript/sdk/src/token/EvmERC20WarpRouteReader.hardhat-test.ts b/typescript/sdk/src/token/EvmERC20WarpRouteReader.hardhat-test.ts index 079a60b24dd..95bf501cc34 100644 --- a/typescript/sdk/src/token/EvmERC20WarpRouteReader.hardhat-test.ts +++ b/typescript/sdk/src/token/EvmERC20WarpRouteReader.hardhat-test.ts @@ -129,9 +129,12 @@ describe('ERC20WarpRouterReader', async () => { it('should derive a token type from contract', async () => { const typesToDerive = [ TokenType.collateral, + TokenType.collateralMemo, TokenType.collateralVault, TokenType.synthetic, + TokenType.syntheticMemo, TokenType.native, + TokenType.nativeMemo, ] as const; await Promise.all( diff --git a/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts b/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts index ebac91b0d69..8ea70a1ebc3 100644 --- a/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts +++ b/typescript/sdk/src/token/EvmERC20WarpRouteReader.ts @@ -2,7 +2,9 @@ import { compareVersions } from 'compare-versions'; import { BigNumber, Contract } from 'ethers'; import { + HypERC20CollateralMemo__factory, HypERC20Collateral__factory, + HypERC20Memo__factory, HypERC20__factory, HypERC4626Collateral__factory, HypERC4626OwnerCollateral__factory, @@ -64,6 +66,7 @@ import { OwnerStatus, TokenMetadata, XERC20TokenMetadata, + XERC20Type, isMovableCollateralTokenConfig, } from './types.js'; import { getExtraLockBoxConfigs } from './xerc20.js'; @@ -108,10 +111,15 @@ export class EvmERC20WarpRouteReader extends EvmRouterReader { this.deriveHypCollateralCctpTokenConfig.bind(this), [TokenType.collateralVaultRebase]: this.deriveHypCollateralVaultRebaseTokenConfig.bind(this), + [TokenType.collateralMemo]: + this.deriveHypCollateralMemoTokenConfig.bind(this), [TokenType.native]: this.deriveHypNativeTokenConfig.bind(this), + [TokenType.nativeMemo]: this.deriveHypNativeMemoTokenConfig.bind(this), [TokenType.nativeOpL2]: this.deriveOpL2TokenConfig.bind(this), [TokenType.nativeOpL1]: this.deriveOpL1TokenConfig.bind(this), [TokenType.synthetic]: this.deriveHypSyntheticTokenConfig.bind(this), + [TokenType.syntheticMemo]: + this.deriveHypSyntheticMemoTokenConfig.bind(this), [TokenType.syntheticRebase]: this.deriveHypSyntheticRebaseConfig.bind(this), [TokenType.nativeScaled]: null, @@ -330,6 +338,82 @@ export class EvmERC20WarpRouteReader extends EvmRouterReader { * @returns The derived token type, which can be one of: collateralVault, collateral, native, or synthetic. */ async deriveTokenType(warpRouteAddress: Address): Promise { + // FORK PATCH: Check for native types first to avoid calling ERC20 methods on non-ERC20 contracts + // This prevents reverts when reading native/nativeMemo token routes + try { + // Quick check if contract accepts ETH (native token indicator) + await this.multiProvider.estimateGas( + this.chain, + { + to: warpRouteAddress, + value: BigNumber.from(0), + }, + NON_ZERO_SENDER_ADDRESS, + ); + + // Check if it has memo support + let hasMemoSupport = false; + try { + // Use minimal ABI to check for transferRemoteMemo without importing full factory + const memoCheckAbi = [ + 'function transferRemoteMemo(uint32,bytes32,uint256,bytes)', + ]; + const contract = new Contract( + warpRouteAddress, + memoCheckAbi, + this.provider, + ); + contract.interface.encodeFunctionData('transferRemoteMemo', [ + 0, + '0x' + '0'.repeat(64), + 0, + '0x', + ]); + hasMemoSupport = true; + } catch { + hasMemoSupport = false; + } + + // Both nativeMemo and syntheticMemo have transferRemoteMemo + // Distinguish by checking for ERC20 totalSupply (synthetic has it, native doesn't) + if (hasMemoSupport) { + try { + const erc20Abi = ['function totalSupply() view returns (uint256)']; + const erc20Contract = new Contract( + warpRouteAddress, + erc20Abi, + this.provider, + ); + await erc20Contract.totalSupply(); + // Has totalSupply, so it's a synthetic token with memo support + // Don't return here - let it fall through to standard detection + // which will properly detect it as syntheticMemo + } catch { + // No totalSupply means it's a native wrapper with memo support + return TokenType.nativeMemo; + } + } else { + // Gas estimation passed but no memo support + // Check if it's truly a native token or synthetic + try { + const erc20Abi = ['function totalSupply() view returns (uint256)']; + const erc20Contract = new Contract( + warpRouteAddress, + erc20Abi, + this.provider, + ); + await erc20Contract.totalSupply(); + // Has totalSupply, so it's a synthetic token + // Don't return here - let it fall through to standard detection + } catch { + // No totalSupply means it's a native wrapper without memo support + return TokenType.native; + } + } + } catch { + // Not a native token, continue with standard detection + } + const contractTypes: Partial< Record > = { @@ -345,6 +429,11 @@ export class EvmERC20WarpRouteReader extends EvmRouterReader { factory: HypXERC20Lockbox__factory, method: 'lockbox', }, + // Check memo types before their non-memo counterparts + [TokenType.collateralMemo]: { + factory: HypERC20CollateralMemo__factory, + method: 'transferRemoteMemo', + }, [TokenType.collateral]: { factory: HypERC20Collateral__factory, method: 'wrappedToken', @@ -353,6 +442,11 @@ export class EvmERC20WarpRouteReader extends EvmRouterReader { factory: HypERC4626__factory, method: 'collateralDomain', }, + // Check syntheticMemo before synthetic + [TokenType.syntheticMemo]: { + factory: HypERC20Memo__factory, + method: 'transferRemoteMemo', + }, [TokenType.synthetic]: { factory: HypERC20__factory, method: 'decimals', @@ -369,6 +463,35 @@ export class EvmERC20WarpRouteReader extends EvmRouterReader { )) { try { const warpRoute = factory.connect(warpRouteAddress, this.provider); + // For memo types, check if the method exists rather than calling it + if (method === 'transferRemoteMemo') { + // Check if the function exists by trying to encode a call to it + try { + warpRoute.interface.encodeFunctionData(method, [ + 0, + '0x' + '0'.repeat(64), + 0, + '0x', + ]); + // If encoding succeeds, the method exists + // For collateralMemo, also check if wrappedToken exists to distinguish from syntheticMemo + // Both collateralMemo and syntheticMemo have transferRemoteMemo, but only collateralMemo has wrappedToken + if (tokenType === TokenType.collateralMemo) { + try { + await warpRoute.wrappedToken(); + return TokenType.collateralMemo; + } catch { + // If wrappedToken doesn't exist, it's not collateralMemo, continue to check syntheticMemo + continue; + } + } + // For syntheticMemo, just return if transferRemoteMemo exists + return tokenType as TokenType; + } catch { + // Method doesn't exist, continue to next type + continue; + } + } await warpRoute[method](); if (tokenType === TokenType.collateral) { const wrappedToken = await warpRoute.wrappedToken(); @@ -413,23 +536,10 @@ export class EvmERC20WarpRouteReader extends EvmRouterReader { } } - // Finally check native - // Using estimateGas to send 0 wei. Success implies that the Warp Route has a receive() function - try { - await this.multiProvider.estimateGas( - this.chain, - { - to: warpRouteAddress, - value: BigNumber.from(0), - }, - NON_ZERO_SENDER_ADDRESS, // Use non-zero address as signer is not provided for read commands - ); - return TokenType.native; - } catch (e) { - throw Error(`Error accessing token specific method ${e}`); - } finally { - this.setSmartProviderLogLevel(getLogLevel()); // returns to original level defined by rootLogger - } + // Native types are now checked at the beginning of the function (FORK PATCH) + // If we reach here, we couldn't determine the token type + this.setSmartProviderLogLevel(getLogLevel()); // returns to original level defined by rootLogger + throw Error(`Unable to determine token type for ${warpRouteAddress}`); } async fetchXERC20Config( @@ -451,9 +561,11 @@ export class EvmERC20WarpRouteReader extends EvmRouterReader { logger: this.logger, }); + // TODO: fix this such that it fetches from WL's values too return { xERC20: { warpRouteLimits: { + type: XERC20Type.Velo, rateLimitPerSecond: ( await xERC20.rateLimitPerSecond(warpRouteAddress) ).toString(), @@ -597,6 +709,27 @@ export class EvmERC20WarpRouteReader extends EvmRouterReader { }; } + private async deriveHypCollateralMemoTokenConfig( + hypToken: Address, + ): Promise { + const hypCollateralTokenInstance = HypERC20CollateralMemo__factory.connect( + hypToken, + this.provider, + ); + + const collateralTokenAddress = + await hypCollateralTokenInstance.wrappedToken(); + const erc20TokenMetadata = await this.fetchERC20Metadata( + collateralTokenAddress, + ); + + return { + ...erc20TokenMetadata, + type: TokenType.collateralMemo, + token: collateralTokenAddress, + }; + } + private async deriveHypCollateralVaultTokenConfig( hypToken: Address, ): Promise { @@ -632,6 +765,17 @@ export class EvmERC20WarpRouteReader extends EvmRouterReader { }; } + private async deriveHypSyntheticMemoTokenConfig( + hypTokenAddress: Address, + ): Promise { + const erc20TokenMetadata = await this.fetchERC20Metadata(hypTokenAddress); + + return { + ...erc20TokenMetadata, + type: TokenType.syntheticMemo, + }; + } + private async deriveHypNativeTokenConfig( _address: Address, ): Promise { @@ -652,6 +796,26 @@ export class EvmERC20WarpRouteReader extends EvmRouterReader { }; } + private async deriveHypNativeMemoTokenConfig( + _address: Address, + ): Promise { + const chainMetadata = this.multiProvider.getChainMetadata(this.chain); + if (!chainMetadata.nativeToken) { + throw new Error( + `Warp route config specifies native memo token but chain metadata for chain "${this.chain}" does not provide native token details`, + ); + } + + const { name, symbol, decimals } = chainMetadata.nativeToken; + return { + type: TokenType.nativeMemo, + name, + symbol, + decimals, + isNft: false, + }; + } + private async deriveOpL2TokenConfig( _address: Address, ): Promise { diff --git a/typescript/sdk/src/token/IToken.ts b/typescript/sdk/src/token/IToken.ts index a7c7e804094..1e7ea04f99a 100644 --- a/typescript/sdk/src/token/IToken.ts +++ b/typescript/sdk/src/token/IToken.ts @@ -80,6 +80,7 @@ export interface IToken extends TokenArgs { isNft(): boolean; isNative(): boolean; + isHypNative(): boolean; isHypToken(): boolean; isIbcToken(): boolean; isMultiChainToken(): boolean; diff --git a/typescript/sdk/src/token/Token.test.ts b/typescript/sdk/src/token/Token.test.ts index d4bd4daef26..8c3b5a6acac 100644 --- a/typescript/sdk/src/token/Token.test.ts +++ b/typescript/sdk/src/token/Token.test.ts @@ -39,6 +39,14 @@ const STANDARD_TO_TOKEN: Record = { symbol: 'INJ', name: 'Injective Coin', }, + [TokenStandard.EvmHypNativeMemo]: { + chainName: TestChainName.test2, + standard: TokenStandard.EvmHypNativeMemo, + addressOrDenom: '0x26f32245fCF5Ad53159E875d5Cae62aEcf19c2d4', // TODO: check + decimals: 18, + symbol: 'INJ', + name: 'Injective Coin', + }, [TokenStandard.EvmHypCollateral]: { chainName: TestChainName.test3, standard: TokenStandard.EvmHypCollateral, @@ -48,6 +56,15 @@ const STANDARD_TO_TOKEN: Record = { symbol: 'USDC', name: 'USDC', }, + [TokenStandard.EvmHypCollateralMemo]: { + chainName: TestChainName.test3, + standard: TokenStandard.EvmHypCollateralMemo, + addressOrDenom: '0x31b5234A896FbC4b3e2F7237592D054716762131', // TODO: check + collateralAddressOrDenom: '0x64544969ed7ebf5f083679233325356ebe738930', // TODO: check + decimals: 18, + symbol: 'USDC', + name: 'USDC', + }, [TokenStandard.EvmHypRebaseCollateral]: { chainName: TestChainName.test3, standard: TokenStandard.EvmHypRebaseCollateral, @@ -78,6 +95,14 @@ const STANDARD_TO_TOKEN: Record = { [TokenStandard.EvmHypSynthetic]: { chainName: TestChainName.test2, standard: TokenStandard.EvmHypSynthetic, + addressOrDenom: '0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147', // TODO: check + decimals: 6, + symbol: 'USDC', + name: 'USDC', + }, + [TokenStandard.EvmHypSyntheticMemo]: { + chainName: TestChainName.test2, + standard: TokenStandard.EvmHypSyntheticMemo, addressOrDenom: '0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147', decimals: 6, symbol: 'USDC', @@ -224,6 +249,10 @@ const STANDARD_TO_TOKEN: Record = { [TokenStandard.StarknetHypCollateral]: null, [TokenStandard.StarknetHypNative]: null, [TokenStandard.StarknetHypSynthetic]: null, + + [TokenStandard.RadixHypCollateral]: null, + [TokenStandard.RadixNative]: null, + [TokenStandard.RadixHypSynthetic]: null, }; const PROTOCOL_TO_ADDRESS_FOR_BALANCE_CHECK: Partial< @@ -275,4 +304,126 @@ describe('Token', () => { sandbox.restore(); }); } + + describe('isFungibleWith', () => { + const evmNativeToken = Token.FromChainMetadataNativeToken(test1); + + const evmHypNativeToken = new Token({ + chainName: TestChainName.test1, + standard: TokenStandard.EvmHypNative, + addressOrDenom: '0x26f32245fCF5Ad53159E875d5Cae62aEcf19c2d4', + decimals: 18, + symbol: 'ETH', + name: 'Ether', + }); + + const evmHypNativeToken2 = new Token({ + chainName: TestChainName.test1, + standard: TokenStandard.EvmHypNative, + addressOrDenom: '0x41b5234A896FbC4b3e2F7237592D054716762131', + decimals: 18, + symbol: 'ETH', + name: 'Ether', + }); + + const evmHypCollateralToken = new Token({ + chainName: TestChainName.test1, + standard: TokenStandard.EvmHypCollateral, + addressOrDenom: '0x31b5234A896FbC4b3e2F7237592D054716762131', + collateralAddressOrDenom: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + decimals: 6, + symbol: 'USDC', + name: 'USD Coin', + }); + + const evmHypSyntheticToken = new Token({ + chainName: TestChainName.test1, + standard: TokenStandard.EvmHypSynthetic, + addressOrDenom: '0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147', + decimals: 6, + symbol: 'USDC', + name: 'USD Coin', + }); + + const cosmosIbcToken = new Token({ + chainName: testCosmosChain.name, + standard: TokenStandard.CosmosIbc, + addressOrDenom: + 'ibc/773B4D0A3CD667B2275D5A4A7A2F0909C0BA0F4059C0B9181E680DDF4965DCC7', + decimals: 6, + symbol: 'TIA', + name: 'TIA', + }); + + const cosmosNativeToken = new Token({ + chainName: testCosmosChain.name, + standard: TokenStandard.CosmosNative, + addressOrDenom: + 'ibc/773B4D0A3CD667B2275D5A4A7A2F0909C0BA0F4059C0B9181E680DDF4965DCC7', + decimals: 6, + symbol: 'TIA', + name: 'TIA', + }); + + it('returns false for undefined token', () => { + expect(evmHypCollateralToken.isFungibleWith(undefined)).to.be.false; + }); + + it('returns false for tokens on different chains', () => { + const differentChainToken = new Token({ + chainName: TestChainName.test2, + standard: TokenStandard.ERC20, + addressOrDenom: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + decimals: 6, + symbol: 'USDC', + name: 'USD Coin', + }); + expect(evmHypCollateralToken.isFungibleWith(differentChainToken)).to.be + .false; + }); + + it('returns true for identical tokens', () => { + expect(evmHypCollateralToken.isFungibleWith(evmHypCollateralToken)).to.be + .true; + }); + + it('returns false for collateralized token and non-matching token', () => { + expect(evmHypCollateralToken.isFungibleWith(evmHypSyntheticToken)).to.be + .false; + }); + + it('returns false for non-IBC tokens with different standards', () => { + expect(evmHypCollateralToken.isFungibleWith(cosmosNativeToken)).to.be + .false; + }); + + it('returns false for HypNative token and different token standards', () => { + expect(evmHypNativeToken.isFungibleWith(evmHypCollateralToken)).to.be + .false; + }); + + it('returns true for collateralized token without collateral address and native token', () => { + expect(evmHypNativeToken2.isFungibleWith(evmNativeToken)).to.be.true; + }); + + it('returns true for collateralized token without collateral address and HypNative token', () => { + expect(evmHypNativeToken2.isFungibleWith(evmHypNativeToken)).to.be.true; + }); + + it('returns true for IBC token and matching native token', () => { + expect(cosmosIbcToken.isFungibleWith(cosmosNativeToken)).to.be.true; + }); + + it('returns false for IBC token and non-matching native token', () => { + const nonMatchingNativeToken = new Token({ + chainName: testCosmosChain.name, + standard: TokenStandard.CosmosNative, + addressOrDenom: 'different_denom', + decimals: 6, + symbol: 'OTHER', + name: 'Other Token', + }); + expect(cosmosIbcToken.isFungibleWith(nonMatchingNativeToken)).to.be.false; + }); + }); }); diff --git a/typescript/sdk/src/token/Token.ts b/typescript/sdk/src/token/Token.ts index f32e1ef590b..8205194d0f9 100644 --- a/typescript/sdk/src/token/Token.ts +++ b/typescript/sdk/src/token/Token.ts @@ -18,6 +18,7 @@ import type { IToken, TokenArgs } from './IToken.js'; import { TokenAmount } from './TokenAmount.js'; import { TokenConnection, TokenConnectionType } from './TokenConnection.js'; import { + PROTOCOL_TO_HYP_NATIVE_STANDARD, PROTOCOL_TO_NATIVE_STANDARD, TOKEN_COLLATERALIZED_STANDARDS, TOKEN_HYP_STANDARDS, @@ -59,6 +60,11 @@ import type { IHypTokenAdapter, ITokenAdapter, } from './adapters/ITokenAdapter.js'; +import { + RadixHypCollateralAdapter, + RadixHypSyntheticAdapter, + RadixNativeTokenAdapter, +} from './adapters/RadixTokenAdapter.js'; import { SealevelHypCollateralAdapter, SealevelHypNativeAdapter, @@ -156,6 +162,10 @@ export class Token implements IToken { {}, addressOrDenom, ); + } else if (standard === TokenStandard.RadixNative) { + return new RadixNativeTokenAdapter(chainName, multiProvider, { + token: addressOrDenom, + }); } else if (this.isHypToken()) { return this.getHypAdapter(multiProvider); } else if (this.isIbcToken()) { @@ -197,12 +207,16 @@ export class Token implements IToken { `Token chain ${chainName} not found in multiProvider`, ); - if (standard === TokenStandard.EvmHypNative) { + if ( + standard === TokenStandard.EvmHypNative || + standard === TokenStandard.EvmHypNativeMemo + ) { return new EvmHypNativeAdapter(chainName, multiProvider, { token: addressOrDenom, }); } else if ( standard === TokenStandard.EvmHypCollateral || + standard === TokenStandard.EvmHypCollateralMemo || standard === TokenStandard.EvmHypOwnerCollateral ) { return new EvmHypCollateralAdapter(chainName, multiProvider, { @@ -216,7 +230,10 @@ export class Token implements IToken { return new EvmHypCollateralFiatAdapter(chainName, multiProvider, { token: addressOrDenom, }); - } else if (standard === TokenStandard.EvmHypSynthetic) { + } else if ( + standard === TokenStandard.EvmHypSynthetic || + standard === TokenStandard.EvmHypSyntheticMemo + ) { return new EvmHypSyntheticAdapter(chainName, multiProvider, { token: addressOrDenom, }); @@ -319,6 +336,14 @@ export class Token implements IToken { return new StarknetHypCollateralAdapter(chainName, multiProvider, { warpRouter: addressOrDenom, }); + } else if (standard === TokenStandard.RadixHypCollateral) { + return new RadixHypCollateralAdapter(chainName, multiProvider, { + token: addressOrDenom, + }); + } else if (standard === TokenStandard.RadixHypSynthetic) { + return new RadixHypSyntheticAdapter(chainName, multiProvider, { + token: addressOrDenom, + }); } else { throw new Error(`No hyp adapter found for token standard: ${standard}`); } @@ -389,6 +414,12 @@ export class Token implements IToken { return Object.values(PROTOCOL_TO_NATIVE_STANDARD).includes(this.standard); } + isHypNative(): boolean { + return Object.values(PROTOCOL_TO_HYP_NATIVE_STANDARD).includes( + this.standard, + ); + } + isCollateralized(): boolean { return TOKEN_COLLATERALIZED_STANDARDS.includes(this.standard); } @@ -469,7 +500,10 @@ export class Token implements IToken { return true; } - if (!this.collateralAddressOrDenom && token.isNative()) { + if ( + !this.collateralAddressOrDenom && + (token.isNative() || token.isHypNative()) + ) { return true; } } diff --git a/typescript/sdk/src/token/TokenMetadataMap.ts b/typescript/sdk/src/token/TokenMetadataMap.ts index e9e20b440f4..eabd1164055 100644 --- a/typescript/sdk/src/token/TokenMetadataMap.ts +++ b/typescript/sdk/src/token/TokenMetadataMap.ts @@ -15,6 +15,13 @@ export class TokenMetadataMap { this.tokenMetadataMap.set(chain, metadata); } + update(chain: string, metadata: TokenMetadata): void { + this.tokenMetadataMap.set( + chain, + Object.assign(this.tokenMetadataMap.get(chain) ?? {}, metadata), + ); + } + getDecimals(chain: string): number | undefined { const config = this.tokenMetadataMap.get(chain); if (config) return config.decimals!; diff --git a/typescript/sdk/src/token/TokenStandard.ts b/typescript/sdk/src/token/TokenStandard.ts index 7fa021b1a3e..8449bd4eb3b 100644 --- a/typescript/sdk/src/token/TokenStandard.ts +++ b/typescript/sdk/src/token/TokenStandard.ts @@ -13,11 +13,14 @@ export enum TokenStandard { ERC721 = 'ERC721', EvmNative = 'EvmNative', EvmHypNative = 'EvmHypNative', + EvmHypNativeMemo = 'EvmHypNativeMemo', EvmHypCollateral = 'EvmHypCollateral', + EvmHypCollateralMemo = 'EvmHypCollateralMemo', EvmHypOwnerCollateral = 'EvmHypOwnerCollateral', EvmHypRebaseCollateral = 'EvmHypRebaseCollateral', EvmHypCollateralFiat = 'EvmHypCollateralFiat', EvmHypSynthetic = 'EvmHypSynthetic', + EvmHypSyntheticMemo = 'EvmHypSyntheticMemo', EvmHypSyntheticRebase = 'EvmHypSyntheticRebase', EvmHypXERC20 = 'EvmHypXERC20', EvmHypXERC20Lockbox = 'EvmHypXERC20Lockbox', @@ -50,10 +53,15 @@ export enum TokenStandard { CosmNativeHypCollateral = 'CosmosNativeHypCollateral', CosmNativeHypSynthetic = 'CosmosNativeHypSynthetic', - //Starknet + // Starknet StarknetHypNative = 'StarknetHypNative', StarknetHypCollateral = 'StarknetHypCollateral', StarknetHypSynthetic = 'StarknetHypSynthetic', + + // Radix + RadixNative = 'RadixNative', + RadixHypCollateral = 'RadixHypCollateral', + RadixHypSynthetic = 'RadixHypSynthetic', } // Allows for omission of protocol field in token args @@ -63,11 +71,14 @@ export const TOKEN_STANDARD_TO_PROTOCOL: Record = { ERC721: ProtocolType.Ethereum, EvmNative: ProtocolType.Ethereum, EvmHypNative: ProtocolType.Ethereum, + EvmHypNativeMemo: ProtocolType.Ethereum, EvmHypCollateral: ProtocolType.Ethereum, + EvmHypCollateralMemo: ProtocolType.Ethereum, EvmHypOwnerCollateral: ProtocolType.Ethereum, EvmHypRebaseCollateral: ProtocolType.Ethereum, EvmHypCollateralFiat: ProtocolType.Ethereum, EvmHypSynthetic: ProtocolType.Ethereum, + EvmHypSyntheticMemo: ProtocolType.Ethereum, EvmHypSyntheticRebase: ProtocolType.Ethereum, EvmHypXERC20: ProtocolType.Ethereum, EvmHypXERC20Lockbox: ProtocolType.Ethereum, @@ -104,6 +115,11 @@ export const TOKEN_STANDARD_TO_PROTOCOL: Record = { StarknetHypCollateral: ProtocolType.Starknet, StarknetHypNative: ProtocolType.Starknet, StarknetHypSynthetic: ProtocolType.Starknet, + + // Radix + RadixNative: ProtocolType.Radix, + RadixHypCollateral: ProtocolType.Radix, + RadixHypSynthetic: ProtocolType.Radix, }; export const TOKEN_STANDARD_TO_PROVIDER_TYPE: Record< @@ -129,7 +145,9 @@ export const TOKEN_NFT_STANDARDS = [ export const TOKEN_COLLATERALIZED_STANDARDS = [ TokenStandard.EvmHypCollateral, + TokenStandard.EvmHypCollateralMemo, TokenStandard.EvmHypNative, + TokenStandard.EvmHypNativeMemo, TokenStandard.SealevelHypCollateral, TokenStandard.SealevelHypNative, TokenStandard.CwHypCollateral, @@ -146,6 +164,11 @@ export const XERC20_STANDARDS = [ TokenStandard.EvmHypVSXERC20Lockbox, ]; +export const LOCKBOX_STANDARDS = [ + TokenStandard.EvmHypXERC20Lockbox, + TokenStandard.EvmHypVSXERC20Lockbox, +]; + export const MINT_LIMITED_STANDARDS = [ TokenStandard.EvmHypXERC20, TokenStandard.EvmHypXERC20Lockbox, @@ -156,11 +179,14 @@ export const MINT_LIMITED_STANDARDS = [ export const TOKEN_HYP_STANDARDS = [ TokenStandard.EvmHypNative, + TokenStandard.EvmHypNativeMemo, TokenStandard.EvmHypCollateral, + TokenStandard.EvmHypCollateralMemo, TokenStandard.EvmHypCollateralFiat, TokenStandard.EvmHypOwnerCollateral, TokenStandard.EvmHypRebaseCollateral, TokenStandard.EvmHypSynthetic, + TokenStandard.EvmHypSyntheticMemo, TokenStandard.EvmHypSyntheticRebase, TokenStandard.EvmHypXERC20, TokenStandard.EvmHypXERC20Lockbox, @@ -177,6 +203,8 @@ export const TOKEN_HYP_STANDARDS = [ TokenStandard.StarknetHypNative, TokenStandard.StarknetHypCollateral, TokenStandard.StarknetHypSynthetic, + TokenStandard.RadixHypCollateral, + TokenStandard.RadixHypSynthetic, ]; export const TOKEN_MULTI_CHAIN_STANDARDS = [ @@ -215,7 +243,22 @@ export const tokenTypeToStandard = ( } throw new Error( - `token type ${tokenType} not available on protocol Cosmos Native`, + `token type ${tokenType} not available on protocol ${protocolType}`, + ); + } + case ProtocolType.Radix: { + if ( + RADIX_SUPPORTED_TOKEN_TYPES.includes( + tokenType as RadixSupportedTokenTypes, + ) + ) { + return RADIX_TOKEN_TYPE_TO_STANDARD[ + tokenType as RadixSupportedTokenTypes + ]; + } + + throw new Error( + `token type ${tokenType} not available on protocol ${protocolType}`, ); } default: { @@ -228,7 +271,9 @@ export const tokenTypeToStandard = ( export const EVM_TOKEN_TYPE_TO_STANDARD: Record = { [TokenType.native]: TokenStandard.EvmHypNative, + [TokenType.nativeMemo]: TokenStandard.EvmHypNativeMemo, [TokenType.collateral]: TokenStandard.EvmHypCollateral, + [TokenType.collateralMemo]: TokenStandard.EvmHypCollateralMemo, [TokenType.collateralFiat]: TokenStandard.EvmHypCollateralFiat, [TokenType.XERC20]: TokenStandard.EvmHypXERC20, [TokenType.XERC20Lockbox]: TokenStandard.EvmHypXERC20Lockbox, @@ -236,6 +281,7 @@ export const EVM_TOKEN_TYPE_TO_STANDARD: Record = { [TokenType.collateralVaultRebase]: TokenStandard.EvmHypRebaseCollateral, [TokenType.collateralUri]: TokenStandard.EvmHypCollateral, [TokenType.synthetic]: TokenStandard.EvmHypSynthetic, + [TokenType.syntheticMemo]: TokenStandard.EvmHypSyntheticMemo, [TokenType.syntheticRebase]: TokenStandard.EvmHypSyntheticRebase, [TokenType.syntheticUri]: TokenStandard.EvmHypSynthetic, [TokenType.nativeScaled]: TokenStandard.EvmHypNative, @@ -280,6 +326,21 @@ export const STARKNET_TOKEN_TYPE_TO_STANDARD: Record< [TokenType.synthetic]: TokenStandard.StarknetHypSynthetic, }; +export const RADIX_SUPPORTED_TOKEN_TYPES = [ + TokenType.collateral, + TokenType.synthetic, +] as const; + +type RadixSupportedTokenTypes = (typeof RADIX_SUPPORTED_TOKEN_TYPES)[number]; + +export const RADIX_TOKEN_TYPE_TO_STANDARD: Record< + RadixSupportedTokenTypes, + TokenStandard +> = { + [TokenType.collateral]: TokenStandard.RadixHypCollateral, + [TokenType.synthetic]: TokenStandard.RadixHypSynthetic, +}; + export const PROTOCOL_TO_NATIVE_STANDARD: Record = { [ProtocolType.Ethereum]: TokenStandard.EvmNative, @@ -287,4 +348,18 @@ export const PROTOCOL_TO_NATIVE_STANDARD: Record = [ProtocolType.CosmosNative]: TokenStandard.CosmosNative, [ProtocolType.Sealevel]: TokenStandard.SealevelNative, [ProtocolType.Starknet]: TokenStandard.StarknetHypNative, + [ProtocolType.Radix]: TokenStandard.RadixNative, }; + +export const PROTOCOL_TO_HYP_NATIVE_STANDARD: Record< + ProtocolType, + TokenStandard +> = { + [ProtocolType.Ethereum]: TokenStandard.EvmHypNative, + [ProtocolType.Cosmos]: TokenStandard.CwHypNative, + [ProtocolType.Sealevel]: TokenStandard.SealevelHypNative, + [ProtocolType.Starknet]: TokenStandard.StarknetHypNative, + // collateral and native are the same for cosmosnative and radix + [ProtocolType.Radix]: TokenStandard.RadixHypCollateral, + [ProtocolType.CosmosNative]: TokenStandard.CosmNativeHypCollateral, +}; diff --git a/typescript/sdk/src/token/adapters/CosmosModuleTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmosModuleTokenAdapter.ts index 965dcfad0b0..a30437a3d54 100644 --- a/typescript/sdk/src/token/adapters/CosmosModuleTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmosModuleTokenAdapter.ts @@ -255,13 +255,22 @@ export class CosmNativeHypCollateralAdapter ); } + const destinationMetadata = this.multiProvider.getChainMetadata( + params.destination, + ); + const destinationProtocol = destinationMetadata.protocol; + const msg: MsgRemoteTransferEncodeObject = { typeUrl: '/hyperlane.warp.v1.MsgRemoteTransfer', value: { sender: params.fromAccountOwner, recipient: addressToBytes32( - convertToProtocolAddress(params.recipient, ProtocolType.Ethereum), - ProtocolType.Ethereum, + convertToProtocolAddress( + params.recipient, + destinationMetadata.protocol, + destinationMetadata.bech32Prefix, + ), + destinationProtocol, ), amount: params.weiAmountOrId.toString(), token_id: this.tokenId, diff --git a/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts b/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts index 4cd8def5c06..2954872ef6d 100644 --- a/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/CosmosTokenAdapter.ts @@ -1,4 +1,4 @@ -import { MsgTransferEncodeObject } from '@cosmjs/stargate'; +import { MsgSendEncodeObject, MsgTransferEncodeObject } from '@cosmjs/stargate'; import { Address, Domain, assert } from '@hyperlane-xyz/utils'; @@ -21,7 +21,7 @@ const COSMOS_IBC_TRANSFER_TIMEOUT = 600_000; // 10 minutes // Interacts with native tokens on a Cosmos chain (e.g TIA on Celestia) export class CosmNativeTokenAdapter extends BaseCosmosAdapter - implements ITokenAdapter + implements ITokenAdapter { constructor( public readonly chainName: ChainName, @@ -68,9 +68,21 @@ export class CosmNativeTokenAdapter } async populateTransferTx( - _transferParams: TransferParams, - ): Promise { - throw new Error('TODO not yet implemented'); + transferParams: TransferParams, + ): Promise { + return { + typeUrl: '/cosmos.bank.v1beta1.MsgSend', + value: { + fromAddress: transferParams.fromAccountOwner, + toAddress: transferParams.recipient, + amount: [ + { + amount: transferParams.weiAmountOrId.toString(), + denom: this.properties.ibcDenom, + }, + ], + }, + }; } async getTotalSupply(): Promise { @@ -131,6 +143,12 @@ export class CosmIbcTokenAdapter return { amount: 0n, addressOrDenom: this.properties.ibcDenom }; } + override async populateTransferTx( + _transferParams: TransferParams, + ): Promise { + throw new Error('TODO not yet implemented'); + } + async populateTransferRemoteTx( transferParams: TransferRemoteParams, memo = '', diff --git a/typescript/sdk/src/token/adapters/EvmTokenAdapter.ts b/typescript/sdk/src/token/adapters/EvmTokenAdapter.ts index d070dfba576..d472d80ce8d 100644 --- a/typescript/sdk/src/token/adapters/EvmTokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/EvmTokenAdapter.ts @@ -51,11 +51,13 @@ import { IHypXERC20Adapter, IMovableCollateralRouterAdapter, ITokenAdapter, + IXERC20Adapter, IXERC20VSAdapter, InterchainGasQuote, RateLimitMidPoint, TransferParams, TransferRemoteParams, + xERC20Limits, } from './ITokenAdapter.js'; // An estimate of the gas amount for a typical EVM token router transferRemote transaction @@ -302,7 +304,7 @@ export class EvmHypCollateralAdapter ); } - protected async getWrappedTokenAddress(): Promise
{ + async getWrappedTokenAddress(): Promise
{ if (!this.wrappedTokenAddress) { this.wrappedTokenAddress = await this.collateralContract.wrappedToken(); } @@ -830,6 +832,50 @@ export class EvmHypNativeAdapter }, ); } + + override async getBridgedSupply(): Promise { + const balance = await this.getProvider().getBalance(this.addresses.token); + return BigInt(balance.toString()); + } +} + +export class EvmXERC20Adapter + extends EvmTokenAdapter + implements IXERC20Adapter +{ + xERC20: IXERC20; + + constructor( + public readonly chainName: ChainName, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: { token: Address }, + ) { + super(chainName, multiProvider, addresses); + + this.xERC20 = IXERC20__factory.connect(addresses.token, this.getProvider()); + } + + async getLimits(bridge: Address): Promise { + const mint = await this.xERC20.mintingMaxLimitOf(bridge); + const burn = await this.xERC20.burningMaxLimitOf(bridge); + + return { + mint: BigInt(mint.toString()), + burn: BigInt(burn.toString()), + }; + } + + async populateSetLimitsTx( + bridge: Address, + mint: bigint, + burn: bigint, + ): Promise { + return this.xERC20.populateTransaction.setLimits( + bridge, + mint.toString(), + burn.toString(), + ); + } } export class EvmXERC20VSAdapter diff --git a/typescript/sdk/src/token/adapters/ITokenAdapter.ts b/typescript/sdk/src/token/adapters/ITokenAdapter.ts index b22a9823416..3ad728bcd7b 100644 --- a/typescript/sdk/src/token/adapters/ITokenAdapter.ts +++ b/typescript/sdk/src/token/adapters/ITokenAdapter.ts @@ -30,6 +30,11 @@ export interface RateLimitMidPoint { midPoint: bigint; } +export interface xERC20Limits { + mint: bigint; + burn: bigint; +} + export interface ITokenAdapter { getBalance(address: Address): Promise; getTotalSupply(): Promise; @@ -56,6 +61,7 @@ export interface IMovableCollateralRouterAdapter extends ITokenAdapter { amount: Numberish, isWarp: boolean, ): Promise; + getWrappedTokenAddress(): Promise
; populateRebalanceTx( domain: Domain, @@ -116,6 +122,10 @@ export interface IXERC20VSAdapter extends ITokenAdapter { ): Promise; } +export interface IXERC20Adapter extends ITokenAdapter { + getLimits(bridge: Address): Promise; +} + export interface IHypCollateralFiatAdapter extends IHypTokenAdapter { getMintLimit(): Promise; } diff --git a/typescript/sdk/src/token/adapters/RadixTokenAdapter.ts b/typescript/sdk/src/token/adapters/RadixTokenAdapter.ts new file mode 100644 index 00000000000..4d96b930740 --- /dev/null +++ b/typescript/sdk/src/token/adapters/RadixTokenAdapter.ts @@ -0,0 +1,256 @@ +import { BigNumber } from 'bignumber.js'; + +import { RadixSDK } from '@hyperlane-xyz/radix-sdk'; +import { + Address, + Domain, + addressToBytes32, + assert, + strip0x, +} from '@hyperlane-xyz/utils'; + +import { BaseRadixAdapter } from '../../app/MultiProtocolApp.js'; +import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider.js'; +import { RadixSDKTransaction } from '../../providers/ProviderType.js'; +import { ChainName } from '../../types.js'; +import { TokenMetadata } from '../types.js'; + +import { + IHypTokenAdapter, + ITokenAdapter, + InterchainGasQuote, + TransferParams, + TransferRemoteParams, +} from './ITokenAdapter.js'; + +export class RadixNativeTokenAdapter + extends BaseRadixAdapter + implements ITokenAdapter +{ + protected provider: RadixSDK; + protected tokenId: string; + + protected async getResourceAddress(): Promise { + return this.tokenId; + } + + constructor( + public readonly chainName: ChainName, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: { token: Address }, + ) { + super(chainName, multiProvider, addresses); + + this.provider = this.getProvider(); + this.tokenId = addresses.token; + } + + async getBalance(address: string): Promise { + const resource = await this.getResourceAddress(); + return this.provider.base.getBalance({ + address, + resource, + }); + } + + async getMetadata(): Promise { + const { + name, + symbol, + divisibility: decimals, + } = await this.provider.query.warp.getToken({ + token: this.tokenId, + }); + + assert( + name !== undefined, + `name on radix token ${this.tokenId} is undefined`, + ); + assert( + symbol !== undefined, + `symbol on radix token ${this.tokenId} is undefined`, + ); + assert( + decimals !== undefined, + `divisibility on radix token ${this.tokenId} is undefined`, + ); + + return { + name, + symbol, + decimals, + }; + } + + async getMinimumTransferAmount(_recipient: Address): Promise { + return 0n; + } + + async isApproveRequired(): Promise { + return false; + } + + populateApproveTx( + _transferParams: TransferParams, + ): Promise { + throw new Error('Approve not required for native tokens'); + } + + async isRevokeApprovalRequired(_: Address, __: Address): Promise { + return false; + } + + async populateTransferTx( + transferParams: TransferParams, + ): Promise { + const resource = await this.getResourceAddress(); + + assert(transferParams.fromAccountOwner, `no sender in transfer params`); + + return { + networkId: this.provider.getNetworkId(), + manifest: await this.provider.base.transfer({ + from_address: transferParams.fromAccountOwner!, + to_address: transferParams.recipient, + resource_address: resource, + amount: transferParams.weiAmountOrId.toString(), + }), + }; + } + + async getTotalSupply(): Promise { + const resource = await this.getResourceAddress(); + return this.provider.base.getTotalSupply({ + resource, + }); + } +} + +export class RadixHypCollateralAdapter + extends RadixNativeTokenAdapter + implements IHypTokenAdapter +{ + constructor( + public readonly chainName: ChainName, + public readonly multiProvider: MultiProtocolProvider, + public readonly addresses: { token: Address }, + ) { + super(chainName, multiProvider, addresses); + } + + protected async getResourceAddress(): Promise { + const { origin_denom } = await this.provider.query.warp.getToken({ + token: this.tokenId, + }); + return origin_denom; + } + + async getDomains(): Promise { + const { remote_routers } = await this.provider.query.warp.getRemoteRouters({ + token: this.tokenId, + }); + + return remote_routers.map((router) => parseInt(router.receiver_domain)); + } + + async getRouterAddress(domain: Domain): Promise { + const { remote_routers } = await this.provider.query.warp.getRemoteRouters({ + token: this.tokenId, + }); + + const router = remote_routers.find( + (router) => parseInt(router.receiver_domain) === domain, + ); + + if (!router) { + throw new Error(`Router with domain "${domain}" not found`); + } + + return Buffer.from(strip0x(router.receiver_contract), 'hex'); + } + + async getAllRouters(): Promise> { + const { remote_routers } = await this.provider.query.warp.getRemoteRouters({ + token: this.tokenId, + }); + + return remote_routers.map((router) => ({ + domain: parseInt(router.receiver_domain), + address: Buffer.from(strip0x(router.receiver_contract), 'hex'), + })); + } + + async getBridgedSupply(): Promise { + return this.provider.query.warp.getBridgedSupply({ token: this.tokenId }); + } + + async quoteTransferRemoteGas( + destination: Domain, + ): Promise { + const { resource: addressOrDenom, amount } = + await this.provider.query.warp.quoteRemoteTransfer({ + token: this.tokenId, + destination_domain: destination, + }); + + return { + addressOrDenom, + amount, + }; + } + + async populateTransferRemoteTx( + params: TransferRemoteParams, + ): Promise { + assert(params.fromAccountOwner, `no sender in remote transfer params`); + + if (!params.interchainGas) { + params.interchainGas = await this.quoteTransferRemoteGas( + params.destination, + ); + } + + const { remote_routers } = await this.provider.query.warp.getRemoteRouters({ + token: this.tokenId, + }); + + const router = remote_routers.find( + (router) => parseInt(router.receiver_domain) === params.destination, + ); + + if (!router) { + throw new Error( + `Failed to find remote router for token id and destination: ${this.tokenId},${params.destination}`, + ); + } + + if (!params.interchainGas.addressOrDenom) { + throw new Error( + `Require denom for max fee, didn't receive and denom in the interchainGas quote`, + ); + } + + return { + networkId: this.provider.getNetworkId(), + manifest: await this.provider.populate.warp.remoteTransfer({ + from_address: params.fromAccountOwner!, + recipient: strip0x(addressToBytes32(params.recipient)), + amount: params.weiAmountOrId.toString(), + token: this.tokenId, + destination_domain: params.destination, + gas_limit: router.gas, + custom_hook_id: params.customHook || '', + custom_hook_metadata: '', + max_fee: { + denom: params.interchainGas.addressOrDenom || '', + // convert the attos back to a Decimal with scale 18 + amount: new BigNumber(params.interchainGas.amount.toString()) + .div(new BigNumber(10).pow(18)) + .toString(), + }, + }), + }; + } +} + +export class RadixHypSyntheticAdapter extends RadixHypCollateralAdapter {} diff --git a/typescript/sdk/src/token/checker.test.ts b/typescript/sdk/src/token/checker.test.ts new file mode 100644 index 00000000000..3f415710a19 --- /dev/null +++ b/typescript/sdk/src/token/checker.test.ts @@ -0,0 +1,273 @@ +import { expect } from 'chai'; + +import type { TokenRouter } from '@hyperlane-xyz/core'; + +import { TestChainName, testChainMetadata } from '../consts/testChains.js'; +import { MultiProvider } from '../providers/MultiProvider.js'; +import { ChainMap } from '../types.js'; + +import { HypERC20App } from './app.js'; +import { HypERC20Checker } from './checker.js'; +import { TokenType } from './config.js'; +import { HypTokenRouterConfig } from './types.js'; + +describe('HypERC20Checker.checkDecimalConsistency', () => { + function buildChecker(configMap: ChainMap) { + const mp = new MultiProvider(testChainMetadata); + // We do not exercise app-dependent code paths; a minimal stub suffices. + const dummyApp = {} as unknown as HypERC20App; + return new HypERC20Checker(mp, dummyApp, configMap); + } + + function dummyToken(address: string): TokenRouter { + return { address } as unknown as TokenRouter; + } + + const owner = '0x000000000000000000000000000000000000dEaD'; + const mailbox = '0x000000000000000000000000000000000000b001'; + + it('does not add violation when decimals are uniform (unscaled)', () => { + const config: ChainMap = { + [TestChainName.test1]: { + type: TokenType.native, + owner, + mailbox, + name: 'TKN', + symbol: 'TKN', + decimals: 18, + }, + [TestChainName.test2]: { + type: TokenType.native, + owner, + mailbox, + name: 'TKN', + symbol: 'TKN', + decimals: 18, + }, + }; + + const checker = buildChecker(config); + const chainDecimals = { + [TestChainName.test1]: 18, + [TestChainName.test2]: 18, + } as Record; + + checker.checkDecimalConsistency( + TestChainName.test1, + dummyToken('0x1111111111111111111111111111111111111111'), + chainDecimals, + 'actual', + true, + ); + + expect(checker.violations).to.have.length(0); + }); + + it('does not add violation when decimals are non-uniform but correct scale is provided', () => { + const config: ChainMap = { + [TestChainName.test1]: { + type: TokenType.native, + owner, + mailbox, + name: 'TKN', + symbol: 'TKN', + decimals: 18, + }, + [TestChainName.test2]: { + type: TokenType.native, + owner, + mailbox, + name: 'TKN', + symbol: 'TKN', + decimals: 6, + // 18 -> 6 implies scale 10^(18-6) = 10^12 + scale: 1_000_000_000_000, + }, + }; + + const checker = buildChecker(config); + const chainDecimals = { + [TestChainName.test1]: 18, + [TestChainName.test2]: 6, + } as Record; + + checker.checkDecimalConsistency( + TestChainName.test1, + dummyToken('0x2222222222222222222222222222222222222222'), + chainDecimals, + 'actual', + true, + ); + + expect(checker.violations).to.have.length(0); + }); + + it('adds violation when decimals are non-uniform and scale is incorrect/missing', () => { + const config: ChainMap = { + [TestChainName.test1]: { + type: TokenType.native, + owner, + mailbox, + name: 'TKN', + symbol: 'TKN', + decimals: 18, + }, + [TestChainName.test2]: { + type: TokenType.native, + owner, + mailbox, + name: 'TKN', + symbol: 'TKN', + decimals: 6, + scale: 1000, // incorrect + }, + }; + + const checker = buildChecker(config); + const chainDecimals = { + [TestChainName.test1]: 18, + [TestChainName.test2]: 6, + } as Record; + + checker.checkDecimalConsistency( + TestChainName.test1, + dummyToken('0x3333333333333333333333333333333333333333'), + chainDecimals, + 'actual', + true, + ); + + expect(checker.violations).to.have.length(1); + expect(checker.violations[0].type).to.equal('TokenDecimalsMismatch'); + }); + + it('adds violation when nonEmpty is true and all decimals are undefined', () => { + const config: ChainMap> = { + [TestChainName.test1]: { + type: TokenType.native, + owner, + mailbox, + name: 'TKN', + symbol: 'TKN', + }, + [TestChainName.test2]: { + type: TokenType.native, + owner, + mailbox, + name: 'TKN', + symbol: 'TKN', + }, + }; + + const checker = buildChecker(config as ChainMap); + const chainDecimals = { + [TestChainName.test1]: undefined, + [TestChainName.test2]: undefined, + } as Record; + + checker.checkDecimalConsistency( + TestChainName.test1, + dummyToken('0x4444444444444444444444444444444444444444'), + chainDecimals, + 'actual', + true, + ); + + expect(checker.violations).to.have.length(1); + expect(checker.violations[0].type).to.equal('TokenDecimalsMismatch'); + }); + + it('adds violation when some chains define decimals and others do not (nonEmpty=false)', () => { + const config: ChainMap> = { + [TestChainName.test1]: { + type: TokenType.native, + owner, + mailbox, + name: 'TKN', + symbol: 'TKN', + decimals: 18, + }, + [TestChainName.test2]: { + type: TokenType.native, + owner, + mailbox, + name: 'TKN', + symbol: 'TKN', + // decimals omitted + }, + [TestChainName.test3]: { + type: TokenType.native, + owner, + mailbox, + name: 'TKN', + symbol: 'TKN', + decimals: 18, + }, + }; + + const checker = buildChecker(config as ChainMap); + const chainDecimals = { + [TestChainName.test1]: 18, + [TestChainName.test2]: undefined, + [TestChainName.test3]: 18, + } as Record; + + checker.checkDecimalConsistency( + TestChainName.test1, + dummyToken('0x5555555555555555555555555555555555555555'), + chainDecimals, + 'config', + false, + ); + + expect(checker.violations).to.have.length(1); + expect(checker.violations[0].type).to.equal('TokenDecimalsMismatch'); + }); + + it('adds violation when some chains define decimals and others do not (nonEmpty=true)', () => { + const config: ChainMap> = { + [TestChainName.test1]: { + type: TokenType.native, + owner, + mailbox, + name: 'TKN', + symbol: 'TKN', + decimals: 18, + }, + [TestChainName.test2]: { + type: TokenType.native, + owner, + mailbox, + name: 'TKN', + symbol: 'TKN', + // decimals omitted + }, + [TestChainName.test3]: { + type: TokenType.native, + owner, + mailbox, + name: 'TKN', + symbol: 'TKN', + decimals: 18, + }, + }; + + const checker = buildChecker(config as ChainMap); + const chainDecimals = { + [TestChainName.test1]: 18, + [TestChainName.test2]: undefined, + [TestChainName.test3]: 18, + } as Record; + + checker.checkDecimalConsistency( + TestChainName.test1, + dummyToken('0x6666666666666666666666666666666666666666'), + chainDecimals, + 'actual', + true, + ); + + expect(checker.violations).to.have.length(1); + expect(checker.violations[0].type).to.equal('TokenDecimalsMismatch'); + }); +}); diff --git a/typescript/sdk/src/token/checker.ts b/typescript/sdk/src/token/checker.ts index 28d39dca90a..bacbfd1c714 100644 --- a/typescript/sdk/src/token/checker.ts +++ b/typescript/sdk/src/token/checker.ts @@ -18,6 +18,7 @@ import { TokenMismatchViolation } from '../deploy/types.js'; import { ProxiedRouterChecker } from '../router/ProxiedRouterChecker.js'; import { ProxiedFactories } from '../router/types.js'; import { ChainName } from '../types.js'; +import { verifyScale } from '../utils/decimals.js'; import { HypERC20App } from './app.js'; import { NON_ZERO_SENDER_ADDRESS, TokenType } from './config.js'; @@ -238,7 +239,8 @@ export class HypERC20Checker extends ProxiedRouterChecker< decimals = await (hypToken as unknown as ERC20).decimals(); } else if ( isCollateralTokenConfig(expectedConfig) || - isXERC20TokenConfig(expectedConfig) + isXERC20TokenConfig(expectedConfig) || + isCctpTokenConfig(expectedConfig) ) { const collateralToken = await this.getCollateralToken(chain); decimals = await collateralToken.decimals(); @@ -288,26 +290,75 @@ export class HypERC20Checker extends ProxiedRouterChecker< chain: ChainName, hypToken: TokenRouter, chainDecimals: Record, - decimalType: string, + decimalType: 'actual' | 'config', nonEmpty: boolean, ) { - const uniqueChainDecimals = new Set( - Object.values(chainDecimals).filter((decimals) => !!decimals), + const definedDecimals = Object.values(chainDecimals).filter( + (decimals): decimals is number => decimals !== undefined, ); - if ( - uniqueChainDecimals.size > 1 || - (nonEmpty && uniqueChainDecimals.size === 0) - ) { + const uniqueChainDecimals = new Set(definedDecimals); + + // Disallow partial specification: some chains define decimals while others don't + const totalChains = Object.keys(chainDecimals).length; + const definedCount = definedDecimals.length; + if (definedCount > 0 && definedCount < totalChains) { + const violation: TokenMismatchViolation = { + type: 'TokenDecimalsMismatch', + chain, + expected: `consistent ${decimalType} decimals specified across all chains (considering scale)`, + actual: JSON.stringify(chainDecimals, (_k, v) => + v === undefined ? 'undefined' : v, + ), + tokenAddress: hypToken.address, + }; + this.addViolation(violation); + return; + } + + // If we require non-empty and nothing is defined, report immediately + if (nonEmpty && uniqueChainDecimals.size === 0) { const violation: TokenMismatchViolation = { type: 'TokenDecimalsMismatch', chain, - expected: `${ - nonEmpty ? 'non-empty and ' : '' - }consistent ${decimalType} decimals`, - actual: JSON.stringify(chainDecimals), + expected: `non-empty and consistent ${decimalType} decimals (considering scale)`, + actual: JSON.stringify(chainDecimals, (_k, v) => + v === undefined ? 'undefined' : v, + ), tokenAddress: hypToken.address, }; this.addViolation(violation); + return; + } + + // If unscaled decimals agree, no need to check scale + if (uniqueChainDecimals.size <= 1) return; + + // Build a TokenMetadata map from all chains; at this point decimals are defined on all chains + const metadataMap = new Map( + Object.entries(chainDecimals).map(([chn, decimals]) => [ + chn, + { + name: this.configMap[chn]?.name ?? 'unknown', + symbol: this.configMap[chn]?.symbol ?? 'unknown', + decimals: decimals as number, + scale: this.configMap[chn]?.scale ?? 1, + }, + ]), + ); + + if (verifyScale(metadataMap)) { + return; // Decimals are consistent when accounting for scale } + + const violation: TokenMismatchViolation = { + type: 'TokenDecimalsMismatch', + chain, + expected: `consistent ${decimalType} decimals (considering scale)`, + actual: JSON.stringify(chainDecimals, (_k, v) => + v === undefined ? 'undefined' : v, + ), + tokenAddress: hypToken.address, + }; + this.addViolation(violation); } } diff --git a/typescript/sdk/src/token/config.ts b/typescript/sdk/src/token/config.ts index cd4f5d8a417..6b79c856189 100644 --- a/typescript/sdk/src/token/config.ts +++ b/typescript/sdk/src/token/config.ts @@ -1,8 +1,10 @@ export enum TokenType { synthetic = 'synthetic', + syntheticMemo = 'syntheticMemo', syntheticRebase = 'syntheticRebase', syntheticUri = 'syntheticUri', collateral = 'collateral', + collateralMemo = 'collateralMemo', collateralVault = 'collateralVault', collateralVaultRebase = 'collateralVaultRebase', XERC20 = 'xERC20', @@ -11,6 +13,7 @@ export enum TokenType { collateralUri = 'collateralUri', collateralCctp = 'collateralCctp', native = 'native', + nativeMemo = 'nativeMemo', nativeOpL2 = 'nativeOpL2', nativeOpL1 = 'nativeOpL1', // backwards compatible alias to native @@ -28,11 +31,14 @@ const isMovableCollateralTokenTypeMap = { [TokenType.collateralUri]: false, [TokenType.collateralVault]: true, [TokenType.collateralVaultRebase]: true, + [TokenType.collateralMemo]: true, [TokenType.native]: true, + [TokenType.nativeMemo]: true, [TokenType.nativeOpL1]: false, [TokenType.nativeOpL2]: false, [TokenType.nativeScaled]: true, [TokenType.synthetic]: false, + [TokenType.syntheticMemo]: false, [TokenType.syntheticRebase]: false, [TokenType.syntheticUri]: false, } as const; @@ -50,8 +56,10 @@ export function isMovableCollateralTokenType(type: TokenType): boolean { export const gasOverhead = (tokenType: TokenType): number => { switch (tokenType) { case TokenType.synthetic: + case TokenType.syntheticMemo: return 64_000; case TokenType.native: + case TokenType.nativeMemo: return 44_000; default: return 68_000; diff --git a/typescript/sdk/src/token/contracts.ts b/typescript/sdk/src/token/contracts.ts index b1c87ac81fb..fadeefa4ae4 100644 --- a/typescript/sdk/src/token/contracts.ts +++ b/typescript/sdk/src/token/contracts.ts @@ -1,5 +1,7 @@ import { + HypERC20CollateralMemo__factory, HypERC20Collateral__factory, + HypERC20Memo__factory, HypERC20__factory, HypERC721Collateral__factory, HypERC721URICollateral__factory, @@ -9,6 +11,7 @@ import { HypERC4626OwnerCollateral__factory, HypERC4626__factory, HypFiatToken__factory, + HypNativeMemo__factory, HypNative__factory, HypXERC20Lockbox__factory, HypXERC20__factory, @@ -21,9 +24,11 @@ import { TokenType } from './config.js'; export const hypERC20contracts = { [TokenType.synthetic]: 'HypERC20', + [TokenType.syntheticMemo]: 'HypERC20Memo', [TokenType.syntheticRebase]: 'HypERC4626', [TokenType.syntheticUri]: 'HypERC721', [TokenType.collateral]: 'HypERC20Collateral', + [TokenType.collateralMemo]: 'HypERC20CollateralMemo', [TokenType.collateralFiat]: 'HypFiatToken', [TokenType.collateralUri]: 'HypERC721Collateral', [TokenType.XERC20]: 'HypXERC20', @@ -32,6 +37,7 @@ export const hypERC20contracts = { [TokenType.collateralVaultRebase]: 'HypERC4626Collateral', [TokenType.collateralCctp]: 'TokenBridgeCctp', [TokenType.native]: 'HypNative', + [TokenType.nativeMemo]: 'HypNativeMemo', [TokenType.nativeOpL2]: 'OPL2TokenBridgeNative', [TokenType.nativeOpL1]: 'OpL1TokenBridgeNative', // uses same contract as native @@ -41,7 +47,9 @@ export type HypERC20contracts = typeof hypERC20contracts; export const hypERC20factories = { [TokenType.synthetic]: new HypERC20__factory(), + [TokenType.syntheticMemo]: new HypERC20Memo__factory(), [TokenType.collateral]: new HypERC20Collateral__factory(), + [TokenType.collateralMemo]: new HypERC20CollateralMemo__factory(), [TokenType.collateralCctp]: new TokenBridgeCctp__factory(), [TokenType.collateralVault]: new HypERC4626OwnerCollateral__factory(), [TokenType.collateralVaultRebase]: new HypERC4626Collateral__factory(), @@ -50,6 +58,7 @@ export const hypERC20factories = { [TokenType.XERC20]: new HypXERC20__factory(), [TokenType.XERC20Lockbox]: new HypXERC20Lockbox__factory(), [TokenType.native]: new HypNative__factory(), + [TokenType.nativeMemo]: new HypNativeMemo__factory(), [TokenType.nativeOpL2]: new OpL2NativeTokenBridge__factory(), // assume V1 for now [TokenType.nativeOpL1]: new OpL1V1NativeTokenBridge__factory(), diff --git a/typescript/sdk/src/token/deploy.ts b/typescript/sdk/src/token/deploy.ts index fbd6dac561b..dbffdb54f83 100644 --- a/typescript/sdk/src/token/deploy.ts +++ b/typescript/sdk/src/token/deploy.ts @@ -228,7 +228,7 @@ abstract class TokenDeployer< if (isNativeTokenConfig(config)) { const nativeToken = multiProvider.getChainMetadata(chain).nativeToken; if (nativeToken) { - metadataMap.set( + metadataMap.update( chain, TokenMetadataSchema.parse({ ...nativeToken, @@ -254,7 +254,7 @@ abstract class TokenDeployer< erc721.name(), erc721.symbol(), ]); - metadataMap.set( + metadataMap.update( chain, TokenMetadataSchema.parse({ name, @@ -290,7 +290,7 @@ abstract class TokenDeployer< erc20.decimals(), ]); - metadataMap.set( + metadataMap.update( chain, TokenMetadataSchema.parse({ name, diff --git a/typescript/sdk/src/token/nativeTokenMetadata.ts b/typescript/sdk/src/token/nativeTokenMetadata.ts index ad0181b8a38..79383ec8f24 100644 --- a/typescript/sdk/src/token/nativeTokenMetadata.ts +++ b/typescript/sdk/src/token/nativeTokenMetadata.ts @@ -34,4 +34,11 @@ export const PROTOCOL_TO_DEFAULT_NATIVE_TOKEN: Record< name: 'Ether', symbol: 'ETH', }, + [ProtocolType.Radix]: { + decimals: 18, + denom: + 'resource_rdx1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxradxrd', + name: 'Radix', + symbol: 'XRD', + }, }; diff --git a/typescript/sdk/src/token/types.test.ts b/typescript/sdk/src/token/types.test.ts index cec6aedb2b1..9c060523549 100644 --- a/typescript/sdk/src/token/types.test.ts +++ b/typescript/sdk/src/token/types.test.ts @@ -14,11 +14,16 @@ import { const SOME_ADDRESS = ethers.Wallet.createRandom().address; const COLLATERAL_TYPES = [ TokenType.collateral, + TokenType.collateralMemo, TokenType.collateralUri, TokenType.collateralVault, ]; -const NON_COLLATERAL_TYPES = [TokenType.synthetic, TokenType.syntheticUri]; +const NON_COLLATERAL_TYPES = [ + TokenType.synthetic, + TokenType.syntheticMemo, + TokenType.syntheticUri, +]; describe('WarpRouteDeployConfigSchema refine', () => { let config: WarpRouteDeployConfig; diff --git a/typescript/sdk/src/token/types.ts b/typescript/sdk/src/token/types.ts index 999593a4f4e..a258add9f85 100644 --- a/typescript/sdk/src/token/types.ts +++ b/typescript/sdk/src/token/types.ts @@ -65,7 +65,11 @@ export const BaseMovableTokenConfigSchema = z.object({ }); export const NativeTokenConfigSchema = TokenMetadataSchema.partial().extend({ - type: z.enum([TokenType.native, TokenType.nativeScaled]), + type: z.enum([ + TokenType.native, + TokenType.nativeMemo, + TokenType.nativeScaled, + ]), ...BaseMovableTokenConfigSchema.shape, }); export type NativeTokenConfig = z.infer; @@ -98,6 +102,7 @@ export const CollateralTokenConfigSchema = TokenMetadataSchema.partial().extend( { type: z.enum([ TokenType.collateral, + TokenType.collateralMemo, TokenType.collateralVault, TokenType.collateralVaultRebase, TokenType.collateralFiat, @@ -115,22 +120,42 @@ export const CollateralTokenConfigSchema = TokenMetadataSchema.partial().extend( export type CollateralTokenConfig = z.infer; export const isCollateralTokenConfig = isCompliant(CollateralTokenConfigSchema); -const xERC20LimitConfigSchema = z.object({ +export enum XERC20Type { + Velo = 'velo', + Standard = 'standard', +} + +// Velo variant +const XERC20VSLimitConfigSchema = z.object({ + type: z.literal(XERC20Type.Velo), bufferCap: z.string().optional(), rateLimitPerSecond: z.string().optional(), }); -export type XERC20LimitConfig = z.infer; +export type XERC20VSLimitConfig = z.infer; + +const XERC20StandardLimitConfigSchema = z.object({ + type: z.literal(XERC20Type.Standard), + mint: z.string().optional(), + burn: z.string().optional(), +}); +export type XERC20StandardLimitConfig = z.infer< + typeof XERC20StandardLimitConfigSchema +>; +const xERC20Limits = z.discriminatedUnion('type', [ + XERC20VSLimitConfigSchema, + XERC20StandardLimitConfigSchema, +]); const xERC20ExtraBridgesLimitConfigsSchema = z.object({ lockbox: z.string(), - limits: xERC20LimitConfigSchema, + limits: xERC20Limits, }); const xERC20TokenMetadataSchema = z.object({ xERC20: z .object({ extraBridges: z.array(xERC20ExtraBridgesLimitConfigsSchema).optional(), - warpRouteLimits: xERC20LimitConfigSchema, + warpRouteLimits: xERC20Limits, }) .optional(), }); @@ -176,7 +201,11 @@ export const isCollateralRebaseTokenConfig = isCompliant( ); export const SyntheticTokenConfigSchema = TokenMetadataSchema.partial().extend({ - type: z.enum([TokenType.synthetic, TokenType.syntheticUri]), + type: z.enum([ + TokenType.synthetic, + TokenType.syntheticMemo, + TokenType.syntheticUri, + ]), initialSupply: z.string().or(z.number()).optional(), }); export type SyntheticTokenConfig = z.infer; diff --git a/typescript/sdk/src/token/xerc20.ts b/typescript/sdk/src/token/xerc20.ts index 022c47da572..a4b6b545fbc 100644 --- a/typescript/sdk/src/token/xerc20.ts +++ b/typescript/sdk/src/token/xerc20.ts @@ -14,7 +14,7 @@ import { GetEventLogsResponse } from '../rpc/evm/types.js'; import { viemLogFromGetEventLogsResponse } from '../rpc/evm/utils.js'; import { ChainNameOrId } from '../types.js'; -import { XERC20TokenExtraBridgesLimits } from './types.js'; +import { XERC20TokenExtraBridgesLimits, XERC20Type } from './types.js'; const minimalXERC20VSABI = [ { @@ -191,6 +191,7 @@ async function getLockboxesFromLogs( .map((log) => ({ lockbox: log.args.bridge, limits: { + type: XERC20Type.Velo, bufferCap: log.args.bufferCap.toString(), rateLimitPerSecond: log.args.rateLimitPerSecond.toString(), }, diff --git a/typescript/sdk/src/types.ts b/typescript/sdk/src/types.ts index 6d4aee7435a..d742aefc570 100644 --- a/typescript/sdk/src/types.ts +++ b/typescript/sdk/src/types.ts @@ -1,9 +1,11 @@ -import type { ethers } from 'ethers'; +import type { BigNumber, Signer, ethers } from 'ethers'; import { z } from 'zod'; -import type { Domain, ProtocolType } from '@hyperlane-xyz/utils'; +import { SigningHyperlaneModuleClient } from '@hyperlane-xyz/cosmos-sdk'; +import type { Address, Domain, ProtocolType } from '@hyperlane-xyz/utils'; import { ZHash } from './metadata/customZodTypes.js'; +import { MultiProvider } from './providers/MultiProvider.js'; // An alias for string to clarify type is a chain name export type ChainName = string; @@ -37,3 +39,19 @@ export const PausableSchema = OwnableSchema.extend({ paused: z.boolean(), }); export type PausableConfig = z.infer; + +export type TypedSigner = Signer | SigningHyperlaneModuleClient; + +export interface IMultiProtocolSignerManager { + getMultiProvider(): Promise; + + getEVMSigner(chain: ChainName): Signer; + getCosmosNativeSigner(chain: ChainName): SigningHyperlaneModuleClient; + + getSignerAddress(chain: ChainName): Promise
; + getBalance(params: { + address: Address; + chain: ChainName; + denom?: string; + }): Promise; +} diff --git a/typescript/sdk/src/utils/cosmos.ts b/typescript/sdk/src/utils/cosmos.ts index 30e9bcb002d..bfe79229bf0 100644 --- a/typescript/sdk/src/utils/cosmos.ts +++ b/typescript/sdk/src/utils/cosmos.ts @@ -2,765 +2,513 @@ import { z } from 'zod'; // Generated from https://github.com/cosmos/chain-registry/blob/master/chain.schema.json // using https://stefanterdell.github.io/json-schema-to-zod-react/ -export const CosmosChainSchema = z - .object({ - $schema: z - .string() - .regex(new RegExp('^(\\.\\./)+chain\\.schema\\.json$')) - .min(1) - .optional(), - chain_name: z.string().regex(new RegExp('[a-z0-9]+')).min(1), - chain_type: z - .enum([ - 'cosmos', - 'eip155', - 'bip122', - 'polkadot', - 'solana', - 'algorand', - 'arweave', - 'ergo', - 'fil', - 'hedera', - 'monero', - 'reef', - 'stacks', - 'starknet', - 'stellar', - 'tezos', - 'vechain', - 'waves', - 'xrpl', - 'unknown', - ]) - .describe( - "The 'type' of chain as the corresponding CAIP-2 Namespace value. E.G., 'cosmos' or 'eip155'. Namespaces can be found here: https://github.com/ChainAgnostic/namespaces/tree/main.", - ), - chain_id: z.string().min(1).optional(), - pre_fork_chain_name: z - .string() - .regex(new RegExp('[a-z0-9]+')) - .min(1) - .optional(), - pretty_name: z.string().min(1).optional(), - website: z.string().url().min(1).optional(), - update_link: z.string().url().min(1).optional(), - status: z.enum(['live', 'upcoming', 'killed']).optional(), - network_type: z.enum(['mainnet', 'testnet', 'devnet']).optional(), - bech32_prefix: z - .string() - .min(1) - .describe( - "The default prefix for the human-readable part of addresses that identifies the coin type. Must be registered with SLIP-0173. E.g., 'cosmos'", - ) - .optional(), - bech32_config: z - .object({ - bech32PrefixAccAddr: z - .string() - .min(1) - .describe("e.g., 'cosmos'") - .optional(), - bech32PrefixAccPub: z - .string() - .min(1) - .describe("e.g., 'cosmospub'") - .optional(), - bech32PrefixValAddr: z - .string() - .min(1) - .describe("e.g., 'cosmosvaloper'") - .optional(), - bech32PrefixValPub: z - .string() - .min(1) - .describe("e.g., 'cosmosvaloperpub'") - .optional(), - bech32PrefixConsAddr: z - .string() - .min(1) - .describe("e.g., 'cosmosvalcons'") - .optional(), - bech32PrefixConsPub: z - .string() - .min(1) - .describe("e.g., 'cosmosvalconspub'") - .optional(), - }) - .strict() - .describe('Used to override the bech32_prefix for specific uses.') - .optional(), - daemon_name: z.string().min(1).optional(), - node_home: z.string().min(1).optional(), - key_algos: z - .array( - z.enum(['secp256k1', 'ethsecp256k1', 'ed25519', 'sr25519', 'bn254']), - ) - .optional(), - slip44: z.number().optional(), - alternative_slip44s: z.array(z.number()).optional(), - fees: z - .object({ - fee_tokens: z.array( - z - .object({ - denom: z.string().min(1), - fixed_min_gas_price: z.number().optional(), - low_gas_price: z.number().optional(), - average_gas_price: z.number().optional(), - high_gas_price: z.number().optional(), - gas_costs: z - .object({ - cosmos_send: z.number().optional(), - ibc_transfer: z.number().optional(), - }) - .strict() - .optional(), - }) - .strict(), - ), - }) - .strict() - .optional(), - staking: z - .object({ - staking_tokens: z.array( - z.object({ denom: z.string().min(1) }).strict(), - ), - lock_duration: z - .object({ - blocks: z - .number() - .describe( - 'The number of blocks for which the staked tokens are locked.', - ) - .optional(), - time: z - .string() - .min(1) - .describe( - 'The approximate time for which the staked tokens are locked.', - ) - .optional(), - }) - .strict() - .optional(), - }) - .strict() - .optional(), - codebase: z - .object({ - git_repo: z.string().url().min(1).optional(), - recommended_version: z.string().min(1).optional(), - compatible_versions: z.array(z.string().min(1)).optional(), - go_version: z - .string() - .regex(new RegExp('^[0-9]+\\.[0-9]+(\\.[0-9]+)?$')) - .min(1) - .describe('Minimum accepted go version to build the binary.') - .optional(), - language: z - .object({ - type: z.enum(['go', 'rust', 'solidity', 'other']), - version: z - .string() - .min(1) - .describe("Simple version string (e.g., 'v1.0.0').") - .optional(), - repo: z - .string() - .url() - .min(1) - .describe('URL of the code repository.') - .optional(), - tag: z - .string() - .min(1) - .describe( - "Detailed version identifier (e.g., 'v1.0.0-a1s2f43g').", - ) - .optional(), - }) - .strict() - .optional(), - binaries: z - .object({ - 'linux/amd64': z.string().url().min(1).optional(), - 'linux/arm64': z.string().url().min(1).optional(), - 'darwin/amd64': z.string().url().min(1).optional(), - 'darwin/arm64': z.string().url().min(1).optional(), - 'windows/amd64': z.string().url().min(1).optional(), - 'windows/arm64': z.string().url().min(1).optional(), - }) - .strict() - .optional(), - cosmos_sdk_version: z.string().min(1).optional(), - sdk: z - .object({ - type: z.enum(['cosmos', 'penumbra', 'other']), - version: z - .string() - .min(1) - .describe("Simple version string (e.g., 'v1.0.0').") - .optional(), - repo: z - .string() - .url() - .min(1) - .describe('URL of the code repository.') - .optional(), - tag: z - .string() - .min(1) - .describe( - "Detailed version identifier (e.g., 'v1.0.0-a1s2f43g').", - ) - .optional(), - }) - .strict() - .optional(), - consensus: z - .object({ - type: z.enum(['tendermint', 'cometbft', 'sei-tendermint']), - version: z - .string() - .min(1) - .describe("Simple version string (e.g., 'v1.0.0').") - .optional(), - repo: z - .string() - .url() - .min(1) - .describe('URL of the code repository.') - .optional(), - tag: z - .string() - .min(1) - .describe( - "Detailed version identifier (e.g., 'v1.0.0-a1s2f43g').", - ) - .optional(), - }) - .strict() - .optional(), - cosmwasm_version: z.string().min(1).optional(), - cosmwasm_enabled: z.boolean().optional(), - cosmwasm_path: z - .string() - .regex(new RegExp('^\\$HOME.*$')) - .min(1) - .describe( - 'Relative path to the cosmwasm directory. ex. $HOME/.juno/data/wasm', - ) - .optional(), - cosmwasm: z - .object({ - version: z - .string() - .min(1) - .describe("Simple version string (e.g., 'v1.0.0').") - .optional(), - repo: z - .string() - .url() - .min(1) - .describe('URL of the code repository.') - .optional(), - tag: z - .string() - .min(1) - .describe( - "Detailed version identifier (e.g., 'v1.0.0-a1s2f43g').", - ) - .optional(), - enabled: z.boolean().optional(), - path: z - .string() - .regex(new RegExp('^\\$HOME.*$')) - .min(1) - .describe( - 'Relative path to the cosmwasm directory. ex. $HOME/.juno/data/wasm', - ) - .optional(), - }) - .strict() - .optional(), - ibc_go_version: z.string().min(1).optional(), - ics_enabled: z - .array( - z - .enum(['ics20-1', 'ics27-1', 'mauth']) - .describe('IBC app or ICS standard.'), - ) - .describe( - 'List of IBC apps (usually corresponding to a ICS standard) which have been enabled on the network.', - ) - .optional(), - ibc: z - .object({ - type: z.enum(['go', 'rust', 'other']), - version: z - .string() - .min(1) - .describe("Simple version string (e.g., 'v1.0.0').") - .optional(), - repo: z - .string() - .url() - .min(1) - .describe('URL of the code repository.') - .optional(), - tag: z - .string() - .min(1) - .describe( - "Detailed version identifier (e.g., 'v1.0.0-a1s2f43g').", - ) - .optional(), - ics_enabled: z - .array( - z - .enum(['ics20-1', 'ics27-1', 'mauth']) - .describe('IBC app or ICS standard.'), - ) - .describe( - 'List of IBC apps (usually corresponding to a ICS standard) which have been enabled on the network.', - ) - .optional(), - }) - .strict() - .optional(), - genesis: z - .object({ - name: z.string().min(1).optional(), - genesis_url: z.string().url().min(1), - ics_ccv_url: z.string().url().min(1).optional(), - }) - .strict() - .optional(), - versions: z - .array( - z - .object({ - name: z.string().min(1).describe('Official Upgrade Name'), - tag: z.string().min(1).describe('Git Upgrade Tag').optional(), - height: z.number().describe('Block Height').optional(), - proposal: z - .number() - .describe( - 'Proposal that will officially signal community acceptance of the upgrade.', - ) - .optional(), - previous_version_name: z - .string() - .min(1) - .describe('[Optional] Name of the previous version') - .optional(), - next_version_name: z - .string() - .min(0) - .describe('[Optional] Name of the following version') - .optional(), - recommended_version: z.string().min(1).optional(), - compatible_versions: z.array(z.string().min(1)).optional(), - go_version: z - .string() - .regex(new RegExp('^[0-9]+\\.[0-9]+(\\.[0-9]+)?$')) - .min(1) - .describe('Minimum accepted go version to build the binary.') - .optional(), - language: z - .object({ - type: z.enum(['go', 'rust', 'solidity', 'other']), - version: z - .string() - .min(1) - .describe("Simple version string (e.g., 'v1.0.0').") - .optional(), - repo: z - .string() - .url() - .min(1) - .describe('URL of the code repository.') - .optional(), - tag: z - .string() - .min(1) - .describe( - "Detailed version identifier (e.g., 'v1.0.0-a1s2f43g').", - ) - .optional(), - }) - .strict() - .optional(), - cosmos_sdk_version: z.string().min(1).optional(), - sdk: z - .object({ - type: z.enum(['cosmos', 'penumbra', 'other']), - version: z - .string() - .min(1) - .describe("Simple version string (e.g., 'v1.0.0').") - .optional(), - repo: z - .string() - .url() - .min(1) - .describe('URL of the code repository.') - .optional(), - tag: z - .string() - .min(1) - .describe( - "Detailed version identifier (e.g., 'v1.0.0-a1s2f43g').", - ) - .optional(), - }) - .strict() - .optional(), - consensus: z - .object({ - type: z.enum(['tendermint', 'cometbft', 'sei-tendermint']), - version: z - .string() - .min(1) - .describe("Simple version string (e.g., 'v1.0.0').") - .optional(), - repo: z - .string() - .url() - .min(1) - .describe('URL of the code repository.') - .optional(), - tag: z - .string() - .min(1) - .describe( - "Detailed version identifier (e.g., 'v1.0.0-a1s2f43g').", - ) - .optional(), - }) - .strict() - .optional(), - cosmwasm_version: z.string().min(1).optional(), - cosmwasm_enabled: z.boolean().optional(), - cosmwasm_path: z - .string() - .regex(new RegExp('^\\$HOME.*$')) - .min(1) - .describe( - 'Relative path to the cosmwasm directory. ex. $HOME/.juno/data/wasm', - ) - .optional(), - cosmwasm: z - .object({ - version: z - .string() - .min(1) - .describe("Simple version string (e.g., 'v1.0.0').") - .optional(), - repo: z - .string() - .url() - .min(1) - .describe('URL of the code repository.') - .optional(), - tag: z - .string() - .min(1) - .describe( - "Detailed version identifier (e.g., 'v1.0.0-a1s2f43g').", - ) - .optional(), - enabled: z.boolean().optional(), - path: z - .string() - .regex(new RegExp('^\\$HOME.*$')) - .min(1) - .describe( - 'Relative path to the cosmwasm directory. ex. $HOME/.juno/data/wasm', - ) - .optional(), - }) - .strict() - .optional(), - ibc_go_version: z.string().min(1).optional(), - ics_enabled: z - .array( - z - .enum(['ics20-1', 'ics27-1', 'mauth']) - .describe('IBC app or ICS standard.'), - ) - .describe( - 'List of IBC apps (usually corresponding to a ICS standard) which have been enabled on the network.', - ) - .optional(), - ibc: z - .object({ - type: z.enum(['go', 'rust', 'other']), - version: z - .string() - .min(1) - .describe("Simple version string (e.g., 'v1.0.0').") - .optional(), - repo: z - .string() - .url() - .min(1) - .describe('URL of the code repository.') - .optional(), - tag: z - .string() - .min(1) - .describe( - "Detailed version identifier (e.g., 'v1.0.0-a1s2f43g').", - ) - .optional(), - ics_enabled: z - .array( - z - .enum(['ics20-1', 'ics27-1', 'mauth']) - .describe('IBC app or ICS standard.'), - ) - .describe( - 'List of IBC apps (usually corresponding to a ICS standard) which have been enabled on the network.', - ) - .optional(), - }) - .strict() - .optional(), - binaries: z - .object({ - 'linux/amd64': z.string().url().min(1).optional(), - 'linux/arm64': z.string().url().min(1).optional(), - 'darwin/amd64': z.string().url().min(1).optional(), - 'darwin/arm64': z.string().url().min(1).optional(), - 'windows/amd64': z.string().url().min(1).optional(), - 'windows/arm64': z.string().url().min(1).optional(), - }) - .strict() - .optional(), - }) - .strict(), - ) - .optional(), - }) - .strict() - .optional(), - images: z - .array( +export const CosmosChainSchema = z.object({ + $schema: z + .string() + .regex(new RegExp('^(\\.\\./)+chain\\.schema\\.json$')) + .min(1) + .optional(), + chain_name: z.string().regex(new RegExp('[a-z0-9]+')).min(1), + chain_type: z + .enum([ + 'cosmos', + 'eip155', + 'bip122', + 'polkadot', + 'solana', + 'algorand', + 'arweave', + 'ergo', + 'fil', + 'hedera', + 'monero', + 'reef', + 'stacks', + 'starknet', + 'stellar', + 'tezos', + 'vechain', + 'waves', + 'xrpl', + 'unknown', + ]) + .describe( + "The 'type' of chain as the corresponding CAIP-2 Namespace value. E.G., 'cosmos' or 'eip155'. Namespaces can be found here: https://github.com/ChainAgnostic/namespaces/tree/main.", + ), + chain_id: z.string().min(1).optional(), + pre_fork_chain_name: z + .string() + .regex(new RegExp('[a-z0-9]+')) + .min(1) + .optional(), + pretty_name: z.string().min(1).optional(), + website: z.string().url().min(1).optional(), + status: z.enum(['live', 'upcoming', 'killed']).optional(), + network_type: z.enum(['mainnet', 'testnet', 'devnet']).optional(), + bech32_prefix: z + .string() + .min(1) + .describe( + "The default prefix for the human-readable part of addresses that identifies the coin type. Must be registered with SLIP-0173. E.g., 'cosmos'", + ) + .optional(), + bech32_config: z + .object({ + bech32PrefixAccAddr: z + .string() + .min(1) + .describe("e.g., 'cosmos'") + .optional(), + bech32PrefixAccPub: z + .string() + .min(1) + .describe("e.g., 'cosmospub'") + .optional(), + bech32PrefixValAddr: z + .string() + .min(1) + .describe("e.g., 'cosmosvaloper'") + .optional(), + bech32PrefixValPub: z + .string() + .min(1) + .describe("e.g., 'cosmosvaloperpub'") + .optional(), + bech32PrefixConsAddr: z + .string() + .min(1) + .describe("e.g., 'cosmosvalcons'") + .optional(), + bech32PrefixConsPub: z + .string() + .min(1) + .describe("e.g., 'cosmosvalconspub'") + .optional(), + }) + .strict() + .describe('Used to override the bech32_prefix for specific uses.') + .optional(), + daemon_name: z.string().min(1).optional(), + node_home: z.string().min(1).optional(), + key_algos: z + .array(z.enum(['secp256k1', 'ethsecp256k1', 'ed25519', 'sr25519', 'bn254'])) + .optional(), + slip44: z.number().optional(), + alternative_slip44s: z.array(z.number()).optional(), + fees: z + .object({ + fee_tokens: z.array( z .object({ - image_sync: z - .object({ - chain_name: z - .string() - .min(1) - .describe( - "The chain name or platform from which the object resides. E.g., 'cosmoshub', 'ethereum', 'forex', or 'nasdaq'", - ), - base_denom: z - .string() - .min(1) - .describe( - "The base denom of the asset from which the object originates. E.g., when describing ATOM from Cosmos Hub, specify 'uatom', NOT 'atom' nor 'ATOM'; base units are unique per platform.", - ) - .optional(), - }) - .strict() - .describe( - 'The (primary) key used to identify an object within the Chain Registry.', - ) - .optional(), - png: z - .string() - .regex( - new RegExp( - '^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.png$', - ), - ) - .min(1) - .optional(), - svg: z - .string() - .regex( - new RegExp( - '^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.svg$', - ), - ) - .min(1) - .optional(), - theme: z + denom: z.string().min(1), + fixed_min_gas_price: z.number().optional(), + low_gas_price: z.number().optional(), + average_gas_price: z.number().optional(), + high_gas_price: z.number().optional(), + gas_costs: z .object({ - primary_color_hex: z - .string() - .regex(new RegExp('^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$')) - .min(1) - .optional(), - background_color_hex: z - .string() - .regex( - new RegExp('^(#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})|none)$'), - ) - .min(1) - .optional(), - circle: z.boolean().optional(), - dark_mode: z.boolean().optional(), - monochrome: z.boolean().optional(), + cosmos_send: z.number().optional(), + ibc_transfer: z.number().optional(), }) .strict() .optional(), }) - .strict() - .and(z.union([z.any(), z.any()])), - ) - .optional(), - logo_URIs: z - .object({ - png: z - .string() - .regex( - new RegExp( - '^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.png$', - ), - ) - .min(1) - .optional(), - svg: z - .string() - .regex( - new RegExp( - '^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.svg$', - ), - ) - .min(1) - .optional(), - }) - .strict() - .optional(), - description: z.string().min(1).max(3000).optional(), - peers: z - .object({ - seeds: z - .array( - z - .object({ - id: z.string().min(1), - address: z.string().min(1), - provider: z.string().min(1).optional(), - }) - .strict(), - ) - .optional(), - persistent_peers: z - .array( - z - .object({ - id: z.string().min(1), - address: z.string().min(1), - provider: z.string().min(1).optional(), - }) - .strict(), - ) - .optional(), - }) - .strict() - .optional(), - apis: z - .object({ - rpc: z - .array( - z - .object({ - address: z.string().min(1), - provider: z.string().min(1).optional(), - archive: z.boolean().default(false), - }) - .strict(), - ) - .optional(), - rest: z - .array( - z - .object({ - address: z.string().min(1), - provider: z.string().min(1).optional(), - archive: z.boolean().default(false), - }) - .strict(), - ) - .optional(), - grpc: z - .array( - z - .object({ - address: z.string().min(1), - provider: z.string().min(1).optional(), - archive: z.boolean().default(false), - }) - .strict(), - ) - .optional(), - wss: z - .array( - z - .object({ - address: z.string().min(1), - provider: z.string().min(1).optional(), - archive: z.boolean().default(false), - }) - .strict(), - ) - .optional(), - 'grpc-web': z - .array( - z - .object({ - address: z.string().min(1), - provider: z.string().min(1).optional(), - archive: z.boolean().default(false), - }) - .strict(), - ) - .optional(), - 'evm-http-jsonrpc': z - .array( - z - .object({ - address: z.string().min(1), - provider: z.string().min(1).optional(), - archive: z.boolean().default(false), - }) - .strict(), - ) - .optional(), - }) - .strict() - .optional(), - explorers: z - .array( - z - .object({ - kind: z.string().min(1).optional(), - url: z.string().min(1).optional(), - tx_page: z.string().min(1).optional(), - account_page: z.string().min(1).optional(), - validator_page: z.string().min(1).optional(), - proposal_page: z.string().min(1).optional(), - block_page: z.string().min(1).optional(), - }) .strict(), - ) - .optional(), - keywords: z.array(z.string().min(1)).optional(), - extra_codecs: z.array(z.enum(['ethermint', 'injective'])).optional(), - }) - .strict() - .and(z.intersection(z.any(), z.any())) - .describe( - 'Cosmos Chain.json is a metadata file that contains information about a cosmos sdk based chain.', - ); - + ), + }) + .strict() + .optional(), + staking: z + .object({ + staking_tokens: z.array(z.object({ denom: z.string().min(1) }).strict()), + lock_duration: z + .object({ + blocks: z + .number() + .describe( + 'The number of blocks for which the staked tokens are locked.', + ) + .optional(), + time: z + .string() + .min(1) + .describe( + 'The approximate time for which the staked tokens are locked.', + ) + .optional(), + }) + .strict() + .optional(), + }) + .strict() + .optional(), + codebase: z + .object({ + git_repo: z.string().url().min(1).optional(), + recommended_version: z.string().min(1).optional(), + compatible_versions: z.array(z.string().min(1)).optional(), + tag: z + .string() + .regex(new RegExp('^[A-Za-z0-9._/@-]+$')) + .min(1) + .describe('Git Upgrade Tag') + .optional(), + language: z + .object({ + type: z.enum(['go', 'rust', 'solidity', 'other']), + version: z + .string() + .regex(new RegExp('^v?\\d+(\\.\\d+){0,2}$')) + .min(1) + .describe("Simple version string (e.g., 'v1.0.0').") + .optional(), + repo: z + .string() + .url() + .min(1) + .describe('URL of the code repository.') + .optional(), + tag: z + .string() + .regex(new RegExp('^[A-Za-z0-9._/@-]+$')) + .min(1) + .describe('Git Upgrade Tag') + .optional(), + }) + .strict() + .optional(), + binaries: z + .object({ + 'linux/amd64': z.string().url().min(1).optional(), + 'linux/arm64': z.string().url().min(1).optional(), + 'darwin/amd64': z.string().url().min(1).optional(), + 'darwin/arm64': z.string().url().min(1).optional(), + 'windows/amd64': z.string().url().min(1).optional(), + 'windows/arm64': z.string().url().min(1).optional(), + }) + .strict() + .optional(), + sdk: z + .object({ + type: z.enum(['cosmos', 'penumbra', 'other']), + version: z + .string() + .regex(new RegExp('^v?\\d+(\\.\\d+){0,2}$')) + .min(1) + .describe("Simple version string (e.g., 'v1.0.0').") + .optional(), + repo: z + .string() + .url() + .min(1) + .describe('URL of the code repository.') + .optional(), + tag: z + .string() + .regex(new RegExp('^[A-Za-z0-9._/@-]+$')) + .min(1) + .describe('Git Upgrade Tag') + .optional(), + }) + .strict() + .optional(), + consensus: z + .object({ + type: z.enum(['tendermint', 'cometbft', 'sei-tendermint']), + version: z + .string() + .regex(new RegExp('^v?\\d+(\\.\\d+){0,2}$')) + .min(1) + .describe("Simple version string (e.g., 'v1.0.0').") + .optional(), + repo: z + .string() + .url() + .min(1) + .describe('URL of the code repository.') + .optional(), + tag: z + .string() + .regex(new RegExp('^[A-Za-z0-9._/@-]+$')) + .min(1) + .describe('Git Upgrade Tag') + .optional(), + }) + .strict() + .optional(), + cosmwasm: z + .object({ + version: z + .string() + .regex(new RegExp('^v?\\d+(\\.\\d+){0,2}$')) + .min(1) + .describe("Simple version string (e.g., 'v1.0.0').") + .optional(), + repo: z + .string() + .url() + .min(1) + .describe('URL of the code repository.') + .optional(), + tag: z + .string() + .regex(new RegExp('^[A-Za-z0-9._/@-]+$')) + .min(1) + .describe('Git Upgrade Tag') + .optional(), + enabled: z.boolean().optional(), + path: z + .string() + .regex(new RegExp('^\\$HOME.*$')) + .min(1) + .describe( + 'Relative path to the cosmwasm directory. ex. $HOME/.juno/data/wasm', + ) + .optional(), + }) + .strict() + .optional(), + ibc: z + .object({ + type: z.enum(['go', 'rust', 'other']), + version: z + .string() + .regex(new RegExp('^v?\\d+(\\.\\d+){0,2}$')) + .min(1) + .describe("Simple version string (e.g., 'v1.0.0').") + .optional(), + repo: z + .string() + .url() + .min(1) + .describe('URL of the code repository.') + .optional(), + tag: z + .string() + .regex(new RegExp('^[A-Za-z0-9._/@-]+$')) + .min(1) + .describe('Git Upgrade Tag') + .optional(), + ics_enabled: z + .array( + z + .enum(['ics20-1', 'ics27-1', 'mauth']) + .describe('IBC app or ICS standard.'), + ) + .describe( + 'List of IBC apps (usually corresponding to a ICS standard) which have been enabled on the network.', + ) + .optional(), + }) + .strict() + .optional(), + genesis: z + .object({ + name: z.string().min(1).optional(), + genesis_url: z.string().url().min(1), + ics_ccv_url: z.string().url().min(1).optional(), + }) + .strict() + .optional(), + }) + .strict() + .optional(), + images: z + .array( + z + .object({ + image_sync: z + .object({ + chain_name: z + .string() + .min(1) + .describe( + "The chain name or platform from which the object resides. E.g., 'cosmoshub', 'ethereum', 'forex', or 'nasdaq'", + ), + base_denom: z + .string() + .min(1) + .describe( + "The base denom of the asset from which the object originates. E.g., when describing ATOM from Cosmos Hub, specify 'uatom', NOT 'atom' nor 'ATOM'; base units are unique per platform.", + ) + .optional(), + }) + .strict() + .describe( + 'The (primary) key used to identify an object within the Chain Registry.', + ) + .optional(), + png: z + .string() + .regex( + new RegExp( + '^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.png$', + ), + ) + .min(1) + .optional(), + svg: z + .string() + .regex( + new RegExp( + '^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.svg$', + ), + ) + .min(1) + .optional(), + theme: z + .object({ + circle: z.boolean().optional(), + dark_mode: z.boolean().optional(), + monochrome: z.boolean().optional(), + }) + .strict() + .optional(), + }) + .strict() + .and(z.union([z.any(), z.any()])), + ) + .optional(), + logo_URIs: z + .object({ + png: z + .string() + .regex( + new RegExp( + '^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.png$', + ), + ) + .min(1) + .optional(), + svg: z + .string() + .regex( + new RegExp( + '^https://raw\\.githubusercontent\\.com/cosmos/chain-registry/master/(|testnets/|_non-cosmos/)[a-z0-9]+/images/.+\\.svg$', + ), + ) + .min(1) + .optional(), + }) + .strict() + .optional(), + description: z.string().min(1).max(3000).optional(), + peers: z + .object({ + seeds: z + .array( + z + .object({ + id: z.string().min(1), + address: z.string().min(1), + provider: z.string().min(1).optional(), + }) + .strict(), + ) + .optional(), + persistent_peers: z + .array( + z + .object({ + id: z.string().min(1), + address: z.string().min(1), + provider: z.string().min(1).optional(), + }) + .strict(), + ) + .optional(), + }) + .strict() + .optional(), + apis: z + .object({ + rpc: z + .array( + z + .object({ + address: z.string().min(1), + provider: z.string().min(1).optional(), + archive: z.boolean().default(false), + }) + .strict(), + ) + .optional(), + rest: z + .array( + z + .object({ + address: z.string().min(1), + provider: z.string().min(1).optional(), + archive: z.boolean().default(false), + }) + .strict(), + ) + .optional(), + grpc: z + .array( + z + .object({ + address: z.string().min(1), + provider: z.string().min(1).optional(), + archive: z.boolean().default(false), + }) + .strict(), + ) + .optional(), + wss: z + .array( + z + .object({ + address: z.string().min(1), + provider: z.string().min(1).optional(), + archive: z.boolean().default(false), + }) + .strict(), + ) + .optional(), + 'grpc-web': z + .array( + z + .object({ + address: z.string().min(1), + provider: z.string().min(1).optional(), + archive: z.boolean().default(false), + }) + .strict(), + ) + .optional(), + 'evm-http-jsonrpc': z + .array( + z + .object({ + address: z.string().min(1), + provider: z.string().min(1).optional(), + archive: z.boolean().default(false), + }) + .strict(), + ) + .optional(), + }) + .strict() + .optional(), + explorers: z + .array( + z + .object({ + kind: z.string().min(1).optional(), + url: z.string().min(1).optional(), + tx_page: z.string().min(1).optional(), + account_page: z.string().min(1).optional(), + validator_page: z.string().min(1).optional(), + proposal_page: z.string().min(1).optional(), + block_page: z.string().min(1).optional(), + }) + .strict(), + ) + .optional(), + keywords: z.array(z.string().min(1)).optional(), + extra_codecs: z.array(z.enum(['ethermint', 'injective'])).optional(), +}); // .strict().and(z.intersection(z.any(), z.any())) is similar to .passthrough() // using this way as it's exactly as generated by the tool diff --git a/typescript/sdk/src/warp/WarpCore.ts b/typescript/sdk/src/warp/WarpCore.ts index 1b9843e4f06..bab39cf7c89 100644 --- a/typescript/sdk/src/warp/WarpCore.ts +++ b/typescript/sdk/src/warp/WarpCore.ts @@ -24,6 +24,7 @@ import { Token } from '../token/Token.js'; import { TokenAmount } from '../token/TokenAmount.js'; import { parseTokenConnectionId } from '../token/TokenConnection.js'; import { + LOCKBOX_STANDARDS, MINT_LIMITED_STANDARDS, TOKEN_COLLATERALIZED_STANDARDS, TOKEN_STANDARD_TO_PROVIDER_TYPE, @@ -535,10 +536,7 @@ export class WarpCore { } async getTokenCollateral(token: IToken): Promise { - if ( - token.standard === TokenStandard.EvmHypXERC20Lockbox || - token.standard === TokenStandard.EvmHypVSXERC20Lockbox - ) { + if (LOCKBOX_STANDARDS.includes(token.standard)) { const adapter = token.getAdapter( this.multiProvider, ) as EvmHypXERC20LockboxAdapter; diff --git a/typescript/tsconfig/CHANGELOG.md b/typescript/tsconfig/CHANGELOG.md index 32f1cb94caf..3fc72ab274d 100644 --- a/typescript/tsconfig/CHANGELOG.md +++ b/typescript/tsconfig/CHANGELOG.md @@ -1,5 +1,19 @@ # @hyperlane-xyz/tsconfig +## 18.2.0 + +## 18.1.0 + +## 18.0.0 + +## 17.0.0 + +## 16.2.0 + +## 16.1.1 + +## 16.1.0 + ## 16.0.0 ## 15.0.0 diff --git a/typescript/tsconfig/package.json b/typescript/tsconfig/package.json index 438da032da4..a8e2373d9c5 100644 --- a/typescript/tsconfig/package.json +++ b/typescript/tsconfig/package.json @@ -1,6 +1,6 @@ { "name": "@hyperlane-xyz/tsconfig", - "version": "16.0.0", + "version": "18.2.0", "description": "Hyperlane TypeScript config", "private": true, "type": "module", diff --git a/typescript/utils/CHANGELOG.md b/typescript/utils/CHANGELOG.md index 56faf28482a..5f30a914f66 100644 --- a/typescript/utils/CHANGELOG.md +++ b/typescript/utils/CHANGELOG.md @@ -1,5 +1,31 @@ # @hyperlane-xyz/utils +## 18.2.0 + +## 18.1.0 + +## 18.0.0 + +### Minor Changes + +- cfc0eb2a7: Add radix explorer type + +## 17.0.0 + +### Major Changes + +- 8c15edc67: Added Radix Protocol Type + +### Patch Changes + +- e0bda316a: Ignore dynamic import in webpack builds + +## 16.2.0 + +## 16.1.1 + +## 16.1.0 + ## 16.0.0 ## 15.0.0 diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 9fe4843f1de..461d2c0d6a2 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -1,10 +1,11 @@ { "name": "@hyperlane-xyz/utils", "description": "General utilities and types for the Hyperlane network", - "version": "16.0.0", + "version": "18.2.0", "dependencies": { "@cosmjs/encoding": "^0.32.4", "@solana/web3.js": "^1.95.4", + "bech32": "^2.0.0", "bignumber.js": "^9.1.1", "ethers": "^5.8.0", "lodash-es": "^4.17.21", diff --git a/typescript/utils/src/addresses.test.ts b/typescript/utils/src/addresses.test.ts index 6e8fdf786bc..a3f753b601f 100644 --- a/typescript/utils/src/addresses.test.ts +++ b/typescript/utils/src/addresses.test.ts @@ -13,6 +13,7 @@ const ETH_NON_ZERO_ADDR = '0x0000000000000000000000000000000000000001'; const COS_ZERO_ADDR = 'cosmos1000'; const COS_NON_ZERO_ADDR = 'neutron1jyyjd3x0jhgswgm6nnctxvzla8ypx50tew3ayxxwkrjfxhvje6kqzvzudq'; +const COSMOS_PREFIX = 'neutron'; const COSMOS_NATIVE_ZERO_ADDR = '0x0000000000000000000000000000000000000000000000000000000000000000'; const COSMOS_NATIVE_NON_ZERO_ADDR = @@ -81,6 +82,7 @@ describe('Address utilities', () => { bytesToProtocolAddress( addressToBytes(COSMOS_NATIVE_NON_ZERO_ADDR), ProtocolType.CosmosNative, + COSMOS_PREFIX, ), ).to.equal(COSMOS_NATIVE_NON_ZERO_ADDR); expect( diff --git a/typescript/utils/src/addresses.ts b/typescript/utils/src/addresses.ts index 5d7af105cfb..02b12485f34 100644 --- a/typescript/utils/src/addresses.ts +++ b/typescript/utils/src/addresses.ts @@ -1,5 +1,6 @@ import { fromBech32, normalizeBech32, toBech32 } from '@cosmjs/encoding'; import { PublicKey } from '@solana/web3.js'; +import { bech32m } from 'bech32'; import { Wallet, utils as ethersUtils } from 'ethers'; import { addAddressPadding, @@ -16,6 +17,8 @@ const EVM_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/; const SEALEVEL_ADDRESS_REGEX = /^[a-zA-Z0-9]{36,44}$/; const COSMOS_NATIVE_ADDRESS_REGEX = /^(0x)?[0-9a-fA-F]{64}$/; const STARKNET_ADDRESS_REGEX = /^(0x)?[0-9a-fA-F]{64}$/; +const RADIX_ADDRESS_REGEX = + /^(account|component)_(rdx|sim|tdx_[\d]_)[a-z0-9]{55}$/; const HEX_BYTES32_REGEX = /^0x[a-fA-F0-9]{64}$/; @@ -33,12 +36,14 @@ const EVM_TX_HASH_REGEX = /^0x([A-Fa-f0-9]{64})$/; const SEALEVEL_TX_HASH_REGEX = /^[a-zA-Z1-9]{88}$/; const COSMOS_TX_HASH_REGEX = /^(0x)?[A-Fa-f0-9]{64}$/; const STARKNET_TX_HASH_REGEX = /^(0x)?[0-9a-fA-F]{64}$/; +const RADIX_TX_HASH_REGEX = /^txid_(rdx|sim|tdx_[\d]_)[a-z0-9]{59}$/; const EVM_ZEROISH_ADDRESS_REGEX = /^(0x)?0*$/; const SEALEVEL_ZEROISH_ADDRESS_REGEX = /^1+$/; const COSMOS_ZEROISH_ADDRESS_REGEX = /^[a-z]{1,10}?1[0]+$/; const COSMOS_NATIVE_ZEROISH_ADDRESS_REGEX = /^(0x)?0*$/; const STARKNET_ZEROISH_ADDRESS_REGEX = /^(0x)?0*$/; +const RADIX_ZEROISH_ADDRESS_REGEX = /^0*$/; export const ZERO_ADDRESS_HEX_32 = '0x0000000000000000000000000000000000000000000000000000000000000000'; @@ -71,6 +76,10 @@ export function isAddressStarknet(address: Address) { return STARKNET_ADDRESS_REGEX.test(address); } +export function isAddressRadix(address: Address) { + return RADIX_ADDRESS_REGEX.test(address); +} + export function getAddressProtocolType(address: Address) { if (!address) return undefined; if (isAddressEvm(address)) { @@ -83,6 +92,8 @@ export function getAddressProtocolType(address: Address) { return ProtocolType.Sealevel; } else if (isAddressStarknet(address)) { return ProtocolType.Starknet; + } else if (isAddressRadix(address)) { + return ProtocolType.Radix; } else { return undefined; } @@ -148,6 +159,15 @@ export function isValidAddressStarknet(address: Address) { } } +export function isValidAddressRadix(address: Address) { + try { + const isValid = address && RADIX_ADDRESS_REGEX.test(address); + return !!isValid; + } catch { + return false; + } +} + export function isValidAddress(address: Address, protocol?: ProtocolType) { return routeAddressUtil( { @@ -156,6 +176,7 @@ export function isValidAddress(address: Address, protocol?: ProtocolType) { [ProtocolType.Cosmos]: isValidAddressCosmos, [ProtocolType.CosmosNative]: isValidAddressCosmos, [ProtocolType.Starknet]: isValidAddressStarknet, + [ProtocolType.Radix]: isValidAddressRadix, }, address, false, @@ -198,6 +219,11 @@ export function normalizeAddressStarknet(address: Address) { return address; } } + +export function normalizeAddressRadix(address: Address) { + return address; +} + export function normalizeAddress(address: Address, protocol?: ProtocolType) { return routeAddressUtil( { @@ -205,6 +231,8 @@ export function normalizeAddress(address: Address, protocol?: ProtocolType) { [ProtocolType.Sealevel]: normalizeAddressSealevel, [ProtocolType.Cosmos]: normalizeAddressCosmos, [ProtocolType.CosmosNative]: normalizeAddressCosmos, + [ProtocolType.Starknet]: normalizeAddressStarknet, + [ProtocolType.Radix]: normalizeAddressRadix, }, address, address, @@ -228,6 +256,10 @@ export function eqAddressStarknet(a1: Address, a2: Address) { return normalizeAddressStarknet(a1) === normalizeAddressStarknet(a2); } +export function eqAddressRadix(a1: Address, a2: Address) { + return normalizeAddressRadix(a1) === normalizeAddressRadix(a2); +} + export function eqAddress(a1: Address, a2: Address) { const p1 = getAddressProtocolType(a1); const p2 = getAddressProtocolType(a2); @@ -239,6 +271,7 @@ export function eqAddress(a1: Address, a2: Address) { [ProtocolType.Cosmos]: (_a1) => eqAddressCosmos(_a1, a2), [ProtocolType.CosmosNative]: (_a1) => eqAddressCosmos(_a1, a2), [ProtocolType.Starknet]: (_a1) => eqAddressStarknet(_a1, a2), + [ProtocolType.Radix]: (_a1) => eqAddressRadix(_a1, a2), }, a1, false, @@ -262,6 +295,10 @@ export function isValidTransactionHashStarknet(input: string) { return STARKNET_TX_HASH_REGEX.test(input); } +export function isValidTransactionHashRadix(input: string) { + return RADIX_TX_HASH_REGEX.test(input); +} + export function isValidTransactionHash(input: string, protocol: ProtocolType) { if (protocol === ProtocolType.Ethereum) { return isValidTransactionHashEvm(input); @@ -273,6 +310,8 @@ export function isValidTransactionHash(input: string, protocol: ProtocolType) { return isValidTransactionHashCosmos(input); } else if (protocol === ProtocolType.Starknet) { return isValidTransactionHashStarknet(input); + } else if (protocol === ProtocolType.Radix) { + return isValidTransactionHashRadix(input); } else { return false; } @@ -284,7 +323,8 @@ export function isZeroishAddress(address: Address) { SEALEVEL_ZEROISH_ADDRESS_REGEX.test(address) || COSMOS_ZEROISH_ADDRESS_REGEX.test(address) || COSMOS_NATIVE_ZEROISH_ADDRESS_REGEX.test(address) || - STARKNET_ZEROISH_ADDRESS_REGEX.test(address) + STARKNET_ZEROISH_ADDRESS_REGEX.test(address) || + RADIX_ZEROISH_ADDRESS_REGEX.test(address) ); } @@ -338,6 +378,21 @@ export function addressToBytesStarknet(address: Address): Uint8Array { return num.hexToBytes(normalizedAddress); } +export function addressToBytesRadix(address: Address): Uint8Array { + let byteArray = new Uint8Array( + bech32m.fromWords(bech32m.decode(address).words), + ); + + // Ensure the byte array is 32 bytes long, padding from the left if necessary + if (byteArray.length < 32) { + const paddedArray = new Uint8Array(32); + paddedArray.set(byteArray, 32 - byteArray.length); + byteArray = paddedArray; + } + + return byteArray; +} + export function addressToBytes( address: Address, protocol?: ProtocolType, @@ -349,6 +404,7 @@ export function addressToBytes( [ProtocolType.Cosmos]: addressToBytesCosmos, [ProtocolType.CosmosNative]: addressToBytesCosmosNative, [ProtocolType.Starknet]: addressToBytesStarknet, + [ProtocolType.Radix]: addressToBytesRadix, }, address, new Uint8Array(), @@ -417,8 +473,26 @@ export function bytesToAddressCosmos( return toBech32(prefix, bytes); } -export function bytesToAddressCosmosNative(bytes: Uint8Array): Address { - return ensure0x(Buffer.from(bytes).toString('hex')); +export function bytesToAddressCosmosNative( + bytes: Uint8Array, + prefix: string, +): Address { + if (!prefix) throw new Error('Prefix required for Cosmos Native address'); + + // if the bytes are of length 32 we have to check if the bytes are a cosmos + // native account address or an ID from the hyperlane cosmos module. A cosmos + // native account address is padded with 12 bytes in front. + if (bytes.length === 32) { + if (bytes.slice(0, 12).every((b) => !b)) { + // since the first 12 bytes are empty we know it is an account address + return toBech32(prefix, bytes.slice(12)); + } + // else it is an ID from the hyperlane cosmos module and we just need + // to represent the bytes in hex + return ensure0x(Buffer.from(bytes).toString('hex')); + } + + return toBech32(prefix, bytes); } export function bytesToAddressStarknet(bytes: Uint8Array): Address { @@ -426,6 +500,24 @@ export function bytesToAddressStarknet(bytes: Uint8Array): Address { return addAddressPadding(hexString); } +export function bytesToAddressRadix( + bytes: Uint8Array, + prefix: string, +): Address { + if (!prefix) throw new Error('Prefix required for Radix address'); + // If the bytes array is larger than or equal to 30 bytes, take the last 30 bytes + // Otherwise, pad with zeros from the left up to 30 bytes + if (bytes.length >= 30) { + bytes = bytes.slice(bytes.length - 30); + } else { + const paddedBytes = new Uint8Array(30); + paddedBytes.set(bytes, 30 - bytes.length); + bytes = paddedBytes; + } + + return bech32m.encode(prefix, bech32m.toWords(bytes)); +} + export function bytesToProtocolAddress( bytes: Uint8Array, toProtocol: ProtocolType, @@ -442,9 +534,11 @@ export function bytesToProtocolAddress( } else if (toProtocol === ProtocolType.Cosmos) { return bytesToAddressCosmos(bytes, prefix!); } else if (toProtocol === ProtocolType.CosmosNative) { - return bytesToAddressCosmosNative(bytes); + return bytesToAddressCosmosNative(bytes, prefix!); } else if (toProtocol === ProtocolType.Starknet) { return bytesToAddressStarknet(bytes); + } else if (toProtocol === ProtocolType.Radix) { + return bytesToAddressRadix(bytes, prefix!); } else { throw new Error(`Unsupported protocol for address ${toProtocol}`); } @@ -478,3 +572,16 @@ export function isPrivateKeyEvm(privateKey: string): boolean { throw new Error('Provided Private Key is not EVM compatible!'); } } + +export function hexToRadixCustomPrefix( + hex: string, + module: string, + prefix?: string, + length = 32, +) { + let bytes = addressToBytes(hex); + bytes = bytes.slice(bytes.length - length); + prefix = prefix || 'account_rdx'; + prefix = prefix.replace('account', module); + return bech32m.encode(prefix, bech32m.toWords(bytes)); +} diff --git a/typescript/utils/src/index.ts b/typescript/utils/src/index.ts index 0852a2a4d17..2c11b799b7c 100644 --- a/typescript/utils/src/index.ts +++ b/typescript/utils/src/index.ts @@ -1,4 +1,5 @@ export { + hexToRadixCustomPrefix, addressToByteHexString, addressToBytes, addressToBytes32, @@ -6,11 +7,13 @@ export { addressToBytesEvm, addressToBytesSol, addressToBytesStarknet, + addressToBytesRadix, bytes32ToAddress, bytesToAddressCosmos, bytesToAddressEvm, bytesToAddressSol, bytesToAddressStarknet, + bytesToAddressRadix, bytesToProtocolAddress, capitalizeAddress, convertToProtocolAddress, @@ -20,6 +23,7 @@ export { eqAddressEvm, eqAddressSol, eqAddressStarknet, + eqAddressRadix, getAddressProtocolType, isAddress, isAddressCosmos, @@ -27,23 +31,27 @@ export { isAddressEvm, isAddressSealevel, isAddressStarknet, + isAddressRadix, isValidAddress, isValidAddressCosmos, isValidAddressEvm, isValidAddressSealevel, isValidAddressStarknet, + isValidAddressRadix, isPrivateKeyEvm, isValidTransactionHash, isValidTransactionHashCosmos, isValidTransactionHashEvm, isValidTransactionHashSealevel, isValidTransactionHashStarknet, + isValidTransactionHashRadix, isZeroishAddress, normalizeAddress, normalizeAddressCosmos, normalizeAddressEvm, normalizeAddressSealevel, normalizeAddressStarknet, + normalizeAddressRadix, padBytesToLength, shortenAddress, strip0x, diff --git a/typescript/utils/src/logging.ts b/typescript/utils/src/logging.ts index ee7d2fedc49..179c815a948 100644 --- a/typescript/utils/src/logging.ts +++ b/typescript/utils/src/logging.ts @@ -142,7 +142,7 @@ export async function tryInitializeGcpLogger(options?: { try { const { createGcpLoggingPinoConfig } = await import( - '@google-cloud/pino-logging-gcp-config' + /* webpackIgnore: true */ '@google-cloud/pino-logging-gcp-config' ); const serviceContext = options ? { diff --git a/typescript/utils/src/types.ts b/typescript/utils/src/types.ts index 39b0cec8746..92ef0dc1cb3 100644 --- a/typescript/utils/src/types.ts +++ b/typescript/utils/src/types.ts @@ -7,6 +7,7 @@ export enum ProtocolType { Cosmos = 'cosmos', CosmosNative = 'cosmosnative', Starknet = 'starknet', + Radix = 'radix', } // A type that also allows for literal values of the enum export type ProtocolTypeValue = `${ProtocolType}`; @@ -17,6 +18,7 @@ export const ProtocolSmallestUnit = { [ProtocolType.Cosmos]: 'uATOM', [ProtocolType.CosmosNative]: 'uATOM', [ProtocolType.Starknet]: 'fri', + [ProtocolType.Radix]: 'attos', }; /********* BASIC TYPES *********/ diff --git a/typescript/widgets/CHANGELOG.md b/typescript/widgets/CHANGELOG.md index a83723764f3..da345634c6d 100644 --- a/typescript/widgets/CHANGELOG.md +++ b/typescript/widgets/CHANGELOG.md @@ -1,5 +1,95 @@ # @hyperlane-xyz/widgets +## 18.2.0 + +### Patch Changes + +- Updated dependencies [fed6906e4] +- Updated dependencies [dfa9d368c] +- Updated dependencies [ca64e73cd] +- Updated dependencies [dfa9d368c] + - @hyperlane-xyz/sdk@18.2.0 + - @hyperlane-xyz/cosmos-sdk@18.2.0 + - @hyperlane-xyz/utils@18.2.0 + +## 18.1.0 + +### Patch Changes + +- 73be9b8d2: Don't use radix-engine-toolkit for frontend application usage. +- Updated dependencies [73be9b8d2] + - @hyperlane-xyz/sdk@18.1.0 + - @hyperlane-xyz/cosmos-sdk@18.1.0 + - @hyperlane-xyz/utils@18.1.0 + +## 18.0.0 + +### Patch Changes + +- Updated dependencies [552b253b9] +- Updated dependencies [ba832828f] +- Updated dependencies [cfc0eb2a7] + - @hyperlane-xyz/sdk@18.0.0 + - @hyperlane-xyz/utils@18.0.0 + - @hyperlane-xyz/cosmos-sdk@18.0.0 + +## 17.0.0 + +### Major Changes + +- 8c15edc67: Added Radix Protocol Type + +### Patch Changes + +- Updated dependencies [400c02460] +- Updated dependencies [8c15edc67] +- Updated dependencies [76a5db49a] +- Updated dependencies [6583df016] +- Updated dependencies [e0bda316a] +- Updated dependencies [7f542b288] + - @hyperlane-xyz/sdk@17.0.0 + - @hyperlane-xyz/utils@17.0.0 + - @hyperlane-xyz/cosmos-sdk@17.0.0 + +## 16.2.0 + +### Minor Changes + +- a89018a3f: Include lockbox check for `useEthereumWatchAsset` + +### Patch Changes + +- Updated dependencies [22ceaa109] +- Updated dependencies [ce4974214] +- Updated dependencies [a89018a3f] + - @hyperlane-xyz/sdk@16.2.0 + - @hyperlane-xyz/cosmos-sdk@16.2.0 + - @hyperlane-xyz/utils@16.2.0 + +## 16.1.1 + +### Patch Changes + +- Updated dependencies [ea77b6ae4] + - @hyperlane-xyz/sdk@16.1.1 + - @hyperlane-xyz/cosmos-sdk@16.1.1 + - @hyperlane-xyz/utils@16.1.1 + +## 16.1.0 + +### Minor Changes + +- b9753277e: Add useWatchAsset fns and refactor switchNetwork + +### Patch Changes + +- Updated dependencies [2a2c29c39] +- Updated dependencies [e69ac9f62] +- Updated dependencies [d9b8a7551] + - @hyperlane-xyz/sdk@16.1.0 + - @hyperlane-xyz/cosmos-sdk@16.1.0 + - @hyperlane-xyz/utils@16.1.0 + ## 16.0.0 ### Patch Changes diff --git a/typescript/widgets/package.json b/typescript/widgets/package.json index 6926fa76214..304ab836873 100644 --- a/typescript/widgets/package.json +++ b/typescript/widgets/package.json @@ -1,7 +1,7 @@ { "name": "@hyperlane-xyz/widgets", "description": "Common react components for Hyperlane projects", - "version": "16.0.0", + "version": "18.2.0", "peerDependencies": { "react": "^18", "react-dom": "^18" @@ -10,10 +10,12 @@ "@cosmjs/stargate": "^0.32.4", "@cosmos-kit/react": "^2.18.0", "@headlessui/react": "^2.1.8", - "@hyperlane-xyz/cosmos-sdk": "16.0.0", - "@hyperlane-xyz/sdk": "16.0.0", - "@hyperlane-xyz/utils": "16.0.0", + "@hyperlane-xyz/cosmos-sdk": "18.2.0", + "@hyperlane-xyz/sdk": "18.2.0", + "@hyperlane-xyz/utils": "18.2.0", "@interchain-ui/react": "^1.23.28", + "@radixdlt/babylon-gateway-api-sdk": "^1.10.1", + "@radixdlt/radix-dapp-toolkit": "^2.2.1", "@rainbow-me/rainbowkit": "^2.2.0", "@solana/wallet-adapter-react": "^0.15.32", "@solana/wallet-adapter-react-ui": "^0.9.31", diff --git a/typescript/widgets/src/index.ts b/typescript/widgets/src/index.ts index e61b4957ca8..7f3e0d4c5a8 100644 --- a/typescript/widgets/src/index.ts +++ b/typescript/widgets/src/index.ts @@ -102,6 +102,8 @@ export { useCosmosDisconnectFn, useCosmosTransactionFns, useCosmosWalletDetails, + useCosmosSwitchNetwork, + useCosmosWatchAsset, } from './walletIntegrations/cosmos.js'; export { getWagmiChainConfigs, @@ -111,6 +113,8 @@ export { useEthereumDisconnectFn, useEthereumTransactionFns, useEthereumWalletDetails, + useEthereumSwitchNetwork, + useEthereumWatchAsset, } from './walletIntegrations/ethereum.js'; export { getAccountAddressAndPubKey, @@ -123,6 +127,7 @@ export { useDisconnectFns, useTransactionFns, useWalletDetails, + useWatchAsset, } from './walletIntegrations/multiProtocol.js'; export { MultiProtocolWalletModal } from './walletIntegrations/MultiProtocolWalletModal.js'; export { @@ -132,6 +137,8 @@ export { useSolanaDisconnectFn, useSolanaTransactionFns, useSolanaWalletDetails, + useSolanaSwitchNetwork, + useSolanaWatchAsset, } from './walletIntegrations/solana.js'; export { getStarknetChains, @@ -141,6 +148,8 @@ export { useStarknetDisconnectFn, useStarknetTransactionFns, useStarknetWalletDetails, + useStarknetSwitchNetwork, + useStarknetWatchAsset, } from './walletIntegrations/starknet.js'; export type { AccountInfo, @@ -149,7 +158,10 @@ export type { ChainTransactionFns, SendTransactionFn, SwitchNetworkFn, + SwitchNetworkFns, WalletDetails, + SendMultiTransactionFn, + WatchAssetFns, } from './walletIntegrations/types.js'; export { ethers5TxToWagmiTx, @@ -157,3 +169,9 @@ export { getChainsForProtocol, } from './walletIntegrations/utils.js'; export { WalletLogo } from './walletIntegrations/WalletLogo.js'; +export { + RdtProvider, + GatewayApiProvider, + PopupProvider, +} from './walletIntegrations/radix/RadixProviders.js'; +export { AccountProvider } from './walletIntegrations/radix/AccountContext.js'; diff --git a/typescript/widgets/src/logos/Radix.tsx b/typescript/widgets/src/logos/Radix.tsx new file mode 100644 index 00000000000..aa309f31e8f --- /dev/null +++ b/typescript/widgets/src/logos/Radix.tsx @@ -0,0 +1,20 @@ +import React, { SVGProps, memo } from 'react'; + +function _RadixLogo(props: SVGProps) { + return ( + + + + + ); +} + +export const RadixLogo = memo(_RadixLogo); diff --git a/typescript/widgets/src/logos/protocols.ts b/typescript/widgets/src/logos/protocols.ts index e6bafadd0ac..aa7fab91ca7 100644 --- a/typescript/widgets/src/logos/protocols.ts +++ b/typescript/widgets/src/logos/protocols.ts @@ -4,6 +4,7 @@ import { ProtocolType } from '@hyperlane-xyz/utils'; import { CosmosLogo } from './Cosmos.js'; import { EthereumLogo } from './Ethereum.js'; +import { RadixLogo } from './Radix.js'; import { SolanaLogo } from './Solana.js'; import { StarknetLogo } from './Starknet.js'; @@ -16,4 +17,5 @@ export const PROTOCOL_TO_LOGO: Record< [ProtocolType.Cosmos]: CosmosLogo, [ProtocolType.CosmosNative]: CosmosLogo, [ProtocolType.Starknet]: StarknetLogo, + [ProtocolType.Radix]: RadixLogo, }; diff --git a/typescript/widgets/src/stories/ChainSearchMenu.stories.tsx b/typescript/widgets/src/stories/ChainSearchMenu.stories.tsx index 7a3e1a4b397..f4f3cabc94c 100644 --- a/typescript/widgets/src/stories/ChainSearchMenu.stories.tsx +++ b/typescript/widgets/src/stories/ChainSearchMenu.stories.tsx @@ -26,12 +26,12 @@ export const DefaultChainSearch = { export const WithCustomField = { args: { - chainMetadata: pick(chainMetadata, ['alfajores', 'arbitrum', 'ethereum']), + chainMetadata: pick(chainMetadata, ['sepolia', 'arbitrum', 'ethereum']), onChangeOverrideMetadata: () => {}, customListItemField: { header: 'Warp Routes', data: { - alfajores: { display: '1 token', sortValue: 1 }, + sepolia: { display: '1 token', sortValue: 1 }, arbitrum: { display: '2 tokens', sortValue: 2 }, ethereum: { display: '1 token', sortValue: 1 }, }, @@ -42,7 +42,7 @@ export const WithCustomField = { export const WithCustomFieldAsNull = { args: { - chainMetadata: pick(chainMetadata, ['alfajores', 'arbitrum', 'ethereum']), + chainMetadata: pick(chainMetadata, ['sepolia', 'arbitrum', 'ethereum']), onChangeOverrideMetadata: () => {}, customListItemField: null, showAddChainButton: true, @@ -60,13 +60,13 @@ export const WithDefaultSortField = { export const WithDefaultSortFieldAsCustom = { args: { - chainMetadata: pick(chainMetadata, ['alfajores', 'arbitrum', 'ethereum']), + chainMetadata: pick(chainMetadata, ['sepolia', 'arbitrum', 'ethereum']), onChangeOverrideMetadata: () => {}, showAddChainButton: true, customListItemField: { header: 'Warp Routes', data: { - alfajores: { display: '1 token', sortValue: 1 }, + sepolia: { display: '1 token', sortValue: 1 }, arbitrum: { display: '2 tokens', sortValue: 2 }, ethereum: { display: '1 token', sortValue: 1 }, }, @@ -77,7 +77,7 @@ export const WithDefaultSortFieldAsCustom = { export const WithOverrideChain = { args: { - chainMetadata: pick(chainMetadata, ['alfajores']), + chainMetadata: pick(chainMetadata, ['sepolia']), overrideChainMetadata: { arbitrum: { ...chainMetadata['arbitrum'], displayName: 'Fake Arb' }, }, @@ -88,7 +88,7 @@ export const WithOverrideChain = { export const WithDisabledChains = { args: { - chainMetadata: pick(chainMetadata, ['alfajores', 'base']), + chainMetadata: pick(chainMetadata, ['sepolia', 'base']), overrideChainMetadata: { arbitrum: { ...chainMetadata['arbitrum'], @@ -111,7 +111,7 @@ export const WithDisabledChains = { customListItemField: { header: 'Warp Routes', data: { - alfajores: { display: '1 token', sortValue: 1 }, + sepolia: { display: '1 token', sortValue: 1 }, arbitrum: { display: '2 tokens', sortValue: 2 }, ethereum: { display: '1 token', sortValue: 1 }, base: { display: '2 tokens', sortValue: 2 }, diff --git a/typescript/widgets/src/styles.css b/typescript/widgets/src/styles.css index c9765379036..d8311f42b01 100755 --- a/typescript/widgets/src/styles.css +++ b/typescript/widgets/src/styles.css @@ -14,3 +14,39 @@ scrollbar-width: none; /* Firefox */ } } + +.RadixWalletPopupOverlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: #00000078; + z-index: 999; +} + +.RadixWalletPopup { + position: fixed; + + top: 50vh; + left: 50vw; + transform: translate(-50%, -50%); + + width: 500px; + padding: 20px; + + border: 1px solid black; + border-radius: 1em; + box-shadow: -1px 5px 5px black; + + background-color: white; + + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + + color: #011627; + + z-index: 1000; +} diff --git a/typescript/widgets/src/walletIntegrations/MultiProtocolWalletModal.tsx b/typescript/widgets/src/walletIntegrations/MultiProtocolWalletModal.tsx index 5b28363ce6a..395f0c961dc 100644 --- a/typescript/widgets/src/walletIntegrations/MultiProtocolWalletModal.tsx +++ b/typescript/widgets/src/walletIntegrations/MultiProtocolWalletModal.tsx @@ -66,6 +66,15 @@ export function MultiProtocolWalletModal({ Starknet )} + {includesProtocol(ProtocolType.Radix) && ( + + Radix + + )} ); diff --git a/typescript/widgets/src/walletIntegrations/cosmos.ts b/typescript/widgets/src/walletIntegrations/cosmos.ts index 5b71fc27d6d..9bccb968284 100644 --- a/typescript/widgets/src/walletIntegrations/cosmos.ts +++ b/typescript/widgets/src/walletIntegrations/cosmos.ts @@ -13,6 +13,7 @@ import { cosmoshub } from '@hyperlane-xyz/registry'; import { ChainMetadata, ChainName, + IToken, MultiProtocolProvider, ProviderType, TypedTransactionReceipt, @@ -28,7 +29,9 @@ import { ActiveChainInfo, ChainAddress, ChainTransactionFns, + SwitchNetworkFns, WalletDetails, + WatchAssetFns, } from './types.js'; import { getChainsForProtocol } from './utils.js'; @@ -100,12 +103,9 @@ export function useCosmosActiveChain( return useMemo(() => ({}) as ActiveChainInfo, []); } -export function useCosmosTransactionFns( +export function useCosmosSwitchNetwork( multiProvider: MultiProtocolProvider, -): ChainTransactionFns { - const cosmosChains = getCosmosChainNames(multiProvider); - const chainToContext = useChains(cosmosChains); - +): SwitchNetworkFns { const onSwitchNetwork = useCallback( async (chainName: ChainName) => { const displayName = @@ -118,6 +118,29 @@ export function useCosmosTransactionFns( [multiProvider], ); + return { switchNetwork: onSwitchNetwork }; +} + +export function useCosmosWatchAsset( + _multiProvider: MultiProtocolProvider, +): WatchAssetFns { + const onAddAsset = useCallback( + async (_token: IToken, _activeChainName: ChainName) => { + throw new Error('Watch asset not available for cosmos'); + }, + [], + ); + + return { addAsset: onAddAsset }; +} + +export function useCosmosTransactionFns( + multiProvider: MultiProtocolProvider, +): ChainTransactionFns { + const cosmosChains = getCosmosChainNames(multiProvider); + const chainToContext = useChains(cosmosChains); + const { switchNetwork } = useCosmosSwitchNetwork(multiProvider); + const onSendTx = useCallback( async ({ tx, @@ -133,7 +156,7 @@ export function useCosmosTransactionFns( throw new Error(`Cosmos wallet not connected for ${chainName}`); if (activeChainName && activeChainName !== chainName) - await onSwitchNetwork(chainName); + await switchNetwork(chainName); logger.debug(`Sending tx on chain ${chainName}`); const { @@ -195,7 +218,7 @@ export function useCosmosTransactionFns( }; return { hash: result.transactionHash, confirm }; }, - [onSwitchNetwork, chainToContext], + [switchNetwork, chainToContext], ); const onMultiSendTx = useCallback( @@ -210,13 +233,13 @@ export function useCosmosTransactionFns( }) => { throw new Error('Multi Transactions not supported on Cosmos'); }, - [onSwitchNetwork, multiProvider], + [], ); return { sendTransaction: onSendTx, sendMultiTransaction: onMultiSendTx, - switchNetwork: onSwitchNetwork, + switchNetwork, }; } diff --git a/typescript/widgets/src/walletIntegrations/ethereum.ts b/typescript/widgets/src/walletIntegrations/ethereum.ts index b78fcb14113..58a4dd9ab5f 100644 --- a/typescript/widgets/src/walletIntegrations/ethereum.ts +++ b/typescript/widgets/src/walletIntegrations/ethereum.ts @@ -4,6 +4,7 @@ import { sendTransaction, switchChain, waitForTransactionReceipt, + watchAsset, } from '@wagmi/core'; import { useCallback, useMemo } from 'react'; import { Chain as ViemChain } from 'viem'; @@ -11,6 +12,9 @@ import { useAccount, useConfig, useDisconnect } from 'wagmi'; import { ChainName, + EvmHypXERC20LockboxAdapter, + IToken, + LOCKBOX_STANDARDS, MultiProtocolProvider, ProviderType, TypedTransactionReceipt, @@ -25,7 +29,9 @@ import { AccountInfo, ActiveChainInfo, ChainTransactionFns, + SwitchNetworkFns, WalletDetails, + WatchAssetFns, } from './types.js'; import { ethers5TxToWagmiTx, getChainsForProtocol } from './utils.js'; @@ -86,9 +92,9 @@ export function useEthereumActiveChain( ); } -export function useEthereumTransactionFns( +export function useEthereumSwitchNetwork( multiProvider: MultiProtocolProvider, -): ChainTransactionFns { +): SwitchNetworkFns { const config = useConfig(); const onSwitchNetwork = useCallback( @@ -101,6 +107,54 @@ export function useEthereumTransactionFns( }, [config, multiProvider], ); + + return { switchNetwork: onSwitchNetwork }; +} + +export function useEthereumWatchAsset( + multiProvider: MultiProtocolProvider, +): WatchAssetFns { + const { switchNetwork } = useEthereumSwitchNetwork(multiProvider); + const config = useConfig(); + + const onAddAsset = useCallback( + async (token: IToken, activeChainName: ChainName) => { + const chainName = token.chainName; + // If the active chain is different from tx origin chain, try to switch network first + if (activeChainName && activeChainName !== chainName) + await switchNetwork(chainName); + + let tokenAddress = ''; + if (LOCKBOX_STANDARDS.includes(token.standard)) { + const adapter = token.getAdapter( + multiProvider, + ) as EvmHypXERC20LockboxAdapter; + tokenAddress = await adapter.getWrappedTokenAddress(); + } else { + tokenAddress = token.collateralAddressOrDenom || token.addressOrDenom; + } + + return watchAsset(config, { + type: 'ERC20', + options: { + address: tokenAddress, + decimals: token.decimals, + symbol: token.symbol, + }, + }); + }, + [config, switchNetwork, multiProvider], + ); + + return { addAsset: onAddAsset }; +} + +export function useEthereumTransactionFns( + multiProvider: MultiProtocolProvider, +): ChainTransactionFns { + const config = useConfig(); + const { switchNetwork } = useEthereumSwitchNetwork(multiProvider); + // Note, this doesn't use wagmi's prepare + send pattern because we're potentially sending two transactions // The prepare hooks are recommended to use pre-click downtime to run async calls, but since the flow // may require two serial txs, the prepare hooks aren't useful and complicate hook architecture considerably. @@ -121,7 +175,7 @@ export function useEthereumTransactionFns( // If the active chain is different from tx origin chain, try to switch network first if (activeChainName && activeChainName !== chainName) - await onSwitchNetwork(chainName); + await switchNetwork(chainName); // Since the network switching is not foolproof, we also force a network check here const chainId = multiProvider.getChainMetadata(chainName) @@ -153,7 +207,7 @@ export function useEthereumTransactionFns( return { hash, confirm }; }, - [config, onSwitchNetwork, multiProvider], + [config, switchNetwork, multiProvider], ); const onMultiSendTx = useCallback( @@ -168,13 +222,13 @@ export function useEthereumTransactionFns( }) => { throw new Error('Multi Transactions not supported on EVM'); }, - [config, onSwitchNetwork, multiProvider], + [], ); return { sendTransaction: onSendTx, sendMultiTransaction: onMultiSendTx, - switchNetwork: onSwitchNetwork, + switchNetwork, }; } diff --git a/typescript/widgets/src/walletIntegrations/multiProtocol.tsx b/typescript/widgets/src/walletIntegrations/multiProtocol.tsx index 71357dbdaa5..10bea1b2a34 100644 --- a/typescript/widgets/src/walletIntegrations/multiProtocol.tsx +++ b/typescript/widgets/src/walletIntegrations/multiProtocol.tsx @@ -13,6 +13,7 @@ import { useCosmosDisconnectFn, useCosmosTransactionFns, useCosmosWalletDetails, + useCosmosWatchAsset, } from './cosmos.js'; import { useEthereumAccount, @@ -21,7 +22,17 @@ import { useEthereumDisconnectFn, useEthereumTransactionFns, useEthereumWalletDetails, + useEthereumWatchAsset, } from './ethereum.js'; +import { + useRadixAccount, + useRadixActiveChain, + useRadixConnectFn, + useRadixDisconnectFn, + useRadixTransactionFns, + useRadixWalletDetails, + useRadixWatchAsset, +} from './radix.js'; import { useSolanaAccount, useSolanaActiveChain, @@ -29,6 +40,7 @@ import { useSolanaDisconnectFn, useSolanaTransactionFns, useSolanaWalletDetails, + useSolanaWatchAsset, } from './solana.js'; import { useStarknetAccount, @@ -37,12 +49,14 @@ import { useStarknetDisconnectFn, useStarknetTransactionFns, useStarknetWalletDetails, + useStarknetWatchAsset, } from './starknet.js'; import { AccountInfo, ActiveChainInfo, ChainTransactionFns, WalletDetails, + WatchAssetFns, } from './types.js'; const logger = widgetLogger.child({ @@ -60,6 +74,7 @@ export function useAccounts( const solAccountInfo = useSolanaAccount(multiProvider); const cosmAccountInfo = useCosmosAccount(multiProvider); const starknetAccountInfo = useStarknetAccount(multiProvider); + const radixAccountInfo = useRadixAccount(multiProvider); // Filtered ready accounts const readyAccounts = useMemo( () => @@ -68,8 +83,15 @@ export function useAccounts( solAccountInfo, cosmAccountInfo, starknetAccountInfo, + radixAccountInfo, ].filter((a) => a.isReady), - [evmAccountInfo, solAccountInfo, cosmAccountInfo, starknetAccountInfo], + [ + evmAccountInfo, + solAccountInfo, + cosmAccountInfo, + starknetAccountInfo, + radixAccountInfo, + ], ); // Check if any of the ready accounts are blacklisted @@ -89,6 +111,7 @@ export function useAccounts( [ProtocolType.Cosmos]: cosmAccountInfo, [ProtocolType.CosmosNative]: cosmAccountInfo, [ProtocolType.Starknet]: starknetAccountInfo, + [ProtocolType.Radix]: radixAccountInfo, }, readyAccounts, }), @@ -97,6 +120,7 @@ export function useAccounts( solAccountInfo, cosmAccountInfo, starknetAccountInfo, + radixAccountInfo, readyAccounts, ], ); @@ -190,6 +214,7 @@ export function useWalletDetails(): Record { const solWallet = useSolanaWalletDetails(); const cosmosWallet = useCosmosWalletDetails(); const starknetWallet = useStarknetWalletDetails(); + const radixWallet = useRadixWalletDetails(); return useMemo( () => ({ @@ -198,8 +223,9 @@ export function useWalletDetails(): Record { [ProtocolType.Cosmos]: cosmosWallet, [ProtocolType.CosmosNative]: cosmosWallet, [ProtocolType.Starknet]: starknetWallet, + [ProtocolType.Radix]: radixWallet, }), - [evmWallet, solWallet, cosmosWallet, starknetWallet], + [evmWallet, solWallet, cosmosWallet, starknetWallet, radixWallet], ); } @@ -208,6 +234,7 @@ export function useConnectFns(): Record void> { const onConnectSolana = useSolanaConnectFn(); const onConnectCosmos = useCosmosConnectFn(); const onConnectStarknet = useStarknetConnectFn(); + const onConnectRadix = useRadixConnectFn(); return useMemo( () => ({ @@ -216,8 +243,15 @@ export function useConnectFns(): Record void> { [ProtocolType.Cosmos]: onConnectCosmos, [ProtocolType.CosmosNative]: onConnectCosmos, [ProtocolType.Starknet]: onConnectStarknet, + [ProtocolType.Radix]: onConnectRadix, }), - [onConnectEthereum, onConnectSolana, onConnectCosmos, onConnectStarknet], + [ + onConnectEthereum, + onConnectSolana, + onConnectCosmos, + onConnectStarknet, + onConnectRadix, + ], ); } @@ -226,6 +260,7 @@ export function useDisconnectFns(): Record Promise> { const disconnectSol = useSolanaDisconnectFn(); const disconnectCosmos = useCosmosDisconnectFn(); const disconnectStarknet = useStarknetDisconnectFn(); + const disconnectRadix = useRadixDisconnectFn(); const onClickDisconnect = (env: ProtocolType, disconnectFn?: () => Promise | void) => @@ -260,8 +295,18 @@ export function useDisconnectFns(): Record Promise> { ProtocolType.Starknet, disconnectStarknet, ), + [ProtocolType.Radix]: onClickDisconnect( + ProtocolType.Radix, + disconnectRadix, + ), }), - [disconnectEvm, disconnectSol, disconnectCosmos, disconnectStarknet], + [ + disconnectEvm, + disconnectSol, + disconnectCosmos, + disconnectStarknet, + disconnectRadix, + ], ); } @@ -273,13 +318,14 @@ export function useActiveChains(multiProvider: MultiProtocolProvider): { const solChain = useSolanaActiveChain(multiProvider); const cosmChain = useCosmosActiveChain(multiProvider); const starknetChain = useStarknetActiveChain(multiProvider); + const radixChain = useRadixActiveChain(multiProvider); const readyChains = useMemo( () => - [evmChain, solChain, cosmChain, starknetChain].filter( + [evmChain, solChain, cosmChain, starknetChain, radixChain].filter( (c) => !!c.chainDisplayName, ), - [evmChain, solChain, cosmChain, starknetChain], + [evmChain, solChain, cosmChain, starknetChain, radixChain], ); return useMemo( @@ -290,10 +336,11 @@ export function useActiveChains(multiProvider: MultiProtocolProvider): { [ProtocolType.Cosmos]: cosmChain, [ProtocolType.CosmosNative]: cosmChain, [ProtocolType.Starknet]: starknetChain, + [ProtocolType.Radix]: radixChain, }, readyChains, }), - [evmChain, solChain, cosmChain, readyChains, starknetChain], + [evmChain, solChain, cosmChain, readyChains, starknetChain, radixChain], ); } @@ -320,6 +367,11 @@ export function useTransactionFns( sendTransaction: onSendStarknetTx, sendMultiTransaction: onSendMultiStarknetTx, } = useStarknetTransactionFns(multiProvider); + const { + switchNetwork: onSwitchRadixNetwork, + sendTransaction: onSendRadixTx, + sendMultiTransaction: onSendMultiRadixTx, + } = useRadixTransactionFns(multiProvider); return useMemo( () => ({ @@ -348,6 +400,11 @@ export function useTransactionFns( sendMultiTransaction: onSendMultiStarknetTx, switchNetwork: onSwitchStarknetNetwork, }, + [ProtocolType.Radix]: { + sendTransaction: onSendRadixTx, + sendMultiTransaction: onSendMultiRadixTx, + switchNetwork: onSwitchRadixNetwork, + }, }), [ onSendEvmTx, @@ -358,6 +415,48 @@ export function useTransactionFns( onSwitchCosmNetwork, onSendStarknetTx, onSwitchStarknetNetwork, + onSendRadixTx, + onSwitchRadixNetwork, + ], + ); +} + +export function useWatchAsset( + multiProvider: MultiProtocolProvider, +): Record { + const { addAsset: evmAddAsset } = useEthereumWatchAsset(multiProvider); + const { addAsset: solanaAddAsset } = useSolanaWatchAsset(multiProvider); + const { addAsset: cosmosAddAsset } = useCosmosWatchAsset(multiProvider); + const { addAsset: starknetAddAsset } = useStarknetWatchAsset(multiProvider); + const { addAsset: radixAddAsset } = useRadixWatchAsset(multiProvider); + + return useMemo( + () => ({ + [ProtocolType.Ethereum]: { + addAsset: evmAddAsset, + }, + [ProtocolType.Sealevel]: { + addAsset: solanaAddAsset, + }, + [ProtocolType.Cosmos]: { + addAsset: cosmosAddAsset, + }, + [ProtocolType.CosmosNative]: { + addAsset: cosmosAddAsset, + }, + [ProtocolType.Starknet]: { + addAsset: starknetAddAsset, + }, + [ProtocolType.Radix]: { + addAsset: radixAddAsset, + }, + }), + [ + evmAddAsset, + solanaAddAsset, + cosmosAddAsset, + starknetAddAsset, + radixAddAsset, ], ); } diff --git a/typescript/widgets/src/walletIntegrations/radix.ts b/typescript/widgets/src/walletIntegrations/radix.ts new file mode 100644 index 00000000000..34e12e7d5f6 --- /dev/null +++ b/typescript/widgets/src/walletIntegrations/radix.ts @@ -0,0 +1,225 @@ +import { + DataRequestBuilder, + generateRolaChallenge, +} from '@radixdlt/radix-dapp-toolkit'; +import { useCallback, useMemo } from 'react'; + +import { + ChainName, + IToken, + MultiProtocolProvider, + ProviderType, + RadixSDKTransaction, + TypedTransactionReceipt, + WarpTypedTransaction, +} from '@hyperlane-xyz/sdk'; +import { ProtocolType, assert, retryAsync } from '@hyperlane-xyz/utils'; + +import { useAccount } from './radix/AccountContext.js'; +import { usePopup } from './radix/RadixProviders.js'; +import { useGatewayApi } from './radix/hooks/useGatewayApi.js'; +import { useRdt } from './radix/hooks/useRdt.js'; +import { + AccountInfo, + ActiveChainInfo, + ChainTransactionFns, + SwitchNetworkFns, + WalletDetails, + WatchAssetFns, +} from './types.js'; + +export function useRadixAccount( + _multiProvider: MultiProtocolProvider, +): AccountInfo { + const { accounts } = useAccount(); + + return { + protocol: ProtocolType.Radix, + addresses: accounts.map((account) => ({ + address: account.address, + })), + publicKey: undefined, // we don't need the public key for radix + isReady: !!accounts.length, + }; +} + +export function useRadixWalletDetails() { + const name = 'Radix Wallet'; + const logoUrl = + 'https://raw.githubusercontent.com/radixdlt/radix-dapp-toolkit/refs/heads/main/docs/radix-logo.png'; + + return useMemo( + () => ({ + name, + logoUrl, + }), + [name, logoUrl], + ); +} + +export function useRadixConnectFn(): () => void { + const rdt = useRdt(); + assert(rdt, `radix dapp toolkit not defined`); + + const popUp = usePopup(); + assert(popUp, `radix wallet popup not defined`); + + const { setAccounts } = useAccount(); + + rdt.walletApi.provideChallengeGenerator(async () => { + return generateRolaChallenge(); + }); + + const connect = async () => { + popUp.setShowPopUp(true); + + rdt.walletApi.setRequestData( + DataRequestBuilder.accounts().exactly(1).reset(), + ); + const result = await rdt.walletApi.sendRequest(); + if (result.isOk()) { + setAccounts( + result.value.accounts.map((p) => ({ + address: p.address, + })), + ); + } + popUp.setShowPopUp(false); + }; + + return connect; +} + +export function useRadixDisconnectFn(): () => Promise { + const rdt = useRdt(); + assert(rdt, `radix dapp toolkit not defined`); + + const { setAccounts } = useAccount(); + + const safeDisconnect = async () => { + rdt.disconnect(); + setAccounts([]); + }; + + return safeDisconnect; +} + +export function useRadixActiveChain( + _multiProvider: MultiProtocolProvider, +): ActiveChainInfo { + // Radix doesn't has the concept of an active chain + return useMemo(() => ({}) as ActiveChainInfo, []); +} + +export function useRadixSwitchNetwork( + multiProvider: MultiProtocolProvider, +): SwitchNetworkFns { + const onSwitchNetwork = useCallback( + async (chainName: ChainName) => { + const displayName = + multiProvider.getChainMetadata(chainName).displayName || chainName; + // Radix does not have switch capability + throw new Error( + `Radix wallet must be connected to origin chain ${displayName}`, + ); + }, + [multiProvider], + ); + + return { switchNetwork: onSwitchNetwork }; +} + +export function useRadixWatchAsset( + _multiProvider: MultiProtocolProvider, +): WatchAssetFns { + const onAddAsset = useCallback( + async (_token: IToken, _activeChainName: ChainName) => { + throw new Error('Watch asset not available for Radix'); + }, + [], + ); + + return { addAsset: onAddAsset }; +} + +export function useRadixTransactionFns( + multiProvider: MultiProtocolProvider, +): ChainTransactionFns { + const rdt = useRdt(); + const gatewayApi = useGatewayApi(); + const { switchNetwork } = useRadixSwitchNetwork(multiProvider); + + const onSendTx = useCallback( + async ({ + tx, + chainName: _, + activeChainName: __, + }: { + tx: WarpTypedTransaction; + chainName: ChainName; + activeChainName?: ChainName; + }) => { + assert(rdt, `radix dapp toolkit is not defined`); + assert(gatewayApi, `gateway api is not defined`); + + const transaction = tx.transaction as never as RadixSDKTransaction; + const transactionManifest = transaction.manifest as string; + + const transactionResult = await rdt.walletApi.sendTransaction({ + transactionManifest, + version: 1, + }); + + if (transactionResult.isErr()) { + throw transactionResult.error; + } + + const confirm = async (): Promise => { + assert( + transactionResult.isOk(), + `Radix tx failed: ${transactionResult}`, + ); + + const receipt = await retryAsync( + () => + gatewayApi.transaction.getCommittedDetails( + transactionResult.value.transactionIntentHash, + ), + 5, + 5000, + ); + + return { + type: tx.type as ProviderType.Radix, + receipt: { + ...receipt, + transactionHash: transactionResult.value.transactionIntentHash, + }, + }; + }; + return { hash: transactionResult.value.transactionIntentHash, confirm }; + }, + [switchNetwork], + ); + + const onMultiSendTx = useCallback( + async ({ + txs: _, + chainName: __, + activeChainName: ___, + }: { + txs: WarpTypedTransaction[]; + chainName: ChainName; + activeChainName?: ChainName; + }) => { + throw new Error('Multi Transactions not supported on Radix'); + }, + [], + ); + + return { + sendTransaction: onSendTx, + sendMultiTransaction: onMultiSendTx, + switchNetwork, + }; +} diff --git a/typescript/widgets/src/walletIntegrations/radix/AccountContext.tsx b/typescript/widgets/src/walletIntegrations/radix/AccountContext.tsx new file mode 100644 index 00000000000..a3f3c60c0e1 --- /dev/null +++ b/typescript/widgets/src/walletIntegrations/radix/AccountContext.tsx @@ -0,0 +1,40 @@ +import React, { createContext, useContext, useState } from 'react'; + +export type RadixAccount = { + address: string; +}; + +interface AccountContextType { + accounts: RadixAccount[]; + setAccounts: (accounts: RadixAccount[]) => void; + selectedAccount: string; + setSelectedAccount: (account: string) => void; +} + +const AccountContext = createContext({ + accounts: [], + setAccounts: () => {}, + selectedAccount: '', + setSelectedAccount: () => {}, +}); + +export function useAccount() { + return useContext(AccountContext); +} + +export const AccountProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const [accounts, setAccounts] = useState([]); + const [selectedAccount, setSelectedAccount] = useState(''); + + return ( + + {children} + + ); +}; diff --git a/typescript/widgets/src/walletIntegrations/radix/RadixProviders.tsx b/typescript/widgets/src/walletIntegrations/radix/RadixProviders.tsx new file mode 100644 index 00000000000..ca72b3697de --- /dev/null +++ b/typescript/widgets/src/walletIntegrations/radix/RadixProviders.tsx @@ -0,0 +1,68 @@ +import { GatewayApiClient } from '@radixdlt/babylon-gateway-api-sdk'; +import { RadixDappToolkit } from '@radixdlt/radix-dapp-toolkit'; +import React, { useContext, useState } from 'react'; + +import { SpinnerIcon } from '../../icons/Spinner.js'; + +import { GatewayApiContext, PopupContext, RdtContext } from './contexts.js'; + +export const RdtProvider = ({ + value, + children, +}: { + value: RadixDappToolkit | null; + children: React.ReactNode; +}) => {children}; + +export const GatewayApiProvider = ({ + value, + children, +}: { + value: GatewayApiClient | null; + children: React.ReactNode; +}) => ( + + {children} + +); + +export const PopupProvider = ({ children }: { children: React.ReactNode }) => { + const [showPopUp, setShowPopUp] = useState(false); + + return ( + + {showPopUp && ( +
+
+
+ + Login Request Pending +
+
+ Open Your Radix Wallet App to complete the request +
+ +
+
+ )} + {children} +
+ ); +}; + +export const usePopup = () => useContext(PopupContext); diff --git a/typescript/widgets/src/walletIntegrations/radix/contexts.ts b/typescript/widgets/src/walletIntegrations/radix/contexts.ts new file mode 100644 index 00000000000..ef54b08df88 --- /dev/null +++ b/typescript/widgets/src/walletIntegrations/radix/contexts.ts @@ -0,0 +1,10 @@ +import { GatewayApiClient } from '@radixdlt/babylon-gateway-api-sdk'; +import { RadixDappToolkit } from '@radixdlt/radix-dapp-toolkit'; +import { createContext } from 'react'; + +export const GatewayApiContext = createContext(null); +export const RdtContext = createContext(null); +export const PopupContext = createContext<{ + showPopUp: boolean; + setShowPopUp: React.Dispatch>; +} | null>(null); diff --git a/typescript/widgets/src/walletIntegrations/radix/hooks/useGatewayApi.ts b/typescript/widgets/src/walletIntegrations/radix/hooks/useGatewayApi.ts new file mode 100644 index 00000000000..14dbed23c91 --- /dev/null +++ b/typescript/widgets/src/walletIntegrations/radix/hooks/useGatewayApi.ts @@ -0,0 +1,5 @@ +import { useContext } from 'react'; + +import { GatewayApiContext } from '../contexts.js'; + +export const useGatewayApi = () => useContext(GatewayApiContext); diff --git a/typescript/widgets/src/walletIntegrations/radix/hooks/useRdt.ts b/typescript/widgets/src/walletIntegrations/radix/hooks/useRdt.ts new file mode 100644 index 00000000000..46a8b64dd91 --- /dev/null +++ b/typescript/widgets/src/walletIntegrations/radix/hooks/useRdt.ts @@ -0,0 +1,5 @@ +import { useContext } from 'react'; + +import { RdtContext } from '../contexts.js'; + +export const useRdt = () => useContext(RdtContext); diff --git a/typescript/widgets/src/walletIntegrations/solana.ts b/typescript/widgets/src/walletIntegrations/solana.ts index 8dd299aad4b..6198713da95 100644 --- a/typescript/widgets/src/walletIntegrations/solana.ts +++ b/typescript/widgets/src/walletIntegrations/solana.ts @@ -5,6 +5,7 @@ import { useCallback, useMemo } from 'react'; import { ChainName, + IToken, MultiProtocolProvider, ProviderType, TypedTransactionReceipt, @@ -18,7 +19,9 @@ import { AccountInfo, ActiveChainInfo, ChainTransactionFns, + SwitchNetworkFns, WalletDetails, + WatchAssetFns, } from './types.js'; import { findChainByRpcUrl } from './utils.js'; @@ -85,14 +88,32 @@ export function useSolanaActiveChain( }, [connectionEndpoint, multiProvider]); } +export function useSolanaSwitchNetwork(): SwitchNetworkFns { + const onSwitchNetwork = useCallback(async (chainName: ChainName) => { + logger.warn(`Solana wallet must be connected to origin chain ${chainName}`); + }, []); + + return { switchNetwork: onSwitchNetwork }; +} + +export function useSolanaWatchAsset( + _multiProvider: MultiProtocolProvider, +): WatchAssetFns { + const onAddAsset = useCallback( + async (_token: IToken, _activeChainName: ChainName) => { + throw new Error('Watch asset not available for solana'); + }, + [], + ); + + return { addAsset: onAddAsset }; +} + export function useSolanaTransactionFns( multiProvider: MultiProtocolProvider, ): ChainTransactionFns { const { sendTransaction: sendSolTransaction } = useWallet(); - - const onSwitchNetwork = useCallback(async (chainName: ChainName) => { - logger.warn(`Solana wallet must be connected to origin chain ${chainName}`); - }, []); + const { switchNetwork } = useSolanaSwitchNetwork(); const onSendTx = useCallback( async ({ @@ -107,7 +128,7 @@ export function useSolanaTransactionFns( if (tx.type !== ProviderType.SolanaWeb3) throw new Error(`Unsupported tx type: ${tx.type}`); if (activeChainName && activeChainName !== chainName) - await onSwitchNetwork(chainName); + await switchNetwork(chainName); const rpcUrl = multiProvider.getRpcUrl(chainName); const connection = new Connection(rpcUrl, 'confirmed'); const { @@ -131,7 +152,7 @@ export function useSolanaTransactionFns( return { hash: signature, confirm }; }, - [onSwitchNetwork, sendSolTransaction, multiProvider], + [switchNetwork, sendSolTransaction, multiProvider], ); const onMultiSendTx = useCallback( @@ -146,12 +167,12 @@ export function useSolanaTransactionFns( }) => { throw new Error('Multi Transactions not supported on Solana'); }, - [onSwitchNetwork, sendSolTransaction, multiProvider], + [], ); return { sendTransaction: onSendTx, sendMultiTransaction: onMultiSendTx, - switchNetwork: onSwitchNetwork, + switchNetwork, }; } diff --git a/typescript/widgets/src/walletIntegrations/starknet.ts b/typescript/widgets/src/walletIntegrations/starknet.ts index f4e0e7d02fe..39197002536 100644 --- a/typescript/widgets/src/walletIntegrations/starknet.ts +++ b/typescript/widgets/src/walletIntegrations/starknet.ts @@ -13,6 +13,7 @@ import { StarknetkitConnector, useStarknetkitConnectModal } from 'starknetkit'; import { ChainName, + IToken, MultiProtocolProvider, ProviderType, TypedTransactionReceipt, @@ -27,7 +28,9 @@ import { AccountInfo, ActiveChainInfo, ChainTransactionFns, + SwitchNetworkFns, WalletDetails, + WatchAssetFns, } from './types.js'; import { getChainsForProtocol } from './utils.js'; @@ -105,12 +108,9 @@ export function useStarknetActiveChain( ); } -export function useStarknetTransactionFns( +export function useStarknetSwitchNetwork( multiProvider: MultiProtocolProvider, -): ChainTransactionFns { - const { account } = useAccount(); - - const { sendAsync } = useSendTransaction({}); +): SwitchNetworkFns { const { switchChainAsync } = useSwitchChain({}); const onSwitchNetwork = useCallback( @@ -121,14 +121,39 @@ export function useStarknetTransactionFns( chainId: chainId.toString(), }); // Some wallets seem to require a brief pause after switch - await sleep(2000); + await sleep(4000); } catch { - logger.warn('Failed to switch chain'); + // some wallets like braavos do not support chain switching + logger.warn('Failed to switch chain.'); } }, [multiProvider, switchChainAsync], ); + return { switchNetwork: onSwitchNetwork }; +} + +export function useStarknetWatchAsset( + _multiProvider: MultiProtocolProvider, +): WatchAssetFns { + const onAddAsset = useCallback( + async (_token: IToken, _activeChainName: ChainName) => { + throw new Error('Watch asset not available for starknet'); + }, + [], + ); + + return { addAsset: onAddAsset }; +} + +export function useStarknetTransactionFns( + multiProvider: MultiProtocolProvider, +): ChainTransactionFns { + const { account } = useAccount(); + + const { sendAsync } = useSendTransaction({}); + const { switchNetwork } = useStarknetSwitchNetwork(multiProvider); + const onSendTx = useCallback( async ({ tx, @@ -145,7 +170,7 @@ export function useStarknetTransactionFns( activeChainName, }); }, - [account, multiProvider, onSwitchNetwork, sendAsync], + [account, multiProvider, switchNetwork, sendAsync], ); const onMultiSendTx = useCallback( @@ -165,7 +190,7 @@ export function useStarknetTransactionFns( } if (activeChainName && activeChainName !== chainName) { - await onSwitchNetwork(chainName); + await switchNetwork(chainName); } if (!account) { @@ -197,13 +222,13 @@ export function useStarknetTransactionFns( throw error; } }, - [account, multiProvider, onSwitchNetwork, sendAsync], + [account, multiProvider, switchNetwork, sendAsync], ); return { sendTransaction: onSendTx, sendMultiTransaction: onMultiSendTx, - switchNetwork: onSwitchNetwork, + switchNetwork, }; } diff --git a/typescript/widgets/src/walletIntegrations/types.ts b/typescript/widgets/src/walletIntegrations/types.ts index 239b28afe1a..5cc7c199686 100644 --- a/typescript/widgets/src/walletIntegrations/types.ts +++ b/typescript/widgets/src/walletIntegrations/types.ts @@ -1,5 +1,6 @@ import { ChainName, + IToken, TypedTransactionReceipt, WarpTypedTransaction, } from '@hyperlane-xyz/sdk'; @@ -56,3 +57,11 @@ export interface ChainTransactionFns { sendMultiTransaction: SendMultiTransactionFn; switchNetwork?: SwitchNetworkFn; } + +export interface SwitchNetworkFns { + switchNetwork: SwitchNetworkFn; +} + +export interface WatchAssetFns { + addAsset: (token: IToken, activeChainName: ChainName) => Promise; +} diff --git a/yarn.lock b/yarn.lock index b2b15959748..1526d809b4e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8100,10 +8100,10 @@ __metadata: dependencies: "@eth-optimism/sdk": "npm:^3.3.3" "@google-cloud/pino-logging-gcp-config": "npm:^1.0.6" - "@hyperlane-xyz/core": "npm:9.0.2" - "@hyperlane-xyz/sdk": "npm:16.0.0" + "@hyperlane-xyz/core": "npm:9.0.9" + "@hyperlane-xyz/sdk": "npm:18.2.0" "@hyperlane-xyz/tsconfig": "workspace:^" - "@hyperlane-xyz/utils": "npm:16.0.0" + "@hyperlane-xyz/utils": "npm:18.2.0" "@jest/globals": "npm:^29.7.0" "@prisma/client": "npm:^6.8.2" "@types/cors": "npm:^2" @@ -8143,12 +8143,12 @@ __metadata: "@eslint/js": "npm:^9.31.0" "@ethersproject/abi": "npm:*" "@ethersproject/providers": "npm:*" - "@hyperlane-xyz/cosmos-sdk": "npm:16.0.0" - "@hyperlane-xyz/http-registry-server": "npm:16.0.0" + "@hyperlane-xyz/cosmos-sdk": "npm:18.2.0" + "@hyperlane-xyz/http-registry-server": "npm:18.2.0" "@hyperlane-xyz/registry": "npm:20.0.0" - "@hyperlane-xyz/sdk": "npm:16.0.0" + "@hyperlane-xyz/sdk": "npm:18.2.0" "@hyperlane-xyz/tsconfig": "workspace:^" - "@hyperlane-xyz/utils": "npm:16.0.0" + "@hyperlane-xyz/utils": "npm:18.2.0" "@inquirer/core": "npm:9.0.10" "@inquirer/figures": "npm:1.0.5" "@inquirer/prompts": "npm:3.3.2" @@ -8190,7 +8190,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/core@npm:9.0.2, @hyperlane-xyz/core@workspace:solidity": +"@hyperlane-xyz/core@npm:9.0.9, @hyperlane-xyz/core@workspace:solidity": version: 0.0.0-use.local resolution: "@hyperlane-xyz/core@workspace:solidity" dependencies: @@ -8198,7 +8198,7 @@ __metadata: "@chainlink/contracts-ccip": "npm:^1.5.0" "@eth-optimism/contracts": "npm:^0.6.0" "@hyperlane-xyz/tsconfig": "workspace:^" - "@hyperlane-xyz/utils": "npm:16.0.0" + "@hyperlane-xyz/utils": "npm:18.2.0" "@matterlabs/hardhat-zksync-solc": "npm:1.2.5" "@matterlabs/hardhat-zksync-verify": "npm:1.7.1" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" @@ -8235,13 +8235,13 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/cosmos-sdk@npm:16.0.0, @hyperlane-xyz/cosmos-sdk@workspace:typescript/cosmos-sdk": +"@hyperlane-xyz/cosmos-sdk@npm:18.2.0, @hyperlane-xyz/cosmos-sdk@workspace:typescript/cosmos-sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/cosmos-sdk@workspace:typescript/cosmos-sdk" dependencies: "@cosmjs/stargate": "npm:^0.32.4" "@eslint/js": "npm:^9.31.0" - "@hyperlane-xyz/cosmos-types": "npm:16.0.0" + "@hyperlane-xyz/cosmos-types": "npm:18.2.0" "@hyperlane-xyz/tsconfig": "workspace:^" "@types/mocha": "npm:^10.0.1" "@typescript-eslint/eslint-plugin": "npm:^8.1.6" @@ -8258,7 +8258,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/cosmos-types@npm:16.0.0, @hyperlane-xyz/cosmos-types@workspace:typescript/cosmos-types": +"@hyperlane-xyz/cosmos-types@npm:18.2.0, @hyperlane-xyz/cosmos-types@workspace:typescript/cosmos-types": version: 0.0.0-use.local resolution: "@hyperlane-xyz/cosmos-types@workspace:typescript/cosmos-types" dependencies: @@ -8308,14 +8308,14 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/helloworld@npm:16.0.0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": +"@hyperlane-xyz/helloworld@npm:18.2.0, @hyperlane-xyz/helloworld@workspace:typescript/helloworld": version: 0.0.0-use.local resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: "@eslint/js": "npm:^9.31.0" - "@hyperlane-xyz/core": "npm:9.0.2" + "@hyperlane-xyz/core": "npm:9.0.9" "@hyperlane-xyz/registry": "npm:20.0.0" - "@hyperlane-xyz/sdk": "npm:16.0.0" + "@hyperlane-xyz/sdk": "npm:18.2.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@openzeppelin/contracts-upgradeable": "npm:^4.9.3" @@ -8350,14 +8350,14 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/http-registry-server@npm:16.0.0, @hyperlane-xyz/http-registry-server@workspace:typescript/http-registry-server": +"@hyperlane-xyz/http-registry-server@npm:18.2.0, @hyperlane-xyz/http-registry-server@workspace:typescript/http-registry-server": version: 0.0.0-use.local resolution: "@hyperlane-xyz/http-registry-server@workspace:typescript/http-registry-server" dependencies: "@hyperlane-xyz/registry": "npm:20.0.0" - "@hyperlane-xyz/sdk": "npm:16.0.0" + "@hyperlane-xyz/sdk": "npm:18.2.0" "@hyperlane-xyz/tsconfig": "workspace:^" - "@hyperlane-xyz/utils": "npm:16.0.0" + "@hyperlane-xyz/utils": "npm:18.2.0" "@types/chai": "npm:^4.2.21" "@types/chai-as-promised": "npm:^8" "@types/express": "npm:^5.0.3" @@ -8399,11 +8399,11 @@ __metadata: "@ethersproject/providers": "npm:*" "@google-cloud/pino-logging-gcp-config": "npm:^1.0.6" "@google-cloud/secret-manager": "npm:^5.5.0" - "@hyperlane-xyz/helloworld": "npm:16.0.0" + "@hyperlane-xyz/helloworld": "npm:18.2.0" "@hyperlane-xyz/registry": "npm:20.0.0" - "@hyperlane-xyz/sdk": "npm:16.0.0" + "@hyperlane-xyz/sdk": "npm:18.2.0" "@hyperlane-xyz/tsconfig": "workspace:^" - "@hyperlane-xyz/utils": "npm:16.0.0" + "@hyperlane-xyz/utils": "npm:18.2.0" "@inquirer/prompts": "npm:3.3.2" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-etherscan": "npm:^3.0.3" @@ -8435,6 +8435,7 @@ __metadata: prettier: "npm:^3.5.3" prom-client: "npm:^14.0.1" prompts: "npm:^2.4.2" + smol-toml: "npm:1.4.2" tmp: "npm:^0.2.3" tsx: "npm:^4.19.1" typescript: "npm:5.3.3" @@ -8469,6 +8470,32 @@ __metadata: languageName: unknown linkType: soft +"@hyperlane-xyz/radix-sdk@npm:18.2.0, @hyperlane-xyz/radix-sdk@workspace:typescript/radix-sdk": + version: 0.0.0-use.local + resolution: "@hyperlane-xyz/radix-sdk@workspace:typescript/radix-sdk" + dependencies: + "@eslint/js": "npm:^9.31.0" + "@hyperlane-xyz/tsconfig": "workspace:^" + "@hyperlane-xyz/utils": "npm:18.2.0" + "@radixdlt/babylon-core-api-sdk": "npm:^1.3.0" + "@radixdlt/babylon-gateway-api-sdk": "npm:^1.10.1" + "@radixdlt/radix-engine-toolkit": "npm:^1.0.5" + "@types/mocha": "npm:^10.0.1" + "@typescript-eslint/eslint-plugin": "npm:^8.1.6" + "@typescript-eslint/parser": "npm:^8.1.6" + bignumber.js: "npm:^9.1.1" + eslint: "npm:^9.31.0" + eslint-config-prettier: "npm:^10.1.8" + eslint-import-resolver-typescript: "npm:^4.4.4" + eslint-plugin-import: "npm:^2.32.0" + mocha: "npm:^11.5.0" + mocha-steps: "npm:^1.3.0" + prettier: "npm:^3.5.3" + typescript: "npm:5.3.3" + typescript-eslint: "npm:^8.37.0" + languageName: unknown + linkType: soft + "@hyperlane-xyz/registry@npm:20.0.0": version: 20.0.0 resolution: "@hyperlane-xyz/registry@npm:20.0.0" @@ -8480,7 +8507,7 @@ __metadata: languageName: node linkType: hard -"@hyperlane-xyz/sdk@npm:16.0.0, @hyperlane-xyz/sdk@workspace:typescript/sdk": +"@hyperlane-xyz/sdk@npm:18.2.0, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" dependencies: @@ -8488,13 +8515,15 @@ __metadata: "@aws-sdk/client-s3": "npm:^3.577.0" "@chain-registry/types": "npm:^0.50.122" "@cosmjs/cosmwasm-stargate": "npm:^0.32.4" + "@cosmjs/proto-signing": "npm:^0.32.4" "@cosmjs/stargate": "npm:^0.32.4" - "@hyperlane-xyz/core": "npm:9.0.2" - "@hyperlane-xyz/cosmos-sdk": "npm:16.0.0" + "@hyperlane-xyz/core": "npm:9.0.9" + "@hyperlane-xyz/cosmos-sdk": "npm:18.2.0" "@hyperlane-xyz/eslint-config": "workspace:^" - "@hyperlane-xyz/starknet-core": "npm:16.0.0" + "@hyperlane-xyz/radix-sdk": "npm:18.2.0" + "@hyperlane-xyz/starknet-core": "npm:18.2.0" "@hyperlane-xyz/tsconfig": "workspace:^" - "@hyperlane-xyz/utils": "npm:16.0.0" + "@hyperlane-xyz/utils": "npm:18.2.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" "@safe-global/api-kit": "npm:1.3.0" @@ -8538,7 +8567,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/starknet-core@npm:16.0.0, @hyperlane-xyz/starknet-core@workspace:starknet": +"@hyperlane-xyz/starknet-core@npm:18.2.0, @hyperlane-xyz/starknet-core@workspace:starknet": version: 0.0.0-use.local resolution: "@hyperlane-xyz/starknet-core@workspace:starknet" dependencies: @@ -8564,7 +8593,7 @@ __metadata: languageName: unknown linkType: soft -"@hyperlane-xyz/utils@npm:16.0.0, @hyperlane-xyz/utils@workspace:typescript/utils": +"@hyperlane-xyz/utils@npm:18.2.0, @hyperlane-xyz/utils@workspace:typescript/utils": version: 0.0.0-use.local resolution: "@hyperlane-xyz/utils@workspace:typescript/utils" dependencies: @@ -8578,6 +8607,7 @@ __metadata: "@types/sinon-chai": "npm:^3.2.12" "@typescript-eslint/eslint-plugin": "npm:^8.1.6" "@typescript-eslint/parser": "npm:^8.1.6" + bech32: "npm:^2.0.0" bignumber.js: "npm:^9.1.1" chai: "npm:^4.5.0" eslint: "npm:^9.31.0" @@ -8616,12 +8646,14 @@ __metadata: "@emotion/styled": "npm:^11.13.0" "@eslint/js": "npm:^9.31.0" "@headlessui/react": "npm:^2.1.8" - "@hyperlane-xyz/cosmos-sdk": "npm:16.0.0" + "@hyperlane-xyz/cosmos-sdk": "npm:18.2.0" "@hyperlane-xyz/registry": "npm:20.0.0" - "@hyperlane-xyz/sdk": "npm:16.0.0" + "@hyperlane-xyz/sdk": "npm:18.2.0" "@hyperlane-xyz/tsconfig": "workspace:^" - "@hyperlane-xyz/utils": "npm:16.0.0" + "@hyperlane-xyz/utils": "npm:18.2.0" "@interchain-ui/react": "npm:^1.23.28" + "@radixdlt/babylon-gateway-api-sdk": "npm:^1.10.1" + "@radixdlt/radix-dapp-toolkit": "npm:^2.2.1" "@rainbow-me/rainbowkit": "npm:^2.2.0" "@solana/wallet-adapter-react": "npm:^0.15.32" "@solana/wallet-adapter-react-ui": "npm:^0.9.31" @@ -9619,6 +9651,13 @@ __metadata: languageName: node linkType: hard +"@lit-labs/ssr-dom-shim@npm:^1.4.0": + version: 1.4.0 + resolution: "@lit-labs/ssr-dom-shim@npm:1.4.0" + checksum: 10/a592a2d134f6f9c0e40aef2122226114b82d22f3308d375cb28e231342ee1dec8529bfcf283e8c9d80511c5cfc54bb6eaaaecf5f93f9a04d2be9d1663ab54705 + languageName: node + linkType: hard + "@lit/reactive-element@npm:^1.3.0, @lit/reactive-element@npm:^1.6.0": version: 1.6.3 resolution: "@lit/reactive-element@npm:1.6.3" @@ -9628,6 +9667,15 @@ __metadata: languageName: node linkType: hard +"@lit/reactive-element@npm:^2.1.0": + version: 2.1.1 + resolution: "@lit/reactive-element@npm:2.1.1" + dependencies: + "@lit-labs/ssr-dom-shim": "npm:^1.4.0" + checksum: 10/5bd091d8149a8cb07e51ab58e218693204563ab87894528c7639e828aac9eb463b6d68048557c37a43f493cf310fff75ee337c3ced274903879c5e5e335df919 + languageName: node + linkType: hard + "@manypkg/find-root@npm:^1.1.0": version: 1.1.0 resolution: "@manypkg/find-root@npm:1.1.0" @@ -10152,6 +10200,13 @@ __metadata: languageName: node linkType: hard +"@noble/ed25519@npm:2.0.0": + version: 2.0.0 + resolution: "@noble/ed25519@npm:2.0.0" + checksum: 10/6c5c6614e5215c81c5528157c6a3f70be3995db55c6412e60617d917f859c09a0a98f52a7aae6d25cb4bfc480262fdf201598f9d1dd3c1601437dc3499c7ddcd + languageName: node + linkType: hard + "@noble/hashes@npm:1.0.0, @noble/hashes@npm:~1.0.0": version: 1.0.0 resolution: "@noble/hashes@npm:1.0.0" @@ -10159,6 +10214,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:1.3.0": + version: 1.3.0 + resolution: "@noble/hashes@npm:1.3.0" + checksum: 10/4680a71941c06ac897cc9eab9d229717d5af1147cea5e8cd4942190c817426ad3173ded750d897f58d764b869f9347d4fc3f6b3c16574541ac81906efa9ddc36 + languageName: node + linkType: hard + "@noble/hashes@npm:1.3.1": version: 1.3.1 resolution: "@noble/hashes@npm:1.3.1" @@ -11787,6 +11849,57 @@ __metadata: languageName: node linkType: hard +"@radixdlt/babylon-core-api-sdk@npm:^1.3.0": + version: 1.3.0 + resolution: "@radixdlt/babylon-core-api-sdk@npm:1.3.0" + checksum: 10/1a38296483d39fc30246fbbd99710c448f056132c462e0deb1941912e36f9e831c35de635bdc57c92b33bfe9d8a499e2dbf291be31494c22f98fe6388b616fc6 + languageName: node + linkType: hard + +"@radixdlt/babylon-gateway-api-sdk@npm:^1.10.1": + version: 1.10.1 + resolution: "@radixdlt/babylon-gateway-api-sdk@npm:1.10.1" + checksum: 10/9d0c5158784a979f426ec486a18bf9906c8fef21dd2f8d1d0dacad48b0314d6dfb1686a45966c3a987154b23d2cdccfcccb80f2185a46edcfb8d49ccd9e0ccec + languageName: node + linkType: hard + +"@radixdlt/radix-dapp-toolkit@npm:^2.2.1": + version: 2.2.1 + resolution: "@radixdlt/radix-dapp-toolkit@npm:2.2.1" + dependencies: + "@noble/curves": "npm:^1.4.0" + base64url: "npm:^3.0.1" + blakejs: "npm:^1.2.1" + buffer: "npm:^6.0.3" + immer: "npm:^10.0.4" + lit: "npm:^3.1.2" + lit-html: "npm:^3.1.2" + neverthrow: "npm:^8.0.0" + rxjs: "npm:^7.8.1" + tslog: "npm:>=4.8.0" + uuid: "npm:^10.0.0" + valibot: "npm:0.42.1" + checksum: 10/d4d3c3d504291cbbec68dc7b4189281df0818677c51257c8a2f76f6112bd6b86dff051b7a1b35fb172facf5a0374d49c4475b32ae25ad972d5c65d31d6fa1e53 + languageName: node + linkType: hard + +"@radixdlt/radix-engine-toolkit@npm:^1.0.5": + version: 1.0.5 + resolution: "@radixdlt/radix-engine-toolkit@npm:1.0.5" + dependencies: + "@noble/ed25519": "npm:2.0.0" + "@noble/hashes": "npm:1.3.0" + "@types/secp256k1": "npm:4.0.3" + "@types/secure-random": "npm:1.1.0" + blakejs: "npm:1.2.1" + change-case: "npm:4.1.2" + decimal.js: "npm:10.4.3" + reflect-metadata: "npm:0.1.13" + secp256k1: "npm:5.0.0" + checksum: 10/8459ea52b0e8ac88c8695d36e102ef442274156f05565cf88bcd03336dd1969a6fbe037121c80ace4cd6769ac84a6f3e91a4f7a21acfe1c93c609a2fcc0a37f4 + languageName: node + linkType: hard + "@rainbow-me/rainbowkit@npm:^2.2.0": version: 2.2.0 resolution: "@rainbow-me/rainbowkit@npm:2.2.0" @@ -13561,6 +13674,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-x64-gnu@npm:^4.24.0": + version: 4.49.0 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.49.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-musl@npm:4.19.1": version: 4.19.1 resolution: "@rollup/rollup-linux-x64-musl@npm:4.19.1" @@ -17234,7 +17354,7 @@ __metadata: languageName: node linkType: hard -"@types/secp256k1@npm:^4.0.1": +"@types/secp256k1@npm:4.0.3, @types/secp256k1@npm:^4.0.1": version: 4.0.3 resolution: "@types/secp256k1@npm:4.0.3" dependencies: @@ -17243,6 +17363,15 @@ __metadata: languageName: node linkType: hard +"@types/secure-random@npm:1.1.0": + version: 1.1.0 + resolution: "@types/secure-random@npm:1.1.0" + dependencies: + "@types/node": "npm:*" + checksum: 10/ba8a664b2fa3084eac0243f84ee9a31f8bb9a57a4ffcab8f09bbfc62c9abb5ac1d31d496fd8be817fbe73be4e90a0debf7e069c58bb1f7a799f060b4a09eb3ce + languageName: node + linkType: hard + "@types/seedrandom@npm:3.0.1": version: 3.0.1 resolution: "@types/seedrandom@npm:3.0.1" @@ -20066,6 +20195,13 @@ __metadata: languageName: node linkType: hard +"base64url@npm:^3.0.1": + version: 3.0.1 + resolution: "base64url@npm:3.0.1" + checksum: 10/a77b2a3a526b3343e25be424de3ae0aa937d78f6af7c813ef9020ef98001c0f4e2323afcd7d8b2d2978996bf8c42445c3e9f60c218c622593e5fdfd54a3d6e18 + languageName: node + linkType: hard + "bcrypt-pbkdf@npm:^1.0.0, bcrypt-pbkdf@npm:^1.0.2": version: 1.0.2 resolution: "bcrypt-pbkdf@npm:1.0.2" @@ -20082,6 +20218,13 @@ __metadata: languageName: node linkType: hard +"bech32@npm:^2.0.0": + version: 2.0.0 + resolution: "bech32@npm:2.0.0" + checksum: 10/fa15acb270b59aa496734a01f9155677b478987b773bf701f465858bf1606c6a970085babd43d71ce61895f1baa594cb41a2cd1394bd2c6698f03cc2d811300e + languageName: node + linkType: hard + "better-opn@npm:^3.0.2": version: 3.0.2 resolution: "better-opn@npm:3.0.2" @@ -20246,7 +20389,7 @@ __metadata: languageName: node linkType: hard -"blakejs@npm:^1.1.0": +"blakejs@npm:1.2.1, blakejs@npm:^1.1.0, blakejs@npm:^1.2.1": version: 1.2.1 resolution: "blakejs@npm:1.2.1" checksum: 10/0638b1bd058b21892633929c43005aa6a4cc4b2ac5b338a146c3c076622f1b360795bd7a4d1f077c9b01863ed2df0c1504a81c5b520d164179120434847e6cd7 @@ -20878,6 +21021,16 @@ __metadata: languageName: node linkType: hard +"camel-case@npm:^4.1.2": + version: 4.1.2 + resolution: "camel-case@npm:4.1.2" + dependencies: + pascal-case: "npm:^3.1.2" + tslib: "npm:^2.0.3" + checksum: 10/bcbd25cd253b3cbc69be3f535750137dbf2beb70f093bdc575f73f800acc8443d34fd52ab8f0a2413c34f1e8203139ffc88428d8863e4dfe530cfb257a379ad6 + languageName: node + linkType: hard + "camelcase-css@npm:^2.0.1": version: 2.0.1 resolution: "camelcase-css@npm:2.0.1" @@ -20924,6 +21077,17 @@ __metadata: languageName: node linkType: hard +"capital-case@npm:^1.0.4": + version: 1.0.4 + resolution: "capital-case@npm:1.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + upper-case-first: "npm:^2.0.2" + checksum: 10/41fa8fa87f6d24d0835a2b4a9341a3eaecb64ac29cd7c5391f35d6175a0fa98ab044e7f2602e1ec3afc886231462ed71b5b80c590b8b41af903ec2c15e5c5931 + languageName: node + linkType: hard + "capnp-ts@npm:^0.7.0": version: 0.7.0 resolution: "capnp-ts@npm:0.7.0" @@ -21102,6 +21266,26 @@ __metadata: languageName: node linkType: hard +"change-case@npm:4.1.2": + version: 4.1.2 + resolution: "change-case@npm:4.1.2" + dependencies: + camel-case: "npm:^4.1.2" + capital-case: "npm:^1.0.4" + constant-case: "npm:^3.0.4" + dot-case: "npm:^3.0.4" + header-case: "npm:^2.0.4" + no-case: "npm:^3.0.4" + param-case: "npm:^3.0.4" + pascal-case: "npm:^3.1.2" + path-case: "npm:^3.0.4" + sentence-case: "npm:^3.0.4" + snake-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/e4bc4a093a1f7cce8b33896665cf9e456e3bc3cc0def2ad7691b1994cfca99b3188d0a513b16855b01a6bd20692fcde12a7d4d87a5615c4c515bbbf0e651f116 + languageName: node + linkType: hard + "char-regex@npm:^1.0.2": version: 1.0.2 resolution: "char-regex@npm:1.0.2" @@ -21784,6 +21968,17 @@ __metadata: languageName: node linkType: hard +"constant-case@npm:^3.0.4": + version: 3.0.4 + resolution: "constant-case@npm:3.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + upper-case: "npm:^2.0.2" + checksum: 10/6c3346d51afc28d9fae922e966c68eb77a19d94858dba230dd92d7b918b37d36db50f0311e9ecf6847e43e934b1c01406a0936973376ab17ec2c471fbcfb2cf3 + languageName: node + linkType: hard + "content-disposition@npm:0.5.4": version: 0.5.4 resolution: "content-disposition@npm:0.5.4" @@ -22451,6 +22646,13 @@ __metadata: languageName: node linkType: hard +"decimal.js@npm:10.4.3": + version: 10.4.3 + resolution: "decimal.js@npm:10.4.3" + checksum: 10/de663a7bc4d368e3877db95fcd5c87b965569b58d16cdc4258c063d231ca7118748738df17cd638f7e9dd0be8e34cec08d7234b20f1f2a756a52fc5a38b188d0 + languageName: node + linkType: hard + "decode-uri-component@npm:^0.2.0": version: 0.2.0 resolution: "decode-uri-component@npm:0.2.0" @@ -23013,6 +23215,16 @@ __metadata: languageName: node linkType: hard +"dot-case@npm:^3.0.4": + version: 3.0.4 + resolution: "dot-case@npm:3.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/a65e3519414856df0228b9f645332f974f2bf5433370f544a681122eab59e66038fc3349b4be1cdc47152779dac71a5864f1ccda2f745e767c46e9c6543b1169 + languageName: node + linkType: hard + "dot-prop@npm:^6.0.0": version: 6.0.1 resolution: "dot-prop@npm:6.0.1" @@ -27494,6 +27706,16 @@ __metadata: languageName: node linkType: hard +"header-case@npm:^2.0.4": + version: 2.0.4 + resolution: "header-case@npm:2.0.4" + dependencies: + capital-case: "npm:^1.0.4" + tslib: "npm:^2.0.3" + checksum: 10/571c83eeb25e8130d172218712f807c0b96d62b020981400bccc1503a7cf14b09b8b10498a962d2739eccf231d950e3848ba7d420b58a6acd2f9283439546cd9 + languageName: node + linkType: hard + "heap@npm:>= 0.2.0": version: 0.2.7 resolution: "heap@npm:0.2.7" @@ -27864,7 +28086,7 @@ __metadata: languageName: node linkType: hard -"immer@npm:^10.1.1": +"immer@npm:^10.0.4, immer@npm:^10.1.1": version: 10.1.1 resolution: "immer@npm:10.1.1" checksum: 10/9dacf1e8c201d69191ccd88dc5d733bafe166cd45a5a360c5d7c88f1de0dff974a94114d72b35f3106adfe587fcfb131c545856184a2247d89d735ad25589863 @@ -30351,6 +30573,17 @@ __metadata: languageName: node linkType: hard +"lit-element@npm:^4.2.0": + version: 4.2.1 + resolution: "lit-element@npm:4.2.1" + dependencies: + "@lit-labs/ssr-dom-shim": "npm:^1.4.0" + "@lit/reactive-element": "npm:^2.1.0" + lit-html: "npm:^3.3.0" + checksum: 10/0d1d306cb12c3ba840cd9baf376997891ece751220049aa4a3cbd6bab25ba21e30d45012662eddaccccc94fe9930e8a0ef36fb779bf22fbcd2184b7a794fee3d + languageName: node + linkType: hard + "lit-html@npm:^2.8.0": version: 2.8.0 resolution: "lit-html@npm:2.8.0" @@ -30360,6 +30593,15 @@ __metadata: languageName: node linkType: hard +"lit-html@npm:^3.1.2, lit-html@npm:^3.3.0": + version: 3.3.1 + resolution: "lit-html@npm:3.3.1" + dependencies: + "@types/trusted-types": "npm:^2.0.2" + checksum: 10/7b438ca58670313beac29e4bb3b56f4ac8150def10b9fa222ad04f1caf6189a194ced8bfed3296cf1c94003a1bd1960ef4c22b00bbeda15fda6b7be398b27e9f + languageName: node + linkType: hard + "lit@npm:2.8.0": version: 2.8.0 resolution: "lit@npm:2.8.0" @@ -30371,6 +30613,17 @@ __metadata: languageName: node linkType: hard +"lit@npm:^3.1.2": + version: 3.3.1 + resolution: "lit@npm:3.3.1" + dependencies: + "@lit/reactive-element": "npm:^2.1.0" + lit-element: "npm:^4.2.0" + lit-html: "npm:^3.3.0" + checksum: 10/ec70ff33db610537fd7cfc608cc7728126ecfae2d5593aa94844c614d2f6840448f1b995a58aeba593b0bf0e8169af5988036c11d3c5b55313fe8e722417c17d + languageName: node + linkType: hard + "load-yaml-file@npm:^0.2.0": version: 0.2.0 resolution: "load-yaml-file@npm:0.2.0" @@ -30624,6 +30877,15 @@ __metadata: languageName: node linkType: hard +"lower-case@npm:^2.0.2": + version: 2.0.2 + resolution: "lower-case@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/83a0a5f159ad7614bee8bf976b96275f3954335a84fad2696927f609ddae902802c4f3312d86668722e668bef41400254807e1d3a7f2e8c3eede79691aa1f010 + languageName: node + linkType: hard + "lowercase-keys@npm:^1.0.0": version: 1.0.1 resolution: "lowercase-keys@npm:1.0.1" @@ -31912,6 +32174,18 @@ __metadata: languageName: node linkType: hard +"neverthrow@npm:^8.0.0": + version: 8.2.0 + resolution: "neverthrow@npm:8.2.0" + dependencies: + "@rollup/rollup-linux-x64-gnu": "npm:^4.24.0" + dependenciesMeta: + "@rollup/rollup-linux-x64-gnu": + optional: true + checksum: 10/7f7a419ef30aa9d7e8e1f180dc6ed8c9d815bc8c6e8a68166f2ce448308af2f2c305c4bd37b8ff7885ba544eb8d9821f690496d7468cccdd8772d2908749c25e + languageName: node + linkType: hard + "next-tick@npm:^1.1.0": version: 1.1.0 resolution: "next-tick@npm:1.1.0" @@ -31952,6 +32226,16 @@ __metadata: languageName: node linkType: hard +"no-case@npm:^3.0.4": + version: 3.0.4 + resolution: "no-case@npm:3.0.4" + dependencies: + lower-case: "npm:^2.0.2" + tslib: "npm:^2.0.3" + checksum: 10/0b2ebc113dfcf737d48dde49cfebf3ad2d82a8c3188e7100c6f375e30eafbef9e9124aadc3becef237b042fd5eb0aad2fd78669c20972d045bbe7fea8ba0be5c + languageName: node + linkType: hard + "nock@npm:13.5.4": version: 13.5.4 resolution: "nock@npm:13.5.4" @@ -31999,6 +32283,15 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^5.0.0": + version: 5.1.0 + resolution: "node-addon-api@npm:5.1.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10/595f59ffb4630564f587c502119cbd980d302e482781021f3b479f5fc7e41cf8f2f7280fdc2795f32d148e4f3259bd15043c52d4a3442796aa6f1ae97b959636 + languageName: node + linkType: hard + "node-addon-api@npm:^7.0.0": version: 7.1.1 resolution: "node-addon-api@npm:7.1.1" @@ -33043,6 +33336,16 @@ __metadata: languageName: node linkType: hard +"param-case@npm:^3.0.4": + version: 3.0.4 + resolution: "param-case@npm:3.0.4" + dependencies: + dot-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/b34227fd0f794e078776eb3aa6247442056cb47761e9cd2c4c881c86d84c64205f6a56ef0d70b41ee7d77da02c3f4ed2f88e3896a8fefe08bdfb4deca037c687 + languageName: node + linkType: hard + "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -33092,6 +33395,16 @@ __metadata: languageName: node linkType: hard +"pascal-case@npm:^3.1.2": + version: 3.1.2 + resolution: "pascal-case@npm:3.1.2" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/ba98bfd595fc91ef3d30f4243b1aee2f6ec41c53b4546bfa3039487c367abaa182471dcfc830a1f9e1a0df00c14a370514fa2b3a1aacc68b15a460c31116873e + languageName: node + linkType: hard + "patch-package@npm:^6.4.7": version: 6.5.1 resolution: "patch-package@npm:6.5.1" @@ -33123,6 +33436,16 @@ __metadata: languageName: node linkType: hard +"path-case@npm:^3.0.4": + version: 3.0.4 + resolution: "path-case@npm:3.0.4" + dependencies: + dot-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/61de0526222629f65038a66f63330dd22d5b54014ded6636283e1d15364da38b3cf29e4433aa3f9d8b0dba407ae2b059c23b0104a34ee789944b1bc1c5c7e06d + languageName: node + linkType: hard + "path-exists@npm:^3.0.0": version: 3.0.0 resolution: "path-exists@npm:3.0.0" @@ -35138,6 +35461,13 @@ __metadata: languageName: node linkType: hard +"reflect-metadata@npm:0.1.13": + version: 0.1.13 + resolution: "reflect-metadata@npm:0.1.13" + checksum: 10/732570da35d2d96f8fdd5aac60fb263aa92f6512eaded5962b052bd9e90f22a9dec5aaf0d7ff4bfe97646c9530e8444e8435c2d80b24d0bdf938b5d47f6f5b83 + languageName: node + linkType: hard + "reflect.getprototypeof@npm:^1.0.4": version: 1.0.6 resolution: "reflect.getprototypeof@npm:1.0.6" @@ -35974,6 +36304,15 @@ __metadata: languageName: node linkType: hard +"rxjs@npm:^7.8.1": + version: 7.8.2 + resolution: "rxjs@npm:7.8.2" + dependencies: + tslib: "npm:^2.1.0" + checksum: 10/03dff09191356b2b87d94fbc1e97c4e9eb3c09d4452399dddd451b09c2f1ba8d56925a40af114282d7bc0c6fe7514a2236ca09f903cf70e4bbf156650dddb49d + languageName: node + linkType: hard + "safe-array-concat@npm:^1.0.1": version: 1.0.1 resolution: "safe-array-concat@npm:1.0.1" @@ -36159,6 +36498,18 @@ __metadata: languageName: node linkType: hard +"secp256k1@npm:5.0.0": + version: 5.0.0 + resolution: "secp256k1@npm:5.0.0" + dependencies: + elliptic: "npm:^6.5.4" + node-addon-api: "npm:^5.0.0" + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.2.0" + checksum: 10/6e146c876ef202dbfbb35836d6ccd0ea3779dc09bad632bb9e0fe2e702848a4ee96638f39da54895430de832232d6292d858529e2eda56db3ddda13e40d7facc + languageName: node + linkType: hard + "secure-json-parse@npm:^2.4.0": version: 2.7.0 resolution: "secure-json-parse@npm:2.7.0" @@ -36336,6 +36687,17 @@ __metadata: languageName: node linkType: hard +"sentence-case@npm:^3.0.4": + version: 3.0.4 + resolution: "sentence-case@npm:3.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + upper-case-first: "npm:^2.0.2" + checksum: 10/3cfe6c0143e649132365695706702d7f729f484fa7b25f43435876efe7af2478243eefb052bacbcce10babf9319fd6b5b6bc59b94c80a1c819bcbb40651465d5 + languageName: node + linkType: hard + "serialize-javascript@npm:6.0.0": version: 6.0.0 resolution: "serialize-javascript@npm:6.0.0" @@ -36825,6 +37187,23 @@ __metadata: languageName: node linkType: hard +"smol-toml@npm:1.4.2": + version: 1.4.2 + resolution: "smol-toml@npm:1.4.2" + checksum: 10/00c3c45859b44a8a9624cd15d4210c1b85dfa27cf432ade832d7c8a4ee96a79c415594cafe42a6708299de57d3b56e74e645396a0e0e1076f8f7b6302a5520b3 + languageName: node + linkType: hard + +"snake-case@npm:^3.0.4": + version: 3.0.4 + resolution: "snake-case@npm:3.0.4" + dependencies: + dot-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10/0a7a79900bbb36f8aaa922cf111702a3647ac6165736d5dc96d3ef367efc50465cac70c53cd172c382b022dac72ec91710608e5393de71f76d7142e6fd80e8a3 + languageName: node + linkType: hard + "socket.io-client@npm:^4.5.1": version: 4.8.1 resolution: "socket.io-client@npm:4.8.1" @@ -38989,6 +39368,13 @@ __metadata: languageName: node linkType: hard +"tslog@npm:>=4.8.0": + version: 4.9.3 + resolution: "tslog@npm:4.9.3" + checksum: 10/134ea14335902b64a49e3ab02f03bab7746e114e1bed6c4a0e9aaf7927d2f7816f5fad50406b905e52fac9f9897cb3f19bd7be84e2c23153f0fc7752277d2622 + languageName: node + linkType: hard + "tsort@npm:0.0.1": version: 0.0.1 resolution: "tsort@npm:0.0.1" @@ -39985,6 +40371,24 @@ __metadata: languageName: node linkType: hard +"upper-case-first@npm:^2.0.2": + version: 2.0.2 + resolution: "upper-case-first@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/4487db4701effe3b54ced4b3e4aa4d9ab06c548f97244d04aafb642eedf96a76d5a03cf5f38f10f415531d5792d1ac6e1b50f2a76984dc6964ad530f12876409 + languageName: node + linkType: hard + +"upper-case@npm:^2.0.2": + version: 2.0.2 + resolution: "upper-case@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10/508723a2b03ab90cf1d6b7e0397513980fab821cbe79c87341d0e96cedefadf0d85f9d71eac24ab23f526a041d585a575cfca120a9f920e44eb4f8a7cf89121c + languageName: node + linkType: hard + "uqr@npm:^0.1.2": version: 0.1.2 resolution: "uqr@npm:0.1.2" @@ -40251,6 +40655,18 @@ __metadata: languageName: node linkType: hard +"valibot@npm:0.42.1": + version: 0.42.1 + resolution: "valibot@npm:0.42.1" + peerDependencies: + typescript: ">=5" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/3e6213d03fa1d9141566a7f9c8e24a783837f7e5216fe92658d29b6abc41a65c266ed5114f7002b9585ddb67f617505bd21b61639c8614a1457d584ef028a117 + languageName: node + linkType: hard + "validate-npm-package-license@npm:^3.0.1": version: 3.0.4 resolution: "validate-npm-package-license@npm:3.0.4"