diff --git a/.changeset/khaki-days-float.md b/.changeset/khaki-days-float.md index 0c98ce9582..137910ce8a 100644 --- a/.changeset/khaki-days-float.md +++ b/.changeset/khaki-days-float.md @@ -1,5 +1,5 @@ --- -"@hyperlane-xyz/sdk": patch +'@hyperlane-xyz/sdk': patch --- Allow gasLimit overrides in the SDK/CLI for deploy txs diff --git a/.changeset/moody-colts-dress.md b/.changeset/moody-colts-dress.md new file mode 100644 index 0000000000..0be0e6a985 --- /dev/null +++ b/.changeset/moody-colts-dress.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': minor +--- + +Remove consts such as chainMetadata from SDK diff --git a/.changeset/nine-masks-guess.md b/.changeset/nine-masks-guess.md new file mode 100644 index 0000000000..3e34519c7b --- /dev/null +++ b/.changeset/nine-masks-guess.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Restructure CLI params around registries diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml deleted file mode 100644 index 94192960cf..0000000000 --- a/.github/workflows/cron.yml +++ /dev/null @@ -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 }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 84c0164c0a..6fc7eba09a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: node-version: 18.x - name: Install Dependencies - run: yarn + run: yarn install --immutable - name: Create Release PR or Publish to NPM id: changesets diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fbda5c9de2..5afe02b48b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,6 +18,7 @@ env: LOG_FORMAT: PRETTY CARGO_TERM_COLOR: always RUST_BACKTRACE: full + REGISTRY_URI: ../../node_modules/@hyperlane-xyz/registry/dist jobs: yarn-install: diff --git a/rust/config/mainnet_config.json b/rust/config/mainnet_config.json index 99a3eef8c7..303daf485c 100644 --- a/rust/config/mainnet_config.json +++ b/rust/config/mainnet_config.json @@ -77,6 +77,8 @@ "index": { "from": 143649797 }, + "interchainAccountIsm": "0xfa8bfcE55B3A0631dF38257615cEF7FCD3523A48", + "interchainAccountRouter": "0xCD0CFFf6eFD943b4b81f2c15847730dbcD30e3aE", "interchainGasPaymaster": "0x3b6044acd6767f017e99318AA6Ef93b7B06A5a22", "interchainSecurityModule": "0xD0DBBF922076352cC50B285A0023536561F00EEa", "mailbox": "0x979Ca5202784112f4738403dBec5D0F3B9daabB9", @@ -104,6 +106,7 @@ "technicalStack": "arbitrumnitro", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x1df063280C4166AF9a725e3828b4dAC6c7113B08" }, "avalanche": { @@ -131,6 +134,8 @@ "index": { "from": 36874693 }, + "interchainAccountIsm": "0x786c26C1857032617c215f265509d6E44e44Bfe3", + "interchainAccountRouter": "0xA967A6CE0e73fAf672843DECaA372511996E8852", "interchainGasPaymaster": "0x95519ba800BBd0d34eeAE026fEc620AD978176C0", "interchainSecurityModule": "0xA36B02a83564f52d9244310Ea439ee6F6AfeFb60", "mailbox": "0xFf06aFcaABaDDd1fb08371f9ccA15D73D51FeBD6", @@ -161,6 +166,7 @@ "storageGasOracle": "0x175821F30AdCAA4bbB72Ce98eF76C2E0De2C3f21", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x9Cad0eC82328CEE2386Ec14a12E81d070a27712f" }, "base": { @@ -188,6 +194,8 @@ "index": { "from": 5695475 }, + "interchainAccountIsm": "0x861908E6c8F992537F557da5Fb5876836036b347", + "interchainAccountRouter": "0xa85F9e4fdA2FFF1c07f2726a630443af3faDF830", "interchainGasPaymaster": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", "interchainSecurityModule": "0x5D1e7D7c5B9e6dDC8439F67F10c578f2A1084f6F", "mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", @@ -218,16 +226,11 @@ "staticMerkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "staticMessageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "storageGasOracle": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", + "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x182E8d7c5F1B06201b102123FC7dF0EaeB445a7B" }, "blast": { "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", "family": "routescan", @@ -303,6 +306,8 @@ "index": { "from": 32893043 }, + "interchainAccountIsm": "0xB274Bbbc1df5f1d1763216A93d473fde6f5de043", + "interchainAccountRouter": "0x4BBd67dC995572b40Dc6B3eB6CdE5185a5373868", "interchainGasPaymaster": "0x78E25e7f84416e69b9339B0A6336EB6EFfF6b451", "interchainSecurityModule": "0xab3df354baBee6c2B88E2CeD3b2e030e31aA5e61", "mailbox": "0x2971b9Aec44bE4eb673DF1B88cDB57b96eefe8a4", @@ -335,6 +340,7 @@ "storageGasOracle": "0x91d23D603d60445411C06e6443d81395593B7940", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "timelockController": "0x0000000000000000000000000000000000000000", "transactionOverrides": { "gasPrice": 3000000000 }, @@ -371,6 +377,8 @@ "index": { "from": 22102340 }, + "interchainAccountIsm": "0x30a8DEc5318e2aAa9ad5b069fC606c4CfF6f5676", + "interchainAccountRouter": "0x4ED23E3885e1651E62564F78817D91865beba575", "interchainGasPaymaster": "0x571f1435613381208477ac5d6974310d88AC7cB7", "interchainSecurityModule": "0x99e8E56Dce3402D6E09A82718937fc1cA2A9491E", "mailbox": "0x50da3B3907A08a24fe4999F4Dcf337E8dC7954bb", @@ -399,6 +407,7 @@ "storageGasOracle": "0xD9A9966E7dA9a7f0032bF449FB12696a638E673C", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0xCeF677b65FDaA6804d4403083bb12B8dB3991FE1" }, "ethereum": { @@ -431,6 +440,8 @@ "index": { "from": 18422581 }, + "interchainAccountIsm": "0x609707355a53d2aAb6366f48E2b607C599D26B29", + "interchainAccountRouter": "0x8dBae9B1616c46A20591fE0006Bf015E28ca5cC9", "interchainGasPaymaster": "0x9e6B1022bE9BBF5aFd152483DAD9b88911bC8611", "interchainSecurityModule": "0xB42b88243F749F47697F01Ae1cbBCA9d4763902a", "mailbox": "0xc005dc82818d67AF737725bD4bf75435d065D239", @@ -460,6 +471,7 @@ "storageGasOracle": "0xc9a103990A8dB11b4f627bc5CD1D0c2685484Ec5", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "timelockController": "0x0000000000000000000000000000000000000000", "transactionOverrides": { "maxFeePerGas": 150000000000, "maxPriorityFeePerGas": 5000000000 @@ -491,6 +503,8 @@ "index": { "from": 30620793 }, + "interchainAccountIsm": "0x5a56dff3D92D635372718f86e6dF09C1129CFf53", + "interchainAccountRouter": "0x5E59EBAedeB691408EBAcF6C37218fa2cFcaC9f2", "interchainGasPaymaster": "0xDd260B99d302f0A3fF885728c086f729c06f227f", "interchainSecurityModule": "0x8e1aa0687B6d939D5a44304D13B7c922ebB012f1", "mailbox": "0xaD09d78f4c6b9dA2Ae82b1D34107802d380Bb74f", @@ -521,6 +535,7 @@ "storageGasOracle": "0x5E01d8F34b629E3f92d69546bbc4142A7Adee7e9", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x87ED6926abc9E38b9C7C19f835B41943b622663c" }, "inevm": { @@ -548,6 +563,8 @@ "index": { "from": 18972465 }, + "interchainAccountIsm": "0x31894E7a734540B343d67E491148EB4FC9f7A45B", + "interchainAccountRouter": "0x4E55aDA3ef1942049EA43E904EB01F4A0a9c39bd", "interchainGasPaymaster": "0x19dc38aeae620380430C200a6E990D5Af5480117", "interchainSecurityModule": "0x3052aD50De54aAAc5D364d80bBE681d29e924597", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -574,6 +591,7 @@ "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "storageGasOracle": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", + "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x15ab173bDB6832f9b64276bA128659b0eD77730B" }, "injective": { @@ -636,6 +654,8 @@ "index": { "from": 437300 }, + "interchainAccountIsm": "0xA34ceDf9068C5deE726C67A4e1DCfCc2D6E2A7fD", + "interchainAccountRouter": "0x0f6fF770Eda6Ba1433C39cCf47d4059b254224Aa", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", "interchainSecurityModule": "0xEda7cCD2A8CF717dc997D0002e363e4D10bF5c0d", "isTestnet": false, @@ -663,6 +683,7 @@ "storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117", "testRecipient": "0x4E1c88DD261BEe2941e6c1814597e30F53330428", "testTokenRecipient": "0x5060eCD5dFAD300A90592C04e504600A7cdcF70b", + "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9" }, "mode": { @@ -740,6 +761,8 @@ "index": { "from": 4719713 }, + "interchainAccountIsm": "0x799eA6f430f5CA901b59335fFC2fA10531106009", + "interchainAccountRouter": "0x6b142f596FFc761ac3fFaaC1ecaDe54f4EE09977", "interchainGasPaymaster": "0x14760E32C0746094cF14D97124865BC7F0F7368F", "interchainSecurityModule": "0x373836DFa82f2D27ec79Ca32A197Aa1665F0E1e9", "mailbox": "0x094d03E751f49908080EFf000Dd6FD177fd44CC3", @@ -766,6 +789,7 @@ "storageGasOracle": "0x448b7ADB0dA36d41AA2AfDc9d63b97541A7b3819", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "timelockController": "0x0000000000000000000000000000000000000000", "transactionOverrides": { "maxFeePerGas": 350000000000, "maxPriorityFeePerGas": 50000000000 @@ -836,6 +860,8 @@ "index": { "from": 111290758 }, + "interchainAccountIsm": "0x0389faCac114023C123E22F3E54394944cAbcb48", + "interchainAccountRouter": "0x33Ef006E7083BB38E0AFe3C3979F4e9b84415bf1", "interchainGasPaymaster": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", "interchainSecurityModule": "0x04938856bE60c8e734ffDe5f720E2238302BE8D2", "mailbox": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D", @@ -862,6 +888,7 @@ "storageGasOracle": "0x27e88AeB8EA4B159d81df06355Ea3d20bEB1de38", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x30f5b08e01808643221528BB2f7953bf2830Ef38" }, "polygon": { @@ -889,6 +916,8 @@ "index": { "from": 49108065 }, + "interchainAccountIsm": "0x90384bC552e3C48af51Ef7D9473A9bF87431f5c7", + "interchainAccountRouter": "0x5e80f3474825B61183c0F0f0726796F589082420", "interchainGasPaymaster": "0x0071740Bf129b05C4684abfbBeD248D80971cce2", "interchainSecurityModule": "0x9a795fB62f86146ec06e2377e3C95Af65c7C20eB", "mailbox": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB", @@ -921,6 +950,7 @@ "storageGasOracle": "0xA3a24EC5670F1F416AB9fD554FcE2f226AE9D7eB", "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", + "timelockController": "0x0000000000000000000000000000000000000000", "transactionOverrides": { "maxFeePerGas": 800000000000, "maxPriorityFeePerGas": 50000000000 @@ -952,6 +982,8 @@ "index": { "from": 6577743 }, + "interchainAccountIsm": "0xC49aF4965264FA7BB6424CE37aA06773ad177224", + "interchainAccountRouter": "0xF15D70941dE2Bf95A23d6488eBCbedE0a444137f", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", "interchainSecurityModule": "0xf2BEE9D2c15Ba9D7e06799B5912dE1F05533c141", "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", @@ -979,6 +1011,7 @@ "staticMerkleRootMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", "staticMessageIdMultisigIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", "storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117", + "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9" }, "scroll": { @@ -1005,6 +1038,8 @@ "index": { "from": 271840 }, + "interchainAccountIsm": "0xb89c6ED617f5F46175E41551350725A09110bbCE", + "interchainAccountRouter": "0x9629c28990F11c31735765A6FD59E1E1bC197DbD", "interchainGasPaymaster": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", "interchainSecurityModule": "0xaDc0cB48E8DB81855A930C0C1165ea3dCe4Ba5C7", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -1029,6 +1064,7 @@ "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", "storageGasOracle": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", + "timelockController": "0x0000000000000000000000000000000000000000", "transactionOverrides": { "gasPrice": 2000000000 }, @@ -1057,6 +1093,8 @@ "chunk": 1000, "from": 73573878 }, + "interchainAccountIsm": "0xD1E267d2d7876e97E217BfE61c34AB50FEF52807", + "interchainAccountRouter": "0x1956848601549de5aa0c887892061fA5aB4f6fC4", "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", "interchainSecurityModule": "0xBD70Ea9D599a0FC8158B026797177773C3445730", "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", @@ -1085,6 +1123,7 @@ "storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117", "testRecipient": "0x17E216fBb22dF4ef8A6640ae9Cb147C92710ac84", "testTokenRecipient": "0xe042D1fbDf59828dd16b9649Ede7abFc856F7a6c", + "timelockController": "0x0000000000000000000000000000000000000000", "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9" } }, diff --git a/rust/config/testnet_config.json b/rust/config/testnet_config.json index e79181250d..e1bf849c27 100644 --- a/rust/config/testnet_config.json +++ b/rust/config/testnet_config.json @@ -227,7 +227,7 @@ "domainRoutingIsmFactory": "0x54148470292C24345fb828B003461a9444414517", "fallbackRoutingHook": "0x19Be55D859368e02d7b9C00803Eb677BDC1359Bd", "index": { - "from": 4206 + "from": 5284139 }, "interchainAccountIsm": "0x7c115c16E34c74afdb88bd268EaB19bC705891FE", "interchainAccountRouter": "0xB6F8aA9B1b314A6E6DFB465DD3e0E95936347517", @@ -382,10 +382,10 @@ "solanatestnet": { "blockExplorers": [ { - "apiUrl": "https://explorer.solana.com", + "apiUrl": "https://explorer.solana.com?cluster=testnet", "family": "other", "name": "Solana Explorer", - "url": "https://explorer.solana.com" + "url": "https://explorer.solana.com?cluster=testnet" } ], "blocks": { diff --git a/solidity/exportBuildArtifact.sh b/solidity/exportBuildArtifact.sh index 43a7848c35..e6d5d503b8 100755 --- a/solidity/exportBuildArtifact.sh +++ b/solidity/exportBuildArtifact.sh @@ -6,7 +6,9 @@ cd "$(dirname "$0")" # Define the artifacts directory artifactsDir="./artifacts/build-info" # 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 echo 'Finding and processing hardhat build artifact...' @@ -26,7 +28,10 @@ if [ ! -f "$jsonFiles" ]; then fi # 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.' else echo 'Failed to process build artifact with jq' diff --git a/solidity/package.json b/solidity/package.json index 976c6c625f..4c51e20da2 100644 --- a/solidity/package.json +++ b/solidity/package.json @@ -37,12 +37,12 @@ "exports": { ".": "./dist/index.js", "./mailbox": "./dist/contracts/Mailbox.js", - "./buildArtifact.json": "./buildArtifact.json", + "./buildArtifact.js": "./dist/buildArtifact.js", + "./buildArtifact.json": "./dist/buildArtifact.json", "./contracts": "./contracts" }, "types": "./dist/index.d.ts", "files": [ - "/buildArtifact.json", "/dist", "/contracts" ], @@ -57,7 +57,7 @@ ], "license": "Apache-2.0", "scripts": { - "build": "yarn hardhat-esm compile && ./exportBuildArtifact.sh && tsc", + "build": "yarn hardhat-esm compile && tsc && ./exportBuildArtifact.sh", "lint": "solhint contracts/**/*.sol", "clean": "yarn hardhat-esm clean && rm -rf ./dist ./cache ./types ./coverage ./out ./forge-cache", "coverage": "./coverage.sh", diff --git a/typescript/cli/.gitignore b/typescript/cli/.gitignore index 8ee1783630..e417d75c24 100644 --- a/typescript/cli/.gitignore +++ b/typescript/cli/.gitignore @@ -1,5 +1,13 @@ .env* /dist /cache + +# Deployment artifacts and local registry configs /configs -/artifacts \ No newline at end of file +/artifacts +/chains +/deployments + +# Test artifacts +/test-configs/**/addresses.yaml +/test-configs/*/deployments \ No newline at end of file diff --git a/typescript/cli/ci-test.sh b/typescript/cli/ci-test.sh index 27a31f23aa..3b4ccecf5e 100755 --- a/typescript/cli/ci-test.sh +++ b/typescript/cli/ci-test.sh @@ -15,14 +15,11 @@ _main() { # with the routing over igp hook (which is closer to production deployment) TEST_TYPE=$1 if [ -z "$TEST_TYPE" ]; then - echo "Usage: ci-test.sh " + echo "Usage: ci-test.sh <$TEST_TYPE_PRESET_HOOK | $TEST_TYPE_CONFIGURED_HOOK | $TEST_TYPE_PI_CORE>" exit 1 fi - HOOK_FLAG=false - if [ "$TEST_TYPE" == $TEST_TYPE_CONFIGURED_HOOK ]; then - HOOK_FLAG=true - fi + prepare_environment_vars; prepare_anvil; @@ -48,23 +45,36 @@ _main() { echo "Done"; } -prepare_anvil() { +prepare_environment_vars() { ANVIL_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 CHAIN1=anvil1 CHAIN2=anvil2 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 # use different chain names and config for pi<>core test if [ "$TEST_TYPE" == $TEST_TYPE_PI_CORE ]; then - CHAIN1=anvil 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 CHAIN1_CAPS=$(echo "${CHAIN1}" | 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 CHAIN2_PORT=8555 @@ -73,6 +83,8 @@ prepare_anvil() { rm -rf /tmp/${CHAIN1}* rm -rf /tmp/${CHAIN2}* 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 # kill child processes on exit, but only locally because @@ -95,7 +107,7 @@ prepare_anvil() { if [ "$TEST_TYPE" == $TEST_TYPE_PI_CORE ]; then # Fetch the RPC of chain to fork 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 ../../ # run the fork chain @@ -112,8 +124,6 @@ prepare_anvil() { fi set -e - - echo "{}" > /tmp/empty-artifacts.json } reset_anvil() { @@ -129,31 +139,20 @@ run_hyperlane_deploy_core_dry_run() { return; 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" yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ --dry-run alfajores \ --targets alfajores \ - --chains ${EXAMPLES_PATH}/dry-run/anvil-chains.yaml \ - --artifacts /tmp/empty-artifacts.json \ + --registry ${TEST_CONFIGS_PATH}/dry-run \ + --overrides " " \ $(if [ "$HOOK_FLAG" == "true" ]; then echo "--hook ${EXAMPLES_PATH}/hooks.yaml"; fi) \ - --ism ${EXAMPLES_PATH}/ism.yaml \ - --out /tmp \ + --ism ${TEST_CONFIGS_PATH}/dry-run/ism.yaml \ --key 0xfaD1C94469700833717Fa8a3017278BC1cA8031C \ --yes - AFTER_CORE_DRY_RUN=$(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}) - 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` + check_deployer_balance; } run_hyperlane_deploy_warp_dry_run() { @@ -161,63 +160,44 @@ run_hyperlane_deploy_warp_dry_run() { return; 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" yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \ --dry-run alfajores \ - --chains ${EXAMPLES_PATH}/dry-run/anvil-chains.yaml \ - --core $CORE_ARTIFACTS_PATH \ - --config ${EXAMPLES_PATH}/dry-run/warp-route-deployment.yaml \ - --out /tmp \ + --overrides ${TEST_CONFIGS_PATH}/dry-run \ + --config ${TEST_CONFIGS_PATH}/dry-run/warp-route-deployment.yaml \ --key 0xfaD1C94469700833717Fa8a3017278BC1cA8031C \ --yes - AFTER_WARP_DRY_RUN=$(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}) - 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 + check_deployer_balance; } 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}" yarn workspace @hyperlane-xyz/cli run hyperlane deploy core \ + --registry $REGISTRY_PATH \ + --overrides " " \ --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) \ - --ism ${EXAMPLES_PATH}/ism.yaml \ - --out /tmp \ + --ism $CORE_ISM_PATH \ + --agent /tmp/agent-config.json \ --key $ANVIL_KEY \ --yes - AFTER_CORE=$(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}) - 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` + check_deployer_balance; } run_hyperlane_deploy_warp() { + update_deployer_balance; + echo -e "\nDeploying hypNative warp route" yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \ - --chains ${EXAMPLES_PATH}/anvil-chains.yaml \ - --core $CORE_ARTIFACTS_PATH \ - --config ${EXAMPLES_PATH}/warp-route-deployment.yaml \ - --out /tmp \ + --registry $REGISTRY_PATH \ + --overrides " " \ + --config $WARP_DEPLOY_CONFIG_PATH \ --key $ANVIL_KEY \ --yes @@ -228,51 +208,43 @@ run_hyperlane_deploy_warp() { echo "Deploying hypCollateral warp route" yarn workspace @hyperlane-xyz/cli run hyperlane deploy warp \ - --chains ${EXAMPLES_PATH}/anvil-chains.yaml \ - --core $CORE_ARTIFACTS_PATH \ + --registry $REGISTRY_PATH \ + --overrides " " \ --config /tmp/warp-collateral-deployment.json \ - --out /tmp \ --key $ANVIL_KEY \ --yes - AFTER_WARP=$(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}) - WARP_MIN_GAS=$(bc <<< "($AFTER_CORE - $AFTER_WARP) / $GAS_PRICE") - echo "Gas used: $WARP_MIN_GAS" + check_deployer_balance; } run_hyperlane_send_message() { + update_deployer_balance; + echo -e "\nSending test message" yarn workspace @hyperlane-xyz/cli run hyperlane send message \ + --registry $REGISTRY_PATH \ + --overrides " " \ --origin ${CHAIN1} \ --destination ${CHAIN2} \ - --messageBody "Howdy!" \ - --chains ${EXAMPLES_PATH}/anvil-chains.yaml \ - --core $CORE_ARTIFACTS_PATH \ + --body "Howdy!" \ --quick \ --key $ANVIL_KEY \ | tee /tmp/message1 - AFTER_MSG=$(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}) - MSG_MIN_GAS=$(bc <<< "($AFTER_WARP - $AFTER_MSG) / $GAS_PRICE") - echo "Gas used: $MSG_MIN_GAS" + check_deployer_balance; MESSAGE1_ID=`cat /tmp/message1 | grep "Message ID" | grep -E -o '0x[0-9a-f]+'` echo "Message 1 ID: $MESSAGE1_ID" - WARP_CONFIG_FILE=`find /tmp/warp-config* -type f -exec ls -t1 {} + | head -1` - CHAIN1_ROUTER="${CHAIN1_CAPS}_ROUTER" - declare $CHAIN1_ROUTER=$(jq -r --arg CHAIN1 "$CHAIN1" '.tokens[] | select(.chainName==$CHAIN1) | .addressOrDenom' $WARP_CONFIG_FILE) + WARP_CONFIG_FILE="$REGISTRY_PATH/deployments/warp_routes/FAKE/${CHAIN1}-${CHAIN2}-config.yaml" echo -e "\nSending test warp transfer" yarn workspace @hyperlane-xyz/cli run hyperlane send transfer \ + --registry $REGISTRY_PATH \ + --overrides " " \ --origin ${CHAIN1} \ --destination ${CHAIN2} \ - --chains ${EXAMPLES_PATH}/anvil-chains.yaml \ - --core $CORE_ARTIFACTS_PATH \ --warp ${WARP_CONFIG_FILE} \ - --router ${!CHAIN1_ROUTER} \ --quick \ --key $ANVIL_KEY \ | tee /tmp/message2 @@ -303,7 +275,7 @@ run_validator() { VALIDATOR_PORT=$((VALIDATOR_PORT+1)) 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_VALIDATOR_INTERVAL=1 export HYP_VALIDATOR_TYPE=hexKey @@ -340,7 +312,7 @@ run_relayer() { cargo build --bin 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_ALLOWLOCALCHECKPOINTSYNCERS=true export HYP_DB=/tmp/relayer @@ -367,8 +339,8 @@ run_hyperlane_status() { yarn workspace @hyperlane-xyz/cli run hyperlane status \ --id $2 \ --destination ${CHAIN2} \ - --chains ${EXAMPLES_PATH}/anvil-chains.yaml \ - --core $CORE_ARTIFACTS_PATH \ + --registry $REGISTRY_PATH \ + --overrides " " \ | tee /tmp/message-status-$1 if ! grep -q "$2 was delivered" /tmp/message-status-$1; then echo "ERROR: Message $1 was not delivered" @@ -379,6 +351,17 @@ run_hyperlane_status() { 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 "$@"; exit; diff --git a/typescript/cli/cli.ts b/typescript/cli/cli.ts index e974f81f71..0ced1ef0fd 100644 --- a/typescript/cli/cli.ts +++ b/typescript/cli/cli.ts @@ -11,11 +11,16 @@ import { deployCommand } from './src/commands/deploy.js'; import { hookCommand } from './src/commands/hook.js'; import { ismCommand } from './src/commands/ism.js'; import { + keyCommandOption, logFormatCommandOption, logLevelCommandOption, + overrideRegistryUriCommandOption, + registryUriCommandOption, + skipConfirmationOption, } from './src/commands/options.js'; import { sendCommand } from './src/commands/send.js'; import { statusCommand } from './src/commands/status.js'; +import { contextMiddleware } from './src/context/context.js'; import { configureLogger, errorRed } from './src/logger.js'; import { checkVersion } from './src/utils/version-check.js'; import { VERSION } from './src/version.js'; @@ -32,10 +37,17 @@ try { .scriptName('hyperlane') .option('log', logFormatCommandOption) .option('verbosity', logLevelCommandOption) - .global(['log', 'verbosity']) - .middleware((argv) => { - configureLogger(argv.log as LogFormat, argv.verbosity as LogLevel); - }) + .option('registry', registryUriCommandOption) + .option('overrides', overrideRegistryUriCommandOption) + .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(configCommand) .command(deployCommand) diff --git a/typescript/cli/examples/anvil-chains.yaml b/typescript/cli/examples/anvil-chains.yaml deleted file mode 100644 index be2f4a9f2a..0000000000 --- a/typescript/cli/examples/anvil-chains.yaml +++ /dev/null @@ -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 diff --git a/typescript/cli/examples/chain-config.yaml b/typescript/cli/examples/chain-config.yaml index 5a7cb32bef..f889be4819 100644 --- a/typescript/cli/examples/chain-config.yaml +++ b/typescript/cli/examples/chain-config.yaml @@ -1,50 +1,42 @@ # 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 --- -# You can define a full config for a new chain -mychainname: +# Required fields: +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: - 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: - - 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 + - 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 diff --git a/typescript/cli/examples/dry-run/anvil-chains.yaml b/typescript/cli/examples/dry-run/anvil-chains.yaml deleted file mode 100644 index f8169f4791..0000000000 --- a/typescript/cli/examples/dry-run/anvil-chains.yaml +++ /dev/null @@ -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 diff --git a/typescript/cli/examples/fork/anvil-chains.yaml b/typescript/cli/examples/fork/anvil-chains.yaml deleted file mode 100644 index 70b889af38..0000000000 --- a/typescript/cli/examples/fork/anvil-chains.yaml +++ /dev/null @@ -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 diff --git a/typescript/cli/package.json b/typescript/cli/package.json index 198347301e..13316ab08e 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -3,6 +3,7 @@ "version": "3.10.0", "description": "A command-line utility for common Hyperlane operations", "dependencies": { + "@hyperlane-xyz/registry": "^1.0.7", "@hyperlane-xyz/sdk": "3.10.0", "@hyperlane-xyz/utils": "3.10.0", "@inquirer/prompts": "^3.0.0", @@ -12,7 +13,7 @@ "latest-version": "^8.0.0", "terminal-link": "^3.0.0", "tsx": "^4.7.1", - "yaml": "^2.3.1", + "yaml": "^2.4.1", "yargs": "^17.7.2", "zod": "^3.21.2" }, diff --git a/typescript/cli/src/commands/chains.ts b/typescript/cli/src/commands/chains.ts index 938f6219e6..9d0e970fa2 100644 --- a/typescript/cli/src/commands/chains.ts +++ b/typescript/cli/src/commands/chains.ts @@ -1,22 +1,17 @@ import { CommandModule } from 'yargs'; -import { - Chains, - CoreChainName, - HyperlaneEnvironment, - chainMetadata, - hyperlaneContractAddresses, - hyperlaneEnvironments, -} from '@hyperlane-xyz/sdk'; - +import { CommandModuleWithContext } from '../context/types.js'; import { log, logBlue, logGray, logTable } from '../logger.js'; +const ChainTypes = ['mainnet', 'testnet']; +type ChainType = (typeof ChainTypes)[number]; + /** * Parent command */ export const chainsCommand: CommandModule = { command: 'chains', - describe: 'View information about core Hyperlane chains', + describe: 'View information about Hyperlane chains in a registry', builder: (yargs) => yargs .command(listCommand) @@ -29,39 +24,39 @@ export const chainsCommand: CommandModule = { /** * List command */ -const listCommand: CommandModule = { +const listCommand: CommandModuleWithContext<{ type: ChainType }> = { command: 'list', - describe: 'List all core chains included in the Hyperlane SDK', - builder: (yargs) => - yargs.option('environment', { - alias: 'e', - describe: 'Specify the environment to list chains for', - choices: ['mainnet', 'testnet'], - }), - handler: (args) => { - const environment = args.environment as HyperlaneEnvironment | undefined; - - const serializer = (env: HyperlaneEnvironment) => - Object.keys(hyperlaneEnvironments[env]).reduce((result, chain) => { - const { chainId, displayName } = chainMetadata[chain]; - result[chain] = { + describe: 'List all chains included in a registry', + builder: { + type: { + describe: 'Specify the type of chains', + choices: ChainTypes, + }, + }, + handler: async ({ type, context }) => { + const logChainsForType = (type: ChainType) => { + logBlue(`\nHyperlane ${type} chains:`); + logGray('------------------------------'); + const chains = Object.values(context.chainMetadata).filter((c) => { + if (type === 'mainnet') return !c.isTestnet; + else return !!c.isTestnet; + }); + const tableData = chains.reduce((result, chain) => { + const { chainId, displayName } = chain; + result[chain.name] = { 'Display Name': displayName, 'Chain Id': chainId, }; return result; }, {}); - - const logChainsForEnv = (env: HyperlaneEnvironment) => { - logBlue(`\nHyperlane core ${env} chains:`); - logGray('------------------------------'); - logTable(serializer(env)); + logTable(tableData); }; - if (environment) { - logChainsForEnv(environment); + if (type) { + logChainsForType(type); } else { - logChainsForEnv('mainnet'); - logChainsForEnv('testnet'); + logChainsForType('mainnet'); + logChainsForType('testnet'); } }, }; @@ -69,28 +64,27 @@ const listCommand: CommandModule = { /** * Addresses command */ -const addressesCommand: CommandModule = { +const addressesCommand: CommandModuleWithContext<{ name: string }> = { command: 'addresses', describe: 'Display the addresses of core Hyperlane contracts', - builder: (yargs) => - yargs.options({ - name: { - type: 'string', - description: 'Chain to display addresses for', - choices: Object.values(Chains), - alias: 'chain', - }, - }), - handler: (args) => { - const name = args.name as CoreChainName | undefined; - if (name && hyperlaneContractAddresses[name]) { + builder: { + name: { + type: 'string', + description: 'Chain to display addresses for', + alias: 'chain', + }, + }, + handler: async ({ name, context }) => { + if (name) { + const result = await context.registry.getChainAddresses(name); logBlue('Hyperlane contract addresses for:', name); logGray('---------------------------------'); - log(JSON.stringify(hyperlaneContractAddresses[name], null, 2)); + log(JSON.stringify(result, null, 2)); } else { - logBlue('Hyperlane core contract addresses:'); + const result = await context.registry.getAddresses(); + logBlue('Hyperlane contract addresses:'); logGray('----------------------------------'); - log(JSON.stringify(hyperlaneContractAddresses, null, 2)); + log(JSON.stringify(result, null, 2)); } }, }; diff --git a/typescript/cli/src/commands/config.ts b/typescript/cli/src/commands/config.ts index b11e7d163d..0e6340c30d 100644 --- a/typescript/cli/src/commands/config.ts +++ b/typescript/cli/src/commands/config.ts @@ -11,14 +11,10 @@ import { createWarpRouteDeployConfig, readWarpRouteDeployConfig, } from '../config/warp.js'; +import { CommandModuleWithContext } from '../context/types.js'; import { log, logGreen } from '../logger.js'; -import { FileFormat } from '../utils/files.js'; -import { - chainsCommandOption, - fileFormatOption, - outputFileOption, -} from './options.js'; +import { inputFileOption, outputFileOption } from './options.js'; /** * Parent command @@ -52,84 +48,61 @@ const createCommand: CommandModule = { handler: () => log('Command required'), }; -const createChainConfigCommand: CommandModule = { +const createChainConfigCommand: CommandModuleWithContext<{}> = { command: 'chain', describe: 'Create a new, minimal Hyperlane chain config (aka chain metadata)', - builder: (yargs) => - yargs.options({ - 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 }); + handler: async ({ context }) => { + await createChainConfig({ context }); process.exit(0); }, }; -const createIsmConfigCommand: CommandModule = { +const createIsmConfigCommand: CommandModuleWithContext<{ + out: string; + advanced: boolean; +}> = { command: 'ism', describe: 'Create a basic or advanced ISM config for a validator set', - builder: (yargs) => - yargs.options({ - output: outputFileOption('./configs/ism.yaml'), - format: fileFormatOption, - chains: chainsCommandOption, - advanced: { - type: 'boolean', - describe: 'Create an advanced ISM configuration', - default: false, - }, - }), - 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 }); + builder: { + out: outputFileOption('./configs/ism.yaml'), + advanced: { + type: 'boolean', + describe: 'Create an advanced ISM configuration', + default: false, + }, + }, + handler: async ({ context, out, advanced }) => { + if (advanced) { + await createIsmConfigMap({ context, outPath: out }); } else { - await createMultisigConfig({ format, outPath, chainConfigPath }); + await createMultisigConfig({ context, outPath: out }); } - process.exit(0); }, }; -const createHookConfigCommand: CommandModule = { +const createHookConfigCommand: CommandModuleWithContext<{ out: string }> = { command: 'hooks', describe: 'Create a new hooks config (required & default)', - builder: (yargs) => - yargs.options({ - output: outputFileOption('./configs/hooks.yaml'), - format: fileFormatOption, - chains: chainsCommandOption, - }), - handler: async (argv: any) => { - const format: FileFormat = argv.format; - const outPath: string = argv.output; - const chainConfigPath: string = argv.chains; - await createHooksConfigMap({ format, outPath, chainConfigPath }); + builder: { + out: outputFileOption('./configs/hooks.yaml'), + }, + handler: async ({ context, out }) => { + await createHooksConfigMap({ context, outPath: out }); process.exit(0); }, }; -const createWarpRouteDeployConfigCommand: CommandModule = { +const createWarpRouteDeployConfigCommand: CommandModuleWithContext<{ + out: string; +}> = { command: 'warp', describe: 'Create a new Warp Route deployment config', - builder: (yargs) => - yargs.options({ - output: outputFileOption('./configs/warp-route-deployment.yaml'), - format: fileFormatOption, - chains: chainsCommandOption, - }), - handler: async (argv: any) => { - const format: FileFormat = argv.format; - const outPath: string = argv.output; - const chainConfigPath: string = argv.chains; - await createWarpRouteDeployConfig({ format, outPath, chainConfigPath }); + builder: { + out: outputFileOption('./configs/warp-route-deployment.yaml'), + }, + handler: async ({ context, out }) => { + await createWarpRouteDeployConfig({ context, outPath: out }); process.exit(0); }, }; @@ -151,75 +124,52 @@ const validateCommand: CommandModule = { handler: () => log('Command required'), }; -const validateChainCommand: CommandModule = { +const validateChainCommand: CommandModuleWithContext<{ path: string }> = { command: 'chain', - describe: 'Validate a chain config in a YAML or JSON file', - builder: (yargs) => - yargs.options({ - path: { - type: 'string', - description: 'Input file path', - demandOption: true, - }, - }), - handler: async (argv) => { - const path = argv.path as string; + describe: 'Validate a chain config file', + builder: { + path: inputFileOption, + }, + handler: async ({ path }) => { readChainConfigs(path); + logGreen(`All chain configs in ${path} are valid`); process.exit(0); }, }; -const validateIsmCommand: CommandModule = { +const validateIsmCommand: CommandModuleWithContext<{ path: string }> = { command: 'ism', - describe: 'Validate the basic ISM config in a YAML or JSON file', - builder: (yargs) => - yargs.options({ - path: { - type: 'string', - description: 'Input file path', - demandOption: true, - }, - }), - handler: async (argv) => { - const path = argv.path as string; + describe: 'Validate the basic ISM config file', + builder: { + path: inputFileOption, + }, + handler: async ({ path }) => { readMultisigConfig(path); logGreen('Config is valid'); process.exit(0); }, }; -const validateIsmAdvancedCommand: CommandModule = { +const validateIsmAdvancedCommand: CommandModuleWithContext<{ path: string }> = { command: 'ism-advanced', - describe: 'Validate the advanced ISM config in a YAML or JSON file', - builder: (yargs) => - yargs.options({ - path: { - type: 'string', - description: 'Input file path', - demandOption: true, - }, - }), - handler: async (argv) => { - const path = argv.path as string; + describe: 'Validate the advanced ISM config file', + builder: { + path: inputFileOption, + }, + handler: async ({ path }) => { readIsmConfig(path); logGreen('Config is valid'); process.exit(0); }, }; -const validateWarpCommand: CommandModule = { +const validateWarpCommand: CommandModuleWithContext<{ path: string }> = { command: 'warp', - describe: 'Validate a Warp Route config in a YAML or JSON file', - builder: (yargs) => - yargs.options({ - path: { - type: 'string', - description: 'Input file path', - demandOption: true, - }, - }), - handler: async (argv) => { - const path = argv.path as string; + describe: 'Validate a Warp Route deployment config file', + builder: { + path: inputFileOption, + }, + handler: async ({ path }) => { readWarpRouteDeployConfig(path); logGreen('Config is valid'); process.exit(0); diff --git a/typescript/cli/src/commands/deploy.ts b/typescript/cli/src/commands/deploy.ts index 4f53bd295a..97bd1124d3 100644 --- a/typescript/cli/src/commands/deploy.ts +++ b/typescript/cli/src/commands/deploy.ts @@ -1,5 +1,9 @@ import { CommandModule } from 'yargs'; +import { + CommandModuleWithContext, + CommandModuleWithWriteContext, +} from '../context/types.js'; import { runKurtosisAgentDeploy } from '../deploy/agent.js'; import { runCoreDeploy } from '../deploy/core.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 { - AgentCommandOptions, - CoreCommandOptions, - WarpCommandOptions, agentConfigCommandOption, agentTargetsCommandOption, - chainsCommandOption, - coreArtifactsOption, coreTargetsCommandOption, dryRunOption, hookCommandOption, ismCommandOption, - keyCommandOption, originCommandOption, - outDirCommandOption, - skipConfirmationOption, warpConfigCommandOption, } from './options.js'; -export enum Command { - DEPLOY = 'deploy', - KURTOSIS_AGENTS = 'kurtosis-agents', - CORE = 'core', - WARP = 'warp', -} - /** * Parent command */ export const deployCommand: CommandModule = { - command: Command.DEPLOY, + command: 'deploy', describe: 'Permissionlessly deploy a Hyperlane contracts or extensions', builder: (yargs) => yargs @@ -51,28 +40,26 @@ export const deployCommand: CommandModule = { /** * Agent command */ -const agentCommand: CommandModule = { - command: Command.KURTOSIS_AGENTS, +const agentCommand: CommandModuleWithContext<{ + origin?: string; + targets?: string; + config?: string; +}> = { + command: 'kurtosis-agents', describe: 'Deploy Hyperlane agents with Kurtosis', - builder: (yargs) => - yargs.options({ - origin: originCommandOption, - targets: agentTargetsCommandOption, - chains: chainsCommandOption, - config: agentConfigCommandOption, - }), - handler: async (argv: any) => { + builder: { + origin: originCommandOption, + targets: agentTargetsCommandOption, + config: agentConfigCommandOption(true), + }, + handler: async ({ context, origin, targets, config }) => { logGray('Hyperlane Agent Deployment with Kurtosis'); logGray('----------------------------------------'); - const chainConfigPath: string = argv.chains; - const originChain: string = argv.origin; - const agentConfigurationPath: string = argv.config; - const relayChains: string = argv.targets; await runKurtosisAgentDeploy({ - originChain, - relayChains, - chainConfigPath, - agentConfigurationPath, + context, + originChain: origin, + relayChains: targets, + agentConfigurationPath: config, }); process.exit(0); }, @@ -81,34 +68,23 @@ const agentCommand: CommandModule = { /** * Core command */ -const coreCommand: CommandModule = { - command: Command.CORE, +const coreCommand: CommandModuleWithWriteContext<{ + targets: string; + ism?: string; + hook?: string; + 'dry-run': boolean; + agent: string; +}> = { + command: 'core', describe: 'Deploy core Hyperlane contracts', - builder: (yargs) => - yargs.options({ - targets: coreTargetsCommandOption, - chains: chainsCommandOption, - artifacts: coreArtifactsOption, - ism: ismCommandOption, - hook: hookCommandOption, - 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 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; - + builder: { + targets: coreTargetsCommandOption, + ism: ismCommandOption, + hook: hookCommandOption, + agent: agentConfigCommandOption(false, './configs/agent.json'), + 'dry-run': dryRunOption, + }, + handler: async ({ context, targets, ism, hook, agent, dryRun }) => { logGray( `Hyperlane permissionless core deployment${dryRun ? ' dry-run' : ''}`, ); @@ -117,16 +93,13 @@ const coreCommand: CommandModule = { if (dryRun) await verifyAnvil(); try { + const chains = targets?.split(',').map((r: string) => r.trim()); await runCoreDeploy({ - key, - chainConfigPath, + context, chains, - artifactsPath, - ismConfigPath, - hookConfigPath, - outPath, - skipConfirmation, - dryRun, + ismConfigPath: ism, + hookConfigPath: hook, + agentOutPath: agent, }); } catch (error: any) { evaluateIfDryRunFailure(error, dryRun); @@ -139,28 +112,17 @@ const coreCommand: CommandModule = { /** * Warp command */ -const warpCommand: CommandModule = { - command: Command.WARP, +const warpCommand: CommandModuleWithWriteContext<{ + config: string; + 'dry-run': boolean; +}> = { + command: 'warp', describe: 'Deploy Warp Route contracts', - builder: (yargs) => - yargs.options({ - config: warpConfigCommandOption, - core: coreArtifactsOption, - chains: chainsCommandOption, - 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; - + builder: { + config: warpConfigCommandOption, + 'dry-run': dryRunOption, + }, + handler: async ({ context, config, dryRun }) => { logGray(`Hyperlane warp route deployment${dryRun ? ' dry-run' : ''}`); logGray('------------------------------------------------'); @@ -168,13 +130,8 @@ const warpCommand: CommandModule = { try { await runWarpRouteDeploy({ - key, - chainConfigPath, - warpRouteDeploymentConfigPath, - coreArtifactsPath, - outPath, - skipConfirmation, - dryRun, + context, + warpRouteDeploymentConfigPath: config, }); } catch (error: any) { evaluateIfDryRunFailure(error, dryRun); diff --git a/typescript/cli/src/commands/hook.ts b/typescript/cli/src/commands/hook.ts index dd284198c6..3c09fe60dc 100644 --- a/typescript/cli/src/commands/hook.ts +++ b/typescript/cli/src/commands/hook.ts @@ -1,13 +1,12 @@ import { CommandModule } from 'yargs'; +import { CommandModuleWithContext } from '../context/types.js'; import { readHookConfig } from '../hook/read.js'; import { log } from '../logger.js'; import { addressCommandOption, chainCommandOption, - chainsCommandOption, - fileFormatOption, outputFileOption, } from './options.js'; @@ -26,28 +25,23 @@ export const hookCommand: CommandModule = { // hyperlane hook read --chain polygon --address 0xca4cCe24E7e06241846F5EA0cda9947F0507C40C // IGP hook on inevm (may take 5s): // hyperlane hook read --chain inevm --address 0x19dc38aeae620380430C200a6E990D5Af5480117 -export const read: CommandModule = { +export const read: CommandModuleWithContext<{ + chain: string; + address: string; + out: string; +}> = { command: 'read', describe: 'Reads onchain Hook configuration for a given address', - builder: (yargs) => - yargs.options({ - chains: chainsCommandOption, - chain: { - ...chainCommandOption, - demandOption: true, - }, - address: addressCommandOption('Address of the Hook to read.', true), - format: fileFormatOption, - output: outputFileOption(), - }), - handler: async (argv: any) => { - await readHookConfig({ - chain: argv.chain, - address: argv.address, - chainConfigPath: argv.chains, - format: argv.format, - output: argv.output, - }); + builder: { + chain: { + ...chainCommandOption, + demandOption: true, + }, + address: addressCommandOption('Address of the Hook to read.', true), + out: outputFileOption(), + }, + handler: async (args) => { + await readHookConfig(args); process.exit(0); }, }; diff --git a/typescript/cli/src/commands/ism.ts b/typescript/cli/src/commands/ism.ts index 10c4044734..f19c90035a 100644 --- a/typescript/cli/src/commands/ism.ts +++ b/typescript/cli/src/commands/ism.ts @@ -1,13 +1,12 @@ import { CommandModule } from 'yargs'; +import { CommandModuleWithContext } from '../context/types.js'; import { readIsmConfig } from '../ism/read.js'; import { log } from '../logger.js'; import { addressCommandOption, chainCommandOption, - chainsCommandOption, - fileFormatOption, outputFileOption, } from './options.js'; @@ -28,31 +27,26 @@ export const ismCommand: CommandModule = { // hyperlane ism read --chain inevm --address 0x79A7c7Fe443971CBc6baD623Fdf8019C379a7178 // Test ISM on alfajores testnet // hyperlane ism read --chain alfajores --address 0xdB52E4853b6A40D2972E6797E0BDBDb3eB761966 -export const read: CommandModule = { +export const read: CommandModuleWithContext<{ + chain: string; + address: string; + out: string; +}> = { command: 'read', describe: 'Reads onchain ISM configuration for a given address', - builder: (yargs) => - yargs.options({ - chains: chainsCommandOption, - chain: { - ...chainCommandOption, - demandOption: true, - }, - address: addressCommandOption( - 'Address of the Interchain Security Module to read.', - true, - ), - format: fileFormatOption, - output: outputFileOption(), - }), - handler: async (argv: any) => { - await readIsmConfig({ - chain: argv.chain, - address: argv.address, - chainConfigPath: argv.chains, - format: argv.format, - output: argv.output, - }); + builder: { + chain: { + ...chainCommandOption, + demandOption: true, + }, + address: addressCommandOption( + 'Address of the Interchain Security Module to read.', + true, + ), + out: outputFileOption(), + }, + handler: async (argv) => { + await readIsmConfig(argv); process.exit(0); }, }; diff --git a/typescript/cli/src/commands/options.ts b/typescript/cli/src/commands/options.ts index d278701e23..6e33a82db3 100644 --- a/typescript/cli/src/commands/options.ts +++ b/typescript/cli/src/commands/options.ts @@ -1,9 +1,12 @@ import { Options } from 'yargs'; +import { DEFAULT_GITHUB_REGISTRY } from '@hyperlane-xyz/registry'; import { LogFormat, LogLevel } from '@hyperlane-xyz/utils'; import { ENV } from '../utils/env.js'; +/* Global options */ + export const logFormatCommandOption: Options = { type: 'string', description: 'Log output format', @@ -16,33 +19,36 @@ export const logLevelCommandOption: Options = { choices: Object.values(LogLevel), }; -export type CommandOptions = { - chains: Options; +export const registryUriCommandOption: 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; - targets: Options; - config: Options; + +export const overrideRegistryUriCommandOption: Options = { + type: 'string', + description: 'Path to a local registry to override the default registry', + default: './', }; -export type CoreCommandOptions = CommandOptions & { - targets: Options; - artifacts: Options; - ism: Options; - hook: Options; - out: Options; - key: Options; - yes: Options; - 'dry-run': Options; + +export const skipConfirmationOption: Options = { + type: 'boolean', + description: 'Skip confirmation prompts', + default: false, + alias: 'y', }; -export type WarpCommandOptions = CommandOptions & { - config: Options; - core: Options; - out: Options; - key: Options; - yes: Options; - 'dry-run': Options; + +export const keyCommandOption: Options = { + type: 'string', + description: `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`, + alias: 'k', + default: ENV.HYP_KEY, }; +/* Command-specific options */ + export const coreTargetsCommandOption: Options = { type: 'string', description: @@ -79,52 +85,24 @@ export const warpConfigCommandOption: Options = { 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 = { type: 'string', - description: 'File path to Warp config', + description: 'File path to Warp Route config', alias: 'w', + // TODO make this optional and have the commands get it from the registry + demandOption: true, }; -export const agentConfigCommandOption: Options = { - type: 'string', - description: 'File path to agent configuration artifacts', -}; - -export const fileFormatOption: Options = { +export const agentConfigCommandOption = ( + isIn: boolean, + defaultPath?: string, +): Options => ({ type: 'string', - description: 'Output file format', - choices: ['json', 'yaml'], - default: 'yaml', - alias: 'f', -}; + description: `${ + isIn ? 'Input' : 'Output' + } file path for the agent configuration`, + default: defaultPath, +}); export const outputFileOption = (defaultPath?: string): Options => ({ type: 'string', @@ -133,18 +111,18 @@ export const outputFileOption = (defaultPath?: string): Options => ({ alias: 'o', }); -export const skipConfirmationOption: Options = { - type: 'boolean', - description: 'Skip confirmation prompts', - default: false, - alias: 'y', +export const inputFileOption: Options = { + type: 'string', + description: 'Input file path', + alias: 'i', + demandOption: true, }; export const dryRunOption: Options = { type: 'string', description: 'Chain name to fork and simulate deployment. Please ensure an anvil node instance is running during execution via `anvil`.', - alias: ['d', 'dr'], + alias: ['d'], }; export const chainCommandOption: Options = { diff --git a/typescript/cli/src/commands/send.ts b/typescript/cli/src/commands/send.ts index 87af7345be..6d2334465d 100644 --- a/typescript/cli/src/commands/send.ts +++ b/typescript/cli/src/commands/send.ts @@ -1,16 +1,12 @@ import { ethers } from 'ethers'; import { CommandModule, Options } from 'yargs'; +import { CommandModuleWithWriteContext } from '../context/types.js'; import { log } from '../logger.js'; import { sendTestMessage } from '../send/message.js'; import { sendTestTransfer } from '../send/transfer.js'; -import { - chainsCommandOption, - coreArtifactsOption, - keyCommandOption, - warpConfigOption, -} from './options.js'; +import { warpConfigOption } from './options.js'; /** * Parent command @@ -27,18 +23,10 @@ export const sendCommand: CommandModule = { handler: () => log('Command required'), }; -export const selfrelay: Options = { - type: 'boolean', - description: 'Relay message on destination chain', - default: false, - alias: ['s', 'sr'], -}; - /** * Message command */ export const messageOptions: { [k: string]: Options } = { - key: keyCommandOption, origin: { type: 'string', description: 'Origin chain to send message from', @@ -47,53 +35,61 @@ export const messageOptions: { [k: string]: Options } = { type: 'string', description: 'Destination chain to send message to', }, - chains: chainsCommandOption, - core: coreArtifactsOption, timeout: { type: 'number', description: 'Timeout in seconds', default: 5 * 60, }, - 'self-relay': selfrelay, quick: { type: 'boolean', description: 'Skip wait for message to be delivered', 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', describe: 'Send a test message to a remote chain', - builder: (yargs) => - yargs.options({ - ...messageOptions, - messageBody: { - type: 'string', - description: 'Optional Message body', - default: 'Hello!', - }, - }), - handler: async (argv: any) => { - const key: string | undefined = argv.key; - const chainConfigPath: string = argv.chains; - const coreArtifactsPath: string | undefined = argv.core; - const origin: string | undefined = argv.origin; - const destination: string | undefined = argv.destination; - const timeoutSec: number = argv.timeout; - const skipWaitForDelivery: boolean = argv.quick; - const messageBody: string = argv.messageBody; - const selfRelay: boolean = argv['selfrelay']; + builder: { + ...messageOptions, + body: { + type: 'string', + description: 'Optional Message body', + default: 'Hello!', + }, + }, + handler: async ({ + context, + origin, + destination, + timeout, + quick, + relay, + body, + }) => { await sendTestMessage({ - key, - chainConfigPath, - coreArtifactsPath, + context, origin, destination, - messageBody: ethers.utils.hexlify(ethers.utils.toUtf8Bytes(messageBody)), - timeoutSec, - skipWaitForDelivery, - selfRelay, + messageBody: ethers.utils.hexlify(ethers.utils.toUtf8Bytes(body)), + timeoutSec: timeout, + skipWaitForDelivery: quick, + selfRelay: relay, }); process.exit(0); }, @@ -102,53 +98,50 @@ const messageCommand: CommandModule = { /** * Transfer command */ -const transferCommand: CommandModule = { +const transferCommand: CommandModuleWithWriteContext< + MessageOptionsArgTypes & { + warp: string; + router?: string; + wei: string; + recipient?: string; + } +> = { command: 'transfer', describe: 'Send a test token transfer on a warp route', - builder: (yargs) => - yargs.options({ - ...messageOptions, - warp: warpConfigOption, - router: { - type: 'string', - description: 'The address of the token router contract', - }, - wei: { - type: 'string', - description: 'Amount in wei to send', - default: 1, - }, - recipient: { - type: 'string', - description: 'Token recipient address (defaults to sender)', - }, - }), - handler: async (argv: any) => { - const key: string | undefined = argv.key; - const chainConfigPath: string = argv.chains; - const coreArtifactsPath: string | undefined = argv.core; - const warpConfigPath: string = argv.warp; - 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']; + builder: { + ...messageOptions, + warp: warpConfigOption, + wei: { + type: 'string', + description: 'Amount in wei to send', + default: 1, + }, + recipient: { + type: 'string', + description: 'Token recipient address (defaults to sender)', + }, + }, + handler: async ({ + context, + origin, + destination, + timeout, + quick, + relay, + warp, + wei, + recipient, + }) => { await sendTestTransfer({ - key, - chainConfigPath, - coreArtifactsPath, - warpConfigPath, + context, + warpConfigPath: warp, origin, destination, - routerAddress, wei, recipient, - timeoutSec, - skipWaitForDelivery, - selfRelay, + timeoutSec: timeout, + skipWaitForDelivery: quick, + selfRelay: relay, }); process.exit(0); }, diff --git a/typescript/cli/src/commands/signCommands.ts b/typescript/cli/src/commands/signCommands.ts new file mode 100644 index 0000000000..8df41a0a02 --- /dev/null +++ b/typescript/cli/src/commands/signCommands.ts @@ -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])) + ); +} diff --git a/typescript/cli/src/commands/status.ts b/typescript/cli/src/commands/status.ts index 94f60494a2..3f0e2a4f6b 100644 --- a/typescript/cli/src/commands/status.ts +++ b/typescript/cli/src/commands/status.ts @@ -1,37 +1,27 @@ -import { CommandModule } from 'yargs'; - +import { CommandModuleWithContext } from '../context/types.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', describe: 'Check status of a message', - builder: (yargs) => - yargs.options({ - ...messageOptions, - id: { - type: 'string', - description: 'Message ID', - }, - }), - 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; - + builder: { + ...messageOptions, + id: { + type: 'string', + description: 'Message ID', + }, + }, + handler: async ({ context, origin, destination, id, relay }) => { await checkMessageStatus({ - chainConfigPath, - coreArtifactsPath, - messageId, + context, + messageId: id, destination, origin, - selfRelay, - key, + selfRelay: relay, }); process.exit(0); }, diff --git a/typescript/cli/src/config/artifacts.ts b/typescript/cli/src/config/artifacts.ts deleted file mode 100644 index 47d0ed6733..0000000000 --- a/typescript/cli/src/config/artifacts.ts +++ /dev/null @@ -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>(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 | 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; -} diff --git a/typescript/cli/src/config/chain.test.ts b/typescript/cli/src/config/chain.test.ts index 99f2a55c73..a7555291b2 100644 --- a/typescript/cli/src/config/chain.test.ts +++ b/typescript/cli/src/config/chain.test.ts @@ -6,13 +6,6 @@ describe('readChainConfigs', () => { const chainToMetadata = readChainConfigs('./examples/chain-config.yaml'); it('parses and validates correctly', () => { - expect(chainToMetadata.mychainname.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', - ); + expect(chainToMetadata.chainId).to.equal(1234567890); }); }); diff --git a/typescript/cli/src/config/chain.ts b/typescript/cli/src/config/chain.ts index 980f72a90d..c47b8f1c7c 100644 --- a/typescript/cli/src/config/chain.ts +++ b/typescript/cli/src/config/chain.ts @@ -1,80 +1,41 @@ import { confirm, input } from '@inquirer/prompts'; -import { - ChainMap, - ChainMetadata, - ChainMetadataSchema, - chainMetadata as coreChainMetadata, -} from '@hyperlane-xyz/sdk'; -import { ProtocolType, objMerge } from '@hyperlane-xyz/utils'; +import { ChainMetadata, ChainMetadataSchema } from '@hyperlane-xyz/sdk'; +import { ProtocolType } from '@hyperlane-xyz/utils'; -import { getMultiProvider } from '../context.js'; +import { CommandContext } from '../context/types.js'; import { errorRed, log, logBlue, logGreen } from '../logger.js'; -import { - FileFormat, - isFile, - mergeYamlOrJson, - readYamlOrJson, -} from '../utils/files.js'; +import { readYamlOrJson } from '../utils/files.js'; export function readChainConfigs(filePath: string) { log(`Reading file configs in ${filePath}`); - const chainToMetadata = readYamlOrJson>(filePath); + const chainMetadata = readYamlOrJson(filePath); if ( - !chainToMetadata || - typeof chainToMetadata !== 'object' || - !Object.keys(chainToMetadata).length + !chainMetadata || + typeof chainMetadata !== 'object' || + !Object.keys(chainMetadata).length ) { errorRed(`No configs found in ${filePath}`); process.exit(1); } // Validate configs from file and merge in core configs as needed - for (const chain of Object.keys(chainToMetadata)) { - if (coreChainMetadata[chain]) { - // For core chains, merge in the default config to allow users to override only some fields - chainToMetadata[chain] = objMerge( - coreChainMetadata[chain], - chainToMetadata[chain], - ); - } - 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); + const parseResult = ChainMetadataSchema.safeParse(chainMetadata); + if (!parseResult.success) { + errorRed( + `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`, + ); + errorRed(JSON.stringify(parseResult.error.errors)); + process.exit(1); } + return chainMetadata; } export async function createChainConfig({ - format, - outPath, + context, }: { - format: FileFormat; - outPath: string; + context: CommandContext; }) { logBlue('Creating a new chain config'); const name = await input({ @@ -149,8 +110,8 @@ export async function createChainConfig({ } const parseResult = ChainMetadataSchema.safeParse(metadata); if (parseResult.success) { - logGreen(`Chain config is valid, writing to file ${outPath}`); - mergeYamlOrJson(outPath, { [name]: metadata }, format); + logGreen(`Chain config is valid, writing to registry`); + await context.registry.updateChain({ chainName: metadata.name, metadata }); } else { 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`, diff --git a/typescript/cli/src/config/hooks.ts b/typescript/cli/src/config/hooks.ts index b2a2213af9..aeb449deca 100644 --- a/typescript/cli/src/config/hooks.ts +++ b/typescript/cli/src/config/hooks.ts @@ -9,7 +9,6 @@ import { GasOracleContractType, HookType, HooksConfig, - chainMetadata, } from '@hyperlane-xyz/sdk'; import { Address, @@ -18,11 +17,10 @@ import { toWei, } from '@hyperlane-xyz/utils'; +import { CommandContext } from '../context/types.js'; import { errorRed, log, logBlue, logGreen, logRed } from '../logger.js'; import { runMultiChainSelectionStep } from '../utils/chains.js'; -import { FileFormat, mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; - -import { readChainConfigsIfExists } from './chain.js'; +import { mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; const ProtocolFeeSchema = z.object({ type: z.literal(HookType.PROTOCOL_FEE), @@ -118,17 +116,14 @@ export function readHooksConfigMap(filePath: string) { } export async function createHooksConfigMap({ - format, + context, outPath, - chainConfigPath, }: { - format: FileFormat; + context: CommandContext; outPath: string; - chainConfigPath: string; }) { logBlue('Creating a new hook config'); - const customChains = readChainConfigsIfExists(chainConfigPath); - const chains = await runMultiChainSelectionStep(customChains); + const chains = await runMultiChainSelectionStep(context.chainMetadata); const result: HooksConfigMap = {}; for (const chain of chains) { @@ -137,12 +132,12 @@ export async function createHooksConfigMap({ const remotes = chains.filter((c) => c !== chain); result[chain] = { ...result[chain], - [hookRequirements]: await createHookConfig(chain, remotes), + [hookRequirements]: await createHookConfig(context, chain, remotes), }; } if (isValidHookConfigMap(result)) { logGreen(`Hook config is valid, writing to file ${outPath}`); - mergeYamlOrJson(outPath, result, format); + mergeYamlOrJson(outPath, result); } else { errorRed( `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( + context: CommandContext, chain: ChainName, remotes: ChainName[], ): Promise { @@ -195,13 +191,13 @@ export async function createHookConfig( if (hookType === HookType.MERKLE_TREE) { lastConfig = { type: HookType.MERKLE_TREE }; } else if (hookType === HookType.PROTOCOL_FEE) { - lastConfig = await createProtocolFeeConfig(chain); + lastConfig = await createProtocolFeeConfig(context, chain); } else if (hookType === HookType.INTERCHAIN_GAS_PAYMASTER) { lastConfig = await createIGPConfig(remotes); } else if (hookType === HookType.AGGREGATION) { - lastConfig = await createAggregationConfig(chain, remotes); + lastConfig = await createAggregationConfig(context, chain, remotes); } else if (hookType === HookType.ROUTING) { - lastConfig = await createRoutingConfig(chain, remotes); + lastConfig = await createRoutingConfig(context, chain, remotes); } else { throw new Error(`Invalid hook type: ${hookType}`); } @@ -209,6 +205,7 @@ export async function createHookConfig( } export async function createProtocolFeeConfig( + context: CommandContext, chain: ChainName, ): Promise { const owner = await input({ @@ -232,6 +229,7 @@ export async function createProtocolFeeConfig( const maxProtocolFee = toWei( await input({ message: `Enter max protocol fee ${nativeTokenAndDecimals( + context, chain, )} e.g. 1.0)`, }), @@ -239,6 +237,7 @@ export async function createProtocolFeeConfig( const protocolFee = toWei( await input({ message: `Enter protocol fee in ${nativeTokenAndDecimals( + context, chain, )} e.g. 0.01)`, }), @@ -305,6 +304,7 @@ export async function createIGPConfig( } export async function createAggregationConfig( + context: CommandContext, chain: ChainName, remotes: ChainName[], ): Promise { @@ -317,7 +317,7 @@ export async function createAggregationConfig( const hooks: Array = []; for (let i = 0; i < hooksNum; i++) { logBlue(`Creating hook ${i + 1} of ${hooksNum} ...`); - hooks.push(await createHookConfig(chain, remotes)); + hooks.push(await createHookConfig(context, chain, remotes)); } return { type: HookType.AGGREGATION, @@ -326,6 +326,7 @@ export async function createAggregationConfig( } export async function createRoutingConfig( + context: CommandContext, origin: ChainName, remotes: ChainName[], ): Promise { @@ -339,7 +340,7 @@ export async function createRoutingConfig( await confirm({ 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; } return { @@ -349,10 +350,10 @@ export async function createRoutingConfig( }; } -function nativeTokenAndDecimals(chain: ChainName) { +function nativeTokenAndDecimals(context: CommandContext, chain: ChainName) { return `10^${ - chainMetadata[chain].nativeToken?.decimals ?? '18' + context.chainMetadata[chain].nativeToken?.decimals ?? '18' } which you cannot exceed (in ${ - chainMetadata[chain].nativeToken?.symbol ?? 'eth' + context.chainMetadata[chain].nativeToken?.symbol ?? 'eth' }`; } diff --git a/typescript/cli/src/config/ism.ts b/typescript/cli/src/config/ism.ts index ce21ea9dfd..66075c4938 100644 --- a/typescript/cli/src/config/ism.ts +++ b/typescript/cli/src/config/ism.ts @@ -3,6 +3,7 @@ import { z } from 'zod'; import { ChainMap, ChainName, IsmType, ZHash } from '@hyperlane-xyz/sdk'; +import { CommandContext } from '../context/types.js'; import { errorRed, log, @@ -12,9 +13,7 @@ import { logRed, } from '../logger.js'; import { runMultiChainSelectionStep } from '../utils/chains.js'; -import { FileFormat, mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; - -import { readChainConfigsIfExists } from './chain.js'; +import { mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; const MultisigIsmConfigSchema = z.object({ type: z.union([ @@ -113,22 +112,19 @@ export function isValildIsmConfig(config: any) { } export async function createIsmConfigMap({ - format, + context, outPath, - chainConfigPath, }: { - format: FileFormat; + context: CommandContext; outPath: string; - chainConfigPath: string; }) { logBlue('Creating a new advanced ISM config'); logBoldUnderlinedRed('WARNING: USE AT YOUR RISK.'); 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.', ); - const customChains = readChainConfigsIfExists(chainConfigPath); const chains = await runMultiChainSelectionStep( - customChains, + context.chainMetadata, 'Select chains to configure ISM for', true, ); @@ -146,7 +142,7 @@ export async function createIsmConfigMap({ if (isValildIsmConfig(result)) { logGreen(`ISM config is valid, writing to file ${outPath}`); - mergeYamlOrJson(outPath, result, format); + mergeYamlOrJson(outPath, result); } else { errorRed( `ISM config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/ism.yaml for an example`, diff --git a/typescript/cli/src/config/multisig.ts b/typescript/cli/src/config/multisig.ts index d79477dc7e..bb2e0ebbf0 100644 --- a/typescript/cli/src/config/multisig.ts +++ b/typescript/cli/src/config/multisig.ts @@ -9,12 +9,10 @@ import { objMap, } from '@hyperlane-xyz/utils'; -import { sdkContractAddressesMap } from '../context.js'; +import { CommandContext } from '../context/types.js'; import { errorRed, log, logBlue, logGreen } from '../logger.js'; import { runMultiChainSelectionStep } from '../utils/chains.js'; -import { FileFormat, mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; - -import { readChainConfigsIfExists } from './chain.js'; +import { mergeYamlOrJson, readYamlOrJson } from '../utils/files.js'; const MultisigConfigMapSchema = z.object({}).catchall( z.object({ @@ -64,21 +62,19 @@ export function isValidMultisigConfig(config: any) { } export async function createMultisigConfig({ - format, + context, outPath, - chainConfigPath, }: { - format: FileFormat; + context: CommandContext; outPath: string; - chainConfigPath: string; }) { logBlue('Creating a new multisig config'); 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.', ); - const customChains = readChainConfigsIfExists(chainConfigPath); - const chains = await runMultiChainSelectionStep(customChains); + const chains = await runMultiChainSelectionStep(context.chainMetadata); + const chainAddresses = await context.registry.getAddresses(); const result: MultisigConfigMap = {}; let lastConfig: MultisigConfigMap['string'] | undefined = undefined; const repeat = false; @@ -88,7 +84,7 @@ export async function createMultisigConfig({ result[chain] = lastConfig; continue; } - if (Object.keys(sdkContractAddressesMap).includes(chain)) { + if (Object.keys(chainAddresses).includes(chain)) { const reuseCoreConfig = await confirm({ message: 'Use existing Hyperlane validators for this chain?', }); @@ -118,7 +114,7 @@ export async function createMultisigConfig({ if (isValidMultisigConfig(result)) { logGreen(`Multisig config is valid, writing to file ${outPath}`); - mergeYamlOrJson(outPath, result, format); + mergeYamlOrJson(outPath, result); } else { errorRed( `Multisig config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/ism.yaml for an example`, diff --git a/typescript/cli/src/config/warp.ts b/typescript/cli/src/config/warp.ts index 832e9f47c8..2410915d0d 100644 --- a/typescript/cli/src/config/warp.ts +++ b/typescript/cli/src/config/warp.ts @@ -3,31 +3,27 @@ import { ethers } from 'ethers'; import { TokenType, + WarpCoreConfig, + WarpCoreConfigSchema, WarpRouteDeployConfig, WarpRouteDeployConfigSchema, } from '@hyperlane-xyz/sdk'; +import { CommandContext } from '../context/types.js'; import { errorRed, logBlue, logGreen } from '../logger.js'; import { runMultiChainSelectionStep, runSingleChainSelectionStep, } 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); if (!config) throw new Error(`No warp route deploy config found at ${filePath}`); - const result = WarpRouteDeployConfigSchema.safeParse(config); - if (!result.success) { - const firstIssue = result.error.issues[0]; - throw new Error( - `Invalid warp config: ${firstIssue.path} => ${firstIssue.message}`, - ); - } - return result.data; + return WarpRouteDeployConfigSchema.parse(config); } export function isValidWarpRouteDeployConfig(config: any) { @@ -35,18 +31,15 @@ export function isValidWarpRouteDeployConfig(config: any) { } export async function createWarpRouteDeployConfig({ - format, + context, outPath, - chainConfigPath, }: { - format: FileFormat; + context: CommandContext; outPath: string; - chainConfigPath: string; }) { logBlue('Creating a new warp route deployment config'); - const customChains = readChainConfigsIfExists(chainConfigPath); const baseChain = await runSingleChainSelectionStep( - customChains, + context.chainMetadata, 'Select base chain with the original token to warp', ); @@ -74,7 +67,7 @@ export async function createWarpRouteDeployConfig({ : await input({ message: addressMessage }); const syntheticChains = await runMultiChainSelectionStep( - customChains, + context.chainMetadata, 'Select chains to which the base token will be connected', ); @@ -104,7 +97,7 @@ export async function createWarpRouteDeployConfig({ if (isValidWarpRouteDeployConfig(result)) { logGreen(`Warp Route config is valid, writing to file ${outPath}`); - writeYamlOrJson(outPath, result, format); + writeYamlOrJson(outPath, result); } else { 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`, @@ -112,3 +105,11 @@ export async function createWarpRouteDeployConfig({ 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); +} diff --git a/typescript/cli/src/context.test.ts b/typescript/cli/src/context.test.ts deleted file mode 100644 index 41805197e4..0000000000 --- a/typescript/cli/src/context.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/typescript/cli/src/context.ts b/typescript/cli/src/context.ts deleted file mode 100644 index 2a0c467d75..0000000000 --- a/typescript/cli/src/context.ts +++ /dev/null @@ -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 = { - ...hyperlaneEnvironments.testnet, - ...hyperlaneEnvironments.mainnet, -}; - -export function getMergedContractAddresses( - artifacts?: HyperlaneContractsMap, - 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, - (key, v): v is any => key !== 'interchainGasPaymaster', - ), - ); - } - return objMerge( - sdkContractsAddressesToRecover, - artifacts || {}, - ) as HyperlaneContractsMap; -} - -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; - multiProvider: MultiProvider; -} - -// This makes return type dynamic based on the input settings -type CommandContext

= CommandContextBase & - (P extends { keyConfig: object } - ? { signer: ethers.Signer } - : { signer: undefined }) & - (P extends { coreConfig: object } - ? { coreArtifacts: HyperlaneContractsMap } - : { 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

({ - chainConfigPath, - coreConfig, - keyConfig, - skipConfirmation, - warpConfig, -}: P): Promise> { - 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(warpConfigPath); - } - - const multiProvider = getMultiProvider(customChains, signer); - - return { - customChains, - signer, - multiProvider, - coreArtifacts, - warpCoreConfig, - } as CommandContext

; -} - -/** - * Retrieves dry-run context for the user-selected command - * @returns dry-run context for the current command - */ -export async function getDryRunContext

({ - chainConfigPath, - chains, - coreConfig, - keyConfig, - skipConfirmation, -}: P): Promise> { - 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

; -} - -/** - * 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, - signer?: ethers.Signer, -) { - const chainConfigs = { ...chainMetadata, ...customChains }; - const multiProvider = new MultiProvider(chainConfigs); - if (signer) multiProvider.setSharedSigner(signer); - return multiProvider; -} diff --git a/typescript/cli/src/context/context.ts b/typescript/cli/src/context/context.ts new file mode 100644 index 0000000000..e3984408cb --- /dev/null +++ b/typescript/cli/src/context/context.ts @@ -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) { + 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 { + 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 { + 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; +} diff --git a/typescript/cli/src/context/types.ts b/typescript/cli/src/context/types.ts new file mode 100644 index 0000000000..b7ef32050c --- /dev/null +++ b/typescript/cli/src/context/types.ts @@ -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; + 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 = CommandModule< + {}, + Args & { context: CommandContext } +>; + +export type CommandModuleWithWriteContext = CommandModule< + {}, + Args & { context: WriteCommandContext } +>; diff --git a/typescript/cli/src/deploy/agent.ts b/typescript/cli/src/deploy/agent.ts index 3338d5b1b5..6658aa536e 100644 --- a/typescript/cli/src/deploy/agent.ts +++ b/typescript/cli/src/deploy/agent.ts @@ -2,7 +2,7 @@ import terminalLink from 'terminal-link'; import { toBase64 } from '@hyperlane-xyz/utils'; -import { getContext } from '../context.js'; +import { CommandContext } from '../context/types.js'; import { logBlue, logGreen } from '../logger.js'; import { runMultiChainSelectionStep, @@ -11,27 +11,25 @@ import { import { readJson, runFileSelectionStep } from '../utils/files.js'; export async function runKurtosisAgentDeploy({ + context, originChain, relayChains, - chainConfigPath, agentConfigurationPath, }: { - originChain: string; - relayChains: string; - chainConfigPath: string; - agentConfigurationPath: string; + context: CommandContext; + originChain?: string; + relayChains?: string; + agentConfigurationPath?: string; }) { - const { customChains } = await getContext({ chainConfigPath }); - if (!originChain) { originChain = await runSingleChainSelectionStep( - customChains, + context.chainMetadata, 'Select the origin chain', ); } if (!relayChains) { const selectedRelayChains = await runMultiChainSelectionStep( - customChains, + context.chainMetadata, 'Select chains to relay between', true, ); @@ -44,7 +42,7 @@ export async function runKurtosisAgentDeploy({ 'No agent config json was provided. Please specify the agent config json filepath.', ); agentConfigurationPath = await runFileSelectionStep( - './artifacts', + './configs', 'agent config json', 'agent-config', ); diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 68eb805511..f3da40e48b 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -1,24 +1,22 @@ import { confirm } from '@inquirer/prompts'; import { ethers } from 'ethers'; +import { ChainAddresses, IRegistry } from '@hyperlane-xyz/registry'; import { ChainMap, ChainName, CoreConfig, - DeployedIsm, GasOracleContractType, HooksConfig, HyperlaneAddressesMap, HyperlaneContractsMap, HyperlaneCore, HyperlaneCoreDeployer, - HyperlaneDeploymentArtifacts, HyperlaneIsmFactory, HyperlaneProxyFactoryDeployer, IgpConfig, IsmConfig, IsmType, - MultiProvider, MultisigConfig, RoutingIsmConfig, buildAgentConfig, @@ -27,20 +25,13 @@ import { multisigIsmVerificationCost, serializeContractsMap, } 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 { readIsmConfig } from '../config/ism.js'; import { readMultisigConfig } from '../config/multisig.js'; import { MINIMUM_CORE_DEPLOY_GAS } from '../consts.js'; -import { - getContext, - getDryRunContext, - getMergedContractAddresses, - sdkContractAddressesMap, -} from '../context.js'; +import { WriteCommandContext } from '../context/types.js'; import { log, logBlue, @@ -50,12 +41,7 @@ import { logRed, } from '../logger.js'; import { runMultiChainSelectionStep } from '../utils/chains.js'; -import { - getArtifactsFiles, - prepNewArtifactsFiles, - runFileSelectionStep, - writeJson, -} from '../utils/files.js'; +import { runFileSelectionStep, writeJson } from '../utils/files.js'; import { completeDeploy, @@ -65,62 +51,36 @@ import { runPreflightChecksForChains, } from './utils.js'; +const CONTRACT_CACHE_EXCLUSIONS = ['interchainGasPaymaster']; + /** * Executes the core deploy command. */ export async function runCoreDeploy({ - key, - chainConfigPath, + context, chains, ismConfigPath, hookConfigPath, - artifactsPath, - outPath, - skipConfirmation, - dryRun, + agentOutPath, }: { - key?: string; - chainConfigPath: string; + context: WriteCommandContext; chains?: ChainName[]; ismConfigPath?: string; hookConfigPath?: string; - artifactsPath?: string; - outPath: string; - skipConfirmation: boolean; - dryRun: string; + agentOutPath: string; }) { - const context = dryRun - ? await getDryRunContext({ - chainConfigPath, - 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; + const { chainMetadata, signer, dryRunChain, skipConfirmation } = context; + + if (dryRunChain) chains = [dryRunChain]; else if (!chains?.length) { if (skipConfirmation) throw new Error('No chains provided'); chains = await runMultiChainSelectionStep( - customChains, + chainMetadata, 'Select chains to connect:', true, ); } - const artifacts = await runArtifactStep( - chains, - skipConfirmation, - artifactsPath, - ); const result = await runIsmStep(chains, skipConfirmation, ismConfigPath); // we can either specify the full ISM config or just the multisig config const isIsmConfig = isISMConfig(result); @@ -131,16 +91,12 @@ export async function runCoreDeploy({ const hooksConfig = await runHookStep(chains, hookConfigPath); const deploymentParams: DeployParams = { + context, chains, - signer, - multiProvider, - artifacts, ismConfigs, multisigConfigs, hooksConfig, - outPath, - skipConfirmation, - dryRun, + agentOutPath, }; await runDeployPlanStep(deploymentParams); @@ -149,41 +105,13 @@ export async function runCoreDeploy({ minGas: MINIMUM_CORE_DEPLOY_GAS, }); - const userAddress = dryRun ? key! : await signer.getAddress(); + const userAddress = await signer.getAddress(); - const initialBalances = await prepareDeploy( - multiProvider, - userAddress, - chains, - ); + const initialBalances = await prepareDeploy(context, userAddress, chains); await executeDeploy(deploymentParams); - await completeDeploy( - 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, - }); + await completeDeploy(context, 'core', initialBalances, userAddress, chains); } async function runIsmStep( @@ -271,24 +199,16 @@ async function runHookStep( } interface DeployParams { + context: WriteCommandContext; chains: ChainName[]; - signer: ethers.Signer; - multiProvider: MultiProvider; - artifacts?: HyperlaneAddressesMap; ismConfigs?: ChainMap; multisigConfigs?: ChainMap; hooksConfig?: ChainMap; - outPath: string; - skipConfirmation: boolean; - dryRun: string; + agentOutPath: string; } -async function runDeployPlanStep({ - chains, - signer, - artifacts, - skipConfirmation, -}: DeployParams) { +async function runDeployPlanStep({ context, chains }: DeployParams) { + const { signer, skipConfirmation } = context; const address = await signer.getAddress(); logBlue('\nDeployment plan'); @@ -296,9 +216,7 @@ async function runDeployPlanStep({ log(`Transaction signer and owner of new contracts will be ${address}`); log(`Deploying to ${chains.join(', ')}`); log( - `There are several contracts required for each chain but contracts in the Hyperlane SDK ${ - artifacts ? 'or your artifacts ' : '' - }will be skipped`, + `There are several contracts required for each chain but contracts in your provided registries will be skipped`, ); if (skipConfirmation) return; @@ -309,36 +227,26 @@ async function runDeployPlanStep({ } async function executeDeploy({ + context, chains, - signer, - multiProvider, - outPath, - artifacts = {}, ismConfigs = {}, multisigConfigs = {}, hooksConfig = {}, - dryRun, + agentOutPath, }: DeployParams) { logBlue('All systems ready, captain! Beginning deployment...'); + const { signer, multiProvider, registry } = context; - const [contractsFilePath, agentFilePath] = prepNewArtifactsFiles( - outPath, - getArtifactsFiles( - [ - { filename: 'core-deployment', description: 'Contract addresses' }, - { filename: 'agent-config', description: 'Agent configs' }, - ], - dryRun, - ), - ); + let chainAddresses = await registry.getAddresses(); + chainAddresses = filterAddressesToCache(chainAddresses); const owner = await signer.getAddress(); - const mergedContractAddrs = getMergedContractAddresses(artifacts, chains); + let artifacts: HyperlaneAddressesMap = {}; // 1. Deploy ISM factories to all deployable chains that don't have them. logBlue('Deploying ISM factory contracts'); const ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider); - ismFactoryDeployer.cacheAddressesMap(mergedContractAddrs); + ismFactoryDeployer.cacheAddressesMap(chainAddresses); const ismFactoryConfig = chains.reduce((chainMap, curr) => { chainMap[curr] = {}; @@ -346,33 +254,32 @@ async function executeDeploy({ }, {} as ChainMap<{}>); const ismFactoryContracts = await ismFactoryDeployer.deploy(ismFactoryConfig); - artifacts = writeMergedAddresses( - contractsFilePath, - artifacts, + artifacts = await updateChainAddresses( + registry, ismFactoryContracts, + artifacts, ); + logGreen('ISM factory contracts deployed'); // Build an IsmFactory that covers all chains so that we can // use it to deploy ISMs to remote chains. const ismFactory = HyperlaneIsmFactory.fromAddressesMap( - mergedContractAddrs, + chainAddresses, multiProvider, ); // 3. Construct ISM configs for all deployable chains - const ismContracts: ChainMap<{ interchainSecurityModule: DeployedIsm }> = {}; const defaultIsms: ChainMap = {}; for (const ismOrigin of chains) { defaultIsms[ismOrigin] = ismConfigs[ismOrigin] ?? buildIsmConfig(owner, ismOrigin, chains, multisigConfigs); } - artifacts = writeMergedAddresses(contractsFilePath, artifacts, ismContracts); // 4. Deploy core contracts to chains logBlue(`Deploying core contracts to ${chains.join(', ')}`); const coreDeployer = new HyperlaneCoreDeployer(multiProvider, ismFactory); - coreDeployer.cacheAddressesMap(mergedContractAddrs as any); + coreDeployer.cacheAddressesMap(chainAddresses as any); const coreConfigs = buildCoreConfigMap( owner, chains, @@ -390,16 +297,27 @@ async function executeDeploy({ }; } artifacts = objMerge(artifacts, isms); - artifacts = writeMergedAddresses(contractsFilePath, artifacts, coreContracts); + artifacts = await updateChainAddresses(registry, coreContracts, artifacts); logGreen('✅ Core contracts deployed'); + log(JSON.stringify(artifacts, null, 2)); - log('Writing agent configs'); - await writeAgentConfig(agentFilePath, artifacts, chains, multiProvider); - logGreen('Agent configs written'); + await writeAgentConfig(context, artifacts, chains, agentOutPath); logBlue('Deployment is complete!'); - logBlue(`Contract address artifacts are in ${contractsFilePath}`); - logBlue(`Agent configs are in ${agentFilePath}`); +} + +function filterAddressesToCache(addressesMap: ChainMap) { + // 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( @@ -473,23 +391,40 @@ export function buildIgpConfigMap( return configMap; } -function writeMergedAddresses( - filePath: string, - aAddresses: HyperlaneAddressesMap, - bContracts: HyperlaneContractsMap, -): HyperlaneAddressesMap { - const bAddresses = serializeContractsMap(bContracts); - const mergedAddresses = objMerge(aAddresses, bAddresses); - writeJson(filePath, mergedAddresses); +async function updateChainAddresses( + registry: IRegistry, + newContracts: HyperlaneContractsMap, + otherAddresses: HyperlaneAddressesMap, +) { + let newAddresses = serializeContractsMap(newContracts); + // The HyperlaneCoreDeployer is returning a nested object with ISM addresses + // 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; } async function writeAgentConfig( - filePath: string, + context: WriteCommandContext, artifacts: HyperlaneAddressesMap, chains: ChainName[], - multiProvider: MultiProvider, + outPath: string, ) { + if (context.isDryRun) return; + log('Writing agent configs'); + const { multiProvider, registry } = context; const startBlocks: ChainMap = {}; const core = HyperlaneCore.fromAddressesMap(artifacts, multiProvider); @@ -498,22 +433,19 @@ async function writeAgentConfig( startBlocks[chain] = (await mailbox.deployedBlock()).toNumber(); } - const mergedAddressesMap = objMerge( - sdkContractAddressesMap, - artifacts, - ) as ChainMap; - + const chainAddresses = await registry.getAddresses(); for (const chain of chains) { - if (!mergedAddressesMap[chain].interchainGasPaymaster) { - mergedAddressesMap[chain].interchainGasPaymaster = + if (!chainAddresses[chain].interchainGasPaymaster) { + chainAddresses[chain].interchainGasPaymaster = ethers.constants.AddressZero; } } const agentConfig = buildAgentConfig( chains, // Use only the chains that were deployed to multiProvider, - mergedAddressesMap, + chainAddresses as any, startBlocks, ); - writeJson(filePath, agentConfig); + writeJson(outPath, agentConfig); + logGreen('Agent configs written'); } diff --git a/typescript/cli/src/deploy/dry-run.ts b/typescript/cli/src/deploy/dry-run.ts index 2bd1ece82e..5ece8aaf3e 100644 --- a/typescript/cli/src/deploy/dry-run.ts +++ b/typescript/cli/src/deploy/dry-run.ts @@ -6,7 +6,6 @@ import { setFork, } from '@hyperlane-xyz/sdk'; -import { Command } from '../commands/deploy.js'; import { logGray, logGreen, warnYellow } from '../logger.js'; import { ENV } from '../utils/env.js'; @@ -51,14 +50,14 @@ export async function verifyAnvil() { * @param error the thrown error * @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')) warnYellow( '⛔️ [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(); logGreen(`✅ ${toUpperCamelCase(command)} dry-run completed successfully`); diff --git a/typescript/cli/src/deploy/utils.ts b/typescript/cli/src/deploy/utils.ts index 3919aacca7..c6d18c2b9c 100644 --- a/typescript/cli/src/deploy/utils.ts +++ b/typescript/cli/src/deploy/utils.ts @@ -4,14 +4,13 @@ import { ChainMap, ChainName, IsmConfig, - MultiProvider, MultisigConfig, getLocalProvider, } from '@hyperlane-xyz/sdk'; import { Address, ProtocolType } from '@hyperlane-xyz/utils'; -import { Command } from '../commands/deploy.js'; import { parseIsmConfig } from '../config/ism.js'; +import { WriteCommandContext } from '../context/types.js'; import { log, logGreen, logPink } from '../logger.js'; import { assertGasBalances } from '../utils/balances.js'; import { ENV } from '../utils/env.js'; @@ -20,17 +19,15 @@ import { assertSigner } from '../utils/keys.js'; import { completeDryRun } from './dry-run.js'; export async function runPreflightChecks({ + context, origin, remotes, - signer, - multiProvider, minGas, chainsToGasCheck, }: { + context: WriteCommandContext; origin: ChainName; remotes: ChainName[]; - signer: ethers.Signer; - multiProvider: MultiProvider; minGas: string; chainsToGasCheck?: ChainName[]; }) { @@ -44,30 +41,28 @@ export async function runPreflightChecks({ logGreen('✅ Origin and remote are distinct'); return runPreflightChecksForChains({ + context, chains: [origin, ...remotes], - signer, - multiProvider, minGas, chainsToGasCheck, }); } export async function runPreflightChecksForChains({ + context, chains, - signer, - multiProvider, minGas, chainsToGasCheck, }: { + context: WriteCommandContext; chains: ChainName[]; - signer: ethers.Signer; - multiProvider: MultiProvider; minGas: string; // Chains for which to assert a native balance // Defaults to all chains if not specified chainsToGasCheck?: ChainName[]; }) { log('Running pre-flight checks for chains...'); + const { signer, multiProvider } = context; if (!chains?.length) throw new Error('Empty chain selection'); for (const chain of chains) { @@ -103,15 +98,15 @@ export function isZODISMConfig(filepath: string): boolean { } export async function prepareDeploy( - multiProvider: MultiProvider, + context: WriteCommandContext, userAddress: Address, chains: ChainName[], - dryRun: boolean = false, ): Promise> { + const { multiProvider, isDryRun } = context; const initialBalances: Record = {}; await Promise.all( chains.map(async (chain: ChainName) => { - const provider = dryRun + const provider = isDryRun ? getLocalProvider(ENV.ANVIL_IP_ADDR, ENV.ANVIL_PORT) : multiProvider.getProvider(chain); const currentBalance = await provider.getBalance(userAddress); @@ -122,31 +117,31 @@ export async function prepareDeploy( } export async function completeDeploy( - command: Command, + context: WriteCommandContext, + command: string, initialBalances: Record, - multiProvider: MultiProvider, userAddress: Address, chains: ChainName[], - dryRun: string, ) { + const { multiProvider, isDryRun } = context; if (chains.length > 0) logPink(`⛽️ Gas Usage Statistics`); for (const chain of chains) { - const provider = dryRun + const provider = isDryRun ? getLocalProvider(ENV.ANVIL_IP_ADDR, ENV.ANVIL_PORT) : multiProvider.getProvider(chain); const currentBalance = await provider.getBalance(userAddress); const balanceDelta = initialBalances[chain].sub(currentBalance); - if (dryRun && balanceDelta.lt(0)) break; + if (isDryRun && balanceDelta.lt(0)) break; logPink( `\t- Gas required for ${command} ${ - dryRun ? 'dry-run' : 'deploy' + isDryRun ? 'dry-run' : 'deploy' } on ${chain}: ${ethers.utils.formatEther(balanceDelta)} ${ multiProvider.getChainMetadata(chain).nativeToken?.symbol }`, ); } - if (dryRun) await completeDryRun(command); + if (isDryRun) await completeDryRun(command); } export function toUpperCamelCase(string: string) { diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 2b6b083111..ba6207a550 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -1,5 +1,4 @@ import { confirm, input } from '@inquirer/prompts'; -import { ethers } from 'ethers'; import { ChainMap, @@ -12,6 +11,7 @@ import { MinimalTokenMetadata, MultiProtocolProvider, MultiProvider, + RouterConfig, TOKEN_TYPE_TO_STANDARD, TokenConfig, TokenFactories, @@ -24,45 +24,25 @@ import { isNativeConfig, isSyntheticConfig, } from '@hyperlane-xyz/sdk'; -import { RouterConfig } from '@hyperlane-xyz/sdk'; -import { Address, ProtocolType, objMap } from '@hyperlane-xyz/utils'; +import { ProtocolType } from '@hyperlane-xyz/utils'; -import { Command } from '../commands/deploy.js'; import { readWarpRouteDeployConfig } from '../config/warp.js'; import { MINIMUM_WARP_DEPLOY_GAS } from '../consts.js'; -import { - getContext, - getDryRunContext, - getMergedContractAddresses, -} from '../context.js'; +import { WriteCommandContext } from '../context/types.js'; import { log, logBlue, logGray, logGreen } from '../logger.js'; -import { - getArtifactsFiles, - isFile, - prepNewArtifactsFiles, - runFileSelectionStep, - writeJson, -} from '../utils/files.js'; +import { isFile, runFileSelectionStep } from '../utils/files.js'; import { completeDeploy, prepareDeploy, runPreflightChecks } from './utils.js'; export async function runWarpRouteDeploy({ - key, - chainConfigPath, + context, warpRouteDeploymentConfigPath, - coreArtifactsPath, - outPath, - skipConfirmation, - dryRun, }: { - key?: string; - chainConfigPath: string; + context: WriteCommandContext; warpRouteDeploymentConfigPath?: string; - coreArtifactsPath?: string; - outPath: string; - skipConfirmation: boolean; - dryRun: string; }) { + const { signer, skipConfirmation } = context; + if ( !warpRouteDeploymentConfigPath || !isFile(warpRouteDeploymentConfigPath) @@ -83,36 +63,14 @@ export async function runWarpRouteDeploy({ 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({ + context, warpRouteConfig, - coreArtifacts, - multiProvider, - signer, - skipConfirmation, }); const deploymentParams = { + context, ...configs, - signer, - multiProvider, - outPath, - skipConfirmation, - dryRun, }; logBlue('Warp route deployment plan'); @@ -123,41 +81,26 @@ export async function runWarpRouteDeploy({ minGas: MINIMUM_WARP_DEPLOY_GAS, }); - const userAddress = dryRun ? key! : await signer.getAddress(); + const userAddress = await signer.getAddress(); const chains = [deploymentParams.origin, ...configs.remotes]; - const initialBalances = await prepareDeploy( - multiProvider, - userAddress, - chains, - ); + const initialBalances = await prepareDeploy(context, userAddress, chains); await executeDeploy(deploymentParams); - await completeDeploy( - Command.WARP, - initialBalances, - multiProvider, - userAddress, - chains, - dryRun, - ); + await completeDeploy(context, 'warp', initialBalances, userAddress, chains); } async function runBuildConfigStep({ + context, warpRouteConfig, - multiProvider, - signer, - coreArtifacts, - skipConfirmation, }: { + context: WriteCommandContext; warpRouteConfig: WarpRouteDeployConfig; - multiProvider: MultiProvider; - signer: ethers.Signer; - coreArtifacts?: HyperlaneContractsMap; - skipConfirmation: boolean; }) { + const { registry, signer, multiProvider, skipConfirmation } = context; log('Assembling token configs'); + const chainAddresses = await registry.getAddresses(); const owner = await signer.getAddress(); const requiredRouterFields: Array = ['mailbox']; const remotes: string[] = []; @@ -167,20 +110,16 @@ async function runBuildConfigStep({ /// @todo Remove this artifact when multi-collateral is enabled let baseChainName = ''; 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)) { - const mergedContractAddrs = getMergedContractAddresses( - coreArtifacts, - Object.keys(warpRouteConfig), - ); // the artifacts, and the SDK as a fallback config.owner = owner; - config.mailbox = config.mailbox || mergedContractAddrs[chain]?.mailbox; + config.mailbox = config.mailbox || chainAddresses[chain]?.mailbox; config.interchainSecurityModule = config.interchainSecurityModule || - mergedContractAddrs[chain]?.interchainSecurityModule || - mergedContractAddrs[chain]?.multisigIsm; - // config.ismFactory: mergedContractAddrs[baseChainName].domainRoutingIsmFactory, // TODO fix when updating from routingIsm + chainAddresses[chain]?.interchainSecurityModule || + chainAddresses[chain]?.multisigIsm; + // config.ismFactory: chainAddresses[baseChainName].domainRoutingIsmFactory, // TODO fix when updating from routingIsm if (isCollateralConfig(config) || isNativeConfig(config)) { // Store the base metadata @@ -232,24 +171,20 @@ async function runBuildConfigStep({ } interface DeployParams { + context: WriteCommandContext; configMap: WarpRouteDeployConfig; metadata: MinimalTokenMetadata; origin: ChainName; remotes: ChainName[]; - signer: ethers.Signer; - multiProvider: MultiProvider; - outPath: string; - skipConfirmation: boolean; - dryRun: string; } async function runDeployPlanStep({ + context, configMap, origin, remotes, - signer, - skipConfirmation, }: DeployParams) { + const { signer, skipConfirmation } = context; const address = await signer.getAddress(); const baseToken = configMap[origin]; @@ -273,27 +208,16 @@ async function runDeployPlanStep({ async function executeDeploy(params: DeployParams) { logBlue('All systems ready, captain! Beginning deployment...'); - const { configMap, multiProvider, outPath } = params; - - const [contractsFilePath, tokenConfigPath] = prepNewArtifactsFiles( - outPath, - getArtifactsFiles( - [ - { - filename: 'warp-route-deployment', - description: 'Contract addresses', - }, - { filename: 'warp-config', description: 'Warp config' }, - ], - params.dryRun, - ), - ); + const { + configMap, + context: { registry, multiProvider, isDryRun }, + } = params; const deployer = configMap.isNft ? new HypERC721Deployer(multiProvider) : new HypERC20Deployer(multiProvider); - const config = params.dryRun + const config = isDryRun ? { [params.origin]: configMap[params.origin] } : configMap; @@ -304,12 +228,10 @@ async function executeDeploy(params: DeployParams) { logGreen('✅ Hyp token deployments complete'); log('Writing deployment artifacts'); - writeTokenDeploymentArtifacts(contractsFilePath, deployedContracts, params); - writeWarpConfig(tokenConfigPath, deployedContracts, params); - + const warpCoreConfig = getWarpCoreConfig(params, deployedContracts); + await registry.addWarpRoute(warpCoreConfig); + log(JSON.stringify(warpCoreConfig, null, 2)); logBlue('Deployment is complete!'); - logBlue(`Contract address artifacts are in ${contractsFilePath}`); - logBlue(`Warp config is in ${tokenConfigPath}`); } async function fetchBaseTokenMetadata( @@ -344,28 +266,11 @@ async function fetchBaseTokenMetadata( function getTokenName(token: TokenConfig) { return token.type === TokenType.native ? 'native' : token.name; } -function writeTokenDeploymentArtifacts( - filePath: string, - contracts: HyperlaneContractsMap, - { 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( - filePath: string, - contracts: HyperlaneContractsMap, +function getWarpCoreConfig( { configMap, metadata }: DeployParams, -) { + contracts: HyperlaneContractsMap, +): WarpCoreConfig { const warpCoreConfig: WarpCoreConfig = { tokens: [] }; // First pass, create token configs @@ -406,5 +311,5 @@ function writeWarpConfig( } } - writeJson(filePath, warpCoreConfig); + return warpCoreConfig; } diff --git a/typescript/cli/src/hook/read.ts b/typescript/cli/src/hook/read.ts index b79cf4ae4c..5fdda67da8 100644 --- a/typescript/cli/src/hook/read.ts +++ b/typescript/cli/src/hook/read.ts @@ -1,48 +1,34 @@ import { ChainName, EvmHookReader } from '@hyperlane-xyz/sdk'; import { Address, ProtocolType, stringifyObject } from '@hyperlane-xyz/utils'; -import { readChainConfigsIfExists } from '../config/chain.js'; -import { getMultiProvider } from '../context.js'; +import { CommandContext } from '../context/types.js'; import { log, logBlue, logRed } from '../logger.js'; -import { - FileFormat, - resolveFileFormat, - writeFileAtPath, -} from '../utils/files.js'; +import { resolveFileFormat, writeFileAtPath } from '../utils/files.js'; /** * Read Hook config for a specified chain and address, logging or writing result to file. */ export async function readHookConfig({ + context, chain, address, - chainConfigPath, - format, - output, + out, }: { + context: CommandContext; chain: ChainName; address: Address; - chainConfigPath: string; - format: FileFormat; - output?: string; + out?: string; }): Promise { - const customChains = readChainConfigsIfExists(chainConfigPath); - const multiProvider = getMultiProvider(customChains); - - if (multiProvider.getProtocol(chain) === ProtocolType.Ethereum) { - const hookReader = new EvmHookReader(multiProvider, chain); + if (context.multiProvider.getProtocol(chain) === ProtocolType.Ethereum) { + const hookReader = new EvmHookReader(context.multiProvider, chain); const config = await hookReader.deriveHookConfig(address); - const stringConfig = stringifyObject( - config, - resolveFileFormat(output, format), - 2, - ); - if (!output) { + const stringConfig = stringifyObject(config, resolveFileFormat(out), 2); + if (!out) { logBlue(`Hook Config at ${address} on ${chain}:`); log(stringConfig); } else { - writeFileAtPath(output, stringConfig + '\n'); - logBlue(`Hook Config written to ${output}.`); + writeFileAtPath(out, stringConfig + '\n'); + logBlue(`Hook Config written to ${out}.`); } return; } diff --git a/typescript/cli/src/ism/read.ts b/typescript/cli/src/ism/read.ts index 4c378f7b91..ba17fd6052 100644 --- a/typescript/cli/src/ism/read.ts +++ b/typescript/cli/src/ism/read.ts @@ -1,48 +1,34 @@ import { ChainName, EvmIsmReader } from '@hyperlane-xyz/sdk'; import { Address, ProtocolType, stringifyObject } from '@hyperlane-xyz/utils'; -import { readChainConfigsIfExists } from '../config/chain.js'; -import { getMultiProvider } from '../context.js'; +import { CommandContext } from '../context/types.js'; import { log, logBlue, logRed } from '../logger.js'; -import { - FileFormat, - resolveFileFormat, - writeFileAtPath, -} from '../utils/files.js'; +import { resolveFileFormat, writeFileAtPath } from '../utils/files.js'; /** * Read ISM config for a specified chain and address, logging or writing result to file. */ export async function readIsmConfig({ + context, chain, address, - chainConfigPath, - format, - output, + out, }: { + context: CommandContext; chain: ChainName; address: Address; - chainConfigPath: string; - format: FileFormat; - output?: string; + out?: string; }): Promise { - const customChains = readChainConfigsIfExists(chainConfigPath); - const multiProvider = getMultiProvider(customChains); - - if (multiProvider.getProtocol(chain) === ProtocolType.Ethereum) { - const ismReader = new EvmIsmReader(multiProvider, chain); + if (context.multiProvider.getProtocol(chain) === ProtocolType.Ethereum) { + const ismReader = new EvmIsmReader(context.multiProvider, chain); const config = await ismReader.deriveIsmConfig(address); - const stringConfig = stringifyObject( - config, - resolveFileFormat(output, format), - 2, - ); - if (!output) { + const stringConfig = stringifyObject(config, resolveFileFormat(out), 2); + if (!out) { logBlue(`ISM Config at ${address} on ${chain}:`); log(stringConfig); } else { - writeFileAtPath(output, stringConfig + '\n'); - logBlue(`ISM Config written to ${output}.`); + writeFileAtPath(out, stringConfig + '\n'); + logBlue(`ISM Config written to ${out}.`); } return; } diff --git a/typescript/cli/src/registry/MergedRegistry.ts b/typescript/cli/src/registry/MergedRegistry.ts new file mode 100644 index 0000000000..fe6737cf83 --- /dev/null +++ b/typescript/cli/src/registry/MergedRegistry.ts @@ -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; + isDryRun?: boolean; + logger?: Logger; +} + +export class MergedRegistry extends BaseRegistry implements IRegistry { + public readonly type = RegistryType.Local; + public readonly registries: Array; + 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 { + const results = await this.multiRegistryRead((r) => + r.listRegistryContent(), + ); + return results.reduce((acc, content) => objMerge(acc, content), { + chains: {}, + deployments: {}, + }); + } + + async getChains(): Promise> { + return objKeys(await this.getMetadata); + } + + async getMetadata(): Promise> { + const results = await this.multiRegistryRead((r) => r.getMetadata()); + return results.reduce((acc, content) => objMerge(acc, content), {}); + } + + async getChainMetadata(chainName: ChainName): Promise { + return (await this.getMetadata())[chainName] || null; + } + + async getAddresses(): Promise> { + const results = await this.multiRegistryRead((r) => r.getAddresses()); + return results.reduce((acc, content) => objMerge(acc, content), {}); + } + + async getChainAddresses( + chainName: ChainName, + ): Promise { + return (await this.getAddresses())[chainName] || null; + } + + async addChain(chain: { + chainName: ChainName; + metadata?: ChainMetadata; + addresses?: ChainAddresses; + }): Promise { + return this.multiRegistryWrite( + async (registry) => await registry.addChain(chain), + `adding chain ${chain.chainName}`, + ); + } + + async updateChain(chain: { + chainName: ChainName; + metadata?: ChainMetadata; + addresses?: ChainAddresses; + }): Promise { + return this.multiRegistryWrite( + async (registry) => await registry.updateChain(chain), + `updating chain ${chain.chainName}`, + ); + } + + async removeChain(chain: ChainName): Promise { + return this.multiRegistryWrite( + async (registry) => await registry.removeChain(chain), + `removing chain ${chain}`, + ); + } + + async addWarpRoute(config: WarpCoreConfig): Promise { + return this.multiRegistryWrite( + async (registry) => await registry.addWarpRoute(config), + 'adding warp route', + ); + } + + protected multiRegistryRead( + readFn: (registry: IRegistry) => Promise | R, + ) { + return Promise.all(this.registries.map(readFn)); + } + + protected async multiRegistryWrite( + writeFn: (registry: IRegistry) => Promise, + logMsg: string, + ): Promise { + 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, + ); + } + } + } +} diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index eff47709e9..e48e712376 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -1,23 +1,16 @@ import { ethers } from 'ethers'; -import { - ChainName, - HyperlaneContractsMap, - HyperlaneCore, - MultiProvider, -} from '@hyperlane-xyz/sdk'; +import { ChainName, HyperlaneCore } from '@hyperlane-xyz/sdk'; import { addressToBytes32, timeout } from '@hyperlane-xyz/utils'; 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 { errorRed, log, logBlue, logGreen } from '../logger.js'; import { runSingleChainSelectionStep } from '../utils/chains.js'; export async function sendTestMessage({ - key, - chainConfigPath, - coreArtifactsPath, + context, origin, destination, messageBody, @@ -25,9 +18,7 @@ export async function sendTestMessage({ skipWaitForDelivery, selfRelay, }: { - key?: string; - chainConfigPath: string; - coreArtifactsPath?: string; + context: WriteCommandContext; origin?: ChainName; destination?: ChainName; messageBody: string; @@ -35,43 +26,36 @@ export async function sendTestMessage({ skipWaitForDelivery: boolean; selfRelay?: boolean; }) { - const { signer, multiProvider, customChains, coreArtifacts } = - await getContext({ - chainConfigPath, - coreConfig: { coreArtifactsPath }, - keyConfig: { key }, - }); + const { chainMetadata } = context; if (!origin) { origin = await runSingleChainSelectionStep( - customChains, + chainMetadata, 'Select the origin chain', ); } if (!destination) { destination = await runSingleChainSelectionStep( - customChains, + chainMetadata, 'Select the destination chain', ); } await runPreflightChecks({ + context, origin, remotes: [destination], - multiProvider, - signer, minGas: MINIMUM_TEST_SEND_GAS, chainsToGasCheck: [origin], }); await timeout( executeDelivery({ + context, origin, destination, messageBody, - multiProvider, - coreArtifacts, skipWaitForDelivery, selfRelay, }), @@ -81,30 +65,26 @@ export async function sendTestMessage({ } async function executeDelivery({ + context, origin, destination, messageBody, - multiProvider, - coreArtifacts, skipWaitForDelivery, selfRelay, }: { + context: CommandContext; origin: ChainName; destination: ChainName; messageBody: string; - multiProvider: MultiProvider; - coreArtifacts?: HyperlaneContractsMap; skipWaitForDelivery: boolean; selfRelay?: boolean; }) { - const mergedContractAddrs = getMergedContractAddresses(coreArtifacts); - const core = HyperlaneCore.fromAddressesMap( - mergedContractAddrs, - multiProvider, - ); + const { registry, multiProvider } = context; + const chainAddresses = await registry.getAddresses(); + const core = HyperlaneCore.fromAddressesMap(chainAddresses, multiProvider); const mailbox = core.getContracts(origin).mailbox; - let hook = mergedContractAddrs[origin]?.customHook; + let hook = chainAddresses[origin]?.customHook; if (hook) { logBlue(`Using custom hook ${hook} for ${origin} -> ${destination}`); } else { @@ -115,7 +95,7 @@ async function executeDelivery({ const destinationDomain = multiProvider.getDomainId(destination); let txReceipt: ethers.ContractReceipt; try { - const recipient = mergedContractAddrs[destination].testRecipient; + const recipient = chainAddresses[destination].testRecipient; if (!recipient) { throw new Error(`Unable to find TestRecipient for ${destination}`); } @@ -153,6 +133,7 @@ async function executeDelivery({ log(`Message: ${JSON.stringify(message)}`); if (selfRelay) { + log('Attempting self-relay of message'); await core.relayMessage(message); logGreen('Message was self-relayed!'); return; diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index 52289feb1f..161c37cb24 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -1,94 +1,78 @@ -import { select } from '@inquirer/prompts'; -import { ethers } from 'ethers'; - import { ChainName, - HyperlaneContractsMap, HyperlaneCore, MultiProtocolProvider, - MultiProvider, ProviderType, + Token, TokenAmount, WarpCore, WarpCoreConfig, } 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 { getContext, getMergedContractAddresses } from '../context.js'; +import { WriteCommandContext } from '../context/types.js'; import { runPreflightChecks } from '../deploy/utils.js'; import { logBlue, logGreen, logRed } from '../logger.js'; import { runSingleChainSelectionStep } from '../utils/chains.js'; +import { runTokenSelectionStep } from '../utils/tokens.js'; export async function sendTestTransfer({ - key, - chainConfigPath, - coreArtifactsPath, + context, warpConfigPath, origin, destination, - routerAddress, wei, recipient, timeoutSec, skipWaitForDelivery, selfRelay, }: { - key?: string; - chainConfigPath: string; - coreArtifactsPath?: string; + context: WriteCommandContext; warpConfigPath: string; origin?: ChainName; destination?: ChainName; - routerAddress?: Address; wei: string; recipient?: string; timeoutSec: number; skipWaitForDelivery: boolean; selfRelay?: boolean; }) { - const { signer, multiProvider, customChains, coreArtifacts, warpCoreConfig } = - await getContext({ - chainConfigPath, - coreConfig: { coreArtifactsPath }, - keyConfig: { key }, - warpConfig: { warpConfigPath }, - }); + const { chainMetadata } = context; + + const warpCoreConfig = readWarpRouteConfig(warpConfigPath); if (!origin) { origin = await runSingleChainSelectionStep( - customChains, + chainMetadata, 'Select the origin chain', ); } if (!destination) { destination = await runSingleChainSelectionStep( - customChains, + chainMetadata, 'Select the destination chain', ); } await runPreflightChecks({ + context, origin, remotes: [destination], - multiProvider, - signer, minGas: MINIMUM_TEST_SEND_GAS, chainsToGasCheck: [origin], }); await timeout( executeDelivery({ + context, origin, destination, warpCoreConfig, - routerAddress, wei, recipient, - signer, - multiProvider, - coreArtifacts, skipWaitForDelivery, selfRelay, }), @@ -98,39 +82,32 @@ export async function sendTestTransfer({ } async function executeDelivery({ + context, origin, destination, warpCoreConfig, - routerAddress, wei, recipient, - multiProvider, - signer, - coreArtifacts, skipWaitForDelivery, selfRelay, }: { + context: WriteCommandContext; origin: ChainName; destination: ChainName; warpCoreConfig: WarpCoreConfig; - routerAddress?: Address; wei: string; recipient?: string; - multiProvider: MultiProvider; - signer: ethers.Signer; - coreArtifacts?: HyperlaneContractsMap; skipWaitForDelivery: boolean; selfRelay?: boolean; }) { + const { signer, multiProvider, registry } = context; + const signerAddress = await signer.getAddress(); recipient ||= signerAddress; - const mergedContractAddrs = getMergedContractAddresses(coreArtifacts); + const chainAddresses = await registry.getAddresses(); - const core = HyperlaneCore.fromAddressesMap( - mergedContractAddrs, - multiProvider, - ); + const core = HyperlaneCore.fromAddressesMap(chainAddresses, multiProvider); const provider = multiProvider.getProvider(origin); const connectedSigner = signer.connect(provider); @@ -140,31 +117,17 @@ async function executeDelivery({ warpCoreConfig, ); - if (!routerAddress) { - const tokensForRoute = warpCore.getTokensForRoute(origin, destination); - if (tokensForRoute.length === 0) { - 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}`, - ); + let token: Token; + const tokensForRoute = warpCore.getTokensForRoute(origin, destination); + if (tokensForRoute.length === 0) { + logRed(`No Warp Routes found from ${origin} to ${destination}`); 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(); diff --git a/typescript/cli/src/status/message.ts b/typescript/cli/src/status/message.ts index 161d0465e2..77118b6de0 100644 --- a/typescript/cli/src/status/message.ts +++ b/typescript/cli/src/status/message.ts @@ -2,38 +2,26 @@ import { input } from '@inquirer/prompts'; 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 { runSingleChainSelectionStep } from '../utils/chains.js'; export async function checkMessageStatus({ - chainConfigPath, - coreArtifactsPath, + context, messageId, destination, origin, selfRelay, - key, }: { - chainConfigPath: string; - coreArtifactsPath?: string; + context: CommandContext; messageId?: string; destination?: ChainName; origin?: ChainName; selfRelay?: boolean; - key?: string; }) { - const keyConfig = selfRelay ? { key } : undefined; - - const { multiProvider, customChains, coreArtifacts } = await getContext({ - chainConfigPath, - coreConfig: { coreArtifactsPath }, - keyConfig, - }); - if (!destination) { destination = await runSingleChainSelectionStep( - customChains, + context.chainMetadata, '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( - mergedContractAddrs, - multiProvider, + chainAddresses, + context.multiProvider, ); const mailbox = core.getContracts(destination).mailbox; log(`Checking status of message ${messageId} on ${destination}`); @@ -62,7 +50,7 @@ export async function checkMessageStatus({ // TODO: implement option for tx receipt input if (!origin) { origin = await runSingleChainSelectionStep( - customChains, + context.chainMetadata, 'Select the origin chain', ); } diff --git a/typescript/cli/src/utils/chains.ts b/typescript/cli/src/utils/chains.ts index 0e61586d1d..e3eeecfaeb 100644 --- a/typescript/cli/src/utils/chains.ts +++ b/typescript/cli/src/utils/chains.ts @@ -2,24 +2,19 @@ import { Separator, checkbox } from '@inquirer/prompts'; import select from '@inquirer/select'; import chalk from 'chalk'; -import { - ChainMap, - ChainMetadata, - mainnetChainsMetadata, - testnetChainsMetadata, -} from '@hyperlane-xyz/sdk'; +import { ChainMap, ChainMetadata } 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 new chain in the list const NEW_CHAIN_MARKER = '__new__'; export async function runSingleChainSelectionStep( - customChains: ChainMap, + chainMetadata: ChainMap, message = 'Select chain', ) { - const choices = getChainChoices(customChains); + const choices = getChainChoices(chainMetadata); const chain = (await select({ message, choices, @@ -30,11 +25,11 @@ export async function runSingleChainSelectionStep( } export async function runMultiChainSelectionStep( - customChains: ChainMap, + chainMetadata: ChainMap, message = 'Select chains', requireMultiple = false, ) { - const choices = getChainChoices(customChains); + const choices = getChainChoices(chainMetadata); while (true) { logTip('Use SPACE key to select chains, then press ENTER'); const chains = (await checkbox({ @@ -51,26 +46,25 @@ export async function runMultiChainSelectionStep( } } -function getChainChoices(customChains: ChainMap) { +function getChainChoices(chainMetadata: ChainMap) { const chainsToChoices = (chains: ChainMetadata[]) => 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['0']['choices'] = [ - new Separator('--Custom Chains--'), - ...chainsToChoices(Object.values(customChains)), { name: '(New custom chain)', value: NEW_CHAIN_MARKER }, new Separator('--Mainnet Chains--'), - ...chainsToChoices(mainnetChainsMetadata), + ...chainsToChoices(mainnetChains), new Separator('--Testnet Chains--'), - ...chainsToChoices(testnetChainsMetadata), + ...chainsToChoices(testnetChains), ]; return choices; } function handleNewChain(chainNames: string[]) { if (chainNames.includes(NEW_CHAIN_MARKER)) { - logBlue( - 'To use a new chain, use the --config argument add them to that file', - ); log( chalk.blue('Use the'), chalk.magentaBright('hyperlane config create'), diff --git a/typescript/cli/src/utils/files.ts b/typescript/cli/src/utils/files.ts index 276eb84b2a..9734b34885 100644 --- a/typescript/cli/src/utils/files.ts +++ b/typescript/cli/src/utils/files.ts @@ -6,9 +6,7 @@ import { parse as yamlParse, stringify as yamlStringify } from 'yaml'; import { objMerge } from '@hyperlane-xyz/utils'; -import { log, logBlue } from '../logger.js'; - -import { getTimestampForFilename } from './time.js'; +import { log } from '../logger.js'; export type FileFormat = 'yaml' | 'json'; @@ -118,7 +116,7 @@ export function writeYamlOrJson( export function mergeYamlOrJson( filepath: string, obj: Record, - format?: FileFormat, + format: FileFormat = 'yaml', ) { return resolveYamlOrJsonFn( filepath, @@ -170,38 +168,6 @@ export function resolveFileFormat( return undefined; } -export function prepNewArtifactsFiles( - outPath: string, - files: Array, -) { - 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 { - if (dryRun) - defaultFiles.map((defaultFile: ArtifactsFile) => { - defaultFile.filename = `dry-run_${defaultFile.filename}`; - }); - return defaultFiles; -} - export async function runFileSelectionStep( folderPath: string, description: string, diff --git a/typescript/cli/src/utils/keys.ts b/typescript/cli/src/utils/keys.ts index 7f5b6f6b05..7e8d68dc85 100644 --- a/typescript/cli/src/utils/keys.ts +++ b/typescript/cli/src/utils/keys.ts @@ -4,8 +4,6 @@ import { ethers, providers } from 'ethers'; import { impersonateAccount } from '@hyperlane-xyz/sdk'; import { Address, ensure0x } from '@hyperlane-xyz/utils'; -import { ContextSettings, KeyConfig } from '../context.js'; - const ETHEREUM_ADDRESS_LENGTH = 42; const DEFAULT_KEY_TYPE = 'private key'; const IMPERSONATED_KEY_TYPE = 'address'; @@ -14,34 +12,32 @@ const IMPERSONATED_KEY_TYPE = 'address'; * Retrieves a signer for the current command-context. * @returns the signer */ -export async function getSigner

({ - keyConfig, +export async function getSigner({ + key, skipConfirmation, -}: P): Promise { - if (!keyConfig) return undefined; - - const key = await retrieveKey(DEFAULT_KEY_TYPE, keyConfig, skipConfirmation); - - return privateKeyToSigner(key); +}: { + key?: string; + skipConfirmation?: boolean; +}) { + key ||= await retrieveKey(DEFAULT_KEY_TYPE, skipConfirmation); + const signer = privateKeyToSigner(key); + return { key, signer }; } /** * Retrieves an impersonated signer for the current command-context. * @returns the impersonated signer */ -export async function getImpersonatedSigner

({ - keyConfig, +export async function getImpersonatedSigner({ + key, skipConfirmation, -}: P): Promise { - if (!keyConfig) return undefined; - - const key = await retrieveKey( - IMPERSONATED_KEY_TYPE, - keyConfig, - skipConfirmation, - ); - - return await addressToImpersonatedSigner(key); +}: { + key?: string; + skipConfirmation?: boolean; +}) { + key ||= await retrieveKey(IMPERSONATED_KEY_TYPE, skipConfirmation); + const signer = await addressToImpersonatedSigner(key); + return { key, signer }; } /** @@ -91,15 +87,11 @@ function privateKeyToSigner(key: string): ethers.Wallet { async function retrieveKey( keyType: string, - keyConfig: KeyConfig, skipConfirmation: boolean | undefined, ): Promise { - if (keyConfig.key) return keyConfig.key; - else if (skipConfirmation) throw new Error(`No ${keyType} provided`); + if (skipConfirmation) throw new Error(`No ${keyType} provided`); else return await input({ - message: - keyConfig.promptMessage || - `Please enter ${keyType} or use the HYP_KEY environment variable.`, + message: `Please enter ${keyType} or use the HYP_KEY environment variable.`, }); } diff --git a/typescript/cli/src/utils/tokens.ts b/typescript/cli/src/utils/tokens.ts new file mode 100644 index 0000000000..ed876238cd --- /dev/null +++ b/typescript/cli/src/utils/tokens.ts @@ -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; +} diff --git a/typescript/cli/src/utils/version-check.ts b/typescript/cli/src/utils/version-check.ts index 3f75574deb..9b240fa418 100644 --- a/typescript/cli/src/utils/version-check.ts +++ b/typescript/cli/src/utils/version-check.ts @@ -4,7 +4,12 @@ import { log } from '../logger.js'; import { VERSION } from '../version.js'; 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'); + process.argv = argv; if (VERSION < currentVersion) { log(`Your CLI version: ${VERSION}, latest version: ${currentVersion}`); } diff --git a/typescript/cli/test-configs/anvil/chains/anvil1/metadata.yaml b/typescript/cli/test-configs/anvil/chains/anvil1/metadata.yaml new file mode 100644 index 0000000000..50e1aa5d55 --- /dev/null +++ b/typescript/cli/test-configs/anvil/chains/anvil1/metadata.yaml @@ -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 \ No newline at end of file diff --git a/typescript/cli/test-configs/anvil/chains/anvil2/metadata.yaml b/typescript/cli/test-configs/anvil/chains/anvil2/metadata.yaml new file mode 100644 index 0000000000..68ed64bfe7 --- /dev/null +++ b/typescript/cli/test-configs/anvil/chains/anvil2/metadata.yaml @@ -0,0 +1,7 @@ +--- +chainId: 31338 +domainId: 31338 +name: anvil2 +protocol: ethereum +rpcUrls: + - http: http://127.0.0.1:8555 diff --git a/typescript/cli/test-configs/dry-run/chains/alfajores/metadata.yaml b/typescript/cli/test-configs/dry-run/chains/alfajores/metadata.yaml new file mode 100644 index 0000000000..a66f02c82f --- /dev/null +++ b/typescript/cli/test-configs/dry-run/chains/alfajores/metadata.yaml @@ -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 \ No newline at end of file diff --git a/typescript/cli/test-configs/dry-run/chains/anvil/metadata.yaml b/typescript/cli/test-configs/dry-run/chains/anvil/metadata.yaml new file mode 100644 index 0000000000..2836513cc6 --- /dev/null +++ b/typescript/cli/test-configs/dry-run/chains/anvil/metadata.yaml @@ -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 \ No newline at end of file diff --git a/typescript/cli/test-configs/dry-run/chains/fuji/metadata.yaml b/typescript/cli/test-configs/dry-run/chains/fuji/metadata.yaml new file mode 100644 index 0000000000..c19460ae28 --- /dev/null +++ b/typescript/cli/test-configs/dry-run/chains/fuji/metadata.yaml @@ -0,0 +1,6 @@ +chainId: 43113 +domainId: 43113 +name: fuji +protocol: ethereum +rpcUrls: + - http: https://api.avax-test.network/ext/bc/C/rpc \ No newline at end of file diff --git a/typescript/cli/examples/dry-run/ism.yaml b/typescript/cli/test-configs/dry-run/ism.yaml similarity index 100% rename from typescript/cli/examples/dry-run/ism.yaml rename to typescript/cli/test-configs/dry-run/ism.yaml diff --git a/typescript/cli/examples/dry-run/warp-route-deployment.yaml b/typescript/cli/test-configs/dry-run/warp-route-deployment.yaml similarity index 100% rename from typescript/cli/examples/dry-run/warp-route-deployment.yaml rename to typescript/cli/test-configs/dry-run/warp-route-deployment.yaml diff --git a/typescript/cli/test-configs/fork/chains/anvil1/metadata.yaml b/typescript/cli/test-configs/fork/chains/anvil1/metadata.yaml new file mode 100644 index 0000000000..197c0b783f --- /dev/null +++ b/typescript/cli/test-configs/fork/chains/anvil1/metadata.yaml @@ -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 \ No newline at end of file diff --git a/typescript/cli/test-configs/fork/chains/ethereum/metadata.yaml b/typescript/cli/test-configs/fork/chains/ethereum/metadata.yaml new file mode 100644 index 0000000000..d7155bfc03 --- /dev/null +++ b/typescript/cli/test-configs/fork/chains/ethereum/metadata.yaml @@ -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 \ No newline at end of file diff --git a/typescript/cli/examples/fork/ism.yaml b/typescript/cli/test-configs/fork/ism.yaml similarity index 97% rename from typescript/cli/examples/fork/ism.yaml rename to typescript/cli/test-configs/fork/ism.yaml index f5eab57405..fc85a3bb0c 100644 --- a/typescript/cli/examples/fork/ism.yaml +++ b/typescript/cli/test-configs/fork/ism.yaml @@ -1,9 +1,8 @@ # 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 # - --- -anvil: +anvil1: threshold: 1 # Number: Signatures required to approve a message validators: # Array: List of validator addresses - '0xa0ee7a142d267c1f36714e4a8f75612f20a79720' diff --git a/typescript/cli/examples/fork/warp-route-deployment.yaml b/typescript/cli/test-configs/fork/warp-route-deployment.yaml similarity index 99% rename from typescript/cli/examples/fork/warp-route-deployment.yaml rename to typescript/cli/test-configs/fork/warp-route-deployment.yaml index 7aa1ff621a..845ce5247d 100644 --- a/typescript/cli/examples/fork/warp-route-deployment.yaml +++ b/typescript/cli/test-configs/fork/warp-route-deployment.yaml @@ -10,7 +10,7 @@ # fastCollateral # fastSynthetic --- -anvil: +anvil1: type: native # token: "0x123" # Collateral/vault address. Required for collateral types # owner: "0x123" # Optional owner address for synthetic token diff --git a/typescript/helloworld/package.json b/typescript/helloworld/package.json index 1805b5f33f..3834296e41 100644 --- a/typescript/helloworld/package.json +++ b/typescript/helloworld/package.json @@ -4,6 +4,7 @@ "version": "3.10.0", "dependencies": { "@hyperlane-xyz/core": "3.10.0", + "@hyperlane-xyz/registry": "^1.0.7", "@hyperlane-xyz/sdk": "3.10.0", "@openzeppelin/contracts-upgradeable": "^4.9.3", "ethers": "^5.7.2" diff --git a/typescript/helloworld/src/deploy/config.ts b/typescript/helloworld/src/deploy/config.ts index d678e91aa6..48b082c5d6 100644 --- a/typescript/helloworld/src/deploy/config.ts +++ b/typescript/helloworld/src/deploy/config.ts @@ -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; -// SET DESIRED NETWORKS HERE +// SET DESIRED NETWORKS HERE OR USE THE DEFAULT SET FROM THE REGISTRY export const prodConfigs = { - alfajores: chainMetadata.alfajores, + ...chainMetadata, }; diff --git a/typescript/helloworld/src/scripts/check.ts b/typescript/helloworld/src/scripts/check.ts index 2ee8efb621..adf58f0086 100644 --- a/typescript/helloworld/src/scripts/check.ts +++ b/typescript/helloworld/src/scripts/check.ts @@ -1,8 +1,11 @@ +import { chainAddresses } from '@hyperlane-xyz/registry'; import { + ChainMap, HyperlaneCore, MultiProvider, attachContractsMap, } from '@hyperlane-xyz/sdk'; +import type { Address } from '@hyperlane-xyz/utils'; import { HelloWorldApp } from '../app/app.js'; import { helloWorldFactories } from '../app/contracts.js'; @@ -10,7 +13,7 @@ import { HelloWorldChecker } from '../deploy/check.js'; import { prodConfigs } from '../deploy/config.js'; // COPY FROM OUTPUT OF DEPLOYMENT SCRIPT OR IMPORT FROM ELSEWHERE -const deploymentAddresses = {}; +const deploymentAddresses: ChainMap> = {}; // SET CONTRACT OWNER ADDRESS HERE const ownerAddress = '0x123...'; @@ -24,7 +27,9 @@ async function check() { 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 config = core.getRouterConfig(ownerAddress); diff --git a/typescript/helloworld/src/scripts/deploy.ts b/typescript/helloworld/src/scripts/deploy.ts index c891ab060e..8bdd9fde47 100644 --- a/typescript/helloworld/src/scripts/deploy.ts +++ b/typescript/helloworld/src/scripts/deploy.ts @@ -1,5 +1,6 @@ import { Wallet } from 'ethers'; +import { chainAddresses } from '@hyperlane-xyz/registry'; import { HyperlaneCore, MultiProvider, @@ -17,7 +18,9 @@ async function main() { const multiProvider = new MultiProvider(prodConfigs); 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 deployer = new HelloWorldDeployer(multiProvider); diff --git a/typescript/helloworld/src/test/helloworld.test.ts b/typescript/helloworld/src/test/helloworld.test.ts index 7fde27c17f..3ea6d58097 100644 --- a/typescript/helloworld/src/test/helloworld.test.ts +++ b/typescript/helloworld/src/test/helloworld.test.ts @@ -4,10 +4,10 @@ import hre from 'hardhat'; import { ChainMap, - Chains, HyperlaneIsmFactory, HyperlaneProxyFactoryDeployer, MultiProvider, + TestChainName, TestCoreApp, TestCoreDeployer, } from '@hyperlane-xyz/sdk'; @@ -17,8 +17,8 @@ import { HelloWorldDeployer } from '../deploy/deploy.js'; import { HelloWorld } from '../types/index.js'; describe('HelloWorld', async () => { - const localChain = Chains.test1; - const remoteChain = Chains.test2; + const localChain = TestChainName.test1; + const remoteChain = TestChainName.test2; let localDomain: number; let remoteDomain: number; diff --git a/typescript/infra/README.md b/typescript/infra/README.md new file mode 100644 index 0000000000..c501b102e3 --- /dev/null +++ b/typescript/infra/README.md @@ -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 diff --git a/typescript/infra/config/environments/mainnet3/agent.ts b/typescript/infra/config/environments/mainnet3/agent.ts index fc5ef2ea01..8d1c9e2102 100644 --- a/typescript/infra/config/environments/mainnet3/agent.ts +++ b/typescript/infra/config/environments/mainnet3/agent.ts @@ -1,10 +1,7 @@ import { - Chains, GasPaymentEnforcement, GasPaymentEnforcementPolicyType, RpcConsensusType, - chainMetadata, - getDomainId, } from '@hyperlane-xyz/sdk'; import { @@ -19,8 +16,9 @@ import { import { ALL_KEY_ROLES, Role } from '../../../src/roles.js'; import { Contexts } from '../../contexts.js'; -import { environment, supportedChainNames } from './chains.js'; +import { environment } from './chains.js'; import { helloWorld } from './helloworld.js'; +import { supportedChainNames } from './supportedChainNames.js'; import { validatorChainConfig } from './validators.js'; import ancient8EthereumUsdcAddresses from './warp/ancient8-USDC-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 = { // Generally, we run all production validators in the Hyperlane context. [Role.Validator]: { - [Chains.arbitrum]: true, - [Chains.ancient8]: true, - [Chains.avalanche]: true, - [Chains.base]: true, - [Chains.blast]: true, - [Chains.bsc]: true, - [Chains.celo]: true, - [Chains.ethereum]: true, - [Chains.neutron]: true, - [Chains.mantapacific]: true, - [Chains.mode]: true, - [Chains.moonbeam]: true, - [Chains.optimism]: true, - [Chains.polygon]: true, - [Chains.gnosis]: true, - [Chains.scroll]: true, - [Chains.polygonzkevm]: true, - [Chains.injective]: true, - [Chains.inevm]: true, - [Chains.viction]: true, + arbitrum: true, + ancient8: true, + avalanche: true, + base: true, + blast: true, + bsc: true, + celo: true, + ethereum: true, + neutron: true, + mantapacific: true, + mode: true, + moonbeam: true, + optimism: true, + polygon: true, + gnosis: true, + scroll: true, + polygonzkevm: true, + injective: true, + inevm: true, + viction: true, }, [Role.Relayer]: { - [Chains.arbitrum]: true, - [Chains.ancient8]: true, - [Chains.avalanche]: true, - [Chains.base]: true, - [Chains.blast]: true, - [Chains.bsc]: true, - [Chains.celo]: true, - [Chains.ethereum]: true, + arbitrum: true, + ancient8: true, + avalanche: true, + base: true, + blast: true, + bsc: true, + celo: true, + ethereum: true, // At the moment, we only relay between Neutron and Manta Pacific on the neutron context. - [Chains.neutron]: false, - [Chains.mantapacific]: true, - [Chains.mode]: true, - [Chains.moonbeam]: true, - [Chains.optimism]: true, - [Chains.polygon]: true, - [Chains.gnosis]: true, - [Chains.scroll]: true, - [Chains.polygonzkevm]: true, - [Chains.injective]: true, - [Chains.inevm]: true, - [Chains.viction]: true, + neutron: false, + mantapacific: false, + mode: true, + moonbeam: true, + optimism: true, + polygon: true, + gnosis: true, + scroll: true, + polygonzkevm: true, + injective: true, + inevm: true, + viction: true, }, [Role.Scraper]: { - [Chains.arbitrum]: true, - [Chains.ancient8]: true, - [Chains.avalanche]: true, - [Chains.base]: true, - [Chains.blast]: true, - [Chains.bsc]: true, - [Chains.celo]: true, - [Chains.ethereum]: true, + arbitrum: true, + ancient8: true, + avalanche: true, + base: true, + blast: true, + bsc: true, + celo: true, + ethereum: true, // Cannot scrape non-EVM chains - [Chains.neutron]: false, - [Chains.mantapacific]: true, - [Chains.mode]: true, - [Chains.moonbeam]: true, - [Chains.optimism]: true, - [Chains.polygon]: true, - [Chains.gnosis]: true, - [Chains.scroll]: true, - [Chains.polygonzkevm]: true, + neutron: false, + mantapacific: true, + mode: true, + moonbeam: true, + optimism: true, + polygon: true, + gnosis: true, + scroll: true, + polygonzkevm: true, // Cannot scrape non-EVM chains - [Chains.injective]: false, - [Chains.inevm]: true, + injective: false, + inevm: true, // Has RPC non-compliance that breaks scraping. - [Chains.viction]: false, + viction: false, }, }; @@ -257,11 +255,7 @@ const neutron: RootAgentConfig = { ...contextBase, contextChainNames: { validator: [], - relayer: [ - chainMetadata.neutron.name, - chainMetadata.mantapacific.name, - chainMetadata.arbitrum.name, - ], + relayer: ['neutron', 'mantapacific', 'arbitrum'], scraper: [], }, context: Contexts.Neutron, diff --git a/typescript/infra/config/environments/mainnet3/chains.ts b/typescript/infra/config/environments/mainnet3/chains.ts index bd3604bc87..29f33e9365 100644 --- a/typescript/infra/config/environments/mainnet3/chains.ts +++ b/typescript/infra/config/environments/mainnet3/chains.ts @@ -1,17 +1,11 @@ -import { - ChainMap, - ChainMetadata, - Mainnets, - chainMetadata, -} from '@hyperlane-xyz/sdk'; +import { ChainMap, ChainMetadata } from '@hyperlane-xyz/sdk'; +import { objKeys } from '@hyperlane-xyz/utils'; 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. -// These chains may be any protocol type. -export const supportedChainNames = Mainnets; +import { supportedChainNames } from './supportedChainNames.js'; -export type MainnetChains = (typeof supportedChainNames)[number]; export const environment = 'mainnet3'; const { @@ -22,15 +16,15 @@ const { export const ethereumMainnetConfigs: ChainMap = { ...defaultEthereumMainnetConfigs, bsc: { - ...chainMetadata.bsc, + ...getChain('bsc'), transactionOverrides: { gasPrice: 3 * 10 ** 9, // 3 gwei }, }, polygon: { - ...chainMetadata.polygon, + ...getChain('polygon'), blocks: { - ...chainMetadata.polygon.blocks, + ...getChain('polygon').blocks, confirmations: 3, }, transactionOverrides: { @@ -41,9 +35,9 @@ export const ethereumMainnetConfigs: ChainMap = { }, }, ethereum: { - ...chainMetadata.ethereum, + ...getChain('ethereum'), blocks: { - ...chainMetadata.ethereum.blocks, + ...getChain('ethereum').blocks, confirmations: 3, }, transactionOverrides: { @@ -52,7 +46,7 @@ export const ethereumMainnetConfigs: ChainMap = { }, }, scroll: { - ...chainMetadata.scroll, + ...getChain('scroll'), transactionOverrides: { // 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 - @@ -61,7 +55,7 @@ export const ethereumMainnetConfigs: ChainMap = { }, }, moonbeam: { - ...chainMetadata.moonbeam, + ...getChain('moonbeam'), transactionOverrides: { maxFeePerGas: 350 * 10 ** 9, // 350 gwei maxPriorityFeePerGas: 50 * 10 ** 9, // 50 gwei @@ -74,6 +68,4 @@ export const mainnetConfigs: ChainMap = { ...nonEthereumMainnetConfigs, }; -export const ethereumChainNames = Object.keys( - ethereumMainnetConfigs, -) as MainnetChains[]; +export const ethereumChainNames = objKeys(ethereumMainnetConfigs); diff --git a/typescript/infra/config/environments/mainnet3/core.ts b/typescript/infra/config/environments/mainnet3/core.ts index dce9e1fd08..258b37c1c8 100644 --- a/typescript/infra/config/environments/mainnet3/core.ts +++ b/typescript/infra/config/environments/mainnet3/core.ts @@ -18,15 +18,18 @@ import { RoutingIsmConfig, defaultMultisigConfigs, } 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 { DEPLOYER, owners } from './owners.js'; +import { supportedChainNames } from './supportedChainNames.js'; export const core: ChainMap = objMap(owners, (local, owner) => { const originMultisigs: ChainMap = Object.fromEntries( supportedChainNames + .filter((chain) => getChain(chain).protocol === ProtocolType.Ethereum) .filter((chain) => chain !== local) .map((origin) => [origin, defaultMultisigConfigs[origin]]), ); diff --git a/typescript/infra/config/environments/mainnet3/igp.ts b/typescript/infra/config/environments/mainnet3/igp.ts index 4a537fd6f9..3643ca76a1 100644 --- a/typescript/infra/config/environments/mainnet3/igp.ts +++ b/typescript/infra/config/environments/mainnet3/igp.ts @@ -16,20 +16,17 @@ import { getTokenExchangeRateFromValues, } from '../../../src/config/gas-oracle.js'; -import { - MainnetChains, - ethereumChainNames, - supportedChainNames, -} from './chains.js'; +import { ethereumChainNames } from './chains.js'; import gasPrices from './gasPrices.json'; import { DEPLOYER, owners } from './owners.js'; +import { supportedChainNames } from './supportedChainNames.js'; import rawTokenPrices from './tokenPrices.json'; const tokenPrices: ChainMap = rawTokenPrices; const FOREIGN_DEFAULT_OVERHEAD = 600_000; // cosmwasm warp route somewhat arbitrarily chosen -const remoteOverhead = (remote: MainnetChains) => +const remoteOverhead = (remote: ChainName) => ethereumChainNames.includes(remote) ? multisigIsmVerificationCost( defaultMultisigConfigs[remote].threshold, @@ -53,11 +50,11 @@ function getTokenExchangeRate(local: ChainName, remote: ChainName): BigNumber { const storageGasOracleConfig: AllStorageGasOracleConfigs = getAllStorageGasOracleConfigs( - supportedChainNames, + ethereumChainNames, gasPrices, getTokenExchangeRate, (local) => parseFloat(tokenPrices[local]), - (local) => remoteOverhead(local as MainnetChains), + (local) => remoteOverhead(local), ); export const igp: ChainMap = objMap(owners, (local, owner) => ({ @@ -72,7 +69,7 @@ export const igp: ChainMap = objMap(owners, (local, owner) => ({ overhead: Object.fromEntries( exclude(local, supportedChainNames).map((remote) => [ remote, - remoteOverhead(remote as MainnetChains), + remoteOverhead(remote), ]), ), oracleConfig: storageGasOracleConfig[local], diff --git a/typescript/infra/config/environments/mainnet3/liquidityLayer.ts b/typescript/infra/config/environments/mainnet3/liquidityLayer.ts index 98847a8d5a..e4aec1e77d 100644 --- a/typescript/infra/config/environments/mainnet3/liquidityLayer.ts +++ b/typescript/infra/config/environments/mainnet3/liquidityLayer.ts @@ -2,29 +2,27 @@ import { BridgeAdapterConfig, BridgeAdapterType, ChainMap, - Chains, RpcConsensusType, - chainMetadata, - getDomainId, } from '@hyperlane-xyz/sdk'; import { LiquidityLayerRelayerConfig } from '../../../src/config/middleware.js'; +import { getDomainId } from '../../registry.js'; import { environment } from './chains.js'; const circleDomainMapping = [ { - hyperlaneDomain: getDomainId(chainMetadata[Chains.ethereum]), + hyperlaneDomain: getDomainId('ethereum'), circleDomain: 0, }, { - hyperlaneDomain: getDomainId(chainMetadata[Chains.avalanche]), + hyperlaneDomain: getDomainId('avalanche'), circleDomain: 1, }, ]; export const bridgeAdapterConfigs: ChainMap = { - [Chains.ethereum]: { + ethereum: { circle: { type: BridgeAdapterType.Circle, tokenMessengerAddress: '0xBd3fa81B58Ba92a82136038B25aDec7066af3155', @@ -33,7 +31,7 @@ export const bridgeAdapterConfigs: ChainMap = { circleDomainMapping, }, }, - [Chains.avalanche]: { + avalanche: { circle: { type: BridgeAdapterType.Circle, tokenMessengerAddress: '0x6B25532e1060CE10cc3B0A99e5683b91BFDe6982', diff --git a/typescript/infra/config/environments/mainnet3/owners.ts b/typescript/infra/config/environments/mainnet3/owners.ts index ab949f366e..3b2d89d7ae 100644 --- a/typescript/infra/config/environments/mainnet3/owners.ts +++ b/typescript/infra/config/environments/mainnet3/owners.ts @@ -1,11 +1,8 @@ -import { - AddressesMap, - ChainMap, - OwnableConfig, - hyperlaneEnvironments, -} from '@hyperlane-xyz/sdk'; +import { AddressesMap, ChainMap, OwnableConfig } from '@hyperlane-xyz/sdk'; import { Address, objFilter, objMap } from '@hyperlane-xyz/utils'; +import { getMainnetAddresses } from '../../registry.js'; + import { ethereumChainNames } from './chains.js'; export const timelocks: ChainMap

= { @@ -13,8 +10,7 @@ export const timelocks: ChainMap
= { }; export function localAccountRouters(): ChainMap
{ - const coreAddresses: ChainMap = - hyperlaneEnvironments['mainnet']; + const coreAddresses: ChainMap = getMainnetAddresses(); const filteredAddresses = objFilter( coreAddresses, (local, addressMap): addressMap is AddressesMap => diff --git a/typescript/infra/config/environments/mainnet3/supportedChainNames.ts b/typescript/infra/config/environments/mainnet3/supportedChainNames.ts new file mode 100644 index 0000000000..03a7d1655d --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/supportedChainNames.ts @@ -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', +]; diff --git a/typescript/infra/config/environments/mainnet3/token-bridge.ts b/typescript/infra/config/environments/mainnet3/token-bridge.ts index 7dc12ce63f..f516455025 100644 --- a/typescript/infra/config/environments/mainnet3/token-bridge.ts +++ b/typescript/infra/config/environments/mainnet3/token-bridge.ts @@ -1,19 +1,18 @@ import { BridgeAdapterType, ChainMap, - Chains, CircleBridgeAdapterConfig, - chainMetadata, - getDomainId, } from '@hyperlane-xyz/sdk'; +import { getDomainId } from '../../registry.js'; + const circleDomainMapping = [ - { hyperlaneDomain: getDomainId(chainMetadata[Chains.fuji]), circleDomain: 1 }, + { hyperlaneDomain: getDomainId('fuji'), circleDomain: 1 }, ]; // Circle deployed contracts export const circleBridgeAdapterConfig: ChainMap = { - [Chains.fuji]: { + fuji: { type: BridgeAdapterType.Circle, tokenMessengerAddress: '0x0fc1103927af27af808d03135214718bcedbe9ad', messageTransmitterAddress: '0x52fffb3ee8fa7838e9858a2d5e454007b9027c3c', diff --git a/typescript/infra/config/environments/mainnet3/validators.ts b/typescript/infra/config/environments/mainnet3/validators.ts index ad68b2a0a6..45d7a469af 100644 --- a/typescript/infra/config/environments/mainnet3/validators.ts +++ b/typescript/infra/config/environments/mainnet3/validators.ts @@ -1,7 +1,6 @@ -import { chainMetadata, getReorgPeriod } from '@hyperlane-xyz/sdk'; - import { ValidatorBaseChainConfigMap } from '../../../src/config/agent/validator.js'; import { Contexts } from '../../contexts.js'; +import { getReorgPeriod } from '../../registry.js'; import { validatorBaseConfigsFn } from '../utils.js'; import { environment } from './chains.js'; @@ -13,7 +12,7 @@ export const validatorChainConfig = ( return { ancient8: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.ancient8), + reorgPeriod: getReorgPeriod('ancient8'), validators: validatorsConfig( { [Contexts.Hyperlane]: ['0xbb5842ae0e05215b53df4787a29144efb7e67551'], @@ -27,7 +26,7 @@ export const validatorChainConfig = ( }, celo: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.celo), + reorgPeriod: getReorgPeriod('celo'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -47,7 +46,7 @@ export const validatorChainConfig = ( }, ethereum: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.ethereum), + reorgPeriod: getReorgPeriod('ethereum'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -67,7 +66,7 @@ export const validatorChainConfig = ( }, avalanche: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.avalanche), + reorgPeriod: getReorgPeriod('avalanche'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -87,7 +86,7 @@ export const validatorChainConfig = ( }, polygon: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.polygon), + reorgPeriod: getReorgPeriod('polygon'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -107,7 +106,7 @@ export const validatorChainConfig = ( }, bsc: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.bsc), + reorgPeriod: getReorgPeriod('bsc'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -127,7 +126,7 @@ export const validatorChainConfig = ( }, arbitrum: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.arbitrum), + reorgPeriod: getReorgPeriod('arbitrum'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -147,7 +146,7 @@ export const validatorChainConfig = ( }, optimism: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.optimism), + reorgPeriod: getReorgPeriod('optimism'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -167,7 +166,7 @@ export const validatorChainConfig = ( }, moonbeam: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.moonbeam), + reorgPeriod: getReorgPeriod('moonbeam'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -187,7 +186,7 @@ export const validatorChainConfig = ( }, gnosis: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.gnosis), + reorgPeriod: getReorgPeriod('gnosis'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -207,7 +206,7 @@ export const validatorChainConfig = ( }, base: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.base), + reorgPeriod: getReorgPeriod('base'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -227,7 +226,7 @@ export const validatorChainConfig = ( }, injective: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.injective), + reorgPeriod: getReorgPeriod('injective'), validators: validatorsConfig( { [Contexts.Hyperlane]: ['0xbfb8911b72cfb138c7ce517c57d9c691535dc517'], @@ -239,7 +238,7 @@ export const validatorChainConfig = ( }, inevm: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.inevm), + reorgPeriod: getReorgPeriod('inevm'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -259,7 +258,7 @@ export const validatorChainConfig = ( }, scroll: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.scroll), + reorgPeriod: getReorgPeriod('scroll'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -279,7 +278,7 @@ export const validatorChainConfig = ( }, polygonzkevm: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.polygonzkevm), + reorgPeriod: getReorgPeriod('polygonzkevm'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -299,7 +298,7 @@ export const validatorChainConfig = ( }, neutron: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.neutron), + reorgPeriod: getReorgPeriod('neutron'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -319,7 +318,7 @@ export const validatorChainConfig = ( }, mantapacific: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.mantapacific), + reorgPeriod: getReorgPeriod('mantapacific'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -339,7 +338,7 @@ export const validatorChainConfig = ( }, viction: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.viction), + reorgPeriod: getReorgPeriod('viction'), validators: validatorsConfig( { [Contexts.Hyperlane]: ['0x1f87c368f8e05a85ef9126d984a980a20930cb9c'], @@ -355,7 +354,7 @@ export const validatorChainConfig = ( }, blast: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.blast), + reorgPeriod: getReorgPeriod('blast'), validators: validatorsConfig( { [Contexts.Hyperlane]: ['0xf20c0b09f597597c8d2430d3d72dfddaf09177d1'], @@ -369,7 +368,7 @@ export const validatorChainConfig = ( }, mode: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.mode), + reorgPeriod: getReorgPeriod('mode'), validators: validatorsConfig( { [Contexts.Hyperlane]: ['0x7eb2e1920a4166c19d6884c1cec3d2cf356fc9b7'], diff --git a/typescript/infra/config/environments/test/agent.ts b/typescript/infra/config/environments/test/agent.ts index 1ee2f75ec2..9260cdc86c 100644 --- a/typescript/infra/config/environments/test/agent.ts +++ b/typescript/infra/config/environments/test/agent.ts @@ -7,7 +7,7 @@ import { RootAgentConfig } from '../../../src/config/agent/agent.js'; import { ALL_KEY_ROLES } from '../../../src/roles.js'; import { Contexts } from '../../contexts.js'; -import { agentChainNames, chainNames } from './chains.js'; +import { agentChainNames, testChainNames } from './chains.js'; import { validators } from './validators.js'; const roleBase = { @@ -24,7 +24,7 @@ const hyperlane: RootAgentConfig = { context: Contexts.Hyperlane, rolesWithKeys: ALL_KEY_ROLES, contextChainNames: agentChainNames, - environmentChainNames: chainNames, + environmentChainNames: testChainNames, relayer: { ...roleBase, gasPaymentEnforcement: [ diff --git a/typescript/infra/config/environments/test/chains.ts b/typescript/infra/config/environments/test/chains.ts index aa2a721d31..ee1d22d0aa 100644 --- a/typescript/infra/config/environments/test/chains.ts +++ b/typescript/infra/config/environments/test/chains.ts @@ -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'; -export const testConfigs: ChainMap = { - test1: chainMetadata.test1, - test2: chainMetadata.test2, - test3: chainMetadata.test3, -}; - -export type TestChains = keyof typeof testConfigs; -export const chainNames = Object.keys(testConfigs) as TestChains[]; +export const testChainNames = defaultTestChains; +export const testChainMetadata = { ...defaultTestChainMetadata }; export const agentChainNames: AgentChainNames = { - [Role.Validator]: chainNames, - [Role.Relayer]: chainNames, - [Role.Scraper]: chainNames, + [Role.Validator]: testChainNames, + [Role.Relayer]: testChainNames, + [Role.Scraper]: testChainNames, }; diff --git a/typescript/infra/config/environments/test/gas-oracle.ts b/typescript/infra/config/environments/test/gas-oracle.ts index cb83799592..cacf8f3a33 100644 --- a/typescript/infra/config/environments/test/gas-oracle.ts +++ b/typescript/infra/config/environments/test/gas-oracle.ts @@ -12,7 +12,7 @@ import { getAllStorageGasOracleConfigs, } from '../../../src/config/gas-oracle.js'; -import { chainNames } from './chains.js'; +import { testChainNames } from './chains.js'; const TEST_TOKEN_EXCHANGE_RATE = ethers.utils.parseUnits( '1', @@ -37,4 +37,8 @@ function getTokenExchangeRate( } export const storageGasOracleConfig: AllStorageGasOracleConfigs = - getAllStorageGasOracleConfigs(chainNames, gasPrices, getTokenExchangeRate); + getAllStorageGasOracleConfigs( + testChainNames, + gasPrices, + getTokenExchangeRate, + ); diff --git a/typescript/infra/config/environments/test/igp.ts b/typescript/infra/config/environments/test/igp.ts index a876187c1a..b47be48e66 100644 --- a/typescript/infra/config/environments/test/igp.ts +++ b/typescript/infra/config/environments/test/igp.ts @@ -1,18 +1,19 @@ import { ChainMap, + ChainName, GasOracleContractType, IgpConfig, multisigIsmVerificationCost, } from '@hyperlane-xyz/sdk'; 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 { owners } from './owners.js'; -function getGasOracles(local: TestChains) { +function getGasOracles(local: ChainName) { return Object.fromEntries( - exclude(local, chainNames).map((name) => [ + exclude(local, testChainNames).map((name) => [ name, GasOracleContractType.StorageGasOracle, ]), @@ -21,7 +22,7 @@ function getGasOracles(local: TestChains) { export const igp: ChainMap = objMap(owners, (chain, ownerConfig) => { const overhead = Object.fromEntries( - exclude(chain, chainNames).map((remote) => [ + exclude(chain, testChainNames).map((remote) => [ remote, multisigIsmVerificationCost( multisigIsm[remote].threshold, diff --git a/typescript/infra/config/environments/test/index.ts b/typescript/infra/config/environments/test/index.ts index 1b8df1c351..c21e411e5b 100644 --- a/typescript/infra/config/environments/test/index.ts +++ b/typescript/infra/config/environments/test/index.ts @@ -1,11 +1,10 @@ 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 { agents } from './agent.js'; -import { testConfigs } from './chains.js'; import { core } from './core.js'; import { igp } from './igp.js'; import { infra } from './infra.js'; @@ -13,7 +12,7 @@ import { owners } from './owners.js'; export const environment: EnvironmentConfig = { environment: 'test', - chainMetadataConfigs: testConfigs, + chainMetadataConfigs: testChainMetadata, agents, core, igp, diff --git a/typescript/infra/config/environments/test/owners.ts b/typescript/infra/config/environments/test/owners.ts index a8cba2ecd3..2499a5addf 100644 --- a/typescript/infra/config/environments/test/owners.ts +++ b/typescript/infra/config/environments/test/owners.ts @@ -1,9 +1,9 @@ import { ChainMap, OwnableConfig } from '@hyperlane-xyz/sdk'; -import { chainNames } from './chains.js'; +import { testChainNames } from './chains.js'; // Owner is hardhat account 0 const OWNER_ADDRESS = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'; export const owners: ChainMap = Object.fromEntries( - chainNames.map((chain) => [chain, { owner: OWNER_ADDRESS }]), + testChainNames.map((chain) => [chain, { owner: OWNER_ADDRESS }]), ); diff --git a/typescript/infra/config/environments/testnet4/agent.ts b/typescript/infra/config/environments/testnet4/agent.ts index 71114952ea..00c60c2dbe 100644 --- a/typescript/infra/config/environments/testnet4/agent.ts +++ b/typescript/infra/config/environments/testnet4/agent.ts @@ -1,5 +1,4 @@ import { - Chains, GasPaymentEnforcement, GasPaymentEnforcementPolicyType, RpcConsensusType, @@ -14,8 +13,9 @@ import { routerMatchingList } from '../../../src/config/agent/relayer.js'; import { ALL_KEY_ROLES, Role } from '../../../src/roles.js'; import { Contexts } from '../../contexts.js'; -import { environment, supportedChainNames } from './chains.js'; +import { environment } from './chains.js'; import { helloWorld } from './helloworld.js'; +import { supportedChainNames } from './supportedChainNames.js'; import { validatorChainConfig } from './validators.js'; 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. export const hyperlaneContextAgentChainConfig: AgentChainConfig = { [Role.Validator]: { - [Chains.alfajores]: true, - [Chains.bsctestnet]: true, - [Chains.eclipsetestnet]: true, - [Chains.fuji]: true, - [Chains.plumetestnet]: true, - [Chains.scrollsepolia]: true, - [Chains.sepolia]: true, - [Chains.solanatestnet]: true, + alfajores: true, + bsctestnet: true, + eclipsetestnet: true, + fuji: true, + plumetestnet: true, + scrollsepolia: true, + sepolia: true, + solanatestnet: true, }, [Role.Relayer]: { - [Chains.alfajores]: true, - [Chains.bsctestnet]: true, - [Chains.eclipsetestnet]: true, - [Chains.fuji]: true, - [Chains.plumetestnet]: true, - [Chains.scrollsepolia]: true, - [Chains.sepolia]: true, - [Chains.solanatestnet]: true, + alfajores: true, + bsctestnet: true, + eclipsetestnet: true, + fuji: true, + plumetestnet: true, + scrollsepolia: true, + sepolia: true, + solanatestnet: true, }, [Role.Scraper]: { - [Chains.alfajores]: true, - [Chains.bsctestnet]: true, + alfajores: true, + bsctestnet: true, // Cannot scrape non-EVM chains - [Chains.eclipsetestnet]: false, - [Chains.fuji]: true, - [Chains.plumetestnet]: true, - [Chains.scrollsepolia]: true, - [Chains.sepolia]: true, + eclipsetestnet: false, + fuji: true, + plumetestnet: true, + scrollsepolia: true, + sepolia: true, // Cannot scrape non-EVM chains - [Chains.solanatestnet]: false, + solanatestnet: false, }, }; diff --git a/typescript/infra/config/environments/testnet4/chains.ts b/typescript/infra/config/environments/testnet4/chains.ts index 7a8926c97c..318d67e7b8 100644 --- a/typescript/infra/config/environments/testnet4/chains.ts +++ b/typescript/infra/config/environments/testnet4/chains.ts @@ -1,33 +1,24 @@ -import { - ChainMap, - ChainMetadata, - Chains, - chainMetadata, -} from '@hyperlane-xyz/sdk'; +import { ChainMap, ChainMetadata } from '@hyperlane-xyz/sdk'; +import { objKeys } from '@hyperlane-xyz/utils'; -// All supported chains for the testnet4 environment. -// These chains may be any protocol type. -export const supportedChainNames = [ - Chains.alfajores, - Chains.bsctestnet, - Chains.eclipsetestnet, - Chains.fuji, - Chains.plumetestnet, - Chains.scrollsepolia, - Chains.sepolia, - Chains.solanatestnet, -]; +import { getChainMetadatas } from '../../../src/config/chain.js'; +import { getChain } from '../../registry.js'; + +import { supportedChainNames } from './supportedChainNames.js'; export const environment = 'testnet4'; +const { ethereumMetadatas: defaultEthereumMainnetConfigs } = + getChainMetadatas(supportedChainNames); + export const testnetConfigs: ChainMap = { - ...Object.fromEntries( - supportedChainNames.map((chain) => [chain, chainMetadata[chain]]), - ), + ...defaultEthereumMainnetConfigs, bsctestnet: { - ...chainMetadata.bsctestnet, + ...getChain('bsctestnet'), transactionOverrides: { gasPrice: 8 * 10 ** 9, // 8 gwei }, }, }; + +export const ethereumChainNames = objKeys(defaultEthereumMainnetConfigs); diff --git a/typescript/infra/config/environments/testnet4/core.ts b/typescript/infra/config/environments/testnet4/core.ts index 1274b74505..df52631f3e 100644 --- a/typescript/infra/config/environments/testnet4/core.ts +++ b/typescript/infra/config/environments/testnet4/core.ts @@ -18,17 +18,20 @@ import { RoutingIsmConfig, defaultMultisigConfigs, } 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 { owners } from './owners.js'; +import { supportedChainNames } from './supportedChainNames.js'; export const core: ChainMap = objMap( owners, (local, ownerConfig) => { const originMultisigs: ChainMap = Object.fromEntries( supportedChainNames + .filter((chain) => getChain(chain).protocol === ProtocolType.Ethereum) .filter((chain) => chain !== local) .map((origin) => [origin, defaultMultisigConfigs[origin]]), ); diff --git a/typescript/infra/config/environments/testnet4/gas-oracle.ts b/typescript/infra/config/environments/testnet4/gas-oracle.ts index 4357c660f6..18d94c2e4d 100644 --- a/typescript/infra/config/environments/testnet4/gas-oracle.ts +++ b/typescript/infra/config/environments/testnet4/gas-oracle.ts @@ -13,7 +13,7 @@ import { getTokenExchangeRateFromValues, } 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 const gasPrices: ChainMap = { @@ -72,7 +72,7 @@ function getTokenExchangeRate(local: ChainName, remote: ChainName): BigNumber { export const storageGasOracleConfig: AllStorageGasOracleConfigs = getAllStorageGasOracleConfigs( - supportedChainNames, + ethereumChainNames, objMap(gasPrices, (_, gasPrice) => ({ amount: gasPrice.toString(), decimals: 1, diff --git a/typescript/infra/config/environments/testnet4/igp.ts b/typescript/infra/config/environments/testnet4/igp.ts index c2dc97a36f..f0736d46e1 100644 --- a/typescript/infra/config/environments/testnet4/igp.ts +++ b/typescript/infra/config/environments/testnet4/igp.ts @@ -6,9 +6,9 @@ import { } from '@hyperlane-xyz/sdk'; import { Address, exclude, objMap } from '@hyperlane-xyz/utils'; -import { supportedChainNames } from './chains.js'; import { storageGasOracleConfig } from './gas-oracle.js'; import { owners } from './owners.js'; +import { supportedChainNames } from './supportedChainNames.js'; export const igp: ChainMap = objMap(owners, (chain, ownerConfig) => { return { diff --git a/typescript/infra/config/environments/testnet4/liquidityLayer.ts b/typescript/infra/config/environments/testnet4/liquidityLayer.ts index 8e43b14bc2..0057b68e82 100644 --- a/typescript/infra/config/environments/testnet4/liquidityLayer.ts +++ b/typescript/infra/config/environments/testnet4/liquidityLayer.ts @@ -2,32 +2,31 @@ import { BridgeAdapterConfig, BridgeAdapterType, ChainMap, - Chains, - chainMetadata, - getDomainId, } from '@hyperlane-xyz/sdk'; +import { getDomainId } from '../../registry.js'; + const circleDomainMapping = [ - { hyperlaneDomain: getDomainId(chainMetadata[Chains.fuji]), circleDomain: 1 }, + { hyperlaneDomain: getDomainId('fuji'), circleDomain: 1 }, ]; const wormholeDomainMapping = [ { - hyperlaneDomain: getDomainId(chainMetadata[Chains.fuji]), + hyperlaneDomain: getDomainId('fuji'), wormholeDomain: 6, }, { - hyperlaneDomain: getDomainId(chainMetadata[Chains.bsctestnet]), + hyperlaneDomain: getDomainId('bsctestnet'), wormholeDomain: 4, }, { - hyperlaneDomain: getDomainId(chainMetadata[Chains.alfajores]), + hyperlaneDomain: getDomainId('alfajores'), wormholeDomain: 14, }, ]; export const bridgeAdapterConfigs: ChainMap = { - [Chains.fuji]: { + fuji: { portal: { type: BridgeAdapterType.Portal, portalBridgeAddress: '0x61E44E506Ca5659E6c0bba9b678586fA2d729756', @@ -41,14 +40,14 @@ export const bridgeAdapterConfigs: ChainMap = { circleDomainMapping, }, }, - [Chains.bsctestnet]: { + bsctestnet: { portal: { type: BridgeAdapterType.Portal, portalBridgeAddress: '0x9dcF9D205C9De35334D646BeE44b2D2859712A09', wormholeDomainMapping, }, }, - [Chains.alfajores]: { + alfajores: { portal: { type: BridgeAdapterType.Portal, portalBridgeAddress: '0x05ca6037eC51F8b712eD2E6Fa72219FEaE74E153', diff --git a/typescript/infra/config/environments/testnet4/owners.ts b/typescript/infra/config/environments/testnet4/owners.ts index af2871a6c2..00f3b00f96 100644 --- a/typescript/infra/config/environments/testnet4/owners.ts +++ b/typescript/infra/config/environments/testnet4/owners.ts @@ -1,13 +1,13 @@ import { ChainMap, OwnableConfig } from '@hyperlane-xyz/sdk'; -import { supportedChainNames } from '../testnet4/chains.js'; +import { ethereumChainNames } from './chains.js'; const ETHEREUM_DEPLOYER_ADDRESS = '0xfaD1C94469700833717Fa8a3017278BC1cA8031C'; // const SEALEVEL_DEPLOYER_ADDRESS = '6DjHX6Ezjpq3zZMZ8KsqyoFYo1zPSDoiZmLLkxD4xKXS'; export const owners: ChainMap = { ...Object.fromEntries( - supportedChainNames.map((chain) => [ + ethereumChainNames.map((chain) => [ chain, { owner: ETHEREUM_DEPLOYER_ADDRESS }, ]), diff --git a/typescript/infra/config/environments/testnet4/supportedChainNames.ts b/typescript/infra/config/environments/testnet4/supportedChainNames.ts new file mode 100644 index 0000000000..c65393e550 --- /dev/null +++ b/typescript/infra/config/environments/testnet4/supportedChainNames.ts @@ -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', +]; diff --git a/typescript/infra/config/environments/testnet4/token-bridge.ts b/typescript/infra/config/environments/testnet4/token-bridge.ts index 8e43b14bc2..0057b68e82 100644 --- a/typescript/infra/config/environments/testnet4/token-bridge.ts +++ b/typescript/infra/config/environments/testnet4/token-bridge.ts @@ -2,32 +2,31 @@ import { BridgeAdapterConfig, BridgeAdapterType, ChainMap, - Chains, - chainMetadata, - getDomainId, } from '@hyperlane-xyz/sdk'; +import { getDomainId } from '../../registry.js'; + const circleDomainMapping = [ - { hyperlaneDomain: getDomainId(chainMetadata[Chains.fuji]), circleDomain: 1 }, + { hyperlaneDomain: getDomainId('fuji'), circleDomain: 1 }, ]; const wormholeDomainMapping = [ { - hyperlaneDomain: getDomainId(chainMetadata[Chains.fuji]), + hyperlaneDomain: getDomainId('fuji'), wormholeDomain: 6, }, { - hyperlaneDomain: getDomainId(chainMetadata[Chains.bsctestnet]), + hyperlaneDomain: getDomainId('bsctestnet'), wormholeDomain: 4, }, { - hyperlaneDomain: getDomainId(chainMetadata[Chains.alfajores]), + hyperlaneDomain: getDomainId('alfajores'), wormholeDomain: 14, }, ]; export const bridgeAdapterConfigs: ChainMap = { - [Chains.fuji]: { + fuji: { portal: { type: BridgeAdapterType.Portal, portalBridgeAddress: '0x61E44E506Ca5659E6c0bba9b678586fA2d729756', @@ -41,14 +40,14 @@ export const bridgeAdapterConfigs: ChainMap = { circleDomainMapping, }, }, - [Chains.bsctestnet]: { + bsctestnet: { portal: { type: BridgeAdapterType.Portal, portalBridgeAddress: '0x9dcF9D205C9De35334D646BeE44b2D2859712A09', wormholeDomainMapping, }, }, - [Chains.alfajores]: { + alfajores: { portal: { type: BridgeAdapterType.Portal, portalBridgeAddress: '0x05ca6037eC51F8b712eD2E6Fa72219FEaE74E153', diff --git a/typescript/infra/config/environments/testnet4/validators.ts b/typescript/infra/config/environments/testnet4/validators.ts index d4e4be74aa..6e594ffa73 100644 --- a/typescript/infra/config/environments/testnet4/validators.ts +++ b/typescript/infra/config/environments/testnet4/validators.ts @@ -1,7 +1,6 @@ -import { chainMetadata, getReorgPeriod } from '@hyperlane-xyz/sdk'; - import { ValidatorBaseChainConfigMap } from '../../../src/config/agent/validator.js'; import { Contexts } from '../../contexts.js'; +import { getReorgPeriod } from '../../registry.js'; import { validatorBaseConfigsFn } from '../utils.js'; import { environment } from './chains.js'; @@ -13,7 +12,7 @@ export const validatorChainConfig = ( return { alfajores: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.alfajores), + reorgPeriod: getReorgPeriod('alfajores'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -33,7 +32,7 @@ export const validatorChainConfig = ( }, fuji: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.alfajores), + reorgPeriod: getReorgPeriod('alfajores'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -53,7 +52,7 @@ export const validatorChainConfig = ( }, bsctestnet: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.bsctestnet), + reorgPeriod: getReorgPeriod('bsctestnet'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -73,7 +72,7 @@ export const validatorChainConfig = ( }, scrollsepolia: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.scrollsepolia), + reorgPeriod: getReorgPeriod('scrollsepolia'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -93,7 +92,7 @@ export const validatorChainConfig = ( }, sepolia: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.sepolia), + reorgPeriod: getReorgPeriod('sepolia'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -113,7 +112,7 @@ export const validatorChainConfig = ( }, plumetestnet: { interval: 5, - reorgPeriod: getReorgPeriod(chainMetadata.plumetestnet), + reorgPeriod: getReorgPeriod('plumetestnet'), validators: validatorsConfig( { [Contexts.Hyperlane]: [ @@ -133,7 +132,7 @@ export const validatorChainConfig = ( }, solanatestnet: { interval: 1, - reorgPeriod: getReorgPeriod(chainMetadata.solanatestnet), + reorgPeriod: getReorgPeriod('solanatestnet'), validators: validatorsConfig( { [Contexts.Hyperlane]: ['0xd4ce8fa138d4e083fc0e480cca0dbfa4f5f30bd5'], @@ -145,7 +144,7 @@ export const validatorChainConfig = ( }, eclipsetestnet: { interval: 1, - reorgPeriod: getReorgPeriod(chainMetadata.eclipsetestnet), + reorgPeriod: getReorgPeriod('eclipsetestnet'), validators: validatorsConfig( { [Contexts.Hyperlane]: ['0xf344f34abca9a444545b5295066348a0ae22dda3'], diff --git a/typescript/infra/config/environments/utils.ts b/typescript/infra/config/environments/utils.ts index d9e25417ee..a9286077ba 100644 --- a/typescript/infra/config/environments/utils.ts +++ b/typescript/infra/config/environments/utils.ts @@ -1,4 +1,4 @@ -import { CoreChainName } from '@hyperlane-xyz/sdk'; +import { ChainName } from '@hyperlane-xyz/sdk'; import { CheckpointSyncerType, @@ -16,7 +16,7 @@ export const s3BucketRegion = 'us-east-1'; export const s3BucketName = ( context: Contexts, environment: string, - chainName: CoreChainName, + chainName: ChainName, index: number, ) => `${context}-${environment}-${chainName}-validator-${index}`; @@ -35,7 +35,7 @@ export const validatorBaseConfigsFn = ( context: Contexts, ): (( addresses: Record, - chain: CoreChainName, + chain: ChainName, ) => ValidatorBaseConfig[]) => { return (addresses, chain) => { return addresses[context].map((address, index) => { diff --git a/typescript/infra/config/multisigIsm.ts b/typescript/infra/config/multisigIsm.ts index 038b0331b2..418abd7e4f 100644 --- a/typescript/infra/config/multisigIsm.ts +++ b/typescript/infra/config/multisigIsm.ts @@ -9,16 +9,8 @@ import { import { DeployEnvironment } from '../src/config/environment.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'; - -const chains = { - mainnet3: mainnet3Chains, - testnet4: testnet4Chains, - test: testChains, -}; +import { getEnvChains } from './registry.js'; export const multisigIsms = ( env: DeployEnvironment, @@ -30,7 +22,12 @@ export const multisigIsms = ( context === Contexts.ReleaseCandidate ? rcMultisigIsmConfigs : defaultMultisigConfigs; - return buildMultisigIsmConfigs(type, local, chains[env], multisigConfigs); + return buildMultisigIsmConfigs( + type, + local, + getEnvChains(env), + multisigConfigs, + ); }; export const multisigIsm = ( diff --git a/typescript/infra/config/registry.ts b/typescript/infra/config/registry.ts new file mode 100644 index 0000000000..91abea01ce --- /dev/null +++ b/typescript/infra/config/registry.ts @@ -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 { + return getRegistry().getMetadata(); +} + +export function getChainAddresses(): ChainMap { + 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 { + const envChains = getEnvChains(env); + return objFilter( + getChainAddresses(), + (chain, addresses): addresses is ChainAddresses => + getEnvChains(env).includes(chain), + ); +} + +export function getMainnetAddresses(): ChainMap { + return getEnvAddresses('mainnet3'); +} + +export function getTestnetAddresses(): ChainMap { + return getEnvAddresses('testnet4'); +} diff --git a/typescript/infra/config/routingIsm.ts b/typescript/infra/config/routingIsm.ts index b7a1267309..b4cabd2d58 100644 --- a/typescript/infra/config/routingIsm.ts +++ b/typescript/infra/config/routingIsm.ts @@ -6,22 +6,14 @@ import { IsmType, ModuleType, RoutingIsmConfig, - TestChains, } from '@hyperlane-xyz/sdk'; import { DeployEnvironment } from '../src/config/environment.js'; import { Contexts } from './contexts.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'; - -const chains = { - test: TestChains, - testnet4: testnet4Chains, - mainnet3: mainnet3Chains, -}; +import { getEnvChains } from './registry.js'; // Intended to be the "entrypoint" ISM. // Routing ISM => Aggregation (1/2) @@ -34,7 +26,9 @@ export const routingIsm = ( local: ChainName, context: Contexts, ): RoutingIsmConfig | string => { - const aggregationIsms: ChainMap = chains[environment] + const aggregationIsms: ChainMap = getEnvChains( + environment, + ) .filter((chain) => chain !== local) .reduce( (acc, chain) => ({ diff --git a/typescript/infra/config/warp.ts b/typescript/infra/config/warp.ts index 831b7cafe4..a878cfc247 100644 --- a/typescript/infra/config/warp.ts +++ b/typescript/infra/config/warp.ts @@ -2,8 +2,6 @@ import { ethers } from 'ethers'; import { ChainMap, - ERC20RouterConfig, - HyperlaneCore, HyperlaneIsmFactory, MultiProvider, RouterConfig, @@ -13,23 +11,18 @@ import { defaultMultisigConfigs, } from '@hyperlane-xyz/sdk'; -import { Modules, getAddresses } from '../scripts/agent-utils'; -import { - EnvironmentConfig, - deployEnvToSdkEnv, -} from '../src/config/environment'; -import { tokens } from '../src/config/warp'; +import { Modules, getAddresses } from '../scripts/agent-utils.js'; +import { getHyperlaneCore } from '../scripts/core-utils.js'; +import { EnvironmentConfig } from '../src/config/environment.js'; +import { tokens } from '../src/config/warp.js'; -import { DEPLOYER } from './environments/mainnet3/owners'; +import { DEPLOYER } from './environments/mainnet3/owners.js'; export async function getWarpConfig( multiProvider: MultiProvider, envConfig: EnvironmentConfig, ): Promise> { - const core = HyperlaneCore.fromEnvironment( - deployEnvToSdkEnv[envConfig.environment], - multiProvider, - ); + const { core } = await getHyperlaneCore(envConfig.environment, multiProvider); const ismFactory = HyperlaneIsmFactory.fromAddressesMap( getAddresses(envConfig.environment, Modules.PROXY_FACTORY), multiProvider, diff --git a/typescript/infra/fork.sh b/typescript/infra/fork.sh index 765e98a47a..02631cecf2 100755 --- a/typescript/infra/fork.sh +++ b/typescript/infra/fork.sh @@ -13,7 +13,7 @@ trap 'jobs -p | xargs -r kill' EXIT # exit 1 on any subsequent failures set -e -RPC_URL=`yarn tsx ./scripts/print-chain-metadatas.ts -e $ENVIRONMENT | jq -r ".$CHAIN.rpcUrls[0].http"` +RPC_URL=`LOG_LEVEL=error yarn tsx ./scripts/print-chain-metadatas.ts -e $ENVIRONMENT | jq -r ".$CHAIN.rpcUrls[0].http"` anvil --fork-url $RPC_URL --fork-retry-backoff 3 --compute-units-per-second 200 --gas-price 1 --silent & ANVIL_PID=$! @@ -25,21 +25,25 @@ done # echo all subsequent commands set -x +echo "Checking deploy" yarn tsx ./scripts/check-deploy.ts -e $ENVIRONMENT -f $CHAIN -m $MODULE -# get balance +echo "Getting balance" DEPLOYER="0xa7ECcdb9Be08178f896c26b7BbD8C3D4E844d9Ba" BEFORE=$(cast balance $DEPLOYER --rpc-url http://localhost:8545) +echo "Deploying" yarn tsx ./scripts/deploy.ts -e $ENVIRONMENT -f $CHAIN -m $MODULE AFTER=$(cast balance $DEPLOYER --rpc-url http://localhost:8545) DEPLOY_DELTA="$((BEFORE-AFTER))" BEFORE=$(cast balance $DEPLOYER --rpc-url http://localhost:8545) +echo "Checking deploy with --govern" yarn tsx ./scripts/check-deploy.ts -e $ENVIRONMENT -f $CHAIN --govern -m $MODULE AFTER=$(cast balance $DEPLOYER --rpc-url http://localhost:8545) GOVERN_DELTA="$((BEFORE-AFTER))" +echo "Checking deploy without --govern" yarn tsx ./scripts/check-deploy.ts -e $ENVIRONMENT -f $CHAIN -m $MODULE diff --git a/typescript/infra/package.json b/typescript/infra/package.json index bf100b8d7b..cb062f9e76 100644 --- a/typescript/infra/package.json +++ b/typescript/infra/package.json @@ -13,6 +13,7 @@ "@ethersproject/hardware-wallets": "^5.7.0", "@ethersproject/providers": "^5.7.2", "@hyperlane-xyz/helloworld": "3.10.0", + "@hyperlane-xyz/registry": "^1.0.7", "@hyperlane-xyz/sdk": "3.10.0", "@hyperlane-xyz/utils": "3.10.0", "@nomiclabs/hardhat-etherscan": "^3.0.3", diff --git a/typescript/infra/scripts/agent-utils.ts b/typescript/infra/scripts/agent-utils.ts index 74cfe023a8..7226bdf0b1 100644 --- a/typescript/infra/scripts/agent-utils.ts +++ b/typescript/infra/scripts/agent-utils.ts @@ -1,22 +1,20 @@ -import path, { dirname, join } from 'path'; -import { fileURLToPath } from 'url'; +import path, { join } from 'path'; import yargs, { Argv } from 'yargs'; +import { ChainAddresses } from '@hyperlane-xyz/registry'; import { - AllChains, ChainMap, ChainMetadata, ChainName, - Chains, CoreConfig, - HyperlaneEnvironment, MultiProvider, RpcConsensusType, - chainMetadata, collectValidators, } from '@hyperlane-xyz/sdk'; import { + Address, ProtocolType, + objFilter, objMap, promiseObjAll, rootLogger, @@ -26,24 +24,31 @@ import { import { Contexts } from '../config/contexts.js'; import { agents } from '../config/environments/agents.js'; import { validatorBaseConfigsFn } from '../config/environments/utils.js'; +import { + getChain, + getChainAddresses, + getChains, + getEnvChains, + getRegistry, +} from '../config/registry.js'; import { getCurrentKubernetesContext } from '../src/agents/index.js'; import { getCloudAgentKey } from '../src/agents/key-utils.js'; import { CloudAgentKey } from '../src/agents/keys.js'; import { RootAgentConfig } from '../src/config/agent/agent.js'; import { fetchProvider } from '../src/config/chain.js'; import { + AgentEnvironment, DeployEnvironment, EnvironmentConfig, - EnvironmentNames, - deployEnvToSdkEnv, + assertEnvironment, } from '../src/config/environment.js'; import { Role } from '../src/roles.js'; import { assertContext, assertRole, getInfraPath, - readJSON, readJSONAtPath, + writeMergedJSONAtPath, } from '../src/utils/utils.js'; const debugLog = rootLogger.child({ module: 'infra:scripts:utils' }).debug; @@ -63,7 +68,7 @@ export enum Modules { WARP = 'warp', } -export const SDK_MODULES = [ +export const REGISTRY_MODULES = [ Modules.PROXY_FACTORY, Modules.CORE, Modules.INTERCHAIN_GAS_PAYMASTER, @@ -87,14 +92,14 @@ export function withModuleAndFork(args: Argv) { .demandOption('module', 'hyperlane module to deploy') .alias('m', 'module') .describe('fork', 'network to fork') - .choices('fork', Object.values(Chains)) + .choices('fork', getChains()) .alias('f', 'fork'); } export function withNetwork(args: Argv) { return args .describe('network', 'network to target') - .choices('network', Object.values(Chains)) + .choices('network', getChains()) .alias('n', 'network'); } @@ -132,7 +137,7 @@ export function withKeyRoleAndChain(args: Argv) { .alias('r', 'role') .describe('chain', 'chain name') - .choices('chain', AllChains) + .choices('chain', getChains()) .demandOption('chain') .alias('c', 'chain') @@ -159,15 +164,6 @@ export function withBuildArtifactPath(args: Argv) { .alias('b', 'buildArtifactPath'); } -export function assertEnvironment(env: string): DeployEnvironment { - if (EnvironmentNames.includes(env)) { - return env as DeployEnvironment; - } - throw new Error( - `Invalid environment ${env}, must be one of ${EnvironmentNames}`, - ); -} - // not requiring to build coreConfig to get agentConfig export async function getAgentConfigsBasedOnArgs(argv?: { environment: DeployEnvironment; @@ -205,7 +201,7 @@ export async function getAgentConfigsBasedOnArgs(argv?: { ...baseConfig, [context]: Array(validatorCount).fill('0x0'), }, - chain as Chains, + chain, ); // the hardcoded fields are not strictly necessary to be accurate for create-keys.ts // ideally would still get them from the chainMetadata @@ -214,8 +210,8 @@ export async function getAgentConfigsBasedOnArgs(argv?: { } agentConfig.validators.chains[chain] = { - interval: chainMetadata[chain].blocks?.estimateBlockTime ?? 1, // dummy value - reorgPeriod: chainMetadata[chain].blocks?.reorgPeriod ?? 0, // dummy value + interval: getChain(chain).blocks?.estimateBlockTime ?? 1, // dummy value + reorgPeriod: getChain(chain).blocks?.reorgPeriod ?? 0, // dummy value validators, }; @@ -346,10 +342,6 @@ export async function getKeysForRole( return keys; } -export function getContractAddressesSdkFilepath() { - return path.join('../sdk/src/consts/environments'); -} - export function getEnvironmentDirectory(environment: DeployEnvironment) { return path.join('./config/environments/', environment); } @@ -377,29 +369,53 @@ export function getModuleDirectory( return path.join(getEnvironmentDirectory(environment), suffixFn()); } -export function getAddressesPath( +export function isRegistryModule( environment: DeployEnvironment, module: Modules, ) { - const isSdkArtifact = SDK_MODULES.includes(module) && environment !== 'test'; - - return isSdkArtifact - ? path.join( - getContractAddressesSdkFilepath(), - `${deployEnvToSdkEnv[environment]}.json`, - ) - : path.join(getModuleDirectory(environment, module), 'addresses.json'); + return REGISTRY_MODULES.includes(module) && environment !== 'test'; +} + +// Where non-registry module addresses are dumped. +// This package must die in fire. +function getInfraLandfillPath(environment: DeployEnvironment, module: Modules) { + return path.join(getModuleDirectory(environment, module), 'addresses.json'); } export function getAddresses(environment: DeployEnvironment, module: Modules) { - return readJSONAtPath(getAddressesPath(environment, module)); + if (isRegistryModule(environment, module)) { + const allAddresses = getChainAddresses(); + const envChains = getEnvChains(environment); + return objFilter(allAddresses, (chain, _): _ is ChainAddresses => { + return envChains.includes(chain); + }); + } else { + return readJSONAtPath(getInfraLandfillPath(environment, module)); + } +} + +export function writeAddresses( + environment: DeployEnvironment, + module: Modules, + addressesMap: ChainMap>, +) { + if (isRegistryModule(environment, module)) { + for (const [chainName, addresses] of Object.entries(addressesMap)) { + getRegistry().updateChain({ chainName, addresses }); + } + } else { + writeMergedJSONAtPath( + getInfraLandfillPath(environment, module), + addressesMap, + ); + } } export function getAgentConfigDirectory() { return path.join('../../', 'rust', 'config'); } -export function getAgentConfigJsonPath(environment: HyperlaneEnvironment) { +export function getAgentConfigJsonPath(environment: AgentEnvironment) { return path.join(getAgentConfigDirectory(), `${environment}_config.json`); } diff --git a/typescript/infra/scripts/agents/update-agent-config.ts b/typescript/infra/scripts/agents/update-agent-config.ts index 303252b07f..2ec6431b84 100644 --- a/typescript/infra/scripts/agents/update-agent-config.ts +++ b/typescript/infra/scripts/agents/update-agent-config.ts @@ -1,7 +1,6 @@ -import { deployEnvToSdkEnv } from '../../src/config/environment'; -import { writeAgentConfig } from '../../src/deployment/deploy'; -import { Modules, getAddressesPath, getArgs } from '../agent-utils'; -import { getEnvironmentConfig } from '../core-utils'; +import { writeAgentConfig } from '../../src/deployment/deploy.js'; +import { getArgs } from '../agent-utils.js'; +import { getEnvironmentConfig } from '../core-utils.js'; async function main() { const { environment } = await getArgs().argv; @@ -9,9 +8,7 @@ async function main() { let multiProvider = await envConfig.getMultiProvider(); - const addressesPath = getAddressesPath(environment, Modules.CORE); - - await writeAgentConfig(addressesPath, multiProvider, environment); + await writeAgentConfig(multiProvider, environment); } main() diff --git a/typescript/infra/scripts/announce-validators.ts b/typescript/infra/scripts/announce-validators.ts index 8bdb09ab05..05250bad83 100644 --- a/typescript/infra/scripts/announce-validators.ts +++ b/typescript/infra/scripts/announce-validators.ts @@ -3,11 +3,11 @@ import { ethers } from 'ethers'; import { readFileSync } from 'fs'; import * as path from 'path'; -import { AllChains, ChainName, HyperlaneCore } from '@hyperlane-xyz/sdk'; +import { ChainName } from '@hyperlane-xyz/sdk'; +import { getChains } from '../config/registry.js'; import { S3Validator } from '../src/agents/aws/validator.js'; import { CheckpointSyncerType } from '../src/config/agent/validator.js'; -import { deployEnvToSdkEnv } from '../src/config/environment.js'; import { isEthereumProtocolChain } from '../src/utils/utils.js'; import { @@ -15,12 +15,12 @@ import { getArgs as getRootArgs, withContext, } from './agent-utils.js'; -import { getEnvironmentConfig } from './core-utils.js'; +import { getHyperlaneCore } from './core-utils.js'; function getArgs() { return withContext(getRootArgs()) .describe('chain', 'chain on which to register') - .choices('chain', AllChains) + .choices('chain', getChains()) .describe( 'location', 'location, e.g. s3://hyperlane-testnet4-sepolia-validator-0/us-east-1', @@ -36,13 +36,7 @@ function getArgs() { async function main() { const { environment, context, chain, location } = await getArgs(); - const config = getEnvironmentConfig(environment); - const multiProvider = await config.getMultiProvider(); - // environments union doesn't work well with typescript - const core = HyperlaneCore.fromEnvironment( - deployEnvToSdkEnv[environment], - multiProvider, - ); + const { core, multiProvider } = await getHyperlaneCore(environment); const announcements: { storageLocation: string; diff --git a/typescript/infra/scripts/check-deploy.ts b/typescript/infra/scripts/check-deploy.ts index 81720905ad..47160c889d 100644 --- a/typescript/infra/scripts/check-deploy.ts +++ b/typescript/infra/scripts/check-deploy.ts @@ -2,7 +2,6 @@ import { HelloWorldChecker } from '@hyperlane-xyz/helloworld'; import { HypERC20App, HypERC20Checker, - HyperlaneCore, HyperlaneCoreChecker, HyperlaneIgp, HyperlaneIgpChecker, @@ -11,13 +10,11 @@ import { InterchainAccountChecker, InterchainQuery, InterchainQueryChecker, - TokenType, resolveOrDeployAccountOwner, } from '@hyperlane-xyz/sdk'; import { Contexts } from '../config/contexts.js'; import { getWarpConfig } from '../config/warp.js'; -import { deployEnvToSdkEnv } from '../src/config/environment.js'; import { HyperlaneAppGovernor } from '../src/govern/HyperlaneAppGovernor.js'; import { HyperlaneCoreGovernor } from '../src/govern/HyperlaneCoreGovernor.js'; import { HyperlaneIgpGovernor } from '../src/govern/HyperlaneIgpGovernor.js'; @@ -32,7 +29,7 @@ import { withContext, withModuleAndFork, } from './agent-utils.js'; -import { getEnvironmentConfig } from './core-utils.js'; +import { getEnvironmentConfig, getHyperlaneCore } from './core-utils.js'; import { getHelloWorldApp } from './helloworld/utils.js'; function getArgs() { @@ -67,11 +64,13 @@ async function check() { } } - const env = deployEnvToSdkEnv[environment]; - const core = HyperlaneCore.fromEnvironment(env, multiProvider); - const ismFactory = HyperlaneIsmFactory.fromEnvironment(env, multiProvider); + const { core, chainAddresses } = await getHyperlaneCore(environment); + const ismFactory = HyperlaneIsmFactory.fromAddressesMap( + chainAddresses, + multiProvider, + ); const routerConfig = core.getRouterConfig(envConfig.owners); - const ica = InterchainAccount.fromEnvironment(env, multiProvider); + const ica = InterchainAccount.fromAddressesMap(chainAddresses, multiProvider); let governor: HyperlaneAppGovernor; if (module === Modules.CORE) { @@ -83,7 +82,7 @@ async function check() { ); governor = new HyperlaneCoreGovernor(checker); } else if (module === Modules.INTERCHAIN_GAS_PAYMASTER) { - const igp = HyperlaneIgp.fromEnvironment(env, multiProvider); + const igp = HyperlaneIgp.fromAddressesMap(chainAddresses, multiProvider); const checker = new HyperlaneIgpChecker(multiProvider, igp, envConfig.igp); governor = new HyperlaneIgpGovernor(checker); } else if (module === Modules.INTERCHAIN_ACCOUNTS) { @@ -94,7 +93,7 @@ async function check() { ); governor = new ProxiedRouterGovernor(checker); } else if (module === Modules.INTERCHAIN_QUERY_SYSTEM) { - const iqs = InterchainQuery.fromEnvironment(env, multiProvider); + const iqs = InterchainQuery.fromAddressesMap(chainAddresses, multiProvider); const checker = new InterchainQueryChecker( multiProvider, iqs, @@ -108,7 +107,10 @@ async function check() { Role.Deployer, Contexts.Hyperlane, // Owner should always be from the hyperlane context ); - const ismFactory = HyperlaneIsmFactory.fromEnvironment(env, multiProvider); + const ismFactory = HyperlaneIsmFactory.fromAddressesMap( + chainAddresses, + multiProvider, + ); const checker = new HelloWorldChecker( multiProvider, app, diff --git a/typescript/infra/scripts/core-utils.ts b/typescript/infra/scripts/core-utils.ts index a5dbbadca9..0ea862a259 100644 --- a/typescript/infra/scripts/core-utils.ts +++ b/typescript/infra/scripts/core-utils.ts @@ -1,5 +1,8 @@ +import { HyperlaneCore, MultiProvider } from '@hyperlane-xyz/sdk'; + import { Contexts } from '../config/contexts.js'; import { environments } from '../config/environments/index.js'; +import { getEnvAddresses } from '../config/registry.js'; import { DeployEnvironment } from '../src/config/environment.js'; import { getAgentConfig, getArgs, withContext } from './agent-utils.js'; @@ -21,3 +24,14 @@ export async function getConfigsBasedOnArgs(argv?: { const agentConfig = getAgentConfig(context, environment); return { envConfig, agentConfig, context, environment }; } + +export async function getHyperlaneCore( + env: DeployEnvironment, + multiProvider?: MultiProvider, +) { + const config = getEnvironmentConfig(env); + multiProvider = multiProvider || (await config.getMultiProvider()); + const chainAddresses = getEnvAddresses(env); + const core = HyperlaneCore.fromAddressesMap(chainAddresses, multiProvider); + return { core, multiProvider, chainAddresses }; +} diff --git a/typescript/infra/scripts/debug-message.ts b/typescript/infra/scripts/debug-message.ts index a71022c53f..72f48f9f52 100644 --- a/typescript/infra/scripts/debug-message.ts +++ b/typescript/infra/scripts/debug-message.ts @@ -7,10 +7,11 @@ import { } from '@hyperlane-xyz/sdk'; import { bytes32ToAddress, ensure0x, messageId } from '@hyperlane-xyz/utils'; -import { deployEnvToSdkEnv } from '../src/config/environment.js'; +import { getChainMetadata } from '../config/registry.js'; import { assertChain } from '../src/utils/utils.js'; import { getArgs } from './agent-utils.js'; +import { getHyperlaneCore } from './core-utils.js'; async function main() { const argv = await getArgs() @@ -37,12 +38,8 @@ async function main() { // Intentionally use public RPC providers to avoid requiring access to our GCP secrets // to run this script - const multiProvider = new MultiProvider(); - - const core = HyperlaneCore.fromEnvironment( - deployEnvToSdkEnv[argv.environment], - multiProvider, - ); + const multiProvider = new MultiProvider(getChainMetadata()); + const { core } = await getHyperlaneCore(argv.environment, multiProvider); const originProvider = multiProvider.getProvider(argv.originChain); const dispatchReceipt = await originProvider.getTransactionReceipt( diff --git a/typescript/infra/scripts/deploy.ts b/typescript/infra/scripts/deploy.ts index 7d3d451972..c761c59c14 100644 --- a/typescript/infra/scripts/deploy.ts +++ b/typescript/infra/scripts/deploy.ts @@ -9,7 +9,6 @@ import { ExplorerLicenseType, FallbackRoutingHookConfig, HypERC20Deployer, - HyperlaneCore, HyperlaneCoreDeployer, HyperlaneDeployer, HyperlaneHookDeployer, @@ -21,15 +20,14 @@ import { InterchainQueryDeployer, LiquidityLayerDeployer, TestRecipientDeployer, - hyperlaneEnvironments, } from '@hyperlane-xyz/sdk'; import { objMap } from '@hyperlane-xyz/utils'; import { Contexts } from '../config/contexts.js'; import { core as coreConfig } from '../config/environments/mainnet3/core.js'; import { DEPLOYER } from '../config/environments/mainnet3/owners.js'; +import { getEnvAddresses } from '../config/registry.js'; import { getWarpConfig } from '../config/warp.js'; -import { deployEnvToSdkEnv } from '../src/config/environment.js'; import { deployWithArtifacts } from '../src/deployment/deploy.js'; import { TestQuerySenderDeployer } from '../src/deployment/testcontracts/testquerysender.js'; import { @@ -41,7 +39,6 @@ import { impersonateAccount, useLocalProvider } from '../src/utils/fork.js'; import { Modules, getAddresses, - getAddressesPath, getArgs, getModuleDirectory, withBuildArtifactPath, @@ -49,7 +46,7 @@ import { withModuleAndFork, withNetwork, } from './agent-utils.js'; -import { getEnvironmentConfig } from './core-utils.js'; +import { getEnvironmentConfig, getHyperlaneCore } from './core-utils.js'; async function main() { const { @@ -63,7 +60,6 @@ async function main() { withNetwork(withModuleAndFork(withBuildArtifactPath(getArgs()))), ).argv; const envConfig = getEnvironmentConfig(environment); - const env = deployEnvToSdkEnv[environment]; let multiProvider = await envConfig.getMultiProvider(); @@ -133,17 +129,17 @@ async function main() { config = envConfig.igp; deployer = new HyperlaneIgpDeployer(multiProvider, contractVerifier); } else if (module === Modules.INTERCHAIN_ACCOUNTS) { - const core = HyperlaneCore.fromEnvironment(env, multiProvider); + const { core } = await getHyperlaneCore(environment, multiProvider); config = core.getRouterConfig(envConfig.owners); deployer = new InterchainAccountDeployer(multiProvider, contractVerifier); const addresses = getAddresses(environment, Modules.INTERCHAIN_ACCOUNTS); InterchainAccount.fromAddressesMap(addresses, multiProvider); } else if (module === Modules.INTERCHAIN_QUERY_SYSTEM) { - const core = HyperlaneCore.fromEnvironment(env, multiProvider); + const { core } = await getHyperlaneCore(environment, multiProvider); config = core.getRouterConfig(envConfig.owners); deployer = new InterchainQueryDeployer(multiProvider, contractVerifier); } else if (module === Modules.LIQUIDITY_LAYER) { - const core = HyperlaneCore.fromEnvironment(env, multiProvider); + const { core } = await getHyperlaneCore(environment, multiProvider); const routerConfig = core.getRouterConfig(envConfig.owners); if (!envConfig.liquidityLayerConfig) { throw new Error(`No liquidity layer config for ${environment}`); @@ -178,7 +174,7 @@ async function main() { })); deployer = new TestQuerySenderDeployer(multiProvider, contractVerifier); } else if (module === Modules.HELLO_WORLD) { - const core = HyperlaneCore.fromEnvironment(env, multiProvider); + const { core } = await getHyperlaneCore(environment, multiProvider); config = core.getRouterConfig(envConfig.owners); deployer = new HelloWorldDeployer( multiProvider, @@ -192,7 +188,7 @@ async function main() { ); deployer = new HyperlaneHookDeployer( multiProvider, - hyperlaneEnvironments[env], + getEnvAddresses(environment), ismFactory, ); // Config is intended to be changed for ad-hoc use cases: @@ -212,20 +208,19 @@ async function main() { console.log(`Deploying to ${modulePath}`); - const addresses = getAddressesPath(environment, module); const verification = path.join(modulePath, 'verification.json'); const cache = { - addresses, verification, read: environment !== 'test', write: !fork, + environment, + module, }; // Don't write agent config in fork tests const agentConfig = module === Modules.CORE && !fork ? { - addresses, environment, multiProvider, } diff --git a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts index 7a5364366f..5ee9aba1ce 100644 --- a/typescript/infra/scripts/funding/fund-keys-from-deployer.ts +++ b/typescript/infra/scripts/funding/fund-keys-from-deployer.ts @@ -7,7 +7,6 @@ import { format } from 'util'; import { ChainMap, ChainName, - Chains, HyperlaneIgp, MultiProvider, RpcConsensusType, @@ -15,6 +14,7 @@ import { import { Address, objFilter, objMap, rootLogger } from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts.js'; +import { getEnvAddresses } from '../../config/registry.js'; import { KeyAsAddress, fetchLocalKeyAddresses, @@ -25,10 +25,7 @@ import { LocalAgentKey, ReadOnlyCloudAgentKey, } from '../../src/agents/keys.js'; -import { - DeployEnvironment, - deployEnvToSdkEnv, -} from '../../src/config/environment.js'; +import { DeployEnvironment } from '../../src/config/environment.js'; import { ContextAndRoles, ContextAndRolesMap, @@ -59,9 +56,7 @@ const nativeBridges = { }, }; -type L2Chain = Chains.optimism | Chains.arbitrum | Chains.base; - -const L2Chains: ChainName[] = [Chains.optimism, Chains.arbitrum, Chains.base]; +const L2Chains: ChainName[] = ['optimism', 'arbitrum', 'base']; const L2ToL1: ChainMap = { optimism: 'ethereum', @@ -278,8 +273,8 @@ class ContextFunder { }, ); - this.igp = HyperlaneIgp.fromEnvironment( - deployEnvToSdkEnv[this.environment], + this.igp = HyperlaneIgp.fromAddressesMap( + getEnvAddresses(this.environment), multiProvider, ); this.keysToFundPerChain = objMap(roleKeysPerChain, (_chain, roleKeys) => { @@ -526,7 +521,7 @@ class ContextFunder { desiredBalanceEther.mul(5), ); if (bridgeAmount.gt(0)) { - await this.bridgeToL2(chain as L2Chain, funderAddress, bridgeAmount); + await this.bridgeToL2(chain, funderAddress, bridgeAmount); } } } @@ -654,7 +649,7 @@ class ContextFunder { }); } - private async bridgeToL2(l2Chain: L2Chain, to: string, amount: BigNumber) { + private async bridgeToL2(l2Chain: ChainName, to: string, amount: BigNumber) { const l1Chain = L2ToL1[l2Chain]; logger.info('Bridging ETH to L2', { amount: ethers.utils.formatEther(amount), @@ -683,7 +678,7 @@ class ContextFunder { } private async bridgeToOptimism( - l2Chain: L2Chain, + l2Chain: ChainName, amount: BigNumber, to: string, ) { @@ -700,7 +695,7 @@ class ContextFunder { }); } - private async bridgeToArbitrum(l2Chain: L2Chain, amount: BigNumber) { + private async bridgeToArbitrum(l2Chain: ChainName, amount: BigNumber) { const l1Chain = L2ToL1[l2Chain]; const l2Network = await getL2Network( this.multiProvider.getDomainId(l2Chain), @@ -714,7 +709,7 @@ class ContextFunder { } private async bridgeToScroll( - l2Chain: L2Chain, + l2Chain: ChainName, amount: BigNumber, to: Address, ) { diff --git a/typescript/infra/scripts/funding/reclaim-from-igp.ts b/typescript/infra/scripts/funding/reclaim-from-igp.ts index 91e96a2ad2..2621c7c69e 100644 --- a/typescript/infra/scripts/funding/reclaim-from-igp.ts +++ b/typescript/infra/scripts/funding/reclaim-from-igp.ts @@ -3,7 +3,7 @@ import { BigNumber } from 'ethers'; import { HyperlaneIgp } from '@hyperlane-xyz/sdk'; import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; -import { deployEnvToSdkEnv } from '../../src/config/environment.js'; +import { getEnvAddresses } from '../../config/registry.js'; import { getArgs } from '../agent-utils.js'; import { getEnvironmentConfig } from '../core-utils.js'; @@ -14,8 +14,8 @@ async function main() { const { environment } = await getArgs().argv; const environmentConfig = getEnvironmentConfig(environment); const multiProvider = await environmentConfig.getMultiProvider(); - const igp = HyperlaneIgp.fromEnvironment( - deployEnvToSdkEnv[environment], + const igp = HyperlaneIgp.fromAddressesMap( + getEnvAddresses(environment), multiProvider, ); diff --git a/typescript/infra/scripts/helloworld/utils.ts b/typescript/infra/scripts/helloworld/utils.ts index 17ea929589..38ab8969f1 100644 --- a/typescript/infra/scripts/helloworld/utils.ts +++ b/typescript/infra/scripts/helloworld/utils.ts @@ -4,7 +4,6 @@ import { helloWorldFactories, } from '@hyperlane-xyz/helloworld'; import { - HyperlaneCore, HyperlaneIgp, MultiProtocolCore, MultiProtocolProvider, @@ -13,18 +12,16 @@ import { attachContractsMap, attachContractsMapAndGetForeignDeployments, filterChainMapToProtocol, - hyperlaneEnvironments, igpFactories, } from '@hyperlane-xyz/sdk'; import { ProtocolType, objMap } from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts.js'; -import { - EnvironmentConfig, - deployEnvToSdkEnv, -} from '../../src/config/environment.js'; +import { getEnvAddresses } from '../../config/registry.js'; +import { EnvironmentConfig } from '../../src/config/environment.js'; import { HelloWorldConfig } from '../../src/config/helloworld/types.js'; import { Role } from '../../src/roles.js'; +import { getHyperlaneCore } from '../core-utils.js'; export async function getHelloWorldApp( coreConfig: EnvironmentConfig, @@ -47,8 +44,8 @@ export async function getHelloWorldApp( multiProvider, ); - const core = HyperlaneCore.fromEnvironment( - deployEnvToSdkEnv[coreConfig.environment], + const { core } = await getHyperlaneCore( + coreConfig.environment, multiProvider, ); return new HelloWorldApp( @@ -71,8 +68,8 @@ export async function getHelloWorldMultiProtocolApp( keyRole, connectionType, ); - const sdkEnvName = deployEnvToSdkEnv[coreConfig.environment]; - const envAddresses = hyperlaneEnvironments[sdkEnvName]; + + const envAddresses = getEnvAddresses(coreConfig.environment); const keys = await coreConfig.getKeys(keyContext, keyRole); // Fetch all the keys, which is required to get the address for @@ -117,7 +114,7 @@ export async function getHelloWorldMultiProtocolApp( // } const core = MultiProtocolCore.fromAddressesMap( - envAddresses, + envAddresses as any, multiProtocolProvider, ); diff --git a/typescript/infra/scripts/list-validator-checkpoint-indices.ts b/typescript/infra/scripts/list-validator-checkpoint-indices.ts index 25a2ab3e29..ca539be797 100644 --- a/typescript/infra/scripts/list-validator-checkpoint-indices.ts +++ b/typescript/infra/scripts/list-validator-checkpoint-indices.ts @@ -1,20 +1,14 @@ -import { HyperlaneCore } from '@hyperlane-xyz/sdk'; import { concurrentMap } from '@hyperlane-xyz/utils'; import { S3Validator } from '../src/agents/aws/validator.js'; -import { deployEnvToSdkEnv } from '../src/config/environment.js'; import { getArgs, getValidatorsByChain } from './agent-utils.js'; -import { getEnvironmentConfig } from './core-utils.js'; +import { getEnvironmentConfig, getHyperlaneCore } from './core-utils.js'; async function main() { const { environment } = await getArgs().argv; const config = getEnvironmentConfig(environment); - const multiProvider = await config.getMultiProvider(); - const core = HyperlaneCore.fromEnvironment( - deployEnvToSdkEnv[environment], - multiProvider, - ); + const { core } = await getHyperlaneCore(environment); const validators = Object.entries(getValidatorsByChain(config.core)).flatMap( ([chain, set]) => [...set].map((validator) => ({ chain, validator })), diff --git a/typescript/infra/scripts/module-can-verify.ts b/typescript/infra/scripts/module-can-verify.ts index 5aa915b334..af782a402a 100644 --- a/typescript/infra/scripts/module-can-verify.ts +++ b/typescript/infra/scripts/module-can-verify.ts @@ -1,23 +1,15 @@ -import { HyperlaneCore, moduleCanCertainlyVerify } from '@hyperlane-xyz/sdk'; +import { moduleCanCertainlyVerify } from '@hyperlane-xyz/sdk'; import { ProtocolType } from '@hyperlane-xyz/utils'; -import { deployEnvToSdkEnv } from '../src/config/environment.js'; - import { getArgs } from './agent-utils.js'; -import { getEnvironmentConfig } from './core-utils.js'; +import { getHyperlaneCore } from './core-utils.js'; async function main() { const args = await getArgs().argv; const { environment } = args; - const config = getEnvironmentConfig(environment); - const multiProvider = await config.getMultiProvider(); - - const core = HyperlaneCore.fromEnvironment( - deployEnvToSdkEnv[environment], - multiProvider, - ); + const { core, multiProvider } = await getHyperlaneCore(environment); for (const local of core.chains()) { if ( diff --git a/typescript/infra/scripts/print-multisig-ism-config.ts b/typescript/infra/scripts/print-multisig-ism-config.ts index ff4ac9cc89..61ff67fa3b 100644 --- a/typescript/infra/scripts/print-multisig-ism-config.ts +++ b/typescript/infra/scripts/print-multisig-ism-config.ts @@ -1,6 +1,7 @@ -import { AllChains, IsmType } from '@hyperlane-xyz/sdk'; +import { IsmType } from '@hyperlane-xyz/sdk'; import { multisigIsms } from '../config/multisigIsm.js'; +import { getChains } from '../config/registry.js'; import { getArgs, withContext } from './agent-utils.js'; @@ -10,7 +11,7 @@ import { getArgs, withContext } from './agent-utils.js'; async function main() { const args = await withContext(getArgs()) .describe('local', 'local chain') - .choices('local', AllChains) + .choices('local', getChains()) .demandOption('local').argv; const config = multisigIsms( diff --git a/typescript/infra/scripts/safe-delegate.ts b/typescript/infra/scripts/safe-delegate.ts index f2b8d39f00..f067c2db8c 100644 --- a/typescript/infra/scripts/safe-delegate.ts +++ b/typescript/infra/scripts/safe-delegate.ts @@ -4,8 +4,7 @@ import { LedgerSigner } from '@ethersproject/hardware-wallets'; import '@ethersproject/hardware-wallets/thirdparty'; import { AddSafeDelegateProps } from '@safe-global/api-kit'; -import { AllChains } from '@hyperlane-xyz/sdk'; - +import { getChains } from '../config/registry.js'; import { getSafeDelegates, getSafeService } from '../src/utils/safe.js'; import { getArgs as getRootArgs } from './agent-utils.js'; @@ -14,7 +13,7 @@ import { getEnvironmentConfig } from './core-utils.js'; function getArgs() { return getRootArgs() .describe('chain', 'chain of the validator to inspect') - .choices('chain', AllChains) + .choices('chain', getChains()) .demandOption('chain') .describe('action', 'add or remove') .choices('action', ['add', 'remove']) diff --git a/typescript/infra/scripts/send-test-messages.ts b/typescript/infra/scripts/send-test-messages.ts index b18e796629..f602f4b30e 100644 --- a/typescript/infra/scripts/send-test-messages.ts +++ b/typescript/infra/scripts/send-test-messages.ts @@ -5,10 +5,10 @@ import yargs from 'yargs'; import { Mailbox, TestSendReceiver__factory } from '@hyperlane-xyz/core'; import { ChainName, - Chains, HookType, HyperlaneCore, MultiProvider, + TestChainName, } from '@hyperlane-xyz/sdk'; import { addressToBytes32, sleep } from '@hyperlane-xyz/utils'; @@ -102,7 +102,7 @@ async function main() { const signer = new Wallet(ANVIL_KEY); const multiProvider = MultiProvider.createTestMultiProvider({ signer }); - const provider = multiProvider.getProvider(Chains.test1); + const provider = multiProvider.getProvider(TestChainName.test1); const addresses = JSON.parse( fs.readFileSync('./config/environments/test/core/addresses.json', 'utf8'), diff --git a/typescript/infra/scripts/verify-validators.ts b/typescript/infra/scripts/verify-validators.ts index 559161c9ab..755d568a97 100644 --- a/typescript/infra/scripts/verify-validators.ts +++ b/typescript/infra/scripts/verify-validators.ts @@ -1,20 +1,14 @@ -import { HyperlaneCore } from '@hyperlane-xyz/sdk'; import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; import { S3Validator } from '../src/agents/aws/validator.js'; -import { deployEnvToSdkEnv } from '../src/config/environment.js'; import { getArgs, getValidatorsByChain } from './agent-utils.js'; -import { getEnvironmentConfig } from './core-utils.js'; +import { getEnvironmentConfig, getHyperlaneCore } from './core-utils.js'; async function main() { const { environment } = await getArgs().argv; const config = getEnvironmentConfig(environment); - const multiProvider = await config.getMultiProvider(); - const core = HyperlaneCore.fromEnvironment( - deployEnvToSdkEnv[environment], - multiProvider, - ); + const { core } = await getHyperlaneCore(environment); await promiseObjAll( objMap(getValidatorsByChain(config.core), async (chain, set) => { diff --git a/typescript/infra/scripts/verify.ts b/typescript/infra/scripts/verify.ts index 0a8c03b3ef..9c15c6597b 100644 --- a/typescript/infra/scripts/verify.ts +++ b/typescript/infra/scripts/verify.ts @@ -5,18 +5,14 @@ import { VerificationInput, } from '@hyperlane-xyz/sdk'; +import { assertEnvironment } from '../src/config/environment.js'; import { extractBuildArtifact, fetchExplorerApiKeys, } from '../src/deployment/verify.js'; import { readJSONAtPath } from '../src/utils/utils.js'; -import { - assertEnvironment, - getArgs, - withBuildArtifactPath, - withNetwork, -} from './agent-utils.js'; +import { getArgs, withBuildArtifactPath, withNetwork } from './agent-utils.js'; import { getEnvironmentConfig } from './core-utils.js'; async function main() { diff --git a/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts b/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts index 13e5f36774..e7ebfbc282 100644 --- a/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts +++ b/typescript/infra/scripts/warp-routes/monitor-warp-routes-balances.ts @@ -22,6 +22,7 @@ import { rootLogger, } from '@hyperlane-xyz/utils'; +import { getChainMetadata } from '../../config/registry.js'; import { startMetricsServer } from '../../src/utils/metrics.js'; import { readYaml } from '../../src/utils/utils.js'; @@ -75,7 +76,7 @@ async function main(): Promise { startMetricsServer(metricsRegister); logger.info('Starting Warp Route balance monitor'); - const multiProtocolProvider = new MultiProtocolProvider(); + const multiProtocolProvider = new MultiProtocolProvider(getChainMetadata()); setInterval(async () => { try { diff --git a/typescript/infra/src/agents/index.ts b/typescript/infra/src/agents/index.ts index 6105536dc4..eb8a680294 100644 --- a/typescript/infra/src/agents/index.ts +++ b/typescript/infra/src/agents/index.ts @@ -1,9 +1,10 @@ import fs from 'fs'; import { join } from 'path'; -import { ChainName, RpcConsensusType, chainMetadata } from '@hyperlane-xyz/sdk'; +import { ChainName, RpcConsensusType } from '@hyperlane-xyz/sdk'; import { Contexts } from '../../config/contexts.js'; +import { getChain } from '../../config/registry.js'; import { AgentConfigHelper, AgentContextConfig, @@ -122,7 +123,7 @@ export abstract class AgentHelmManager { context: this.context, aws: !!this.config.aws, chains: this.config.contextChainNames[this.role].map((chain) => { - const metadata = chainMetadata[chain]; + const metadata = getChain(chain); const reorgPeriod = metadata.blocks?.reorgPeriod; if (reorgPeriod === undefined) { throw new Error(`No reorg period found for chain ${chain}`); diff --git a/typescript/infra/src/agents/key-utils.ts b/typescript/infra/src/agents/key-utils.ts index e9d352e137..46262ed8ba 100644 --- a/typescript/infra/src/agents/key-utils.ts +++ b/typescript/infra/src/agents/key-utils.ts @@ -10,8 +10,8 @@ import localKathyAddresses from '../../config/kathy.json'; import localRelayerAddresses from '../../config/relayer.json'; import { getAWValidatorsPath } from '../../scripts/agent-utils.js'; import { getJustHelloWorldConfig } from '../../scripts/helloworld/utils.js'; -import { AgentContextConfig, RootAgentConfig } from '../config/agent/agent'; -import { DeployEnvironment } from '../config/environment'; +import { AgentContextConfig, RootAgentConfig } from '../config/agent/agent.js'; +import { DeployEnvironment } from '../config/environment.js'; import { Role } from '../roles.js'; import { execCmd, diff --git a/typescript/infra/src/config/agent/agent.ts b/typescript/infra/src/config/agent/agent.ts index 0b630981f0..1ccd1d45b8 100644 --- a/typescript/infra/src/config/agent/agent.ts +++ b/typescript/infra/src/config/agent/agent.ts @@ -5,11 +5,11 @@ import { ChainMap, ChainName, RpcConsensusType, - chainMetadata, } from '@hyperlane-xyz/sdk'; import { ProtocolType, objMap } from '@hyperlane-xyz/utils'; import { Contexts } from '../../../config/contexts.js'; +import { getChain } from '../../../config/registry.js'; import { AgentChainNames, AgentRole, Role } from '../../roles.js'; import { DeployEnvironment } from '../environment.js'; import { HelmImageValues } from '../infrastructure.js'; @@ -195,12 +195,14 @@ export const allAgentChainNames = (agentChainNames: AgentChainNames) => [ // Returns the default KeyConfig for the `chainName`'s chain signer. // For Ethereum or Sealevel, this is a hexKey, for Cosmos, this is a cosmosKey. export function defaultChainSignerKeyConfig(chainName: ChainName): KeyConfig { - const metadata = chainMetadata[chainName]; + const metadata = getChain(chainName); switch (metadata?.protocol) { case ProtocolType.Cosmos: if (metadata.bech32Prefix === undefined) { - throw new Error(`Bech32 prefix for cosmos chain ${name} is undefined`); + throw new Error( + `Bech32 prefix for cosmos chain ${chainName} is undefined`, + ); } return { type: AgentSignerKeyType.Cosmos, prefix: metadata.bech32Prefix }; // For Ethereum and Sealevel, use a hex key @@ -220,7 +222,7 @@ export function getAgentChainNamesFromConfig( ): AgentChainNames { ensureAgentChainConfigIncludesAllChainNames(config, supportedChainNames); - return objMap(config, (role, roleConfig) => + return objMap(config, (_, roleConfig) => Object.entries(roleConfig) .filter(([_chain, enabled]) => enabled) .map(([chain]) => chain), diff --git a/typescript/infra/src/config/agent/relayer.ts b/typescript/infra/src/config/agent/relayer.ts index f09d177f44..3d88c16ff5 100644 --- a/typescript/infra/src/config/agent/relayer.ts +++ b/typescript/infra/src/config/agent/relayer.ts @@ -9,11 +9,10 @@ import { HyperlaneFactories, MatchingList, RelayerConfig as RelayerAgentConfig, - chainMetadata, - getDomainId, } from '@hyperlane-xyz/sdk'; import { Address, ProtocolType, addressToBytes32 } from '@hyperlane-xyz/utils'; +import { getChain, getDomainId } from '../../../config/registry.js'; import { AgentAwsUser } from '../../agents/aws/user.js'; import { Role } from '../../roles.js'; import { HelmStatefulSetValues } from '../infrastructure.js'; @@ -114,7 +113,7 @@ export class RelayerConfigHelper extends AgentConfigHelper { // AWS keys only work for Ethereum chains for (const chainName of this.relayChains) { - if (chainMetadata[chainName].protocol === ProtocolType.Ethereum) { + if (getChain(chainName).protocol === ProtocolType.Ethereum) { chainSigners[chainName] = awsKey; } } @@ -180,9 +179,9 @@ export function matchingList( ); matchingList.push({ - originDomain: getDomainId(chainMetadata[source]), + originDomain: getDomainId(source), senderAddress: uniqueAddresses(addressesMap[source]), - destinationDomain: getDomainId(chainMetadata[destination]), + destinationDomain: getDomainId(destination), recipientAddress: uniqueAddresses(addressesMap[destination]), }); } diff --git a/typescript/infra/src/config/agent/validator.ts b/typescript/infra/src/config/agent/validator.ts index ac8fd6dd67..d424c76c39 100644 --- a/typescript/infra/src/config/agent/validator.ts +++ b/typescript/infra/src/config/agent/validator.ts @@ -4,10 +4,10 @@ import { ValidatorConfig as AgentValidatorConfig, ChainMap, ChainName, - chainMetadata, } from '@hyperlane-xyz/sdk'; import { ProtocolType } from '@hyperlane-xyz/utils'; +import { getChain } from '../../../config/registry.js'; import { ValidatorAgentAwsUser } from '../../agents/aws/validator-user.js'; import { Role } from '../../roles.js'; import { HelmStatefulSetValues } from '../infrastructure.js'; @@ -118,7 +118,7 @@ export class ValidatorConfigHelper extends AgentConfigHelper { cfg: ValidatorBaseConfig, idx: number, ): Promise { - const metadata = chainMetadata[this.chainName]; + const metadata = getChain(this.chainName); const protocol = metadata.protocol; let validator: KeyConfig = { type: AgentSignerKeyType.Hex }; diff --git a/typescript/infra/src/config/chain.ts b/typescript/infra/src/config/chain.ts index ddd66fa13f..b64035b2b5 100644 --- a/typescript/infra/src/config/chain.ts +++ b/typescript/infra/src/config/chain.ts @@ -2,16 +2,14 @@ import { providers } from 'ethers'; import { ChainMetadata, - ChainMetadataManager, ChainName, - CoreChainName, HyperlaneSmartProvider, ProviderRetryOptions, RpcConsensusType, - chainMetadata, } from '@hyperlane-xyz/sdk'; import { ProtocolType, objFilter } from '@hyperlane-xyz/utils'; +import { getChain } from '../../config/registry.js'; import { getSecretRpcEndpoint } from '../agents/index.js'; import { DeployEnvironment } from './environment.js'; @@ -26,14 +24,13 @@ export async function fetchProvider( chainName: ChainName, connectionType: RpcConsensusType = RpcConsensusType.Single, ): Promise { - const cmm = new ChainMetadataManager(chainMetadata); - const chainData = cmm.tryGetChainMetadata(chainName); - if (!chainData) { + const chainMetadata = getChain(chainName); + if (!chainMetadata) { throw Error(`Unsupported chain: ${chainName}`); } - const chainId = chainData.chainId; + const chainId = chainMetadata.chainId; const single = connectionType === RpcConsensusType.Single; - let rpcData = chainData.rpcUrls.map((url) => url.http); + let rpcData = chainMetadata.rpcUrls.map((url) => url.http); if (rpcData.length === 0) { rpcData = await getSecretRpcEndpoint(environment, chainName, !single); } @@ -56,10 +53,10 @@ export async function fetchProvider( } } -export function getChainMetadatas(chains: Array) { +export function getChainMetadatas(chains: Array) { const allMetadatas = Object.fromEntries( chains - .map((chain) => chainMetadata[chain]) + .map((chain) => getChain(chain)) .map((metadata) => [metadata.name, metadata]), ); diff --git a/typescript/infra/src/config/environment.ts b/typescript/infra/src/config/environment.ts index 63a04f3b49..fbb9b74c75 100644 --- a/typescript/infra/src/config/environment.ts +++ b/typescript/infra/src/config/environment.ts @@ -4,12 +4,12 @@ import { ChainMetadata, ChainName, CoreConfig, - HyperlaneEnvironment, IgpConfig, MultiProvider, OwnableConfig, RpcConsensusType, } from '@hyperlane-xyz/sdk'; +import { objKeys } from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts.js'; import { environments } from '../../config/environments/index.js'; @@ -22,13 +22,20 @@ import { HelloWorldConfig } from './helloworld/types.js'; import { InfrastructureConfig } from './infrastructure.js'; import { LiquidityLayerRelayerConfig } from './middleware.js'; -// TODO: fix this? -export const EnvironmentNames = ['test', 'testnet4', 'mainnet3']; export type DeployEnvironment = keyof typeof environments; export type EnvironmentChain = Extract< keyof (typeof environments)[E], ChainName >; +export enum AgentEnvironment { + Testnet = 'testnet', + Mainnet = 'mainnet', +} +export const envNameToAgentEnv: Record = { + test: AgentEnvironment.Testnet, + testnet4: AgentEnvironment.Testnet, + mainnet3: AgentEnvironment.Mainnet, +}; export type EnvironmentConfig = { environment: DeployEnvironment; @@ -56,11 +63,10 @@ export type EnvironmentConfig = { }; }; -export const deployEnvToSdkEnv: Record< - DeployEnvironment, - HyperlaneEnvironment -> = { - test: 'testnet', // TODO: remove this - mainnet3: 'mainnet', - testnet4: 'testnet', -}; +export function assertEnvironment(env: string): DeployEnvironment { + const envNames = objKeys(environments); + if (envNames.includes(env as any)) { + return env as DeployEnvironment; + } + throw new Error(`Invalid environment ${env}, must be one of ${envNames}`); +} diff --git a/typescript/infra/src/config/gas-oracle.ts b/typescript/infra/src/config/gas-oracle.ts index 82fcb77b04..d12ae71471 100644 --- a/typescript/infra/src/config/gas-oracle.ts +++ b/typescript/infra/src/config/gas-oracle.ts @@ -6,11 +6,11 @@ import { ChainName, StorageGasOracleConfig as DestinationOracleConfig, TOKEN_EXCHANGE_RATE_SCALE, - chainMetadata, getCosmosRegistryChain, } from '@hyperlane-xyz/sdk'; import { ProtocolType, convertDecimals } from '@hyperlane-xyz/utils'; +import { getChain } from '../../config/registry.js'; import { isEthereumProtocolChain, mustGetChainNativeToken, @@ -222,7 +222,7 @@ export function getTokenExchangeRateFromValues( export async function getCosmosChainGasPrice( chain: ChainName, ): Promise { - const metadata = chainMetadata[chain]; + const metadata = getChain(chain); if (!metadata) { throw new Error(`No metadata found for Cosmos chain ${chain}`); } diff --git a/typescript/infra/src/deployment/deploy.ts b/typescript/infra/src/deployment/deploy.ts index 64ec3e349a..7812dec3a9 100644 --- a/typescript/infra/src/deployment/deploy.ts +++ b/typescript/infra/src/deployment/deploy.ts @@ -1,11 +1,10 @@ +import { ChainAddresses } from '@hyperlane-xyz/registry'; import { ChainMap, ChainName, - HyperlaneAddresses, HyperlaneCore, HyperlaneDeployer, HyperlaneDeploymentArtifacts, - HyperlaneEnvironment, MultiProvider, buildAgentConfig, serializeContractsMap, @@ -20,10 +19,13 @@ import { import { Contexts } from '../../config/contexts.js'; import { + Modules, + getAddresses, getAgentConfig, getAgentConfigJsonPath, + writeAddresses, } from '../../scripts/agent-utils.js'; -import { DeployEnvironment, deployEnvToSdkEnv } from '../config/environment.js'; +import { DeployEnvironment, envNameToAgentEnv } from '../config/environment.js'; import { getCosmosChainGasPrice } from '../config/gas-oracle.js'; import { chainIsProtocol, @@ -36,26 +38,20 @@ export async function deployWithArtifacts( configMap: ChainMap, deployer: HyperlaneDeployer, cache: { - addresses: string; verification: string; read: boolean; write: boolean; + environment: DeployEnvironment; + module: Modules; }, targetNetwork?: ChainName, agentConfig?: { multiProvider: MultiProvider; - addresses: string; environment: DeployEnvironment; }, ) { if (cache.read) { - let addressesMap = {}; - try { - addressesMap = readJSONAtPath(cache.addresses); - } catch (e) { - console.error('Failed to load cached addresses'); - } - + const addressesMap = getAddresses(cache.environment, cache.module); deployer.cacheAddressesMap(addressesMap); } @@ -84,14 +80,14 @@ export async function deployWithArtifacts( export async function postDeploy( deployer: HyperlaneDeployer, cache: { - addresses: string; verification: string; read: boolean; write: boolean; + environment: DeployEnvironment; + module: Modules; }, agentConfig?: { multiProvider: MultiProvider; - addresses: string; environment: DeployEnvironment; }, ) { @@ -102,7 +98,7 @@ export async function postDeploy( const addresses = objMerge(deployedAddresses, cachedAddresses); // cache addresses of deployed contracts - writeMergedJSONAtPath(cache.addresses, addresses); + writeAddresses(cache.environment, cache.module, addresses); let savedVerification = {}; try { @@ -117,30 +113,24 @@ export async function postDeploy( writeJsonAtPath(cache.verification, inputs); } if (agentConfig) { - await writeAgentConfig( - agentConfig.addresses, - agentConfig.multiProvider, - agentConfig.environment, - ); + await writeAgentConfig(agentConfig.multiProvider, agentConfig.environment); } } export async function writeAgentConfig( - addressesPath: string, multiProvider: MultiProvider, environment: DeployEnvironment, ) { - let addresses: ChainMap> = {}; - try { - addresses = readJSONAtPath(addressesPath); - } catch (e) { - console.error('Failed to load cached addresses'); - } + const addresses = getAddresses(environment, Modules.CORE); + const addressesForEnv = objFilter( + addresses, + (chain, _): _ is ChainAddresses => multiProvider.hasChain(chain), + ); - const core = HyperlaneCore.fromAddressesMap(addresses, multiProvider); + const core = HyperlaneCore.fromAddressesMap(addressesForEnv, multiProvider); // Write agent config indexing from the deployed Mailbox which stores the block number at deployment const startBlocks = await promiseObjAll( - objMap(addresses, async (chain, _) => { + objMap(addressesForEnv, async (chain, _) => { // If the index.from is specified in the chain metadata, use that. const indexFrom = multiProvider.getChainMetadata(chain).index?.from; if (indexFrom !== undefined) { @@ -179,12 +169,12 @@ export async function writeAgentConfig( const agentConfig = buildAgentConfig( environmentChains, multiProvider, - addresses as ChainMap, + addressesForEnv as ChainMap, startBlocks, additionalConfig, ); writeMergedJSONAtPath( - getAgentConfigJsonPath(deployEnvToSdkEnv[environment]), + getAgentConfigJsonPath(envNameToAgentEnv[environment]), agentConfig, ); } diff --git a/typescript/infra/src/utils/safe.ts b/typescript/infra/src/utils/safe.ts index 107ff17807..ae6343dde8 100644 --- a/typescript/infra/src/utils/safe.ts +++ b/typescript/infra/src/utils/safe.ts @@ -2,35 +2,41 @@ import SafeApiKit from '@safe-global/api-kit'; import Safe, { EthersAdapter } from '@safe-global/protocol-kit'; import { ethers } from 'ethers'; -import { ChainName, MultiProvider, chainMetadata } from '@hyperlane-xyz/sdk'; +import { ChainName, MultiProvider } from '@hyperlane-xyz/sdk'; + +import { getChain } from '../../config/registry.js'; + +// NOTE about Safe: +// Accessing lib through .default due to https://github.com/safe-global/safe-core-sdk/issues/419 +// See also https://github.com/safe-global/safe-core-sdk/issues/514 export function getSafeService( chain: ChainName, multiProvider: MultiProvider, -): SafeApiKit { +): SafeApiKit.default { const signer = multiProvider.getSigner(chain); const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer }); - const txServiceUrl = chainMetadata[chain].gnosisSafeTransactionServiceUrl; + const txServiceUrl = getChain(chain).gnosisSafeTransactionServiceUrl; if (!txServiceUrl) throw new Error(`must provide tx service url for ${chain}`); - return new SafeApiKit({ txServiceUrl, ethAdapter }); + return new SafeApiKit.default({ txServiceUrl, ethAdapter }); } export function getSafe( chain: ChainName, multiProvider: MultiProvider, safeAddress: string, -): Promise { +): Promise { const signer = multiProvider.getSigner(chain); const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer }); - return Safe.create({ + return Safe.default.create({ ethAdapter, safeAddress: safeAddress, }); } export async function getSafeDelegates( - service: SafeApiKit, + service: SafeApiKit.default, safeAddress: string, ) { const delegateResponse = await service.getSafeDelegates({ safeAddress }); diff --git a/typescript/infra/src/utils/utils.ts b/typescript/infra/src/utils/utils.ts index ece13f62d1..476fe3913f 100644 --- a/typescript/infra/src/utils/utils.ts +++ b/typescript/infra/src/utils/utils.ts @@ -8,17 +8,12 @@ import path, { dirname, join } from 'path'; import { fileURLToPath } from 'url'; import { parse as yamlParse } from 'yaml'; -import { - AllChains, - ChainMetadata, - ChainName, - CoreChainName, - NativeToken, - chainMetadata, -} from '@hyperlane-xyz/sdk'; +import { ChainName, NativeToken } from '@hyperlane-xyz/sdk'; import { ProtocolType, objMerge } from '@hyperlane-xyz/utils'; import { Contexts } from '../../config/contexts.js'; +import { testChainNames } from '../../config/environments/test/chains.js'; +import { getChain, getChains } from '../../config/registry.js'; import { FundableRole, Role } from '../roles.js'; export function include(condition: boolean, data: any) { @@ -197,9 +192,8 @@ export function assertFundableRole(roleStr: string): FundableRole { return role; } -export function assertChain(chainStr: string) { - const chain = chainStr as ChainName; - if (!AllChains.includes(chain as CoreChainName)) { +export function assertChain(chain: ChainName) { + if (!getChains().includes(chain) && !testChainNames.includes(chain)) { throw Error(`Invalid chain ${chain}`); } return chain; @@ -256,7 +250,7 @@ export function diagonalize(array: Array>): Array { } export function mustGetChainNativeToken(chain: ChainName): NativeToken { - const metadata = chainMetadata[chain]; + const metadata = getChain(chain); if (!metadata.nativeToken) { throw new Error(`No native token for chain ${chain}`); } @@ -264,8 +258,8 @@ export function mustGetChainNativeToken(chain: ChainName): NativeToken { } export function chainIsProtocol(chainName: ChainName, protocol: ProtocolType) { - if (!chainMetadata[chainName]) throw new Error(`Unknown chain ${chainName}`); - return chainMetadata[chainName].protocol === protocol; + if (!getChain(chainName)) throw new Error(`Unknown chain ${chainName}`); + return getChain(chainName).protocol === protocol; } export function isEthereumProtocolChain(chainName: ChainName) { diff --git a/typescript/infra/test/agent-configs.test.ts b/typescript/infra/test/agent-configs.test.ts index 8fba68843a..ced36006ab 100644 --- a/typescript/infra/test/agent-configs.test.ts +++ b/typescript/infra/test/agent-configs.test.ts @@ -1,11 +1,12 @@ import { expect } from 'chai'; import { hyperlaneContextAgentChainConfig as mainnet3AgentChainConfig } from '../config/environments/mainnet3/agent.js'; -import { supportedChainNames as mainnet3SupportedChainNames } from '../config/environments/mainnet3/chains.js'; +import { supportedChainNames as mainnet3SupportedChainNames } from '../config/environments/mainnet3/supportedChainNames.js'; import { hyperlaneContextAgentChainConfig as testnet4AgentChainConfig } from '../config/environments/testnet4/agent.js'; -import { supportedChainNames as testnet4SupportedChainNames } from '../config/environments/testnet4/chains.js'; +import { supportedChainNames as testnet4SupportedChainNames } from '../config/environments/testnet4/supportedChainNames.js'; import { getAgentConfigJsonPath } from '../scripts/agent-utils.js'; import { ensureAgentChainConfigIncludesAllChainNames } from '../src/config/agent/agent.js'; +import { AgentEnvironment } from '../src/config/environment.js'; import { readJSONAtPath } from '../src/utils/utils.js'; const environmentChainConfigs = { @@ -13,12 +14,16 @@ const environmentChainConfigs = { agentChainConfig: mainnet3AgentChainConfig, // We read the agent config from the file system instead of importing // to get around the agent JSON configs living outside the typescript rootDir - agentJsonConfig: readJSONAtPath(getAgentConfigJsonPath('mainnet')), + agentJsonConfig: readJSONAtPath( + getAgentConfigJsonPath(AgentEnvironment.Mainnet), + ), supportedChainNames: mainnet3SupportedChainNames, }, testnet4: { agentChainConfig: testnet4AgentChainConfig, - agentJsonConfig: readJSONAtPath(getAgentConfigJsonPath('testnet')), + agentJsonConfig: readJSONAtPath( + getAgentConfigJsonPath(AgentEnvironment.Testnet), + ), supportedChainNames: testnet4SupportedChainNames, }, }; diff --git a/typescript/infra/test/govern.hardhat-test.ts b/typescript/infra/test/govern.hardhat-test.ts index a94e5dbc15..07f94c0cef 100644 --- a/typescript/infra/test/govern.hardhat-test.ts +++ b/typescript/infra/test/govern.hardhat-test.ts @@ -12,7 +12,6 @@ import { AccountConfig, ChainMap, ChainName, - Chains, HyperlaneApp, HyperlaneAppChecker, HyperlaneContractsMap, @@ -24,6 +23,7 @@ import { MultiProvider, OwnableConfig, RouterConfig, + TestChainName, TestCoreApp, TestCoreDeployer, resolveOrDeployAccountOwner, @@ -43,7 +43,7 @@ export class TestApp extends HyperlaneApp<{}> {} export class TestChecker extends HyperlaneAppChecker { async checkChain(_: string): Promise { this.addViolation({ - chain: Chains.test2, + chain: TestChainName.test2, type: 'test', expected: 0, actual: 1, @@ -90,8 +90,8 @@ export class HyperlaneTestGovernor extends HyperlaneAppGovernor< } describe('ICA governance', async () => { - const localChain = Chains.test1; - const remoteChain = Chains.test2; + const localChain = TestChainName.test1; + const remoteChain = TestChainName.test2; let signer: SignerWithAddress; let multiProvider: MultiProvider; @@ -129,7 +129,7 @@ describe('ICA governance', async () => { icaApp = new InterchainAccount(contracts, multiProvider); accountConfig = { - origin: Chains.test1, + origin: TestChainName.test1, owner: signer.address, localRouter: remote.address, }; @@ -149,7 +149,7 @@ describe('ICA governance', async () => { const configMap = { [localChain]: { owner: signer.address }, [remoteChain]: { - owner: { origin: Chains.test1, owner: signer.address }, + owner: { origin: TestChainName.test1, owner: signer.address }, }, }; @@ -172,7 +172,7 @@ describe('ICA governance', async () => { // arrange const newIsm = randomAddress(); - await governor.checker.checkChain(Chains.test2); + await governor.checker.checkChain(TestChainName.test2); const call = { to: recipient.address, data: recipient.interface.encodeFunctionData( diff --git a/typescript/infra/tsconfig.json b/typescript/infra/tsconfig.json index 97ee75a9a8..20cf059b11 100644 --- a/typescript/infra/tsconfig.json +++ b/typescript/infra/tsconfig.json @@ -6,8 +6,8 @@ "outDir": "./dist/", "rootDir": "./", "noUnusedLocals": false, - "module": "es2022", - "moduleResolution": "node", + "module": "nodenext", + "moduleResolution": "nodenext", }, "exclude": ["./node_modules/", "./dist/", "./tmp.ts"], "include": [ diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index bd98fa188b..a1a60493e1 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -39,7 +39,7 @@ "ts-node": "^10.8.0", "tsx": "^4.7.1", "typescript": "5.3.3", - "yaml": "^2.3.1" + "yaml": "^2.4.1" }, "type": "module", "exports": { @@ -75,7 +75,6 @@ "test:ci": "yarn test", "test:unit": "mocha --config .mocharc.json './src/**/*.test.ts' --exit", "test:hardhat": "NODE_OPTIONS='--experimental-loader ts-node/esm/transpile-only --no-warnings=ExperimentalWarning' hardhat --config hardhat.config.cts test $(find ./src -name \"*.hardhat-test.ts\")", - "test:metadata": "tsx ./src/test/metadata-check.ts", "test:foundry": "./scripts/foundry-test.sh" }, "peerDependencies": { diff --git a/typescript/sdk/src/app/MultiProtocolApp.test.ts b/typescript/sdk/src/app/MultiProtocolApp.test.ts index 49251ab6dc..381eafb1bf 100644 --- a/typescript/sdk/src/app/MultiProtocolApp.test.ts +++ b/typescript/sdk/src/app/MultiProtocolApp.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import { ProtocolType } from '@hyperlane-xyz/utils'; -import { Chains } from '../consts/chains.js'; +import { TestChainName } from '../consts/testChains.js'; import { MultiProtocolProvider } from '../providers/MultiProtocolProvider.js'; import { @@ -23,20 +23,18 @@ class TestMultiProtocolApp extends MultiProtocolApp { } describe('MultiProtocolApp', () => { - describe('constructs', () => { - const multiProvider = new MultiProtocolProvider(); - it('creates an app class and gleans types from generic', async () => { - const addresses = { - ethereum: {}, - }; - const app = new TestMultiProtocolApp( - multiProvider.intersect(Object.keys(addresses)).result, - addresses, - ); - expect(app).to.be.instanceOf(MultiProtocolApp); - expect(app.adapter(Chains.ethereum).protocol).to.eql( - ProtocolType.Ethereum, - ); - }); + const multiProvider = MultiProtocolProvider.createTestMultiProtocolProvider(); + it('creates an app class and gleans types from generic', async () => { + const addresses = { + test1: {}, + }; + const app = new TestMultiProtocolApp( + multiProvider.intersect(Object.keys(addresses)).result, + addresses, + ); + expect(app).to.be.instanceOf(MultiProtocolApp); + expect(app.adapter(TestChainName.test1).protocol).to.eql( + ProtocolType.Ethereum, + ); }); }); diff --git a/typescript/sdk/src/consts/chainMetadata.ts b/typescript/sdk/src/consts/chainMetadata.ts deleted file mode 100644 index 254c54a0ed..0000000000 --- a/typescript/sdk/src/consts/chainMetadata.ts +++ /dev/null @@ -1,1085 +0,0 @@ -import { ProtocolType } from '@hyperlane-xyz/utils'; - -import { - ChainMetadata, - ChainTechnicalStack, - ExplorerFamily, -} from '../metadata/chainMetadataTypes.js'; -import { ChainMap } from '../types.js'; - -import { Chains, Mainnets, Testnets } from './chains.js'; - -/** - * Common native currencies - */ -export const avaxToken = { decimals: 18, name: 'Avalanche', symbol: 'AVAX' }; -export const bnbToken = { decimals: 18, name: 'BNB', symbol: 'BNB' }; -export const celoToken = { decimals: 18, name: 'CELO', symbol: 'CELO' }; -export const etherToken = { decimals: 18, name: 'Ether', symbol: 'ETH' }; -export const maticToken = { decimals: 18, name: 'MATIC', symbol: 'MATIC' }; -export const xDaiToken = { decimals: 18, name: 'xDai', symbol: 'xDai' }; -export const solToken = { decimals: 9, name: 'Sol', symbol: 'SOL' }; - -export const alfajores: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://api-alfajores.celoscan.io/api', - family: ExplorerFamily.Etherscan, - name: 'CeloScan', - url: 'https://alfajores.celoscan.io', - }, - { - apiUrl: 'https://explorer.celo.org/alfajores/api', - family: ExplorerFamily.Blockscout, - name: 'Blockscout', - url: 'https://explorer.celo.org/alfajores', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 5, - reorgPeriod: 0, - }, - chainId: 44787, - displayName: 'Alfajores', - domainId: 44787, - isTestnet: true, - name: Chains.alfajores, - nativeToken: celoToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'https://alfajores-forno.celo-testnet.org' }], -}; - -export const ancient8: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://scan.ancient8.gg/api', - family: ExplorerFamily.Blockscout, - name: 'Ancient8 Explorer', - url: 'https://scan.ancient8.gg', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 2, - reorgPeriod: 0, - }, - chainId: 888888888, - displayName: 'Ancient8', - domainId: 888888888, - gasCurrencyCoinGeckoId: 'ethereum', - isTestnet: false, - name: Chains.ancient8, - nativeToken: etherToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'https://rpc.ancient8.gg' }], - technicalStack: ChainTechnicalStack.Other, -}; - -export const arbitrum: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://api.arbiscan.io/api', - family: ExplorerFamily.Etherscan, - name: 'Arbiscan', - url: 'https://arbiscan.io', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 3, - reorgPeriod: 0, - }, - chainId: 42161, - displayName: 'Arbitrum', - domainId: 42161, - gasCurrencyCoinGeckoId: 'ethereum', - gnosisSafeTransactionServiceUrl: - 'https://safe-transaction-arbitrum.safe.global/', - index: { - // Arbitrum Nitro flavored chains record the L1 block number they were deployed at, - // not the L2 block number. See https://docs.arbitrum.io/build-decentralized-apps/arbitrum-vs-ethereum/block-numbers-and-time#ethereum-block-numbers-within-arbitrum. - // This is the block that the Mailbox was deployed at: - // https://arbiscan.io/tx/0x946b241bfa1465d8de7247c155a533d2ee9437a2763a0399f1ca458f13b5efa5 - from: 143649797, - }, - name: Chains.arbitrum, - nativeToken: etherToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'https://arb1.arbitrum.io/rpc' }], - technicalStack: ChainTechnicalStack.ArbitrumNitro, -}; - -export const avalanche: ChainMetadata = { - blockExplorers: [ - { - apiUrl: - 'https://api.routescan.io/v2/network/mainnet/evm/43114/etherscan/api', - family: ExplorerFamily.Routescan, - name: 'SnowTrace', - url: 'https://snowtrace.io', - }, - ], - blocks: { - confirmations: 3, - estimateBlockTime: 2, - reorgPeriod: 3, - }, - chainId: 43114, - displayName: 'Avalanche', - domainId: 43114, - gasCurrencyCoinGeckoId: 'avalanche-2', - gnosisSafeTransactionServiceUrl: - 'https://safe-transaction-avalanche.safe.global/', - name: Chains.avalanche, - nativeToken: avaxToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [ - { - http: 'https://api.avax.network/ext/bc/C/rpc', - pagination: { - maxBlockRange: 100000, - minBlockNumber: 6765067, - }, - }, - ], -}; - -export const base: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://api.basescan.org/api', - family: ExplorerFamily.Etherscan, - name: 'BaseScan', - url: 'https://basescan.org', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 2, - reorgPeriod: 1, - }, - chainId: 8453, - displayName: 'Base', - domainId: 8453, - gasCurrencyCoinGeckoId: 'ethereum', - gnosisSafeTransactionServiceUrl: 'https://safe-transaction-base.safe.global/', - name: Chains.base, - nativeToken: etherToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [ - { http: 'https://base.publicnode.com/' }, - { http: 'https://mainnet.base.org' }, - { http: 'https://base.blockpi.network/v1/rpc/public' }, - ], -}; - -export const blast: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://api.blastscan.io/api', - family: ExplorerFamily.Etherscan, - name: 'Blast Explorer', - url: 'https://blastscan.io', - }, - { - apiUrl: - 'https://api.routescan.io/v2/network/mainnet/evm/81457/etherscan/api', - family: ExplorerFamily.Routescan, - name: 'Blast Explorer', - url: 'https://blastexplorer.io', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 2, - reorgPeriod: 1, - }, - chainId: 81457, - displayName: 'Blast', - domainId: 81457, - gasCurrencyCoinGeckoId: 'ethereum', - name: Chains.blast, - nativeToken: etherToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'https://rpc.blast.io' }], - technicalStack: ChainTechnicalStack.Other, -}; - -export const bsc: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://api.bscscan.com/api', - family: ExplorerFamily.Etherscan, - name: 'BscScan', - url: 'https://bscscan.com', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 3, - reorgPeriod: 15, - }, - chainId: 56, - displayName: 'Binance Smart Chain', - displayNameShort: 'Binance', - domainId: 56, - gasCurrencyCoinGeckoId: 'binancecoin', - gnosisSafeTransactionServiceUrl: 'https://safe-transaction-bsc.safe.global/', - name: Chains.bsc, - nativeToken: bnbToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [ - { http: 'https://rpc.ankr.com/bsc' }, - { http: 'https://bsc.drpc.org' }, - { http: 'https://bscrpc.com' }, - ], -}; - -export const bsctestnet: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://api-testnet.bscscan.com/api', - family: ExplorerFamily.Etherscan, - name: 'BscScan', - url: 'https://testnet.bscscan.com', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 3, - reorgPeriod: 9, - }, - chainId: 97, - displayName: 'BSC Testnet', - domainId: 97, - isTestnet: true, - name: Chains.bsctestnet, - nativeToken: bnbToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [ - { http: 'https://bsc-testnet.publicnode.com' }, - { http: 'https://bsc-testnet.blockpi.network/v1/rpc/public' }, - ], -}; - -export const celo: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://api.celoscan.io/api', - family: ExplorerFamily.Etherscan, - name: 'CeloScan', - url: 'https://celoscan.io', - }, - { - apiUrl: 'https://explorer.celo.org/mainnet/api', - family: ExplorerFamily.Blockscout, - name: 'Blockscout', - url: 'https://explorer.celo.org', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 5, - reorgPeriod: 0, - }, - chainId: 42220, - displayName: 'Celo', - domainId: 42220, - // The official Gnosis safe URL `https://safe-transaction-celo.safe.global` doesn't work well - // with delegates on a multisig created with the old unofficial Celo tooling. - gnosisSafeTransactionServiceUrl: - 'https://mainnet-tx-svc.celo-safe-prod.celo-networks-dev.org/', - name: Chains.celo, - nativeToken: celoToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'https://forno.celo.org' }], -}; - -export const chiado: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://gnosis-chiado.blockscout.com/api', - family: ExplorerFamily.Blockscout, - name: 'GnosisScan', - url: 'https://gnosis-chiado.blockscout.com', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 5, - reorgPeriod: 14, - }, - chainId: 10200, - displayName: 'Chiado', - domainId: 10200, - isTestnet: true, - name: Chains.chiado, - nativeToken: xDaiToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'https://gnosis-chiado.publicnode.com' }], -}; - -export const ethereum: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://api.etherscan.io/api', - family: ExplorerFamily.Etherscan, - name: 'Etherscan', - url: 'https://etherscan.io', - }, - { - apiUrl: 'https://eth.blockscout.com/api', - family: ExplorerFamily.Blockscout, - name: 'Blockscout', - url: 'https://blockscout.com/eth/mainnet', - }, - ], - blocks: { - confirmations: 7, - estimateBlockTime: 13, - reorgPeriod: 14, - }, - chainId: 1, - displayName: 'Ethereum', - domainId: 1, - gnosisSafeTransactionServiceUrl: - 'https://safe-transaction-mainnet.safe.global/', - name: Chains.ethereum, - nativeToken: etherToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [ - { http: 'https://ethereum.publicnode.com' }, - { http: 'https://cloudflare-eth.com' }, - ], -}; - -export const fuji: ChainMetadata = { - blockExplorers: [ - { - apiUrl: - 'https://api.routescan.io/v2/network/testnet/evm/43113/etherscan/api', - family: ExplorerFamily.Etherscan, - name: 'SnowTrace', - url: 'https://testnet.snowtrace.io', - }, - ], - blocks: { - confirmations: 3, - estimateBlockTime: 2, - reorgPeriod: 3, - }, - chainId: 43113, - displayName: 'Fuji', - domainId: 43113, - isTestnet: true, - name: Chains.fuji, - nativeToken: avaxToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [ - { - http: 'https://api.avax-test.network/ext/bc/C/rpc', - pagination: { maxBlockRange: 2048 }, - }, - ], -}; - -export const gnosis: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://api.gnosisscan.io/api', - family: ExplorerFamily.Etherscan, - name: 'GnosisScan', - url: 'https://gnosisscan.io', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 5, - reorgPeriod: 14, - }, - chainId: 100, - displayName: 'Gnosis', - domainId: 100, - gasCurrencyCoinGeckoId: 'xdai', - gnosisSafeTransactionServiceUrl: - 'https://safe-transaction-gnosis-chain.safe.global/', - name: Chains.gnosis, - nativeToken: xDaiToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [ - { - http: 'https://rpc.gnosischain.com', - pagination: { - maxBlockRange: 10000, - minBlockNumber: 25997478, - }, - }, - ], -}; - -export const inevm: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://inevm.calderaexplorer.xyz/api', - family: ExplorerFamily.Blockscout, - name: 'Caldera inEVM Explorer', - url: 'https://inevm.calderaexplorer.xyz', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 3, - reorgPeriod: 0, - }, - chainId: 2525, - displayName: 'Injective EVM', - displayNameShort: 'inEVM', - domainId: 2525, - gasCurrencyCoinGeckoId: 'injective-protocol', - name: Chains.inevm, - nativeToken: { - decimals: 18, - name: 'Injective', - symbol: 'INJ', - }, - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'https://inevm.calderachain.xyz/http' }], -}; - -export const injective: ChainMetadata = { - bech32Prefix: 'inj', - blockExplorers: [], - blocks: { - confirmations: 1, - estimateBlockTime: 1, - reorgPeriod: 10, - }, - chainId: 'injective-1', - displayName: 'Injective', - domainId: 6909546, - gasCurrencyCoinGeckoId: 'injective-protocol', - grpcUrls: [{ http: 'sentry.chain.grpc.injective.network:443' }], - name: Chains.injective, - nativeToken: { - decimals: 18, - denom: 'inj', - name: 'Injective', - symbol: 'INJ', - }, - protocol: ProtocolType.Cosmos, - restUrls: [{ http: 'https://sentry.lcd.injective.network:443' }], - rpcUrls: [{ http: 'https://sentry.tm.injective.network:443' }], - slip44: 118, -}; - -export const mantapacific: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://pacific-explorer.manta.network/api', - family: ExplorerFamily.Blockscout, - name: 'Manta Pacific Explorer', - url: 'https://pacific-explorer.manta.network', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 3, - reorgPeriod: 1, - }, - chainId: 169, - displayName: 'Manta Pacific', - displayNameShort: 'Manta', - domainId: 169, - gasCurrencyCoinGeckoId: 'ethereum', - isTestnet: false, - name: Chains.mantapacific, - nativeToken: { - decimals: 18, - name: 'Ether', - symbol: 'ETH', - }, - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'https://pacific-rpc.manta.network/http' }], -}; - -export const mode: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://explorer.mode.network/api', - family: ExplorerFamily.Blockscout, - name: 'Mode Explorer', - url: 'https://explorer.mode.network', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 2, - reorgPeriod: 1, - }, - chainId: 34443, - displayName: 'Mode', - domainId: 34443, - gasCurrencyCoinGeckoId: 'ethereum', - name: Chains.mode, - nativeToken: etherToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'https://mainnet.mode.network' }], - technicalStack: ChainTechnicalStack.Other, -}; - -export const moonbeam: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://api-moonbeam.moonscan.io/api', - family: ExplorerFamily.Etherscan, - name: 'MoonScan', - url: 'https://moonscan.io', - }, - ], - blocks: { - confirmations: 2, - estimateBlockTime: 12, - reorgPeriod: 2, - }, - chainId: 1284, - displayName: 'Moonbeam', - domainId: 1284, - gnosisSafeTransactionServiceUrl: - 'https://transaction.multisig.moonbeam.network', - name: Chains.moonbeam, - nativeToken: { - decimals: 18, - name: 'GLMR', - symbol: 'GLMR', - }, - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'https://rpc.api.moonbeam.network' }], -}; - -export const nautilus: ChainMetadata = { - blocks: { - confirmations: 1, - estimateBlockTime: 1, - reorgPeriod: 1, - }, - chainId: 22222, - displayName: 'Nautilus', - domainId: 22222, - name: Chains.nautilus, - nativeToken: { - decimals: 18, - name: 'Zebec', - symbol: 'ZBC', - }, - protocol: ProtocolType.Ethereum, - rpcUrls: [ - { - http: 'https://api.nautilus.nautchain.xyz', - }, - ], -}; - -export const neutron: ChainMetadata = { - bech32Prefix: 'neutron', - blockExplorers: [ - { - // TODO API not actually supported, using url to meet validation requirements - apiUrl: 'https://www.mintscan.io/neutron', - family: ExplorerFamily.Other, - name: 'Mintscan', - url: 'https://www.mintscan.io/neutron', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 3, - reorgPeriod: 1, - }, - chainId: 'neutron-1', - displayName: 'Neutron', - domainId: 1853125230, - gasCurrencyCoinGeckoId: 'neutron-3', - grpcUrls: [{ http: 'grpc-kralum.neutron-1.neutron.org:80' }], - isTestnet: false, - name: Chains.neutron, - nativeToken: { - decimals: 6, - denom: 'untrn', - name: 'Neutron', - symbol: 'NTRN', - }, - protocol: ProtocolType.Cosmos, - restUrls: [{ http: 'https://rest-lb.neutron.org' }], - rpcUrls: [{ http: 'https://rpc-kralum.neutron-1.neutron.org' }], - slip44: 118, - transactionOverrides: { - gasPrice: '0.0075', - }, -}; - -export const optimism: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://api-optimistic.etherscan.io/api', - family: ExplorerFamily.Etherscan, - name: 'Etherscan', - url: 'https://optimistic.etherscan.io', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 3, - reorgPeriod: 0, - }, - chainId: 10, - displayName: 'Optimism', - domainId: 10, - gasCurrencyCoinGeckoId: 'ethereum', - gnosisSafeTransactionServiceUrl: - 'https://safe-transaction-optimism.safe.global/', - name: Chains.optimism, - nativeToken: etherToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'https://mainnet.optimism.io' }], -}; - -export const plumetestnet: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://plume-testnet.explorer.caldera.xyz/api', - family: ExplorerFamily.Blockscout, - name: 'Plume Testnet Explorer', - url: 'https://plume-testnet.explorer.caldera.xyz', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 3, - reorgPeriod: 0, - }, - chainId: 161221135, - displayName: 'Plume Testnet', - domainId: 161221135, - index: { - // Arbitrum Nitro flavored chains record the L1 block number they were deployed at, - // not the L2 block number. See https://docs.arbitrum.io/build-decentralized-apps/arbitrum-vs-ethereum/block-numbers-and-time#ethereum-block-numbers-within-arbitrum. - // This is the block that the Mailbox was deployed at: - // https://plume-testnet.explorer.caldera.xyz/tx/0x72d11097bc54e318a1c3e6a74c8f59d0f2dbed1478854e633ee71e65b7b2a2f8 - from: 4206, - }, - isTestnet: true, - name: Chains.plumetestnet, - nativeToken: { - decimals: 18, - name: 'Ether', - symbol: 'ETH', - }, - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'https://plume-testnet.rpc.caldera.xyz/http' }], - technicalStack: ChainTechnicalStack.ArbitrumNitro, -}; - -export const polygon: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://api.polygonscan.com/api', - family: ExplorerFamily.Etherscan, - name: 'PolygonScan', - url: 'https://polygonscan.com', - }, - ], - blocks: { - confirmations: 200, - estimateBlockTime: 2, - reorgPeriod: 256, - }, - chainId: 137, - displayName: 'Polygon', - domainId: 137, - gasCurrencyCoinGeckoId: 'matic-network', - gnosisSafeTransactionServiceUrl: - 'https://safe-transaction-polygon.safe.global/', - name: Chains.polygon, - nativeToken: etherToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [ - { - http: 'https://polygon-bor.publicnode.com', - }, - { http: 'https://polygon-rpc.com' }, - { http: 'https://rpc.ankr.com/polygon' }, - ], -}; - -export const polygonzkevm: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://api-zkevm.polygonscan.com/api', - family: ExplorerFamily.Etherscan, - name: 'PolygonScan', - url: 'https://zkevm.polygonscan.com', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 10, - reorgPeriod: 1, - }, - chainId: 1101, - displayName: 'Polygon zkEVM', - displayNameShort: 'zkEVM', - domainId: 1101, - gasCurrencyCoinGeckoId: 'ethereum', - name: Chains.polygonzkevm, - nativeToken: etherToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [ - { http: 'https://zkevm-rpc.com' }, - { http: 'https://rpc.ankr.com/polygon_zkevm' }, - ], -}; - -// Testnet for Nautilus -export const proteustestnet: ChainMetadata = { - blocks: { - confirmations: 1, - estimateBlockTime: 1, - reorgPeriod: 1, - }, - chainId: 88002, - displayName: 'Proteus Testnet', - domainId: 88002, - name: Chains.proteustestnet, - nativeToken: { - decimals: 18, - name: 'Zebec', - symbol: 'ZBC', - }, - protocol: ProtocolType.Ethereum, - rpcUrls: [ - { - http: 'https://api.proteus.nautchain.xyz/solana', - }, - ], -}; - -export const scroll: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://api.scrollscan.com/api', - family: ExplorerFamily.Etherscan, - name: 'Scroll Explorer', - url: 'https://scrollscan.com/', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 3, - reorgPeriod: 1, - }, - chainId: 534352, - displayName: 'Scroll', - domainId: 534352, - gasCurrencyCoinGeckoId: 'ethereum', - name: Chains.scroll, - nativeToken: etherToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'https://scroll.blockpi.network/v1/rpc/public' }], -}; - -export const scrollsepolia: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://api-sepolia.scrollscan.com/api', - family: ExplorerFamily.Etherscan, - name: 'Scroll Explorer', - url: 'https://sepolia.scrollscan.dev/', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 3, - reorgPeriod: 1, - }, - chainId: 534351, - displayName: 'Scroll Sepolia', - domainId: 534351, - isTestnet: true, - name: Chains.scrollsepolia, - nativeToken: etherToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'https://sepolia-rpc.scroll.io' }], -}; - -export const sepolia: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://api-sepolia.etherscan.io/api', - family: ExplorerFamily.Etherscan, - name: 'Etherscan', - url: 'https://sepolia.etherscan.io', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 13, - reorgPeriod: 2, - }, - chainId: 11155111, - displayName: 'Sepolia', - domainId: 11155111, - isTestnet: true, - name: Chains.sepolia, - nativeToken: etherToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [ - { http: 'https://ethereum-sepolia.publicnode.com' }, - { http: 'https://ethereum-sepolia.blockpi.network/v1/rpc/public' }, - { http: 'https://rpc.sepolia.org' }, - ], -}; - -export const solana: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://explorer.solana.com', - family: ExplorerFamily.Other, - name: 'Solana Explorer', - url: 'https://explorer.solana.com', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 0.4, - reorgPeriod: 0, - }, - // Uses the same ChainId as https://www.alchemy.com/chain-connect/chain/solana - chainId: 1399811149, - displayName: 'Solana', - domainId: 1399811149, - name: 'solana', - nativeToken: solToken, - protocol: ProtocolType.Sealevel, - rpcUrls: [{ http: 'https://api.mainnet-beta.solana.com' }], -}; - -export const solanatestnet: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://explorer.solana.com', - family: ExplorerFamily.Other, - name: 'Solana Explorer', - url: 'https://explorer.solana.com', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 0.4, - reorgPeriod: 0, - }, - chainId: 1399811150, - displayName: 'Solana Testnet', - displayNameShort: 'Sol Testnet', - domainId: 1399811150, - isTestnet: true, - name: 'solanatestnet', - nativeToken: solToken, - protocol: ProtocolType.Sealevel, - rpcUrls: [{ http: 'https://api.testnet.solana.com' }], -}; - -export const solanadevnet: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://explorer.solana.com', - family: ExplorerFamily.Other, - name: 'Solana Explorer', - url: 'https://explorer.solana.com', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 0.4, - reorgPeriod: 0, - }, - chainId: 1399811151, - displayName: 'Solana Devnet', - displayNameShort: 'Sol Devnet', - domainId: 1399811151, - isTestnet: true, - name: 'solanadevnet', - nativeToken: solToken, - protocol: ProtocolType.Sealevel, - rpcUrls: [{ http: 'https://api.devnet.solana.com' }], -}; - -export const eclipsetestnet: ChainMetadata = { - blocks: { - confirmations: 1, - estimateBlockTime: 0.4, - reorgPeriod: 0, - }, - chainId: 239092742, - displayName: 'Eclipse Testnet', - domainId: 239092742, - isTestnet: true, - name: 'eclipsetestnet', - nativeToken: { - ...etherToken, - decimals: 9, - }, - protocol: ProtocolType.Sealevel, - rpcUrls: [{ http: 'https://testnet.dev2.eclipsenetwork.xyz' }], -}; - -export const test1: ChainMetadata = { - blockExplorers: [], - blocks: { - confirmations: 1, - estimateBlockTime: 3, - reorgPeriod: 0, - }, - chainId: 13371, - displayName: 'Test 1', - domainId: 13371, - isTestnet: true, - name: Chains.test1, - nativeToken: etherToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'http://127.0.0.1:8545' }], -}; - -export const test2: ChainMetadata = { - blockExplorers: [], - blocks: { - confirmations: 1, - estimateBlockTime: 3, - reorgPeriod: 1, - }, - chainId: 13372, - displayName: 'Test 2', - domainId: 13372, - isTestnet: true, - name: Chains.test2, - nativeToken: etherToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'http://127.0.0.1:8545' }], -}; - -export const test3: ChainMetadata = { - blockExplorers: [], - blocks: { - confirmations: 1, - estimateBlockTime: 3, - reorgPeriod: 2, - }, - chainId: 13373, - displayName: 'Test 3', - domainId: 13373, - isTestnet: true, - name: Chains.test3, - nativeToken: etherToken, - protocol: ProtocolType.Ethereum, - rpcUrls: [{ http: 'http://127.0.0.1:8545' }], -}; - -export const viction: ChainMetadata = { - blockExplorers: [ - { - apiUrl: 'https://www.vicscan.xyz/api', - family: ExplorerFamily.Other, - name: 'Vicscan', - url: 'https://www.vicscan.xyz', - }, - ], - blocks: { - confirmations: 1, - estimateBlockTime: 2, - reorgPeriod: 0, - }, - chainId: 88, - displayName: 'Viction', - domainId: 88, - gasCurrencyCoinGeckoId: 'tomochain', - name: Chains.viction, - nativeToken: { - decimals: 18, - name: 'Viction', - symbol: 'VIC', - }, - protocol: ProtocolType.Ethereum, - rpcUrls: [ - { - http: 'https://rpc.tomochain.com', - }, - { - http: 'https://viction.blockpi.network/v1/rpc/public', - }, - ], -}; - -/** - * Collection maps - * - * NOTE: When adding chains here, consider also adding the - * corresponding chain logo images in the /sdk/logos/* folders - */ -export const chainMetadata: ChainMap = { - alfajores, - ancient8, - arbitrum, - avalanche, - base, - blast, - bsc, - bsctestnet, - celo, - chiado, - eclipsetestnet, - ethereum, - fuji, - gnosis, - inevm, - injective, - mantapacific, - mode, - moonbeam, - nautilus, - neutron, - optimism, - plumetestnet, - polygon, - polygonzkevm, - proteustestnet, - scroll, - scrollsepolia, - sepolia, - solana, - solanadevnet, - solanatestnet, - test1, - test2, - test3, - viction, -}; - -export const chainIdToMetadata = Object.values(chainMetadata).reduce< - Record ->((result, chain) => { - result[chain.chainId] = chain; - return result; -}, {}); - -export const mainnetChainsMetadata: Array = Mainnets.map( - (chainName) => chainMetadata[chainName], -); -export const testnetChainsMetadata: Array = Testnets.map( - (chainName) => chainMetadata[chainName], -); - -export const solanaChainToClusterName: ChainMap = { - solana: 'mainnet-beta', - solanadevnet: 'devnet', - solanatestnet: 'testnet', -}; diff --git a/typescript/sdk/src/consts/chains.ts b/typescript/sdk/src/consts/chains.ts deleted file mode 100644 index 979aa2bb22..0000000000 --- a/typescript/sdk/src/consts/chains.ts +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Enumeration of Hyperlane supported chains - * Must be string type to be used with Object.keys - */ -export enum Chains { - alfajores = 'alfajores', - ancient8 = 'ancient8', - arbitrum = 'arbitrum', - avalanche = 'avalanche', - base = 'base', - blast = 'blast', - bsc = 'bsc', - bsctestnet = 'bsctestnet', - celo = 'celo', - chiado = 'chiado', - ethereum = 'ethereum', - fuji = 'fuji', - gnosis = 'gnosis', - inevm = 'inevm', - injective = 'injective', - mantapacific = 'mantapacific', - mode = 'mode', - moonbeam = 'moonbeam', - nautilus = 'nautilus', - neutron = 'neutron', - optimism = 'optimism', - plumetestnet = 'plumetestnet', - polygon = 'polygon', - polygonzkevm = 'polygonzkevm', - proteustestnet = 'proteustestnet', - scroll = 'scroll', - scrollsepolia = 'scrollsepolia', - sepolia = 'sepolia', - solana = 'solana', - solanadevnet = 'solanadevnet', - solanatestnet = 'solanatestnet', - eclipsetestnet = 'eclipsetestnet', - viction = 'viction', - test1 = 'test1', - test2 = 'test2', - test3 = 'test3', -} - -export type CoreChainName = keyof typeof Chains; - -export enum DeprecatedChains { - arbitrumkovan = 'arbitrumkovan', - arbitrumrinkeby = 'arbitrumrinkeby', - kovan = 'kovan', - rinkeby = 'rinkeby', - optimismkovan = 'optimismkovan', - optimismrinkeby = 'optimismrinkeby', -} - -export const AllDeprecatedChains = Object.keys(DeprecatedChains) as string[]; - -export const Mainnets: Array = [ - Chains.arbitrum, - Chains.ancient8, - Chains.avalanche, - Chains.base, - Chains.blast, - Chains.bsc, - Chains.celo, - Chains.ethereum, - Chains.neutron, - Chains.mantapacific, - Chains.mode, - Chains.moonbeam, - Chains.optimism, - Chains.polygon, - Chains.gnosis, - Chains.scroll, - Chains.polygonzkevm, - Chains.injective, - Chains.inevm, - Chains.viction, - // Chains.solana, -]; - -export const Testnets: Array = [ - Chains.alfajores, - Chains.bsctestnet, - Chains.chiado, - Chains.fuji, - Chains.plumetestnet, - Chains.scrollsepolia, - Chains.sepolia, - Chains.solanadevnet, - Chains.solanatestnet, - Chains.eclipsetestnet, -]; - -export const TestChains: Array = [ - Chains.test1, - Chains.test2, - Chains.test3, -]; - -export const AllChains: Array = [ - ...Mainnets, - ...Testnets, - ...TestChains, -]; diff --git a/typescript/sdk/src/consts/environments/index.ts b/typescript/sdk/src/consts/environments/index.ts deleted file mode 100644 index 4fa77cc6c9..0000000000 --- a/typescript/sdk/src/consts/environments/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { objMerge } from '@hyperlane-xyz/utils'; - -import { ChainName } from '../../types.js'; -import { CoreChainName } from '../chains.js'; - -import mainnet from './mainnet.json' assert { type: 'json' }; -import testnet from './testnet.json' assert { type: 'json' }; - -export const hyperlaneEnvironments = { mainnet, testnet }; - -export type HyperlaneEnvironment = keyof typeof hyperlaneEnvironments; -export type HyperlaneEnvironmentChain = Extract< - keyof (typeof hyperlaneEnvironments)[E], - ChainName ->; - -// Note, this assumes no chain name is repeated across environments -export const hyperlaneContractAddresses = objMerge( - hyperlaneEnvironments.testnet, - hyperlaneEnvironments.mainnet, -) as Record< - CoreChainName, - (typeof hyperlaneEnvironments)['mainnet']['ethereum'] ->; diff --git a/typescript/sdk/src/consts/environments/mainnet.json b/typescript/sdk/src/consts/environments/mainnet.json deleted file mode 100644 index 3aaa416ab1..0000000000 --- a/typescript/sdk/src/consts/environments/mainnet.json +++ /dev/null @@ -1,351 +0,0 @@ -{ - "ancient8": { - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "fallbackRoutingHook": "0x5E01d8F34b629E3f92d69546bbc4142A7Adee7e9", - "interchainGasPaymaster": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", - "interchainSecurityModule": "0x6E3387e12C6e181BF8e712eCa9c60ccEEaBD1c67", - "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "merkleTreeHook": "0x811808Dd29ba8B0FC6C0ec0b5537035E59745162", - "pausableHook": "0x66DC49405Ae2956f7E87FEAa9fE8f506C8987462", - "protocolFee": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", - "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "storageGasOracle": "0x59Bf7c7b458375b1A7c453aE70EaCb376E65CDAF", - "testRecipient": "0x2Fa570E83009eaEef3a1cbd496a9a30F05266634", - "validatorAnnounce": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d" - }, - "arbitrum": { - "aggregationHook": "0xe0cb37cFc47296f1c4eD77EFf92Aed478644d10c", - "domainRoutingIsmFactory": "0xa2931C37957f3079d3B21b877d56E1db930e02a5", - "fallbackRoutingHook": "0x9e8fFb1c26099e75Dd5D794030e2E9AA51471c25", - "interchainGasPaymaster": "0x3b6044acd6767f017e99318AA6Ef93b7B06A5a22", - "interchainSecurityModule": "0xD0DBBF922076352cC50B285A0023536561F00EEa", - "mailbox": "0x979Ca5202784112f4738403dBec5D0F3B9daabB9", - "merkleTreeHook": "0x748040afB89B8FdBb992799808215419d36A0930", - "pausableHook": "0xEf30f29Dcd3FCB1DCcDA9C7Cbf2A5957E8Ee9Cc3", - "protocolFee": "0xD0199067DACb8526e7dc524a9a7DCBb57Cd25421", - "proxyAdmin": "0x80Cebd56A65e46c474a1A101e89E76C4c51D179c", - "staticAggregationHookFactory": "0x9B5f440bBb64Fee337F37e03362b628711Ea09C7", - "staticAggregationIsmFactory": "0xD4883084389fC1Eeb4dAfB2ADcFc36B711c310EB", - "staticMerkleRootMultisigIsmFactory": "0x3C330D4A2e2b8443AFaB8E326E64ab4251B7Eae0", - "staticMessageIdMultisigIsmFactory": "0x12Df53079d399a47e9E730df095b712B0FDFA791", - "storageGasOracle": "0xD3805207b65d99C075ceA938Fa7c0587026a5DF5", - "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "validatorAnnounce": "0x1df063280C4166AF9a725e3828b4dAC6c7113B08" - }, - "avalanche": { - "aggregationHook": "0x0165a22BA489F7DA37DAf6397781777D9FCB5708", - "domainRoutingIsmFactory": "0x28F7907911C7E321c596686AE6D1F20516450037", - "fallbackRoutingHook": "0x61D15D571D5f7A9eF0D1938f072f430bBF024747", - "interchainGasPaymaster": "0x95519ba800BBd0d34eeAE026fEc620AD978176C0", - "interchainSecurityModule": "0xA36B02a83564f52d9244310Ea439ee6F6AfeFb60", - "mailbox": "0xFf06aFcaABaDDd1fb08371f9ccA15D73D51FeBD6", - "merkleTreeHook": "0x84eea61D679F42D92145fA052C89900CBAccE95A", - "pausableHook": "0x239eB860770F1C48ABAC9bE9825d20e3E7c018df", - "protocolFee": "0xEc4AdA26E51f2685279F37C8aE62BeAd8212D597", - "proxyAdmin": "0xd7CF8c05fd81b8cA7CfF8E6C49B08a9D63265c9B", - "staticAggregationHookFactory": "0x3bF6Ac986C7Af9A9Ac356C0e99C0041EFd8D96e7", - "staticAggregationIsmFactory": "0xa5E13796eB7d2EDCc88012c8cfF90D69B51FcF9f", - "staticMerkleRootMultisigIsmFactory": "0x896cF1D1B66cD211633eDd589fF158E8Cfaf9B54", - "staticMessageIdMultisigIsmFactory": "0x8819D653DF5b1FC0DdB32189a2704E471AF8483c", - "storageGasOracle": "0x175821F30AdCAA4bbB72Ce98eF76C2E0De2C3f21", - "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "validatorAnnounce": "0x9Cad0eC82328CEE2386Ec14a12E81d070a27712f" - }, - "base": { - "aggregationHook": "0x13f3d4B0Ee0a713430fded9E18f7fb6c91A6E41F", - "domainRoutingIsmFactory": "0x7E27456a839BFF31CA642c060a2b68414Cb6e503", - "fallbackRoutingHook": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", - "interchainGasPaymaster": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", - "interchainSecurityModule": "0x5D1e7D7c5B9e6dDC8439F67F10c578f2A1084f6F", - "mailbox": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "merkleTreeHook": "0x19dc38aeae620380430C200a6E990D5Af5480117", - "pausableHook": "0x46fa3A5780e5B90Eaf34BDED554d5353B5ABE9E7", - "protocolFee": "0x99ca8c74cE7Cfa9d72A51fbb05F9821f5f826b3a", - "proxyAdmin": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "staticAggregationHookFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "staticAggregationIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticMerkleRootMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "staticMessageIdMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "storageGasOracle": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", - "validatorAnnounce": "0x182E8d7c5F1B06201b102123FC7dF0EaeB445a7B" - }, - "blast": { - "domainRoutingIsmFactory": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "fallbackRoutingHook": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", - "interchainGasPaymaster": "0xB3fCcD379ad66CED0c91028520C64226611A48c9", - "interchainSecurityModule": "0x0986f6D82A47045788b0ce8EF68f6C0D77726854", - "mailbox": "0x3a867fCfFeC2B790970eeBDC9023E75B0a172aa7", - "merkleTreeHook": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", - "pausableHook": "0xE0C452DDA7506f0F4dE5C8C1d383F7aD866eA4F0", - "protocolFee": "0x12582c7B0f43c6A667CBaA7fA8b112F7fb1E69F0", - "proxyAdmin": "0xeA87ae93Fa0019a82A727bfd3eBd1cFCa8f64f1D", - "staticAggregationHookFactory": "0x4Ed7d626f1E96cD1C0401607Bf70D95243E3dEd1", - "staticAggregationIsmFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticMerkleRootMultisigIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticMessageIdMultisigIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "storageGasOracle": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", - "testRecipient": "0x17E216fBb22dF4ef8A6640ae9Cb147C92710ac84", - "validatorAnnounce": "0xFC62DeF1f08793aBf0E67f69257c6be258194F72" - }, - "bsc": { - "aggregationHook": "0x402Fc106576462a892355d69ACF03D46A888ae88", - "domainRoutingIsmFactory": "0xe6Af5720d34213C805C08e2470aea979e3F72F75", - "fallbackRoutingHook": "0x237E81f87F57Badad9e09f13CC676D986cA852e7", - "interchainGasPaymaster": "0x78E25e7f84416e69b9339B0A6336EB6EFfF6b451", - "interchainSecurityModule": "0xab3df354baBee6c2B88E2CeD3b2e030e31aA5e61", - "mailbox": "0x2971b9Aec44bE4eb673DF1B88cDB57b96eefe8a4", - "merkleTreeHook": "0xFDb9Cd5f9daAA2E4474019405A328a88E7484f26", - "pausableHook": "0x7DBdAd1b4A922B65d37d7258a4227b6658344b7f", - "protocolFee": "0xA8Aa5f14a5463a78E45CC068F11c867949F3E367", - "proxyAdmin": "0x65993Af9D0D3a64ec77590db7ba362D6eB78eF70", - "staticAggregationHookFactory": "0xe70E86a7D1e001D419D71F960Cb6CaD59b6A3dB6", - "staticAggregationIsmFactory": "0x38B3878c4fb44d201DA924c4a04bae3EE728c065", - "staticMerkleRootMultisigIsmFactory": "0xfADBc81Ca8A957F1Bf7c78bCc575b28DBDE042b6", - "staticMessageIdMultisigIsmFactory": "0x4B1d8352E35e3BDE36dF5ED2e73C24E35c4a96b7", - "storageGasOracle": "0x91d23D603d60445411C06e6443d81395593B7940", - "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "validatorAnnounce": "0x7024078130D9c2100fEA474DAD009C2d1703aCcd" - }, - "celo": { - "aggregationHook": "0xc65890329066FB20c339Bc5C22f1756e9D3a4fF5", - "domainRoutingIsm": "0xf18E32428dad0802C5D6F723cB80A6Da889777c4", - "domainRoutingIsmFactory": "0x2A2c22B0a8615ad24839fA6Af302E896Af32d1a3", - "fallbackRoutingHook": "0xDC98a856fb9112894c2fE32267DA8bF35645FAF3", - "interchainGasPaymaster": "0x571f1435613381208477ac5d6974310d88AC7cB7", - "interchainSecurityModule": "0x99e8E56Dce3402D6E09A82718937fc1cA2A9491E", - "mailbox": "0x50da3B3907A08a24fe4999F4Dcf337E8dC7954bb", - "merkleTreeHook": "0x04dB778f05854f26E67e0a66b740BBbE9070D366", - "pausableHook": "0x80672c5D9Fd26B235654C24adc1CFcDeb8d15115", - "pausableIsm": "0x6Bc4437ce69696C9461Cbc89582c259AC8847A58", - "protocolFee": "0x89886d431f9c3eEE64DCD6dAbA3f7D689D98D899", - "proxyAdmin": "0x90f9a2E9eCe93516d65FdaB726a3c62F5960a1b9", - "staticAggregationHookFactory": "0xc3745652EFB8555A8b064A0EA78d295133d326D2", - "staticAggregationIsm": "0x99e8E56Dce3402D6E09A82718937fc1cA2A9491E", - "staticAggregationIsmFactory": "0x1722dd970a1F56040712129f5Eeb76B003fd7500", - "staticMerkleRootMultisigIsmFactory": "0x4C96a1abc44dc846775CE702C9E9BE821D3b487c", - "staticMessageIdMultisigIsmFactory": "0xaB402f227e892Ef37C105bf06619c0fa106a1fB2", - "storageGasOracle": "0xD9A9966E7dA9a7f0032bF449FB12696a638E673C", - "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "validatorAnnounce": "0xCeF677b65FDaA6804d4403083bb12B8dB3991FE1" - }, - "ethereum": { - "aggregationHook": "0xb87AC8EA4533AE017604E44470F7c1E550AC6F10", - "domainRoutingIsmFactory": "0x28fA9552F19039b450498B0d8e5DEAe0d0aAc559", - "fallbackRoutingHook": "0x571f1435613381208477ac5d6974310d88AC7cB7", - "interchainGasPaymaster": "0x9e6B1022bE9BBF5aFd152483DAD9b88911bC8611", - "interchainSecurityModule": "0xB42b88243F749F47697F01Ae1cbBCA9d4763902a", - "mailbox": "0xc005dc82818d67AF737725bD4bf75435d065D239", - "merkleTreeHook": "0x48e6c30B97748d1e2e03bf3e9FbE3890ca5f8CCA", - "pausableHook": "0x3A66Dc852e56d3748838b3C27CF381105b83705b", - "protocolFee": "0x8B05BF30F6247a90006c5837eA63C7905D79e6d8", - "proxyAdmin": "0x75EE15Ee1B4A75Fa3e2fDF5DF3253c25599cc659", - "staticAggregationHookFactory": "0x6D2555A8ba483CcF4409C39013F5e9a3285D3C9E", - "staticAggregationIsmFactory": "0x46FA191Ad972D9674Ed752B69f9659A0d7b22846", - "staticMerkleRootMultisigIsmFactory": "0x47e8aF9e30C32Ab91060ED587894288786761B45", - "staticMessageIdMultisigIsmFactory": "0xfA21D9628ADce86531854C2B7ef00F07394B0B69", - "storageGasOracle": "0xc9a103990A8dB11b4f627bc5CD1D0c2685484Ec5", - "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "validatorAnnounce": "0xCe74905e51497b4adD3639366708b821dcBcff96" - }, - "gnosis": { - "aggregationHook": "0xdD1FA1C12496474c1dDC67a658Ba81437F818861", - "domainRoutingIsmFactory": "0xbB5Df000113e767dE11343A16f83De733e5bCC0F", - "fallbackRoutingHook": "0x24f5E353dD03E103Ba2372F7D6FC0cf3A66f849c", - "interchainGasPaymaster": "0xDd260B99d302f0A3fF885728c086f729c06f227f", - "interchainSecurityModule": "0x8e1aa0687B6d939D5a44304D13B7c922ebB012f1", - "mailbox": "0xaD09d78f4c6b9dA2Ae82b1D34107802d380Bb74f", - "merkleTreeHook": "0x2684C6F89E901987E1FdB7649dC5Be0c57C61645", - "pausableHook": "0xf728C884De5275a608dEC222dACd0f2BF2E23AB6", - "protocolFee": "0x9c2214467Daf9e2e1F45b36d08ce0b9C65BFeA88", - "proxyAdmin": "0x81a92A1a272cb09d7b4970b07548463dC7aE0cB7", - "staticAggregationHookFactory": "0xbC8AA096dabDf4A0200BB9f8D4Cbb644C3D86d7B", - "staticAggregationIsmFactory": "0x11EF91d17c5ad3330DbCa709a8841743d3Af6819", - "staticMerkleRootMultisigIsmFactory": "0x8E273260EAd8B72A085B19346A676d355740e875", - "staticMessageIdMultisigIsmFactory": "0x603f46cc520d2fc22957b81e206408590808F02F", - "storageGasOracle": "0x5E01d8F34b629E3f92d69546bbc4142A7Adee7e9", - "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "validatorAnnounce": "0x87ED6926abc9E38b9C7C19f835B41943b622663c" - }, - "inevm": { - "aggregationHook": "0xe0dDb5dE7D52918237cC1Ae131F29dcAbcb0F62B", - "domainRoutingIsm": "0xBD70Ea9D599a0FC8158B026797177773C3445730", - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "interchainGasPaymaster": "0x19dc38aeae620380430C200a6E990D5Af5480117", - "interchainSecurityModule": "0x3052aD50De54aAAc5D364d80bBE681d29e924597", - "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "merkleTreeHook": "0x0972954923a1e2b2aAb04Fa0c4a0797e5989Cd65", - "pausableHook": "0xBDa330Ea8F3005C421C8088e638fBB64fA71b9e0", - "pausableIsm": "0x6Fae4D9935E2fcb11fC79a64e917fb2BF14DaFaa", - "protocolFee": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsm": "0x3052aD50De54aAAc5D364d80bBE681d29e924597", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "storageGasOracle": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", - "validatorAnnounce": "0x15ab173bDB6832f9b64276bA128659b0eD77730B" - }, - "mantapacific": { - "aggregationHook": "0x8464aF853363B8d6844070F68b0AB34Cb6523d0F", - "domainRoutingIsm": "0xDEed16fe4b1c9b2a93483EDFf34C77A9b57D31Ff", - "domainRoutingIsmFactory": "0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147", - "fallbackRoutingHook": "0xD1E267d2d7876e97E217BfE61c34AB50FEF52807", - "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "interchainSecurityModule": "0xEda7cCD2A8CF717dc997D0002e363e4D10bF5c0d", - "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", - "pausableHook": "0x7556a0E61d577D921Cba8Fca0d7D6299d36E607E", - "protocolFee": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", - "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "staticAggregationHookFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticAggregationIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "staticMerkleRootMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMessageIdMultisigIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117", - "testRecipient": "0x4E1c88DD261BEe2941e6c1814597e30F53330428", - "testTokenRecipient": "0x5060eCD5dFAD300A90592C04e504600A7cdcF70b", - "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9" - }, - "mode": { - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "fallbackRoutingHook": "0x8F1E22d309baa69D398a03cc88E9b46037e988AA", - "interchainGasPaymaster": "0x931dFCc8c1141D6F532FD023bd87DAe0080c835d", - "interchainSecurityModule": "0x8dfE6790DbB2Ecc1bEdb0eECfc1Ff467Ae5d8C89", - "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "merkleTreeHook": "0xE2ee936bEa8e42671c400aC96dE198E06F2bA2A6", - "pausableHook": "0xA1ac41d8A663fd317cc3BD94C7de92dC4BA4a882", - "protocolFee": "0xea820f9BCFD5E16a0dd42071EB61A29874Ad81A4", - "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "storageGasOracle": "0xC9B8ea6230d6687a4b13fD3C0b8f0Ec607B26465", - "testRecipient": "0x12582c7B0f43c6A667CBaA7fA8b112F7fb1E69F0", - "validatorAnnounce": "0x48083C69f5a42c6B69ABbAd48AE195BD36770ee2" - }, - "moonbeam": { - "aggregationHook": "0x23cca255aE83F57F39EAf9D14fB9FdaDF22D5863", - "domainRoutingIsmFactory": "0x8061Af3A459093540d17823D651BC5E2A92669a7", - "fallbackRoutingHook": "0x6C2D6eA0969F7Aa0A850CCA88c7BFACa563B2361", - "interchainGasPaymaster": "0x14760E32C0746094cF14D97124865BC7F0F7368F", - "interchainSecurityModule": "0x373836DFa82f2D27ec79Ca32A197Aa1665F0E1e9", - "mailbox": "0x094d03E751f49908080EFf000Dd6FD177fd44CC3", - "merkleTreeHook": "0x87403b85f6f316e7ba91ba1fa6C3Fb7dD4095547", - "pausableHook": "0xe28f2AEEB42ee83CAd068D9A9a449c8b868C137f", - "protocolFee": "0xCd3e29A9D293DcC7341295996a118913F7c582c0", - "proxyAdmin": "0x6A9cdA3dd1F593983BFd142Eb35e6ce4137bd5ce", - "staticAggregationHookFactory": "0x59cC3E7A49DdC4893eB8754c7908f96072A7DbE8", - "staticAggregationIsmFactory": "0x40c6Abcb6A2CdC8882d4bEcaC47927005c7Bb8c2", - "staticMerkleRootMultisigIsmFactory": "0xE2f485bc031Feb5a4C41C1967bf028653d75f0C3", - "staticMessageIdMultisigIsmFactory": "0x84Df48F8f241f11d0fA302d09d73030429Bd9C73", - "storageGasOracle": "0x448b7ADB0dA36d41AA2AfDc9d63b97541A7b3819", - "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "validatorAnnounce": "0x8c1001eBee6F25b31863A55EadfF149aF88B356F" - }, - "optimism": { - "aggregationHook": "0x4ccC6d8eB79f2a1EC9bcb0f211fef7907631F91f", - "domainRoutingIsmFactory": "0xD2e905108c5e44dADA680274740f896Ea96Cf2Fb", - "fallbackRoutingHook": "0xD4b132C6d4AA93A4247F1A91e1ED929c0572a43d", - "interchainGasPaymaster": "0xD8A76C4D91fCbB7Cc8eA795DFDF870E48368995C", - "interchainSecurityModule": "0x04938856bE60c8e734ffDe5f720E2238302BE8D2", - "mailbox": "0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D", - "merkleTreeHook": "0x68eE9bec9B4dbB61f69D9D293Ae26a5AACb2e28f", - "pausableHook": "0xf753CA2269c8A7693ce1808b5709Fbf36a65D47A", - "protocolFee": "0xD71Ff941120e8f935b8b1E2C1eD72F5d140FF458", - "proxyAdmin": "0xE047cb95FB3b7117989e911c6afb34771183fC35", - "staticAggregationHookFactory": "0x15DEeAB8dECDe553bb0B1F9C00984cbcae1af3D7", - "staticAggregationIsmFactory": "0x7491843F3A5Ba24E0f17a22645bDa04A1Ae2c584", - "staticMerkleRootMultisigIsmFactory": "0xCA6Cb9Bc3cfF9E11003A06617cF934B684Bc78BC", - "staticMessageIdMultisigIsmFactory": "0xAa4Be20E9957fE21602c74d7C3cF5CB1112EA9Ef", - "storageGasOracle": "0x27e88AeB8EA4B159d81df06355Ea3d20bEB1de38", - "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "validatorAnnounce": "0x30f5b08e01808643221528BB2f7953bf2830Ef38" - }, - "polygon": { - "aggregationHook": "0x34dAb05650Cf590088bA18aF9d597f3e081bCc47", - "domainRoutingIsmFactory": "0x0d0E816eE4557689d34fAd5885C53b9393C1D9fA", - "fallbackRoutingHook": "0xca4cCe24E7e06241846F5EA0cda9947F0507C40C", - "interchainGasPaymaster": "0x0071740Bf129b05C4684abfbBeD248D80971cce2", - "interchainSecurityModule": "0x9a795fB62f86146ec06e2377e3C95Af65c7C20eB", - "mailbox": "0x5d934f4e2f797775e53561bB72aca21ba36B96BB", - "merkleTreeHook": "0x73FbD25c3e817DC4B4Cd9d00eff6D83dcde2DfF6", - "pausableHook": "0x748040afB89B8FdBb992799808215419d36A0930", - "protocolFee": "0xF8F3629e308b4758F8396606405989F8D8C9c578", - "proxyAdmin": "0xC4F7590C5d30BE959225dC75640657954A86b980", - "staticAggregationHookFactory": "0xFeeB86e70e4a640cDd29636CCE19BD6fe8628135", - "staticAggregationIsmFactory": "0x81AdDD9Ca89105063DaDEBd5B4408551Ce850E22", - "staticMerkleRootMultisigIsmFactory": "0xa9E0E18E78b098c2DE36c42E4DDEA13ce214c592", - "staticMessageIdMultisigIsmFactory": "0xEa5Be2AD66BB1BA321B7aCf0A079fBE304B09Ca0", - "storageGasOracle": "0xA3a24EC5670F1F416AB9fD554FcE2f226AE9D7eB", - "testRecipient": "0x36FdA966CfffF8a9Cdc814f546db0e6378bFef35", - "testTokenRecipient": "0x85ac1164878e017b67660a74ff1f41f3D05C02Bb", - "validatorAnnounce": "0x454E1a1E1CA8B51506090f1b5399083658eA4Fc5" - }, - "polygonzkevm": { - "aggregationHook": "0x8464aF853363B8d6844070F68b0AB34Cb6523d0F", - "domainRoutingIsmFactory": "0xe4057c5B0c43Dc18E36b08C39B419F190D29Ac2d", - "fallbackRoutingHook": "0x01aE937A7B05d187bBCBE80F44F41879D3D335a4", - "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "interchainSecurityModule": "0xf2BEE9D2c15Ba9D7e06799B5912dE1F05533c141", - "mailbox": "0x3a464f746D23Ab22155710f44dB16dcA53e0775E", - "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", - "pausableHook": "0xc2FbB9411186AB3b1a6AFCCA702D1a80B48b197c", - "protocolFee": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", - "proxyAdmin": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "staticAggregationHookFactory": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticAggregationIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "staticMerkleRootMultisigIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMessageIdMultisigIsmFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117", - "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9" - }, - "scroll": { - "aggregationHook": "0x9Bc0FAf446E128a618A88a2F28960Fb2Ca169faE", - "domainRoutingIsmFactory": "0xe03dad16074BC5EEA9A9311257BF02Eb0B6AAA2b", - "fallbackRoutingHook": "0xDa7cECb05C4aeB02c1aFDE277d4306a2da7Bd762", - "interchainGasPaymaster": "0xBF12ef4B9f307463D3FB59c3604F294dDCe287E2", - "interchainSecurityModule": "0xaDc0cB48E8DB81855A930C0C1165ea3dCe4Ba5C7", - "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "merkleTreeHook": "0x6119E37Bd66406A1Db74920aC79C15fB8411Ba76", - "pausableHook": "0x4Eb82Ee35b0a1c1d776E3a3B547f9A9bA6FCC9f2", - "protocolFee": "0xc3F23848Ed2e04C0c6d41bd7804fa8f89F940B94", - "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "storageGasOracle": "0x481171eb1aad17eDE6a56005B7F1aB00C581ef13", - "validatorAnnounce": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638" - }, - "viction": { - "domainRoutingIsmFactory": "0x1052eF3419f26Bec74Ed7CEf4a4FA6812Bc09908", - "interchainGasPaymaster": "0x0D63128D887159d63De29497dfa45AFc7C699AE4", - "interchainSecurityModule": "0xBD70Ea9D599a0FC8158B026797177773C3445730", - "mailbox": "0x2f2aFaE1139Ce54feFC03593FeE8AB2aDF4a85A7", - "merkleTreeHook": "0x149db7afD694722747035d5AEC7007ccb6F8f112", - "protocolFee": "0xd83A4F747fE80Ed98839e05079B1B7Fe037b1638", - "proxyAdmin": "0x0761b0827849abbf7b0cC09CE14e1C93D87f5004", - "staticAggregationHookFactory": "0xEb9FcFDC9EfDC17c1EC5E1dc085B98485da213D6", - "staticAggregationIsmFactory": "0x8F7454AC98228f3504Bb91eA3D8Adafe6406110A", - "staticMerkleRootMultisigIsmFactory": "0x2C1FAbEcd7bFBdEBF27CcdB67baADB38b6Df90fC", - "staticMessageIdMultisigIsmFactory": "0x8b83fefd896fAa52057798f6426E9f0B080FCCcE", - "storageGasOracle": "0x19dc38aeae620380430C200a6E990D5Af5480117", - "testRecipient": "0x17E216fBb22dF4ef8A6640ae9Cb147C92710ac84", - "testTokenRecipient": "0xe042D1fbDf59828dd16b9649Ede7abFc856F7a6c", - "validatorAnnounce": "0x2fa5F5C96419C222cDbCeC797D696e6cE428A7A9" - } -} diff --git a/typescript/sdk/src/consts/environments/testnet.json b/typescript/sdk/src/consts/environments/testnet.json deleted file mode 100644 index 59ce4ab373..0000000000 --- a/typescript/sdk/src/consts/environments/testnet.json +++ /dev/null @@ -1,132 +0,0 @@ -{ - "alfajores": { - "aggregationHook": "0xdBabD76358897E68E4964647C1fb8Bf524f5EFdB", - "domainRoutingIsmFactory": "0x30d9A03762431F8A917a0C469E7A62Bf55092Ca6", - "fallbackRoutingHook": "0x3528B1aeF3a3d29E0eae90ad777A2b4A6a48aC3F", - "interchainAccountIsm": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E", - "interchainAccountRouter": "0xEbA64c8a9b4a61a9210d5fe7E4375380999C821b", - "interchainGasPaymaster": "0x44769b0f4a6f01339e131a691cc2eebbb519d297", - "mailbox": "0xEf9F292fcEBC3848bF4bB92a96a04F9ECBb78E59", - "merkleTreeHook": "0x221FA9CBaFcd6c1C3d206571Cf4427703e023FFa", - "protocolFee": "0xC9D50584F08Bf6cCD1004d14c7062044b45E3b48", - "proxyAdmin": "0x4eDBf5846D973c53AF478cf62aB5bC92807521e3", - "staticAggregationHookFactory": "0x71bB34Ee833467443628CEdFAA188B2387827Cee", - "staticAggregationIsmFactory": "0x4bE8AC22f506B1504C93C3A5b1579C5e7c550D9C", - "staticMerkleRootMultisigIsmFactory": "0xa9C7e306C0941896CA1fd528aA59089571D8D67E", - "staticMessageIdMultisigIsmFactory": "0xC1b8c0e56D6a34940Ee2B86172450B54AFd633A7", - "storageGasOracle": "0x8356113754C7aCa297Db3089b89F87CC125499fb", - "testRecipient": "0x6489d13AcAd3B8dce4c5B31f375DE4f9451E7b38", - "testTokenRecipient": "0x92dC0a76452a9D9358D2d2dEd8CddA209DF67c45", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0x3726EE36a2A9e11a40d1ffD7D9A1A16e0154cDA0" - }, - "bsctestnet": { - "aggregationHook": "0x3d675bB93250Ab7603F40cbb9194bae210784627", - "fallbackRoutingHook": "0x2670ED2EC08cAd135307556685a96bD4c16b007b", - "interchainAccountIsm": "0xa9D8Ec959F34272B1a56D09AF00eeee58970d3AE", - "interchainAccountRouter": "0x6d2B3e304E58c2a19f1492E7cf15CaF63Ce6e0d2", - "interchainGasPaymaster": "0x0dD20e410bdB95404f71c5a4e7Fa67B892A5f949", - "mailbox": "0xF9F6F5646F478d5ab4e20B0F910C92F1CCC9Cc6D", - "merkleTreeHook": "0xc6cbF39A747f5E28d1bDc8D9dfDAb2960Abd5A8f", - "protocolFee": "0x3eF0a63B8976b838704Bcc93C78C56b6653E5a39", - "proxyAdmin": "0xb12282d2E838Aa5f2A4F9Ee5f624a77b7199A078", - "staticAggregationHookFactory": "0xa1145B39F1c7Ef9aA593BC1DB1634b00CC020942", - "staticAggregationIsmFactory": "0x40613dE82d672605Ab051C64079022Bb4F8bDE4f", - "staticMerkleRootMultisigIsmFactory": "0x3E235B90197E1D6b5DB5ad5aD49f2c1ED6406382", - "staticMessageIdMultisigIsmFactory": "0x0D96aF0c01c4bbbadaaF989Eb489c8783F35B763", - "storageGasOracle": "0x124EBCBC018A5D4Efe639f02ED86f95cdC3f6498", - "testRecipient": "0xfbcD1c00a3d809f36cC1A15918694B17B32c0b6c", - "testTokenRecipient": "0x260f6024119549a40595d0937471e607411E8ea5", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0xf09701B0a93210113D175461b6135a96773B5465" - }, - "fuji": { - "aggregationHook": "0x8E9b4006171c6B75111823e7545Ee5400CEce0B3", - "domainRoutingIsmFactory": "0x683a81E0e1a238dcA7341e04c08d3bba6f0Cb74f", - "fallbackRoutingHook": "0xc684f7F50DB4b2563218512e021fBdd0BeD6b57E", - "interchainAccountIsm": "0xfaB4815BDC5c60c6bD625459C8577aFdD79D9311", - "interchainAccountRouter": "0xeEF6933122894fF217a7dd07510b3D64b747e29b", - "interchainGasPaymaster": "0x6895d3916B94b386fAA6ec9276756e16dAe7480E", - "interchainSecurityModule": "0x4B02673875ba86729f1490247A14A01c4D6B0a67", - "mailbox": "0x5b6CFf85442B851A8e6eaBd2A4E4507B5135B3B0", - "merkleTreeHook": "0x9ff6ac3dAf63103620BBf76136eA1AFf43c2F612", - "pausableHook": "0x495e9E119b2aa848b418EF6A4d30b42803de43A9", - "protocolFee": "0xEbA64c8a9b4a61a9210d5fe7E4375380999C821b", - "proxyAdmin": "0x378dA02f7dC3c23A8B5ecE32b8056CdF01e8d477", - "staticAggregationHookFactory": "0x99554CC33cBCd6EDDd2f3fc9c7C9194Cb3b5df1E", - "staticAggregationIsmFactory": "0xF588129ed84F219A1f0f921bE7Aa1B2176516858", - "staticMerkleRootMultisigIsmFactory": "0x93F50Ac4E5663DAAb03508008d592f6260964f62", - "staticMessageIdMultisigIsmFactory": "0x90e1F9918F304645e4F6324E5C0EAc70138C84Ce", - "storageGasOracle": "0x9305dE34306886d615B096Bdf23b94a978f6a6c0", - "testRecipient": "0x44a7e1d76fD8AfA244AdE7278336E3D5C658D398", - "testTokenRecipient": "0x9CC10c844B3Bbae2444E39991aB027C4A05D1F2e", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0x4f7179A691F8a684f56cF7Fed65171877d30739a" - }, - "plumetestnet": { - "aggregationHook": "0x31dF0EEE7Dc7565665468698a0da221225619a1B", - "domainRoutingIsmFactory": "0x54148470292C24345fb828B003461a9444414517", - "fallbackRoutingHook": "0x19Be55D859368e02d7b9C00803Eb677BDC1359Bd", - "interchainAccountIsm": "0x7c115c16E34c74afdb88bd268EaB19bC705891FE", - "interchainAccountRouter": "0xB6F8aA9B1b314A6E6DFB465DD3e0E95936347517", - "interchainGasPaymaster": "0x28B02B97a850872C4D33C3E024fab6499ad96564", - "interchainSecurityModule": "0x7B40deb01A127E3A5eECdbCDF263e41899a90078", - "mailbox": "0x33dB966328Ea213b0f76eF96CA368AB37779F065", - "merkleTreeHook": "0xddf4C3e791caCaFd26D7fb275549739B38ae6e75", - "pausableHook": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "protocolFee": "0x1b33611fCc073aB0737011d5512EF673Bff74962", - "proxyAdmin": "0x589C201a07c26b4725A4A829d772f24423da480B", - "staticAggregationHookFactory": "0x6966b0E55883d49BFB24539356a2f8A673E02039", - "staticAggregationIsmFactory": "0xC2E36cd6e32e194EE11f15D9273B64461A4D49A2", - "staticMerkleRootMultisigIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "staticMessageIdMultisigIsmFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "storageGasOracle": "0x267B6B6eAf6790faE5D5E9070F28a9cE64CbF279", - "testRecipient": "0xe0B988062A0C6492177d64823Ab95a9c256c2a5F", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0x20c44b1E3BeaDA1e9826CFd48BeEDABeE9871cE9" - }, - "scrollsepolia": { - "aggregationHook": "0x7b63Aa270335F8896717c2A809205F4b650E4268", - "domainRoutingIsmFactory": "0x17866ebE0e503784a9461d3e753dEeD0d3F61153", - "fallbackRoutingHook": "0xE1CCB130389f687bf745Dd6dc05E50da17d9ea96", - "interchainAccountIsm": "0xE023239c8dfc172FF008D8087E7442d3eBEd9350", - "interchainAccountRouter": "0xe17c37212d785760E8331D4A4395B17b34Ba8cDF", - "interchainGasPaymaster": "0x86fb9F1c124fB20ff130C41a79a432F770f67AFD", - "mailbox": "0x3C5154a193D6e2955650f9305c8d80c18C814A68", - "merkleTreeHook": "0x863E8c26621c52ACa1849C53500606e73BA272F0", - "protocolFee": "0x5821f3B6eE05F3dC62b43B74AB1C8F8E6904b1C8", - "proxyAdmin": "0x598facE78a4302f11E3de0bee1894Da0b2Cb71F8", - "staticAggregationHookFactory": "0x44b764045BfDC68517e10e783E69B376cef196B2", - "staticAggregationIsmFactory": "0x16B710b86CAd07E6F1C531861a16F5feC29dba37", - "staticMerkleRootMultisigIsmFactory": "0x275aCcCa81cAD931dC6fB6E49ED233Bc99Bed4A7", - "staticMessageIdMultisigIsmFactory": "0xeb6f11189197223c656807a83B0DD374f9A6dF44", - "storageGasOracle": "0x6b1bb4ce664Bb4164AEB4d3D2E7DE7450DD8084C", - "testRecipient": "0xa3AB7E6cE24E6293bD5320A53329Ef2f4DE73fCA", - "testTokenRecipient": "0xc76E477437065093D353b7d56c81ff54D167B0Ab", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0x527768930D889662Fe7ACF64294871e86e4C2381" - }, - "sepolia": { - "aggregationHook": "0xe3147d5618f5e2e100690B50ec923009a4cde14A", - "domainRoutingIsmFactory": "0x3F100cBBE5FD5466BdB4B3a15Ac226957e7965Ad", - "fallbackRoutingHook": "0x17Dc724B7a2F09141C13b8AC33B396073785c2BC", - "interchainAccountIsm": "0x83a3068B719F764d413625dA77468ED74789ae02", - "interchainAccountRouter": "0x8e131c8aE5BF1Ed38D05a00892b6001a7d37739d", - "interchainGasPaymaster": "0x6f2756380FD49228ae25Aa7F2817993cB74Ecc56", - "interchainSecurityModule": "0x958124472b14B7940Ed5317C44a2508791dB1d48", - "mailbox": "0xfFAEF09B3cd11D9b20d1a19bECca54EEC2884766", - "merkleTreeHook": "0x4917a9746A7B6E0A57159cCb7F5a6744247f2d0d", - "pausableHook": "0xa68022e53Fd28119D07C8336a8eC84A298Fd38Fd", - "protocolFee": "0x13AC3349Cb159fE86A22cf42DdA803D9f7309DB5", - "proxyAdmin": "0x97Bbc6bBaFa5Ce3b2FA966c121Af63bD09e940f8", - "staticAggregationHookFactory": "0x160C28C92cA453570aD7C031972b58d5Dd128F72", - "staticAggregationIsmFactory": "0xC83e12EF2627ACE445C298e6eC418684918a6002", - "staticMerkleRootMultisigIsmFactory": "0x0a71AcC99967829eE305a285750017C4916Ca269", - "staticMessageIdMultisigIsmFactory": "0xFEb9585b2f948c1eD74034205a7439261a9d27DD", - "storageGasOracle": "0x71775B071F77F1ce52Ece810ce084451a3045FFe", - "testRecipient": "0xeDc1A3EDf87187085A3ABb7A9a65E1e7aE370C07", - "testTokenRecipient": "0x031AD9c560D37baC7d6Bd2d27A2443bAfd10101A", - "timelockController": "0x0000000000000000000000000000000000000000", - "validatorAnnounce": "0xE6105C59480a1B7DD3E4f28153aFdbE12F4CfCD9" - } -} diff --git a/typescript/sdk/src/consts/multisigIsm.ts b/typescript/sdk/src/consts/multisigIsm.ts index e7978085f6..d384e073d3 100644 --- a/typescript/sdk/src/consts/multisigIsm.ts +++ b/typescript/sdk/src/consts/multisigIsm.ts @@ -1,6 +1,7 @@ import { MultisigConfig } from '../ism/types.js'; import { ChainMap } from '../types.js'; +// TODO: consider migrating these to the registry too export const defaultMultisigConfigs: ChainMap = { alfajores: { threshold: 2, diff --git a/typescript/sdk/src/consts/multisigIsmVerifyCosts.json b/typescript/sdk/src/consts/multisigIsmVerifyCosts.json deleted file mode 100644 index 6f54991bec..0000000000 --- a/typescript/sdk/src/consts/multisigIsmVerifyCosts.json +++ /dev/null @@ -1,209 +0,0 @@ -{ - "1": { - "1": 151966 - }, - "2": { - "1": 152776, - "2": 159337 - }, - "3": { - "1": 153154, - "2": 159736, - "3": 166518 - }, - "4": { - "1": 153132, - "2": 160545, - "3": 166460, - "4": 173857 - }, - "5": { - "1": 153561, - "2": 160948, - "3": 166887, - "4": 174283, - "5": 180843 - }, - "6": { - "1": 154381, - "2": 160950, - "3": 167741, - "4": 174289, - "5": 181661, - "6": 187506 - }, - "7": { - "1": 154780, - "2": 161377, - "3": 168127, - "4": 174660, - "5": 182044, - "6": 187871, - "7": 195228 - }, - "8": { - "1": 154751, - "2": 162176, - "3": 168111, - "4": 175458, - "5": 182029, - "6": 188708, - "7": 195207, - "8": 202564 - }, - "9": { - "1": 155175, - "2": 162574, - "3": 168509, - "4": 175880, - "5": 182451, - "6": 189116, - "7": 195619, - "8": 202926, - "9": 209459 - }, - "10": { - "1": 155972, - "2": 162544, - "3": 169303, - "4": 175872, - "5": 183245, - "6": 189087, - "7": 196433, - "8": 202928, - "9": 210335, - "10": 216833 - }, - "11": { - "1": 156396, - "2": 162991, - "3": 169735, - "4": 176229, - "5": 183626, - "6": 189463, - "7": 196820, - "8": 203319, - "9": 210689, - "10": 217148, - "11": 223689 - }, - "12": { - "1": 156357, - "2": 163757, - "3": 169683, - "4": 177068, - "5": 183616, - "6": 190277, - "7": 196772, - "8": 204155, - "9": 210671, - "10": 218015, - "11": 223683, - "12": 231012 - }, - "13": { - "1": 156768, - "2": 164180, - "3": 170119, - "4": 177491, - "5": 184053, - "6": 190701, - "7": 197221, - "8": 204529, - "9": 211064, - "10": 217592, - "11": 224150, - "12": 231454, - "13": 237947 - }, - "14": { - "1": 157580, - "2": 164149, - "3": 170915, - "4": 177487, - "5": 184847, - "6": 190692, - "7": 198036, - "8": 204574, - "9": 211882, - "10": 217598, - "11": 224890, - "12": 231451, - "13": 238707, - "14": 245204 - }, - "15": { - "1": 158012, - "2": 164551, - "3": 171264, - "4": 177885, - "5": 185270, - "6": 191103, - "7": 198448, - "8": 204980, - "9": 212313, - "10": 217945, - "11": 225300, - "12": 231846, - "13": 239126, - "14": 245674, - "15": 252955 - }, - "16": { - "1": 158003, - "2": 164767, - "3": 171299, - "4": 178660, - "5": 185205, - "6": 191885, - "7": 198396, - "8": 205716, - "9": 212275, - "10": 218798, - "11": 225261, - "12": 232591, - "13": 239076, - "14": 246406, - "15": 252929, - "16": 260235 - }, - "17": { - "1": 158372, - "2": 165146, - "3": 171710, - "4": 179071, - "5": 185629, - "6": 192270, - "7": 198792, - "8": 206150, - "9": 212684, - "10": 219180, - "11": 225718, - "12": 233010, - "13": 239520, - "14": 246837, - "15": 253362, - "16": 260667, - "17": 266157 - }, - "18": { - "1": 159204, - "2": 165132, - "3": 172505, - "4": 179062, - "5": 186460, - "6": 192264, - "7": 199633, - "8": 206105, - "9": 213500, - "10": 219164, - "11": 226493, - "12": 232978, - "13": 240321, - "14": 246819, - "15": 254162, - "16": 260635, - "17": 266966, - "18": 273420 - } -} diff --git a/typescript/sdk/src/consts/multisigIsmVerifyCosts.ts b/typescript/sdk/src/consts/multisigIsmVerifyCosts.ts new file mode 100644 index 0000000000..2891527b2c --- /dev/null +++ b/typescript/sdk/src/consts/multisigIsmVerifyCosts.ts @@ -0,0 +1,210 @@ +/* eslint-disable sort-keys */ +export const multisigIsmVerifyCosts = { + '1': { + '1': 151966, + }, + '2': { + '1': 152776, + '2': 159337, + }, + '3': { + '1': 153154, + '2': 159736, + '3': 166518, + }, + '4': { + '1': 153132, + '2': 160545, + '3': 166460, + '4': 173857, + }, + '5': { + '1': 153561, + '2': 160948, + '3': 166887, + '4': 174283, + '5': 180843, + }, + '6': { + '1': 154381, + '2': 160950, + '3': 167741, + '4': 174289, + '5': 181661, + '6': 187506, + }, + '7': { + '1': 154780, + '2': 161377, + '3': 168127, + '4': 174660, + '5': 182044, + '6': 187871, + '7': 195228, + }, + '8': { + '1': 154751, + '2': 162176, + '3': 168111, + '4': 175458, + '5': 182029, + '6': 188708, + '7': 195207, + '8': 202564, + }, + '9': { + '1': 155175, + '2': 162574, + '3': 168509, + '4': 175880, + '5': 182451, + '6': 189116, + '7': 195619, + '8': 202926, + '9': 209459, + }, + '10': { + '1': 155972, + '2': 162544, + '3': 169303, + '4': 175872, + '5': 183245, + '6': 189087, + '7': 196433, + '8': 202928, + '9': 210335, + '10': 216833, + }, + '11': { + '1': 156396, + '2': 162991, + '3': 169735, + '4': 176229, + '5': 183626, + '6': 189463, + '7': 196820, + '8': 203319, + '9': 210689, + '10': 217148, + '11': 223689, + }, + '12': { + '1': 156357, + '2': 163757, + '3': 169683, + '4': 177068, + '5': 183616, + '6': 190277, + '7': 196772, + '8': 204155, + '9': 210671, + '10': 218015, + '11': 223683, + '12': 231012, + }, + '13': { + '1': 156768, + '2': 164180, + '3': 170119, + '4': 177491, + '5': 184053, + '6': 190701, + '7': 197221, + '8': 204529, + '9': 211064, + '10': 217592, + '11': 224150, + '12': 231454, + '13': 237947, + }, + '14': { + '1': 157580, + '2': 164149, + '3': 170915, + '4': 177487, + '5': 184847, + '6': 190692, + '7': 198036, + '8': 204574, + '9': 211882, + '10': 217598, + '11': 224890, + '12': 231451, + '13': 238707, + '14': 245204, + }, + '15': { + '1': 158012, + '2': 164551, + '3': 171264, + '4': 177885, + '5': 185270, + '6': 191103, + '7': 198448, + '8': 204980, + '9': 212313, + '10': 217945, + '11': 225300, + '12': 231846, + '13': 239126, + '14': 245674, + '15': 252955, + }, + '16': { + '1': 158003, + '2': 164767, + '3': 171299, + '4': 178660, + '5': 185205, + '6': 191885, + '7': 198396, + '8': 205716, + '9': 212275, + '10': 218798, + '11': 225261, + '12': 232591, + '13': 239076, + '14': 246406, + '15': 252929, + '16': 260235, + }, + '17': { + '1': 158372, + '2': 165146, + '3': 171710, + '4': 179071, + '5': 185629, + '6': 192270, + '7': 198792, + '8': 206150, + '9': 212684, + '10': 219180, + '11': 225718, + '12': 233010, + '13': 239520, + '14': 246837, + '15': 253362, + '16': 260667, + '17': 266157, + }, + '18': { + '1': 159204, + '2': 165132, + '3': 172505, + '4': 179062, + '5': 186460, + '6': 192264, + '7': 199633, + '8': 206105, + '9': 213500, + '10': 219164, + '11': 226493, + '12': 232978, + '13': 240321, + '14': 246819, + '15': 254162, + '16': 260635, + '17': 266966, + '18': 273420, + }, +}; diff --git a/typescript/sdk/src/consts/testChains.ts b/typescript/sdk/src/consts/testChains.ts new file mode 100644 index 0000000000..2737967261 --- /dev/null +++ b/typescript/sdk/src/consts/testChains.ts @@ -0,0 +1,120 @@ +import { ProtocolType } from '@hyperlane-xyz/utils'; + +import { + ChainMetadata, + ExplorerFamily, +} from '../metadata/chainMetadataTypes.js'; +import { ChainMap, ChainName } from '../types.js'; + +export enum TestChainName { + test1 = 'test1', + test2 = 'test2', + test3 = 'test3', +} + +export const testChains: Array = Object.values(TestChainName); + +export const test1: ChainMetadata = { + blockExplorers: [ + { + apiKey: 'fakekey', + apiUrl: 'https://api.etherscan.io/api', + family: ExplorerFamily.Etherscan, + name: 'Etherscan', + url: 'https://etherscan.io', + }, + ], + blocks: { + confirmations: 1, + estimateBlockTime: 3, + reorgPeriod: 0, + }, + chainId: 13371, + displayName: 'Test 1', + domainId: 13371, + isTestnet: true, + name: 'test1', + nativeToken: { decimals: 18, name: 'Ether', symbol: 'ETH' }, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'http://127.0.0.1:8545' }], +}; + +export const test2: ChainMetadata = { + ...test1, + blocks: { + confirmations: 1, + estimateBlockTime: 3, + reorgPeriod: 1, + }, + chainId: 13372, + displayName: 'Test 2', + domainId: 13372, + name: 'test2', +}; + +export const test3: ChainMetadata = { + ...test1, + blocks: { + confirmations: 1, + estimateBlockTime: 3, + reorgPeriod: 2, + }, + chainId: 13373, + displayName: 'Test 3', + domainId: 13373, + name: 'test3', +}; + +export const testChainMetadata: ChainMap = { + test1, + test2, + test3, +}; + +export const testCosmosChain: ChainMetadata = { + bech32Prefix: 'testcosmos', + blockExplorers: [ + { + apiUrl: 'https://www.mintscan.io/cosmos', + family: ExplorerFamily.Other, + name: 'Mintscan', + url: 'https://www.mintscan.io/cosmos', + }, + ], + chainId: 'testcosmos', + domainId: 123456789, + grpcUrls: [], + name: 'testcosmos', + nativeToken: { decimals: 6, denom: 'uatom', name: 'Atom', symbol: 'ATOM' }, + protocol: ProtocolType.Cosmos, + restUrls: [], + rpcUrls: [{ http: 'http://127.0.0.1:1317' }], + slip44: 118, +}; + +export const testSealevelChain: ChainMetadata = { + blockExplorers: [ + { + apiUrl: 'https://explorer.solana.com?cluster=devnet', + family: ExplorerFamily.Other, + name: 'Solana Explorer', + url: 'https://explorer.solana.com?cluster=devnet', + }, + ], + chainId: 987654321, + domainId: 987654321, + name: 'testsealevel', + nativeToken: { decimals: 9, name: 'Sol', symbol: 'SOL' }, + protocol: ProtocolType.Sealevel, + rpcUrls: [{ http: 'http://127.0.0.1:8899' }], +}; + +export const multiProtocolTestChainMetadata: ChainMap = { + ...testChainMetadata, + testcosmos: testCosmosChain, + testsealevel: testSealevelChain, +}; + +export const multiProtocolTestChains: Array = Object.keys( + multiProtocolTestChainMetadata, +); diff --git a/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts b/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts index 8062134823..90b3cfee43 100644 --- a/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts +++ b/typescript/sdk/src/core/CoreDeployer.hardhat-test.ts @@ -5,7 +5,7 @@ import sinon from 'sinon'; import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; -import { TestChains } from '../consts/chains.js'; +import { TestChainName, testChains } from '../consts/testChains.js'; import { HyperlaneContractsMap } from '../contracts/types.js'; import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js'; import { HookConfig } from '../hook/types.js'; @@ -37,7 +37,7 @@ describe('core', async () => { const proxyFactoryDeployer = new HyperlaneProxyFactoryDeployer( multiProvider, ); - coreConfig = testCoreConfig(TestChains, signer.address); + coreConfig = testCoreConfig(testChains, signer.address); const ismFactories = await proxyFactoryDeployer.deploy(coreConfig); ismFactory = new HyperlaneIsmFactory(ismFactories, multiProvider); deployer = new HyperlaneCoreDeployer(multiProvider, ismFactory); @@ -84,7 +84,7 @@ describe('core', async () => { ); // number of set hook transactions - const numTransactions = 2 * TestChains.length; + const numTransactions = 2 * testChains.length; const nonceAfter = await signer.getTransactionCount(); expect(nonceAfter).to.equal(nonceBefore + numTransactions); }); @@ -115,7 +115,7 @@ describe('core', async () => { // 3x1 for aggregation ISM deploy // 3x1 for setting ISM transaction for mailbox // 3x1 for setting ISM transaction for test recipient - const numTransactions = 3 * TestChains.length; + const numTransactions = 3 * testChains.length; const nonceAfter = await signer.getTransactionCount(); expect(nonceAfter).to.equal(nonceBefore + numTransactions); }); @@ -205,13 +205,16 @@ describe('core', async () => { }); it('persists partial failure', async () => { - expect(deployer.deployedContracts).to.have.keys(['test1', 'test2']); + expect(deployer.deployedContracts).to.have.keys([ + TestChainName.test1, + TestChainName.test2, + ]); }); it('can be resumed from partial (chain) failure', async () => { sinon.restore(); // restore normal deployer behavior and test3 will be deployed const result = await deployer.deploy(coreConfig); - expect(result).to.have.keys(['test1', 'test2', 'test3']); + expect(result).to.have.keys(testChains); // Each test network key has entries about the other test networks, where ISM details are stored. // With this exception, the keys should be the same, so we check the intersections for equality. const testnetKeysIntersection = Object.keys(result.test1).filter( diff --git a/typescript/sdk/src/core/HyperlaneCore.test.ts b/typescript/sdk/src/core/HyperlaneCore.test.ts index bcf6b89358..a22cf557ec 100644 --- a/typescript/sdk/src/core/HyperlaneCore.test.ts +++ b/typescript/sdk/src/core/HyperlaneCore.test.ts @@ -4,13 +4,9 @@ import { HyperlaneCore } from './HyperlaneCore.js'; describe('HyperlaneCore', () => { describe('fromEnvironment', () => { - it('creates an object for mainnet', async () => { - const multiProvider = new MultiProvider(); - HyperlaneCore.fromEnvironment('mainnet', multiProvider); - }); it('creates an object for testnet', async () => { - const multiProvider = new MultiProvider(); - HyperlaneCore.fromEnvironment('testnet', multiProvider); + const multiProvider = MultiProvider.createTestMultiProvider(); + HyperlaneCore.fromAddressesMap({ test1: {} }, multiProvider); }); }); }); diff --git a/typescript/sdk/src/core/HyperlaneCore.ts b/typescript/sdk/src/core/HyperlaneCore.ts index c38d013a3e..42d88863b4 100644 --- a/typescript/sdk/src/core/HyperlaneCore.ts +++ b/typescript/sdk/src/core/HyperlaneCore.ts @@ -16,11 +16,6 @@ import { } from '@hyperlane-xyz/utils'; import { HyperlaneApp } from '../app/HyperlaneApp.js'; -import { chainMetadata } from '../consts/chainMetadata.js'; -import { - HyperlaneEnvironment, - hyperlaneEnvironments, -} from '../consts/environments/index.js'; import { appFromAddressesMapHelper } from '../contracts/contracts.js'; import { HyperlaneAddressesMap } from '../contracts/types.js'; import { OwnableConfig } from '../deploy/types.js'; @@ -34,17 +29,6 @@ import { CoreFactories, coreFactories } from './contracts.js'; import { DispatchedMessage } from './types.js'; export class HyperlaneCore extends HyperlaneApp { - static fromEnvironment( - env: Env, - multiProvider: MultiProvider, - ): HyperlaneCore { - const envAddresses = hyperlaneEnvironments[env]; - if (!envAddresses) { - throw new Error(`No addresses found for ${env}`); - } - return HyperlaneCore.fromAddressesMap(envAddresses, multiProvider); - } - static fromAddressesMap( addressesMap: HyperlaneAddressesMap, multiProvider: MultiProvider, @@ -72,7 +56,7 @@ export class HyperlaneCore extends HyperlaneApp { return objFilter( config, (chainName, _): _ is RouterConfig => - chainMetadata[chainName].protocol === ProtocolType.Ethereum, + this.multiProvider.getProtocol(chainName) === ProtocolType.Ethereum, ); }; diff --git a/typescript/sdk/src/core/MultiProtocolCore.test.ts b/typescript/sdk/src/core/MultiProtocolCore.test.ts index 2eab2e96fd..2c88462f02 100644 --- a/typescript/sdk/src/core/MultiProtocolCore.test.ts +++ b/typescript/sdk/src/core/MultiProtocolCore.test.ts @@ -1,94 +1,28 @@ import { expect } from 'chai'; import { ethers } from 'ethers'; -import { ethereum } from '../consts/chainMetadata.js'; -import { Chains } from '../consts/chains.js'; +import { TestChainName, test1 } from '../consts/testChains.js'; import { MultiProtocolProvider } from '../providers/MultiProtocolProvider.js'; -import { ProviderType } from '../providers/ProviderType.js'; import { MultiProtocolCore } from './MultiProtocolCore.js'; import { EvmCoreAdapter } from './adapters/EvmCoreAdapter.js'; describe('MultiProtocolCore', () => { - describe('constructs', () => { - it('with constructor', () => { - const multiProvider = new MultiProtocolProvider({ - ethereum: { - ...ethereum, - }, - }); - const core = new MultiProtocolCore(multiProvider, { - ethereum: { - validatorAnnounce: ethers.constants.AddressZero, - proxyAdmin: ethers.constants.AddressZero, - mailbox: ethers.constants.AddressZero, - }, - }); - expect(core).to.be.instanceOf(MultiProtocolCore); - const ethAdapter = core.adapter(Chains.ethereum); - expect(ethAdapter).to.be.instanceOf(EvmCoreAdapter); + it('constructs', () => { + const multiProvider = new MultiProtocolProvider({ + test1: { + ...test1, + }, }); - it('from environment', () => { - const multiProvider = new MultiProtocolProvider(); - const core = MultiProtocolCore.fromEnvironment('mainnet', multiProvider); - expect(core).to.be.instanceOf(MultiProtocolCore); - const ethAdapter = core.adapter(Chains.ethereum); - expect(ethAdapter).to.be.instanceOf(EvmCoreAdapter); + const core = new MultiProtocolCore(multiProvider, { + test1: { + validatorAnnounce: ethers.constants.AddressZero, + proxyAdmin: ethers.constants.AddressZero, + mailbox: ethers.constants.AddressZero, + }, }); - }); - - // TODO: update for v3 - describe.skip('checks delivery', () => { - it('to EVM', async () => { - const multiProvider = new MultiProtocolProvider(); - const core = MultiProtocolCore.fromEnvironment('mainnet', multiProvider); - // https://arbiscan.io//tx/0x9da03376486327fc9b1e8069538e0fef91641055cb3a2ff89460c7955ab68264#eventlog - const receipt = { - transactionHash: - '0x9da03376486327fc9b1e8069538e0fef91641055cb3a2ff89460c7955ab68264', - logs: [ - { - data: '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000005300000013670000a4b100000000000000000000000096271ca0ab9eefb3ca481749c0ca4c705fd4f523000000890000000000000000000000006c0ac8cea75232aa7bed8cbe9c4f820e7a77a9c348656c6c6f2100000000000000000000000000', - topics: [ - '0x769f711d20c679153d382254f59892613b58a97cc876b249134ac25c80f9c814', - '0x00000000000000000000000096271ca0ab9eefb3ca481749c0ca4c705fd4f523', - '0x0000000000000000000000000000000000000000000000000000000000000089', - '0x0000000000000000000000006c0ac8cea75232aa7bed8cbe9c4f820e7a77a9c3', - ], - }, - ], - } as ethers.providers.TransactionReceipt; - // Should return immediately - await core.waitForMessagesProcessed(Chains.arbitrum, Chains.polygon, { - type: ProviderType.EthersV5, - receipt, - }); - }).timeout(10000); - - it('to Sealevel', async () => { - const multiProvider = new MultiProtocolProvider(); - const core = MultiProtocolCore.fromEnvironment('mainnet', multiProvider); - // https://arbiscan.io//tx/0x9da03376486327fc9b1e8069538e0fef91641055cb3a2ff89460c7955ab68264#eventlog - const receipt = { - transactionHash: - '0x9da03376486327fc9b1e8069538e0fef91641055cb3a2ff89460c7955ab68264', - logs: [ - { - data: '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000053000000136d0000a4b100000000000000000000000096271ca0ab9eefb3ca481749c0ca4c705fd4f523536f6c4d3797d0096b18b5b645c346a66d7f18c6c5738782c6bce24da57a3462bdef82b148656c6c6f2100000000000000000000000000', - topics: [ - '0x769f711d20c679153d382254f59892613b58a97cc876b249134ac25c80f9c814', - '0x00000000000000000000000096271ca0ab9eefb3ca481749c0ca4c705fd4f523', - '0x00000000000000000000000000000000000000000000000000000000536f6c4d', - '0x3797d0096b18b5b645c346a66d7f18c6c5738782c6bce24da57a3462bdef82b1', - ], - }, - ], - } as ethers.providers.TransactionReceipt; - // Should return immediately - await core.waitForMessagesProcessed(Chains.arbitrum, Chains.solana, { - type: ProviderType.EthersV5, - receipt, - }); - }).timeout(10000); + expect(core).to.be.instanceOf(MultiProtocolCore); + const ethAdapter = core.adapter(TestChainName.test1); + expect(ethAdapter).to.be.instanceOf(EvmCoreAdapter); }); }); diff --git a/typescript/sdk/src/core/MultiProtocolCore.ts b/typescript/sdk/src/core/MultiProtocolCore.ts index 0d8411a951..560567b727 100644 --- a/typescript/sdk/src/core/MultiProtocolCore.ts +++ b/typescript/sdk/src/core/MultiProtocolCore.ts @@ -1,10 +1,6 @@ import { HexString, ProtocolType, rootLogger } from '@hyperlane-xyz/utils'; import { AdapterClassType, MultiProtocolApp } from '../app/MultiProtocolApp.js'; -import { - HyperlaneEnvironment, - hyperlaneEnvironments, -} from '../consts/environments/index.js'; import { MultiProtocolProvider } from '../providers/MultiProtocolProvider.js'; import { TypedTransactionReceipt } from '../providers/ProviderType.js'; import { ChainMap, ChainName } from '../types.js'; @@ -27,17 +23,6 @@ export class MultiProtocolCore extends MultiProtocolApp< super(multiProvider, addresses, logger); } - static fromEnvironment( - env: Env, - multiProvider: MultiProtocolProvider, - ): MultiProtocolCore { - const envAddresses = hyperlaneEnvironments[env]; - if (!envAddresses) { - throw new Error(`No addresses found for ${env}`); - } - return MultiProtocolCore.fromAddressesMap(envAddresses, multiProvider); - } - static fromAddressesMap( addressesMap: ChainMap, multiProvider: MultiProtocolProvider, diff --git a/typescript/sdk/src/core/TestCoreDeployer.ts b/typescript/sdk/src/core/TestCoreDeployer.ts index 32a994c5c9..f9ad829a78 100644 --- a/typescript/sdk/src/core/TestCoreDeployer.ts +++ b/typescript/sdk/src/core/TestCoreDeployer.ts @@ -1,4 +1,4 @@ -import { TestChains } from '../consts/chains.js'; +import { testChains } from '../consts/testChains.js'; import { HyperlaneContracts } from '../contracts/types.js'; import { testCoreConfig } from '../test/testUtils.js'; import { ChainMap } from '../types.js'; @@ -9,7 +9,7 @@ import { CoreFactories } from './contracts.js'; export class TestCoreDeployer extends HyperlaneCoreDeployer { async deploy(): Promise>> { - return super.deploy(testCoreConfig(TestChains)); + return super.deploy(testCoreConfig(testChains)); } async deployApp(): Promise { diff --git a/typescript/sdk/src/core/adapters/CosmWasmCoreAdapter.test.ts b/typescript/sdk/src/core/adapters/CosmWasmCoreAdapter.test.ts index fb56d868bd..8866e22c1c 100644 --- a/typescript/sdk/src/core/adapters/CosmWasmCoreAdapter.test.ts +++ b/typescript/sdk/src/core/adapters/CosmWasmCoreAdapter.test.ts @@ -1,6 +1,10 @@ import { expect } from 'chai'; -import { Chains } from '../../consts/chains.js'; +import { + multiProtocolTestChainMetadata, + test1, + testCosmosChain, +} from '../../consts/testChains.js'; import { MultiProtocolProvider } from '../../providers/MultiProtocolProvider.js'; import { ProviderType } from '../../providers/ProviderType.js'; @@ -15,8 +19,16 @@ describe('CosmWasmCoreAdapter', () => { it('constructs', () => { adapter = new CosmWasmCoreAdapter( - Chains.injective, - new MultiProtocolProvider(), + testCosmosChain.name, + MultiProtocolProvider.createTestMultiProtocolProvider({ + ...multiProtocolTestChainMetadata, + inevm: { + ...test1, + name: 'inevm', + chainId: 2525, + domainId: 2525, + }, + }), { mailbox: '' }, ); expect(adapter).to.be.instanceOf(CosmWasmCoreAdapter); diff --git a/typescript/sdk/src/core/testHyperlaneDeploy.hardhat-test.ts b/typescript/sdk/src/core/testHyperlaneDeploy.hardhat-test.ts index 274969fce7..3ba603c15c 100644 --- a/typescript/sdk/src/core/testHyperlaneDeploy.hardhat-test.ts +++ b/typescript/sdk/src/core/testHyperlaneDeploy.hardhat-test.ts @@ -7,7 +7,7 @@ import hre from 'hardhat'; import { TestMailbox, TestRecipient__factory } from '@hyperlane-xyz/core'; import { addressToBytes32 } from '@hyperlane-xyz/utils'; -import { Chains } from '../consts/chains.js'; +import { TestChainName } from '../consts/testChains.js'; import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js'; import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js'; import { MultiProvider } from '../providers/MultiProvider.js'; @@ -15,8 +15,8 @@ import { MultiProvider } from '../providers/MultiProvider.js'; import { TestCoreApp } from './TestCoreApp.js'; import { TestCoreDeployer } from './TestCoreDeployer.js'; -const localChain = Chains.test1; -const remoteChain = Chains.test2; +const localChain = TestChainName.test1; +const remoteChain = TestChainName.test2; const message = '0xdeadbeef'; describe('TestCoreDeployer', async () => { diff --git a/typescript/sdk/src/deploy/HyperlaneDeployer.ts b/typescript/sdk/src/deploy/HyperlaneDeployer.ts index c7b5845b62..46f4db67ea 100644 --- a/typescript/sdk/src/deploy/HyperlaneDeployer.ts +++ b/typescript/sdk/src/deploy/HyperlaneDeployer.ts @@ -13,7 +13,7 @@ import { TimelockController__factory, TransparentUpgradeableProxy__factory, } from '@hyperlane-xyz/core'; -import SdkBuildArtifact from '@hyperlane-xyz/core/buildArtifact.json' assert { type: 'json' }; +import { buildArtifact as coreBuildArtifact } from '@hyperlane-xyz/core/buildArtifact.js'; import { Address, ProtocolType, @@ -91,11 +91,11 @@ export abstract class HyperlaneDeployer< ); } - // if none provided, instantiate a default verifier with SDK's included build artifact + // if none provided, instantiate a default verifier with the default core contract build artifact this.options.contractVerifier ??= new ContractVerifier( multiProvider, {}, - SdkBuildArtifact, + coreBuildArtifact, ExplorerLicenseType.MIT, ); } diff --git a/typescript/sdk/src/gas/HyperlaneIgp.ts b/typescript/sdk/src/gas/HyperlaneIgp.ts index 3fe71b2ae2..58b9f22909 100644 --- a/typescript/sdk/src/gas/HyperlaneIgp.ts +++ b/typescript/sdk/src/gas/HyperlaneIgp.ts @@ -4,10 +4,6 @@ import { InterchainGasPaymaster__factory } from '@hyperlane-xyz/core'; import { Address } from '@hyperlane-xyz/utils'; import { HyperlaneApp } from '../app/HyperlaneApp.js'; -import { - HyperlaneEnvironment, - hyperlaneEnvironments, -} from '../consts/environments/index.js'; import { appFromAddressesMapHelper } from '../contracts/contracts.js'; import { HyperlaneAddressesMap } from '../contracts/types.js'; import { MultiProvider } from '../providers/MultiProvider.js'; @@ -16,18 +12,6 @@ import { ChainName } from '../types.js'; import { IgpFactories, igpFactories } from './contracts.js'; export class HyperlaneIgp extends HyperlaneApp { - static fromEnvironment( - env: Env, - multiProvider: MultiProvider, - ): HyperlaneIgp { - const envAddresses = hyperlaneEnvironments[env]; - if (!envAddresses) { - throw new Error(`No addresses found for ${env}`); - } - /// @ts-ignore - return HyperlaneIgp.fromAddressesMap(envAddresses, multiProvider); - } - static fromAddressesMap( addressesMap: HyperlaneAddressesMap, multiProvider: MultiProvider, diff --git a/typescript/sdk/src/gas/HyperlaneIgpChecker.ts b/typescript/sdk/src/gas/HyperlaneIgpChecker.ts index c6ca5dae03..fe5e3a7fea 100644 --- a/typescript/sdk/src/gas/HyperlaneIgpChecker.ts +++ b/typescript/sdk/src/gas/HyperlaneIgpChecker.ts @@ -3,7 +3,6 @@ import { BigNumber } from 'ethers'; import { eqAddress } from '@hyperlane-xyz/utils'; import { BytecodeHash } from '../consts/bytecode.js'; -import { chainMetadata } from '../consts/chainMetadata.js'; import { HyperlaneAppChecker } from '../deploy/HyperlaneAppChecker.js'; import { proxyImplementation } from '../deploy/proxy.js'; import { ChainName } from '../types.js'; @@ -90,9 +89,7 @@ export class HyperlaneIgpChecker extends HyperlaneAppChecker< expectedOverhead = 0; } - const remoteId = - chainMetadata[remote]?.domainId ?? - this.multiProvider.getDomainId(remote); + const remoteId = this.multiProvider.getDomainId(remote); const existingOverhead = await defaultIsmIgp.destinationGasLimit( remoteId, 0, @@ -131,9 +128,7 @@ export class HyperlaneIgpChecker extends HyperlaneAppChecker< Object.keys(this.configMap[local].oracleConfig ?? {}), ); for (const remote of remotes) { - const remoteId = - chainMetadata[remote]?.domainId ?? - this.multiProvider.getDomainId(remote); + const remoteId = this.multiProvider.getDomainId(remote); const destinationGasConfigs = await igp.destinationGasConfigs(remoteId); const actualGasOracle = destinationGasConfigs.gasOracle; const expectedGasOracle = coreContracts.storageGasOracle.address; diff --git a/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts b/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts index fa86f88663..e5e1e14524 100644 --- a/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts +++ b/typescript/sdk/src/gas/HyperlaneIgpDeployer.ts @@ -7,7 +7,6 @@ import { } from '@hyperlane-xyz/core'; import { eqAddress, rootLogger } from '@hyperlane-xyz/utils'; -import { chainMetadata } from '../consts/chainMetadata.js'; import { TOKEN_EXCHANGE_RATE_SCALE } from '../consts/igp.js'; import { HyperlaneContracts } from '../contracts/types.js'; import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer.js'; @@ -50,9 +49,7 @@ export class HyperlaneIgpDeployer extends HyperlaneDeployer< const gasParamsToSet: InterchainGasPaymaster.GasParamStruct[] = []; for (const [remote, newGasOverhead] of Object.entries(config.overhead)) { - const remoteId = - chainMetadata[remote]?.domainId ?? - this.multiProvider.getDomainId(remote); + const remoteId = this.multiProvider.getDomainId(remote); const currentGasConfig = await igp.destinationGasConfigs(remoteId); if ( @@ -104,9 +101,7 @@ export class HyperlaneIgpDeployer extends HyperlaneDeployer< // For each remote, check if the gas oracle has the correct data for (const [remote, desired] of Object.entries(config.oracleConfig)) { // check core metadata for non EVMs and fallback to multiprovider for custom EVMs - const remoteDomain = - chainMetadata[remote]?.domainId ?? - this.multiProvider.getDomainId(remote); + const remoteDomain = this.multiProvider.getDomainId(remote); const actual = await gasOracle.remoteGasData(remoteDomain); diff --git a/typescript/sdk/src/gas/oracle/configure-gas-oracles.hardhat-test.ts b/typescript/sdk/src/gas/oracle/configure-gas-oracles.hardhat-test.ts index 205398af64..129ea852d6 100644 --- a/typescript/sdk/src/gas/oracle/configure-gas-oracles.hardhat-test.ts +++ b/typescript/sdk/src/gas/oracle/configure-gas-oracles.hardhat-test.ts @@ -4,6 +4,7 @@ import hre from 'hardhat'; import { InterchainGasPaymaster } from '@hyperlane-xyz/core'; +import { TestChainName } from '../../consts/testChains.js'; import { MultiProvider } from '../../providers/MultiProvider.js'; import { testIgpConfig } from '../../test/testUtils.js'; import { ChainMap } from '../../types.js'; @@ -11,8 +12,8 @@ import { HyperlaneIgpDeployer } from '../HyperlaneIgpDeployer.js'; import { IgpConfig } from '../types.js'; describe('HyperlaneIgpDeployer', () => { - const local = 'test1'; - const remote = 'test2'; + const local = TestChainName.test1; + const remote = TestChainName.test2; let remoteId: number; let deployer: HyperlaneIgpDeployer; let igp: InterchainGasPaymaster; diff --git a/typescript/sdk/src/gas/token-prices.test.ts b/typescript/sdk/src/gas/token-prices.test.ts index c92a065d54..48deda8bc8 100644 --- a/typescript/sdk/src/gas/token-prices.test.ts +++ b/typescript/sdk/src/gas/token-prices.test.ts @@ -1,16 +1,16 @@ import { expect } from 'chai'; -import { Chains } from '../consts/chains.js'; -import { MockCoinGecko } from '../test/testUtils.js'; +import { TestChainName, testChainMetadata } from '../consts/testChains.js'; +import { MockCoinGecko } from '../test/MockCoinGecko.js'; import { CoinGeckoTokenPriceGetter } from './token-prices.js'; describe('TokenPriceGetter', () => { let tokenPriceGetter: CoinGeckoTokenPriceGetter; let mockCoinGecko: MockCoinGecko; - const chainA = Chains.ethereum, - chainB = Chains.polygon, - priceA = 10, + const chainA = TestChainName.test1, + chainB = TestChainName.test2, + priceA = 1, priceB = 5.5; before(async () => { mockCoinGecko = new MockCoinGecko(); @@ -20,6 +20,7 @@ describe('TokenPriceGetter', () => { mockCoinGecko.setTokenPrice(chainB, priceB); tokenPriceGetter = new CoinGeckoTokenPriceGetter( mockCoinGecko, + testChainMetadata, undefined, 0, ); @@ -43,8 +44,8 @@ describe('TokenPriceGetter', () => { chainA, chainB, ); - const expectedExchangeRate = priceA / priceB; - expect(exchangeRate).to.equal(expectedExchangeRate); + // Should equal 1 because testnet prices are always forced to 1 + expect(exchangeRate).to.equal(1); }); }); }); diff --git a/typescript/sdk/src/gas/token-prices.ts b/typescript/sdk/src/gas/token-prices.ts index 700c22536d..26d17cd8bc 100644 --- a/typescript/sdk/src/gas/token-prices.ts +++ b/typescript/sdk/src/gas/token-prices.ts @@ -2,8 +2,6 @@ import CoinGecko from 'coingecko-api'; import { rootLogger, sleep } from '@hyperlane-xyz/utils'; -import { chainMetadata as defaultChainMetadata } from '../consts/chainMetadata.js'; -import { CoreChainName, Mainnets } from '../consts/chains.js'; import { ChainMetadata } from '../metadata/chainMetadataTypes.js'; import { ChainMap, ChainName } from '../types.js'; @@ -75,9 +73,9 @@ export class CoinGeckoTokenPriceGetter implements TokenPriceGetter { constructor( coinGecko: CoinGeckoInterface, + chainMetadata: ChainMap, expirySeconds?: number, sleepMsBetweenRequests = 5000, - chainMetadata = defaultChainMetadata, ) { this.coinGecko = coinGecko; this.cache = new TokenPriceCache(expirySeconds); @@ -86,12 +84,14 @@ export class CoinGeckoTokenPriceGetter implements TokenPriceGetter { } static withDefaultCoinGecko( + chainMetadata: ChainMap, expirySeconds?: number, sleepMsBetweenRequests = 5000, ): CoinGeckoTokenPriceGetter { const coinGecko = new CoinGecko(); return new CoinGeckoTokenPriceGetter( coinGecko, + chainMetadata, expirySeconds, sleepMsBetweenRequests, ); @@ -111,8 +111,7 @@ export class CoinGeckoTokenPriceGetter implements TokenPriceGetter { } private async getTokenPrices(chains: ChainName[]): Promise { - // TODO improve PI support here? - const isMainnet = chains.map((c) => Mainnets.includes(c as CoreChainName)); + const isMainnet = chains.map((c) => !this.metadata[c].isTestnet); const allMainnets = isMainnet.every((v) => v === true); const allTestnets = isMainnet.every((v) => v === false); if (allTestnets) { diff --git a/typescript/sdk/src/hook/HyperlaneHookDeployer.ts b/typescript/sdk/src/hook/HyperlaneHookDeployer.ts index c7ead69aa0..3e521b094a 100644 --- a/typescript/sdk/src/hook/HyperlaneHookDeployer.ts +++ b/typescript/sdk/src/hook/HyperlaneHookDeployer.ts @@ -11,7 +11,6 @@ import { } from '@hyperlane-xyz/core'; import { Address, addressToBytes32, rootLogger } from '@hyperlane-xyz/utils'; -import { chainMetadata } from '../consts/chainMetadata.js'; import { HyperlaneContracts } from '../contracts/types.js'; import { CoreAddresses } from '../core/contracts.js'; import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer.js'; @@ -294,8 +293,7 @@ export class HyperlaneHookDeployer extends HyperlaneDeployer< const routingConfigs: DomainRoutingHook.HookConfigStruct[] = []; for (const [dest, hookConfig] of Object.entries(config.domains)) { - const destDomain = - chainMetadata[dest]?.domainId ?? this.multiProvider.getDomainId(dest); + const destDomain = this.multiProvider.getDomainId(dest); if (typeof hookConfig === 'string') { routingConfigs.push({ destination: destDomain, diff --git a/typescript/sdk/src/hook/read.test.ts b/typescript/sdk/src/hook/read.test.ts index b058586866..dd98039676 100644 --- a/typescript/sdk/src/hook/read.test.ts +++ b/typescript/sdk/src/hook/read.test.ts @@ -16,8 +16,7 @@ import { } from '@hyperlane-xyz/core'; import { WithAddress } from '@hyperlane-xyz/utils'; -import { chainMetadata } from '../consts/chainMetadata.js'; -import { Chains } from '../consts/chains.js'; +import { TestChainName, test1 } from '../consts/testChains.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { EvmHookReader } from './read.js'; @@ -39,9 +38,8 @@ describe('EvmHookReader', () => { beforeEach(() => { sandbox = sinon.createSandbox(); - multiProvider = new MultiProvider(); - multiProvider.setProvider(Chains.ethereum, ethers.getDefaultProvider()); - evmHookReader = new EvmHookReader(multiProvider, Chains.ethereum); + multiProvider = MultiProvider.createTestMultiProvider(); + evmHookReader = new EvmHookReader(multiProvider, TestChainName.test1); }); afterEach(() => { @@ -158,9 +156,7 @@ describe('EvmHookReader', () => { hookType: sandbox.stub().resolves(OnchainHookType.ID_AUTH_ISM), owner: sandbox.stub().resolves(mockOwner), l1Messenger: sandbox.stub().resolves(l1Messenger), - destinationDomain: sandbox - .stub() - .resolves(chainMetadata.ethereum.domainId), + destinationDomain: sandbox.stub().resolves(test1.domainId), }; sandbox .stub(OPStackHook__factory, 'connect') @@ -174,7 +170,7 @@ describe('EvmHookReader', () => { address: mockAddress, type: HookType.OP_STACK, nativeBridge: l1Messenger, - destinationChain: Chains.ethereum, + destinationChain: TestChainName.test1, }; // top-level method infers hook type diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 3881cc6d5d..c185d14426 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -7,35 +7,23 @@ export { MultiProtocolApp, } from './app/MultiProtocolApp.js'; export { - chainIdToMetadata, - chainMetadata, - mainnetChainsMetadata, - solanaChainToClusterName, - testnetChainsMetadata, -} from './consts/chainMetadata.js'; -export { - AllChains, - AllDeprecatedChains, - Chains, - CoreChainName, - DeprecatedChains, - Mainnets, - TestChains, - Testnets, -} from './consts/chains.js'; -export { - TOKEN_EXCHANGE_RATE_SCALE, TOKEN_EXCHANGE_RATE_DECIMALS, + TOKEN_EXCHANGE_RATE_SCALE, } from './consts/igp.js'; -export { - HyperlaneEnvironment, - HyperlaneEnvironmentChain, - hyperlaneContractAddresses, - hyperlaneEnvironments, -} from './consts/environments/index.js'; export { MAILBOX_VERSION } from './consts/mailbox.js'; export { defaultMultisigConfigs } from './consts/multisigIsm.js'; export { SEALEVEL_SPL_NOOP_ADDRESS } from './consts/sealevel.js'; +export { + TestChainName, + multiProtocolTestChainMetadata, + test1, + test2, + test3, + testChainMetadata, + testChains, + testCosmosChain, + testSealevelChain, +} from './consts/testChains.js'; export { attachContracts, attachContractsMap, @@ -77,6 +65,7 @@ export { coreFactories, } from './core/contracts.js'; export { HyperlaneLifecyleEvent } from './core/events.js'; +export { EvmCoreReader } from './core/read.js'; export { CoreConfig, CoreViolationType, @@ -136,7 +125,6 @@ export { } from './gas/types.js'; export { HyperlaneHookDeployer } from './hook/HyperlaneHookDeployer.js'; export { EvmHookReader } from './hook/read.js'; -export { EvmCoreReader } from './core/read.js'; export { AggregationHookConfig, DomainRoutingHookConfig, @@ -151,11 +139,11 @@ export { ProtocolFeeHookConfig, } from './hook/types.js'; export { HyperlaneIsmFactory } from './ism/HyperlaneIsmFactory.js'; -export { EvmIsmReader } from './ism/read.js'; export { buildAggregationIsmConfigs, buildMultisigIsmConfigs, } from './ism/multisig.js'; +export { EvmIsmReader } from './ism/read.js'; export { AggregationIsmConfig, DeployedIsm, @@ -432,8 +420,8 @@ export { TokenMetadata, TokenType, isCollateralConfig, - isSyntheticConfig, isNativeConfig, + isSyntheticConfig, isUriConfig, } from './token/config.js'; export { @@ -442,23 +430,17 @@ export { TokenFactories, } from './token/contracts.js'; export { HypERC20Deployer, HypERC721Deployer } from './token/deploy.js'; -export { - ChainMap, - ChainName, - ChainNameOrId, - Connection, - TestChainNames, -} from './types.js'; -export { getCosmosRegistryChain } from './utils/cosmos.js'; +export { ChainMap, ChainName, ChainNameOrId, Connection } from './types.js'; export { MultiGeneric } from './utils/MultiGeneric.js'; +export { getCosmosRegistryChain } from './utils/cosmos.js'; export { filterByChains } from './utils/filter.js'; export { ANVIL_RPC_METHODS, + getLocalProvider, + impersonateAccount, resetFork, setFork, - impersonateAccount, stopImpersonatingAccount, - getLocalProvider, } from './utils/fork.js'; export { multisigIsmVerificationCost } from './utils/ism.js'; export { @@ -466,10 +448,7 @@ export { SealevelInstructionWrapper, getSealevelAccountDataSchema, } from './utils/sealevelSerialization.js'; -export { - chainMetadataToWagmiChain, - wagmiChainMetadata, -} from './utils/wagmi.js'; +export { chainMetadataToWagmiChain } from './utils/wagmi.js'; export { WarpCore, WarpCoreOptions } from './warp/WarpCore.js'; export { FeeConstantConfig, @@ -480,10 +459,10 @@ export { WarpTypedTransaction, } from './warp/types.js'; +export { AggregationIsmConfigSchema } from './ism/schemas.js'; export { MailboxClientConfigSchema as mailboxClientConfigSchema } from './router/schemas.js'; export { WarpRouteDeployConfigSchema, TokenRouterConfigSchema as tokenRouterConfigSchema, } from './token/schemas.js'; -export { AggregationIsmConfigSchema } from './ism/schemas.js'; -export { WarpRouteDeployConfig, TokenRouterConfig } from './token/types.js'; +export { TokenRouterConfig, WarpRouteDeployConfig } from './token/types.js'; diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts index b1ab5cb318..2fe0b8f340 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.hardhat-test.ts @@ -5,7 +5,7 @@ import hre from 'hardhat'; import { DomainRoutingIsm, TrustedRelayerIsm } from '@hyperlane-xyz/core'; import { Address } from '@hyperlane-xyz/utils'; -import { TestChains } from '../consts/chains.js'; +import { TestChainName, testChains } from '../consts/testChains.js'; import { TestCoreApp } from '../core/TestCoreApp.js'; import { TestCoreDeployer } from '../core/TestCoreDeployer.js'; import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js'; @@ -55,7 +55,7 @@ const randomIsmConfig = (depth = 0, maxDepth = 2): IsmConfig => { type: IsmType.ROUTING, owner: randomAddress(), domains: Object.fromEntries( - TestChains.map((c) => [c, randomIsmConfig(depth + 1)]), + testChains.map((c) => [c, randomIsmConfig(depth + 1)]), ), }; return config; @@ -88,7 +88,7 @@ describe('HyperlaneIsmFactory', async () => { let ismFactoryDeployer: HyperlaneProxyFactoryDeployer; let exampleRoutingConfig: RoutingIsmConfig; let mailboxAddress: Address, newMailboxAddress: Address; - const chain = 'test1'; + const chain = TestChainName.test1; beforeEach(async () => { const [signer] = await hre.ethers.getSigners(); @@ -110,10 +110,9 @@ describe('HyperlaneIsmFactory', async () => { type: IsmType.ROUTING, owner: await multiProvider.getSignerAddress(chain), domains: Object.fromEntries( - TestChains.filter((c) => c !== 'test1').map((c) => [ - c, - randomMultisigIsmConfig(3, 5), - ]), + testChains + .filter((c) => c !== TestChainName.test1) + .map((c) => [c, randomMultisigIsmConfig(3, 5)]), ), }; }); @@ -343,7 +342,10 @@ describe('HyperlaneIsmFactory', async () => { // deleting the domain and removing from multiprovider should unenroll the domain // NB: we'll deploy new multisigIsms for the domains bc of new factories but the routingIsm address should be the same because of existingIsmAddress delete exampleRoutingConfig.domains['test3']; - multiProvider = multiProvider.intersect(['test1', 'test2']).result; + multiProvider = multiProvider.intersect([ + TestChainName.test1, + 'test2', + ]).result; ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider); ismFactory = new HyperlaneIsmFactory( await ismFactoryDeployer.deploy( diff --git a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts index c264eaf172..1f679ca62b 100644 --- a/typescript/sdk/src/ism/HyperlaneIsmFactory.ts +++ b/typescript/sdk/src/ism/HyperlaneIsmFactory.ts @@ -29,11 +29,6 @@ import { } from '@hyperlane-xyz/utils'; import { HyperlaneApp } from '../app/HyperlaneApp.js'; -import { chainMetadata } from '../consts/chainMetadata.js'; -import { - HyperlaneEnvironment, - hyperlaneEnvironments, -} from '../consts/environments/index.js'; import { appFromAddressesMapHelper } from '../contracts/contracts.js'; import { HyperlaneAddressesMap } from '../contracts/types.js'; import { HyperlaneDeployer } from '../deploy/HyperlaneDeployer.js'; @@ -68,18 +63,6 @@ export class HyperlaneIsmFactory extends HyperlaneApp { this.deployer = deployer; } - static fromEnvironment( - env: Env, - multiProvider: MultiProvider, - ): HyperlaneIsmFactory { - const envAddresses = hyperlaneEnvironments[env]; - if (!envAddresses) { - throw new Error(`No addresses found for ${env}`); - } - /// @ts-ignore - return HyperlaneIsmFactory.fromAddressesMap(envAddresses, multiProvider); - } - static fromAddressesMap( addressesMap: HyperlaneAddressesMap, multiProvider: MultiProvider, @@ -265,9 +248,7 @@ export class HyperlaneIsmFactory extends HyperlaneApp { config.domains = objFilter( config.domains, (domain, config): config is IsmConfig => { - const domainId = - chainMetadata[domain]?.domainId ?? - this.multiProvider.tryGetDomainId(domain); + const domainId = this.multiProvider.tryGetDomainId(domain); if (domainId === null) { logger.warn( `Domain ${domain} doesn't have chain metadata provided, skipping ...`, @@ -276,10 +257,8 @@ export class HyperlaneIsmFactory extends HyperlaneApp { return domainId !== null; }, ); - const safeConfigDomains = Object.keys(config.domains).map( - (domain) => - chainMetadata[domain]?.domainId ?? - this.multiProvider.getDomainId(domain), + const safeConfigDomains = Object.keys(config.domains).map((domain) => + this.multiProvider.getDomainId(domain), ); const delta: RoutingIsmDelta = existingIsmAddress ? await routingModuleDelta( diff --git a/typescript/sdk/src/ism/read.test.ts b/typescript/sdk/src/ism/read.test.ts index 3790d3137e..a50287de63 100644 --- a/typescript/sdk/src/ism/read.test.ts +++ b/typescript/sdk/src/ism/read.test.ts @@ -18,7 +18,7 @@ import { } from '@hyperlane-xyz/core'; import { WithAddress } from '@hyperlane-xyz/utils'; -import { Chains } from '../consts/chains.js'; +import { TestChainName } from '../consts/testChains.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { EvmIsmReader } from './read.js'; @@ -39,9 +39,8 @@ describe('EvmIsmReader', () => { beforeEach(() => { sandbox = sinon.createSandbox(); - multiProvider = new MultiProvider(); - multiProvider.setProvider(Chains.ethereum, ethers.getDefaultProvider()); - evmIsmReader = new EvmIsmReader(multiProvider, Chains.ethereum); + multiProvider = MultiProvider.createTestMultiProvider(); + evmIsmReader = new EvmIsmReader(multiProvider, TestChainName.test1); }); afterEach(() => { diff --git a/typescript/sdk/src/ism/utils.ts b/typescript/sdk/src/ism/utils.ts index cfe6de8643..2ee8a0d06e 100644 --- a/typescript/sdk/src/ism/utils.ts +++ b/typescript/sdk/src/ism/utils.ts @@ -21,7 +21,6 @@ import { rootLogger, } from '@hyperlane-xyz/utils'; -import { chainMetadata } from '../consts/chainMetadata.js'; import { HyperlaneContracts } from '../contracts/types.js'; import { ProxyFactoryFactories } from '../deploy/contracts.js'; import { resolveOrDeployAccountOwner } from '../deploy/types.js'; @@ -351,11 +350,8 @@ export async function routingModuleDelta( domain.toNumber(), ); // config.domains is already filtered to only include domains in the multiprovider - const safeConfigDomains = objMap( - config.domains, - (chainName) => - chainMetadata[chainName]?.domainId ?? - multiProvider.getDomainId(chainName), + const safeConfigDomains = objMap(config.domains, (chainName) => + multiProvider.getDomainId(chainName), ); const delta: RoutingIsmDelta = { diff --git a/typescript/sdk/src/metadata/ChainMetadataManager.ts b/typescript/sdk/src/metadata/ChainMetadataManager.ts index 59c7730dae..c8de16302e 100644 --- a/typescript/sdk/src/metadata/ChainMetadataManager.ts +++ b/typescript/sdk/src/metadata/ChainMetadataManager.ts @@ -2,7 +2,6 @@ import { Logger } from 'pino'; import { ProtocolType, exclude, pick, rootLogger } from '@hyperlane-xyz/utils'; -import { chainMetadata as defaultChainMetadata } from '../consts/chainMetadata.js'; import { ChainMap, ChainName, ChainNameOrId } from '../types.js'; import { @@ -14,9 +13,9 @@ import { } from './blockExplorer.js'; import { ChainMetadata, + ChainMetadataSchema, ExplorerFamily, getDomainId, - safeParseChainMetadata, } from './chainMetadataTypes.js'; export interface ChainMetadataManagerOptions { @@ -37,9 +36,7 @@ export class ChainMetadataManager { * or the SDK's default metadata if not provided */ constructor( - chainMetadata: ChainMap< - ChainMetadata - > = defaultChainMetadata as ChainMap>, + chainMetadata: ChainMap>, options: ChainMetadataManagerOptions = {}, ) { Object.entries(chainMetadata).forEach(([key, cm]) => { @@ -61,14 +58,7 @@ export class ChainMetadataManager { * @throws if chain's name or domain/chain ID collide */ addChain(metadata: ChainMetadata): void { - const parseResult = safeParseChainMetadata(metadata); - if (!parseResult.success) { - throw new Error( - `Invalid chain metadata for ${ - metadata.name - }: ${parseResult.error.format()}`, - ); - } + ChainMetadataSchema.parse(metadata); // Ensure no two chains have overlapping names/domainIds/chainIds for (const chainMetadata of Object.values(this.metadata)) { const { name, chainId, domainId } = chainMetadata; diff --git a/typescript/sdk/src/metadata/agentConfig.test.ts b/typescript/sdk/src/metadata/agentConfig.test.ts index 5bd8a0f435..4271695769 100644 --- a/typescript/sdk/src/metadata/agentConfig.test.ts +++ b/typescript/sdk/src/metadata/agentConfig.test.ts @@ -1,23 +1,23 @@ import { expect } from 'chai'; -import { Chains } from '../consts/chains.js'; +import { TestChainName } from '../consts/testChains.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { buildAgentConfig } from './agentConfig.js'; describe('Agent config', () => { const args: Parameters = [ - [Chains.ethereum], - new MultiProvider(), + [TestChainName.test1], + MultiProvider.createTestMultiProvider(), { - ethereum: { + test1: { mailbox: '0xmailbox', interchainGasPaymaster: '0xgas', validatorAnnounce: '0xannounce', merkleTreeHook: '0xmerkle', }, }, - { ethereum: 0 }, + { test1: 0 }, ]; it('Should generate a new agent config', () => { @@ -26,13 +26,15 @@ describe('Agent config', () => { 'chains', 'defaultRpcConsensusType', ]); - expect(result.chains[Chains.ethereum].mailbox).to.equal('0xmailbox'); - expect(result.chains[Chains.ethereum].interchainGasPaymaster).to.equal( + expect(result.chains[TestChainName.test1].mailbox).to.equal('0xmailbox'); + expect(result.chains[TestChainName.test1].interchainGasPaymaster).to.equal( '0xgas', ); - expect(result.chains[Chains.ethereum].validatorAnnounce).to.equal( + expect(result.chains[TestChainName.test1].validatorAnnounce).to.equal( '0xannounce', ); - expect(result.chains[Chains.ethereum].merkleTreeHook).to.equal('0xmerkle'); + expect(result.chains[TestChainName.test1].merkleTreeHook).to.equal( + '0xmerkle', + ); }); }); diff --git a/typescript/sdk/src/metadata/blockExplorer.test.ts b/typescript/sdk/src/metadata/blockExplorer.test.ts new file mode 100644 index 0000000000..53a358876b --- /dev/null +++ b/typescript/sdk/src/metadata/blockExplorer.test.ts @@ -0,0 +1,55 @@ +import { expect } from 'chai'; + +import { + test1, + testCosmosChain, + testSealevelChain, +} from '../consts/testChains.js'; + +import { + getExplorerAddressUrl, + getExplorerApiUrl, + getExplorerBaseUrl, + getExplorerTxUrl, +} from './blockExplorer.js'; + +const TEST_CHAINS = [test1, testCosmosChain, testSealevelChain]; +const EXPECTED_RESULTS = [ + [ + 'https://etherscan.io/', + 'https://api.etherscan.io/api?apikey=fakekey', + 'https://etherscan.io/tx/0x123', + 'https://etherscan.io/address/0x123', + ], + [ + 'https://www.mintscan.io/cosmos', + null, + 'https://www.mintscan.io/cosmos/tx/0x123', + 'https://www.mintscan.io/cosmos/address/0x123', + ], + [ + 'https://explorer.solana.com/?cluster=devnet', + null, + 'https://explorer.solana.com/tx/0x123?cluster=devnet', + 'https://explorer.solana.com/address/0x123?cluster=devnet', + ], +]; + +describe('Block explorer utils', () => { + TEST_CHAINS.map((chain, i) => { + it(`gets a base url correctly for protocol ${chain.protocol}`, () => { + expect(getExplorerBaseUrl(chain)).to.equal(EXPECTED_RESULTS[i][0]); + }); + it(`gets an api url for protocol ${chain.protocol}`, () => { + expect(getExplorerApiUrl(chain)).to.equal(EXPECTED_RESULTS[i][1]); + }); + it(`gets a tx url for protocol ${chain.protocol}`, () => { + expect(getExplorerTxUrl(chain, '0x123')).to.equal(EXPECTED_RESULTS[i][2]); + }); + it(`gets an address url for protocol ${chain.protocol}`, () => { + expect(getExplorerAddressUrl(chain, '0x123')).to.equal( + EXPECTED_RESULTS[i][3], + ); + }); + }); +}); diff --git a/typescript/sdk/src/metadata/blockExplorer.ts b/typescript/sdk/src/metadata/blockExplorer.ts index 00df97cd4c..2da0039373 100644 --- a/typescript/sdk/src/metadata/blockExplorer.ts +++ b/typescript/sdk/src/metadata/blockExplorer.ts @@ -1,19 +1,10 @@ import { ProtocolType } from '@hyperlane-xyz/utils'; -import { solanaChainToClusterName } from '../consts/chainMetadata.js'; - import { ChainMetadata, ExplorerFamily } from './chainMetadataTypes.js'; export function getExplorerBaseUrl(metadata: ChainMetadata): string | null { if (!metadata?.blockExplorers?.length) return null; const url = new URL(metadata.blockExplorers[0].url); - // TODO consider move handling of these chain/protocol specific quirks to ChainMetadata - if ( - metadata.protocol === ProtocolType.Sealevel && - solanaChainToClusterName[metadata.name] - ) { - url.searchParams.set('cluster', solanaChainToClusterName[metadata.name]); - } return url.toString(); } @@ -73,5 +64,7 @@ function appendToPath(baseUrl: string, pathExtension: string) { let currentPath = base.pathname; if (currentPath.endsWith('/')) currentPath = currentPath.slice(0, -1); const newPath = `${currentPath}/${pathExtension}`; - return new URL(newPath, base); + const newUrl = new URL(newPath, base); + newUrl.search = base.searchParams.toString(); + return newUrl; } diff --git a/typescript/sdk/src/metadata/chainMetadata.test.ts b/typescript/sdk/src/metadata/chainMetadata.test.ts index 2720d31293..773eb9c033 100644 --- a/typescript/sdk/src/metadata/chainMetadata.test.ts +++ b/typescript/sdk/src/metadata/chainMetadata.test.ts @@ -2,8 +2,6 @@ import { expect } from 'chai'; import { ProtocolType } from '@hyperlane-xyz/utils'; -import { chainMetadata } from '../consts/chainMetadata.js'; - import { ChainMetadata, isValidChainMetadata } from './chainMetadataTypes.js'; const minimalSchema: ChainMetadata = { @@ -111,13 +109,4 @@ describe('ChainMetadataSchema', () => { }), ).to.eq(false); }); - - it('Works for all SDK chain metadata consts', () => { - for (const chain of Object.keys(chainMetadata)) { - const isValid = isValidChainMetadata(chainMetadata[chain]); - // eslint-disable-next-line no-console - if (!isValid) console.error(`Invalid chain metadata for ${chain}`); - expect(isValid).to.eq(true); - } - }); }); diff --git a/typescript/sdk/src/metadata/health.ts b/typescript/sdk/src/metadata/health.ts deleted file mode 100644 index 0318cf7856..0000000000 --- a/typescript/sdk/src/metadata/health.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { Mailbox__factory } from '@hyperlane-xyz/core'; -import { - Address, - ProtocolType, - rootLogger, - timeout, -} from '@hyperlane-xyz/utils'; - -import { chainIdToMetadata } from '../consts/chainMetadata.js'; -import { CoreChainName } from '../consts/chains.js'; -import { hyperlaneContractAddresses } from '../consts/environments/index.js'; -import { - CosmJsProvider, - CosmJsWasmProvider, - EthersV5Provider, - ProviderType, - SolanaWeb3Provider, -} from '../providers/ProviderType.js'; -import { protocolToDefaultProviderBuilder } from '../providers/providerBuilders.js'; - -import { - getExplorerAddressUrl, - getExplorerBaseUrl, - getExplorerTxUrl, -} from './blockExplorer.js'; -import { ChainMetadata, RpcUrl } from './chainMetadataTypes.js'; - -const HEALTH_CHECK_TIMEOUT = 5000; // 5s - -const logger = rootLogger.child({ module: 'metadata-health' }); - -export async function isRpcHealthy( - rpc: RpcUrl, - chainId: string | number, - protocol: ProtocolType, -): Promise { - try { - const builder = protocolToDefaultProviderBuilder[protocol]; - const provider = builder([rpc], chainId); - let resultPromise; - if (provider.type === ProviderType.EthersV5) - resultPromise = isEthersV5ProviderHealthy(provider.provider, chainId); - else if (provider.type === ProviderType.SolanaWeb3) - resultPromise = isSolanaWeb3ProviderHealthy(provider.provider, chainId); - else if ( - provider.type === ProviderType.CosmJsWasm || - provider.type === ProviderType.CosmJs - ) - resultPromise = isCosmJsProviderHealthy(provider.provider, chainId); - else - throw new Error( - `Unsupported provider type ${provider.type}, new health check required`, - ); - const result = await timeout( - resultPromise, - HEALTH_CHECK_TIMEOUT, - 'RPC health check timed out', - ); - return result; - } catch (error) { - logger.error(`Provider error for ${rpc.http}`, error); - return false; - } -} - -export async function isEthersV5ProviderHealthy( - provider: EthersV5Provider['provider'], - chainId: string | number, -): Promise { - logger.debug(`Checking ethers provider for ${chainId}`); - const blockNumber = await provider.getBlockNumber(); - if (!blockNumber || blockNumber < 0) return false; - logger.debug(`Block number is okay for ${chainId}`); - - const chainName = chainIdToMetadata[chainId]?.name as CoreChainName; - if (chainName && hyperlaneContractAddresses[chainName]) { - const mailboxAddr = hyperlaneContractAddresses[chainName].mailbox; - const mailbox = Mailbox__factory.createInterface(); - const topics = mailbox.encodeFilterTopics( - mailbox.events['DispatchId(bytes32)'], - [], - ); - logger.debug(`Checking mailbox logs for ${chainId}`); - const mailboxLogs = await provider.getLogs({ - address: mailboxAddr, - topics, - fromBlock: blockNumber - 99, - toBlock: blockNumber, - }); - if (!mailboxLogs) return false; - logger.debug(`Mailbox logs okay for ${chainId}`); - } - return true; -} - -export async function isSolanaWeb3ProviderHealthy( - provider: SolanaWeb3Provider['provider'], - chainId: string | number, -): Promise { - logger.debug(`Checking solana provider for ${chainId}`); - const blockNumber = await provider.getBlockHeight(); - if (!blockNumber || blockNumber < 0) return false; - logger.debug(`Block number is okay for ${chainId}`); - return true; -} - -export async function isCosmJsProviderHealthy( - provider: CosmJsProvider['provider'] | CosmJsWasmProvider['provider'], - chainId: string | number, -): Promise { - const readyProvider = await provider; - const blockNumber = await readyProvider.getHeight(); - if (!blockNumber || blockNumber < 0) return false; - logger.debug(`Block number is okay for ${chainId}`); - return true; -} - -export async function isBlockExplorerHealthy( - chainMetadata: ChainMetadata, - address?: Address, - txHash?: string, -): Promise { - try { - const baseUrl = getExplorerBaseUrl(chainMetadata); - if (!baseUrl) return false; - logger.debug(`Got base url: ${baseUrl}`); - - logger.debug(`Checking explorer home for ${chainMetadata.name}`); - const homeReq = await fetch(baseUrl); - if (!homeReq.ok) return false; - logger.debug(`Explorer home okay for ${chainMetadata.name}`); - - if (address) { - logger.debug(`Checking explorer address page for ${chainMetadata.name}`); - const addressUrl = getExplorerAddressUrl(chainMetadata, address); - if (!addressUrl) return false; - logger.debug(`Got address url: ${addressUrl}`); - const addressReq = await fetch(addressUrl); - if (!addressReq.ok && addressReq.status !== 404) return false; - logger.debug(`Explorer address page okay for ${chainMetadata.name}`); - } - - if (txHash) { - logger.debug(`Checking explorer tx page for ${chainMetadata.name}`); - const txUrl = getExplorerTxUrl(chainMetadata, txHash); - if (!txUrl) return false; - logger.debug(`Got tx url: ${txUrl}`); - const txReq = await fetch(txUrl); - if (!txReq.ok && txReq.status !== 404) return false; - logger.debug(`Explorer tx page okay for ${chainMetadata.name}`); - } - - return true; - } catch (error) { - logger.error(`Explorer error for ${chainMetadata.name}`, error); - return false; - } -} diff --git a/typescript/sdk/src/middleware/account/InterchainAccount.ts b/typescript/sdk/src/middleware/account/InterchainAccount.ts index bc38c9c405..8d13e8f843 100644 --- a/typescript/sdk/src/middleware/account/InterchainAccount.ts +++ b/typescript/sdk/src/middleware/account/InterchainAccount.ts @@ -4,19 +4,11 @@ import { InterchainAccountRouter } from '@hyperlane-xyz/core'; import { Address, CallData, - ProtocolType, addressToBytes32, bytes32ToAddress, } from '@hyperlane-xyz/utils'; -import { - HyperlaneEnvironment, - hyperlaneEnvironments, -} from '../../consts/environments/index.js'; -import { - appFromAddressesMapHelper, - filterChainMapToProtocol, -} from '../../contracts/contracts.js'; +import { appFromAddressesMapHelper } from '../../contracts/contracts.js'; import { HyperlaneAddressesMap, HyperlaneContracts, @@ -46,23 +38,6 @@ export class InterchainAccount extends RouterApp { return contracts.interchainAccountRouter; } - static fromEnvironment( - env: Env, - multiProvider: MultiProvider, - ): InterchainAccount { - const envAddresses = hyperlaneEnvironments[env]; - if (!envAddresses) { - throw new Error(`No addresses found for ${env}`); - } - // Filter out non-EVM chains, as interchain accounts are EVM only at the moment. - const ethAddresses = filterChainMapToProtocol( - envAddresses, - ProtocolType.Ethereum, - multiProvider, - ); - return InterchainAccount.fromAddressesMap(ethAddresses, multiProvider); - } - static fromAddressesMap( addressesMap: HyperlaneAddressesMap, multiProvider: MultiProvider, diff --git a/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts b/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts index b476bb7b6b..dbf3dbc6b1 100644 --- a/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts +++ b/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts @@ -8,7 +8,7 @@ import { TestRecipient__factory, } from '@hyperlane-xyz/core'; -import { Chains } from '../../consts/chains.js'; +import { TestChainName } from '../../consts/testChains.js'; import { HyperlaneContractsMap } from '../../contracts/types.js'; import { TestCoreApp } from '../../core/TestCoreApp.js'; import { TestCoreDeployer } from '../../core/TestCoreDeployer.js'; @@ -25,8 +25,8 @@ import { InterchainAccountFactories } from './contracts.js'; import { AccountConfig } from './types.js'; describe('InterchainAccounts', async () => { - const localChain = Chains.test1; - const remoteChain = Chains.test2; + const localChain = TestChainName.test1; + const remoteChain = TestChainName.test2; let signer: SignerWithAddress; let contracts: HyperlaneContractsMap; diff --git a/typescript/sdk/src/middleware/liquidity-layer/liquidity-layer.hardhat-test.ts b/typescript/sdk/src/middleware/liquidity-layer/liquidity-layer.hardhat-test.ts index 5ba99e188c..51732aaff3 100644 --- a/typescript/sdk/src/middleware/liquidity-layer/liquidity-layer.hardhat-test.ts +++ b/typescript/sdk/src/middleware/liquidity-layer/liquidity-layer.hardhat-test.ts @@ -16,8 +16,7 @@ import { } from '@hyperlane-xyz/core'; import { addressToBytes32, objMap } from '@hyperlane-xyz/utils'; -import { chainMetadata } from '../../consts/chainMetadata.js'; -import { Chains } from '../../consts/chains.js'; +import { TestChainName, test1, test2 } from '../../consts/testChains.js'; import { TestCoreApp } from '../../core/TestCoreApp.js'; import { TestCoreDeployer } from '../../core/TestCoreDeployer.js'; import { HyperlaneProxyFactoryDeployer } from '../../deploy/HyperlaneProxyFactoryDeployer.js'; @@ -35,10 +34,10 @@ import { } from './LiquidityLayerRouterDeployer.js'; describe.skip('LiquidityLayerRouter', async () => { - const localChain = Chains.test1; - const remoteChain = Chains.test2; - const localDomain = chainMetadata[localChain].chainId; - const remoteDomain = chainMetadata[remoteChain].chainId; + const localChain = TestChainName.test1; + const remoteChain = TestChainName.test2; + const localDomain = test1.domainId!; + const remoteDomain = test2.domainId!; let signer: SignerWithAddress; let local: LiquidityLayerRouter; diff --git a/typescript/sdk/src/middleware/query/InterchainQuery.ts b/typescript/sdk/src/middleware/query/InterchainQuery.ts index 4cc51e5e05..24477794eb 100644 --- a/typescript/sdk/src/middleware/query/InterchainQuery.ts +++ b/typescript/sdk/src/middleware/query/InterchainQuery.ts @@ -1,9 +1,5 @@ import { InterchainQueryRouter } from '@hyperlane-xyz/core'; -import { - HyperlaneEnvironment, - hyperlaneEnvironments, -} from '../../consts/environments/index.js'; import { appFromAddressesMapHelper } from '../../contracts/contracts.js'; import { HyperlaneAddressesMap, @@ -24,18 +20,6 @@ export class InterchainQuery extends RouterApp { return contracts.interchainQueryRouter; } - static fromEnvironment( - env: Env, - multiProvider: MultiProvider, - ): InterchainQuery { - const envAddresses = hyperlaneEnvironments[env]; - if (!envAddresses) { - throw new Error(`No addresses found for ${env}`); - } - /// @ts-ignore - return InterchainQuery.fromAddressesMap(envAddresses, multiProvider); - } - static fromAddressesMap( addressesMap: HyperlaneAddressesMap, multiProvider: MultiProvider, diff --git a/typescript/sdk/src/middleware/query/queries.hardhat-test.ts b/typescript/sdk/src/middleware/query/queries.hardhat-test.ts index 6d38c47c47..00ab9b855a 100644 --- a/typescript/sdk/src/middleware/query/queries.hardhat-test.ts +++ b/typescript/sdk/src/middleware/query/queries.hardhat-test.ts @@ -9,8 +9,7 @@ import { } from '@hyperlane-xyz/core'; import { addressToBytes32 } from '@hyperlane-xyz/utils'; -import { chainMetadata } from '../../consts/chainMetadata.js'; -import { Chains } from '../../consts/chains.js'; +import { TestChainName, test1, test2 } from '../../consts/testChains.js'; import { HyperlaneContractsMap } from '../../contracts/types.js'; import { TestCoreApp } from '../../core/TestCoreApp.js'; import { TestCoreDeployer } from '../../core/TestCoreDeployer.js'; @@ -26,10 +25,10 @@ import { InterchainQueryDeployer } from './InterchainQueryDeployer.js'; import { InterchainQueryFactories } from './contracts.js'; describe.skip('InterchainQueryRouter', async () => { - const localChain = Chains.test1; - const remoteChain = Chains.test2; - const localDomain = chainMetadata[localChain].chainId; - const remoteDomain = chainMetadata[remoteChain].chainId; + const localChain = TestChainName.test1; + const remoteChain = TestChainName.test2; + const localDomain = test1.domainId!; + const remoteDomain = test2.domainId!; let contracts: HyperlaneContractsMap; let signer: SignerWithAddress; diff --git a/typescript/sdk/src/providers/MultiProtocolProvider.test.ts b/typescript/sdk/src/providers/MultiProtocolProvider.test.ts index b1ac8e5b56..30469e9549 100644 --- a/typescript/sdk/src/providers/MultiProtocolProvider.test.ts +++ b/typescript/sdk/src/providers/MultiProtocolProvider.test.ts @@ -1,26 +1,25 @@ import { expect } from 'chai'; -import { ethereum } from '../consts/chainMetadata.js'; -import { Chains } from '../consts/chains.js'; +import { TestChainName, test1 } from '../consts/testChains.js'; import { MultiProtocolProvider } from '../providers/MultiProtocolProvider.js'; describe('MultiProtocolProvider', () => { describe('constructs', () => { it('creates a multi protocol provider without type extension', async () => { - const multiProvider = new MultiProtocolProvider(); - const ethMetadata = multiProvider.getChainMetadata(Chains.ethereum); - expect(ethMetadata.name).to.equal(Chains.ethereum); + const multiProvider = new MultiProtocolProvider({ test1 }); + const metadata = multiProvider.getChainMetadata(TestChainName.test1); + expect(metadata.name).to.equal(TestChainName.test1); }); it('creates a multi protocol provider with type extension', async () => { const multiProvider = new MultiProtocolProvider<{ foo: string; bar: number; }>({ - [Chains.ethereum]: { ...ethereum, foo: '0x123', bar: 1 }, + test1: { ...test1, foo: '0x123', bar: 1 }, }); - const ethMetadata = multiProvider.getChainMetadata(Chains.ethereum); - expect(ethMetadata.foo).to.equal('0x123'); - expect(ethMetadata.bar).to.equal(1); + const metadata = multiProvider.getChainMetadata(TestChainName.test1); + expect(metadata.foo).to.equal('0x123'); + expect(metadata.bar).to.equal(1); }); }); }); diff --git a/typescript/sdk/src/providers/MultiProtocolProvider.ts b/typescript/sdk/src/providers/MultiProtocolProvider.ts index 3ed9dee61c..6148f76a00 100644 --- a/typescript/sdk/src/providers/MultiProtocolProvider.ts +++ b/typescript/sdk/src/providers/MultiProtocolProvider.ts @@ -3,13 +3,14 @@ import { Logger } from 'pino'; import { Address, HexString, + ProtocolType, objFilter, objMap, pick, rootLogger, } from '@hyperlane-xyz/utils'; -import { chainMetadata as defaultChainMetadata } from '../consts/chainMetadata.js'; +import { multiProtocolTestChainMetadata } from '../consts/testChains.js'; import { ChainMetadataManager } from '../metadata/ChainMetadataManager.js'; import type { ChainMetadata } from '../metadata/chainMetadataTypes.js'; import type { ChainMap, ChainName, ChainNameOrId } from '../types.js'; @@ -63,9 +64,7 @@ export class MultiProtocolProvider< public readonly logger: Logger; constructor( - chainMetadata: ChainMap< - ChainMetadata - > = defaultChainMetadata as ChainMap>, + chainMetadata: ChainMap>, protected readonly options: MultiProtocolProviderOptions = {}, ) { super(chainMetadata, options); @@ -258,4 +257,23 @@ export class MultiProtocolProvider< }); return { intersection, result: multiProvider }; } + + /** + * Creates a MultiProvider for test networks + */ + static createTestMultiProtocolProvider( + metadata = multiProtocolTestChainMetadata, + providers: Partial> = {}, + ): MultiProtocolProvider { + const mp = new MultiProtocolProvider(metadata); + const providerMap: ChainMap = {}; + for (const [protocol, provider] of Object.entries(providers)) { + const chains = Object.values(metadata).filter( + (m) => m.protocol === protocol, + ); + chains.forEach((c) => (providerMap[c.name] = provider)); + } + mp.setProviders(providerMap); + return mp as MultiProtocolProvider; + } } diff --git a/typescript/sdk/src/providers/MultiProvider.ts b/typescript/sdk/src/providers/MultiProvider.ts index 219a73f5c9..ecaec67f8b 100644 --- a/typescript/sdk/src/providers/MultiProvider.ts +++ b/typescript/sdk/src/providers/MultiProvider.ts @@ -11,8 +11,7 @@ import { Logger } from 'pino'; import { Address, pick, rootLogger } from '@hyperlane-xyz/utils'; -import { chainMetadata as defaultChainMetadata } from '../consts/chainMetadata.js'; -import { CoreChainName, TestChains } from '../consts/chains.js'; +import { testChainMetadata, testChains } from '../consts/testChains.js'; import { ChainMetadataManager } from '../metadata/ChainMetadataManager.js'; import { ChainMetadata } from '../metadata/chainMetadataTypes.js'; import { ChainMap, ChainName, ChainNameOrId } from '../types.js'; @@ -47,7 +46,7 @@ export class MultiProvider extends ChainMetadataManager { * or the SDK's default metadata if not provided */ constructor( - chainMetadata?: ChainMap>, + chainMetadata: ChainMap>, readonly options: MultiProviderOptions = {}, ) { super(chainMetadata, options); @@ -88,7 +87,7 @@ export class MultiProvider extends ChainMetadataManager { if (this.providers[name]) return this.providers[name]; - if (TestChains.includes(name as CoreChainName)) { + if (testChains.includes(name)) { this.providers[name] = new providers.JsonRpcProvider( 'http://127.0.0.1:8545', 31337, @@ -399,11 +398,10 @@ export class MultiProvider extends ChainMetadataManager { */ static createTestMultiProvider( params: { signer?: Signer; provider?: Provider } = {}, - chains: ChainName[] = TestChains, + chains: ChainName[] = testChains, ): MultiProvider { const { signer, provider } = params; - const chainMetadata = pick(defaultChainMetadata, chains); - const mp = new MultiProvider(chainMetadata); + const mp = new MultiProvider(testChainMetadata); if (signer) { mp.setSharedSigner(signer); } diff --git a/typescript/sdk/src/providers/SmartProvider/SmartProvider.test.ts b/typescript/sdk/src/providers/SmartProvider/SmartProvider.test.ts deleted file mode 100644 index 957eab65cb..0000000000 --- a/typescript/sdk/src/providers/SmartProvider/SmartProvider.test.ts +++ /dev/null @@ -1,197 +0,0 @@ -/* eslint-disable no-console */ -import { expect } from 'chai'; -import { ethers } from 'ethers'; - -import { eqAddress } from '@hyperlane-xyz/utils'; - -import { chainMetadata } from '../../consts/chainMetadata.js'; -import { ChainMetadata } from '../../metadata/chainMetadataTypes.js'; - -import { ProviderMethod } from './ProviderMethods.js'; -import { HyperlaneSmartProvider } from './SmartProvider.js'; - -const DEFAULT_ACCOUNT = '0xfaD1C94469700833717Fa8a3017278BC1cA8031C'; -const WETH_CONTRACT = '0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9'; -const WETH_TRANSFER_TOPIC0 = - '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'; -const WETH_CALL_DATA = - '0x70a082310000000000000000000000004f7a67464b5976d7547c860109e4432d50afb38e'; -const TRANSFER_TX_HASH = - '0x7a975792c023733b3013ada23e1f556f5a06443765ec576e56d0b0aa3c4bdc74'; - -const pagination = { maxBlockRange: 1000 }; -const sepoliaRpcConfig1 = { ...chainMetadata.sepolia.rpcUrls[0], pagination }; -const sepoliaRpcConfig2 = { ...chainMetadata.sepolia.rpcUrls[1], pagination }; -const justExplorersConfig: ChainMetadata = { - ...chainMetadata.sepolia, - rpcUrls: [] as any, -}; -const justRpcsConfig: ChainMetadata = { - ...chainMetadata.sepolia, - rpcUrls: [sepoliaRpcConfig1, sepoliaRpcConfig2], - blockExplorers: [], -}; -const combinedConfig: ChainMetadata = { - ...chainMetadata.sepolia, - rpcUrls: [sepoliaRpcConfig1], -}; -const configs: [string, ChainMetadata][] = [ - ['Just Explorers', justExplorersConfig], - ['Just RPCs', justRpcsConfig], - ['Combined configs', combinedConfig], -]; - -describe.skip('SmartProvider', () => { - let provider: HyperlaneSmartProvider; - - const itDoesIfSupported = (method: ProviderMethod, fn: () => any) => { - it(method, () => { - if (provider.supportedMethods.includes(method)) { - return fn(); - } - }).timeout(30_000); - }; - - for (const [description, config] of configs) { - describe(description, () => { - provider = HyperlaneSmartProvider.fromChainMetadata(config, { - debug: true, - baseRetryDelayMs: 1000, - fallbackStaggerMs: 3000, - maxRetries: 3, - }); - - itDoesIfSupported(ProviderMethod.GetBlock, async () => { - const latestBlock = await provider.getBlock('latest'); - console.debug('Latest block #', latestBlock.number); - expect(latestBlock.number).to.be.greaterThan(0); - expect(latestBlock.timestamp).to.be.greaterThan( - Date.now() / 1000 - 60 * 60 * 24, - ); - const firstBlock = await provider.getBlock(1); - expect(firstBlock.number).to.equal(1); - }); - - itDoesIfSupported(ProviderMethod.GetBlockNumber, async () => { - const result = await provider.getBlockNumber(); - console.debug('Latest block #', result); - expect(result).to.be.greaterThan(0); - }); - - itDoesIfSupported(ProviderMethod.GetGasPrice, async () => { - const result = await provider.getGasPrice(); - console.debug('Gas price', result.toString()); - expect(result.toNumber()).to.be.greaterThan(0); - }); - - itDoesIfSupported(ProviderMethod.GetBalance, async () => { - const result = await provider.getBalance(DEFAULT_ACCOUNT); - console.debug('Balance', result.toString()); - expect(parseFloat(ethers.utils.formatEther(result))).to.be.greaterThan( - 1, - ); - }); - - itDoesIfSupported(ProviderMethod.GetCode, async () => { - const result = await provider.getCode(WETH_CONTRACT); - console.debug('Weth code snippet', result.substring(0, 12)); - expect(result.length).to.be.greaterThan(100); - }); - - itDoesIfSupported(ProviderMethod.GetStorageAt, async () => { - const result = await provider.getStorageAt(WETH_CONTRACT, 0); - console.debug('Weth storage', result); - expect(result.length).to.be.greaterThan(20); - }); - - itDoesIfSupported(ProviderMethod.GetTransactionCount, async () => { - const result = await provider.getTransactionCount( - DEFAULT_ACCOUNT, - 'latest', - ); - console.debug('Tx Count', result); - expect(result).to.be.greaterThan(40); - }); - - itDoesIfSupported(ProviderMethod.GetTransaction, async () => { - const result = await provider.getTransaction(TRANSFER_TX_HASH); - console.debug('Transaction confirmations', result.confirmations); - expect(result.confirmations).to.be.greaterThan(1000); - }); - - itDoesIfSupported(ProviderMethod.GetTransactionReceipt, async () => { - const result = await provider.getTransactionReceipt(TRANSFER_TX_HASH); - console.debug('Transaction receipt', result.confirmations); - expect(result.confirmations).to.be.greaterThan(1000); - }); - - itDoesIfSupported(ProviderMethod.GetLogs, async () => { - const latestBlockNumber = await provider.getBlockNumber(); - const minBlockNumber = latestBlockNumber - 10_000; - - console.debug('Testing logs with small from/to range'); - const result1 = await provider.getLogs({ - address: WETH_CONTRACT, - topics: [WETH_TRANSFER_TOPIC0], - fromBlock: minBlockNumber, - toBlock: minBlockNumber + 100, - }); - expect(result1.length).to.be.greaterThan(0); - expect(eqAddress(result1[0].address, WETH_CONTRACT)).to.be.true; - - console.debug('Testing logs with large from/to range'); - const result2 = await provider.getLogs({ - address: WETH_CONTRACT, - topics: [WETH_TRANSFER_TOPIC0], - fromBlock: minBlockNumber, - toBlock: 'latest', - }); - expect(result2.length).to.be.greaterThan(0); - expect(eqAddress(result2[0].address, WETH_CONTRACT)).to.be.true; - }); - - itDoesIfSupported(ProviderMethod.EstimateGas, async () => { - const result = await provider.estimateGas({ - to: DEFAULT_ACCOUNT, - from: DEFAULT_ACCOUNT, - value: 1, - }); - expect(result.toNumber()).to.be.greaterThan(10_000); - }); - - itDoesIfSupported(ProviderMethod.Call, async () => { - const result = await provider.call({ - to: WETH_CONTRACT, - from: DEFAULT_ACCOUNT, - data: WETH_CALL_DATA, - }); - expect(result).to.equal( - '0x0000000000000000000000000000000000000000000000000000000000000000', - ); - }); - - it('Handles parallel requests', async () => { - const result1Promise = provider.call({ - to: WETH_CONTRACT, - from: DEFAULT_ACCOUNT, - data: WETH_CALL_DATA, - }); - const result2Promise = provider.getBlockNumber(); - const result3Promise = provider.getTransaction(TRANSFER_TX_HASH); - const [result1, result2, result3] = await Promise.all([ - result1Promise, - result2Promise, - result3Promise, - ]); - expect(result1.length).to.be.greaterThan(0); - expect(result2).to.be.greaterThan(0); - expect(!!result3).to.be.true; - }).timeout(15_000); - }); - - it('Reports as healthy', async () => { - const result = await provider.isHealthy(); - expect(result).to.be.true; - }); - } -}); diff --git a/typescript/sdk/src/router/MultiProtocolRouterApps.test.ts b/typescript/sdk/src/router/MultiProtocolRouterApps.test.ts index 9449871e8c..a5940dd9f8 100644 --- a/typescript/sdk/src/router/MultiProtocolRouterApps.test.ts +++ b/typescript/sdk/src/router/MultiProtocolRouterApps.test.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { ethers } from 'ethers'; -import { Chains } from '../consts/chains.js'; +import { TestChainName } from '../consts/testChains.js'; import { MultiProtocolProvider } from '../providers/MultiProtocolProvider.js'; import { MultiProtocolRouterApp } from './MultiProtocolRouterApps.js'; @@ -10,17 +10,18 @@ import { RouterAddress } from './types.js'; describe('MultiProtocolRouterApp', () => { describe('constructs', () => { - const multiProvider = new MultiProtocolProvider(); + const multiProvider = + MultiProtocolProvider.createTestMultiProtocolProvider(); it('creates an app class', async () => { const addresses = { - ethereum: { router: ethers.constants.AddressZero }, + test1: { router: ethers.constants.AddressZero }, }; const app = new MultiProtocolRouterApp( multiProvider.intersect(Object.keys(addresses)).result, addresses, ); expect(app).to.be.instanceOf(MultiProtocolRouterApp); - const ethAdapter = app.adapter(Chains.ethereum); + const ethAdapter = app.adapter(TestChainName.test1); expect(ethAdapter).to.be.instanceOf(EvmRouterAdapter); expect(!!ethAdapter.remoteRouter).to.be.true; }); diff --git a/typescript/sdk/src/test/MockCoinGecko.ts b/typescript/sdk/src/test/MockCoinGecko.ts new file mode 100644 index 0000000000..f2b1934308 --- /dev/null +++ b/typescript/sdk/src/test/MockCoinGecko.ts @@ -0,0 +1,50 @@ +import type { + CoinGeckoInterface, + CoinGeckoResponse, + CoinGeckoSimpleInterface, + CoinGeckoSimplePriceParams, +} from '../gas/token-prices.js'; +import type { ChainName } from '../types.js'; + +// A mock CoinGecko intended to be used by tests +export class MockCoinGecko implements CoinGeckoInterface { + // Prices keyed by coingecko id + private tokenPrices: Record; + // Whether or not to fail to return a response, keyed by coingecko id + private fail: Record; + + constructor() { + this.tokenPrices = {}; + this.fail = {}; + } + + price(params: CoinGeckoSimplePriceParams): CoinGeckoResponse { + const data: any = {}; + for (const id of params.ids) { + if (this.fail[id]) { + return Promise.reject(`Failed to fetch price for ${id}`); + } + data[id] = { + usd: this.tokenPrices[id], + }; + } + return Promise.resolve({ + success: true, + message: '', + code: 200, + data, + }); + } + + get simple(): CoinGeckoSimpleInterface { + return this; + } + + setTokenPrice(chain: ChainName, price: number): void { + this.tokenPrices[chain] = price; + } + + setFail(chain: ChainName, fail: boolean): void { + this.fail[chain] = fail; + } +} diff --git a/typescript/sdk/src/test/metadata-check.ts b/typescript/sdk/src/test/metadata-check.ts deleted file mode 100644 index 97d704a7dc..0000000000 --- a/typescript/sdk/src/test/metadata-check.ts +++ /dev/null @@ -1,107 +0,0 @@ -/* eslint-disable no-console */ -import { ethers } from 'ethers'; - -import { Address, ProtocolType } from '@hyperlane-xyz/utils'; - -import { chainMetadata } from '../consts/chainMetadata.js'; -import { CoreChainName, TestChains } from '../consts/chains.js'; -import { isBlockExplorerHealthy, isRpcHealthy } from '../metadata/health.js'; -import { ChainMap } from '../types.js'; - -const PROTOCOL_TO_ADDRESS: Record = { - [ProtocolType.Ethereum]: ethers.constants.AddressZero, - [ProtocolType.Sealevel]: '11111111111111111111111111111111', - [ProtocolType.Cosmos]: 'cosmos100000000000000000000000000000000000000', -}; - -const PROTOCOL_TO_TX_HASH: Record = { - [ProtocolType.Ethereum]: ethers.constants.HashZero, - [ProtocolType.Sealevel]: - '1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111', - [ProtocolType.Cosmos]: - '0000000000000000000000000000000000000000000000000000000000000000', -}; - -async function main() { - const results: ChainMap<{ - goodRpcs: number; - badRpcs: number; - goodExplorers: number; - badExplorers: number; - }> = {}; - const badList: string[] = []; - - for (const metadata of Object.values(chainMetadata)) { - if (TestChains.includes(metadata.name as CoreChainName)) continue; - - console.log(`Checking metadata health for ${metadata.name}`); - if (!metadata.rpcUrls) { - console.error(`No rpcUrls for ${metadata.name}, invalid chain metadata`); - } - if (!metadata.blockExplorers?.length) { - console.warn( - `No block explorers for ${metadata.name}, consider adding one`, - ); - } - - results[metadata.name] = { - goodRpcs: 0, - badRpcs: 0, - goodExplorers: 0, - badExplorers: 0, - }; - - for (const rpc of metadata.rpcUrls) { - const isHealthy = await isRpcHealthy( - rpc, - metadata.chainId, - metadata.protocol, - ); - if (!isHealthy) { - console.error(`RPC ${rpc.http} for ${metadata.name} is not healthy`); - results[metadata.name].badRpcs += 1; - badList.push(rpc.http); - } else { - results[metadata.name].goodRpcs += 1; - } - } - - if (!metadata.blockExplorers?.length) continue; - // This only tests the first explorer since that's - // what the related utils use anyway - const firstExplorerUrl = metadata.blockExplorers[0].url; - const isHealthy = await isBlockExplorerHealthy( - metadata, - PROTOCOL_TO_ADDRESS[metadata.protocol], - PROTOCOL_TO_TX_HASH[metadata.protocol], - ); - if (!isHealthy) { - console.error( - `Explorer ${firstExplorerUrl} for ${metadata.name} is not healthy`, - ); - results[metadata.name].badExplorers += 1; - badList.push(firstExplorerUrl); - } else { - results[metadata.name].goodExplorers += 1; - } - } - - console.table(results); - console.log('The bad ones:\n==============='); - console.log(badList); - - if (badList.length) { - console.error('Some RPCs or block explorers are unhealthy'); - process.exit(1); - } -} - -main() - .then(() => { - console.log('Done'); - process.exit(0); - }) - .catch((err) => { - console.error('Unhandled error running test:', err); - process.exit(1); - }); diff --git a/typescript/sdk/src/test/testUtils.ts b/typescript/sdk/src/test/testUtils.ts index 22ce7eacab..3bc19c0e67 100644 --- a/typescript/sdk/src/test/testUtils.ts +++ b/typescript/sdk/src/test/testUtils.ts @@ -1,21 +1,16 @@ import { BigNumber, ethers } from 'ethers'; +import sinon from 'sinon'; import { Address, exclude, objMap } from '@hyperlane-xyz/utils'; -import { chainMetadata } from '../consts/chainMetadata.js'; import { HyperlaneContractsMap } from '../contracts/types.js'; import { CoreFactories } from '../core/contracts.js'; import { CoreConfig } from '../core/types.js'; import { IgpFactories } from '../gas/contracts.js'; -import { - CoinGeckoInterface, - CoinGeckoResponse, - CoinGeckoSimpleInterface, - CoinGeckoSimplePriceParams, -} from '../gas/token-prices.js'; import { IgpConfig } from '../gas/types.js'; import { HookType } from '../hook/types.js'; import { IsmType } from '../ism/types.js'; +import { MultiProtocolProvider } from '../providers/MultiProtocolProvider.js'; import { RouterConfig } from '../router/types.js'; import { ChainMap, ChainName } from '../types.js'; @@ -97,47 +92,30 @@ export function testIgpConfig( ); } -// A mock CoinGecko intended to be used by tests -export class MockCoinGecko implements CoinGeckoInterface { - // Prices keyed by coingecko id - private tokenPrices: Record; - // Whether or not to fail to return a response, keyed by coingecko id - private fail: Record; - - constructor() { - this.tokenPrices = {}; - this.fail = {}; - } - - price(params: CoinGeckoSimplePriceParams): CoinGeckoResponse { - const data: any = {}; - for (const id of params.ids) { - if (this.fail[id]) { - return Promise.reject(`Failed to fetch price for ${id}`); - } - data[id] = { - usd: this.tokenPrices[id], - }; - } - return Promise.resolve({ - success: true, - message: '', - code: 200, - data, - }); - } - - get simple(): CoinGeckoSimpleInterface { - return this; - } - - setTokenPrice(chain: ChainName, price: number): void { - const id = chainMetadata[chain].gasCurrencyCoinGeckoId || chain; - this.tokenPrices[id] = price; - } - - setFail(chain: ChainName, fail: boolean): void { - const id = chainMetadata[chain].gasCurrencyCoinGeckoId || chain; - this.fail[id] = fail; - } +/** + * Takes a MultiProtocolProvider instance and stubs it's get*Provider methods to + * return mock providers. More provider methods can be added her as needed. + * Note: callers should call `sandbox.restore()` after tests complete. + */ +export function stubMultiProtocolProvider( + multiProvider: MultiProtocolProvider, +): sinon.SinonSandbox { + const sandbox = sinon.createSandbox(); + sandbox.stub(multiProvider, 'getEthersV5Provider').returns({ + getBalance: async () => '100', + } as any); + sandbox.stub(multiProvider, 'getCosmJsProvider').returns({ + getBalance: async () => ({ amount: '100' }), + } as any); + sandbox.stub(multiProvider, 'getCosmJsWasmProvider').returns({ + getBalance: async () => ({ amount: '100' }), + queryContractSmart: async () => ({ + type: { native: { fungible: { denom: 'denom' } } }, + }), + } as any); + sandbox.stub(multiProvider, 'getSolanaWeb3Provider').returns({ + getBalance: async () => '100', + getTokenAccountBalance: async () => ({ value: { amount: '100' } }), + } as any); + return sandbox; } diff --git a/typescript/sdk/src/token/Token.test.ts b/typescript/sdk/src/token/Token.test.ts index a23e2f424b..c7e39786ce 100644 --- a/typescript/sdk/src/token/Token.test.ts +++ b/typescript/sdk/src/token/Token.test.ts @@ -4,9 +4,14 @@ import { ethers } from 'ethers'; import { Address, ProtocolType } from '@hyperlane-xyz/utils'; -import { chainMetadata } from '../consts/chainMetadata.js'; -import { Chains } from '../consts/chains.js'; +import { + TestChainName, + test1, + testCosmosChain, + testSealevelChain, +} from '../consts/testChains.js'; import { MultiProtocolProvider } from '../providers/MultiProtocolProvider.js'; +import { stubMultiProtocolProvider } from '../test/testUtils.js'; import { TokenArgs } from './IToken.js'; import { Token } from './Token.js'; @@ -16,7 +21,7 @@ import { TokenStandard } from './TokenStandard.js'; const STANDARD_TO_TOKEN: Record = { // EVM [TokenStandard.ERC20]: { - chainName: Chains.ethereum, + chainName: TestChainName.test1, standard: TokenStandard.ERC20, addressOrDenom: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', decimals: 6, @@ -24,11 +29,9 @@ const STANDARD_TO_TOKEN: Record = { name: 'USDC', }, [TokenStandard.ERC721]: null, - [TokenStandard.EvmNative]: Token.FromChainMetadataNativeToken( - chainMetadata.optimism, - ), + [TokenStandard.EvmNative]: Token.FromChainMetadataNativeToken(test1), [TokenStandard.EvmHypNative]: { - chainName: Chains.inevm, + chainName: TestChainName.test2, standard: TokenStandard.EvmHypNative, addressOrDenom: '0x26f32245fCF5Ad53159E875d5Cae62aEcf19c2d4', decimals: 18, @@ -36,7 +39,7 @@ const STANDARD_TO_TOKEN: Record = { name: 'Injective Coin', }, [TokenStandard.EvmHypCollateral]: { - chainName: Chains.bsctestnet, + chainName: TestChainName.test3, standard: TokenStandard.EvmHypCollateral, addressOrDenom: '0x31b5234A896FbC4b3e2F7237592D054716762131', collateralAddressOrDenom: '0x64544969ed7ebf5f083679233325356ebe738930', @@ -44,8 +47,8 @@ const STANDARD_TO_TOKEN: Record = { symbol: 'USDC', name: 'USDC', }, - [TokenStandard.EvmHypcollateralVault]: { - chainName: Chains.bsctestnet, + [TokenStandard.EvmHypCollateralVault]: { + chainName: TestChainName.test3, standard: TokenStandard.EvmHypCollateral, addressOrDenom: '0x31b5234A896FbC4b3e2F7237592D054716762131', collateralAddressOrDenom: '0x64544969ed7ebf5f083679233325356ebe738930', @@ -54,7 +57,7 @@ const STANDARD_TO_TOKEN: Record = { name: 'USDC', }, [TokenStandard.EvmHypSynthetic]: { - chainName: Chains.inevm, + chainName: TestChainName.test2, standard: TokenStandard.EvmHypSynthetic, addressOrDenom: '0x8358D8291e3bEDb04804975eEa0fe9fe0fAfB147', decimals: 6, @@ -64,7 +67,7 @@ const STANDARD_TO_TOKEN: Record = { // Sealevel [TokenStandard.SealevelSpl]: { - chainName: Chains.solana, + chainName: testSealevelChain.name, standard: TokenStandard.SealevelSpl, addressOrDenom: 'So11111111111111111111111111111111111111112', decimals: 9, @@ -72,16 +75,15 @@ const STANDARD_TO_TOKEN: Record = { name: 'SOL', }, [TokenStandard.SealevelSpl2022]: { - chainName: Chains.solana, + chainName: testSealevelChain.name, standard: TokenStandard.SealevelSpl2022, addressOrDenom: '21zHSATJqhNkcpoNkhFzPJW9LARSmoinLEeDtdygGuWh', decimals: 6, symbol: 'SOLMAX', name: 'Solana Maxi', }, - [TokenStandard.SealevelNative]: Token.FromChainMetadataNativeToken( - chainMetadata.solana, - ), + [TokenStandard.SealevelNative]: + Token.FromChainMetadataNativeToken(testSealevelChain), [TokenStandard.SealevelHypNative]: null, [TokenStandard.SealevelHypCollateral]: null, [TokenStandard.SealevelHypSynthetic]: null, @@ -89,11 +91,10 @@ const STANDARD_TO_TOKEN: Record = { // Cosmos [TokenStandard.CosmosIcs20]: null, [TokenStandard.CosmosIcs721]: null, - [TokenStandard.CosmosNative]: Token.FromChainMetadataNativeToken( - chainMetadata.neutron, - ), + [TokenStandard.CosmosNative]: + Token.FromChainMetadataNativeToken(testCosmosChain), [TokenStandard.CosmosIbc]: { - chainName: Chains.neutron, + chainName: testCosmosChain.name, standard: TokenStandard.CosmosIbc, addressOrDenom: 'ibc/773B4D0A3CD667B2275D5A4A7A2F0909C0BA0F4059C0B9181E680DDF4965DCC7', @@ -103,7 +104,7 @@ const STANDARD_TO_TOKEN: Record = { }, [TokenStandard.CW20]: null, [TokenStandard.CWNative]: { - chainName: Chains.neutron, + chainName: testCosmosChain.name, standard: TokenStandard.CWNative, addressOrDenom: 'ibc/5751B8BCDA688FD0A8EC0B292EEF1CDEAB4B766B63EC632778B196D317C40C3A', @@ -113,7 +114,7 @@ const STANDARD_TO_TOKEN: Record = { }, [TokenStandard.CW721]: null, [TokenStandard.CwHypNative]: { - chainName: Chains.injective, + chainName: testCosmosChain.name, standard: TokenStandard.CwHypNative, addressOrDenom: 'inj1mv9tjvkaw7x8w8y9vds8pkfq46g2vcfkjehc6k', igpTokenAddressOrDenom: 'inj', @@ -122,7 +123,7 @@ const STANDARD_TO_TOKEN: Record = { name: 'Injective Coin', }, [TokenStandard.CwHypCollateral]: { - chainName: Chains.neutron, + chainName: testCosmosChain.name, standard: TokenStandard.CwHypCollateral, addressOrDenom: 'neutron1jyyjd3x0jhgswgm6nnctxvzla8ypx50tew3ayxxwkrjfxhvje6kqzvzudq', @@ -151,10 +152,12 @@ const STANDARD_TO_ADDRESS: Partial> = { }; describe('Token', () => { - it('Handles all standards', async () => { - const multiProvider = new MultiProtocolProvider(); - for (const tokenArgs of Object.values(STANDARD_TO_TOKEN)) { - if (!tokenArgs) continue; + for (const tokenArgs of Object.values(STANDARD_TO_TOKEN)) { + if (!tokenArgs) continue; + it(`Handles ${tokenArgs.standard} standard`, async () => { + const multiProvider = + MultiProtocolProvider.createTestMultiProtocolProvider(); + console.debug('Testing token standard', tokenArgs.standard); const token = new Token(tokenArgs); expect(token.standard).to.eql(tokenArgs.standard); @@ -164,18 +167,16 @@ describe('Token', () => { PROTOCOL_TO_ADDRESS[token.protocol]; if (!address) throw new Error(`No address for standard ${tokenArgs.standard}`); + + const sandbox = stubMultiProtocolProvider(multiProvider); + // @ts-ignore simple extra mock for the Ethers V5 token contract call + adapter.contract = { + balanceOf: async () => '100', + }; + const balance = await adapter.getBalance(address); expect(typeof balance).to.eql('bigint'); - } - }) - .timeout(120_000) - .retries(3); - - it('Constructs from ChainMetadata', () => { - for (const metadata of Object.values(chainMetadata)) { - if (!metadata.nativeToken) continue; - const token = Token.FromChainMetadataNativeToken(metadata); - expect(token.symbol).to.eql(metadata.nativeToken.symbol); - } - }); + sandbox.restore(); + }); + } }); diff --git a/typescript/sdk/src/token/TokenAmount.test.ts b/typescript/sdk/src/token/TokenAmount.test.ts index 56f15a8d87..4c989e881f 100644 --- a/typescript/sdk/src/token/TokenAmount.test.ts +++ b/typescript/sdk/src/token/TokenAmount.test.ts @@ -1,24 +1,21 @@ import { expect } from 'chai'; import { ethers } from 'ethers'; -import { chainMetadata } from '../consts/chainMetadata.js'; -import { Chains } from '../consts/chains.js'; +import { TestChainName, test2 } from '../consts/testChains.js'; import { Token } from './Token.js'; import { TokenAmount } from './TokenAmount.js'; import { TokenStandard } from './TokenStandard.js'; const token1 = new Token({ - chainName: Chains.ethereum, + chainName: TestChainName.test1, standard: TokenStandard.ERC20, addressOrDenom: ethers.constants.AddressZero, decimals: 4, symbol: 'FAKE', name: 'Fake Token', }); -const token2 = Token.FromChainMetadataNativeToken( - chainMetadata[Chains.neutron], -); +const token2 = Token.FromChainMetadataNativeToken(test2); describe('TokenAmount', () => { let tokenAmount1: TokenAmount; @@ -33,7 +30,7 @@ describe('TokenAmount', () => { it('Formats human readable string', () => { expect(tokenAmount1.getDecimalFormattedAmount()).to.eq(12345.6789); - expect(tokenAmount2.getDecimalFormattedAmount()).to.eq(0.000001); + expect(tokenAmount2.getDecimalFormattedAmount()).to.eq(1e-18); }); it('Does arithmetic', () => { diff --git a/typescript/sdk/src/token/TokenStandard.ts b/typescript/sdk/src/token/TokenStandard.ts index 90fcbbb5e1..f2958304e7 100644 --- a/typescript/sdk/src/token/TokenStandard.ts +++ b/typescript/sdk/src/token/TokenStandard.ts @@ -14,7 +14,7 @@ export enum TokenStandard { EvmNative = 'EvmNative', EvmHypNative = 'EvmHypNative', EvmHypCollateral = 'EvmHypCollateral', - EvmHypcollateralVault = 'EvmHypcollateralVault', + EvmHypCollateralVault = 'EvmHypCollateralVault', EvmHypSynthetic = 'EvmHypSynthetic', // Sealevel (Solana) @@ -48,7 +48,7 @@ export const TOKEN_STANDARD_TO_PROTOCOL: Record = { EvmNative: ProtocolType.Ethereum, EvmHypNative: ProtocolType.Ethereum, EvmHypCollateral: ProtocolType.Ethereum, - EvmHypcollateralVault: ProtocolType.Ethereum, + EvmHypCollateralVault: ProtocolType.Ethereum, EvmHypSynthetic: ProtocolType.Ethereum, // Sealevel (Solana) @@ -129,7 +129,7 @@ export const TOKEN_COSMWASM_STANDARDS = [ export const TOKEN_TYPE_TO_STANDARD: Record = { [TokenType.native]: TokenStandard.EvmHypNative, [TokenType.collateral]: TokenStandard.EvmHypCollateral, - [TokenType.collateralVault]: TokenStandard.EvmHypcollateralVault, + [TokenType.collateralVault]: TokenStandard.EvmHypCollateralVault, [TokenType.collateralUri]: TokenStandard.EvmHypCollateral, [TokenType.fastCollateral]: TokenStandard.EvmHypCollateral, [TokenType.synthetic]: TokenStandard.EvmHypSynthetic, diff --git a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts index 7c8ce2ac4d..1205d123bb 100644 --- a/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts +++ b/typescript/sdk/src/token/adapters/CosmWasmTokenAdapter.test.ts @@ -13,8 +13,7 @@ import { Tendermint37Client } from '@cosmjs/tendermint-rpc'; import { Address } from '@hyperlane-xyz/utils'; -import { chainMetadata } from '../../consts/chainMetadata.js'; -import { Chains } from '../../consts/chains.js'; +import { TestChainName, testCosmosChain } from '../../consts/testChains.js'; import { CosmWasmCoreAdapter } from '../../core/adapters/CosmWasmCoreAdapter.js'; import { MailboxResponse, @@ -30,13 +29,11 @@ const neutronAddresses = { 'neutron17w4q6efzym3p4c6umyp4cjf2ustjtmwfqdhd7rt2fpcpk9fmjzsq0kj0f8', }; -const neutron = chainMetadata.neutron; -const mantapacific = chainMetadata.mantapacific; - -const multiProtocolProvider = new MultiProtocolProvider(); +const multiProtocolProvider = + MultiProtocolProvider.createTestMultiProtocolProvider(); const adapter = new CosmWasmCoreAdapter( - Chains.neutron, + testCosmosChain.name, multiProtocolProvider, neutronAddresses, ); @@ -44,12 +41,14 @@ const adapter = new CosmWasmCoreAdapter( export async function getSigningClient(pkey: string) { const wallet = await DirectSecp256k1Wallet.fromKey( Buffer.from(pkey, 'hex'), - neutron.bech32Prefix!, + testCosmosChain.bech32Prefix!, ); const [account] = await wallet.getAccounts(); - const clientBase = await Tendermint37Client.connect(neutron.rpcUrls[0].http); + const clientBase = await Tendermint37Client.connect( + testCosmosChain.rpcUrls[0].http, + ); const gasPrice = GasPrice.fromString('0.1untrn'); @@ -263,11 +262,11 @@ export async function summary() { if (defaultIsmContract.label === 'hpl_ism_multisig') { const multisigAdapter = new CosmWasmMultisigAdapter( - neutron.name, + testCosmosChain.name, multiProtocolProvider, { multisig: defaultIsm }, ); - const multisigConfig = await multisigAdapter.getConfig(mantapacific.name); + const multisigConfig = await multisigAdapter.getConfig(TestChainName.test1); const owner = await getOwner(defaultIsm); summary.defaultIsm = { ...multisigConfig, @@ -362,7 +361,7 @@ export async function summary() { export async function rotateValidators() { const multisigAdapter = new CosmWasmMultisigAdapter( - neutron.name, + testCosmosChain.name, multiProtocolProvider, { multisig: @@ -370,7 +369,7 @@ export async function rotateValidators() { }, ); const instructions = await multisigAdapter.configureMultisig({ - [mantapacific.name]: { + [TestChainName.test1]: { threshold: 5, validators: [ '8e668c97ad76d0e28375275c41ece4972ab8a5bc', // hyperlane @@ -395,4 +394,4 @@ export async function rotateValidators() { // console.log(tx); } -summary().catch(console.error); +// summary().catch(console.error); diff --git a/typescript/sdk/src/token/deploy.hardhat-test.ts b/typescript/sdk/src/token/deploy.hardhat-test.ts index 5051b505a5..72a3028481 100644 --- a/typescript/sdk/src/token/deploy.hardhat-test.ts +++ b/typescript/sdk/src/token/deploy.hardhat-test.ts @@ -7,7 +7,7 @@ import { ERC20Test__factory, Mailbox__factory, } from '@hyperlane-xyz/core'; -import { Chains, RouterConfig } from '@hyperlane-xyz/sdk'; +import { RouterConfig, TestChainName } from '@hyperlane-xyz/sdk'; import { objMap } from '@hyperlane-xyz/utils'; import { TestCoreApp } from '../core/TestCoreApp.js'; @@ -90,12 +90,12 @@ describe('TokenDeployer', async () => { ).deriveWarpRouteConfig(address); } it('should derive ERC20RouterConfig from collateral correctly', async () => { - const baseConfig = routerConfigMap[Chains.test1]; + const baseConfig = routerConfigMap[TestChainName.test1]; const mailbox = Mailbox__factory.connect(baseConfig.mailbox, signer); // Create config const config: { [key: string]: any } = { - [Chains.test1]: { + [TestChainName.test1]: { type: TokenType.collateral, token: token.address, hook: await mailbox.defaultHook(), @@ -111,12 +111,12 @@ describe('TokenDeployer', async () => { // Derive config and check if each value matches const derivedConfig: Partial = await deriveWarpConfig( - Chains.test1, - warpRoute[Chains.test1].collateral.address, + TestChainName.test1, + warpRoute[TestChainName.test1].collateral.address, ); for (const [key, value] of Object.entries(derivedConfig)) { - const deployedValue = config[Chains.test1][key]; + const deployedValue = config[TestChainName.test1][key]; if (deployedValue) expect(deployedValue).to.equal(value); } diff --git a/typescript/sdk/src/types.ts b/typescript/sdk/src/types.ts index 8eabb6a74b..ca4d4acdfe 100644 --- a/typescript/sdk/src/types.ts +++ b/typescript/sdk/src/types.ts @@ -6,8 +6,6 @@ import type { ChainId, Domain } from '@hyperlane-xyz/utils'; export type ChainName = string; // A map of chain names to a value type export type ChainMap = Record; -// The names of test chains, should be kept up to date if new are added -export type TestChainNames = 'test1' | 'test2' | 'test3'; export type ChainNameOrId = ChainName | ChainId | Domain; diff --git a/typescript/sdk/src/utils/MultiGeneric.ts b/typescript/sdk/src/utils/MultiGeneric.ts index 35c7e66a85..45ed53e751 100644 --- a/typescript/sdk/src/utils/MultiGeneric.ts +++ b/typescript/sdk/src/utils/MultiGeneric.ts @@ -1,4 +1,3 @@ -import { AllDeprecatedChains } from '../consts/chains.js'; import { ChainMap, ChainName } from '../types.js'; // Generalized map container for chain name to some value @@ -35,9 +34,7 @@ export class MultiGeneric { } chains(): ChainName[] { - return Object.keys(this.chainMap).filter( - (chain) => !AllDeprecatedChains.includes(chain), - ); + return Object.keys(this.chainMap); } forEach(fn: (n: ChainName, dc: Value) => void): void { diff --git a/typescript/sdk/src/utils/ism.ts b/typescript/sdk/src/utils/ism.ts index 7cd35e13f4..ad7c115960 100644 --- a/typescript/sdk/src/utils/ism.ts +++ b/typescript/sdk/src/utils/ism.ts @@ -1,4 +1,4 @@ -import multisigIsmVerifyCosts from '../consts/multisigIsmVerifyCosts.json' assert { type: 'json' }; +import { multisigIsmVerifyCosts } from '../consts/multisigIsmVerifyCosts.js'; export function multisigIsmVerificationCost(m: number, n: number): number { if ( diff --git a/typescript/sdk/src/utils/wagmi.ts b/typescript/sdk/src/utils/wagmi.ts index b393bd3ea0..62c3de3434 100644 --- a/typescript/sdk/src/utils/wagmi.ts +++ b/typescript/sdk/src/utils/wagmi.ts @@ -1,30 +1,17 @@ import type { Chain as WagmiChain } from '@wagmi/chains'; -import { ProtocolType, objFilter, objMap } from '@hyperlane-xyz/utils'; - -import { chainMetadata, etherToken } from '../consts/chainMetadata.js'; +import { test1 } from '../consts/testChains.js'; import { ChainMetadata, getChainIdNumber, } from '../metadata/chainMetadataTypes.js'; -import type { ChainMap } from '../types.js'; - -// For convenient use in wagmi-based apps -export const wagmiChainMetadata: ChainMap = objMap( - objFilter( - chainMetadata, - (_, metadata): metadata is ChainMetadata => - metadata.protocol === ProtocolType.Ethereum, - ), - (_, metadata) => chainMetadataToWagmiChain(metadata), -); export function chainMetadataToWagmiChain(metadata: ChainMetadata): WagmiChain { return { id: getChainIdNumber(metadata), name: metadata.displayName || metadata.name, - network: metadata.name as string, - nativeCurrency: metadata.nativeToken || etherToken, + network: metadata.name, + nativeCurrency: metadata.nativeToken || test1.nativeToken!, rpcUrls: { public: { http: [metadata.rpcUrls[0].http] }, default: { http: [metadata.rpcUrls[0].http] }, diff --git a/typescript/sdk/src/warp/WarpCore.test.ts b/typescript/sdk/src/warp/WarpCore.test.ts index fb81573102..e24ebd5b77 100644 --- a/typescript/sdk/src/warp/WarpCore.test.ts +++ b/typescript/sdk/src/warp/WarpCore.test.ts @@ -3,8 +3,12 @@ import fs from 'fs'; import sinon from 'sinon'; import { parse as yamlParse } from 'yaml'; -import { chainMetadata } from '../consts/chainMetadata.js'; -import { Chains } from '../consts/chains.js'; +import { + test1, + test2, + testCosmosChain, + testSealevelChain, +} from '../consts/testChains.js'; import { MultiProtocolProvider } from '../providers/MultiProtocolProvider.js'; import { ProviderType } from '../providers/ProviderType.js'; import { Token } from '../token/Token.js'; @@ -23,7 +27,7 @@ const MOCK_BALANCE = BigInt('10000000000000000000'); // 10 units @ 18 decimals const MOCK_ADDRESS = '0x0000000000000000000000000000000000000001'; describe('WarpCore', () => { - const multiProvider = new MultiProtocolProvider(); + const multiProvider = MultiProtocolProvider.createTestMultiProtocolProvider(); let warpCore: WarpCore; let evmHypNative: Token; let evmHypSynthetic: Token; @@ -39,10 +43,10 @@ describe('WarpCore', () => { it('Constructs', () => { const fromArgs = new WarpCore(multiProvider, [ - Token.FromChainMetadataNativeToken(chainMetadata[Chains.ethereum]), + Token.FromChainMetadataNativeToken(test1), ]); const exampleConfig = yamlParse( - fs.readFileSync('./src/warp/example-warp-core-config.yaml', 'utf-8'), + fs.readFileSync('./src/warp/test-warp-core-config.yaml', 'utf-8'), ); const fromConfig = WarpCore.FromConfig(multiProvider, exampleConfig); expect(fromArgs).to.be.instanceOf(WarpCore); @@ -62,14 +66,19 @@ describe('WarpCore', () => { it('Finds tokens', () => { expect( - warpCore.findToken(Chains.ethereum, evmHypNative.addressOrDenom), + warpCore.findToken(test1.name, evmHypNative.addressOrDenom), ).to.be.instanceOf(Token); expect( - warpCore.findToken(Chains.ethereum, sealevelHypSynthetic.addressOrDenom), - ).to.be.null; + warpCore.findToken( + testSealevelChain.name, + sealevelHypSynthetic.addressOrDenom, + ), + ).to.be.instanceOf(Token); expect( - warpCore.findToken(Chains.neutron, cw20.addressOrDenom), + warpCore.findToken(testCosmosChain.name, cw20.addressOrDenom), ).to.be.instanceOf(Token); + expect(warpCore.findToken(test1.name, sealevelHypSynthetic.addressOrDenom)) + .to.be.null; }); it('Gets transfer gas quote', async () => { @@ -110,26 +119,29 @@ describe('WarpCore', () => { ).to.equal(interchainQuote.amount); }; - await testQuote(evmHypNative, Chains.arbitrum, TokenStandard.EvmNative); - await testQuote(evmHypNative, Chains.neutron, TokenStandard.EvmNative); - await testQuote(evmHypNative, Chains.solana, TokenStandard.EvmNative); - await testQuote(evmHypSynthetic, Chains.ethereum, TokenStandard.EvmNative); + await testQuote(evmHypNative, test1.name, TokenStandard.EvmNative); + await testQuote( + evmHypNative, + testCosmosChain.name, + TokenStandard.EvmNative, + ); + await testQuote( + evmHypNative, + testSealevelChain.name, + TokenStandard.EvmNative, + ); + await testQuote(evmHypSynthetic, test2.name, TokenStandard.EvmNative); await testQuote( sealevelHypSynthetic, - Chains.ethereum, + test2.name, TokenStandard.SealevelNative, ); - await testQuote(cosmosIbc, Chains.arbitrum, TokenStandard.CosmosNative); + await testQuote(cosmosIbc, test1.name, TokenStandard.CosmosNative); // Note, this route uses an igp quote const config - await testQuote( - cwHypCollateral, - Chains.arbitrum, - TokenStandard.CosmosNative, - { - amount: 1n, - addressOrDenom: 'untrn', - }, - ); + await testQuote(cwHypCollateral, test2.name, TokenStandard.CosmosNative, { + amount: 1n, + addressOrDenom: 'atom', + }); stubs.forEach((s) => s.restore()); }); @@ -144,7 +156,7 @@ describe('WarpCore', () => { const testCollateral = async ( token: Token, destination: ChainName, - expectedBigResult = true, + expectedBigResult: boolean, ) => { const smallResult = await warpCore.isDestinationCollateralSufficient({ originTokenAmount: token.amount(TRANSFER_AMOUNT), @@ -164,19 +176,17 @@ describe('WarpCore', () => { ).to.equal(expectedBigResult); }; - await testCollateral(evmHypNative, Chains.arbitrum); - await testCollateral(evmHypNative, Chains.neutron, false); - await testCollateral(evmHypNative, Chains.solana); - await testCollateral(cwHypCollateral, Chains.arbitrum); + await testCollateral(evmHypNative, test2.name, true); + await testCollateral(evmHypNative, testCosmosChain.name, false); + await testCollateral(evmHypNative, testSealevelChain.name, true); + await testCollateral(cwHypCollateral, test1.name, false); stubs.forEach((s) => s.restore()); }); it('Validates transfers', async () => { const balanceStubs = warpCore.tokens.map((t) => - sinon - .stub(t, 'getBalance') - .returns(Promise.resolve({ amount: MOCK_BALANCE } as any)), + sinon.stub(t, 'getBalance').resolves({ amount: MOCK_BALANCE } as any), ); const quoteStubs = warpCore.tokens.map((t) => sinon.stub(t, 'getHypAdapter').returns({ @@ -188,7 +198,7 @@ describe('WarpCore', () => { const validResult = await warpCore.validateTransfer({ originTokenAmount: evmHypNative.amount(TRANSFER_AMOUNT), - destination: Chains.arbitrum, + destination: test2.name, recipient: MOCK_ADDRESS, sender: MOCK_ADDRESS, }); @@ -204,7 +214,7 @@ describe('WarpCore', () => { const invalidRecipient = await warpCore.validateTransfer({ originTokenAmount: evmHypNative.amount(TRANSFER_AMOUNT), - destination: Chains.neutron, + destination: testCosmosChain.name, recipient: MOCK_ADDRESS, sender: MOCK_ADDRESS, }); @@ -212,7 +222,7 @@ describe('WarpCore', () => { const invalidAmount = await warpCore.validateTransfer({ originTokenAmount: evmHypNative.amount(-10), - destination: Chains.arbitrum, + destination: test1.name, recipient: MOCK_ADDRESS, sender: MOCK_ADDRESS, }); @@ -220,7 +230,7 @@ describe('WarpCore', () => { const insufficientBalance = await warpCore.validateTransfer({ originTokenAmount: evmHypNative.amount(BIG_TRANSFER_AMOUNT), - destination: Chains.arbitrum, + destination: test1.name, recipient: MOCK_ADDRESS, sender: MOCK_ADDRESS, }); @@ -264,17 +274,13 @@ describe('WarpCore', () => { }); }; - await testGetTxs(evmHypNative, Chains.arbitrum); - await testGetTxs(evmHypNative, Chains.neutron); - await testGetTxs(evmHypNative, Chains.solana); - await testGetTxs(evmHypSynthetic, Chains.ethereum); - await testGetTxs( - sealevelHypSynthetic, - Chains.ethereum, - ProviderType.SolanaWeb3, - ); - await testGetTxs(cwHypCollateral, Chains.arbitrum, ProviderType.CosmJsWasm); - await testGetTxs(cosmosIbc, Chains.arbitrum, ProviderType.CosmJs); + await testGetTxs(evmHypNative, test1.name); + await testGetTxs(evmHypNative, testCosmosChain.name); + await testGetTxs(evmHypNative, testSealevelChain.name); + await testGetTxs(evmHypSynthetic, test2.name); + await testGetTxs(sealevelHypSynthetic, test2.name, ProviderType.SolanaWeb3); + await testGetTxs(cwHypCollateral, test1.name, ProviderType.CosmJsWasm); + await testGetTxs(cosmosIbc, test1.name, ProviderType.CosmJs); coreStub.restore(); adapterStubs.forEach((s) => s.restore()); diff --git a/typescript/sdk/src/warp/WarpCore.ts b/typescript/sdk/src/warp/WarpCore.ts index fd205371fe..9c9da1ca60 100644 --- a/typescript/sdk/src/warp/WarpCore.ts +++ b/typescript/sdk/src/warp/WarpCore.ts @@ -602,6 +602,8 @@ export class WarpCore { if (amount > senderBalance) return { amount: 'Insufficient balance' }; // Next, ensure balances can cover the COMBINED amount and fees + // The combined will be more than originTokenAmount if the transfer + // fee token == the either of the fee tokens const feeEstimate = await this.estimateTransferRemoteFees({ originToken: token, destination, @@ -619,15 +621,17 @@ export class WarpCore { return { amount: 'Insufficient balance for gas and transfer' }; } - // Finally, ensure there's sufficient balance for the IGP fee, which may - // be a different token than the transfer token + // Finally, if the IGP fee token differs from the transfer token, + // ensure there's sufficient balance for the IGP fee const igpQuote = feeEstimate.interchainQuote; - const igpTokenBalance = await igpQuote.token.getBalance( - this.multiProvider, - sender, - ); - if (igpTokenBalance.amount < igpQuote.amount) { - return { amount: `Insufficient ${igpQuote.token.symbol} for gas` }; + if (!token.isFungibleWith(igpQuote.token)) { + const igpTokenBalance = await igpQuote.token.getBalance( + this.multiProvider, + sender, + ); + if (igpTokenBalance.amount < igpQuote.amount) { + return { amount: `Insufficient ${igpQuote.token.symbol} for gas` }; + } } return null; diff --git a/typescript/sdk/src/warp/example-warp-core-config.yaml b/typescript/sdk/src/warp/example-warp-core-config.yaml deleted file mode 100644 index c074b4caa6..0000000000 --- a/typescript/sdk/src/warp/example-warp-core-config.yaml +++ /dev/null @@ -1,73 +0,0 @@ -# An example Warp Core config -# Contains the token + route data needed to create a Warp Core ---- -tokens: - # Eth Mainnet HypNative token - - chainName: ethereum - standard: EvmHypNative - decimals: 18 - symbol: ETH - name: Ether - addressOrDenom: '0x1234567890123456789012345678901234567890' - connections: - - { token: ethereum|arbitrum|0x9876543210987654321098765432109876543210 } - - { token: cosmos|neutron|neutron1abcdefghijklmnopqrstuvwxyz1234567890ab } - - { token: sealevel|solana|s0LaBcEeFgHiJkLmNoPqRsTuVwXyZ456789012345678 } - # Arbitrum HypSynthetic token - - chainName: arbitrum - standard: EvmHypSynthetic - decimals: 18 - symbol: ETH - name: Ether - addressOrDenom: '0x9876543210987654321098765432109876543210' - connections: - - { token: ethereum|ethereum|0x1234567890123456789012345678901234567890 } - - { token: cosmos|neutron|neutron1abcdefghijklmnopqrstuvwxyz1234567890ab } - # Solana HypSynthetic - - chainName: solana - standard: SealevelHypSynthetic - decimals: 9 - symbol: ETH.sol - name: Ether on Solana - addressOrDenom: s0LaBcEeFgHiJkLmNoPqRsTuVwXyZ456789012345678 - connections: - - { token: ethereum|ethereum|0x1234567890123456789012345678901234567890 } - # Cosmos Neutron HypCollateral token - - chainName: neutron - standard: CwHypCollateral - decimals: 18 - symbol: ETH.in - name: Ether on Neutron - addressOrDenom: neutron1abcdefghijklmnopqrstuvwxyz1234567890ab - collateralAddressOrDenom: neutron1c0ll4t3r4lc0ll4t3r4lc0ll4t3r4lc0ll4t3r - connections: - - { token: ethereum|ethereum|0x1234567890123456789012345678901234567890 } - - { token: ethereum|arbitrum|0x9876543210987654321098765432109876543210 } - # Cosmos Neutron Collateralized token - - chainName: neutron - standard: CW20 - decimals: 18 - symbol: ETH.in - name: Ether on Neutron - addressOrDenom: neutron1c0ll4t3r4lc0ll4t3r4lc0ll4t3r4lc0ll4t3r - # Cosmos Injective token with IBC two-hop - - chainName: injective - standard: CosmosIbc - decimals: 18 - symbol: INJ - name: Injective - addressOrDenom: inj - connections: - - token: ethereum|arbitrum|0x9876543210987654321098765432109876543210 - type: ibc - sourcePort: transfer - sourceChannel: channel-1 - intermediateChainName: neutron - intermediateIbcDenom: untrn - intermediateRouterAddress: neutron1abcdefghijklmnopqrstuvwxyz1234567890ab -options: - interchainFeeConstants: - - origin: neutron - destination: arbitrum - amount: 1 - addressOrDenom: untrn diff --git a/typescript/sdk/src/warp/test-warp-core-config.yaml b/typescript/sdk/src/warp/test-warp-core-config.yaml new file mode 100644 index 0000000000..1143297268 --- /dev/null +++ b/typescript/sdk/src/warp/test-warp-core-config.yaml @@ -0,0 +1,86 @@ +# An example Warp Core config +# Contains the token + route data needed to create a Warp Core +--- +tokens: + # test1 HypNative token + - chainName: test1 + standard: EvmHypNative + decimals: 18 + symbol: ETH + name: Ether + addressOrDenom: '0x1234567890123456789012345678901234567890' + connections: + - { token: ethereum|test2|0x9876543210987654321098765432109876543210 } + - { + token: cosmos|testcosmos|testcosmos1abcdefghijklmnopqrstuvwxyz1234567890ab, + } + - { + token: sealevel|testsealevel|s0LaBcEeFgHiJkLmNoPqRsTuVwXyZ456789012345678, + } + # test2 HypSynthetic token + - chainName: test2 + standard: EvmHypSynthetic + decimals: 18 + symbol: ETH + name: Ether + addressOrDenom: '0x9876543210987654321098765432109876543210' + connections: + - { token: ethereum|test1|0x1234567890123456789012345678901234567890 } + - { + token: cosmos|testcosmos|testcosmos1abcdefghijklmnopqrstuvwxyz1234567890ab, + } + # testsealevel HypSynthetic + - chainName: testsealevel + standard: SealevelHypSynthetic + decimals: 9 + symbol: ETH.sol + name: Ether on testsealevel + addressOrDenom: s0LaBcEeFgHiJkLmNoPqRsTuVwXyZ456789012345678 + connections: + - { token: ethereum|test1|0x1234567890123456789012345678901234567890 } + # Cosmos testcosmos HypCollateral token + - chainName: testcosmos + standard: CwHypCollateral + decimals: 18 + symbol: ETH.cos + name: Ether on testcosmos + addressOrDenom: testcosmos1abcdefghijklmnopqrstuvwxyz1234567890ab + collateralAddressOrDenom: testcosmos1c0ll4t3r4lc0ll4t3r4lc0ll4t3r4lc0ll4t3r + connections: + - { token: ethereum|test1|0x1234567890123456789012345678901234567890 } + - { token: ethereum|test2|0x9876543210987654321098765432109876543210 } + # Cosmos testcosmos Collateralized token + - chainName: testcosmos + standard: CW20 + decimals: 18 + symbol: ETH.cos + name: Ether on testcosmos + addressOrDenom: testcosmos1c0ll4t3r4lc0ll4t3r4lc0ll4t3r4lc0ll4t3r + # Cosmos testcosmos token with IBC two-hop + - chainName: testcosmos + standard: CosmosIbc + decimals: 18 + symbol: untrn + name: untrn + addressOrDenom: untrn + connections: + - token: ethereum|test2|0x9876543210987654321098765432109876543210 + type: ibc + sourcePort: transfer + sourceChannel: channel-1 + intermediateChainName: testcosmos + intermediateIbcDenom: untrn + intermediateRouterAddress: testcosmos1abcdefghijklmnopqrstuvwxyz1234567890ab + - chainName: testcosmos + standard: CosmosNative + decimals: 18 + symbol: atom + name: atom + addressOrDenom: atom + +options: + interchainFeeConstants: + - origin: testcosmos + destination: test2 + amount: 1 + addressOrDenom: atom diff --git a/typescript/utils/package.json b/typescript/utils/package.json index 726158b7f5..f031084918 100644 --- a/typescript/utils/package.json +++ b/typescript/utils/package.json @@ -8,7 +8,7 @@ "bignumber.js": "^9.1.1", "ethers": "^5.7.2", "pino": "^8.19.0", - "yaml": "^2.3.1" + "yaml": "^2.4.1" }, "devDependencies": { "@types/mocha": "^10.0.1", diff --git a/typescript/utils/src/index.ts b/typescript/utils/src/index.ts index d7f0e52979..6a1752a632 100644 --- a/typescript/utils/src/index.ts +++ b/typescript/utils/src/index.ts @@ -146,5 +146,6 @@ export { TokenCaip19Id, WithAddress, } from './types.js'; +export { isHttpsUrl } from './url.js'; export { assert } from './validation.js'; export { BaseValidator } from './validator.js'; diff --git a/typescript/utils/src/url.ts b/typescript/utils/src/url.ts new file mode 100644 index 0000000000..c93e96cf71 --- /dev/null +++ b/typescript/utils/src/url.ts @@ -0,0 +1,8 @@ +export function isHttpsUrl(value: string) { + try { + const url = new URL(value); + return url.protocol === 'https:'; + } catch (error) { + return false; + } +} diff --git a/yarn.lock b/yarn.lock index 64a0cf4153..45eebca5ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4954,6 +4954,7 @@ __metadata: version: 0.0.0-use.local resolution: "@hyperlane-xyz/cli@workspace:typescript/cli" dependencies: + "@hyperlane-xyz/registry": "npm:^1.0.7" "@hyperlane-xyz/sdk": "npm:3.10.0" "@hyperlane-xyz/utils": "npm:3.10.0" "@inquirer/prompts": "npm:^3.0.0" @@ -4974,7 +4975,7 @@ __metadata: terminal-link: "npm:^3.0.0" tsx: "npm:^4.7.1" typescript: "npm:^5.1.6" - yaml: "npm:^2.3.1" + yaml: "npm:^2.4.1" yargs: "npm:^17.7.2" zod: "npm:^3.21.2" bin: @@ -5038,6 +5039,7 @@ __metadata: resolution: "@hyperlane-xyz/helloworld@workspace:typescript/helloworld" dependencies: "@hyperlane-xyz/core": "npm:3.10.0" + "@hyperlane-xyz/registry": "npm:^1.0.7" "@hyperlane-xyz/sdk": "npm:3.10.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" "@nomiclabs/hardhat-waffle": "npm:^2.0.6" @@ -5084,6 +5086,7 @@ __metadata: "@ethersproject/hardware-wallets": "npm:^5.7.0" "@ethersproject/providers": "npm:^5.7.2" "@hyperlane-xyz/helloworld": "npm:3.10.0" + "@hyperlane-xyz/registry": "npm:^1.0.7" "@hyperlane-xyz/sdk": "npm:3.10.0" "@hyperlane-xyz/utils": "npm:3.10.0" "@nomiclabs/hardhat-ethers": "npm:^2.2.3" @@ -5137,6 +5140,16 @@ __metadata: languageName: unknown linkType: soft +"@hyperlane-xyz/registry@npm:^1.0.7": + version: 1.0.7 + resolution: "@hyperlane-xyz/registry@npm:1.0.7" + dependencies: + yaml: "npm:^2" + zod: "npm:^3.21.2" + checksum: fb112e2a1fdec539c6ef457ab44b9b5a835b719f2d4cc5cb0efb4413e2647402ed826d597f4c63b552f86fcf91bf69d4d1e5934aee8498f83dac61007a9d5650 + languageName: node + linkType: hard + "@hyperlane-xyz/sdk@npm:3.10.0, @hyperlane-xyz/sdk@workspace:typescript/sdk": version: 0.0.0-use.local resolution: "@hyperlane-xyz/sdk@workspace:typescript/sdk" @@ -5174,7 +5187,7 @@ __metadata: tsx: "npm:^4.7.1" typescript: "npm:5.3.3" viem: "npm:^1.20.0" - yaml: "npm:^2.3.1" + yaml: "npm:^2.4.1" zod: "npm:^3.21.2" peerDependencies: "@ethersproject/abi": "*" @@ -5224,7 +5237,7 @@ __metadata: pino: "npm:^8.19.0" prettier: "npm:^2.8.8" typescript: "npm:5.3.3" - yaml: "npm:^2.3.1" + yaml: "npm:^2.4.1" languageName: unknown linkType: soft @@ -24504,10 +24517,12 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.3.1": - version: 2.3.1 - resolution: "yaml@npm:2.3.1" - checksum: 66501d597e43766eb94dc175d28ec8b2c63087d6a78783e59b4218eee32b9172740f9f27d54b7bc0ca8af61422f7134929f9974faeaac99d583787e793852fd2 +"yaml@npm:^2, yaml@npm:^2.4.1": + version: 2.4.1 + resolution: "yaml@npm:2.4.1" + bin: + yaml: bin.mjs + checksum: 2c54fd69ef59126758ae710f9756405a7d41abcbb61aca894250d0e81e76057c14dc9bb00a9528f72f99b8f24077f694a6f7fd09cdd6711fcec2eebfbb5df409 languageName: node linkType: hard