Migrate SDK consts to Registry and use new registry utilities (#3615)

### Description

- Remove most consts from SDK
- Refactor tests that relied on chainMetadata
- Remove JSON imports/exports in the SDK
- Update the HelloWorld package to read metadata from the registry lib
- Update the Infra package to read/write to a LocalRegistry
- Update the CLI to read and write from registries

### Drive-by changes

- Fix typo in `EvmHypCollateralVault` token standard 
- Use consistent, newest yaml lib version

### Related issues

Fixes https://github.com/hyperlane-xyz/issues/issues/917
Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/2810

### Backward compatibility

No: 
- Several SDK exports like `chainMetadata` have been removed
- Common args for the CLI like `--chain` have been replaced with `--registry` and `--overrides`
pull/3685/head
J M Rossy 7 months ago committed by GitHub
parent 292879126c
commit 3528b281e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      .changeset/khaki-days-float.md
  2. 5
      .changeset/moody-colts-dress.md
  3. 5
      .changeset/nine-masks-guess.md
  4. 100
      .github/workflows/cron.yml
  5. 2
      .github/workflows/release.yml
  6. 1
      .github/workflows/test.yml
  7. 51
      rust/config/mainnet_config.json
  8. 6
      rust/config/testnet_config.json
  9. 9
      solidity/exportBuildArtifact.sh
  10. 6
      solidity/package.json
  11. 10
      typescript/cli/.gitignore
  12. 153
      typescript/cli/ci-test.sh
  13. 20
      typescript/cli/cli.ts
  14. 22
      typescript/cli/examples/anvil-chains.yaml
  15. 84
      typescript/cli/examples/chain-config.yaml
  16. 17
      typescript/cli/examples/dry-run/anvil-chains.yaml
  17. 21
      typescript/cli/examples/fork/anvil-chains.yaml
  18. 3
      typescript/cli/package.json
  19. 94
      typescript/cli/src/commands/chains.ts
  20. 170
      typescript/cli/src/commands/config.ts
  21. 151
      typescript/cli/src/commands/deploy.ts
  22. 38
      typescript/cli/src/commands/hook.ts
  23. 44
      typescript/cli/src/commands/ism.ts
  24. 114
      typescript/cli/src/commands/options.ts
  25. 161
      typescript/cli/src/commands/send.ts
  26. 11
      typescript/cli/src/commands/signCommands.ts
  27. 42
      typescript/cli/src/commands/status.ts
  28. 86
      typescript/cli/src/config/artifacts.ts
  29. 9
      typescript/cli/src/config/chain.test.ts
  30. 79
      typescript/cli/src/config/chain.ts
  31. 41
      typescript/cli/src/config/hooks.ts
  32. 16
      typescript/cli/src/config/ism.ts
  33. 20
      typescript/cli/src/config/multisig.ts
  34. 41
      typescript/cli/src/config/warp.ts
  35. 23
      typescript/cli/src/context.test.ts
  36. 213
      typescript/cli/src/context.ts
  37. 134
      typescript/cli/src/context/context.ts
  38. 43
      typescript/cli/src/context/types.ts
  39. 20
      typescript/cli/src/deploy/agent.ts
  40. 242
      typescript/cli/src/deploy/core.ts
  41. 5
      typescript/cli/src/deploy/dry-run.ts
  42. 39
      typescript/cli/src/deploy/utils.ts
  43. 169
      typescript/cli/src/deploy/warp.ts
  44. 38
      typescript/cli/src/hook/read.ts
  45. 38
      typescript/cli/src/ism/read.ts
  46. 156
      typescript/cli/src/registry/MergedRegistry.ts
  47. 53
      typescript/cli/src/send/message.ts
  48. 97
      typescript/cli/src/send/transfer.ts
  49. 28
      typescript/cli/src/status/message.ts
  50. 32
      typescript/cli/src/utils/chains.ts
  51. 38
      typescript/cli/src/utils/files.ts
  52. 48
      typescript/cli/src/utils/keys.ts
  53. 19
      typescript/cli/src/utils/tokens.ts
  54. 5
      typescript/cli/src/utils/version-check.ts
  55. 14
      typescript/cli/test-configs/anvil/chains/anvil1/metadata.yaml
  56. 7
      typescript/cli/test-configs/anvil/chains/anvil2/metadata.yaml
  57. 13
      typescript/cli/test-configs/dry-run/chains/alfajores/metadata.yaml
  58. 10
      typescript/cli/test-configs/dry-run/chains/anvil/metadata.yaml
  59. 6
      typescript/cli/test-configs/dry-run/chains/fuji/metadata.yaml
  60. 0
      typescript/cli/test-configs/dry-run/ism.yaml
  61. 0
      typescript/cli/test-configs/dry-run/warp-route-deployment.yaml
  62. 11
      typescript/cli/test-configs/fork/chains/anvil1/metadata.yaml
  63. 9
      typescript/cli/test-configs/fork/chains/ethereum/metadata.yaml
  64. 3
      typescript/cli/test-configs/fork/ism.yaml
  65. 2
      typescript/cli/test-configs/fork/warp-route-deployment.yaml
  66. 1
      typescript/helloworld/package.json
  67. 7
      typescript/helloworld/src/deploy/config.ts
  68. 9
      typescript/helloworld/src/scripts/check.ts
  69. 5
      typescript/helloworld/src/scripts/deploy.ts
  70. 6
      typescript/helloworld/src/test/helloworld.test.ts
  71. 8
      typescript/infra/README.md
  72. 132
      typescript/infra/config/environments/mainnet3/agent.ts
  73. 32
      typescript/infra/config/environments/mainnet3/chains.ts
  74. 7
      typescript/infra/config/environments/mainnet3/core.ts
  75. 15
      typescript/infra/config/environments/mainnet3/igp.ts
  76. 12
      typescript/infra/config/environments/mainnet3/liquidityLayer.ts
  77. 12
      typescript/infra/config/environments/mainnet3/owners.ts
  78. 24
      typescript/infra/config/environments/mainnet3/supportedChainNames.ts
  79. 9
      typescript/infra/config/environments/mainnet3/token-bridge.ts
  80. 43
      typescript/infra/config/environments/mainnet3/validators.ts
  81. 4
      typescript/infra/config/environments/test/agent.ts
  82. 21
      typescript/infra/config/environments/test/chains.ts
  83. 8
      typescript/infra/config/environments/test/gas-oracle.ts
  84. 9
      typescript/infra/config/environments/test/igp.ts
  85. 5
      typescript/infra/config/environments/test/index.ts
  86. 4
      typescript/infra/config/environments/test/owners.ts
  87. 52
      typescript/infra/config/environments/testnet4/agent.ts
  88. 35
      typescript/infra/config/environments/testnet4/chains.ts
  89. 7
      typescript/infra/config/environments/testnet4/core.ts
  90. 4
      typescript/infra/config/environments/testnet4/gas-oracle.ts
  91. 2
      typescript/infra/config/environments/testnet4/igp.ts
  92. 19
      typescript/infra/config/environments/testnet4/liquidityLayer.ts
  93. 4
      typescript/infra/config/environments/testnet4/owners.ts
  94. 12
      typescript/infra/config/environments/testnet4/supportedChainNames.ts
  95. 19
      typescript/infra/config/environments/testnet4/token-bridge.ts
  96. 19
      typescript/infra/config/environments/testnet4/validators.ts
  97. 6
      typescript/infra/config/environments/utils.ts
  98. 17
      typescript/infra/config/multisigIsm.ts
  99. 109
      typescript/infra/config/registry.ts
  100. 14
      typescript/infra/config/routingIsm.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,5 +1,5 @@
--- ---
"@hyperlane-xyz/sdk": patch '@hyperlane-xyz/sdk': patch
--- ---
Allow gasLimit overrides in the SDK/CLI for deploy txs Allow gasLimit overrides in the SDK/CLI for deploy txs

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/sdk': minor
---
Remove consts such as chainMetadata from SDK

@ -0,0 +1,5 @@
---
'@hyperlane-xyz/cli': minor
---
Restructure CLI params around registries

@ -1,100 +0,0 @@
name: cron
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows
on:
schedule:
- cron: '45 14 * * *'
workflow_dispatch:
env:
LOG_LEVEL: DEBUG
LOG_FORMAT: PRETTY
jobs:
# copied from test.yml
yarn-install:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v3
with:
node-version: 18
- uses: actions/checkout@v3
with:
ref: ${{ github.sha }}
submodules: recursive
- name: yarn-cache
uses: actions/cache@v3
with:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
- name: yarn-install
run: yarn install
# copied from test.yml
yarn-build:
runs-on: ubuntu-latest
needs: [yarn-install]
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.sha }}
submodules: recursive
fetch-depth: 0
- name: yarn-cache
uses: actions/cache@v3
with:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
- name: build-cache
uses: actions/cache@v3
with:
path: |
./*
!./rust
key: ${{ github.sha }}
- name: build
run: yarn build
metadata-check:
runs-on: ubuntu-latest
needs: [yarn-build]
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.sha }}
submodules: recursive
fetch-depth: 0
- name: yarn-cache
uses: actions/cache@v3
with:
path: |
**/node_modules
.yarn
key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }}
- name: build-cache
uses: actions/cache@v3
with:
path: |
./*
!./rust
key: ${{ github.sha }}
- name: Metadata Health Check
run: yarn workspace @hyperlane-xyz/sdk run test:metadata
- name: Post to discord webhook if metadata check fails
if: failure()
run: |
curl -X POST -H 'Content-type: application/json' --data '{"content":"SDK metadata check failed, see ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}' ${{ secrets.DISCORD_WEBHOOK_URL }}

@ -31,7 +31,7 @@ jobs:
node-version: 18.x node-version: 18.x
- name: Install Dependencies - name: Install Dependencies
run: yarn run: yarn install --immutable
- name: Create Release PR or Publish to NPM - name: Create Release PR or Publish to NPM
id: changesets id: changesets

@ -18,6 +18,7 @@ env:
LOG_FORMAT: PRETTY LOG_FORMAT: PRETTY
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
RUST_BACKTRACE: full RUST_BACKTRACE: full
REGISTRY_URI: ../../node_modules/@hyperlane-xyz/registry/dist
jobs: jobs:
yarn-install: yarn-install:

@ -77,6 +77,8 @@
"index": { "index": {
"from": 143649797 "from": 143649797
}, },
"interchainAccountIsm": "0xfa8bfcE55B3A0631dF38257615cEF7FCD3523A48",
"interchainAccountRouter": "0xCD0CFFf6eFD943b4b81f2c15847730dbcD30e3aE",
"interchainGasPaymaster": "0x3b6044acd6767f017e99318AA6Ef93b7B06A5a22", "interchainGasPaymaster": "0x3b6044acd6767f017e99318AA6Ef93b7B06A5a22",
"interchainSecurityModule": "0xD0DBBF922076352cC50B285A0023536561F00EEa", "interchainSecurityModule": "0xD0DBBF922076352cC50B285A0023536561F00EEa",
"mailbox": "0x979Ca5202784112f4738403dBec5D0F3B9daabB9", "mailbox": "0x979Ca5202784112f4738403dBec5D0F3B9daabB9",
@ -104,6 +106,7 @@
"technicalStack": "arbitrumnitro", "technicalStack": "arbitrumnitro",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35",
"testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb",
"timelockController": "0x0000000000000000000000000000000000000000",
"validatorAnnounce": "0x1df063280C4166AF9a725e3828b4dAC6c7113B08" "validatorAnnounce": "0x1df063280C4166AF9a725e3828b4dAC6c7113B08"
}, },
"avalanche": { "avalanche": {
@ -131,6 +134,8 @@
"index": { "index": {
"from": 36874693 "from": 36874693
}, },
"interchainAccountIsm": "0x786c26C1857032617c215f265509d6E44e44Bfe3",
"interchainAccountRouter": "0xA967A6CE0e73fAf672843DECaA372511996E8852",
"interchainGasPaymaster": "0x95519ba800BBd0d34eeAE026fEc620AD978176C0", "interchainGasPaymaster": "0x95519ba800BBd0d34eeAE026fEc620AD978176C0",
"interchainSecurityModule": "0xA36B02a83564f52d9244310Ea439ee6F6AfeFb60", "interchainSecurityModule": "0xA36B02a83564f52d9244310Ea439ee6F6AfeFb60",
"mailbox": "0xFf06aFcaABaDDd1fb08371f9ccA15D73D51FeBD6", "mailbox": "0xFf06aFcaABaDDd1fb08371f9ccA15D73D51FeBD6",
@ -161,6 +166,7 @@
"storageGasOracle": "0x175821F30AdCAA4bbB72Ce98eF76C2E0De2C3f21", "storageGasOracle": "0x175821F30AdCAA4bbB72Ce98eF76C2E0De2C3f21",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35",
"testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb",
"timelockController": "0x0000000000000000000000000000000000000000",
"validatorAnnounce": "0x9Cad0eC82328CEE2386Ec14a12E81d070a27712f" "validatorAnnounce": "0x9Cad0eC82328CEE2386Ec14a12E81d070a27712f"
}, },
"base": { "base": {
@ -188,6 +194,8 @@
"index": { "index": {
"from": 5695475 "from": 5695475
}, },
"interchainAccountIsm": "0x861908E6c8F992537F557da5Fb5876836036b347",
"interchainAccountRouter": "0xa85F9e4fdA2FFF1c07f2726a630443af3faDF830",
"interchainGasPaymaster": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", "interchainGasPaymaster": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94",
"interchainSecurityModule": "0x5D1e7D7c5B9e6dDC8439F67F10c578f2A1084f6F", "interchainSecurityModule": "0x5D1e7D7c5B9e6dDC8439F67F10c578f2A1084f6F",
"mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", "mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D",
@ -218,16 +226,11 @@
"staticMerkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "staticMerkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE",
"staticMessageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "staticMessageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A",
"storageGasOracle": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", "storageGasOracle": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2",
"timelockController": "0x0000000000000000000000000000000000000000",
"validatorAnnounce": "0x182E8d7c5F1B06201b102123FC7dF0EaeB445a7B" "validatorAnnounce": "0x182E8d7c5F1B06201b102123FC7dF0EaeB445a7B"
}, },
"blast": { "blast": {
"blockExplorers": [ "blockExplorers": [
{
"apiUrl": "https://api.blastscan.io/api",
"family": "etherscan",
"name": "Blast Explorer",
"url": "https://blastscan.io"
},
{ {
"apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/81457/etherscan/api", "apiUrl": "https://api.routescan.io/v2/network/mainnet/evm/81457/etherscan/api",
"family": "routescan", "family": "routescan",
@ -303,6 +306,8 @@
"index": { "index": {
"from": 32893043 "from": 32893043
}, },
"interchainAccountIsm": "0xB274Bbbc1df5f1d1763216A93d473fde6f5de043",
"interchainAccountRouter": "0x4BBd67dC995572b40Dc6B3eB6CdE5185a5373868",
"interchainGasPaymaster": "0x78E25e7f84416e69b9339B0A6336EB6EFfF6b451", "interchainGasPaymaster": "0x78E25e7f84416e69b9339B0A6336EB6EFfF6b451",
"interchainSecurityModule": "0xab3df354baBee6c2B88E2CeD3b2e030e31aA5e61", "interchainSecurityModule": "0xab3df354baBee6c2B88E2CeD3b2e030e31aA5e61",
"mailbox": "0x2971b9Aec44bE4eb673DF1B88cDB57b96eefe8a4", "mailbox": "0x2971b9Aec44bE4eb673DF1B88cDB57b96eefe8a4",
@ -335,6 +340,7 @@
"storageGasOracle": "0x91d23D603d60445411C06e6443d81395593B7940", "storageGasOracle": "0x91d23D603d60445411C06e6443d81395593B7940",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35",
"testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb",
"timelockController": "0x0000000000000000000000000000000000000000",
"transactionOverrides": { "transactionOverrides": {
"gasPrice": 3000000000 "gasPrice": 3000000000
}, },
@ -371,6 +377,8 @@
"index": { "index": {
"from": 22102340 "from": 22102340
}, },
"interchainAccountIsm": "0x30a8DEc5318e2aAa9ad5b069fC606c4CfF6f5676",
"interchainAccountRouter": "0x4ED23E3885e1651E62564F78817D91865beba575",
"interchainGasPaymaster": "0x571f1435613381208477ac5d6974310d88AC7cB7", "interchainGasPaymaster": "0x571f1435613381208477ac5d6974310d88AC7cB7",
"interchainSecurityModule": "0x99e8E56Dce3402D6E09A82718937fc1cA2A9491E", "interchainSecurityModule": "0x99e8E56Dce3402D6E09A82718937fc1cA2A9491E",
"mailbox": "0x50da3B3907A08a24fe4999F4Dcf337E8dC7954bb", "mailbox": "0x50da3B3907A08a24fe4999F4Dcf337E8dC7954bb",
@ -399,6 +407,7 @@
"storageGasOracle": "0xD9A9966E7dA9a7f0032bF449FB12696a638E673C", "storageGasOracle": "0xD9A9966E7dA9a7f0032bF449FB12696a638E673C",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35",
"testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb",
"timelockController": "0x0000000000000000000000000000000000000000",
"validatorAnnounce": "0xCeF677b65FDaA6804d4403083bb12B8dB3991FE1" "validatorAnnounce": "0xCeF677b65FDaA6804d4403083bb12B8dB3991FE1"
}, },
"ethereum": { "ethereum": {
@ -431,6 +440,8 @@
"index": { "index": {
"from": 18422581 "from": 18422581
}, },
"interchainAccountIsm": "0x609707355a53d2aAb6366f48E2b607C599D26B29",
"interchainAccountRouter": "0x8dBae9B1616c46A20591fE0006Bf015E28ca5cC9",
"interchainGasPaymaster": "0x9e6B1022bE9BBF5aFd152483DAD9b88911bC8611", "interchainGasPaymaster": "0x9e6B1022bE9BBF5aFd152483DAD9b88911bC8611",
"interchainSecurityModule": "0xB42b88243F749F47697F01Ae1cbBCA9d4763902a", "interchainSecurityModule": "0xB42b88243F749F47697F01Ae1cbBCA9d4763902a",
"mailbox": "0xc005dc82818d67AF737725bD4bf75435d065D239", "mailbox": "0xc005dc82818d67AF737725bD4bf75435d065D239",
@ -460,6 +471,7 @@
"storageGasOracle": "0xc9a103990A8dB11b4f627bc5CD1D0c2685484Ec5", "storageGasOracle": "0xc9a103990A8dB11b4f627bc5CD1D0c2685484Ec5",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35",
"testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb",
"timelockController": "0x0000000000000000000000000000000000000000",
"transactionOverrides": { "transactionOverrides": {
"maxFeePerGas": 150000000000, "maxFeePerGas": 150000000000,
"maxPriorityFeePerGas": 5000000000 "maxPriorityFeePerGas": 5000000000
@ -491,6 +503,8 @@
"index": { "index": {
"from": 30620793 "from": 30620793
}, },
"interchainAccountIsm": "0x5a56dff3D92D635372718f86e6dF09C1129CFf53",
"interchainAccountRouter": "0x5E59EBAedeB691408EBAcF6C37218fa2cFcaC9f2",
"interchainGasPaymaster": "0xDd260B99d302f0A3fF885728c086f729c06f227f", "interchainGasPaymaster": "0xDd260B99d302f0A3fF885728c086f729c06f227f",
"interchainSecurityModule": "0x8e1aa0687B6d939D5a44304D13B7c922ebB012f1", "interchainSecurityModule": "0x8e1aa0687B6d939D5a44304D13B7c922ebB012f1",
"mailbox": "0xaD09d78f4c6b9dA2Ae82b1D34107802d380Bb74f", "mailbox": "0xaD09d78f4c6b9dA2Ae82b1D34107802d380Bb74f",
@ -521,6 +535,7 @@
"storageGasOracle": "0x5E01d8F34b629E3f92d69546bbc4142A7Adee7e9", "storageGasOracle": "0x5E01d8F34b629E3f92d69546bbc4142A7Adee7e9",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35",
"testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb",
"timelockController": "0x0000000000000000000000000000000000000000",
"validatorAnnounce": "0x87ED6926abc9E38b9C7C19f835B41943b622663c" "validatorAnnounce": "0x87ED6926abc9E38b9C7C19f835B41943b622663c"
}, },
"inevm": { "inevm": {
@ -548,6 +563,8 @@
"index": { "index": {
"from": 18972465 "from": 18972465
}, },
"interchainAccountIsm": "0x31894E7a734540B343d67E491148EB4FC9f7A45B",
"interchainAccountRouter": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd",
"interchainGasPaymaster": "0x19dc38aeae620380430C200a6E990D5Af5480117", "interchainGasPaymaster": "0x19dc38aeae620380430C200a6E990D5Af5480117",
"interchainSecurityModule": "0x3052aD50De54aAAc5D364d80bBE681d29e924597", "interchainSecurityModule": "0x3052aD50De54aAAc5D364d80bBE681d29e924597",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
@ -574,6 +591,7 @@
"staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC",
"staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE",
"storageGasOracle": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", "storageGasOracle": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76",
"timelockController": "0x0000000000000000000000000000000000000000",
"validatorAnnounce": "0x15ab173bDB6832f9b64276bA128659b0eD77730B" "validatorAnnounce": "0x15ab173bDB6832f9b64276bA128659b0eD77730B"
}, },
"injective": { "injective": {
@ -636,6 +654,8 @@
"index": { "index": {
"from": 437300 "from": 437300
}, },
"interchainAccountIsm": "0xA34ceDf9068C5deE726C67A4e1DCfCc2D6E2A7fD",
"interchainAccountRouter": "0x0f6fF770Eda6Ba1433C39cCf47d4059b254224Aa",
"interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4",
"interchainSecurityModule": "0xEda7cCD2A8CF717dc997D0002e363e4D10bF5c0d", "interchainSecurityModule": "0xEda7cCD2A8CF717dc997D0002e363e4D10bF5c0d",
"isTestnet": false, "isTestnet": false,
@ -663,6 +683,7 @@
"storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117", "storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117",
"testRecipient": "0x4E1c88DD261BEe2941e6c1814597e30F53330428", "testRecipient": "0x4E1c88DD261BEe2941e6c1814597e30F53330428",
"testTokenRecipient": "0x5060eCD5dFAD300A90592C04e504600A7cdcF70b", "testTokenRecipient": "0x5060eCD5dFAD300A90592C04e504600A7cdcF70b",
"timelockController": "0x0000000000000000000000000000000000000000",
"validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9" "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9"
}, },
"mode": { "mode": {
@ -740,6 +761,8 @@
"index": { "index": {
"from": 4719713 "from": 4719713
}, },
"interchainAccountIsm": "0x799eA6f430f5CA901b59335fFC2fA10531106009",
"interchainAccountRouter": "0x6b142f596FFc761ac3fFaaC1ecaDe54f4EE09977",
"interchainGasPaymaster": "0x14760E32C0746094cF14D97124865BC7F0F7368F", "interchainGasPaymaster": "0x14760E32C0746094cF14D97124865BC7F0F7368F",
"interchainSecurityModule": "0x373836DFa82f2D27ec79Ca32A197Aa1665F0E1e9", "interchainSecurityModule": "0x373836DFa82f2D27ec79Ca32A197Aa1665F0E1e9",
"mailbox": "0x094d03E751f49908080EFf000Dd6FD177fd44CC3", "mailbox": "0x094d03E751f49908080EFf000Dd6FD177fd44CC3",
@ -766,6 +789,7 @@
"storageGasOracle": "0x448b7ADB0dA36d41AA2AfDc9d63b97541A7b3819", "storageGasOracle": "0x448b7ADB0dA36d41AA2AfDc9d63b97541A7b3819",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35",
"testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb",
"timelockController": "0x0000000000000000000000000000000000000000",
"transactionOverrides": { "transactionOverrides": {
"maxFeePerGas": 350000000000, "maxFeePerGas": 350000000000,
"maxPriorityFeePerGas": 50000000000 "maxPriorityFeePerGas": 50000000000
@ -836,6 +860,8 @@
"index": { "index": {
"from": 111290758 "from": 111290758
}, },
"interchainAccountIsm": "0x0389faCac114023C123E22F3E54394944cAbcb48",
"interchainAccountRouter": "0x33Ef006E7083BB38E0AFe3C3979F4e9b84415bf1",
"interchainGasPaymaster": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", "interchainGasPaymaster": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C",
"interchainSecurityModule": "0x04938856bE60c8e734ffDe5f720E2238302BE8D2", "interchainSecurityModule": "0x04938856bE60c8e734ffDe5f720E2238302BE8D2",
"mailbox": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D", "mailbox": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D",
@ -862,6 +888,7 @@
"storageGasOracle": "0x27e88AeB8EA4B159d81df06355Ea3d20bEB1de38", "storageGasOracle": "0x27e88AeB8EA4B159d81df06355Ea3d20bEB1de38",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35",
"testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb",
"timelockController": "0x0000000000000000000000000000000000000000",
"validatorAnnounce": "0x30f5b08e01808643221528BB2f7953bf2830Ef38" "validatorAnnounce": "0x30f5b08e01808643221528BB2f7953bf2830Ef38"
}, },
"polygon": { "polygon": {
@ -889,6 +916,8 @@
"index": { "index": {
"from": 49108065 "from": 49108065
}, },
"interchainAccountIsm": "0x90384bC552e3C48af51Ef7D9473A9bF87431f5c7",
"interchainAccountRouter": "0x5e80f3474825B61183c0F0f0726796F589082420",
"interchainGasPaymaster": "0x0071740Bf129b05C4684abfbBeD248D80971cce2", "interchainGasPaymaster": "0x0071740Bf129b05C4684abfbBeD248D80971cce2",
"interchainSecurityModule": "0x9a795fB62f86146ec06e2377e3C95Af65c7C20eB", "interchainSecurityModule": "0x9a795fB62f86146ec06e2377e3C95Af65c7C20eB",
"mailbox": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB", "mailbox": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB",
@ -921,6 +950,7 @@
"storageGasOracle": "0xA3a24EC5670F1F416AB9fD554FcE2f226AE9D7eB", "storageGasOracle": "0xA3a24EC5670F1F416AB9fD554FcE2f226AE9D7eB",
"testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35",
"testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb",
"timelockController": "0x0000000000000000000000000000000000000000",
"transactionOverrides": { "transactionOverrides": {
"maxFeePerGas": 800000000000, "maxFeePerGas": 800000000000,
"maxPriorityFeePerGas": 50000000000 "maxPriorityFeePerGas": 50000000000
@ -952,6 +982,8 @@
"index": { "index": {
"from": 6577743 "from": 6577743
}, },
"interchainAccountIsm": "0xC49aF4965264FA7BB6424CE37aA06773ad177224",
"interchainAccountRouter": "0xF15D70941dE2Bf95A23d6488eBCbedE0a444137f",
"interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4",
"interchainSecurityModule": "0xf2BEE9D2c15Ba9D7e06799B5912dE1F05533c141", "interchainSecurityModule": "0xf2BEE9D2c15Ba9D7e06799B5912dE1F05533c141",
"mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E",
@ -979,6 +1011,7 @@
"staticMerkleRootMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "staticMerkleRootMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A",
"staticMessageIdMultisigIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "staticMessageIdMultisigIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6",
"storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117", "storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117",
"timelockController": "0x0000000000000000000000000000000000000000",
"validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9" "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9"
}, },
"scroll": { "scroll": {
@ -1005,6 +1038,8 @@
"index": { "index": {
"from": 271840 "from": 271840
}, },
"interchainAccountIsm": "0xb89c6ED617f5F46175E41551350725A09110bbCE",
"interchainAccountRouter": "0x9629c28990F11c31735765A6FD59E1E1bC197DbD",
"interchainGasPaymaster": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", "interchainGasPaymaster": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2",
"interchainSecurityModule": "0xaDc0cB48E8DB81855A930C0C1165ea3dCe4Ba5C7", "interchainSecurityModule": "0xaDc0cB48E8DB81855A930C0C1165ea3dCe4Ba5C7",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
@ -1029,6 +1064,7 @@
"staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC",
"staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE",
"storageGasOracle": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", "storageGasOracle": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13",
"timelockController": "0x0000000000000000000000000000000000000000",
"transactionOverrides": { "transactionOverrides": {
"gasPrice": 2000000000 "gasPrice": 2000000000
}, },
@ -1057,6 +1093,8 @@
"chunk": 1000, "chunk": 1000,
"from": 73573878 "from": 73573878
}, },
"interchainAccountIsm": "0xD1E267d2d7876e97E217BfE61c34AB50FEF52807",
"interchainAccountRouter": "0x1956848601549de5aa0c887892061fA5aB4f6fC4",
"interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4",
"interchainSecurityModule": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "interchainSecurityModule": "0xBD70Ea9D599a0FC8158B026797177773C3445730",
"mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7",
@ -1085,6 +1123,7 @@
"storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117", "storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117",
"testRecipient": "0x17E216fBb22dF4ef8A6640ae9Cb147C92710ac84", "testRecipient": "0x17E216fBb22dF4ef8A6640ae9Cb147C92710ac84",
"testTokenRecipient": "0xe042D1fbDf59828dd16b9649Ede7abFc856F7a6c", "testTokenRecipient": "0xe042D1fbDf59828dd16b9649Ede7abFc856F7a6c",
"timelockController": "0x0000000000000000000000000000000000000000",
"validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9" "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9"
} }
}, },

@ -227,7 +227,7 @@
"domainRoutingIsmFactory": "0x54148470292C24345fb828B003461a9444414517", "domainRoutingIsmFactory": "0x54148470292C24345fb828B003461a9444414517",
"fallbackRoutingHook": "0x19Be55D859368e02d7b9C00803Eb677BDC1359Bd", "fallbackRoutingHook": "0x19Be55D859368e02d7b9C00803Eb677BDC1359Bd",
"index": { "index": {
"from": 4206 "from": 5284139
}, },
"interchainAccountIsm": "0x7c115c16E34c74afdb88bd268EaB19bC705891FE", "interchainAccountIsm": "0x7c115c16E34c74afdb88bd268EaB19bC705891FE",
"interchainAccountRouter": "0xB6F8aA9B1b314A6E6DFB465DD3e0E95936347517", "interchainAccountRouter": "0xB6F8aA9B1b314A6E6DFB465DD3e0E95936347517",
@ -382,10 +382,10 @@
"solanatestnet": { "solanatestnet": {
"blockExplorers": [ "blockExplorers": [
{ {
"apiUrl": "https://explorer.solana.com", "apiUrl": "https://explorer.solana.com?cluster=testnet",
"family": "other", "family": "other",
"name": "Solana Explorer", "name": "Solana Explorer",
"url": "https://explorer.solana.com" "url": "https://explorer.solana.com?cluster=testnet"
} }
], ],
"blocks": { "blocks": {

@ -6,7 +6,9 @@ cd "$(dirname "$0")"
# Define the artifacts directory # Define the artifacts directory
artifactsDir="./artifacts/build-info" artifactsDir="./artifacts/build-info"
# Define the output file # Define the output file
outputFile="./buildArtifact.json" outputFileJson="./dist/buildArtifact.json"
outputFileJs="./dist/buildArtifact.js"
outputFileTsd="./dist/buildArtifact.d.ts"
# log that we're in the script # log that we're in the script
echo 'Finding and processing hardhat build artifact...' echo 'Finding and processing hardhat build artifact...'
@ -26,7 +28,10 @@ if [ ! -f "$jsonFiles" ]; then
fi fi
# Extract required keys and write to outputFile # Extract required keys and write to outputFile
if jq -c '{input, solcLongVersion}' "$jsonFiles" > "$outputFile"; then if jq -c '{input, solcLongVersion}' "$jsonFiles" > "$outputFileJson"; then
echo "export const buildArtifact = " > "$outputFileJs"
cat "$outputFileJson" >> "$outputFileJs"
echo "export const buildArtifact: any" > "$outputFileTsd"
echo 'Finished processing build artifact.' echo 'Finished processing build artifact.'
else else
echo 'Failed to process build artifact with jq' echo 'Failed to process build artifact with jq'

@ -37,12 +37,12 @@
"exports": { "exports": {
".": "./dist/index.js", ".": "./dist/index.js",
"./mailbox": "./dist/contracts/Mailbox.js", "./mailbox": "./dist/contracts/Mailbox.js",
"./buildArtifact.json": "./buildArtifact.json", "./buildArtifact.js": "./dist/buildArtifact.js",
"./buildArtifact.json": "./dist/buildArtifact.json",
"./contracts": "./contracts" "./contracts": "./contracts"
}, },
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"files": [ "files": [
"/buildArtifact.json",
"/dist", "/dist",
"/contracts" "/contracts"
], ],
@ -57,7 +57,7 @@
], ],
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": { "scripts": {
"build": "yarn hardhat-esm compile && ./exportBuildArtifact.sh && tsc", "build": "yarn hardhat-esm compile && tsc && ./exportBuildArtifact.sh",
"lint": "solhint contracts/**/*.sol", "lint": "solhint contracts/**/*.sol",
"clean": "yarn hardhat-esm clean && rm -rf ./dist ./cache ./types ./coverage ./out ./forge-cache", "clean": "yarn hardhat-esm clean && rm -rf ./dist ./cache ./types ./coverage ./out ./forge-cache",
"coverage": "./coverage.sh", "coverage": "./coverage.sh",

