Merge remote-tracking branch 'upstream/main' into feat/starknet

pull/3658/head
0xevolve 3 weeks ago
commit 4fe269dc01
  1. 5
      .changeset/breezy-turkeys-march.md
  2. 5
      .changeset/clean-dingos-switch.md
  3. 5
      .changeset/cold-dingos-give.md
  4. 4
      .changeset/config.json
  5. 6
      .changeset/cuddly-baboons-drive.md
  6. 8
      .changeset/dirty-cameras-breathe.md
  7. 5
      .changeset/dirty-items-sparkle.md
  8. 6
      .changeset/dirty-swans-drum.md
  9. 5
      .changeset/fast-schools-battle.md
  10. 5
      .changeset/fifty-chefs-visit.md
  11. 6
      .changeset/four-years-tease.md
  12. 5
      .changeset/fresh-pigs-work.md
  13. 7
      .changeset/gorgeous-shirts-film.md
  14. 5
      .changeset/healthy-boats-lie.md
  15. 6
      .changeset/long-queens-deny.md
  16. 5
      .changeset/neat-sloths-agree.md
  17. 6
      .changeset/pink-bats-mix.md
  18. 5
      .changeset/pink-poets-think.md
  19. 11
      .changeset/plenty-pens-peel.md
  20. 5
      .changeset/pretty-dots-look.md
  21. 5
      .changeset/proud-horses-smash.md
  22. 5
      .changeset/proud-turkeys-type.md
  23. 5
      .changeset/real-guests-search.md
  24. 6
      .changeset/rich-donkeys-visit.md
  25. 6
      .changeset/shaggy-shrimps-sneeze.md
  26. 5
      .changeset/short-cobras-wink.md
  27. 5
      .changeset/silent-berries-attend.md
  28. 5
      .changeset/sixty-eggs-smoke.md
  29. 5
      .changeset/sour-ladybugs-appear.md
  30. 5
      .changeset/sweet-houses-type.md
  31. 5
      .changeset/thin-tips-explain.md
  32. 5
      .changeset/tidy-meals-add.md
  33. 5
      .changeset/two-tigers-sniff.md
  34. 5
      .changeset/warm-foxes-jam.md
  35. 5
      .changeset/warm-zoos-smell.md
  36. 2
      .codespell/.codespellrc
  37. 2
      .codespell/ignore.txt
  38. 7
      .eslintrc
  39. 2
      .gitattributes
  40. 21
      .github/CODEOWNERS
  41. 37
      .github/actions/yarn-build-with-cache/action.yml
  42. 24
      .github/workflows/agent-release-artifacts.yml
  43. 14
      .github/workflows/codespell.yml
  44. 4
      .github/workflows/monorepo-docker.yml
  45. 8
      .github/workflows/rust-docker.yml
  46. 18
      .github/workflows/rust-skipped.yml
  47. 79
      .github/workflows/rust.yml
  48. 19
      .github/workflows/static-analysis.yml
  49. 4
      .github/workflows/storage-analysis.yml
  50. 106
      .github/workflows/test-skipped.yml
  51. 322
      .github/workflows/test.yml
  52. 1
      .gitignore
  53. 7
      .husky/pre-commit
  54. 2
      .registryrc
  55. 6
      .vscode/settings.json
  56. 1
      Dockerfile
  57. 7
      codecov.yml
  58. 14
      mono.code-workspace
  59. 1
      package.json
  60. 38
      rust/Dockerfile
  61. 4
      rust/README.md
  62. 170
      rust/agents/relayer/src/server/message_retry.rs
  63. 39
      rust/agents/scraper/src/conversions.rs
  64. 314
      rust/agents/validator/src/submit.rs
  65. 2
      rust/chains/hyperlane-cosmos/src/libs/mod.rs
  66. 452
      rust/chains/hyperlane-cosmos/src/mailbox.rs
  67. 106
      rust/chains/hyperlane-cosmos/src/providers/mod.rs
  68. 54
      rust/chains/hyperlane-ethereum/src/config.rs
  69. 530
      rust/chains/hyperlane-fuel/abis/Mailbox.abi.json
  70. 164
      rust/chains/hyperlane-fuel/src/mailbox.rs
  71. 43
      rust/chains/hyperlane-fuel/src/provider.rs
  72. 35
      rust/chains/hyperlane-sealevel/Cargo.toml
  73. 29
      rust/chains/hyperlane-sealevel/src/client.rs
  74. 23
      rust/chains/hyperlane-sealevel/src/error.rs
  75. 84
      rust/chains/hyperlane-sealevel/src/provider.rs
  76. 93
      rust/chains/hyperlane-sealevel/src/utils.rs
  77. 974
      rust/config/testnet_config.json
  78. 2
      rust/hyperlane-base/src/db/mod.rs
  79. 0
      rust/main/.cargo/config.toml
  80. 0
      rust/main/.vscode/extensions.json
  81. 0
      rust/main/.vscode/settings.json
  82. 4382
      rust/main/Cargo.lock
  83. 95
      rust/main/Cargo.toml
  84. 0
      rust/main/agents/relayer/.cargo/config.toml
  85. 14
      rust/main/agents/relayer/Cargo.toml
  86. 0
      rust/main/agents/relayer/src/lib.rs
  87. 0
      rust/main/agents/relayer/src/main.rs
  88. 0
      rust/main/agents/relayer/src/memory_profiler.rs
  89. 0
      rust/main/agents/relayer/src/merkle_tree/builder.rs
  90. 0
      rust/main/agents/relayer/src/merkle_tree/mod.rs
  91. 2
      rust/main/agents/relayer/src/merkle_tree/processor.rs
  92. 0
      rust/main/agents/relayer/src/msg/blacklist.rs
  93. 11
      rust/main/agents/relayer/src/msg/gas_payment/mod.rs
  94. 0
      rust/main/agents/relayer/src/msg/gas_payment/policies/minimum.rs
  95. 0
      rust/main/agents/relayer/src/msg/gas_payment/policies/mod.rs
  96. 0
      rust/main/agents/relayer/src/msg/gas_payment/policies/none.rs
  97. 0
      rust/main/agents/relayer/src/msg/gas_payment/policies/on_chain_fee_quoting.rs
  98. 1
      rust/main/agents/relayer/src/msg/metadata/aggregation.rs
  99. 7
      rust/main/agents/relayer/src/msg/metadata/base.rs
  100. 2
      rust/main/agents/relayer/src/msg/metadata/ccip_read.rs
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': minor
---
Add ether's error reasoning handling to SmartProvider to show clearer error messages

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/cli': minor
---
re-enable space key for multiselect cli prompt

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/sdk': patch
---
Optimize HyperlaneRelayer routing config derivation

@ -1,8 +1,8 @@
{
"$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [["@hyperlane-xyz/*"]],
"commit": true,
"fixed": [["@hyperlane-xyz/!(core)|*"]],
"linked": [],
"access": "public",
"baseBranch": "main",

@ -0,0 +1,6 @@
---
'@hyperlane-xyz/sdk': minor
'@hyperlane-xyz/core': minor
---
Checking for sufficient fees in `AbstractMessageIdAuthHook` and refund surplus

@ -1,8 +0,0 @@
---
'@hyperlane-xyz/helloworld': minor
'@hyperlane-xyz/widgets': minor
'@hyperlane-xyz/infra': minor
'@hyperlane-xyz/cli': minor
---
Update to registry v2.5.0

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': minor
---
Deploy to arbitrumsepolia, basesepolia, ecotestnet, optimismsepolia, polygonamoy

@ -0,0 +1,6 @@
---
'@hyperlane-xyz/utils': patch
'@hyperlane-xyz/sdk': patch
---
Dedupe internals of hook and ISM module deploy code

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/cli': patch
---
Require at least 1 chain selection in warp init

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': minor
---
Deploy to zircuit

@ -1,6 +0,0 @@
---
'@hyperlane-xyz/cli': patch
'@hyperlane-xyz/sdk': patch
---
feat: Add long-running CLI relayer

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/sdk': minor
---
Deploy to apechain, arbitrumnova, b3, fantom, gravity, harmony, kaia, morph, orderly, snaxchain, zeronetwork, zksync. Update default metadata in `HyperlaneCore` to `0x00001` to ensure empty metadata does not break on zksync.

@ -1,7 +0,0 @@
---
'@hyperlane-xyz/cli': minor
'@hyperlane-xyz/sdk': minor
'@hyperlane-xyz/core': minor
---
Added SDK support for ArbL2ToL1Hook/ISM for selfrelay

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/utils': patch
---
fix median utils func + add test

@ -0,0 +1,6 @@
---
'@hyperlane-xyz/cli': minor
'@hyperlane-xyz/sdk': minor
---
Add feat to allow updates to destination gas using warp apply

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/core': minor
---
Added msg.value to preverifyMessage to commit it as part of external hook payload

@ -0,0 +1,6 @@
---
'@hyperlane-xyz/cli': minor
'@hyperlane-xyz/sdk': minor
---
Add optional proxy admin reuse in warp route deployments and admin proxy ownership transfer in warp apply

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': minor
---
Deploy to solana + eclipse

@ -0,0 +1,11 @@
---
'@hyperlane-xyz/widgets': minor
---
Update widgets with components from explorer and warp ui
- Add icons: Discord, Docs, Github, History, LinkedIn, Medium, Twitter, Wallet and Web
- Add animation component: Fade component
- Add components: DatetimeField and SelectField
- New stories: IconList and Fade
- Add "Icon" suffix for icons that did not have it

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/cli': minor
---
Add output of hyperlane warp read to ./configs/warp-route-deployment.yaml

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/widgets': patch
---
- Update ChainSearchMenu with improvements

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/sdk': minor
---
Update default validator sets. Throw in `InterchainAccount.getOrDeployAccount` if the origin router is the zero address.

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/core': patch
---
fix: only evaluate dynamic revert reasons in reverting branch

@ -1,6 +0,0 @@
---
'@hyperlane-xyz/sdk': minor
'@hyperlane-xyz/core': minor
---
Added yield route with yield going to message recipient.

@ -0,0 +1,6 @@
---
'@hyperlane-xyz/cli': minor
'@hyperlane-xyz/sdk': minor
---
Add `hyperlane warp verify` to allow post-deployment verification.

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/core': minor
---
feat: attributable fraud for signers

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/core': minor
---
disabled the ICARouter's ability to change hook given that the user doesn't expect the hook to change after they deploy their ICA account. Hook is not part of the derivation like ism on the destination chain and hence, cannot be configured custom by the user.

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/cli': minor
---
Enable configuration of IGP hooks in the CLI

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/cli': minor
---
Remove registry.getUri() from core read logging to prevent registry error

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/sdk': patch
---
Fix ICA ISM self relay

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/sdk': minor
---
Introduce utils that can be reused by the CLI and Infra for fetching token prices from Coingecko and gas prices from EVM/Cosmos chains.

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/utils': patch
---
Filter undefined/null values in invertKeysAndValues function

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/core': minor
---
Implement checkpoint fraud proofs for use in slashing

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/sdk': minor
---
Supprt passing foreignDeployments to HypERC20App constructor

@ -1,5 +0,0 @@
---
'@hyperlane-xyz/cli': minor
---
Add check & confirm for existing mailbox to core deploy to allow users to decide if they want to deploy a new mailbox

@ -1,5 +1,5 @@
[codespell]
skip = .git,node_modules,yarn.lock,Cargo.lock,./typescript/helloworld,./rust/config
skip = .git,node_modules,yarn.lock,Cargo.lock,./typescript/helloworld,./rust/main/config,./rust/sealevel/environments/mainnet3/chain-config.json
count =
quiet-level = 3
ignore-words = ./.codespell/ignore.txt

@ -4,4 +4,4 @@ received
receivedFrom
ser
readded
re-use
re-use

@ -24,6 +24,13 @@
"no-ex-assign": ["error"],
"no-constant-condition": ["off"],
"no-return-await": ["error"],
"no-restricted-imports": ["error", {
"name": "console",
"message": "Please use a logger and/or the utils' package assert"
}, {
"name": "fs",
"message": "Avoid use of node-specific libraries"
}],
"guard-for-in": ["error"],
"@typescript-eslint/ban-ts-comment": ["off"],
"@typescript-eslint/explicit-module-boundary-types": ["off"],

2
.gitattributes vendored

@ -1,4 +1,4 @@
typescript/sdk/src/cw-types/*.types.ts linguist-generated=true
rust/chains/hyperlane-ethereum/abis/*.abi.json linguist-generated=true
rust/main/chains/hyperlane-ethereum/abis/*.abi.json linguist-generated=true
solidity/contracts/interfaces/avs/*.sol linguist-vendored=true
solidity/contracts/avs/ECDSA*.sol linguist-vendored=true

@ -1,29 +1,22 @@
# File extension owners
*.sol @yorhodes @tkporter @aroralanuk @nbayindirli
*.ts @yorhodes @jmrossy @nbayindirli
*.rs @tkporter @daniel-savu
*.md @Skunkchain @avious00
*.sol @yorhodes @aroralanuk @ltyu
*.ts @yorhodes @jmrossy
*.rs @tkporter @daniel-savu @ameten
# Package owners
## Contracts
solidity/ @yorhodes @tkporter @aroralanuk @nbayindirli
solidity/ @yorhodes @tkporter @aroralanuk @ltyu
## Agents
rust/ @tkporter @daniel-savu
## SDK
typescript/sdk @yorhodes @jmrossy
## Token
typescript/token @yorhodes @jmrossy @tkporter @aroralanuk @nbayindirli
## Hello World
typescript/helloworld @yorhodes
typescript/sdk @yorhodes @jmrossy @ltyu @paulbalaji
## CLI
typescript/cli @jmrossy @yorhodes @aroralanuk @nbayindirli
typescript/cli @jmrossy @yorhodes @ltyu
## Infra
typescript/infra @tkporter
typescript/infra @tkporter @paulbalaji @Mo-Hussain

@ -0,0 +1,37 @@
name: 'Yarn Build with Cache'
description: 'Run yarn build using yarn cache'
inputs:
ref:
description: 'The Git ref to checkout'
required: true
runs:
using: "composite"
steps:
- name: Cache
uses: buildjet/cache@v4
id: cache
with:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
# Typically, the cache will be hit, but if there's a network error when
# restoring the cache, let's run the install step ourselves.
- name: Install dependencies
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
run: |
yarn install
CHANGES=$(git status -s --ignore-submodules)
if [[ ! -z $CHANGES ]]; then
echo "Changes found: $CHANGES"
git diff
exit 1
fi
- name: Build
shell: bash
run: yarn build

@ -16,7 +16,7 @@ env:
jobs:
prepare:
runs-on: larger-runner
runs-on: ubuntu-latest
outputs:
tag_date: ${{ steps.taggen.outputs.TAG_DATE }}
tag_sha: ${{ steps.taggen.outputs.TAG_SHA }}
@ -58,30 +58,28 @@ jobs:
linker = "aarch64-linux-gnu-gcc"
EOF
- name: setup rust
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
profile: minimal
target: ${{ matrix.TARGET }}
- name: setup target
run: rustup target add ${{ matrix.TARGET }}
working-directory: ./rust
working-directory: ./rust/main
- name: build
run: cargo build --release --target ${{ matrix.TARGET }} --bin relayer --bin scraper --bin validator
working-directory: ./rust
working-directory: ./rust/main
- name: make executable
if: ${{ matrix.OS == 'larger-runner' || matrix.OS == 'macos-latest' }}
run: chmod ug+x,-w relayer scraper validator
working-directory: rust/target/${{ matrix.TARGET }}/release
working-directory: rust/main/target/${{ matrix.TARGET }}/release
- name: upload binaries
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.TARGET }}-${{ needs.prepare.outputs.tag_sha }}-${{ needs.prepare.outputs.tag_date }}
path: |
rust/target/${{ matrix.TARGET }}/release/relayer
rust/target/${{ matrix.TARGET }}/release/relayer.exe
rust/target/${{ matrix.TARGET }}/release/scraper
rust/target/${{ matrix.TARGET }}/release/scraper.exe
rust/target/${{ matrix.TARGET }}/release/validator
rust/target/${{ matrix.TARGET }}/release/validator.exe
rust/main/target/${{ matrix.TARGET }}/release/relayer
rust/main/target/${{ matrix.TARGET }}/release/relayer.exe
rust/main/target/${{ matrix.TARGET }}/release/scraper
rust/main/target/${{ matrix.TARGET }}/release/scraper.exe
rust/main/target/${{ matrix.TARGET }}/release/validator
rust/main/target/${{ matrix.TARGET }}/release/validator.exe
if-no-files-found: error

@ -21,15 +21,13 @@ jobs:
- name: Checkout the repository
uses: actions/checkout@v4
- name: pip cache
uses: actions/cache@v4
- name: Setup python
uses: actions/setup-python@v5
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: ${{ runner.os }}-pip-
python-version: '3.x'
- name: Install prerequisites
run: sudo pip install -r ./.codespell/requirements.txt
- name: Install codespell requirements
run: pip install -r ./.codespell/requirements.txt
- name: Spell check
run: codespell --config=./.codespell/.codespellrc
run: codespell --config=./.codespell/.codespellrc

@ -29,7 +29,7 @@ jobs:
GCLOUD_SERVICE_KEY: ${{ secrets.GCLOUD_SERVICE_KEY }}
if: "${{ env.GCLOUD_SERVICE_KEY != '' }}"
# runs if GCLOUD_SERVICE_KEY is defined, so we set the output to true
run: echo "::set-output name=defined::true"
run: echo "defined=true" >> $GITHUB_OUTPUT
build-and-push-to-gcr:
runs-on: ubuntu-latest
@ -77,7 +77,7 @@ jobs:
echo "REGISTRY_VERSION=$REGISTRY_VERSION" >> $GITHUB_ENV
- name: Build and push
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: ./
file: ./Dockerfile

@ -24,10 +24,10 @@ jobs:
GCLOUD_SERVICE_KEY: ${{ secrets.GCLOUD_SERVICE_KEY }}
if: "${{ env.GCLOUD_SERVICE_KEY != '' }}"
# runs if GCLOUD_SERVICE_KEY is defined, so we set the output to true
run: echo "::set-output name=defined::true"
run: echo "defined=true" >> $GITHUB_OUTPUT
build-and-push-to-gcr:
runs-on: ubuntu-latest
runs-on: buildjet-8vcpu-ubuntu-2204
# uses check-env to determine if secrets.GCLOUD_SERVICE_KEY is defined
needs: [check-env]
@ -63,12 +63,10 @@ jobs:
username: _json_key
password: ${{ secrets.GCLOUD_SERVICE_KEY }}
- name: Build and push
uses: docker/build-push-action@v5
uses: docker/build-push-action@v6
with:
context: .
file: ./rust/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

@ -4,16 +4,18 @@ name: rust
on:
push:
branches: [main]
paths-ignore:
- 'rust/**'
- .github/workflows/rust.yml
paths:
- '**/*'
- '!rust/main/**'
- '!rust/sealevel/**'
- '!.github/workflows/rust.yml'
pull_request:
branches: [main]
paths-ignore:
- 'rust/**'
- .github/workflows/rust.yml
# Support for merge queues
merge_group:
paths:
- '**/*'
- '!rust/main/**'
- '!rust/sealevel/**'
- '!.github/workflows/rust.yml'
env:
CARGO_TERM_COLOR: always

@ -1,19 +1,23 @@
name: rust
on:
# Triggers the workflow on pushes to main branch
# only if rust/** or .github/workflows/rust.yml is changed
push:
branches: [main]
paths:
- 'rust/**'
- 'rust/main/**'
- 'rust/sealevel/**'
- .github/workflows/rust.yml
# Triggers the workflow on pull requests
# only if rust/** or .github/workflows/rust.yml is changed
pull_request:
branches: [main]
paths:
- 'rust/**'
- 'rust/main/**'
- 'rust/sealevel/**'
- .github/workflows/rust.yml
# Support for merge queues
merge_group:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
concurrency:
@ -24,71 +28,84 @@ env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: full
defaults:
run:
working-directory: ./rust
jobs:
test-rs:
runs-on: larger-runner
runs-on: buildjet-8vcpu-ubuntu-2204
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
- uses: dtolnay/rust-toolchain@stable
- name: rust cache
uses: Swatinem/rust-cache@v2
with:
prefix-key: "v3-rust"
prefix-key: 'v3'
shared-key: 'rust'
cache-provider: 'buildjet'
save-if: ${{ !startsWith(github.ref, 'refs/heads/gh-readonly-queue') }}
workspaces: |
./rust
./rust/main
./rust/sealevel
- name: Free disk space
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: Run tests
- 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
lint-rs:
runs-on: larger-runner
runs-on: buildjet-8vcpu-ubuntu-2204
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: actions-rs/toolchain@v1
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
profile: minimal
components: rustfmt, clippy
target: wasm32-unknown-unknown
- name: rust cache
uses: Swatinem/rust-cache@v2
with:
prefix-key: "v3-rust"
prefix-key: 'v3'
shared-key: 'rust'
cache-provider: 'buildjet'
save-if: ${{ !startsWith(github.ref, 'refs/heads/gh-readonly-queue') }}
workspaces: |
./rust
./rust/main
./rust/sealevel
- name: Free disk space
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: Check
- 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
- name: Rustfmt
working-directory: ./rust/sealevel
- name: Rustfmt for main workspace
run: cargo fmt --all -- --check
- name: Clippy
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
- name: Setup WASM
working-directory: ./rust/sealevel
- name: Setup WASM for main workspace
run: rustup target add wasm32-unknown-unknown
- name: Check WASM
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

@ -24,12 +24,14 @@ jobs:
submodules: recursive
- name: yarn-cache
uses: actions/cache@v4
uses: buildjet/cache@v4
with:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-cache-
- name: yarn-install
run: yarn install
@ -54,3 +56,18 @@ jobs:
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: ${{ steps.slither.outputs.sarif }}
category: "slither"
- name: Olympix Integrated Security
uses: olympix/integrated-security@main
env:
OLYMPIX_API_TOKEN: ${{ secrets.OLYMPIX_API_TOKEN }}
OLYMPIX_CLI_LOG_LEVEL: 0
with:
args: -p ./solidity/contracts --output-format sarif --output-path ./
- name: Upload result to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: olympix.sarif
category: "olympix"

@ -24,12 +24,14 @@ jobs:
node-version: 18
- name: yarn-cache
uses: actions/cache@v4
uses: buildjet/cache@v4
with:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-cache-
- name: yarn-install
run: yarn install

@ -1,106 +0,0 @@
name: test
on:
push:
branches: [main]
paths:
- '*.md'
- '!**/*'
pull_request:
branches:
- '*'
paths:
- '*.md'
- '!**/*'
merge_group:
concurrency:
group: e2e-${{ github.ref }}
cancel-in-progress: ${{ github.ref_name != 'main' }}
jobs:
yarn-install:
runs-on: ubuntu-latest
steps:
- name: Instant pass
run: echo "yarn-install job passed"
yarn-build:
runs-on: ubuntu-latest
steps:
- name: Instant pass
run: echo "yarn-build job passed"
lint-prettier:
runs-on: ubuntu-latest
steps:
- name: Instant pass
run: echo "lint-prettier job passed"
yarn-test:
runs-on: ubuntu-latest
steps:
- name: Instant pass
run: echo "yarn-test job passed"
agent-configs:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
environment: [mainnet3, testnet4]
steps:
- name: Instant pass
run: echo "agent-configs job passed"
e2e-matrix:
runs-on: ubuntu-latest
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') || github.event_name == 'merge_group'
strategy:
matrix:
e2e-type: [cosmwasm, non-cosmwasm]
steps:
- name: Instant pass
run: echo "e2e-matrix job passed"
e2e:
runs-on: ubuntu-latest
if: always()
steps:
- name: Instant pass
run: echo "e2e job passed"
cli-advanced-e2e:
runs-on: ubuntu-latest
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.base_ref == 'main') || github.event_name == 'merge_group'
strategy:
matrix:
include:
- test-type: preset_hook_enabled
- test-type: configure_hook_enabled
- test-type: pi_with_core_chain
steps:
- name: Instant pass
run: echo "cli-advanced-e2e job passed"
env-test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
environment: [mainnet3]
chain: [ethereum, arbitrum, optimism, inevm, viction]
module: [core, igp]
include:
- environment: testnet4
chain: sepolia
module: core
steps:
- name: Instant pass
run: echo "env-test job passed"
coverage:
runs-on: ubuntu-latest
steps:
- name: Instant pass
run: echo "coverage job passed"