@ -1,5 +1,13 @@
.env* .env*
/dist /dist
/cache /cache
# Deployment artifacts and local registry configs
/configs /configs
/artifacts /artifacts
/chains
/deployments
# Test artifacts
/test-configs/**/addresses.yaml
/test-configs/*/deployments

@ -15,14 +15,11 @@ _main() {
# with the routing over igp hook (which is closer to production deployment) # with the routing over igp hook (which is closer to production deployment)
TEST_TYPE=$1 TEST_TYPE=$1
if [ -z "$TEST_TYPE" ]; then if [ -z "$TEST_TYPE" ]; then
echo "Usage: ci-test.sh <test-type>" echo "Usage: ci-test.sh <$TEST_TYPE_PRESET_HOOK | $TEST_TYPE_CONFIGURED_HOOK | $TEST_TYPE_PI_CORE>"
exit 1 exit 1
fi fi
HOOK_FLAG=false prepare_environment_vars;
if [ "$TEST_TYPE" == $TEST_TYPE_CONFIGURED_HOOK ]; then
HOOK_FLAG=true
fi
prepare_anvil; prepare_anvil;
@ -48,23 +45,36 @@ _main() {
echo "Done"; echo "Done";
} }
prepare_anvil() { prepare_environment_vars() {
ANVIL_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 ANVIL_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
CHAIN1=anvil1 CHAIN1=anvil1
CHAIN2=anvil2 CHAIN2=anvil2
EXAMPLES_PATH=./examples EXAMPLES_PATH=./examples
TEST_CONFIGS_PATH=./test-configs
CLI_PATH=./typescript/cli
REGISTRY_PATH="$TEST_CONFIGS_PATH/anvil"
CORE_ISM_PATH="$EXAMPLES_PATH/ism.yaml"
WARP_DEPLOY_CONFIG_PATH="$EXAMPLES_PATH/warp-route-deployment.yaml"
DEPLOY_ERC20_PATH=./src/tests/deployTestErc20.ts DEPLOY_ERC20_PATH=./src/tests/deployTestErc20.ts
# use different chain names and config for pi<>core test # use different chain names and config for pi<>core test
if [ "$TEST_TYPE" == $TEST_TYPE_PI_CORE ]; then if [ "$TEST_TYPE" == $TEST_TYPE_PI_CORE ]; then
CHAIN1=anvil
CHAIN2=ethereum CHAIN2=ethereum
EXAMPLES_PATH=./examples/fork REGISTRY_PATH="$TEST_CONFIGS_PATH/fork"
CORE_ISM_PATH="$REGISTRY_PATH/ism.yaml"
WARP_DEPLOY_CONFIG_PATH="$REGISTRY_PATH/warp-route-deployment.yaml"
fi fi
CHAIN1_CAPS=$(echo "${CHAIN1}" | tr '[:lower:]' '[:upper:]') CHAIN1_CAPS=$(echo "${CHAIN1}" | tr '[:lower:]' '[:upper:]')
CHAIN2_CAPS=$(echo "${CHAIN2}" | tr '[:lower:]' '[:upper:]') CHAIN2_CAPS=$(echo "${CHAIN2}" | tr '[:lower:]' '[:upper:]')
HOOK_FLAG=false
if [ "$TEST_TYPE" == $TEST_TYPE_CONFIGURED_HOOK ]; then
HOOK_FLAG=true
fi
}
prepare_anvil() {
CHAIN1_PORT=8545 CHAIN1_PORT=8545
CHAIN2_PORT=8555 CHAIN2_PORT=8555
@ -73,6 +83,8 @@ prepare_anvil() {
rm -rf /tmp/${CHAIN1}* rm -rf /tmp/${CHAIN1}*
rm -rf /tmp/${CHAIN2}* rm -rf /tmp/${CHAIN2}*
rm -rf /tmp/relayer rm -rf /tmp/relayer
rm -f $CLI_PATH/$TEST_CONFIGS_PATH/*/chains/*/addresses.yaml
rm -rf $CLI_PATH/$TEST_CONFIGS_PATH/*/deployments
if [[ $OSTYPE == 'darwin'* ]]; then if [[ $OSTYPE == 'darwin'* ]]; then
# kill child processes on exit, but only locally because # kill child processes on exit, but only locally because
@ -95,7 +107,7 @@ prepare_anvil() {
if [ "$TEST_TYPE" == $TEST_TYPE_PI_CORE ]; then if [ "$TEST_TYPE" == $TEST_TYPE_PI_CORE ]; then
# Fetch the RPC of chain to fork # Fetch the RPC of chain to fork
cd typescript/infra cd typescript/infra
RPC_URL=$(yarn tsx scripts/print-chain-metadatas.ts -e mainnet3 | jq -r ".${CHAIN2}.rpcUrls[0].http") RPC_URL=$(LOG_LEVEL=error yarn tsx scripts/print-chain-metadatas.ts -e mainnet3 | jq -r ".${CHAIN2}.rpcUrls[0].http")
cd ../../ cd ../../
# run the fork chain # run the fork chain
@ -112,8 +124,6 @@ prepare_anvil() {
fi fi
set -e set -e
echo "{}" > /tmp/empty-artifacts.json
} }
reset_anvil() { reset_anvil() {
@ -129,31 +139,20 @@ run_hyperlane_deploy_core_dry_run() {
return; return;
fi fi
BEFORE_CORE_DRY_RUN=$(cast balance $DEPLOYER --rpc-url http://127.0.0.1:${CHAIN1_PORT}); update_deployer_balance;
echo -e "\nDry-running contract deployments to Alfajores" echo -e "\nDry-running contract deployments to Alfajores"
yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \
--dry-run alfajores \ --dry-run alfajores \
--targets alfajores \ --targets alfajores \
--chains ${EXAMPLES_PATH}/dry-run/anvil-chains.yaml \ --registry ${TEST_CONFIGS_PATH}/dry-run \
--artifacts /tmp/empty-artifacts.json \ --overrides " " \
$(if [ "$HOOK_FLAG" == "true" ]; then echo "--hook ${EXAMPLES_PATH}/hooks.yaml"; fi) \ $(if [ "$HOOK_FLAG" == "true" ]; then echo "--hook ${EXAMPLES_PATH}/hooks.yaml"; fi) \
--ism ${EXAMPLES_PATH}/ism.yaml \ --ism ${TEST_CONFIGS_PATH}/dry-run/ism.yaml \
--out /tmp \
--key 0xfaD1C94469700833717Fa8a3017278BC1cA8031C \ --key 0xfaD1C94469700833717Fa8a3017278BC1cA8031C \
--yes --yes
AFTER_CORE_DRY_RUN=$(cast balance $DEPLOYER --rpc-url http://127.0.0.1:${CHAIN1_PORT}) check_deployer_balance;
GAS_PRICE=$(cast gas-price --rpc-url http://127.0.0.1:${CHAIN1_PORT})
CORE_MIN_GAS=$(bc <<< "($BEFORE_CORE_DRY_RUN - $AFTER_CORE_DRY_RUN) / $GAS_PRICE")
echo "Gas used: $CORE_MIN_GAS"
CORE_ARTIFACTS_PATH=`find /tmp/core-deployment* -type f -exec ls -t1 {} + | head -1`
echo "Core artifacts:"
echo $CORE_ARTIFACTS_PATH
cat $CORE_ARTIFACTS_PATH
AGENT_CONFIG_FILENAME=`ls -t1 /tmp | grep agent-config | head -1`
} }
run_hyperlane_deploy_warp_dry_run() { run_hyperlane_deploy_warp_dry_run() {
@ -161,63 +160,44 @@ run_hyperlane_deploy_warp_dry_run() {
return; return;
fi fi
BEFORE_WARP_DRY_RUN=$(cast balance $DEPLOYER --rpc-url http://127.0.0.1:${CHAIN1_PORT}); update_deployer_balance;
echo -e "\nDry-running warp route deployments to Alfajores" echo -e "\nDry-running warp route deployments to Alfajores"
yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \ yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \
--dry-run alfajores \ --dry-run alfajores \
--chains ${EXAMPLES_PATH}/dry-run/anvil-chains.yaml \ --overrides ${TEST_CONFIGS_PATH}/dry-run \
--core $CORE_ARTIFACTS_PATH \ --config ${TEST_CONFIGS_PATH}/dry-run/warp-route-deployment.yaml \
--config ${EXAMPLES_PATH}/dry-run/warp-route-deployment.yaml \
--out /tmp \
--key 0xfaD1C94469700833717Fa8a3017278BC1cA8031C \ --key 0xfaD1C94469700833717Fa8a3017278BC1cA8031C \
--yes --yes
AFTER_WARP_DRY_RUN=$(cast balance $DEPLOYER --rpc-url http://127.0.0.1:${CHAIN1_PORT}) check_deployer_balance;
GAS_PRICE=$(cast gas-price --rpc-url http://127.0.0.1:${CHAIN1_PORT})
WARP_MIN_GAS=$(bc <<< "($BEFORE_WARP_DRY_RUN - $AFTER_WARP_DRY_RUN) / $GAS_PRICE")
echo "Gas used: $WARP_MIN_GAS"
WARP_ARTIFACTS_PATH=`find /tmp/dry-run_warp-route-deployment* -type f -exec ls -t1 {} + | head -1`
echo "Warp dry-run artifacts:"
echo $WARP_ARTIFACTS_PATH
cat $WARP_ARTIFACTS_PATH
} }
run_hyperlane_deploy_core() { run_hyperlane_deploy_core() {
BEFORE_CORE=$(cast balance $DEPLOYER --rpc-url http://127.0.0.1:${CHAIN1_PORT}); update_deployer_balance;
echo -e "\nDeploying contracts to ${CHAIN1} and ${CHAIN2}" echo -e "\nDeploying contracts to ${CHAIN1} and ${CHAIN2}"
yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \
--registry $REGISTRY_PATH \
--overrides " " \
--targets ${CHAIN1},${CHAIN2} \ --targets ${CHAIN1},${CHAIN2} \
--chains ${EXAMPLES_PATH}/anvil-chains.yaml \
--artifacts /tmp/empty-artifacts.json \
$(if [ "$HOOK_FLAG" == "true" ]; then echo "--hook ${EXAMPLES_PATH}/hooks.yaml"; fi) \ $(if [ "$HOOK_FLAG" == "true" ]; then echo "--hook ${EXAMPLES_PATH}/hooks.yaml"; fi) \
--ism ${EXAMPLES_PATH}/ism.yaml \ --ism $CORE_ISM_PATH \
--out /tmp \ --agent /tmp/agent-config.json \
--key $ANVIL_KEY \ --key $ANVIL_KEY \
--yes --yes
AFTER_CORE=$(cast balance $DEPLOYER --rpc-url http://127.0.0.1:${CHAIN1_PORT}) check_deployer_balance;
GAS_PRICE=$(cast gas-price --rpc-url http://127.0.0.1:${CHAIN1_PORT})
CORE_MIN_GAS=$(bc <<< "($BEFORE_CORE - $AFTER_CORE) / $GAS_PRICE")
echo "Gas used: $CORE_MIN_GAS"
CORE_ARTIFACTS_PATH=`find /tmp/core-deployment* -type f -exec ls -t1 {} + | head -1`
echo "Core artifacts:"
echo $CORE_ARTIFACTS_PATH
cat $CORE_ARTIFACTS_PATH
AGENT_CONFIG_FILENAME=`ls -t1 /tmp | grep agent-config | head -1`
} }
run_hyperlane_deploy_warp() { run_hyperlane_deploy_warp() {
update_deployer_balance;
echo -e "\nDeploying hypNative warp route" echo -e "\nDeploying hypNative warp route"
yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \ yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \
--chains ${EXAMPLES_PATH}/anvil-chains.yaml \ --registry $REGISTRY_PATH \
--core $CORE_ARTIFACTS_PATH \ --overrides " " \
--config ${EXAMPLES_PATH}/warp-route-deployment.yaml \ --config $WARP_DEPLOY_CONFIG_PATH \
--out /tmp \
--key $ANVIL_KEY \ --key $ANVIL_KEY \
--yes --yes
@ -228,51 +208,43 @@ run_hyperlane_deploy_warp() {
echo "Deploying hypCollateral warp route" echo "Deploying hypCollateral warp route"
yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \ yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \
--chains ${EXAMPLES_PATH}/anvil-chains.yaml \ --registry $REGISTRY_PATH \
--core $CORE_ARTIFACTS_PATH \ --overrides " " \
--config /tmp/warp-collateral-deployment.json \ --config /tmp/warp-collateral-deployment.json \
--out /tmp \
--key $ANVIL_KEY \ --key $ANVIL_KEY \
--yes --yes
AFTER_WARP=$(cast balance $DEPLOYER --rpc-url http://127.0.0.1:${CHAIN1_PORT}) check_deployer_balance;
GAS_PRICE=$(cast gas-price --rpc-url http://127.0.0.1:${CHAIN1_PORT})
WARP_MIN_GAS=$(bc <<< "($AFTER_CORE - $AFTER_WARP) / $GAS_PRICE")
echo "Gas used: $WARP_MIN_GAS"
} }
run_hyperlane_send_message() { run_hyperlane_send_message() {
update_deployer_balance;
echo -e "\nSending test message" echo -e "\nSending test message"
yarn workspace @hyperlane-xyz/cli run hyperlane send message \ yarn workspace @hyperlane-xyz/cli run hyperlane send message \
--registry $REGISTRY_PATH \
--overrides " " \
--origin ${CHAIN1} \ --origin ${CHAIN1} \
--destination ${CHAIN2} \ --destination ${CHAIN2} \
--messageBody "Howdy!" \ --body "Howdy!" \
--chains ${EXAMPLES_PATH}/anvil-chains.yaml \
--core $CORE_ARTIFACTS_PATH \
--quick \ --quick \
--key $ANVIL_KEY \ --key $ANVIL_KEY \
| tee /tmp/message1 | tee /tmp/message1
AFTER_MSG=$(cast balance $DEPLOYER --rpc-url http://127.0.0.1:${CHAIN1_PORT}) check_deployer_balance;
GAS_PRICE=$(cast gas-price --rpc-url http://127.0.0.1:${CHAIN1_PORT})
MSG_MIN_GAS=$(bc <<< "($AFTER_WARP - $AFTER_MSG) / $GAS_PRICE")
echo "Gas used: $MSG_MIN_GAS"
MESSAGE1_ID=`cat /tmp/message1 | grep "Message ID" | grep -E -o '0x[0-9a-f]+'` MESSAGE1_ID=`cat /tmp/message1 | grep "Message ID" | grep -E -o '0x[0-9a-f]+'`
echo "Message 1 ID: $MESSAGE1_ID" echo "Message 1 ID: $MESSAGE1_ID"
WARP_CONFIG_FILE=`find /tmp/warp-config* -type f -exec ls -t1 {} + | head -1` WARP_CONFIG_FILE="$REGISTRY_PATH/deployments/warp_routes/FAKE/${CHAIN1}-${CHAIN2}-config.yaml"
CHAIN1_ROUTER="${CHAIN1_CAPS}_ROUTER"
declare $CHAIN1_ROUTER=$(jq -r --arg CHAIN1 "$CHAIN1" '.tokens[] | select(.chainName==$CHAIN1) | .addressOrDenom' $WARP_CONFIG_FILE)
echo -e "\nSending test warp transfer" echo -e "\nSending test warp transfer"
yarn workspace @hyperlane-xyz/cli run hyperlane send transfer \ yarn workspace @hyperlane-xyz/cli run hyperlane send transfer \
--registry $REGISTRY_PATH \
--overrides " " \
--origin ${CHAIN1} \ --origin ${CHAIN1} \
--destination ${CHAIN2} \ --destination ${CHAIN2} \
--chains ${EXAMPLES_PATH}/anvil-chains.yaml \
--core $CORE_ARTIFACTS_PATH \
--warp ${WARP_CONFIG_FILE} \ --warp ${WARP_CONFIG_FILE} \
--router ${!CHAIN1_ROUTER} \
--quick \ --quick \
--key $ANVIL_KEY \ --key $ANVIL_KEY \
| tee /tmp/message2 | tee /tmp/message2
@ -303,7 +275,7 @@ run_validator() {
VALIDATOR_PORT=$((VALIDATOR_PORT+1)) VALIDATOR_PORT=$((VALIDATOR_PORT+1))
echo "Running validator on $CHAIN on port $VALIDATOR_PORT" echo "Running validator on $CHAIN on port $VALIDATOR_PORT"
export CONFIG_FILES=/tmp/${AGENT_CONFIG_FILENAME} export CONFIG_FILES=/tmp/agent-config.json
export HYP_ORIGINCHAINNAME=${CHAIN} export HYP_ORIGINCHAINNAME=${CHAIN}
export HYP_VALIDATOR_INTERVAL=1 export HYP_VALIDATOR_INTERVAL=1
export HYP_VALIDATOR_TYPE=hexKey export HYP_VALIDATOR_TYPE=hexKey
@ -340,7 +312,7 @@ run_relayer() {
cargo build --bin relayer cargo build --bin relayer
echo "Running relayer" echo "Running relayer"
export CONFIG_FILES=/tmp/${AGENT_CONFIG_FILENAME} export CONFIG_FILES=/tmp/agent-config.json
export HYP_RELAYCHAINS=${CHAIN1},${CHAIN2} export HYP_RELAYCHAINS=${CHAIN1},${CHAIN2}
export HYP_ALLOWLOCALCHECKPOINTSYNCERS=true export HYP_ALLOWLOCALCHECKPOINTSYNCERS=true
export HYP_DB=/tmp/relayer export HYP_DB=/tmp/relayer
@ -367,8 +339,8 @@ run_hyperlane_status() {
yarn workspace @hyperlane-xyz/cli run hyperlane status \ yarn workspace @hyperlane-xyz/cli run hyperlane status \
--id $2 \ --id $2 \
--destination ${CHAIN2} \ --destination ${CHAIN2} \
--chains ${EXAMPLES_PATH}/anvil-chains.yaml \ --registry $REGISTRY_PATH \
--core $CORE_ARTIFACTS_PATH \ --overrides " " \
| tee /tmp/message-status-$1 | tee /tmp/message-status-$1
if ! grep -q "$2 was delivered" /tmp/message-status-$1; then if ! grep -q "$2 was delivered" /tmp/message-status-$1; then
echo "ERROR: Message $1 was not delivered" echo "ERROR: Message $1 was not delivered"
@ -379,6 +351,17 @@ run_hyperlane_status() {
done done
} }
update_deployer_balance() {
OLD_BALANCE=$(cast balance $DEPLOYER --rpc-url http://127.0.0.1:${CHAIN1_PORT});
}
check_deployer_balance() {
NEW_BALANCE=$(cast balance $DEPLOYER --rpc-url http://127.0.0.1:${CHAIN1_PORT})
GAS_PRICE=$(cast gas-price --rpc-url http://127.0.0.1:${CHAIN1_PORT})
GAS_USED=$(bc <<< "($OLD_BALANCE - $NEW_BALANCE) / $GAS_PRICE")
echo "Gas used: $GAS_USED"
}
_main "$@"; _main "$@";
exit; exit;

@ -11,11 +11,16 @@ import { deployCommand } from './src/commands/deploy.js';
import { hookCommand } from './src/commands/hook.js'; import { hookCommand } from './src/commands/hook.js';
import { ismCommand } from './src/commands/ism.js'; import { ismCommand } from './src/commands/ism.js';
import { import {
keyCommandOption,
logFormatCommandOption, logFormatCommandOption,
logLevelCommandOption, logLevelCommandOption,
overrideRegistryUriCommandOption,
registryUriCommandOption,
skipConfirmationOption,
} from './src/commands/options.js'; } from './src/commands/options.js';
import { sendCommand } from './src/commands/send.js'; import { sendCommand } from './src/commands/send.js';
import { statusCommand } from './src/commands/status.js'; import { statusCommand } from './src/commands/status.js';
import { contextMiddleware } from './src/context/context.js';
import { configureLogger, errorRed } from './src/logger.js'; import { configureLogger, errorRed } from './src/logger.js';
import { checkVersion } from './src/utils/version-check.js'; import { checkVersion } from './src/utils/version-check.js';
import { VERSION } from './src/version.js'; import { VERSION } from './src/version.js';
@ -32,10 +37,17 @@ try {
.scriptName('hyperlane') .scriptName('hyperlane')
.option('log', logFormatCommandOption) .option('log', logFormatCommandOption)
.option('verbosity', logLevelCommandOption) .option('verbosity', logLevelCommandOption)
.global(['log', 'verbosity']) .option('registry', registryUriCommandOption)
.middleware((argv) => { .option('overrides', overrideRegistryUriCommandOption)
configureLogger(argv.log as LogFormat, argv.verbosity as LogLevel); .option('key', keyCommandOption)
}) .option('yes', skipConfirmationOption)
.global(['log', 'verbosity', 'registry', 'overrides', 'yes'])
.middleware([
(argv) => {
configureLogger(argv.log as LogFormat, argv.verbosity as LogLevel);
},
contextMiddleware,
])
.command(chainsCommand) .command(chainsCommand)
.command(configCommand) .command(configCommand)
.command(deployCommand) .command(deployCommand)

@ -1,22 +0,0 @@
# Configs for describing chain metadata for use in Hyperlane deployments or apps
# Consists of a map of chain names to metadata
# Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts
---
anvil1:
chainId: 31337
domainId: 31337
name: anvil1
protocol: ethereum
rpcUrls:
- http: http://127.0.0.1:8545
nativeToken:
name: Ether
symbol: ETH
decimals: 18
anvil2:
chainId: 31338
domainId: 31338
name: anvil2
protocol: ethereum
rpcUrls:
- http: http://127.0.0.1:8555

@ -1,50 +1,42 @@
# Configs for describing chain metadata for use in Hyperlane deployments or apps # Configs for describing chain metadata for use in Hyperlane deployments or apps
# Consists of a map of chain names to metadata
# Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts # Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts
--- ---
# You can define a full config for a new chain # Required fields:
mychainname: chainId: 1234567890 # Number: Use EIP-155 for EVM chains
domainId: 1234567890 # Number: Recommend matching chainId when possible
name: mychainname # String: Unique identifier for the chain, must match key above
protocol: ethereum # ProtocolType: Ethereum, Sealevel, etc.
rpcUrls: # Array: List of RPC configs
# Only http field is required
- http: https://mychain.com/rpc # String: HTTP URL of the RPC endpoint (preferably HTTPS)
# Others here are optional
pagination:
maxBlockRange: 1000 # Number
maxBlockAge: 1000 # Number
minBlockNumber: 1000 # Number
retry:
maxRequests: 5 # Number
baseRetryMs: 1000 # Number
# Optional fields, not required for Hyperlane deployments but useful for apps:
isTestnet: false # Boolean: Whether the chain is considered a testnet or a mainnet
blockExplorers: # Array: List of BlockExplorer configs
# Required fields: # Required fields:
chainId: 1234567890 # Number: Use EIP-155 for EVM chains - name: My Chain Explorer # String: Human-readable name for the explorer
domainId: 1234567890 # Number: Recommend matching chainId when possible url: https://mychain.com/explorer # String: Base URL for the explorer
name: mychainname # String: Unique identifier for the chain, must match key above apiUrl: https://mychain.com/api # String: Base URL for the explorer API
protocol: ethereum # ProtocolType: Ethereum, Sealevel, etc. # Optional fields:
rpcUrls: # Array: List of RPC configs apiKey: myapikey # String: API key for the explorer (optional)
# Only http field is required family: etherscan # ExplorerFamily: See ExplorerFamily for valid values
- http: https://mychain.com/rpc # String: HTTP URL of the RPC endpoint (preferably HTTPS) nativeToken:
# Others here are optional name: Eth # String
pagination: symbol: ETH # String
maxBlockRange: 1000 # Number decimals: 18 # Number
maxBlockAge: 1000 # Number displayName: My Chain Name # String: Human-readable name of the chain
minBlockNumber: 1000 # Number displayNameShort: My Chain # String: A shorter human-readable name
retry: logoURI: https://mychain.com/logo.png # String: URI to a logo image for the chain
maxRequests: 5 # Number blocks:
baseRetryMs: 1000 # Number confirmations: 12 # Number: Blocks to wait before considering a transaction confirmed
# Optional fields, not required for Hyperlane deployments but useful for apps: reorgPeriod: 100 # Number: Blocks before a transaction has a near-zero chance of reverting
isTestnet: false # Boolean: Whether the chain is considered a testnet or a mainnet estimateBlockTime: 15 # Number: Rough estimate of time per block in seconds
blockExplorers: # Array: List of BlockExplorer configs # transactionOverrides: # Object: Properties to include when forming transaction requests
# Required fields: # Any tx fields are allowed
- name: My Chain Explorer # String: Human-readable name for the explorer
url: https://mychain.com/explorer # String: Base URL for the explorer
apiUrl: https://mychain.com/api # String: Base URL for the explorer API
# Optional fields:
apiKey: myapikey # String: API key for the explorer (optional)
family: etherscan # ExplorerFamily: See ExplorerFamily for valid values
nativeToken:
name: Eth # String
symbol: ETH # String
decimals: 18 # Number
displayName: My Chain Name # String: Human-readable name of the chain
displayNameShort: My Chain # String: A shorter human-readable name
logoURI: https://mychain.com/logo.png # String: URI to a logo image for the chain
blocks:
confirmations: 12 # Number: Blocks to wait before considering a transaction confirmed
reorgPeriod: 100 # Number: Blocks before a transaction has a near-zero chance of reverting
estimateBlockTime: 15 # Number: Rough estimate of time per block in seconds
# transactionOverrides: # Object: Properties to include when forming transaction requests
# Any tx fields are allowed
# Alternatively, you can extend a core chain config with only fields to be overridden
sepolia:
rpcUrls:
- http: https://mycustomrpc.com

@ -1,17 +0,0 @@
anvil:
chainId: 31337
domainId: 31337
name: anvil
protocol: ethereum
rpcUrls:
- http: http://127.0.0.1:8545
nativeToken:
name: Ether
symbol: ETH
decimals: 18
alfajores:
rpcUrls:
- http: https://alfajores-forno.celo-testnet.org
blocks:
confirmations: 1
estimateBlockTime: 1

@ -1,21 +0,0 @@
# Configs for describing chain metadata for use in Hyperlane deployments or apps
# Consists of a map of chain names to metadata
# Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts
---
anvil:
chainId: 31337
domainId: 31337
name: anvil
protocol: ethereum
rpcUrls:
- http: http://127.0.0.1:8545
nativeToken:
name: Ether
symbol: ETH
decimals: 18
ethereum:
rpcUrls:
- http: http://127.0.0.1:8555
blocks:
confirmations: 1
estimateBlockTime: 1

@ -3,6 +3,7 @@
"version": "3.10.0", "version": "3.10.0",
"description": "A command-line utility for common Hyperlane operations", "description": "A command-line utility for common Hyperlane operations",
"dependencies": { "dependencies": {
"@hyperlane-xyz/registry": "^1.0.7",
"@hyperlane-xyz/sdk": "3.10.0", "@hyperlane-xyz/sdk": "3.10.0",
"@hyperlane-xyz/utils": "3.10.0", "@hyperlane-xyz/utils": "3.10.0",
"@inquirer/prompts": "^3.0.0", "@inquirer/prompts": "^3.0.0",
@ -12,7 +13,7 @@
"latest-version": "^8.0.0", "latest-version": "^8.0.0",
"terminal-link": "^3.0.0", "terminal-link": "^3.0.0",
"tsx": "^4.7.1", "tsx": "^4.7.1",
"yaml": "^2.3.1", "yaml": "^2.4.1",
"yargs": "^17.7.2", "yargs": "^17.7.2",
"zod": "^3.21.2" "zod": "^3.21.2"
}, },

@ -1,22 +1,17 @@
import { CommandModule } from 'yargs'; import { CommandModule } from 'yargs';
import { import { CommandModuleWithContext } from '../context/types.js';
Chains,
CoreChainName,
HyperlaneEnvironment,
chainMetadata,
hyperlaneContractAddresses,
hyperlaneEnvironments,
} from '@hyperlane-xyz/sdk';
import { log, logBlue, logGray, logTable } from '../logger.js'; import { log, logBlue, logGray, logTable } from '../logger.js';
const ChainTypes = ['mainnet', 'testnet'];
type ChainType = (typeof ChainTypes)[number];
/** /**
* Parent command * Parent command
*/ */
export const chainsCommand: CommandModule = { export const chainsCommand: CommandModule = {
command: 'chains', command: 'chains',
describe: 'View information about core Hyperlane chains', describe: 'View information about Hyperlane chains in a registry',
builder: (yargs) => builder: (yargs) =>
yargs yargs
.command(listCommand) .command(listCommand)
@ -29,39 +24,39 @@ export const chainsCommand: CommandModule = {
/** /**
* List command * List command
*/ */
const listCommand: CommandModule = { const listCommand: CommandModuleWithContext<{ type: ChainType }> = {
command: 'list', command: 'list',
describe: 'List all core chains included in the Hyperlane SDK', describe: 'List all chains included in a registry',
builder: (yargs) => builder: {
yargs.option('environment', { type: {
alias: 'e', describe: 'Specify the type of chains',
describe: 'Specify the environment to list chains for', choices: ChainTypes,
choices: ['mainnet', 'testnet'], },
}), },
handler: (args) => { handler: async ({ type, context }) => {
const environment = args.environment as HyperlaneEnvironment | undefined; const logChainsForType = (type: ChainType) => {
logBlue(`\nHyperlane ${type} chains:`);
const serializer = (env: HyperlaneEnvironment) => logGray('------------------------------');
Object.keys(hyperlaneEnvironments[env]).reduce<any>((result, chain) => { const chains = Object.values(context.chainMetadata).filter((c) => {
const { chainId, displayName } = chainMetadata[chain]; if (type === 'mainnet') return !c.isTestnet;
result[chain] = { else return !!c.isTestnet;
});
const tableData = chains.reduce<any>((result, chain) => {
const { chainId, displayName } = chain;
result[chain.name] = {
'Display Name': displayName, 'Display Name': displayName,
'Chain Id': chainId, 'Chain Id': chainId,
}; };
return result; return result;
}, {}); }, {});
logTable(tableData);
const logChainsForEnv = (env: HyperlaneEnvironment) => {
logBlue(`\nHyperlane core ${env} chains:`);
logGray('------------------------------');
logTable(serializer(env));
}; };
if (environment) { if (type) {
logChainsForEnv(environment); logChainsForType(type);
} else { } else {
logChainsForEnv('mainnet'); logChainsForType('mainnet');
logChainsForEnv('testnet'); logChainsForType('testnet');
} }
}, },
}; };
@ -69,28 +64,27 @@ const listCommand: CommandModule = {
/** /**
* Addresses command * Addresses command
*/ */
const addressesCommand: CommandModule = { const addressesCommand: CommandModuleWithContext<{ name: string }> = {
command: 'addresses', command: 'addresses',
describe: 'Display the addresses of core Hyperlane contracts', describe: 'Display the addresses of core Hyperlane contracts',
builder: (yargs) => builder: {
yargs.options({ name: {
name: { type: 'string',
type: 'string', description: 'Chain to display addresses for',
description: 'Chain to display addresses for', alias: 'chain',
choices: Object.values(Chains), },
alias: 'chain', },
}, handler: async ({ name, context }) => {
}), if (name) {
handler: (args) => { const result = await context.registry.getChainAddresses(name);
const name = args.name as CoreChainName | undefined;
if (name && hyperlaneContractAddresses[name]) {
logBlue('Hyperlane contract addresses for:', name); logBlue('Hyperlane contract addresses for:', name);
logGray('---------------------------------'); logGray('---------------------------------');
log(JSON.stringify(hyperlaneContractAddresses[name], null, 2)); log(JSON.stringify(result, null, 2));
} else { } else {
logBlue('Hyperlane core contract addresses:'); const result = await context.registry.getAddresses();
logBlue('Hyperlane contract addresses:');
logGray('----------------------------------'); logGray('----------------------------------');
log(JSON.stringify(hyperlaneContractAddresses, null, 2)); log(JSON.stringify(result, null, 2));
} }
}, },
}; };

@ -11,14 +11,10 @@ import {
createWarpRouteDeployConfig, createWarpRouteDeployConfig,
readWarpRouteDeployConfig, readWarpRouteDeployConfig,
} from '../config/warp.js'; } from '../config/warp.js';
import { CommandModuleWithContext } from '../context/types.js';
import { log, logGreen } from '../logger.js'; import { log, logGreen } from '../logger.js';
import { FileFormat } from '../utils/files.js';
import { import { inputFileOption, outputFileOption } from './options.js';
chainsCommandOption,
fileFormatOption,
outputFileOption,
} from './options.js';
/** /**
* Parent command * Parent command
@ -52,84 +48,61 @@ const createCommand: CommandModule = {
handler: () => log('Command required'), handler: () => log('Command required'),
}; };
const createChainConfigCommand: CommandModule = { const createChainConfigCommand: CommandModuleWithContext<{}> = {
command: 'chain', command: 'chain',
describe: 'Create a new, minimal Hyperlane chain config (aka chain metadata)', describe: 'Create a new, minimal Hyperlane chain config (aka chain metadata)',
builder: (yargs) => handler: async ({ context }) => {
yargs.options({ await createChainConfig({ context });
output: outputFileOption('./configs/chains.yaml'),
format: fileFormatOption,
}),
handler: async (argv: any) => {
const format: FileFormat = argv.format;
const outPath: string = argv.output;
await createChainConfig({ format, outPath });
process.exit(0); process.exit(0);
}, },
}; };
const createIsmConfigCommand: CommandModule = { const createIsmConfigCommand: CommandModuleWithContext<{
out: string;
advanced: boolean;
}> = {
command: 'ism', command: 'ism',
describe: 'Create a basic or advanced ISM config for a validator set', describe: 'Create a basic or advanced ISM config for a validator set',
builder: (yargs) => builder: {
yargs.options({ out: outputFileOption('./configs/ism.yaml'),
output: outputFileOption('./configs/ism.yaml'), advanced: {
format: fileFormatOption, type: 'boolean',
chains: chainsCommandOption, describe: 'Create an advanced ISM configuration',
advanced: { default: false,
type: 'boolean', },
describe: 'Create an advanced ISM configuration', },
default: false, handler: async ({ context, out, advanced }) => {
}, if (advanced) {
}), await createIsmConfigMap({ context, outPath: out });
handler: async (argv: any) => {
const format: FileFormat = argv.format;
const outPath: string = argv.output;
const chainConfigPath: string = argv.chains;
const isAdvanced: boolean = argv.advanced;
if (isAdvanced) {
await createIsmConfigMap({ format, outPath, chainConfigPath });
} else { } else {
await createMultisigConfig({ format, outPath, chainConfigPath }); await createMultisigConfig({ context, outPath: out });
} }
process.exit(0); process.exit(0);
}, },
}; };
const createHookConfigCommand: CommandModule = { const createHookConfigCommand: CommandModuleWithContext<{ out: string }> = {
command: 'hooks', command: 'hooks',
describe: 'Create a new hooks config (required & default)', describe: 'Create a new hooks config (required & default)',
builder: (yargs) => builder: {
yargs.options({ out: outputFileOption('./configs/hooks.yaml'),
output: outputFileOption('./configs/hooks.yaml'), },
format: fileFormatOption, handler: async ({ context, out }) => {
chains: chainsCommandOption, await createHooksConfigMap({ context, outPath: out });
}),
handler: async (argv: any) => {
const format: FileFormat = argv.format;
const outPath: string = argv.output;
const chainConfigPath: string = argv.chains;
await createHooksConfigMap({ format, outPath, chainConfigPath });
process.exit(0); process.exit(0);
}, },
}; };
const createWarpRouteDeployConfigCommand: CommandModule = { const createWarpRouteDeployConfigCommand: CommandModuleWithContext<{
out: string;
}> = {
command: 'warp', command: 'warp',
describe: 'Create a new Warp Route deployment config', describe: 'Create a new Warp Route deployment config',
builder: (yargs) => builder: {
yargs.options({ out: outputFileOption('./configs/warp-route-deployment.yaml'),
output: outputFileOption('./configs/warp-route-deployment.yaml'), },
format: fileFormatOption, handler: async ({ context, out }) => {
chains: chainsCommandOption, await createWarpRouteDeployConfig({ context, outPath: out });
}),
handler: async (argv: any) => {
const format: FileFormat = argv.format;
const outPath: string = argv.output;
const chainConfigPath: string = argv.chains;
await createWarpRouteDeployConfig({ format, outPath, chainConfigPath });
process.exit(0); process.exit(0);
}, },
}; };
@ -151,75 +124,52 @@ const validateCommand: CommandModule = {
handler: () => log('Command required'), handler: () => log('Command required'),
}; };
const validateChainCommand: CommandModule = { const validateChainCommand: CommandModuleWithContext<{ path: string }> = {
command: 'chain', command: 'chain',
describe: 'Validate a chain config in a YAML or JSON file', describe: 'Validate a chain config file',
builder: (yargs) => builder: {
yargs.options({ path: inputFileOption,
path: { },
type: 'string', handler: async ({ path }) => {
description: 'Input file path',
demandOption: true,
},
}),
handler: async (argv) => {
const path = argv.path as string;
readChainConfigs(path); readChainConfigs(path);
logGreen(`All chain configs in ${path} are valid`);
process.exit(0); process.exit(0);
}, },
}; };
const validateIsmCommand: CommandModule = { const validateIsmCommand: CommandModuleWithContext<{ path: string }> = {
command: 'ism', command: 'ism',
describe: 'Validate the basic ISM config in a YAML or JSON file', describe: 'Validate the basic ISM config file',
builder: (yargs) => builder: {
yargs.options({ path: inputFileOption,
path: { },
type: 'string', handler: async ({ path }) => {
description: 'Input file path',
demandOption: true,
},
}),
handler: async (argv) => {
const path = argv.path as string;
readMultisigConfig(path); readMultisigConfig(path);
logGreen('Config is valid'); logGreen('Config is valid');
process.exit(0); process.exit(0);
}, },
}; };
const validateIsmAdvancedCommand: CommandModule = { const validateIsmAdvancedCommand: CommandModuleWithContext<{ path: string }> = {
command: 'ism-advanced', command: 'ism-advanced',
describe: 'Validate the advanced ISM config in a YAML or JSON file', describe: 'Validate the advanced ISM config file',
builder: (yargs) => builder: {
yargs.options({ path: inputFileOption,
path: { },
type: 'string', handler: async ({ path }) => {
description: 'Input file path',
demandOption: true,
},
}),
handler: async (argv) => {
const path = argv.path as string;
readIsmConfig(path); readIsmConfig(path);
logGreen('Config is valid'); logGreen('Config is valid');
process.exit(0); process.exit(0);
}, },
}; };
const validateWarpCommand: CommandModule = { const validateWarpCommand: CommandModuleWithContext<{ path: string }> = {
command: 'warp', command: 'warp',
describe: 'Validate a Warp Route config in a YAML or JSON file', describe: 'Validate a Warp Route deployment config file',
builder: (yargs) => builder: {
yargs.options({ path: inputFileOption,
path: { },
type: 'string', handler: async ({ path }) => {
description: 'Input file path',
demandOption: true,
},
}),
handler: async (argv) => {
const path = argv.path as string;
readWarpRouteDeployConfig(path); readWarpRouteDeployConfig(path);
logGreen('Config is valid'); logGreen('Config is valid');
process.exit(0); process.exit(0);

@ -1,5 +1,9 @@
import { CommandModule } from 'yargs'; import { CommandModule } from 'yargs';
import {
CommandModuleWithContext,
CommandModuleWithWriteContext,
} from '../context/types.js';
import { runKurtosisAgentDeploy } from '../deploy/agent.js'; import { runKurtosisAgentDeploy } from '../deploy/agent.js';
import { runCoreDeploy } from '../deploy/core.js'; import { runCoreDeploy } from '../deploy/core.js';
import { evaluateIfDryRunFailure, verifyAnvil } from '../deploy/dry-run.js'; import { evaluateIfDryRunFailure, verifyAnvil } from '../deploy/dry-run.js';
@ -7,36 +11,21 @@ import { runWarpRouteDeploy } from '../deploy/warp.js';
import { log, logGray } from '../logger.js'; import { log, logGray } from '../logger.js';
import { import {
AgentCommandOptions,
CoreCommandOptions,
WarpCommandOptions,
agentConfigCommandOption, agentConfigCommandOption,
agentTargetsCommandOption, agentTargetsCommandOption,
chainsCommandOption,
coreArtifactsOption,
coreTargetsCommandOption, coreTargetsCommandOption,
dryRunOption, dryRunOption,
hookCommandOption, hookCommandOption,
ismCommandOption, ismCommandOption,
keyCommandOption,
originCommandOption, originCommandOption,
outDirCommandOption,
skipConfirmationOption,
warpConfigCommandOption, warpConfigCommandOption,
} from './options.js'; } from './options.js';
export enum Command {
DEPLOY = 'deploy',
KURTOSIS_AGENTS = 'kurtosis-agents',
CORE = 'core',
WARP = 'warp',
}
/** /**
* Parent command * Parent command
*/ */
export const deployCommand: CommandModule = { export const deployCommand: CommandModule = {
command: Command.DEPLOY, command: 'deploy',
describe: 'Permissionlessly deploy a Hyperlane contracts or extensions', describe: 'Permissionlessly deploy a Hyperlane contracts or extensions',
builder: (yargs) => builder: (yargs) =>
yargs yargs
@ -51,28 +40,26 @@ export const deployCommand: CommandModule = {
/** /**
* Agent command * Agent command
*/ */
const agentCommand: CommandModule = { const agentCommand: CommandModuleWithContext<{
command: Command.KURTOSIS_AGENTS, origin?: string;
targets?: string;
config?: string;
}> = {
command: 'kurtosis-agents',
describe: 'Deploy Hyperlane agents with Kurtosis', describe: 'Deploy Hyperlane agents with Kurtosis',
builder: (yargs) => builder: {
yargs.options<AgentCommandOptions>({ origin: originCommandOption,
origin: originCommandOption, targets: agentTargetsCommandOption,
targets: agentTargetsCommandOption, config: agentConfigCommandOption(true),
chains: chainsCommandOption, },
config: agentConfigCommandOption, handler: async ({ context, origin, targets, config }) => {
}),
handler: async (argv: any) => {
logGray('Hyperlane Agent Deployment with Kurtosis'); logGray('Hyperlane Agent Deployment with Kurtosis');
logGray('----------------------------------------'); logGray('----------------------------------------');
const chainConfigPath: string = argv.chains;
const originChain: string = argv.origin;
const agentConfigurationPath: string = argv.config;
const relayChains: string = argv.targets;
await runKurtosisAgentDeploy({ await runKurtosisAgentDeploy({
originChain, context,
relayChains, originChain: origin,
chainConfigPath, relayChains: targets,
agentConfigurationPath, agentConfigurationPath: config,
}); });
process.exit(0); process.exit(0);
}, },
@ -81,34 +68,23 @@ const agentCommand: CommandModule = {
/** /**
* Core command * Core command
*/ */
const coreCommand: CommandModule = { const coreCommand: CommandModuleWithWriteContext<{
command: Command.CORE, targets: string;
ism?: string;
hook?: string;
'dry-run': boolean;
agent: string;
}> = {
command: 'core',
describe: 'Deploy core Hyperlane contracts', describe: 'Deploy core Hyperlane contracts',
builder: (yargs) => builder: {
yargs.options<CoreCommandOptions>({ targets: coreTargetsCommandOption,
targets: coreTargetsCommandOption, ism: ismCommandOption,
chains: chainsCommandOption, hook: hookCommandOption,
artifacts: coreArtifactsOption, agent: agentConfigCommandOption(false, './configs/agent.json'),
ism: ismCommandOption, 'dry-run': dryRunOption,
hook: hookCommandOption, },
out: outDirCommandOption, handler: async ({ context, targets, ism, hook, agent, dryRun }) => {
key: keyCommandOption,
yes: skipConfirmationOption,
'dry-run': dryRunOption,
}),
handler: async (argv: any) => {
const key: string | undefined = argv.key;
const chainConfigPath: string = argv.chains;
const outPath: string = argv.out;
const chains: string[] | undefined = argv.targets
?.split(',')
.map((r: string) => r.trim());
const artifactsPath: string = argv.artifacts;
const ismConfigPath: string = argv.ism;
const hookConfigPath: string = argv.hook;
const skipConfirmation: boolean = argv.yes;
const dryRun: string = argv.dryRun;
logGray( logGray(
`Hyperlane permissionless core deployment${dryRun ? ' dry-run' : ''}`, `Hyperlane permissionless core deployment${dryRun ? ' dry-run' : ''}`,
); );
@ -117,16 +93,13 @@ const coreCommand: CommandModule = {
if (dryRun) await verifyAnvil(); if (dryRun) await verifyAnvil();
try { try {
const chains = targets?.split(',').map((r: string) => r.trim());
await runCoreDeploy({ await runCoreDeploy({
key, context,
chainConfigPath,
chains, chains,
artifactsPath, ismConfigPath: ism,
ismConfigPath, hookConfigPath: hook,
hookConfigPath, agentOutPath: agent,
outPath,
skipConfirmation,
dryRun,
}); });
} catch (error: any) { } catch (error: any) {
evaluateIfDryRunFailure(error, dryRun); evaluateIfDryRunFailure(error, dryRun);
@ -139,28 +112,17 @@ const coreCommand: CommandModule = {
/** /**
* Warp command * Warp command
*/ */
const warpCommand: CommandModule = { const warpCommand: CommandModuleWithWriteContext<{
command: Command.WARP, config: string;
'dry-run': boolean;
}> = {
command: 'warp',
describe: 'Deploy Warp Route contracts', describe: 'Deploy Warp Route contracts',
builder: (yargs) => builder: {
yargs.options<WarpCommandOptions>({ config: warpConfigCommandOption,
config: warpConfigCommandOption, 'dry-run': dryRunOption,
core: coreArtifactsOption, },
chains: chainsCommandOption, handler: async ({ context, config, dryRun }) => {
out: outDirCommandOption,
key: keyCommandOption,
yes: skipConfirmationOption,
'dry-run': dryRunOption,
}),
handler: async (argv: any) => {
const key: string | undefined = argv.key;
const chainConfigPath: string = argv.chains;
const warpRouteDeploymentConfigPath: string | undefined = argv.config;
const coreArtifactsPath: string | undefined = argv.core;
const outPath: string = argv.out;
const skipConfirmation: boolean = argv.yes;
const dryRun: string = argv.dryRun;
logGray(`Hyperlane warp route deployment${dryRun ? ' dry-run' : ''}`); logGray(`Hyperlane warp route deployment${dryRun ? ' dry-run' : ''}`);
logGray('------------------------------------------------'); logGray('------------------------------------------------');
@ -168,13 +130,8 @@ const warpCommand: CommandModule = {
try { try {
await runWarpRouteDeploy({ await runWarpRouteDeploy({
key, context,
chainConfigPath, warpRouteDeploymentConfigPath: config,
warpRouteDeploymentConfigPath,
coreArtifactsPath,
outPath,
skipConfirmation,
dryRun,
}); });
} catch (error: any) { } catch (error: any) {
evaluateIfDryRunFailure(error, dryRun); evaluateIfDryRunFailure(error, dryRun);

@ -1,13 +1,12 @@
import { CommandModule } from 'yargs'; import { CommandModule } from 'yargs';
import { CommandModuleWithContext } from '../context/types.js';
import { readHookConfig } from '../hook/read.js'; import { readHookConfig } from '../hook/read.js';
import { log } from '../logger.js'; import { log } from '../logger.js';
import { import {
addressCommandOption, addressCommandOption,
chainCommandOption, chainCommandOption,
chainsCommandOption,
fileFormatOption,
outputFileOption, outputFileOption,
} from './options.js'; } from './options.js';
@ -26,28 +25,23 @@ export const hookCommand: CommandModule = {
// hyperlane hook read --chain polygon --address 0xca4cCe24E7e06241846F5EA0cda9947F0507C40C // hyperlane hook read --chain polygon --address 0xca4cCe24E7e06241846F5EA0cda9947F0507C40C
// IGP hook on inevm (may take 5s): // IGP hook on inevm (may take 5s):
// hyperlane hook read --chain inevm --address 0x19dc38aeae620380430C200a6E990D5Af5480117 // hyperlane hook read --chain inevm --address 0x19dc38aeae620380430C200a6E990D5Af5480117
export const read: CommandModule = { export const read: CommandModuleWithContext<{
chain: string;
address: string;
out: string;
}> = {
command: 'read', command: 'read',
describe: 'Reads onchain Hook configuration for a given address', describe: 'Reads onchain Hook configuration for a given address',
builder: (yargs) => builder: {
yargs.options({ chain: {
chains: chainsCommandOption, ...chainCommandOption,
chain: { demandOption: true,
...chainCommandOption, },
demandOption: true, address: addressCommandOption('Address of the Hook to read.', true),
}, out: outputFileOption(),
address: addressCommandOption('Address of the Hook to read.', true), },
format: fileFormatOption, handler: async (args) => {
output: outputFileOption(), await readHookConfig(args);
}),
handler: async (argv: any) => {
await readHookConfig({
chain: argv.chain,
address: argv.address,
chainConfigPath: argv.chains,
format: argv.format,
output: argv.output,
});
process.exit(0); process.exit(0);
}, },
}; };

@ -1,13 +1,12 @@
import { CommandModule } from 'yargs'; import { CommandModule } from 'yargs';
import { CommandModuleWithContext } from '../context/types.js';
import { readIsmConfig } from '../ism/read.js'; import { readIsmConfig } from '../ism/read.js';
import { log } from '../logger.js'; import { log } from '../logger.js';
import { import {
addressCommandOption, addressCommandOption,
chainCommandOption, chainCommandOption,
chainsCommandOption,
fileFormatOption,
outputFileOption, outputFileOption,
} from './options.js'; } from './options.js';
@ -28,31 +27,26 @@ export const ismCommand: CommandModule = {
// hyperlane ism read --chain inevm --address 0x79A7c7Fe443971CBc6baD623Fdf8019C379a7178 // hyperlane ism read --chain inevm --address 0x79A7c7Fe443971CBc6baD623Fdf8019C379a7178
// Test ISM on alfajores testnet // Test ISM on alfajores testnet
// hyperlane ism read --chain alfajores --address 0xdB52E4853b6A40D2972E6797E0BDBDb3eB761966 // hyperlane ism read --chain alfajores --address 0xdB52E4853b6A40D2972E6797E0BDBDb3eB761966
export const read: CommandModule = { export const read: CommandModuleWithContext<{
chain: string;
address: string;
out: string;
}> = {
command: 'read', command: 'read',
describe: 'Reads onchain ISM configuration for a given address', describe: 'Reads onchain ISM configuration for a given address',
builder: (yargs) => builder: {
yargs.options({ chain: {
chains: chainsCommandOption, ...chainCommandOption,
chain: { demandOption: true,
...chainCommandOption, },
demandOption: true, address: addressCommandOption(
}, 'Address of the Interchain Security Module to read.',
address: addressCommandOption( true,
'Address of the Interchain Security Module to read.', ),
true, out: outputFileOption(),
), },
format: fileFormatOption, handler: async (argv) => {
output: outputFileOption(), await readIsmConfig(argv);
}),
handler: async (argv: any) => {
await readIsmConfig({
chain: argv.chain,
address: argv.address,
chainConfigPath: argv.chains,
format: argv.format,
output: argv.output,
});
process.exit(0); process.exit(0);
}, },
}; };

@ -1,9 +1,12 @@
import { Options } from 'yargs'; import { Options } from 'yargs';
import { DEFAULT_GITHUB_REGISTRY } from '@hyperlane-xyz/registry';
import { LogFormat, LogLevel } from '@hyperlane-xyz/utils'; import { LogFormat, LogLevel } from '@hyperlane-xyz/utils';
import { ENV } from '../utils/env.js'; import { ENV } from '../utils/env.js';
/* Global options */
export const logFormatCommandOption: Options = { export const logFormatCommandOption: Options = {
type: 'string', type: 'string',
description: 'Log output format', description: 'Log output format',
@ -16,33 +19,36 @@ export const logLevelCommandOption: Options = {
choices: Object.values(LogLevel), choices: Object.values(LogLevel),
}; };
export type CommandOptions = { export const registryUriCommandOption: Options = {
chains: Options; type: 'string',
description: 'Registry URI, such as a Github repo URL or a local file path',
alias: 'r',
default: DEFAULT_GITHUB_REGISTRY,
}; };
export type AgentCommandOptions = CommandOptions & {
origin: Options; export const overrideRegistryUriCommandOption: Options = {
targets: Options; type: 'string',
config: Options; description: 'Path to a local registry to override the default registry',
default: './',
}; };
export type CoreCommandOptions = CommandOptions & {
targets: Options; export const skipConfirmationOption: Options = {
artifacts: Options; type: 'boolean',
ism: Options; description: 'Skip confirmation prompts',
hook: Options; default: false,
out: Options; alias: 'y',
key: Options;
yes: Options;
'dry-run': Options;
}; };
export type WarpCommandOptions = CommandOptions & {
config: Options; export const keyCommandOption: Options = {
core: Options; type: 'string',
out: Options; description: `A hex private key or seed phrase for transaction signing, or use the HYP_KEY env var.
key: Options; Dry-run: An address to simulate transaction signing on a forked network`,
yes: Options; alias: 'k',
'dry-run': Options; default: ENV.HYP_KEY,
}; };
/* Command-specific options */
export const coreTargetsCommandOption: Options = { export const coreTargetsCommandOption: Options = {
type: 'string', type: 'string',
description: description:
@ -79,52 +85,24 @@ export const warpConfigCommandOption: Options = {
alias: 'w', alias: 'w',
}; };
export const keyCommandOption: Options = {
type: 'string',
description: `Default: A hex private key or seed phrase for transaction signing, or use the HYP_KEY env var.
Dry-run: An address to simulate transaction signing on a forked network, or use the HYP_KEY env var.`,
alias: 'k',
default: ENV.HYP_KEY,
};
export const chainsCommandOption: Options = {
type: 'string',
description: 'A path to a JSON or YAML file with chain configs',
default: './configs/chains.yaml',
alias: 'c',
};
export const outDirCommandOption: Options = {
type: 'string',
description: 'A folder name output artifacts into',
default: './artifacts',
alias: 'o',
};
export const coreArtifactsOption: Options = {
type: 'string',
description: 'File path to core deployment output artifacts',
alias: 'a',
};
export const warpConfigOption: Options = { export const warpConfigOption: Options = {
type: 'string', type: 'string',
description: 'File path to Warp config', description: 'File path to Warp Route config',
alias: 'w', alias: 'w',
// TODO make this optional and have the commands get it from the registry
demandOption: true,
}; };
export const agentConfigCommandOption: Options = { export const agentConfigCommandOption = (
type: 'string', isIn: boolean,
description: 'File path to agent configuration artifacts', defaultPath?: string,
}; ): Options => ({
export const fileFormatOption: Options = {
type: 'string', type: 'string',
description: 'Output file format', description: `${
choices: ['json', 'yaml'], isIn ? 'Input' : 'Output'
default: 'yaml', } file path for the agent configuration`,
alias: 'f', default: defaultPath,
}; });
export const outputFileOption = (defaultPath?: string): Options => ({ export const outputFileOption = (defaultPath?: string): Options => ({
type: 'string', type: 'string',
@ -133,18 +111,18 @@ export const outputFileOption = (defaultPath?: string): Options => ({
alias: 'o', alias: 'o',
}); });
export const skipConfirmationOption: Options = { export const inputFileOption: Options = {
type: 'boolean', type: 'string',
description: 'Skip confirmation prompts', description: 'Input file path',
default: false, alias: 'i',
alias: 'y', demandOption: true,
}; };
export const dryRunOption: Options = { export const dryRunOption: Options = {
type: 'string', type: 'string',
description: description:
'Chain name to fork and simulate deployment. Please ensure an anvil node instance is running during execution via `anvil`.', 'Chain name to fork and simulate deployment. Please ensure an anvil node instance is running during execution via `anvil`.',
alias: ['d', 'dr'], alias: ['d'],
}; };
export const chainCommandOption: Options = { export const chainCommandOption: Options = {

@ -1,16 +1,12 @@
import { ethers } from 'ethers'; import { ethers } from 'ethers';
import { CommandModule, Options } from 'yargs'; import { CommandModule, Options } from 'yargs';
import { CommandModuleWithWriteContext } from '../context/types.js';
import { log } from '../logger.js'; import { log } from '../logger.js';
import { sendTestMessage } from '../send/message.js'; import { sendTestMessage } from '../send/message.js';
import { sendTestTransfer } from '../send/transfer.js'; import { sendTestTransfer } from '../send/transfer.js';
import { import { warpConfigOption } from './options.js';
chainsCommandOption,
coreArtifactsOption,
keyCommandOption,
warpConfigOption,
} from './options.js';
/** /**
* Parent command * Parent command
@ -27,18 +23,10 @@ export const sendCommand: CommandModule = {
handler: () => log('Command required'), handler: () => log('Command required'),
}; };
export const selfrelay: Options = {
type: 'boolean',
description: 'Relay message on destination chain',
default: false,
alias: ['s', 'sr'],
};
/** /**
* Message command * Message command
*/ */
export const messageOptions: { [k: string]: Options } = { export const messageOptions: { [k: string]: Options } = {
key: keyCommandOption,
origin: { origin: {
type: 'string', type: 'string',
description: 'Origin chain to send message from', description: 'Origin chain to send message from',
@ -47,53 +35,61 @@ export const messageOptions: { [k: string]: Options } = {
type: 'string', type: 'string',
description: 'Destination chain to send message to', description: 'Destination chain to send message to',
}, },
chains: chainsCommandOption,
core: coreArtifactsOption,
timeout: { timeout: {
type: 'number', type: 'number',
description: 'Timeout in seconds', description: 'Timeout in seconds',
default: 5 * 60, default: 5 * 60,
}, },
'self-relay': selfrelay,
quick: { quick: {
type: 'boolean', type: 'boolean',
description: 'Skip wait for message to be delivered', description: 'Skip wait for message to be delivered',
default: false, default: false,
}, },
relay: {
type: 'boolean',
description: 'Handle self-relay of message on destination chain',
default: false,
},
}; };
const messageCommand: CommandModule = { export interface MessageOptionsArgTypes {
origin?: string;
destination?: string;
timeout: number;
quick: boolean;
relay: boolean;
}
const messageCommand: CommandModuleWithWriteContext<
MessageOptionsArgTypes & { body: string }
> = {
command: 'message', command: 'message',
describe: 'Send a test message to a remote chain', describe: 'Send a test message to a remote chain',
builder: (yargs) => builder: {
yargs.options({ ...messageOptions,
...messageOptions, body: {
messageBody: { type: 'string',
type: 'string', description: 'Optional Message body',
description: 'Optional Message body', default: 'Hello!',
default: 'Hello!', },
}, },
}), handler: async ({
handler: async (argv: any) => { context,
const key: string | undefined = argv.key; origin,
const chainConfigPath: string = argv.chains; destination,
const coreArtifactsPath: string | undefined = argv.core; timeout,
const origin: string | undefined = argv.origin; quick,
const destination: string | undefined = argv.destination; relay,
const timeoutSec: number = argv.timeout; body,
const skipWaitForDelivery: boolean = argv.quick; }) => {
const messageBody: string = argv.messageBody;
const selfRelay: boolean = argv['selfrelay'];
await sendTestMessage({ await sendTestMessage({
key, context,
chainConfigPath,
coreArtifactsPath,
origin, origin,
destination, destination,
messageBody: ethers.utils.hexlify(ethers.utils.toUtf8Bytes(messageBody)), messageBody: ethers.utils.hexlify(ethers.utils.toUtf8Bytes(body)),
timeoutSec, timeoutSec: timeout,
skipWaitForDelivery, skipWaitForDelivery: quick,
selfRelay, selfRelay: relay,
}); });
process.exit(0); process.exit(0);
}, },
@ -102,53 +98,50 @@ const messageCommand: CommandModule = {
/** /**
* Transfer command * Transfer command
*/ */
const transferCommand: CommandModule = { const transferCommand: CommandModuleWithWriteContext<
MessageOptionsArgTypes & {
warp: string;
router?: string;
wei: string;
recipient?: string;
}
> = {
command: 'transfer', command: 'transfer',
describe: 'Send a test token transfer on a warp route', describe: 'Send a test token transfer on a warp route',
builder: (yargs) => builder: {
yargs.options({ ...messageOptions,
...messageOptions, warp: warpConfigOption,
warp: warpConfigOption, wei: {
router: { type: 'string',
type: 'string', description: 'Amount in wei to send',
description: 'The address of the token router contract', default: 1,
}, },
wei: { recipient: {
type: 'string', type: 'string',
description: 'Amount in wei to send', description: 'Token recipient address (defaults to sender)',
default: 1, },
}, },
recipient: { handler: async ({
type: 'string', context,
description: 'Token recipient address (defaults to sender)', origin,
}, destination,
}), timeout,
handler: async (argv: any) => { quick,
const key: string | undefined = argv.key; relay,
const chainConfigPath: string = argv.chains; warp,
const coreArtifactsPath: string | undefined = argv.core; wei,
const warpConfigPath: string = argv.warp; recipient,
const origin: string | undefined = argv.origin; }) => {
const destination: string | undefined = argv.destination;
const timeoutSec: number = argv.timeout;
const routerAddress: string | undefined = argv.router;
const wei: string = argv.wei;
const recipient: string | undefined = argv.recipient;
const skipWaitForDelivery: boolean = argv.quick;
const selfRelay: boolean = argv['self-relay'];
await sendTestTransfer({ await sendTestTransfer({
key, context,
chainConfigPath, warpConfigPath: warp,
coreArtifactsPath,
warpConfigPath,
origin, origin,
destination, destination,
routerAddress,
wei, wei,
recipient, recipient,
timeoutSec, timeoutSec: timeout,
skipWaitForDelivery, skipWaitForDelivery: quick,
selfRelay, selfRelay: relay,
}); });
process.exit(0); process.exit(0);
}, },

@ -0,0 +1,11 @@
// Commands that send tx and require a key to sign.
// It's useful to have this listed here so the context
// middleware can request keys up front when required.
export const SIGN_COMMANDS = ['deploy', 'send'];
export function isSignCommand(argv: any): boolean {
return (
SIGN_COMMANDS.includes(argv._[0]) ||
(argv._.length > 1 && SIGN_COMMANDS.includes(argv._[1]))
);
}

@ -1,37 +1,27 @@
import { CommandModule } from 'yargs'; import { CommandModuleWithContext } from '../context/types.js';
import { checkMessageStatus } from '../status/message.js'; import { checkMessageStatus } from '../status/message.js';
import { messageOptions } from './send.js'; import { MessageOptionsArgTypes, messageOptions } from './send.js';
export const statusCommand: CommandModule = { export const statusCommand: CommandModuleWithContext<
MessageOptionsArgTypes & { id?: string }
> = {
command: 'status', command: 'status',
describe: 'Check status of a message', describe: 'Check status of a message',
builder: (yargs) => builder: {
yargs.options({ ...messageOptions,
...messageOptions, id: {
id: { type: 'string',
type: 'string', description: 'Message ID',
description: 'Message ID', },
}, },
}), handler: async ({ context, origin, destination, id, relay }) => {
handler: async (argv: any) => {
const chainConfigPath: string = argv.chains;
const coreArtifactsPath: string | undefined = argv.core;
const messageId: string | undefined = argv.id;
const destination: string | undefined = argv.destination;
const origin: string | undefined = argv.origin;
const selfRelay: boolean = argv['self-relay'];
const key: string | undefined = argv.key;
await checkMessageStatus({ await checkMessageStatus({
chainConfigPath, context,
coreArtifactsPath, messageId: id,
messageId,
destination, destination,
origin, origin,
selfRelay, selfRelay: relay,
key,
}); });
process.exit(0); process.exit(0);
}, },

@ -1,86 +0,0 @@
import { confirm } from '@inquirer/prompts';
import { ZodTypeAny, z } from 'zod';
import { ChainName, HyperlaneContractsMap } from '@hyperlane-xyz/sdk';
import { log, logBlue } from '../logger.js';
import { readYamlOrJson, runFileSelectionStep } from '../utils/files.js';
const RecursiveObjectSchema: ZodTypeAny = z.lazy(() =>
z.object({}).catchall(z.union([z.string(), RecursiveObjectSchema])),
);
const DeploymentArtifactsSchema = z.object({}).catchall(RecursiveObjectSchema);
export function readDeploymentArtifacts(filePath: string) {
const artifacts = readYamlOrJson<HyperlaneContractsMap<any>>(filePath);
if (!artifacts) throw new Error(`No artifacts found at ${filePath}`);
const result = DeploymentArtifactsSchema.safeParse(artifacts);
if (!result.success) {
const firstIssue = result.error.issues[0];
logBlue(
`Read deployment artifacts from ${JSON.stringify(
result.error.issues,
null,
4,
)}`,
);
throw new Error(
`Invalid artifacts: ${firstIssue.path} => ${firstIssue.message}`,
);
}
return artifacts;
}
/**
* Prompts the user to specify deployment artifacts, or to generate new ones if none are present or selected.
* @returns the selected artifacts, or undefined if they are to be generated from scratch
*/
export async function runDeploymentArtifactStep({
artifactsPath,
message,
selectedChains,
defaultArtifactsPath = './artifacts',
defaultArtifactsNamePattern = 'core-deployment',
skipConfirmation = false,
dryRun = false,
}: {
artifactsPath?: string;
message?: string;
selectedChains?: ChainName[];
defaultArtifactsPath?: string;
defaultArtifactsNamePattern?: string;
skipConfirmation?: boolean;
dryRun?: boolean;
}): Promise<HyperlaneContractsMap<any> | undefined> {
if (!artifactsPath) {
if (skipConfirmation) return undefined;
if (dryRun) defaultArtifactsNamePattern = 'dry-run_core-deployment';
const useArtifacts = await confirm({
message: message || 'Do you want use some existing contract addresses?',
});
if (!useArtifacts) return undefined;
artifactsPath = await runFileSelectionStep(
defaultArtifactsPath,
'contract deployment artifacts',
defaultArtifactsNamePattern,
);
}
const artifacts = readDeploymentArtifacts(artifactsPath);
if (selectedChains) {
const artifactChains = Object.keys(artifacts).filter((c) =>
selectedChains.includes(c),
);
if (artifactChains.length === 0) {
log('No artifacts found for selected chains');
} else {
log(`Found existing artifacts for chains: ${artifactChains.join(', ')}`);
}
}
return artifacts;
}

@ -6,13 +6,6 @@ describe('readChainConfigs', () => {
const chainToMetadata = readChainConfigs('./examples/chain-config.yaml'); const chainToMetadata = readChainConfigs('./examples/chain-config.yaml');
it('parses and validates correctly', () => { it('parses and validates correctly', () => {
expect(chainToMetadata.mychainname.chainId).to.equal(1234567890); expect(chainToMetadata.chainId).to.equal(1234567890);
});
it('merges core configs', () => {
expect(chainToMetadata.sepolia.chainId).to.equal(11155111);
expect(chainToMetadata.sepolia.rpcUrls[0].http).to.equal(
'https://mycustomrpc.com',
);
}); });
}); });

@ -1,80 +1,41 @@
import { confirm, input } from '@inquirer/prompts'; import { confirm, input } from '@inquirer/prompts';
import { import { ChainMetadata, ChainMetadataSchema } from '@hyperlane-xyz/sdk';
ChainMap, import { ProtocolType } from '@hyperlane-xyz/utils';
ChainMetadata,
ChainMetadataSchema,
chainMetadata as coreChainMetadata,
} from '@hyperlane-xyz/sdk';
import { ProtocolType, objMerge } from '@hyperlane-xyz/utils';
import { getMultiProvider } from '../context.js'; import { CommandContext } from '../context/types.js';
import { errorRed, log, logBlue, logGreen } from '../logger.js'; import { errorRed, log, logBlue, logGreen } from '../logger.js';
import { import { readYamlOrJson } from '../utils/files.js';
FileFormat,
isFile,
mergeYamlOrJson,
readYamlOrJson,
} from '../utils/files.js';
export function readChainConfigs(filePath: string) { export function readChainConfigs(filePath: string) {
log(`Reading file configs in ${filePath}`); log(`Reading file configs in ${filePath}`);
const chainToMetadata = readYamlOrJson<ChainMap<ChainMetadata>>(filePath); const chainMetadata = readYamlOrJson<ChainMetadata>(filePath);
if ( if (
!chainToMetadata || !chainMetadata ||
typeof chainToMetadata !== 'object' || typeof chainMetadata !== 'object' ||
!Object.keys(chainToMetadata).length !Object.keys(chainMetadata).length
) { ) {
errorRed(`No configs found in ${filePath}`); errorRed(`No configs found in ${filePath}`);
process.exit(1); process.exit(1);
} }
// Validate configs from file and merge in core configs as needed // Validate configs from file and merge in core configs as needed
for (const chain of Object.keys(chainToMetadata)) { const parseResult = ChainMetadataSchema.safeParse(chainMetadata);
if (coreChainMetadata[chain]) { if (!parseResult.success) {
// For core chains, merge in the default config to allow users to override only some fields errorRed(
chainToMetadata[chain] = objMerge( `Chain config for ${filePath} is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/chain-config.yaml for an example`,
coreChainMetadata[chain], );
chainToMetadata[chain], errorRed(JSON.stringify(parseResult.error.errors));
); process.exit(1);
}
const parseResult = ChainMetadataSchema.safeParse(chainToMetadata[chain]);
if (!parseResult.success) {
errorRed(
`Chain config for ${chain} is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/chain-config.yaml for an example`,
);
errorRed(JSON.stringify(parseResult.error.errors));
process.exit(1);
}
if (chainToMetadata[chain].name !== chain) {
errorRed(`Chain ${chain} name does not match key`);
process.exit(1);
}
}
// Ensure MultiProvider accepts this metadata
getMultiProvider(chainToMetadata);
logGreen(`All chain configs in ${filePath} are valid`);
return chainToMetadata;
}
export function readChainConfigsIfExists(filePath?: string) {
if (!filePath || !isFile(filePath)) {
log('No chain config file provided');
return {};
} else {
return readChainConfigs(filePath);
} }
return chainMetadata;
} }
export async function createChainConfig({ export async function createChainConfig({
format, context,
outPath,
}: { }: {
format: FileFormat; context: CommandContext;
outPath: string;
}) { }) {
logBlue('Creating a new chain config'); logBlue('Creating a new chain config');
const name = await input({ const name = await input({
@ -149,8 +110,8 @@ export async function createChainConfig({
} }
const parseResult = ChainMetadataSchema.safeParse(metadata); const parseResult = ChainMetadataSchema.safeParse(metadata);
if (parseResult.success) { if (parseResult.success) {
logGreen(`Chain config is valid, writing to file ${outPath}`); logGreen(`Chain config is valid, writing to registry`);
mergeYamlOrJson(outPath, { [name]: metadata }, format); await context.registry.updateChain({ chainName: metadata.name, metadata });
} else { } else {
errorRed( errorRed(
`Chain config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/chain-config.yaml for an example`, `Chain config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/chain-config.yaml for an example`,

@ -9,7 +9,6 @@ import {
GasOracleContractType, GasOracleContractType,
HookType, HookType,
HooksConfig, HooksConfig,
chainMetadata,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { import {
Address, Address,
@ -18,11 +17,10 @@ import {
toWei, toWei,
} from '@hyperlane-xyz/utils'; } from '@hyperlane-xyz/utils';
import { CommandContext } from '../context/types.js';
import { errorRed, log, logBlue, logGreen, logRed } from '../logger.js'; import { errorRed, log, logBlue, logGreen, logRed } from '../logger.js';
import { runMultiChainSelectionStep } from '../utils/chains.js'; import { runMultiChainSelectionStep } from '../utils/chains.js';
import { FileFormat, mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; import { mergeYamlOrJson, readYamlOrJson } from '../utils/files.js';
import { readChainConfigsIfExists } from './chain.js';
const ProtocolFeeSchema = z.object({ const ProtocolFeeSchema = z.object({
type: z.literal(HookType.PROTOCOL_FEE), type: z.literal(HookType.PROTOCOL_FEE),
@ -118,17 +116,14 @@ export function readHooksConfigMap(filePath: string) {
} }
export async function createHooksConfigMap({ export async function createHooksConfigMap({
format, context,
outPath, outPath,
chainConfigPath,
}: { }: {
format: FileFormat; context: CommandContext;
outPath: string; outPath: string;
chainConfigPath: string;
}) { }) {
logBlue('Creating a new hook config'); logBlue('Creating a new hook config');
const customChains = readChainConfigsIfExists(chainConfigPath); const chains = await runMultiChainSelectionStep(context.chainMetadata);
const chains = await runMultiChainSelectionStep(customChains);
const result: HooksConfigMap = {}; const result: HooksConfigMap = {};
for (const chain of chains) { for (const chain of chains) {
@ -137,12 +132,12 @@ export async function createHooksConfigMap({
const remotes = chains.filter((c) => c !== chain); const remotes = chains.filter((c) => c !== chain);
result[chain] = { result[chain] = {
...result[chain], ...result[chain],
[hookRequirements]: await createHookConfig(chain, remotes), [hookRequirements]: await createHookConfig(context, chain, remotes),
}; };
} }
if (isValidHookConfigMap(result)) { if (isValidHookConfigMap(result)) {
logGreen(`Hook config is valid, writing to file ${outPath}`); logGreen(`Hook config is valid, writing to file ${outPath}`);
mergeYamlOrJson(outPath, result, format); mergeYamlOrJson(outPath, result);
} else { } else {
errorRed( errorRed(
`Hook config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/hooks.yaml for an example`, `Hook config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/hooks.yaml for an example`,
@ -153,6 +148,7 @@ export async function createHooksConfigMap({
} }
export async function createHookConfig( export async function createHookConfig(
context: CommandContext,
chain: ChainName, chain: ChainName,
remotes: ChainName[], remotes: ChainName[],
): Promise<HookConfig> { ): Promise<HookConfig> {
@ -195,13 +191,13 @@ export async function createHookConfig(
if (hookType === HookType.MERKLE_TREE) { if (hookType === HookType.MERKLE_TREE) {
lastConfig = { type: HookType.MERKLE_TREE }; lastConfig = { type: HookType.MERKLE_TREE };
} else if (hookType === HookType.PROTOCOL_FEE) { } else if (hookType === HookType.PROTOCOL_FEE) {
lastConfig = await createProtocolFeeConfig(chain); lastConfig = await createProtocolFeeConfig(context, chain);
} else if (hookType === HookType.INTERCHAIN_GAS_PAYMASTER) { } else if (hookType === HookType.INTERCHAIN_GAS_PAYMASTER) {
lastConfig = await createIGPConfig(remotes); lastConfig = await createIGPConfig(remotes);
} else if (hookType === HookType.AGGREGATION) { } else if (hookType === HookType.AGGREGATION) {
lastConfig = await createAggregationConfig(chain, remotes); lastConfig = await createAggregationConfig(context, chain, remotes);
} else if (hookType === HookType.ROUTING) { } else if (hookType === HookType.ROUTING) {
lastConfig = await createRoutingConfig(chain, remotes); lastConfig = await createRoutingConfig(context, chain, remotes);
} else { } else {
throw new Error(`Invalid hook type: ${hookType}`); throw new Error(`Invalid hook type: ${hookType}`);
} }
@ -209,6 +205,7 @@ export async function createHookConfig(
} }
export async function createProtocolFeeConfig( export async function createProtocolFeeConfig(
context: CommandContext,
chain: ChainName, chain: ChainName,
): Promise<HookConfig> { ): Promise<HookConfig> {
const owner = await input({ const owner = await input({
@ -232,6 +229,7 @@ export async function createProtocolFeeConfig(
const maxProtocolFee = toWei( const maxProtocolFee = toWei(
await input({ await input({
message: `Enter max protocol fee ${nativeTokenAndDecimals( message: `Enter max protocol fee ${nativeTokenAndDecimals(
context,
chain, chain,
)} e.g. 1.0)`, )} e.g. 1.0)`,
}), }),
@ -239,6 +237,7 @@ export async function createProtocolFeeConfig(
const protocolFee = toWei( const protocolFee = toWei(
await input({ await input({
message: `Enter protocol fee in ${nativeTokenAndDecimals( message: `Enter protocol fee in ${nativeTokenAndDecimals(
context,
chain, chain,
)} e.g. 0.01)`, )} e.g. 0.01)`,
}), }),
@ -305,6 +304,7 @@ export async function createIGPConfig(
} }
export async function createAggregationConfig( export async function createAggregationConfig(
context: CommandContext,
chain: ChainName, chain: ChainName,
remotes: ChainName[], remotes: ChainName[],
): Promise<HookConfig> { ): Promise<HookConfig> {
@ -317,7 +317,7 @@ export async function createAggregationConfig(
const hooks: Array<HookConfig> = []; const hooks: Array<HookConfig> = [];
for (let i = 0; i < hooksNum; i++) { for (let i = 0; i < hooksNum; i++) {
logBlue(`Creating hook ${i + 1} of ${hooksNum} ...`); logBlue(`Creating hook ${i + 1} of ${hooksNum} ...`);
hooks.push(await createHookConfig(chain, remotes)); hooks.push(await createHookConfig(context, chain, remotes));
} }
return { return {
type: HookType.AGGREGATION, type: HookType.AGGREGATION,
@ -326,6 +326,7 @@ export async function createAggregationConfig(
} }
export async function createRoutingConfig( export async function createRoutingConfig(
context: CommandContext,
origin: ChainName, origin: ChainName,
remotes: ChainName[], remotes: ChainName[],
): Promise<HookConfig> { ): Promise<HookConfig> {
@ -339,7 +340,7 @@ export async function createRoutingConfig(
await confirm({ await confirm({
message: `You are about to configure hook for remote chain ${chain}. Continue?`, message: `You are about to configure hook for remote chain ${chain}. Continue?`,
}); });
const config = await createHookConfig(origin, remotes); const config = await createHookConfig(context, origin, remotes);
domainsMap[chain] = config; domainsMap[chain] = config;
} }
return { return {
@ -349,10 +350,10 @@ export async function createRoutingConfig(
}; };
} }
function nativeTokenAndDecimals(chain: ChainName) { function nativeTokenAndDecimals(context: CommandContext, chain: ChainName) {
return `10^${ return `10^${
chainMetadata[chain].nativeToken?.decimals ?? '18' context.chainMetadata[chain].nativeToken?.decimals ?? '18'
} which you cannot exceed (in ${ } which you cannot exceed (in ${
chainMetadata[chain].nativeToken?.symbol ?? 'eth' context.chainMetadata[chain].nativeToken?.symbol ?? 'eth'
}`; }`;
} }

@ -3,6 +3,7 @@ import { z } from 'zod';
import { ChainMap, ChainName, IsmType, ZHash } from '@hyperlane-xyz/sdk'; import { ChainMap, ChainName, IsmType, ZHash } from '@hyperlane-xyz/sdk';
import { CommandContext } from '../context/types.js';
import { import {
errorRed, errorRed,
log, log,
@ -12,9 +13,7 @@ import {
logRed, logRed,
} from '../logger.js'; } from '../logger.js';
import { runMultiChainSelectionStep } from '../utils/chains.js'; import { runMultiChainSelectionStep } from '../utils/chains.js';
import { FileFormat, mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; import { mergeYamlOrJson, readYamlOrJson } from '../utils/files.js';
import { readChainConfigsIfExists } from './chain.js';
const MultisigIsmConfigSchema = z.object({ const MultisigIsmConfigSchema = z.object({
type: z.union([ type: z.union([
@ -113,22 +112,19 @@ export function isValildIsmConfig(config: any) {
} }
export async function createIsmConfigMap({ export async function createIsmConfigMap({
format, context,
outPath, outPath,
chainConfigPath,
}: { }: {
format: FileFormat; context: CommandContext;
outPath: string; outPath: string;
chainConfigPath: string;
}) { }) {
logBlue('Creating a new advanced ISM config'); logBlue('Creating a new advanced ISM config');
logBoldUnderlinedRed('WARNING: USE AT YOUR RISK.'); logBoldUnderlinedRed('WARNING: USE AT YOUR RISK.');
logRed( logRed(
'Advanced ISM configs require knowledge of different ISM types and how they work together topologically. If possible, use the basic ISM configs are recommended.', 'Advanced ISM configs require knowledge of different ISM types and how they work together topologically. If possible, use the basic ISM configs are recommended.',
); );
const customChains = readChainConfigsIfExists(chainConfigPath);
const chains = await runMultiChainSelectionStep( const chains = await runMultiChainSelectionStep(
customChains, context.chainMetadata,
'Select chains to configure ISM for', 'Select chains to configure ISM for',
true, true,
); );
@ -146,7 +142,7 @@ export async function createIsmConfigMap({
if (isValildIsmConfig(result)) { if (isValildIsmConfig(result)) {
logGreen(`ISM config is valid, writing to file ${outPath}`); logGreen(`ISM config is valid, writing to file ${outPath}`);
mergeYamlOrJson(outPath, result, format); mergeYamlOrJson(outPath, result);
} else { } else {
errorRed( errorRed(
`ISM config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/ism.yaml for an example`, `ISM config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/ism.yaml for an example`,

@ -9,12 +9,10 @@ import {
objMap, objMap,
} from '@hyperlane-xyz/utils'; } from '@hyperlane-xyz/utils';
import { sdkContractAddressesMap } from '../context.js'; import { CommandContext } from '../context/types.js';
import { errorRed, log, logBlue, logGreen } from '../logger.js'; import { errorRed, log, logBlue, logGreen } from '../logger.js';
import { runMultiChainSelectionStep } from '../utils/chains.js'; import { runMultiChainSelectionStep } from '../utils/chains.js';
import { FileFormat, mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; import { mergeYamlOrJson, readYamlOrJson } from '../utils/files.js';
import { readChainConfigsIfExists } from './chain.js';
const MultisigConfigMapSchema = z.object({}).catchall( const MultisigConfigMapSchema = z.object({}).catchall(
z.object({ z.object({
@ -64,21 +62,19 @@ export function isValidMultisigConfig(config: any) {
} }
export async function createMultisigConfig({ export async function createMultisigConfig({
format, context,
outPath, outPath,
chainConfigPath,
}: { }: {
format: FileFormat; context: CommandContext;
outPath: string; outPath: string;
chainConfigPath: string;
}) { }) {
logBlue('Creating a new multisig config'); logBlue('Creating a new multisig config');
log( log(
'Select your own chain below to run your own validators. If you want to reuse existing Hyperlane validators instead of running your own, do not select additional mainnet or testnet chains.', 'Select your own chain below to run your own validators. If you want to reuse existing Hyperlane validators instead of running your own, do not select additional mainnet or testnet chains.',
); );
const customChains = readChainConfigsIfExists(chainConfigPath); const chains = await runMultiChainSelectionStep(context.chainMetadata);
const chains = await runMultiChainSelectionStep(customChains);
const chainAddresses = await context.registry.getAddresses();
const result: MultisigConfigMap = {}; const result: MultisigConfigMap = {};
let lastConfig: MultisigConfigMap['string'] | undefined = undefined; let lastConfig: MultisigConfigMap['string'] | undefined = undefined;
const repeat = false; const repeat = false;
@ -88,7 +84,7 @@ export async function createMultisigConfig({
result[chain] = lastConfig; result[chain] = lastConfig;
continue; continue;
} }
if (Object.keys(sdkContractAddressesMap).includes(chain)) { if (Object.keys(chainAddresses).includes(chain)) {
const reuseCoreConfig = await confirm({ const reuseCoreConfig = await confirm({
message: 'Use existing Hyperlane validators for this chain?', message: 'Use existing Hyperlane validators for this chain?',
}); });
@ -118,7 +114,7 @@ export async function createMultisigConfig({
if (isValidMultisigConfig(result)) { if (isValidMultisigConfig(result)) {
logGreen(`Multisig config is valid, writing to file ${outPath}`); logGreen(`Multisig config is valid, writing to file ${outPath}`);
mergeYamlOrJson(outPath, result, format); mergeYamlOrJson(outPath, result);
} else { } else {
errorRed( errorRed(
`Multisig config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/ism.yaml for an example`, `Multisig config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/ism.yaml for an example`,

@ -3,31 +3,27 @@ import { ethers } from 'ethers';
import { import {
TokenType, TokenType,
WarpCoreConfig,
WarpCoreConfigSchema,
WarpRouteDeployConfig, WarpRouteDeployConfig,
WarpRouteDeployConfigSchema, WarpRouteDeployConfigSchema,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { CommandContext } from '../context/types.js';
import { errorRed, logBlue, logGreen } from '../logger.js'; import { errorRed, logBlue, logGreen } from '../logger.js';
import { import {
runMultiChainSelectionStep, runMultiChainSelectionStep,
runSingleChainSelectionStep, runSingleChainSelectionStep,
} from '../utils/chains.js'; } from '../utils/chains.js';
import { FileFormat, readYamlOrJson, writeYamlOrJson } from '../utils/files.js'; import { readYamlOrJson, writeYamlOrJson } from '../utils/files.js';
import { readChainConfigsIfExists } from './chain.js'; export function readWarpRouteDeployConfig(
filePath: string,
export function readWarpRouteDeployConfig(filePath: string) { ): WarpRouteDeployConfig {
const config = readYamlOrJson(filePath); const config = readYamlOrJson(filePath);
if (!config) if (!config)
throw new Error(`No warp route deploy config found at ${filePath}`); throw new Error(`No warp route deploy config found at ${filePath}`);
const result = WarpRouteDeployConfigSchema.safeParse(config); return WarpRouteDeployConfigSchema.parse(config);
if (!result.success) {
const firstIssue = result.error.issues[0];
throw new Error(
`Invalid warp config: ${firstIssue.path} => ${firstIssue.message}`,
);
}
return result.data;
} }
export function isValidWarpRouteDeployConfig(config: any) { export function isValidWarpRouteDeployConfig(config: any) {
@ -35,18 +31,15 @@ export function isValidWarpRouteDeployConfig(config: any) {
} }
export async function createWarpRouteDeployConfig({ export async function createWarpRouteDeployConfig({
format, context,
outPath, outPath,
chainConfigPath,
}: { }: {
format: FileFormat; context: CommandContext;
outPath: string; outPath: string;
chainConfigPath: string;
}) { }) {
logBlue('Creating a new warp route deployment config'); logBlue('Creating a new warp route deployment config');
const customChains = readChainConfigsIfExists(chainConfigPath);
const baseChain = await runSingleChainSelectionStep( const baseChain = await runSingleChainSelectionStep(
customChains, context.chainMetadata,
'Select base chain with the original token to warp', 'Select base chain with the original token to warp',
); );
@ -74,7 +67,7 @@ export async function createWarpRouteDeployConfig({
: await input({ message: addressMessage }); : await input({ message: addressMessage });
const syntheticChains = await runMultiChainSelectionStep( const syntheticChains = await runMultiChainSelectionStep(
customChains, context.chainMetadata,
'Select chains to which the base token will be connected', 'Select chains to which the base token will be connected',
); );
@ -104,7 +97,7 @@ export async function createWarpRouteDeployConfig({
if (isValidWarpRouteDeployConfig(result)) { if (isValidWarpRouteDeployConfig(result)) {
logGreen(`Warp Route config is valid, writing to file ${outPath}`); logGreen(`Warp Route config is valid, writing to file ${outPath}`);
writeYamlOrJson(outPath, result, format); writeYamlOrJson(outPath, result);
} else { } else {
errorRed( errorRed(
`Warp route deployment config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/warp-route-deployment.yaml for an example`, `Warp route deployment config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/warp-route-deployment.yaml for an example`,
@ -112,3 +105,11 @@ export async function createWarpRouteDeployConfig({
throw new Error('Invalid multisig config'); throw new Error('Invalid multisig config');
} }
} }
// Note, this is different than the function above which reads a config
// for a DEPLOYMENT. This gets a config for using a warp route (aka WarpCoreConfig)
export function readWarpRouteConfig(filePath: string): WarpCoreConfig {
const config = readYamlOrJson(filePath);
if (!config) throw new Error(`No warp route config found at ${filePath}`);
return WarpCoreConfigSchema.parse(config);
}

@ -1,23 +0,0 @@
import { expect } from 'chai';
import { ethers } from 'ethers';
import { getContext } from './context.js';
describe('context', () => {
it('Gets minimal read-only context correctly', async () => {
const context = await getContext({ chainConfigPath: './fakePath' });
expect(!!context.multiProvider).to.be.true;
expect(context.customChains).to.eql({});
});
it('Handles conditional type correctly', async () => {
const randomWallet = ethers.Wallet.createRandom();
const context = await getContext({
chainConfigPath: './fakePath',
keyConfig: { key: randomWallet.privateKey },
});
expect(!!context.multiProvider).to.be.true;
expect(context.customChains).to.eql({});
expect(await context.signer.getAddress()).to.eql(randomWallet.address);
});
});

@ -1,213 +0,0 @@
import { input } from '@inquirer/prompts';
import { ethers } from 'ethers';
import {
ChainMap,
ChainMetadata,
ChainName,
HyperlaneContractsMap,
MultiProvider,
WarpCoreConfig,
chainMetadata,
hyperlaneEnvironments,
} from '@hyperlane-xyz/sdk';
import { objFilter, objMap, objMerge } from '@hyperlane-xyz/utils';
import { runDeploymentArtifactStep } from './config/artifacts.js';
import { readChainConfigsIfExists } from './config/chain.js';
import { forkNetworkToMultiProvider } from './deploy/dry-run.js';
import { runSingleChainSelectionStep } from './utils/chains.js';
import { readYamlOrJson } from './utils/files.js';
import { getImpersonatedSigner, getSigner } from './utils/keys.js';
export const sdkContractAddressesMap: HyperlaneContractsMap<any> = {
...hyperlaneEnvironments.testnet,
...hyperlaneEnvironments.mainnet,
};
export function getMergedContractAddresses(
artifacts?: HyperlaneContractsMap<any>,
chains?: ChainName[],
) {
// if chains include non sdkContractAddressesMap chains, don't recover interchainGasPaymaster
let sdkContractsAddressesToRecover = sdkContractAddressesMap;
if (
chains?.some(
(chain) => !Object.keys(sdkContractAddressesMap).includes(chain),
)
) {
sdkContractsAddressesToRecover = objMap(sdkContractAddressesMap, (_, v) =>
objFilter(
v as ChainMap<any>,
(key, v): v is any => key !== 'interchainGasPaymaster',
),
);
}
return objMerge(
sdkContractsAddressesToRecover,
artifacts || {},
) as HyperlaneContractsMap<any>;
}
export type KeyConfig = {
key?: string;
promptMessage?: string;
};
export interface ContextSettings {
chainConfigPath?: string;
chains?: ChainName[];
coreConfig?: {
coreArtifactsPath?: string;
promptMessage?: string;
};
keyConfig?: KeyConfig;
skipConfirmation?: boolean;
warpConfig?: {
warpConfigPath?: string;
promptMessage?: string;
};
}
interface CommandContextBase {
chains: ChainName[];
customChains: ChainMap<ChainMetadata>;
multiProvider: MultiProvider;
}
// This makes return type dynamic based on the input settings
type CommandContext<P extends ContextSettings> = CommandContextBase &
(P extends { keyConfig: object }
? { signer: ethers.Signer }
: { signer: undefined }) &
(P extends { coreConfig: object }
? { coreArtifacts: HyperlaneContractsMap<any> }
: { coreArtifacts: undefined }) &
(P extends { warpConfig: object }
? { warpCoreConfig: WarpCoreConfig }
: { warpCoreConfig: undefined });
/**
* Retrieves context for the user-selected command
* @returns context for the current command
*/
export async function getContext<P extends ContextSettings>({
chainConfigPath,
coreConfig,
keyConfig,
skipConfirmation,
warpConfig,
}: P): Promise<CommandContext<P>> {
const customChains = readChainConfigsIfExists(chainConfigPath);
const signer = await getSigner({
keyConfig,
skipConfirmation,
});
let coreArtifacts = undefined;
if (coreConfig) {
coreArtifacts =
(await runDeploymentArtifactStep({
artifactsPath: coreConfig.coreArtifactsPath,
message:
coreConfig.promptMessage ||
'Do you want to use some core deployment address artifacts? This is required for PI chains (non-core chains).',
skipConfirmation,
})) || {};
}
let warpCoreConfig = undefined;
if (warpConfig) {
let warpConfigPath = warpConfig.warpConfigPath;
if (!warpConfigPath) {
// prompt for path to token config
warpConfigPath = await input({
message:
warpConfig.promptMessage ||
'Please provide a path to the Warp config',
});
}
warpCoreConfig = readYamlOrJson<WarpCoreConfig>(warpConfigPath);
}
const multiProvider = getMultiProvider(customChains, signer);
return {
customChains,
signer,
multiProvider,
coreArtifacts,
warpCoreConfig,
} as CommandContext<P>;
}
/**
* Retrieves dry-run context for the user-selected command
* @returns dry-run context for the current command
*/
export async function getDryRunContext<P extends ContextSettings>({
chainConfigPath,
chains,
coreConfig,
keyConfig,
skipConfirmation,
}: P): Promise<CommandContext<P>> {
const customChains = readChainConfigsIfExists(chainConfigPath);
let coreArtifacts = undefined;
if (coreConfig) {
coreArtifacts =
(await runDeploymentArtifactStep({
artifactsPath: coreConfig.coreArtifactsPath,
message:
coreConfig.promptMessage ||
'Do you want to use some core deployment address artifacts? This is required for PI chains (non-core chains).',
skipConfirmation,
})) || {};
}
const multiProvider = getMultiProvider(customChains);
if (!chains?.length) {
if (skipConfirmation) throw new Error('No chains provided');
chains = [
await runSingleChainSelectionStep(
customChains,
'Select chain to dry-run against:',
),
];
}
await forkNetworkToMultiProvider(multiProvider, chains[0]);
const impersonatedSigner = await getImpersonatedSigner({
keyConfig,
skipConfirmation,
});
if (impersonatedSigner) multiProvider.setSharedSigner(impersonatedSigner);
return {
chains,
signer: impersonatedSigner,
multiProvider,
coreArtifacts,
} as CommandContext<P>;
}
/**
* Retrieves a new MultiProvider based on all known chain metadata & custom user chains
* @param customChains Custom chains specified by the user
* @returns a new MultiProvider
*/
export function getMultiProvider(
customChains: ChainMap<ChainMetadata>,
signer?: ethers.Signer,
) {
const chainConfigs = { ...chainMetadata, ...customChains };
const multiProvider = new MultiProvider(chainConfigs);
if (signer) multiProvider.setSharedSigner(signer);
return multiProvider;
}

@ -0,0 +1,134 @@
import { ethers } from 'ethers';
import { IRegistry } from '@hyperlane-xyz/registry';
import { ChainName, MultiProvider } from '@hyperlane-xyz/sdk';
import { isSignCommand } from '../commands/signCommands.js';
import { forkNetworkToMultiProvider } from '../deploy/dry-run.js';
import { MergedRegistry } from '../registry/MergedRegistry.js';
import { runSingleChainSelectionStep } from '../utils/chains.js';
import { getImpersonatedSigner, getSigner } from '../utils/keys.js';
import {
CommandContext,
ContextSettings,
WriteCommandContext,
} from './types.js';
export async function contextMiddleware(argv: Record<string, any>) {
const isDryRun = !!argv.dryRun;
const requiresKey = isSignCommand(argv);
const settings: ContextSettings = {
registryUri: argv.registry,
registryOverrideUri: argv.overrides,
key: argv.key,
requiresKey,
skipConfirmation: argv.yes,
};
const context = isDryRun
? await getDryRunContext(settings, argv.dryRun)
: await getContext(settings);
argv.context = context;
}
/**
* Retrieves context for the user-selected command
* @returns context for the current command
*/
export async function getContext({
registryUri,
registryOverrideUri,
key,
requiresKey,
skipConfirmation,
}: ContextSettings): Promise<CommandContext> {
const registry = getRegistry(registryUri, registryOverrideUri);
let signer: ethers.Wallet | undefined = undefined;
if (requiresKey) {
({ key, signer } = await getSigner({ key, skipConfirmation }));
}
const multiProvider = await getMultiProvider(registry, signer);
return {
registry,
chainMetadata: multiProvider.metadata,
multiProvider,
key,
signer,
skipConfirmation: !!skipConfirmation,
} as CommandContext;
}
/**
* Retrieves dry-run context for the user-selected command
* @returns dry-run context for the current command
*/
export async function getDryRunContext(
{ registryUri, registryOverrideUri, key, skipConfirmation }: ContextSettings,
chain?: ChainName,
): Promise<CommandContext> {
const registry = getRegistry(registryUri, registryOverrideUri, true);
const chainMetadata = await registry.getMetadata();
if (!chain) {
if (skipConfirmation) throw new Error('No chains provided');
chain = await runSingleChainSelectionStep(
chainMetadata,
'Select chain to dry-run against:',
);
}
const multiProvider = await getMultiProvider(registry);
await forkNetworkToMultiProvider(multiProvider, chain);
const { key: impersonatedKey, signer: impersonatedSigner } =
await getImpersonatedSigner({
key,
skipConfirmation,
});
multiProvider.setSharedSigner(impersonatedSigner);
return {
registry,
chainMetadata: multiProvider.metadata,
key: impersonatedKey,
signer: impersonatedSigner,
multiProvider: multiProvider,
skipConfirmation: !!skipConfirmation,
isDryRun: true,
dryRunChain: chain,
} as WriteCommandContext;
}
/**
* Creates a new MergedRegistry using the provided URIs
* The intention of the MergedRegistry is to join the common data
* from a primary URI (such as the Hyperlane default Github repo)
* and an override one (such as a local directory)
* @returns a new MergedRegistry
*/
function getRegistry(
primaryRegistryUri: string,
overrideRegistryUri: string,
isDryRun?: boolean,
): IRegistry {
const registryUris = [primaryRegistryUri, overrideRegistryUri]
.map((r) => r.trim())
.filter((r) => !!r);
return new MergedRegistry({
registryUris,
isDryRun,
});
}
/**
* Retrieves a new MultiProvider based on all known chain metadata & custom user chains
* @param customChains Custom chains specified by the user
* @returns a new MultiProvider
*/
async function getMultiProvider(registry: IRegistry, signer?: ethers.Signer) {
const chainMetadata = await registry.getMetadata();
const multiProvider = new MultiProvider(chainMetadata);
if (signer) multiProvider.setSharedSigner(signer);
return multiProvider;
}

@ -0,0 +1,43 @@
import type { ethers } from 'ethers';
import type { CommandModule } from 'yargs';
import type { IRegistry } from '@hyperlane-xyz/registry';
import type {
ChainMap,
ChainMetadata,
MultiProvider,
} from '@hyperlane-xyz/sdk';
export interface ContextSettings {
registryUri: string;
registryOverrideUri: string;
key?: string;
requiresKey?: boolean;
skipConfirmation?: boolean;
}
export interface CommandContext {
registry: IRegistry;
chainMetadata: ChainMap<ChainMetadata>;
multiProvider: MultiProvider;
skipConfirmation: boolean;
key?: string;
signer?: ethers.Signer;
}
export interface WriteCommandContext extends CommandContext {
key: string;
signer: ethers.Signer;
isDryRun?: boolean;
dryRunChain?: string;
}
export type CommandModuleWithContext<Args> = CommandModule<
{},
Args & { context: CommandContext }
>;
export type CommandModuleWithWriteContext<Args> = CommandModule<
{},
Args & { context: WriteCommandContext }
>;

@ -2,7 +2,7 @@ import terminalLink from 'terminal-link';
import { toBase64 } from '@hyperlane-xyz/utils'; import { toBase64 } from '@hyperlane-xyz/utils';
import { getContext } from '../context.js'; import { CommandContext } from '../context/types.js';
import { logBlue, logGreen } from '../logger.js'; import { logBlue, logGreen } from '../logger.js';
import { import {
runMultiChainSelectionStep, runMultiChainSelectionStep,
@ -11,27 +11,25 @@ import {
import { readJson, runFileSelectionStep } from '../utils/files.js'; import { readJson, runFileSelectionStep } from '../utils/files.js';
export async function runKurtosisAgentDeploy({ export async function runKurtosisAgentDeploy({
context,
originChain, originChain,
relayChains, relayChains,
chainConfigPath,
agentConfigurationPath, agentConfigurationPath,
}: { }: {
originChain: string; context: CommandContext;
relayChains: string; originChain?: string;
chainConfigPath: string; relayChains?: string;
agentConfigurationPath: string; agentConfigurationPath?: string;
}) { }) {
const { customChains } = await getContext({ chainConfigPath });
if (!originChain) { if (!originChain) {
originChain = await runSingleChainSelectionStep( originChain = await runSingleChainSelectionStep(
customChains, context.chainMetadata,
'Select the origin chain', 'Select the origin chain',
); );
} }
if (!relayChains) { if (!relayChains) {
const selectedRelayChains = await runMultiChainSelectionStep( const selectedRelayChains = await runMultiChainSelectionStep(
customChains, context.chainMetadata,
'Select chains to relay between', 'Select chains to relay between',
true, true,
); );
@ -44,7 +42,7 @@ export async function runKurtosisAgentDeploy({
'No agent config json was provided. Please specify the agent config json filepath.', 'No agent config json was provided. Please specify the agent config json filepath.',
); );
agentConfigurationPath = await runFileSelectionStep( agentConfigurationPath = await runFileSelectionStep(
'./artifacts', './configs',
'agent config json', 'agent config json',
'agent-config', 'agent-config',
); );

@ -1,24 +1,22 @@
import { confirm } from '@inquirer/prompts'; import { confirm } from '@inquirer/prompts';
import { ethers } from 'ethers'; import { ethers } from 'ethers';
import { ChainAddresses, IRegistry } from '@hyperlane-xyz/registry';
import { import {
ChainMap, ChainMap,
ChainName, ChainName,
CoreConfig, CoreConfig,
DeployedIsm,
GasOracleContractType, GasOracleContractType,
HooksConfig, HooksConfig,
HyperlaneAddressesMap, HyperlaneAddressesMap,
HyperlaneContractsMap, HyperlaneContractsMap,
HyperlaneCore, HyperlaneCore,
HyperlaneCoreDeployer, HyperlaneCoreDeployer,
HyperlaneDeploymentArtifacts,
HyperlaneIsmFactory, HyperlaneIsmFactory,
HyperlaneProxyFactoryDeployer, HyperlaneProxyFactoryDeployer,
IgpConfig, IgpConfig,
IsmConfig, IsmConfig,
IsmType, IsmType,
MultiProvider,
MultisigConfig, MultisigConfig,
RoutingIsmConfig, RoutingIsmConfig,
buildAgentConfig, buildAgentConfig,
@ -27,20 +25,13 @@ import {
multisigIsmVerificationCost, multisigIsmVerificationCost,
serializeContractsMap, serializeContractsMap,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { Address, objFilter, objMerge } from '@hyperlane-xyz/utils'; import { Address, objFilter, objMap, objMerge } from '@hyperlane-xyz/utils';
import { Command } from '../commands/deploy.js';
import { runDeploymentArtifactStep } from '../config/artifacts.js';
import { presetHookConfigs, readHooksConfigMap } from '../config/hooks.js'; import { presetHookConfigs, readHooksConfigMap } from '../config/hooks.js';
import { readIsmConfig } from '../config/ism.js'; import { readIsmConfig } from '../config/ism.js';
import { readMultisigConfig } from '../config/multisig.js'; import { readMultisigConfig } from '../config/multisig.js';
import { MINIMUM_CORE_DEPLOY_GAS } from '../consts.js'; import { MINIMUM_CORE_DEPLOY_GAS } from '../consts.js';
import { import { WriteCommandContext } from '../context/types.js';
getContext,
getDryRunContext,
getMergedContractAddresses,
sdkContractAddressesMap,
} from '../context.js';
import { import {
log, log,
logBlue, logBlue,
@ -50,12 +41,7 @@ import {
logRed, logRed,
} from '../logger.js'; } from '../logger.js';
import { runMultiChainSelectionStep } from '../utils/chains.js'; import { runMultiChainSelectionStep } from '../utils/chains.js';
import { import { runFileSelectionStep, writeJson } from '../utils/files.js';
getArtifactsFiles,
prepNewArtifactsFiles,
runFileSelectionStep,
writeJson,
} from '../utils/files.js';
import { import {
completeDeploy, completeDeploy,
@ -65,62 +51,36 @@ import {
runPreflightChecksForChains, runPreflightChecksForChains,
} from './utils.js'; } from './utils.js';
const CONTRACT_CACHE_EXCLUSIONS = ['interchainGasPaymaster'];
/** /**
* Executes the core deploy command. * Executes the core deploy command.
*/ */
export async function runCoreDeploy({ export async function runCoreDeploy({
key, context,
chainConfigPath,
chains, chains,
ismConfigPath, ismConfigPath,
hookConfigPath, hookConfigPath,
artifactsPath, agentOutPath,
outPath,
skipConfirmation,
dryRun,
}: { }: {
key?: string; context: WriteCommandContext;
chainConfigPath: string;
chains?: ChainName[]; chains?: ChainName[];
ismConfigPath?: string; ismConfigPath?: string;
hookConfigPath?: string; hookConfigPath?: string;
artifactsPath?: string; agentOutPath: string;
outPath: string;
skipConfirmation: boolean;
dryRun: string;
}) { }) {
const context = dryRun const { chainMetadata, signer, dryRunChain, skipConfirmation } = context;
? await getDryRunContext({
chainConfigPath, if (dryRunChain) chains = [dryRunChain];
chains: [dryRun],
keyConfig: { key },
skipConfirmation,
})
: await getContext({
chainConfigPath,
keyConfig: { key },
skipConfirmation,
});
const customChains = context.customChains;
const multiProvider = context.multiProvider;
const signer = context.signer;
if (dryRun) chains = context.chains;
else if (!chains?.length) { else if (!chains?.length) {
if (skipConfirmation) throw new Error('No chains provided'); if (skipConfirmation) throw new Error('No chains provided');
chains = await runMultiChainSelectionStep( chains = await runMultiChainSelectionStep(
customChains, chainMetadata,
'Select chains to connect:', 'Select chains to connect:',
true, true,
); );
} }
const artifacts = await runArtifactStep(
chains,
skipConfirmation,
artifactsPath,
);
const result = await runIsmStep(chains, skipConfirmation, ismConfigPath); const result = await runIsmStep(chains, skipConfirmation, ismConfigPath);
// we can either specify the full ISM config or just the multisig config // we can either specify the full ISM config or just the multisig config
const isIsmConfig = isISMConfig(result); const isIsmConfig = isISMConfig(result);
@ -131,16 +91,12 @@ export async function runCoreDeploy({
const hooksConfig = await runHookStep(chains, hookConfigPath); const hooksConfig = await runHookStep(chains, hookConfigPath);
const deploymentParams: DeployParams = { const deploymentParams: DeployParams = {
context,
chains, chains,
signer,
multiProvider,
artifacts,
ismConfigs, ismConfigs,
multisigConfigs, multisigConfigs,
hooksConfig, hooksConfig,
outPath, agentOutPath,
skipConfirmation,
dryRun,
}; };
await runDeployPlanStep(deploymentParams); await runDeployPlanStep(deploymentParams);
@ -149,41 +105,13 @@ export async function runCoreDeploy({
minGas: MINIMUM_CORE_DEPLOY_GAS, minGas: MINIMUM_CORE_DEPLOY_GAS,
}); });
const userAddress = dryRun ? key! : await signer.getAddress(); const userAddress = await signer.getAddress();
const initialBalances = await prepareDeploy( const initialBalances = await prepareDeploy(context, userAddress, chains);
multiProvider,
userAddress,
chains,
);
await executeDeploy(deploymentParams); await executeDeploy(deploymentParams);
await completeDeploy( await completeDeploy(context, 'core', initialBalances, userAddress, chains);
Command.CORE,
initialBalances,
multiProvider,
userAddress,
chains,
dryRun,
);
}
function runArtifactStep(
selectedChains: ChainName[],
skipConfirmation: boolean,
artifactsPath?: string,
dryRun?: boolean,
) {
logBlue(
'\nDeployments can be totally new or can use some existing contract addresses.',
);
return runDeploymentArtifactStep({
artifactsPath,
selectedChains,
skipConfirmation,
dryRun,
});
} }
async function runIsmStep( async function runIsmStep(
@ -271,24 +199,16 @@ async function runHookStep(
} }
interface DeployParams { interface DeployParams {
context: WriteCommandContext;
chains: ChainName[]; chains: ChainName[];
signer: ethers.Signer;
multiProvider: MultiProvider;
artifacts?: HyperlaneAddressesMap<any>;
ismConfigs?: ChainMap<IsmConfig>; ismConfigs?: ChainMap<IsmConfig>;
multisigConfigs?: ChainMap<MultisigConfig>; multisigConfigs?: ChainMap<MultisigConfig>;
hooksConfig?: ChainMap<HooksConfig>; hooksConfig?: ChainMap<HooksConfig>;
outPath: string; agentOutPath: string;
skipConfirmation: boolean;
dryRun: string;
} }
async function runDeployPlanStep({ async function runDeployPlanStep({ context, chains }: DeployParams) {
chains, const { signer, skipConfirmation } = context;
signer,
artifacts,
skipConfirmation,
}: DeployParams) {
const address = await signer.getAddress(); const address = await signer.getAddress();
logBlue('\nDeployment plan'); logBlue('\nDeployment plan');
@ -296,9 +216,7 @@ async function runDeployPlanStep({
log(`Transaction signer and owner of new contracts will be ${address}`); log(`Transaction signer and owner of new contracts will be ${address}`);
log(`Deploying to ${chains.join(', ')}`); log(`Deploying to ${chains.join(', ')}`);
log( log(
`There are several contracts required for each chain but contracts in the Hyperlane SDK ${ `There are several contracts required for each chain but contracts in your provided registries will be skipped`,
artifacts ? 'or your artifacts ' : ''
}will be skipped`,
); );
if (skipConfirmation) return; if (skipConfirmation) return;
@ -309,36 +227,26 @@ async function runDeployPlanStep({
} }
async function executeDeploy({ async function executeDeploy({
context,
chains, chains,
signer,
multiProvider,
outPath,
artifacts = {},
ismConfigs = {}, ismConfigs = {},
multisigConfigs = {}, multisigConfigs = {},
hooksConfig = {}, hooksConfig = {},
dryRun, agentOutPath,
}: DeployParams) { }: DeployParams) {
logBlue('All systems ready, captain! Beginning deployment...'); logBlue('All systems ready, captain! Beginning deployment...');
const { signer, multiProvider, registry } = context;
const [contractsFilePath, agentFilePath] = prepNewArtifactsFiles( let chainAddresses = await registry.getAddresses();
outPath, chainAddresses = filterAddressesToCache(chainAddresses);
getArtifactsFiles(
[
{ filename: 'core-deployment', description: 'Contract addresses' },
{ filename: 'agent-config', description: 'Agent configs' },
],
dryRun,
),
);
const owner = await signer.getAddress(); const owner = await signer.getAddress();
const mergedContractAddrs = getMergedContractAddresses(artifacts, chains); let artifacts: HyperlaneAddressesMap<any> = {};
// 1. Deploy ISM factories to all deployable chains that don't have them. // 1. Deploy ISM factories to all deployable chains that don't have them.
logBlue('Deploying ISM factory contracts'); logBlue('Deploying ISM factory contracts');
const ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider); const ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider);
ismFactoryDeployer.cacheAddressesMap(mergedContractAddrs); ismFactoryDeployer.cacheAddressesMap(chainAddresses);
const ismFactoryConfig = chains.reduce((chainMap, curr) => { const ismFactoryConfig = chains.reduce((chainMap, curr) => {
chainMap[curr] = {}; chainMap[curr] = {};
@ -346,33 +254,32 @@ async function executeDeploy({
}, {} as ChainMap<{}>); }, {} as ChainMap<{}>);
const ismFactoryContracts = await ismFactoryDeployer.deploy(ismFactoryConfig); const ismFactoryContracts = await ismFactoryDeployer.deploy(ismFactoryConfig);
artifacts = writeMergedAddresses( artifacts = await updateChainAddresses(
contractsFilePath, registry,
artifacts,
ismFactoryContracts, ismFactoryContracts,
artifacts,
); );
logGreen('ISM factory contracts deployed'); logGreen('ISM factory contracts deployed');
// Build an IsmFactory that covers all chains so that we can // Build an IsmFactory that covers all chains so that we can
// use it to deploy ISMs to remote chains. // use it to deploy ISMs to remote chains.
const ismFactory = HyperlaneIsmFactory.fromAddressesMap( const ismFactory = HyperlaneIsmFactory.fromAddressesMap(
mergedContractAddrs, chainAddresses,
multiProvider, multiProvider,
); );
// 3. Construct ISM configs for all deployable chains // 3. Construct ISM configs for all deployable chains
const ismContracts: ChainMap<{ interchainSecurityModule: DeployedIsm }> = {};
const defaultIsms: ChainMap<IsmConfig> = {}; const defaultIsms: ChainMap<IsmConfig> = {};
for (const ismOrigin of chains) { for (const ismOrigin of chains) {
defaultIsms[ismOrigin] = defaultIsms[ismOrigin] =
ismConfigs[ismOrigin] ?? ismConfigs[ismOrigin] ??
buildIsmConfig(owner, ismOrigin, chains, multisigConfigs); buildIsmConfig(owner, ismOrigin, chains, multisigConfigs);
} }
artifacts = writeMergedAddresses(contractsFilePath, artifacts, ismContracts);
// 4. Deploy core contracts to chains // 4. Deploy core contracts to chains
logBlue(`Deploying core contracts to ${chains.join(', ')}`); logBlue(`Deploying core contracts to ${chains.join(', ')}`);
const coreDeployer = new HyperlaneCoreDeployer(multiProvider, ismFactory); const coreDeployer = new HyperlaneCoreDeployer(multiProvider, ismFactory);
coreDeployer.cacheAddressesMap(mergedContractAddrs as any); coreDeployer.cacheAddressesMap(chainAddresses as any);
const coreConfigs = buildCoreConfigMap( const coreConfigs = buildCoreConfigMap(
owner, owner,
chains, chains,
@ -390,16 +297,27 @@ async function executeDeploy({
}; };
} }
artifacts = objMerge(artifacts, isms); artifacts = objMerge(artifacts, isms);
artifacts = writeMergedAddresses(contractsFilePath, artifacts, coreContracts); artifacts = await updateChainAddresses(registry, coreContracts, artifacts);
logGreen('✅ Core contracts deployed'); logGreen('✅ Core contracts deployed');
log(JSON.stringify(artifacts, null, 2));
log('Writing agent configs'); await writeAgentConfig(context, artifacts, chains, agentOutPath);
await writeAgentConfig(agentFilePath, artifacts, chains, multiProvider);
logGreen('Agent configs written');
logBlue('Deployment is complete!'); logBlue('Deployment is complete!');
logBlue(`Contract address artifacts are in ${contractsFilePath}`); }
logBlue(`Agent configs are in ${agentFilePath}`);
function filterAddressesToCache(addressesMap: ChainMap<ChainAddresses>) {
// Filter out the certain addresses that must always be
// deployed when deploying to a PI chain.
// See https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/2983
// And https://github.com/hyperlane-xyz/hyperlane-monorepo/pull/3183
return objMap(addressesMap, (_chain, addresses) =>
objFilter(
addresses,
(contract, _address): _address is string =>
!CONTRACT_CACHE_EXCLUSIONS.includes(contract),
),
);
} }
function buildIsmConfig( function buildIsmConfig(
@ -473,23 +391,40 @@ export function buildIgpConfigMap(
return configMap; return configMap;
} }
function writeMergedAddresses( async function updateChainAddresses(
filePath: string, registry: IRegistry,
aAddresses: HyperlaneAddressesMap<any>, newContracts: HyperlaneContractsMap<any>,
bContracts: HyperlaneContractsMap<any>, otherAddresses: HyperlaneAddressesMap<any>,
): HyperlaneAddressesMap<any> { ) {
const bAddresses = serializeContractsMap(bContracts); let newAddresses = serializeContractsMap(newContracts);
const mergedAddresses = objMerge(aAddresses, bAddresses); // The HyperlaneCoreDeployer is returning a nested object with ISM addresses
writeJson(filePath, mergedAddresses); // from other chains, which don't need to be in the artifacts atm.
newAddresses = objMap(newAddresses, (_, newChainAddresses) => {
// For each chain in the addresses chainmap, filter the values to those that are just strings
return objFilter(
newChainAddresses,
(_, value): value is string => typeof value === 'string',
);
});
const mergedAddresses = objMerge(otherAddresses, newAddresses);
for (const chainName of Object.keys(newContracts)) {
await registry.updateChain({
chainName,
addresses: mergedAddresses[chainName],
});
}
return mergedAddresses; return mergedAddresses;
} }
async function writeAgentConfig( async function writeAgentConfig(
filePath: string, context: WriteCommandContext,
artifacts: HyperlaneAddressesMap<any>, artifacts: HyperlaneAddressesMap<any>,
chains: ChainName[], chains: ChainName[],
multiProvider: MultiProvider, outPath: string,
) { ) {
if (context.isDryRun) return;
log('Writing agent configs');
const { multiProvider, registry } = context;
const startBlocks: ChainMap<number> = {}; const startBlocks: ChainMap<number> = {};
const core = HyperlaneCore.fromAddressesMap(artifacts, multiProvider); const core = HyperlaneCore.fromAddressesMap(artifacts, multiProvider);
@ -498,22 +433,19 @@ async function writeAgentConfig(
startBlocks[chain] = (await mailbox.deployedBlock()).toNumber(); startBlocks[chain] = (await mailbox.deployedBlock()).toNumber();
} }
const mergedAddressesMap = objMerge( const chainAddresses = await registry.getAddresses();
sdkContractAddressesMap,
artifacts,
) as ChainMap<HyperlaneDeploymentArtifacts>;
for (const chain of chains) { for (const chain of chains) {
if (!mergedAddressesMap[chain].interchainGasPaymaster) { if (!chainAddresses[chain].interchainGasPaymaster) {
mergedAddressesMap[chain].interchainGasPaymaster = chainAddresses[chain].interchainGasPaymaster =
ethers.constants.AddressZero; ethers.constants.AddressZero;
} }
} }
const agentConfig = buildAgentConfig( const agentConfig = buildAgentConfig(
chains, // Use only the chains that were deployed to chains, // Use only the chains that were deployed to
multiProvider, multiProvider,
mergedAddressesMap, chainAddresses as any,
startBlocks, startBlocks,
); );
writeJson(filePath, agentConfig); writeJson(outPath, agentConfig);
logGreen('Agent configs written');
} }

@ -6,7 +6,6 @@ import {
setFork, setFork,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { Command } from '../commands/deploy.js';
import { logGray, logGreen, warnYellow } from '../logger.js'; import { logGray, logGreen, warnYellow } from '../logger.js';
import { ENV } from '../utils/env.js'; import { ENV } from '../utils/env.js';
@ -51,14 +50,14 @@ export async function verifyAnvil() {
* @param error the thrown error * @param error the thrown error
* @param dryRun the chain name to execute the dry-run on * @param dryRun the chain name to execute the dry-run on
*/ */
export function evaluateIfDryRunFailure(error: any, dryRun: string) { export function evaluateIfDryRunFailure(error: any, dryRun: boolean) {
if (dryRun && error.message.includes('call revert exception')) if (dryRun && error.message.includes('call revert exception'))
warnYellow( warnYellow(
'⛔ [dry-run] The current RPC may not support forking. Please consider using a different RPC provider.', '⛔ [dry-run] The current RPC may not support forking. Please consider using a different RPC provider.',
); );
} }
export async function completeDryRun(command: Command) { export async function completeDryRun(command: string) {
await resetFork(); await resetFork();
logGreen(`${toUpperCamelCase(command)} dry-run completed successfully`); logGreen(`${toUpperCamelCase(command)} dry-run completed successfully`);

@ -4,14 +4,13 @@ import {
ChainMap, ChainMap,
ChainName, ChainName,
IsmConfig, IsmConfig,
MultiProvider,
MultisigConfig, MultisigConfig,
getLocalProvider, getLocalProvider,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { Address, ProtocolType } from '@hyperlane-xyz/utils'; import { Address, ProtocolType } from '@hyperlane-xyz/utils';
import { Command } from '../commands/deploy.js';
import { parseIsmConfig } from '../config/ism.js'; import { parseIsmConfig } from '../config/ism.js';
import { WriteCommandContext } from '../context/types.js';
import { log, logGreen, logPink } from '../logger.js'; import { log, logGreen, logPink } from '../logger.js';
import { assertGasBalances } from '../utils/balances.js'; import { assertGasBalances } from '../utils/balances.js';
import { ENV } from '../utils/env.js'; import { ENV } from '../utils/env.js';
@ -20,17 +19,15 @@ import { assertSigner } from '../utils/keys.js';
import { completeDryRun } from './dry-run.js'; import { completeDryRun } from './dry-run.js';
export async function runPreflightChecks({ export async function runPreflightChecks({
context,
origin, origin,
remotes, remotes,
signer,
multiProvider,
minGas, minGas,
chainsToGasCheck, chainsToGasCheck,
}: { }: {
context: WriteCommandContext;
origin: ChainName; origin: ChainName;
remotes: ChainName[]; remotes: ChainName[];
signer: ethers.Signer;
multiProvider: MultiProvider;
minGas: string; minGas: string;
chainsToGasCheck?: ChainName[]; chainsToGasCheck?: ChainName[];
}) { }) {
@ -44,30 +41,28 @@ export async function runPreflightChecks({
logGreen('✅ Origin and remote are distinct'); logGreen('✅ Origin and remote are distinct');
return runPreflightChecksForChains({ return runPreflightChecksForChains({
context,
chains: [origin, ...remotes], chains: [origin, ...remotes],
signer,
multiProvider,
minGas, minGas,
chainsToGasCheck, chainsToGasCheck,
}); });
} }
export async function runPreflightChecksForChains({ export async function runPreflightChecksForChains({
context,
chains, chains,
signer,
multiProvider,
minGas, minGas,
chainsToGasCheck, chainsToGasCheck,
}: { }: {
context: WriteCommandContext;
chains: ChainName[]; chains: ChainName[];
signer: ethers.Signer;
multiProvider: MultiProvider;
minGas: string; minGas: string;
// Chains for which to assert a native balance // Chains for which to assert a native balance
// Defaults to all chains if not specified // Defaults to all chains if not specified
chainsToGasCheck?: ChainName[]; chainsToGasCheck?: ChainName[];
}) { }) {
log('Running pre-flight checks for chains...'); log('Running pre-flight checks for chains...');
const { signer, multiProvider } = context;
if (!chains?.length) throw new Error('Empty chain selection'); if (!chains?.length) throw new Error('Empty chain selection');
for (const chain of chains) { for (const chain of chains) {
@ -103,15 +98,15 @@ export function isZODISMConfig(filepath: string): boolean {
} }
export async function prepareDeploy( export async function prepareDeploy(
multiProvider: MultiProvider, context: WriteCommandContext,
userAddress: Address, userAddress: Address,
chains: ChainName[], chains: ChainName[],
dryRun: boolean = false,
): Promise<Record<string, BigNumber>> { ): Promise<Record<string, BigNumber>> {
const { multiProvider, isDryRun } = context;
const initialBalances: Record<string, BigNumber> = {}; const initialBalances: Record<string, BigNumber> = {};
await Promise.all( await Promise.all(
chains.map(async (chain: ChainName) => { chains.map(async (chain: ChainName) => {
const provider = dryRun const provider = isDryRun
? getLocalProvider(ENV.ANVIL_IP_ADDR, ENV.ANVIL_PORT) ? getLocalProvider(ENV.ANVIL_IP_ADDR, ENV.ANVIL_PORT)
: multiProvider.getProvider(chain); : multiProvider.getProvider(chain);
const currentBalance = await provider.getBalance(userAddress); const currentBalance = await provider.getBalance(userAddress);
@ -122,31 +117,31 @@ export async function prepareDeploy(
} }
export async function completeDeploy( export async function completeDeploy(
command: Command, context: WriteCommandContext,
command: string,
initialBalances: Record<string, BigNumber>, initialBalances: Record<string, BigNumber>,
multiProvider: MultiProvider,
userAddress: Address, userAddress: Address,
chains: ChainName[], chains: ChainName[],
dryRun: string,
) { ) {
const { multiProvider, isDryRun } = context;
if (chains.length > 0) logPink(` Gas Usage Statistics`); if (chains.length > 0) logPink(` Gas Usage Statistics`);
for (const chain of chains) { for (const chain of chains) {
const provider = dryRun const provider = isDryRun
? getLocalProvider(ENV.ANVIL_IP_ADDR, ENV.ANVIL_PORT) ? getLocalProvider(ENV.ANVIL_IP_ADDR, ENV.ANVIL_PORT)
: multiProvider.getProvider(chain); : multiProvider.getProvider(chain);
const currentBalance = await provider.getBalance(userAddress); const currentBalance = await provider.getBalance(userAddress);
const balanceDelta = initialBalances[chain].sub(currentBalance); const balanceDelta = initialBalances[chain].sub(currentBalance);
if (dryRun && balanceDelta.lt(0)) break; if (isDryRun && balanceDelta.lt(0)) break;
logPink( logPink(
`\t- Gas required for ${command} ${ `\t- Gas required for ${command} ${
dryRun ? 'dry-run' : 'deploy' isDryRun ? 'dry-run' : 'deploy'
} on ${chain}: ${ethers.utils.formatEther(balanceDelta)} ${ } on ${chain}: ${ethers.utils.formatEther(balanceDelta)} ${
multiProvider.getChainMetadata(chain).nativeToken?.symbol multiProvider.getChainMetadata(chain).nativeToken?.symbol
}`, }`,
); );
} }
if (dryRun) await completeDryRun(command); if (isDryRun) await completeDryRun(command);
} }
export function toUpperCamelCase(string: string) { export function toUpperCamelCase(string: string) {

@ -1,5 +1,4 @@
import { confirm, input } from '@inquirer/prompts'; import { confirm, input } from '@inquirer/prompts';
import { ethers } from 'ethers';
import { import {
ChainMap, ChainMap,
@ -12,6 +11,7 @@ import {
MinimalTokenMetadata, MinimalTokenMetadata,
MultiProtocolProvider, MultiProtocolProvider,
MultiProvider, MultiProvider,
RouterConfig,
TOKEN_TYPE_TO_STANDARD, TOKEN_TYPE_TO_STANDARD,
TokenConfig, TokenConfig,
TokenFactories, TokenFactories,
@ -24,45 +24,25 @@ import {
isNativeConfig, isNativeConfig,
isSyntheticConfig, isSyntheticConfig,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { RouterConfig } from '@hyperlane-xyz/sdk'; import { ProtocolType } from '@hyperlane-xyz/utils';
import { Address, ProtocolType, objMap } from '@hyperlane-xyz/utils';
import { Command } from '../commands/deploy.js';
import { readWarpRouteDeployConfig } from '../config/warp.js'; import { readWarpRouteDeployConfig } from '../config/warp.js';
import { MINIMUM_WARP_DEPLOY_GAS } from '../consts.js'; import { MINIMUM_WARP_DEPLOY_GAS } from '../consts.js';
import { import { WriteCommandContext } from '../context/types.js';
getContext,
getDryRunContext,
getMergedContractAddresses,
} from '../context.js';
import { log, logBlue, logGray, logGreen } from '../logger.js'; import { log, logBlue, logGray, logGreen } from '../logger.js';
import { import { isFile, runFileSelectionStep } from '../utils/files.js';
getArtifactsFiles,
isFile,
prepNewArtifactsFiles,
runFileSelectionStep,
writeJson,
} from '../utils/files.js';
import { completeDeploy, prepareDeploy, runPreflightChecks } from './utils.js'; import { completeDeploy, prepareDeploy, runPreflightChecks } from './utils.js';
export async function runWarpRouteDeploy({ export async function runWarpRouteDeploy({
key, context,
chainConfigPath,
warpRouteDeploymentConfigPath, warpRouteDeploymentConfigPath,
coreArtifactsPath,
outPath,
skipConfirmation,
dryRun,
}: { }: {
key?: string; context: WriteCommandContext;
chainConfigPath: string;
warpRouteDeploymentConfigPath?: string; warpRouteDeploymentConfigPath?: string;
coreArtifactsPath?: string;
outPath: string;
skipConfirmation: boolean;
dryRun: string;
}) { }) {
const { signer, skipConfirmation } = context;
if ( if (
!warpRouteDeploymentConfigPath || !warpRouteDeploymentConfigPath ||
!isFile(warpRouteDeploymentConfigPath) !isFile(warpRouteDeploymentConfigPath)
@ -83,36 +63,14 @@ export async function runWarpRouteDeploy({
warpRouteDeploymentConfigPath, warpRouteDeploymentConfigPath,
); );
const { multiProvider, signer, coreArtifacts } = dryRun
? await getDryRunContext({
chainConfigPath,
chains: [dryRun],
coreConfig: { coreArtifactsPath },
keyConfig: { key },
skipConfirmation,
})
: await getContext({
chainConfigPath,
coreConfig: { coreArtifactsPath },
keyConfig: { key },
skipConfirmation,
});
const configs = await runBuildConfigStep({ const configs = await runBuildConfigStep({
context,
warpRouteConfig, warpRouteConfig,
coreArtifacts,
multiProvider,
signer,
skipConfirmation,
}); });
const deploymentParams = { const deploymentParams = {
context,
...configs, ...configs,
signer,
multiProvider,
outPath,
skipConfirmation,
dryRun,
}; };
logBlue('Warp route deployment plan'); logBlue('Warp route deployment plan');
@ -123,41 +81,26 @@ export async function runWarpRouteDeploy({
minGas: MINIMUM_WARP_DEPLOY_GAS, minGas: MINIMUM_WARP_DEPLOY_GAS,
}); });
const userAddress = dryRun ? key! : await signer.getAddress(); const userAddress = await signer.getAddress();
const chains = [deploymentParams.origin, ...configs.remotes]; const chains = [deploymentParams.origin, ...configs.remotes];
const initialBalances = await prepareDeploy( const initialBalances = await prepareDeploy(context, userAddress, chains);
multiProvider,
userAddress,
chains,
);
await executeDeploy(deploymentParams); await executeDeploy(deploymentParams);
await completeDeploy( await completeDeploy(context, 'warp', initialBalances, userAddress, chains);
Command.WARP,
initialBalances,
multiProvider,
userAddress,
chains,
dryRun,
);
} }
async function runBuildConfigStep({ async function runBuildConfigStep({
context,
warpRouteConfig, warpRouteConfig,
multiProvider,
signer,
coreArtifacts,
skipConfirmation,
}: { }: {
context: WriteCommandContext;
warpRouteConfig: WarpRouteDeployConfig; warpRouteConfig: WarpRouteDeployConfig;
multiProvider: MultiProvider;
signer: ethers.Signer;
coreArtifacts?: HyperlaneContractsMap<any>;
skipConfirmation: boolean;
}) { }) {
const { registry, signer, multiProvider, skipConfirmation } = context;
log('Assembling token configs'); log('Assembling token configs');
const chainAddresses = await registry.getAddresses();
const owner = await signer.getAddress(); const owner = await signer.getAddress();
const requiredRouterFields: Array<keyof ConnectionClientConfig> = ['mailbox']; const requiredRouterFields: Array<keyof ConnectionClientConfig> = ['mailbox'];
const remotes: string[] = []; const remotes: string[] = [];
@ -167,20 +110,16 @@ async function runBuildConfigStep({
/// @todo Remove this artifact when multi-collateral is enabled /// @todo Remove this artifact when multi-collateral is enabled
let baseChainName = ''; let baseChainName = '';
let baseMetadata = {} as MinimalTokenMetadata; let baseMetadata = {} as MinimalTokenMetadata;
// Create config that coalesce together values from the config file, // Define configs that coalesce together values from the config file
for (const [chain, config] of Object.entries(warpRouteConfig)) { for (const [chain, config] of Object.entries(warpRouteConfig)) {
const mergedContractAddrs = getMergedContractAddresses(
coreArtifacts,
Object.keys(warpRouteConfig),
);
// the artifacts, and the SDK as a fallback // the artifacts, and the SDK as a fallback
config.owner = owner; config.owner = owner;
config.mailbox = config.mailbox || mergedContractAddrs[chain]?.mailbox; config.mailbox = config.mailbox || chainAddresses[chain]?.mailbox;
config.interchainSecurityModule = config.interchainSecurityModule =
config.interchainSecurityModule || config.interchainSecurityModule ||
mergedContractAddrs[chain]?.interchainSecurityModule || chainAddresses[chain]?.interchainSecurityModule ||
mergedContractAddrs[chain]?.multisigIsm; chainAddresses[chain]?.multisigIsm;
// config.ismFactory: mergedContractAddrs[baseChainName].domainRoutingIsmFactory, // TODO fix when updating from routingIsm // config.ismFactory: chainAddresses[baseChainName].domainRoutingIsmFactory, // TODO fix when updating from routingIsm
if (isCollateralConfig(config) || isNativeConfig(config)) { if (isCollateralConfig(config) || isNativeConfig(config)) {
// Store the base metadata // Store the base metadata
@ -232,24 +171,20 @@ async function runBuildConfigStep({
} }
interface DeployParams { interface DeployParams {
context: WriteCommandContext;
configMap: WarpRouteDeployConfig; configMap: WarpRouteDeployConfig;
metadata: MinimalTokenMetadata; metadata: MinimalTokenMetadata;
origin: ChainName; origin: ChainName;
remotes: ChainName[]; remotes: ChainName[];
signer: ethers.Signer;
multiProvider: MultiProvider;
outPath: string;
skipConfirmation: boolean;
dryRun: string;
} }
async function runDeployPlanStep({ async function runDeployPlanStep({
context,
configMap, configMap,
origin, origin,
remotes, remotes,
signer,
skipConfirmation,
}: DeployParams) { }: DeployParams) {
const { signer, skipConfirmation } = context;
const address = await signer.getAddress(); const address = await signer.getAddress();
const baseToken = configMap[origin]; const baseToken = configMap[origin];
@ -273,27 +208,16 @@ async function runDeployPlanStep({
async function executeDeploy(params: DeployParams) { async function executeDeploy(params: DeployParams) {
logBlue('All systems ready, captain! Beginning deployment...'); logBlue('All systems ready, captain! Beginning deployment...');
const { configMap, multiProvider, outPath } = params; const {
configMap,
const [contractsFilePath, tokenConfigPath] = prepNewArtifactsFiles( context: { registry, multiProvider, isDryRun },
outPath, } = params;
getArtifactsFiles(
[
{
filename: 'warp-route-deployment',
description: 'Contract addresses',
},
{ filename: 'warp-config', description: 'Warp config' },
],
params.dryRun,
),
);
const deployer = configMap.isNft const deployer = configMap.isNft
? new HypERC721Deployer(multiProvider) ? new HypERC721Deployer(multiProvider)
: new HypERC20Deployer(multiProvider); : new HypERC20Deployer(multiProvider);
const config = params.dryRun const config = isDryRun
? { [params.origin]: configMap[params.origin] } ? { [params.origin]: configMap[params.origin] }
: configMap; : configMap;
@ -304,12 +228,10 @@ async function executeDeploy(params: DeployParams) {
logGreen('✅ Hyp token deployments complete'); logGreen('✅ Hyp token deployments complete');
log('Writing deployment artifacts'); log('Writing deployment artifacts');
writeTokenDeploymentArtifacts(contractsFilePath, deployedContracts, params); const warpCoreConfig = getWarpCoreConfig(params, deployedContracts);
writeWarpConfig(tokenConfigPath, deployedContracts, params); await registry.addWarpRoute(warpCoreConfig);
log(JSON.stringify(warpCoreConfig, null, 2));
logBlue('Deployment is complete!'); logBlue('Deployment is complete!');
logBlue(`Contract address artifacts are in ${contractsFilePath}`);
logBlue(`Warp config is in ${tokenConfigPath}`);
} }
async function fetchBaseTokenMetadata( async function fetchBaseTokenMetadata(
@ -344,28 +266,11 @@ async function fetchBaseTokenMetadata(
function getTokenName(token: TokenConfig) { function getTokenName(token: TokenConfig) {
return token.type === TokenType.native ? 'native' : token.name; return token.type === TokenType.native ? 'native' : token.name;
} }
function writeTokenDeploymentArtifacts(
filePath: string,
contracts: HyperlaneContractsMap<TokenFactories>,
{ configMap }: DeployParams,
) {
const artifacts: ChainMap<{
router: Address;
tokenType: TokenType;
}> = objMap(contracts, (chain, contract) => {
return {
router: contract[configMap[chain].type as keyof TokenFactories].address,
tokenType: configMap[chain].type,
};
});
writeJson(filePath, artifacts);
}
function writeWarpConfig( function getWarpCoreConfig(
filePath: string,
contracts: HyperlaneContractsMap<TokenFactories>,
{ configMap, metadata }: DeployParams, { configMap, metadata }: DeployParams,
) { contracts: HyperlaneContractsMap<TokenFactories>,
): WarpCoreConfig {
const warpCoreConfig: WarpCoreConfig = { tokens: [] }; const warpCoreConfig: WarpCoreConfig = { tokens: [] };
// First pass, create token configs // First pass, create token configs
@ -406,5 +311,5 @@ function writeWarpConfig(
} }
} }
writeJson(filePath, warpCoreConfig); return warpCoreConfig;
} }

@ -1,48 +1,34 @@
import { ChainName, EvmHookReader } from '@hyperlane-xyz/sdk'; import { ChainName, EvmHookReader } from '@hyperlane-xyz/sdk';
import { Address, ProtocolType, stringifyObject } from '@hyperlane-xyz/utils'; import { Address, ProtocolType, stringifyObject } from '@hyperlane-xyz/utils';
import { readChainConfigsIfExists } from '../config/chain.js'; import { CommandContext } from '../context/types.js';
import { getMultiProvider } from '../context.js';
import { log, logBlue, logRed } from '../logger.js'; import { log, logBlue, logRed } from '../logger.js';
import { import { resolveFileFormat, writeFileAtPath } from '../utils/files.js';
FileFormat,
resolveFileFormat,
writeFileAtPath,
} from '../utils/files.js';
/** /**
* Read Hook config for a specified chain and address, logging or writing result to file. * Read Hook config for a specified chain and address, logging or writing result to file.
*/ */
export async function readHookConfig({ export async function readHookConfig({
context,
chain, chain,
address, address,
chainConfigPath, out,
format,
output,
}: { }: {
context: CommandContext;
chain: ChainName; chain: ChainName;
address: Address; address: Address;
chainConfigPath: string; out?: string;
format: FileFormat;
output?: string;
}): Promise<void> { }): Promise<void> {
const customChains = readChainConfigsIfExists(chainConfigPath); if (context.multiProvider.getProtocol(chain) === ProtocolType.Ethereum) {
const multiProvider = getMultiProvider(customChains); const hookReader = new EvmHookReader(context.multiProvider, chain);
if (multiProvider.getProtocol(chain) === ProtocolType.Ethereum) {
const hookReader = new EvmHookReader(multiProvider, chain);
const config = await hookReader.deriveHookConfig(address); const config = await hookReader.deriveHookConfig(address);
const stringConfig = stringifyObject( const stringConfig = stringifyObject(config, resolveFileFormat(out), 2);
config, if (!out) {
resolveFileFormat(output, format),
2,
);
if (!output) {
logBlue(`Hook Config at ${address} on ${chain}:`); logBlue(`Hook Config at ${address} on ${chain}:`);
log(stringConfig); log(stringConfig);
} else { } else {
writeFileAtPath(output, stringConfig + '\n'); writeFileAtPath(out, stringConfig + '\n');
logBlue(`Hook Config written to ${output}.`); logBlue(`Hook Config written to ${out}.`);
} }
return; return;
} }

@ -1,48 +1,34 @@
import { ChainName, EvmIsmReader } from '@hyperlane-xyz/sdk'; import { ChainName, EvmIsmReader } from '@hyperlane-xyz/sdk';
import { Address, ProtocolType, stringifyObject } from '@hyperlane-xyz/utils'; import { Address, ProtocolType, stringifyObject } from '@hyperlane-xyz/utils';
import { readChainConfigsIfExists } from '../config/chain.js'; import { CommandContext } from '../context/types.js';
import { getMultiProvider } from '../context.js';
import { log, logBlue, logRed } from '../logger.js'; import { log, logBlue, logRed } from '../logger.js';
import { import { resolveFileFormat, writeFileAtPath } from '../utils/files.js';
FileFormat,
resolveFileFormat,
writeFileAtPath,
} from '../utils/files.js';
/** /**
* Read ISM config for a specified chain and address, logging or writing result to file. * Read ISM config for a specified chain and address, logging or writing result to file.
*/ */
export async function readIsmConfig({ export async function readIsmConfig({
context,
chain, chain,
address, address,
chainConfigPath, out,
format,
output,
}: { }: {
context: CommandContext;
chain: ChainName; chain: ChainName;
address: Address; address: Address;
chainConfigPath: string; out?: string;
format: FileFormat;
output?: string;
}): Promise<void> { }): Promise<void> {
const customChains = readChainConfigsIfExists(chainConfigPath); if (context.multiProvider.getProtocol(chain) === ProtocolType.Ethereum) {
const multiProvider = getMultiProvider(customChains); const ismReader = new EvmIsmReader(context.multiProvider, chain);
if (multiProvider.getProtocol(chain) === ProtocolType.Ethereum) {
const ismReader = new EvmIsmReader(multiProvider, chain);
const config = await ismReader.deriveIsmConfig(address); const config = await ismReader.deriveIsmConfig(address);
const stringConfig = stringifyObject( const stringConfig = stringifyObject(config, resolveFileFormat(out), 2);
config, if (!out) {
resolveFileFormat(output, format),
2,
);
if (!output) {
logBlue(`ISM Config at ${address} on ${chain}:`); logBlue(`ISM Config at ${address} on ${chain}:`);
log(stringConfig); log(stringConfig);
} else { } else {
writeFileAtPath(output, stringConfig + '\n'); writeFileAtPath(out, stringConfig + '\n');
logBlue(`ISM Config written to ${output}.`); logBlue(`ISM Config written to ${out}.`);
} }
return; return;
} }

@ -0,0 +1,156 @@
import { Logger } from 'pino';
import {
BaseRegistry,
ChainAddresses,
GithubRegistry,
IRegistry,
RegistryContent,
RegistryType,
} from '@hyperlane-xyz/registry';
import { LocalRegistry } from '@hyperlane-xyz/registry/local';
import {
ChainMap,
ChainMetadata,
ChainName,
WarpCoreConfig,
} from '@hyperlane-xyz/sdk';
import {
isHttpsUrl,
objKeys,
objMerge,
rootLogger,
} from '@hyperlane-xyz/utils';
export interface MergedRegistryOptions {
registryUris: Array<string>;
isDryRun?: boolean;
logger?: Logger;
}
export class MergedRegistry extends BaseRegistry implements IRegistry {
public readonly type = RegistryType.Local;
public readonly registries: Array<IRegistry>;
public readonly isDryRun: boolean;
constructor({ registryUris, logger, isDryRun }: MergedRegistryOptions) {
logger ||= rootLogger.child({ module: 'MergedRegistry' });
super({ uri: '__merged_registry__', logger });
if (!registryUris.length)
throw new Error('At least one registry URI is required');
this.registries = registryUris.map((uri, index) => {
if (isHttpsUrl(uri)) {
return new GithubRegistry({ uri, logger: logger!.child({ index }) });
} else {
return new LocalRegistry({ uri, logger: logger!.child({ index }) });
}
});
this.isDryRun = !!isDryRun;
}
async listRegistryContent(): Promise<RegistryContent> {
const results = await this.multiRegistryRead((r) =>
r.listRegistryContent(),
);
return results.reduce((acc, content) => objMerge(acc, content), {
chains: {},
deployments: {},
});
}
async getChains(): Promise<Array<ChainName>> {
return objKeys(await this.getMetadata);
}
async getMetadata(): Promise<ChainMap<ChainMetadata>> {
const results = await this.multiRegistryRead((r) => r.getMetadata());
return results.reduce((acc, content) => objMerge(acc, content), {});
}
async getChainMetadata(chainName: ChainName): Promise<ChainMetadata | null> {
return (await this.getMetadata())[chainName] || null;
}
async getAddresses(): Promise<ChainMap<ChainAddresses>> {
const results = await this.multiRegistryRead((r) => r.getAddresses());
return results.reduce((acc, content) => objMerge(acc, content), {});
}
async getChainAddresses(
chainName: ChainName,
): Promise<ChainAddresses | null> {
return (await this.getAddresses())[chainName] || null;
}
async addChain(chain: {
chainName: ChainName;
metadata?: ChainMetadata;
addresses?: ChainAddresses;
}): Promise<void> {
return this.multiRegistryWrite(
async (registry) => await registry.addChain(chain),
`adding chain ${chain.chainName}`,
);
}
async updateChain(chain: {
chainName: ChainName;
metadata?: ChainMetadata;
addresses?: ChainAddresses;
}): Promise<void> {
return this.multiRegistryWrite(
async (registry) => await registry.updateChain(chain),
`updating chain ${chain.chainName}`,
);
}
async removeChain(chain: ChainName): Promise<void> {
return this.multiRegistryWrite(
async (registry) => await registry.removeChain(chain),
`removing chain ${chain}`,
);
}
async addWarpRoute(config: WarpCoreConfig): Promise<void> {
return this.multiRegistryWrite(
async (registry) => await registry.addWarpRoute(config),
'adding warp route',
);
}
protected multiRegistryRead<R>(
readFn: (registry: IRegistry) => Promise<R> | R,
) {
return Promise.all(this.registries.map(readFn));
}
protected async multiRegistryWrite(
writeFn: (registry: IRegistry) => Promise<void>,
logMsg: string,
): Promise<void> {
if (this.isDryRun) return;
for (const registry of this.registries) {
// TODO remove this when GithubRegistry supports write methods
if (registry.type === RegistryType.Github) {
this.logger.warn(`skipping ${logMsg} at ${registry.type} registry`);
continue;
}
try {
this.logger.info(
`${logMsg} at ${registry.type} registry at ${registry.uri}`,
);
await writeFn(registry);
this.logger.info(`done ${logMsg} at ${registry.type} registry`);
} catch (error) {
// To prevent loss of artifacts, MergedRegistry write methods are failure tolerant
this.logger.error(
`failure ${logMsg} at ${registry.type} registry`,
error,
);
}
}
}
}

@ -1,23 +1,16 @@
import { ethers } from 'ethers'; import { ethers } from 'ethers';
import { import { ChainName, HyperlaneCore } from '@hyperlane-xyz/sdk';
ChainName,
HyperlaneContractsMap,
HyperlaneCore,
MultiProvider,
} from '@hyperlane-xyz/sdk';
import { addressToBytes32, timeout } from '@hyperlane-xyz/utils'; import { addressToBytes32, timeout } from '@hyperlane-xyz/utils';
import { MINIMUM_TEST_SEND_GAS } from '../consts.js'; import { MINIMUM_TEST_SEND_GAS } from '../consts.js';
import { getContext, getMergedContractAddresses } from '../context.js'; import { CommandContext, WriteCommandContext } from '../context/types.js';
import { runPreflightChecks } from '../deploy/utils.js'; import { runPreflightChecks } from '../deploy/utils.js';
import { errorRed, log, logBlue, logGreen } from '../logger.js'; import { errorRed, log, logBlue, logGreen } from '../logger.js';
import { runSingleChainSelectionStep } from '../utils/chains.js'; import { runSingleChainSelectionStep } from '../utils/chains.js';
export async function sendTestMessage({ export async function sendTestMessage({
key, context,
chainConfigPath,
coreArtifactsPath,
origin, origin,
destination, destination,
messageBody, messageBody,
@ -25,9 +18,7 @@ export async function sendTestMessage({
skipWaitForDelivery, skipWaitForDelivery,
selfRelay, selfRelay,
}: { }: {
key?: string; context: WriteCommandContext;
chainConfigPath: string;
coreArtifactsPath?: string;
origin?: ChainName; origin?: ChainName;
destination?: ChainName; destination?: ChainName;
messageBody: string; messageBody: string;
@ -35,43 +26,36 @@ export async function sendTestMessage({
skipWaitForDelivery: boolean; skipWaitForDelivery: boolean;
selfRelay?: boolean; selfRelay?: boolean;
}) { }) {
const { signer, multiProvider, customChains, coreArtifacts } = const { chainMetadata } = context;
await getContext({
chainConfigPath,
coreConfig: { coreArtifactsPath },
keyConfig: { key },
});
if (!origin) { if (!origin) {
origin = await runSingleChainSelectionStep( origin = await runSingleChainSelectionStep(
customChains, chainMetadata,
'Select the origin chain', 'Select the origin chain',
); );
} }
if (!destination) { if (!destination) {
destination = await runSingleChainSelectionStep( destination = await runSingleChainSelectionStep(
customChains, chainMetadata,
'Select the destination chain', 'Select the destination chain',
); );
} }
await runPreflightChecks({ await runPreflightChecks({
context,
origin, origin,
remotes: [destination], remotes: [destination],
multiProvider,
signer,
minGas: MINIMUM_TEST_SEND_GAS, minGas: MINIMUM_TEST_SEND_GAS,
chainsToGasCheck: [origin], chainsToGasCheck: [origin],
}); });
await timeout( await timeout(
executeDelivery({ executeDelivery({
context,
origin, origin,
destination, destination,
messageBody, messageBody,
multiProvider,
coreArtifacts,
skipWaitForDelivery, skipWaitForDelivery,
selfRelay, selfRelay,
}), }),
@ -81,30 +65,26 @@ export async function sendTestMessage({
} }
async function executeDelivery({ async function executeDelivery({
context,
origin, origin,
destination, destination,
messageBody, messageBody,
multiProvider,
coreArtifacts,
skipWaitForDelivery, skipWaitForDelivery,
selfRelay, selfRelay,
}: { }: {
context: CommandContext;
origin: ChainName; origin: ChainName;
destination: ChainName; destination: ChainName;
messageBody: string; messageBody: string;
multiProvider: MultiProvider;
coreArtifacts?: HyperlaneContractsMap<any>;
skipWaitForDelivery: boolean; skipWaitForDelivery: boolean;
selfRelay?: boolean; selfRelay?: boolean;
}) { }) {
const mergedContractAddrs = getMergedContractAddresses(coreArtifacts); const { registry, multiProvider } = context;
const core = HyperlaneCore.fromAddressesMap( const chainAddresses = await registry.getAddresses();
mergedContractAddrs, const core = HyperlaneCore.fromAddressesMap(chainAddresses, multiProvider);
multiProvider,
);
const mailbox = core.getContracts(origin).mailbox; const mailbox = core.getContracts(origin).mailbox;
let hook = mergedContractAddrs[origin]?.customHook; let hook = chainAddresses[origin]?.customHook;
if (hook) { if (hook) {
logBlue(`Using custom hook ${hook} for ${origin} -> ${destination}`); logBlue(`Using custom hook ${hook} for ${origin} -> ${destination}`);
} else { } else {
@ -115,7 +95,7 @@ async function executeDelivery({
const destinationDomain = multiProvider.getDomainId(destination); const destinationDomain = multiProvider.getDomainId(destination);
let txReceipt: ethers.ContractReceipt; let txReceipt: ethers.ContractReceipt;
try { try {
const recipient = mergedContractAddrs[destination].testRecipient; const recipient = chainAddresses[destination].testRecipient;
if (!recipient) { if (!recipient) {
throw new Error(`Unable to find TestRecipient for ${destination}`); throw new Error(`Unable to find TestRecipient for ${destination}`);
} }
@ -153,6 +133,7 @@ async function executeDelivery({
log(`Message: ${JSON.stringify(message)}`); log(`Message: ${JSON.stringify(message)}`);
if (selfRelay) { if (selfRelay) {
log('Attempting self-relay of message');
await core.relayMessage(message); await core.relayMessage(message);
logGreen('Message was self-relayed!'); logGreen('Message was self-relayed!');
return; return;

@ -1,94 +1,78 @@
import { select } from '@inquirer/prompts';
import { ethers } from 'ethers';
import { import {
ChainName, ChainName,
HyperlaneContractsMap,
HyperlaneCore, HyperlaneCore,
MultiProtocolProvider, MultiProtocolProvider,
MultiProvider,
ProviderType, ProviderType,
Token,
TokenAmount, TokenAmount,
WarpCore, WarpCore,
WarpCoreConfig, WarpCoreConfig,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { Address, timeout } from '@hyperlane-xyz/utils'; import { timeout } from '@hyperlane-xyz/utils';
import { readWarpRouteConfig } from '../config/warp.js';
import { MINIMUM_TEST_SEND_GAS } from '../consts.js'; import { MINIMUM_TEST_SEND_GAS } from '../consts.js';
import { getContext, getMergedContractAddresses } from '../context.js'; import { WriteCommandContext } from '../context/types.js';
import { runPreflightChecks } from '../deploy/utils.js'; import { runPreflightChecks } from '../deploy/utils.js';
import { logBlue, logGreen, logRed } from '../logger.js'; import { logBlue, logGreen, logRed } from '../logger.js';
import { runSingleChainSelectionStep } from '../utils/chains.js'; import { runSingleChainSelectionStep } from '../utils/chains.js';
import { runTokenSelectionStep } from '../utils/tokens.js';
export async function sendTestTransfer({ export async function sendTestTransfer({
key, context,
chainConfigPath,
coreArtifactsPath,
warpConfigPath, warpConfigPath,
origin, origin,
destination, destination,
routerAddress,
wei, wei,
recipient, recipient,
timeoutSec, timeoutSec,
skipWaitForDelivery, skipWaitForDelivery,
selfRelay, selfRelay,
}: { }: {
key?: string; context: WriteCommandContext;
chainConfigPath: string;
coreArtifactsPath?: string;
warpConfigPath: string; warpConfigPath: string;
origin?: ChainName; origin?: ChainName;
destination?: ChainName; destination?: ChainName;
routerAddress?: Address;
wei: string; wei: string;
recipient?: string; recipient?: string;
timeoutSec: number; timeoutSec: number;
skipWaitForDelivery: boolean; skipWaitForDelivery: boolean;
selfRelay?: boolean; selfRelay?: boolean;
}) { }) {
const { signer, multiProvider, customChains, coreArtifacts, warpCoreConfig } = const { chainMetadata } = context;
await getContext({
chainConfigPath, const warpCoreConfig = readWarpRouteConfig(warpConfigPath);
coreConfig: { coreArtifactsPath },
keyConfig: { key },
warpConfig: { warpConfigPath },
});
if (!origin) { if (!origin) {
origin = await runSingleChainSelectionStep( origin = await runSingleChainSelectionStep(
customChains, chainMetadata,
'Select the origin chain', 'Select the origin chain',
); );
} }
if (!destination) { if (!destination) {
destination = await runSingleChainSelectionStep( destination = await runSingleChainSelectionStep(
customChains, chainMetadata,
'Select the destination chain', 'Select the destination chain',
); );
} }
await runPreflightChecks({ await runPreflightChecks({
context,
origin, origin,
remotes: [destination], remotes: [destination],
multiProvider,
signer,
minGas: MINIMUM_TEST_SEND_GAS, minGas: MINIMUM_TEST_SEND_GAS,
chainsToGasCheck: [origin], chainsToGasCheck: [origin],
}); });
await timeout( await timeout(
executeDelivery({ executeDelivery({
context,
origin, origin,
destination, destination,
warpCoreConfig, warpCoreConfig,
routerAddress,
wei, wei,
recipient, recipient,
signer,
multiProvider,
coreArtifacts,
skipWaitForDelivery, skipWaitForDelivery,
selfRelay, selfRelay,
}), }),
@ -98,39 +82,32 @@ export async function sendTestTransfer({
} }
async function executeDelivery({ async function executeDelivery({
context,
origin, origin,
destination, destination,
warpCoreConfig, warpCoreConfig,
routerAddress,
wei, wei,
recipient, recipient,
multiProvider,
signer,
coreArtifacts,
skipWaitForDelivery, skipWaitForDelivery,
selfRelay, selfRelay,
}: { }: {
context: WriteCommandContext;
origin: ChainName; origin: ChainName;
destination: ChainName; destination: ChainName;
warpCoreConfig: WarpCoreConfig; warpCoreConfig: WarpCoreConfig;
routerAddress?: Address;
wei: string; wei: string;
recipient?: string; recipient?: string;
multiProvider: MultiProvider;
signer: ethers.Signer;
coreArtifacts?: HyperlaneContractsMap<any>;
skipWaitForDelivery: boolean; skipWaitForDelivery: boolean;
selfRelay?: boolean; selfRelay?: boolean;
}) { }) {
const { signer, multiProvider, registry } = context;
const signerAddress = await signer.getAddress(); const signerAddress = await signer.getAddress();
recipient ||= signerAddress; recipient ||= signerAddress;
const mergedContractAddrs = getMergedContractAddresses(coreArtifacts); const chainAddresses = await registry.getAddresses();
const core = HyperlaneCore.fromAddressesMap( const core = HyperlaneCore.fromAddressesMap(chainAddresses, multiProvider);
mergedContractAddrs,
multiProvider,
);
const provider = multiProvider.getProvider(origin); const provider = multiProvider.getProvider(origin);
const connectedSigner = signer.connect(provider); const connectedSigner = signer.connect(provider);
@ -140,31 +117,17 @@ async function executeDelivery({
warpCoreConfig, warpCoreConfig,
); );
if (!routerAddress) { let token: Token;
const tokensForRoute = warpCore.getTokensForRoute(origin, destination); const tokensForRoute = warpCore.getTokensForRoute(origin, destination);
if (tokensForRoute.length === 0) { if (tokensForRoute.length === 0) {
logRed(`No Warp Routes found from ${origin} to ${destination}`); logRed(`No Warp Routes found from ${origin} to ${destination}`);
throw new Error('Error finding warp route');
}
routerAddress = (await select({
message: `Select router address`,
choices: [
...tokensForRoute.map((t) => ({
value: t.addressOrDenom,
description: `${t.name} ($${t.symbol})`,
})),
],
pageSize: 10,
})) as string;
}
const token = warpCore.findToken(origin, routerAddress);
if (!token) {
logRed(
`No Warp Routes found from ${origin} to ${destination} with router address ${routerAddress}`,
);
throw new Error('Error finding warp route'); throw new Error('Error finding warp route');
} else if (tokensForRoute.length === 1) {
token = tokensForRoute[0];
} else {
logBlue(`Please select a token from the Warp config`);
const routerAddress = await runTokenSelectionStep(tokensForRoute);
token = warpCore.findToken(origin, routerAddress)!;
} }
const senderAddress = await signer.getAddress(); const senderAddress = await signer.getAddress();

@ -2,38 +2,26 @@ import { input } from '@inquirer/prompts';
import { ChainName, HyperlaneCore } from '@hyperlane-xyz/sdk'; import { ChainName, HyperlaneCore } from '@hyperlane-xyz/sdk';
import { getContext, getMergedContractAddresses } from '../context.js'; import { CommandContext } from '../context/types.js';
import { log, logBlue, logGreen } from '../logger.js'; import { log, logBlue, logGreen } from '../logger.js';
import { runSingleChainSelectionStep } from '../utils/chains.js'; import { runSingleChainSelectionStep } from '../utils/chains.js';
export async function checkMessageStatus({ export async function checkMessageStatus({
chainConfigPath, context,
coreArtifactsPath,
messageId, messageId,
destination, destination,
origin, origin,
selfRelay, selfRelay,
key,
}: { }: {
chainConfigPath: string; context: CommandContext;
coreArtifactsPath?: string;
messageId?: string; messageId?: string;
destination?: ChainName; destination?: ChainName;
origin?: ChainName; origin?: ChainName;
selfRelay?: boolean; selfRelay?: boolean;
key?: string;
}) { }) {
const keyConfig = selfRelay ? { key } : undefined;
const { multiProvider, customChains, coreArtifacts } = await getContext({
chainConfigPath,
coreConfig: { coreArtifactsPath },
keyConfig,
});
if (!destination) { if (!destination) {
destination = await runSingleChainSelectionStep( destination = await runSingleChainSelectionStep(
customChains, context.chainMetadata,
'Select the destination chain', 'Select the destination chain',
); );
} }
@ -44,10 +32,10 @@ export async function checkMessageStatus({
}); });
} }
const mergedContractAddrs = getMergedContractAddresses(coreArtifacts); const chainAddresses = await context.registry.getAddresses();
const core = HyperlaneCore.fromAddressesMap( const core = HyperlaneCore.fromAddressesMap(
mergedContractAddrs, chainAddresses,
multiProvider, context.multiProvider,
); );
const mailbox = core.getContracts(destination).mailbox; const mailbox = core.getContracts(destination).mailbox;
log(`Checking status of message ${messageId} on ${destination}`); log(`Checking status of message ${messageId} on ${destination}`);
@ -62,7 +50,7 @@ export async function checkMessageStatus({
// TODO: implement option for tx receipt input // TODO: implement option for tx receipt input
if (!origin) { if (!origin) {
origin = await runSingleChainSelectionStep( origin = await runSingleChainSelectionStep(
customChains, context.chainMetadata,
'Select the origin chain', 'Select the origin chain',
); );
} }

@ -2,24 +2,19 @@ import { Separator, checkbox } from '@inquirer/prompts';
import select from '@inquirer/select'; import select from '@inquirer/select';
import chalk from 'chalk'; import chalk from 'chalk';
import { import { ChainMap, ChainMetadata } from '@hyperlane-xyz/sdk';
ChainMap,
ChainMetadata,
mainnetChainsMetadata,
testnetChainsMetadata,
} from '@hyperlane-xyz/sdk';
import { log, logBlue, logRed, logTip } from '../logger.js'; import { log, logRed, logTip } from '../logger.js';
// A special value marker to indicate user selected // A special value marker to indicate user selected
// a new chain in the list // a new chain in the list
const NEW_CHAIN_MARKER = '__new__'; const NEW_CHAIN_MARKER = '__new__';
export async function runSingleChainSelectionStep( export async function runSingleChainSelectionStep(
customChains: ChainMap<ChainMetadata>, chainMetadata: ChainMap<ChainMetadata>,
message = 'Select chain', message = 'Select chain',
) { ) {
const choices = getChainChoices(customChains); const choices = getChainChoices(chainMetadata);
const chain = (await select({ const chain = (await select({
message, message,
choices, choices,
@ -30,11 +25,11 @@ export async function runSingleChainSelectionStep(
} }
export async function runMultiChainSelectionStep( export async function runMultiChainSelectionStep(
customChains: ChainMap<ChainMetadata>, chainMetadata: ChainMap<ChainMetadata>,
message = 'Select chains', message = 'Select chains',
requireMultiple = false, requireMultiple = false,
) { ) {
const choices = getChainChoices(customChains); const choices = getChainChoices(chainMetadata);
while (true) { while (true) {
logTip('Use SPACE key to select chains, then press ENTER'); logTip('Use SPACE key to select chains, then press ENTER');
const chains = (await checkbox({ const chains = (await checkbox({
@ -51,26 +46,25 @@ export async function runMultiChainSelectionStep(
} }
} }
function getChainChoices(customChains: ChainMap<ChainMetadata>) { function getChainChoices(chainMetadata: ChainMap<ChainMetadata>) {
const chainsToChoices = (chains: ChainMetadata[]) => const chainsToChoices = (chains: ChainMetadata[]) =>
chains.map((c) => ({ name: c.name, value: c.name })); chains.map((c) => ({ name: c.name, value: c.name }));
const chains = Object.values(chainMetadata);
const testnetChains = chains.filter((c) => !!c.isTestnet);
const mainnetChains = chains.filter((c) => !c.isTestnet);
const choices: Parameters<typeof select>['0']['choices'] = [ const choices: Parameters<typeof select>['0']['choices'] = [
new Separator('--Custom Chains--'),
...chainsToChoices(Object.values(customChains)),
{ name: '(New custom chain)', value: NEW_CHAIN_MARKER }, { name: '(New custom chain)', value: NEW_CHAIN_MARKER },
new Separator('--Mainnet Chains--'), new Separator('--Mainnet Chains--'),
...chainsToChoices(mainnetChainsMetadata), ...chainsToChoices(mainnetChains),
new Separator('--Testnet Chains--'), new Separator('--Testnet Chains--'),
...chainsToChoices(testnetChainsMetadata), ...chainsToChoices(testnetChains),
]; ];
return choices; return choices;
} }
function handleNewChain(chainNames: string[]) { function handleNewChain(chainNames: string[]) {
if (chainNames.includes(NEW_CHAIN_MARKER)) { if (chainNames.includes(NEW_CHAIN_MARKER)) {
logBlue(
'To use a new chain, use the --config argument add them to that file',
);
log( log(
chalk.blue('Use the'), chalk.blue('Use the'),
chalk.magentaBright('hyperlane config create'), chalk.magentaBright('hyperlane config create'),

@ -6,9 +6,7 @@ import { parse as yamlParse, stringify as yamlStringify } from 'yaml';
import { objMerge } from '@hyperlane-xyz/utils'; import { objMerge } from '@hyperlane-xyz/utils';
import { log, logBlue } from '../logger.js'; import { log } from '../logger.js';
import { getTimestampForFilename } from './time.js';
export type FileFormat = 'yaml' | 'json'; export type FileFormat = 'yaml' | 'json';
@ -118,7 +116,7 @@ export function writeYamlOrJson(
export function mergeYamlOrJson( export function mergeYamlOrJson(
filepath: string, filepath: string,
obj: Record<string, any>, obj: Record<string, any>,
format?: FileFormat, format: FileFormat = 'yaml',
) { ) {
return resolveYamlOrJsonFn( return resolveYamlOrJsonFn(
filepath, filepath,
@ -170,38 +168,6 @@ export function resolveFileFormat(
return undefined; return undefined;
} }
export function prepNewArtifactsFiles(
outPath: string,
files: Array<ArtifactsFile>,
) {
const timestamp = getTimestampForFilename();
const newPaths: string[] = [];
for (const file of files) {
const filePath = path.join(outPath, `${file.filename}-${timestamp}.json`);
// Write empty object to ensure permissions are okay
writeJson(filePath, {});
newPaths.push(filePath);
logBlue(`${file.description} will be written to ${filePath}`);
}
return newPaths;
}
/**
* Retrieves artifacts file metadata for the current command.
* @param dryRun whether or not the current command is being dry-run
* @returns the artifacts files
*/
export function getArtifactsFiles(
defaultFiles: ArtifactsFile[],
dryRun: string = '',
): Array<ArtifactsFile> {
if (dryRun)
defaultFiles.map((defaultFile: ArtifactsFile) => {
defaultFile.filename = `dry-run_${defaultFile.filename}`;
});
return defaultFiles;
}
export async function runFileSelectionStep( export async function runFileSelectionStep(
folderPath: string, folderPath: string,
description: string, description: string,

@ -4,8 +4,6 @@ import { ethers, providers } from 'ethers';
import { impersonateAccount } from '@hyperlane-xyz/sdk'; import { impersonateAccount } from '@hyperlane-xyz/sdk';
import { Address, ensure0x } from '@hyperlane-xyz/utils'; import { Address, ensure0x } from '@hyperlane-xyz/utils';
import { ContextSettings, KeyConfig } from '../context.js';
const ETHEREUM_ADDRESS_LENGTH = 42; const ETHEREUM_ADDRESS_LENGTH = 42;
const DEFAULT_KEY_TYPE = 'private key'; const DEFAULT_KEY_TYPE = 'private key';
const IMPERSONATED_KEY_TYPE = 'address'; const IMPERSONATED_KEY_TYPE = 'address';
@ -14,34 +12,32 @@ const IMPERSONATED_KEY_TYPE = 'address';
* Retrieves a signer for the current command-context. * Retrieves a signer for the current command-context.
* @returns the signer * @returns the signer
*/ */
export async function getSigner<P extends ContextSettings>({ export async function getSigner({
keyConfig, key,
skipConfirmation, skipConfirmation,
}: P): Promise<providers.JsonRpcSigner | ethers.Wallet | undefined> { }: {
if (!keyConfig) return undefined; key?: string;
skipConfirmation?: boolean;
const key = await retrieveKey(DEFAULT_KEY_TYPE, keyConfig, skipConfirmation); }) {
key ||= await retrieveKey(DEFAULT_KEY_TYPE, skipConfirmation);
return privateKeyToSigner(key); const signer = privateKeyToSigner(key);
return { key, signer };
} }
/** /**
* Retrieves an impersonated signer for the current command-context. * Retrieves an impersonated signer for the current command-context.
* @returns the impersonated signer * @returns the impersonated signer
*/ */
export async function getImpersonatedSigner<P extends ContextSettings>({ export async function getImpersonatedSigner({
keyConfig, key,
skipConfirmation, skipConfirmation,
}: P): Promise<providers.JsonRpcSigner | ethers.Wallet | undefined> { }: {
if (!keyConfig) return undefined; key?: string;
skipConfirmation?: boolean;
const key = await retrieveKey( }) {
IMPERSONATED_KEY_TYPE, key ||= await retrieveKey(IMPERSONATED_KEY_TYPE, skipConfirmation);
keyConfig, const signer = await addressToImpersonatedSigner(key);
skipConfirmation, return { key, signer };
);
return await addressToImpersonatedSigner(key);
} }
/** /**
@ -91,15 +87,11 @@ function privateKeyToSigner(key: string): ethers.Wallet {
async function retrieveKey( async function retrieveKey(
keyType: string, keyType: string,
keyConfig: KeyConfig,
skipConfirmation: boolean | undefined, skipConfirmation: boolean | undefined,
): Promise<string> { ): Promise<string> {
if (keyConfig.key) return keyConfig.key; if (skipConfirmation) throw new Error(`No ${keyType} provided`);
else if (skipConfirmation) throw new Error(`No ${keyType} provided`);
else else
return await input({ return await input({
message: message: `Please enter ${keyType} or use the HYP_KEY environment variable.`,
keyConfig.promptMessage ||
`Please enter ${keyType} or use the HYP_KEY environment variable.`,
}); });
} }

@ -0,0 +1,19 @@
import select from '@inquirer/select';
import { Token } from '@hyperlane-xyz/sdk';
export async function runTokenSelectionStep(
tokens: Token[],
message = 'Select token',
) {
const choices = tokens.map((t) => ({
name: `${t.symbol} - ${t.addressOrDenom}`,
value: t.addressOrDenom,
}));
const routerAddress = (await select({
message,
choices,
pageSize: 20,
})) as string;
return routerAddress;
}

@ -4,7 +4,12 @@ import { log } from '../logger.js';
import { VERSION } from '../version.js'; import { VERSION } from '../version.js';
export async function checkVersion() { export async function checkVersion() {
const argv = process.argv;
// The latestVersion lib (or one of its deps) is confused by the --registry value
// in the CLI's args, so we need to clear the args before calling it
process.argv = [];
const currentVersion = await latestVersion('@hyperlane-xyz/cli'); const currentVersion = await latestVersion('@hyperlane-xyz/cli');
process.argv = argv;
if (VERSION < currentVersion) { if (VERSION < currentVersion) {
log(`Your CLI version: ${VERSION}, latest version: ${currentVersion}`); log(`Your CLI version: ${VERSION}, latest version: ${currentVersion}`);
} }

@ -0,0 +1,14 @@
# Configs for describing chain metadata for use in Hyperlane deployments or apps
# Consists of a map of chain names to metadata
# Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts
---
chainId: 31337
domainId: 31337
name: anvil1
protocol: ethereum
rpcUrls:
- http: http://127.0.0.1:8545
nativeToken:
name: Ether
symbol: ETH
decimals: 18

@ -0,0 +1,7 @@
---
chainId: 31338
domainId: 31338
name: anvil2
protocol: ethereum
rpcUrls:
- http: http://127.0.0.1:8555

@ -0,0 +1,13 @@
chainId: 44787
domainId: 44787
name: alfajores
nativeToken:
decimals: 18
name: CELO
symbol: CELO
protocol: ethereum
rpcUrls:
- http: https://alfajores-forno.celo-testnet.org
blocks:
confirmations: 1
estimateBlockTime: 1

@ -0,0 +1,10 @@
chainId: 31337
domainId: 31337
name: anvil
protocol: ethereum
rpcUrls:
- http: http://127.0.0.1:8545
nativeToken:
name: Ether
symbol: ETH
decimals: 18

@ -0,0 +1,6 @@
chainId: 43113
domainId: 43113
name: fuji
protocol: ethereum
rpcUrls:
- http: https://api.avax-test.network/ext/bc/C/rpc

@ -0,0 +1,11 @@
---
chainId: 31337
domainId: 31337
name: anvil1
protocol: ethereum
rpcUrls:
- http: http://127.0.0.1:8545
nativeToken:
name: Ether
symbol: ETH
decimals: 18

@ -0,0 +1,9 @@
chainId: 1
domainId: 1
name: ethereum
protocol: ethereum
rpcUrls:
- http: http://127.0.0.1:8555
blocks:
confirmations: 1
estimateBlockTime: 1

@ -1,9 +1,8 @@
# A config for a multisig Interchain Security Module (ISM) # A config for a multisig Interchain Security Module (ISM)
# Schema: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/ism/types.ts # Schema: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/ism/types.ts
# #
--- ---
anvil: anvil1:
threshold: 1 # Number: Signatures required to approve a message threshold: 1 # Number: Signatures required to approve a message
validators: # Array: List of validator addresses validators: # Array: List of validator addresses
- '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720'

@ -10,7 +10,7 @@
# fastCollateral # fastCollateral
# fastSynthetic # fastSynthetic
--- ---
anvil: anvil1:
type: native type: native
# token: "0x123" # Collateral/vault address. Required for collateral types # token: "0x123" # Collateral/vault address. Required for collateral types
# owner: "0x123" # Optional owner address for synthetic token # owner: "0x123" # Optional owner address for synthetic token

@ -4,6 +4,7 @@
"version": "3.10.0", "version": "3.10.0",
"dependencies": { "dependencies": {
"@hyperlane-xyz/core": "3.10.0", "@hyperlane-xyz/core": "3.10.0",
"@hyperlane-xyz/registry": "^1.0.7",
"@hyperlane-xyz/sdk": "3.10.0", "@hyperlane-xyz/sdk": "3.10.0",
"@openzeppelin/contracts-upgradeable": "^4.9.3", "@openzeppelin/contracts-upgradeable": "^4.9.3",
"ethers": "^5.7.2" "ethers": "^5.7.2"

@ -1,8 +1,9 @@
import { RouterConfig, chainMetadata } from '@hyperlane-xyz/sdk'; import { chainMetadata } from '@hyperlane-xyz/registry';
import { RouterConfig } from '@hyperlane-xyz/sdk';
export type HelloWorldConfig = RouterConfig; export type HelloWorldConfig = RouterConfig;
// SET DESIRED NETWORKS HERE // SET DESIRED NETWORKS HERE OR USE THE DEFAULT SET FROM THE REGISTRY
export const prodConfigs = { export const prodConfigs = {
alfajores: chainMetadata.alfajores, ...chainMetadata,
}; };

@ -1,8 +1,11 @@
import { chainAddresses } from '@hyperlane-xyz/registry';
import { import {
ChainMap,
HyperlaneCore, HyperlaneCore,
MultiProvider, MultiProvider,
attachContractsMap, attachContractsMap,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import type { Address } from '@hyperlane-xyz/utils';
import { HelloWorldApp } from '../app/app.js'; import { HelloWorldApp } from '../app/app.js';
import { helloWorldFactories } from '../app/contracts.js'; import { helloWorldFactories } from '../app/contracts.js';
@ -10,7 +13,7 @@ import { HelloWorldChecker } from '../deploy/check.js';
import { prodConfigs } from '../deploy/config.js'; import { prodConfigs } from '../deploy/config.js';
// COPY FROM OUTPUT OF DEPLOYMENT SCRIPT OR IMPORT FROM ELSEWHERE // COPY FROM OUTPUT OF DEPLOYMENT SCRIPT OR IMPORT FROM ELSEWHERE
const deploymentAddresses = {}; const deploymentAddresses: ChainMap<Record<string, Address>> = {};
// SET CONTRACT OWNER ADDRESS HERE // SET CONTRACT OWNER ADDRESS HERE
const ownerAddress = '0x123...'; const ownerAddress = '0x123...';
@ -24,7 +27,9 @@ async function check() {
helloWorldFactories, helloWorldFactories,
); );
const core = HyperlaneCore.fromEnvironment('testnet', multiProvider); // If the default registry does not contain the core contract addresses you need,
// Replace `chainAddresses` with a custom map of addresses
const core = HyperlaneCore.fromAddressesMap(chainAddresses, multiProvider);
const app = new HelloWorldApp(core, contractsMap, multiProvider); const app = new HelloWorldApp(core, contractsMap, multiProvider);
const config = core.getRouterConfig(ownerAddress); const config = core.getRouterConfig(ownerAddress);

@ -1,5 +1,6 @@
import { Wallet } from 'ethers'; import { Wallet } from 'ethers';
import { chainAddresses } from '@hyperlane-xyz/registry';
import { import {
HyperlaneCore, HyperlaneCore,
MultiProvider, MultiProvider,
@ -17,7 +18,9 @@ async function main() {
const multiProvider = new MultiProvider(prodConfigs); const multiProvider = new MultiProvider(prodConfigs);
multiProvider.setSharedSigner(signer); multiProvider.setSharedSigner(signer);
const core = HyperlaneCore.fromEnvironment('testnet', multiProvider); // If the default registry does not contain the core contract addresses you need,
// Replace `chainAddresses` with a custom map of addresses
const core = HyperlaneCore.fromAddressesMap(chainAddresses, multiProvider);
const config = core.getRouterConfig(signer.address); const config = core.getRouterConfig(signer.address);
const deployer = new HelloWorldDeployer(multiProvider); const deployer = new HelloWorldDeployer(multiProvider);

@ -4,10 +4,10 @@ import hre from 'hardhat';
import { import {
ChainMap, ChainMap,
Chains,
HyperlaneIsmFactory, HyperlaneIsmFactory,
HyperlaneProxyFactoryDeployer, HyperlaneProxyFactoryDeployer,
MultiProvider, MultiProvider,
TestChainName,
TestCoreApp, TestCoreApp,
TestCoreDeployer, TestCoreDeployer,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
@ -17,8 +17,8 @@ import { HelloWorldDeployer } from '../deploy/deploy.js';
import { HelloWorld } from '../types/index.js'; import { HelloWorld } from '../types/index.js';
describe('HelloWorld', async () => { describe('HelloWorld', async () => {
const localChain = Chains.test1; const localChain = TestChainName.test1;
const remoteChain = Chains.test2; const remoteChain = TestChainName.test2;
let localDomain: number; let localDomain: number;
let remoteDomain: number; let remoteDomain: number;

@ -0,0 +1,8 @@
# @hyperlane-xyz/infra
Various scripts and utilities for managing and deploying Hyperlane infrastructure.
## Tips
- To enable more verbose logging, see the log env vars mentioned in the [root readme](../../README.md)
- To configure the local registry path, set the `REGISTRY_URI` env var

@ -1,10 +1,7 @@
import { import {
Chains,
GasPaymentEnforcement, GasPaymentEnforcement,
GasPaymentEnforcementPolicyType, GasPaymentEnforcementPolicyType,
RpcConsensusType, RpcConsensusType,
chainMetadata,
getDomainId,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { import {
@ -19,8 +16,9 @@ import {
import { ALL_KEY_ROLES, Role } from '../../../src/roles.js'; import { ALL_KEY_ROLES, Role } from '../../../src/roles.js';
import { Contexts } from '../../contexts.js'; import { Contexts } from '../../contexts.js';
import { environment, supportedChainNames } from './chains.js'; import { environment } from './chains.js';
import { helloWorld } from './helloworld.js'; import { helloWorld } from './helloworld.js';
import { supportedChainNames } from './supportedChainNames.js';
import { validatorChainConfig } from './validators.js'; import { validatorChainConfig } from './validators.js';
import ancient8EthereumUsdcAddresses from './warp/ancient8-USDC-addresses.json'; import ancient8EthereumUsdcAddresses from './warp/ancient8-USDC-addresses.json';
import arbitrumTIAAddresses from './warp/arbitrum-TIA-addresses.json'; import arbitrumTIAAddresses from './warp/arbitrum-TIA-addresses.json';
@ -46,74 +44,74 @@ const repo = 'gcr.io/abacus-labs-dev/hyperlane-agent';
export const hyperlaneContextAgentChainConfig: AgentChainConfig = { export const hyperlaneContextAgentChainConfig: AgentChainConfig = {
// Generally, we run all production validators in the Hyperlane context. // Generally, we run all production validators in the Hyperlane context.
[Role.Validator]: { [Role.Validator]: {
[Chains.arbitrum]: true, arbitrum: true,
[Chains.ancient8]: true, ancient8: true,
[Chains.avalanche]: true, avalanche: true,
[Chains.base]: true, base: true,
[Chains.blast]: true, blast: true,
[Chains.bsc]: true, bsc: true,
[Chains.celo]: true, celo: true,
[Chains.ethereum]: true, ethereum: true,
[Chains.neutron]: true, neutron: true,
[Chains.mantapacific]: true, mantapacific: true,
[Chains.mode]: true, mode: true,
[Chains.moonbeam]: true, moonbeam: true,
[Chains.optimism]: true, optimism: true,
[Chains.polygon]: true, polygon: true,
[Chains.gnosis]: true, gnosis: true,
[Chains.scroll]: true, scroll: true,
[Chains.polygonzkevm]: true, polygonzkevm: true,
[Chains.injective]: true, injective: true,
[Chains.inevm]: true, inevm: true,
[Chains.viction]: true, viction: true,
}, },
[Role.Relayer]: { [Role.Relayer]: {
[Chains.arbitrum]: true, arbitrum: true,
[Chains.ancient8]: true, ancient8: true,
[Chains.avalanche]: true, avalanche: true,
[Chains.base]: true, base: true,
[Chains.blast]: true, blast: true,
[Chains.bsc]: true, bsc: true,
[Chains.celo]: true, celo: true,
[Chains.ethereum]: true, ethereum: true,
// At the moment, we only relay between Neutron and Manta Pacific on the neutron context. // At the moment, we only relay between Neutron and Manta Pacific on the neutron context.
[Chains.neutron]: false, neutron: false,
[Chains.mantapacific]: true, mantapacific: false,
[Chains.mode]: true, mode: true,
[Chains.moonbeam]: true, moonbeam: true,
[Chains.optimism]: true, optimism: true,
[Chains.polygon]: true, polygon: true,
[Chains.gnosis]: true, gnosis: true,
[Chains.scroll]: true, scroll: true,
[Chains.polygonzkevm]: true, polygonzkevm: true,
[Chains.injective]: true, injective: true,
[Chains.inevm]: true, inevm: true,
[Chains.viction]: true, viction: true,
}, },
[Role.Scraper]: { [Role.Scraper]: {
[Chains.arbitrum]: true, arbitrum: true,
[Chains.ancient8]: true, ancient8: true,
[Chains.avalanche]: true, avalanche: true,
[Chains.base]: true, base: true,
[Chains.blast]: true, blast: true,
[Chains.bsc]: true, bsc: true,
[Chains.celo]: true, celo: true,
[Chains.ethereum]: true, ethereum: true,
// Cannot scrape non-EVM chains // Cannot scrape non-EVM chains
[Chains.neutron]: false, neutron: false,
[Chains.mantapacific]: true, mantapacific: true,
[Chains.mode]: true, mode: true,
[Chains.moonbeam]: true, moonbeam: true,
[Chains.optimism]: true, optimism: true,
[Chains.polygon]: true, polygon: true,
[Chains.gnosis]: true, gnosis: true,
[Chains.scroll]: true, scroll: true,
[Chains.polygonzkevm]: true, polygonzkevm: true,
// Cannot scrape non-EVM chains // Cannot scrape non-EVM chains
[Chains.injective]: false, injective: false,
[Chains.inevm]: true, inevm: true,
// Has RPC non-compliance that breaks scraping. // Has RPC non-compliance that breaks scraping.
[Chains.viction]: false, viction: false,
}, },
}; };
@ -257,11 +255,7 @@ const neutron: RootAgentConfig = {
...contextBase, ...contextBase,
contextChainNames: { contextChainNames: {
validator: [], validator: [],
relayer: [ relayer: ['neutron', 'mantapacific', 'arbitrum'],
chainMetadata.neutron.name,
chainMetadata.mantapacific.name,
chainMetadata.arbitrum.name,
],
scraper: [], scraper: [],
}, },
context: Contexts.Neutron, context: Contexts.Neutron,

@ -1,17 +1,11 @@
import { import { ChainMap, ChainMetadata } from '@hyperlane-xyz/sdk';
ChainMap, import { objKeys } from '@hyperlane-xyz/utils';
ChainMetadata,
Mainnets,
chainMetadata,
} from '@hyperlane-xyz/sdk';
import { getChainMetadatas } from '../../../src/config/chain.js'; import { getChainMetadatas } from '../../../src/config/chain.js';
import { getChain } from '../../registry.js';
// The `Mainnets` from the SDK are all supported chains for the mainnet3 environment. import { supportedChainNames } from './supportedChainNames.js';
// These chains may be any protocol type.
export const supportedChainNames = Mainnets;
export type MainnetChains = (typeof supportedChainNames)[number];
export const environment = 'mainnet3'; export const environment = 'mainnet3';
const { const {
@ -22,15 +16,15 @@ const {
export const ethereumMainnetConfigs: ChainMap<ChainMetadata> = { export const ethereumMainnetConfigs: ChainMap<ChainMetadata> = {
...defaultEthereumMainnetConfigs, ...defaultEthereumMainnetConfigs,
bsc: { bsc: {
...chainMetadata.bsc, ...getChain('bsc'),
transactionOverrides: { transactionOverrides: {
gasPrice: 3 * 10 ** 9, // 3 gwei gasPrice: 3 * 10 ** 9, // 3 gwei
}, },
}, },
polygon: { polygon: {
...chainMetadata.polygon, ...getChain('polygon'),
blocks: { blocks: {
...chainMetadata.polygon.blocks, ...getChain('polygon').blocks,
confirmations: 3, confirmations: 3,
}, },
transactionOverrides: { transactionOverrides: {
@ -41,9 +35,9 @@ export const ethereumMainnetConfigs: ChainMap<ChainMetadata> = {
}, },
}, },
ethereum: { ethereum: {
...chainMetadata.ethereum, ...getChain('ethereum'),
blocks: { blocks: {
...chainMetadata.ethereum.blocks, ...getChain('ethereum').blocks,
confirmations: 3, confirmations: 3,
}, },
transactionOverrides: { transactionOverrides: {
@ -52,7 +46,7 @@ export const ethereumMainnetConfigs: ChainMap<ChainMetadata> = {
}, },
}, },
scroll: { scroll: {
...chainMetadata.scroll, ...getChain('scroll'),
transactionOverrides: { transactionOverrides: {
// Scroll doesn't use EIP 1559 and the gas price that's returned is sometimes // Scroll doesn't use EIP 1559 and the gas price that's returned is sometimes
// too low for the transaction to be included in a reasonable amount of time - // too low for the transaction to be included in a reasonable amount of time -
@ -61,7 +55,7 @@ export const ethereumMainnetConfigs: ChainMap<ChainMetadata> = {
}, },
}, },
moonbeam: { moonbeam: {
...chainMetadata.moonbeam, ...getChain('moonbeam'),
transactionOverrides: { transactionOverrides: {
maxFeePerGas: 350 * 10 ** 9, // 350 gwei maxFeePerGas: 350 * 10 ** 9, // 350 gwei
maxPriorityFeePerGas: 50 * 10 ** 9, // 50 gwei maxPriorityFeePerGas: 50 * 10 ** 9, // 50 gwei
@ -74,6 +68,4 @@ export const mainnetConfigs: ChainMap<ChainMetadata> = {
...nonEthereumMainnetConfigs, ...nonEthereumMainnetConfigs,
}; };
export const ethereumChainNames = Object.keys( export const ethereumChainNames = objKeys(ethereumMainnetConfigs);
ethereumMainnetConfigs,
) as MainnetChains[];

@ -18,15 +18,18 @@ import {
RoutingIsmConfig, RoutingIsmConfig,
defaultMultisigConfigs, defaultMultisigConfigs,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { Address, objMap } from '@hyperlane-xyz/utils'; import { Address, ProtocolType, objMap } from '@hyperlane-xyz/utils';
import { getChain } from '../../registry.js';
import { supportedChainNames } from './chains.js';
import { igp } from './igp.js'; import { igp } from './igp.js';
import { DEPLOYER, owners } from './owners.js'; import { DEPLOYER, owners } from './owners.js';
import { supportedChainNames } from './supportedChainNames.js';
export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => { export const core: ChainMap<CoreConfig> = objMap(owners, (local, owner) => {
const originMultisigs: ChainMap<MultisigConfig> = Object.fromEntries( const originMultisigs: ChainMap<MultisigConfig> = Object.fromEntries(
supportedChainNames supportedChainNames
.filter((chain) => getChain(chain).protocol === ProtocolType.Ethereum)
.filter((chain) => chain !== local) .filter((chain) => chain !== local)
.map((origin) => [origin, defaultMultisigConfigs[origin]]), .map((origin) => [origin, defaultMultisigConfigs[origin]]),
); );

@ -16,20 +16,17 @@ import {
getTokenExchangeRateFromValues, getTokenExchangeRateFromValues,
} from '../../../src/config/gas-oracle.js'; } from '../../../src/config/gas-oracle.js';
import { import { ethereumChainNames } from './chains.js';
MainnetChains,
ethereumChainNames,
supportedChainNames,
} from './chains.js';
import gasPrices from './gasPrices.json'; import gasPrices from './gasPrices.json';
import { DEPLOYER, owners } from './owners.js'; import { DEPLOYER, owners } from './owners.js';
import { supportedChainNames } from './supportedChainNames.js';
import rawTokenPrices from './tokenPrices.json'; import rawTokenPrices from './tokenPrices.json';
const tokenPrices: ChainMap<string> = rawTokenPrices; const tokenPrices: ChainMap<string> = rawTokenPrices;
const FOREIGN_DEFAULT_OVERHEAD = 600_000; // cosmwasm warp route somewhat arbitrarily chosen const FOREIGN_DEFAULT_OVERHEAD = 600_000; // cosmwasm warp route somewhat arbitrarily chosen
const remoteOverhead = (remote: MainnetChains) => const remoteOverhead = (remote: ChainName) =>
ethereumChainNames.includes(remote) ethereumChainNames.includes(remote)
? multisigIsmVerificationCost( ? multisigIsmVerificationCost(
defaultMultisigConfigs[remote].threshold, defaultMultisigConfigs[remote].threshold,
@ -53,11 +50,11 @@ function getTokenExchangeRate(local: ChainName, remote: ChainName): BigNumber {
const storageGasOracleConfig: AllStorageGasOracleConfigs = const storageGasOracleConfig: AllStorageGasOracleConfigs =
getAllStorageGasOracleConfigs( getAllStorageGasOracleConfigs(
supportedChainNames, ethereumChainNames,
gasPrices, gasPrices,
getTokenExchangeRate, getTokenExchangeRate,
(local) => parseFloat(tokenPrices[local]), (local) => parseFloat(tokenPrices[local]),
(local) => remoteOverhead(local as MainnetChains), (local) => remoteOverhead(local),
); );
export const igp: ChainMap<IgpConfig> = objMap(owners, (local, owner) => ({ export const igp: ChainMap<IgpConfig> = objMap(owners, (local, owner) => ({
@ -72,7 +69,7 @@ export const igp: ChainMap<IgpConfig> = objMap(owners, (local, owner) => ({
overhead: Object.fromEntries( overhead: Object.fromEntries(
exclude(local, supportedChainNames).map((remote) => [ exclude(local, supportedChainNames).map((remote) => [
remote, remote,
remoteOverhead(remote as MainnetChains), remoteOverhead(remote),
]), ]),
), ),
oracleConfig: storageGasOracleConfig[local], oracleConfig: storageGasOracleConfig[local],

@ -2,29 +2,27 @@ import {
BridgeAdapterConfig, BridgeAdapterConfig,
BridgeAdapterType, BridgeAdapterType,
ChainMap, ChainMap,
Chains,
RpcConsensusType, RpcConsensusType,
chainMetadata,
getDomainId,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { LiquidityLayerRelayerConfig } from '../../../src/config/middleware.js'; import { LiquidityLayerRelayerConfig } from '../../../src/config/middleware.js';
import { getDomainId } from '../../registry.js';
import { environment } from './chains.js'; import { environment } from './chains.js';
const circleDomainMapping = [ const circleDomainMapping = [
{ {
hyperlaneDomain: getDomainId(chainMetadata[Chains.ethereum]), hyperlaneDomain: getDomainId('ethereum'),
circleDomain: 0, circleDomain: 0,
}, },
{ {
hyperlaneDomain: getDomainId(chainMetadata[Chains.avalanche]), hyperlaneDomain: getDomainId('avalanche'),
circleDomain: 1, circleDomain: 1,
}, },
]; ];
export const bridgeAdapterConfigs: ChainMap<BridgeAdapterConfig> = { export const bridgeAdapterConfigs: ChainMap<BridgeAdapterConfig> = {
[Chains.ethereum]: { ethereum: {
circle: { circle: {
type: BridgeAdapterType.Circle, type: BridgeAdapterType.Circle,
tokenMessengerAddress: '0xBd3fa81B58Ba92a82136038B25aDec7066af3155', tokenMessengerAddress: '0xBd3fa81B58Ba92a82136038B25aDec7066af3155',
@ -33,7 +31,7 @@ export const bridgeAdapterConfigs: ChainMap<BridgeAdapterConfig> = {
circleDomainMapping, circleDomainMapping,
}, },
}, },
[Chains.avalanche]: { avalanche: {
circle: { circle: {
type: BridgeAdapterType.Circle, type: BridgeAdapterType.Circle,
tokenMessengerAddress: '0x6B25532e1060CE10cc3B0A99e5683b91BFDe6982', tokenMessengerAddress: '0x6B25532e1060CE10cc3B0A99e5683b91BFDe6982',

@ -1,11 +1,8 @@
import { import { AddressesMap, ChainMap, OwnableConfig } from '@hyperlane-xyz/sdk';
AddressesMap,
ChainMap,
OwnableConfig,
hyperlaneEnvironments,
} from '@hyperlane-xyz/sdk';
import { Address, objFilter, objMap } from '@hyperlane-xyz/utils'; import { Address, objFilter, objMap } from '@hyperlane-xyz/utils';
import { getMainnetAddresses } from '../../registry.js';
import { ethereumChainNames } from './chains.js'; import { ethereumChainNames } from './chains.js';
export const timelocks: ChainMap<Address | undefined> = { export const timelocks: ChainMap<Address | undefined> = {
@ -13,8 +10,7 @@ export const timelocks: ChainMap<Address | undefined> = {
}; };
export function localAccountRouters(): ChainMap<Address> { export function localAccountRouters(): ChainMap<Address> {
const coreAddresses: ChainMap<AddressesMap> = const coreAddresses: ChainMap<AddressesMap> = getMainnetAddresses();
hyperlaneEnvironments['mainnet'];
const filteredAddresses = objFilter( const filteredAddresses = objFilter(
coreAddresses, coreAddresses,
(local, addressMap): addressMap is AddressesMap => (local, addressMap): addressMap is AddressesMap =>

@ -0,0 +1,24 @@
// These chains may be any protocol type.
// Placing them here instead of adjacent chains file to avoid circular dep
export const supportedChainNames = [
'arbitrum',
'ancient8',
'avalanche',
'blast',
'bsc',
'celo',
'ethereum',
'neutron',
'mantapacific',
'mode',
'moonbeam',
'optimism',
'polygon',
'gnosis',
'base',
'scroll',
'polygonzkevm',
'injective',
'inevm',
'viction',
];

@ -1,19 +1,18 @@
import { import {
BridgeAdapterType, BridgeAdapterType,
ChainMap, ChainMap,
Chains,
CircleBridgeAdapterConfig, CircleBridgeAdapterConfig,
chainMetadata,
getDomainId,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { getDomainId } from '../../registry.js';
const circleDomainMapping = [ const circleDomainMapping = [
{ hyperlaneDomain: getDomainId(chainMetadata[Chains.fuji]), circleDomain: 1 }, { hyperlaneDomain: getDomainId('fuji'), circleDomain: 1 },
]; ];
// Circle deployed contracts // Circle deployed contracts
export const circleBridgeAdapterConfig: ChainMap<CircleBridgeAdapterConfig> = { export const circleBridgeAdapterConfig: ChainMap<CircleBridgeAdapterConfig> = {
[Chains.fuji]: { fuji: {
type: BridgeAdapterType.Circle, type: BridgeAdapterType.Circle,
tokenMessengerAddress: '0x0fc1103927af27af808d03135214718bcedbe9ad', tokenMessengerAddress: '0x0fc1103927af27af808d03135214718bcedbe9ad',
messageTransmitterAddress: '0x52fffb3ee8fa7838e9858a2d5e454007b9027c3c', messageTransmitterAddress: '0x52fffb3ee8fa7838e9858a2d5e454007b9027c3c',

@ -1,7 +1,6 @@
import { chainMetadata, getReorgPeriod } from '@hyperlane-xyz/sdk';
import { ValidatorBaseChainConfigMap } from '../../../src/config/agent/validator.js'; import { ValidatorBaseChainConfigMap } from '../../../src/config/agent/validator.js';
import { Contexts } from '../../contexts.js'; import { Contexts } from '../../contexts.js';
import { getReorgPeriod } from '../../registry.js';
import { validatorBaseConfigsFn } from '../utils.js'; import { validatorBaseConfigsFn } from '../utils.js';
import { environment } from './chains.js'; import { environment } from './chains.js';
@ -13,7 +12,7 @@ export const validatorChainConfig = (
return { return {
ancient8: { ancient8: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.ancient8), reorgPeriod: getReorgPeriod('ancient8'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: ['0xbb5842ae0e05215b53df4787a29144efb7e67551'], [Contexts.Hyperlane]: ['0xbb5842ae0e05215b53df4787a29144efb7e67551'],
@ -27,7 +26,7 @@ export const validatorChainConfig = (
}, },
celo: { celo: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.celo), reorgPeriod: getReorgPeriod('celo'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -47,7 +46,7 @@ export const validatorChainConfig = (
}, },
ethereum: { ethereum: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.ethereum), reorgPeriod: getReorgPeriod('ethereum'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -67,7 +66,7 @@ export const validatorChainConfig = (
}, },
avalanche: { avalanche: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.avalanche), reorgPeriod: getReorgPeriod('avalanche'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -87,7 +86,7 @@ export const validatorChainConfig = (
}, },
polygon: { polygon: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.polygon), reorgPeriod: getReorgPeriod('polygon'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -107,7 +106,7 @@ export const validatorChainConfig = (
}, },
bsc: { bsc: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.bsc), reorgPeriod: getReorgPeriod('bsc'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -127,7 +126,7 @@ export const validatorChainConfig = (
}, },
arbitrum: { arbitrum: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.arbitrum), reorgPeriod: getReorgPeriod('arbitrum'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -147,7 +146,7 @@ export const validatorChainConfig = (
}, },
optimism: { optimism: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.optimism), reorgPeriod: getReorgPeriod('optimism'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -167,7 +166,7 @@ export const validatorChainConfig = (
}, },
moonbeam: { moonbeam: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.moonbeam), reorgPeriod: getReorgPeriod('moonbeam'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -187,7 +186,7 @@ export const validatorChainConfig = (
}, },
gnosis: { gnosis: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.gnosis), reorgPeriod: getReorgPeriod('gnosis'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -207,7 +206,7 @@ export const validatorChainConfig = (
}, },
base: { base: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.base), reorgPeriod: getReorgPeriod('base'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -227,7 +226,7 @@ export const validatorChainConfig = (
}, },
injective: { injective: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.injective), reorgPeriod: getReorgPeriod('injective'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: ['0xbfb8911b72cfb138c7ce517c57d9c691535dc517'], [Contexts.Hyperlane]: ['0xbfb8911b72cfb138c7ce517c57d9c691535dc517'],
@ -239,7 +238,7 @@ export const validatorChainConfig = (
}, },
inevm: { inevm: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.inevm), reorgPeriod: getReorgPeriod('inevm'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -259,7 +258,7 @@ export const validatorChainConfig = (
}, },
scroll: { scroll: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.scroll), reorgPeriod: getReorgPeriod('scroll'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -279,7 +278,7 @@ export const validatorChainConfig = (
}, },
polygonzkevm: { polygonzkevm: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.polygonzkevm), reorgPeriod: getReorgPeriod('polygonzkevm'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -299,7 +298,7 @@ export const validatorChainConfig = (
}, },
neutron: { neutron: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.neutron), reorgPeriod: getReorgPeriod('neutron'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -319,7 +318,7 @@ export const validatorChainConfig = (
}, },
mantapacific: { mantapacific: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.mantapacific), reorgPeriod: getReorgPeriod('mantapacific'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -339,7 +338,7 @@ export const validatorChainConfig = (
}, },
viction: { viction: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.viction), reorgPeriod: getReorgPeriod('viction'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: ['0x1f87c368f8e05a85ef9126d984a980a20930cb9c'], [Contexts.Hyperlane]: ['0x1f87c368f8e05a85ef9126d984a980a20930cb9c'],
@ -355,7 +354,7 @@ export const validatorChainConfig = (
}, },
blast: { blast: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.blast), reorgPeriod: getReorgPeriod('blast'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: ['0xf20c0b09f597597c8d2430d3d72dfddaf09177d1'], [Contexts.Hyperlane]: ['0xf20c0b09f597597c8d2430d3d72dfddaf09177d1'],
@ -369,7 +368,7 @@ export const validatorChainConfig = (
}, },
mode: { mode: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.mode), reorgPeriod: getReorgPeriod('mode'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: ['0x7eb2e1920a4166c19d6884c1cec3d2cf356fc9b7'], [Contexts.Hyperlane]: ['0x7eb2e1920a4166c19d6884c1cec3d2cf356fc9b7'],

@ -7,7 +7,7 @@ import { RootAgentConfig } from '../../../src/config/agent/agent.js';
import { ALL_KEY_ROLES } from '../../../src/roles.js'; import { ALL_KEY_ROLES } from '../../../src/roles.js';
import { Contexts } from '../../contexts.js'; import { Contexts } from '../../contexts.js';
import { agentChainNames, chainNames } from './chains.js'; import { agentChainNames, testChainNames } from './chains.js';
import { validators } from './validators.js'; import { validators } from './validators.js';
const roleBase = { const roleBase = {
@ -24,7 +24,7 @@ const hyperlane: RootAgentConfig = {
context: Contexts.Hyperlane, context: Contexts.Hyperlane,
rolesWithKeys: ALL_KEY_ROLES, rolesWithKeys: ALL_KEY_ROLES,
contextChainNames: agentChainNames, contextChainNames: agentChainNames,
environmentChainNames: chainNames, environmentChainNames: testChainNames,
relayer: { relayer: {
...roleBase, ...roleBase,
gasPaymentEnforcement: [ gasPaymentEnforcement: [

@ -1,18 +1,15 @@
import { ChainMap, ChainMetadata, chainMetadata } from '@hyperlane-xyz/sdk'; import {
testChainMetadata as defaultTestChainMetadata,
testChains as defaultTestChains,
} from '@hyperlane-xyz/sdk';
import { AgentChainNames, Role } from '../../../src/roles.js'; import { AgentChainNames, Role } from '../../../src/roles.js';
export const testConfigs: ChainMap<ChainMetadata> = { export const testChainNames = defaultTestChains;
test1: chainMetadata.test1, export const testChainMetadata = { ...defaultTestChainMetadata };
test2: chainMetadata.test2,
test3: chainMetadata.test3,
};
export type TestChains = keyof typeof testConfigs;
export const chainNames = Object.keys(testConfigs) as TestChains[];
export const agentChainNames: AgentChainNames = { export const agentChainNames: AgentChainNames = {
[Role.Validator]: chainNames, [Role.Validator]: testChainNames,
[Role.Relayer]: chainNames, [Role.Relayer]: testChainNames,
[Role.Scraper]: chainNames, [Role.Scraper]: testChainNames,
}; };

@ -12,7 +12,7 @@ import {
getAllStorageGasOracleConfigs, getAllStorageGasOracleConfigs,
} from '../../../src/config/gas-oracle.js'; } from '../../../src/config/gas-oracle.js';
import { chainNames } from './chains.js'; import { testChainNames } from './chains.js';
const TEST_TOKEN_EXCHANGE_RATE = ethers.utils.parseUnits( const TEST_TOKEN_EXCHANGE_RATE = ethers.utils.parseUnits(
'1', '1',
@ -37,4 +37,8 @@ function getTokenExchangeRate(
} }
export const storageGasOracleConfig: AllStorageGasOracleConfigs = export const storageGasOracleConfig: AllStorageGasOracleConfigs =
getAllStorageGasOracleConfigs(chainNames, gasPrices, getTokenExchangeRate); getAllStorageGasOracleConfigs(
testChainNames,
gasPrices,
getTokenExchangeRate,
);

@ -1,18 +1,19 @@
import { import {
ChainMap, ChainMap,
ChainName,
GasOracleContractType, GasOracleContractType,
IgpConfig, IgpConfig,
multisigIsmVerificationCost, multisigIsmVerificationCost,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { Address, exclude, objMap } from '@hyperlane-xyz/utils'; import { Address, exclude, objMap } from '@hyperlane-xyz/utils';
import { TestChains, chainNames } from './chains.js'; import { testChainNames } from './chains.js';
import { multisigIsm } from './multisigIsm.js'; import { multisigIsm } from './multisigIsm.js';
import { owners } from './owners.js'; import { owners } from './owners.js';
function getGasOracles(local: TestChains) { function getGasOracles(local: ChainName) {
return Object.fromEntries( return Object.fromEntries(
exclude(local, chainNames).map((name) => [ exclude(local, testChainNames).map((name) => [
name, name,
GasOracleContractType.StorageGasOracle, GasOracleContractType.StorageGasOracle,
]), ]),
@ -21,7 +22,7 @@ function getGasOracles(local: TestChains) {
export const igp: ChainMap<IgpConfig> = objMap(owners, (chain, ownerConfig) => { export const igp: ChainMap<IgpConfig> = objMap(owners, (chain, ownerConfig) => {
const overhead = Object.fromEntries( const overhead = Object.fromEntries(
exclude(chain, chainNames).map((remote) => [ exclude(chain, testChainNames).map((remote) => [
remote, remote,
multisigIsmVerificationCost( multisigIsmVerificationCost(
multisigIsm[remote].threshold, multisigIsm[remote].threshold,

@ -1,11 +1,10 @@
import { JsonRpcProvider } from '@ethersproject/providers'; import { JsonRpcProvider } from '@ethersproject/providers';
import { MultiProvider } from '@hyperlane-xyz/sdk'; import { MultiProvider, testChainMetadata } from '@hyperlane-xyz/sdk';
import { EnvironmentConfig } from '../../../src/config/environment.js'; import { EnvironmentConfig } from '../../../src/config/environment.js';
import { agents } from './agent.js'; import { agents } from './agent.js';
import { testConfigs } from './chains.js';
import { core } from './core.js'; import { core } from './core.js';
import { igp } from './igp.js'; import { igp } from './igp.js';
import { infra } from './infra.js'; import { infra } from './infra.js';
@ -13,7 +12,7 @@ import { owners } from './owners.js';
export const environment: EnvironmentConfig = { export const environment: EnvironmentConfig = {
environment: 'test', environment: 'test',
chainMetadataConfigs: testConfigs, chainMetadataConfigs: testChainMetadata,
agents, agents,
core, core,
igp, igp,

@ -1,9 +1,9 @@
import { ChainMap, OwnableConfig } from '@hyperlane-xyz/sdk'; import { ChainMap, OwnableConfig } from '@hyperlane-xyz/sdk';
import { chainNames } from './chains.js'; import { testChainNames } from './chains.js';
// Owner is hardhat account 0 // Owner is hardhat account 0
const OWNER_ADDRESS = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'; const OWNER_ADDRESS = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';
export const owners: ChainMap<OwnableConfig> = Object.fromEntries( export const owners: ChainMap<OwnableConfig> = Object.fromEntries(
chainNames.map((chain) => [chain, { owner: OWNER_ADDRESS }]), testChainNames.map((chain) => [chain, { owner: OWNER_ADDRESS }]),
); );

@ -1,5 +1,4 @@
import { import {
Chains,
GasPaymentEnforcement, GasPaymentEnforcement,
GasPaymentEnforcementPolicyType, GasPaymentEnforcementPolicyType,
RpcConsensusType, RpcConsensusType,
@ -14,8 +13,9 @@ import { routerMatchingList } from '../../../src/config/agent/relayer.js';
import { ALL_KEY_ROLES, Role } from '../../../src/roles.js'; import { ALL_KEY_ROLES, Role } from '../../../src/roles.js';
import { Contexts } from '../../contexts.js'; import { Contexts } from '../../contexts.js';
import { environment, supportedChainNames } from './chains.js'; import { environment } from './chains.js';
import { helloWorld } from './helloworld.js'; import { helloWorld } from './helloworld.js';
import { supportedChainNames } from './supportedChainNames.js';
import { validatorChainConfig } from './validators.js'; import { validatorChainConfig } from './validators.js';
import plumetestnetSepoliaAddresses from './warp/plumetestnet-sepolia-addresses.json'; import plumetestnetSepoliaAddresses from './warp/plumetestnet-sepolia-addresses.json';
@ -32,36 +32,36 @@ const repo = 'gcr.io/abacus-labs-dev/hyperlane-agent';
// to allow for more fine-grained control over which chains are enabled for each agent role. // to allow for more fine-grained control over which chains are enabled for each agent role.
export const hyperlaneContextAgentChainConfig: AgentChainConfig = { export const hyperlaneContextAgentChainConfig: AgentChainConfig = {
[Role.Validator]: { [Role.Validator]: {
[Chains.alfajores]: true, alfajores: true,
[Chains.bsctestnet]: true, bsctestnet: true,
[Chains.eclipsetestnet]: true, eclipsetestnet: true,
[Chains.fuji]: true, fuji: true,
[Chains.plumetestnet]: true, plumetestnet: true,
[Chains.scrollsepolia]: true, scrollsepolia: true,
[Chains.sepolia]: true, sepolia: true,
[Chains.solanatestnet]: true, solanatestnet: true,
}, },
[Role.Relayer]: { [Role.Relayer]: {
[Chains.alfajores]: true, alfajores: true,
[Chains.bsctestnet]: true, bsctestnet: true,
[Chains.eclipsetestnet]: true, eclipsetestnet: true,
[Chains.fuji]: true, fuji: true,
[Chains.plumetestnet]: true, plumetestnet: true,
[Chains.scrollsepolia]: true, scrollsepolia: true,
[Chains.sepolia]: true, sepolia: true,
[Chains.solanatestnet]: true, solanatestnet: true,
}, },
[Role.Scraper]: { [Role.Scraper]: {
[Chains.alfajores]: true, alfajores: true,
[Chains.bsctestnet]: true, bsctestnet: true,
// Cannot scrape non-EVM chains // Cannot scrape non-EVM chains
[Chains.eclipsetestnet]: false, eclipsetestnet: false,
[Chains.fuji]: true, fuji: true,
[Chains.plumetestnet]: true, plumetestnet: true,
[Chains.scrollsepolia]: true, scrollsepolia: true,
[Chains.sepolia]: true, sepolia: true,
// Cannot scrape non-EVM chains // Cannot scrape non-EVM chains
[Chains.solanatestnet]: false, solanatestnet: false,
}, },
}; };

@ -1,33 +1,24 @@
import { import { ChainMap, ChainMetadata } from '@hyperlane-xyz/sdk';
ChainMap, import { objKeys } from '@hyperlane-xyz/utils';
ChainMetadata,
Chains,
chainMetadata,
} from '@hyperlane-xyz/sdk';
// All supported chains for the testnet4 environment. import { getChainMetadatas } from '../../../src/config/chain.js';
// These chains may be any protocol type. import { getChain } from '../../registry.js';
export const supportedChainNames = [
Chains.alfajores, import { supportedChainNames } from './supportedChainNames.js';
Chains.bsctestnet,
Chains.eclipsetestnet,
Chains.fuji,
Chains.plumetestnet,
Chains.scrollsepolia,
Chains.sepolia,
Chains.solanatestnet,
];
export const environment = 'testnet4'; export const environment = 'testnet4';
const { ethereumMetadatas: defaultEthereumMainnetConfigs } =
getChainMetadatas(supportedChainNames);
export const testnetConfigs: ChainMap<ChainMetadata> = { export const testnetConfigs: ChainMap<ChainMetadata> = {
...Object.fromEntries( ...defaultEthereumMainnetConfigs,
supportedChainNames.map((chain) => [chain, chainMetadata[chain]]),
),
bsctestnet: { bsctestnet: {
...chainMetadata.bsctestnet, ...getChain('bsctestnet'),
transactionOverrides: { transactionOverrides: {
gasPrice: 8 * 10 ** 9, // 8 gwei gasPrice: 8 * 10 ** 9, // 8 gwei
}, },
}, },
}; };
export const ethereumChainNames = objKeys(defaultEthereumMainnetConfigs);

@ -18,17 +18,20 @@ import {
RoutingIsmConfig, RoutingIsmConfig,
defaultMultisigConfigs, defaultMultisigConfigs,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { Address, objMap } from '@hyperlane-xyz/utils'; import { Address, ProtocolType, objMap } from '@hyperlane-xyz/utils';
import { getChain } from '../../registry.js';
import { supportedChainNames } from './chains.js';
import { igp } from './igp.js'; import { igp } from './igp.js';
import { owners } from './owners.js'; import { owners } from './owners.js';
import { supportedChainNames } from './supportedChainNames.js';
export const core: ChainMap<CoreConfig> = objMap( export const core: ChainMap<CoreConfig> = objMap(
owners, owners,
(local, ownerConfig) => { (local, ownerConfig) => {
const originMultisigs: ChainMap<MultisigConfig> = Object.fromEntries( const originMultisigs: ChainMap<MultisigConfig> = Object.fromEntries(
supportedChainNames supportedChainNames
.filter((chain) => getChain(chain).protocol === ProtocolType.Ethereum)
.filter((chain) => chain !== local) .filter((chain) => chain !== local)
.map((origin) => [origin, defaultMultisigConfigs[origin]]), .map((origin) => [origin, defaultMultisigConfigs[origin]]),
); );

@ -13,7 +13,7 @@ import {
getTokenExchangeRateFromValues, getTokenExchangeRateFromValues,
} from '../../../src/config/gas-oracle.js'; } from '../../../src/config/gas-oracle.js';
import { supportedChainNames } from './chains.js'; import { ethereumChainNames } from './chains.js';
// Taken by looking at each testnet and overestimating gas prices // Taken by looking at each testnet and overestimating gas prices
const gasPrices: ChainMap<BigNumber> = { const gasPrices: ChainMap<BigNumber> = {
@ -72,7 +72,7 @@ function getTokenExchangeRate(local: ChainName, remote: ChainName): BigNumber {
export const storageGasOracleConfig: AllStorageGasOracleConfigs = export const storageGasOracleConfig: AllStorageGasOracleConfigs =
getAllStorageGasOracleConfigs( getAllStorageGasOracleConfigs(
supportedChainNames, ethereumChainNames,
objMap(gasPrices, (_, gasPrice) => ({ objMap(gasPrices, (_, gasPrice) => ({
amount: gasPrice.toString(), amount: gasPrice.toString(),
decimals: 1, decimals: 1,

@ -6,9 +6,9 @@ import {
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { Address, exclude, objMap } from '@hyperlane-xyz/utils'; import { Address, exclude, objMap } from '@hyperlane-xyz/utils';
import { supportedChainNames } from './chains.js';
import { storageGasOracleConfig } from './gas-oracle.js'; import { storageGasOracleConfig } from './gas-oracle.js';
import { owners } from './owners.js'; import { owners } from './owners.js';
import { supportedChainNames } from './supportedChainNames.js';
export const igp: ChainMap<IgpConfig> = objMap(owners, (chain, ownerConfig) => { export const igp: ChainMap<IgpConfig> = objMap(owners, (chain, ownerConfig) => {
return { return {

@ -2,32 +2,31 @@ import {
BridgeAdapterConfig, BridgeAdapterConfig,
BridgeAdapterType, BridgeAdapterType,
ChainMap, ChainMap,
Chains,
chainMetadata,
getDomainId,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { getDomainId } from '../../registry.js';
const circleDomainMapping = [ const circleDomainMapping = [
{ hyperlaneDomain: getDomainId(chainMetadata[Chains.fuji]), circleDomain: 1 }, { hyperlaneDomain: getDomainId('fuji'), circleDomain: 1 },
]; ];
const wormholeDomainMapping = [ const wormholeDomainMapping = [
{ {
hyperlaneDomain: getDomainId(chainMetadata[Chains.fuji]), hyperlaneDomain: getDomainId('fuji'),
wormholeDomain: 6, wormholeDomain: 6,
}, },
{ {
hyperlaneDomain: getDomainId(chainMetadata[Chains.bsctestnet]), hyperlaneDomain: getDomainId('bsctestnet'),
wormholeDomain: 4, wormholeDomain: 4,
}, },
{ {
hyperlaneDomain: getDomainId(chainMetadata[Chains.alfajores]), hyperlaneDomain: getDomainId('alfajores'),
wormholeDomain: 14, wormholeDomain: 14,
}, },
]; ];
export const bridgeAdapterConfigs: ChainMap<BridgeAdapterConfig> = { export const bridgeAdapterConfigs: ChainMap<BridgeAdapterConfig> = {
[Chains.fuji]: { fuji: {
portal: { portal: {
type: BridgeAdapterType.Portal, type: BridgeAdapterType.Portal,
portalBridgeAddress: '0x61E44E506Ca5659E6c0bba9b678586fA2d729756', portalBridgeAddress: '0x61E44E506Ca5659E6c0bba9b678586fA2d729756',
@ -41,14 +40,14 @@ export const bridgeAdapterConfigs: ChainMap<BridgeAdapterConfig> = {
circleDomainMapping, circleDomainMapping,
}, },
}, },
[Chains.bsctestnet]: { bsctestnet: {
portal: { portal: {
type: BridgeAdapterType.Portal, type: BridgeAdapterType.Portal,
portalBridgeAddress: '0x9dcF9D205C9De35334D646BeE44b2D2859712A09', portalBridgeAddress: '0x9dcF9D205C9De35334D646BeE44b2D2859712A09',
wormholeDomainMapping, wormholeDomainMapping,
}, },
}, },
[Chains.alfajores]: { alfajores: {
portal: { portal: {
type: BridgeAdapterType.Portal, type: BridgeAdapterType.Portal,
portalBridgeAddress: '0x05ca6037eC51F8b712eD2E6Fa72219FEaE74E153', portalBridgeAddress: '0x05ca6037eC51F8b712eD2E6Fa72219FEaE74E153',

@ -1,13 +1,13 @@
import { ChainMap, OwnableConfig } from '@hyperlane-xyz/sdk'; import { ChainMap, OwnableConfig } from '@hyperlane-xyz/sdk';
import { supportedChainNames } from '../testnet4/chains.js'; import { ethereumChainNames } from './chains.js';
const ETHEREUM_DEPLOYER_ADDRESS = '0xfaD1C94469700833717Fa8a3017278BC1cA8031C'; const ETHEREUM_DEPLOYER_ADDRESS = '0xfaD1C94469700833717Fa8a3017278BC1cA8031C';
// const SEALEVEL_DEPLOYER_ADDRESS = '6DjHX6Ezjpq3zZMZ8KsqyoFYo1zPSDoiZmLLkxD4xKXS'; // const SEALEVEL_DEPLOYER_ADDRESS = '6DjHX6Ezjpq3zZMZ8KsqyoFYo1zPSDoiZmLLkxD4xKXS';
export const owners: ChainMap<OwnableConfig> = { export const owners: ChainMap<OwnableConfig> = {
...Object.fromEntries( ...Object.fromEntries(
supportedChainNames.map((chain) => [ ethereumChainNames.map((chain) => [
chain, chain,
{ owner: ETHEREUM_DEPLOYER_ADDRESS }, { owner: ETHEREUM_DEPLOYER_ADDRESS },
]), ]),

@ -0,0 +1,12 @@
// These chains may be any protocol type.
// Placing them here instead of adjacent chains file to avoid circular dep
export const supportedChainNames = [
'alfajores',
'bsctestnet',
'eclipsetestnet',
'fuji',
'plumetestnet',
'scrollsepolia',
'sepolia',
'solanatestnet',
];

@ -2,32 +2,31 @@ import {
BridgeAdapterConfig, BridgeAdapterConfig,
BridgeAdapterType, BridgeAdapterType,
ChainMap, ChainMap,
Chains,
chainMetadata,
getDomainId,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { getDomainId } from '../../registry.js';
const circleDomainMapping = [ const circleDomainMapping = [
{ hyperlaneDomain: getDomainId(chainMetadata[Chains.fuji]), circleDomain: 1 }, { hyperlaneDomain: getDomainId('fuji'), circleDomain: 1 },
]; ];
const wormholeDomainMapping = [ const wormholeDomainMapping = [
{ {
hyperlaneDomain: getDomainId(chainMetadata[Chains.fuji]), hyperlaneDomain: getDomainId('fuji'),
wormholeDomain: 6, wormholeDomain: 6,
}, },
{ {
hyperlaneDomain: getDomainId(chainMetadata[Chains.bsctestnet]), hyperlaneDomain: getDomainId('bsctestnet'),
wormholeDomain: 4, wormholeDomain: 4,
}, },
{ {
hyperlaneDomain: getDomainId(chainMetadata[Chains.alfajores]), hyperlaneDomain: getDomainId('alfajores'),
wormholeDomain: 14, wormholeDomain: 14,
}, },
]; ];
export const bridgeAdapterConfigs: ChainMap<BridgeAdapterConfig> = { export const bridgeAdapterConfigs: ChainMap<BridgeAdapterConfig> = {
[Chains.fuji]: { fuji: {
portal: { portal: {
type: BridgeAdapterType.Portal, type: BridgeAdapterType.Portal,
portalBridgeAddress: '0x61E44E506Ca5659E6c0bba9b678586fA2d729756', portalBridgeAddress: '0x61E44E506Ca5659E6c0bba9b678586fA2d729756',
@ -41,14 +40,14 @@ export const bridgeAdapterConfigs: ChainMap<BridgeAdapterConfig> = {
circleDomainMapping, circleDomainMapping,
}, },
}, },
[Chains.bsctestnet]: { bsctestnet: {
portal: { portal: {
type: BridgeAdapterType.Portal, type: BridgeAdapterType.Portal,
portalBridgeAddress: '0x9dcF9D205C9De35334D646BeE44b2D2859712A09', portalBridgeAddress: '0x9dcF9D205C9De35334D646BeE44b2D2859712A09',
wormholeDomainMapping, wormholeDomainMapping,
}, },
}, },
[Chains.alfajores]: { alfajores: {
portal: { portal: {
type: BridgeAdapterType.Portal, type: BridgeAdapterType.Portal,
portalBridgeAddress: '0x05ca6037eC51F8b712eD2E6Fa72219FEaE74E153', portalBridgeAddress: '0x05ca6037eC51F8b712eD2E6Fa72219FEaE74E153',

@ -1,7 +1,6 @@
import { chainMetadata, getReorgPeriod } from '@hyperlane-xyz/sdk';
import { ValidatorBaseChainConfigMap } from '../../../src/config/agent/validator.js'; import { ValidatorBaseChainConfigMap } from '../../../src/config/agent/validator.js';
import { Contexts } from '../../contexts.js'; import { Contexts } from '../../contexts.js';
import { getReorgPeriod } from '../../registry.js';
import { validatorBaseConfigsFn } from '../utils.js'; import { validatorBaseConfigsFn } from '../utils.js';
import { environment } from './chains.js'; import { environment } from './chains.js';
@ -13,7 +12,7 @@ export const validatorChainConfig = (
return { return {
alfajores: { alfajores: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.alfajores), reorgPeriod: getReorgPeriod('alfajores'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -33,7 +32,7 @@ export const validatorChainConfig = (
}, },
fuji: { fuji: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.alfajores), reorgPeriod: getReorgPeriod('alfajores'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -53,7 +52,7 @@ export const validatorChainConfig = (
}, },
bsctestnet: { bsctestnet: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.bsctestnet), reorgPeriod: getReorgPeriod('bsctestnet'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -73,7 +72,7 @@ export const validatorChainConfig = (
}, },
scrollsepolia: { scrollsepolia: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.scrollsepolia), reorgPeriod: getReorgPeriod('scrollsepolia'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -93,7 +92,7 @@ export const validatorChainConfig = (
}, },
sepolia: { sepolia: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.sepolia), reorgPeriod: getReorgPeriod('sepolia'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -113,7 +112,7 @@ export const validatorChainConfig = (
}, },
plumetestnet: { plumetestnet: {
interval: 5, interval: 5,
reorgPeriod: getReorgPeriod(chainMetadata.plumetestnet), reorgPeriod: getReorgPeriod('plumetestnet'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: [ [Contexts.Hyperlane]: [
@ -133,7 +132,7 @@ export const validatorChainConfig = (
}, },
solanatestnet: { solanatestnet: {
interval: 1, interval: 1,
reorgPeriod: getReorgPeriod(chainMetadata.solanatestnet), reorgPeriod: getReorgPeriod('solanatestnet'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: ['0xd4ce8fa138d4e083fc0e480cca0dbfa4f5f30bd5'], [Contexts.Hyperlane]: ['0xd4ce8fa138d4e083fc0e480cca0dbfa4f5f30bd5'],
@ -145,7 +144,7 @@ export const validatorChainConfig = (
}, },
eclipsetestnet: { eclipsetestnet: {
interval: 1, interval: 1,
reorgPeriod: getReorgPeriod(chainMetadata.eclipsetestnet), reorgPeriod: getReorgPeriod('eclipsetestnet'),
validators: validatorsConfig( validators: validatorsConfig(
{ {
[Contexts.Hyperlane]: ['0xf344f34abca9a444545b5295066348a0ae22dda3'], [Contexts.Hyperlane]: ['0xf344f34abca9a444545b5295066348a0ae22dda3'],

@ -1,4 +1,4 @@
import { CoreChainName } from '@hyperlane-xyz/sdk'; import { ChainName } from '@hyperlane-xyz/sdk';
import { import {
CheckpointSyncerType, CheckpointSyncerType,
@ -16,7 +16,7 @@ export const s3BucketRegion = 'us-east-1';
export const s3BucketName = ( export const s3BucketName = (
context: Contexts, context: Contexts,
environment: string, environment: string,
chainName: CoreChainName, chainName: ChainName,
index: number, index: number,
) => `${context}-${environment}-${chainName}-validator-${index}`; ) => `${context}-${environment}-${chainName}-validator-${index}`;
@ -35,7 +35,7 @@ export const validatorBaseConfigsFn = (
context: Contexts, context: Contexts,
): (( ): ((
addresses: Record<Contexts, string[]>, addresses: Record<Contexts, string[]>,
chain: CoreChainName, chain: ChainName,
) => ValidatorBaseConfig[]) => { ) => ValidatorBaseConfig[]) => {
return (addresses, chain) => { return (addresses, chain) => {
return addresses[context].map((address, index) => { return addresses[context].map((address, index) => {

@ -9,16 +9,8 @@ import {
import { DeployEnvironment } from '../src/config/environment.js'; import { DeployEnvironment } from '../src/config/environment.js';
import { Contexts } from './contexts.js'; import { Contexts } from './contexts.js';
import { supportedChainNames as mainnet3Chains } from './environments/mainnet3/chains.js';
import { chainNames as testChains } from './environments/test/chains.js';
import { supportedChainNames as testnet4Chains } from './environments/testnet4/chains.js';
import { rcMultisigIsmConfigs } from './rcMultisigIsmConfigs.js'; import { rcMultisigIsmConfigs } from './rcMultisigIsmConfigs.js';
import { getEnvChains } from './registry.js';
const chains = {
mainnet3: mainnet3Chains,
testnet4: testnet4Chains,
test: testChains,
};
export const multisigIsms = ( export const multisigIsms = (
env: DeployEnvironment, env: DeployEnvironment,
@ -30,7 +22,12 @@ export const multisigIsms = (
context === Contexts.ReleaseCandidate context === Contexts.ReleaseCandidate
? rcMultisigIsmConfigs ? rcMultisigIsmConfigs
: defaultMultisigConfigs; : defaultMultisigConfigs;
return buildMultisigIsmConfigs(type, local, chains[env], multisigConfigs); return buildMultisigIsmConfigs(
type,
local,
getEnvChains(env),
multisigConfigs,
);
}; };
export const multisigIsm = ( export const multisigIsm = (

@ -0,0 +1,109 @@
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
import { ChainAddresses } from '@hyperlane-xyz/registry';
import { LocalRegistry } from '@hyperlane-xyz/registry/local';
import {
ChainMap,
ChainMetadata,
ChainName,
getDomainId as resolveDomainId,
getReorgPeriod as resolveReorgPeriod,
} from '@hyperlane-xyz/sdk';
import { objFilter, rootLogger } from '@hyperlane-xyz/utils';
import type { DeployEnvironment } from '../src/config/environment.js';
import { supportedChainNames as mainnet3Chains } from './environments/mainnet3/supportedChainNames.js';
import {
testChainMetadata,
testChainNames as testChains,
} from './environments/test/chains.js';
import { supportedChainNames as testnet4Chains } from './environments/testnet4/supportedChainNames.js';
const DEFAULT_REGISTRY_URI = join(
dirname(fileURLToPath(import.meta.url)),
'../../../../',
'hyperlane-registry',
);
// A global Registry singleton
// All uses of chain metadata or chain address artifacts should go through this registry.
let registry: LocalRegistry;
export function setRegistry(reg: LocalRegistry) {
registry = reg;
}
export function getRegistry(): LocalRegistry {
if (!registry) {
const registryUri = process.env.REGISTRY_URI || DEFAULT_REGISTRY_URI;
rootLogger.info('Using registry URI:', registryUri);
registry = new LocalRegistry({
uri: registryUri,
logger: rootLogger.child({ module: 'infra-registry' }),
});
}
return registry;
}
export function getChains(): ChainName[] {
return getRegistry().getChains();
}
export function getChain(chainName: ChainName): ChainMetadata {
if (testChains.includes(chainName)) {
return testChainMetadata[chainName];
}
return getRegistry().getChainMetadata(chainName);
}
export function getDomainId(chainName: ChainName): number {
return resolveDomainId(getChain(chainName));
}
export function getReorgPeriod(chainName: ChainName): number {
return resolveReorgPeriod(getChain(chainName));
}
export function getChainMetadata(): ChainMap<ChainMetadata> {
return getRegistry().getMetadata();
}
export function getChainAddresses(): ChainMap<ChainAddresses> {
return getRegistry().getAddresses();
}
export function getEnvChains(env: DeployEnvironment): ChainName[] {
if (env === 'mainnet3') return mainnet3Chains;
if (env === 'testnet4') return testnet4Chains;
if (env === 'test') return testChains;
throw Error(`Unsupported deploy environment: ${env}`);
}
export function getMainnets(): ChainName[] {
return getEnvChains('mainnet3');
}
export function getTestnets(): ChainName[] {
return getEnvChains('testnet4');
}
export function getEnvAddresses(
env: DeployEnvironment,
): ChainMap<ChainAddresses> {
const envChains = getEnvChains(env);
return objFilter(
getChainAddresses(),
(chain, addresses): addresses is ChainAddresses =>
getEnvChains(env).includes(chain),
);
}
export function getMainnetAddresses(): ChainMap<ChainAddresses> {
return getEnvAddresses('mainnet3');
}
export function getTestnetAddresses(): ChainMap<ChainAddresses> {
return getEnvAddresses('testnet4');
}

@ -6,22 +6,14 @@ import {
IsmType, IsmType,
ModuleType, ModuleType,
RoutingIsmConfig, RoutingIsmConfig,
TestChains,
} from '@hyperlane-xyz/sdk'; } from '@hyperlane-xyz/sdk';
import { DeployEnvironment } from '../src/config/environment.js'; import { DeployEnvironment } from '../src/config/environment.js';
import { Contexts } from './contexts.js'; import { Contexts } from './contexts.js';
import { environments } from './environments/index.js'; import { environments } from './environments/index.js';
import { ethereumChainNames as mainnet3Chains } from './environments/mainnet3/chains.js';
import { supportedChainNames as testnet4Chains } from './environments/testnet4/chains.js';
import { multisigIsm } from './multisigIsm.js'; import { multisigIsm } from './multisigIsm.js';
import { getEnvChains } from './registry.js';
const chains = {
test: TestChains,
testnet4: testnet4Chains,
mainnet3: mainnet3Chains,
};
// Intended to be the "entrypoint" ISM. // Intended to be the "entrypoint" ISM.
// Routing ISM => Aggregation (1/2) // Routing ISM => Aggregation (1/2)
@ -34,7 +26,9 @@ export const routingIsm = (
local: ChainName, local: ChainName,
context: Contexts, context: Contexts,
): RoutingIsmConfig | string => { ): RoutingIsmConfig | string => {
const aggregationIsms: ChainMap<AggregationIsmConfig> = chains[environment] const aggregationIsms: ChainMap<AggregationIsmConfig> = getEnvChains(
environment,
)
.filter((chain) => chain !== local) .filter((chain) => chain !== local)
.reduce( .reduce(
(acc, chain) => ({ (acc, chain) => ({

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

Loading…
Cancel
Save