@ -1,16 +1,14 @@
name: test
on:
# Triggers the workflow on pushes to main branch
# Triggers the workflow on pushes to main branch, ignoring md files
push:
branches: [main]
# Triggers on pull requests ignoring md files
# Triggers on pull requests, ignoring md files
pull_request:
branches:
- '*' # run against all branches
# Support for merge queues
- '*'
merge_group:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
concurrency:
@ -37,12 +35,14 @@ jobs:
submodules: recursive
- name: yarn-cache
uses: actions/cache@v4
uses: buildjet/cache@v4
with:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-cache-
- name: yarn-install
run: |
@ -54,35 +54,6 @@ jobs:
exit 1
fi
yarn-build:
runs-on: ubuntu-latest
needs: [yarn-install]
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
submodules: recursive
fetch-depth: 0
- name: yarn-cache
uses: actions/cache@v4
with:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
- name: build-cache
uses: actions/cache@v4
with:
path: |
./*
!./rust
key: ${{ github.event.pull_request.head.sha || github.sha }}
- name: build
run: yarn build
lint-prettier:
runs-on: ubuntu-latest
needs: [yarn-install]
@ -94,12 +65,13 @@ jobs:
fetch-depth: 0
- name: yarn-cache
uses: actions/cache@v4
uses: buildjet/cache@v4
with:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
fail-on-cache-miss: true
- name: lint
run: yarn lint
@ -115,7 +87,7 @@ jobs:
yarn-test:
runs-on: ubuntu-latest
needs: [yarn-build]
needs: [yarn-install]
steps:
- uses: actions/checkout@v4
with:
@ -126,13 +98,10 @@ jobs:
- name: foundry-install
uses: foundry-rs/foundry-toolchain@v1
- name: build-cache
uses: actions/cache@v4
- name: yarn-build
uses: ./.github/actions/yarn-build-with-cache
with:
path: |
./*
!./rust
key: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Checkout registry
uses: ./.github/actions/checkout-registry
@ -140,9 +109,33 @@ jobs:
- name: Unit Tests
run: yarn test:ci
cli-e2e:
runs-on: ubuntu-latest
needs: [yarn-install]
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: yarn-build
uses: ./.github/actions/yarn-build-with-cache
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Checkout registry
uses: ./.github/actions/checkout-registry
- name: CLI e2e tests
run: yarn --cwd typescript/cli test:e2e
agent-configs:
runs-on: ubuntu-latest
needs: [yarn-build]
needs: [yarn-install]
strategy:
fail-fast: false
matrix:
@ -153,21 +146,10 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
- name: yarn-cache
uses: actions/cache@v4
with:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
- name: build-cache
uses: actions/cache@v4
- name: yarn-build
uses: ./.github/actions/yarn-build-with-cache
with:
path: |
./*
!./rust
key: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Checkout registry
uses: ./.github/actions/checkout-registry
@ -183,10 +165,11 @@ jobs:
fi
e2e-matrix:
runs-on: larger-runner
runs-on: buildjet-8vcpu-ubuntu-2204
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'
needs: [yarn-build]
needs: [yarn-install]
strategy:
fail-fast: false
matrix:
e2e-type: [starknet, cosmwasm, non-cosmwasm]
steps:
@ -198,23 +181,24 @@ jobs:
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: setup rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
uses: dtolnay/rust-toolchain@stable
- name: rust cache
uses: Swatinem/rust-cache@v2
with:
prefix-key: "v1-${{ runner.os }}-rust-cache"
prefix-key: 'v2-rust-e2e'
shared-key: ${{ matrix.e2e-type }}
cache-provider: 'buildjet'
save-if: ${{ !startsWith(github.ref, 'refs/heads/gh-readonly-queue') }}
workspaces: |
./rust
./rust/main
${{ matrix.e2e-type == 'non-cosmwasm' && './rust/sealevel' || '' }}
- name: Free disk space
run: |
@ -230,21 +214,15 @@ jobs:
mold-version: 2.0.0
make-default: true
- name: yarn-cache
uses: actions/cache@v4
- name: yarn-build
uses: ./.github/actions/yarn-build-with-cache
with:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: build-cache
uses: actions/cache@v4
with:
path: |
./*
!./rust
key: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Install system dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -qq -y libudev-dev pkg-config protobuf-compiler
- name: Checkout registry
uses: ./.github/actions/checkout-registry
@ -252,7 +230,7 @@ jobs:
- name: agent tests (CosmWasm)
run: cargo test --release --package run-locally --bin run-locally --features cosmos test-utils -- cosmos::test --nocapture
if: matrix.e2e-type == 'cosmwasm'
working-directory: ./rust
working-directory: ./rust/main
env:
RUST_BACKTRACE: 'full'
@ -263,152 +241,35 @@ jobs:
env:
RUST_BACKTRACE: 'full'
- name: agent tests excluding CosmWasm
- name: Check for Rust file changes
id: check-rust-changes
run: |
if [[ -n "$(git diff ${{ github.event.pull_request.head.sha || github.sha }} ${{ github.event.pull_request.base.sha }} -- ./rust)" ]]; then
echo "rust_changes=true" >> $GITHUB_OUTPUT
echo "$(git diff ${{ github.event.pull_request.head.sha || github.sha }} ${{ github.event.pull_request.base.sha }} -- ./rust)"
else
echo "rust_changes=false" >> $GITHUB_OUTPUT
fi
- name: agent tests (EVM and Sealevel)
run: cargo run --release --bin run-locally --features test-utils
if: matrix.e2e-type == 'non-cosmwasm'
working-directory: ./rust
working-directory: ./rust/main
env:
E2E_CI_MODE: 'true'
E2E_CI_TIMEOUT_SEC: '600'
E2E_KATHY_MESSAGES: '20'
RUST_BACKTRACE: 'full'
e2e:
runs-on: ubuntu-latest
needs: [e2e-matrix]
if: always() # This ensures that the job runs even if the e2e jobs fail
steps:
- name: Report Matrix Result
run: |
echo "All e2e-matrix jobs have completed."
# You can add additional commands here to report the result as needed
prebuild-cli-e2e:
runs-on: larger-runner
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'
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
submodules: recursive
- name: setup rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
- name: rust cache
uses: Swatinem/rust-cache@v2
with:
prefix-key: "v1-${{ runner.os }}-rust-cache"
shared-key: "cli-e2e"
workspaces: |
./rust
- name: Free disk space
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
uses: rui314/setup-mold@v1
with:
mold-version: 2.0.0
make-default: true
- name: Build validator
run: cargo build --bin validator --features test-utils
working-directory: ./rust
env:
RUST_BACKTRACE: 'full'
- name: Build relayer
run: cargo build --bin relayer --features test-utils
working-directory: ./rust
env:
RUST_BACKTRACE: 'full'
cli-advanced-e2e:
runs-on: larger-runner
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'
needs: [yarn-build, prebuild-cli-e2e]
strategy:
matrix:
include:
- test-type: preset_hook_enabled
- test-type: configure_hook_enabled
- test-type: pi_with_core_chain
steps:
- uses: actions/setup-node@v4
with:
node-version: 18
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
submodules: recursive
- name: foundry-install
uses: foundry-rs/foundry-toolchain@v1
- name: setup rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
- name: rust cache
uses: Swatinem/rust-cache@v2
with:
prefix-key: "v1-${{ runner.os }}-rust-cache"
shared-key: "cli-e2e"
workspaces: |
./rust
- name: Free disk space
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
uses: rui314/setup-mold@v1
with:
mold-version: 2.0.0
make-default: true
- name: yarn-cache
uses: actions/cache@v4
with:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
- name: build-cache
uses: actions/cache@v4
with:
path: |
./*
!./rust
key: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Checkout registry
uses: ./.github/actions/checkout-registry
- name: cli e2e tests
run: ./typescript/cli/ci-advanced-test.sh ${{ matrix.test-type }}
SEALEVEL_ENABLED: ${{ steps.check-rust-changes.outputs.rust_changes }}
env-test:
runs-on: ubuntu-latest
timeout-minutes: 3
needs: [yarn-build]
env:
MAINNET3_ARBITRUM_RPC_URLS: ${{ secrets.MAINNET3_ARBITRUM_RPC_URLS }}
MAINNET3_OPTIMISM_RPC_URLS: ${{ secrets.MAINNET3_OPTIMISM_RPC_URLS }}
timeout-minutes: 10
needs: [yarn-install]
strategy:
fail-fast: false
matrix:
@ -428,13 +289,10 @@ jobs:
- name: foundry-install
uses: foundry-rs/foundry-toolchain@v1
- name: build-cache
uses: actions/cache@v4
- name: yarn-build
uses: ./.github/actions/yarn-build-with-cache
with:
path: |
./*
!./rust
key: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Checkout registry
uses: ./.github/actions/checkout-registry
@ -444,29 +302,17 @@ jobs:
coverage:
runs-on: ubuntu-latest
needs: [yarn-test]
needs: [yarn-install]
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
- name: yarn-cache
uses: actions/cache@v4
- name: yarn-build
uses: ./.github/actions/yarn-build-with-cache
with:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
- name: build-cache
uses: actions/cache@v4
with:
path: |
./*
!./rust
key: ${{ github.event.pull_request.head.sha || github.sha }}
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: foundry-install
uses: foundry-rs/foundry-toolchain@v1

1
.gitignore vendored

@ -26,6 +26,5 @@ yarn-error.log
.idea
**/*.ignore
.vscode
tsconfig.editor.json

@ -7,6 +7,9 @@ echo "📝 If you haven't yet, please add a changeset for your changes via 'yarn
# if any *.rs files have changed
if git diff --staged --exit-code --name-only | grep -q -E ".*\.rs$"; then
echo "Running cargo fmt pre-commit hook"
cargo fmt --all --check --manifest-path rust/Cargo.toml
echo "Running cargo fmt pre-commit hook for rust/main"
cargo fmt --all --check --manifest-path rust/main/Cargo.toml
echo "Running cargo fmt pre-commit hook for rust/sealevel"
cargo fmt --all --check --manifest-path rust/sealevel/Cargo.toml
fi

@ -1 +1 @@
v2.5.0
302be4817c063629cec70c0b02322b250df71122

@ -0,0 +1,6 @@
{
"rust-analyzer.linkedProjects": [
"./rust/main/Cargo.toml",
"./rust/sealevel/Cargo.toml",
],
}

@ -18,6 +18,7 @@ COPY typescript/cli/package.json ./typescript/cli/
COPY typescript/infra/package.json ./typescript/infra/
COPY typescript/ccip-server/package.json ./typescript/ccip-server/
COPY typescript/widgets/package.json ./typescript/widgets/
COPY typescript/github-proxy/package.json ./typescript/github-proxy/
COPY solidity/package.json ./solidity/
RUN yarn install && yarn cache clean

@ -1,3 +1,10 @@
coverage:
status:
project:
default:
target: auto # strictly increasing coverage
threshold: 3% # buffer for coverage drop
comment:
layout: "header, diff, flags, components" # show component info in the PR comment

@ -17,6 +17,13 @@
"**/.git/**": true,
"**/node_modules/*/**": true
},
"cSpell.words": [
"hyperlane"
],
"rust-analyzer.linkedProjects": [
"./rust/main/Cargo.toml",
"./rust/sealevel/Cargo.toml",
],
},
"folders": [
{
@ -30,7 +37,10 @@
"path": "./solidity"
},
{
"path": "./rust"
"path": "./rust/main"
},
{
"path": "./rust/sealevel"
}
],
"extensions": {
@ -63,6 +73,8 @@
"yoavbls.pretty-ts-errors",
// Yaml language support
"redhat.vscode-yaml",
// Rust language support
"rust-lang.rust-analyzer"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []

@ -20,6 +20,7 @@
"packageManager": "yarn@4.0.2",
"private": true,
"scripts": {
"agent-configs": "yarn --cwd typescript/infra/ update-agent-config:mainnet3 && yarn --cwd typescript/infra/ update-agent-config:testnet4 && yarn prettier",
"build": "yarn workspaces foreach --all --parallel --topological run build",
"clean": "yarn workspaces foreach --all --parallel run clean",
"prettier": "yarn workspaces foreach --since --parallel run prettier",

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:experimental
FROM rust:1.72.1 as builder
FROM rust:1.80.1 as builder
WORKDIR /usr/src
# 1a: Prepare for static linking
@ -9,35 +9,34 @@ RUN apt-get update && \
apt-get install -y musl-tools clang && \
rustup target add x86_64-unknown-linux-musl
RUN mkdir rust
RUN mkdir -p rust/main
RUN mkdir -p rust/sealevel
# Add workspace to workdir
COPY rust/agents rust/agents
COPY rust/chains rust/chains
COPY rust/hyperlane-base rust/hyperlane-base
COPY rust/hyperlane-core rust/hyperlane-core
COPY rust/hyperlane-test rust/hyperlane-test
COPY rust/ethers-prometheus rust/ethers-prometheus
COPY rust/utils rust/utils
COPY rust/main/agents rust/main/agents
COPY rust/main/chains rust/main/chains
COPY rust/main/hyperlane-base rust/main/hyperlane-base
COPY rust/main/hyperlane-core rust/main/hyperlane-core
COPY rust/main/hyperlane-test rust/main/hyperlane-test
COPY rust/main/ethers-prometheus rust/main/ethers-prometheus
COPY rust/main/utils rust/main/utils
COPY rust/sealevel rust/sealevel
COPY rust/Cargo.toml rust/.
COPY rust/Cargo.lock rust/.
COPY rust/main/Cargo.toml rust/main/.
COPY rust/main/Cargo.lock rust/main/.
# Required for VERGEN_GIT_SHA to be populated
COPY .git .git
WORKDIR /usr/src/rust
WORKDIR /usr/src/rust/main
# Build binaries
RUN \
--mount=id=cargo,type=cache,sharing=locked,target=/usr/src/target \
--mount=id=cargo-home-registry,type=cache,sharing=locked,target=/usr/local/cargo/registry \
--mount=id=cargo-home-git,type=cache,sharing=locked,target=/usr/local/cargo/git \
RUSTFLAGS="--cfg tokio_unstable" cargo build --release --bin validator --bin relayer --bin scraper && \
mkdir -p /release && \
cp /usr/src/rust/target/release/validator /release && \
cp /usr/src/rust/target/release/relayer /release && \
cp /usr/src/rust/target/release/scraper /release
cp /usr/src/rust/main/target/release/validator /release && \
cp /usr/src/rust/main/target/release/relayer /release && \
cp /usr/src/rust/main/target/release/scraper /release
## 2: Copy the binaries to release image
FROM ubuntu:22.04
@ -49,7 +48,8 @@ RUN apt-get update && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY rust/config ./config
RUN mkdir -p /app/config
COPY rust/main/config /app/config
COPY --from=builder /release/* .
RUN chmod 777 /app && \

@ -90,7 +90,7 @@ env $(cat ./config/validator.fuji.env | grep -v "#" | xargs) ./target/debug/vali
Clone `hyperlane-registry` repo next to `hyperlane-monorepo` repo.
To perform an automated e2e test of the agents locally, from within the `hyperlane-monorepo/rust` directory, run:
To perform an automated e2e test of the agents locally, from within the `hyperlane-monorepo/rust/main` directory, run:
```bash
cargo run --release --bin run-locally
@ -117,7 +117,7 @@ cd rust
### Deploy Procedure
The contract addresses of each deploy can be found in `rust/config`. The agents will
The contract addresses of each deploy can be found in `rust/main/config`. The agents will
automatically pull in all configs in this directory.
When agents are deployed to point at a new environment, they cease to point at

@ -1,170 +0,0 @@
use axum::{
extract::{Query, State},
routing, Router,
};
use derive_new::new;
use hyperlane_core::{ChainCommunicationError, QueueOperation, H256};
use serde::Deserialize;
use std::str::FromStr;
use tokio::sync::broadcast::Sender;
const MESSAGE_RETRY_API_BASE: &str = "/message_retry";
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MessageRetryRequest {
MessageId(H256),
DestinationDomain(u32),
}
impl PartialEq<QueueOperation> for &MessageRetryRequest {
fn eq(&self, other: &QueueOperation) -> bool {
match self {
MessageRetryRequest::MessageId(message_id) => message_id == &other.id(),
MessageRetryRequest::DestinationDomain(destination_domain) => {
destination_domain == &other.destination_domain().id()
}
}
}
}
#[derive(new, Clone)]
pub struct MessageRetryApi {
tx: Sender<MessageRetryRequest>,
}
#[derive(Deserialize)]
struct RawMessageRetryRequest {
message_id: Option<String>,
destination_domain: Option<u32>,
}
impl TryFrom<RawMessageRetryRequest> for Vec<MessageRetryRequest> {
type Error = ChainCommunicationError;
fn try_from(request: RawMessageRetryRequest) -> Result<Self, Self::Error> {
let mut retry_requests = Vec::new();
if let Some(message_id) = request.message_id {
retry_requests.push(MessageRetryRequest::MessageId(H256::from_str(&message_id)?));
}
if let Some(destination_domain) = request.destination_domain {
retry_requests.push(MessageRetryRequest::DestinationDomain(destination_domain));
}
Ok(retry_requests)
}
}
async fn retry_message(
State(tx): State<Sender<MessageRetryRequest>>,
Query(request): Query<RawMessageRetryRequest>,
) -> String {
let retry_requests: Vec<MessageRetryRequest> = match request.try_into() {
Ok(retry_requests) => retry_requests,
// Technically it's bad practice to print the error message to the user, but
// this endpoint is for debugging purposes only.
Err(err) => {
return format!("Failed to parse retry request: {}", err);
}
};
if retry_requests.is_empty() {
return "No retry requests found. Please provide either a message_id or destination_domain.".to_string();
}
if let Err(err) = retry_requests
.into_iter()
.map(|req| tx.send(req))
.collect::<Result<Vec<_>, _>>()
{
return format!("Failed to send retry request to the queue: {}", err);
}
"Moved message(s) to the front of the queue".to_string()
}
impl MessageRetryApi {
pub fn router(&self) -> Router {
Router::new()
.route("/", routing::get(retry_message))
.with_state(self.tx.clone())
}
pub fn get_route(&self) -> (&'static str, Router) {
(MESSAGE_RETRY_API_BASE, self.router())
}
}
#[cfg(test)]
mod tests {
use crate::server::ENDPOINT_MESSAGES_QUEUE_SIZE;
use super::*;
use axum::http::StatusCode;
use ethers::utils::hex::ToHex;
use std::net::SocketAddr;
use tokio::sync::broadcast::{Receiver, Sender};
fn setup_test_server() -> (SocketAddr, Receiver<MessageRetryRequest>) {
let broadcast_tx = Sender::<MessageRetryRequest>::new(ENDPOINT_MESSAGES_QUEUE_SIZE);
let message_retry_api = MessageRetryApi::new(broadcast_tx.clone());
let (path, retry_router) = message_retry_api.get_route();
let app = Router::new().nest(path, retry_router);
// Running the app in the background using a test server
let server =
axum::Server::bind(&"127.0.0.1:0".parse().unwrap()).serve(app.into_make_service());
let addr = server.local_addr();
tokio::spawn(server);
(addr, broadcast_tx.subscribe())
}
#[tokio::test]
async fn test_message_id_retry() {
let (addr, mut rx) = setup_test_server();
// Create a random message ID
let message_id = H256::random();
// Send a GET request to the server
let response = reqwest::get(format!(
"http://{}{}?message_id={}",
addr,
MESSAGE_RETRY_API_BASE,
message_id.encode_hex::<String>()
))
.await
.unwrap();
// Check that the response status code is OK
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(
rx.try_recv().unwrap(),
MessageRetryRequest::MessageId(message_id)
);
}
#[tokio::test]
async fn test_destination_domain_retry() {
let (addr, mut rx) = setup_test_server();
// Create a random destination domain
let destination_domain = 42;
// Send a GET request to the server
let response = reqwest::get(format!(
"http://{}{}?destination_domain={}",
addr, MESSAGE_RETRY_API_BASE, destination_domain
))
.await
.unwrap();
// Check that the response status code is OK
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(
rx.try_recv().unwrap(),
MessageRetryRequest::DestinationDomain(destination_domain)
);
}
}

@ -1,39 +0,0 @@
use num_bigint::{BigInt, Sign};
use sea_orm::prelude::BigDecimal;
use hyperlane_core::{H256, U256};
// Creates a big-endian hex representation of the address
pub fn address_to_bytes(data: &H256) -> Vec<u8> {
if hex::is_h160(data.as_fixed_bytes()) {
// take the last 20 bytes
data.as_fixed_bytes()[12..32].into()
} else {
h256_to_bytes(data)
}
}
// Creates a big-endian hex representation of the address
pub fn bytes_to_address(data: Vec<u8>) -> eyre::Result<H256> {
if (data.len() != 20) && (data.len() != 32) {
return Err(eyre::eyre!("Invalid address length"));
}
if data.len() == 20 {
let mut prefix = vec![0; 12];
prefix.extend(data);
Ok(H256::from_slice(&prefix[..]))
} else {
Ok(H256::from_slice(&data[..]))
}
}
// Creates a big-endian hex representation of the address hash
pub fn h256_to_bytes(data: &H256) -> Vec<u8> {
data.as_fixed_bytes().as_slice().into()
}
pub fn u256_to_decimal(v: U256) -> BigDecimal {
let mut buf = [0u8; 32];
v.to_little_endian(&mut buf);
BigDecimal::from(BigInt::from_bytes_le(Sign::Plus, &buf as &[u8]))
}

@ -1,314 +0,0 @@
use std::num::NonZeroU64;
use std::sync::Arc;
use std::time::{Duration, Instant};
use std::vec;
use hyperlane_core::rpc_clients::call_and_retry_indefinitely;
use hyperlane_core::{ChainResult, MerkleTreeHook};
use prometheus::IntGauge;
use tokio::time::sleep;
use tracing::{debug, error, info};
use hyperlane_base::{db::HyperlaneRocksDB, CheckpointSyncer, CoreMetrics};
use hyperlane_core::{
accumulator::incremental::IncrementalMerkle, Checkpoint, CheckpointWithMessageId,
HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneSignerExt,
};
use hyperlane_ethereum::SingletonSignerHandle;
#[derive(Clone)]
pub(crate) struct ValidatorSubmitter {
interval: Duration,
reorg_period: Option<NonZeroU64>,
signer: SingletonSignerHandle,
merkle_tree_hook: Arc<dyn MerkleTreeHook>,
checkpoint_syncer: Arc<dyn CheckpointSyncer>,
message_db: HyperlaneRocksDB,
metrics: ValidatorSubmitterMetrics,
}
impl ValidatorSubmitter {
pub(crate) fn new(
interval: Duration,
reorg_period: u64,
merkle_tree_hook: Arc<dyn MerkleTreeHook>,
signer: SingletonSignerHandle,
checkpoint_syncer: Arc<dyn CheckpointSyncer>,
message_db: HyperlaneRocksDB,
metrics: ValidatorSubmitterMetrics,
) -> Self {
Self {
reorg_period: NonZeroU64::new(reorg_period),
interval,
merkle_tree_hook,
signer,
checkpoint_syncer,
message_db,
metrics,
}
}
pub(crate) fn checkpoint(&self, tree: &IncrementalMerkle) -> Checkpoint {
Checkpoint {
root: tree.root(),
index: tree.index(),
merkle_tree_hook_address: self.merkle_tree_hook.address(),
mailbox_domain: self.merkle_tree_hook.domain().id(),
}
}
/// Submits signed checkpoints from index 0 until the target checkpoint (inclusive).
/// Runs idly forever once the target checkpoint is reached to avoid exiting the task.
pub(crate) async fn backfill_checkpoint_submitter(self, target_checkpoint: Checkpoint) {
let mut tree = IncrementalMerkle::default();
self.submit_checkpoints_until_correctness_checkpoint(&mut tree, &target_checkpoint)
.await;
info!(
?target_checkpoint,
"Backfill checkpoint submitter successfully reached target checkpoint"
);
}
/// Submits signed checkpoints indefinitely, starting from the `tree`.
pub(crate) async fn checkpoint_submitter(self, mut tree: IncrementalMerkle) {
// How often to log checkpoint info - once every minute
let checkpoint_info_log_period = Duration::from_secs(60);
// The instant in which we last logged checkpoint info, if at all
let mut latest_checkpoint_info_log: Option<Instant> = None;
// Returns whether checkpoint info should be logged based off the
// checkpoint_info_log_period having elapsed since the last log.
// Sets latest_checkpoint_info_log to the current instant if true.
let mut should_log_checkpoint_info = || {
if let Some(instant) = latest_checkpoint_info_log {
if instant.elapsed() < checkpoint_info_log_period {
return false;
}
}
latest_checkpoint_info_log = Some(Instant::now());
true
};
loop {
// Lag by reorg period because this is our correctness checkpoint.
let latest_checkpoint = call_and_retry_indefinitely(|| {
let merkle_tree_hook = self.merkle_tree_hook.clone();
Box::pin(async move { merkle_tree_hook.latest_checkpoint(self.reorg_period).await })
})
.await;
self.metrics
.latest_checkpoint_observed
.set(latest_checkpoint.index as i64);
if should_log_checkpoint_info() {
info!(
?latest_checkpoint,
tree_count = tree.count(),
"Latest checkpoint"
);
}
// This may occur e.g. if RPC providers are unreliable and make calls against
// inconsistent block tips.
//
// In this case, we just sleep a bit until we fetch a new latest checkpoint
// that at least meets the tree.
if tree_exceeds_checkpoint(&latest_checkpoint, &tree) {
debug!(
?latest_checkpoint,
tree_count = tree.count(),
"Latest checkpoint is behind tree, sleeping briefly"
);
sleep(self.interval).await;
continue;
}
self.submit_checkpoints_until_correctness_checkpoint(&mut tree, &latest_checkpoint)
.await;
self.metrics
.latest_checkpoint_processed
.set(latest_checkpoint.index as i64);
sleep(self.interval).await;
}
}
/// Submits signed checkpoints relating to the given tree until the correctness checkpoint (inclusive).
/// Only submits the signed checkpoints once the correctness checkpoint is reached.
async fn submit_checkpoints_until_correctness_checkpoint(
&self,
tree: &mut IncrementalMerkle,
correctness_checkpoint: &Checkpoint,
) {
// This should never be called with a tree that is ahead of the correctness checkpoint.
assert!(
!tree_exceeds_checkpoint(correctness_checkpoint, tree),
"tree (count: {}) is ahead of correctness checkpoint {:?}",
tree.count(),
correctness_checkpoint,
);
// All intermediate checkpoints will be stored here and signed once the correctness
// checkpoint is reached.
let mut checkpoint_queue = vec![];
// If the correctness checkpoint is ahead of the tree, we need to ingest more messages.
//
// 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.
while tree.count() as u32 <= correctness_checkpoint.index {
if let Some(insertion) = self
.message_db
.retrieve_merkle_tree_insertion_by_leaf_index(&(tree.count() as u32))
.unwrap_or_else(|err| {
panic!(
"Error fetching merkle tree insertion for leaf index {}: {}",
tree.count(),
err
)
})
{
debug!(
index = insertion.index(),
queue_length = checkpoint_queue.len(),
"Ingesting leaf to tree"
);
let message_id = insertion.message_id();
tree.ingest(message_id);
let checkpoint = self.checkpoint(tree);
checkpoint_queue.push(CheckpointWithMessageId {
checkpoint,
message_id,
});
} else {
// If we haven't yet indexed the next merkle tree insertion but know that
// it will soon exist (because we know the correctness checkpoint), wait a bit and
// try again.
sleep(Duration::from_millis(100)).await
}
}
// At this point we know that correctness_checkpoint.index == tree.index().
assert_eq!(
correctness_checkpoint.index,
tree.index(),
"correctness checkpoint index {} != tree index {}",
correctness_checkpoint.index,
tree.index(),
);
let checkpoint = self.checkpoint(tree);
// If the tree's checkpoint doesn't match the correctness checkpoint, something went wrong
// and we bail loudly.
if checkpoint != *correctness_checkpoint {
error!(
?checkpoint,
?correctness_checkpoint,
"Incorrect tree root, something went wrong"
);
panic!("Incorrect tree root, something went wrong");
}
if !checkpoint_queue.is_empty() {
info!(
index = checkpoint.index,
queue_len = checkpoint_queue.len(),
"Reached tree consistency"
);
self.sign_and_submit_checkpoints(checkpoint_queue).await;
info!(
index = checkpoint.index,
"Signed all queued checkpoints until index"
);
}
}
async fn sign_and_submit_checkpoint(
&self,
checkpoint: CheckpointWithMessageId,
) -> ChainResult<()> {
let existing = self
.checkpoint_syncer
.fetch_checkpoint(checkpoint.index)
.await?;
if existing.is_some() {
debug!(index = checkpoint.index, "Checkpoint already submitted");
return Ok(());
}
let signed_checkpoint = self.signer.sign(checkpoint).await?;
self.checkpoint_syncer
.write_checkpoint(&signed_checkpoint)
.await?;
debug!(index = checkpoint.index, "Signed and submitted checkpoint");
// TODO: move these into S3 implementations
// small sleep before signing next checkpoint to avoid rate limiting
sleep(Duration::from_millis(100)).await;
Ok(())
}
/// Signs and submits any previously unsubmitted checkpoints.
async fn sign_and_submit_checkpoints(&self, checkpoints: Vec<CheckpointWithMessageId>) {
let last_checkpoint = checkpoints.as_slice()[checkpoints.len() - 1];
// Submits checkpoints to the store in reverse order. This speeds up processing historic checkpoints (those before the validator is spun up),
// since those are the most likely to make messages become processable.
// A side effect is that new checkpoints will also be submitted in reverse order.
for queued_checkpoint in checkpoints.into_iter().rev() {
// certain checkpoint stores rate limit very aggressively, so we retry indefinitely
call_and_retry_indefinitely(|| {
let self_clone = self.clone();
Box::pin(async move {
self_clone
.sign_and_submit_checkpoint(queued_checkpoint)
.await?;
Ok(())
})
})
.await;
}
call_and_retry_indefinitely(|| {
let self_clone = self.clone();
Box::pin(async move {
self_clone
.checkpoint_syncer
.update_latest_index(last_checkpoint.index)
.await?;
Ok(())
})
})
.await;
}
}
/// Returns whether the tree exceeds the checkpoint.
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
}
#[derive(Clone)]
pub(crate) struct ValidatorSubmitterMetrics {
latest_checkpoint_observed: IntGauge,
latest_checkpoint_processed: IntGauge,
}
impl ValidatorSubmitterMetrics {
pub fn new(metrics: &CoreMetrics, mailbox_chain: &HyperlaneDomain) -> Self {
let chain_name = mailbox_chain.name();
Self {
latest_checkpoint_observed: metrics
.latest_checkpoint()
.with_label_values(&["validator_observed", chain_name]),
latest_checkpoint_processed: metrics
.latest_checkpoint()
.with_label_values(&["validator_processed", chain_name]),
}
}
}

@ -1,2 +0,0 @@
/// This module contains all the verification variables the libraries used by the Hyperlane Cosmos chain.
pub mod address;

@ -1,452 +0,0 @@
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use std::{
fmt::{Debug, Formatter},
io::Cursor,
num::NonZeroU64,
ops::RangeInclusive,
str::FromStr,
};
use crate::payloads::{general, mailbox};
use crate::rpc::{CosmosWasmIndexer, ParsedEvent, WasmIndexer};
use crate::CosmosProvider;
use crate::{
address::CosmosAddress,
payloads::mailbox::{GeneralMailboxQuery, ProcessMessageRequest, ProcessMessageRequestInner},
utils::execute_and_parse_log_futures,
};
use crate::{grpc::WasmProvider, HyperlaneCosmosError};
use crate::{signers::Signer, utils::get_block_height_for_lag, ConnectionConf};
use async_trait::async_trait;
use cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse;
use once_cell::sync::Lazy;
use tendermint::abci::EventAttribute;
use crate::utils::{CONTRACT_ADDRESS_ATTRIBUTE_KEY, CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64};
use hyperlane_core::{
utils::bytes_to_hex, ChainResult, HyperlaneChain, HyperlaneContract, HyperlaneDomain,
HyperlaneMessage, HyperlaneProvider, Indexed, Indexer, LogMeta, Mailbox, TxCostEstimate,
TxOutcome, H256, U256,
};
use hyperlane_core::{
ChainCommunicationError, ContractLocator, Decode, RawHyperlaneMessage, SequenceAwareIndexer,
};
use tracing::{instrument, warn};
#[derive(Clone)]
/// A reference to a Mailbox contract on some Cosmos chain
pub struct CosmosMailbox {
config: ConnectionConf,
domain: HyperlaneDomain,
address: H256,
provider: CosmosProvider,
}
impl CosmosMailbox {
/// Create a new cosmos mailbox
pub fn new(
conf: ConnectionConf,
locator: ContractLocator,
signer: Option<Signer>,
) -> ChainResult<Self> {
let provider = CosmosProvider::new(
locator.domain.clone(),
conf.clone(),
Some(locator.clone()),
signer,
)?;
Ok(Self {
config: conf,
domain: locator.domain.clone(),
address: locator.address,
provider,
})
}
/// Prefix used in the bech32 address encoding
pub fn bech32_prefix(&self) -> String {
self.config.get_bech32_prefix()
}
fn contract_address_bytes(&self) -> usize {
self.config.get_contract_address_bytes()
}
}
impl HyperlaneContract for CosmosMailbox {
fn address(&self) -> H256 {
self.address
}
}
impl HyperlaneChain for CosmosMailbox {
fn domain(&self) -> &HyperlaneDomain {
&self.domain
}
fn provider(&self) -> Box<dyn HyperlaneProvider> {
Box::new(self.provider.clone())
}
}
impl Debug for CosmosMailbox {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
todo!()
}
}
#[async_trait]
impl Mailbox for CosmosMailbox {
#[instrument(level = "debug", err, ret, skip(self))]
async fn count(&self, lag: Option<NonZeroU64>) -> ChainResult<u32> {
let block_height = get_block_height_for_lag(self.provider.grpc(), lag).await?;
self.nonce_at_block(block_height).await
}
#[instrument(level = "debug", err, ret, skip(self))]
async fn delivered(&self, id: H256) -> ChainResult<bool> {
let id = hex::encode(id);
let payload = mailbox::DeliveredRequest {
message_delivered: mailbox::DeliveredRequestInner { id },
};
let delivered = match self
.provider
.grpc()
.wasm_query(GeneralMailboxQuery { mailbox: payload }, None)
.await
{
Ok(v) => {
let response: mailbox::DeliveredResponse = serde_json::from_slice(&v)?;
response.delivered
}
Err(err) => {
warn!(
"error while checking the message delivery status: {:?}",
err
);
false
}
};
Ok(delivered)
}
#[instrument(err, ret, skip(self))]
async fn default_ism(&self) -> ChainResult<H256> {
let payload = mailbox::DefaultIsmRequest {
default_ism: general::EmptyStruct {},
};
let data = self
.provider
.grpc()
.wasm_query(GeneralMailboxQuery { mailbox: payload }, None)
.await?;
let response: mailbox::DefaultIsmResponse = serde_json::from_slice(&data)?;
// convert bech32 to H256
let ism = CosmosAddress::from_str(&response.default_ism)?;
Ok(ism.digest())
}
#[instrument(err, ret, skip(self))]
async fn recipient_ism(&self, recipient: H256) -> ChainResult<H256> {
let address = CosmosAddress::from_h256(
recipient,
&self.bech32_prefix(),
self.contract_address_bytes(),
)?
.address();
let payload = mailbox::RecipientIsmRequest {
recipient_ism: mailbox::RecipientIsmRequestInner {
recipient_addr: address,
},
};
let data = self
.provider
.grpc()
.wasm_query(GeneralMailboxQuery { mailbox: payload }, None)
.await?;
let response: mailbox::RecipientIsmResponse = serde_json::from_slice(&data)?;
// convert bech32 to H256
let ism = CosmosAddress::from_str(&response.ism)?;
Ok(ism.digest())
}
#[instrument(err, ret, skip(self))]
async fn process(
&self,
message: &HyperlaneMessage,
metadata: &[u8],
tx_gas_limit: Option<U256>,
) -> ChainResult<TxOutcome> {
let process_message = ProcessMessageRequest {
process: ProcessMessageRequestInner {
message: hex::encode(RawHyperlaneMessage::from(message)),
metadata: hex::encode(metadata),
},
};
let response: TxResponse = self
.provider
.grpc()
.wasm_send(process_message, tx_gas_limit)
.await?;
Ok(response.try_into()?)
}
#[instrument(err, ret, skip(self), fields(msg=%message, metadata=%bytes_to_hex(metadata)))]
async fn process_estimate_costs(
&self,
message: &HyperlaneMessage,
metadata: &[u8],
) -> ChainResult<TxCostEstimate> {
let process_message = ProcessMessageRequest {
process: ProcessMessageRequestInner {
message: hex::encode(RawHyperlaneMessage::from(message)),
metadata: hex::encode(metadata),
},
};
let gas_limit = self
.provider
.grpc()
.wasm_estimate_gas(process_message)
.await?;
let result = TxCostEstimate {
gas_limit: gas_limit.into(),
gas_price: self.provider.grpc().gas_price(),
l2_gas_limit: None,
};
Ok(result)
}
fn process_calldata(&self, message: &HyperlaneMessage, metadata: &[u8]) -> Vec<u8> {
todo!() // not required
}
}
impl CosmosMailbox {
#[instrument(level = "debug", err, ret, skip(self))]
async fn nonce_at_block(&self, block_height: Option<u64>) -> ChainResult<u32> {
let payload = mailbox::NonceRequest {
nonce: general::EmptyStruct {},
};
let data = self
.provider
.grpc()
.wasm_query(GeneralMailboxQuery { mailbox: payload }, block_height)
.await?;
let response: mailbox::NonceResponse = serde_json::from_slice(&data)?;
Ok(response.nonce)
}
}
// ------------------ Indexer ------------------
const MESSAGE_ATTRIBUTE_KEY: &str = "message";
static MESSAGE_ATTRIBUTE_KEY_BASE64: Lazy<String> =
Lazy::new(|| BASE64.encode(MESSAGE_ATTRIBUTE_KEY));
/// Struct that retrieves event data for a Cosmos Mailbox contract
#[derive(Debug, Clone)]
pub struct CosmosMailboxIndexer {
mailbox: CosmosMailbox,
indexer: Box<CosmosWasmIndexer>,
}
impl CosmosMailboxIndexer {
/// The message dispatch event type from the CW contract.
const MESSAGE_DISPATCH_EVENT_TYPE: &str = "mailbox_dispatch";
/// Create a reference to a mailbox at a specific Cosmos address on some
/// chain
pub fn new(
conf: ConnectionConf,
locator: ContractLocator,
signer: Option<Signer>,
reorg_period: u32,
) -> ChainResult<Self> {
let mailbox = CosmosMailbox::new(conf.clone(), locator.clone(), signer.clone())?;
let indexer = CosmosWasmIndexer::new(
conf,
locator,
Self::MESSAGE_DISPATCH_EVENT_TYPE.into(),
reorg_period,
)?;
Ok(Self {
mailbox,
indexer: Box::new(indexer),
})
}
#[instrument(err)]
fn hyperlane_message_parser(
attrs: &Vec<EventAttribute>,
) -> ChainResult<ParsedEvent<HyperlaneMessage>> {
let mut contract_address: Option<String> = None;
let mut message: Option<HyperlaneMessage> = None;
for attr in attrs {
let key = attr.key.as_str();
let value = attr.value.as_str();
match key {
CONTRACT_ADDRESS_ATTRIBUTE_KEY => {
contract_address = Some(value.to_string());
}
v if *CONTRACT_ADDRESS_ATTRIBUTE_KEY_BASE64 == v => {
contract_address = Some(String::from_utf8(
BASE64
.decode(value)
.map_err(Into::<HyperlaneCosmosError>::into)?,
)?);
}
MESSAGE_ATTRIBUTE_KEY => {
// Intentionally using read_from to get a Result::Err if there's
// an issue with the message.
let mut reader = Cursor::new(hex::decode(value)?);
message = Some(HyperlaneMessage::read_from(&mut reader)?);
}
v if *MESSAGE_ATTRIBUTE_KEY_BASE64 == v => {
// Intentionally using read_from to get a Result::Err if there's
// an issue with the message.
let mut reader = Cursor::new(hex::decode(String::from_utf8(
BASE64
.decode(value)
.map_err(Into::<HyperlaneCosmosError>::into)?,
)?)?);
message = Some(HyperlaneMessage::read_from(&mut reader)?);
}
_ => {}
}
}
let contract_address = contract_address
.ok_or_else(|| ChainCommunicationError::from_other_str("missing contract_address"))?;
let message =
message.ok_or_else(|| ChainCommunicationError::from_other_str("missing message"))?;
Ok(ParsedEvent::new(contract_address, message))
}
}
#[async_trait]
impl Indexer<HyperlaneMessage> for CosmosMailboxIndexer {
async fn fetch_logs_in_range(
&self,
range: RangeInclusive<u32>,
) -> ChainResult<Vec<(Indexed<HyperlaneMessage>, LogMeta)>> {
let logs_futures: Vec<_> = range
.map(|block_number| {
let self_clone = self.clone();
tokio::spawn(async move {
let logs = self_clone
.indexer
.get_logs_in_block(
block_number,
Self::hyperlane_message_parser,
"HyperlaneMessageCursor",
)
.await;
(logs, block_number)
})
})
.collect();
execute_and_parse_log_futures(logs_futures).await
}
async fn get_finalized_block_number(&self) -> ChainResult<u32> {
self.indexer.get_finalized_block_number().await
}
}
#[async_trait]
impl Indexer<H256> for CosmosMailboxIndexer {
async fn fetch_logs_in_range(
&self,
range: RangeInclusive<u32>,
) -> ChainResult<Vec<(Indexed<H256>, LogMeta)>> {
// TODO: implement when implementing Cosmos scraping
todo!()
}
async fn get_finalized_block_number(&self) -> ChainResult<u32> {
self.indexer.get_finalized_block_number().await
}
}
#[async_trait]
impl SequenceAwareIndexer<H256> for CosmosMailboxIndexer {
async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option<u32>, u32)> {
let tip = Indexer::<H256>::get_finalized_block_number(&self).await?;
// No sequence for message deliveries.
Ok((None, tip))
}
}
#[async_trait]
impl SequenceAwareIndexer<HyperlaneMessage> for CosmosMailboxIndexer {
async fn latest_sequence_count_and_tip(&self) -> ChainResult<(Option<u32>, u32)> {
let tip = Indexer::<HyperlaneMessage>::get_finalized_block_number(&self).await?;
let sequence = self.mailbox.nonce_at_block(Some(tip.into())).await?;
Ok((Some(sequence), tip))
}
}
#[cfg(test)]
mod tests {
use hyperlane_core::HyperlaneMessage;
use crate::{rpc::ParsedEvent, utils::event_attributes_from_str};
use super::*;
#[test]
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 expected = ParsedEvent::new(
"neutron1sjzzd4gwkggy6hrrs8kxxatexzcuz3jecsxm3wqgregkulzj8r7qlnuef4".into(),
HyperlaneMessage::from(hex::decode("03000000006e74726e0000000000000000000000006ba6343a09a60ac048d0e99f50b76fd99eff1063000000a9000000000000000000000000281973b53c9aacec128ac964a6f750fea40912aa48656c6c6f2066726f6d204e657574726f6e204d61696e6e657420746f204d616e74612050616369666963206f63742032392c2031323a353520616d").unwrap()),
);
let assert_parsed_event = |attrs: &Vec<EventAttribute>| {
let parsed_event = CosmosMailboxIndexer::hyperlane_message_parser(attrs).unwrap();
assert_eq!(parsed_event, expected);
};
// Non-base64 version
let non_base64_attrs = event_attributes_from_str(
r#"[{"key":"_contract_address","value":"neutron1sjzzd4gwkggy6hrrs8kxxatexzcuz3jecsxm3wqgregkulzj8r7qlnuef4","index":true},{"key":"sender","value":"0000000000000000000000006ba6343a09a60ac048d0e99f50b76fd99eff1063","index":true},{"key":"destination","value":"169","index":true},{"key":"recipient","value":"000000000000000000000000281973b53c9aacec128ac964a6f750fea40912aa","index":true},{"key":"message","value":"03000000006e74726e0000000000000000000000006ba6343a09a60ac048d0e99f50b76fd99eff1063000000a9000000000000000000000000281973b53c9aacec128ac964a6f750fea40912aa48656c6c6f2066726f6d204e657574726f6e204d61696e6e657420746f204d616e74612050616369666963206f63742032392c2031323a353520616d","index":true}]"#,
);
assert_parsed_event(&non_base64_attrs);
// Base64 version
let base64_attrs = event_attributes_from_str(
r#"[{"key":"X2NvbnRyYWN0X2FkZHJlc3M=","value":"bmV1dHJvbjFzanp6ZDRnd2tnZ3k2aHJyczhreHhhdGV4emN1ejNqZWNzeG0zd3FncmVna3Vsemo4cjdxbG51ZWY0","index":true},{"key":"c2VuZGVy","value":"MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNmJhNjM0M2EwOWE2MGFjMDQ4ZDBlOTlmNTBiNzZmZDk5ZWZmMTA2Mw==","index":true},{"key":"ZGVzdGluYXRpb24=","value":"MTY5","index":true},{"key":"cmVjaXBpZW50","value":"MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMjgxOTczYjUzYzlhYWNlYzEyOGFjOTY0YTZmNzUwZmVhNDA5MTJhYQ==","index":true},{"key":"bWVzc2FnZQ==","value":"MDMwMDAwMDAwMDZlNzQ3MjZlMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNmJhNjM0M2EwOWE2MGFjMDQ4ZDBlOTlmNTBiNzZmZDk5ZWZmMTA2MzAwMDAwMGE5MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMjgxOTczYjUzYzlhYWNlYzEyOGFjOTY0YTZmNzUwZmVhNDA5MTJhYTQ4NjU2YzZjNmYyMDY2NzI2ZjZkMjA0ZTY1NzU3NDcyNmY2ZTIwNGQ2MTY5NmU2ZTY1NzQyMDc0NmYyMDRkNjE2ZTc0NjEyMDUwNjE2MzY5NjY2OTYzMjA2ZjYzNzQyMDMyMzkyYzIwMzEzMjNhMzUzNTIwNjE2ZA==","index":true}]"#,
);
assert_parsed_event(&base64_attrs);
}
}

@ -1,106 +0,0 @@
use async_trait::async_trait;
use hyperlane_core::{
BlockInfo, ChainInfo, ChainResult, ContractLocator, HyperlaneChain, HyperlaneDomain,
HyperlaneProvider, TxnInfo, H256, U256,
};
use tendermint_rpc::{client::CompatMode, HttpClient};
use crate::{ConnectionConf, CosmosAmount, HyperlaneCosmosError, Signer};
use self::grpc::WasmGrpcProvider;
/// cosmos grpc provider
pub mod grpc;
/// cosmos rpc provider
pub mod rpc;
/// Abstraction over a connection to a Cosmos chain
#[derive(Debug, Clone)]
pub struct CosmosProvider {
domain: HyperlaneDomain,
canonical_asset: String,
grpc_client: WasmGrpcProvider,
rpc_client: HttpClient,
}
impl CosmosProvider {
/// Create a reference to a Cosmos chain
pub fn new(
domain: HyperlaneDomain,
conf: ConnectionConf,
locator: Option<ContractLocator>,
signer: Option<Signer>,
) -> ChainResult<Self> {
let gas_price = CosmosAmount::try_from(conf.get_minimum_gas_price().clone())?;
let grpc_client = WasmGrpcProvider::new(
domain.clone(),
conf.clone(),
gas_price.clone(),
locator,
signer,
)?;
let rpc_client = HttpClient::builder(
conf.get_rpc_url()
.parse()
.map_err(Into::<HyperlaneCosmosError>::into)?,
)
// Consider supporting different compatibility modes.
.compat_mode(CompatMode::latest())
.build()
.map_err(Into::<HyperlaneCosmosError>::into)?;
Ok(Self {
domain,
rpc_client,
grpc_client,
canonical_asset: conf.get_canonical_asset(),
})
}
/// Get a grpc client
pub fn grpc(&self) -> &WasmGrpcProvider {
&self.grpc_client
}
/// Get an rpc client
pub fn rpc(&self) -> &HttpClient {
&self.rpc_client
}
}
impl HyperlaneChain for CosmosProvider {
fn domain(&self) -> &HyperlaneDomain {
&self.domain
}
fn provider(&self) -> Box<dyn HyperlaneProvider> {
Box::new(self.clone())
}
}
#[async_trait]
impl HyperlaneProvider for CosmosProvider {
async fn get_block_by_hash(&self, _hash: &H256) -> ChainResult<BlockInfo> {
todo!() // FIXME
}
async fn get_txn_by_hash(&self, _hash: &H256) -> ChainResult<TxnInfo> {
todo!() // FIXME
}
async fn is_contract(&self, _address: &H256) -> ChainResult<bool> {
// FIXME
Ok(true)
}
async fn get_balance(&self, address: String) -> ChainResult<U256> {
Ok(self
.grpc_client
.get_balance(address, self.canonical_asset.clone())
.await?)
}
async fn get_chain_metrics(&self) -> ChainResult<Option<ChainInfo>> {
Ok(None)
}
}

@ -1,54 +0,0 @@
use hyperlane_core::{config::OperationBatchConfig, U256};
use url::Url;
/// Ethereum RPC connection configuration
#[derive(Debug, Clone)]
pub enum RpcConnectionConf {
/// An HTTP-only quorum.
HttpQuorum {
/// List of urls to connect to
urls: Vec<Url>,
},
/// An HTTP-only fallback set.
HttpFallback {
/// List of urls to connect to in order of priority
urls: Vec<Url>,
},
/// HTTP connection details
Http {
/// Url to connect to
url: Url,
},
/// Websocket connection details
Ws {
/// Url to connect to
url: Url,
},
}
/// Ethereum connection configuration
#[derive(Debug, Clone)]
pub struct ConnectionConf {
/// RPC connection configuration
pub rpc_connection: RpcConnectionConf,
/// Transaction overrides to use when sending transactions.
pub transaction_overrides: TransactionOverrides,
/// Operation batching configuration
pub operation_batch: OperationBatchConfig,
}
/// Ethereum transaction overrides.
#[derive(Debug, Clone, Default)]
pub struct TransactionOverrides {
/// Gas price to use for transactions, in wei.
/// If specified, non-1559 transactions will be used with this gas price.
pub gas_price: Option<U256>,
/// Gas limit to use for transactions.
/// If unspecified, the gas limit will be estimated.
/// If specified, transactions will use `max(estimated_gas, gas_limit)`
pub gas_limit: Option<U256>,
/// Max fee per gas to use for EIP-1559 transactions.
pub max_fee_per_gas: Option<U256>,
/// Max priority fee per gas to use for EIP-1559 transactions.
pub max_priority_fee_per_gas: Option<U256>,
}

@ -1,530 +0,0 @@
{
"types": [
{
"typeId": 0,
"type": "()",
"components": [],
"typeParameters": null
},
{
"typeId": 1,
"type": "(_, _)",
"components": [
{
"name": "__tuple_element",
"type": 2,
"typeArguments": null
},
{
"name": "__tuple_element",
"type": 20,
"typeArguments": null
}
],
"typeParameters": null
},
{
"typeId": 2,
"type": "b256",
"components": null,
"typeParameters": null
},
{
"typeId": 3,
"type": "bool",
"components": null,
"typeParameters": null
},
{
"typeId": 4,
"type": "enum Identity",
"components": [
{
"name": "Address",
"type": 14,
"typeArguments": null
},
{
"name": "ContractId",
"type": 15,
"typeArguments": null
}
],
"typeParameters": null
},
{
"typeId": 5,
"type": "enum Option",
"components": [
{
"name": "None",
"type": 0,
"typeArguments": null
},
{
"name": "Some",
"type": 6,
"typeArguments": null
}
],
"typeParameters": [
6
]
},
{
"typeId": 6,
"type": "generic T",
"components": null,
"typeParameters": null
},
{
"typeId": 7,
"type": "raw untyped ptr",
"components": null,
"typeParameters": null
},
{
"typeId": 8,
"type": "str[12]",
"components": null,
"typeParameters": null
},
{
"typeId": 9,
"type": "str[16]",
"components": null,
"typeParameters": null
},
{
"typeId": 10,
"type": "str[6]",
"components": null,
"typeParameters": null
},
{
"typeId": 11,
"type": "str[7]",
"components": null,
"typeParameters": null
},
{
"typeId": 12,
"type": "str[8]",
"components": null,
"typeParameters": null
},
{
"typeId": 13,
"type": "str[9]",
"components": null,
"typeParameters": null
},
{
"typeId": 14,
"type": "struct Address",
"components": [
{
"name": "value",
"type": 2,
"typeArguments": null
}
],
"typeParameters": null
},
{
"typeId": 15,
"type": "struct ContractId",
"components": [
{
"name": "value",
"type": 2,
"typeArguments": null
}
],
"typeParameters": null
},
{
"typeId": 16,
"type": "struct Message",
"components": [
{
"name": "version",
"type": 22,
"typeArguments": null
},
{
"name": "nonce",
"type": 20,
"typeArguments": null
},
{
"name": "origin",
"type": 20,
"typeArguments": null
},
{
"name": "sender",
"type": 2,
"typeArguments": null
},
{
"name": "destination",
"type": 20,
"typeArguments": null
},
{
"name": "recipient",
"type": 2,
"typeArguments": null
},
{
"name": "body",
"type": 19,
"typeArguments": [
{
"name": "",
"type": 22,
"typeArguments": null
}
]
}
],
"typeParameters": null
},
{
"typeId": 17,
"type": "struct OwnershipTransferredEvent",
"components": [
{
"name": "previous_owner",
"type": 5,
"typeArguments": [
{
"name": "",
"type": 4,
"typeArguments": null
}
]
},
{
"name": "new_owner",
"type": 5,
"typeArguments": [
{
"name": "",
"type": 4,
"typeArguments": null
}
]
}
],
"typeParameters": null
},
{
"typeId": 18,
"type": "struct RawVec",
"components": [
{
"name": "ptr",
"type": 7,
"typeArguments": null
},
{
"name": "cap",
"type": 21,
"typeArguments": null
}
],
"typeParameters": [
6
]
},
{
"typeId": 19,
"type": "struct Vec",
"components": [
{
"name": "buf",
"type": 18,
"typeArguments": [
{
"name": "",
"type": 6,
"typeArguments": null
}
]
},
{
"name": "len",
"type": 21,
"typeArguments": null
}
],
"typeParameters": [
6
]
},
{
"typeId": 20,
"type": "u32",
"components": null,
"typeParameters": null
},
{
"typeId": 21,
"type": "u64",
"components": null,
"typeParameters": null
},
{
"typeId": 22,
"type": "u8",
"components": null,
"typeParameters": null
}
],
"functions": [
{
"inputs": [],
"name": "count",
"output": {
"name": "",
"type": 20,
"typeArguments": null
}
},
{
"inputs": [
{
"name": "message_id",
"type": 2,
"typeArguments": null
}
],
"name": "delivered",
"output": {
"name": "",
"type": 3,
"typeArguments": null
}
},
{
"inputs": [
{
"name": "destination_domain",
"type": 20,
"typeArguments": null
},
{
"name": "recipient",
"type": 2,
"typeArguments": null
},
{
"name": "message_body",
"type": 19,
"typeArguments": [
{
"name": "",
"type": 22,
"typeArguments": null
}
]
}
],
"name": "dispatch",
"output": {
"name": "",
"type": 2,
"typeArguments": null
}
},
{
"inputs": [],
"name": "get_default_ism",
"output": {
"name": "",
"type": 15,
"typeArguments": null
}
},
{
"inputs": [],
"name": "latest_checkpoint",
"output": {
"name": "",
"type": 1,
"typeArguments": null
}
},
{
"inputs": [
{
"name": "metadata",
"type": 19,
"typeArguments": [
{
"name": "",
"type": 22,
"typeArguments": null
}
]
},
{
"name": "_message",
"type": 16,
"typeArguments": null
}
],
"name": "process",
"output": {
"name": "",
"type": 0,
"typeArguments": null
}
},
{
"inputs": [],
"name": "root",
"output": {
"name": "",
"type": 2,
"typeArguments": null
}
},
{
"inputs": [
{
"name": "module",
"type": 15,
"typeArguments": null
}
],
"name": "set_default_ism",
"output": {
"name": "",
"type": 0,
"typeArguments": null
}
},
{
"inputs": [],
"name": "owner",
"output": {
"name": "",
"type": 5,
"typeArguments": [
{
"name": "",
"type": 4,
"typeArguments": null
}
]
}
},
{
"inputs": [
{
"name": "new_owner",
"type": 5,
"typeArguments": [
{
"name": "",
"type": 4,
"typeArguments": null
}
]
}
],
"name": "transfer_ownership",
"output": {
"name": "",
"type": 0,
"typeArguments": null
}
}
],
"loggedTypes": [
{
"logId": 0,
"loggedType": {
"name": "",
"type": 8,
"typeArguments": null
}
},
{
"logId": 1,
"loggedType": {
"name": "",
"type": 9,
"typeArguments": null
}
},
{
"logId": 2,
"loggedType": {
"name": "",
"type": 12,
"typeArguments": null
}
},
{
"logId": 3,
"loggedType": {
"name": "",
"type": 8,
"typeArguments": null
}
},
{
"logId": 4,
"loggedType": {
"name": "",
"type": 13,
"typeArguments": null
}
},
{
"logId": 5,
"loggedType": {
"name": "",
"type": 11,
"typeArguments": null
}
},
{
"logId": 6,
"loggedType": {
"name": "",
"type": 2,
"typeArguments": null
}
},
{
"logId": 7,
"loggedType": {
"name": "",
"type": 10,
"typeArguments": null
}
},
{
"logId": 8,
"loggedType": {
"name": "",
"type": 10,
"typeArguments": null
}
},
{
"logId": 9,
"loggedType": {
"name": "",
"type": 17,
"typeArguments": []
}
}
],
"messagesTypes": []
}

@ -1,164 +0,0 @@
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use std::num::NonZeroU64;
use std::ops::RangeInclusive;
use async_trait::async_trait;
use fuels::prelude::{Bech32ContractId, WalletUnlocked};
use hyperlane_core::Indexed;
use tracing::instrument;
use hyperlane_core::{
utils::bytes_to_hex, ChainCommunicationError, ChainResult, ContractLocator, HyperlaneAbi,
HyperlaneChain, HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider,
Indexer, LogMeta, Mailbox, TxCostEstimate, TxOutcome, H256, U256,
};
use crate::{
contracts::mailbox::Mailbox as FuelMailboxInner, conversions::*, make_provider, ConnectionConf,
};
/// A reference to a Mailbox contract on some Fuel chain
pub struct FuelMailbox {
contract: FuelMailboxInner,
domain: HyperlaneDomain,
}
impl FuelMailbox {
/// Create a new fuel mailbox
pub fn new(
conf: &ConnectionConf,
locator: ContractLocator,
mut wallet: WalletUnlocked,
) -> ChainResult<Self> {
let provider = make_provider(conf)?;
wallet.set_provider(provider);
let address = Bech32ContractId::from_h256(&locator.address);
Ok(FuelMailbox {
contract: FuelMailboxInner::new(address, wallet),
domain: locator.domain.clone(),
})
}
}
impl HyperlaneContract for FuelMailbox {
fn address(&self) -> H256 {
self.contract.contract_id().into_h256()
}
}
impl HyperlaneChain for FuelMailbox {
fn domain(&self) -> &HyperlaneDomain {
&self.domain
}
fn provider(&self) -> Box<dyn HyperlaneProvider> {
todo!()
}
}
impl Debug for FuelMailbox {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self as &dyn HyperlaneContract)
}
}
#[async_trait]
impl Mailbox for FuelMailbox {
#[instrument(level = "debug", err, ret, skip(self))]
async fn count(&self, lag: Option<NonZeroU64>) -> ChainResult<u32> {
assert!(
lag.is_none(),
"Fuel does not support querying point-in-time"
);
self.contract
.methods()
.count()
.simulate()
.await
.map(|r| r.value)
.map_err(ChainCommunicationError::from_other)
}
#[instrument(level = "debug", err, ret, skip(self))]
async fn delivered(&self, id: H256) -> ChainResult<bool> {
todo!()
}
#[instrument(err, ret, skip(self))]
async fn default_ism(&self) -> ChainResult<H256> {
todo!()
}
#[instrument(err, ret, skip(self))]
async fn recipient_ism(&self, recipient: H256) -> ChainResult<H256> {
todo!()
}
#[instrument(err, ret, skip(self))]
async fn process(
&self,
message: &HyperlaneMessage,
metadata: &[u8],
tx_gas_limit: Option<U256>,
) -> ChainResult<TxOutcome> {
todo!()
}
#[instrument(err, ret, skip(self), fields(msg=%message, metadata=%bytes_to_hex(metadata)))]
async fn process_estimate_costs(
&self,
message: &HyperlaneMessage,
metadata: &[u8],
) -> ChainResult<TxCostEstimate> {
todo!()
}
fn process_calldata(&self, message: &HyperlaneMessage, metadata: &[u8]) -> Vec<u8> {
todo!()
}
}
/// Struct that retrieves event data for a Fuel Mailbox contract
#[derive(Debug)]
pub struct FuelMailboxIndexer {}
#[async_trait]
impl Indexer<HyperlaneMessage> for FuelMailboxIndexer {
async fn fetch_logs_in_range(
&self,
range: RangeInclusive<u32>,
) -> ChainResult<Vec<(Indexed<HyperlaneMessage>, LogMeta)>> {
todo!()
}
async fn get_finalized_block_number(&self) -> ChainResult<u32> {
todo!()
}
}
#[async_trait]
impl Indexer<H256> for FuelMailboxIndexer {
async fn fetch_logs_in_range(
&self,
range: RangeInclusive<u32>,
) -> ChainResult<Vec<(Indexed<H256>, LogMeta)>> {
todo!()
}
async fn get_finalized_block_number(&self) -> ChainResult<u32> {
todo!()
}
}
struct FuelMailboxAbi;
impl HyperlaneAbi for FuelMailboxAbi {
const SELECTOR_SIZE_BYTES: usize = 8;
fn fn_map() -> HashMap<Vec<u8>, &'static str> {
// Can't support this without Fuels exporting it in the generated code
todo!()
}
}

@ -1,43 +0,0 @@
use async_trait::async_trait;
use hyperlane_core::{
BlockInfo, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo,
H256, U256,
};
/// A wrapper around a fuel provider to get generic blockchain information.
#[derive(Debug)]
pub struct FuelProvider {}
impl HyperlaneChain for FuelProvider {
fn domain(&self) -> &HyperlaneDomain {
todo!()
}
fn provider(&self) -> Box<dyn HyperlaneProvider> {
todo!()
}
}
#[async_trait]
impl HyperlaneProvider for FuelProvider {
async fn get_block_by_hash(&self, hash: &H256) -> ChainResult<BlockInfo> {
todo!()
}
async fn get_txn_by_hash(&self, hash: &H256) -> ChainResult<TxnInfo> {
todo!()
}
async fn is_contract(&self, address: &H256) -> ChainResult<bool> {
todo!()
}
async fn get_balance(&self, address: String) -> ChainResult<U256> {
todo!()
}
async fn get_chain_metrics(&self) -> ChainResult<Option<ChainInfo>> {
Ok(None)
}
}

@ -1,35 +0,0 @@
cargo-features = ["workspace-inheritance"]
[package]
name = "hyperlane-sealevel"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
async-trait.workspace = true
base64.workspace = true
borsh.workspace = true
derive-new.workspace = true
jsonrpc-core.workspace = true
num-traits.workspace = true
serde.workspace = true
solana-account-decoder.workspace = true
solana-client.workspace = true
solana-sdk.workspace = true
solana-transaction-status.workspace = true
thiserror.workspace = true
tracing-futures.workspace = true
tracing.workspace = true
url.workspace = true
account-utils = { path = "../../sealevel/libraries/account-utils" }
hyperlane-core = { path = "../../hyperlane-core", features = ["solana", "async"] }
hyperlane-sealevel-interchain-security-module-interface = { path = "../../sealevel/libraries/interchain-security-module-interface" }
hyperlane-sealevel-mailbox = { path = "../../sealevel/programs/mailbox", features = ["no-entrypoint"] }
hyperlane-sealevel-igp = { path = "../../sealevel/programs/hyperlane-sealevel-igp", features = ["no-entrypoint"] }
hyperlane-sealevel-message-recipient-interface = { path = "../../sealevel/libraries/message-recipient-interface" }
hyperlane-sealevel-multisig-ism-message-id = { path = "../../sealevel/programs/ism/multisig-ism-message-id", features = ["no-entrypoint"] }
hyperlane-sealevel-validator-announce = { path = "../../sealevel/programs/validator-announce", features = ["no-entrypoint"] }
multisig-ism = { path = "../../sealevel/libraries/multisig-ism" }
serializable-account-meta = { path = "../../sealevel/libraries/serializable-account-meta" }

@ -1,29 +0,0 @@
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::commitment_config::CommitmentConfig;
/// Kludge to implement Debug for RpcClient.
pub struct RpcClientWithDebug(RpcClient);
impl RpcClientWithDebug {
pub fn new(rpc_endpoint: String) -> Self {
Self(RpcClient::new(rpc_endpoint))
}
pub fn new_with_commitment(rpc_endpoint: String, commitment: CommitmentConfig) -> Self {
Self(RpcClient::new_with_commitment(rpc_endpoint, commitment))
}
}
impl std::fmt::Debug for RpcClientWithDebug {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("RpcClient { ... }")
}
}
impl std::ops::Deref for RpcClientWithDebug {
type Target = RpcClient;
fn deref(&self) -> &Self::Target {
&self.0
}
}

@ -1,23 +0,0 @@
use hyperlane_core::ChainCommunicationError;
use solana_client::client_error::ClientError;
use solana_sdk::pubkey::ParsePubkeyError;
/// Errors from the crates specific to the hyperlane-sealevel
/// 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 HyperlaneSealevelError {
/// ParsePubkeyError error
#[error("{0}")]
ParsePubkeyError(#[from] ParsePubkeyError),
/// ClientError error
#[error("{0}")]
ClientError(#[from] ClientError),
}
impl From<HyperlaneSealevelError> for ChainCommunicationError {
fn from(value: HyperlaneSealevelError) -> Self {
ChainCommunicationError::from_other(value)
}
}

@ -1,84 +0,0 @@
use std::{str::FromStr, sync::Arc};
use async_trait::async_trait;
use hyperlane_core::{
BlockInfo, ChainInfo, ChainResult, HyperlaneChain, HyperlaneDomain, HyperlaneProvider, TxnInfo,
H256, U256,
};
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey};
use crate::{client::RpcClientWithDebug, error::HyperlaneSealevelError, ConnectionConf};
/// A wrapper around a Sealevel provider to get generic blockchain information.
#[derive(Debug)]
pub struct SealevelProvider {
domain: HyperlaneDomain,
rpc_client: Arc<RpcClientWithDebug>,
}
impl SealevelProvider {
/// Create a new Sealevel provider.
pub fn new(domain: HyperlaneDomain, conf: &ConnectionConf) -> Self {
// Set the `processed` commitment at rpc level
let rpc_client = Arc::new(RpcClientWithDebug::new_with_commitment(
conf.url.to_string(),
CommitmentConfig::processed(),
));
SealevelProvider { domain, rpc_client }
}
/// Get an rpc client
pub fn rpc(&self) -> &RpcClientWithDebug {
&self.rpc_client
}
/// Get the balance of an address
pub async fn get_balance(&self, address: String) -> ChainResult<U256> {
let pubkey = Pubkey::from_str(&address).map_err(Into::<HyperlaneSealevelError>::into)?;
let balance = self
.rpc_client
.get_balance(&pubkey)
.await
.map_err(Into::<HyperlaneSealevelError>::into)?;
Ok(balance.into())
}
}
impl HyperlaneChain for SealevelProvider {
fn domain(&self) -> &HyperlaneDomain {
&self.domain
}
fn provider(&self) -> Box<dyn HyperlaneProvider> {
Box::new(SealevelProvider {
domain: self.domain.clone(),
rpc_client: self.rpc_client.clone(),
})
}
}
#[async_trait]
impl HyperlaneProvider for SealevelProvider {
async fn get_block_by_hash(&self, _hash: &H256) -> ChainResult<BlockInfo> {
todo!() // FIXME
}
async fn get_txn_by_hash(&self, _hash: &H256) -> ChainResult<TxnInfo> {
todo!() // FIXME
}
async fn is_contract(&self, _address: &H256) -> ChainResult<bool> {
// FIXME
Ok(true)
}
async fn get_balance(&self, address: String) -> ChainResult<U256> {
self.get_balance(address).await
}
async fn get_chain_metrics(&self) -> ChainResult<Option<ChainInfo>> {
Ok(None)
}
}

@ -1,93 +0,0 @@
use base64::Engine;
use borsh::{BorshDeserialize, BorshSerialize};
use hyperlane_core::{ChainCommunicationError, ChainResult};
use serializable_account_meta::{SerializableAccountMeta, SimulationReturnData};
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::{
commitment_config::CommitmentConfig,
instruction::{AccountMeta, Instruction},
message::Message,
signature::{Keypair, Signer},
transaction::Transaction,
};
use solana_transaction_status::UiReturnDataEncoding;
use crate::client::RpcClientWithDebug;
/// Simulates an instruction, and attempts to deserialize it into a T.
/// If no return data at all was returned, returns Ok(None).
/// If some return data was returned but deserialization was unsuccessful,
/// an Err is returned.
pub async fn simulate_instruction<T: BorshDeserialize + BorshSerialize>(
rpc_client: &RpcClient,
payer: &Keypair,
instruction: Instruction,
) -> ChainResult<Option<T>> {
let commitment = CommitmentConfig::finalized();
let (recent_blockhash, _) = rpc_client
.get_latest_blockhash_with_commitment(commitment)
.await
.map_err(ChainCommunicationError::from_other)?;
let return_data = rpc_client
.simulate_transaction(&Transaction::new_unsigned(Message::new_with_blockhash(
&[instruction],
Some(&payer.pubkey()),
&recent_blockhash,
)))
.await
.map_err(ChainCommunicationError::from_other)?
.value
.return_data;
if let Some(return_data) = return_data {
let bytes = match return_data.data.1 {
UiReturnDataEncoding::Base64 => base64::engine::general_purpose::STANDARD
.decode(return_data.data.0)
.map_err(ChainCommunicationError::from_other)?,
};
let decoded_data =
T::try_from_slice(bytes.as_slice()).map_err(ChainCommunicationError::from_other)?;
return Ok(Some(decoded_data));
}
Ok(None)
}
/// Simulates an Instruction that will return a list of AccountMetas.
pub async fn get_account_metas(
rpc_client: &RpcClient,
payer: &Keypair,
instruction: Instruction,
) -> ChainResult<Vec<AccountMeta>> {
// If there's no data at all, default to an empty vec.
let account_metas = simulate_instruction::<SimulationReturnData<Vec<SerializableAccountMeta>>>(
rpc_client,
payer,
instruction,
)
.await?
.map(|serializable_account_metas| {
serializable_account_metas
.return_data
.into_iter()
.map(|serializable_account_meta| serializable_account_meta.into())
.collect()
})
.unwrap_or_else(Vec::new);
Ok(account_metas)
}
pub async fn get_finalized_block_number(rpc_client: &RpcClientWithDebug) -> ChainResult<u32> {
let height = rpc_client
.get_block_height()
.await
.map_err(ChainCommunicationError::from_other)?
.try_into()
// FIXME solana block height is u64...
.expect("sealevel block height exceeds u32::MAX");
Ok(height)
}

@ -1,974 +0,0 @@
{
"chains": {
"alfajores": {
"aggregationHook": "0xdBabD76358897E68E4964647C1fb8Bf524f5EFdB",
"blockExplorers": [
{
"apiUrl": "https://api-alfajores.celoscan.io/api",
"family": "etherscan",
"name": "CeloScan",
"url": "https://alfajores.celoscan.io"
},
{
"apiUrl": "https://explorer.celo.org/alfajores/api",
"family": "blockscout",
"name": "Blockscout",
"url": "https://explorer.celo.org/alfajores"
}
],
"blocks": {
"confirmations": 1,
"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": "0xEE179dd1b1beD39449e81c003D9629e92A5c0085",
"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"
},
"arbitrumsepolia": {
"aggregationHook": "0xD2670EedcD21116c6F0B331Ce391eA4B3Bf1aB19",
"blockExplorers": [
{
"apiUrl": "https://api-sepolia.arbiscan.io/api",
"family": "etherscan",
"name": "Arbiscan",
"url": "https://sepolia.arbiscan.io"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 3,
"reorgPeriod": 0
},
"chainId": 421614,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Arbitrum Sepolia",
"domainId": 421614,
"domainRoutingIsm": "0xaCA2f65aFDa2cbC8BF28DdE096eCF83aCd121c0b",
"domainRoutingIsmFactory": "0xd785272D240B07719e417622cbd2cfA0E584d1bd",
"fallbackRoutingHook": "0xAb9B273366D794B7F80B4378bc8Aaca75C6178E2",
"index": {
"from": 49690504
},
"interchainGasPaymaster": "0xc756cFc1b7d0d4646589EDf10eD54b201237F5e8",
"interchainSecurityModule": "0x29cEAFEE1F76B9CE271750f86B2bD12C23F9dDb6",
"isTestnet": true,
"mailbox": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8",
"merkleTreeHook": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C",
"name": "arbitrumsepolia",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"pausableHook": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD",
"pausableIsm": "0x54ba950390670f27892AFFC670Ba6ed598E5B8Df",
"protocol": "ethereum",
"protocolFee": "0x75f3E2a4f424401195A5E176246Ecc9f7e7680ff",
"proxyAdmin": "0x666a24F62f7A97BA33c151776Eb3D9441a059eB8",
"rpcUrls": [
{
"http": "https://public.stackup.sh/api/v1/node/arbitrum-sepolia"
},
{
"http": "https://arbitrum-sepolia.blockpi.network/v1/rpc/public"
}
],
"staticAggregationHookFactory": "0x0526E47C49742C15F8817ef8cf0d8FFc72139D4F",
"staticAggregationIsm": "0x29cEAFEE1F76B9CE271750f86B2bD12C23F9dDb6",
"staticAggregationIsmFactory": "0xfc8d0D2E15A36f1A3F3aE3Cb127B706c1f23Aadc",
"staticMerkleRootMultisigIsmFactory": "0x1D5EbC3e15e9ECDe0e3530C85899556797eeaea5",
"staticMessageIdMultisigIsmFactory": "0xF7F0DaB0BECE4498dAc7eb616e288809D4499371",
"storageGasOracle": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75",
"technicalStack": "arbitrumnitro",
"testRecipient": "0x6c13643B3927C57DB92c790E4E3E7Ee81e13f78C",
"validatorAnnounce": "0x1b33611fCc073aB0737011d5512EF673Bff74962"
},
"basesepolia": {
"aggregationHook": "0xccA408a6A9A6dc405C3278647421eb4317466943",
"blockExplorers": [
{
"apiUrl": "https://api-sepolia.basescan.org/api",
"family": "etherscan",
"name": "BaseScan",
"url": "https://sepolia.basescan.org"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 2,
"reorgPeriod": 1
},
"chainId": 84532,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Base Sepolia",
"domainId": 84532,
"domainRoutingIsm": "0x4ac19e0bafc2aF6B98094F0a1B817dF196551219",
"domainRoutingIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37",
"fallbackRoutingHook": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75",
"index": {
"from": 13851043
},
"interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564",
"interchainSecurityModule": "0x815a9642497Ee1E9F061f8b828C85Eb7193DecfC",
"isTestnet": true,
"mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"merkleTreeHook": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD",
"name": "basesepolia",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"pausableHook": "0x19Be55D859368e02d7b9C00803Eb677BDC1359Bd",
"pausableIsm": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C",
"protocol": "ethereum",
"protocolFee": "0x1b33611fCc073aB0737011d5512EF673Bff74962",
"proxyAdmin": "0x44b764045BfDC68517e10e783E69B376cef196B2",
"rpcUrls": [
{
"http": "https://sepolia.base.org"
},
{
"http": "https://base-sepolia-rpc.publicnode.com"
}
],
"staticAggregationHookFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44",
"staticAggregationIsm": "0x815a9642497Ee1E9F061f8b828C85Eb7193DecfC",
"staticAggregationIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7",
"staticMerkleRootMultisigIsmFactory": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8",
"staticMessageIdMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213",
"storageGasOracle": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8",
"testRecipient": "0x783c4a0bB6663359281aD4a637D5af68F83ae213",
"validatorAnnounce": "0x20c44b1E3BeaDA1e9826CFd48BeEDABeE9871cE9"
},
"bsctestnet": {
"aggregationHook": "0x3d675bB93250Ab7603F40cbb9194bae210784627",
"blockExplorers": [
{
"apiUrl": "https://api-testnet.bscscan.com/api",
"family": "etherscan",
"name": "BscScan",
"url": "https://testnet.bscscan.com"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 3,
"reorgPeriod": 9
},
"chainId": 97,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "BSC Testnet",
"domainId": 97,
"domainRoutingIsm": "0x82A4aac9bED3DFDfb9569031E19d18431204681C",
"domainRoutingIsmFactory": "0xD2a0c68ed92D1Eb3C699D2808b06dd7b70367F92",
"fallbackRoutingHook": "0x2670ED2EC08cAd135307556685a96bD4c16b007b",
"index": {
"chunk": 1000,
"from": 34323977
},
"interchainAccountIsm": "0xa9D8Ec959F34272B1a56D09AF00eeee58970d3AE",
"interchainAccountRouter": "0x6d2B3e304E58c2a19f1492E7cf15CaF63Ce6e0d2",
"interchainGasPaymaster": "0x0dD20e410bdB95404f71c5a4e7Fa67B892A5f949",
"interchainSecurityModule": "0xE758870D4E50c2aF2b03341808d54d79F5ec3c1E",
"isTestnet": true,
"mailbox": "0xF9F6F5646F478d5ab4e20B0F910C92F1CCC9Cc6D",
"merkleTreeHook": "0xc6cbF39A747f5E28d1bDc8D9dfDAb2960Abd5A8f",
"name": "bsctestnet",
"nativeToken": {
"decimals": 18,
"name": "BNB",
"symbol": "BNB"
},
"pausableHook": "0xA71E50eFd93600933650A324AE43d395a8aE4AC7",
"pausableIsm": "0xb98B1596897d3c122111ea6b6b9014A95920F459",
"protocol": "ethereum",
"protocolFee": "0x3eF0a63B8976b838704Bcc93C78C56b6653E5a39",
"proxyAdmin": "0xb12282d2E838Aa5f2A4F9Ee5f624a77b7199A078",
"rpcUrls": [
{
"http": "https://bsc-testnet.publicnode.com"
},
{
"http": "https://bsc-testnet.blockpi.network/v1/rpc/public"
}
],
"staticAggregationHookFactory": "0xa1145B39F1c7Ef9aA593BC1DB1634b00CC020942",
"staticAggregationIsm": "0x8fb481f65d04c590b8507F75D05Ed29594590376",
"staticAggregationIsmFactory": "0x40613dE82d672605Ab051C64079022Bb4F8bDE4f",
"staticMerkleRootMultisigIsmFactory": "0x3E235B90197E1D6b5DB5ad5aD49f2c1ED6406382",
"staticMessageIdMultisigIsmFactory": "0x0D96aF0c01c4bbbadaaF989Eb489c8783F35B763",
"storageGasOracle": "0x124EBCBC018A5D4Efe639f02ED86f95cdC3f6498",
"testRecipient": "0xfbcD1c00a3d809f36cC1A15918694B17B32c0b6c",
"testTokenRecipient": "0x260f6024119549a40595d0937471e607411E8ea5",
"timelockController": "0x0000000000000000000000000000000000000000",
"transactionOverrides": {
"gasPrice": 8000000000
},
"validatorAnnounce": "0xf09701B0a93210113D175461b6135a96773B5465"
},
"connextsepolia": {
"aggregationHook": "0x331eb40963dc11F5BB271308c42d97ac6e41F124",
"blockExplorers": [
{
"apiUrl": "https://connext-sepolia.blockscout.com/api",
"family": "blockscout",
"name": "Connext Explorer",
"url": "https://connext-sepolia.blockscout.com"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 10,
"reorgPeriod": 0
},
"chainId": 6398,
"deployer": {
"name": "Everclear",
"url": "https://everclear.org"
},
"displayName": "Connext Sepolia",
"domainId": 6398,
"domainRoutingIsm": "0x4ac19e0bafc2aF6B98094F0a1B817dF196551219",
"domainRoutingIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37",
"fallbackRoutingHook": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE",
"index": {
"from": 4950
},
"interchainGasPaymaster": "0xeC7eb4196Bd601DEa7585A744FbFB4CF11278450",
"interchainSecurityModule": "0x415a45C98288059Cce9c32AE4a09AB19C91d5056",
"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"
},
"eclipsetestnet": {
"blocks": {
"confirmations": 1,
"estimateBlockTime": 0.4,
"reorgPeriod": 0
},
"chainId": 239092742,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Eclipse Testnet",
"domainId": 239092742,
"index": {
"from": 1,
"mode": "sequence"
},
"interchainGasPaymaster": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy",
"isTestnet": true,
"mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"merkleTreeHook": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"name": "eclipsetestnet",
"nativeToken": {
"decimals": 9,
"name": "Ether",
"symbol": "ETH"
},
"protocol": "sealevel",
"rpcUrls": [
{
"http": "https://testnet.dev2.eclipsenetwork.xyz"
}
],
"validatorAnnounce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3"
},
"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": "0x815a9642497Ee1E9F061f8b828C85Eb7193DecfC",
"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"
},
"fuji": {
"aggregationHook": "0x8E9b4006171c6B75111823e7545Ee5400CEce0B3",
"blockExplorers": [
{
"apiUrl": "https://api.routescan.io/v2/network/testnet/evm/43113/etherscan/api",
"family": "etherscan",
"name": "SnowTrace",
"url": "https://testnet.snowtrace.io"
}
],
"blocks": {
"confirmations": 3,
"estimateBlockTime": 2,
"reorgPeriod": 3
},
"chainId": 43113,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Fuji",
"domainId": 43113,
"domainRoutingIsm": "0xe82EbDCC1B546CEDa0cf5B5495728700f6dE41B4",
"domainRoutingIsmFactory": "0x683a81E0e1a238dcA7341e04c08d3bba6f0Cb74f",
"fallbackRoutingHook": "0xc684f7F50DB4b2563218512e021fBdd0BeD6b57E",
"index": {
"from": 26503317
},
"interchainAccountIsm": "0xfaB4815BDC5c60c6bD625459C8577aFdD79D9311",
"interchainAccountRouter": "0xeEF6933122894fF217a7dd07510b3D64b747e29b",
"interchainGasPaymaster": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E",
"interchainSecurityModule": "0x3dA17519c799f86000FADb7bfCCdc0CaB0D36fDd",
"isTestnet": true,
"mailbox": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0",
"merkleTreeHook": "0x9ff6ac3dAf63103620BBf76136eA1AFf43c2F612",
"name": "fuji",
"nativeToken": {
"decimals": 18,
"name": "Avalanche",
"symbol": "AVAX"
},
"pausableHook": "0x495e9E119b2aa848b418EF6A4d30b42803de43A9",
"pausableIsm": "0x8e3D9139547be48942Aa4a489bc250FEa51679fa",
"protocol": "ethereum",
"protocolFee": "0xEbA64c8a9b4a61a9210d5fe7E4375380999C821b",
"proxyAdmin": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477",
"rpcUrls": [
{
"http": "https://api.avax-test.network/ext/bc/C/rpc",
"pagination": {
"maxBlockRange": 2048
}
}
],
"staticAggregationHookFactory": "0x99554CC33cBCd6EDDd2f3fc9c7C9194Cb3b5df1E",
"staticAggregationIsm": "0xdDB75f480841A8333A31c4198ecf1780936B222D",
"staticAggregationIsmFactory": "0xF588129ed84F219A1f0f921bE7Aa1B2176516858",
"staticMerkleRootMultisigIsmFactory": "0x93F50Ac4E5663DAAb03508008d592f6260964f62",
"staticMessageIdMultisigIsmFactory": "0x90e1F9918F304645e4F6324E5C0EAc70138C84Ce",
"storageGasOracle": "0x9305dE34306886d615B096Bdf23b94a978f6a6c0",
"testRecipient": "0x44a7e1d76fD8AfA244AdE7278336E3D5C658D398",
"testTokenRecipient": "0x9CC10c844B3Bbae2444E39991aB027C4A05D1F2e",
"timelockController": "0x0000000000000000000000000000000000000000",
"validatorAnnounce": "0x4f7179A691F8a684f56cF7Fed65171877d30739a"
},
"holesky": {
"aggregationHook": "0xb1FfD51f03c69A0a3e5AFEBDE639752DB1d56bc9",
"blockExplorers": [
{
"apiUrl": "https://api-holesky.etherscan.io/api",
"family": "etherscan",
"name": "Etherscan",
"url": "https://holesky.etherscan.io"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 13,
"reorgPeriod": 2
},
"chainId": 17000,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Holesky",
"domainId": 17000,
"domainRoutingIsm": "0x51890869940b4B7eD7426A612676eC63223eF3Db",
"domainRoutingIsmFactory": "0xDDcFEcF17586D08A5740B7D91735fcCE3dfe3eeD",
"fallbackRoutingHook": "0x07009DA2249c388aD0f416a235AfE90D784e1aAc",
"index": {
"from": 1543015
},
"interchainGasPaymaster": "0x5CBf4e70448Ed46c2616b04e9ebc72D29FF0cfA9",
"interchainSecurityModule": "0x7C32096A30Ef0bC550e3b8AcB0D87F72A3910D04",
"isTestnet": true,
"mailbox": "0x46f7C5D896bbeC89bE1B19e4485e59b4Be49e9Cc",
"merkleTreeHook": "0x98AAE089CaD930C64a76dD2247a2aC5773a4B8cE",
"name": "holesky",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"pausableHook": "0xF7561c34f17A32D5620583A3397C304e7038a7F6",
"pausableIsm": "0x80fE4Cb8c70fc60B745d4ffD4403c27a8cBC9e02",
"protocol": "ethereum",
"protocolFee": "0x6b1bb4ce664Bb4164AEB4d3D2E7DE7450DD8084C",
"proxyAdmin": "0x33dB966328Ea213b0f76eF96CA368AB37779F065",
"rpcUrls": [
{
"http": "https://ethereum-holesky-rpc.publicnode.com"
}
],
"staticAggregationHookFactory": "0x589C201a07c26b4725A4A829d772f24423da480B",
"staticAggregationIsm": "0x296Be783e0b1CdDed847E34e6587C24dDb710cf9",
"staticAggregationIsmFactory": "0x54148470292C24345fb828B003461a9444414517",
"staticMerkleRootMultisigIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2",
"staticMessageIdMultisigIsmFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"storageGasOracle": "0x2b2a158B4059C840c7aC67399B153bb567D06303",
"testRecipient": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD",
"validatorAnnounce": "0xAb9B273366D794B7F80B4378bc8Aaca75C6178E2"
},
"optimismsepolia": {
"aggregationHook": "0xccA408a6A9A6dc405C3278647421eb4317466943",
"blockExplorers": [
{
"apiUrl": "https://api-sepolia-optimistic.etherscan.io/api",
"family": "etherscan",
"name": "OP Sepolia Explorer",
"url": "https://sepolia-optimistic.etherscan.io"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 2,
"reorgPeriod": 0
},
"chainId": 11155420,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Optimism Sepolia",
"domainId": 11155420,
"domainRoutingIsm": "0x4ac19e0bafc2aF6B98094F0a1B817dF196551219",
"domainRoutingIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37",
"fallbackRoutingHook": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75",
"gasCurrencyCoinGeckoId": "ethereum",
"index": {
"from": 15833917
},
"interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564",
"interchainSecurityModule": "0x815a9642497Ee1E9F061f8b828C85Eb7193DecfC",
"isTestnet": true,
"mailbox": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"merkleTreeHook": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD",
"name": "optimismsepolia",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"pausableHook": "0x19Be55D859368e02d7b9C00803Eb677BDC1359Bd",
"pausableIsm": "0xAD34A66Bf6dB18E858F6B686557075568c6E031C",
"protocol": "ethereum",
"protocolFee": "0x1b33611fCc073aB0737011d5512EF673Bff74962",
"proxyAdmin": "0x44b764045BfDC68517e10e783E69B376cef196B2",
"rpcUrls": [
{
"http": "https://sepolia.optimism.io"
}
],
"staticAggregationHookFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44",
"staticAggregationIsm": "0x815a9642497Ee1E9F061f8b828C85Eb7193DecfC",
"staticAggregationIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7",
"staticMerkleRootMultisigIsmFactory": "0x6E7b29CB2A7617405B4d30C6f84bBD51b4Bb4be8",
"staticMessageIdMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213",
"storageGasOracle": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8",
"testRecipient": "0x783c4a0bB6663359281aD4a637D5af68F83ae213",
"validatorAnnounce": "0x20c44b1E3BeaDA1e9826CFd48BeEDABeE9871cE9"
},
"plumetestnet": {
"aggregationHook": "0x31dF0EEE7Dc7565665468698a0da221225619a1B",
"blockExplorers": [
{
"apiUrl": "https://plume-testnet.explorer.caldera.xyz/api",
"family": "blockscout",
"name": "Plume Testnet Explorer",
"url": "https://plume-testnet.explorer.caldera.xyz"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 5,
"reorgPeriod": 0
},
"chainId": 161221135,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Plume Testnet",
"domainId": 161221135,
"domainRoutingIsm": "0xF554Be1611572dF824556e8060bf90Fe5bE7Ff08",
"domainRoutingIsmFactory": "0x54148470292C24345fb828B003461a9444414517",
"fallbackRoutingHook": "0x19Be55D859368e02d7b9C00803Eb677BDC1359Bd",
"index": {
"from": 5284139
},
"interchainAccountIsm": "0x7c115c16E34c74afdb88bd268EaB19bC705891FE",
"interchainAccountRouter": "0xB6F8aA9B1b314A6E6DFB465DD3e0E95936347517",
"interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564",
"interchainSecurityModule": "0x5708e5CAd632898E16D6A7bA286EC8b8eFD3056b",
"isTestnet": true,
"mailbox": "0x33dB966328Ea213b0f76eF96CA368AB37779F065",
"merkleTreeHook": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75",
"name": "plumetestnet",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"pausableHook": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8",
"pausableIsm": "0xDaAc5C311d6e9134C725171Ef06a8a2BaAF4a10f",
"protocol": "ethereum",
"protocolFee": "0x1b33611fCc073aB0737011d5512EF673Bff74962",
"proxyAdmin": "0x589C201a07c26b4725A4A829d772f24423da480B",
"rpcConsensusType": "single",
"rpcUrls": [
{
"http": "https://plume-testnet.rpc.caldera.xyz/http"
}
],
"staticAggregationHookFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039",
"staticAggregationIsm": "0xDaE6E59fB970F8df1cCCC7d230a7cdeD8BDfCb95",
"staticAggregationIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2",
"staticMerkleRootMultisigIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37",
"staticMessageIdMultisigIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2",
"storageGasOracle": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279",
"technicalStack": "arbitrumnitro",
"testRecipient": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F",
"timelockController": "0x0000000000000000000000000000000000000000",
"validatorAnnounce": "0x20c44b1E3BeaDA1e9826CFd48BeEDABeE9871cE9"
},
"polygonamoy": {
"aggregationHook": "0x06a54A2db82D37410C1383c51F96Bd7b3ABD243E",
"blockExplorers": [
{
"apiUrl": "https://api-amoy.polygonscan.com/api",
"family": "etherscan",
"name": "Polygon Amoy Explorer",
"url": "https://amoy.polygonscan.com"
}
],
"blocks": {
"confirmations": 5,
"estimateBlockTime": 2,
"reorgPeriod": 10
},
"chainId": 80002,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Polygon Amoy",
"domainId": 80002,
"domainRoutingIsm": "0x2a2F4AAaf726abb4B969c2804D38e188555683b5",
"domainRoutingIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2",
"fallbackRoutingHook": "0x19Be55D859368e02d7b9C00803Eb677BDC1359Bd",
"index": {
"from": 10634605
},
"interchainGasPaymaster": "0x6c13643B3927C57DB92c790E4E3E7Ee81e13f78C",
"interchainSecurityModule": "0x831Ee59F524C94A320821f4d30B6581Ace69379d",
"isTestnet": true,
"mailbox": "0x54148470292C24345fb828B003461a9444414517",
"merkleTreeHook": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75",
"name": "polygonamoy",
"nativeToken": {
"decimals": 18,
"name": "MATIC",
"symbol": "MATIC"
},
"pausableHook": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8",
"pausableIsm": "0xAb9B273366D794B7F80B4378bc8Aaca75C6178E2",
"protocol": "ethereum",
"protocolFee": "0x66b71A4e18FbE09a6977A6520B47fEDdffA82a1c",
"proxyAdmin": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2",
"rpcUrls": [
{
"http": "https://rpc-amoy.polygon.technology"
},
{
"http": "https://polygon-amoy-bor-rpc.publicnode.com"
},
{
"http": "https://polygon-amoy.blockpi.network/v1/rpc/public"
},
{
"http": "https://rpc.ankr.com/polygon_amoy"
}
],
"staticAggregationHookFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37",
"staticAggregationIsm": "0x1D770b978d915bD96F2ad41b25824C4193EBAfA2",
"staticAggregationIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44",
"staticMerkleRootMultisigIsmFactory": "0xfc6e546510dC9d76057F1f76633FCFfC188CB213",
"staticMessageIdMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7",
"storageGasOracle": "0xD0680F80F4f947968206806C2598Cbc5b6FE5b03",
"testRecipient": "0x04438ef7622f5412f82915F59caD4f704C61eA48",
"validatorAnnounce": "0x11918DC33E067C5DA83EEF58E50F856398b8Df4C"
},
"scrollsepolia": {
"aggregationHook": "0x7b63Aa270335F8896717c2A809205F4b650E4268",
"blockExplorers": [
{
"apiUrl": "https://api-sepolia.scrollscan.com/api",
"family": "etherscan",
"name": "Scroll Explorer",
"url": "https://sepolia.scrollscan.dev/"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 3,
"reorgPeriod": 1
},
"chainId": 534351,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Scroll Sepolia",
"domainId": 534351,
"domainRoutingIsm": "0x606511c593fd78AFbeD8A2e02EDe2C6179722276",
"domainRoutingIsmFactory": "0x17866ebE0e503784a9461d3e753dEeD0d3F61153",
"fallbackRoutingHook": "0xE1CCB130389f687bf745Dd6dc05E50da17d9ea96",
"index": {
"from": 1344054
},
"interchainAccountIsm": "0xE023239c8dfc172FF008D8087E7442d3eBEd9350",
"interchainAccountRouter": "0xe17c37212d785760E8331D4A4395B17b34Ba8cDF",
"interchainGasPaymaster": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD",
"interchainSecurityModule": "0x476a8f40c81E69f5d676ccaA9709c0dE61F907E1",
"isTestnet": true,
"mailbox": "0x3C5154a193D6e2955650f9305c8d80c18C814A68",
"merkleTreeHook": "0x863E8c26621c52ACa1849C53500606e73BA272F0",
"name": "scrollsepolia",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"pausableHook": "0x390A48fC8Cb8F29B3ffE1B95aD3773414B8DD704",
"pausableIsm": "0xcD19Ff7306E04EA6b8f4B5Ab1c5A198c186aaB42",
"protocol": "ethereum",
"protocolFee": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8",
"proxyAdmin": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8",
"rpcUrls": [
{
"http": "https://sepolia-rpc.scroll.io"
},
{
"http": "https://rpc.ankr.com/scroll_sepolia_testnet"
},
{
"http": "https://scroll-sepolia.blockpi.network/v1/rpc/public"
},
{
"http": "https://scroll-sepolia.chainstacklabs.com"
},
{
"http": "https://scroll-public.scroll-testnet.quiknode.pro"
}
],
"staticAggregationHookFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2",
"staticAggregationIsm": "0xcc5BAaa44A8749c7C566b1cb578a315427632053",
"staticAggregationIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37",
"staticMerkleRootMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7",
"staticMessageIdMultisigIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44",
"storageGasOracle": "0x6b1bb4ce664Bb4164AEB4d3D2E7DE7450DD8084C",
"testRecipient": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA",
"testTokenRecipient": "0xc76E477437065093D353b7d56c81ff54D167B0Ab",
"timelockController": "0x0000000000000000000000000000000000000000",
"transactionOverrides": {
"gasPrice": 500000000
},
"validatorAnnounce": "0x527768930D889662Fe7ACF64294871e86e4C2381"
},
"sepolia": {
"aggregationHook": "0xe3147d5618f5e2e100690B50ec923009a4cde14A",
"blockExplorers": [
{
"apiUrl": "https://api-sepolia.etherscan.io/api",
"family": "etherscan",
"name": "Etherscan",
"url": "https://sepolia.etherscan.io"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 13,
"reorgPeriod": 2
},
"chainId": 11155111,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Sepolia",
"domainId": 11155111,
"domainRoutingIsm": "0xfa9a26cCc5417d1C1D03C949b5013Bb5898dA905",
"domainRoutingIsmFactory": "0x3F100cBBE5FD5466BdB4B3a15Ac226957e7965Ad",
"fallbackRoutingHook": "0x17Dc724B7a2F09141C13b8AC33B396073785c2BC",
"gnosisSafeTransactionServiceUrl": "https://safe-transaction-sepolia.safe.global",
"index": {
"from": 4517401
},
"interchainAccountIsm": "0x83a3068B719F764d413625dA77468ED74789ae02",
"interchainAccountRouter": "0x8e131c8aE5BF1Ed38D05a00892b6001a7d37739d",
"interchainGasPaymaster": "0x6f2756380FD49228ae25Aa7F2817993cB74Ecc56",
"interchainSecurityModule": "0x76B21a2241f6A6FbaE624A7Fe5d7D3919C9ce3E3",
"isTestnet": true,
"mailbox": "0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766",
"merkleTreeHook": "0x4917a9746A7B6E0A57159cCb7F5a6744247f2d0d",
"name": "sepolia",
"nativeToken": {
"decimals": 18,
"name": "Ether",
"symbol": "ETH"
},
"pausableHook": "0xa68022e53Fd28119D07C8336a8eC84A298Fd38Fd",
"pausableIsm": "0x21bdaB116d9DA77E312910fB53aD35dD82C8a76c",
"protocol": "ethereum",
"protocolFee": "0x13AC3349Cb159fE86A22cf42DdA803D9f7309DB5",
"proxyAdmin": "0x97Bbc6bBaFa5Ce3b2FA966c121Af63bD09e940f8",
"rpcUrls": [
{
"http": "https://ethereum-sepolia.publicnode.com"
},
{
"http": "https://ethereum-sepolia.blockpi.network/v1/rpc/public"
},
{
"http": "https://rpc.sepolia.org"
}
],
"staticAggregationHookFactory": "0x160C28C92cA453570aD7C031972b58d5Dd128F72",
"staticAggregationIsm": "0x7856078C4881e236660Be82fdb12473B4a33cCFf",
"staticAggregationIsmFactory": "0xC83e12EF2627ACE445C298e6eC418684918a6002",
"staticMerkleRootMultisigIsmFactory": "0x0a71AcC99967829eE305a285750017C4916Ca269",
"staticMessageIdMultisigIsmFactory": "0xFEb9585b2f948c1eD74034205a7439261a9d27DD",
"storageGasOracle": "0x71775B071F77F1ce52Ece810ce084451a3045FFe",
"testRecipient": "0xeDc1A3EDf87187085A3ABb7A9a65E1e7aE370C07",
"testTokenRecipient": "0x031AD9c560D37baC7d6Bd2d27A2443bAfd10101A",
"timelockController": "0x0000000000000000000000000000000000000000",
"validatorAnnounce": "0xE6105C59480a1B7DD3E4f28153aFdbE12F4CfCD9"
},
"solanatestnet": {
"blockExplorers": [
{
"apiUrl": "https://explorer.solana.com?cluster=testnet",
"family": "other",
"name": "Solana Explorer",
"url": "https://explorer.solana.com?cluster=testnet"
}
],
"blocks": {
"confirmations": 1,
"estimateBlockTime": 0.4,
"reorgPeriod": 0
},
"chainId": 1399811150,
"deployer": {
"name": "Abacus Works",
"url": "https://www.hyperlane.xyz"
},
"displayName": "Solana Testnet",
"displayNameShort": "Sol Testnet",
"domainId": 1399811150,
"index": {
"from": 1,
"mode": "sequence"
},
"interchainGasPaymaster": "9SQVtTNsbipdMzumhzi6X8GwojiSMwBfqAhS7FgyTcqy",
"isTestnet": true,
"mailbox": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"merkleTreeHook": "75HBBLae3ddeneJVrZeyrDfv6vb7SMC3aCpBucSXS5aR",
"name": "solanatestnet",
"nativeToken": {
"decimals": 9,
"name": "Sol",
"symbol": "SOL"
},
"protocol": "sealevel",
"rpcUrls": [
{
"http": "https://api.testnet.solana.com"
}
],
"validatorAnnounce": "8qNYSi9EP1xSnRjtMpyof88A26GBbdcrsa61uSaHiwx3"
},
"superpositiontestnet": {
"aggregationHook": "0x331eb40963dc11F5BB271308c42d97ac6e41F124",
"blockExplorers": [
{
"apiUrl": "https://testnet-explorer.superposition.so/api",
"family": "blockscout",
"name": "CatScan",
"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": "0x415a45C98288059Cce9c32AE4a09AB19C91d5056",
"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"
}
},
"defaultRpcConsensusType": "fallback"
}

@ -1,2 +0,0 @@
pub use rocks::*;
mod rocks;

File diff suppressed because it is too large Load Diff

@ -12,31 +12,9 @@ members = [
"hyperlane-base",
"hyperlane-core",
"hyperlane-test",
"sealevel/client",
"sealevel/libraries/access-control",
"sealevel/libraries/account-utils",
"sealevel/libraries/ecdsa-signature",
"sealevel/libraries/hyperlane-sealevel-connection-client",
"sealevel/libraries/hyperlane-sealevel-token",
"sealevel/libraries/interchain-security-module-interface",
"sealevel/libraries/message-recipient-interface",
"sealevel/libraries/multisig-ism",
"sealevel/libraries/serializable-account-meta",
"sealevel/libraries/test-transaction-utils",
"sealevel/libraries/test-utils",
"sealevel/programs/hyperlane-sealevel-igp",
"sealevel/programs/hyperlane-sealevel-igp-test",
"sealevel/programs/hyperlane-sealevel-token",
"sealevel/programs/hyperlane-sealevel-token-collateral",
"sealevel/programs/hyperlane-sealevel-token-native",
"sealevel/programs/ism/multisig-ism-message-id",
"sealevel/programs/ism/test-ism",
"sealevel/programs/mailbox",
"sealevel/programs/mailbox-test",
"sealevel/programs/test-send-receiver",
"sealevel/programs/validator-announce",
"utils/abigen",
"utils/backtrace-oneline",
"utils/crypto",
"utils/hex",
"utils/run-locally",
]
@ -68,6 +46,7 @@ cainome = { git = "https://github.com/cartridge-gg/cainome", tag = "v0.2.9", fea
"abigen-rs",
] }
clap = "4"
chrono = "*"
color-eyre = "0.6"
config = "0.13.3"
console-subscriber = "0.2.0"
@ -89,26 +68,26 @@ dhat = "0.3.3"
ed25519-dalek = "~1.0"
eyre = "=0.6.8"
fixed-hash = "0.8.0"
fuels = "0.38"
fuels-code-gen = "0.38"
fuels = "0.65.0"
fuels-code-gen = "0.65.0"
futures = "0.3"
futures-util = "0.3"
generic-array = { version = "0.14", features = ["serde", "more_lengths"] }
# Required for WASM support https://docs.rs/getrandom/latest/getrandom/#webassembly-support
bech32 = "0.9.1"
elliptic-curve = "0.12.3"
elliptic-curve = "0.13.8"
getrandom = { version = "0.2", features = ["js"] }
hex = "0.4.3"
http = "*"
http = "0.2.12"
hyper = "0.14"
hyper-tls = "0.5.0"
hyperlane-cosmwasm-interface = "=0.0.6-rc6"
injective-protobuf = "0.2.2"
injective-std = "0.1.5"
injective-std = "=0.1.5"
itertools = "*"
jobserver = "=0.1.26"
jsonrpc-core = "18.0"
k256 = { version = "0.13.1", features = ["std", "ecdsa"] }
k256 = { version = "0.13.4", features = ["arithmetic", "std", "ecdsa"] }
log = "0.4"
macro_rules_attribute = "0.2"
maplit = "1.0"
@ -180,7 +159,7 @@ tendermint-rpc = { version = "0.32.0", features = ["http-client", "tokio"] }
thiserror = "1.0"
time = "0.3"
tiny-keccak = "2.0.2"
tokio = { version = "1.37", features = ["parking_lot", "tracing"] }
tokio = { version = "1.4", features = ["parking_lot", "tracing"] }
tokio-metrics = { version = "0.3.1", default-features = false }
tokio-test = "0.4"
toml_edit = "0.19.14"
@ -197,11 +176,45 @@ url = "2.3"
walkdir = "2"
warp = "0.3"
which = "4.3"
ya-gcp = { version = "0.11.1", features = ["storage"] }
ya-gcp = { version = "0.11.3", features = ["storage"] }
## TODO: remove this
cosmwasm-schema = "1.2.7"
[profile.release.package.access-control]
overflow-checks = true
[profile.release.package.account-utils]
overflow-checks = true
[profile.release.package.ecdsa-signature]
overflow-checks = true
[profile.release.package.hyperlane-sealevel-interchain-security-module-interface]
overflow-checks = true
[profile.release.package.hyperlane-sealevel-message-recipient-interface]
overflow-checks = true
[profile.release.package.multisig-ism]
overflow-checks = true
[profile.release.package.serializable-account-meta]
overflow-checks = true
[profile.release.package.hyperlane-sealevel-mailbox]
overflow-checks = true
[profile.release.package.hyperlane-sealevel-igp]
overflow-checks = true
[profile.release.package.hyperlane-sealevel-multisig-ism-message-id]
overflow-checks = true
[profile.release.package.hyperlane-sealevel-validator-announce]
overflow-checks = true
[workspace.dependencies.ethers]
features = []
git = "https://github.com/hyperlane-xyz/ethers-rs"
@ -252,21 +265,6 @@ git = "https://github.com/hyperlane-xyz/solana.git"
tag = "hyperlane-1.14.13-2023-07-04"
version = "=1.14.13"
[patch.crates-io.solana-banks-client]
git = "https://github.com/hyperlane-xyz/solana.git"
tag = "hyperlane-1.14.13-2023-07-04"
version = "=1.14.13"
[patch.crates-io.solana-banks-interface]
git = "https://github.com/hyperlane-xyz/solana.git"
tag = "hyperlane-1.14.13-2023-07-04"
version = "=1.14.13"
[patch.crates-io.solana-banks-server]
git = "https://github.com/hyperlane-xyz/solana.git"
tag = "hyperlane-1.14.13-2023-07-04"
version = "=1.14.13"
[patch.crates-io.solana-clap-utils]
git = "https://github.com/hyperlane-xyz/solana.git"
tag = "hyperlane-1.14.13-2023-07-04"
@ -287,11 +285,6 @@ git = "https://github.com/hyperlane-xyz/solana.git"
tag = "hyperlane-1.14.13-2023-07-04"
version = "=1.14.13"
[patch.crates-io.solana-program-test]
git = "https://github.com/hyperlane-xyz/solana.git"
tag = "hyperlane-1.14.13-2023-07-04"
version = "=1.14.13"
[patch.crates-io.solana-sdk]
git = "https://github.com/hyperlane-xyz/solana.git"
tag = "hyperlane-1.14.13-2023-07-04"

@ -1,4 +1,3 @@
cargo-features = ["workspace-inheritance"]
[package]
name = "relayer"
@ -35,13 +34,21 @@ serde.workspace = true
serde_json.workspace = true
strum.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["rt", "macros", "parking_lot", "rt-multi-thread"] }
tokio = { workspace = true, features = [
"rt",
"macros",
"parking_lot",
"rt-multi-thread",
] }
tokio-metrics.workspace = true
tracing-futures.workspace = true
tracing.workspace = true
typetag.workspace = true
hyperlane-core = { path = "../../hyperlane-core", features = ["agent", "async"] }
hyperlane-core = { path = "../../hyperlane-core", features = [
"agent",
"async",
] }
hyperlane-base = { path = "../../hyperlane-base", features = ["test-utils"] }
hyperlane-ethereum = { path = "../../chains/hyperlane-ethereum" }
@ -51,6 +58,7 @@ mockall.workspace = true
tokio-test.workspace = true
hyperlane-test = { path = "../../hyperlane-test" }
hyperlane-base = { path = "../../hyperlane-base", features = ["test-utils"] }
hyperlane-core = { path = "../../hyperlane-core", features = ["agent", "async", "test-utils"] }
[features]
default = ["color-eyre", "oneline-errors"]

@ -7,7 +7,7 @@ use std::{
use async_trait::async_trait;
use derive_new::new;
use eyre::Result;
use hyperlane_base::db::HyperlaneRocksDB;
use hyperlane_base::db::{HyperlaneDb, HyperlaneRocksDB};
use hyperlane_core::{HyperlaneDomain, MerkleTreeInsertion};
use prometheus::IntGauge;
use tokio::sync::RwLock;

@ -105,7 +105,7 @@ impl GasPaymentEnforcer {
for (policy, whitelist) in &self.policies {
if !whitelist.msg_matches(message, true) {
trace!(
msg=%message,
hyp_message=%message,
?policy,
?whitelist,
"Message did not match whitelist for policy"
@ -114,16 +114,17 @@ impl GasPaymentEnforcer {
}
trace!(
msg=%message,
hyp_message=%message,
?policy,
?whitelist,
"Message matched whitelist for policy"
);
debug!(
msg=%message,
hyp_message=%message,
?policy,
?current_payment,
?current_expenditure,
?tx_cost_estimate,
"Evaluating if message meets gas payment requirement",
);
return policy
@ -148,7 +149,7 @@ impl GasPaymentEnforcer {
}
error!(
msg=%message,
hyp_message=%message,
policies=?self.policies,
"No gas payment policy matched for message; consider adding a default policy to the end of the policies array which uses a wildcard whitelist."
);
@ -158,7 +159,7 @@ impl GasPaymentEnforcer {
pub fn record_tx_outcome(&self, message: &HyperlaneMessage, outcome: TxOutcome) -> Result<()> {
// This log is required in E2E, hence the use of a `const`
debug!(
msg=%message,
hyp_message=%message,
?outcome,
"{}",
GAS_EXPENDITURE_LOG_MESSAGE,

@ -118,6 +118,7 @@ impl AggregationIsmMetadataBuilder {
#[async_trait]
impl MetadataBuilder for AggregationIsmMetadataBuilder {
#[instrument(err, skip(self), ret)]
#[allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue
async fn build(
&self,
ism_address: H256,

@ -1,3 +1,6 @@
#![allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue
#![allow(clippy::unnecessary_get_then_check)] // TODO: `rustc` 1.80.1 clippy issue
use std::{
collections::HashMap,
fmt::Debug,
@ -19,7 +22,7 @@ use crate::{
use async_trait::async_trait;
use derive_new::new;
use eyre::{Context, Result};
use hyperlane_base::db::HyperlaneRocksDB;
use hyperlane_base::db::{HyperlaneDb, HyperlaneRocksDB};
use hyperlane_base::{
settings::{ChainConf, CheckpointSyncerConf},
CheckpointSyncer, CoreMetrics, MultisigCheckpointSyncer,
@ -397,7 +400,7 @@ impl BaseMetadataBuilder {
continue;
}
match config.build(None).await {
match config.build_and_validate(None).await {
Ok(checkpoint_syncer) => {
// found the syncer for this validator
checkpoint_syncers.insert(validator.into(), checkpoint_syncer.into());

@ -1,3 +1,5 @@
#![allow(clippy::blocks_in_conditions)] // TODO: `rustc` 1.80.1 clippy issue
use async_trait::async_trait;
use derive_more::Deref;
use derive_new::new;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save